串口(UART)自動(dòng)波特率識別程序設計
以下文章來(lái)源于痞子衡嵌入式 ,作者痞子衡
大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是嵌入式里串口(UART)自動(dòng)波特率識別程序設計與實(shí)現。
串口(UART)是嵌入式里最基礎最常用也最簡(jiǎn)單的一種通訊(數據傳輸)方式,可以說(shuō)是工程師入門(mén)通訊領(lǐng)域的啟蒙老師,同時(shí)串口打印也是嵌入式項目里非常經(jīng)典的調試與交互方式。
最精簡(jiǎn)的串口僅使用兩根單向信號線(xiàn):TXD、RXD,這兩根信號線(xiàn)是獨立工作的,因此數據收發(fā)既可分開(kāi)也可同時(shí)進(jìn)行,這就是所謂的全雙工。串口沒(méi)有主從機概念,并且沒(méi)有專(zhuān)門(mén)的時(shí)鐘信號 SCK,所以串口通信也屬于異步傳輸。
說(shuō)到異步傳輸,這就不得不提波特率(每秒鐘傳輸bit數)的問(wèn)題了,通信雙方必須使用一致的波特率才能完成正確的數據傳輸。正常情況下,我們都是為兩個(gè)串口設備事先約定好波特率,比如 MCU 與上位機通信,在 MCU 程序里按 115200 的波特率去初始化 UART 外設,然后上位機串口調試助手也設置 115200 波特率,雙方再聯(lián)合工作。
有時(shí)候,我們也希望能有一種靈活的波特率約定方式,比如建立通信前,在上位機串口調試助手里隨意設置一種波特率,然后按這個(gè)波特率發(fā)送數據,MCU 端能自動(dòng)識別出這個(gè)波特率,并用識別出來(lái)的波特率去初始化 UART 外設,然后再進(jìn)行后續數據傳輸,這種方式就叫自動(dòng)波特率識別。痞子衡今天要分享的就是在 MCU 里實(shí)現自動(dòng)波特率識別的程序設計:
程序主頁(yè):https://github.com/JayHeng/cortex-m-apps/tree/master/components/autobaud
一、串口(UART)自動(dòng)波特率識別程序設計
1.1 函數接口定義
首先是設計自動(dòng)波特率識別程序頭文件:autobaud.h ,這個(gè)頭文件里直接定義如下 3 個(gè)接口函數原型。涵蓋必備的初始化流程 init()、deinit(),以及最核心的波特率識別功能
get_rate()。 //! @brief 初始化波特率識別 void autobaud_init(void); //! @brief 檢測波特率識別是否已完成,并獲取波特率值 bool autobaud_get_rate(uint32_t *rate); //! @brief 關(guān)閉波特率識別 void autobaud_deinit(void);
1.2 識別設計思想
關(guān)于識別,因為上位機數據是從 RXD 引腳過(guò)來(lái)的,所以在 MCU 里需要先將 RXD 引腳配置成普通數字輸入 GPIO(這個(gè)引腳需要上拉,默認保持高電平),然后檢測這個(gè) GPIO 的電平跳變(一般用下降沿)并計時(shí)。
下圖是典型的 UART 單字節傳輸時(shí)序,I/O 空閑狀態(tài)是高電平,傳輸時(shí)總是由 1bit 低電平起始位開(kāi)啟,然后是從 LSB 到 MSB 的 8bit 數據位,校驗位是可選項(我們暫不開(kāi)啟),最后由 1bit 高電平停止位結束,I/O 回歸高電平空閑狀態(tài)。
Note 1:檢測下降沿跳變,是因為 I/O 空閑為高,起始位的存在保證了每 Byte 傳輸周期總是從下降沿開(kāi)始。
Note 2:起始位和停止位兩個(gè) bit 的存在還兼有波特率容錯的功能,通信雙方波特率在 3% 的誤差內數據傳輸均可以正常進(jìn)行。
雖然我們不需要約定上位機波特率,但是要想實(shí)現波特率自動(dòng)識別,上位機初始傳輸的數據卻必須要事先約定好(可理解為接頭暗號),這涉及到 MCU 里檢測電平跳變次數與相應計時(shí)計算。MCU識別完成后將暗號發(fā)回給上位機確認。
痞子衡設計的接頭暗號是 0x5A, 0xA6 兩個(gè)字節,兩字節暗號相比單字節暗號容錯性更好一些(以防 I/O 上有干擾,導致誤識別),根據指定的暗號和 UART 傳輸時(shí)序圖,我們很容易得到如下常量定義:
enum _autobaud_counts { //! 0x5A 字節對應的下降沿個(gè)數 kFirstByteRequiredFallingEdges = 4, //! 0xA6 字節對應的下降沿個(gè)數 kSecondByteRequiredFallingEdges = 3, //! 0x5A 字節(從起始位到停止位)第一個(gè)下降沿到最后一個(gè)下降沿之間的實(shí)際bit數 kNumberOfBitsForFirstByteMeasured = 8, //! 0xA6 字節(從起始位到停止位)第一個(gè)下降沿到最后一個(gè)下降沿之間的實(shí)際bit數 kNumberOfBitsForSecondByteMeasured = 7, //! 兩個(gè)下降沿之間允許的最大超時(shí)(us) kMaximumTimeBetweenFallingEdges = 80000, //! 對實(shí)際檢測出的波特率值做對齊處理,以便于更好地配置UART模塊 kAutobaudStepSize = 1200 };
上述常量定義里,kMaximumTimeBetweenFallingEdges 指定了兩個(gè)下降沿之間允許的最大時(shí)間間隔,超過(guò)這個(gè)時(shí)間,自動(dòng)波特率程序將丟掉前面統計的下降沿個(gè)數,重頭開(kāi)始識別,這個(gè)設計也是為了防止 I/O 上有電平干擾,導致誤識別。
kAutobaudStepSize 常量是為了對檢測出的波特率值做對齊處理,公式是 rounded = stepSize * (value/stepSize + 0.5),其中 value 是實(shí)際檢測出的波特率值,rounded 是對齊后的波特率值,用對齊后的波特率值能更好地配置UART外設(這跟UART模塊里波特率發(fā)生器SBR設計有關(guān))。
最后就是 I/O 電平下降沿檢測方法設計,這里既可以用軟件查詢(xún)(就是循環(huán)讀取 I/O 輸入電平,比較當前值與上一次值的差異),也可以使用GPIO模塊自帶的邊沿中斷功能。推薦使用后者,一方面計時(shí)更精確,另外也不用阻塞系統。檢測到下降沿發(fā)生就調用一次如下 pin_transition_callback() 函數,在這個(gè)函數里統計跳變次數以及計時(shí)。
//! @brief 管腳下降沿跳變回調函數 static void pin_transition_callback(void);
1.3 主代碼實(shí)現
根據上一小節描述的設計思想,我們很容易寫(xiě)出下面的主代碼(autobaud_irq.c),代碼里痞子衡都做了詳細注釋。有一點(diǎn)要提的是關(guān)于其中系統計時(shí),可參考痞子衡舊文 《嵌入式里通用微秒(microseconds)計時(shí)函數框架設計與實(shí)現》 。
//! @brief 使能GPIO管腳中斷 extern void enable_autobaud_pin_irq(pin_irq_callback_t func); //! @brief 關(guān)閉GPIO管腳中斷 extern void disable_autobaud_pin_irq(void); //!< 已檢測到的下降沿個(gè)數 static uint32_t s_transitionCount; //!< 0x5A 字節檢測期間內對應計數值 static uint64_t s_firstByteTotalTicks; //!< 0xA6 字節檢測期間內對應計數值 static uint64_t s_secondByteTotalTicks; //!< 上一次下降沿發(fā)生時(shí)系統計數值 static uint64_t s_lastToggleTicks; //!< 下降沿之間最大超時(shí)對應計數值 static uint64_t s_ticksBetweenFailure; void autobaud_init(void) { s_transitionCount = 0; s_firstByteTotalTicks = 0; s_secondByteTotalTicks = 0; s_lastToggleTicks = 0; // 計算出下降沿之間最大超時(shí)對應計數值 s_ticksBetweenFailure = microseconds_convert_to_ticks(kMaximumTimeBetweenFallingEdges); // 使能GPIO管腳中斷,并注冊中斷處理回調函數 enable_autobaud_pin_irq(pin_transition_callback); } void autobaud_deinit(void) { // 關(guān)閉GPIO管腳中斷 disable_autobaud_pin_irq(); } bool autobaud_get_rate(uint32_t *rate) { if (s_transitionCount == (kFirstByteRequiredFallingEdges + kSecondByteRequiredFallingEdges)) { // 計算出實(shí)際檢測到的波特率值 uint32_t calculatedBaud = (microseconds_get_clock() * (kNumberOfBitsForFirstByteMeasured + kNumberOfBitsForSecondByteMeasured)) / (uint32_t)(s_firstByteTotalTicks + s_secondByteTotalTicks); // 對實(shí)際檢測出的波特率值做對齊處理 // 公式: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++; // 如果本次下降沿與上次下降沿之間間隔過(guò)長(cháng),則從頭開(kāi)始檢測 uint64_t delta = ticks - s_lastToggleTicks; if (delta > s_ticksBetweenFailure) { s_transitionCount = 1; } switch (s_transitionCount) { case 1: // 0x5A 字節檢測時(shí)間起點(diǎn) s_firstByteTotalTicks = ticks; break; case kFirstByteRequiredFallingEdges: // 得到 0x5A 字節檢測期間內對應計數值 s_firstByteTotalTicks = ticks - s_firstByteTotalTicks; break; case (kFirstByteRequiredFallingEdges + 1): // 0xA6 字節檢測時(shí)間起點(diǎn) s_secondByteTotalTicks = ticks; break; case (kFirstByteRequiredFallingEdges + kSecondByteRequiredFallingEdges): // 得到 0xA6 字節檢測期間內對應計數值 s_secondByteTotalTicks = ticks - s_secondByteTotalTicks; // 關(guān)閉GPIO管腳中斷 disable_autobaud_pin_irq(); break; } // 記錄本次下降沿發(fā)生時(shí)系統計數值 s_lastToggleTicks = ticks; }
二、串口(UART)自動(dòng)波特率識別程序實(shí)現
前面講的都是硬件無(wú)關(guān)設計,但最終還是要落實(shí)到具體 MCU 平臺上的,其中 GPIO 中斷部分是跟 MCU 緊相關(guān)的。我們以恩智浦 i.MXRT1011 為例來(lái)介紹硬件實(shí)現。
2.1 管腳中斷方式實(shí)現(基于i.MXRT1011)
恩智浦 MIMXRT1010-EVK 有板載調試器 DAPLink,這個(gè) DAPLink 中也集成了 USB 轉串口的功能,對應的 UART 引腳是 IOMUXC_GPIO_09_LPUART1_RXD 和 IOMUXC_GPIO_10_LPUART1_TXD,我們就選用這個(gè)管腳 GPIO1[9] 做自動(dòng)波特率檢測,實(shí)現代碼如下:
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; // 開(kāi)啟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中斷發(fā)生時(shí) if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func) { //執行一次回調函數 s_pin_irq_func(); GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag); } }
2.2 在MIMXRT1010-EVK上實(shí)測
一切就緒,我們現在來(lái)實(shí)測一下,主函數流程很簡(jiǎn)單,測試結果也表明達到了預期效果,每次將 MCU 程序復位運行后,串口調試助手里可任意設置波特率。
int main(void) { // 略去系統時(shí)鐘配置... // 初始化定時(shí)器 microseconds_init(); // 將GPIO1_9先配成輸入GPIO bool setGpio = true; uart_pinmux_config(setGpio); // 初始化波特率識別 autobaud_init(); // 檢測波特率識別是否已完成,并獲取波特率值 uint32_t baudrate; while (!autobaud_get_rate(&baudrate)); // 關(guān)閉波特率識別 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)自動(dòng)波特率識別程序設計與實(shí)現痞子衡便介紹完畢了,掌聲在哪里~~~
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。
助聽(tīng)器原理相關(guān)文章:助聽(tīng)器原理