汇编基础语法参考:初识汇编语言-CSDN博客
本文主要参考正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 》第八章
STM32 GPIO 回顾
我们一般拿到一款全新的芯片,第一个要做的事情的就是驱动其 GPIO,控制其 GPIO 输出高低电平,我们学习 I.MX6U 也一样的,先来学习一下 I.MX6U 的 GPIO。在学习 I.MX6U的 GPIO 之前,我们先来回顾一下 STM32 的 GPIO 初始化(如果没有学过 STM32 就不用回顾了),我们以最常见的 STM32F103 为例来看一下 STM32 的 GPIO 初始化,示例代码如下:
上述代码就是使用库函数来初始化 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.MX6ULL 为例来讲解。
I.MX6ULL 的参考手册路径为:开发板光盘->7、I.MX6U 参考资料->2、I.MX6ULL 芯片资料 ->IMX6ULL 参考手册.pdf;
I.MX6ULL 的数据手册有三种,分别对应:车规级、工业级和商用 级。从我们写代码的角度看,这三份数据手册一模一样的,做硬件的在选型的时候才需要注意一下,我们就用商用级的手册,商用级数据手册路径为:开发板光盘->7、I.MX6UL 芯片资料->2、I.MX6ULL 芯片资料->IMX6ULL 数据手册(商用级)pdf。
带着上面四个疑问打开这两份手册,然后就是“啃”手册。
I.MX6U IO 命名
STM32 中的 IO 都是 PA0~15、PB0~15 这样命名的,I.MX6U 的 IO 是怎么命名的呢?打开I.MX6ULL 参考手册的第 32 章“Chapter 32: IOMUX Controller(IOMUXC)”,第 32 章的书签如图 8.1.2.1 所示:
这里我们重点关注常用的IO,这些常用IO的截图没截全,因为实在是太多太多了,根本截不完。自行查阅手册即可。
从图 8.1.2.1 可以看出,I.MX6ULL 的 IO 分为两类:SNVS 域的和通用的,这两类 IO 本质上都是一样的,我们就有下面的常用 IO 为例,讲解一下 I.MX6ULL 的 IO 命名方式。
I.MX6ULL 的 GPIO 并不像 STM32一样以 PA0~15 这样命名,他是根据某个 IO 所拥有的功能来命名的。形如“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”的就是 GPIO 命名,命名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是 GPIO 命名,比如我们一看GPIO1_IO00 就知道这个肯定能做GPIO;再比如看到IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA这个引脚命名,就知道这个 IO 肯定能做 为 UART1 的发送引脚
等等,诸如此类。
这里面的IO命名都是按照其默认功能来的,“Chapter 32: IOMUX Controller(IOMUXC)”这一章列出了 I.MX6ULL 的所有 IO,如果你找遍 32 章的书签,你会发现貌似 GPIO 只有 GPIO1_IO00~GPIO1_IO09,难道I.MX6ULL 的 GPIO 只有这 10 个?显然不是的, 我们知道 STM32 的很多 IO 是可以复用为其它功能的,那么 I.MX6ULL 的其它 IO 也 是 可 以 复 用 为 GPIO 功能。同样的,GPIO1_IO00~GPIO_IO09 也是可以复用为其它外设引脚的,接下来就是 I.MX6ULL IO 复用。
I.MX6U IO 复用
以“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”这个 IO 为例,打开参考手册的 1568 页, 如图 8.1.3.1 所示:
从图 8.1.3.1 可以看到有个名为:IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00 的寄存器,寄存器地址为 0X020E005C,这个寄存器是 32 位的,但是只用到了最低 5 位,其中 bit0~bit3(MUX_MODE)就是设置 GPIO1_IO00 的复用功能的。GPIO1_IO00 一共可以复用为 9种功能 IO,分别对应 ALT0~ALT8,其中 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 可以复用为 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。
注意,stm32是按照PA、PB、PC……的命名来编排io引脚的,而且,每个组的io数量是一样的,而imx6u每组的io数量是不一样的。不过,都只是一个名字而已,本质上都是寄存器的设置。
如果只想看每个 IO 能复用什么外设的话可以直接查阅《IMX6ULL 参考手册》的第 4 章“Chapter 4 External Signals and Pin Multiplexing”。如果我们要编写代码,设置某个 IO 的复用功能的话就需要查阅第 32 章“Chapter 32: IOMUX Controller(IOMUXC)”,第 32 章详细的列出了所有 IO 对应的复用配置寄存器。
至此我们就解决了 8.1.1 中的第 3 个疑问,那就是 I.MX6U 的 IO 是有复用功能的,和 STM32一样,如果某个 IO 要作为某个外设引脚使用的话,是需要配置复用寄存器的。
I.MX6U IO 配置
细心的读者应该会发现在《I.MX6UL 参考手册》第 32 章“Chapter 32: 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 是做什么的呢?找到这个书签对应的 1787 页,如图 8.1.4.1 所示:
从图 8.1.4.1 中可以看出,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 也是个寄存器,寄存器地址为 0X020E02E8。这也是个 32 位寄存器,但是只用到了其中的低 17 位,在看这写位的具体含义之前,先来看一下图 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 所示:
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 所示:
DSE(bit5:3):对应图 8.1.4.2 中的 DSE,当 IO 用作输出的时候用来设置 IO 的驱动能力,总共有 8 个可选选项,如表 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 的默认电平如何设置等等,所以我们接着继续看。
注意,以上设置IO复用和IO配置,都是IO的能力,并不是GPIO的能力,注意区分。GPIO只是IO复用功能之一。
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 参考手册》的第 28 章“Chapter 28 General Purpose Input/Ouput(GPIO)”,GPIO 结构如图 8.1.5.1 所示:
在 图 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 所示:
此寄存器是 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 所示:
GDIR 寄存器也是 32 位的,此寄存器用来设置某个 IO 的工作方向,是输入还是输出。同样的,每个 IO 对应一个位,如果要设置 GPIO 为输入的话就设置相应的位为 0,如果要设置为输出的话就设置为 1。比如要设置 GPIO1_IO00 为输入,那么 GPIO1.GDIR=0;
接下来看 PSR 寄存器,这是 GPIO 状态寄存器,如图 8.1.5.4 所示:
同样的 PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。功能和输入状态下的 DR 寄存器一样。
接下来看ICR1和ICR2这两个寄存器,都是中断控制寄存器,ICR1用于配置低16个GPIO,ICR2 用于配置高 16 个 GPIO,ICR1 寄存器如图 8.1.5.5 所示:
寄存器ICR1 用于 IO0~15 的配置, 寄存器ICR2 用于 IO16~31 的配置。ICR1 寄存器中一个 GPIO 用两个位,这两个位用来配置中断的触发方式,和 STM32 的中断很类似,可配置的选线如表 8.1.5.1所示:
以GPIO1_IO15为例,如果要设置GPIO1_IO15为上升沿触发中断,那么GPIO1.ICR1=2<<30,如果要设置 GPIO1 的 IO16~31 的话就需要设置 ICR2 寄存器了。
接下来看 IMR 寄存器,这是中断屏蔽寄存器,如图 8.1.5.6 所示:
IMR 寄存器也是一个 GPIO 对应一个位,IMR 寄存器用来控制 GPIO 的中断禁止和使能,如果使能某个 GPIO 的中断,那么设置相应的位为 1 即可,反之,如果要禁止中断,那么就设置相应的位为 0 即可。例如,要使能 GPIO1_IO00 的中断,那么就可以设置 GPIO1.MIR=1 即可。
接下来看寄存器 ISR,ISR 是中断状态寄存器,寄存器如图 8.1.5.7 所示:
ISR 寄存器也是 32 位寄存器,一个 GPIO 对应一个位,只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置 1。所以,我们可以通过读取 ISR 寄存器来判断 GPIO 中断是否发生,相当于 ISR 中的这些位就是中断标志位。当我们处理完中断以后,必须清除中断标志位,清除方法就是向 ISR 中相应的位写 1,也就是写 1 清零。
最后来看一下 EDGE_SEL 寄存器,这是边沿选择寄存器,寄存器如图 8.1.5.8 所示:
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 对应的电平。
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 所示:
CCM_CCGR0 是个 32 位寄存器,其中每 2 位控制一个外设的时钟,比如 bit31:30 控制着GPIO2 的外设时钟,两个位就有 4 种操作方式,如表 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,设置输入/输出、是否使用中断、默认输出电平等。
硬件原理分析
打开 I.MX6U-ALPHA 开发板底板原理图,底板原理图和核心板原理图都放到了开发板光盘中,路径为: 开发板光盘->2、开发板原理图->IMX6UL_ALPHA_V1.0(底板原理图)。I.MX6UALPHA 开发板上有一个 LED 灯,原理图如下 8.2.1 所示:
从图 8.2.1 可以看出,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0 就亮,输出 1 就灭。
汇编控制LED实验程序编写
按照 上面内容中讲的,我们需要对 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 组寄存器地址,在《IMX6ULL 参考手册》的 1357 页,如图 8.3.1 所示:
本实验中 GPIO1_IO03 是作为输出功能的,因此 GPIO1_GDIR 的 bit3 要设置为 1,表示输
出。
经过前面几步,GPIO1_IO03 已经配置好了,只需要向 GPIO1_DR 寄存器的 bit3 写入 0 即可控制 GPIO1_IO03 输出低电平,打开 LED,向 bit3 写入 1 可控制 GPIO1_IO03 输出高电平,关闭 LED。
本实验完整工程在开发板光盘中,路径为:开发板光盘-> 1、例程源码-> 1、裸机例程-> 1_leds,如果要打开这个工程的话一定要将“1_leds”整个文件夹复制到一个没有中文路径的目录中,否则直接打开工程可能会报错。
所有的裸机实验我们都在 Ubuntu 下完成,使用 VSCode 编辑器!
既然是实验,肯定要自己动手创建工程,直接在vscode中新建一个名为“1_leds”的文件夹,然后在“1_leds”这个目录下新建一个名为“led.s”的汇编文件,然后在 led.s 这个文件中编写汇编程序。
在 led.s 中输入如下代码:
待补充。
我们来详细的分析一下上面的汇编代码,我们以后分析代码都根据行号来分析。
第 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 函数执行,进入一个死循环。
Ubuntu 交叉编译工具链安装
交叉编译器安装
ARM 裸机、Uboot 移植、Linux 移植这些都需要在 Ubuntu 下进行编译,编译就需要编译器,我们在第三章“Linux C 编程入门”里面已经讲解了如何在 Liux 进行 C 语言开发,里面使用 GCC 编译器进行代码编译,但是 Ubuntu 自带的 gcc 编译器是针对 X86 架构的!而我们现在要编译的是 ARM 架构的代码,所以我们需要一个在 X86 架构的 PC 上运行,可以编译 ARM 架构代码的 GCC 编译器,这个编译器就叫做交叉编译器,总结一下交叉编译器就是:
1、它肯定是一个 GCC 编译器。
2、这个 GCC 编译器是运行在 X86 架构的 PC 上的。
3、这个 GCC 编译器是编译 ARM 架构代码的,也就是编译出来的可执行文件是在 ARM 芯片上运行的。
交叉编译器中“交叉”的意思就是在一个架构上编译另外一个架构的代码,相当于两种架构“交叉”起来了。交叉编译器有很多种,我们使用 Linaro 出品的交叉编译器,Linaro 是一间非营利性质的开放源代码软件工程公司,Linaro 开发了很多软件,最著名的就是 Linaro GCC 编译工具链(编译器),关于 Linaro 详细的介绍可以到 Linaro 官网查阅。Linaro GCC 编译器下载地址如下:
打开以后下载界面如图 4.3.1.1 所示:
在图 4.3.1.1 中有很多种 GCC 交叉编译工具链,因为我们所使用的 I.MX6U-ALPHA 开发板是一个 Cortex-A7 内核的开发板,因此选择 arm-linux-gnueabihf,点击后面的“Binaries”进入可执行文件下载界面,如图 4.3.1.2 所示:
在写本教程的时候最新的编译器版本是 7.3.1,但是笔者在测试 7.3.1 版本编译器的时候发现编译完成后的 uboot 无法运行。所以这里不推荐使用最新版的编译器。笔者测试过 4.9 版本的编 译 器 可 以 正 常 工 作 , 所 以 我 们 需 要 下 载 4.9 版 本 的 编 译 器 , 下 载 地 址 为 :
如 图
图 4.3.1.3 中有很多种交叉编译器,我们只需要关注这两种:
gcc-linaro-4.9.4-2017.01-i686_arm-linux-gnueabihf.tar.tar.xz
和
gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
第一个是针对 32 位系统的,第二个是针对 64 位系统的。大家根据自己所使用的 Ubuntu 系统类型选择合适的版本,比如我安装的 Ubuntu 16.04 是 64 位系统,因此我要使用
gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
这两种交叉编译器我们已经下载好放到了开发板光盘中,路径:5、开发工具->1、交叉编译器。我们要先将交叉编译工具拷贝到 Ubuntu 中,在 4.2.1 小节中我们在当前用户根目录下创建了一个名为“linux”的文件夹,在这个 linux 文件夹里面再创建一个名为“tool”的文件夹,用来存放一些开发工具。使用前面已经安装好的 FileZilla 将交叉编译器拷贝到 Ubuntu 中刚刚新建的“tool”文件夹中,操作如图 4.3.1.4 所示:
拷贝完成的话 FileZilla 会有提示,如图 4.3.1.5:
在 Ubuntu 中创建目录:/usr/local/arm,命令如下:
sudo mkdir /usr/local/arm
创建完成以后将刚刚拷贝的交叉编译器复制到/usr/local/arm 这个目录中,在终端使用命令“ cd” 进入 到 存放 有交 叉编 译 器的 目录 , 比如 我 前面 将交 叉 编译 器拷 贝 到了 目录“/home/zuozhongkai/linux/tool”中,然后使用如下命令将交叉编译器复制到/usr/local/arm 中:
sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f
操作步骤如图 4.3.1.6 所示:
拷贝完成以后在/usr/local/arm 目录中对交叉编译工具进行解压,解压命令如下:
sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
等待解压完成,解压完成以后会生成一个名为“gcc-linaro-4.9.4-2017.01-x86_64_arm-linux
-gnueabihf”的文件夹,这个文件夹里面就是我们的交叉编译工具链。
修改环境变量,使用 VI 打开/etc/profile 文件,命令如下:
sudo vi /etc/profile
打开/etc/profile 以后,在最后面输入如下所示内容:
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
添加完成以后的/etc/profile 如图 4.3.1.7 所示:
修改好以后就保存退出,重启 Ubuntu 系统,交叉编译工具链(编译器)就安装成功了。
安装相关库
在使用交叉编译器之前还需要安装一下其它的库,命令如下:
sudo apt-get install lsb-core lib32stdc++6
等待这些库安装完成。
交叉编译器验证
首先查看一下交叉编译工具的版本号,输入如下命令:
arm-linux-gnueabihf-gcc -v
如果交叉编译器安装正确的话就会显示版本号,如图 4.3.3.1 所示:
从图 4.3.3.1 中可以看出当前交叉编译器的版本号为 4.9.4,说明交叉编译工具链安装成功。第三章“Linux C 编程入门”中使用 Ubuntu 自带的 GCC 编译器,我们用的是命令“gcc”。要使用刚刚安装的交叉编译器的时候使用的命令是“arm-linux-gnueabihf-gcc”,“arm-linux-gnueabihf-gcc”的含义如下:
1、arm 表示这是编译 arm 架构代码的编译器。
2、linux 表示运行在 linux 环境下。
3、gnueabihf 表示嵌入式二进制接口。
4、gcc 表示是 gcc 工具。
最好的验证方法就是直接编译一个例程,我们就编译第一个裸机例程“1_leds”试试。
编译下载验证
如果你是在 Windows 下使用 Source Insight 编写的代码,就需要通过 FileZilla 将编写好的代码发送的 Ubuntu 中去编译,FileZilla 的使用参考 4.1 小节。因为我们现在是直接在 Ubuntu 下使用 VSCode 编译的代码,所以不需要使用 FileZilla 将代码发送到 Ubuntu 下,可以直接进行编译,在编译之前我们先了解几个编译工具。
编译链接工具
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 文件并不是我们可以下载到开发板中运行的文件,一个工程中所有的 C文件和汇编文件都会编译生成一个对应的.o 文件,我们需要将这.o 文件链接起来组合成可执行文件。
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 中的这些.o 文件肯定会被 MDK 链接到某个地址去,如果使用 MDK 开发 STM32的话肯定对图 8.4.1.3 所示界面很熟悉:
图 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 文件就详细的描述了各个.o 文件都是链接到了什么地址,如图 8.4.1.5所示:
从图 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,这是因为stm32是直接在flash中运行程序的。但是linux不一样,程序存在flash里,然后要加载到内存中去运行。
本教程所有的裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,这个链接地址可以在 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 所示:
led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还需要将 led.elf 文件转换为.bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具了。
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 所示:
至此我们终于等到了想要的东西—led.bin 文件。
arm-linux-gnueabihf-objdump 反汇编
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件,如图 8.4.1.8 所示:
可以打开 led.dis 文件看一下,看看是不是汇编代码,如图 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 文件了。
创建 Makefile 文件
是用“touch”命令在工程根目录下创建一个名为“Makefile”的文件,如图 8.4.1.12 所示:
创建好 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所示:
如果我们要清理工程的话执行“make clean”即可,如图 8.4.1.14 所示:
至此,有关代码编译、arm-linux-gnueabihf 交叉编译器的使用就到这里了,我们接下来讲解如何将 led.bin 烧写到 SD 卡中。
代码烧写
我们学习 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 所示:
2、给予 imxdownload 可执行权限
我们直接将软件 imxdownload 从 Windows 下复制到 Ubuntu 中以后,imxdownload 默认是没有可执行权限的。我们需要给予 imxdownload 可执行权限,使用命令“chmod”,命令如下:
通过对比图 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 所示:
从图中可以看到当前电脑有/dev/sda、/dev/sda1、/dev/sda2 和/dev/sda5 这 5 个存储设备,使用读卡器将 SD 卡插到电脑,一定要确保 SD 卡是挂载到了 Ubuntu 系统中,而不是 Windows下。SD 卡挂载到电脑以后,VMware 右下角会出现如图 8.4.3.4 所示图标:
如图 8.4.3.4 所示,在 VMware 右小角有个图标看着像硬盘一样的图标: ,这个图标就表示当前有存储设备插入,我们将鼠标放上去就会有提示当前设备名字,比如我这里提示“Realtek USB3.0 Card Reader”,这是我的读卡器的名字。如果是灰色的话就表示 SD 卡挂载到了Windows 下,而不是 Ubuntu 上,所以我现在这个电脑的 SD 卡就是挂载到了 Windiows 下,我肯定要将其挂载到 Ubuntu 中,因为我要在 Ubuntu 下烧写代码。方法很简单,点击图标 ,点击以后如图 8.4.3.5 所示:
点击图 8.4.3.5 中的“连接(断开与主机的连接)(C)”,点击以后会弹出如图 8.4.3.6 所示提示界面:
图 8.4.3.6 提示你有个 USB 要从主机(Windows)拔出,插入虚拟机中,点击“确定”按钮即可。SD 卡插入到 Ubuntu 以后,图标就会变,不是灰色的了。在输入命令“ls/dev/sd*” 来查看当前 Ubuutu 下的存储设备,如图 8.4.3.7 所示:
从图 8.4.3.7 中可以看到,我的电脑多出了/dev/sdb、/dev/sdc、/dev/sdd、/dev/sdd1、/dev/sde 和/dev/sdf 这 6 个存储设备。这是因为我的读卡器是多合一读卡器,所以会多出来这么多,如果你用的单一读卡器那么应该只会出现一个该 SD 卡标号和 n 个分区标号,比如/dev/sdb、/dev/sdb1、 /dev/sdb2 等等。那这 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> <SD Card>
其中.bin 就是要烧写的.bin 文件,SD Card 就是你要烧写的 SD 卡,比如我的电脑使用如下命令烧写 led.bin 到/dev/sdd 中:
./imxdownload led.bin /dev/sdd
不能烧写到/dev/sda 或 sda1 设备里面!那是系统磁盘。
烧写的过程中可能会让你输入密码,输入你的 Ubuntu 密码即可完成烧写,烧写过程如图8.4.3.8 所示:
在图 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 所示:
load.imx 这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容,在 led.bin 文件前面添加了一些数据头以后生成的。最终烧写到 SD 卡里面的就是这个 load.imx 文件,而非led.bin。也就是说,这个工具会先生成.imx文件,然后再烧到sd卡中。
代码验证
代码已经烧写到了 SD 卡中了,接下来就是将 SD 卡插到开发板的 SD 卡槽中,然后设置拨码开关为 SD 卡启动,拨码开关设置如图 8.4.4.1 所示:
设置好以后按一下开发板的复位键,如果代码运行正常的话 LED0 就会被点亮,如图 8.4.4.2所示:
如图 8.4.4.2 所示,LED0 被正常点亮,可能 LED0 之前会有一点微亮,那是因为 I.MX6U的 IO 默认电平可能让 LED0 导通了,但是 IO 的默认配置内部可能有很大的电阻,所以电流就很小,导致 LED0 微亮。但是我们自己编写代码、配置好 IO 以后就不会有这个问题,LED0 就很亮了。
本小节我们详细的讲解了如何编译代码,并且如何将代码烧写进 SD 卡中进行测试。后续我们的所有裸机实验和 Uboot 实验都使用的这种方法进行代码的烧写和测试。