STM32
直播中

fansz

8年用户 1223经验值
擅长:制造/封装
私信 关注
[问答]

怎么实现STM32串口DMA收发?

STM32 DMA具有哪些功能?
怎么实现STM32串口DMA收发?

回帖(1)

王茜

2021-12-6 09:55:07
一、STM32 DMA简介与功能说明

1、STM32F4 DMA简介

DMA(Direct memory access),即直接存储器访问。用于在外设与存储器之间以及存储器与存储器之间提供一种高速数据传输的方式。它无需CPU参与,通过硬件方式为RAM与I/O设备提供一条直接传送数据的通道。 STM32F4有2个DMA控制器,每个DMA控制器有8个数据流,每个数据流有8个通道(或请求)。其中2个DMA的请求映射如下:










2、DMA主要特性



  • 每个DMA控制器有8个数据流(Stream),每个数据流有8个通道
  • 每个数据有单独的四级32bit先进先出存储器缓冲区(FIFO),可用FIFO模式或直接模式:
    FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2或3/4
    直接模式:每个DMA请求会立即启动对存储器的传输。当在直接模式下降DMA请求配置为以存储器到外设模式传输数据时,DMA仅会将一个数据从存储器预加载到内部FIFO,从而保证一旦外设触发DMA请求时则立即传输数据
  • DMA数据流请求之间的优先级可通过软件编程的方式决定(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如:请求0的优先级高于请求1)
  • 要传输的数据的数目可以由DMA控制器或外设管理:
    DMA流控制器:要传输的数据数量范围1~65535,可用软件编程
    外设流控制器:要传输的数据数量未知,由源或目标外设控制
  • 每个数据流都支持循环缓冲区管理
  • 有5个事件标志(DMA半传输、DMA传输完成、DMA传输错误、DMA FIFO错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求。

3、DMA传输方向



  • 外设到存储器的传输
  • 存储器到外设的传输
  • 存储器到存储器的传输(仅DMA2可用)

4、DMA事务

DMA事务有给定数目的数据传输序列组成。其数据传输宽度(8bit、16bit、32bit)可用软件编程。
每个DMA传输包含三项操作:



  • 通过DMA_SxPAR(外设)或DMA_SxM0AR(存储器)寄存器寻址,从外设数据寄存器或存储器单元中加载数据。
  • 通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元中。
  • DMA_SxNDTR计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。

5、DMA数据流

8个数据流都可以提供源和目标之间的单向传输链路。
每个数据流配置后都可以执行:



  • 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。
  • 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当DMA正在进行自/至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)

6、源、目标和传输模式

传输方向使用DAM_SxCR寄存器中的DIR[1:0]bit进行配置,有三种传输方向:存储器到外设、外设到存储器或存储器到存储器。相应的源和目标地址如下图:





当传输数据宽度(在DMA_SxCR寄存器的PSIZE或MSIZE位中编程)分别是半字或字时,写入DMA_SxPAR或DMA_SxM0AR/M1AR寄存器的外设或存储器地址必须分别在字或半字地址的边界对齐。
7、指针递增与循环模式

根据DMA_SxCR寄存器中PINC和MINC位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用,一般使用的是禁止模式。
如果使能了递增模式,则根据在DMA_SxCR寄存器PSIZE或MSIZE位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增1(对应一个字节)、2(对应半字)或 4(对应字)。
如果将PINCOS位置 1,则不论PSIZE值是多少,下一次传输的地址总是前一次传输的地址递增 4(自动与 32 位地址对齐)。
循环模式可用于处理循环缓冲区和连续数据流(例如ADC扫描模式)。可以使用DMA_SxCR寄存器中的CIRC位使能此特性。当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。

8、双缓冲模式
通过将DMA_SxCR寄存器中的DBM位置1,即可使能双缓冲区模式。此模式在处理数据量较大的情况下,十分有用,比如开启双缓冲采集摄像头数据。双缓冲模式除了有两个存储器指针之外,数据流的工作方式与常规(单缓冲区)数据流的一样。使能双缓冲区模式时,将自动使能循环模式(DMA_SxCR中的CIRC位的状态是“无关”),并在每次事务结束时交换存储器指针。在此模式下,每次事务结束时,DMA控制器都从一个存储器目标交换为另一个存储器目标。 这样,软件在处理一个存储器区域的同时,DMA传输还可以填充/使用第二个存储器区域。其库函数代码如下:


  /*
  * @param  Memory1BaseAddr: the base address of the second buffer (Memory 1)  
  * @param  DMA_CurrentMemory: specifies which memory will be first buffer for
  *         the transactions when the Stream will be enabled.
  *          This parameter can be one of the following values:
  *            @arg DMA_Memory_0: Memory 0 is the current buffer.
  *            @arg DMA_Memory_1: Memory 1 is the current buffer.
  */
