[STM32F1] STM32CubeMx配置DMA+串口空闲中断+FreeRTOS,完美处理串口数据。

[复制链接]
3815|5
 楼主| cry1109 发表于 2020-10-21 11:06 | 显示全部楼层 |阅读模式
本帖最后由 cry1109 于 2020-10-21 11:06 编辑



      作为STM32最常用的外设之一,串口广泛应用于单片机与外部设备之间的通信。常见的串口轮训发送+接收中断虽然能够应付一般的项目,但在处理串口的数据时单片机的运行压力较大。使用DMA辅助处理串口的数据,能够大幅缓解单片机的运行压力,提高串口数据的处理能力。
使用CubeMx配置串口+DMA+FreeRTOS,以下内容仅展示了主要涉及到的串口和DMA的配置,操作系统的配置网上教程很多不多说(FreeRTOS的相关接口使用的是CMSIS_V1版版本),以串口3为例:
      USART3基础配置:
35915f8f9de887af3.png
      打开USART3中断:
688925f8f9e1e38efb.png
      配置串口的DMA相关通道:
52905f8f9e5656d65.png
      配置FreeRTOS时创建一个串口数据处理任务USART3_DataProcessTask,用于串口数据的处理,一般这个任务就是用来跑协议的。串口数据的处理过程类似于前后台的关系,空闲中断作为后台负责接收数据,一旦接收到数据后通过消息队列或邮箱将数据发送至前台USART3_DataProcessTask中处理。
      FreeRTOS本来不支持邮箱的,但是在cmsis_os.c中可以找到邮箱(osMail)的相关接口,这个应该是arm或者st自己封装的,实际上还是通过消息队列来实现的。邮箱的使用参照了官方BSP里的FreeRTOS下的osMail相关例程。
      打开freertos.c文件,首先定义邮箱的大小、数据结构、邮箱ID,如下:
  1. /* USER CODE BEGIN Variables */

  2. #define MAIL_SIZE        (uint32_t) 4

  3. typedef struct {
  4.     uint16_t DataLength;
  5.     uint8_t *DataReceive;
  6. } Amail_TypeDef;

  7. osMailQId USART3_MailId;

  8. /* USER CODE END Variables */
      在MX_FREERTOS_Init()中创建刚才定义的邮箱:
  1. osMailQDef(Usart3MailId, MAIL_SIZE, Amail_TypeDef); /* Define mail queue */
  2. USART3_MailId = osMailCreate(osMailQ(Usart3MailId), NULL); /* create mail queue */
      在usart.c中定义定义一个相同的邮箱数据结构,以及一个USART3的数据结构,如下:
  1. /* USER CODE BEGIN 0 */

  2. #define USART3_DATA_SIZE  256

  3. typedef struct {
  4.     uint16_t DataLength;
  5.     uint8_t *DataReceive;
  6. } Amail_TypeDef;

  7. typedef struct {
  8.     uint16_t DataLength;
  9.     uint8_t RxBuffer[USART3_DATA_SIZE];
  10. }USART_TYPE;

  11. static USART_TYPE Usart3;
  12. extern osMailQId USART3_MailId;

  13. /* USER CODE END 0 */
      可以看到,串口的数据结构在定义时直接定义了一个数组,已经为串口接收的数据分配了缓存;而邮箱中定义的是一个指针,所以邮箱在实际发送数据时应该发送的是指针,通过指针访问串口接收的数据。
      CubeMx生成的代码没有发现有空闲中断的回调函数,所以就自己定义一个:
  1. /* USER CODE BEGIN 1 */

  2. void USER_USART3_IdleCallback(void)
  3. {
  4.     Amail_TypeDef *USART3_TxMail;
  5.     BaseType_t xHigherPriorityTaskWoken;

  6.     if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE) != RESET)
  7.     {
  8.         __HAL_UART_CLEAR_IDLEFLAG(&huart3);

  9.         HAL_UART_DMAStop(&huart3);
  10.         USART3_TxMail = osMailAlloc(USART3_MailId, 0); //为邮箱申请内存
  11.         USART3_TxMail->DataLength = USART3_DATA_SIZE-
  12.         __HAL_DMA_GET_COUNTER(&hdma_usart3_rx);        //获取接收数据长度
  13.         HAL_UART_Receive_DMA(&huart3,Usart3.RxBuffer,USART3_DATA_SIZE);        //获取DMA中缓存的串口数据
  14.         USART3_TxMail->DataReceive = Usart3.RxBuffer; //传递缓存串口数据的指针
  15.         osMailPut(USART3_MailId, USART3_TxMail);    //通过邮箱发送串口数据信息
  16.     }
  17.                
  18.     portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  19. }

  20. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart )
  21. {
  22.     if(huart == &huart3)
  23.     {
  24.         ;        //发送完成中断
  25.     }
  26. }

  27. void BSP_USART_Init(void)
  28. {
  29.     __HAL_UART_ENABLE_IT(&huart3,UART_IT_TC);
  30.         
  31.     __HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);
  32.     HAL_UART_Receive_DMA(&huart3,Usart3.RxBuffer,USART3_DATA_SIZE);
  33. };

  34. /* USER CODE END 1 */
      触发空闲中断后邮箱发送数据的流程为:先关闭串口的DMA通道->为邮箱申请内存(因为从刚才定义邮箱到创建邮箱均未为邮箱申请内存)->获取串口接收的数据长度->将长度信息写入邮箱->获取DMA中缓存的串口数据->将缓存串口数据的地址(指针)写入邮箱->通过邮箱发送串口相关的数据信息,最后将USER_USART3_IdleCallback()放到串口3的USART3_IRQHandler中就可以了。别忘了串口初始化:开启发送完成中断、空闲中断、分配串口DMA缓存。空闲中断可有可无,一般RS485通讯中需要用到,数据发送完毕后转为接收模式等。
