I.MX6U-ALPHA 开发板学习 —— 从基础汇编指令到点亮LED

Rebecca ·
更新时间:2024-09-21
· 549 次阅读


参考《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0》

目录【1】 Arm与Cortex【1.1】 Arm下的字节、半字和字【2】 交叉编译简介【2.1】 安装步骤【3】汇编语法【3.1】 GNU 汇编语法【3.2】Cortex-A7 常用汇编指令【3.2.1】数据传输指令【3.2.2】存储器访问指令【3.2.3】压栈和出栈指令【3.2.4】异常处理机制【3.2.5】跳转指令【3.2.6】算术运算指令【3.2.7】逻辑运算指令【4】 I.MX6U-ALPHA 开发板【4.1】 Cortex-A 的内核寄存器组【4.2】 I.MX6U IO的命名【4.3】 区分 IO 和 GPIO【4.4】 I.MX6U IO的复用【4.5】 I.MX6U IO的配置【4.6】 I.MX6U GPIO 配置【4.7】I.MX6U GPIO 时钟使能【4.8】GPIO的配置步骤【5】点亮LED【6】结束 【1】 Arm与Cortex

STM32F系列是中低端的32位ARM微控制器,由意法半导体(ST)公司出品,其内核是Cortex-M3

I.MX6U-ALPHA 开发板是一款以 NXP 的 I.MX6UL/ULL 为核心的 Cortex-A7开发平台

  arm系列从arm11开始,以后的就命名为cortex,并且性能上大幅度提升。从cortex开始,分为三个系列,a系列,r系列,m系列

  m系列与arm7相似,不能跑操作系统(只能跑ucos2),偏向于控制方面,说白了就是一个高级的单片机

  arm7是最早的arm产品。m3是cortex m系列的过渡品,其低端市场被cortex m0的高端替代, 其高端市场又被cortex m4的低端取代

  arm9 和cortex a8 是一个类型的,都是跑操作系统的,现在的高端手机,三星,htc等智能手机,就是用的cortex a8,cortex a9 内核的芯片作为cpu

  (1) ARM7,ARM9属于v4T或v5E架构
  (2) ARM11属于v6架构
  (3) Contex属于v7架构

  ARM7,ARM9的区别在于是否有MMU(存储器管理单元)或MPU(存储器保护单元)
  

【1.1】 Arm下的字节、半字和字

ARM 采用的是32位架构,约定:

Byte : 8 bits Halfword :16 bits (2 byte) Word : 32 bits (4 byte)

  
  

【2】 交叉编译简介

交叉编译器: ubuntu自带的gcc编译器是针对X86架构的,如果需要编译ARM架构的代码,就需要安装一个在X86架构的PC上可以运行且编译ARM架构代码的编译器,这个编译器就叫交叉编译器(在一个架构上编译另一个架构的代码)
1、是GCC编译器
2、在X86架构上运行
3、编译出来的可执行文件是在ARM芯片上运行

启动时,CortexA需要用汇编初始化一些SOC外设,设置好C语言运行环境(SP指针指向DDR)
  

【2.1】 安装步骤

下载链接:Linaro GCC 编译器

下载 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

比如放在目录:/usr/local/arm,然后解压

sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

修改环境变量,在最后面加入内容,然后重启

sudo vi /etc/profile

export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin

安装相关库

sudo apt-get install lsb-core lib32stdc++6

查看是否安装成功,有显示版本号即可

arm-linux-gnueabihf-gcc –v

使用交叉编译器的命令:

arm-linux-gnueabihf-gcc
// 1、arm 表示这是编译 arm 架构代码的编译器
// 2、linux 表示运行在 linux 环境下
// 3、gnueabihf 表示嵌入式二进制接口
// 4、gcc 表示是 gcc 工具

  
  

【3】汇编语法 【3.1】 GNU 汇编语法

  Cortex-A 芯片一上电 SP 指针还没初始化,C 环境还没准备好,所以肯定不能运行 C 代码,必须先用汇编语言设置好 C 环境,比如初始化 DDR、设置 SP指针等等,当汇编把 C 环境设置好了以后才可以运行 C 代码

  编写的是 ARM汇编,编译使用的 GCC 交叉编译器,汇编代码要符合 GNU 语法,GNU 汇编语法适用于所有的架构,并不是 ARM 独享的。每行一条语句,每条语句有三个可选部分,如下:

