【正点原子Linux连载】第八章汇编LED灯试验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
作者:互联网
【正点原子Linux连载】第八章汇编LED灯试验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新
第八章汇编LED灯试验
本章开始编写本教程第一个裸机例程——经典的点灯试验,这也是我们嵌入式Linux学习的第一步。本章使用汇编语言来编写,通过本章了解如何使用汇编语言来初始化I.MX6U外设寄存器、了解I.MX6UL最基本的IO输出功能。万里长征第一步,祝愿大家学习愉快!
8.1 I.MX6U GPIO详解
8.1.1 STM32 GPIO回顾
我们一般拿到一款全新的芯片,第一个要做的事情的就是驱动其GPIO,控制其GPIO输出高低电平,我们学习I.MX6U也一样的,先来学习一下I.MX6U的GPIO。在学习I.MX6U的GPIO之前,我们先来回顾一下STM32的GPIO初始化(如果没有学过STM32就不用回顾了),我们以最常见的STM32F103为例来看一下STM32的GPIO初始化,示例代码如下:
示例代码8.1.1.1 STM32 GPIO初始化
1void LED_Init(void)
2{
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB端口时钟
6
7 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PB5端口配置
8 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度
10 GPIO_Init(GPIOB,&GPIO_InitStructure);//根据设定参数初始化GPIOB.5
11
12 GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB.5 输出高
13}
上述代码就是使用库函数来初始化STM32的一个IO为输出功能,可以看出上述初始化代码中重点要做的事情有一下几个:
①、使能指定GPIO的时钟。
②、初始化GPIO,比如输出功能、上拉、速度等等。
③、STM32有的IO可以作为其它外设引脚,也就是IO复用,如果要将IO作为其它外设引脚使用的话就需要设置IO的复用功能。
④、最后设置GPIO输出高电平或者低电平。
STM32的GPIO初始化就是以上四步,那么会不会也适用于I.MX6U的呢?I.MX6U的GPIO是不是也需要开启相应的时钟?是不是也可以设置复用功能?是不是也可以设置输出或输入、上下拉、速度等等这些?我们现在都不知道,只有去看I.MX6U的数据手册和参考手册才能知道,I.MX6U的数据手册和参考手册我们已经放到了开发板光盘中了,I.MX6U有I.MX6UL和I.MX6ULL两种,这两种型号基本是一样的,我们以I.MX6UL为例来讲解。I.MX6UL的参考手册路径为:开发板光盘->1、I.MX6UL芯片资料->IMX6UL参考手册.pdf,I.MX6UL的数据手册有三种,分别对应:车规级、工业级和商用级。从我们写代码的角度看,这三份数据手册一模一样的,做硬件的在选型的时候才需要注意一下,我们就用商用级的手册,商用级数据手册路径为:开发板光盘->1、I.MX6UL芯片资料->IMX6UL数据手册(商用级)pdf。带着上面四个疑问打开这两份手册,然后就是“啃”手册。
8.1.2 I.MX6U IO命名
STM32中的IO都是PA015、PB015这样命名的,I.MX6U的IO是怎么命名的呢?打开I.MX6UL参考手册的第30章“Chapter30: IOMUX Controller(IOMUXC)”,第30章的书签如图8.1.2.1所示:
图8.1.2.1中的形如“IOMUXC_SW_MUC_CTL_PAD_GPIO1_IO00”的就是GPIO命名,命名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是GPIO命名,比如:GPIO1_IO01、UART1_TX_DATA、JTAG_MOD、SNVS_TAMPER1等等。I.MX6U的GPIO并不像STM32一样以PA0~15这样命名,他是根据某个IO所拥有的功能来命名的。比如我们一看到GPIO1_IO01就知道这个肯定能做GPIO,看到UART1_TX_DATA肯定就知道这个IO肯定能做为UART1的发送引脚。“Chapter30: IOMUX Controller(IOMUXC)”这一章列出了I.MX6U的所有IO,如果你找遍第30章的书签,你会发现貌似GPIO只有GPIO1_IO00GPIO1_IO09,难道I.MX6U的GPIO只有这10个?显然不是的,我们知道STM32的很多IO是可以复用为其它功能的,那么I.MX6U的其它IO也是可以复用为GPIO功能。同样的,GPIO1_IO00GPIO_IO09也是可以复用为其它外设引脚的,接下来就是I.MX6U IO复用。
8.1.3 I.MX6U IO复用
以IO“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”为例,打开参考手册的1329页,如图8.1.3.1所示:
图8.1.3.1 GPIO1_IO00复用
从图8.1.3.1可以看到有个名为:IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00的寄存器,寄存器地址为0X020E005C,这个寄存器是32位的,但是只用到了最低5位,其中bit0bit3(MUX_MODE)就是设置GPIO1_IO00的复用功能的。GPIO1_IO00一共可以复用为9种功能IO,分别对应ALT0ALT8,其中ALT5就是作为GPIO1_IO00。GPIO1_IO00还可以作为I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID等。这个就是I.MX6U的IO复用,我们学习STM32的时候STM32的GPIO也是可以复用的。
再来看一个“IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA”这个IO,这个IO对应的复用如图8.1.3.2所示:
图8.1.3.2 UART1_TX_DATA IO复用
同样的,从图8.1.3.2可以看出,UART1_TX_DATA可以复用为8种不同功能的IO,分为ALT0~ALT5和ALT8、ATL9,其中ALT5表示UART1_TX_DATA可以复用为GPIO1_IO16。
由此可见,I.MX6U的GPIO不止GPIO1_IO00~GPIO1_IO09这10个,其它的IO都可以复用为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能复用什么外设的话可以直接查阅《IMX6UL参考手册》的第4章“Chapter4 ExternalSignalsand Pin Multiplexing”。如果我们要编写代码,设置某个IO的复用功能的话就需要查阅第30章“Chapter30: IOMUX Controller(IOMUXC)”,第30章详细的列出了所有IO对应的复用配置寄存器。
至此我们就解决了8.1.1中的第3个疑问,那就是I.MX6U的IO是有复用功能的,和STM32一样,如果某个IO要作为某个外设引脚使用的话,是需要配置复用寄存器的。
8.1.4 I.MX6U IO配置
细心的读者应该会发现在《I.MX6UL参考手册》第30章“Chapter30: IOMUX Controller(IOMUXC)”的书签中,每一个IO会出现两次,它们的名字差别很小,不仔细看就看不出来,比如GPIO1_IO00有如下两个书签:
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00
上面两个都是跟GPIO_IO00有关的寄存器,名字上的区别就是红色部分,一个是“MUX”,一个是“PAD”。IOMUX_SW_MUX_CTL_PAD_GPIO1_IO00我们前面已经说了,是用来配置GPIO1_IO00复用功能的,那么IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00是做什么的呢?找到这个书签对应的1582页,如图8.1.4.1所示:
图8.1.4.1 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00寄存器
从图8.1.4.1中可以看出,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00也是个寄存器,寄存器地址为0X020E02E8。这也是个32位寄存器,但是只用到了其中的低17位,在看这写位的具体含义之前,先来看一下图8.1.4.2所示的GPIO功能图:
![https://www.icode9.com/i/ll/?i=20210309153501420.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81NTc5NjU2NA==,size_16,color_FFFFFF,t_70)
图8.1.4.2 GPIO功能图。
我们对照着图8.1.4.2来详细看一下寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00的各个位的含义:
HYS(bit16):对应图8.1.4.2中HYS,用来使能迟滞比较器,当IO作为输入功能的时候有效,用于设置输入接收器的施密特触发器是否使能。如果需要对输入波形进行整形的话可以使能此位。此位为0的时候禁止迟滞比较器,为1的时候使能迟滞比较器。
PUS(bit15:14):对应图8.1.4.2中的PUS,用来设置上下拉电阻的,一共有四种选项可以选择,如表8.1.4.1所示:
位设置 含义
00 100K下拉
01 47K上拉
10 100K上拉
11 22K上拉
表8.1.4.1上下拉设置
PUE(bit13):图8.1.4.2没有给出来,当IO作为输入的时候,这个位用来设置IO使用上下拉还是状态保持器。当为0的时候使用状态保持器,当为1的时候使用上下拉。状态保持器在IO作为输入的时候才有用,顾名思义,就是当外部电路断电以后此IO口可以保持住以前的状态。
PKE(bit12):对应图8.1.4.2中的PKE,此为用来使能或者禁止上下拉/状态保持器功能,为0时禁止上下拉/状态保持器,为1时使能上下拉和状态保持器。
ODE(bit11):对应图8.1.4.2中的ODE,当IO作为输出的时候,此位用来禁止或者使能开路输出,此位为0的时候禁止开路输出,当此位为1的时候就使能开路输出功能。
SPEED(bit7:6):对应图8.1.4.2中的SPEED,当IO用作输出的时候,此位用来设置IO速度,设置如表8.1.4.2所示:
位设置 速度
00 低速50M
01 中速100M
10 中速100M
11 最大速度200M
表8.1.4.2速度配置
DSE(bit5:3):对应图8.1.4.2中的DSE,当IO用作输出的时候用来设置IO的驱动能力,总共有8个可选选项,如表8.1.4.3所示:
位设置 速度
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
表8.1.4.3驱动能力设置
SRE(bit0):对应图8.1.4.2中的SRE,设置压摆率,当此位为0的时候是低压摆率,当为1的时候是高压摆率。这里的压摆率就是IO电平跳变所需要的时间,比如从0到1需要多少时间,时间越小波形就越陡,说明压摆率越高;反之,时间越多波形就越缓,压摆率就越低。如果你的产品要过EMC的话那就可以使用小的压摆率,因为波形缓和,如果你当前所使用的IO做高速通信的话就可以使用高压摆率。
通过上面的介绍,可以看出寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00是用来配置GPIO1_IO00的,包括速度设置、驱动能力设置、压摆率设置等等。至此我们就解决了8.1.1中的第2个疑问,那就是I.MX6U的IO是可以设置速度的、而且比STM32的设置要更多。但是我们没有看到如何设置IO为输入还是输出?IO的默认电平如何设置等等,所以我们接着继续看。
8.1.5 I.MX6U GPIO配置
IOMUXC_SW_MUX_CTL_PAD_XX_XX和IOMUXC_SW_PAD_CTL_PAD_XX_XX这两种寄存器都是配置IO的,注意是IO!不是GPIO,GPIO是一个IO众多复用功能中的一种。比如GPIO1_IO00这个IO可以复用为:I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID、ENET1_REF_CLK、MQS_RIGHT、GPIO1_IO00、ENET1_1588_EVENT0_IN、SRC_SYSTEM_RESET和WDOG3_WDOG_B这9个功能,GPIO1_IO00是其中的一种,我们想要把GPIO1_IO00用作哪个外设就复用为哪个外设功能即可。如果我们要用GPIO1_IO00来点个灯、作为按键输入啥的就是使用其GPIO(通用输入输出)的功能。将其复用为GPIO以后还需要对其GPIO的功能进行配置,关于I.MX6U的GPIO请参考《IMX6UL参考手册》的第26章“Chapter 26 General Purpose Input/Ouput(GPIO)”,GPIO结构如图8.1.5.1所示:
图8.1.5.1 GPIO结构图
在图8.1.5.1的左下角的IOMUXC框图里面就有SW_MUX_CTL_PAD_*和SW_PAD_CTL_PAD_*两种寄存器。这两种寄存器前面说了用来设置IO的复用功能和IO属性配置。左上角部分的GPIO框图就是,当IO用作GPIO的时候需要设置的寄存器,一共有八个:DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR和ISR。前面我们说了I.MX6U一共有GPIO1~GPIO5共五组GPIO,每组GPIO都有这8个寄存器。我们来看一下这8个寄存器都是什么含义。
首先来看一下DR寄存器,此寄存器是数据寄存器,结构图如图8.1.5.2所示:
图8.1.5.2 DR寄存器结构图
此寄存器是32位的,一个GPIO组最大只有32个IO,因此DR寄存器中的每个位都对应一个GPIO。当GPIO被配置为输出功能以后,向指定的位写入数据那么相应的IO就会输出相应的高低电平,比如要设置GPIO1_IO00输出高电平,那么就应该设置GPIO1.DR=1。当GPIO被配置为输入模式以后,此寄存器就保存着对应IO的电平值,每个位对对应一个GPIO,例如,当GPIO1_IO00这个引脚接地的话,那么GPIO1.DR的bit0就是0。
看完DR寄存器,接着看GDIR寄存器,这是方向寄存器,用来设置某个GPIO的工作方向的,即输入/输出,GDIR寄存器结构如图8.1.5.3所示:
图8.1.5.3 GDIR寄存器
GDIR寄存器也是32位的,此寄存器用来设置某个IO的工作方向,是输入还是输出。同样的,每个IO对应一个位,如果要设置GPIO为输入的话就设置相应的位为0,如果要设置为输出的话就设置为1。比如要设置GPIO1_IO00为输入,那么GPIO1.GDIR=0;
接下来看PSR寄存器,这是GPIO状态寄存器,如图8.1.5.4所示:
图8.1.5.4 PSR状态寄存器
同样的PSR寄存器也是一个GPIO对应一个位,读取相应的位即可获取对应的GPIO的状态,也就是GPIO的高低电平值。功能和输入状态下的DR寄存器一样。
接下来看ICR1和ICR2这两个寄存器,都是中断控制寄存器,ICR1用于配置低16个GPIO,ICR2用于配置高16个GPIO,ICR1寄存器如图8.1.5.5所示:
图8.1.5.5 ICR1寄存器
ICR1用于IO0~15的配置, ICR2用于IO16~31的配置。ICR1寄存器中一个GPIO用两个位,这两个位用来配置中断的触发方式,和STM32的中断很类似,可配置的选线如表8.1.5.1所示:
位设置 速度
00 低电平触发
01 高电平触发
10 上升沿触发
11 下降沿触发
表8.1.5.1中断触发配置
以GPIO1_IO15为例,如果要设置GPIO1_IO15为上升沿触发中断,那么GPIO1.ICR1=2<<30,如果要设置GPIO1的IO16~31的话就需要设置ICR2寄存器了。
接下来看IMR寄存器,这是中断屏蔽寄存器,如图8.1.5.6所示:
图8.1.5.6 IMR寄存器
IMR寄存器也是一个GPIO对应一个位,IMR寄存器用来控制GPIO的中断禁止和使能,如果使能某个GPIO的中断,那么设置相应的位为1即可,反之,如果要禁止中断,那么就设置相应的位为0即可。例如,要使能GPIO1_IO00的中断,那么就可以设置GPIO1.MIR=1即可。
接下来看寄存器ISR,ISR是中断状态寄存器,寄存器如图8.1.5.7所示:
图8.1.5.7 ISR寄存器
ISR寄存器也是32位寄存器,一个GPIO对应一个位,只要某个GPIO的中断发生,那么ISR中相应的位就会被置1。所以,我们可以通过读取ISR寄存器来判断GPIO中断是否发生,相当于ISR中的这些位就是中断标志位。当我们处理完中断以后,必须清除中断标志位,清除方法就是向ISR中相应的位写1,也就是写1清零。
最后来看一下EDGE_SEL寄存器,这是边沿选择寄存器,寄存器如图8.1.5.8所示:
图8.1.5.8 EDGE_SEL寄存器
EDGE_SEL寄存器用来设置边沿中断,这个寄存器会覆盖ICR1和ICR2的设置,同样是一个GPIO对应一个位。如果相应的位被置1,那么就相当与设置了对应的GPIO是上升沿和下降沿(双边沿)触发。例如,我们设置GPIO1.EDGE_SEL=1,那么就表示GPIO1_IO01是双边沿触发中断,无论GFPIO1_CR1的设置为多少,都是双边沿触发。
关于GPIO的寄存器就讲解到这里,因为GPIO是最常用的功能,我们详细的讲解了GPIO的8个寄存器。至此我们就解决了8.1.1中的第3个和第4个疑问,那就是I.MX6U的IO是需要配置和输出的、是可以设置输出高低电平,也可以读取GPIO对应的电平。
8.1.6 I.MX6U GPIO时钟使能
还有最后一个疑问,那就是I.MX6U的GPIO是否需要使能时钟?STM32的每个外设都有一个外设时钟,GPIO也不例外,要使用某个外设,必须要先使能对应的时钟。I.MX6U其实也一样的,每个外设的时钟都可以独立的使能或禁止,这样可以关闭掉不使用的外设时钟,起到省电的目的。如果要使用某个外设的话必须要先使能其时钟。I.MX6U的系统时钟参考《I.MX6UL参考手册》的第18章“Chapter 18: Clock Controller Module(CCM)”,这一个章主要讲解I.MX6U的时钟系统,很复杂。我们先不研究I.MX6U的时钟系统,我们只看一下CCM里面的外设时钟使能寄存器。CMM有CCM_CCGR0~CCM_CCGR6这7个寄存器,这7个寄存器控制着I.MX6U的所有外设时钟开关,我们以CCM_CCGR0为例来看一下如何禁止或使能一个外设的时钟,CCM_CCGR0结构体如图8.1.6.1所示:
图8.1.6.1 CCM_CCGR0寄存器
CCM_CCGR0是个32为寄存器,其中每2位控制一个外设的时钟,比如bit31:30控制着GPIO2的外设时钟,两个位就有4中操作方式,如表8.1.6.1所示:
位设置 时钟控制
00 所有模式下都关闭外设时钟。
01 只有在运行模式下打开外设时钟,等待模式和停止模式下均关闭外设时钟。
10 未使用(保留)。
11 除了停止模式以外,其他所有模式下时钟都打开。
表 8.1.6.1外设时钟控制
根据表8.1.6.1中的位设置,如果我们要打开GPIO2的外设时钟,那么只需要设置CCM_CCGR0的bit31和bit30都为1即可,也就是CCM_CCGR0=3 << 30。反之,如果要关闭GPIO2的外设时钟,那就设置CCM_CCGR0的bit31和bit30都为0即可。CCM_CCGR0~CCM_CCGR6这7个寄存器操作都是类似的,只是不同的寄存器对应不同的外设时钟而已,为了方便开发,本教程后面所有的例程将I.MX6U的所有外设时钟都打开了。至此我们就解决了8.1.1中的所有问题都解决了,I.MX6U的每个外设的时钟都可以独立的禁止和使能,这个和STM32是一样。总结一下,要将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,设置输入/输出、是否使用中断、默认输出电平等。
8.2硬件原理分析
打开I.MX6U-ALPHA开发板底板原理图,底板原理图和核心板原理图都放到了开发板光盘中,路径为:开发板光盘->2、开发板原理图->IMX6UL_ALPHA_V1.0(底板原理图)。I.MX6U-ALPHA开发板上有一个LED灯,原理图如下8.2.1所示:
图8.2.1 LED原理图
从图8.2.1可以看出,LED0接到了GPIO_3上,GPIO_3就是GPIO1_IO03,当GPIO1_IO03输出低电平(0)的时候发光二极管LED0就会导通点亮,当GPIO1_IO03输出高电平(1)的时候发光二极管LED0不会导通,因此LED0也就不会点亮。所以LED0的亮灭取决于GPIO1_IO03的输出电平,输出0就亮,输出1就灭。
8.3实验程序编写
按照8.1小节中讲的,我们需要对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组寄存器地址,在《IMX6UL参考手册》的1154页,如图8.3.1所示:
图8.3.1 GPIO3对应的GPIO寄存器地址
本实验中GPIO1_IO03是作为输出功能的,因此GPIO3_GDIR的bit3要设置为1,表示输出。
5、控制GPIO的输出电平
经过前面几步,GPIO1_IO03已经配置好了,只需要向GPIO3_DR寄存器的bit3写入0即可控制GPIO1_IO03输出低电平,打开LED,向bit3写入1可控制GPIO1_IO03输出高电平,关闭LED。
本实验完整工程在开发板光盘中,路径为:开发板光盘-> 1、例程源码-> 1、裸机例程-> 1_leds,如果要打开这个工程的话一定要将“1_leds”整个文件夹复制到一个没有中文路径的目录中,否则直接打开工程可能会报错。
所有的裸机实验我们都在Ubuntu下完成,使用VSCode编辑器!
所有的裸机实验我们都在Ubuntu下完成,使用VSCode编辑器!
所有的裸机实验我们都在Ubuntu下完成,使用VSCode编辑器!
既然是实验,肯定要自己动手创建工程,新建一个名为“1_leds”的文件夹,然后在“1_leds”这个目录下新建一个名为“led.s”的汇编文件和一个名为“.vscode”的目录,创建好以后“1_leds”文件夹如图8.3.2所示:
图8.3.2新建的1_leds工程文件夹
图8.3.2中.vscode文件夹里面存放VSCode的工程文件,led.s就是我们新建的汇编文件,我们稍后会在led.s这个文件中编写汇编程序。使用VSCode打开1_leds这个文件夹,打开以后如图8.3.3所示:
图8.3.3 VSCode工程
在led.s中输入如下代码:
示例代码8.3.1 led.s文件源码
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : led.s
作者 : 左忠凯
版本 : V1.0
描述 : 裸机实验1 汇编点灯
使用汇编来点亮开发板上的LED灯,学习和掌握如何用汇编语言来
完成对I.MX6U处理器的GPIO初始化和控制。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/3 左忠凯创建
**************************************************************/
1
2.global _start /* 全局标号 */
3
4/*
5 * 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
6 * GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
7 */
8 _start:
9/* 例程代码 */
10/* 1、使能所有时钟 */
11 ldr r0,=0X020C4068/* 寄存器CCGR0 */
12 ldr r1,=0XFFFFFFFF
13 str r1,[r0]
14
15 ldr r0,=0X020C406C/* 寄存器CCGR1 */
16 str r1,[r0]
17
18 ldr r0,=0X020C4070/* 寄存器CCGR2 */
19 str r1,[r0]
20
21 ldr r0,=0X020C4074/* 寄存器CCGR3 */
22 str r1,[r0]
23
24 ldr r0,=0X020C4078/* 寄存器CCGR4 */
25 str r1,[r0]
26
27 ldr r0,=0X020C407C/* 寄存器CCGR5 */
28 str r1,[r0]
29
30 ldr r0,=0X020C4080/* 寄存器CCGR6 */
31 str r1,[r0]
32
33
34/* 2、设置GPIO1_IO03复用为GPIO1_IO03 */
35 ldr r0,=0X020E0068/* 将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 */
36 ldr r1,=0X5/* 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 */
37 str r1,[r0]
38
39/* 3、配置GPIO1_IO03的IO属性
40 *bit 16:0 HYS关闭
41 *bit [15:14]: 00 默认下拉
42 *bit [13]: 0 kepper功能
43 *bit [12]: 1 pull/keeper使能
44 *bit [11]: 0 关闭开路输出
45 *bit [7:6]: 10 速度100Mhz
46 *bit [5:3]: 110 R0/6驱动能力
47 *bit [0]: 0 低转换率
48 */
49 ldr r0,=0X020E02F4/*寄存器SW_PAD_GPIO1_IO03_BASE */
50 ldr r1,=0X10B0
51 str r1,[r0]
52
53/* 4、设置GPIO1_IO03为输出 */
54 ldr r0,=0X0209C004/*寄存器GPIO1_GDIR */
55 ldr r1,=0X0000008
56 str r1,[r0]
57
58/* 5、打开LED0
59 * 设置GPIO1_IO03输出低电平
60 */
61 ldr r0,=0X0209C000/*寄存器GPIO1_DR */
62 ldr r1,=0
63 str r1,[r0]
64
65/*
66 * 描述: loop死循环
67 */
68 loop:
69 b loop
我们来详细的分析一下上面的汇编代码,我们以后分析代码都根据行号来分析。
第2行定义了一个全局标号_start,代码就是从_start这个标号开始顺序往下执行的。
第11行使用ldr指令向寄存器r0写入0X020C4068,也就是r0=0X020C4068,这个是CCM_CCGR0寄存器的地址。
第12行使用ldr指令向寄存器r1写入0XFFFFFFFF,也就是人r1=0XFFFFFFFF。因为我们要开启所有的外设时钟,因此CCM_CCGR0~CCM_CCGR6所有的寄存器都32为位都要置1,也就是写入0XFFFFFFFF。
第13行使用str将r1中的值写入到r0所保存的地址中去,也就是给0X020C4068这个地址写入0XFFFFFFFF,相当于CCM_CCGR0=0XFFFFFFFF,就是打开CCM_CCGR0寄存器所控制的所有外设时钟。
第15~31行都是向CCM_CCGRX(X=1~6)寄存器写入0XFFFFFFFF。这样我就通过汇编代码使能了I.MX6U的所有外设时钟。
第35~37行是设置GPIO1_IO03的复用功能,GPIO1_IO03的复用寄存器地址为0X020E0068,寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的MUX_MODE设置为5就是将GPIO1_IO03设置为GPIO。
第49~51行是设置GPIO1_IO03的配置寄存器,也就是寄存器IOMUX_SW_PAD_CTL_PAD_GPIO1_IO03的值,此寄存器地址为0X020E02F4,代码里面已经给出了这个寄存器详细的位设置。
第54~63行是设置GPIO功能,经过上面几步操作,GPIO1_IO03这个IO已经被配置为了GPIO功能,所以还需要设置跟GPIO有关的寄存器。第54~56行是设置GPIO1->GDIR寄存器,将GPIO1_IO03设置为输出模式,也就是寄存器的GPIO1_GDIR的bit3置1。
第61~63行设置GPIO1->DR寄存器,也就是设置GPIO1_IO03的输出,我们要点亮开发板上的LED0,那么GPIO1_IO03就必须输出低电平,所以这里设置GPIO1_DR寄存器为0。
第68~69行是死循环,通过b指令,CPU重复不断的跳到loop函数执行,进入一个死循环。
8.4编译下载验证
8.4.1编译代码
如果你是在Windows下使用Source Insight编写的代码,就需要通过FileZilla将编写好的代码发送的Ubuntu中去编译,FileZilla的使用参考4.1小节。因为我们现在是直接在Ubuntu下使用VSCode编译的代码,所以不需要使用FileZilla将代码发送到Ubuntu下,可以直接进行编译,在编译之前我们先了解几个编译工具。
1、arm-linux-gnueabihf-gcc编译文件
我们是要编译出在ARM开发板上运行的可执行文件,所以要使用我们在4.3小节安装的交叉编译器arm-linux-gnueabihf-gcc来编译。因此本试验就一个led.s源文件,所以编译比较简单。先将led.s编译为对应的.o文件,在终端中输入如下命令:
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文件,如图8.4.1.1所示:
图8.4.1.1编译生成led.o文件
图8.4.1.1中led.o文件并不是我们可以下载到开发板中运行的文件,一个工程中所有的C文件和汇编文件都会编译生成一个对应的.o文件,我们需要将这.o文件链接起来组合成可执行文件。
2、arm-linux-gnueabihf-ld链接文件
arm-linux-gnueabihf-ld用来将众多的.o文件链接到一个指定的链接位置。我们在学习SMT32的时候基本就没有听过“链接”这个词,我们一般用MDK编写好代码,然后点击“编译”,MDK或者IAR就会自动帮我们编译好整个工程,最后再点击“下载”就可以将代码下载到开发板中。这是因为链接这个操作MDK或者IAR已经帮你做好了,后面我就以MDK为例给大家讲解。大家可以打开一个STM32的工程,然后编译一下,肯定能找到很多.o文件,如图8.4.1.2所示:
图8.4.1.2 STM32编译生成的.o文件
图8.4.1.2中的这些.o文件肯定会被MDK链接到某个地址去,如果使用MDK开发STM32的话肯定对图8.4.1.3所示界面很熟悉:
图8.4.1.3 STM32配置界面
图8.4.1.3中左侧的IROM1我们都知道是设置STM32芯片的ROM起始地址和大小的,右边的IRAM1是设置STM32芯片的RAM起始地址和大小的。其中0X08000000就是STM32内部ROM的起始地址,编译出来的指令肯定是要从0X08000000这个地址开始存放的。对于STM32来说0X08000000就是它的链接地址,图8.4.1.2中的这些.o文件就是这个链接地址开始依次存放,最终生成一个可以下载的hex或者bin文件,我们可以打开.map文件查看一下这些文件的链接地址,在MDK下打开一个工程的.map文件方法如图8.4.1.4所示:
图8.4.1.4 .map文件打开方法
图8.4.1.4中的.map文件就详细的描述了各个.o文件都是链接到了什么地址,如图8.4.1.5所示:
图8.4.1.5 STM32镜像映射文件
从图8.4.1.5中就可以看出STM32的各个.o文件所处的位置,起始位置是0X08000000。由此可以得知,我们用MDK开发STM32的时候也是有链接的,只是这些工作MDK都帮我们全部做好了,我们不用关心而已。但是我们在Linux下用交叉编译器开发ARM的是时候就需要自己处理这些了。
因此我们现在需要做的就是确定一下本试验最终的可执行文件其运行起始地址,也就是链接地址。这里我们要区分“存储地址”和“运行地址”这两个概念,“存储地址”就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。“运行地址”就是代码运行的时候所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。比如I.MX6U支持SD卡、EMMC、NAND启动,因此代码可以存储到SD卡、EMMC或者NAND中,但是要运行的话就必须将代码从SD卡、EMMC或者NAND中拷贝到其运行地址(链接地址)处,“存储地址”和“运行地址”可以一样,比如STM32的存储起始地址和运行起始地址都是0X08000000。
本教程所有的裸机例程都是烧写到SD卡中,上电以后I.MX6U的内部bootrom程序会将可执行文件拷贝到链接地址处,这个链接地址可以在I.MX6U的内部128KB RAM中(0X900000~0X91FFFF),也可以在外部的DDR中。本教程所有裸机例程的链接地址都在DDR中,链接起始地址为0X87800000。I.MX6U-ALPHA开发板的DDR容量有两种:512MB和256MB,起始地址都为0X80000000,只不过512MB的终止地址为0X9FFFFFFF,而256MB容量的终止地址为0X8FFFFFFF。之所以选择0X87800000这个地址是因为后面要讲的Uboot其链接地址就是0X87800000,这样我们统一使用0X87800000这个链接地址,不容易记混。
确定了链接地址以后就可以使用arm-linux-gnueabihf_ld来将前面编译出来的led.o文件链接到0X87800000这个地址,使用如下命令:
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令中-Ttext就是指定链接地址,“-o”选项指定链接生成的elf文件名,这里我们命名为led.elf。上述命令执行完以后就会在工程目录下多一个led.elf文件,如图8.4.1.6所示:
图8.4.1.6 链接生成led.elf文件
led.elf文件也不是我们最终烧写到SD卡中的可执行文件,我们要烧写的.bin文件,因此还需要将led.elf文件转换为.bin文件,这里我们就需要用到arm-linux-gnueabihf-objcopy这个工具了。
3、arm-linux-gnueabihf-objcopy格式转换
arm-linux-gnueabihf-objcopy更像一个格式转换工具,我们需要用它将led.elf文件转换为led.bin文件,命令如下:
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。上述命令执行完成以后,工程目录如图8.4.1.7所示:
图8.4.1.7 生成最终的led.bin文件
至此我们终于等到了想要的东西——led.bin文件。
4、arm-linux-gnueabihf-objdump反汇编
大多数情况下我们都是用C语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将elf文件反汇编,比如如下命令:
arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为led.dis文件,如图8.4.1.8所示:
图8.4.1.8 反汇编生成led.dis
可以打开led.dis文件看一下,看看是不是汇编代码,如图8.4.1.9所示:
图8.4.1.9 反汇编文件
从图8.4.1.9可以看出led.dis里面是汇编代码,而且还可以看到内存分配情况。在0X87800000处就是全局标号_start,也就是程序开始的地方。通过led.dis这个反汇编文件可以明显的看出到我们的代码已经链接到了以0X87800000为起始地址的区域。
总结一下我们为了编译ARM开发板上运行的led.o这个文件使用了如下命令:
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
如果我们修改了led.s文件,那么就需要在重复一次上面的这些命令,太麻烦了,这个时候我们就可以使用第三章讲解的Makefile文件了。
8.4.2创建Makefile文件
是用“touch”命令在工程根目录下创建一个名为“Makefile”的文件,如图8.4.1.12所示:
图8.4.1.12创建Makefile文件
创建好Makefile文件以后就需要根据Makefile语法编写Makefile文件了,Makefile基本语法我们已经在第三章讲解了,在Makefile中输入如下内容:
示例代码8.4.2.1 Makefile文件源码
led.bin:led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean:
rm -rf *.o led.bin led.elf led.dis
创建好Makefile以后我们就只需要执行一次“make”命令即可完成编译,过程如图8.4.1.13所示:
图8.4.1.13 Makefile执行过程
如果我们要清理工程的话执行“make clean”即可,如图8.4.1.14所示:
图8.4.1.14makeclean清理工程
至此,有关代码编译、arm-linux-gnueabihf交叉编译器的使用就到这里了,我们接下来讲解如何将led.bin烧写到SD卡中。
8.4.3代码烧写
我们学习STM32等其他的单片机的时候,编译完代码以后可以直接通过MDK或者IAR下载到内部的flash中。但是I.MX6U虽然内部有96K的ROM,但是这96K的ROM是NXP自己用的,不向用户开放。所以相当于说I.MX6U是没有内部flash的,但是我们的代码得有地方存放啊,为此,I.MX6U支持从外置的NOR Flash、NAND Flash、SD/EMMC、SPI NOR Flash和QSPI Flash这些存储介质中启动,所以我们可以将代码烧写到这些存储介质中中。在这些存储介质中,除了SD卡以外,其他的一般都是焊接到了板子上的,我们没法直接烧写。但是SD卡是活动的,是可以从板子上插拔的,我们可以将SD卡插到电脑上,在电脑上使用软件将.bin文件烧写到SD卡中,然后再插到板子上就可以了。其他的几种存储介质是我们量产的时候用到的,量产的时候代码就不可能放到SD卡里面了,毕竟SD是活动的,不牢固,而其他的都是焊接到板子上的,很牢固。
因此,我们在调试裸机和Uboot的时候是将代码下载到SD中,因为方便嘛,当调试完成以后量产的时候要将裸机或者Uboot烧写到SPI NOR Flash、EMMC、NAND等这些存储介质中的。那么,如何将我们前面编译出来的led.bin烧写到SD卡中呢?肯定有人会认为直接复制led.bin到SD卡中不就行了,错!编译出来的可执行文件是怎么存放到SD中的,存放的位置是什么?这个NXP是有详细规定的!我们必须按照NXP的规定来将代码烧写到SD卡中,否则代码是绝对运行不起来的。《IMX6UL参考手册》的第8章“Chapter 8 System Boot”就是专门讲解I.MX6U启动的,我们下一章会详细的讲解I.MX6U启动方式的。
正点原子专门编写了一个软件来将编译出来的.bin文件烧写到SD卡中,这个软件叫做“imxdownload”,软件我们已经放到了开发板光盘中,路径为:开发板光盘->5、开发工具->2、Ubuntu下裸机烧写软件->imxdownload,imxdownlaod只能在Ubuntu下使用,使用步骤如下:
1、将imxdownload拷贝到工程根目录下
我们要将imxdownload拷贝到工程根目录下,也就是和led.bin处于同一个文件夹下,要不然烧写会失败的,拷贝完成以后如图8.4.3.1所示:
图8.4.3.1拷贝imxdownload软件
2、给予imxdownload可执行权限
我们直接将软件imxdownload从Windows下复制到Ubuntu中以后,imxdownload默认是没有可执行权限的。我们需要给予imxdownload可执行权限,使用命令“chmod”,命令如下:
图8.4.3.2给予imxdownload可执行权限
通过对比图8.4.3.1和图8.4.3.2可以看到,当给予imxdownload可执行权限以后其名字变成了绿色的,如果没有可执行权限的话其名字颜色是白色的。所以在Ubuntu中我们可以初步的从文件名字的颜色判断其是否具有可执行权限。
3、确定要烧写的SD卡。
准备一张新的SD(TF)卡,确保SD卡里面没有数据,因为我们在烧写代码的时候可能会格式话SD卡!!!
Ubuntu下所有的设备文件都在目录“/dev”里面,所以插上SD卡以后也会出现在“/dev”里面,其中存储设备都是以“/dev/sd”开头的。我们要先看一下不插SD卡的时候电脑都有哪些存储设备,以防插入SD卡以后分不清谁是谁。输入如下所示命令:
ls /dev/sd*
当前电脑的存储文件如图8.4.3.3所示:
图8.4.3.3Ubuntu当前存储文件
从图中可以看到当前电脑有/dev/sda、/dev/sda1、/dev/sda2和/dev/sda5这5个存储设备,使用读卡器将SD卡插到电脑,一定要确保SD卡是挂载到了Ubuntu系统中,而不是Windows下。SD卡挂载到电脑以后,VMware右下角会出现如图8.4.3.4所示图标:
图8.4.3.4插上SD卡以后的提示
如图8.4.3.4所示,在VMware右小角有个图标看着像硬盘一样的图标:,这个图标就表示当前有存储设备插入,我们将鼠标放上去就会有提示当前设备名字,比如我这里提示“Realtek USB3.0 Card Reader”,这是我的读卡器的名字。如果是灰色的话就表示SD卡挂载到了Windows下,而不是Ubuntu上,所以我现在这个电脑的SD卡就是挂载到了Windiows下,我肯定要将其挂载到Ubuntu中,因为我要在Ubuntu下烧写代码。方法很简单,点击图标,点击以后如图8.4.3.5所示:
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=2021030915482537.png#pic_center
图8.4.3.5将SD卡连接到Ubuntu中
点击图8.4.3.5中的“连接(断开与主机的连接)©”,点击以后会弹出如图8.4.3.6所示提示界面:
图8.4.3.6提示界面
图8.4.3.6提示你有个USB要从主机(Windows)拔出,插入虚拟机中,点击“确定”按钮即可。SD卡插入到Ubuntu以后,图标就会变为,不是灰色的了。在输入命令“ls /dev/sd*”来查看当前Ubuutu下的存储设备,如图8.4.3.7所示:
图8.4.3.7当前系统存储设备
从图8.4.3.7中可以看到,我的电脑多出了/dev/sdb、/dev/sdc、/dev/sdd、/dev/sdd1、/dev/sda和/dev/sdf这6个存储设备。这是因为我的读卡器是多合一读卡器,所以会多出来这么多,如果你用的单一读卡器那么应该只会出现一个或者两个。那这6个存储设备哪个才是我的SD卡呢?/dev/sdd和/dev/sdd1是我的SD卡,为什么呢?因为只有/dev/sdd有个对应的/dev/sdd1,/dev/sdd是我的SD卡,/dev/sdd1是SD卡的第一个分区。如果你的SD卡有多个分区的话可能会出现/dev/sdd2、/dev/sdd3等等。确定好SD卡以后我们就可以使用软件imxdownload向SD卡烧写led.bin文件了。
如果你的电脑没有找到SD卡的话,尝试重启一下Ubuntu操作!
4、向SD卡烧写bin文件
使用imxdownload向SD卡烧写led.bin文件,命令格式如下:
./imxdownload <.bin file>
其中.bin就是要烧写的.bin文件,SD Card就是你要烧写的SD卡,比如我的电脑使用如下命令烧写led.bin到/dev/sdd中:
./imxdownload led.bin /dev/sdd
烧写的过程中可能会让你输入密码,输入你的Ubuntu密码即可完成烧写,烧写过程如图8.4.3.8所示:
图8.4.3.8imxdownload烧写过程
在图8.4.3.8中,烧写的最后一行会显示烧写大小、用时和速度,比如led.bin烧写到SD卡中的大小是3.2KB,用时0.0160821s,烧写速度是201KB/s。注意这个烧写速度,如果这个烧写速度在几百KB/s以下那么就是正常烧写。
如果这个烧写速度大于几十MB/s、甚至几百MB/s那么肯定是烧写失败了!
解决方法就是重新插拔SD卡,一般出现这种情况,重新插拔SD卡基本没啥用,只有重启Ubuntu,至于原因,我也不清楚。
烧写完成以后会在当前工程目录下生成一个load.imx的文件,如图8.4.3.9所示:
图8.4.3.9生成的load.imx文件
load.imx这个文件就是软件imxdownload根据NXP官方启动方式介绍的内容,在led.bin文件前面添加了一些数据头以后生成的。最终烧写到SD卡里面的就是这个load.imx文件,而非led.bin。至于具体添加了些什么内容,我们会在下一章讲解。
8.4.4代码验证
代码已经烧写到了SD卡中了,接下来就是将SD卡插到开发板的SD卡槽中,然后设置拨码开关为SD卡启动,拨码开关设置如图8.4.4.1所示:
图8.4.4.1拨码开关SD卡启动设置
设置好以后按一下开发板的复位键,如果代码运行正常的话LED0就会被点亮,如图8.4.4.2所示:
图8.4.4.2 LED0点亮
如图8.4.4.2所示,LED0被正常点亮,可能LED0之前会有一点微亮,那是因为I.MX6U的IO默认电平可能让LED0导通了,但是IO的默认配置内部可能有很大的电阻,所以电流就很小,导致LED0微亮。但是我们自己编写代码、配置好IO以后就不会有这个问题,LED0就很亮了。
本小节我们详细的讲解了如何编译代码,并且如何将代码烧写进SD卡中进行测试。后续我们的所有裸机实验和Uboot实验都使用的这种方法进行代码的烧写和测试。
标签:GPIO1,led,8.4,Linux,原子,正点,IO,寄存器,GPIO 来源: https://blog.csdn.net/weixin_55796564/article/details/114450099