Linux(八)C语言版LED,从汇编到C

启动文件 startup_stm32f10x_hd.s 这个汇编文件就是完成 C 语言环境搭建的,当然还有一些其他的处理,比如中断向量表(中断向量表的设置是在system_stm32f1xx.c,可能处理是在startup_stm32f10x_hd.s下)等等。当 startup_stm32f10x_hd.s 把 C 语言环境初始化完成以后

就会进入 C 语言环境

============================================================================

那么STM32的启动文件是startup_stm32f10x_hd.s,

Linux(八)C语言版LED,从汇编到C_第1张图片

第 1 行代码就是设置栈大小,这里是设置为 0X400=1024 字节。

第 5 行的__initial_sp 就是初始化 SP 指针。

第 11 行是设置堆大小。

第 18 行是复位中断服务函数, STM32 复位完成以后会执行此中断服务函数。

第 22 行调用 SystemInit()函数来完成其他初始化工作。

第 24 行调用__main, __main 是库函数,其会调用 main()函数。

============================================================================

I.MX6U的启动文件start.s执行的功能类似上面的,但是目前先不用考虑中断向量表,只考虑初始化C环境

Linux(八)C语言版LED,从汇编到C_第2张图片

第 1 行定义了一个全局标号_start。

第 7 行就是标号_start 开始的地方,相当于是一个_start 函数,这个_start 就是第一行代码。

第 10~13 行就是设置处理器进入 SVC 模式,Cortex-A 有九个运行模型,这里我们设置处理器运行在 SVC 模式下。处理器模式的设置是通过修改 CPSR(程序状态)寄存器来完成的,因为CPSR不能直接修改,所以先把CPSR先读到寄存器R0中,然后设置R0 的 bit[4:0]为0x13,然后再把R0覆盖到CPSR上。

第 15 行通过 ldr 指令设置 SVC 模式下的 SP 指针=0X80200000,因为 I.MX6U-ALPHA 开发 板 上 的 DDR3 地 址 范 围 是 0X80000000~0XA0000000(512MB) 或 者0X80000000~0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。

第 16 行就是跳转到 main 函数, main 函数就是 C 语言代码了。

============================================================================

启动文件有了,也就是说C环境搭建好了,接下来就是C语言实现功能

main.h 中对各种寄存器进行宏定义,方便main.c的操作

Linux(八)C语言版LED,从汇编到C_第3张图片

Linux(八)C语言版LED,从汇编到C_第4张图片

 

可以看到都是对寄存器地址的宏定义,那么回顾一下volatile的用法:

1 背景:

编译器为了优化而提供缓存效率和调整指令执行顺序。这个变量可能会被意想不到的改变,这样编译器就不会去假设这个变量的值。因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。

2 volatile含义:

表示变量是易变的,即变量可能会被意想不到的被改变,因此编译器直接从变量地址中读取数据

3 volatile作用

作用1:防止编译器为了提高缓存速度将一个变量缓存到寄存器而不写回

作用2:阻止编译器调整操作volatile变量的指令顺序

4 使用场景

中断服务程序;多任务环境

============================================================================

Linux(八)C语言版LED,从汇编到C_第5张图片

Linux(八)C语言版LED,从汇编到C_第6张图片

Linux(八)C语言版LED,从汇编到C_第7张图片

Linux(八)C语言版LED,从汇编到C_第8张图片

Linux(八)C语言版LED,从汇编到C_第9张图片

main.c函数看起来还是蛮简单的

====================有了起始文件、main.c、main.h还不够,还需要Makefile

objs := start.o main.o

ledc.bin:$(objs)

arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^

arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@

arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

%.o:%.s

arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

%.o:%.S

arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

%.o:%.c

arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

clean:

rm -rf *.o ledc.bin ledc.elf ledc.dis

Makefile就是描述哪些文件需要编译、哪些需要重新编译的文件。Makefile 里面还可以执行系统命令。使用的时候只需要一个 make命令即可完成整个工程的自动编译,极大的提高了软件开发的效率。

===========================之后引入了一个链接脚本 imx6ul.lds。

在上面的 Makefile 中我们链接代码的时候使用如下语句:

arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^

上面语句中我们是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件都会链接到以 0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区域,或者叫做段里面,比如在 Linux 里面初始化函数就会放到 init 段里面。因此我们需要能够自定义一些段,这些段的起始地址我们可以自由指定,同样的我们也可以指定一个文件或者函数应该存放到哪个段里面去。要完成这个功能我们就需要使用到链接脚本,看名字就知道链接脚本主要用于链接的,用于描述文件应该如何被链接在一起形成最终的可执行文件。其主要目的是描述输入文件中的段如何被映射到输出文件中,并且控制输出文件中的内存排布。比如我们编译生成的文件一般都包含 text 段、 data 段等等。

..............................

我还是蛮难理解这个链接脚本的,不过直观感受是,一个描述各个输出文件.o是如何被组合成一个目标文件.bin的,以前看map也能看到,bin都是一个一个.o组成的,那么.o是如何组成的也是需要一个顺序的吧,我想链接文件就是干这个用的。

我觉得可以先不去细琢磨,也许以后会茅塞顿开,先用着。

=============================生成了链接文件 imx6ul.lds后,就再去重新编辑一下Makefile

arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^

修改为

arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^

起始就是将-T 后面的 0X87800000 改为 imx6ul.lds,表示使用 imx6ul.lds 这个链接脚本文

件。修改完成以后使用新的 Makefile 和链接脚本文件重新编译工程,编译成功以后就可以烧写

到 SD 卡中验证了

使用软件 imxdownload 将编译出来的 ledc.bin 烧写到 SD 卡中,命令如下:

chmod 777 imxdownload //给予 imxdownload 可执行权限,一次即可

./imxdownload ledc.bin /dev/sdd //烧写到 SD 卡中

============================================================================总结一下C语言版的点亮LED:

对比前面的汇编LED可以看到,真正有区别的地方在于对寄存器的操作由汇编语言改为了C语言,因此汇编LED只需要一个led.s,这个文件直接用汇编语言操作寄存器。而C语言的执行也是需要转换为汇编语言的,那么就需要C环境的搭建。这就是本工程中start.s的作用了。

而由于之前汇编的文件就一个led.s,而本次不仅有了start.s还有main.c、main.h,所以Makefile也更复杂一点,但是效果都是一样的:

  1. objs := start.o main.o ledc.bin:$(objs)

这个就是led.bin是目标文件,其后的start.o 、main.o是依赖文件集合,如果依赖文件集合有所更新,目标文件也一定要更新。如果依赖文件集合缺少哪个,就去生成哪个依赖文件。

  1. 先对依赖文件进行编译,生成执行文件.o
  2. 有了.o集合还需要将它们链接在一起,生成.elf文件
  3. 但是.elf文件并不能直接运行,还需要转换为.bin文件
  4. 因为大部分都是用C语言编写代码,可能会用汇编调试,因此可以用指令将.elf反汇编
  5. clean

那么其中提到的链接文件.elf之前是直接绑定到0X87800000 ,此次用了链接脚本是干啥的呢。

有时间的话看一下:Ubuntu下ARM开发.ld链接文件的学习笔记

https://blog.csdn.net/xiaofeng1234/article/details/39048189

之前没用到链接脚本不是因为没有,而是用的默认连接脚本。那么这次就是不用默认的链接脚本,而是用自己定义的链接脚本了。这个连接脚本是 imx6ul.lds,并不是链接文件.elf。

链接脚本是用来帮助生成链接文件的。

验证通过!LED闪烁

Linux(八)C语言版LED,从汇编到C_第10张图片

你可能感兴趣的:(linux)