• 为什么要学习Cortex-A汇编?
    ①、需要用汇编初始化一些SOC外设。
    ②、使用汇编初始化DDR(I.MX6U不需要)
    ③、设置sp指针,一般指向DDR,设置好C语言运行环境。

一、本节汇编语法知识

1、汇编入口标号
汇编程序的默认入口标号是_start,不过我们也可以在链接脚本中使用 ENTRY 来指明其它的入口点,下面的代码就是使用_starvxdfvgxzdgzdZBcgm,hvllnvp、‘、t。’ 作为入口标号:

.global _start 
_start:
	ldr r0, =0x12 //r0=0x12

2、处理器内部数据传输指令
使用处理器做的最多事情就是在处理器内部来回的传递数据,常见的操作有:
①、将数据从一个寄存器传递到另外一个寄存器。
②、将数据从一个寄存器传递到特殊寄存器,如 CPSR 和 SPSR 寄存器。
③、将立即数传递到寄存器。
数据传输常用的指令有三个:MOV、MRS 和 MSR,这三个指令的用法为:在这里插入图片描述分别来详细的介绍一下如何使用这三个指令:
MOV 指令
MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄
存器里面,使用示例如下:

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

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

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

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

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

3、存储器访问指令
ARM 不能直接访问存储器,比如 RAM 中的数据,I.MX6UL 中的寄存器就是 RAM 类型的,我们用汇编来配置 I.MX6UL 寄存器的时候需要借助存储器访问指令,一般先将要配置的值写入到 Rx(x=0~12)寄存器中,然后借助存储器访问指令将 Rx 中的数据写入到 I.MX6UL 寄存器中。读取 I.MX6UL 寄存器也是一样的,只是过程相反。常用的存储器访问指令有两种:LDR 和 STR。
在这里插入图片描述分别来详细的介绍一下如何使用这两个指令:
LDR 指令
LDR 主要用于从存储加载数据到寄存器 Rx 中,LDR 也可以将一个立即数加载到寄存器 Rx中,LDR 加载立即数的时候要使用“=”,而不是“#”。在嵌入式开发中,LDR 最常用的就是读取 CPU 的寄存器值,比如 I.MX6UL 有个寄存器 GPIO1_GDIR,其地址为 0X0209C004,我们现在要读取这个寄存器中的数据,示例代码如下:

LDR R0, =0X0209C004 //将寄存器地址 0X0209C004 加载到 R0 中,即R0=0X0209C004
LDR R1, [R0] //读取地址 0X0209C004 中的数据到 R1 寄存器中

STR 指令
LDR 是从存储器读取数据,STR 就是将数据写入到存储器中,同样以 I.MX6UL 寄存器GPIO1_GDIR 为例,现在我们要配置寄存器 GPIO1_GDIR 的值为 0X20000002,示例代码如下:

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。
4、跳转指令
这里直接讲解最简单通俗的跳转指令。
B 指令
这是最简单的跳转指令,B 指令会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指令,ARM 处理器就会立即跳转到指定的目标地址。如果要调用的函数不会再返回到原来的执行处,那就可以用 B 指令,如下示例:

 _start:
 
ldr sp,=0X80200000 //设置栈指针
b main //跳转到 main 函数

上述代码就是典型的在汇编中初始化 C 运行环境,然后跳转到 C 文件的 main 函数中运行,上述代码只是初始化了 SP 指针,有些处理器还需要做其他的初始化,比如初始化 DDR 等等。因为跳转到 C 文件以后再也不会回到汇编了,所以在第 4 行使用了 B 指令来完成跳转。

二、汇编LED原理分析

1、ALPHA开发板LED灯硬件原理分析:
在这里插入图片描述在这里插入图片描述容易知道:控制LED灯亮灭的管脚为GPIO3管脚。当GPIO3为高时,灯灭,反之灯亮。
2、I.MX6ULL IO 初始化流程:
①、使能时钟,CCM_CCGR0—CCM_CCGR6这7个寄存器控制着I.MX6ULL所有外设时钟的使能。为了简单,设置CCM_CCGR0—CCM_CCGR6这7个寄存器全部为0XFFFFFFFF,相当于使能所有外设时钟。

  • 参考手册定位
    Chapter 18​: Clock Controller Module (CCM) --> CCM Memory Map/Register Definition --> CCM -->CCM_CCGR0—CCM_CCGR6
  • 参考手册分析
    以CCM_CCGR0为例:
    在这里插入图片描述当寄存器设置为11时表明时钟在所有状态打开(除了停止状态)

在这里插入图片描述在这里插入图片描述故此时需要将CG0–CG15全部置为 11 方可打开所有时钟。

  • IO初始化汇编代码
    参考数据手册后,我们可以发现CCM_CCGR0到CCM_CCGR6依次递增4个地址,所以我们可以写出下列代码
 /* 1、使能所有时钟 */
ldr r0, =0X020C4068 /* 寄存器 CCGR0 */
ldr r1, =0XFFFFFFFF 
str r1, [r0] 

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]

②、IO复用,将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。

  • 参考手册定位
    Chapter 30: IOMUX Controller (IOMUXC) --> IOMUXC Memory Map/Register Definition --> IOMUXC --> IOMUXC_SW_MUX_CTL
    _PAD_GPIO1_IO03
  • 参考手册分析
    在这里插入图片描述我们可以看出:在完成IO复用功能时,我们只需要操作寄存器的位[4:0]即可。为了使其作为GPIO功能使用,则需要置MUX_MODE为0101;我们不强制使该端口作为输入端口用,所以我们需要DISABLE,即SION位置0
  • IO复用汇编代码
