三.C语言版本的LED驱动试验
我们在前面的LED驱动是用汇编写的,在后面的开发过程中是不能用汇编去做的,基本上是靠C去实现的,下面我们就用C语言实现LED的驱动试验
处理器的运行模式
在开始之前要先了解一下I.MX6UL的运行模式,这个要看ARM Cortex-A(armV7)编程手册V4.0。第三张ARM Processor Modes and Registers讲了原有ARMv6的7中运行模式,但新的CoretoxA7新增Trustzone安全扩展,就新增了新的运行模式Monitor,还有支持虚拟化扩展的Hyp模式。索引CortexA7是有9种运行模式的
上面的9种运行模式中,除了User模式外,其他8种都是特权模式,这几种模式可以通过软件任意切换,也可以通过中断或者异常来切换。我们大多数程序都是运行在用户模式,但是该模式下有些资源是受限的,要想访问所有资源就必须切换模式,然而用户模式不能直接切换,要通过借助异常来完成,想要切换模式时,应用程序产生异常,在异常处理的过程中完成模式切换。 这个模式,主要是影响了Cotex-A的寄存器组。
Cortex-A寄存器组
这里的寄存器组是指Cortex-A导内核寄存器而不是外设寄存器,ARM架构提供了16个32位的通用寄存器(R0-R15)来供软件使用
其中前15个计算器(R0-R14)可以用作通用寄存器,R15是程序计数器PC用来存放将要执行的指令,此外ARM还提供了一个当前程序状态寄存器CPSR和一个备份程序状态寄存器SPSR,SPSR是CPSR的备份。而每个状态下通用的寄存器是不同的
在这9种模式下,有些寄存器是所有模式共用的物理寄存器,有些事各自独立拥有的。
程序状态寄存器CPSR
处理器的9个运行模式共用一个CPSR物理寄存器,因此在任意一个模式下都是可以访问这个寄存器的上面。我们讲这个寄存器,是因为他有几个bit是决定处理器运行模式的,看下图
上面的内容告诉我们,CPSR的[4:0]的值决定了处理器的运行模式,具体的设置参数如下表
上面说的这些主要是要构建C语言运行构建。
C语言运行环境的构建
前面说过,IMX6U在上电后需要进行一系列初始化才能构建C语言的运行环境,整个过程如下:
设置处理器模式
首先要将处理器运行模式修改为SVC模式,即把CPSR[4:0]的值设置为10011=0x13,有个要注意的是CPSR属于特殊寄存器,不能用LDR和STR操作,要用MRS和MSR指令
MSR R0,RPSR @将特殊寄存器RPSR里的数据传递给R0
MRS RPSR,R0 @将R0的值传给特殊寄存器RPSR
这里就要用到MRS指令了
设置SP指针
因为C语言运行涉及到出入栈,要靠SP指针实现,SP指针可以指向内部RAM,也可以指向DDR,而我们前面已经讲过DDR的初始化,所以我们就将其指向DDR,可以直接使用。那么讲SP设置到哪了呢?针对于我们用的这块开发板来说,512MB的DDR地址为0x80000000~0x9FFFFFFF。栈大小设置为0x200000=2MB。
这里要有个栈增长方向的概念:栈向上增长或向下增长
对于A7而言,栈是向下增长的,设置SP指针指向内存一定要包含其大小,也就是加上栈的范围后不能超过DDR的起始地址(0x80000000)。
转向程序入口
使用b指令跳转到C语言的函数入口(main函数)。
代码实现
上面的流程搞清楚以后,就要通过代码来实现上述流程,
头文件
为了方便后面的寄存器操作,要先写个头文件,里面放我们要用到寄存器地址
// 头文件main.h,定义寄存器的地址 #ifndef __MAIN_H #define __MAIN_H /*定义要使用的寄存器*/ // 时钟寄存器 #define CCM_CCGR0 *((volatile unsigned int*)0x020c4068) // GPIO复用、电气属性寄存器 #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0x020E0068) #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0x020E02F4) // GPIO1属性寄存器 #define GPIO1_DR *((volatile unsigned int *)0X0209C000) #define GPIO1_GDIR *((volatile unsigned int *)0X0209C004) #define GPIO1_PSR *((volatile unsigned int *)0X0209C008) #define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C) #define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010) #define GPIO1_IMR *((volatile unsigned int *)0X0209C014) #define GPIO1_ISR *((volatile unsigned int *)0X0209C018) #define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C) #endif
汇编代码
在处理器上电后,要通过汇编语言构建C语言环境,过程如上面所述
.global _start _start: /*设置处理器进入SVC模式 */ MRS R0,CPSR @读取CPRS到R0 BIC R0,R0,#0x1f @清除CPSR[4:0](1f为最后5位,清除哪位就把哪位置一) ORR R0,R0,#0x13 @或运算,10011,对应SVC模式 MSR CPSR,R0 /*设置SP指针 ,这里一定要注意是设置SP指针要在DDR初始化之后*/ ldr sp,=0x80200000 /*跳转到C语言main函数 */ b main
代码最后的b main就是跳转到C语言的入口函数main函数,这杨就可以执行C的代码来
C代码
这里添加一个新功能:LED的闪烁。也就是加一个delay功能。先把代码放出来
#include "main.h" /*使能外设时钟*/ void clk_enabled(void) { CCM_CCGR0 = 0xffffffff; } /*初始化LED*/ void led_init(void) { // 复用、电气属性寄存器初始化 SW_MUX_GPIO1_IO03 = 0x5; SW_PAD_GPIO1_IO03 = 0x10B0; // GPIO1方向寄存器, GPIO1_GDIR = 0x8; } void delay_short(volatile unsigned int n) { while(n--){} } /*延时,主频在396Hz下一次循环大概是1ms*/ void delay(volatile unsigned int n){ while(n--){delay_short(0x7ff);} } // 点亮LED void led_on(void) { GPIO1_DR &= ~(1<<3);//bit4清零 } // 关闭LED void led_off(void) { GPIO1_DR |=(1<<3); //bit4置一 } //主函数 int main(void) { /*初始化LED*/ clk_enabled(); /*设置LED闪烁*/ led_init(); while(1) { led_on(); delay(500); led_off(); delay(500); } return 0; }
这里有两条代码要着重讲一下,在对GPIO_DR寄存器进行操作都时候,要对GPIO1_DR的第4个bit进行置零或置一。
1 GPIO1_DR &= ~(1<<3); //bit4清零 2 GPIO1_DR |=(1<<3); //bit4置一
第一个,先将1左移3个bit,就是001000(1前面的0可以忽略),再取反变成110111。我们把GPIO1_DR和这个0111进行与运算,就相当于其他位不变,第4个bit清零
第二个,还是将1左移3个bit成1000,再和GPIO1_DR进行或运算,相当于其他位不变,第4个bit置一。
最后还是我们的Makefile,这里的Makefile文件用到了变量,包括自动化脚本,可以看一下,不明白的也可以直接写文件名
objs = start.o main.o ledc.bin:$(objs) arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o ledc.elf arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@ arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis %.o: %.c arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $< %.o: %.s arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $< clean: rm -rf *.o ledc.bin ledc.elf ledc.dis
烧写SD卡,插入开发板,验证,OK!