????????想了很久,不知道該標題起的是否合適,該篇Blog用于記錄在使用HAL庫的USART
模塊時實際遇到的一個涉及發送方式的問題,用于提醒自身同時也希望能幫到各位。
程序問題敘述
???????? 先來看一段代碼:
void CusUSART_SendByte_IT( uint8_t Byte )
{ while((husart1.State != HAL_USART_STATE_READY));HAL_USART_Transmit_IT(&husart1, &Byte, 1);
}void test_task( void )
{for( ; ; ){HAL_USART_Transmit_IT(&husart1, "TEST RUNNING.\n", strlen("TEST RUNNING.\n"));vTaskDelay(1000);CusUSART_SendByte_IT( 0x42 ); // ASCII: 0x42 BvTaskDelay(1000);}
}
????????上述代碼展示了一個示例RTOS任務,間隔一秒向串口發送字符串"TEST RUNNING.\n
",和字符'B
'。理想情況下,串口接收到的數據應該如下所示:
TEST RUNNING.
B
????????但將程序燒入芯片后,串口接收到的數據如下圖所示:
????????可以看到發送的順序不僅出現錯誤,發送的字符數據也出現了錯誤。
原因
????????在這里需要先知道HAL_USART_Transmit_IT
與HAL_USART_Transmit
方法的本質區別.
HAL_USART_Transmit
本質是以阻塞方式通過 USART
發送數據。也就是說該方法會將指定的數據緩沖區中的數據通過 USART
發送出去,且CPU
會一直等待直到所有數據都發送完成才會返回。
HAL_USART_Transmit_IT
該方法本質是以非阻塞方式通過USART
發送數據。也就是說該方法只是將緩沖區地址和大小設置好,啟動USART
傳輸后會立即返回,實際的傳輸由后臺硬件中斷完成,并不會等待USART發送完成再返回!。數據傳輸完成后,HAL 庫會調用回調函數(HAL_USART_TxCpltCallback
)通知傳輸完成。
????????了解了兩種方法的區別之后,問題就很明顯了:
void CusUSART_SendByte_IT( uint8_t Byte )
{ while((husart1.State != HAL_USART_STATE_READY));HAL_USART_Transmit_IT(&husart1, &Byte, 1);
}
????????在該函數中,待發送的字節 Byte
為局部變量,由外部傳入。作為局部變量,當調用棧結束后即被銷毀。當Transmit_IT
方法調用后,將局部變量Byte
的地址傳入,將傳輸請求提交給后臺后便返回。此時,字符 Byte
的傳輸并未真正開始抑或是開始了并未完成傳輸。而此時在HAL_USART_Transmit_IT
返回后,該函數(Cus)已經結束,Byte
已經被銷毀。進而后臺真正開始發送時,訪問的地址是已經被銷毀了的,自然發送的就是錯誤數據。
????????知曉問題之后,對其作如下修改:
void CusUSART_SendByte_IT( uint8_t Byte )
{ while((husart1.State != HAL_USART_STATE_READY));HAL_USART_Transmit_IT(&husart1, &Byte, 1);for(uint32_t i = 0; i < 5000; i++){ } // 簡單阻塞延時.
}
只要保證在發送完畢前變量不被銷毀即可。當然這種修改方式并不好,我們做如下修改。
void CusUSART_SendByte_IT( uint8_t Byte )
{ static uint8_t temp;while((husart1.State != HAL_USART_STATE_READY));temp = Byte;HAL_USART_Transmit_IT(&husart1, &temp, 1);
}
此時,再將程序燒入,即可得到串口接收到的數據如下:
至于亂序問題,我先把初始化代碼貼出:
void CusUSART_Init( uint32_t baudrate )
{.......HAL_USART_Transmit_IT(&husart1, "USART Init OK!", strlen("USART Init OK!"));
}
????????在該初始化函數中,有一行關鍵代碼HAL_USART_Transmit_IT(&husart1, "USART Init OK!", strlen("USART Init OK!"));
????????初始化函數被調用后,緊接著就啟動USART
任務開始傳輸. 而初始化函數末尾使用了Transmit_IT
方法發送初始化完畢信息,但是要注意!該方法是非阻塞的!當任務啟動開始運行后:
void test_task( void )
{for( ; ; ){// 此處距離初始化函數中的異步IT發送方法的時間間隔太近了!HAL_USART_Transmit_IT(&husart1, "TEST RUNNING.\n", strlen("TEST RUNNING.\n"));vTaskDelay(1000);CusUSART_SendByte_IT( 0x42 );vTaskDelay(1000);}
}
??"USART Init OK!"
還在發送過程中時就進入了任務中,"TEST RUNNING.\n"
再次發出傳輸請求,但是由于后臺仍在處理"USART Init OK!"
,因此此次傳輸請求便被取消了,這也是為什么會先輸出B的關鍵原因所在!
知道了原因,那修改自然不難:
最簡單的便是在問題處代碼前直接加入延時。
vTaskDelay(1000);HAL_USART_Transmit_IT(&husart1, "TEST RUNNING.\n", strlen("TEST RUNNING.\n"));
亦或是在提交發送請求后進行檢查
void test_task( void )
{for( ; ; ){while(HAL_USART_Transmit_IT(&husart1, (uint8_t *)"TEST RUNNING.\n", strlen("TEST RUNNING.\n")) != HAL_OK){vTaskDelay(1);}vTaskDelay(1000);CusUSART_SendByte_IT( 0x42 );vTaskDelay(1000);}
}
?只要前后留出一定的時間裕度即可。運行結果如下:
?????