/* 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]

③、寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。

  • 参考手册定位
    Chapter 32​: IOMUX Controller (IOMUXC) --> IOMUXC Memory Map/Register Definition --> IOMUXC --> IOMUXC_SW_PAD_CTL
    _PAD_GPIO1_IO03
  • 参考手册分析
    在这里插入图片描述在这里插入图片描述 - IO电气属性汇编代码
 /* 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]
  • 小技巧
    为了更方便的将二进制转换为十六进制,则可以使用win10自带的小计算器。
    在这里插入图片描述
    ④、配置GPIO功能,设置输入输出。设置GPIO1_DR寄存器bit3为1,也就是设置为输出模式。设置GPIO1_DR寄存器的bit3,为1表示输出高电平,为0表示输出低电平。
  • 参考手册定位
    Chapter 28​: General Purpose Input/Output (GPIO) --> GPIO Memory Map/Register Definition --> GPIOx --> GPIOx_GDIR,GPIOx_DR
  • 参考手册分析
    在这里插入图片描述我们将寄存器设置为输出模式,故寄存器的第3位置1。
  • IO输入输出模式汇编代码
/* 4、设置 GPIO1_IO03 为输出 */
ldr r0, =0X0209C004 /*寄存器 GPIO1_GDIR */
ldr r1, =0X0000008 
str r1,[r0]
  • LED灯点亮代码
    为了点亮LED,则只需要将GPIO1_DR置0即可
/* 5、打开 LED0
 * 设置 GPIO1_IO03 输出低电平
 */
 ldr r0, =0X0209C000 /*寄存器 GPIO1_DR */
 ldr r1, =0 
 str r1,[r0]

三、最终代码

.global _start /* 全局标号 */
_start:
 /* 1、使能所有时钟 */
ldr r0, =0X020C4068 /* 寄存器 CCGR0 */
ldr r1, =0XFFFFFFFF 
str r1, [r0] 

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

四、编译下载验证

①编写Makefile文件
该文件需要完成的功能:使用arm-linux-gnueabihf-gcc,将.c .s文件变为.o、将所有的.o文件连接为elf格式的可执行文件、将elf文件转为bin文件、
将elf文件转为汇编,反汇编。最终的代码如下:

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

创建好 Makefile 以后我们就只需要执行一次“make”命令即可完成编译。
在这里插入图片描述
如果我们要清理工程的话执行“make clean”即可
在这里插入图片描述至此,有关代码编译、arm-linux-gnueabihf 交叉编译器的使用就到这里了,我们接下来讲解如何将 led.bin 烧写到 SD 卡中。
②代码烧写
我们学习 STM32 等其他的单片机的时候,编译完代码以后可以直接通过 MDK 或者 IAR下载到内部的 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 中,因为方便嘛,当调试完成以后量产的时候要将裸机或者 Uboot 烧写到 SPI NOR Flash、EMMCNAND 等这些存储介质中的。那么,如何将我们前面编译出来的 led.bin 烧写到 SD 卡中呢?肯定有人会认为直接复制led.bin 到 SD 卡中不就行了,错!编译出来的可执行文件是怎么存放到 SD 中的,存放的位置是什么?这个NXP 是有详细规定的!我们必须按照 NXP 的规定来将代码烧写到 SD 卡中,否则代码是绝对运行不起来的。《IMX6UL 参考手册》的第 8 章“Chapter 8 System Boot”就是专门讲解 I.MX6U 启动的。我们需要完成以下步骤:

将 imxdownload 拷贝到工程根目录下
我们要将 imxdownload 拷贝到工程根目录下,也就是和 led.bin 处于同一个文件夹下,要不然烧写会失败的。
在这里插入图片描述给予 imxdownload 可执行权限
我们直接将软件 imxdownload 从 Windows 下复制到 Ubuntu 中以后,imxdownload 默认是没有可执行权限的。我们需要给予 imxdownload 可执行权限,使用命令“chmod”,命令如下:
在这里插入图片描述向 SD 卡烧写 bin 文件
在这里插入图片描述烧写的最后一行会显示烧写大小、用时和速度,比如 led.bin 烧写到 SD 卡
中的大小是 3.2KB,用时 0.0160821s,烧写速度是 201KB/s。注意这个烧写速度,如果这个烧写速度在几百 KB/s 以下那么就是正常烧写。如果这个烧写速度大于几十 MB/s、甚至几百 MB/s 那么肯定是烧写失败了!
烧写完成以后会在当前工程目录下生成一个 load.imx 的文件。
在这里插入图片描述

五、开发板实操

代码已经烧写到了 SD 卡中了,接下来就是将 SD 卡插到开发板的 SD 卡槽中,然后设置拨码开关为 SD 卡启动。
在这里插入图片描述设置好以后按一下开发板的复位键,如果代码运行正常的话 LED0 就会被点亮
在这里插入图片描述LED0 被正常点亮,可能 LED0 之前会有一点微亮,那是因为 I.MX6U
的 IO 默认电平可能让 LED0 导通了,但是 IO 的默认配置内部可能有很大的电阻,所以电流就很小,导致 LED0 微亮。但是我们自己编写代码、配置好 IO 以后就不会有这个问题,LED0 就很亮了。

Logo

更多推荐