0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

树莓派4的hello world程序分析

嵌入式IoT 来源:嵌入式IoT 作者:嵌入式IoT 2020-09-25 15:57 次阅读

树莓派4裸机基础教程:从hello world开始

1.前言

2.项目工程介绍

2.1 Makefile

2.2 link.ld 链接文件

3.从CPU的角度看代码的运行

3.1 start.S文件

3.2 main函数的功能

4.树莓派4串口外设程序

4.1 设置gpio的功能

4.2 配置串口控制器

5.总结

1.前言

当我们去研究一个系统的时候,首先需要从最简单的程序开始入手。前面文章的介绍已经描述了项目的环境搭建以及启动过程。

树莓派4裸机基础教程:环境搭建

树莓派4裸机基础教程:芯片启动到代码执行

本文主要从最简单的裸机代码开始分析,让板子的串口可以输出hello world信息。这篇文章会介绍工程的构建,程序的运行等等一些列的流程,以及树莓派4最后如何输出hello world。在嵌入式开发的过程中,往往都是万事开头难,只有看到了程序正在运行的那一刻,后面的工作也就迎刃而解了。

2.项目工程介绍

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

2.1 Makefile

我们通过Makefile进行相关工程的构建,使用make生成kernel可执行程序文件。对于这种简单的工程,使用Makefile进行工程的构建是很简单的,对于复杂的工程,可以使用scons或者cmake等更加高级的工具,进行工程的构建。

首先来看一下Makefile中的内容:

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

分析一下这个文件的细节:

SRCS=$(wildcard*.c)

其中使用wildcard这个函数来获取当前文件夹中所有的.c文件的列表放在SRCS目录中。

OBJS=$(SRCS:.c=.o)

该句表示环境变量的替换,就是将SRCS列表中的所有的.c文件名替换成.o文件名。

all:cleankernel7.img

当使用make或者make all的时候,会执行clean与kernel7.img对应的命令的指令。

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

根据makefile的语法规则这个解释应该是

目标:源 指令

由于前面的定义只定义了C语言的代码,所以这里也需要将汇编语言的编译加进去。

%.o:%.c arm-none-eabi-gcc$(CFLAGS)-c$< -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

通过arm-none-eabi-ld链接所以的.o文件。arm-none-eabi-objcopy用于生成在arm平台上运行的可执行程序,另外的作用就是去掉一些符号信息。

clean: rmkernel7.elfkernel7.img*.o>/dev/null2>/dev/null||true

用于清理编译过程中的中间文件。

2.2 link.ld 链接文件

由于程序的编译之后,需要进行链接,link文件告诉了程序链接的规则。下面看一下链接文件的内容:

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),数据段(.data)以及bss段(.bss)。首先将代码段的地址. = 0x8000;指向0x8000的地址处,因为默认情况下,树莓派默认启动后,会从0x8000这个地址处开始加载程序并启动。KEEP(*(.text.boot))表示首先将.text.boot的内容放在第一个地址处,目前开始的地址是0x8000。需要注意的是.bss段包含的是初始化为零的数据,通过将这些数据放在一个单独的节中,编译器可以在elf文件中省略一些空间。所以需要记录bss_start与bss_end段。并且将这段空间对齐。如果不对齐,一些函数访问的时候,将会出现异常数据。

3.从CPU的角度看代码的运行

要想真正的理解CPU的执行代码的流程,必须将自己的当作CPU去执行代码的逻辑。

3.1 start.S文件

在start.S文件中,设置了CPU的一些状态,为后续的程序执行准备了环境。

.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

分别来看一下这些代码具体的细节。

.section".text.boot"

表示该段标志为.text.boot,这里表示该文件夹会在链接脚本中链接到开头的地址中。然后将_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

从树莓派启动第一行代码的时候,此时是处于虚拟化模式的,从cpsr_all寄存器中可以读到当前的状态。此时需要退出虚拟化模式。使其运行在Supervisor模式。用eret指令将模式进行切换。

/* Suspend the other cpu cores */ mrc p15, 0, r0, c0, c0, 5 ands r0, #3 bne _halt

因为刚开始的时候,树莓派4是支持4核的,由于当前并不需要这么多核的功能,所以可以让其他的核进入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

接着关闭中断、关闭非对齐检查。为后续的代码运行准备环境。

/* set stack before our code */ ldr sp, =_start

