嵌入式arm(三)arm裸机程序点灯+流水灯+环境文件解释
时间:2023-10-11 13:07:00
本节我们用点灯体验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:Exynos内核型号4412:cortex-A9,架构:armv7;
文件:开发板原理图,Soc数据手册;
环境:文件编写与编译:Linux;下载:超级终端hypertrm;串口下载
编译器:交叉编译器arm-linux-gcc。
二 简略了解SFR特殊功能寄存器
这张图应该很好理解(除了LED灯的方向相反),汇编中的寄存器是cpu操作中的寄存器只能驱动cpu如果要操作,应进行相应的操作cpu外于外设,您必须操作相应的寄存器,因为每个外设都有自己的寄存器来实现不同的功能,所以这些寄存器也被称为特殊功能寄存器,英文缩写SFR,sfr;
三 裸机开发点灯流程
1 看硬件原理图
这个实验是为了驱动一个LED,让它发光,首先要知道这一点LED如何在电路中连接,只有知道电路连接,才能知道如何使灯亮灭。一般来说,这取决于LED控制引脚连接Soc哪个引脚;
2 查手册配置Soc的引脚
对于引脚的相应配置,需要设置相应的寄存器。在芯片手册对应的模块中,需要找到寄存器的地址和用途(哪些是什么)
3 编程
也可以用C语言汇编,但用C更方便;
四 点灯
1 查原理图
1.1 查引脚连接
如图,LED三、四、五分别连接Soc的GPX1_0,GPF3_4,GPF3_5上;
注:GPX1_0:X组第一组第0引脚;
1.2 看驱动方式
(1) 只要三极管导通,二极管的阳极就会连接到高电平,阴极就会通过电阻和三极管接地,LED就亮,三极管截止,LED就灭;
(2) NPN三极管发射极接地,基极给高导通,给低截止;
(3) 可知:Soc引脚输出高电平灯,低电平灯;
2 查手册
2.1 找到的信息
使用两个寄存器:引脚模式设置GPX1CON,引脚输出电平设置GPX1DAT;
(1) GPX1_这个引脚对应的寄存器是GPX1CON;
(2) GPX1CON寄存器是32位,一个引脚需要4位配置,32位对应8个引脚,所以GPX1CON可以管理GPX1_0 GPX1_1…GPX1_7;
(3) GPX1_这个引脚对应的是GPX1CON(地址为0x11000c20)寄存器0-3位,引脚输出,设置为0x其他位置保持不变;
(4)GPX1DAT这个寄存器是管理的GPX1_0-GPX1_7的输出值,不是0就是1,GPX1_0对应的就是GPX1DAT第0GPX1_0输出1,就让GPX1DAT第0位为1;
2.2 搜索过程(手册截图)
2.2.1 GPX1_0
2.2.2 GPF3_4、GPF3_5
3 编程
3.1 点灯C文件
先点亮LED三、思路是配置GPX1_0为输出,输出1;
手册中查到GPX1CON的基地址为0x11000000,偏移量为0x0c所以它的地址是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;
}