一、i.MX6ULL开发板介绍
今天开始从 ARM 内核理论逐渐进入到 i.MX6ULL 裸机开发,主要学习如何在开发板上通过 ARM 汇编程序控制硬件。
使用的开发板为 IMX6ULL-Mini 开发板,核心处理器为 NXP 的 i.MX6ULL。
开发板的基本组成:
IMX6ULL-Mini开发板
│
├── 核心板
│ ├── CPU
│ ├── DDR3L RAM
│ └── eMMC
│
└── 底板
├── LED
├── GPIO
├── UART
├── PWM
└── 其他外设
二、i.MX6ULL核心板
i.MX6ULL 采用:
CPU:NXP i.MX6ULL
架构:ARM
内核:Cortex-A7
主要参数:
-
Cortex-A7 单核处理器;
-
主频为 528MHz(工业级)或 800MHz(商业级);
-
BGA 封装;
-
512MB DDR3L RAM;
-
8GB eMMC;
-
支持 SD 卡、NAND、eMMC 等启动方式。
因此:
Cortex-A7 → ARM处理器内核
i.MX6ULL → NXP生产的SoC
IMX6ULL-Mini → 开发板
三、开发板底板上的LED
今天主要观察和使用了开发板上的 LED 模块。
底板上有:
1. 红色LED
红色 LED 是用户可以控制的 LED,可以通过程序控制其亮灭。
2. 蓝色LED
蓝色 LED 是电源指示灯,用于表示开发板是否正常供电。
3. 510Ω限流电阻
LED 前串联了一个 510Ω 电阻。
主要作用:
限制电流
↓
防止LED电流过大
↓
保护LED灯珠
四、开发环境搭建
今天使用 Visual Studio Code 进行代码编写。
1. 安装VS Code
VS Code 可以用于:
-
编写 ARM 汇编程序;
-
编写启动代码;
-
编写 Makefile;
-
查看程序源码。
为了方便编写汇编代码,可以安装:
Arm Assembly
插件,实现 ARM 汇编代码高亮。
2. 创建工程目录
创建 ARM 裸机工程:
D:\IMX6ULL\led_asm
工程主要用于完成 LED 点亮实验。
五、启动代码start.S
ARM 裸机程序启动时,需要首先执行启动代码。
创建:
start.S
这里需要注意:
.S
是大写的 S。
大写 .S 文件会经过预处理,因此后续可以使用一些宏定义等功能。
1. 设置程序入口
启动代码中使用:
.global _start
将 _start 声明为全局符号。
因此:
_start
↓
程序启动入口
处理器启动后从程序入口开始执行。
六、i.MX6ULL内存空间
当前学习中涉及的 ROM 地址空间:
0x8000 0000
大小:
512MB
可以理解为:
ROM起始地址
↓
0x8000 0000
↓
程序运行空间
裸机程序最终需要被放置到处理器能够访问的指定地址空间中。
七、CPS指令
启动代码中需要设置 ARM 处理器的工作状态,因此学习了 CPS 指令。
基本格式:
CPS<effect> <iflags>{, #<mode>}
也可以写成:
CPS #<mode>
1. 禁止中断
例如:
cpsid i
其中:
i → IRQ中断
f → FIQ中断
a → 异常相关控制
cpsid i 可以用于禁止 IRQ。
2. 设置处理器模式
例如:
cps #0x12
以及:
cps #0x1F
通过修改处理器的工作模式,可以为不同模式设置相应的运行环境。
3. 设置栈指针
例如:
ldr sp, =0x820000000
以及:
ldr sp, =0x840000000
通过 LDR 将指定地址加载到 SP,完成栈指针初始化。
4. 开启中断
例如:
cpsie i
表示重新允许 IRQ。
因此,启动代码中可以形成:
禁止中断
↓
设置工作模式
↓
初始化SP
↓
完成系统基本初始化
↓
开启中断
八、点亮LED需要完成哪些工作
要通过程序控制 i.MX6ULL 的 LED,不能直接对 LED 写数据。
首先需要对 GPIO 对应的 SOC 引脚进行配置。
主要包括三个方面:
GPIO引脚初始化
│
├── ① 复用功能配置
├── ② 电气特性配置
└── ③ GPIO方向配置
之后才能通过 GPIO 数据寄存器控制 LED。
九、IOMUXC复用功能配置
i.MX6ULL 的一个引脚可能具有多个功能。
因此使用 GPIO 前,需要首先设置该引脚的复用功能。
对应模块:
IOMUXC
即:
IOMUX Controller
LED 使用的引脚为:
GPIO1_IO03
对应的复用寄存器:
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
需要设置该寄存器的低四位:
0101
通过复用功能配置,将对应引脚设置为 GPIO 功能。
十、电气特性配置
除了选择引脚功能之外,还需要设置引脚的电气特性。
对应寄存器:
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
该寄存器用于配置 GPIO 引脚相关的电气属性。
因此 GPIO 初始化不仅包括:
功能复用
还包括:
电气特性
十一、GPIO方向配置
完成引脚复用和电气特性配置后,还需要设置 GPIO 的输入输出方向。
对应模块:
GPIO
方向寄存器:
GPIOx_GDIR
主要作用:
设置GPIO为输入
或者
设置GPIO为输出
LED 需要由处理器输出控制信号,因此需要将对应 GPIO 设置为输出。
十二、GPIO数据寄存器
完成 GPIO 初始化后,程序运行过程中通过 GPIO 数据寄存器控制 LED。
对应寄存器:
GPIOx_DR
即:
GPIO Data Register
因此:
GPIOx_GDIR
↓
设置GPIO输入/输出方向
GPIOx_DR
↓
运行过程中控制GPIO输出数据
十三、LED控制的整体流程
今天的 LED 实验可以总结为:
处理器启动
↓
执行start.S
↓
初始化GPIO引脚
↓
设置IOMUXC复用功能
↓
设置引脚电气特性
↓
设置GPIO方向
↓
设置GPIO数据
↓
LED亮/灭
因此,对一个 SOC 外设进行操作,首先需要找到对应的:
1. 引脚
2. 复用寄存器
3. 电气特性寄存器
4. GPIO方向寄存器
5. GPIO数据寄存器
十四、LDR指令访问外设寄存器
ARM 汇编中可以使用 LDR 获取寄存器地址或读取内存中的数据。
常见形式:
LDR Rt, [Rn]
还可以带有立即数偏移:
LDR Rt, [Rn, #+/-imm12]
例如:
LDR r0, =0x020C4068
表示将:
0x020C4068
加载到 r0。
十五、STR指令修改外设寄存器
STR 用于将寄存器中的数据写入内存地址。
例如:
ldr r0, =0x020C4068
ldr r1, =0xFFFFFFFF
str r1, [r0]
执行过程:
r0 = 0x020C4068
↓
作为目标地址
r1 = 0xFFFFFFFF
↓
作为写入数据
str r1, [r0]
↓
将0xFFFFFFFF写入0x020C4068
因此:
LDR → 读取/加载
STR → 写入/存储
对于 SOC 外设来说,本质上就是通过这些寄存器访问指令修改外设寄存器。
十六、ARM交叉编译器
由于开发环境通常是:
Ubuntu PC
而最终程序运行在:
ARM Cortex-A7
所以需要使用 ARM 交叉编译器。
使用:
arm-linux-gnueabihf-gcc
进行 ARM 程序编译。
十七、安装ARM交叉编译器
交叉编译器:
gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
安装到:
/usr/local/arm
解压:
sudo tar -xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
删除压缩包:
sudo rm gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
十八、配置环境变量
进入用户主目录:
vi .bashrc
添加:
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/
之后重新启动虚拟机:
reboot
检查交叉编译器:
arm-linux-gnueabihf-gcc -v
如果能够正常显示版本信息,说明交叉编译器配置成功。
十九、FileZilla文件传输工具
还学习了 FileZilla Client。
FileZilla 是 Windows 下的 FTP 文件传输工具,可以用于:
Windows
↕
Ubuntu
之间传输代码文件。
如果需要手动配置 Ubuntu FTP 服务,可以安装:
sudo apt-get install vsftpd
修改:
/etc/vsftpd.conf
确保:
local_enable=YES
write_enable=YES
然后重启服务:
sudo /etc/init.d/vsftpd restart
查看服务状态:
sudo /etc/init.d/vsftpd status
二十、ARM裸机程序编译流程
今天学习了 ARM 裸机程序从源代码到最终二进制文件的完整过程。
整体流程:
start.S
↓
gcc
↓
start.o
↓
ld
↓
start.elf
↓
objcopy
↓
start.bin
↓
烧写到SD卡
↓
开发板运行
二十一、使用gcc进行汇编
命令:
arm-linux-gnueabihf-gcc -c start.S -o start.o -g
其中:
-c
表示只进行编译/汇编,不进行链接。
生成:
start.o
二十二、使用ld进行链接
命令:
arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o -o start.elf
作用是:
start.o
↓
链接
↓
start.elf
其中:
-Ttext 0x87800000
用于指定代码段的链接地址。
二十三、使用objcopy进行格式转换
命令:
arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
作用:
start.elf
↓
格式转换
↓
start.bin
其中:
-O binary
表示输出为二进制格式。
start.bin 是后续烧写到 SD 卡中的主要程序文件。
二十四、使用objdump反汇编
命令:
arm-linux-gnueabihf-objdump -D start.elf > start.dis
作用:
start.elf
↓
反汇编
↓
start.dis
反汇编主要用于:
查看程序中的机器指令
分析程序执行过程
检查编译、链接结果
它不是烧写程序运行所必须的步骤,主要用于分析和阅读程序。
二十五、编译工具总结
今天接触了四个重要工具:
| 工具 | 作用 | 主要输出 |
|---|---|---|
| gcc | 编译/汇编 | .o |
| ld | 链接 | .elf |
| objcopy | 格式转换 | .bin |
| objdump | 反汇编 | .dis |
完整过程:
.S
↓ gcc
.o
↓ ld
.elf
↓ objcopy
.bin
二十六、将程序烧写到TF卡
首先将 SD 卡连接到 Ubuntu。
查看设备:
ls /dev/sdb
确认 SD 卡设备为:
/dev/sdb
然后将下载工具:
imxdownload
拷贝到 Ubuntu 工程目录。
修改执行权限:
chmod +777 imxdownload
然后执行:
./imxdownload start.bin /dev/sdb
将程序烧写到 SD 卡。
二十七、程序烧写注意事项
烧写过程中需要注意烧写速度。
如果烧写速度出现异常,例如速度过快导致烧写失败,需要:
拔掉USB读卡器
↓
重新启动Ubuntu
↓
重新连接SD卡
↓
再次烧写
二十八、开发板启动方式
i.MX6ULL 支持多种启动方式。
例如:
USB启动
SD卡启动
板载eMMC启动
NAND Flash启动
开发板通过 BOOT 模块的拨码开关选择启动方式。
本次 LED 实验选择:
SD卡启动
二十九、LED实验测试流程
烧写完成后:
1. 选择SD卡启动模式
↓
2. 插入TF卡
↓
3. 给开发板上电
↓
4. 观察蓝色电源指示灯
↓
5. 观察红色LED
如果程序正确运行,就可以观察到 LED 的控制效果。
三十、LED闪烁练习
在完成 LED 点亮之后,可以进一步修改汇编程序,实现 LED 闪烁。
基本思路:
LED亮
↓
延时
↓
LED灭
↓
延时
↓
重新循环
程序结构:
初始化GPIO
↓
设置LED亮
↓
延时函数
↓
设置LED灭
↓
延时函数
↓
循环
因此,LED 闪烁实际上是在 LED 亮灭控制的基础上增加了一个延时函数。
三十一、Makefile
如果每次都手动输入:
gcc ...
ld ...
objcopy ...
objdump ...
会比较繁琐。
因此今天学习了使用 Makefile 简化编译过程。
Makefile 可以自动完成:
编译
↓
链接
↓
格式转换
↓
反汇编
三十二、Makefile三要素
Makefile 最基本的结构:
目标:依赖
规则
例如:
start.bin : start.S
arm-linux-gnueabihf-gcc -c start.S -o start.o -g
arm-linux-gnueabihf-ld -Ttext 0x87800000 start.o -o start.elf
arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
arm-linux-gnueabihf-objdump -D start.elf > start.dis
这里:
目标:
start.bin
依赖:
start.S
规则:
gcc
ld
objcopy
objdump
三十三、clean伪目标
Makefile 中可以定义:
clean:
rm start.o start.elf start.bin start.dis -f
执行:
make clean
即可删除编译生成的文件。
包括:
start.o
start.elf
start.bin
start.dis
这样就可以快速清理工程。
三十四、load伪目标
还可以定义:
load:
./imxdownload start.bin /dev/sdb
这样执行:
make load
就可以自动执行:
./imxdownload start.bin /dev/sdb
完成程序烧写。
三十五、使用变量优化Makefile
为了避免重复书写:
arm-linux-gnueabihf-
可以使用变量。
例如:
COMPLITER = arm-linux-gnueabihf-
CC = $(COMPLITER)gcc
LD = $(COMPLITER)ld
OBJCOPY = $(COMPLITER)objcopy
OBJDUMP = $(COMPLITER)objdump
之后使用:
start.bin : start.S
$(CC) -c start.S -o start.o -g
$(LD) -Ttext 0x87800000 start.o -o start.elf
$(OBJCOPY) -O binary -S -g start.elf start.bin
$(OBJDUMP) -D start.elf > start.dis
这样可以使 Makefile 更简洁,也方便以后更换编译工具。
三十六、今天的完整开发流程
今天学习的整个过程可以总结成:
① 创建工程
↓
② 编写start.S
↓
③ 初始化ARM启动环境
↓
④ 配置IOMUXC
↓
⑤ 配置GPIO方向
↓
⑥ 配置GPIO数据
↓
⑦ 完成LED控制
↓
⑧ 使用交叉编译器
↓
⑨ 生成start.o
↓
⑩ 链接生成start.elf
↓
⑪ 转换生成start.bin
↓
⑫ 反汇编生成start.dis
↓
⑬ 烧写start.bin到SD卡
↓
⑭ 开发板SD卡启动
↓
⑮ 观察LED运行结果