接着设置sp的栈指针,ldr sp, =_start表示将栈指针设置到_start段的地址这里,由于布局的时候,将_start的代码段的地址设置为0x8000,又因为arm上sp栈指针是向低地址方向增长,sp指向的是栈顶。所以我们可以认为0x8000地址之前的空间都是未被使用的,可以作为C语言执行的栈空间使用。

/* 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段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。

/* jump to C code, should not return */ ldr pc, _main

然后设置PC指针。使用ldr pc, _main指令,将_main函数的指针,指向pc。这样下次再执行PC程序的时候就直接执行main函数了。

3.2 main函数的功能

在前面的汇编代码中,为C语言代码执行提供了环境,包括关闭非对齐检查、设置了栈SP的地址、清零了BSS段。这些都是为C代码的执行做准备。在C语言中做了具体的业务。由于目前的裸机代码比较的简单,所以业务也比较容易。

#include"uart.h" voidmain() { //setupserialconsole uart_init(); //sayhello uart_puts("HelloWorld! "); //echoeverythingback while(1){ uart_send(uart_getc()); } }

这个代码就是通过串口输出一个hello world!,然后在while中不断的读串口的输入。那么重点还是放在树莓派串口的初始化上。

4.树莓派4串口外设程序

在做嵌入式的时候,我们总是希望设备与自己是有交互的,比如点亮一个led,或者用串口输出一段字符等等。这都表示程序正常运行。所以会写简单的交互程序也非常的重要。一般比较简单的就是led的呼吸灯。这里用串口,可以做人机交互的信息可以更加的丰富。下面我们来分析一下串口的程序的实现。

在写外设的驱动程序之前,首先需要查看芯片的Peripherals manual。这里查看rpi_DATA_2711_1p0.pdf即可。根据外设空间分布的地址,可以查看如下:

这里由于使用32位的地址空间,根据数据手册,得到芯片的外设的地址的起始地址为0xFE000000。

如果要使用串口,必须要有两个先决条件:

1.相关的gpio配置成串口复用功能

2.配置串口控制器参数

4.1 设置gpio的功能

对于树莓派的gpio,找到对应的地址后,还需要找到其对应的功能。

首先查看树莓派上对应的硬件引脚:

对应的功能如下所示:目前串口使用的硬件引脚为14号与15号引脚。

需要设置的复用功能为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 }

在树莓派中,首先需要选择使能哪些引脚,然后配置成什么模式。对着手册查看,就知道设置这些寄存器位的具体含义了。

4.2 配置串口控制器

串口控制器是需要配置的,目前使用的是AUX的串口控制器,也就是使用的mini UART。所以需要配置串口的一些参数信息。比如串口的波特率、位宽、停止位等等。

*/ 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(); }

目前串口不需要使用中断,所以收发数据都直接从串口的fifo中进行获取。

发送数据

/** *Sendacharacter */ voiduart_send(unsignedintc){ /*waituntilwecansend*/ do{asmvolatile("nop");}while(!(*AUX_MU_LSR&0x20)); /*writethecharactertothebuffer*/ *AUX_MU_IO=c; }

判断当前fifo是否有数据,如果没有就发送到串口的fifo。

charuart_getc(){ charr; /*waituntilsomethingisinthebuffer*/ do{asmvolatile("nop");}while(!(*AUX_MU_LSR&0x01)); /*readitandreturn*/ r=(char)(*AUX_MU_IO); /*convertcarrigereturntonewline*/ returnr==' '?' ':r; }

从串口的fifo中读取字符。

5.总结

从树莓派4的hello world程序分析,详细的描述了串口的输出信息到控制台的过程。前期的c语言运行环境的准备阶段是很多同等系列的芯片都需要去做的事情,后面外设的初始化可能会和具体的硬件平台相关。但是从整体上来看,整个流程还是比较通用的。在不同的芯片与不同的架构上,都需要去做这些基本操作。

本文从最小系统的角度描述了系统启动过程,配置寄存器参数需要对着手册查看,这里也不进行过多的分析,总之多看手册才是学会使用一款芯片的必经之路,只有反复的看,反复的思考理解,才能使用得当。欧阳修《卖油翁》 里说到:无他,但手熟尔。

原文标题:树莓派4裸机基础教程:从hello world开始

文章出处:【微信公众号:嵌入式IoT】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 树莓派
    +关注

    关注

    113

    文章

    1635

    浏览量

    104678

原文标题:树莓派4裸机基础教程:从hello world开始

