芯片手冊
某個Cortex-M4芯片帶有1個UART,支持Tx,Rx 的FIFO功能,而且可以通過寄存器配置FIFO的閾值,芯片的datasheet並不完善,沒有說明RX的FIFO具體有幾個級別,每隔級別的閾值是多少。
但是需要注意的是 TX, RX 的FIFO都可以通過UART 的 DR 寄存器進行訪問。
RX FIFO 閾值
功夫不負有心人,終於在SDK的某段代碼中窺見了RX的幾個FIFO閾值:
默認情況下RX FIFO 是收到32字節才會產生一次RX中斷。
如果收到的數據長度沒有32字節,則一直等,等到滿足32字節,才產生RX中斷。
RX FIFO 閾值 影響RX中斷產生的頻率
之所以注意到這個問題,是因為下面的一件事情:
調試的時候發現電腦發送6個字符,但是開發板並沒有打印6個字符,再加6個字符,還是沒有,繼續加6個字符,直到滿足了32個字節,這個時候才有了RX中斷,把收到的數據打印了出來。
RX 收到的數據不完整
上面雖然收到了數據,但是並沒有把32個字符都保存下來。原來在UART中斷程序中,在RX中斷條件下,每次都把前面的數據清空了。
if (ui32Status & ( AM_REG_UART_IES_RXRIS_M | AM_REG_UART_IES_RTRIS_M)) {
// 其他操作
os_memset(g_uart_buf, '\0', sizeof(g_uart_buf)); // 后來才意識到在 uart 中斷服務程序中每次接收都清空接收緩沖區是一個大錯誤。
// 接收數據
*(g_uart_buf + index++) = AM_REGn(UART, ui32Module, DR);
}
如上面的例子,會導致每一次中斷中清空前面的數據,永遠只能看到后一次FIFO中的數據,最多為32個字節。這樣絕對是錯誤的,因為每次從FIFO中取走了一個字節,后面當FIFO滿時又會產生新的RX中斷。
所以每次中斷中盡量取走所有的數據。該怎么做呢?需要讀UART寄存器狀態,FIFO不空的情況下把所有的數據取走,例如下面:
if(ui32Status & (AM_REG_UART_IES_RXRIS_M | AM_REG_UART_IES_RTRIS_M))
{
while ( !AM_BFRn(UART, 0, FR, RXFE) )
{
if (uart_data_index < UART_BUF_LENGTH) { // 不能超出 g_uart_buf 的長度
*(g_uart_buf + uart_data_index++) = AM_REGn(UART, ui32Module, DR);
} else { // 超出部分直接丟棄
ui8Char = AM_REGn(UART, ui32Module, DR);
}
}
}
還需要注意,如果對端模塊返回的UART數據超過了接收緩沖區的長度,需要丟棄。如果不丟棄則無法退出中斷。
總結
UART 使用主要是初始化,中斷服務程序以及應用程序對收發數據的處理。
初始化部分,需要完成的工作:
- 引腳配置
- UART模塊的時鍾使能
- UART的配置如波特率,數據位,停止位,奇偶校驗,流控制
- FIFO配置,一般重點關注RX的FIFO
- UART模塊使能
- UART中斷使能
- UART中斷子類別使能,例如
AM_HAL_UART_INT_RX
表示RX中斷,AM_HAL_UART_INT_RX_TMOUT
表示超時中斷
中斷服務程序,一般來說主要負責接收,可以按照下面的流程:
- 讀UART的中斷狀態,判定是哪一種UART中斷,例如是RX還是RX TMOUT 中斷
- 根據具體的UART中斷類型采取對應的操作
- 讀取DR寄存器,放到緩沖區,直到滿足某個條件退出讀DR的循環
- 清除中斷,修改某些標志位,如果跑了OS,可以發出信號量通知其他線程
- 退出中斷服務程序
應用程序,在某個時候處理UART接收緩沖區,必要的時候需要清空緩沖區。
如果不清空的話,UART中斷會一直把數據拷貝到緩沖區,直到緩沖區滿,后面的數據就浪費了。同時應用程序也會處理這些重復的數據可能無法進入正常的流程。
按道理說在應用層清空緩沖區就可以了,但是我擔心應用程序在操作緩沖區的時候突然被中斷打斷了,清空操作出錯了或者把剛剛收到的數據清空了,我以為讓中斷程序在某些條件下清空或許是一個不錯的選擇。
如下是我的實現,應用層發出清空緩沖區的請求uart_data_clear()
,如果恰好下一次UART中斷即時,就可以在中斷一開始清空緩沖區。如果下一次UART中斷來的不及時,那么第二次調用uart_data_clear()
就可以在應用層清空緩沖區。
extern unsigned char g_uart_buf[UART_BUF_LENGTH]; // UART 接收緩沖區
extern uint16_t uart_data_index; // 當前寫入UART接收緩沖區的數據的位置。
extern uint8_t uart_data_clear_flag; // 1 -- 表示需要清除數據; 0 -- 不需要清除數據
/**
* 第一次調用,可以讓UART中斷清空緩沖區,第二次調用可以在應用層清空緩沖區
*/
void uart_data_clear(void)
{
// 說明還沒有進入一次UART中斷,這里在應用層主動清空UART數據
if (uart_data_clear_flag) {
os_memset(g_uart_buf, 0, sizeof(g_uart_buf));
uart_data_index = 0;
uart_data_clear_flag = 0;
} else {
// 在UART中斷清空數據
uart_data_clear_flag = 1;
}
}
// 另一個文件中的UART中斷服務程序
void am_uart_isr(void)
{
uint32_t ui32Status;
uint32_t ui32Module = 0;
uint8_t ui8Char = 0;
//
// Read the masked interrupt status from the UART.
//
ui32Status = am_hal_uart_int_status_get(true);
if(ui32Status & (AM_REG_UART_IES_RXRIS_M | AM_REG_UART_IES_RTRIS_M))
{
// 用戶已經處理完了上一次的UART接收數據,可以清空緩沖區以備下一次接收
if (uart_data_clear_flag) {
uart_data_clear_flag = 0;
os_memset(g_uart_buf, '\0', sizeof(g_uart_buf));
uart_data_index = 0;
}
while ( !AM_BFRn(UART, 0, FR, RXFE) )
{
if (uart_data_index < UART_BUF_LENGTH) { // 不能超出 g_uart_buf 的長度
*(g_uart_buf + uart_data_index++) = AM_REGn(UART, ui32Module, DR);
} else { // 超出部分直接丟棄
ui8Char = AM_REGn(UART, ui32Module, DR);
}
}
}
// Clear the UART interrupts.
//
am_hal_uart_int_clear(ui32Status);
// 發出信號量通知其他任務
// do something...
}
聲明
歡迎轉載,請注明出處和作者,同時保留聲明。
作者:LinTeX9527
出處:https://www.cnblogs.com/LinTeX9527/p/9183656.html
本博客的文章如無特殊說明,均為原創,轉載請注明出處。如未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。