首先:操作LED就要操作GPIO alpha的芯片是NXP的IMX6ULL 其GPIO和STM32的命名有所区别
可以看到IMX6ULL的GPIO以其功能进行命名,对应上图中PAD之后的部分
即GPIO_IO00~GPIO_IO09 和JTAG_MOD等,用户按照名字就可以知道对应引脚的功能。并且IMX6ULL的GPIO也是可以复用的。
I.MX6U 的 GPIO 一共有 5 组: GPIO1、 GPIO2、 GPIO3、 GPIO4 和 GPIO5
其中 GPIO1 有 32 个 IO, GPIO2 有 22 个 IO, GPIO3 有 29 个 IO、 GPIO4 有 29 个 IO, GPIO5最少,只有 12 个 IO,这样一共有 124 个 GPIO
对IO的配置
首先要确定IO的复用功能是GPIO,看一下控制复用功能的寄存器(这里截取的是GPIO1_IO00的复用控制寄存器)
显然,bit4是复用功能的“总开关”,设置为1时强制为GPIO1_IO00功能;置0时由bit3:0来决定
bit3:0决定该IO复用为什么功能,可以看到这里ALT5就是复用为GPIO1_IO00功能,除此之外还有八种可选
再看一下IO的功能图
功能图中
HYS:用来使能迟滞比较器,当 IO 作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。
如果需要对输入波形进行整形的话可以使能此位。此位为 0 的时候禁止迟滞比较器,为 1 的时候使能迟滞比较器。
PUS:用来设置上下拉电阻的,一共有四种选项可以选择,分别为100K下拉,47K上拉,100K上拉,22K上拉。
PUE(图中未给出):当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器。
当为 0 的时候使用状态保持器,当为 1 的时候使用上下拉。
状态保持器在IO 作为输入的时候才有用,顾名思义,就是当外部电路断电以后此 IO 口可以保持住以前的状态。
PKE:此为用来使能或者禁止上下拉/状态保持器功能,为0 时禁止上下拉/状态保持器,为 1 时使能上下拉和状态保持器。
ODE:当 IO 作为输出的时候,此位用来禁止或者使能开路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能。
SPEED:当 IO 用作输出的时候,此位用来设置 IO 速度,分别为低俗50M,中速100M,中速100M,最大速度200M。
DSE:当 IO 用作输出的时候用来设置 IO 的驱动能力,总共有 8 个可选选项
SRE:设置压摆率,当此位为 0 的时候是低压摆率,当为 1的时候是高压摆率。
这里的压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时间,时间越小波形就越陡,说明压摆率越高;
对应每个IO,都有一组寄存器去控制,这是毋庸置疑的,下面是GPIO_IO00的控制寄存器
可见 对于IO的每个功能端都有相应的寄存器位进行配置。
GPIO功能配置
但是我们没有看到如何设置 IO 为输入还是输出? 因为上面的配置只是针对IO的配置,对于GPIO来说,它只是IO的一个复用功能而已,所以还要对GPIO的功能进行配置。
所以对于GPIO来说,也需要有相应的寄存器来控制,GPIO的控制框图如下:
可见,配置好SW_MUX_CTL_PAD和SW_PAD_CTL_PAD两个寄存器后,还需要对GPIO进行单独配置,共有DR/GDIR/PSR/ICR1/ICR2/EDGE_SEL/IMR/ISR八个寄存器
I.MX6U 一共有GPIO1~GPIO5 共五组 GPIO,每组 GPIO 都有这 8 个寄存器。
DR寄存器:数据寄存器(data register)
此寄存器是 32 位的,一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO。
当 GPIO 被配置为输出功能以后,向指定的位写入数据那么相应的 IO 就会输出相应的高低电平,比如要设置 GPIO1_IO00 输出高电平,那么就应该设置 GPIO1.DR=1。
当 GPIO 被配置为输入模式以后,此寄存器就保存着对应 IO 的电平值,每个位对对应一个 GPIO,例如,当 GPIO1_IO00 这个引脚接地的话,那么 GPIO1.DR 的 bit0 就是 0。
这里注意:如果GDIR[n]设置为输入,但该IO复用功能不是GPIO,那么读DR[n]始终是0值,所以读之前请确认IO是否复用为GPIO。
GDIR寄存器:方向寄存器(GPIO direction register)
很简单,每一个位对应一个GPIO,对应位为0则该位为输入模式,反之为输出模式。
PSR寄存器:GPIO状态寄存器(pad status register)
这个寄存器是只读的,读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。功能和输入状态下的 DR 寄存器一样。
ICR寄存器:中断配置寄存器(interrupt configuration register)
分为ICR1和ICR2两个寄存器,分别配置低16位和高16位GPIO,即每个GPIO占两位。
通过每个GPIO对应的两位设置可以配置低电平、高电平、上升沿、下降沿四种模式。
IMR寄存器:中断屏蔽寄存器(interrupt mask register)
每个GPIO对应一位,0为屏蔽中断,1为使能中断
(个人认为这里的屏蔽翻译不恰当,因为下面UNMASKED和MASKED的功能和使用中屏蔽的意思好像是相反的呢?)
ISR寄存器:中断状态寄存器(interrupt status register)
当中断发生时,相应中短线对应的位被置1,用来在特定中断组中查询是哪条中短线发生了中断。
在每次处理中断完成后必须手动清零,清除的方法是向要清除的位写1。
EDGE_SEL寄存器:边沿选择寄存器(edge select register)
EDGE_SEL 寄存器用来设置边沿中断,这个寄存器会覆盖 ICR1 和 ICR2 的设置,同样是一个 GPIO 对应一个位。
如果相应的位被置 1,那么就相当与设置了对应的 GPIO 是上升沿和下降沿(双边沿)触发。
例如,我们设置 GPIO1.EDGE_SEL=1,那么就表示 GPIO1_IO01 是双边沿触发中断,无论 GFPIO1_CR1 的设置为多少,都是双边沿触发。
因为ICR中没有双边沿的设置选项。
GPIO时钟配置
配置完相应GPIO的功能还不能使GPIO工作起来,还要对GPIO的时钟进行使能,I.MX6U 的系统时钟参考《I.MX6UL 参考手册》的第 18 章“Chapter 18: Clock Controller Module(CCM)”
先不研究 I.MX6U 的时钟系统,只看一下 CCM 里面的外设时钟使能寄存器。
CMM 有 CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关
CCM_CCGR0 寄存器:CCM时钟门控寄存器(CCM Clock Gating Register 1)
CCM_CCGR0 是个 32 为寄存器,其中每 2 位控制一个外设的时钟 ,例如这里的bit31:30控制GPIO2的时钟
其设置方法为:对于GPIO来说,开启时钟即两位都置1,关闭则两位都置0。
总结起来,要将 I.MX6U 的 IO 作为 GPIO 使用,我们需要以下几步:
①、使能 GPIO 对应的时钟。
②、设置寄存器 IOMUXC_SW_MUX_CTL_PAD_XX_XX,设置 IO 的复用功能,使其复用为 GPIO 功能。
③、设置寄存器 IOMUXC_SW_PAD_CTL_PAD_XX_XX,设置 IO 的上下拉、速度等等。
④、第②步已经将 IO 复用为了 GPIO 功能,所以需要配置 GPIO,设置输入/输出、是否使用中断、默认输出电平等。
硬件分析
显然,低电平点灯,高电平灭灯。
点灯程序编写
对 GPIO1_IO03 做如下设置:
1、使能 GPIO1 时钟
GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制,将这两个位都设置为11即可。本教程所有例程已经将 I.MX6U 的所有外设时钟都已经打开了,因此这一步可以不用做。
2、设置 GPIO1_IO03 的复用功能
找到 GPIO1_IO03 的复用寄存器“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03”的地址为0X020E0068,然后设置此寄存器,将 GPIO1_IO03 这个 IO 复用为 GPIO 功能,也就是 ALT5。
3、配置 GPIO1_IO03
找到 GPIO1_IO03 的配置寄存器“IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03”的地址为0X020E02F4,根据实际使用情况,配置此寄存器。
4、设置 GPIO
我们已经将 GPIO1_IO03 复用为了 GPIO 功能,所以我们需要配置 GPIO。找到 GPIO3 对应的 GPIO 组寄存器地址
本实验中 GPIO1_IO03 是作为输出功能的,因此 GPIO1_GDIR 的 bit3 要设置为 1,表示输出。
5、控制 GPIO 的输出电平
经过前面几步, GPIO1_IO03 已经配置好了,只需要向 GPIO1_DR 寄存器的 bit3 写入 0 即可控制 GPIO1_IO03 输出低电平,打开 LED,向 bit3 写入 1 可控制 GPIO1_IO03 输出高电平,关闭 LED。
具体程序
1 /* 2 * @Author: qjx 3 * @Date: 2020-05-02 14:16:50 4 * @Last Modified by: qjx 5 * @Last Modified time: 2020-05-02 14:48:38 6 * 汇编点灯程序 7 */ 8 9 /*全局标号 */ 10 .global _start 11 12 /* 13 * _start函数,程序从这里开始 14 * 分别完成: 1、时钟使能 15 2、IO复用 16 3、GPIO配置 17 4、控制GPIO.DR寄存器点灯 18 */ 19 20 _start: 21 /*第一步:使能所有时钟 */ 22 23 ldr r0,=0X020C4068 @CCGR0地址 24 ldr r1,=0XFFFFFFFF @设置的值(全1) 25 26 str r1,[r0] @把r1存到r0指向的地址中 27 28 ldr r0,=0X020C406C @CCGR1地址 29 str r1,[r0] @把r1存到r0指向的地址中 30 31 ldr r0,=0X020C4070 @CCGR2地址 32 str r1,[r0] @把r1存到r0指向的地址中 33 34 ldr r0,=0X020C4074 @CCGR3地址 35 str r1,[r0] @把r1存到r0指向的地址中 36 37 ldr r0,=0X020C4078 @CCGR4地址 38 str r1,[r0] @把r1存到r0指向的地址中 39 40 ldr r0,=0X020C407C @CCGR5地址 41 str r1,[r0] @把r1存到r0指向的地址中 42 43 ldr r0,=0X020C4080 @CCGR6地址 44 str r1,[r0] @把r1存到r0指向的地址中 45 46 /*第二步、设置GPIO1_IO03复用为GPIO_IO03 */ 47 ldr r0,=0X020E0068 @SW_MUX_CTL_PAD_GPIO1_IO03的地址 48 ldr r1,=0X00000005 @设置低4位为0101 49 str r1,[r0] @把r1存到r0指向的地址中 50 51 /*第三步、配置GPIO */ 52 /* 53 *bit 16:0 HYS 关闭 54 *bit [15:14]: 00 默认下拉 55 *bit [13]: 0 kepper 功能 56 *bit [12]: 1 pull/keeper 使能 57 *bit [11]: 0 关闭开路输出 58 *bit [7:6]: 10 速度 100Mhz 59 *bit [5:3]: 110 R0/6 驱动能力 60 *bit [0]: 0 低转换率 61 */ 62 ldr r0,=0X020E02F4 @SW_PAD_CTL_PAD_GPIO1_IO03的地址 63 ldr r1,=0X000010B0 @设置对应属性 64 str r1,[r0] @把r1存到r0指向的地址中 65 66 /*第四步、设置GPIO_IO03为输出 */ 67 ldr r0,=0X0209C004 @GPIO1_GDIR的地址 68 ldr r1,=0X00000008 @设置bit3为1 69 str r1,[r0] @把r1存到r0指向的地址中 70 71 /*第五步、打开LED 设置输出低电平 */ 72 ldr r0,=0X0209C000 @GPIO1_DR的地址 73 ldr r1,=0X00000000 @全为0 74 str r1,[r0] @把r1存到r0指向的地址中 75 76 loop: 77 b loop
编译代码
1、 arm-linux-gnueabihf-gcc 编译文件
1 arm-linux-gnueabihf-gcc -g -c led.s -o led.o
上述命令就是将 led.s 编译为 led.o,其中“-g”选项是产生调试信息, GDB 能够使用这些调试信息进行代码调试。
“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o。执行上述命令以后就会编译生成一个 led.o 文件
2、 arm-linux-gnueabihf-ld 链接文件
1 arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件
注:这里的链接地址可以是0X80000000 为了和后面Uboot的地址统一 不容易记混 这里取了0X87800000
3、 arm-linux-gnueabihf-objcopy 格式转换
1 arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
.elf不是最终烧写到SD的文件,需要转换为.bin文件
上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。
到这里就已经具备烧录条件了,alpha需要SD卡启动,要把imxdownload拷贝到工程文件夹下(只能在linux下运行)
给予它可执行权限
1 chmod 777 imxdownload
确定要烧写的SD卡名称后进行烧录
1 ./imxdownload led.bin /dev/(对应的sd卡名称)
然后把SD卡插入卡槽,调整拨码开关至 1 7开
上电可以看到LED微闪一下,稍后正常点亮,说明烧录成功。
续上:
4、 arm-linux-gnueabihf-objdump 反汇编
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
1 arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件
将上面的步骤做成Makefile
1 led.bin:led.s 2 arm-linux-gnueabihf-gcc -g -c led.s -o led.o 3 arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf 4 arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin 5 arm-linux-gnueabihf-objdump -D led.elf > led.dis 6 clean: 7 rm -rf *.o led.bin led.elf led.dis
2020-05-02 15:38:07