前言:
本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
正文:
本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第9.1讲” 的读书笔记。第9.1 介绍了
0. SMT32 格式的LED驱动程序示例
对于STM32而言,使用一个结构体将一个外设的所有寄存器都放在一起,这样就相当于寄存器抽象为外设了。
1. 设计C语言 LED 灯程序,模仿STM32 LED灯驱动程序
在结构体中添加寄存器的时候,一定要注意寄存器的连续性,如果不连续的话要添加寄存器占位。
2. 模仿STM32驱动程序进行源码编写
2.1 创建 imx6u.h 文件,把寄存器组织为结构体
创建 imx6u.h 文件,并模仿STM32驱动程序把寄存器组织为结构体,我们先根据《IMX6ULL参考手册.pdf》中寄存器的地址,在 imx6u.h 文件中的寄存器基地址 BASE。
查找 《IMX6ULL参考手册.pdf》中CCM寄存器的起始基地址,并定义为宏 '#typedef CCM_BASE':
查找 《IMX6ULL参考手册.pdf》中CCM_ANALOG 寄存器的起始基地址,并定义为宏 '#typedef CCM_ANALOG_BASE':
查找 《IMX6ULL参考手册.pdf》中 IOMUXC_SW_MUX 寄存器的起始基地址,并定义为宏 '#typedef IOMUXC_SW_MUX_BASE':
查找 《IMX6ULL参考手册.pdf》中 IOMUXC_SW_PAD 寄存器的起始基地址,并定义为宏 '#typedef IOMUXC_SW_PAD_BASE':
同样方法,查找 《IMX6ULL参考手册.pdf》中GPIO1~GPIO5的寄存器起始基地址,并 定义为宏 '#define GPIOx_BASE' :
参考正点原子提供的第“9.1讲”视频教程的附带源码目录中的 'imx6u.h' 文件和STM32 LED驱动程序文件中,把寄存器组织成 C语言结构体的方法,在 mx6u.h 文件中定义 I.MX6U 的寄存器C语言结构体定义。
定义C语言结构体的时候,有一些细节需要注意:
- 把 I.MX6U 外设寄存器地址组成C语言结构体,利用了C语言结构体中结构体成员地址依次递增的特性。
- 所以需要注意《IMX6ULL参考手册.pdf》文档中,I.MX6U 的外设寄存器地址是不是连续的,特别需要注意的是,I.MX6U 的外设寄存器从 base 基地址开始大部分是连续的但也有几个寄存器的地址中间有不连续的,寄存器地址跳过了几个地址,对于这种情况需要再C语言结构体定义以留下占位成员。
例如, CCM 寄存器其里的如下两个寄存器之间是不连续了,两个寄存器之间间隔了 8个byte的空间。
按照 《IMX6ULL参考手册.pdf》中 “18.6 CCM Memory Map/Register Definition” 中 CCM 寄存器的地址顺序,在 imx6u.h 中定义CCM 寄存器结构体。每个CCM寄存器的名字作为结构体中的一个成员,结构体体总成员的名字按照手册中寄存器地址的顺序排序。
2.2 编写 start.s 汇编文件
同上一讲中的“C语言LED灯驱动程序”中准备C语言运行环境的 start.s 汇编文件,本节中的 start.s 汇编文件通上一讲中的汇编文件内容一样,需要做如下两步:
- 设置CPSR寄存器,让处理器运行在SVC模式
- 设置SP寄存器,准备C语言运行环境
/* 进入svn模式 */
mrs r0, cpsr
bic r0, 0x1f @清零 cpsr M[4:0]
orr r0, 0x13 @cpsr或上0x13,也就是svc模式
msr cpsr, r0 @写cpsr
/* 准备C环境 */
ldr sp, =0x80200000
b main
在本节中,新增加了清零最终链接文件中 .bss 段的内容。使用ARM汇编语言来清零最终文件加载到内存后指定起始位置和结束位置之间的地址区间,这里我们会用到 ARM 汇编的如下指令:
- cmp 指令,比较两个寄存器的值大小
- ble 指令,当状态寄存器CPSR 也就是上一步 CMP 指令比较结果为LE(Less Equal)小于等于时跳转到指定位置。
- stmia 指令,写寄存器内容到内存地址中,ia 后缀的含义是 increse -after ,即先将寄存器写到指定地址,然后在让地址+4 (也就是 incrase after,执行后地址增长)
使用ARM汇编清零内存中 .bss 段的时候,我们也需要用到定义在描述文件链接方式的链接文件 .lds 里的两个符号 '__bss_strart', '__bss_end'。这两个符号是定义在链接脚本里的两个变量,我们在 start.s 汇编文件中使用这两个变量,__bss_start 的值等于 .bss 段的起始地址,__bss_end 的值等于 .bss 段的结束地址。
.global _bss_start
.world __bss_start @使用连接脚本 imx6u.lds 中定义的符号 __bss_start
.global _bss_end
.world __bss_end @使用连接脚本 imx6u.lds 中定义的符号 __bss_end
/* 清零 .bss */
ldr r0, _bss_start
ldr r1, _bss_end
mov r2, #0
bss_loop:
stmia r0!, {r2}
cmp r0, r1
ble bss_loop
最终的 start.s 链接文件的完整内容如下
.global _start
.global _bss_start
.world __bss_start @使用连接脚本 imx6u.lds 中定义的符号 __bss_start
.global _bss_end
.world __bss_end @使用连接脚本 imx6u.lds 中定义的符号 __bss_end
_start:
/* 进入svn模式 */
mrs r0, cpsr
bic r0, 0x1f @清零 cpsr M[4:0]
orr r0, 0x13 @cpsr或上0x13,也就是svc模式
msr cpsr, r0 @写cpsr
/* 清零 .bss */
@ldr r0, =_bss_start @语法错误
ldr r0, _bss_start @正确的语法就是直接使用_bss_start符号,
@不是像之前写立即数到寄存器一样需要在立即数前面加上符号'='
@ldr r1, =_bss_end @语法错误
ldr r1, _bss_end
//ldr r2, =0x0 @ldr指令传送立即数到寄存器r0,立即数前面加上符号'='
mov r2, #0 @mov指令传送立即数到寄存器r0,立即数前面加上符号'#'
bss_loop:
//ldmia r0!, {r2} @语法错误,正确应该是 stmia,因为目的是写寄存器的值到内存中,所以应该是汇编 stm (store)
stmia r0!, {r2} @ia:Increase After
cmp r0, r1 @比较r0, r1
ble bss_loop @如果r0小于等于r1,继续执行循环
/* 准备C环境 */
ldr sp, =0x80200000
b main
2.3 编写 .lds 链接脚本
编写链接脚本文件,链接脚本.lds 和上一节内容完全一样,没有差异。
SECTION {
. = 0x87800000;
.text :
{
start.o
*(.text)
}
.data : { *(.data) }
.rodata : { *(.rodata*) }
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
2.4 编写 man.c 文件
编写 man.c 文件,使用定义的I.MX6ULL寄存器结构体,在这一讲中我们已经参考STM32驱动程序中寄存器的组织方式,讲 I.MX6ULL 处理器手册中的寄存器根据寄存器的用途组织为了C语言结构体,利用C语言结构体提内成员变量地址一次递增的特性,在给出了寄存器组的起始基地址之后,使用C语言指针访问结构体的成员,就访问到 ‘基地址+偏移’ 位置上的I.MX6ULL 寄存器的地址。我们在 imx6u.h 头文件中把 I.MXULL 的我们用到的寄存器组,定义为一下几个寄存器集合组:
- CCM 寄存器组
- CCM_ANALOG 寄存器组
- IOMUX_SW_MUX 寄存器组
- IOMUX_SW_PAD 寄存器组
- GPIO 寄存器组
我们在 main.c C语言源文件中使用 'CCM->CCGR0 = 0xFFFF_FFFF' 就可以写I.MX6ULL 的 CCM_CCGR0 外设时钟寄存器的值。使用C语言结构体来操作寄存器,修改之后的main.c源码如下:
#include "imx6u.h"
void clk_init(void)
{
CCM->CCGR0 = 0xFFFFFFFF;
CCM->CCGR1 = 0xFFFFFFFF;
CCM->CCGR2 = 0xFFFFFFFF;
CCM->CCGR3 = 0xFFFFFFFF;
CCM->CCGR4 = 0xFFFFFFFF;
CCM->CCGR5 = 0xFFFFFFFF;
CCM->CCGR6 = 0xFFFFFFFF;
}
void led_init(void)
{
IOMUX_SW_MUX->GPIO1_IO03 = 0x5; /*服用GPIO1_IO03为GPIO模式*/
IOMUX_SW_PAD->GPIO1_IO03 = 0x10B0; /*设置GPIO1_IO03电气特性*/
GPIO1->GDIR = 0x08; /*bit3设置为1,GPIO为输出OUTPUT*/
GPIO1->DR = 0x0; /*GPIO输出第电平*/
}
void led_on(void)
{
GPIO1->DR &= ~(1<<3); /*bit3清零*/
}
void led_off(void)
{
GPIO1->DR |= (1<<3); /*bit3设置*/
}
void short_delay(unsigned int n)
{
while(n--){
;
}
}
/* sleep 1ms */
void delay(unsigned int m){
while(m--){
short_delay(0x7ff);
}
}
int main(void)
{
clk_init();
led_init();
while(1){
led_on();
delay(50);
led_off();
delay(50);
}
return 0;
}
3. 编译
修改Makefile,之间我们总是输入ARM交叉编译工具链的完整路径来编译源码,例如,'arm-linux-gnueabifh-gcc', 'arm-linux-gnueabihf-ld' 来进行编译,每一次输入完整的路径名比较麻烦,我么可可以在Makefile中定义变量的方式来减少每次输入的路径名长度。
CROSS=arm-linux-gnueabihf-
CC=$(CROSS)gcc
LD=$(CROSS)ld
OBJCOPY=$(CROSS)objcopy
OBJDUMP=$(CROSS)objdump
objs := start.o main.o
ledc.bin : $(objs)
arm-linux-gnueabihf-ld -Timx6u.lds -o ledc.elf $^
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
%.o : %.c
#arm-linux-gnueabihf-gcc -nostdlib -c -o $@ $<
$(CC) -nostdlib -c -o $@ $<
%.o : %.s
#arm-linux-gnueabihf-gcc -nostdlib -c -o $@ $<
$(CC) -nostdlib -c -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf imxdownload
rm -rf ledc.dis
在Makefile里,我们定义了变量 'CROSS=arm-linux-gnueabihf-',然后定义了变量 'LD=$(CROSS)LD', 'OBJCOPY=$(CROSS)objcoy', 'CC=$(CROSS)cc',然后使用定义的变量 '$(LD)', '$(CC)’,这样就不用输入完整的交叉编译工具名了。
4.烧录SD卡验证
执行'make' 编译出来 ledc.bin之后,使用 imxdownload 烧录编译出来的 .bin 文件到SD卡中,把SD卡插到正点原子 I.MX6U APLHA/Mini 开发板验证 LED 灯否正产闪烁。
我验证的结果是,LED灯使用模仿STM32的方式使用C语言结构体来组织I.MX6U 寄存器后,LED灯可以正常的闪烁。
5. 总结:
5.1 VSCode Compare插件
使用 VSCode的 compare 插件来比较两个文本文件的差异,使用方法步骤如下:
- 在VSCode中按下 "F1" 键
- 在出现的搜索框里输入 "compare" 进行搜索,vscode会自动进行补全。
- 选择 "Compare With",打开需要比较的文件,进行比较两个文本文件的差异。
5.2 VSCode TabNine AI 自动代码补全
使用 VSCode 插件 TabNine AI 智能补全工具,实现自动代码 AI 人工智能补全。TabNine 分为收费版和免费版,收费版提供基本代码AI补全功能,收费版提供高级的AI功能。TabNine 会分析你写的源码文件自动提供AI代码补全,也就是你写的源码越多 TabNine AI 代码补全就越精准。实际使用了一些 TabNine AI 代码补全超级好用,一用一个不吱声,推荐。
5.3 发现正点原子示例源码 imx6u.h 的一点小错误
发现了正点原子"9.1讲 模仿STM32驱动程序”视频教程提供示例源码 imx6u.h 里的一点小错误:在 《IMX6ULL参考手册.pdf》手册中章节“18.7 CCM Analog Memory Map/Register Definition”里面,手册定义了 6 个和 AUTO 相关的寄存器,但是在正点原子提供的示例源码 “ imx6u.h” 只有5个AUTO寄存器,丢失了一个 "AUDO_NUM" 寄存器。
哈哈,也算本人发现的一点小错误。正点原子的示例源码和视频教程还是质量很高的,根正点原子哥点赞。