后台已经将数据发送过来,剩下就是前台处理数据了,在USART3_DataProcessTask()任务中处理邮箱数据:
  1. /* USER CODE END USART3_DataProcessTask */
  2. void USART3_DataProcessTask(void const * argument)
  3. {
  4.     /* USER CODE BEGIN USART3_DataProcessTask */
  5.     osEvent USART3_Event;
  6.     Amail_TypeDef *USART3_RxMail;
  7.         
  8.     static uint8_t *RxBuff;
  9.     static uint8_t RxLength = 0;
  10.         
  11.     RxBuff = pvPortMalloc(256);
  12.         
  13.     /* Infinite loop */
  14.     for(;;)
  15.     {

  16.         USART3_Event = osMailGet(USART3_MailId, 0);
  17.         if(USART3_Event.status == osEventMail)
  18.         {
  19.             USART3_RxMail = USART3_Event.value.p;
  20.                         
  21.             RxLength = USART3_RxMail->DataLength;
  22.             RxBuff = USART3_RxMail->DataReceive;
  23.             osMailFree(USART3_MailId, USART3_RxMail);
  24.                         
  25.             for(uint8_t i=0;i<RxLength;i++)
  26.             {
  27.                 printf("%d\r\n",RxBuff[i]);
  28.             }

  29.         }
  30.         osDelay(1);
  31.     }
  32.     /* USER CODE END USART3_DataProcessTask */
  33. }

      在USART3_DataProcessTask()中等待邮箱数据的传入,如果有数据传入打印相关数据信息。别忘了要释放邮箱的内存,否则几次中断过后内存溢出程序卡死。
DMA发送就简单了,简单封装一下就可以拿来用:
  1. void BSP_USART3_SendData(uint8_t *TxBuff,uint16_t BuffLen)
  2. {
  3.     __HAL_DMA_CLEAR_FLAG(&hdma_usart3_tx,DMA_FLAG_TC2);
  4.     HAL_UART_Transmit_DMA(&huart3,TxBuff,BuffLen);                                       
  5. }
      上面的串口DMA收发是待操作系统的,如果不带操作系统就省事儿多了,定义一个全局数据缓存串口数据需要用的时候直接拿来用就可以了。
八层楼 发表于 2020-11-13 11:13 | 显示全部楼层
数据的传输速度最快能达到多少
观海 发表于 2020-11-13 11:15 | 显示全部楼层
用dma是不是有些奢侈了
guanjiaer 发表于 2020-11-13 11:16 | 显示全部楼层
通讯间隔一般是多少
heimaojingzhang 发表于 2020-11-13 11:18 | 显示全部楼层
这个外设是必不可少的啊 太常用了
keaibukelian 发表于 2020-11-13 11:19 | 显示全部楼层
这些串口有没有级别的区别啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则

40

主题

172

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部