三.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
main.h

汇编代码

在处理器上电后,要通过汇编语言构建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
start.s

代码最后的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;
}
main.c

这里有两条代码要着重讲一下,在对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
Makefile

烧写SD卡,插入开发板,验证,OK!

相关