I.MX6ULL裸机开发之汇编LED灯
原理图分析
控制LED灯亮灭,就是操作芯片的GPIO口,既然要操作GPIO就需要知道操作哪一个GPIO,查看原理图:
从原理图中看出LED等接在LED0,并且要控制LED0为低电平的时候,这个LED就会亮;但是LED0并不能看出是芯片的哪个GPIO口,所以得接着找
又看到LED0连在GPIO_3上,继续以GPIO_3为关键字继续找,直到找到芯片的部分
最后找到GPIO_3是对应芯片的GPIO1_IO03
的,也就是通过控制GPIO1_IO03就可以实现控制LED的亮灭。
操作步骤
找到对应的GPIO之后,如果板子此时已经有系统在运行着了,那么操作GPIO对Linux来说无非就是对节点文件的读写。但是,现在板子上还没有系统,更没有所谓的节点文件;那么就需要查阅文档来得知如何去操作该GPIO.
在IMX6ULL参考手册中以 "GPIO1_IO03"
为关键字搜索,找到了IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
从文档中得知,IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03是一个寄存器,主要是用来配置GPIO1_IO03的复用功能的。我们只是单纯的控制高低电平,所以这个寄存器低四位一定是0101
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03也是一个寄存器,该寄存器只能使用0-16位。
第0位:SRE,为1是设置高压摆率,为0是设置低压摆率。压摆率就是IO电平跳变所需要的时间,比如电平从0变成1,所需要的时间越小波形就越陡,说明压摆率越高;反之,需要的时间约多,波形越缓,压摆率越低。如果设备要符合EMC(指设备或系统在正常运行过程中对所在环境产生的电磁干扰不能超过一定的限值,同时器具对所在环境中存在的电磁干扰具有一定程度的抗扰度,即电磁敏感性)测试标准的话,就用低压摆率;如果IO要做高速通信的话,就的使用高压摆率。
第1-2位:保留。
第3-5位:设置IO的驱动能力。电压一定,电阻越小,驱动能力越大。也就是说第3-5位是用来选择电阻大小的。
第6-7位:当IO用作输出的时候,该位设置IO的速度。既然要设置速度,是不是也是需要设置时钟?
第8-10位:保留。
第11位:当IO用作输出的时候,该位设置禁止或者使能开路输出。为0是禁止开路输出;为1是使能开路输出。
第12位:为0时禁止上下拉状态保持器;为1时使能上下拉状态保持器。
第13位:为0时使用状态保持器,为1时使用上下拉。状态保持器作为输入的时候才有用,主要作用就是当外部电路断电之后,状态保持器可以保持断电前IO口的状态。
第14-15位:选择上下拉的电阻阻值。
第16位:位0时禁止迟滞比较器,为1时使能迟滞比较器。当IO口作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使用此位。
设置完这两个寄存器后,还没有设置IO口的方向,但此时还不知道哪个是设置IO口方向的寄存器,只能继续查阅文档。
在目录处找到一个Input/Output(GPIO)。
图中说明了配置一个IO口需要配置DR、GDIR、PSR、CR1、CR2、EDGE_SEL、IMR、ISR以及SW_MUX_CTL_PAD*和SW_PAD_CTL_PAD_一共10个寄存器。每个IO口都会有SW_MUX_CTL_PAD和SW_PAD_CTL_PAD_*两个寄存器,每组IO口都会有DR、GDIR、PSR、CR1、CR2、EDGE_SEL、IMR、ISR这8个寄存器;每组IO口最多32个,也就是说DR、GDIR、PSR、CR1、CR2、EDGE_SEL、IMR、ISR这8个寄存器的每一位对应这个组的一个IO口
。
DR寄存器
数据寄存器,当GPIO设置为是输出功能后,向对应位写入1,IO就会输出高电平,向对应位写入0,IO就会出入低电平。
GDIR寄存器
方向寄存器,设置GPIO是输入还是输出。设置对应位为0,对应IO的方向就是输入;设置对应位为1,对应的IO的方向就是输出。
PSR寄存器
GPIO状态寄存器,读取相应位可得知对应的IO的高低电平。
ICR1和ICR2寄存器
ICR1和ICR2都是中断控制寄存器,ICR1用于配置低16个GPIO,ICR2用于配置高16个GPIO。如上图,00表示低电平触发、01表示高电平触发、10表示上升沿触发、11表示下降沿触发。比如设置GPIO1_IO15为上升沿触发,那么就是:GPIO1_IO01.ICR1=2<<30。
IMR寄存器
中断屏蔽寄存器,用来控制GPIO中断的禁止和使能。对应位为0,就是禁止对应IO的中断;对应位为1,就是使能对应IO的中断。
ISR寄存器
中断状态寄存器,读取对应位的值,为1,那么对应的GPIO的中断就发生了;为0,那么对应的GPIO就没有发生。注意:当处理完中断后,必须清楚对应的中断标志位,也就是向ISP寄存器对应位写1,写1就是清楚标志位。
EDGE_SEL寄存器
EDGE_SEL寄存器用来设置边沿中断,这个寄存器会覆盖ICR1和ICR2的设置。也就是说当对应位设置了1,那么就相当于设置了对应GPIO的中断是双边沿(上升沿和下降沿都有触发的时候叫做双边沿触发)触发,无论是ICR1和ICR2设置了多少,都是双边沿触发。
时钟
每个外设都会有一个时钟,IMX6ULL控制GPIO的时钟寄存器有CCM_CCGR0-CCM_CCGR6一共7个寄存器,每个寄存器控制1组GPIO。GPIO的7个时钟寄存器都是类似的。
在CCM_CCGR1中可以找到GPIO1_IO03对应的控制位:
也就是说想要开启GPIO1_IO03的时钟,只需将CCM_CCGR1寄存器的27-26位写1即可,也就是CCM_CCGR1=3<<26。
总结
要将I.MX6ULL的IO作为GPIO使用,一共有:
- 使能GPIO相应的时钟;
- 设置寄存器IOMUXC_SW_MUX_CTL_PAD_GPIOX_IOXX,设置IO的复用功能;
- 设置寄存器IOMUXC_SW_PAD_CTL_PAD_GPIOX_IOXX,设置IO的上下拉、速度等;
- 设置GPIO的方向,是否使用中断,默认输出电平等。
编写代码
/*
汇编程序入口是 _start
本次的IO是GPIO1_IO03
*/
.global _start /*全局编号 */
_start:
/*初始化所有外设时钟 */
ldr r0, =0x020c4068 @CCM_CCGR0寄存器的地址赋值给r0
ldr r1, =0xffffffff @在r1中存放0xffffffff
str r1, [r0] @将0xffffffff写入到CCM_CCGR0寄存器中
ldr r0, =0x020c406c @CCM_CCGR1
str r1, [r0]
ldr r0, =0x020c4070 @CCM_CCGR2
str r1, [r0]
ldr r0, =0x020c4074 @CCM_CCGR3
str r1, [r0]
ldr r0, =0x020c4078 @CCM_CCGR4
str r1, [r0]
ldr r0, =0x020c407c @CCM_CCGR5
str r1, [r0]
ldr r0, =0x020c4080 @CCM_CCGR6
str r1, [r0]
/*
IO复用:
设置IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器
第0-3位为:0101
寄存器的地址是:0x020e0068
*/
ldr r0, =0x020e0068
ldr r1, =0x5
str r1, [r0]
/*
配置电气属性
设置IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03寄存器
寄存器的地址是:0x020e02f4
第0位:SRE,摇摆率;设置为0,即低摇摆率
第1-2位:保留
第3-5位:选择驱动能力。设置110,也就是R0/6
第6-7位:设置速度。设置10,也就是100MHz
第8-10位:保留
第11位:开路输出。关闭开路输出,设置位0
第12位:使能上下拉状态保持器。设置为1
第13位:使用上下拉还是使用状态保持器。设置为0,即使用状态保持器
第14-15位:选择上下拉的电阻阻值。设置00,使用默认下拉,100K下拉
第16位:是否使用迟滞器(波形整形)。设置为0,不使用迟滞器
结果:1 0000 1011 0000,也就是10b0
*/
ldr r0, =0x020e02f4
ldr r1, =0x10b0
str r1, [r0]
/*
设置GPIO的方向
寄存器是GPIO1_GDIR
寄存器地址:0x0209c004
GPIO1_IO03对应该寄存器的第3位,将该位置1,也就是将这个IO口设置为输出方向
1000 == 0x8
*/
ldr r0, =0x0209c004
ldr r1, =0x8
str r1, [r0]
/*
亮灯
设置GPIO为低电平
寄存器是GPIO1_DR
寄存器地址是0x0209c000
*/
ldr r0, =0x0209c000
ldr r1, =0x0
str r1, [r0]
/*
死循环
*/
loop:
b loop
编译烧写
- 将.s和.c编译成.o文件
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
- 将所有的.o文件链接成elf格式的可执行文件
@链接就是将所有的.o文件链接在一起,并链接到指定的地址中。这个地址就是代码运行的地址。
@6ULL的ROM是不对用户开放的,所以只能使用RAM。RAM分内部和外部,外部也就是DDR,6ULL的内部RAM有128KB地址范围是0x900000-0x91ffff。
@也可以放到外部的DDR中,地址范围是0x80000000-0x9fffffff,大小是512M。
@在这个范围中可以随意取其中的一个地址,但是要考虑本身的程序大小,不能超出0x9fffffff。
@既然要使用到DRR,就需要初始化DDR,但是此次的程序中并没有初始化DDR
@解决办法是需要添加一个头部信息,这个头部信息包含了DDR的初始化参数
@I.MX系列的芯片内部的BOOT ROM会从SD卡、EMMC等外部存储中读取头部信息
@然后初始化DDR,并将程序拷贝到指定的地址中
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
- 将elf文件转为bin文件
arm-linux-gnueabihf-objcopy -O binary -g -S led.elf led.bin
- 将elf文件转为汇编,即反汇编,可以用来调试
arm-linux-gnueabihf-objdump -D led.elf > led.dis
- 烧写到SD卡
../../tool/imxdownload led.bin /dev/sdc
将SD卡插入板子,并选择SD卡启动模式,可以看见电阻R9旁边的LED亮起,说明程序已经运行了。
为了方便,一般编译都会使用makefile
LD_ADDR = 0x87800000
BURN_TOOL_PATH = ../../tool/imxdownload
SD_PATH = /dev/sdc
all:
#编程.s生成.o
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
#把所有.o链接成elf文件
arm-linux-gnueabihf-ld -Ttext $(LD_ADDR) led.o -o led.elf
#elf生成bin文件
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
#反汇编
arm-linux-gnueabihf-objdump -D led.elf > led.dis
burn:
$(BURN_TOOL_PATH) led.bin $(SD_PATH)
clean:
rm -rf *.o led.bin led.elf led.dis led.imx load.imx