天天看點

痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是嵌入式裡序列槽(UART)自動波特率識别程式設計與實作。

  序列槽(UART)是嵌入式裡最基礎最常用也最簡單的一種通訊(資料傳輸)方式,可以說是工程師入門通訊領域的啟蒙老師,同時序列槽列印也是嵌入式項目裡非常經典的調試與互動方式。

  最精簡的序列槽僅使用兩根單向信号線:TXD、RXD,這兩根信号線是獨立工作的,是以資料收發既可分開也可同時進行,這就是所謂的全雙工。序列槽沒有主從機概念,并且沒有專門的時鐘信号 SCK,是以序列槽通信也屬于異步傳輸。

  說到異步傳輸,這就不得不提波特率(每秒鐘傳輸bit數)的問題了,通信雙方必須使用一緻的波特率才能完成正确的資料傳輸。正常情況下,我們都是為兩個序列槽裝置事先約定好波特率,比如 MCU 與上位機通信,在 MCU 程式裡按 115200 的波特率去初始化 UART 外設,然後上位機序列槽調試助手也設定 115200 波特率,雙方再聯合工作。

  有時候,我們也希望能有一種靈活的波特率約定方式,比如建立通信前,在上位機序列槽調試助手裡随意設定一種波特率,然後按這個波特率發送資料,MCU 端能自動識别出這個波特率,并用識别出來的波特率去初始化 UART 外設,然後再進行後續資料傳輸,這種方式就叫自動波特率識别。痞子衡今天要分享的就是在 MCU 裡實作自動波特率識别的程式設計:

  • 程式首頁: https://github.com/JayHeng/cortex-m-apps/tree/master/components/autobaud

一、序列槽(UART)自動波特率識别程式設計

1.1 函數接口定義

  首先是設計自動波特率識别程式頭檔案:autobaud.h ,這個頭檔案裡直接定義如下 3 個接口函數原型。涵蓋必備的初始化流程 init()、deinit(),以及最核心的波特率識别功能 get_rate()。

//! @brief 初始化波特率識别     void autobaud_init(void);     //! @brief 檢測波特率識别是否已完成,并擷取波特率值     bool autobaud_get_rate(uint32_t *rate);     //! @brief 關閉波特率識别     void autobaud_deinit(void);           

1.2 識别設計思想

  關于識别,因為上位機資料是從 RXD 引腳過來的,是以在 MCU 裡需要先将 RXD 引腳配置成普通數字輸入 GPIO(這個引腳需要上拉,預設保持高電平),然後檢測這個 GPIO 的電平跳變(一般用下降沿)并計時。

  下圖是典型的 UART 單位元組傳輸時序,I/O 空閑狀态是高電平,傳輸時總是由 1bit 低電平起始位開啟,然後是從 LSB 到 MSB 的 8bit 資料位,校驗位是可選項(我們暫不開啟),最後由 1bit 高電平停止位結束,I/O 回歸高電平空閑狀态。

  • Note 1:檢測下降沿跳變,是因為 I/O 空閑為高,起始位的存在保證了每 Byte 傳輸周期總是從下降沿開始。
  • Note 2:起始位和停止位兩個 bit 的存在還兼有波特率容錯的功能,通信雙方波特率在 3% 的誤差内資料傳輸均可以正常進行。
痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)

  雖然我們不需要約定上位機波特率,但是要想實作波特率自動識别,上位機初始傳輸的資料卻必須要事先約定好(可了解為接頭暗号),這涉及到 MCU 裡檢測電平跳變次數與相應計時計算。這個接頭暗号是雙向的,MCU 端根據接頭暗号識别出波特率後,再将同樣的接頭暗号通過 UART_TXD 發送給上位機以确認(這部分邏輯不在自動波特率識别程式設計範疇裡,應放在項目整體設計裡)。

  痞子衡設計的接頭暗号是 0x5A, 0xA6 兩個位元組,兩位元組暗号相比單位元組暗号容錯性更好一些(以防 I/O 上有幹擾,導緻誤識别),根據指定的暗号和 UART 傳輸時序圖,我們很容易得到如下常量定義:

enum _autobaud_counts     {         //! 0x5A 位元組對應的下降沿個數         kFirstByteRequiredFallingEdges = 4,         //! 0xA6 位元組對應的下降沿個數         kSecondByteRequiredFallingEdges = 3,         //! 0x5A 位元組(從起始位到停止位)第一個下降沿到最後一個下降沿之間的實際bit數         kNumberOfBitsForFirstByteMeasured = 8,         //! 0xA6 位元組(從起始位到停止位)第一個下降沿到最後一個下降沿之間的實際bit數         kNumberOfBitsForSecondByteMeasured = 7,         //! 兩個下降沿之間允許的最大逾時(us)         kMaximumTimeBetweenFallingEdges = 80000,         //! 對實際檢測出的波特率值做對齊處理,以便于更好地配置UART子產品         kAutobaudStepSize = 1200     };           

