栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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:

	
	mrs r0,cpsr
	bic r0,#0x1f 		
	orr r0,#0x13 		
	msr cpsr,r0 		
	
	ldr sp,=0x80200000  
	b main				
	

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

2、C语言文件
(1)main.h

#ifndef __MAIN_H
#define __MAIN_H


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


#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0x020E0068)
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0x020E02F4)


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


void clk_enable()
{
	CCM_CCGR0=0xffffffff;
	CCM_CCGR1=0xffffffff;
	CCM_CCGR2=0xffffffff;
	CCM_CCGR3=0xffffffff;
	CCM_CCGR4=0xffffffff;
	CCM_CCGR5=0xffffffff;
	CCM_CCGR6=0xffffffff;	
}


void led_init()
{
	
	SW_MUX_GPIO1_IO03=0x05;
	
	SW_PAD_GPIO1_IO03=0x10B0;
	
	GPIO1_GDIR=0x01<<3;
	
	GPIO1_DR=0x00;
}


void led_on()
{
	//将 GPIO1_DR 的 bit3 清零
	GPIO1_DR&=~(0x01<<3);
}


void led_off()
{
	//将 GPIO1_DR 的 bit3 置 1
	GPIO1_DR|=0x01<<3;
}


void delay_short(volatile unsigned int n)
{
	while(n--);
}


void delay(volatile unsigned int n)
{
	while(n--)
	{
		delay_short(0x07ff);
	}
}


int main()
{
	clk_enable(); 
	led_init(); 
	
	while(1) 
	{
		led_off(); 
		delay(500);
		
		led_on(); 
		delay(500);
	}
	
	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: clean

ledc.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:%.s
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

%.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

$^:所有依赖文件的集合
$@:所有目标文件的集合
$<:第一个依赖文件

五、下载验证

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

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

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

LED闪烁视频

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1038629.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号