C语言点亮led
Cortex-A 处理器运行模型
Cortex-A7 处理器有 9 种处理模式。如下图:
所有的处理器模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问。CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。所有的处理器模式都共用一个 CPSR 必然会导致冲突,为此,除了 User 和 Sys 这两个模式以外,其他 7 个模式每个都配备了一个专用的物理状态寄存器,叫做 SPSR(备份程序状态寄存器),当特定的异常中断发生时,SPSR 寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。
N(bit31):当两个补码表示的 有符号整数运算的时候,N=1 表示运算对的结果为负数,N=0表示结果为正数。
Z(bit30):Z=1 表示运算结果为零,Z=0 表示运算结果不为零,对于 CMP 指令,Z=1 表示进行比较的两个数大小相等。
C(bit29):在加法指令中,当结果产生了进位,则 C=1,表示无符号数运算发生上溢,其它情况下 C=0。在减法指令中,当运算中发生借位,则 C=0,表示无符号数运算发生下溢,其它情况下 C=1。对于包含移位操作的非加/减法运算指令,C 中包含最后一次溢出的位的数值,对于其它非加/减运算指令,C 位的值通常不受影响。
V(bit28):对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时,V=1 表示符号位溢出,通常其他位不影响 V 位。
Q(bit27):仅 ARM v5TE_J 架构支持,表示饱和状态,Q=1 表示累积饱和,Q=0 表示累积不饱和。
IT1:0:和 IT7:2一起组成 IT[7:0],作为 IF-THEN 指令执行状态。
J(bit24):仅 ARM_v5TE-J 架构支持,J=1 表示处于 Jazelle 状态,此位通常和 T(bit5)位一起表示当前所使用的指令集。
GE3:0:SIMD 指令有效,大于或等于。
IT7:2:参考 IT[1:0]。
E(bit9):大小端控制位,E=1 表示大端模式,E=0 表示小端模式。
A(bit8):禁止异步中断位,A=1 表示禁止异步中断。
I(bit7):I=1 禁止 IRQ,I=0 使能 IRQ。
F(bit6):F=1 禁止 FIQ,F=0 使能 FIQ。
T(bit5):控制指令执行状态,表明本指令是 ARM 指令还是 Thumb 指令,通常和 J(bit24)一起表明指令类型,参考 J(bit24)位。
M[4:0]:处理器模式控制位
点亮led
在开始部分用汇编来初始化一下 C 语言环境,比如初始化 DDR、设置堆栈指针 SP 等等,当这些工作都做完以后就可以进入 C 语言环境,也就是运行 C 语言代码,一般都是进入 main 函数。所以我们有两部分文件要做:
①、汇编文件:汇编文件只是用来完成 C 语言环境搭建。
②、C 语言文件:C 语言文件就是完成我们的业务层代码的,其实就是我们实际例程要完成的功能。
start.s文件
1 | 1 .global _start /* 全局标号 */ |
第 1 行定义了一个全局标号_start。
第 7 行就是标号_start 开始的地方,相当于是一个_start 函数,这个_start 就是第一行代码。
第 1013 行就是设置处理器进入 SVC 模式,在 6.2 小节的“Cortex-A 处理器运行模型”中,设置处理器运行在 SVC 模式下。处理器模式的设置是通过修改 CPSR(程序状态)寄存器来完成的,其中 M[4:0](CPSR 的 bit[4:0])就是设置处理器运行模式的,如果要将处理器设置为 SVC 模式,那么 M[4:0]就要等于 0X13。1113 行代码就是先使用指令 MRS 将 CPSR寄存器的值读取到 R0 中,然后修改 R0 中的值,设置 R0 的 bit[4:0]为 0X13,然后再使用指令MSR 将修改后的 R0 重新写入到 CPSR 中。
第 15 行通过 ldr 指令设置 SVC 模式下的 SP 指针=0X80200000,因为 I.MX6U-ALPHA 开发板上的 DDR3 地址范围 是 0X800000000XA0000000(512MB) 或 者0X800000000X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。
第 16 行就是跳转到 main 函数,main 函数就是 C 语言代码了。
至此汇编部分程序执行完成,就几行代码,用来设置处理器运行到 SVC 模式下、然后初始化 SP 指针、最终跳转到 C 文件的 main 函数中。
编写led.h文件
1 |
|
编写led.c
1 |
|
之后就编写Makefile文件
1 | objects: start.o led.o |
第一行定义变量,第二行默认目标目的是生成start.o 和led.o,之后是使用arm-linux-gnueabihf-ld进行链接,链接地址是0X87800000,之后是编译为ledc.bin文件,在之后是反汇编,第七行是针对不同文件生成对应的.o文件,用到了自动变量$@和$<,其中“$<”的意思是依赖目标集合的第一个文件
链接脚本编写格式
1 | 1 SECTIONS{ |
第 1 行我们先写了一个关键字“SECTIONS”,后面跟了一个大括号,这个大括号和第 7 行的大括号是一对,这是必须的。看起来就跟 C 语言里面的函数一样。
第 2 行对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位计数器为 0。我们要求代码链接到以 0X10000000 为起始地址的地方,因此这一行给“.”赋值0X10000000,表示以 0X10000000 开始,后面的文件或者段都会以 0X10000000 为起始地址开始链接。
第 3 行的“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链接到“.text”这个段里面的所有文件,“*(.text)”中的“*”是通配符,表示所有输入文件的.text段都放到“.text”中。
第 4 行,我们的要求是数据放到 0X30000000 开始的地方,所以我们需要重新设置定位计数器“.”,将其改为 0X30000000。如果不重新设置的话会怎么样?假设“.text”段大小为 0X10000,那么接下来的.data 段开始地址就是 0X10000000+0X10000=0X10010000,这明显不符合我们的要求。所以我们必须调整定位计数器为 0X30000000。
第 5 行跟第 3 行一样,定义了一个名为“.data”的段,然后所有文件的“.data”段都放到这里面。但是这一行多了一个“ALIGN(4)”,这是什么意思呢?这是用来对“.data”这个段的起始地址做字节对齐的,ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐。
第 6 行定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数据就是那些定义了但是没有被初始化的变量。上面就是链接脚本最基本的语法格式,我们接下来就按照这个基本的语法格式来编写我们本试验的链接脚本,我们本试验的链接脚本要求如下:
①、链接起始地址为 0X87800000。
②、start.o 要被链接到最开始的地方,因为 start.o 里面包含这第一个要执行的命令。
imx6ul.lds 链接脚本代码
1 | 1 SECTIONS{ |
第 11、13 这两行其实就是对这两个符号进行赋值,其值为定位符“.”,这两个符号用来保存.bss 段的起始地址和结束地址,前面说了.bss 段是定义了但是没有被初始化的变量,我们需要手动对.bss 段的变量清零的,因此我们需要知道.bss 段的起始和结束地址,这样我们直接对这段内存赋 0 即可完成清零。通过第 11、13 行代码,.bss 段的起始地址和结束地址就保存在了“__bss_start”和“__bss_end”中,我们就可以直接在汇编或者 C 文件里面使用这两个符号。
修改 Makefile
已经编写好了链接脚本文件:imx6ul.lds,将 Makefile 中的如下一行代码:
1 | arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^ |
改为:
1 | arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^ |