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