石 棟
(昆明學(xué)院 信息工程學(xué)院,云南 昆明 650214)
串口是嵌入式系統(tǒng)開發(fā)中微控制器(MCU)間或微控制器與上位機(jī)之間最基本的通信手段.隨著物聯(lián)網(wǎng)技術(shù)的不斷發(fā)展,MCU的功能越來越強(qiáng)大.但考慮到成本問題,MCU生產(chǎn)商在MCU上僅會配置1~3個UART,以至于在嵌入式系統(tǒng)的開發(fā)過程中如果需要較多的串口通信時,就必須采用擴(kuò)展串口的方式來實(shí)現(xiàn).
目前,相關(guān)文獻(xiàn)報(bào)道的擴(kuò)展串口方法主要有3種基本方案:1)利用硬件擴(kuò)展多串口通信[1].該方式是利用數(shù)字控制的模擬數(shù)據(jù)選擇/分配器,或利用串口的擴(kuò)展芯片來擴(kuò)展串口.2)利用MCU定時器的定時功能實(shí)現(xiàn)軟件模擬串口.該類型的設(shè)計(jì)又分為兩種,一種是利用MCU上的定時器進(jìn)行延時,從而用模擬的方法實(shí)現(xiàn)串口功能[2-5],另一種是使用定時器的輸出比較、輸入捕獲的功能實(shí)現(xiàn)UART功能[6].3)利用軟件空循環(huán)的方式實(shí)現(xiàn)模擬串口[7].這種方法使用for空循環(huán)語句進(jìn)行延時,以實(shí)現(xiàn)控制串口通信每一位的持續(xù)時間.
在現(xiàn)有方案中,方案1的擴(kuò)展方法需增加硬件成本;方案2需占用MCU定時器資源;方案3無法實(shí)現(xiàn)多時鐘頻率、多波特率下正常工作,可移植性差.因此,本文通過分析串口波特率、MCU內(nèi)核時鐘頻率和延時函數(shù)之間的數(shù)學(xué)關(guān)系,借此給出準(zhǔn)確延時的計(jì)算方案,從而在不增加硬件資源、不占用MCU定時器資源的情況下,利用純軟件方法實(shí)現(xiàn)可以在多內(nèi)核時鐘頻率、多波特率下正常工作,且具備可移植、可復(fù)用的GPIO模擬串口構(gòu)件設(shè)計(jì).同時鑒于目前文獻(xiàn)中尚未解決用軟件方法實(shí)現(xiàn)串口功能時代碼時間開銷的測算問題,或只能粗略估算代碼時間開銷、僅能實(shí)現(xiàn)少量個字符接收的問題[7],提出一種較為準(zhǔn)確的調(diào)試方法,從而較好地解決模擬串口的測試問題.
常見串口的基本要素包含串行通信數(shù)據(jù)格式、波特率和奇偶校驗(yàn).圖1[8]給出了8位數(shù)據(jù)、無奇偶校驗(yàn)情況的傳送格式.
波特率是串口每秒內(nèi)傳送的位數(shù),其倒數(shù)就是發(fā)送一位的位長,也稱為位的持續(xù)時間.而奇偶校驗(yàn)分為奇校驗(yàn)和偶校驗(yàn),在使用時其僅能驗(yàn)證字符中含“1”的位數(shù)是奇數(shù)個還是偶數(shù)個,校驗(yàn)功能有限,實(shí)際應(yīng)用中多不使用.因此本設(shè)計(jì)不考慮該設(shè)計(jì)方法.
要實(shí)現(xiàn)通過GPIO模擬串口與上位機(jī)等其他設(shè)備的通信,模擬串口要具備與普通串口相同的異步串口通信格式、波特率等.從圖1可以看出,每發(fā)送(接收)一位都要持續(xù)一定的時間長度,即位長.一方面位長是波特率fbaud的倒數(shù),另一方面位長還等于位長的時鐘周期數(shù)與MCU內(nèi)核時鐘周期(內(nèi)核時鐘頻率fCPU的倒數(shù))的乘積.如果使用軟件延時的方法實(shí)現(xiàn)位長,則軟件延時所需時鐘周期數(shù)就應(yīng)該等于位長的時鐘周期數(shù).那么軟件延時數(shù)N就應(yīng)該與波特率fbaud和MCU內(nèi)核時鐘頻率fCPU間存在數(shù)學(xué)關(guān)系N=f(fbaud,fCPU),找到這一關(guān)系后,就可以用軟件的方式實(shí)現(xiàn)串口位長的精確延時,同時利用該數(shù)學(xué)關(guān)系還可實(shí)現(xiàn)GPIO模擬串口的可復(fù)用與可移植.
本文采用delay()函數(shù)來實(shí)現(xiàn)位延時:
void delay(uint_32 N)
{
uint_32 i;
for(i= 0;i _asm(“NOP”); } 其中,“NOP”是匯編指令,稱為空指令.該指令的功能是無操作,通常用作指令對齊或延時[9].如果能測算出當(dāng)N=1,2,3,…時,delay函數(shù)執(zhí)行所需要的時鐘周期數(shù),并找到其入口參數(shù)N與時鐘周期數(shù)的關(guān)系,就可以利用delay函數(shù)實(shí)現(xiàn)模擬串口的位延時. delay()函數(shù)執(zhí)行的時鐘周期數(shù)可借助定時器測量:將定時器的工作時鐘頻率設(shè)定為MCU的內(nèi)核時鐘頻率,記錄下delay()函數(shù)開始執(zhí)行時和執(zhí)行結(jié)束時定時器的計(jì)數(shù)值,就可以計(jì)算出delay()函數(shù)執(zhí)行的時鐘周期數(shù).由于Cortex-M系列處理器包含一個簡單時鐘定時器SysTick[10].因此,本文利用ARM公司的Cortex-M0+系列MCU中的SysTick定時器進(jìn)行說明,測試方法如下: ……//初始化并使能SysTick定時器. start_ticks=SysTick->VAL;//保存delay()函數(shù)開始時定時器計(jì)數(shù)值. delay(N);//delay()函數(shù)執(zhí)行. end_ticks=SysTick->VAL;//保存delay()函數(shù)結(jié)束時定時器計(jì)數(shù)值. ……//計(jì)算輸出語句或函數(shù)執(zhí)行的時鐘周期數(shù). 在NXP公司基于ARM Cortex-M0+內(nèi)核的KL25上,測量出delay()函數(shù)入口參數(shù)N與函數(shù)執(zhí)行所需時鐘周期數(shù)(Ntick)的關(guān)系,如表1所示. 表1 delay()函數(shù)入口參數(shù)與時鐘周期數(shù)的關(guān)系 從表1可以看出,當(dāng)delay()函數(shù)入口參數(shù)為1時,執(zhí)行delay()函數(shù)需要的時鐘周期數(shù)是55次,隨后delay()函數(shù)的入口參數(shù)每增加1,delay()函數(shù)執(zhí)行需要的時鐘周期數(shù)就增加13次.由此不難得出,delay()函數(shù)的入口參數(shù)(N)與執(zhí)行時鐘周期數(shù)(Ntick)的關(guān)系為: Ntick=13(N-1)+55,(N>0), (1) (1)式中的常數(shù)13和55除決定于MCU的CPU系列外,還與使用的編譯器環(huán)境有關(guān).本文是在NXP的KDS環(huán)境下使用GNU編譯器實(shí)現(xiàn)的.在不同系列的MCU和不同的編譯環(huán)境下可將公式抽象為: Ntick=A(N-1)+B,(N>0), (2) 其中A和B為兩個待定常數(shù),可在具體的MCU和編譯環(huán)境中測量確定.本文以KL25在GNU編譯環(huán)境下進(jìn)行,則A=13,B=55. 串口的波特率fbaud代表了串口 1 s 能夠傳輸?shù)奈?Bit)數(shù),其倒數(shù)就是串口傳輸一個二進(jìn)制位(1 Bit)的位長.那么,串口傳輸一個二進(jìn)制位(1 Bit)所需的時鐘周期數(shù)就應(yīng)該等于串口傳輸一個二進(jìn)制位(1 Bit)所持續(xù)的時間與內(nèi)核時鐘周期的比值,也就是內(nèi)核時鐘頻率fCPU與波特率fbaud的比值,即: (3) 用delay()函數(shù)來實(shí)現(xiàn)模擬串口的位延時,則delay()函數(shù)執(zhí)行延時所需的時鐘周期數(shù)應(yīng)等于串口傳輸1 Bit的時鐘周期數(shù).即: Nticks=Nbit. (4) 將公式(1)和公式(3)帶入公式(4),可得: (5) 從公式(5)可以得出,只要確定MCU的內(nèi)核時鐘頻率以及串口的波特率,就可以確定delay()函數(shù)的入口參數(shù),因此就可用delay()函數(shù)在GPIO模擬串口實(shí)現(xiàn)位延時.即使內(nèi)核時鐘頻率改變或波特率改變,也可用公式(5)計(jì)算出新入口參數(shù)N. 為delay()函數(shù)找到準(zhǔn)確的入口參數(shù)后,就可以編程實(shí)現(xiàn)模擬串口的功能.本文采用蘇州大學(xué)嵌入式實(shí)驗(yàn)中心為KL25開發(fā)的底層驅(qū)動,并選用KL25開發(fā)板,在NXP公司提供的KDS軟件環(huán)境中實(shí)現(xiàn)模擬串口功能.為實(shí)現(xiàn)可移植與可復(fù)用的目的,工程采用構(gòu)件化的思想將模擬串口的功能進(jìn)行封裝,包含iouart.h和iouart.c文件.iouart.h是模擬串口構(gòu)件的頭文件,其內(nèi)容包含聲明保存延時函數(shù)入口參數(shù)的數(shù)組變量,以及模擬串口號的宏定義和函數(shù)聲明.iouart.c提供模擬串口的具體實(shí)現(xiàn).這里僅就模擬串口的初始化、發(fā)送和接收功能進(jìn)行描述,將這3個功能封裝成3個構(gòu)件:初始化函數(shù)iouart_init();發(fā)送一字節(jié)函數(shù)iouart_send1();接收一字節(jié)函數(shù)iouart_re1(). 將GPIO模擬串口的初始化功能封裝成構(gòu)件:iouart_init(uint_8 uartNo,uint_32 baud),參數(shù)uartNo和baud分別代表模擬串口編號和待用的波特率.該構(gòu)件需完成3個任務(wù):1)在MCU上選擇的2個空閑I/O引腳,將其復(fù)用為GPIO功能.2)將發(fā)送功能引腳的GPIO功能定義為輸出,并令其輸出“1”,即輸出空閑電平;將用作接收功能引腳的GPIO功能定義為輸入.3)將MCU的內(nèi)核時鐘頻率(已知值)和選定的波特率代入公式(5)中計(jì)算delay()函數(shù)的入口參數(shù),并將計(jì)算出來的入口參數(shù)結(jié)果放入到一個全局變量中供delay()函數(shù)使用.基本程序如下: void iouart_init(uint_8 uartNo,uint_32 baud) { ……//串口號解析及局部變量的聲明. gpio_init(Uport[ioN],1,1);//初始化TX引腳:GPIO功能、輸出、高電平. gpio_init(Uport[ioN+1],0,1);//初始化RX引腳:GPIO功能、輸入. //計(jì)算特定波特率下“NOP”指令執(zhí)行的延時次數(shù),保存到全局變量nop_ticks[]中供延時函數(shù)使用. nop_ticks[nopN]= ((SystemCoreClock*1.0/baud)-55.0)/13.0+1; } 代碼中,nop_ticks[]是保存delay()函數(shù)入口參數(shù)的數(shù)組,SystemCoreClock用于保存系統(tǒng)時鐘頻率,由MCU在啟動時配置好時鐘頻率后賦值.模擬串口引腳號放置于Uport[]數(shù)組中,在“iouart.c”文件中編寫. GPIO模擬串口構(gòu)件發(fā)送功能封裝為構(gòu)件:iouart_send1(uint_8uartNo, uint_32 ch).設(shè)計(jì)只需嚴(yán)格按照圖1所示的串行通信數(shù)據(jù)格式設(shè)計(jì)即可.構(gòu)件函數(shù)基本程序如下: void iouart_send1(uint_8 uartNo, uint_8 ch) { ……//函數(shù)初始化. gpio_set(Uport[ioN],0);//發(fā)送起始位. delay(nop_ticks[nopN]);//延時一位位長. //發(fā)送8位數(shù)據(jù). for(i=0;i<8;i++) { j= ((ch>>i)&0x01);//獲取第i位狀態(tài). gpio_set(Uport[ioN],j);//發(fā)送1位. delay(nop_ticks[nopN]);//數(shù)據(jù)位延時. } gpio_set(Uport[ioN],1);//發(fā)送停止位. delay(nop_ticks[nopN]);//停止位延時. } GPIO模擬串口構(gòu)件接收功能封裝成構(gòu)件:iouart_re1(uint_8uartNo).設(shè)計(jì)按照圖1所示的串行通信數(shù)據(jù)格式.但與發(fā)送功能比較,有3點(diǎn)不同:1)將開始位持續(xù)時間延長為原來的1.5倍,保證可以在每一個數(shù)據(jù)位的中間位置讀取接收數(shù)據(jù),避免在電平變換時刻錯誤讀取接收引腳的數(shù)值.2)在接收數(shù)據(jù)位的每一位時采用連續(xù)接收3次的策略,實(shí)現(xiàn)濾波功能,避免因信號波動導(dǎo)致讀數(shù)錯誤.3)接收到停止位后不做位延時而直接返回?cái)?shù)據(jù),保證模擬串口接收連續(xù)字符時有充裕的處理時間.uint_8 iouart_re1(uint_8 uartNo)函數(shù)的核心代碼為: uint_8 iouart_re1(uint_8 uartNo) { ……//利用模擬串口號解析出RX引腳號及delay入口參數(shù). k=gpio_get(Uport[ioN]);//獲取起始位. delay(nop_ticks[nopN]); for(i=0;i<8;i++)//讀8位數(shù)據(jù). { //連讀3次濾波,同時可減少由于延時不準(zhǔn)確的影響. k1=gpio_get(Uport[ioN]); _asm(“NOP”); k2=gpio_get(Uport[ioN]); _asm(“NOP”); k3=gpio_get(Uport[ioN]); delay(nop_ticks[nopN]); j=0; //挑選3次讀中2次相同的值為最終值. k1+=(k2+k3); if(k1>=2) j=1; dat|=((j)< } k=gpio_get(Uport[ioN]);//讀停止位. gpio_clear_int(Uport[ioN]);//清除ISF中斷標(biāo)志. return dat } 實(shí)現(xiàn)了發(fā)送、接收功能的基本編程后,由于程序語句執(zhí)行、子函數(shù)調(diào)用都需要時間,就會導(dǎo)致模擬串口在發(fā)送接收數(shù)據(jù)時的時序出現(xiàn)問題.因此還需要對構(gòu)件進(jìn)行調(diào)試方能正常使用.為使GPIO模擬串口能夠正常工作在多種內(nèi)核時鐘頻率下,且能夠使用多種波特率,調(diào)試最好選在低內(nèi)核時鐘頻率、高波特率下進(jìn)行. 3.4.1 發(fā)送構(gòu)件的調(diào)試 由于本構(gòu)件的位延時可以做到非常準(zhǔn)確,因此發(fā)送時序的開始位、停止位可以少調(diào)試甚至不調(diào)試,僅需調(diào)試數(shù)據(jù)位的發(fā)送時序.下面以數(shù)據(jù)位的調(diào)試簡要說明其調(diào)試方法:1)根據(jù)使用的內(nèi)核時鐘頻率和波特率,計(jì)算出位長的理論時鐘周期數(shù)Ntick(公式(3)).2)利用SysTick定時器測量發(fā)送構(gòu)件iouart_send1()發(fā)送1個字節(jié)所用的時鐘周期數(shù)10Nreal(Nreal是發(fā)送1位所需的周期數(shù),發(fā)送1個字節(jié)包含1個開始位、1個結(jié)束位、8個數(shù)據(jù)位),這樣可以得到實(shí)際位長的時鐘周期數(shù)Nreal.然后比較Ndata與Ntick,其差值ΔN=Nreal-Ntick就是由于數(shù)據(jù)位語句執(zhí)行消耗的時鐘周期數(shù),這樣數(shù)據(jù)位的位延時就應(yīng)該減少△N/13次,則數(shù)據(jù)位delay()函數(shù)的入口參數(shù)為N-△N/13,至此完成數(shù)據(jù)位的調(diào)試. 3.4.2 接收構(gòu)件的調(diào)試 本文模擬串口采用GPIO中斷實(shí)現(xiàn)接收功能,則接收功能的調(diào)試有兩個位置:開始位和數(shù)據(jù)位.調(diào)試開始位時,定時器SysTick應(yīng)從進(jìn)入GPIO中斷服務(wù)例程時開始計(jì)時,到iouart_re1()函數(shù)開始位延時后計(jì)時結(jié)束,此時定時器測得的計(jì)數(shù)值就是開始位的實(shí)際位長Nreal,之后處理方法與發(fā)送功能的數(shù)據(jù)位調(diào)試方法基本一致.而數(shù)據(jù)位的調(diào)試與發(fā)送功能的數(shù)據(jù)位調(diào)試基本一致,此處不再詳述. 為實(shí)現(xiàn)GPIO模擬串口的通信功能,本文使用蘇州大學(xué)NXP嵌入式實(shí)驗(yàn)中心的KL25開發(fā)板,在KDS3.0環(huán)境下利用KL25工程框架進(jìn)行測試.測試中本文設(shè)計(jì)的場景是,從上位機(jī)發(fā)送“0x01~0xFF”共255個字符,GPIO模擬串口接收到字符后再回發(fā)到上位機(jī).接收回發(fā)功能在中斷中實(shí)現(xiàn). 測試結(jié)果如圖2所示.該圖是在KL25內(nèi)核時鐘頻率為48 MHz、波特率為9 600 bps情況下的測試結(jié)果.經(jīng)過改變系統(tǒng)內(nèi)核時鐘頻率和波特率進(jìn)行反復(fù)測試的結(jié)果表明,本構(gòu)件在內(nèi)核時鐘頻率分別為24,48 MHz,波特率可以在300,600,1 200,2 400,4 800,9 600,1 440,19 200,38 400 bps下正常工作.若將其移植到NXP基于Cortex-M4F的K64MCU上,只需重新確定公式(2)的待定參數(shù)A和B,模擬串口構(gòu)件就可在K64上正常工作,從而實(shí)現(xiàn)了基于GPIO模擬串口的通用性和可復(fù)用性. 本文從計(jì)算位長的時鐘周期數(shù)出發(fā),測算了delay函數(shù)的入口參數(shù)N及MCU內(nèi)核時鐘頻率、串口波特率三者間的關(guān)系,提出位延時的數(shù)學(xué)模型N=f(fbaud,fCPU), 并根據(jù)該模型設(shè)計(jì)并實(shí)現(xiàn)了GPIO模擬串口構(gòu)件.利用此方法設(shè)計(jì)的GPIO模擬串口構(gòu)件可在不同的內(nèi)核時鐘頻率、不同的波特率下,實(shí)現(xiàn)GPIO模擬串口的可移植和可復(fù)用.同時針對目前文獻(xiàn)尚未報(bào)道如何很好地解決模擬串口調(diào)試方法的問題,提出利用定時器實(shí)現(xiàn)GPIO模擬串口發(fā)送、接收功能的調(diào)試.此外,本設(shè)計(jì)方法可以使GPIO模擬串口具備較好的工作性能.2.1 delay()執(zhí)行周期數(shù)的測量
2.2 串口波特率與MCU內(nèi)核時鐘頻率的關(guān)系
2.3 delay()函數(shù)延時次數(shù)的公式推導(dǎo)
3 可移植與可復(fù)用GPIO模擬串口構(gòu)件設(shè)計(jì)
3.1 GPIO模擬串口構(gòu)件初始化設(shè)計(jì)
3.2 GPIO模擬串口構(gòu)件發(fā)送功能設(shè)計(jì)
3.3 GPIO模擬串口構(gòu)件接收功能設(shè)計(jì)
3.4 GPIO模擬串口構(gòu)件的調(diào)試方法
4 測試與分析
5 結(jié)語