STM32 Uart DMA方式接收数据

      CPU开了一家公司,叫“搬砖有限公司”,刚开始,只有CPU老板会搬砖,每次需要搬砖头的时候,他都要自己把砖头从一个房间搬到另一个房间,影响他做其他事的效率。后面,他招了一个搬砖小能手,名字叫DMA,现在,每次需要搬砖头的时候,他只需要告诉DMA:“一次搬2块,搬1024块砖,从研发部,搬到生产部”就行了,他可以解放双手,做更重要的事。DMA搬完砖头后,会跟老板汇报:“老板,搬完了”。于是,公司效率提高了。

      砖头,是数据。房间,是存储器,或者外设(如串口等)。搬砖,就是把数据从存储器传输到存储器,或者从存储器传输到外设,或者从外设传输到存储器。

      由此可见,DMA(Direct Memory Access)的用途主要是数据传输。

      在《STM32 Uart及其配置》我们讲了 Stm32 Uart 轮询接收数据。

      在《STM32 Uart中断接收》我们讲了 Stm32 Uart 中断接收数据。

      在这篇,我们来讲 Stm32 Uart DMA方式接收数据。

先配置下串口,参照《STM32 Uart及其配置》,接下来设置DMA,在Configuration页,点DMA,进入DMA配置页面:

在DMA配置页面,点Add,在DMA Request那一栏,会出现Select,点它,既然是接收,当然选择 UART4_RX了。

接下来还有几个参数需要设置:Mode、Increment Address、Use Fifo、Data Width、Burst Size,就先说一下这几个参数的含义吧。

Mode:有两个选项,Normal,Circular,简单地说,Normal模式下,Buffer满了,就算了,不再接收,下次要接收,还得重新设置;而Circular模式下,Buffer满了,就从Buffer头开始继续接收,覆盖上一次的数据。文中结尾做了个测试。

别问我怎么知道的,请参考《RM0033 Reference manual》:

ncrement Address,也就是地址自加,外设也就是个单一的寄存器,地址不自加,而存储器,来一个数据,存储,然后指向下一地址,选择自加。

Data Width,数据宽度,有三个选项,Byte、Half Word、Word。

普及一个 Word、Half Word、Byte吧:

在32位机中,Word = 32bit,Half word = 16bit,Byte = 8bit。

在16位机中,Word = 16bit,Half word = 8bit,Byte = 8bit。

在 8 位机中,Word = 16bit,Half word = 8bit,Byte = 8bit。

这里的外设每次接收一个Byte,当然,就选Byte了。

FIFO:First In First Out,可以把它理解为一个缓冲区,用来缓存数据,它最大有4个WORD,可以设置它的门限(threshold)1个(1/4),2个(1/2),3个(3/4),全用(full)。

FIFO 传输,可以简单理解为 外设->FIFO->存储器,如图:

Burst Size:有Single、4、8、16这四个选项,Burst transfer,就是来一个dma请求,传输1(Single)、4、8、16个beats,而这个1(Single)、4、8、16,就是Burst Size。

注意,beats不是bytes,这个beats,取决于上面设置的数据宽度,如果设置的是word,那1beats = 1word = 32bits,如果设置的是half-word,那1beats = 1half-word = 16bit,如果设置的是Byte,那1beats = 1byte = 8bit。

讲了那么多,然而,我们并不需要用到FIFO,也不需要用到burst,我们只需要用sigle便可,当然,知道得多一点,总归会更好一点,也许下次遇到ADC->Memory,或者Memory->Memory,就用到了呢。

最终,我们的配置是这样的:

生成代码,打开工程,打开代码:

DMA初始化代码就是这一段:

    /* UART4 DMA Init */
    /* UART4_RX Init */
    hdma_uart4_rx.Instance = DMA1_Stream2;
    hdma_uart4_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_uart4_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;           // 外设->存储器
    hdma_uart4_rx.Init.PeriphInc = DMA_PINC_DISABLE;                                    // 外设 Increment Address
    hdma_uart4_rx.Init.MemInc = DMA_MINC_ENABLE;            // 存储器 Increment Address
    hdma_uart4_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;   // 外设 Data Width
    hdma_uart4_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     // 存储器 Data Width
    hdma_uart4_rx.Init.Mode = DMA_NORMAL;                                                  // Mode = Normal
    hdma_uart4_rx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_uart4_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;  // 不使用fifo,下面三个参数,都没作用;
    hdma_uart4_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
    hdma_uart4_rx.Init.MemBurst = DMA_MBURST_SINGLE;
    hdma_uart4_rx.Init.PeriphBurst = DMA_PBURST_SINGLE;
    if (HAL_DMA_Init(&hdma_uart4_rx) != HAL_OK)
    {
      _Error_Handler(__FILE__, __LINE__);
    }

DMA的传输过程:来一个Uart数据,来一个DMA请求,DMA获得总线,把数据从外设传输至存储器,待数据传输完成(DMA_SxNDTR = 0),触发DMA完成中断;

有兴趣的同学可以仔细看一下《RM0033 Reference manual》,第9章节,DMA controller。

触发中断后干什么呢?跟下代码,可以看到,在经过一系列的条件判断后,调用了下面这个回调函数:

hdma->XferCpltCallback(hdma);

