樹(shù)莓派4裸機(jī)基礎(chǔ)教程:從hello world開(kāi)始
1.前言
2.項(xiàng)目工程介紹
2.1 Makefile
2.2 link.ld 鏈接文件
3.從CPU的角度看代碼的運(yùn)行
3.1 start.S文件
3.2 main函數(shù)的功能
4.樹(shù)莓派4串口外設(shè)程序
4.1 設(shè)置gpio的功能
4.2 配置串口控制器
5.總結(jié)
1.前言
當(dāng)我們?nèi)パ芯恳粋€(gè)系統(tǒng)的時(shí)候,首先需要從最簡(jiǎn)單的程序開(kāi)始入手。前面文章的介紹已經(jīng)描述了項(xiàng)目的環(huán)境搭建以及啟動(dòng)過(guò)程。
樹(shù)莓派4裸機(jī)基礎(chǔ)教程:環(huán)境搭建
樹(shù)莓派4裸機(jī)基礎(chǔ)教程:芯片啟動(dòng)到代碼執(zhí)行
本文主要從最簡(jiǎn)單的裸機(jī)代碼開(kāi)始分析,讓板子的串口可以輸出hello world信息。這篇文章會(huì)介紹工程的構(gòu)建,程序的運(yùn)行等等一些列的流程,以及樹(shù)莓派4最后如何輸出hello world。在嵌入式開(kāi)發(fā)的過(guò)程中,往往都是萬(wàn)事開(kāi)頭難,只有看到了程序正在運(yùn)行的那一刻,后面的工作也就迎刃而解了。
2.項(xiàng)目工程介紹
最后的工程文件如下所示:
2.1 Makefile
我們通過(guò)Makefile進(jìn)行相關(guān)工程的構(gòu)建,使用make生成kernel可執(zhí)行程序文件。對(duì)于這種簡(jiǎn)單的工程,使用Makefile進(jìn)行工程的構(gòu)建是很簡(jiǎn)單的,對(duì)于復(fù)雜的工程,可以使用scons或者cmake等更加高級(jí)的工具,進(jìn)行工程的構(gòu)建。
首先來(lái)看一下Makefile中的內(nèi)容:
SRCS=$(wildcard*.c) OBJS=$(SRCS:.c=.o) CFLAGS=-march=armv8-a-mtune=cortex-a72-Wall-O2-ffreestanding-nostdinc-nostdlib-nostartfiles all:cleankernel7.img start.o:start.S arm-none-eabi-gcc$(CFLAGS)-cstart.S-ostart.o %.o:%.c arm-none-eabi-gcc$(CFLAGS)-c$-o?$@ kernel7.img:?start.o?$(OBJS) ?arm-none-eabi-ld?-nostdlib?-nostartfiles?start.o?$(OBJS)?-T?link.ld?-o?kernel7.elf ?arm-none-eabi-objcopy?-O?binary?kernel7.elf?kernel7.img clean: ?rm?kernel7.elf?kernel7.img?*.o?>/dev/null2>/dev/null||true
分析一下這個(gè)文件的細(xì)節(jié):
SRCS=$(wildcard*.c)
其中使用wildcard這個(gè)函數(shù)來(lái)獲取當(dāng)前文件夾中所有的.c文件的列表放在SRCS目錄中。
OBJS=$(SRCS:.c=.o)
該句表示環(huán)境變量的替換,就是將SRCS列表中的所有的.c文件名替換成.o文件名。
all:cleankernel7.img
當(dāng)使用make或者make all的時(shí)候,會(huì)執(zhí)行clean與kernel7.img對(duì)應(yīng)的命令的指令。
start.o:start.S arm-none-eabi-gcc$(CFLAGS)-cstart.S-ostart.o
根據(jù)makefile的語(yǔ)法規(guī)則這個(gè)解釋應(yīng)該是
目標(biāo):源 指令
由于前面的定義只定義了C語(yǔ)言的代碼,所以這里也需要將匯編語(yǔ)言的編譯加進(jìn)去。
%.o:%.c arm-none-eabi-gcc$(CFLAGS)-c$-o?$@
其中$<表示第一個(gè)依賴文件的名詞,$@表示目標(biāo)文件的名詞。
kernel7.img:start.o$(OBJS) arm-none-eabi-ld-nostdlib-nostartfilesstart.o$(OBJS)-Tlink.ld-okernel7.elf arm-none-eabi-objcopy-Obinarykernel7.elfkernel7.img
通過(guò)arm-none-eabi-ld鏈接所以的.o文件。arm-none-eabi-objcopy用于生成在arm平臺(tái)上運(yùn)行的可執(zhí)行程序,另外的作用就是去掉一些符號(hào)信息。
clean: rmkernel7.elfkernel7.img*.o>/dev/null2>/dev/null||true
用于清理編譯過(guò)程中的中間文件。
2.2 link.ld 鏈接文件
由于程序的編譯之后,需要進(jìn)行鏈接,link文件告訴了程序鏈接的規(guī)則。下面看一下鏈接文件的內(nèi)容:
SECTIONS { /* * First and formost we need the .init section, containing the code to * be run first. We allow room for the ATAGs and stack and conform to * the bootloader's expectation by putting this code at 0x8000. */ . = 0x8000; .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } /* * Next we put the data. */ .data : { *(.data) } .bss : { . = ALIGN(16); __bss_start = .; *(.bss*) *(COMMON*) __bss_end = .; } } __bss_size = (__bss_end - __bss_start) >> 3;
程序分為代碼段(.text),數(shù)據(jù)段(.data)以及bss段(.bss)。首先將代碼段的地址. = 0x8000;指向0x8000的地址處,因?yàn)槟J(rèn)情況下,樹(shù)莓派默認(rèn)啟動(dòng)后,會(huì)從0x8000這個(gè)地址處開(kāi)始加載程序并啟動(dòng)。KEEP(*(.text.boot))表示首先將.text.boot的內(nèi)容放在第一個(gè)地址處,目前開(kāi)始的地址是0x8000。需要注意的是.bss段包含的是初始化為零的數(shù)據(jù),通過(guò)將這些數(shù)據(jù)放在一個(gè)單獨(dú)的節(jié)中,編譯器可以在elf文件中省略一些空間。所以需要記錄bss_start與bss_end段。并且將這段空間對(duì)齊。如果不對(duì)齊,一些函數(shù)訪問(wèn)的時(shí)候,將會(huì)出現(xiàn)異常數(shù)據(jù)。
3.從CPU的角度看代碼的運(yùn)行
要想真正的理解CPU的執(zhí)行代碼的流程,必須將自己的當(dāng)作CPU去執(zhí)行代碼的邏輯。
3.1 start.S文件
在start.S文件中,設(shè)置了CPU的一些狀態(tài),為后續(xù)的程序執(zhí)行準(zhǔn)備了環(huán)境。
.equMode_USR,0x10 .equMode_FIQ,0x11 .equMode_IRQ,0x12 .equMode_SVC,0x13 .equMode_ABT,0x17 .equMode_UND,0x1B .equMode_SYS,0x1F .section".text.boot" /*entry*/ .globl_start _start: /*CheckforHYPmode*/ mrsr0,cpsr_all andr0,r0,#0x1F movr8,#0x1A cmpr0,r8 beqoverHyped bcontinue overHyped:/*GetoutofHYPmode*/ adrr1,continue msrELR_hyp,r1 mrsr1,cpsr_all andr1,r1,#0x1f;@CPSR_MODE_MASK orrr1,r1,#0x13;@CPSR_MODE_SUPERVISOR msrSPSR_hyp,r1 eret continue: /*Suspendtheothercpucores*/ mrcp15,0,r0,c0,c0,5 andsr0,#3 bne_halt /*setthecputoSVC32modeanddisableinterrupt*/ cps#Mode_SVC /*disablethedataalignmentcheck*/ mrcp15,0,r1,c1,c0,0 bicr1,#(1<<1) ????mcr?p15,?0,?r1,?c1,?c0,?0 ????/*?set?stack?before?our?code?*/ ????ldr?sp,?=_start ????/*?clear?.bss?*/ ????mov?????r0,#0???????????????????/*?get?a?zero???????????????????????*/ ????ldr?????r1,=__bss_start?????????/*?bss?start????????????????????????*/ ????ldr?????r2,=__bss_end???????????/*?bss?end??????????????????????????*/ bss_loop: ????cmp?????r1,r2???????????????????/*?check?if?data?to?clear???????????*/ ????strlo???r0,[r1],#4??????????????/*?clear?4?bytes????????????????????*/ ????blo?????bss_loop????????????????/*?loop?until?done??????????????????*/ ????/*?jump?to?C?code,?should?not?return?*/ ????ldr?????pc,?_main ????b?_halt _main: ????.word?main _halt: ????wfe ????b?_halt
分別來(lái)看一下這些代碼具體的細(xì)節(jié)。
.section".text.boot"
表示該段標(biāo)志為.text.boot,這里表示該文件夾會(huì)在鏈接腳本中鏈接到開(kāi)頭的地址中。然后將_start指定到0x8000的地址處。
/*entry*/ .globl_start _start: /*CheckforHYPmode*/ mrsr0,cpsr_all andr0,r0,#0x1F movr8,#0x1A cmpr0,r8 beqoverHyped bcontinue overHyped:/*GetoutofHYPmode*/ adrr1,continue msrELR_hyp,r1 mrsr1,cpsr_all andr1,r1,#0x1f;@CPSR_MODE_MASK orrr1,r1,#0x13;@CPSR_MODE_SUPERVISOR msrSPSR_hyp,r1 eret
從樹(shù)莓派啟動(dòng)第一行代碼的時(shí)候,此時(shí)是處于虛擬化模式的,從cpsr_all寄存器中可以讀到當(dāng)前的狀態(tài)。此時(shí)需要退出虛擬化模式。使其運(yùn)行在Supervisor模式。用eret指令將模式進(jìn)行切換。
/* Suspend the other cpu cores */ mrc p15, 0, r0, c0, c0, 5 ands r0, #3 bne _halt
因?yàn)閯傞_(kāi)始的時(shí)候,樹(shù)莓派4是支持4核的,由于當(dāng)前并不需要這么多核的功能,所以可以讓其他的核進(jìn)入low-power standby低功耗模式WFE(Wait for event)。
/* set the cpu to SVC32 mode and disable interrupt */ cps #Mode_SVC /* disable the data alignment check */ mrc p15, 0, r1, c1, c0, 0 bic r1, #(1<<1) mcr p15, 0, r1, c1, c0, 0
接著關(guān)閉中斷、關(guān)閉非對(duì)齊檢查。為后續(xù)的代碼運(yùn)行準(zhǔn)備環(huán)境。
/* set stack before our code */ ldr sp, =_start
接著設(shè)置sp的棧指針,ldr sp, =_start表示將棧指針設(shè)置到_start段的地址這里,由于布局的時(shí)候,將_start的代碼段的地址設(shè)置為0x8000,又因?yàn)閍rm上sp棧指針是向低地址方向增長(zhǎng),sp指向的是棧頂。所以我們可以認(rèn)為0x8000地址之前的空間都是未被使用的,可以作為C語(yǔ)言執(zhí)行的棧空間使用。
/* clear .bss */ mov r0,#0 /* get a zero */ ldr r1,=__bss_start /* bss start */ ldr r2,=__bss_end /* bss end */ bss_loop: cmp r1,r2 /* check if data to clear */ strlo r0,[r1],#4 /* clear 4 bytes */ blo bss_loop /* loop until done */
接著清空BSS段,BSS段通常是指用來(lái)存放程序中未初始化的或者初始化為0的全局變量和靜態(tài)變量的一塊內(nèi)存區(qū)域。特點(diǎn)是可讀寫的,在程序執(zhí)行之前BSS段會(huì)自動(dòng)清0。
/* jump to C code, should not return */ ldr pc, _main
然后設(shè)置PC指針。使用ldr pc, _main指令,將_main函數(shù)的指針,指向pc。這樣下次再執(zhí)行PC程序的時(shí)候就直接執(zhí)行main函數(shù)了。
3.2 main函數(shù)的功能
在前面的匯編代碼中,為C語(yǔ)言代碼執(zhí)行提供了環(huán)境,包括關(guān)閉非對(duì)齊檢查、設(shè)置了棧SP的地址、清零了BSS段。這些都是為C代碼的執(zhí)行做準(zhǔn)備。在C語(yǔ)言中做了具體的業(yè)務(wù)。由于目前的裸機(jī)代碼比較的簡(jiǎn)單,所以業(yè)務(wù)也比較容易。
#include"uart.h" voidmain() { //setupserialconsole uart_init(); //sayhello uart_puts("HelloWorld! "); //echoeverythingback while(1){ uart_send(uart_getc()); } }
這個(gè)代碼就是通過(guò)串口輸出一個(gè)hello world!,然后在while中不斷的讀串口的輸入。那么重點(diǎn)還是放在樹(shù)莓派串口的初始化上。
4.樹(shù)莓派4串口外設(shè)程序
在做嵌入式的時(shí)候,我們總是希望設(shè)備與自己是有交互的,比如點(diǎn)亮一個(gè)led,或者用串口輸出一段字符等等。這都表示程序正常運(yùn)行。所以會(huì)寫簡(jiǎn)單的交互程序也非常的重要。一般比較簡(jiǎn)單的就是led的呼吸燈。這里用串口,可以做人機(jī)交互的信息可以更加的豐富。下面我們來(lái)分析一下串口的程序的實(shí)現(xiàn)。
在寫外設(shè)的驅(qū)動(dòng)程序之前,首先需要查看芯片的Peripherals manual。這里查看rpi_DATA_2711_1p0.pdf即可。根據(jù)外設(shè)空間分布的地址,可以查看如下:
這里由于使用32位的地址空間,根據(jù)數(shù)據(jù)手冊(cè),得到芯片的外設(shè)的地址的起始地址為0xFE000000。
如果要使用串口,必須要有兩個(gè)先決條件:
1.相關(guān)的gpio配置成串口復(fù)用功能
2.配置串口控制器參數(shù)
4.1 設(shè)置gpio的功能
對(duì)于樹(shù)莓派的gpio,找到對(duì)應(yīng)的地址后,還需要找到其對(duì)應(yīng)的功能。
首先查看樹(shù)莓派上對(duì)應(yīng)的硬件引腳:
對(duì)應(yīng)的功能如下所示:目前串口使用的硬件引腳為14號(hào)與15號(hào)引腳。
需要設(shè)置的復(fù)用功能為ALT5。
有了這些信息之后,就可以配置GPFSEL1的功能了。
/** *gpio14RXgpio15TX */ voiduart_gpio_init() { registerunsignedintr; /*mapUART1toGPIOpins*/ r=*GPFSEL1; r&=~((7<<12)|(7<<15));?//?gpio14,?gpio15 ????r|=(2<<12)|(2<<15);????//?alt5 ????*GPFSEL1?=?r; ????*GPPUD?=?0;????????????//?enable?pins?14?and?15 ????r=150;?while(r--)?{?asm?volatile("nop");?} ????*GPPUDCLK0?=?(1<<14)|(1<<15); ????r=150;?while(r--)?{?asm?volatile("nop");?} ????*GPPUDCLK0?=?0;????????//?flush?GPIO?setup ????*AUX_MU_CNTL?=?3;??????//?enable?Tx,?Rx }
在樹(shù)莓派中,首先需要選擇使能哪些引腳,然后配置成什么模式。對(duì)著手冊(cè)查看,就知道設(shè)置這些寄存器位的具體含義了。
4.2 配置串口控制器
串口控制器是需要配置的,目前使用的是AUX的串口控制器,也就是使用的mini UART。所以需要配置串口的一些參數(shù)信息。比如串口的波特率、位寬、停止位等等。
*/ voiduart_init() { /*initializeUART1*/ *AUX_ENABLE|=1;//enableUART1,AUXminiuart *AUX_MU_CNTL=0; *AUX_MU_LCR=3;//8bits *AUX_MU_MCR=0; *AUX_MU_IER=0; *AUX_MU_IIR=0xc6;//disableinterrupts *AUX_MU_BAUD=270;//115200baud uart_gpio_init(); }
目前串口不需要使用中斷,所以收發(fā)數(shù)據(jù)都直接從串口的fifo中進(jìn)行獲取。
發(fā)送數(shù)據(jù)
/** *Sendacharacter */ voiduart_send(unsignedintc){ /*waituntilwecansend*/ do{asmvolatile("nop");}while(!(*AUX_MU_LSR&0x20)); /*writethecharactertothebuffer*/ *AUX_MU_IO=c; }
判斷當(dāng)前fifo是否有數(shù)據(jù),如果沒(méi)有就發(fā)送到串口的fifo。
charuart_getc(){ charr; /*waituntilsomethingisinthebuffer*/ do{asmvolatile("nop");}while(!(*AUX_MU_LSR&0x01)); /*readitandreturn*/ r=(char)(*AUX_MU_IO); /*convertcarrigereturntonewline*/ returnr==' '?' ':r; }
從串口的fifo中讀取字符。
5.總結(jié)
從樹(shù)莓派4的hello world程序分析,詳細(xì)的描述了串口的輸出信息到控制臺(tái)的過(guò)程。前期的c語(yǔ)言運(yùn)行環(huán)境的準(zhǔn)備階段是很多同等系列的芯片都需要去做的事情,后面外設(shè)的初始化可能會(huì)和具體的硬件平臺(tái)相關(guān)。但是從整體上來(lái)看,整個(gè)流程還是比較通用的。在不同的芯片與不同的架構(gòu)上,都需要去做這些基本操作。
本文從最小系統(tǒng)的角度描述了系統(tǒng)啟動(dòng)過(guò)程,配置寄存器參數(shù)需要對(duì)著手冊(cè)查看,這里也不進(jìn)行過(guò)多的分析,總之多看手冊(cè)才是學(xué)會(huì)使用一款芯片的必經(jīng)之路,只有反復(fù)的看,反復(fù)的思考理解,才能使用得當(dāng)。歐陽(yáng)修《賣油翁》 里說(shuō)到:無(wú)他,但手熟爾。
原文標(biāo)題:樹(shù)莓派4裸機(jī)基礎(chǔ)教程:從hello world開(kāi)始
文章出處:【微信公眾號(hào):嵌入式IoT】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
樹(shù)莓派
+關(guān)注
關(guān)注
117文章
1710瀏覽量
105880
原文標(biāo)題:樹(shù)莓派4裸機(jī)基礎(chǔ)教程:從hello world開(kāi)始
文章出處:【微信號(hào):Embeded_IoT,微信公眾號(hào):嵌入式IoT】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
使用MCUXpresso for VS Code插件開(kāi)發(fā)Zephyr的hello world

樹(shù)莓派傳感器使用方法 樹(shù)莓派 Raspberry Pi 4優(yōu)缺點(diǎn)
ARM開(kāi)發(fā)板與樹(shù)莓派的比較
什么是樹(shù)莓派?樹(shù)莓派是什么架構(gòu)的
樹(shù)莓派4B的WiFi配置過(guò)程
樹(shù)莓派4b支持多大的sd卡
樹(shù)莓派4b和什么性能計(jì)算機(jī)相當(dāng)
樹(shù)莓派4b相當(dāng)于什么CPU
樹(shù)莓派4B的性能特點(diǎn)及應(yīng)用
鴻蒙OpenHarmony【輕量系統(tǒng) 編寫“Hello World”程序】 (基于Hi3861開(kāi)發(fā)板)

鴻蒙OpenHarmony【小型系統(tǒng) 編寫“Hello World”程序】 (基于Hi3516開(kāi)發(fā)板)

鴻蒙OpenHarmony【標(biāo)準(zhǔn)系統(tǒng) 編寫“Hello World”程序】(基于RK3568開(kāi)發(fā)板)

鴻蒙OpenHarmony【標(biāo)準(zhǔn)系統(tǒng)編寫“Hello World”程序】 (基于RK3568開(kāi)發(fā)板)

鴻蒙OpenHarmony【小型系統(tǒng)編寫“Hello World”程序】 (基于Hi3516開(kāi)發(fā)板)

鴻蒙OpenHarmony【輕量系統(tǒng)編寫“Hello World”程序】 (基于Hi3861開(kāi)發(fā)板)

評(píng)論