基于Cortex-A9 LED匯編、C語言驅(qū)動編寫
0. 前言
一般我們購買一個開發(fā)板,廠家都會給出對應(yīng)的電路圖文件,我們可以通過搜索對應(yīng)名稱來查找到對應(yīng)的外設(shè)。對于驅(qū)動工程師來說,我們只需要知道外設(shè)與SOC交互的一些數(shù)據(jù)線和信號線即可。
用主控芯片控制這些外設(shè)的一般步驟:
- 看電路原理圖,弄明白主控芯片和外設(shè)是怎么連接的,對于驅(qū)動工程師來說,主要是看外設(shè)的一些clk、數(shù)據(jù)引腳、控制引腳是如何連接的;
- 外設(shè)一般都會連接到SOC的1個或者多個控制器上,比如i2c、spi、gpio等,有的是數(shù)據(jù)線有的是信號線,中斷線等;
- 根據(jù)電路連接和需求對主控芯片進行設(shè)置,往往對外設(shè)的設(shè)置都是通過寄存器操作實現(xiàn);
- 書寫相應(yīng)代碼,實現(xiàn)功能,不同類型的外設(shè),代碼結(jié)構(gòu)也不盡相同,比如按鍵,我們既可以通過輪詢方式讀取按鍵信息,也可以通過中斷方式來讀取。
下面我們就以華清遠見的fs4412開發(fā)板為例來看如何編寫led的裸機程序。SOC exynos 4412 datahseet 下載地址:
https://download.csdn.net/download/daocaokafei/12533438
一、LED燈電路圖
首先看下led電路圖:
LED電路圖
- 該板子有4個LED,是發(fā)光二極管,有電流是為藍色;
- led都接了上拉電阻;
- 三極管的基極接了SOC的某個GPIO引腳;
- 比如GPX1_0,當該引腳為高電平是,三極管pn結(jié)導(dǎo)通,于是LED3兩側(cè)就有了電勢差,LED3被點亮,如果該引腳為低電平,pn結(jié)截止,LED3兩側(cè)就沒有了電勢差,LED3熄滅。
下面是CPU核訪問GPIO控制器的數(shù)據(jù)通路:
AHB:高速總線
APB Bridge:APB總線橋
APB:外設(shè)總線,低速總線
GPIO掛載在APB總線上
GPIO 與 SOC
由上圖可知,cpu要訪問GPIO的寄存器需要經(jīng)過的路徑。
二、GPIO
GPIO(General Purpose I/O Ports)意思為通用輸入/輸出端口,通俗地說,就是一些引腳,可以通過它們輸出高低電平或者通過它們讀入引腳的狀態(tài)-是高電平或是低電平。
用戶可以通過GPIO口和硬件進行數(shù)據(jù)交互(如UART),控制硬件工作(如LED、蜂鳴器等),讀取硬件的工作狀態(tài)信號(如中斷信號)等。GPIO口的使用非常廣泛。
1. GPIO的優(yōu)點
- 低功耗:GPIO具有更低的功率損耗(大約1µA,µC的工作電流則為100µA)。
- 集成I²C從機接口:GPIO內(nèi)置I²C從機接口,即使在待機模式下也能夠全速工作。
- 小封裝:GPIO器件提供最小的封裝尺寸—3mm x 3mm QFN!
- 低成本:您不用為沒有使用的功能買單!
- 快速上市:不需要編寫額外的代碼、文檔,不需要任何維護工作!
- 靈活的燈光控制:內(nèi)置多路高分辨率的PWM輸出。
- 可預(yù)先確定響應(yīng)時間:縮短或確定外部事件與中斷之間的響應(yīng)時間。
- 更好的燈光效果:匹配的電流輸出確保均勻的顯示亮度。
- 布線簡單:僅需使用2條I²C總線或3條SPI總線。
2. exynos4412 GPIO特性
- 172 個外部中斷
- 32個外部可喚醒中斷
- 252個多功能 input/output ports
- 在休眠模式下也可以控制GPIO引腳,但不包括 GPX0, GPX1, GPX2, and GPX3
3. 6 General Purpose Input/Output (GPIO) Control
Exynos 4412 SCP 包括304個多功能 input/output端口引腳和164 存儲端口引腳. 總共 37 個端口分組和兩個存儲端口分組.。
下圖為GPIO模塊圖:
GPIO Block Diagram
三、如何操作GPIO?
主要通過寄存器來操作GPIO引腳。
GPxCON用于選擇引腳功能,GPxDAT用于讀/寫引腳數(shù)據(jù);另外,GPxUP用于確定是否使用內(nèi)部上拉電阻。其中x為A、B…..H、J等。
1. GPxCON寄存器
從寄存器的名字可以看出,它用于配置(Configure)-選擇引腳功能。
LED3是連接到GPX1_0,該引腳說明如下:
GPX1CON
由上圖所示,
GPX1CON地址為0x1100C20;
LED3是輸出設(shè)備,所以需要將GPX1CON[3:0]設(shè)置為0x1,但是能修改其他的bite。
2. GPxDAT寄存器
GPxDAT用于讀/寫引腳;當引腳被設(shè)為輸入時,讀此寄存器可知相應(yīng)引腳的電平狀態(tài)是高還是低;當引腳被設(shè)為輸出時,寫此寄存器相應(yīng)位可以令此引腳輸出高電平或是低電平。
GPX1DAT
- GPX1DAT的地址是0x1100C24
- LED3對應(yīng)的輸出引腳是GPX1DAT[0],點燈只需要將該引腳設(shè)置為1即可,滅燈將bite0置0。
3. GPxUP寄存器
GPxUP:某位為1時,相應(yīng)引腳無內(nèi)部上拉電阻;為0時,相應(yīng)引腳使用內(nèi)部上拉電阻。
上拉電阻的作用在于:當GPIO引腳處于第三態(tài)(即不是輸出高電平,也不是輸出低電平,而是呈高阻態(tài),即相當于沒接芯片)時,它的電平狀態(tài)由上拉電阻、下拉電阻確定。
本例不用設(shè)置。
四、驅(qū)動編寫
下面我們分別用匯編和C語言來給LED編寫驅(qū)動程序。
1. 匯編代碼
大家如果掌握了我之前講解的匯編指令的知識點,那么這個代碼很容易就能看明白:
- .globl _start
- .arm
- _start:
- LDR R0,=0x11000C20 @將配置寄存器GPX1CON的地址寫入到R0
- LDR R1,[R0] @讀取寄存器GPX1CON的值保存到R1
- BIC R1,R1,#0x0000000f @將R1的3:0位清0,目的是不覆蓋到其他bit的值
- ORR R1,R1,#0x00000001 @將R1的3:0位置1
- STR R1,[R0] @將R1的值寫回寄存器GPX1CON
- loop:
- LDR R0,=0x11000C24 @將data寄存器GPX1DAT的地址寫入到R0
- LDR R1,[R0] @讀取寄存器GPX1DAT的值保存到R1
- ORR R1,R1,#0x01 @將R1的值bite0 設(shè)置為1,即拉高,點燈
- STR R1,[R0] @將R1的值寫回寄存器GPX1DAT
- BL delay @調(diào)用延時函數(shù)
- LDR R1,[R0]
- BIC R1,R1,#0x01 @將R1的值bite0 設(shè)置為0,即拉低,滅燈
- STR R1,[R0]
- BL delay
- B loop
- delay: @delay延時函數(shù)
- LDR R2,=0xfffffff
- loop1:
- SUB R2,R2,#0x1
- CMP R2,#0x0
- BNE loop1
- MOV PC,LR @返回
- .end
Makefile
- TARGET=gcd
- all:
- arm-none-linux-gnueabi-gcc -O0 -g -c -o $(TARGET).o $(TARGET).s
- arm-none-linux-gnueabi-ld $(TARGET).o -Ttext 0x40008000 -N -o $(TARGET).elf
- arm-none-linux-gnueabi-objcopy -O binary -S $(TARGET).elf $(TARGET).bin
- clean:
- rm -rf *.o *.elf *.dis *.bin
程序功能很簡單,就是讓LED3呈現(xiàn)一閃一閃的效果。
執(zhí)行make,最終生成的gcd.bin文件。
2. c語言實現(xiàn)
如果要進入C語言執(zhí)行環(huán)境,那么就必須為設(shè)置??臻g,函數(shù)調(diào)用參數(shù)和返回值會壓棧。
start.s
- .text
- .global _start
- _start:
- ldr sp,=0x70000000 /*get stack top pointer*/
- b main
main.c
- /* GPX1 */
- typedef struct {
- unsigned int CON;
- unsigned int DAT;
- unsigned int PUD;
- unsigned int DRV;
- }gpx1;
- #define GPX1 (* (volatile gpx1 *)0x11000C20 )
- void led_init(void)
- {
- GPX1.CON = GPX1.CON & (~(0x0000000f)) | 0x00000001;
- }
- void led_on(int n)
- {
- GPX1.DAT = GPX1.DAT|0x01;
- }
- void led_off()
- {
- GPX1.DAT = GPX1.DAT&(~(0x01));
- }
- void delay_ms(unsigned int num)
- { int i,j;
- for(i=num; i>0;i--)
- for(j=1000;j>0;j--)
- ;
- }
- int main(void)
- {
- led_init ();
- while (1) {
- led_on();
- delay_ms(500);
- led_off();
- delay_ms(500);
- }
- while(1);
- return 0;
- }
map.lds
- OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
- OUTPUT_ARCH(arm)
- ENTRY(_start)
- SECTIONS
- {
- . = 0x40008000; ;從該地址開始
- . = ALIGN(4);
- .text : ;指定代碼段
- {
- gcd.o(.text) ;代碼的第一個部分,絕對不能錯
- *(.text)
- }
- . = ALIGN(4);
- .rodata : ;只讀數(shù)據(jù)段
- { *(.rodata) }
- . = ALIGN(4);
- .data : ;讀寫數(shù)據(jù)段
- { *(.data) }
- . = ALIGN(4);
- .bss :
- { *(.bss) }
- }
Makefile
- TARGET=gcd
- TARGETC=main
- all:
- arm-none-eabi-gcc -O0 -g -c -o $(TARGETC).o $(TARGETC).c
- arm-none-eabi-gcc -O0 -g -c -o $(TARGET).o $(TARGET).s
- arm-none-eabi-gcc -O0 -g -S -o $(TARGETC).s $(TARGETC).c
- arm-none-eabi-ld $(TARGETC).o $(TARGET).o -Tmap.lds -o $(TARGET).elf
- arm-none-eabi-objcopy -O binary -S $(TARGET).elf $(TARGET).bin
- clean:
- rm -rf *.o *.elf *.dis *.bin
執(zhí)行make命令,最終生成的gcd.bin文件。
這段代碼中,讀者可能不能理解的是下面的定義:
- typedef struct {
- unsigned int CON;
- unsigned int DAT;
- unsigned int PUD;
- unsigned int DRV;
- }gpx1;
- #define GPX1 (* (volatile gpx1 *)0x11000C20 )
GPX1宏定義
由上圖所示:
- (volatile gpx1 *)0x11000C20 ) :將常量0x11000C20 強轉(zhuǎn)成struct gpx1類型指針
- (* (volatile gpx1 *)0x11000C20 ):查找指針對應(yīng)的內(nèi)存驅(qū)動,即對應(yīng)整個結(jié)構(gòu)體變量,結(jié)構(gòu)體變量地址為0x11000C20
- #define GPX1 (* (volatile gpx1 *)0x11000C20 ) :GPX1等價于地址為0x11000C20的結(jié)構(gòu)體變量
這樣我們要想操作GPX1的寄存器,就可以像結(jié)構(gòu)體變量一樣操作即可。
3. 測試
采用UBOOT自帶的命令loadb,通過串口以baud速率下載binary(.bin)至SDRAM中某一地址中,然后用go 命令從某地址處開始執(zhí)行程序。
該命令使用了kermit protocol,嵌入式系統(tǒng)通常使用該協(xié)議與pc傳送文件。
操作步驟如下:
- 串口連接開發(fā)板,開發(fā)板啟動后在讀秒階段,立即按下回車,進入uboot命令界面
- 執(zhí)行l(wèi)oadb 40008000 【該地址與Makefile 和map.lds文件中的地址保持一致】
- 選擇菜單transfer->send Kermit,
- 然后選擇我們編譯好的gcd.bin文件,
- 點擊OK,出現(xiàn)"Staring kermit transfer."字樣,
- 執(zhí)行 go 40008000,運行程序
執(zhí)行結(jié)果:
led
可以看到LED閃爍的現(xiàn)象。
5. 注意
該種測試方法需要bootloader選用uboot,并且需要串口工具支持Kermit協(xié)議,一口君使用的是SecureCRT7.3.3版本【其他低一些的版本可能不支持該協(xié)議】,該軟件的下載和安裝方法【安裝方法有點繁瑣】可以公眾號后臺回復(fù)【SecureCRT】。
SecureCRT版本