我们今天分享如何用裸机开发,点亮I.MX6ULL;我使用的是正点原子阿尔法IMX6ULL开发板,EMCC版本,目前还是处于学习中。
作为点灯大师,万里长征第一步从点灯开始。
如何点亮LED
我们在编写STM32点灯代码时候,第一步要做的是了解需要点亮哪一个灯,查看原理图找到LED的连接引脚和什么样的输出状态才能让LED点亮。
我们找到正点原子的原理图,找到LED的部分,发现其的LED是与GPIO0_IO03连接,当属于引脚输出低电平时候点亮。
配置引脚
我们对比STM32使用标准库对一个GPIO引脚初始化的代码,
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能 PB 端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PB5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化 GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
}
可以发现STM32初始化引脚分为
- 初始化时钟
- GPIO配置
- GPIO是否使用复用功能
- GPIO输出电平
那么很显然,我们要用的IMX6ULL和STM32的步骤是相同的,下面我们就开始照着官方参考手册一行行写汇编。
配置部分
初始化时钟
查找官方参考手册,寻找“Clock Controller Module”(时钟控制模块),在该目录下有我们所用的寄存器
我们就拿CCM Clock Gating Register 0 (CCM_CCGR0)进行分析,在手册中是这样介绍的“These bits are used to turn on/off the clock to each module independently. The following table details the possible clock activity conditions for each module”大概跟大家翻译一下,这些位都是可以独立控制模块时钟的使能的,下面就是它的详细介绍。
可以看到我们有3种状态,2个位表示:
- 00:时钟使能的
- 01:时钟只在运行的时候启动,等待或停止模式下是不使能的
- 10:无意义
- 11:除了停止模式,其他时候都是运行的
我们可以选择11来使能时钟,我们使用的是GPIO1,我们找到,GPIO1的时钟使能是通过CCM_CCGR1的27-26位控制的。
我们只需要让CCM_CCGR1中的27-26位为1就可以了,或者我们直接让所有位均为1。
GPIO复用配置
我们点亮LED使用的肯定是输出模式,控制GPIO复用模式的寄存器为“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03”,我们需要将它设置为0x5。
GPIO电气属性
设置GPIO电气属性的寄存器为“IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03”
0 SRE:设置压摆率,当此位为0,是低压摆率;当为1时,是高压摆率。这里压摆率就是IO电平跳变的时间,电平跳变的时间越小波形越陡,说明压摆率越高,反之则压摆率越低。如果此IO用于高速通信,建议使用高压摆率。
5:3 DSE:用于设置驱动能力,共有8个选择:
7:6 SPEED:设置IO的速率
- 00:低速 50M
- 01/10:中速 100M
- 11:最大速度 200M
11 ODE:设置IO禁止或者使能开漏输出,0为禁止开漏输出,1为使能开漏输出。
12 PKE:设置IO禁止或者使能上下拉/状态保持器功能,1为使能上下拉和状态保持器,0为禁止上下拉/状态保持器。
13 PUR:设置使用上下拉还是使用状态保存期,0是用状态保存期,q的使用上下拉。
15:14 PUS:设置上下拉电阻
16 HYS:用来使能迟滞比较器(不了解)
现在我们已经分析完了电气属性中每个位的功能,没有分析到的就是无用的位,我们现在开始编写汇编代码。
配置GPIO功能
配置GPIO的输入或输出的寄存器是”GPIOx_GDIR“,我们使用的GPIO1对应的地址为0x0209C004,该寄存器中的每一个位对应一个IO,0为输入,1为输出。
设置电平状态
这是电平状态的寄存器为”GPIOxDR“,我们使用的为”GPIO1DR“
汇编代码
.global _start @全局标号
_start:
/* 使能时钟
* 使能所有时钟
*/
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]
/* 配置GPIO
* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器地址为0x02E0068
* 设置 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03为5
*/
ldr r0, =0x02E0068
ldr r1, =0x5
str r1,[r0]
/*设置电气属性
* IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03)寄存器地址为0x020E02F4
* 0 压摆率:0
* 5:3 驱动能力:110
* 7:6 速率:10
* 11 开路输出: 0
* 12 上下拉/状态保存使能:1
* 13 上下拉/状态保存设置:0
* 14:15 上下拉电阻:00
* 迟滞比较器使能:0
*/
ldr r0, =0x020E02F4
ldr r1, =0x10B0
str r1, [r0]
/*设置GPIO功能
* GPIO1_GDIR寄存器地址为0x0209C004
* 设置GPIO1_GDIR的bit3为1
*/
ldr r0, =0x0209C004
ldr r1, =0x8
str r1, [r0]
/* 开灯,设置GPIO0_IO3为0
* GPIO1DR寄存器的地址为0x0209C000
*/
ldr r0, =0x0209C000
ldr r1, =0x0
str r1, [r0]
loop:
b loop
编译程序
将.c.s文件变为.o、
需要使用交叉编译器arm-linux-gnueabihf-gcc来编译,工作空间的文件目录下输入指令:
//arm-linux-gnueabihf-gcc -g -c 汇编文件 -o 转换后的文件名.o
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
“-g”选项是产生调试信息,GND能够使用这些调试信息进行代码调试;“-c”选项是编译源文件,而不是链接;“-o”选项是指定编译产生的文件名称。
将所有.o文件连接为elf格式的可执行文件
需要使用交叉编译器arm-linux-gnueabihf-ld来连接,将多个.o文件连接到一个指定的链接位置(地址)。所以我们在链接的时候要指定链接起始地址,起始地址就是代码运行的起始地址。
6ULL链接地址应指向RAM,因为Cortex-A没有内部flash。RAM范围为(0x900000~0x91FFFF)。或者放到外部DDR中。(正点原子阿尔法开发板的512MB)DDR的地址范围为(0x80000000~0x9FFFFFFF)。
正点原子使用文档中,使用的是0x87800000,是与Uboot统一使用,不容易记混。
我们使用命令
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
- -Ttext 表示链接地址
- -o 表示指定链接生成的elf文件
我们此时工作空间就多了一个elf文件,但是我们最后烧录的是bin文件,所以还需要将elf转为bin文件。
将elf文件转为bin文件
使用命令“arm-linux-gnueabihf-objcopy”
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
- -O指定什么格式输出
- binary 二进制格式输出
- -S表示不要复制源文件中的重定位信息和符号信息
- -g表示不复制源文件的调试信息
将elf文件转为汇编(反汇编)
arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前工作空间下出现一个名为 led.dis 文件。
烧录程序
烧录使用的是SD卡方式,(SD卡必须支持FAT32模式),32G一下默认为FAT32模式。
在Ubuntu下向SD卡烧写程序,烧写程序为将bin文件烧写到SD卡绝对地址上。 而且对于IMX6ULL而言,不能直接烧录bin文件,比如要在bin文件添加头部。这个部分我使用的是正点原子提供的imxdownload软件。
将imxdownload复制到工作空间中
将imxdownload复制到工作空间,并基于执行权限,这里的777是给予全部权限,然后可以看到我们的imxdownload会变成绿色。
chmod 777 imxdownload
查看SD卡位置
使用ls /dev/sd* -l查找,我们在拔出时候查看一次,插入之后再查看一次,新增的就是我们的SD卡位置。
可以看到,我的虚拟机上每次插拔,sdb会重新出现,同时伴随着sdb1。
烧写
烧写的命令是
// ./imxdownload 烧写的文件 烧写的地址
./imxdownload led.bin /dev/sdb
烧写完成后,他会显示时间和内存大小,与STM32类似。然后我们可以发现,此时我们工作空间中多了一个load.imx文件,那么load.imx就是我们要最后烧录的SD卡的文件。
插板
我们将SD卡插入到板子中,按复位键后(将拨码拨到SD卡模式),1-2秒后LED点亮。
为什么会延时:因为内部Uboot运行,首先检测从什么地方启动代码,从SD卡中读代码,SD卡头部中又有一些配置信息。然后先把我们代码放到我们的链接地址中,然后才是运行我们的代码,所以会有1-2s的延时。
恭喜你,成功称为I.MX6ULL的点灯大师!!!
在Linux中体现编译烧录的过程,岂不快哉?
如果你看到这了,那么我再讲一点吧
快速的编译烧录
我们编译的时候是在一直输命令,这样太麻烦。我们最好的方法就是创建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
并在最后清零工程
总体下来就是:
led.bin:led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o //(前面的缩进一定要用Tab进行,否则报错)
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
clean:
rm -rf *.o led.bin led.elf
使用:
在工作空间中执行make指令;清理的话就是make clean。