IMX6U的GPIO功能使用的时候比2440或者51单片机要复杂一些。首先,我们必须理解一个概念, 即引脚IO功能复用(Pad multiplexing)。我们知道,对于一款Soc来说,某个引脚一般不会只能拿来做某一种特定功能。比如作为UART的TXD引脚,不仅可以用做串口的发送引脚,它还可以用来点灯。具体到某个引脚在实际板子上的用途,Soc的生产厂商自然是不知道的。为了方便芯片的使用者,I.MX6U的设计者并没有规定某个引脚只能做什么用途,而是设计了一个IO复用控制器(IOMUX Controller),由使用者通过软件来设置引脚的具体功能。而引脚的多种不同功能,被称之为IO功能复用。需要注意的是,我们耳熟能详的GPIO只不过是众多复用功能的一种。因此不能猜测,Led点灯实验必然需要通过设置IO复用功能寄存器来设置某个引脚的复用功能为GPIO。之后再通过GPIO中提供的寄存器点亮Led。此外,在Soc中,一个模块往往可以通过是否为其提供时钟来使能或者失能该模块,因此需要使用引脚的GPIO功能,还需要为其使能时钟才行。
要将 I.MX6U 的 IO 作为 GPIO 使用,我们需要一下几步:
-
使能 GPIO 对应的时钟;
-
设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能;
-
设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等;
1. 引脚复用功能 + 引脚电气属性:
IOMUXC_SW_MUX_CTL_PAD:引脚复用功能 _ GPIO1_IO03
IOMUXC_SW_PAD_CTL_PAD :电气属性 _ GPIO1_IO03
2. GPIO模块:
GPIOx_DR:向GPIOx组某个引脚写0或1(低电平/高电平)
GPIOx_GDIR:设置GPIOx组某个引脚方向(输入 0 和输出 1)
GPIOx_PSR(只读):读取GPIOx组某个引脚的状态
GPIOx_ICR:设置GPIOx组某个引脚中断触发方式(低电平、高电平、下降沿、上升沿)
GPIOx_IMR:设置GPIOx组某个引脚中断屏蔽/允许
GPIOx_ISR:用来查询GPIOx组某个引脚是否产生中断
GPIOx_EDGE_SEL:设置中断触发方式为双边沿触发
3. 时钟:
CCM_CCGR0 - CCM_CCGR6:0xFFFFFFFF 打开所有外设时钟门
cpp
.global _start
_start:
ldr pc, =_reset_handler
ldr pc, =_undefine_handler
ldr pc, =_software_handler
ldr pc, =_prefetch_abort_handler
ldr pc, =_data_abort_handler
ldr pc, =_reserved_handler
ldr pc, =_irq_handler
ldr pc, =_fiq_handler
_fiq_handler:
ldr pc, =_fiq_handler
_irq_handler:
ldr pc, =_irq_handler
_reserved_handler:
ldr pc, =_reserved_handler
_data_abort_handler:
ldr pc, =_data_abort_handler
_prefetch_abort_handler:
ldr pc, =_prefetch_abort_handler
_software_handler:
ldr pc, =_software_handler
_undefine_handler:
ldr pc, =_undefine_handler
_reset_handler:
// 设置irq模式
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x12
msr cpsr, r0
// 设置irq模式下的栈(32M)
ldr sp, =0x86000000
// 设置system模式
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x1F
msr cpsr, r0
// 设置system模式下的栈
ldr sp, =0x84000000
// 打开所有外设时钟门
bl _enable_clocks
// 初始化led
bl _led_init
// led亮
bl _led_on
b finished
_led_on:
ldr r0, =0x0209C000
ldr r1, [r0]
bic r1, r1, #(1 << 3)
str r1, [r0]
bx lr
_led_init:
// 配置引脚复用功能:GPIO1_IO03
ldr r0, =0x020E0068
mov r1, #0x05
str r1, [r0]
// 配置引脚电气属性:默认属性(0x10B0)
ldr r0, =0x020E02F4
ldr r1, =0x10B0
str r1, [r0]
// 配置引脚方向:输出模式
ldr r0, =0x0209C004
ldr r1, [r0]
orr r1, r1, #(1 << 3)
str r1, [r0]
bx lr
_enable_clocks:
// CCM_CCGR0 clock enable
ldr r0, =0x020C4068
ldr r1, =0xFFFFFFFF
str r1, [r0]
// CCM_CCGR1 clock enable
ldr r0, =0x020C406C
str r1, [r0]
// CCM_CCGR2 clock enable
ldr r0, =0x020C4070
str r1, [r0]
// CCM_CCGR3 clock enable
ldr r0, =0x020C4074
str r1, [r0]
// CCM_CCGR4 clock enable
ldr r0, =0x020C4078
str r1, [r0]
// CCM_CCGR5 clock enable
ldr r0, =0x020C407C
str r1, [r0]
// CCM_CCGR6 clock enable
ldr r0, =0x020C4080
str r1, [r0]
bx lr
finished:
b finished
编译流程:
1、使用arm-linux-gnueabihf-gcc编译文件
有了工具链,就可以编译我们led_asm.s文件了, 命令为:arm-linux-gnueabihf-gcc -g -c led.s -o led_.o。其中"-g"选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。"-c"选项是编译源文件,但是不链接。"-o"选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为led.o。执行上述命令以后就会编译生成一个 led.o 文件。
2、使用arm-linux-gnueabihf-ld 链接文件
确定了链接地址以后就可以使用 arm-linux-gnueabihf-ld 来将前面编译出来的 led.o 文件链接到 0X87800000 这个地址,使用如下命令: arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf。
3、用arm-linux-gnueabihf-objcopy 格式转换
led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还需要将 led.elf 文件转换为.bin 文件,arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin。上述命令中,"-O"选项指定以什么格式输出,后面的"binary"表示以二进制格式输出,选项"-S"表示不要复制源文件中的重定位信息和符号信息,"-g"表示不复制源文件中的调试信息。
4、使用arm-linux-gnueabihf-objdump 反汇编
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就 需要进行反汇编,当然这个工作目前对于我们不是必须的。一般来说可以将 elf 文件反汇编,比如如下命令: arm-linux-gnueabihf-objdump -D led.elf > led.dis。
使用命令./imxdownload led.bin /dev/sdb 。注意千万不能写成./imxdownload led.bin/dev/sda,sda是系统磁盘,会造成Ubuntu损坏的!