上一部分我们已经整理出了所需寄存器的基地址、初始值,因为我们要给寄存器赋值,其实就是向指定地址写入内容。下面所有用到的基地址和初始化值都在上一篇总结好了。
【裸机驱动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:
/* 开始编写汇编代码 */
1、初始化时钟源
上一篇《初始化时钟源》的末尾已经列举了哪些寄存器是需要被初始化的,以及初始化的值是多少,全都一一列举了。一共有 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 复用
基地址和初始化值:
/* 2. 设置IO复用 */
ldr r1, =0x20E0068
ldr r2, =0x5 @ 指定为 GPIO 功能
str r2, [r1]
3、初始化GPIO(设置电气属性)
基地址和初始化值:
/* 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、GPIO 输出
基地址和初始化值:
/* 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 工具来进行交叉编译,所以你的虚拟机上需要事先装好交叉编译工具链,交叉编译的步骤:
- 源文件 .s 编译生成 .o 文件
- 链接 .o 文件
- 转换格式(转换成二进制文件)
- 反汇编
1、编译生成 .o 文件
led.bin: led.s
arm-linux-gnueabihf-gcc -c -o led.o $^
- arm-linux-gnueabihf-gcc:表示arm环境下的编译器
- -c:表示到汇编阶段就停下来,此时会生成 .o 文件
- -o:表示输出文件的名字
- $^:makefile内置变量,表示依赖文件,即冒号右边的所有内容(led.s)
2、链接 .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
- -Ttext:指定的就是链接地址
- -o:生成 .elf 文件。保存了足够的系统相关信息使它能支持不同平台上的交叉编译和交叉链接,可移植性很强。既可用于编译链接,也可用于加载执行。
3、格式转换
上面得到的可以看做是一种比较通用的文件,既可以用于编译链接,又可用于程序执行,但是下面我们要把这个文件用于程序执行,生成可以在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 $@
- -O:指定以什么格式输出,binary 表示以二进制输出
- -S:表示不要复制源文件中的重定位信息和符号信息
- -g:表示不要复制调试信息
4、反汇编
其实到上面“格式转换”就可以结束了,执行这一步的目的是,方便后面调试,经过反汇编,我们可以通过汇编代码来调试代码,比如查看函数实际堆栈调用情况、实际初始化顺序是否正常等。
前面也说到,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
三、烧写到SD卡
这里使用的是正点原子官方的 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是分开的)
四、选择运行地址为 0x87800000 的原因
运行程序时,一般都会先把程序拷贝到内存,然后CPU再从内存中逐指令读取。内存分为内部RAM 和 外部DDR。
- RAM:CPU内部的一段可用内存,imx6ull 内部RAM的大小为 128K(0X900000~0X91FFFF)
- DDR:CPU外的存储器,封装在SOC 中,DDR的大小为 256M 或者 512M(虽然看着有2048,但是受到总线约束,CPU可访问的大小是256M)
因此,这里我们选择DDR,0x87800000 就在DDR内存范围内,这样也方便后续的 uboot 移植。
五、完整汇编代码和Makefile文件
1、汇编代码
.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
2、Makefile文件
如果将交叉编译工具链的根路径添加到了环境变量,那么这里就无需指定交叉编译工具链的根路径。
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