1、前言
GPIO的全称为General Purpose Input/Output,也就是通用输入/输出接口,它是嵌入式SoC上最基本、最常用的外设,在我们开始接触一款新的嵌入式芯片的时候,首先需要了解和使用的就是GPIO,就好比我们编程的第一个程序"Hello World"一样。
GPIO通用输入/输出外设提供了通用的可配置输入或者输出引脚,当引脚被配置为输出的时候,可以写入SoC内部寄存器进而控制引脚上的驱动状态,当引脚被配置为输入时,可以通过读取SoC内部寄存器的状态来确定引脚的输入状态,另外,GPIO外设也能产生芯片内核中断,GPIO是芯片IOMUX Controller(IO复用控制器)的模块之一。
2、IOMUX复用GPIO原理
在较复杂的嵌入式SoC中,芯片引脚往往是有限的,为了能够让引脚充分地发挥作用,都会有一个IO复用控制器,通过配置IO复用控制器的寄存器,可以使IO口复用为GPIO、I2C接口、SPI接口引脚等,因此,在了解GPIO相关的控制原理前,我们需要先了解一下I.MX6UL中的IO复用控制器,对于GPIO的复用机制,如下所示:
上图中的IOMUXC就是IOMUX Controller(IO复用控制器),该模块中主要分成了两部分,第一部分是MUX_MODE,也就是复用模式的配置,例如可以复用为GPIO、I2C等引脚复用模式,和引脚复用相关的配置寄存器名称为SW_MUX_CTL_PAD_*,*表示引脚的名称,以GPIO1_IO00这个引脚为例子,引脚复用模式配置的寄存器为SW_MUX_CTL_PAD_GPIO1_IO00,该寄存器描述在I.MX6UL芯片的参考手册如下:
可以看到寄存器SW_MUX_CTL_PAD_GPIO1_IO00的bit[3:0]是用来配置引脚的复用模式的,总共有9种复用模式,为ALT0~ALT8,当bit[3:0]=0101的时候,引脚被复用为GPIO,也就是通用的输入/输出接口,另外,芯片复位的时候,引脚的复用模式为ALT5,也就是GPIO。
第二部分则是pad settings,也就是引脚的电气属性配置,例如:配置引脚的驱动能力、上下拉等引脚的电气属性,引脚相关的配置寄存器名称为SW_PAD_CTL_PAD_*,*同样是引脚的名称,同样以GPIO1_IO00这个引脚为例子,引脚电气属性相关的配置寄存器名称为SW_PAD_CTL_PAD_GPIO1_IO00,该寄存器在I.MX6UL芯片的参考手册如下(只截取部分):
该寄存器就是配置引脚的电气属性的,在了解寄存器中bit配置的含义前,先来看看GPIO引脚的结构框图,如下所示:
可以对照着上面的引脚框图,来看看SW_PAD_CTL_PAD_GPIO1_IO00寄存器中各bit表示的含义:
HYS(bit16):用于设置输入接收器的施密特触发器是否使能,IO口作为输入引脚时有效,使能的话能对输入波形进行整形。
PUS(bit[15:14]):用来设置引脚的上下拉电阻的,能设置的选项如下:
bit[15:14] | 含义 |
00 | 100K下拉 |
01 | 47K上拉 |
10 | 100K上拉 |
11 | 22K上拉 |
PUE(bit13):IO口作为输入时有效,用来设置上下拉还是状态保持,该bit为1的时候使用上下拉,为0的时候为状态保持,状态保持就是当外部电路断电以后,该IO口能保持住以前的状态。
PKE(bit12):用来使能或者禁止上下拉/状态保持功能。
ODE(bit11):当IO口作为输出的时候,用来使能或者禁止开路输出。
SPEED(bit[7:6]):当IO口作为输出的时候,用来设置IO口的速度,设置选项如下:
bit[7:6] | IO速度 |
00 | 低速50MHz |
01 | 中速100MHz |
10 | 中速100MHz |
11 | 高速200MHz |
DSE(bit[5:3]):当IO口作为输出的时候,用来设置IO口的驱动能力,设置选项如下:
bit[5:3] | IO驱动能力 |
000 | 关闭输出驱动能力 |
001 | R0(3.3V下R0为260欧,1.8V下R0是150欧,接DDR为240欧) |
010 | R0/2 |
011 | R0/3 |
100 | R0/4 |
101 | R0/5 |
110 | R0/6 |
111 | R0/7 |
SRE(bit0):用来设置压摆率(IO电平跳变所需要的时间),时间越小波形越陡,压摆率越高,设置为1的时候为高压摆率。
上面描述的只是IO引脚的IOMUXC相关寄存器,用来设置IO的引脚复用模式和引脚的电气属性配置,当将IO口复用为GPIO后,需要查看芯片参考手册关于GPIO描述的章节,并对其使用到的寄存器有一定的了解。
3、GPIO控制机制
对于I.MX6UL这款SoC,总共有5组GPIO,为GPIO1~GPIO5,GPIO1有32个IO,GPIO2有22个IO,GPIO3有29个IO,GPIO4有29个IO,GPIO5有12个IO,一共有124个GPIO可以使用,GPIO的框图如下所示:
从图中,可以看到,当我们将IO口复用为GPIO的时候,需要关注的有8个寄存器,分别是GPIOx_DR、GPIOx_GDIR、GPIOx_PSR、GPIOx_ICR1、GPIOx_ICR2、GPIOx_IMR、GPIOx_ISR和GPIOx_EDGE_SEL,每组GPIO都有相关的8个寄存器,接下来,我们依次看一下GPIO的寄存器有什么含义:
首先来了解一下GPIOx_DR寄存器,也叫做GPIO数据寄存器,它的定义如下所示:
数据寄存器是一个32bit的寄存器,一个GPIO组最大的引脚数量就是32个,每一个位都对应了一个相应的GPIO,当GPIO被设置为输出模式时,向指定的数据寄存器中的位写入数据,那么对应的IO口就会输出电平值,例如:要设置GPIO1_IO00输出高电平的话,需要向GPIO1_DR寄存器的bit0写入1。当GPIO被设置为输入模式时,数据寄存器就保存着对应的IO口的电平值,每个数据位对应着一个GPIO,例如:当GPIO1_IO00被设置为输入模式时,引脚此时被拉高,如果读取GPIO1_DR寄存器的bit0的话,将返回1,表示此时IO口为高电平。
了解了GPIOx_DR寄存器,接下来看看GPIOx_GDIR寄存器,也叫做GPIO方向寄存器,它的定义如下:
GPIOx_GDIR寄存器也是一个32bit的寄存器,每一个bit对应着一个IO口,该寄存器是用来设置IO口的工作方向,可配置为输入或者输出方向,例如:如果想设置GPIO1_IO00为输入方向的话,需要设置GPIO1_GDIR寄存器的bit0为0,想设置GPIO1_IO00为输出方向的话,需要设置GPIO1_GDIR寄存器的bit0为1。
接下来,看看GPIOx_PSR寄存器,也叫做GPIO引脚状态寄存器,定义如下:
GPIOx_PSR也叫做引脚状态寄存器,该寄存器同样是一个位对应着一个IO口,当GPIO的方向设置为输入时,读取相应的位将返回对应的IO口的电平状态。
接下来,就是GPIOx_ICR1和GPIOx_ICR2寄存器,也叫做GPIO中断控制寄存器,其中GPIOx_ICR1用来设置低16个GPIO,GPIOx_ICR2用来设置高16个GPIO,每两个位域配置一个GPIO,GPIOx_ICR1寄存器的定义如下:
该寄存器用来配置IO00~IO15的中断触发方式,可配置的中断触发方式选项如下:
位设置 | 中断触发方式 |
00 | 低电平触发 |
01 | 高电平触发 |
10 | 上升沿触发 |
11 | 下降沿触发 |
例如:如果要设置GPIO1_IO15引脚的中断触发方式为上升沿触发,那么需要设置GPIO1_ICR1寄存器的bit[31:30]=10,想要设置GPIO1_IO16~GPIO1_IO31引脚的中断触发方式的话,就需要设置GPIO1_ICR2寄存器了。
GPIOx_IMR寄存器也叫做GPIO中断屏蔽寄存器,定义如下所示:
该寄存器的作用是用来控制GPIO外部中断的使能或者禁止的,每个位对应着一个IO口。
GPIOx_ISR寄存器也叫做中断状态寄存器,定义如下所示:
该寄存器同样是一个32位的寄存器,每个位对应着一个IO口,当某个GPIO的中断发生后,GPIOx_ISR寄存器中对应的位将会被置1,可以通过该寄存器的位来判断GPIO中断是否发生,当我们处理完中断后,需要将对应的中断状态标志位清空,清除状态标志位的方法就是往GPIOx_ISR寄存器对应的位写1。
接下来就是GPIOx_EDGE_SEL寄存器,该寄存器也叫做边沿选择寄存器,定义如下:
该寄存器是用来设置边沿中断触发的,同样是每个位对应着一个IO口,当某个位被置1时,将会重写GPIOx_ICR1和GPIOx_ICR2寄存器,此时,对应的GPIO将会是双边沿中断触发。
有关GPIO相关的寄存器就介绍到这,更多的GPIO寄存器描述,可以查看芯片的参考手册。
4、GPIO编程思路
接下来,我们了解一下GPIO的编程思路,主要介绍两种模式,GPIO读模式和GPIO写模式:
(1)GPIO读模式
对于读取输入信号引脚的编程思路如下所示:
- 配置IOMUX去选择GPIO复用模式(通过IOMUX控制器)
- 配置GPIO方向寄存器为输入方向(GPIO_GDIR[GDIR]设置为0)
- 读取GPIO的数据寄存器或者引脚状态寄存器获取当前IO口的电平值
例如,读取GPIO[input3:input0]引脚电平值的伪代码描述如下所示:
// 设置input引脚复用为GPIO模式 write sw_mux_ctrl_<input0>_<input1>_<input2>_<input3>, 32'h00000000 // 设置GDIR寄存器,将GPIO方向设置为输入 write GDIR[31:4, input3_bit, input2_bit, input1_bit, input0_bit], 32'hxxxxxxx0 // 读取DR寄存器,获取GPIO引脚对应的电平值 read DR // 读取PSR寄存器,获取GPIO引脚对应的电平值 read PSR
在GPIO读模式下,需要注意,当GPIO的方向被设置为输入时(GPIO_GDIR=0),对GPIO_DR寄存器的读请求并不会返回GPIO_DR寄存器的数值,而是返回GPIO_PSR寄存器里面的数据,该寄存器的数据对应着GPIO输入信号引脚的电平值。
(2)GPIO写模式
对于驱动输出信号的编程思路如下所示:
- 配置IOMUX将IO口复用为GPIO模式(通过IOMUX控制器),如果需要通过PSR读取引脚当前的电平,也可以使能SION
- 配置GPIO的方向寄存器将GPIO方向设置为输出(GPIO_GDIR[GDIR]设置为1)
- 将值写入到GPIO数据寄存器(GPIO_DR)
例如,驱动IO口电平值4'b0101在GPIO引脚[output3:output0]的伪代码描述如下:
// 设置IO口的复用模式为GPIO模式通过IOMUX write sw_mux_ctrl_pad_<output [0-3]>.mux_mode, <GPIO_MUX_MODE> // 使能SION以便在输出模式通过PSR读取到引脚的电平状态 write sw_mux_ctrl_pad_<output [0-3]>.sion, 1 // 写GDIR寄存器将GPIO方向设置为输出 write GDIR[31:4, output3_bit, output2_bit, output1_bit, output0_bit], 32'hxxxxxxxF // 写DR寄存器,设置对应IO口的电平值 write DR, 32'hxxxxxxx5 // 读取输出的值通过PSR寄存器 read_cmp PSR, 32'hxxxxxxx5
以上,就是GPIO基本的编程思路。
5、GPIO控制实例汇编代码
了解I.MX6U系列SoC的IOMUX控制的基本原理以及GPIO控制机制和相关GPIO配置寄存器后,接下来我们用汇编语言实现一个GPIO的控制,以GPIO1_IO08引脚为例,将该IO口引脚设置为GPIO复用模式,并设置GPIO的方式为输出,并输出高电平,编程思路如下:
- 使能GPIO1的时钟
- 配置SW_MUX_CTL_PAD_GPIO1_IO08寄存器,将GPIO1_IO08引脚复用为GPIO模式
- 配置SW_PAD_CTL_PAD_GPIO1_IO08寄存器,设置GPIO1_IO08引脚的电气属性,例如上/下拉、速度等
- 配置GPIO1_IO08相关的寄存器,设置GPIO的方向、是否使用中断、默认的引脚输出电平等
相关的寄存器地址,可以在I.MX6UL芯片的参考手册中找到,新建汇编文件gpioctrl.S,汇编文件代码如下:
.global _start _start: /* 1、使能时钟 */ ldr r0, =0x020c4068 /* 将寄存器地址CCM_CCGR0写入到r0 */ ldr r1, =0xffffffff /* 将所有外设时钟使能 */ str r1,[r0] ldr r0, =0x020c406c /* 将寄存器地址CCM_CCGR1写入到r0 */ str r1,[r0] ldr r0, =0x020c4070 /* 将寄存器地址CCM_CCGR2写入到r0 */ str r1,[r0] ldr r0, =0x020c4074 /* 将寄存器地址CCM_CCGR3写入到r0 */ str r1,[r0] ldr r0, =0x020c4078 /* 将寄存器地址CCM_CCGR4写入到r0 */ str r1,[r0] ldr r0, =0x020c407c /* 将寄存器地址CCM_CCGR5写入到r0 */ str r1,[r0] ldr r0, =0x020c4080 /* 将寄存器地址CCM_CCGR6写入到r0 */ str r1,[r0] /* 2、设置GPIO1_IO08引脚IO复用为GPIO1_IO08 */ ldr r0, =0x020e007c /* 将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO08写入r0 */ ldr r1, =0x5 /* 设置IO引脚复用模式为GPIO1_IO08 */ str r1,[r0] /* 3、配置GPIO1_IO08引脚电气属性 * bit[16]: 0 关闭HYS * bit[15:14]: 00 默认下拉 * bit[13]: 0 keeper * bit[12]: 1 pull/keeper使能 * bit[11]: 0 禁止开路输出 * bit[10:8]: 000 reserved * bit[7:6]: 10 速度为100MHz * bit[5:3]: 110 驱动能力为R0/6 * bit[2:1]: 00 reserved * bit[0]: 0 低摆率 */ ldr r0, =0x020e0308 /* 将寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO08写入r0 */ ldr r1, =0x10b0 /* 设置GPIO1_IO08引脚电气属性 */ str r1,[r0] /* 4、配置GPIO1_IO08引脚方向为输出 */ ldr r0, =0x0209c004 /* 将寄存器GPIO1_GDIR地址写入r0 */ ldr r1, =0x00000100 /* 将GPIO1_IO08方向设置为输出 */ str r1,[r0] /* 5、控制GPIO1_IO08引脚电平高低 */ ldr r0, =0x0209c000 /* 将寄存器GPIO1_DR地址写入r0 */ ldr r1, =0x00000100 /* 将GPIO1_IO08引脚设置为高电平 */ str r1,[r0]
汇编代码gpioctrl.S编写完成后,如果想要编写的应用程序能在I.MX6UL目标板上运行,那么接下来,就需要先在宿主机中使用gcc交叉编译工具链进行编译链接出gpioctrl.bin文件,对于I.MX6UL芯片的启动要求,还需要在.bin文件前面添加相应的数据结构,例如:IVT、Boot Data以及DCD数据结构,成为.imx结尾的镜像文件,可以借助于uboot源码中mkimage工具进行实现,得到了gpioctrl.imx镜像文件后,烧写到目标板的启动设备即可,上电后便可以执行我们自己编写的汇编程序。
接下来,看看如何使用gcc交叉编译工具编译链接出相应的gpioctrl.bin文件:
首先,将汇编源文件gpioctrl.S编译成gpioctrl.o文件,一个工程下的所有C文件和汇编文件都会编译产生一个对应的.o文件,所有的.o文件链接组合成可执行文件,在宿主机Linux终端下输入下面命令:
$ arm-linux-gnueabihf-gcc -g -c gpioctrl.S -o gpioctrl.o
命令执行后,将会生成相应的gpioctrl.o文件,命令中的"-g"选项表示加入调试信息,能够使用gdb对这些代码进行调试,"-c"选项表示指定编译的源文件,但是并不链接,"-o"选项表示指定目标文件的名称。
编译得到了我们需要的gpioctrl.o文件后,接下来,使用arm-linux-gnueabihf-ld链接工具将.o文件链接到一个指定的地址(DRAM地址0x87800000),可以使用下面命令:
$ arm-linux-gnueabihf-ld -Ttext 0x87800000 gpioctrl.o -o gpioctrl.elf
命令执行后,将会生成相应的gpioctrl.elf文件,"-Ttext"选项是用来指定链接地址的,"-o"选项指定链接后生成的目标文件名。
链接后得到了gpioctrl.elf文件后,接下来,使用arm-linux-gnueabihf-objcopy进行格式转换,因为,我们需要的是二进制文件gpioctrl.bin,也就是用户镜像文件,可以使用下面命令:
$ arm-linux-gnueabihf-objcopy -O binary -S -g gpioctrl.elf gpioctrl.bin
命令执行后,将会生成相应的gpioctrl.bin文件,"-O"选项用来指定以什么文件格式输出,命令的"binary"表示以二进制文件.bin格式输出,"-S"选项表示不需要复制源文件中的重定位信息和符号信息,"-g"选项表示不复制源文件中的调试信息。
此外,如果使用C语言编写的应用程序,想要查看对应的汇编代码的话,需要进行反汇编,一般使用.elf文件进行反汇编,可以使用下面命令进行反汇编:
$ arm-linux-gnueabihf-objdump -D gpioctrl.elf > gpioctrl.dis
命令执行后,将会生成相应的反汇编文件gpioctrl.dis,"-D"选项表示反汇编所有的段。
编译并链接出来用户镜像文件gpioctrl.bin文件后,接下来,我们需要使用uboot源码中集成的mkimage工具将启动要求的数据结构IVT+Boot Data+DCD添加进去得到gpioctrl.imx镜像文件,我们需要烧写到目标板启动设备的文件就是以.imx结尾的可编程镜像文件,里面包含了用户镜像.bin文件,否则的话将不能成功启动I.MX6UL的目标板。
以mx6ul_14x14_evk的板子为例,对于mkimage的使用,如下:
./tools/mkimage -n ./board/freescale/mx6ul_14x14_evk/imximage.cfg.cfgtmp -T imximage -e 0x87800000 -d gpioctrl.bin gpioctrl.imx
在上面命令中,"-e"选项指定了用户镜像的入口地址,该地址需要和gpioctrl.bin文件编译链接的地址一致,命令执行后,将生成能烧写的可编程镜像文件gpioctrl.imx,烧写到目标板的启动设备即可执行我们编写的汇编裸机程序gpioctrl.bin。
6、小结
本篇文章主要介绍了I.MX6U系列SoC的IOMUXC复用的基本机制,以及GPIO的基本控制原理,GPIO使用的基本编程思路等,最后给出了一个简单GPIO控制的汇编实例。