【Linux学习】正点原子裸机篇 C语言LED实验实现

        上一篇使用汇编语言编写LED等驱动实验,在实际工作中较少使用汇编编写嵌入式驱动,毕竟汇编过于底层,难度较大。绝大部分情况下都是使用C语言编写主体程序,只是开始部分使用汇编来初始化C语言环境,比如初始化DDR、设置堆栈指针SP等。因此可理解为下面三部分:

1)设置处理器模式

        设置6ULL的处于SVC模式下,设置CPSR寄存器的bit4-0,也就是M[4:0]为10011=0X13。读写状态寄存器需要用到MRS和MSR指令。MRS将CPSR寄存器数据读出到通用寄存器里,MSR指令将通用寄存器的值写入到CPSR寄存器里面。

2)设置sp指针(C语言需要出栈和入栈)

        Sp可以指向内部RAM,可以指向DDR,目前指向DDR,Sp设置到哪里?512MB的范围是0X80000000-0X9FFFFFFF。栈大小,0x200000=2MB。处理器栈增长方式,对于A7而言向下增长,设置sp指向0x80200000。

3)C语言文件编写

        C语言文件是我们要完成的业务层代码,即实际例程要完成的功能。

1. 实验程序编写

        新建 VScode 工程,工程名字为“ledc”,新建三个文件:start.Smain.c main.h。其中 start.S 是汇编文件,main.c main.h C 语言相关文件。

  1.1 start.S 是汇编文件编写:

.global _start

_start:
/*设置 处理器进入SVC模式*/
    mrs r0, cpsr        /*读取cpsr到r0 */
    bic r0, r0, #0x1f   /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */
    orr r0, r0, #0x13   /* r0 或上 0x13,表示使用 SVC 模式 */
    msr cpsr, r0        /*将r0写入到cpsr */

/*设置SP指针 */
  ldr sp, 0x80200000   /*已经sp设置到ddr里面 */
  b main               /*跳转到C语言main函数 */

/*第1行是定义全局标号_start;

第3行是标号_start开始的地方,相当于_start函数;

第5-8行是设置处理器进入SVC模式;

第11行是通过ldr指令设置SVC模式下SP指针=0X80200000;

第12行是通过b指令,跳转到C语言函数,比如main函数;*/

  1.2 main.h 文件编写

        以宏定义的形式定义了要使用到的所有寄存器,后面的数字代表其地,比如 CCM_CCGR0 寄存器的地址就是 0X020C4068

#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)
#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

  1.3 main.c文件编写

        main.c文件共6个函数:

        1.clk_enable 函数是使能 CCGR0~CCGR6 所控制的所有外设时钟;

        2. led_init 函数是初始化 LED 灯所使用的 IO,包括设置 IO 的复用功能、IO 的属性配置和 GPIO 功能,最终控制 GPIO 输出低电平来打开 LED 灯;

        3. led_on led_off ,用来控制 LED 灯的亮灭的;

        4.delay_short()delay() 这两个函数是延时函数,delay_short()函数是靠空循环来实现延时的,在396Mhz工作主频下实现约1ms的延时.

#include "main.h"
/* 使能外设时钟函数*/
void clk_enable(void)
{
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;
}

/*初始化led函数
    bit 16:0 HYS 关闭
    bit [15:14]: 00 默认下拉
    bit [13]: 0 kepper 功能
    bit [12]: 1 pull/keeper 使能
    bit [11]: 0 关闭开路输出
    bit [7:6]: 10 速度 100Mhz
    bit [5:3]: 110 R0/6 驱动能力
    bit [0]: 0 低转换率*/
