汇编裸机GPIO控制—基于I.MX6UL嵌入式SoC

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控制的汇编实例。

原文地址:https://www.cnblogs.com/Cqlismy/p/12445395.html