第三個實驗例子: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 */ }
|
實際效果: