在线看毛片网站电影-亚洲国产欧美日韩精品一区二区三区,国产欧美乱夫不卡无乱码,国产精品欧美久久久天天影视,精品一区二区三区视频在线观看,亚洲国产精品人成乱码天天看,日韩久久久一区,91精品国产91免费

<menu id="6qfwx"><li id="6qfwx"></li></menu>
    1. <menu id="6qfwx"><dl id="6qfwx"></dl></menu>

      <label id="6qfwx"><ol id="6qfwx"></ol></label><menu id="6qfwx"></menu><object id="6qfwx"><strike id="6qfwx"><noscript id="6qfwx"></noscript></strike></object>
        1. <center id="6qfwx"><dl id="6qfwx"></dl></center>

            博客專欄

            EEPW首頁 > 博客 > 串口(UART)自動波特率識別程序設(shè)計

            串口(UART)自動波特率識別程序設(shè)計

            發(fā)布人:魚鷹談單片機(jī) 時間:2021-07-07 來源:工程師 發(fā)布文章

            以下文章來源于痞子衡嵌入式 ,作者痞子衡

            大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是嵌入式里串口(UART)自動波特率識別程序設(shè)計與實(shí)現(xiàn)。

            串口(UART)是嵌入式里最基礎(chǔ)最常用也最簡單的一種通訊(數(shù)據(jù)傳輸)方式,可以說是工程師入門通訊領(lǐng)域的啟蒙老師,同時串口打印也是嵌入式項目里非常經(jīng)典的調(diào)試與交互方式。

            最精簡的串口僅使用兩根單向信號線:TXD、RXD,這兩根信號線是獨(dú)立工作的,因此數(shù)據(jù)收發(fā)既可分開也可同時進(jìn)行,這就是所謂的全雙工。串口沒有主從機(jī)概念,并且沒有專門的時鐘信號 SCK,所以串口通信也屬于異步傳輸。

            說到異步傳輸,這就不得不提波特率(每秒鐘傳輸bit數(shù))的問題了,通信雙方必須使用一致的波特率才能完成正確的數(shù)據(jù)傳輸。正常情況下,我們都是為兩個串口設(shè)備事先約定好波特率,比如 MCU 與上位機(jī)通信,在 MCU 程序里按 115200 的波特率去初始化 UART 外設(shè),然后上位機(jī)串口調(diào)試助手也設(shè)置 115200 波特率,雙方再聯(lián)合工作。

            有時候,我們也希望能有一種靈活的波特率約定方式,比如建立通信前,在上位機(jī)串口調(diào)試助手里隨意設(shè)置一種波特率,然后按這個波特率發(fā)送數(shù)據(jù),MCU 端能自動識別出這個波特率,并用識別出來的波特率去初始化 UART 外設(shè),然后再進(jìn)行后續(xù)數(shù)據(jù)傳輸,這種方式就叫自動波特率識別。痞子衡今天要分享的就是在 MCU 里實(shí)現(xiàn)自動波特率識別的程序設(shè)計:

            程序主頁:https://github.com/JayHeng/cortex-m-apps/tree/master/components/autobaud

            一、串口(UART)自動波特率識別程序設(shè)計

            1.1 函數(shù)接口定義

            首先是設(shè)計自動波特率識別程序頭文件:autobaud.h ,這個頭文件里直接定義如下 3 個接口函數(shù)原型。涵蓋必備的初始化流程 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 識別設(shè)計思想

            關(guān)于識別,因?yàn)樯衔粰C(jī)數(shù)據(jù)是從 RXD 引腳過來的,所以在 MCU 里需要先將 RXD 引腳配置成普通數(shù)字輸入 GPIO(這個引腳需要上拉,默認(rèn)保持高電平),然后檢測這個 GPIO 的電平跳變(一般用下降沿)并計時。

            下圖是典型的 UART 單字節(jié)傳輸時序,I/O 空閑狀態(tài)是高電平,傳輸時總是由 1bit 低電平起始位開啟,然后是從 LSB 到 MSB 的 8bit 數(shù)據(jù)位,校驗(yàn)位是可選項(我們暫不開啟),最后由 1bit 高電平停止位結(jié)束,I/O 回歸高電平空閑狀態(tài)。

            Note 1:檢測下降沿跳變,是因?yàn)?I/O 空閑為高,起始位的存在保證了每 Byte 傳輸周期總是從下降沿開始。

            Note 2:起始位和停止位兩個 bit 的存在還兼有波特率容錯的功能,通信雙方波特率在 3% 的誤差內(nèi)數(shù)據(jù)傳輸均可以正常進(jìn)行。

            1.png

            雖然我們不需要約定上位機(jī)波特率,但是要想實(shí)現(xiàn)波特率自動識別,上位機(jī)初始傳輸?shù)臄?shù)據(jù)卻必須要事先約定好(可理解為接頭暗號),這涉及到 MCU 里檢測電平跳變次數(shù)與相應(yīng)計時計算。MCU識別完成后將暗號發(fā)回給上位機(jī)確認(rèn)。

            痞子衡設(shè)計的接頭暗號是 0x5A, 0xA6 兩個字節(jié),兩字節(jié)暗號相比單字節(jié)暗號容錯性更好一些(以防 I/O 上有干擾,導(dǎo)致誤識別),根據(jù)指定的暗號和 UART 傳輸時序圖,我們很容易得到如下常量定義:

            enum _autobaud_counts
            {
                //! 0x5A 字節(jié)對應(yīng)的下降沿個數(shù)
                kFirstByteRequiredFallingEdges = 4,
                //! 0xA6 字節(jié)對應(yīng)的下降沿個數(shù)
                kSecondByteRequiredFallingEdges = 3,
                //! 0x5A 字節(jié)(從起始位到停止位)第一個下降沿到最后一個下降沿之間的實(shí)際bit數(shù)
                kNumberOfBitsForFirstByteMeasured = 8,
                //! 0xA6 字節(jié)(從起始位到停止位)第一個下降沿到最后一個下降沿之間的實(shí)際bit數(shù)
                kNumberOfBitsForSecondByteMeasured = 7,
                //! 兩個下降沿之間允許的最大超時(us)
                kMaximumTimeBetweenFallingEdges = 80000,
                //! 對實(shí)際檢測出的波特率值做對齊處理,以便于更好地配置UART模塊
                kAutobaudStepSize = 1200
            };

            上述常量定義里,kMaximumTimeBetweenFallingEdges 指定了兩個下降沿之間允許的最大時間間隔,超過這個時間,自動波特率程序?qū)G掉前面統(tǒng)計的下降沿個數(shù),重頭開始識別,這個設(shè)計也是為了防止 I/O 上有電平干擾,導(dǎo)致誤識別。

            kAutobaudStepSize 常量是為了對檢測出的波特率值做對齊處理,公式是 rounded = stepSize * (value/stepSize + 0.5),其中 value 是實(shí)際檢測出的波特率值,rounded 是對齊后的波特率值,用對齊后的波特率值能更好地配置UART外設(shè)(這跟UART模塊里波特率發(fā)生器SBR設(shè)計有關(guān))。

            最后就是 I/O 電平下降沿檢測方法設(shè)計,這里既可以用軟件查詢(就是循環(huán)讀取 I/O 輸入電平,比較當(dāng)前值與上一次值的差異),也可以使用GPIO模塊自帶的邊沿中斷功能。推薦使用后者,一方面計時更精確,另外也不用阻塞系統(tǒng)。檢測到下降沿發(fā)生就調(diào)用一次如下 pin_transition_callback() 函數(shù),在這個函數(shù)里統(tǒng)計跳變次數(shù)以及計時。

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

            1.3 主代碼實(shí)現(xiàn)

            根據(jù)上一小節(jié)描述的設(shè)計思想,我們很容易寫出下面的主代碼(autobaud_irq.c),代碼里痞子衡都做了詳細(xì)注釋。有一點(diǎn)要提的是關(guān)于其中系統(tǒng)計時,可參考痞子衡舊文 《嵌入式里通用微秒(microseconds)計時函數(shù)框架設(shè)計與實(shí)現(xiàn)》 。

            //! @brief 使能GPIO管腳中斷
            extern void enable_autobaud_pin_irq(pin_irq_callback_t func);
            //! @brief 關(guān)閉GPIO管腳中斷
            extern void disable_autobaud_pin_irq(void);
            //!< 已檢測到的下降沿個數(shù)
            static uint32_t s_transitionCount;
            //!< 0x5A 字節(jié)檢測期間內(nèi)對應(yīng)計數(shù)值
            static uint64_t s_firstByteTotalTicks;
            //!< 0xA6 字節(jié)檢測期間內(nèi)對應(yīng)計數(shù)值
            static uint64_t s_secondByteTotalTicks;
            //!< 上一次下降沿發(fā)生時系統(tǒng)計數(shù)值
            static uint64_t s_lastToggleTicks;
            //!< 下降沿之間最大超時對應(yīng)計數(shù)值
            static uint64_t s_ticksBetweenFailure;
            void autobaud_init(void)
            {
                s_transitionCount = 0;
                s_firstByteTotalTicks = 0;
                s_secondByteTotalTicks = 0;
                s_lastToggleTicks = 0;
                // 計算出下降沿之間最大超時對應(yīng)計數(shù)值
                s_ticksBetweenFailure = microseconds_convert_to_ticks(kMaximumTimeBetweenFallingEdges);
                // 使能GPIO管腳中斷,并注冊中斷處理回調(diào)函數(shù)
                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)
            {
                // 獲取當(dāng)前系統(tǒng)計數(shù)值
                uint64_t ticks = microseconds_get_ticks();
                // 計數(shù)這次檢測到的下降沿
                s_transitionCount++;
                // 如果本次下降沿與上次下降沿之間間隔過長,則從頭開始檢測
                uint64_t delta = ticks - s_lastToggleTicks;
                if (delta > s_ticksBetweenFailure)
                {
                    s_transitionCount = 1;
                }
                switch (s_transitionCount)
                {
                    case 1:
                        // 0x5A 字節(jié)檢測時間起點(diǎn)
                        s_firstByteTotalTicks = ticks;
                        break;
                    case kFirstByteRequiredFallingEdges:
                        // 得到 0x5A 字節(jié)檢測期間內(nèi)對應(yīng)計數(shù)值
                        s_firstByteTotalTicks = ticks - s_firstByteTotalTicks;
                        break;
                    case (kFirstByteRequiredFallingEdges + 1):
                        // 0xA6 字節(jié)檢測時間起點(diǎn)
                        s_secondByteTotalTicks = ticks;
                        break;
                    case (kFirstByteRequiredFallingEdges + kSecondByteRequiredFallingEdges):
                        // 得到 0xA6 字節(jié)檢測期間內(nèi)對應(yīng)計數(shù)值
                        s_secondByteTotalTicks = ticks - s_secondByteTotalTicks;
                        // 關(guān)閉GPIO管腳中斷
                        disable_autobaud_pin_irq();
                        break;
                }
                // 記錄本次下降沿發(fā)生時系統(tǒng)計數(shù)值
                s_lastToggleTicks = ticks;
            }

            二、串口(UART)自動波特率識別程序?qū)崿F(xiàn)

            前面講的都是硬件無關(guān)設(shè)計,但最終還是要落實(shí)到具體 MCU 平臺上的,其中 GPIO 中斷部分是跟 MCU 緊相關(guān)的。我們以恩智浦 i.MXRT1011 為例來介紹硬件實(shí)現(xiàn)。

            2.1 管腳中斷方式實(shí)現(xiàn)(基于i.MXRT1011)

            恩智浦 MIMXRT1010-EVK 有板載調(diào)試器 DAPLink,這個 DAPLink 中也集成了 USB 轉(zhuǎn)串口的功能,對應(yīng)的 UART 引腳是 IOMUXC_GPIO_09_LPUART1_RXD 和 IOMUXC_GPIO_10_LPUART1_TXD,我們就選用這個管腳 GPIO1[9] 做自動波特率檢測,實(shí)現(xiàn)代碼如下:

            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引腳功能切換函數(shù)
            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中斷處理函數(shù)
            void GPIO1_Combined_0_15_IRQHandler(void)
            {
                uint32_t interrupt_flag = (1U << 9);
                // 僅當(dāng)GPIO1_9中斷發(fā)生時
                if ((GPIO_GetPinsInterruptFlags(GPIO1) & interrupt_flag) && s_pin_irq_func)
                {
                    //執(zhí)行一次回調(diào)函數(shù)
                    s_pin_irq_func();
                    GPIO_ClearPinsInterruptFlags(GPIO1, interrupt_flag);
                }
            }

            2.2 在MIMXRT1010-EVK上實(shí)測

            一切就緒,我們現(xiàn)在來實(shí)測一下,主函數(shù)流程很簡單,測試結(jié)果也表明達(dá)到了預(yù)期效果,每次將 MCU 程序復(fù)位運(yùn)行后,串口調(diào)試助手里可任意設(shè)置波特率。

            int main(void)
            {
                // 略去系統(tǒng)時鐘配置...
                // 初始化定時器
                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外設(shè)
                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);
            }

            2.png

            至此,嵌入式里串口(UART)自動波特率識別程序設(shè)計與實(shí)現(xiàn)痞子衡便介紹完畢了,掌聲在哪里~~~

            *博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點(diǎn),如有侵權(quán)請聯(lián)系工作人員刪除。

            助聽器原理相關(guān)文章:助聽器原理




            關(guān)鍵詞: 嵌入式

            相關(guān)推薦

            技術(shù)專區(qū)

            關(guān)閉