label:instruction @ comment

label 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的冒号“:”,任何以冒号“:”结尾的标识符都会被认识是一个标号。

instruction 即指令,也就是汇编指令或伪指令。

@符号,表示后面的是注释,其实在 GNU 汇编文件中也可以使用“ /* ” 和 “ */ ”来注释,comment 就是注释内容

  ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用

  使用.section 伪操作来定义一个段,汇编系统预定义了一些段名:

.text 表示代码段。
.data 初始化的数据段。
.bss 未初始化的数据段。
.rodata 只读数据段。

  可以自定义段:.section .testsection @定义一个 testsetcion 段

  汇编程序的默认入口标号是_start,也可以在链接脚本中使用 ENTRY 来指明其它的入口点

.global _start @.global 是伪操作,表示_start 是一个全局标号 _start: ldr r0, =0x12 @r0=0x12

常见的伪操作有:

.byte 定义单字节数据,比如.byte 0x12

.short 定义双字节数据,比如.byte 0x1234

.long 定义一个 4 字节数据,比如.long 0x12345678

.equ 赋值语句,格式为:.equ 变量名,表达式,比如.equ num, 0x12,表示 num=0x12

.align 数据字节对齐,比如:.align 4 表示 4 字节对齐

.end 表示源文件结束

.global 定义一个全局符号,格式为:.global symbol

GNU 汇编同样也支持函数

函数名: 函数体 返回语句 @返回语句不是必须的 /* SVC 中断 */ SVC_Handler: ldr r0, =SVC_Handler bx r0

【3.2】Cortex-A7 常用汇编指令 【3.2.1】数据传输指令

  MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面

MOV R0,R1 @将寄存器 R1 中的数据传递给 R0,即 R0=R1
MOV R0, #0X12 @将立即数 0X12 传递给 R0 寄存器,即 R0=0X12

  MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令!

MRS R0, CPSR @将特殊寄存器 CPSR 里面的数据传递给 R0,即 R0=CPSR

  MSR 指令和 MRS 刚好相反,MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用MSR

MSR CPSR, R0 @将 R0 中的数据复制到 CPSR 中,CPSR=R0

【3.2.2】存储器访问指令

  每次只能读写存储器中的一个数据

  ARM 不能直接访问存储器,比如 RAM 中的数据,一般先将要配置的值写入到 Rx(x=0~12)寄存器中,然后借助存储器访问指令将 Rx 中的数据写入到寄存器,读取的过程也一样

  LDR 指令主要用于从存储加载数据到寄存器 Rx 中,LDR 也可以将一个立即数加载到寄存器 Rx中,LDR 加载立即数的时候要使用“=”,而不是“#”

LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004

LDR R1, [R0] @读取地址 0X0209C004 中的数据到 R1 寄存器中

  STR 指令则是将数据写入到存储器中

LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004

LDR R1, =0X20000002 @R1 保存要写入到寄存器的值,即 R1=0X20000002

STR R1, [R0] @将 R1 中的值写入到 R0 中所保存的地址中

  LDR 和 STR都是按照字进行读取和写入的,也就是操作的 32 位数据,如果要按照字节、半字进行操作的话可以在指令“LDR”后面加上 B 或 H,比如按字节操作的指令就是 LDRB 和STRB,按半字操作的指令就是 LDRH 和 STRH

【3.2.3】压栈和出栈指令

  保存 R0~ R15 寄存器的操作就叫做现场保护,恢复R0~R15 寄存器的操作就叫做恢复现场。在进行现场保护的时候需要进行压栈(入栈)操作,恢复现场就要进行出栈操作

  压栈的指令为 PUSH,出栈的指令为 POP,PUSH 和 POP 是一种多存储和多加载指令,即可以一次操作多个寄存器数据,他们利用当前的栈指针 SP 来生成地址

PUSH @将寄存器列表存入栈中。 POP @从栈中恢复寄存器列表。

假设当前的 SP 指针指向 0X80000000
对于压栈
PUSH {R0~R3, R12} @ 将 R0 ~ R3 和 R12 压栈
PUSH {LR} @将 LR 进行压栈