void DMA_DoubleBufferModeConfig(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t Memory1BaseAddr,
                                uint32_t DMA_CurrentMemory)
{  
  /* Check the parameters */
  assert_param(IS_DMA_ALL_PERIPH(DMAy_Streamx));
  assert_param(IS_DMA_CURRENT_MEM(DMA_CurrentMemory));
  if (DMA_CurrentMemory != DMA_Memory_0)
  {
    /* Set Memory 1 as current memory address */
    DMAy_Streamx->CR |= (uint32_t)(DMA_SxCR_CT);   
  }
  else
  {
    /* Set Memory 0 as current memory address */
    DMAy_Streamx->CR &= ~(uint32_t)(DMA_SxCR_CT);   
  }
  /* Write to DMAy Streamx M1AR */
  DMAy_Streamx->M1AR = Memory1BaseAddr;
}


注意:在双缓冲区模式下使能数据流时,可遵循下列条件,实时更新 AHB 存储器的基址(DMA_SxM0AR或 DMA_SxM1AR):
● 当DMA_SxCR 寄存器中的CT位为“0”时,可以写入DMA_SxM1AR寄存器。当CT=“1”时,试图写入此寄存器会将错误标志位 (TEIF) 置 1,并自动禁止数据流。
● 当DMA_SxCR 寄存器中的CT位为“1”时,可以写入DMA_SxM0AR寄存器。当CT=“0”时,试图写入此寄存器会将错误标志位 (TEIF) 置 1,并自动禁止数据流。
因此目标存储器依据DMA_SxCR 寄存器中CT值的情况,决定是从存储器 0 更改为 存储器 1(或从存储器 1 更改为存储器 0)。对于所有其它模式(双缓冲区模式除外),一旦使能数据流,存储器地址寄存器即被写保护。


二、代码分析
void DMA_UART_TX(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);    //开启DMA时钟
    DMA_DeInit(DMA2_Stream7);
    while(DMA_GetCmdStatus(DMA2_Stream7) != DISABLE){}   //等待stream可配置,即DMAy_SxCR.EN变为0
   //配置Stream
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;          //从8个channel中选择一个
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;            //外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;      //存储器0地址,双缓存模式还要使用M1AR
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;            //存储器到外设模式
    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;                //数据传输量,以外设数据项为单位
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;        //外设地址保持不变
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                 //存储器地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据位宽:8位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;         //存储器数据位宽:8位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                         //普通模式(与循环模式对应)
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                   //中等优先级
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                  //禁止FIFO模式         
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;             //单次传输
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;     //单次传输
    DMA_Init(DMA2_Stream7, &DMA_InitStructure);


}


由最前面的DMA请求映射图可知,串口1对应DMA2数据流7通道4,即相应的配置DMA2_Stream7,DMA_Channel_4。因为是配置的DMA串口发送,所以外设地址对应(u32)&USART1->DR或(0x40011004,查存储器映射表即可);串口发送模式对应的是存储器到外设,故设置为DMA_DIR_MemoryToPeripheral。在配置其他外设(如SPI,ADC,DAC等)的DMA模式,改变上述地方即可,若为接收端,则需改为DMA_DIR_PeripheralToMemory,即外设到存储器,其他基本不变。


void DMA_TX(uint8_t *tx_buffer,uint16_t length)
{


    DMA_InitTypeDef DMA_InitStructure;
    DMA_Cmd(DMA2_Stream7, DISABLE);                                      //关闭DMA通道
    DMA_SetCurrDataCounter(DMA2_Stream7, (uint16_t)length);              //设置传输字节数
    DMA2_Stream7->CR |= (1 << 10);                                       //发送DMA流的地址不自增
    DMA2_Stream7->M0AR = (uint32_t)tx_buffer;                            //设置接收和发送的内存地址
    DMA_Cmd(DMA2_Stream7, ENABLE);                                       //打开DMA通道
    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);                         //使能串口1的DMA发送
    while( DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) == RESET);    //等待传输完成
    DMA_Cmd(DMA2_Stream7, DISABLE);                                      //关闭DMA通道  
    DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7);                         //清除DMA传输完成标志


}


此处配置的是可变长变地址发送,即每次可发送不同的变量。当然也可以用初始化当中的配置buffer缓冲区和大小,此时若开启了DMA_LISR中TCIF7中断(数据流7传输完成中断标志),达到条件后就会跳到DMA2_Stream7中断服务函数里面,此时查询TCIF7的状态来获得当前DMA的传输状态,传输完成后清除DMA传输完成标志DMA_FLAG_TCIF7即可,代码如下:


void DMA2_Channel7_IRQHandler(void)  
{   
   //判断是否为DMA发送完成中断
    if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)==SET)
    {
        DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);
    }               


}  


至此串口DMA发送数据介绍完毕,下一篇将介绍串口DMA接收数据。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分