• 三.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!

  • 相关阅读:
    配置gem5-gpu模拟环境
    如何避免并发情况下的重复提交
    避免重复执行
    java线程池
    java动态代理
    Java 静态代理
    Java 静态代理和动态代理
    Spring的事务传播性
    mybatis配置(Configuration.xml)详解
    mybati之parameterType传递多个参数
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/15736619.html
Copyright © 2020-2023  润新知