蔡伯峰+王宜懷
摘 要: 匯編編程的復雜性及有關32位ARM Cortex?M0+等內核匯編開發資料和樣例程序的短缺,使編程者學習、研究和開發微處理器匯編程序難度很大。針對這一現狀,在對ARM Cortex?M0+內部寄存器、匯編指令系統等進行深入分析的基礎上,以NXP半導體公司KL系列MCU為藍本,提出一種規范、易用、實用的匯編構件設計編程方法。該方法根據軟件工程構件設計思想,基于構件封裝要點分析和匯編工程框架,設計并實現匯編構件,并給出了典型UART模塊的匯編底層驅動構件的樣例程序。通過對匯編構件設計編程方法的使用和樣例程序的學習與理解,降低嵌入式匯編學習和編程難度,并進而輕松設計其他類似的規范化的匯編構件和程序。
關鍵詞: ARM Cortex?M0+; 匯編構件設計; KL系列MCU; 底層驅動構件; 匯編工程框架; 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架構,支持Thumb指令集和部分Thumb2指令集[1]。NXP半導體公司的KL系列MCU是業內首款采用ARM Cortex?M0+內核的MCU,具有應用設計方便、可擴展性好、超低功耗、品種齊全等特點[2]。目前,對微處理器的應用開發絕大多數編程者使用C語言,構件設計和研究也是針對C語言驅動構件進行的[3?5],這些研究成果并不適合匯編編程,尤其是匯編底層驅動構件設計,因此,匯編資料、樣例程序和相關研究成果非常短缺。匯編編程是嵌入式開發的基本功,掌握好匯編編程可增加編程者的“內力”。學習研究一些組織結構完整、清晰的匯編程序如硬件底層匯編驅動等,對嵌入式開發有很大幫助,也有助于更深層次地理解微處理器軟件的設計。實際上,一些微處理器深層次應用開發如MCU初始化、操作系統調度、快速響應[6]等特殊功能的實現必須使用匯編完成。
本文通過對NXP公司生產的采用ARM Cortex?M0+內核的KL系列MCU進行深入研究,在分析內核特點、內部寄存器及匯編指令系統的基礎上,以提高嵌入式軟件的可重用性和可移植性,降低嵌入式匯編語言學習、開發難度為目標,基于對匯編構件封裝要點分析和筆者研制的匯編工程框架,規范地設計匯編構件,并針對MCU必不可少的典型模塊UART進行具體實現。
1 匯編構件封裝要點分析
在嵌入式軟件領域,由于軟、硬件緊密聯系的特性,使得底層驅動程序開發成為嵌入式軟件開發的重要內容,其好壞直接影響嵌入式系統的穩定性和可靠性。因此,驅動程序開發要按照構件化設計原則進行分析設計,并按照規范的過程實現,以提高其可重用性與可移植性。endprint
匯編底層驅動構件是具有獨立性的功能或函數集合,對其進行封裝要點分析就是要分析出應設計哪些函數及出入口參數。通常可根據MCU各個模塊所具有的基本操作來確定應設計哪些函數。以MCU典型的串口模塊——UART模塊為例,由于它具有初始化、發送和接收三種基本操作,按照構件設計思想,可將其封裝成各個獨立的功能函數,分別為初始化、發送單個字節、接收單個字節。其中,初始化函數用來設定UART模塊工作屬性,發送和接收單個字節函數用來實現實際的通信,但從實際使用出發,還應封裝發送[N]個字節、發送字符串、接收[N]個字節及串口接收中斷使能與禁止等功能函數。
要實現編程的構件化,在分析出各個功能函數后,還要充分設計好函數出入口參數,并提供對外服務接口的使用注釋,因為出入口參數設計的好壞會直接影響構件化編程的成敗。以初始化函數uart_init為例,由于串口可能有多個,每個串口使用的MCGIRCLK,MCGPLLCLK,BUSCLK等時鐘源并不相同[7],收發數據時的波特率也可有多種選擇,所以串口號、時鐘源、波特率都被設計為入口參數,而函數不必有返回值,這樣當應用程序和高層構件在調用時將具有極大的靈活性。
按照uart_init函數設計方法設計的UART構件的所有功能函數見表1。
2 用于匯編構件開發的匯編工程框架
架構清晰、規范易用的匯編工程框架是匯編底層驅動開發的基礎,能極大地降低嵌入式匯編語言學習難度、提高開發效率。筆者經過多年的研究,研制了在主流嵌入式集成開發環境KDS下使用的樹狀結構的匯編工程框架,其文件組織結構見表2。該工程框架為底層驅動構件的開發提供了統一的工程模板,使用它可方便快速地開發底層驅動構件和匯編程序,也方便構件的移植和重用。
通過對匯編框架組織結構的分析可知,使用匯編框架開發驅動構件的步驟為:
1) 在KDS中導入匯編工程框架,以創建匯編底層驅動構件工程項目。
2) 在05_Driver文件夾<05_Driver >中新建以構件名命名的構件文件夾,如
3) 按照提供的gpio構件的頭文件(.inc)和匯編源程序文件(.S)內容布局模板,在新構件文件夾中設計構件頭文件和源程序文件。
4) 在<08_Source>中,按照提供的各個文件的內容布局模板編制測試樣例程序。
5) 工程編譯鏈接后,將目標代碼(.elf)下載到包含有MCU硬件最小系統的目標板上運行測試。
表2 樹狀結構的匯編工程框架
Tab. 2 Assembly engineering framework of tree structure
3 匯編構件頭文件與源程序文件設計
由于對MCU模塊的編程,實際上是對硬件底層寄存器的直接操作,即通過操作相關寄存器對硬件模塊進行操作,因此,可將MCU各個模塊的所有功能函數分別集中放置在以各個模塊名命名的.S源文件中,并按照相對嚴格的構件設計原則封裝,同時配以以各個模塊名命名的.inc頭文件,以定義相應模塊的基本信息和對外接口。這兩種文件分別放置在匯編工程的<05_Driver>文件夾下以模塊名命名的構件文件夾中,如<05_Driver/uart>等。當其他工程需要使用相應構件時,一般只需簡單拷貝構件文件夾中的這兩個文件即可,只有在進行不同芯片間的移植時,才需檢查并修改頭文件中與硬件相關的宏定義(若內核不同,還需修改源文件中的部分匯編指令)。
3.1 UART模塊的編程結構
為了方便理解后文設計實現的匯編底層驅動構件,此處以KL系列MCU的UART模塊為例分析一下其編程模型和使用到的寄存器情況。
UART模塊的主要功能是收發數據,即接收時,將外部單線輸入數據變成一字節并行數據送MCU內部;發送時,將待發送的一字節并行數據轉換為單線輸出[8],因此UART模塊編程時采用的編程模型如圖1所示。其中,UART波特率寄存器用于設置波特率;UART控制寄存器用于設置通信格式、是否校驗和允許中斷等;UART狀態寄存器用于判斷串口收發數據狀態,如數據是否發送出去、是否有數據可收等。而UART的2個數據寄存器分別存放收、發的數據。程序員編程時,直接對數據寄存器操作,再由MCU自動完成對移位寄存器的操作。
KL25系列MCU每個UART模塊都有相對應的8位寄存器,寄存器包括控制類寄存器C1~C5,狀態寄存器S1~S2,波特率寄存器BDH和BDL,數據寄存器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模塊編程相關的各個主要寄存器的功能及寄存器關鍵位[2]見表3。
3.2 頭文件設計
頭文件描述了構件接口,用戶通過頭文件獲取構件服務。一個合格的頭文件應是一份完備、簡明的信息定義和操作使用說明,除包含基本的程序編碼框架、對其他文件的包含語句外,還包含模塊本身及相關寄存器信息的定義、各個功能函數全局聲明與對外服務接口的詳細說明,使用者無需查看源文件就能完全使用該構件。
以UART構件為例,uart.inc包含的部分內容如下:
#---------------------------------------------
#文件名稱:uart.inc
#功能概要:KL25 UART底層驅動構件(匯編)頭文件
#---------------------------------------------
#ifndef UART_INC @編碼框架
#define UART_INC
.include "gpio.inc" @包含外部構件頭文件
.section .rodata @數據定義:各串口的基地址
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掩碼
……
#---------------------------------------------
# 函數名稱:uart_init
# 函數返回:無
#參數說明:r0:((串口號)|(時鐘源KHz)) 例(UART1|BUSCLK)表示UART1、總線時鐘
# r1: 波特率:300、600、1 200…
#功能概要:初始化UART模塊。
#---------------------------------------------
.global uart_init @全局函數聲明
……
#endif
3.3 源程序文件設計
為保證構件工作的獨立性,實現高內聚、低耦合的設計要求,構件的實現內容應封裝在源文件內部。源文件內容包括自身頭文件包含語句、各個功能函數和內部函數的實現代碼。源文件中只允許一處使用“.include xxx”語句,包含自身頭文件,需要包含的內容應在自身頭文件中包含,以便有統一、清晰的程序結構。源文件要給出良好的封裝、簡潔的說明與注釋、清晰的對外接口說明、規范的編程風格等,方便編程者進行學習、研究。限于篇幅,本文僅給出UART構件的uart_send1函數的實現代碼,使用的指令可參考文獻[1,10?11]中關于ARM Cortex?M0+匯編指令集的有關內容。
#---------------------------------------------
#函數名稱:uart_send1
#參數說明:r0:串口號,用0~2表示UART0~2
# r1:待發送的字節
#函數返回:r0:0=正常,1=異常
#功能概要:串行發送1個字節
#---------------------------------------------
uart_send1:
push {r4?r7,lr} @保存現場,將pc(lr)入棧
#初始化循環變量r5和存放各串口基地址的單元地址偏移量
mov r5 ,#0 @r5=0
lsl r0,#2 @r0=r0*4,基地址單元地址偏移量
#在規定時間內(循環一定的次數)輪詢發送緩沖區,一旦為空則將待發字節送數據寄存器D發送,否則退出循環
send1_loop:
ldr r4,=0xFBBB @r4=發送緩沖區是否為空輪詢次數
cmp r5,r4 @判斷循環變量值
bcs send1_exit @達到輪詢次數閾值轉send1_exit
add r5,#1 @當前循環變量加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寄存器內容
uxth r4,r4 @擴展成32位無符號數
ldr r6,=s1_TDRE_mask @r6=S1寄存器的TDRE位掩碼
and r4,r6 @取出S1中TDRE位的值
cmp r4,#0 @判斷發送緩沖區是否為空
beq send1_loop @非空則繼續輪詢,為空則繼續
ldr r6,=UART_D @為空,r6=D寄存器偏移地址
strb r1,[r7,r6] @將待發送字節送D寄存器發送
send1_exit:
#根據是否超時來判斷是否發送成功
ldr r4,=0xFBBB @r4=0xFBBB, 輪詢次數閾值
cmp r5,r4 @比較r5與0xFBBB的大小endprint
bcc send1_suc @小于則發送成功,轉send1_suc
mov r0,#1 @大于等于則發送失敗,r0=1
b send1_end @轉send1_end處理
send1_suc:
mov r0,#0 @發送成功,r0=0
send1_end:
pop {r4?r7,pc} @恢復現場,將lr出棧到pc
在高層構件或應用程序中要使用構件的某個功能函數,需先通過“.include xxx”語句包含構件頭文件,再根據功能函數的出入口參數要求提供參數值,就可通過“bl功能函數名”語句來調用功能函數了。
3.4 匯編構件的測試
測試工程實現功能的是:主程序對UART模塊初始化后,向PC端分別以字符串、單字節、多字節等方式發送數據;開啟UART模塊接收中斷用中斷方式接收PC端發來的數據,接收后回發給PC端。中斷服務例程中,先通過uart_re1函數接收來自PC端的數據,再通過uart_send1函數回發給PC端。
按照前文介紹的“使用匯編框架開發驅動構件的步驟”中所述方法在KDS環境下編制測試工程的相關程序,程序代碼不再贅述。再對測試工程編譯鏈接后,將目標代碼文件(.elf)下載到KL25開發板上,并將開發板的UART接口通過USB?TTL串口線與PC機相連,在PC端運行通用的串口調試器軟件。開發板重新上電后開始測試。
經測試,PC端的串口調試器界面上收到MCU方主程序依次用uart_send_str函數發送的信息“現在測試uart_send_str 函數的功能.”、兩次用uart_send1函數發送的換行符、用uart_sendN函數發送的信息“現在測試uart_sendN 函數的功能.”,當從串口調試器界面上發送信息“這是從本界面發送的信息.”后,立即在界面上收到MCU方回發的信息,如圖2所示。對KL25的3個UART模塊分別進行反復測試。測試結果表明,UART模塊能正確收發數據且運行穩定,說明本文所設計的UART匯編構件正確有效且易于使用。
4 結 語
本文以KL系列MCU為藍本,按照構件化設計原則對匯編底層驅動構件的封裝要點進行詳細的分析,設計了構件的各個功能函數及出入口參數;根據匯編工程框架給出了開發驅動構件的規范步驟和設計實現構件頭文件和源程序文件的方法。在對UART模塊的編程模型、寄存器功能、Cortex?M0+內核指令系統進行深入分析的基礎上,具體實現了UART匯編構件,并對其進行測試。旨在引導讀者通過規范的匯編構件設計編程及樣例程序的學習研究,較快地掌握嵌入式匯編語言的編程方法及在構件設計中的應用,并能自行設計其他匯編構件、編寫匯編程序。
參考文獻
[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底層驅動構件設計[J].福建農林大學學報(自然科學版),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] 胡宗棠,王宜懷.構件化ColdFire系列MCUs通用GPIO驅動設計[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構件研究與實現[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].制造業自動化,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] 王宜懷,朱仕浪,郭蕓.嵌入式技術基礎與實踐:ARM Cortex?M0+ KinetisL系列微控制器[M].3版.北京:清華大學出版社,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