文章出处:【微信号:Embeded_IoT,微信公众号:嵌入式IoT】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【爱芯 Pro 开发板试用体验】CPU性能测试,与树莓4B对比

    选项编译(无并发执行选项,单线程): make link 最后,执行测试程序: ./coremark.exe 执行结束后,输出如下: 可以看到,单核测试成绩为6288.6; 四、与树莓
    发表于 01-01 16:36

    c语言hello world程序编写

    C语言是一种非常经典和常用的编程语言,而"Hello world"程序是每一个编程入门者的必修课。它是用来熟悉编程语言基本语法、环境配置,以及验证编程环境是否正常工作的第一个程序。本文
    的头像 发表于 11-26 09:23 1296次阅读

    树莓都有哪些功能,是用什么编程的?

    怎么学习树莓树莓和传统单片机的区别是哪些,如果有了树莓该学哪些知识
    发表于 11-09 07:51

    我的第一个UVM代码——Hello world

    以下文章来源于ExASIC ,作者陈锋 你刚开始是怎么学UVM的?白皮书还是红皮书?而我是从hello world实验开始的。 就是这段: (为什么是图片不是文字?就是为了不让你们Ctrl-C
    发表于 11-03 10:18

    RA-RTT体验RT-Thead超级简单的hello world!

    在这篇帖子上,我们增加自己的代码,实现串口输出hello world等功能
    的头像 发表于 10-12 14:36 265次阅读
    RA-RTT体验RT-Thead超级简单的<b class='flag-5'>hello</b> <b class='flag-5'>world</b>!

    树莓3树莓4原理图分享

    提供了树莓3、树莓3B、树莓4版本的原理图
    发表于 09-27 07:58

    Raspberry Pi树莓4 Model B产品介绍

    免费分享Raspberry Pi树莓 4 Model B产品简介、机械制图、示意图
    发表于 09-25 07:49

    使用MPLAB Harmony v3的MPLAB Harmony配置器在PIC32单片机上创建Hello World应用程序

    电子发烧友网站提供《使用MPLAB Harmony v3的MPLAB Harmony配置器在PIC32单片机上创建Hello World应用程序.pdf》资料免费下载
    发表于 09-20 14:52 1次下载
    使用MPLAB Harmony v3的MPLAB Harmony配置器在PIC32单片机上创建<b class='flag-5'>Hello</b> <b class='flag-5'>World</b>应用<b class='flag-5'>程序</b>

    使用MPLAB Harmony配置器(MHC)在SAM单片机上创建Hello World应用程序

    电子发烧友网站提供《使用MPLAB Harmony配置器(MHC)在SAM单片机上创建Hello World应用程序.pdf》资料免费下载
    发表于 09-20 10:58 1次下载
    使用MPLAB Harmony配置器(MHC)在SAM单片机上创建<b class='flag-5'>Hello</b> <b class='flag-5'>World</b>应用<b class='flag-5'>程序</b>

    创建一个简单Hello World Linux应用程序的过程

    本教程将向您介绍创建一个简单的Hello World Linux应用程序的过程,然后将该应用程序加载到运行ARM嵌入式Linux的Cortex-A9固定虚拟平台(FVP)模型上。 Co
    发表于 08-28 06:32

    如何使用ARM编译器构建Hello World

    在本教程中,我们将展示如何使用Arm构建一个名为hello_world.C的简单C程序DS-5中的编译器工具链。 您可以找到Arm编译器工具链的概述。本教程假定您已安装并获得Arm DS-5的许可。有关详细信息,请参阅获取从Arm DS-5 Development Stu
    发表于 08-08 07:55

    如何使用Arm Compiler 6构建Hello World

    Virtual上调试站台 本Arm®DS-5 Development Studio教程介绍了一个基本的Hello World C程序。它将是 如果您想在Armv8平台上进行裸机软件开发,这将非常有用,并显示
    发表于 08-08 07:41

    【Milk-V Duo 开发板免费体验】duo第一程序hello world

    duo第一程序hello world 首先在VMware写hello world好代码 #include <stdio.h>
    发表于 07-20 10:08

    我的第一个UVM代码—Hello world

    你刚开始是怎么学UVM的?白皮书还是红皮书?而我是从hello world实验开始的。
    的头像 发表于 06-15 10:49 535次阅读
    我的第一个UVM代码—<b class='flag-5'>Hello</b> <b class='flag-5'>world</b>

    树莓的故障分析检测

    各位大神,我的树莓B4,经常读不出卡数据,卡换个新的,故障依旧。请指点树莓的故障检查与维修。
    发表于 04-27 11:30