汇编
1、为什么要学习Cortex-A汇编:
- 需要用汇编初始化一些SOC外设。
- 使用汇编初始化DDR,I.MX6U不需要。
- 设置sp指针,一般指向DDR,设置好C语言运行环境。
2、使用指南
- 类似ldr这种可以用大写,也可以小写。但用了大写就全部大写,用了小写就全部小写。
有arm内核寄存器还有存储器中的寄存器
初始化流程
LED接在GPIO1_IO03上,低电平点亮
1、stm32
- 使能GPIO时钟。
- 设置IO复用,将其复用为GPIO(pin默认就是GPIO功能,如果要用到pin其他动能就要先设置复用。比如将PA9复用为USART1_TX)
- 配置GPIO的电气属性。
- 使用GPIO,输出高/低电平。
2、I.MX6ULL IO初始化:
-
使能时钟(在章节18),CCGR0-CCGR6这7个寄存器控制着6ULL所有外设时钟的使能。为了简单,设置CCGR0~CCGR6这7个寄存器全部为0XFFFFFFFF,相当于使能所有外设时钟。其实GPIO1_IO03,开启CCGR1的27-26位就行了,给他设置11
-
IO复用,将寄存器IOMUXC_SW_MUXCTL_PAD GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。
-
寄存器IOMUXC_SW_PADCTL_PAD GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。
/*寄存器SW_PAD_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 低转换率
*/
- 配置GPIO功能(第28章),设置输入输出。设置GPIO1_GDR寄存器bit3为1(也就是第四位,因为有IO0),也就是设置为输出模式。设置GPIO1_DR寄存器的bit3,为1表示输出高电平,为0表示输出低电平(如果设置为了输入,那就是读DR寄存器)。(注意:led实验用到的端口是GPIO1_IO3,因此这里选择的是GPIO1的地址 然后是第三位bit3)
makefile讲解
1、系统变量
2、自定义变量
- =,延迟赋值。等到调用的时候赋值。
- :=,立即赋值
- ?=,空赋值
- +=,追加赋值
3、自动化变量
- $<,第一个依赖文件
- $^,全部的依赖文件
- $@,表示目标
汇编代码书写
编写驱动
.global _start /* 全局标号 */
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/*1、时钟使能 我们这里为了方便给他们都使能了*/
ldr r0,=0x020c4068 @CCGR0的寄存器地址
ldr r1,=0xffffffff @要写入的数据 共32位,全部写1 也就是全部使能
str r1,[r0] @将r1写到r0中 也就是写入到寄存器中
@主播是把CCGR0-6都全部使能了。我查阅手册发现,GPIO3是CCGR1,其实只需要这个的27-26位使能就行了
ldr r0,=0x020c406c @CCGR1
str r1,[r0]
/*2、设置GPIO1_IO03复用为GPIO1_IO03
时钟使能 我们这里为了方便给他们都使能了*/
ldr r0,=0x020e0068
ldr r1,=0x5
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为输出 只需要设置bit3为1就行*/
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 @b就是跳转的意思,这样就是不断的跳转到loop
编译
1、使用arm-linux-gnueabihf-gcc,将.c .s文件变为.o
arm-linux-gnueabihf-gcc -g -c leds.s -o led.o
- 上述命令就是将 led.s 编译为 led.o
- 其中 “-g” 选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。“-c” 选项是编译源文件,但是不链接。“-o” 选项是指定编译产生的文件名字 led.o。执行上述命令以后就会编译生成一个 led.o 文件
- .o文件也叫作目标文件object,是一个二进制文件
2、arm-linux-gnueabihf-ld 链接文件,将所有的.o文件连接为elf格式的可执行文件。
- 链接就是将所有.o文件链接在一起,并且链接到指定的地方(也就是确定了执行地址,代码运行的时候所处的地址。还有“存储地址”就是可执行文件存储在哪里,比如这里就是存储在SD卡中,可执行文件的存储地址可以随意选择)。本实验链接的时候要指定链接起始地址。链接起始地址就是代码运行的起始地址。
- 对于6ULL来说,链接起始地址指向RAM地址。RAM分为内部RAM(6ULL内部有rom但是我们用不了,几乎所有cotex-A的芯片内部都没有flash,所以不能存在内部flash)和外部RAM(也就是 DDR)。6ULL内部RAM地址范围0X900000-0X91FFFF。也可以放到外部DDR中,对于I.MX6U-ALPHA开发板,512MB字节DDR版本的核心板,DDR范围就是0X80000000- 0X9FFFFFFF(这里的1就是1byte,0x9FFFFFFF+1-0X8000000的值转为十进制/1024/1024=512MB)。对于256MB的DDR来说,那就0X80000000~0X8FFFFFFF。
- 之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址,不容易记混。
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
- 上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件
- 注意这里的-ld 与-Ttext之间存在一个空格
3、arm-linux-gnueabihf-objcopy 格式转换,将elf文件转为bin文件。
- led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件
- 本系列视频,裸机代码的链接起始地址为0X87800000。要使用DDR,那么必须要初始化DDR,对于I.MX来说bin文件不能直接运行(严格的来说,应该是bin文件不能直接烧写到SD卡、EMMC/NAND等外置存储中,然后从这些外置存储中启动运行。而不是bin文件不能直接运行,使用Jlink将bin文件直接下载到内部RAM中还是可以运行的),需要添加一个头部,这个头部信息包含了DDR的初始化参数,I.MX系列SOC内部boot rom会从SD卡,EMMC等外置存储中读取头部信息,然后初始化DDR,并且将bin文件拷贝到指定的地方。
- Bin的运行地址一定要和链接起始地址一致。位置无关代码除外。
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
- 上述命令中,“-O” 选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。
4、将elf文件转为汇编,反汇编。
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,
因此就需要进行反汇编,一般可以将 elf 文件反汇编。
arm-linux-gnueabihf-objdump -D led.elf > led.dis
- 上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一
个名为 led.dis 文件
从图 可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以 0X87800000 为起始地址的区域。
烧写bin文件
烧写软件就是在b