打印
[N32G45x]

DMA外设到外设传输

[复制链接]
706|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Afanx|  楼主 | 2023-1-7 14:52 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 Afanx 于 2023-1-7 14:52 编辑

N32G45x手册虽然说DMA支持三种模式:支持内存到内存、内存到外设和外设到内存三种传输类型。

但也可以实现外设到外设的传输效果。
比如使用SPI1读取Flash数据,再通过SPI2发送给SPI屏幕。中间不需要SRAM缓存,读取后直接发送。

DMA只需要将MemAddr配置为SPI2->DAT地址就行:
DMA_InitStruct.PeriphAddr   = (uint32_t)(&SPI1->DAT);
DMA_InitStruct.MemAddr      = (uint32_t)(&SPI2->DAT);

SPI1与SPI2都是主机,SPI1使用2个DMA通道分别是Tx和Rx,Tx用于产生时序。
SPI2作为主机,但是“被动”发送,由SPI1的Rx DMA填入数据,自动发送出去。

以下是DMA配置部分代码:

void Test(void)
{
    uint32_t nullDat = 0xFFFFFFFF;
    DMA_InitType DMA_InitStruct;

    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_DMA1, ENABLE);

    DMA_DeInit(DMA1_CH2);
    DMA_DeInit(DMA1_CH3);

    /* DMA1_CH3,SPI1_Tx,用来产生时序,读取数据 */
    /* 这里读取100个数据停止,配置Normal模式 */
    DMA_InitStruct.PeriphAddr     = (uint32_t)(&SPI1->DAT);
    DMA_InitStruct.MemAddr        = (uint32_t)(&nullDat);
    DMA_InitStruct.Direction      = DMA_DIR_PERIPH_DST;
    DMA_InitStruct.BufSize        = 100;
    DMA_InitStruct.PeriphInc      = DMA_PERIPH_INC_DISABLE;
    DMA_InitStruct.DMA_MemoryInc  = DMA_MEM_INC_DISABLE;
    DMA_InitStruct.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
    DMA_InitStruct.MemDataSize    = DMA_MemoryDataSize_Byte;
    DMA_InitStruct.CircularMode   = DMA_MODE_NORMAL;
    DMA_InitStruct.Mem2Mem        = DMA_M2M_DISABLE;
    DMA_InitStruct.Priority       = DMA_PRIORITY_HIGH;
    DMA_Init(DMA1_CH3, &DMA_InitStruct);
    DMA_RequestRemap(DMA1_REMAP_SPI1_TX, DMA1, DMA1_CH3, ENABLE);  // Config DMA Request

    /* DMA1_CH2,SPI1_Rx,用于接收数据,存放到SPI2数据寄存器中,自动发送出去 */
    /* 只要接收到数据就存入SPI2->DAT,配置circural模式 */
    DMA_InitStruct.PeriphAddr   = (uint32_t)(&SPI1->DAT);
    DMA_InitStruct.MemAddr      = (uint32_t)(&SPI2->DAT);
    DMA_InitStruct.Direction    = DMA_DIR_PERIPH_SRC;
    DMA_InitStruct.BufSize      = 1;
    DMA_InitStruct.CircularMode = DMA_MODE_CIRCULAR;
    DMA_Init(DMA1_CH2, &DMA_InitStruct);
    DMA_RequestRemap(DMA1_REMAP_SPI1_RX, DMA1, DMA1_CH2, ENABLE);  // Config DMA Request

    /* 开启片选 */
    GPIO_WriteBit(GPIOA, GPIO_PIN_4, Bit_RESET);   // Enable SPI1 CS
    GPIO_WriteBit(GPIOB, GPIO_PIN_12, Bit_RESET);  // Enable SPI2 CS

    /* 使能外设请求 */
    SPI_I2S_EnableDma(SPI1, SPI_I2S_DMA_RX, ENABLE);  // Enable Request
    SPI_I2S_EnableDma(SPI1, SPI_I2S_DMA_TX, ENABLE);  // Enable Request

    /* 打开DMA(先打开接收,后打开发送) */
    DMA_EnableChannel(DMA1_CH2, ENABLE);  // Enable DMA , SPI1 Rx
    DMA_EnableChannel(DMA1_CH3, ENABLE);  // Enable DMA , SPI1 Tx

    /* 等待传输完成 */
    DelayMs(10);

    /* 关闭片选 */
    GPIO_WriteBit(GPIOA, GPIO_PIN_4, Bit_SET);   // Disable SPI1 CS
    GPIO_WriteBit(GPIOB, GPIO_PIN_12, Bit_SET);  // Disable SPI2 CS
}
传输效果:

注意,发送速度>=接收速度,因此使用不同外设需要调整好波特率。

从上面看来,其实对于DMA来说,不区分外设和内存,都是“地址”,只区分“源地址”和“目标地址”。
只要地址合法,可以实现任意地址到任意地址的传输,甚至包括外挂存储器件的内存映射地址。

按照这种思路,DMA的工作原理也很简单,可以看成一个简单的数据泵:给一个“请求信号”搬一个数据。

这个请求信号也不局限于当前操作的外设,也可以是其他外设产生的信号。比如使用DMA搬运SPI数据,但请求可以来自定时器。
如果将原来的请求更改为下面一条代码:
// DMA_RequestRemap(DMA1_REMAP_SPI1_TX, DMA1, DMA1_CH3, ENABLE);  // 请求来自SPI DAT 发送缓存为空
DMA_RequestRemap(DMA1_REMAP_TIM1_UP, DMA1, DMA1_CH3, ENABLE);  // 请求来自TIM Update事件
同时开启对应外设的DMA请求:
 // SPI_I2S_EnableDma(SPI1, SPI_I2S_DMA_TX, ENABLE);  // 打开 SPI 的DMA请求
TIM_EnableDma(TIM1, TIM_DMA_UPDATE, ENABLE); //打开 TIM1 的DMA请求
此时,会根据TIM Update事件(10us),产生一次DMA操作。达到下面的效果:



使用特权

评论回复
沙发
lspace| | 2023-1-9 17:11 | 只看该作者
这种用涂好像不是很多,但是也是有用的。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

13

主题

51

帖子

2

粉丝