上一部分我们已经整理出了所需寄存器的基地址、初始值,因为我们要给寄存器赋值,其实就是向指定地址写入内容。下面所有用到的基地址和初始化值都在上一篇总结好了。
【裸机驱动LED】使用汇编代码驱动LED(一)—— 寄存器解析篇_仲夏夜之梦~的博客-CSDN博客【裸机驱动LED】使用汇编代码驱动LED(一)—— 寄存器解析篇https://blog.csdn.net/challenglistic/article/details/131047800?spm=1001.2014.3001.5501
目录
一、编写汇编代码
1、初始化时钟源
2、设置 IO 复用
3、初始化GPIO(设置电气属性)
4、GPIO 输出
二、编译汇编代码
1、编译生成 .o 文件
2、链接 .o 文件(确定链接地址 / 运行地址)
3、格式转换
4、反汇编
三、烧写到SD卡
四、选择运行地址为 0x87800000 的原因
五、完整汇编代码和Makefile文件
1、汇编代码
2、Makefile文件
汇编代码对应的是 .s 文件,每个 .s 文件都是以 _start 开头,所以汇编代码的起始模板为:
.global _start
_start:
/* 开始编写汇编代码 */
上一篇《初始化时钟源》的末尾已经列举了哪些寄存器是需要被初始化的,以及初始化的值是多少,全都一一列举了。一共有 7 个时钟源,每个时钟源的基地址之间相差 4 个字节,初始化的值都是 0xFFFFFFFF。下面给出两种写法
写法一:循环初始化
.global _start
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/* 时钟源初始化 */
ldr r1, =0x020C4068 @ 保存 CCGR0 寄存器基地址到寄存器 r1
ldr r2, =0xFFFFFFFF @ 保存初始值到寄存器 r2
mov r3, #7 @ r3 = 7, 七个时钟源,初始化七次
bl init @ 跳转到 init,lr 寄存器会保存下一条指令的地址
init:
cmp r3, #0 @ 判断 r3 寄存器是否为 0
moveq pc, lr @ lr 寄存器保存了回去的地址,如果 r3 == 0,说明已经初始化了7次
strgt r2, [r1], #4 @ 如果 r3 大于0,将 r2 寄存器的内容保存到 r1,然后 r1 自增4
sub r3, r3, #1 @ r3 寄存器自减
b init
写法二:暴力初始化
.global _start
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/* 1. 使能所有时钟 */
ldr r1, =0X020C4068 /* CCGR0 */
ldr r2, =0XFFFFFFFF
str r2, [r1]
ldr r1, =0X020C406C /* CCGR1 */
str r2, [r1]
ldr r1, =0X020C4070 /* CCGR2 */
str r2, [r1]
ldr r1, =0X020C4074 /* CCGR3 */
str r2, [r1]
ldr r1, =0X020C4078 /* CCGR4 */
str r2, [r1]
ldr r1, =0X020C407C /* CCGR5 */
str r2, [r1]
ldr r1, =0X020C4080 /* CCGR6 */
str r2, [r1]
基地址和初始化值:
/* 2. 设置IO复用 */
ldr r1, =0x20E0068
ldr r2, =0x5 @ 指定为 GPIO 功能
str r2, [r1]
基地址和初始化值:
/* 3、配置GPIO1_IO03的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 keeper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
ldr r1, =0x20E02F4
ldr r2, =0x10B0
str r2, [r1]
基地址和初始化值:
/* 4.GPIO1的第3引脚设为输出 */
ldr r1, =0x209C004
ldr r2, =0x00000008
str r2, [r1]
/* 5.GPIO1的第3引脚输出低电平 */
ldr r1, =0x209C000
ldr r2, =0
str r2, [r1]
这里我们使用 Makefile 工具来进行交叉编译,所以你的虚拟机上需要事先装好交叉编译工具链,交叉编译的步骤:
led.bin: led.s
arm-linux-gnueabihf-gcc -c -o led.o $^
实际上我们可能生成很多个 .o 文件,我们将这些 .o 文件链接到内存中的指定位置,这里确定的是最终可执行文件的运行地址。
比如二进制代码一开始保存在SD卡的0x08000000,加载到内存以后放在0x87800000,这里的0x08000000就是存储地址,0x87800000就是运行地址。(允许存储地址和运行地址相同,即一开始就保存到内存)选择 0x87800000 的原因参考本文的第三部分。
led.bin: led.s
arm-linux-gnueabihf-gcc -c -o led.o $^
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
上面得到的可以看做是一种比较通用的文件,既可以用于编译链接,又可用于程序执行,但是下面我们要把这个文件用于程序执行,生成可以在ARM环境下运行的二进制执行文件。
arm-linux-gnueabihf-objcopy 可以看做是一个格式转换工具,我们将用它将 elf 文件转换成 bin 文件。
led.bin: led.s
arm-linux-gnueabihf-gcc -c -o led.o $^
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf $@
其实到上面“格式转换”就可以结束了,执行这一步的目的是,方便后面调试,经过反汇编,我们可以通过汇编代码来调试代码,比如查看函数实际堆栈调用情况、实际初始化顺序是否正常等。
前面也说到,elf 文件包含了足够的源文件信息,当然也可以用于这里的反汇编。
led.bin: led.s
arm-linux-gnueabihf-gcc -c -o led.o $^
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf $@
arm-linux-gnueabihf-objdump -D led.elf > led.dis
这里使用的是正点原子官方的 imxdownload 工具,注意,就算生成了 bin 文件也不能直接放到SD卡里,还需要添加一些头部信息(包含初始化DDR等操作)
首先,要把imxdownload 添加到当前工作目录下,01、例程源码—01、例程源码—01、裸机例程 —01_leds 就可以找到 imxdownload
其次,将 led.bin 烧写到SD卡
./imxdownload led.bin /dev/sdb # 将led.bin烧写到 /dev/sdb
最后,将拨码开关拨到指定位置,插入SD卡,给开发板上电。如果你发现,过了一会,开发板亮红灯说明运行成功。(本人的是mini版,LED0和LED1是同一个;alpha版的LED0 和LED1是分开的)
运行程序时,一般都会先把程序拷贝到内存,然后CPU再从内存中逐指令读取。内存分为内部RAM 和 外部DDR。
因此,这里我们选择DDR,0x87800000 就在DDR内存范围内,这样也方便后续的 uboot 移植。
.global _start
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/* 1.时钟源初始化 */
ldr r1, =0x020C4068 @ 保存 CCGR0 寄存器基地址到寄存器 r1
ldr r2, =0xFFFFFFFF @ 保存初始值到寄存器 r2
mov r3, #7
bl init @ 跳转到 init,LR 寄存器会被设置
/* 2.设置IO复用 */
ldr r1, =0x20E0068
ldr r2, =0x5
str r2, [r1]
/* 3.初始化GPIO */
ldr r1, =0x20E02F4
ldr r2, =0x10B0
str r2, [r1]
/* 4.GPIO1的第3引脚设为输出 */
ldr r1, =0x209C004
ldr r2, =0x00000008
str r2, [r1]
/* 5.GPIO1的第3引脚输出低电平 */
ldr r1, =0x209C000
ldr r2, =0
str r2, [r1]
/*
* 描述: loop死循环
*/
loop:
b loop
init:
cmp r3, #0 @ 判断 r3 寄存器是否为 0
moveq pc, lr @ lr 寄存器保存了回去的地址
strgt r2, [r1], #4 @ 将 r2 寄存器的内容保存到 r1,然后 r1 自增4
sub r3, r3, #1 @ r3 寄存器自减
b init
如果将交叉编译工具链的根路径添加到了环境变量,那么这里就无需指定交叉编译工具链的根路径。
TOOLCHAIN_PATH := /home/pigeon/workspace/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
CC := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-gcc
LD := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-ld
OBJCOPY := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objcopy
OBJDUMP := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objdump
led.bin: led.s
$(CC) -c -o led.o $^
$(LD) -Ttext 0X87800000 led.o -o led.elf
$(OBJCOPY) -O binary -S -g led.elf $@
$(OBJDUMP) -D led.elf > led.dis
.PHONY:clean
clean:
rm -rf *.o *.elf *.dis *.bin