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,就先说一下这几个参数的含义吧。

扫描二维码关注公众号,回复: 2817268 查看本文章

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中断接收

下一篇:

猜你喜欢

转载自blog.csdn.net/ForeverIT/article/details/81750696
今日推荐