嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释

本节我们用点灯来体验一下arm的裸机程序开发;

cortex-A系统虽然比M系统更复杂,但是对于裸机开发,也就是寄存器寄存器寄存器,还是很好上手的(指点灯),那就来体验一把

文章目录

  • 一 环境介绍
  • 二 简略了解SFR特殊功能寄存器
  • 三 裸机开发点灯的流程
    • 1 看硬件的原理图
    • 2 查手册配置Soc的引脚
    • 3 编程
  • 四 点灯
    • 1 查原理图
      • 1.1 查引脚连接
      • 1.2 看驱动方式
    • 2 查手册
      • 2.1 查找到的信息
      • 2.2 查找过程(手册截图)
        • 2.2.1 GPX1_0
        • 2.2.2 GPF3_4、GPF3_5
    • 3 编程
      • 3.1 点灯的C文件
      • 3.2 start.s文件
      • 3.3 map.lds
      • 3.4 Makefile文件
      • 3.5 运行
  • 五 流水灯
    • 1 原理
    • 2 程序

一 环境介绍

使用开发板:FS4412,Soc:Exynos4412,内核型号:cortex-A9,架构:armv7;
文档:开发板原理图,Soc数据手册;
环境:文件编写及编译:Linux;下载程序:超级终端hypertrm;串口下载
编译器:交叉编译器arm-linux-gcc。

二 简略了解SFR特殊功能寄存器

嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第1张图片
这张图应该很好理解吧,(除了LED灯方向画反了),汇编里的寄存器是cpu中的寄存器,操作它们只能驱动cpu进行相应的操作,如果要操作cpu外面的外设,那就得去操作这些外设对应的寄存器,因为每个外设都有自己的寄存器,以实现不同的功能,所以这些寄存器又叫特殊功能寄存器,英文缩写SFR,sfr;

三 裸机开发点灯的流程

1 看硬件的原理图

本次实验是要驱动一个LED,使其发光,那么首先要知道这个LED在电路中是怎么接的,知道了电路接法才能知道怎么能使这个灯亮灭,一般就是要看LED的控制引脚连接的是Soc的哪个引脚;

2 查手册配置Soc的引脚

对这个引脚进行相应的配置,需要设置对应的寄存器,寄存器的用法在芯片手册对应的模块中,需要找到寄存器的地址和这个寄存器的用法(哪几位是干啥的)

3 编程

用C语言,汇编也能用,但是用C更方便;

四 点灯

1 查原理图

嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第2张图片

1.1 查引脚连接

如图,LED3,4,5分别接在Soc的GPX1_0,GPF3_4,GPF3_5上;
注:GPX1_0:第X个大组的第1个小组的第0个引脚;

1.2 看驱动方式

(1) 二极管的阳极接在高电平上,阴极通过电阻和三极管接地,只要三极管导通,LED就亮,三极管截止,LED就灭;
(2) NPN三极管发射极接地,基极给高导通,给低截止;
(3) 可知:Soc引脚输出高电平亮灯,低电平灭灯;

2 查手册

2.1 查找到的信息

有两个寄存器要用到:引脚模式设置GPX1CON,引脚输出电平设置GPX1DAT;
(1) GPX1_0这个引脚对应的寄存器为GPX1CON;
(2) GPX1CON寄存器是32位的,一个引脚需要4位进行配置,32位对应8个引脚,所以GPX1CON可以管理GPX1_0 GPX1_1…GPX1_7;
(3) GPX1_0这个引脚对应的是GPX1CON(地址为0x11000c20)寄存器的0-3位,要引脚输出,设置为0x01,其他位保持不变;
(4)GPX1DAT这个寄存器管理的是GPX1_0-GPX1_7的输出值,不是0就是1,GPX1_0对应的就是GPX1DAT的第0位,要想GPX1_0输出1,就让GPX1DAT的第0位为1就可以;

2.2 查找过程(手册截图)

2.2.1 GPX1_0

在这里插入图片描述
嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第3张图片
嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第4张图片
嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第5张图片

2.2.2 GPF3_4、GPF3_5

嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第6张图片
嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释_第7张图片

3 编程

3.1 点灯的C文件

先点亮LED3,思路就是配置GPX1_0为输出,且输出1;
手册中查到GPX1CON的基地址为0x11000000,偏移量为0x0c20,所以其地址为0x11000c20;因为是32位地址,可以转化为int型的指针然后来访问;即(int *)0x11000c20;访问时,再解引用*((int *)0x11000c20);同理,GPX1DAT的地址为:0x11000c24;
程序为:

//没有包含任何头文件,所以连寄存器都要自己去宏定义;
//用volatile不让编译器去优化,防止出现改了值,但是cache没更新cpu不更新导致没现象;
#define GPX1CON  *((volatile unsigned int *)0x11000c20)
#define GPX1DAT  *((volatile unsigned int *)0x11000c24)
int main()
{
	//设置GPX1_0引脚为输出功能,将GPX1CON的0-3位设置为0x01
	GPX1CON &= ~(0xf << 0);  //将0-3位清0
	GPX1CON |=  (0x1 << 0);  //将0-3设置为0x1
	//设置GPX1_0引脚输出1
	GPX1DAT |=  (0x1 << 0);   //将第0位设置为1
	return 0;
}

3.2 start.s文件

.global _start	@.global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用;
_start:
b main			@从_start进入后执行跳转到main;

3.3 map.lds

