www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 嵌入式IoT

樹(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)目工程介紹

我們還是以第一個(gè)1.compilation_environment的工程作為研究對(duì)象。工程的地址在下面的鏈接中:

https://github.com/bigmagic123/raspi4-bare-metal.git

最后的工程文件如下所示:

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: clean kernel7.img start.o: start.S arm-none-eabi-gcc $(CFLAGS) -c start.S -o start.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/null 2>/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: clean kernel7.img 

當(dāng)使用make或者make all的時(shí)候,會(huì)執(zhí)行clean與kernel7.img對(duì)應(yīng)的命令的指令。

start.o: start.S arm-none-eabi-gcc $(CFLAGS) -c start.S -o start.o

根據(jù)makefile的語(yǔ)法規(guī)則這個(gè)解釋?xiě)?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 -nostartfiles start.o $(OBJS) -T link.ld -o kernel7.elf
 arm-none-eabi-objcopy -O binary kernel7.elf kernel7.img

通過(guò)arm-none-eabi-ld鏈接所以的.o文件。arm-none-eabi-objcopy用于生成在arm平臺(tái)上運(yùn)行的可執(zhí)行程序,另外的作用就是去掉一些符號(hào)信息。

clean: rm kernel7.elf kernel7.img *.o >/dev/null 2>/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)境。

.equ Mode_USR,        0x10
.equ Mode_FIQ,        0x11
.equ Mode_IRQ,        0x12
.equ Mode_SVC,        0x13
.equ Mode_ABT,        0x17
.equ Mode_UND,        0x1B
.equ Mode_SYS,        0x1F

.section ".text.boot" /* entry */
.globl _start
_start:
/* Check for HYP mode */
 mrs r0, cpsr_all
 and r0, r0, #0x1F mov r8, #0x1A cmp r0, r8
 beq overHyped
 b continue overHyped: /* Get out of HYP mode */
 adr r1, continue msr ELR_hyp, r1
 mrs r1, cpsr_all
 and r1, r1, #0x1f    ;@ CPSR_MODE_MASK orr r1, r1, #0x13    ;@ CPSR_MODE_SUPERVISOR msr SPSR_hyp, r1
 eret continue:
 /* Suspend the other cpu cores */
 mrc p15, 0, r0, c0, c0, 5
 ands r0, #3 bne _halt

 /* 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

 /* 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: /* Check for HYP mode */ mrs r0, cpsr_all and r0, r0, #0x1F mov r8, #0x1A cmp r0, r8
 beq overHyped
 b continue overHyped: /* Get out of HYP mode */ adr r1, continue msr ELR_hyp, r1
 mrs r1, cpsr_all and r1, r1, #0x1f ;@ CPSR_MODE_MASK
 orr r1, r1, #0x13 ;@ CPSR_MODE_SUPERVISOR
 msr SPSR_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í)行的??臻g使用。

/* 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)是可讀寫(xiě)的,在程序執(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" void main() { // set up serial console uart_init(); // say hello uart_puts("Hello World!\n"); // echo everything back 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ì)寫(xiě)簡(jiǎn)單的交互程序也非常的重要。一般比較簡(jiǎn)單的就是led的呼吸燈。這里用串口,可以做人機(jī)交互的信息可以更加的豐富。下面我們來(lái)分析一下串口的程序的實(shí)現(xiàn)。

在寫(xiě)外設(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的功能了。

/**
 * gpio14 RX gpio15 TX
 */ void uart_gpio_init() { register unsigned int r; /* map UART1 to GPIO pins */ 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ù)信息。比如串口的波特率、位寬、停止位等等。

*/ void uart_init() { /* initialize UART1 */ *AUX_ENABLE |=1; // enable UART1, AUX mini uart *AUX_MU_CNTL = 0;
 *AUX_MU_LCR = 3; // 8 bits *AUX_MU_MCR = 0;
 *AUX_MU_IER = 0;
 *AUX_MU_IIR = 0xc6; // disable interrupts *AUX_MU_BAUD = 270; // 115200 baud uart_gpio_init();
}

目前串口不需要使用中斷,所以收發(fā)數(shù)據(jù)都直接從串口的fifo中進(jìn)行獲取。

發(fā)送數(shù)據(jù)

/**
 * Send a character
 */ void uart_send(unsigned int c) { /* wait until we can send */ do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x20)); /* write the character to the buffer */ *AUX_MU_IO=c;
}

判斷當(dāng)前fifo是否有數(shù)據(jù),如果沒(méi)有就發(fā)送到串口的fifo。

char uart_getc() { char r; /* wait until something is in the buffer */ do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x01)); /* read it and return */ r=(char)(*AUX_MU_IO); /* convert carrige return to newline */ return r=='\r'?'\n':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)修《賣(mài)油翁》 里說(shuō)到:無(wú)他,但手熟爾。


本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