zoukankan      html  css  js  c++  java
  • 汇编裸机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控制的汇编实例。

  • 相关阅读:
    2.24 Java基础总结 ①内部类基础
    2.24 Java基础总结 ①访问权限②static③静态方法④实例初始化块⑤静态初始化块
    2.24 Java基础总结 ①for-each循环②继承概念③全类名④方法重写
    Shell 编程和Python编程的那些不同之处(一)
    python标准异常
    Python 常用模块总结
    Python正则表达式指南
    新员工入职自动加入所在部门的邮件组。
    c++四则运算代码
    马云语录
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12445395.html
Copyright © 2011-2022 走看看