链接脚本是简单易用的,但包含了很多东西,本次实验用到的是很简单的一个;

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")	 /*elf32-littlearm指定输出可执行文件是elf格式,32位ARM指令,小端,写的三个分别表示为默认为小端;如果是小端,改为小端;如果是大端,改为小端*/ 
OUTPUT_ARCH(arm)/*指定输出可执行文件的平台为ARM*/ 
ENTRY(_start) /*指定输出可执行文件的起始代码段为_start*/ //即程序入口
SECTIONS				//使用'SECTIONS'来描述输出文件的内存布局.
{
/*指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成*/ 
	. = 0;	 			 /*从0x0位置开始,代码应当被载入到地址'0x10000'处*/
	. = ALIGN(4);		/*代码以4字节对齐*/
	.text :
	{
		*(.text)		/*其它代码部分*/ 
	}
    . = ALIGN(4);
    .data : 			 
	{ *(.data) }		/*指定读/写数据段*/ 
    . = ALIGN(4);
    .bss :
    { *(.bss) }
}

简单讲解:
(1) .=0x0;对一个特殊的符号’.‘赋值, 这是一个定位计数器. 如果你没有以其它的方式指定输出节的地址, 那地址值就会被设为定位计数器的现有值. 在’SECTIONS’命令的开始处, 定位计数器拥有值’0’;
每个输出节之前都可以加上对定位计数器进行赋值,来表示这个输出节在内存中的起始位置;
如果不写,那么默认输出节跟在上一个之后,.bss就紧跟在.data内存区后;
(2) . = ALIGN(4)表示以4字节对齐;
(3) .text :定义一个输出节,‘.text’. 冒号是语法需要,现在可以被忽略. 节名后面的花括号中,你列出所有应当被放入到这个输出节中的输入节的名字. '‘是一个通配符,匹配任何文件名. 表达式’(.text)‘意思是所有的输
入文件中的’.text’输入节.
(4) 运行一个程序时第一个被执行到的指令称为"入口点",使用’ENTRY’连接脚本命令来设置入口点;

3.4 Makefile文件

下面的这个makefile文件将start.s和mian.c编译为目标文件,然后再加上链接脚本,链接成一个.elf系统文件,再用objcopy生成.bin可执行的裸机程序,最后用objdump生成反汇编文件,以便查看;
注意:start的所有操作应该在main之前;因为程序入口是start而不是main;

CROSS = arm-none-linux-gnueabi-	 #交叉编译器
CC=$(CROSS)gcc					#交叉编译命令
LD=$(CROSS)ld					#交叉编译链接命令,将所有的.o文件链接生成可执行文件(.elf)
OBJCOPY=$(CROSS)objcopy			#被用来复制一个目标文件的内容到另一个文件中,可用于不同源文件的之间的格式转换,实际上elf文件中的调试信息被删掉了;
all:
	$(CC) -g -c -o start.o start.s 				#-g支持gdb,-c生成目标文件,-o改目标文件名
	$(CC) -g -c -o main.o main.c  
	$(LD) start.o main.o -Tmap.lds -o led.elf	#-T指定链接脚本,
	$(OBJCOPY)  -O binary -S led.elf led.bin	# 用.elf文件生成.bin文件
	$(CROSS)objdump -D led.elf > led.dis		#objdump 将.elf文件进行反汇编生成反汇编文件(.dis),-D是反汇编所有部分,“>”是把结果写入到.dis文件中,如果没有">",那么反汇编语句直接到命令行窗口;
	 
clean:
	rm -f *.o *.elf *.bin *.dis

3.5 运行

使用make启动makefile生成bin文件,下载到开发板就可以看到现象了;不同开发板下载方式和软件都不一样,这里不做讨论;

五 流水灯

1 原理

在四中,我们已经点亮了一个灯,接下来点亮LED4和LED5,再让它们循环地亮和灭即可;

2 程序

这个程序只是把前面的东西组合包装了以下,实现了流水灯,不再作解释;

#define GPX1CON  *((volatile unsigned int *)0x11000c20)
#define GPX1DAT *((volatile unsigned int *)0x11000c24)
#define GPF3CON  *((volatile unsigned int *)0x114001e0)
#define GPF3DAT *((volatile unsigned int *)0x114001e4)

#define led3on GPX1DAT |= (0x1 << 0)   //将第0位设置为1
#define led3of GPX1DAT &=~(0x1 << 0)   //将第0位清0
#define led4on GPF3DAT |= (0x1 << 4)  
#define led4of GPF3DAT &=~(0x1 << 4)   
#define led5on GPF3DAT |= (0x1 << 5)   
#define led5of GPF3DAT &=~(0x1 << 5)   

void delay()
{
	int i=0xfffff;
	while(i--);
}

int main()
{
	//设置GPX1_0引脚为输出功能,将GPX1CON的0-3位设置为0x01
	GPX1CON &= ~(0xf << 0);  //将0-3位清0
	GPX1CON |=  (0x1 << 0);  //将0-3设置为0x1

	GPF3CON &= ~(0xff << 16);  //将16-23位清0
	GPF3CON |=  (0x11 << 16);  //将16-23设置为0x11

	while(1)
	{
		led3on;delay();		
		led3of;delay();		
		led4on;delay();		
		led4of;delay();		
		led5on;delay();
		led5of;delay();
	}
	return 0;
}

你可能感兴趣的:(Arm,arm,单片机,arm开发)