700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > I.MX6ULL ARM裸机开发---C语言LED实验

I.MX6ULL ARM裸机开发---C语言LED实验

时间:2021-11-21 03:55:03

相关推荐

I.MX6ULL ARM裸机开发---C语言LED实验

一、引言

考虑到工作效率,嵌入式驱动开发很少用汇编,大部分是用C语言进行开发。

嵌入式驱动开发开始部分就可以用C语言吗?

当然不是!在开始部分用汇编来初始化一下 C 语言环境,比如初始化 DDR、设置堆栈指针 SP 等等,当这些工作都做完以后就可以进入 C 语言环境,也就是运行 C 语言代码,一般都是进入 main 函数。有两部分文件需要完成:

1、汇编文件

汇编文件用来完成C语言环境搭建。

2、C语言文件

C语言用来完成业务代码。

二、原理概述

1、程序状态寄存器

所有的处理器模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问。CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。

M[4:0]:处理器模式控制位,含义如下表所示:

2、常用汇编指令

(1)MRS指令

MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令。

MRS R0, CPSR @将特殊寄存器 CPSR 里面的数据传递给 R0,即 R0=CPSR

(2)MSR指令

MSR 指令和 MRS 刚好相反,MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用 MSR。

MSR CPSR, R0 @将 R0 中的数据复制到 CPSR 中,即 CPSR=R0

(3)LDR指令

LDR 主要用于从存储加载数据到寄存器 Rx 中,LDR 也可以将一个立即数加载到寄存器 Rx中,LDR 加载立即数的时候要使用“=”,而不是“#”。

LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004 LDR R1, [R0] @读取地址 0X0209C004 中的数据到 R1 寄存器中

(4)STR指令

LDR 是从存储器读取数据,STR 就是将数据写入到存储器中。

LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004 LDR R1,=0X20000002 @R1 保存要写入到寄存器的值,即 R1=0X20000002 STR R1, [R0]@将 R1 中的值写入到 R0 中所保存的地址中

3、栈初始化

栈初始化是C语言环境初始必不可少的部分。ARM采用降栈的方式,SP为堆栈指针。

通过 ldr 指令设置 SVC 模式下的 SP 指针=0X80200000,因为 I.MX6U-ALPHA 开发板上的DDR3 地 址 范 围 是0X80000000-0XA0000000(512MB) 或者0X80000000-0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。

三、代码编写

1、汇编文件(start.s)

.global _start /* 全局标号 *//** 描述: _start 函数,程序从此函数开始执行,此函数主要功能是设置 C * 运行环境。*/_start:/* 进入 SVC 模式 */mrs r0,cpsrbic r0,#0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */orr r0,#0x13 /* r0 或上 0x13,表示使用 SVC 模式 */msr cpsr,r0 /* 将 r0 的数据写入到 cpsr_c 中 */ldr sp,=0x80200000 /* 设置栈指针 */b main/* 跳转到 main 函数 */

通过b main命令跳转到 main 函数,main 函数就是 C 语言代码了。

2、C语言文件

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

(2)main.c

#include "main.h"/** @description : 使能 I.MX6U 所有外设时钟* @param : 无 * @return : 无*/void clk_enable(){CCM_CCGR0=0xffffffff;CCM_CCGR1=0xffffffff;CCM_CCGR2=0xffffffff;CCM_CCGR3=0xffffffff;CCM_CCGR4=0xffffffff;CCM_CCGR5=0xffffffff;CCM_CCGR6=0xffffffff;}/** @description : 初始化LED 对应的 GPIO* @param : 无 * @return : 无*/void led_init(){/* 1、初始化 IO 复用, 复用为 GPIO1_IO03 */SW_MUX_GPIO1_IO03=0x05;/* 2、配置 GPIO1_IO03 的 IO 属性 */SW_PAD_GPIO1_IO03=0x10B0;/* 3、初始化 GPIO, GPIO1_IO03 设置为输出 */GPIO1_GDIR=0x01<<3;/* 4、设置 GPIO1_IO03 输出低电平,打开 LED0 */GPIO1_DR=0x00;}/** @description : 打开LED* @param : 无 * @return : 无*/void led_on(){//将 GPIO1_DR 的 bit3 清零GPIO1_DR&=~(0x01<<3);}/** @description : 关闭LED* @param : 无 * @return : 无*/void led_off(){//将 GPIO1_DR 的 bit3 置 1GPIO1_DR|=0x01<<3;}/** @description : 短时间延时函数* @param - n : 要延时循环次数(空操作循环次数,模式延时)* @return : 无*/void delay_short(volatile unsigned int n){while(n--);}/** @description : 延时函数,在 396Mhz 的主频下延时时间大约为 1ms* @param - n : 要延时的 ms 数* @return : 无*/void delay(volatile unsigned int n){while(n--){delay_short(0x07ff);}}/** @description : main 函数* @param : 无* @return : 无*/int main(){clk_enable(); /* 使能所有的时钟 */led_init(); /* 初始化 led */while(1) /* 死循环 */{led_off(); /* 关闭 LED */delay(500);/* 延时 500ms */led_on(); /* 打开 LED */delay(500);/* 延时 500ms */}return 0;}

我们需要将start.o文件设置链接到开始位置,因为 start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。main 函数就是我们的主函数了,在 main 函数中先调用函数 clk_enable()和 led_init()来完成时钟使能和 LED 初始化,最终在 while(1)循环中实现 LED 循环亮灭,亮灭时间大约是 500ms。

四、编译代码

1、链接脚本

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

上面语句中我们是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件都会链接到以 0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区域,或者叫做段里面,比如在 Linux 里面初始化函数就会放到 init 段里面。因此我们需要能够自定义一些段,这些段的起始地址我们可以自由指定,同样的我们也可以指定一个文件或者函数应该存放到哪个段里面去。要完成这个功能我们就需要使用到链接脚本,看名字就知道链接脚本主要用于链接的,用于描述文件应该如何被链接在一起形成最终的可执行文件。其主要目的是描述输入文件中的段如何被映射到输出文件中,并且控制输出文件中的内存排布。比如我们编译生成的文件一般都包含 text 段、data 段等等。

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 = .;}

.bss 段的起始地址和结束地址就保存在了“__bss_start”和“__bss_end”中,我们就可以直接在汇编或者 C 文件里面使用这两个符号。

2、Makefile

objs := start.o main.o.PHONY: cleanledc.bin:$(objs)arm-linux-gnueabihf-ld -Timx6ul.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:%.sarm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<%.o:%.Sarm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<%.o:%.carm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<clean:rm -rf *.o ledc.bin ledc.elf ledc.dis

$^:所有依赖文件的集合

$@:所有目标文件的集合

$<:第一个依赖文件

五、下载验证

使用 imxdownload 将编译出来的 ledc.bin 烧写到 SD 卡中,命令如下:

chmod +x imxdownload//给予 imxdownload 可执行权限,一次即可 ./imxdownload ledc.bin /dev/sdc//烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 设备里面

烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板,如果代码运行正常的话 LED0 就会以 500ms 的时间间隔亮灭。

LED闪烁视频

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。