对于出栈:
从栈顶SP 当前执行的位置开始,地址依次减小来提取堆栈中的数据到要恢复的寄存器列表中
POP {LR} @先恢复 LR
POP {R0~R3,R12} @ 在恢复 R0 ~ R3,R12

PUSH 和 POP 的另外一种写法:

STMFD SP!,{R0~R3, R12} @R0~R3,R12 入栈
STMFD SP!,{LR} @LR 入栈

LDMFD SP!, {LR} @先恢复 LR
LDMFD SP!, {R0~R3, R12} @再恢复 R0~R3, R12

  STMFD 可以分为两部分:STM 和 FD,同理,LDMFD 也可以分为 LDM 和 FD,STM 和 LDM 是多加载和多存储,可以连续的读写存储器中的多个连续数据;FD(Full Descending)表示满递减,ARM 使用的 FD 类型的堆栈,SP 指向最后最后一个入栈的数值

  

【3.2.4】异常处理机制

  异常的发生会导致程序正常运行的被打断, 并将控制流转移到相应的异常处理(异常响应),有些异常(fiq、irq)事件处理后,系统还希望能回 到当初异常发生时被打断的源程序断点处继续完成源程序的执行(异常返回),类似的还有子程序的调用和返回

  ARM处理器中使用 R14实现对断点和调用点的记录,也称回连接寄存器(LR), 用于记录源程序的断点位置,以便正确的异常返回。

  ARM处理器相应异常时,会自动完成将当前的PC保存到LR寄存器。ARM处理器执行子程序调用指令(BL )时,会自动完成将当前的PC的值减去4的结果数据保存到LR寄存器,即将调用指令的下紧邻指令的地址保存到LR

【3.2.5】跳转指令

①、直接使用跳转指令 B、BL、BX 等。
②、直接向 PC 寄存器里面写入数据。

B 指令: 会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指令,ARM 处理器就会立即跳转到指定的目标地址(不记录当前状态)

在汇编中初始化 C 运行环境,然后跳转到 C 文件 main 函数中运行

_start:
  ldr sp,=0X80200000 @设置栈指针,初始化SP 指针
  b main @跳转到 main 函数

BL 指令: 跳转之前在寄存器 LR(R14)中保存当前 PC 寄存器值,跳转结束后再将 LR 寄存器中的值重新加载到 PC 中来就可以继续从跳转之前的代码处运行,是子程序调用一个基本但常用的手段

  在Cortex-A 处理器的 irq 中断服务函数中,先使用汇编来实现现场的保护和恢复、获取中断号等,然后执行具体的中断处理过程时( C 函数),需要在汇编中调用 C 函数,执行完成后再返回到irq 汇编中断服务函数,执行恢复现场等操作