  上述常量定義裡,kMaximumTimeBetweenFallingEdges 指定了兩個下降沿之間允許的最大時間間隔,超過這個時間,自動波特率程式将丢掉前面統計的下降沿個數,重頭開始識别,這個設計也是為了防止 I/O 上有電平幹擾,導緻誤識别。

  kAutobaudStepSize 常量是為了對檢測出的波特率值做對齊處理,公式是 rounded = stepSize * (value/stepSize + 0.5),其中 value 是實際檢測出的波特率值,rounded 是對齊後的波特率值,用對齊後的波特率值能更好地配置UART外設(這跟UART子產品裡波特率發生器SBR設計有關)。

  最後就是 I/O 電平下降沿檢測方法設計,這裡既可以用軟體查詢(就是循環讀取 I/O 輸入電平,比較目前值與上一次值的差異),也可以使用GPIO子產品自帶的邊沿中斷功能。推薦使用後者,一方面計時更精确,另外也不用阻塞系統。檢測到下降沿發生就調用一次如下 pin_transition_callback() 函數,在這個函數裡統計跳變次數以及計時。

//! @brief 管腳下降沿跳變回調函數     static void pin_transition_callback(void);           

1.3 主代碼實作

  根據上一小節描述的設計思想,我們很容易寫出下面的主代碼(autobaud_irq.c),代碼裡痞子衡都做了詳細注釋。有一點要提的是關于其中系統計時,可參考痞子衡舊文 《嵌入式裡通用微秒(microseconds)計時函數架構設計與實作》 。

//! @brief 使能GPIO管腳中斷     extern void enable_autobaud_pin_irq(pin_irq_callback_t func);     //! @brief 關閉GPIO管腳中斷     extern void disable_autobaud_pin_irq(void);     //!< 已檢測到的下降沿個數     static uint32_t s_transitionCount;     //!< 0x5A 位元組檢測期間内對應計數值     static uint64_t s_firstByteTotalTicks;     //!< 0xA6 位元組檢測期間内對應計數值     static uint64_t s_secondByteTotalTicks;     //!< 上一次下降沿發生時系統計數值     static uint64_t s_lastToggleTicks;     //!< 下降沿之間最大逾時對應計數值     static uint64_t s_ticksBetweenFailure;     void autobaud_init(void)     {         s_transitionCount = 0;         s_firstByteTotalTicks = 0;         s_secondByteTotalTicks = 0;         s_lastToggleTicks = 0;         // 計算出下降沿之間最大逾時對應計數值         s_ticksBetweenFailure = microseconds_convert_to_ticks(kMaximumTimeBetweenFallingEdges);         // 使能GPIO管腳中斷,并注冊中斷處理回調函數         enable_autobaud_pin_irq(pin_transition_callback);     }     void autobaud_deinit(void)     {         // 關閉GPIO管腳中斷         disable_autobaud_pin_irq();     }     bool autobaud_get_rate(uint32_t *rate)     {         if (s_transitionCount == (kFirstByteRequiredFallingEdges + kSecondByteRequiredFallingEdges))         {             // 計算出實際檢測到的波特率值             uint32_t calculatedBaud =                 (microseconds_get_clock() * (kNumberOfBitsForFirstByteMeasured + kNumberOfBitsForSecondByteMeasured)) /                 (uint32_t)(s_firstByteTotalTicks + s_secondByteTotalTicks);             // 對實際檢測出的波特率值做對齊處理             // 公式:rounded = stepSize * (value/stepSize + .5)             *rate = ((((calculatedBaud * 10) / kAutobaudStepSize) + 5) / 10) * kAutobaudStepSize;             return true;         }         else         {             return false;         }     }     void pin_transition_callback(void)     {         // 擷取目前系統計數值         uint64_t ticks = microseconds_get_ticks();         // 計數這次檢測到的下降沿         s_transitionCount++;         // 如果本次下降沿與上次下降沿之間間隔過長,則從頭開始檢測         uint64_t delta = ticks - s_lastToggleTicks;         if (delta > s_ticksBetweenFailure)         {             s_transitionCount = 1;         }         switch (s_transitionCount)         {             case 1:                 // 0x5A 位元組檢測時間起點                 s_firstByteTotalTicks = ticks;                 break;             case kFirstByteRequiredFallingEdges:                 // 得到 0x5A 位元組檢測期間内對應計數值                 s_firstByteTotalTicks = ticks - s_firstByteTotalTicks;                 break;             case (kFirstByteRequiredFallingEdges + 1):                 // 0xA6 位元組檢測時間起點                 s_secondByteTotalTicks = ticks;                 break;             case (kFirstByteRequiredFallingEdges + kSecondByteRequiredFallingEdges):                 // 得到 0xA6 位元組檢測期間内對應計數值                 s_secondByteTotalTicks = ticks - s_secondByteTotalTicks;                 // 關閉GPIO管腳中斷                 disable_autobaud_pin_irq();                 break;         }         // 記錄本次下降沿發生時系統計數值         s_lastToggleTicks = ticks;     }           

二、序列槽(UART)自動波特率識别程式實作

