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

  • 相关阅读:
    poj 3280 Cheapest Palindrome(区间DP)
    POJ 2392 Space Elevator(多重背包)
    HDU 1285 定比赛名次(拓扑排序)
    HDU 2680 Choose the best route(最短路)
    hdu 2899 Strange fuction (三分)
    HDU 4540 威威猫系列故事――打地鼠(DP)
    HDU 3485 Count 101(递推)
    POJ 1315 Don't Get Rooked(dfs)
    脱离eclipse,手动写一个servlet
    解析xml,几种方式
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12445395.html
Copyright © 2011-2022 走看看