push {r0, r1} @保存 r0,r1
cps #0x13 @进入 SVC 模式,允许其他中断再次进去
bl system_irqhandler @加载 C 语言中断处理函数到 r2 寄存器中
cps #0x12 @进入 IRQ 模式
pop {r0, r1}
str r0, [r1, #0X10] @中断执行完成,写 EOIR

  

【3.2.6】算术运算指令

  

【3.2.7】逻辑运算指令

  
  

【4】 I.MX6U-ALPHA 开发板 【4.1】 Cortex-A 的内核寄存器组

  ARM 架构提供了 16 个 32 位的通用寄存器(R0 ~ R15)供软件使用,前 15 个(R0~R14)可以用作通用的数据存储,R15 是程序计数器 PC,用来保存将要执行的指令。ARM 还提供了一个当前程序状态寄存器 CPSR 和一个备份程序状态寄存器 SPSR,SPSR 寄存器就是 CPSR 寄存器的备份

R0~R15 就是通用寄存器:
①、未备份寄存器,即 R0~R7。
②、备份寄存器,即 R8~R14。
③、程序计数器 PC,即 R15。

  ARM 处理器 3 级流水线:取指->译码->执行,如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中,R15(PC)中存放的就是第三条指令,对于 32 位的 ARM 处理器,每条指令是 4 个字节,因此R15 保存着当前执行的指令地址值加 8 个字节

  CPSR 是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。

  当特定的异常中断发生时,SPSR 寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR
  

【4.2】 I.MX6U IO的命名

格式:“IOMUXC_SW_MUC_CTL_PAD_XX_XX”

后面的“XX_XX”就是 GPIO 命名,比如有:GPIO1_IO01、UART1_TX_DATA、JTAG_MOD、SNVS_TAMPER1 等等

【4.3】 区分 IO 和 GPIO IO不是 GPIO,GPIO 是一个 IO 众多复用功能中的一种。比如 GPIO1_IO00 这个 IO 可以复用为: I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID、
ENET1_REF_CLK 、 MQS_RIGHT 、 GPIO1_IO00 、 ENET1_1588_EVENT0_IN 、SRC_SYSTEM_RESET 和 WDOG3_WDOG_B 这 9 个功能 GPIO1_IO00 是其中的一种
【4.4】 I.MX6U IO的复用

  对于寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00,寄存器地址为 0X020E005C, 32 位,但是只用到了最低 5 位,其中bit0 ~ bit3(MUX_MODE)就是设置 GPIO1_IO00 的复用功能;

  GPIO1_IO00 一共可以复用为 9 种功能 IO,分别对应 ALT0~ALT8,其中 ALT5 就是作为 GPIO1_IO00

  IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA寄存器,UART1_TX_DATA 可以复用为 8 种不同功能的 IO,分为
ALT0~ALT5 和 ALT8、ATL9,其中 ALT5 表示 UART1_TX_DATA 可以复用为 GPIO1_IO16

GPIO组

I.MX6U 的 GPIO 一共有 5 组:GPIO1、GPIO2、GPIO3、GPIO4 和 GPIO5, 其中 GPIO1 有 32 个 IO,GPIO2 有 22 个 IO,GPIO3 有 29 个 IO、GPIO4 有 29 个 IO,GPIO5最少,只有 12 个 IO 一共有 124 个 GPIO

【4.5】 I.MX6U IO的配置

  IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00用来配置 GPIO1_IO00 的,包括速度设置、驱动能力设置、压摆率设置等等

  寄存器地址为 0X020E02E8。这也是个 32 位寄存器,但是只用到了其中的低 17 位

1、HYS(bit16):对应上图的 HYS,用来使能迟滞比较器,当 IO 作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使能此位。此位为 0 的时候禁止迟滞比较器,为 1 的时候使能迟滞比较器

2、PUS(bit15:14):对应上图的 PUS,用来设置上下拉电阻的

3、PUE(bit13):当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器。当为 0 的时候使用状态保持器,当为 1 的时候使用上下拉。状态保持器在IO 作为输入的时候才有用,顾名思义,就是当外部电路断电以后此 IO 口可以保持住以前的状态

4、PKE(bit12):对应GPIO电路图的 PKE,此为用来使能或者禁止上下拉/状态保持器功能,为0 时禁止上下拉/状态保持器,为 1 时使能上下拉和状态保持器

5、ODE(bit11):对应GPIO电路图的 ODE,当 IO 作为输出的时候,此位用来禁止或者使能开路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能

6、SPEED(bit7:6):对应GPIO电路图的 SPEED,当 IO 用作输出的时候,此位用来设置 IO 速度

7、DSE(bit5:3):对应GPIO电路图的 DSE,当 IO 用作输出的时候用来设置 IO 的驱动能力

8、SRE(bit0):对应GPIO电路图的 SRE,设置压摆率,当此位为 0 的时候是低压摆率,当为 1的时候是高压摆率

  这里的压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时间,时间越小波形就越陡,说明压摆率越高;反之,时间越多波形就越缓,压摆率就越低

【4.6】 I.MX6U GPIO 配置

  上图的左下角的 IOMUXC 框图里面就有SW_MUX_CTL_PAD_*SW_PAD_CTL_PAD_* 两种寄存器,分别用来设置 IO 的复用功能和 IO 属性配置

  上图的左上角部分为,当 IO 用作 GPIO 的时候需要设置的寄存器:DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR(每组 GPIO 都有这 8 个寄存器)

① DR寄存器,32位,可读写

② GDIR 寄存器(方向寄存器),用来设置某个 GPIO 的工作方
向的,即输入/输出(0/1)

③ PSR 寄存器(状态寄存器),可获取GPIO 的高低电平值,功能和输入状态下的 DR 寄存器一样

④ ICR1和ICR2寄存器(中断控制寄存器),ICR1用于配置低16个GPIO,ICR2 用于配置高 16 个 GPIO,一个 GPIO 用两个位来配置中断的触发方式

⑤ IMR 寄存器(中断屏蔽寄存器),用来控制 GPIO 的中断禁止和使能

⑥ ISR寄存器(中断状态寄存器),只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置 1,处理完中断以后,必须清除中断标志位,清除方法就是向 ISR 中相应的位写 1,也就是写 1 代表清零

⑦ EDGE_SEL 寄存器(边沿选择寄存器),器用来设置边沿中断,这个寄存器会覆盖 ICR1 和 ICR2 的设置,如果相应的位被置 1,那么就相当于设置了对应的 GPIO 是双边沿(上升沿和下降沿)触发

【4.7】I.MX6U GPIO 时钟使能

  每个外设的时钟都可以独立的使能或禁止,这样可以关闭掉不使用的外设时钟,起到省电的目的。如果要使用某个外设的话必须要先使能其时钟

  简单了解一下 CCM 里面的外设时钟使能寄存器包含CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,控制着 I.MX6U 的所有外设时钟开关

  对于CCM_CCGR0:

  CCM_CCGR0为32为位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着GPIO2 的外设时钟

  如果要打开 GPIO2 的外设时钟,只需要设置CCM_CCGR0 的 bit31 和 bit30 都为 1 即可,也就是 CCM_CCGR0=3 << 30。反之,如果要关闭GPIO2 的外设时钟,那就设置CCM_CCGR0 的 bit31 和 bit30 都为0即可

  

【4.8】GPIO的配置步骤 使能 GPIO 对应的时钟 设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能 设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等 第②步已经将 IO 复用为了 GPIO 功能,所以需要配置 GPIO,设置输入/输出、是否使用中断、默认输出电平等


【5】点亮LED

  通过开发板原理图知道,LED0 (接在 GPIO_3 上,GPIO_3 就是 GPIO1_IO03)

步骤:
① GPIO1 时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制
② IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03复用为 ALT5
③ IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03配置
④ 将GPIO3_GDIR 的 bit3 设置为 1,表示输出
⑤ 向 GPIO3_DR 寄存器的 bit3 写入 0 即可控制 GPIO1_IO03 输出低电平,打开 LED,向 bit3 写入 1 可控制 GPIO1_IO03 输出高电平,关闭 LED

  
1、工程目录新建一个名为“led.s”的汇编文件

.global _start /* 全局标号 */ /* * 描述: _start 函数,程序从此函数开始执行此函数完成时钟使能、 * GPIO 初始化、最终控制 GPIO 输出低电平来点亮 LED 灯。 */ _start: //代码从这里开始执行 /* 例程代码 */ /* 1、使能所有时钟 */ ldr r0, =0X020C4068 /* 寄存器 CCGR0 */ //r0=0X020C4068 ldr r1, =0XFFFFFFFF //r1=0XFFFFFFFF str r1, [r0] //将 r1 中的值写入到 r0 所保存的地址中去,相当于 CCM_CCGR0=0XFFFFFFFF,打开 CCM_CCGR0 寄存器所控制的所有外设时钟 //下面和上面一样,打开所有的时钟(方便后面的学习实验) ldr r0, =0X020C406C /* 寄存器 CCGR1 */ str r1, [r0] ldr r0, =0X020C4070 /* 寄存器 CCGR2 */ str r1, [r0] ldr r0, =0X020C4074 /* 寄存器 CCGR3 */ str r1, [r0] ldr r0, =0X020C4078 /* 寄存器 CCGR4 */ str r1, [r0] ldr r0, =0X020C407C /* 寄存器 CCGR5 */ str r1, [r0] ldr r0, =0X020C4080 /* 寄存器 CCGR6 */ str r1, [r0] /* 2、设置 GPIO1_IO03 复用为 GPIO1_IO03 */ ldr r0, =0X020E0068 /* 将寄存器 SW_MUX_GPIO1_IO03_BASE 加载到 r0 中 */ ldr r1, =0X5 /* 设置寄存器 SW_MUX_GPIO1_IO03_BASE 的 MUX_MODE 为 5 */ str r1,[r0] /* 3、配置 GPIO1_IO03 的 IO 属性 *bit 16:0 HYS 关闭 *bit [15:14]: 00 默认下拉 *bit [13]: 0 kepper 功能 *bit [12]: 1 pull/keeper 使能 *bit [11]: 0 关闭开路输出 *bit [7:6]: 10 速度 100Mhz *bit [5:3]: 110 R0/6 驱动能力 *bit [0]: 0 低转换率 */ ldr r0, =0X020E02F4 /*寄存器 SW_PAD_GPIO1_IO03_BASE */ ldr r1, =0X10B0 str r1,[r0] /* 4、设置 GPIO1_IO03 为输出 */ ldr r0, =0X0209C004 /*寄存器 GPIO1_GDIR */ ldr r1, =0X0000008 str r1,[r0] /* 5、打开 LED0 * 设置 GPIO1_IO03 输出低电平 */ ldr r0, =0X0209C000 /*寄存器 GPIO1_DR */ ldr r1, =0 str r1,[r0] /* * 描述: loop 死循环 */ loop: b loop

2、使用交叉编译器 arm-linux-gnueabihf-gcc 来编译,将 led.s 编译为 led.o

arm-linux-gnueabihf-gcc -g -c led.s -o led.o

其中“-g”选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o。执行上述命令以后就会编译生成一个 led.o 文件

3、使用arm-linux-gnueabihf-ld 链接文件

  先确定可执行文件其运行起始地址,也就是链接地址

  “存储地址”就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。“运行地址”就是代码运行的时候所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错

  比如烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,比如使用的链接地址都在 DDR中,链接起始地址为 0X87800000

I.MX6U-ALPHA 开发板的 DDR 容量有两种:512MB 和256MB,起始地址都为 0X80000000,只不过 512MB 的终止地址为0X9FFFFFFF,而 256MB 容量的终止地址为 0X8FFFFFFF 之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址,不容易记混

3、将 led.o 文件链接到 0X87800000 这个地址

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件

4、使用arm-linux-gnueabihf-objcopy 格式转换将 led.elf 文件转换为.bin 文件

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息

5、使用arm-linux-gnueabihf-objdump 反汇编将 elf 文件反汇编

arm-linux-gnueabihf-objdump -D led.elf > led.dis

上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件

总结:

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

6、创建 Makefile 文件
① 在工程根目录下创建一个名为“Makefile”的文件
② 输入以下内容:

led.bin:led.s 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 clean: rm -rf *.o led.bin led.elf led.dis

③ 编译:输入make即可完成从.s文件到.bin文件
④ 清理工程:make clean

7、代码烧写

  虽然I.MX6U 内部有 96K 的 ROM,但是这 96K 的 ROM 是 NXP自己用的,不向用户开放。相当于说 I.MX6U 是没有内部 flash 的,为此,I.MX6U 支持从外置的 NOR Flash、NAND Flash、SD/EMMC、SPI NOR Flash和 QSPI Flash 这些存储介质中启动,所以我们可以将代码烧写到这些存储介质中中

  必须按照 NXP 的规定来将代码烧写到 SD 卡中,才能正确运行代码

  可以使用正点原子专门编写的“imxdownload”工具将编译出来的.bin 文件烧写到 SD 卡中,下载链接:链接: https://pan.baidu.com/s/17322iKZU_z7rsBoyxecu_Q 提取码: 2333

① 将 imxdownload 拷贝到工程根目录下(和 led.bin 处于同一个文件夹下)
② 给予 imxdownload 可执行权限

chmod 777 imxdownload

③ 确定要烧写的 SD 卡,确保 SD 卡里面没有数据,因为过程可能会格式化 SD 卡

④ 输入以下指令查找SD卡:

ls /dev/sd*

  显示结果中:/dev/sdd 和/dev/sdd1 是插入的 SD 卡,/dev/sdd1 是 SD 卡的第一个分区。如果你的 SD 卡有多个分区的话可能会出现/dev/sdd2、/dev/sdd3 等等

⑤ 向 SD 卡烧写 bin 文件

./imxdownload

比如烧写led.bin 到/dev/sdd

./imxdownload led.bin /dev/sdd

烧写速度在几百 KB/s 以下才表示正常烧写

  
  

【6】结束

如有错误,还望指正



alpha mx 汇编指令 学习

需要 登录 后方可回复, 如果你还没有账号请 注册新账号