void led_init(void)
{
    SW_MUX_GPIO1_IO03 = 0x5;     /*复用为GPIO1-IO03*/
    SW_PAD_GPIO1_IO03 = 0X10B0;   /*设置GPIO1-IO03电气属性*/

    /*GPIO初始化*/
    GPIO1_GDIR = 0X8;    /*设置为输出*/
    GPIO1_DR = 0X0;      /*打开led灯*/
}
/*短延时*/
void delay_short(volatile unsigned int n)
{
    while(n--){}
}
/*延迟,一次循环大概是1ms,n:延时ms数,在主频396MHz*/
void delay(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*/

    /*设置LED*/
    while(1){
        led_on();
        delay(500);

        led_off();
        delay(500);
    }
    return 0;
}

2. 编译下载 

  2.1 编写Makefile

objs = start.o main.o

ledc.bin: $(objs)
	arm-linux-gnueabihf-ld -Timx6u.lds -o ledc.elf $^
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

%.o:%.S
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
 
%.o:%.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

/*
第 1 行: 定义了一个变量 objs,objs 包含着要生成 ledc.bin 所需的材料:start.o 和 main.o, start.o 一定要放到 最前面!因为在链接的时 start.o 要在最前面,start.o 是最先要执行的文件!
 
第 3 行: 默认目标,目的是生成最终的可执行文件ledc.bin,ledc.bin 依赖 start.o 和 main.o 。

第 4 行: 是使用 arm-linux-gnueabihf-ld 进行链接,链接起始地址是 0X87800000,但是这一行 用到了自动变量“$^”,“$^”的意思是所有依赖文件的集合,在这里就是 objs 这个变量的值: arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^表示使用 imx6ul.lds 这个链接脚本文件

第 5 行: 使用 arm-linux-gnueabihf-objcopy 来将 ledc.elf 文件转为 ledc.bin,本行也用到了自动变量 “$@”,“$@”的意思是目标集合,在这里就是“ledc.bin”,那么本行就相当于: 
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin

第 6 行: 使用 arm-linux-gnueabihf-objdump 来反汇编,生成 ledc.dis 文件。 

第 8~13行: 针对不同的文件类型将其编译成对应的.o 文件,其实就是汇编.s(.S)和.c 文件。

第 14行: 工程清理规则,通过命令“make clean”就可以清理工程

*/

  2.2  链接脚本文件

SECTIONS{
    . = 0x87800000  //链接到0x87800000,从这里开始
    .text :
    {
        start.o   //第一个代码部分
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)}     //指定只读数据段 4字节对齐
    .data ALIGN(4) : {*(.data)}          //指定读/写数据段 4字节对齐
    __bss_start=.                        //把__bss_start定义为当前位置(起始)
    .bss ALIGN(4) : {*(.bss)*(COMMON)}
    __bss_end=.;                         //把__bss_end定义为当前位置(终地址)
}
    /*. 代表当前
      .text 代表代码段
      .rodata 只读数据段
      .data读/写数据段
      ALIGN(4) 四字节对齐
      .bss 未初始化的数据段
第 1 行:关键字“SECTIONS”;

第 2 行:对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位计数器为0。赋值代表代码链接到以 0X10000000 为起始地址的地方开始;

第 3 行的:“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链 接到“.text”这个段里面的所有文件,“*(.text)”中的“*”是通配符,表示所有输入文件的.text 段都放到“.text”中;

第 4 行:重新调整定位计数器为 0X30000000;

第 5 行:与第3行相同,ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐;

第 6 行:定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数据就是那些定义了但是没有被初始化的变量;

第11-12行:__bss_start” 和“__bss_end”是符号,第 11、13 这两行其实就是对这两个符号进行赋值,其值为定位符“.”, 这两个符号用来保存.bss 段的起始地址和结束地址

  2.3 make和烧写SD卡

        烧写到SD卡中:

        给予imxdownload可执行权限:chmod 777 imxdownload

        烧写命令:./imxdownload led.bin  /dev/sdb

        Imxdownload会向led.bin添加一个头部,生成新的load.imx文件,这个load.imx文件就是最终烧写到SD卡里面。

【Linux学习】正点原子裸机篇 C语言LED实验实现_第1张图片

 

 

你可能感兴趣的:(Linux学习,c语言,linux,arm)