• 【ARM 裸机】C 语言 led 驱动


    前面刚学习了汇编 led 驱动的编写和验证,现在开始就要进入 C 语言 led 驱动编写与验证了 !

    1、C 语言运行环境构建

    1.1、设置处理器模式

    使 6ULL 处于 SVC 模式下,之前已经提到了处理器的九种模式,参考:【ARM 裸机】汇编 led 驱动之基本语法,如何设置成 SVC 模式,需要用到 CPSR 寄存器,CPSR 寄存器一共是 32 位,关注它的第 0~4 位,也就是设置为 M[4:0] 为 10011 = 0x13;
    在这里插入图片描述
    在这里插入图片描述
    读写状态寄存器需要用到 MRS 和 MSR 指令,不能使用 LDR 和 STR 指令来对状态寄存器读写了;
    在这里插入图片描述

    1.2、设置 sp 指针

    sp 指针可以指向内部 RAM,也可以指向 DDR,这里设置成指向 DDR,sp 设置到哪里?以正点原子开发板为例,512 MB 的范围是 0x80000000~0x9FFFFFFF,假定设置的栈的大小是 2 MB(0x20000),A7 的栈增长方式为向下增长,所以要设置成 sp 指针指向 0x80200000;

    1.3、汇编跳转到 C 语言

    使用 b 指令跳转到 C 语言的函数,比如 main 函数;

    2、驱动编写

    在 /home/zsw/linux/IMX6ULL/board_drivers 目录下新建一个 2_ledc 的目录,打开 VScode,在 VScode 中打开文件夹 2_ledc,然后将工作区另存为 ledc,然后新建文件 start.s、main.c、main.h、Makefile 这四个文件;
    在这里插入图片描述
    start.s

    .global _start
    
    _start:
        // 设置处理器为 SVC 模式
        mrs r0, cpsr        // 读取 cpsr 到 r0
        bic r0, r0, #0x1f   // r0 & ~0x1f 清除 cpsr 的 bit4~0    bic  位清除指令
        orr r0, r0, #0x13   // r0 | 0x13 设置成 SVC 模式      orr 按位或
        msr cpsr, r0        // 把 r0 写入 cpsr
    
        // 设置 sp 指针
        ldr sp, =0x80200000
    
        // 跳转到 c 语言
        b main  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    main.c

    #include "main.h"
    
    // 使能外设时钟
    void clk_enable(void)
    {
        CCM_CCGR0 = 0xffffffff;
        CCM_CCGR1 = 0xffffffff;
        CCM_CCGR2 = 0xffffffff;
        CCM_CCGR3 = 0xffffffff;
        CCM_CCGR4 = 0xffffffff;
        CCM_CCGR5 = 0xffffffff;
        CCM_CCGR6 = 0xffffffff;
    }
    // 初始化 led
    void led_init(void)
    {
        SW_MUX_GPIO1_IO03 = 0x5;   // 复用为 GPIO1_IO03
        SW_PAD_GPIO1_IO03 = 0x10b0;   // 配置电气属性
    
        // GPIO 初始化
        GPIO1_GDIR = 0x8;   // 设置为输出
        GPIO1_DR = 0x0;   // 打开 led
    }
    // 短延时
    void delay_short(volatile unsigned int n)
    {
        while(n--){}
    }
    // 长延时,在 396MHz 下一次循环大概 1 ms
    void delay_ms(volatile unsigned int n)
    {
        while(n--)
        {
            delay_short(0x7ff);
        }
    }   
    // 打开 led
    void led_on(void)
    {
        GPIO1_DR &= ~(1<<3);   // bit3 清零
    }
    // 关闭 led
    void led_off(void)
    {
        GPIO1_DR |= (1<<3);   // bit3 置1
    }
    int main(void)
    {
        clk_enable();
        led_init();
    
        // led 闪烁
        while (1)
        {
            led_on();
            delay_ms(500);
            led_off();
            delay_ms(500);
        }
        return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    main.h

    #ifndef __MAIN_H
    #define __MAIN_H
    
    // 定义要使用的寄存器
    
    // CCM相关寄存器地址
    #define CCM_CCGR0           *((volatile unsigned int *)0x020c4068)
    #define CCM_CCGR1           *((volatile unsigned int *)0x020c406c)
    #define CCM_CCGR2           *((volatile unsigned int *)0x020c4070)
    #define CCM_CCGR3  			*((volatile unsigned int *)0x020c4074)
    #define CCM_CCGR4 			*((volatile unsigned int *)0x020c4078)
    #define CCM_CCGR5 			*((volatile unsigned int *)0x020c407c)
    #define CCM_CCGR6 			*((volatile unsigned int *)0x020c4080)
    
    // IOMUX相关寄存器地址 
    #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)
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Makefile

    objs = main.o start.o
    
    ledc.bin: $(objs)
    	arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o main.o -o ledc.elf
    	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin
    	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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、烧写验证

    在这里插入图片描述

    I.MX6ULL_MINI_ledc


    演示视频中可以看到 led 每隔 500 ms 闪烁,到这里也就基本结束了本节的内容,但是还要补充一点: 链接脚本,在 Makefile 中,我们使用了下面这行代码,

    	arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o main.o -o ledc.elf
    
    • 1

    上面的语句中我们是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件都会链接到以 0X87800000 为起始地址的区域,看名字就知道链接脚本主要用于链接的,用于描述文件应该如何被链接在一起形成最终的可执行文件,其主要目的是描述输入文件中的段如何被映射到输出文件中,并且控制输出文件中的内存排布,最简单的链接脚本可以只包含一个命“SECTIONS”,我们可以在这一个“SECTIONS”里面来描述输出文件的内存布局。我们一般编译出来的代码都包含在 text、data、bss 和 rodata 这四个段内;
    下新建一个名为“imx6u.lds”的文件,然后在此文件里面输入如下所示代码,

    SECTIONS{
        . = 0X87800000;
        .text :
        {
            start.o 
            main.o 
            *(.text)
        }
        .rodata ALIGN(4) : {*(.rodata*)} 
        .data ALIGN(4) : { *(.data) } 
        __bss_start = .; 
        .bss ALIGN(4) : { *(.bss) *(COMMON) } 
        __bss_end = .;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第 2 行设置定位计数器为 0X87800000,因为我们的链接地址就是0X87800000;第5行设置链接到开始位置的文件为start.o,因为 start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方;第 6 行是 main.o 这个文件,其实可以不用写出来,因为 main.o 的位置就无所谓了,可以由编译器自行决定链接位置;在第 11、13 行有“__bss_start”和“__bss_end”这两个东西?这个是什么呢?“__bss_start”和“__bss_end”是符号,第 11、13 这两行其实就是对这两个符号进行赋值,其值为定位符“.”,这两个符号用来保存.bss 段的起始地址和结束地址。前面说了.bss 段是定义了但是没有被初始化的变量,我们需要手动对.bss 段的变量清零的,因此我们需要知道.bss 段的起始和结束地址,这样我们直接对这段内存赋 0 即可完成清零。通过第 11、13 行代码,.bss 段的起始地址和结束地址就保存在了“__bss_start”和“__bss_end”中,我们就可以直接在汇编或者 C 文件里面使用这两个符号,修改 Makefile 中的这一行,

    	arm-linux-gnueabihf-ld -Timx6u.lds -o ledc.elf
    
    • 1

    在这里插入图片描述
    我们可以查看反汇编文件中的信息,链接脚本没啥问题;
    在这里插入图片描述
    现在重新编译工程,烧写验证结果是正确的,总结一下,本节从设置处理器模式开始,然后设置 sp 指针,跳转到 C 语言的 main,在 Makefile 中使用了变量,模式规则,自动化变量,最后还引入了链接脚本,东西比较多,需要时间去理解。

  • 相关阅读:
    云原生|kubernetes|kubernetes中的资源(一)---service详解
    RabbitMQ学习-第一部分
    Vue2 + Element UI 封装 Table 递归多层级列表头动态
    200PLC转以太网通讯远创智控模块在手机平板移动平台中的应用案例题
    小林coding网站---mysql基础-MySQL索引的数据结构和算法
    使用容器方式创建firecracker虚拟机
    vue制作自己的组件库(仿ElementUI)
    求解置换流水车间调度问题PFSP的关键路径-附Matlab源码
    Windows下Python环境下载与安装
    Python及Pycharm专业版下载安装教程(Python 3.11版)附JetBrains学生认证教程
  • 原文地址:https://blog.csdn.net/ZSW2027008838/article/details/137867941