<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      痞子衡嵌入式:嵌入式里串口(UART)自動波特率識別程序設計與實現(中斷)


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

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

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

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

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

      一、串口(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% 的誤差內數據傳輸均可以正常進行。

        雖然我們不需要約定上位機波特率,但是要想實現波特率自動識別,上位機初始傳輸的數據卻必須要事先約定好(可理解為接頭暗號),這涉及到 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)的標準流程》):

      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)自動波特率識別程序設計與實現痞子衡便介紹完畢了,掌聲在哪里~~~

      歡迎訂閱

      文章會同時發布到我的 博客園主頁CSDN主頁知乎主頁微信公眾號 平臺上。

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

      posted @ 2021-06-12 11:21  痞子衡  閱讀(3084)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品一线天粉嫩av| 亚洲欧美中文字幕日韩一区二区| 国产精品午夜福利在线观看| 香蕉久久久久久av成人| 亚洲欧美一区二区成人片| 少妇人妻偷人精品无码视频| 绝顶丰满少妇av无码| 国产AV国片精品有毛| 亚洲综合色在线视频WWW| 无为县| 九九热精品在线视频免费| 国产欲女高潮正在播放| 师宗县| 乱码中文字幕| 亚洲国产精久久久久久久春色| 精品无套挺进少妇内谢| 亚洲爆乳少妇无码激情| 好看的国产精品自拍视频| 日日摸夜夜添夜夜添国产三级| 丁香婷婷综合激情五月色| 江达县| 久久这里有精品国产电影网| 国产精品任我爽爆在线播放6080| 国产成人精品中文字幕| 国产精品久久久天天影视香蕉| 最近2019免费中文字幕8| 日本高清视频网站www| 欧美日本在线一区二区三区| 亚洲AV福利天堂在线观看| 国产精品无码a∨麻豆| 午夜福利你懂的在线观看| 久久久综合九色合综| 亚洲精品一区二区三区综合| 丰满少妇被猛烈进出69影院| 国产精品福利自产拍久久| 干老熟女干老穴干老女人| 在线日韩日本国产亚洲| 国产精品乱码一区二区三| 乌克兰丰满女人a级毛片右手影院| 蜜桃无码一区二区三区| 九九热爱视频精品视频|