  前面講的都是硬體無關設計,但最終還是要落實到具體 MCU 平台上的,其中 GPIO 中斷部分是跟 MCU 緊相關的。我們以恩智浦 i.MXRT1011 為例來介紹硬體實作。

2.1 管腳中斷方式實作(基于i.MXRT1011)

  恩智浦 MIMXRT1010-EVK 有闆載調試器 DAPLink,這個 DAPLink 中也內建了 USB 轉序列槽的功能,對應的 UART 引腳是 IOMUXC_GPIO_09_LPUART1_RXD 和 IOMUXC_GPIO_10_LPUART1_TXD,我們就選用這個管腳 GPIO1[9] 做自動波特率檢測,實作代碼如下(注:代碼裡中斷處理函數 GPIO1_Combined_0_15_IRQHandler 實際上有點小缺陷,詳見 《中斷處理函數(IRQHandler)的标準流程》):

  • BSP程式: https://github.com/JayHeng/cortex-m-apps/tree/master/apps/autobaud_imxrt1011/bsp/src/pinmux_utility.c
typedef void (*pin_irq_callback_t)(void);     static pin_irq_callback_t s_pin_irq_func;     //! @brief UART引腳功能切換函數     void uart_pinmux_config(bool setGpio)     {         if (setGpio)         {             IOMUXC_SetUartAutoBaudPinMode(IOMUXC_GPIO_09_GPIOMUX_IO09, GPIO1, 9);         }         else         {             IOMUXC_SetUartPinMode(IOMUXC_GPIO_09_LPUART1_RXD);             IOMUXC_SetUartPinMode(IOMUXC_GPIO_10_LPUART1_TXD);         }     }     //! @brief 使能GPIO管腳中斷     void enable_autobaud_pin_irq(pin_irq_callback_t func)     {         s_pin_irq_func = func;         // 開啟GPIO1_9下降沿中斷         GPIO_SetPinInterruptConfig(GPIO1, 9, kGPIO_IntFallingEdge);         GPIO1->IMR |= (1U << 9);         NVIC_SetPriority(GPIO1_Combined_0_15_IRQn, 1);         NVIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);     }     //! @brief GPIO中斷處理函數     void GPIO1_Combined_0_15_IRQHandler(void)     {         uint32_t interrupt_flag = (1U << 9);         // 僅當GPIO1_9中斷發生時         if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func)         {             //執行一次回調函數             s_pin_irq_func();             GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag);         }     }           

2.2 在MIMXRT1010-EVK上實測

  一切就緒,我們現在來實測一下,主函數流程很簡單,測試結果也表明達到了預期效果,每次将 MCU 程式複位運作後,序列槽調試助手裡可任意設定波特率。

int main(void)     {         // 略去系統時鐘配置...         // 初始化定時器         microseconds_init();         // 将GPIO1_9先配成輸入GPIO         bool setGpio = true;         uart_pinmux_config(setGpio);         // 初始化波特率識别         autobaud_init();         // 檢測波特率識别是否已完成,并擷取波特率值         uint32_t baudrate;         while (!autobaud_get_rate(&baudrate));         // 關閉波特率識别         autobaud_deinit();         // 配置UART1引腳         setGpio = false;         uart_pinmux_config(setGpio);         // 初始化UART1外設         uint32_t uartClkSrcFreq = BOARD_DebugConsoleSrcFreq();         DbgConsole_Init(1, baudrate, kSerialPort_Uart, uartClkSrcFreq);         PRINTF("Autobaud test success\r\n");         PRINTF("Detected baudrate is %d\r\n", baudrate);         while (1);     }           
痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)

  至此,嵌入式裡序列槽(UART)自動波特率識别程式設計與實作痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時釋出到我的 部落格園首頁、CSDN首頁、知乎首頁、微信公衆号 平台上。

微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。

痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)

  最後歡迎關注痞子衡個人微信公衆号【痞子衡嵌入式】,一個專注嵌入式技術的公衆号,跟着痞子衡一起玩轉嵌入式。

痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)
痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)
痞子衡嵌入式:嵌入式裡序列槽(UART)自動波特率識别程式設計與實作(中斷)

  衡傑(痞子衡),目前就職于恩智浦MCU系統部門,擔任嵌入式系統應用工程師。

  專欄内所有文章的轉載請注明出處:http://www.cnblogs.com/henjay724/

  與痞子衡進一步交流或咨詢業務合作請發郵件至 [email protected]

  可以關注痞子衡的Github首頁 https://github.com/JayHeng,有很多好玩的嵌入式項目。

  關于專欄文章有任何疑問請直接在部落格下面留言,痞子衡會及時回複免費(劃重點)答疑。

  痞子衡郵箱已被私信擠爆,技術問題不推薦私信,堅持私信請先掃碼付款(5元起步)再發。