那这个回调函数在哪设置呢?看一下 这个函数:

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;

  /* Check that a Rx process is not already ongoing */
  if(huart->RxState == HAL_UART_STATE_READY)
  {
    if((pData == NULL ) || (Size == 0))
    {
      return HAL_ERROR;
    }
    
    /* Process Locked */
    __HAL_LOCK(huart);
    
    huart->pRxBuffPtr = pData;
    huart->RxXferSize = Size;
    
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;

    /* Set the UART DMA transfer complete callback */
    huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;     // 传输完成回调函数
    
    /* Set the UART DMA Half transfer complete callback */
    huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;
    
    /* Set the DMA error callback */
    huart->hdmarx->XferErrorCallback = UART_DMAError;
    
    /* Set the DMA abort callback */
    huart->hdmarx->XferAbortCallback = NULL;

    /* Enable the DMA Stream */
    tmp = (uint32_t*)&pData;
    HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t*)tmp, Size);

    /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
    __HAL_UART_CLEAR_OREFLAG(huart);

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Parity Error Interrupt */
    SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);

    /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Enable the DMA transfer for the receiver request by setting the DMAR bit
    in the UART CR3 register */
    SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

跟一下传输完成回调函数 UART_DMAReceiveCplt

static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef* huart = ( UART_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent;
  /* DMA Normal mode*/
  if((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
  {
    huart->RxXferCount = 0;

    /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
    CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
  
    /* Disable the DMA transfer for the receiver request by setting the DMAR bit
       in the UART CR3 register */
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;
  }
  HAL_UART_RxCpltCallback(huart);                     // 重写这个函数
}

历经千山万水,最终还是回到重写这个函数上了,参考《STM32 Uart中断接收》。

好了,开始增加我们自己的代码了。

第一步,先声明一个Buffer,用于接收数据:

uint8_t rcvBuffer[1024] = {0xAA};

第二步,确定要接收的字节数:

#define SIZE_RCV            8

第三步,在初始化之后,打开DMA,若有数据,系统会自动把数据写入Buffer里面:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_UART4_Init();
  /* USER CODE BEGIN 2 */
    HAL_UART_Receive_DMA(&huart4, rcvBuffer, SIZE_RCV);    // 这里加这个函数,接收数据
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

第四步,重写回调函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    HAL_UART_Transmit(&huart4, rcvBuffer,SIZE_RCV, 1000);                        // 把数据原封不动还给你
    HAL_UART_Receive_DMA(&huart4, rcvBuffer, SIZE_RCV);                        // 重新打开DMA接收数据
}

好了,编译,烧录,运行,看结果。

看,发8个数据,它就收8个数据。

那么,如果发9个数据呢?它也是收8个数据。如果发7个数据呢?不足8个,不会触发DMA中断。

大家可以自行试一下。

最后,大家可以尝试一下,Mode设置为Circular,结果会是怎么样的?

发送8个字节数据试一下?再发送9个字节试一下?发送第一次?第二次,有什么区别?

只需要把这个配置

 hdma_uart4_rx.Init.Mode = DMA_NORMAL;                                                  // Mode = Normal

更改为

 hdma_uart4_rx.Init.Mode = DMA_CIRCULAR;                                                // Mode = Circular

重新编译,烧录,运行便可;

整个工程及代码呢,请上百度网盘上下载:

    链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg

    密码:07on

    文件夹:\Stm32CubeMx\Code\UartRx_DMA.rar

 

上一篇:《STM32 Uart中断接收

下一篇:《STM32 Uart 接收变长数据

回目录:《前言

  • 21
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是基于STM32标准库的UART DMA收发FIFO例程: ```c #include "stm32f10x.h" #include <stdio.h> #define BUFFER_SIZE 64 uint8_t Rx_Buffer[BUFFER_SIZE]; uint8_t Tx_Buffer[BUFFER_SIZE]; uint8_t Rx_Idx = 0; uint8_t Tx_Idx = 0; void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Rx_Buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel5, &DMA_InitStructure); DMA_DeInit(DMA1_Channel4); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Tx_Buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel4, &DMA_InitStructure); USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); DMA_Cmd(DMA1_Channel5, ENABLE); DMA_Cmd(DMA1_Channel4, DISABLE); USART_Cmd(USART1, ENABLE); } uint8_t USART_GetChar(void) { uint8_t ch = Rx_Buffer[Rx_Idx++]; Rx_Idx %= BUFFER_SIZE; return ch; } void USART_PutChar(uint8_t ch) { Tx_Buffer[Tx_Idx++] = ch; Tx_Idx %= BUFFER_SIZE; DMA_Cmd(DMA1_Channel4, ENABLE); } void DMA1_Channel4_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC4)) { DMA_Cmd(DMA1_Channel4, DISABLE); DMA_ClearITPendingBit(DMA1_IT_TC4); } } int main(void) { USART_Config(); while (1) { if (Rx_Idx != 0) { uint8_t ch = USART_GetChar(); USART_PutChar(ch + 1); } } } ``` 这个例程使用了STM32UART1作为串口通信的接口,并且使用了DMA来进行数据的传输。在这个例程中,定义了一个接收缓冲区`Rx_Buffer`和一个发送缓冲区`Tx_Buffer`,分别用于存储接收到的数据和要发送的数据。收到的数据会存储到接收缓冲区中,并且会在下一次轮询时被处理,处理完后会将发送数据存储到发送缓冲区中,并启动DMA进行发送。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值