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

本文介绍了如何使用C语言配合汇编编写嵌入式系统驱动程序,涉及处理器模式设置、SP指针配置和LED灯控制函数。首先,通过汇编启动并设置SVC模式和SP指向DDR,然后在C语言中实现时钟配置、LED初始化及开关控制功能。
摘要由CSDN通过智能技术生成

        上一篇使用汇编语言编写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卡里面。

 

 

宋宝华嵌入式 C/C++语言精华文章集锦 C/C+语言 struct 深层探索 ............................................................................2 C++中 extern "C"含义深层探索........................................................................7 C 语言高效编程的几招...............................................................................11 想成为嵌入式程序员应知道的 0x10 个基本问题 .........................................................15 C 语言嵌入式系统编程修炼...........................................................................22 C 语言嵌入式系统编程修炼之一:背景 ............................................................22 C 语言嵌入式系统编程修炼之二:软件架构 ........................................................24 C 语言嵌入式系统编程修炼之三:内存操作 ..........................................................30 C 语言嵌入式系统编程修炼之四:屏幕操作 ..........................................................36 C 语言嵌入式系统编程修炼之五:键盘操作 ..........................................................43 C 语言嵌入式系统编程修炼之六:性能优化 ..........................................................46 C/C++语言 void 及 void 指针深层探索 .................................................................50 C/C++语言可变参数表深层探索 .......................................................................54 C/C++数组名与指针区别深层探索 .....................................................................60 C/C++程序员应聘常见面试题深入剖析(1) ..............................................................62 C/C++程序员应聘常见面试题深入剖析(2) ..............................................................67 一道著名外企面试题的抽丝剥茧 ......................................................................74 C/C++结构体的一个高级特性――指定成员的位数 .......................................................78 C/C++中的近指令、远指针和巨指针 ...................................................................80 从两道经典试题谈 C/C++中联合体(union)的使用 ......................................................81 基于 ARM 的嵌入式 Linux 移植真实体验 ................................................................83 基于 ARM 的嵌入式 Linux 移植真实体验(1)――基本概念 ...........................................83 基于 ARM 的嵌入式 Linux 移植真实体验(2)――BootLoader .........................................96 基于 ARM 的嵌入式 Linux 移植真实体验(3)――操作系统 ..........................................111 基于 ARM 的嵌入式 Linux 移植真实体验(4)――设备驱动 ..........................................120 基于 ARM 的嵌入式 Linux 移植真实体验(5)――应用实例 ..........................................135 深入浅出 Linux 设备驱动编程 .......................................................................144 1.Linux 内核模块..............................................................................144 2.字符设备驱动程序 ...........................................................................146 3.设备驱动中的并发控制 .......................................................................151 4.设备的阻塞与非阻塞操作 .....................................................................157
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值