帶FIFO的UART數據接收


芯片手冊

某個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
本博客的文章如無特殊說明,均為原創,轉載請注明出處。如未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM