第三个实验例子:FreeRTOS USART DMA 空闲中断接收 队列
上一篇是串口中断接收数据,然后通过消息队列转发。它实现的方法是每收到一个字节发送一次,这个做法用在串口转发数据上实时性还是不错的,但是在平时多数的串口应用中更多的是需要实际通讯。往往是单片机接收一帧数据,根据通讯协议实现某些功能,然后再回答。为此我尝试采用通过采用DMA方式,利用空闲中断来接收一帧数据,然后发送数据到队列,线程收到数据后处理数据。我采用modbus通讯协议做实验。
CubeMx的FreeRTOS中创建Queue不能直接支持结构体或数组,为了这个问题我反复做了好几种实验。先总结一下:
方法一:
当空闲中断产生,收到一帧数据时,通过xQueueSendToBackFromISR()函数发送帧数据的指针值,然后在线程中用osMessageGet()函数接收后,处理数据。这个做法可以实现功能,但是由于传递的是指针,所以在线程中处理数据时如果串口又有新的数据到达,就会发生问题。
方法二:
自己建一个FIFO的帧数据缓存(定义一帧数据的结构体,再做一个结构体的数组),息队列传递当前一帧数据的指针,这个方法也可以能实现功能,但是我担心自己定义FIFO帧数据缓存,如果代码写得不好是否有不可预测的问题。
方法三:
第三种方法是我要重点介绍的:
- CubeMx设置
串口设置
FreeRTOS设置:
只新建二个Task,Queue之后在Keil手工创建
然后生成代码。
- 程序代码
串口部分参考我之前的博文:STM32的串口空闲中断接收不定长数据
其中串口空闲中断函数要增加消息队列发送的代码。
void Usart1Receive_IDLE(UART_HandleTypeDef *huart) { uint32_t temp; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(huart->Instance==USART1) //USART1 { if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); HAL_UART_DMAStop(&huart1); temp = huart1.hdmarx->Instance->CNDTR; ModbusReceiveData.rx_len = RECEIVELEN - temp; ModbusReceiveData.receive_flag=1; //队列 xQueueSendToBackFromISR(myQ02Handle,&ModbusReceiveData,&xHigherPriorityTaskWoken); ModbusReceiveData.receive_flag=0; portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
HAL_UART_Receive_DMA(&huart1,ModbusReceiveData.usartDMA_rxBuf,RECEIVELEN); } } } |
在freertos.c中添加下列语句:
- 声明
/* USER CODE BEGIN Variables */ extern UART_HandleTypeDef huart1; osMessageQId myQ02Handle; //声明MessageQueue句柄
/* USER CODE END Variables */ |
- 创建MessageQueue
/* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ myQ02Handle=xQueueCreate(3, sizeof(_USART_BUFF_TYPE)); /* USER CODE END RTOS_QUEUES */ |
队列深度可以根据实际情况,我尝试设置为1做测试,可以正常运行。前提是在上位机检查到通讯超时之前单片机已经应答。为了保险起见我最后设置了2 。
- 线程接收
/* USER CODE BEGIN Header_StartTask02 */ /** * @brief Function implementing the myTask02 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask02 */ void StartTask02(void const * argument) { /* USER CODE BEGIN StartTask02 */ _USART_BUFF_TYPE p; /* Infinite loop */ for(;;) {
xQueueReceive(myQ02Handle,(void *)&p,osWaitForever); //接收信息 HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET); if((p).receive_flag) { HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET); checkComm0Modbus(&p) ; //Modbus数据解析 HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET); } HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET); osDelay(1);
} /* USER CODE END StartTask02 */ }
|
实际效果: