ARM---基于I.MX6ULL的启动代码,汇编点灯,交叉编译以及对I.MX6ULL相关寄存器的介绍
目录
一. 汇编启动代码
1.1 异常向量与异常向量表
1.2 启动代码的任务
二.裸机编写启动代码
2.1 IMX6ULL简介
2.2 启动代码的编写
三. IMX6ULL SOC内部寄存器详解以及点灯程序
3.1 时钟相关寄存器
3.2 GPIO配置寄存器
3.2.1 IO复用寄存器---SW_MUX_CTL Register
3.2.2 引脚电气属性配置寄存器---SW_PAD_CTL Register
3.2.3 GPIOx_GDIR方向寄存器
3.2.4 GPIOx_DR 数据寄存器
3.3 示例代码---汇编
3.4 交叉编译
3.4.1 什么是交叉编译
3.4.2 交叉编译流程
1、 使用arm-linux-gnueabihf-gcc编译文件
2、使用arm-linux-gnueabihf-ld 链接文件
3、用arm-linux-gnueabihf-objcopy 格式转换
4、使用arm-linux-gnueabihf-objdump 反汇编
5、烧写程序
6、运行程序
3.5 GPIO相关其他寄存器
3.5.1 GPIO interrupt configuration register 引脚中断配置寄存器
3.5.2 GPIO edge select register
3.5.3 GPIO interrupt mask register
3.5.4 GPIO interrupt status register
3.5.5 GPIO pad status register
一. 汇编启动代码
针对Cortex-A7内核,编写启动代码
在编写启动代码前,首先要回顾一下异常向量与异常向量表的知识:
1.1 异常向量与异常向量表
异常向量:跳转到异常服务函数指令的地址(是一个地址)
异常向量表:类似一个数组,数组里面保存的是跳转到异常处理函数中的指令
Cortex-A7内核的异常向量表:
1.2 启动代码的任务
启动代码启动以后,引导程序到c语言环境下运行。启动代码的目的是为了在处理器复位以后搭建C语言最基本的需求。因此启动代码的主要任务有:
1、初始化异常向量表
2、初始化各工作模式的栈指针寄存器;
3、开启ARM内核中断允许;
4、将工作模式设置为User模式;
5、完成上述工作后,引导程序进入C语言主函数执行;
示例代码:
这里先在keil中编写启动代码,仿真芯片采用的是三星(Samsung)公司生产的s3c2440芯片,内核采用的是arm9系列内核
PRESERVE8 area reset, code, readonly code32 entry ;初始化异常向量表:存放异常跳转指令 ldr pc, =_reset_handler ;0x00000000 复位异常跳转指令 --- 复位默认是SVC模式 ldr pc, =_undefined_handler ;0x00000004 未定义指令异常跳转指令 ldr pc, =_svc_handler ;0x00000008 复位或软中断异常跳转指令 ldr pc, =_prefetch_abort_handler ;0x0000000C 指令存取错误异常跳转指令 ldr pc, =_data_abort_handler ;0x00000010 数据存取错误异常跳转指令 ldr pc, =_reserved_handler ;0x00000014 保留 ldr pc, =_irq_handler ;0x00000018 普通中断异常跳转指令 ldr pc, =_fiq_handler ;0x0000001C 快速中断异常跳转指令 _fiq_handler ldr pc, =_fiq_handler _irq_handler ldr pc, =_irq_handler _reserved_handler ldr pc, =_reserved_handler _data_abort_handler ldr pc, =_data_abort_handler _prefetch_abort_handler ldr pc, =_prefetch_abort_handler _svc_handler import c_svc_handler ;导入C函数 stmfd sp!, {r0-r12, lr} ;保护现场 bl c_svc_handler ;实现函数跳转且将返回地址保存至lr寄存器中 ldmfd sp!, {r0-r12, pc}^ ;直接将lr的值出栈到pc(相当与:先ldmfd sp!, {r0-r12, lr},再 ;mov pc, lr ), ^:表示恢复工作模式 ;如果此时不恢复工作模式,将继续工作在SVC模式,此时的LR寄存器是单独的,并不是之前User模式下的LR寄存器(保存返回到C程序的地址) ;就会导致返回不到C的main函数 ;^:作用是恢复SPSR,恢复工作模式 _undefined_handler ldr pc, =_undefined_handler _reset_handler ;设置SVC模式下的栈 ldr sp, =0x40001000 ;改变工作模式到User mrs r0, cpsr bic r0, #0x1F orr r0, #0x10 msr cpsr_c, r0 ;设置User工作模式下的栈 ldr sp, =0x40001000 sub sp, sp, #1024 ;大小为1K字节 import main b main ;跳转到C程序的主函数执行,这里不用使用bl来保存返回地址 _asm_fun export _asm_fun ;导出函数 svc #7 ;此指令模拟实现软中断,将改变工作模式到SVC模式,并且CPU自动跳转到对应的异常向量(指令的地址) bx lr ;将寄存器lr中的地址恢复给PC,返回到调用处继续执行 finish B finish endC程序:
extern void _asm_fun(void); //延时函数 void delay(unsigned int n) { while(n--) { } } void c_svc_handler(void) { delay(0x1000); } int main() { while(1) { _asm_fun(); /跳转到汇编程序,模拟实现软中断,C程序会自动保存返回地址 delay(0x7FFFFFFF); } return 0; }这里有几个注意点:
1、ldmfd sp!, {r0-r12, pc}^注意这条指令会从当前工作模式的SPSR中恢复软中断之前的CPSR寄存器状态,也就是从SVC工作模式恢复到User工作模式,这时,LR寄存器里面存的就是返回到C程序函数中的地址,如果不恢复工作状态,则使用的是SVC工作模式的LR寄存器,会导致函数返回失败!
二.裸机编写启动代码
2.1 IMX6ULL简介
本次课程实验使用的开发板是由正点原子提供的IMX6ULL-Mini。大多数情况下,一个嵌入式系统硬件部分包括核心板和底板两个部分。核心板主要是将Soc所需的必要器件:RAM,NAND(eMMC)等器件组织在一起,这部分电路设计难度相对较大。底板部分通常是由集成商设计。二者通过接插件连接起来。这样的设计大。大降低了开发难度,节约了开发时间。
核心板介绍:
2.2 启动代码的编写
这里使用Vscode进行启动代码的编写,相比于Keil,语法相当简洁许多,流程和Keil中基本相同
这里要注意,由于现在是真机编程,对启动代码增添了几点
真机下必须设置两种模式的栈,在_reset_handler函数中进行设置
1、IRQ----普通中断模式
1、先切换到IRQ模式下
改变CPSR低五位为0x12
2、设置IRQ模式下的栈
注意真机地址会改变:
栈区要开到核心板上512M的DDR中
对于imx6ull:RAM空间起始地址:0x80000000
总共512M,可算出结束地址:0xA0000000
这里对IRQ模式下栈区的SP指针开设置到:0x86000000,大小为:32M
2、System---用户模式的特权模式
所有程序运行在此模式下,User模式的特权
1、先切换到Sys模式
CPSR的低五位是0x1F
2、设置栈
SP = 86000000-2000000 = 84000000 (由于IRQ模式下栈的大小为32M,故这样计算)
这段空间往下都是Sys模式的栈区
示例代码:
.global _start _start: /初始化异常向量表:存放异常跳转指令 ldr pc, =_reset_handler /0x00000000 复位异常跳转指令 --- 复位默认是SVC模式 ldr pc, =_undefined_handler /0x00000004 未定义指令异常跳转指令 ldr pc, =_svc_handler /0x00000008 复位或软中断异常跳转指令 ldr pc, =_prefetch_abort_handler /0x0000000C 指令存取错误异常跳转指令 ldr pc, =_data_abort_handler /0x00000010 数据存取错误异常跳转指令 ldr pc, =_reserved_handler /0x00000014 保留 ldr pc, =_irq_handler /0x00000018 普通中断异常跳转指令 ldr pc, =_fiq_handler /0x0000001C 快速中断异常跳转指令 _fiq_handler: ldr pc, =_fiq_handler _irq_handler: ldr pc, =_irq_handler _reserved_handler: ldr pc, =_reserved_handler _data_abort_handler: ldr pc, =_data_abort_handler _prefetch_abort_handler: ldr pc, =_prefetch_abort_handler _svc_handler: ldr pc, =_svc_handler _undefined_handler: ldr pc, =_undefined_handler _reset_handler: /将工作模式切换到IRQ模式下 mrs r0, cpsr bic r0, r0, #0x1F orr r0, r0, #0x12 msr cpsr ,r0 /设置IRQ模式下的栈底指针,大小为32M ldr sp, =0x86000000 /将工作模式切换到System模式下 mrs r0, cpsr bic r0, r0, #0x1F orr r0, r0, #0x1F msr cpsr, r0 /设置System模式的栈底指针 ldr sp, =0x84000000 bl finish finish: B finish三. IMX6ULL SOC内部寄存器详解以及点灯程序
1、在编写了启动代码之后,这里实现一个点灯程序,要实现点灯就要输出,要输出就要配置寄存器,所以先对用到的寄存器做详细介绍
2、先看看IMX6ULL底板对应LED部分的原理图:
可以看到LED灯连接到了芯片的GPIO1组的第三个引脚上,还可以看到是低电平点亮,故需要向该引脚输出一个低电平才可以点亮此LED
3、配置寄存器流程:
先打开所有外设的时钟 ---> 配置引脚的复用功能,电气属性,输入输出模式 ---> 向输出寄存器里面写,进行输出
GPIO引脚配置相关寄存器:
3.1 时钟相关寄存器
CCM Clock Gating Register寄存器
CCM_CCGR0 - CCM_CCGR6
i.MX6ULL 芯片有三种运行电源模式:
- RUN:正常全速运行(CPU 执行代码、外设可用)
- WAIT:CPU 休眠、外设可保持时钟(轻休眠)
- STOP:深度低功耗休眠,大部分模块断电
CCGR = Clock Gating Register 外设时钟门控寄存器
作用:独立开关每个外设的时钟,关闭闲置外设降低功耗。
以CCM_CCGR0为例:
可以看到每两位对应着一个外设的时钟模式,这两位的取值和对应的时钟模式如下表所示:
| CG 两位值 | RUN 模式 | WAIT 轻休眠 | STOP 深度休眠 | 适用场景 |
|---|---|---|---|---|
| 00 | 关时钟 | 关时钟 | 关时钟 | 彻底闲置外设 |
| 01 | 开时钟 | 关时钟 | 关时钟 | 常规外设、低功耗产品首选 |
| 10 | 保留无效 | 保留无效 | 保留无效 | 禁止使用 |
| 11 | 开时钟 | 开时钟 | 关时钟 | 休眠需要外设持续工作、调试初始化 |
GPIO1组对应的时钟寄存器在CCM_CCGR1:
为了方便,在代码中对所有外设的时钟都打开,也就是将CCM_CCGR0 - CCM_CCGR6都设置成0xFFFFFFFF
时钟默认全部关闭,CCM_CCGR0 - CCM_CCGR6 = 0x00000000
示例代码:
_enable_clocks: //将所有的外设时钟全部打开 //CCGR0 ldr r0, =0x020C4068 ldr r1, =0xFFFFFFFF str r1, [r0] //CCGR1 ldr r0, =0x020C406C ldr r1, =0xFFFFFFFF str r1, [r0] //CCGR2 ldr r0, =0x020C4070 ldr r1, =0xFFFFFFFF str r1, [r0] //CCGR3 ldr r0, =0x020C4074 ldr r1, =0xFFFFFFFF str r1, [r0] //CCGR4 ldr r0, =0x020C4078 ldr r1, =0xFFFFFFFF str r1, [r0] //CCGR5 ldr r0, =0x020C407C ldr r1, =0xFFFFFFFF str r1, [r0] //CCGR6 ldr r0, =0x020C4080 ldr r1, =0xFFFFFFFF str r1, [r0] bx lr3.2 GPIO配置寄存器
IO功能复用配置寄存器分为两类,引脚设置寄存器(PAD Settings Registers)和复用控制寄
存器(MUX Control Registers)。把这两类简称为第一类和第二类。
3.2.1 IO复用寄存器---SW_MUX_CTL Register
SW_MUX_CTL_PAD_GPIO1_IO03 引脚复用控制寄存器
IO复用控制器(IOMUX Controller)
位域介绍:
这里将一组GPIO的引脚3这种为通用GPIO引脚即可,也就是0x00000005
| SION | 1、只在确实需要监控信号状态的场合开启SION 2、对于高速接口,建议保持SION=0 3、在低功耗应用中,应该谨慎使用SION 4、对于标准的输入管脚,没有必要额外设置SION=1,因为它们默认就是输入使能的 |
MUX_MODE 模式选择位 | 0000:I2C1_SDA,I2C1 总线数据引脚 0001:GPT1_COMPARE3,GPT1 定时器通道 3 比较输出 0010:ALT2:USB_OTG2_OC,USB2 过流检测输入 0100:USDHC1_CD_B,SD 卡 1 卡检测,低有效 0101:GPIO1_IO03,通用 GPIO 引脚 0110:CCM_DI0_EXT_CLK,CCM 外部时钟输入 0 0111:SRC_TESTER_ACK,系统复位测试应答信号 1000:UART1_RX,串口 1 接收引脚 |
3.2.2 引脚电气属性配置寄存器---SW_PAD_CTL Register
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 电气属性功能寄存器
位域介绍:
这里使用默认属性即可,即:0x000010B0
16 HYS | 迟滞:是指是否使能迟滞比较器当IO作为输入功能时有效·用于设置输入接收器的施密特触发器是否使能,如果需要对输入波形进行整形可以使能此位。 0 HYS_0_滞回功能禁用 — 滞回功能已禁用 |
15-14 PUS | 当引脚作为输入时·上拉电阻能够保证在释放总线时引脚呈现高电平。 00 PUS_0_100K_欧姆_下拉 — 100K 欧姆 下拉 |
13 PUE | 当引脚作为输入时·引脚是使用上拉还是保持。所谓保持是指引脚跟随外部电压的变化而变化·例如引脚作为ADC输入引I脚时·就不能使用上来而必须采用跟随方式。 0 :保持 |
12 PKE | 上拉/保持功能开关 0 :上拉/保持功能已禁用 |
11 ODE | ODE是控制输出模式的: ODE=1:开漏输出模式,只能拉低,不能拉高; ODE=0:推挽输出模式,既可以拉高,也可以拉低。 |
7-6 SPEED | 高速模式:更快的信号转换速率、更短的上升/下降时间、更高的工作频率但EMI(电磁干扰)会增加功耗相对较高 低速模式:较慢的信号转换速率、较长的上升/下降时间、EMI较小功耗相对较低适合低速接口 00 速度_0_低_50MHz_ — 低(50MHz) |
5-3 DSE | DSE是驱动强度配置位,主要在输出时使用 DSE 0:输出驱动能力最弱,约为260Ohm DSE1:中等驱动能力,约为130Ohm DSE2:较强驱动能力,约为87Ohm 000 DSE_0_output_driver_disabled_ — 输出驱动器已禁用; |
0 SRE | 设置压摆率,压摆率就是IO电平跳变所需的时间,比如从0到1需要多少时间,时间越小波形越陡,说明压摆率越高,反之,时间越大波形越缓,压摆率越低,如果产品要过EMC则可以使用较低的压摆率,因为波形缓和,如果使用IO做高速通信则可以使用高压摆率 0 SRE_0_慢压摆率 |
3.2.3 GPIOx_GDIR方向寄存器
i.MX6ULL 一共有 5 组 GPIO:GPIO1 ~ GPIO5,每组独立一套寄存器:
GPIOx_GDIR:GPIO 方向寄存器(输入 / 输出选择)
GPIOx_DR:GPIO 数据寄存器(输出电平 / 读取输入电平)
| 0 | GDIR bitN = 0:该引脚配置为输入模式CPU 只能读取引脚外部电平,不能主动输出高低 |
| 1 | GDIR bitN = 1:该引脚配置为输出模式CPU 可以通过 DR 寄存器设置引脚输出高 / 低电平 |
3.2.4 GPIOx_DR 数据寄存器
当引脚作为输出时:向对应的位写1使引脚为高电平,写0引脚为低电平
当引脚作为输入时:读取到1表示引脚为高电平·读取到0表示引脚低电平
3.3 示例代码---汇编
配置流程
1、先打开所有外设的时钟,寄存器地址:0x020c4068
~ 0x020c4080,都写成0xFFFFFFFF
2、配置GPIO1_IO03引脚复用功能
配置电气属性,使用默认属性
配置引脚的工作模式为输出模式
3、写输出寄存器
.global _start _start: /初始化异常向量表:存放异常跳转指令 ldr pc, =_reset_handler /0x00000000 复位异常跳转指令 --- 复位默认是SVC模式 ldr pc, =_undefined_handler /0x00000004 未定义指令异常跳转指令 ldr pc, =_svc_handler /0x00000008 复位或软中断异常跳转指令 ldr pc, =_prefetch_abort_handler /0x0000000C 指令存取错误异常跳转指令 ldr pc, =_data_abort_handler /0x00000010 数据存取错误异常跳转指令 ldr pc, =_reserved_handler /0x00000014 保留 ldr pc, =_irq_handler /0x00000018 普通中断异常跳转指令 ldr pc, =_fiq_handler /0x0000001C 快速中断异常跳转指令 _fiq_handler: ldr pc, =_fiq_handler _irq_handler: ldr pc, =_irq_handler _reserved_handler: ldr pc, =_reserved_handler _data_abort_handler: ldr pc, =_data_abort_handler _prefetch_abort_handler: ldr pc, =_prefetch_abort_handler _svc_handler: ldr pc, =_svc_handler _undefined_handler: ldr pc, =_undefined_handler _reset_handler: /将工作模式切换到IRQ模式下 mrs r0, cpsr bic r0, r0, #0x1F orr r0, r0, #0x12 msr cpsr ,r0 /设置IRQ模式下的栈底指针,大小为32M ldr sp, =0x86000000 /将工作模式切换到System模式下 mrs r0, cpsr bic r0, r0, #0x1F orr r0, r0, #0x1F msr cpsr, r0 /设置System模式的栈底指针 ldr sp, =0x84000000 bl _enable_clocks bl _led_init bl _led_on bl finish _enable_clocks: /将所有的外设时钟全部打开 /CCGR0 ldr r0, =0x020C4068 ldr r1, =0xFFFFFFFF str r1, [r0] /CCGR1 ldr r0, =0x020C406C ldr r1, =0xFFFFFFFF str r1, [r0] /CCGR2 ldr r0, =0x020C4070 ldr r1, =0xFFFFFFFF str r1, [r0] /CCGR3 ldr r0, =0x020C4074 ldr r1, =0xFFFFFFFF str r1, [r0] /CCGR4 ldr r0, =0x020C4078 ldr r1, =0xFFFFFFFF str r1, [r0] /CCGR5 ldr r0, =0x020C407C ldr r1, =0xFFFFFFFF str r1, [r0] /CCGR6 ldr r0, =0x020C4080 ldr r1, =0xFFFFFFFF str r1, [r0] bx lr _led_init: /配置GPIO1_IO3引脚的复用功能,设置其为GPIO功能 /SW_MUX_CTL寄存器 ldr r0, =0x020E0068 mov r1, #0x05 str r1, [r0] /配置GPIO1_IO3引脚的电气属性,使用默认模式 /SW_PAD_CTL寄存器 ldr r0, =0x020E02F4 ldr r1, =0x10B0 str r1, [r0] /配置GPIO1_IO3引脚为输出模式 /GPIO1_GDIR寄存器 ldr r0,=0x0209C004 ldr r1, [r0] /将D3位置1,将GPIO1_IO3引脚设置为输出模式 orr r1, r1, #(1 << 3) str r1,[r0] bx lr _led_on: /控制灯的亮 /GPIO1_DR寄存器 ldr r0, =0x0209C000 ldr r1, [r0] /将D3位清零 bic r1, r1, #(1 << 3) str r1, [r0] bx lr finish: B finish3.4 交叉编译
3.4.1 什么是交叉编译
编写的程序都是需要经过编译才能够生成可执行程序的,之前一直使用一个叫做gcc的编译器编译程序,之后才能运行。接下来要做的事情也是类似的,问题在于编写程序和编译程序的地方是在PC即电脑上,可程序却需要在板子上运行,准确的说是需要在i.mx6ull芯片上运行,因此的面临的局面就是用一台x86架构的计算编译出一个arm架构运行的程序,那么使用原来的gcc肯定是不行了,而是需要使用一种特殊的gcc,这个特殊的gcc就称为交叉编译工具链。交叉编译器中“交叉”的意思就是在一个架构上编译另外一个架构的代码,相当于两种架构“交叉”起来了。交叉编译器有很多种,在此处使用Linaro出品的交叉编译器:Linaro GCC 编译工具链(编译器)
需要在Ubuntu(PC) 下编译出I.MX(ARM) 可以运行的程序, 这种编译环境称为交叉编译环境。 使用的编译工具称为交叉工具链, 总结一下:
1. 它肯定是一个 GCC 编译器
2. 这个 GCC 编译器是运行在 X86 架构的 PC 上的;
3. 这个 GCC 编译器是编译 ARM 架构代码的, 也就是编译出来的可执行文件是在 ARM 芯片
上运行的
3.4.2 交叉编译流程
1、 使用arm-linux-gnueabihf-gcc编译文件
有了工具链, 就可以编译我们led_asm.s文件了, 命令为:
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
其中“-g”选项是产生调试信息, GDB 能够使用这些调试信息进行代码调试。 “-c”选项是编译源文件, 但是不链接。 “-o”选项是指定编译产生的文件名字, 这里我们指定 led.s 编译完成以后的文件名字为led.o。 执行上述命令以后就会编译生成一个 led.o 文件。
2、使用arm-linux-gnueabihf-ld 链接文件
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上面的led.o 文件并不是我们可以下载到开发板中运行的文件, 一个工程中所有的 C文件和汇编文件都会编译生成一个对应的.o 文件, 我们需要将这.o 文件链接起来组合成可执行文件。 那么连接到底是个什么概念呢? 之前我们使用Keil的时候也没有这个步骤呀? 事实上我们使用的Keil是一种IDE, 这种集成开发环境其实组合了编译和链接为一体, 只是不需要我们手动操作罢了。这里我们要区分“存储地址”和“运行地址”这两个概念,“存储地址”就是可执行文件存储在哪里, 可执行文件的存储地址可以随意选择。 “运行地址”就是代码运行的时候所处的地址, 这个我们在链接的时候就已经确定好了, 代码要运行, 那就必须处于运行地址处, 否则代码肯定运行出错。比如 I.MX6U 支持 SD 卡、 EMMC、 NAND 启动, 因此代码可以存储到 SD 卡、 EMMC 或者 NAND 中, 但是要运行的话就必须将代码从 SD 卡、 EMMC 或者NAND 中拷贝到其运行地址(链接地址)处。 需要注意的是“存储地址”和“运行地址”可能是一样的, 比如STM32 的存储起始地址和运行起始地址都是 0X08000000。
本次, 所有的裸机例程都是烧写到 SD 卡中, 上电以后 I.MX6U 的内部 boot rom 程
序会将可执行文件拷贝到链接地址 处 , 这个链接地址可以 在 I.MX6U 的内部 128KB RAM 中
(0X900000~0X91FFFF), 也可以在外部的 DDR 中。本次中所有裸机例程的链接地址都在 DDR中, 链接起始地址为 0X87800000。 I.MX6U-ALPHA 开发板的 DDR 容量有两种: 512MB 和256MB, 起始地址都为 0X80000000, 只不过 512MB 的终止地址为 0X9FFFFFFF, 而 256MB 容量的终止地址为0X8FFFFFFF。 之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址, 不容易记混。
确定了链接地址以后就可以使用 arm-linux-gnueabihf-ld 来将前面编译出来的 led.o 文件链接到
0X87800000 这个地址, 使用如下命令:
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令中-Ttext就是指定链接地址, “-o”选项指定链接生成的 elf 文件名, 这里我们命名为 led.elf。 上述命令执行完以后就会在工程目录下多一个 led.elf 文件。
3、用arm-linux-gnueabihf-objcopy 格式转换
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件, 我们要烧写的.bin 文件, 因此还需要将 led.elf 文件转换为.bin 文件, 这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具
了。
arm-linux-gnueabihf-objcopy 更像一个格式转换工具, 我们需要用它将 led.elf 文件转换为
led.bin 文件, 命令如下:
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
上述命令中,“-O”选项指定以什么格式输出, 后面的“binary”表示以二进制格式输出, 选项“-S”表示不要复制源文件中的重定位信息和符号信息, “-g”表示不复制源文件中的调试信息。 上述命令执行完成以后, 就会在工程目录下多一个 led.bin 文件。 至此我们终于等到了想要的东西—led.bin 文件
4、使用arm-linux-gnueabihf-objdump 反汇编
arm-linux-gnueabihf-objdump -D led.elf > led.dis
大多数情况下都是用 C 语言写试验例程的, 有时候需要查看其汇编代码来调试代码, 因此就
需要进行反汇编, 当然这个工作目前对于我们不是必须的。 一般来说可以将 elf 文件反汇编, 比如如下命令:
arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段, 反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件。 我们可以打开这个文件, 查看其中的内容可以发现这里面都是汇编代码。 注意通过 led.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以0X87800000 为起始地址的区域
总结:
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
5、烧写程序
我们学习 STM32 等其他的单片机的时候, 编译完代码以后可以直接通过 Keil下载到内部的 flash中。 但是 I.MX6U 虽然内部有 96K 的 ROM, 但是这 96K 的 ROM 是 NXP自己用的, 不向用户开放。 所以相当于说 I.MX6U 是没有内部 flash 的, 但是我们的代码得有地方存放啊, 为此, I.MX6U 支持从外置的NOR Flash、 NAND Flash、 SD/EMMC、 SPI NOR Flash和 QSPI Flash 这些存储介质中启动, 所以我们可以将代码烧写到这些存储介质中中。 在这些存储介质中, 除了 SD 卡以外, 其他的一般都是焊接到了板子上的, 我们没法直接烧写。 但是 SD卡是活动的, 是可以从板子上插拔的, 我们可以将 SD 卡插到电脑上,在电脑上使用软件将.bin文件烧写到 SD 卡中, 然后再插到板子上就可以了。 其他的几种存储介质是我们量产的时候用到的, 量产的时候代码就不可能放到 SD 卡里面了, 毕竟 SD 是活动的, 不牢固, 而其他的都是焊接到板子上的, 很牢固。
因此,我们在调试裸机和 Uboot 的时候是将代码下载到 SD 中, 那么, 如何将我们前面编译出来的 led.bin 烧写到 SD 卡中呢? 肯定有人会认为直接复制led.bin 到 SD 卡中不就行了, 错! 编译出来的可执行文件是怎么存放到 SD 中的, 文件存放的位置NXP 是有详细规定的。 我们必须按照 NXP 的规定来将代码烧写到 SD 卡中, 否则代码是绝对运行不起来的。 《IMX6UL 参考手册》 的第 8 章“Chapter 8 SystemBoot”就是专门讲解 I.MX6U 启动的
我 们 使 用 一 个 NXP 提 供 软 件 来 将 编 译 出 来 的 .bin 文 件 烧 写 到 SD 卡 中 , 这 个 软 件 叫 做“imxdownload”,imxdownlaod 只能在 Ubuntu 下使用, 使用方法如下:
首先将工具拷贝到工程目录下并添加运行权限(chmod+权限+文件名)。 之后准备一张新的 SD(TF)卡, 确保 SD 卡里面没有有用的数据, 因为我们在烧写代码的时候可能会格式化 SD 卡。 Ubuntu 下所有的设备文件都在目录“/dev”里面, 所以插上 SD 卡以后也会出现在“/dev”里面, 其中存储设备都是以“/dev/sd”开头的。 我们要先看一下不插 SD 卡的时候电脑都有哪些存储设备, 以防插入 SD 卡以后分不清谁是谁。 输入如下所示命令:ls /dev/sd*。 例如我这里看到的结果是/dev/sda /dev/sda1 /dev/sda2 /dev/sda3四个文件, 分别代表Ubuntu下的磁盘和三个分区。 使用读卡器将 SD 卡插到电脑, 一定要确保 SD 卡是挂载到了 Ubuntu 系统中, 而不是 Windows下。 再次使用上述命令, 我这里看到的是/dev/sda /dev/sda1 /dev/sda2 /dev/sda3 /dev/sdb /dev/sdb1多出的两个文件就是sd卡及其分区了。 我们要做的就是把led.bin文件烧
写到sd卡里去。 使用命令./imxdownload led.bin /dev/sdb。 注意千万不能写成./imxdownload led.bin /dev/sda, sda是系统磁盘, 会造成Ubuntu损坏的!
烧写的最后一行会显示烧写大小、 用时和速度, 比如 led.bin 烧写到 SD 卡中的大小是 3.2KB, 用时 0.0160821s, 烧写速度是 201KB/s。 注意这个烧写速度, 如果这个烧写速度在几百 KB/s 以下那么就是正常烧写。 如果这个烧写速度大于几十 MB/s、 甚至几百 MB/s 那么肯定是烧写失败了。 解决方法就是重新插拔 SD 卡, 一般出现这种情况, 重新插拔 SD 卡基本没啥用,只有重启Ubuntu。
烧写完成以后会在当前工程目录下生成一个load.imx的文件, 这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容,在 led.bin 文件前面添加了一些数据头以后生成的。 最终烧写到 SD卡里面的其实就是这个 load.imx 文件, 而非led.bin
6、运行程序
代码已经烧写到了 SD 卡中了, 接下来就是将 SD 卡插到开发板的 SD 卡槽中, 然后设置拨码开关为 SD 卡启动, 拨码开关设置如图
运行结果:
可以看到红色的LED成功被点亮
3.5 GPIO相关其他寄存器
3.5.1 GPIO interrupt configuration register 引脚中断配置寄存器
GPIOx_ICR1 与 GPIOx_ICR2
作用:配置中断方式
| 00 | LOW_LEVEL:低电平触发 |
| 01 | HIGH_LEVEL:高电平触发 |
| 10 | RISING_EDGE:上升沿触发 |
| 11 | FALLING_EDGE:下降沿触发 |
3.5.2 GPIO edge select register
GPIOx_EDGE_SEL
作用:双边沿中断触发
| GPIO_EDGE_ SEL | 边沿选择。当 GPIO_EDGE_SEL[n] 被设置时,GPIO 会忽略 ICR[n] 的设置,并检测相应输入信号上的任意边沿----双边沿 |
3.5.3 GPIO interrupt mask register
GPIOx_IMR
作用:中断屏蔽寄存器
| IMR | 中断屏蔽位。该寄存器用于启用或禁用32个GPIO信号中的每一个的中断功能。 0 :被屏蔽— 中断 n 已被禁用。 |
3.5.4 GPIO interrupt status register
GPIOx_ISR
作用:中断状态寄存器
读操作:判断哪个引脚产生了中断(中断标志位)
写操作:清除对应引脚的中断标志(写 1 清零,写 0 无作用)
3.5.5 GPIO pad status register
GPIOx_PSR
作用:PSR 寄存器,GPIO 状态寄存器。 PSR 寄存器也是一个 GPIO 对应一个位,
读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。
只读