蔡伯峰+王宜懷
摘 要: 匯編編程的復(fù)雜性及有關(guān)32位ARM Cortex?M0+等內(nèi)核匯編開發(fā)資料和樣例程序的短缺,使編程者學(xué)習(xí)、研究和開發(fā)微處理器匯編程序難度很大。針對這一現(xiàn)狀,在對ARM Cortex?M0+內(nèi)部寄存器、匯編指令系統(tǒng)等進行深入分析的基礎(chǔ)上,以NXP半導(dǎo)體公司KL系列MCU為藍本,提出一種規(guī)范、易用、實用的匯編構(gòu)件設(shè)計編程方法。該方法根據(jù)軟件工程構(gòu)件設(shè)計思想,基于構(gòu)件封裝要點分析和匯編工程框架,設(shè)計并實現(xiàn)匯編構(gòu)件,并給出了典型UART模塊的匯編底層驅(qū)動構(gòu)件的樣例程序。通過對匯編構(gòu)件設(shè)計編程方法的使用和樣例程序的學(xué)習(xí)與理解,降低嵌入式匯編學(xué)習(xí)和編程難度,并進而輕松設(shè)計其他類似的規(guī)范化的匯編構(gòu)件和程序。
關(guān)鍵詞: ARM Cortex?M0+; 匯編構(gòu)件設(shè)計; KL系列MCU; 底層驅(qū)動構(gòu)件; 匯編工程框架; UART
中圖分類號: TN919?34; TP311 文獻標識碼: A 文章編號: 1004?373X(2018)01?0038?05
Abstract: It is difficult for programmer to learn, study and develop the microprocessor assemble program due to the complexity of assembly programming and the shortage of assembly development data and sample programs of the 32?bit ARM Cortex?M0+, and other kernels. In view of this situation, on the basis of the deep analysis of the ARM Cortex?M0+ internal register and assembly instruction system, the KL Series MCU made by NXP is taken as the exsample to present a standardized, usable and practical design method for assembly component. According to the design thought of software engineering component, analysis of component packaging key points and assembly engineering framework, the assembly component was designed and implemented. The sample program of the assembly bottom?driven component of the typical UART module is given. With application of design and programming methods of the assembly component, and by learning and understanding of the sample programaim, the difficulty of the embedded assembly learning and programming is reduced, and the design of other similar standardized assembly components and programs becomes easy.
Keywords: ARM Cortex?M0+; assembly component design; KL series MCU; bottom?layer driving component; assembly project framework; UART
0 引 言
ARM Cortex?M0+微處理器是ARM公司的一款耗電量僅為9 μA/MHz的全球最低功耗的32位微處理器,性價比高、易用,并具有良好的軟件兼容性,可方便地移植到更高性能的Cortex?M3/M4上。該微處理器基于ARMv6M架構(gòu),支持Thumb指令集和部分Thumb2指令集[1]。NXP半導(dǎo)體公司的KL系列MCU是業(yè)內(nèi)首款采用ARM Cortex?M0+內(nèi)核的MCU,具有應(yīng)用設(shè)計方便、可擴展性好、超低功耗、品種齊全等特點[2]。目前,對微處理器的應(yīng)用開發(fā)絕大多數(shù)編程者使用C語言,構(gòu)件設(shè)計和研究也是針對C語言驅(qū)動構(gòu)件進行的[3?5],這些研究成果并不適合匯編編程,尤其是匯編底層驅(qū)動構(gòu)件設(shè)計,因此,匯編資料、樣例程序和相關(guān)研究成果非常短缺。匯編編程是嵌入式開發(fā)的基本功,掌握好匯編編程可增加編程者的“內(nèi)力”。學(xué)習(xí)研究一些組織結(jié)構(gòu)完整、清晰的匯編程序如硬件底層匯編驅(qū)動等,對嵌入式開發(fā)有很大幫助,也有助于更深層次地理解微處理器軟件的設(shè)計。實際上,一些微處理器深層次應(yīng)用開發(fā)如MCU初始化、操作系統(tǒng)調(diào)度、快速響應(yīng)[6]等特殊功能的實現(xiàn)必須使用匯編完成。
本文通過對NXP公司生產(chǎn)的采用ARM Cortex?M0+內(nèi)核的KL系列MCU進行深入研究,在分析內(nèi)核特點、內(nèi)部寄存器及匯編指令系統(tǒng)的基礎(chǔ)上,以提高嵌入式軟件的可重用性和可移植性,降低嵌入式匯編語言學(xué)習(xí)、開發(fā)難度為目標,基于對匯編構(gòu)件封裝要點分析和筆者研制的匯編工程框架,規(guī)范地設(shè)計匯編構(gòu)件,并針對MCU必不可少的典型模塊UART進行具體實現(xiàn)。
1 匯編構(gòu)件封裝要點分析
在嵌入式軟件領(lǐng)域,由于軟、硬件緊密聯(lián)系的特性,使得底層驅(qū)動程序開發(fā)成為嵌入式軟件開發(fā)的重要內(nèi)容,其好壞直接影響嵌入式系統(tǒng)的穩(wěn)定性和可靠性。因此,驅(qū)動程序開發(fā)要按照構(gòu)件化設(shè)計原則進行分析設(shè)計,并按照規(guī)范的過程實現(xiàn),以提高其可重用性與可移植性。endprint
匯編底層驅(qū)動構(gòu)件是具有獨立性的功能或函數(shù)集合,對其進行封裝要點分析就是要分析出應(yīng)設(shè)計哪些函數(shù)及出入口參數(shù)。通??筛鶕?jù)MCU各個模塊所具有的基本操作來確定應(yīng)設(shè)計哪些函數(shù)。以MCU典型的串口模塊——UART模塊為例,由于它具有初始化、發(fā)送和接收三種基本操作,按照構(gòu)件設(shè)計思想,可將其封裝成各個獨立的功能函數(shù),分別為初始化、發(fā)送單個字節(jié)、接收單個字節(jié)。其中,初始化函數(shù)用來設(shè)定UART模塊工作屬性,發(fā)送和接收單個字節(jié)函數(shù)用來實現(xiàn)實際的通信,但從實際使用出發(fā),還應(yīng)封裝發(fā)送[N]個字節(jié)、發(fā)送字符串、接收[N]個字節(jié)及串口接收中斷使能與禁止等功能函數(shù)。
要實現(xiàn)編程的構(gòu)件化,在分析出各個功能函數(shù)后,還要充分設(shè)計好函數(shù)出入口參數(shù),并提供對外服務(wù)接口的使用注釋,因為出入口參數(shù)設(shè)計的好壞會直接影響構(gòu)件化編程的成敗。以初始化函數(shù)uart_init為例,由于串口可能有多個,每個串口使用的MCGIRCLK,MCGPLLCLK,BUSCLK等時鐘源并不相同[7],收發(fā)數(shù)據(jù)時的波特率也可有多種選擇,所以串口號、時鐘源、波特率都被設(shè)計為入口參數(shù),而函數(shù)不必有返回值,這樣當應(yīng)用程序和高層構(gòu)件在調(diào)用時將具有極大的靈活性。
按照uart_init函數(shù)設(shè)計方法設(shè)計的UART構(gòu)件的所有功能函數(shù)見表1。
2 用于匯編構(gòu)件開發(fā)的匯編工程框架
架構(gòu)清晰、規(guī)范易用的匯編工程框架是匯編底層驅(qū)動開發(fā)的基礎(chǔ),能極大地降低嵌入式匯編語言學(xué)習(xí)難度、提高開發(fā)效率。筆者經(jīng)過多年的研究,研制了在主流嵌入式集成開發(fā)環(huán)境KDS下使用的樹狀結(jié)構(gòu)的匯編工程框架,其文件組織結(jié)構(gòu)見表2。該工程框架為底層驅(qū)動構(gòu)件的開發(fā)提供了統(tǒng)一的工程模板,使用它可方便快速地開發(fā)底層驅(qū)動構(gòu)件和匯編程序,也方便構(gòu)件的移植和重用。
通過對匯編框架組織結(jié)構(gòu)的分析可知,使用匯編框架開發(fā)驅(qū)動構(gòu)件的步驟為:
1) 在KDS中導(dǎo)入?yún)R編工程框架,以創(chuàng)建匯編底層驅(qū)動構(gòu)件工程項目。
2) 在05_Driver文件夾<05_Driver >中新建以構(gòu)件名命名的構(gòu)件文件夾,如
3) 按照提供的gpio構(gòu)件的頭文件(.inc)和匯編源程序文件(.S)內(nèi)容布局模板,在新構(gòu)件文件夾中設(shè)計構(gòu)件頭文件和源程序文件。
4) 在<08_Source>中,按照提供的各個文件的內(nèi)容布局模板編制測試樣例程序。
5) 工程編譯鏈接后,將目標代碼(.elf)下載到包含有MCU硬件最小系統(tǒng)的目標板上運行測試。
表2 樹狀結(jié)構(gòu)的匯編工程框架
Tab. 2 Assembly engineering framework of tree structure
3 匯編構(gòu)件頭文件與源程序文件設(shè)計
由于對MCU模塊的編程,實際上是對硬件底層寄存器的直接操作,即通過操作相關(guān)寄存器對硬件模塊進行操作,因此,可將MCU各個模塊的所有功能函數(shù)分別集中放置在以各個模塊名命名的.S源文件中,并按照相對嚴格的構(gòu)件設(shè)計原則封裝,同時配以以各個模塊名命名的.inc頭文件,以定義相應(yīng)模塊的基本信息和對外接口。這兩種文件分別放置在匯編工程的<05_Driver>文件夾下以模塊名命名的構(gòu)件文件夾中,如<05_Driver/uart>等。當其他工程需要使用相應(yīng)構(gòu)件時,一般只需簡單拷貝構(gòu)件文件夾中的這兩個文件即可,只有在進行不同芯片間的移植時,才需檢查并修改頭文件中與硬件相關(guān)的宏定義(若內(nèi)核不同,還需修改源文件中的部分匯編指令)。
3.1 UART模塊的編程結(jié)構(gòu)
為了方便理解后文設(shè)計實現(xiàn)的匯編底層驅(qū)動構(gòu)件,此處以KL系列MCU的UART模塊為例分析一下其編程模型和使用到的寄存器情況。
UART模塊的主要功能是收發(fā)數(shù)據(jù),即接收時,將外部單線輸入數(shù)據(jù)變成一字節(jié)并行數(shù)據(jù)送MCU內(nèi)部;發(fā)送時,將待發(fā)送的一字節(jié)并行數(shù)據(jù)轉(zhuǎn)換為單線輸出[8],因此UART模塊編程時采用的編程模型如圖1所示。其中,UART波特率寄存器用于設(shè)置波特率;UART控制寄存器用于設(shè)置通信格式、是否校驗和允許中斷等;UART狀態(tài)寄存器用于判斷串口收發(fā)數(shù)據(jù)狀態(tài),如數(shù)據(jù)是否發(fā)送出去、是否有數(shù)據(jù)可收等。而UART的2個數(shù)據(jù)寄存器分別存放收、發(fā)的數(shù)據(jù)。程序員編程時,直接對數(shù)據(jù)寄存器操作,再由MCU自動完成對移位寄存器的操作。
KL25系列MCU每個UART模塊都有相對應(yīng)的8位寄存器,寄存器包括控制類寄存器C1~C5,狀態(tài)寄存器S1~S2,波特率寄存器BDH和BDL,數(shù)據(jù)寄存器D,地址匹配寄存器MA1~MA2等幾種,各個寄存器通過映像地址訪問。
UARTi的寄存器映像地址=0x4006_A000+i*1 000+n*1。其中i=0~2;n表示寄存器號,UART0時,n=0~11分別代表寄存器BDH,BDL,C1,C2,S1,S2,C3,D,MA1,MA2,C4,C5,UART1,2時,n=0~8分別代表寄存器BDH,BDL,C1,C2,S1,S2,C3,D,C4[9]。
與UART模塊編程相關(guān)的各個主要寄存器的功能及寄存器關(guān)鍵位[2]見表3。
3.2 頭文件設(shè)計
頭文件描述了構(gòu)件接口,用戶通過頭文件獲取構(gòu)件服務(wù)。一個合格的頭文件應(yīng)是一份完備、簡明的信息定義和操作使用說明,除包含基本的程序編碼框架、對其他文件的包含語句外,還包含模塊本身及相關(guān)寄存器信息的定義、各個功能函數(shù)全局聲明與對外服務(wù)接口的詳細說明,使用者無需查看源文件就能完全使用該構(gòu)件。
以UART構(gòu)件為例,uart.inc包含的部分內(nèi)容如下:
#---------------------------------------------
#文件名稱:uart.inc
#功能概要:KL25 UART底層驅(qū)動構(gòu)件(匯編)頭文件
#---------------------------------------------
#ifndef UART_INC @編碼框架
#define UART_INC
.include "gpio.inc" @包含外部構(gòu)件頭文件
.section .rodata @數(shù)據(jù)定義:各串口的基地址
UART_BASE_PTR: .word 0x4006A000, 0x4006B000,
0x4006C000
.equ UART0,(0<<16) @宏定義各個串口號
.equ UART1,(1<<16)
.equ UART2,(2<<16)
.equ MCGIRCLK,4000 @宏定義各個時鐘源頻率
.equ MCGPLL,48000
.equ BUSCLK,24000
.equ UART_BDH,0x0 @宏定義各寄存器偏移地址
.equ UART_BDL,0x1
.equ UART_C1,0x2
.equ UART_C2,0x3
.equ UART_S1,0x4
.equ UART_D,0x7
.equ S1_TDRE_mask,0x80 @宏定義S1_TDRE掩碼
……
#---------------------------------------------
# 函數(shù)名稱:uart_init
# 函數(shù)返回:無
#參數(shù)說明:r0:((串口號)|(時鐘源KHz)) 例(UART1|BUSCLK)表示UART1、總線時鐘
# r1: 波特率:300、600、1 200…
#功能概要:初始化UART模塊。
#---------------------------------------------
.global uart_init @全局函數(shù)聲明
……
#endif
3.3 源程序文件設(shè)計
為保證構(gòu)件工作的獨立性,實現(xiàn)高內(nèi)聚、低耦合的設(shè)計要求,構(gòu)件的實現(xiàn)內(nèi)容應(yīng)封裝在源文件內(nèi)部。源文件內(nèi)容包括自身頭文件包含語句、各個功能函數(shù)和內(nèi)部函數(shù)的實現(xiàn)代碼。源文件中只允許一處使用“.include xxx”語句,包含自身頭文件,需要包含的內(nèi)容應(yīng)在自身頭文件中包含,以便有統(tǒng)一、清晰的程序結(jié)構(gòu)。源文件要給出良好的封裝、簡潔的說明與注釋、清晰的對外接口說明、規(guī)范的編程風(fēng)格等,方便編程者進行學(xué)習(xí)、研究。限于篇幅,本文僅給出UART構(gòu)件的uart_send1函數(shù)的實現(xiàn)代碼,使用的指令可參考文獻[1,10?11]中關(guān)于ARM Cortex?M0+匯編指令集的有關(guān)內(nèi)容。
#---------------------------------------------
#函數(shù)名稱:uart_send1
#參數(shù)說明:r0:串口號,用0~2表示UART0~2
# r1:待發(fā)送的字節(jié)
#函數(shù)返回:r0:0=正常,1=異常
#功能概要:串行發(fā)送1個字節(jié)
#---------------------------------------------
uart_send1:
push {r4?r7,lr} @保存現(xiàn)場,將pc(lr)入棧
#初始化循環(huán)變量r5和存放各串口基地址的單元地址偏移量
mov r5 ,#0 @r5=0
lsl r0,#2 @r0=r0*4,基地址單元地址偏移量
#在規(guī)定時間內(nèi)(循環(huán)一定的次數(shù))輪詢發(fā)送緩沖區(qū),一旦為空則將待發(fā)字節(jié)送數(shù)據(jù)寄存器D發(fā)送,否則退出循環(huán)
send1_loop:
ldr r4,=0xFBBB @r4=發(fā)送緩沖區(qū)是否為空輪詢次數(shù)
cmp r5,r4 @判斷循環(huán)變量值
bcs send1_exit @達到輪詢次數(shù)閾值轉(zhuǎn)send1_exit
add r5,#1 @當前循環(huán)變量加1
ldr r7,=UART_BASE_PTR @r7=所有UART的基地址
ldr r7,[r7,r0] @r7=特定UART的基地址
ldr r6,=UART_S1 @r6=S1寄存器偏移地址
ldrb r4,[r7,r6] @r4=S1寄存器內(nèi)容
uxth r4,r4 @擴展成32位無符號數(shù)
ldr r6,=s1_TDRE_mask @r6=S1寄存器的TDRE位掩碼
and r4,r6 @取出S1中TDRE位的值
cmp r4,#0 @判斷發(fā)送緩沖區(qū)是否為空
beq send1_loop @非空則繼續(xù)輪詢,為空則繼續(xù)
ldr r6,=UART_D @為空,r6=D寄存器偏移地址
strb r1,[r7,r6] @將待發(fā)送字節(jié)送D寄存器發(fā)送
send1_exit:
#根據(jù)是否超時來判斷是否發(fā)送成功
ldr r4,=0xFBBB @r4=0xFBBB, 輪詢次數(shù)閾值
cmp r5,r4 @比較r5與0xFBBB的大小endprint
bcc send1_suc @小于則發(fā)送成功,轉(zhuǎn)send1_suc
mov r0,#1 @大于等于則發(fā)送失敗,r0=1
b send1_end @轉(zhuǎn)send1_end處理
send1_suc:
mov r0,#0 @發(fā)送成功,r0=0
send1_end:
pop {r4?r7,pc} @恢復(fù)現(xiàn)場,將lr出棧到pc
在高層構(gòu)件或應(yīng)用程序中要使用構(gòu)件的某個功能函數(shù),需先通過“.include xxx”語句包含構(gòu)件頭文件,再根據(jù)功能函數(shù)的出入口參數(shù)要求提供參數(shù)值,就可通過“bl功能函數(shù)名”語句來調(diào)用功能函數(shù)了。
3.4 匯編構(gòu)件的測試
測試工程實現(xiàn)功能的是:主程序?qū)ART模塊初始化后,向PC端分別以字符串、單字節(jié)、多字節(jié)等方式發(fā)送數(shù)據(jù);開啟UART模塊接收中斷用中斷方式接收PC端發(fā)來的數(shù)據(jù),接收后回發(fā)給PC端。中斷服務(wù)例程中,先通過uart_re1函數(shù)接收來自PC端的數(shù)據(jù),再通過uart_send1函數(shù)回發(fā)給PC端。
按照前文介紹的“使用匯編框架開發(fā)驅(qū)動構(gòu)件的步驟”中所述方法在KDS環(huán)境下編制測試工程的相關(guān)程序,程序代碼不再贅述。再對測試工程編譯鏈接后,將目標代碼文件(.elf)下載到KL25開發(fā)板上,并將開發(fā)板的UART接口通過USB?TTL串口線與PC機相連,在PC端運行通用的串口調(diào)試器軟件。開發(fā)板重新上電后開始測試。
經(jīng)測試,PC端的串口調(diào)試器界面上收到MCU方主程序依次用uart_send_str函數(shù)發(fā)送的信息“現(xiàn)在測試uart_send_str 函數(shù)的功能.”、兩次用uart_send1函數(shù)發(fā)送的換行符、用uart_sendN函數(shù)發(fā)送的信息“現(xiàn)在測試uart_sendN 函數(shù)的功能.”,當從串口調(diào)試器界面上發(fā)送信息“這是從本界面發(fā)送的信息.”后,立即在界面上收到MCU方回發(fā)的信息,如圖2所示。對KL25的3個UART模塊分別進行反復(fù)測試。測試結(jié)果表明,UART模塊能正確收發(fā)數(shù)據(jù)且運行穩(wěn)定,說明本文所設(shè)計的UART匯編構(gòu)件正確有效且易于使用。
4 結(jié) 語
本文以KL系列MCU為藍本,按照構(gòu)件化設(shè)計原則對匯編底層驅(qū)動構(gòu)件的封裝要點進行詳細的分析,設(shè)計了構(gòu)件的各個功能函數(shù)及出入口參數(shù);根據(jù)匯編工程框架給出了開發(fā)驅(qū)動構(gòu)件的規(guī)范步驟和設(shè)計實現(xiàn)構(gòu)件頭文件和源程序文件的方法。在對UART模塊的編程模型、寄存器功能、Cortex?M0+內(nèi)核指令系統(tǒng)進行深入分析的基礎(chǔ)上,具體實現(xiàn)了UART匯編構(gòu)件,并對其進行測試。旨在引導(dǎo)讀者通過規(guī)范的匯編構(gòu)件設(shè)計編程及樣例程序的學(xué)習(xí)研究,較快地掌握嵌入式匯編語言的編程方法及在構(gòu)件設(shè)計中的應(yīng)用,并能自行設(shè)計其他匯編構(gòu)件、編寫匯編程序。
參考文獻
[1] ARM. Cortex?M0+ processor technical reference manual [DB/OL]. (2012?12?16) [2016?06?20]. http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0432c/index.html.
[2] NXP. KL25 sub?family reference manual [EB/OL]. (2012?09?01) [2016?06?20]. http://www.nxp.com/.
[3] 蔡劍卿,王宜懷,馮德旺,等.可移植的UART底層驅(qū)動構(gòu)件設(shè)計[J].福建農(nóng)林大學(xué)學(xué)報(自然科學(xué)版),2014,43(3):332?335.
CAI Jianqing, WANG Yihuai, FENG Dewang, et al. Design of UART′s bottom?layer driving component with good portability [J]. Journal of Fujian Agricultural and Forestry University, 2014, 43(3): 332?335.
[4] 胡宗棠,王宜懷.構(gòu)件化ColdFire系列MCUs通用GPIO驅(qū)動設(shè)計[J].微計算機信息,2012,28(4):69?71.
HU Zongtang, WANG Yihuai. Component?oriented general GPIO driver design of ColdFire series MCUs [J]. Microcomputer information, 2012, 28(4): 69?71.
[5] 楊炯,曹金華,王宜懷.基于KL25的UART通信UHM構(gòu)件研究與實現(xiàn)[J].實驗室研究與探索,2014,33(9):122?126.
YANG Jiong, CAO Jinhua, WANG Yihuai. Research and rea?lization of UHM unit for UART based on KL25 [J]. Laboratory research and exploration, 2014, 33(9): 122?126.
[6] 凌藝春,黃飛.匯編程序移植性的研究與實踐[J].制造業(yè)自動化,2011,33(3):174?175.
LING Yichun, HUANG Fei. Research and practice of assembler portability [J]. Manufacturing automation, 2011, 33(3): 174?175.
[7] NXP. Kinetis KL25 sub?family data sheet [EB/OL]. (2014?08?01) [2016?06?20]. http://www.docin.com/p?2035480854.html.
[8] GIOVANI G, SEBASTIAN F. Tracing and recording interrupts in embedded software [J]. Journal of systems architecture, 2012, 58(9): 372?385.
[9] 王宜懷,朱仕浪,郭蕓.嵌入式技術(shù)基礎(chǔ)與實踐:ARM Cortex?M0+ KinetisL系列微控制器[M].3版.北京:清華大學(xué)出版社,2013:48?51.
WANG Yihuai, ZHU Shilang, GUO Yun. Embedded technology foundation and practice: ARM Cortex?M0+ KinetisL series microcontrollers [M]. 3rd ed. Beijing: Tsinghua University Press, 2013: 48?51.
[10] ARM. Cortex?M0+ devices generic user guide [EB/OL]. (2012?12?18) [2016?06?20]. https://wenku.baidu.com/view/f26247f2?f61fb7360b4c65a9.html.
[11] NXP. Kinetis assembler reference manual [EB/OL]. (2014?02?01) [2016?06?20]. http://www.nxp.com/.endprint