ARM(IMX6U)裸机汇编LED灯实验——驱动编写、编译链接起始地址、烧写bin文件到SD卡中并运行

参考:Linux之ARM(IMX6U)裸机汇编LED驱动实验–驱动编写
作者:一只青木呀
发布时间: 2020-08-07 09:13:48
网址:https://blog.csdn.net/weixin_45309916/article/details/107851318

本章开始编写本教程第一个裸机例程——经典的点灯试验,这也是我们嵌入式Linux 学习的第一步。本章使用汇编语言来编写,通过本章了解如何使用汇编语言来初始化I.MX6U 外设寄存器、了解I.MX6UL 最基本的IO 输出功能。

I.MX6U GPIO 详解

STM32 GPIO 回顾

我们一般拿到一款全新的芯片,第一个要做的事情的就是驱动其GPIO,控制其GPIO 输出高低电平,我们学习I.MX6U 也一样的,先来学习一下I.MX6U 的GPIO。在学习I.MX6U的GPIO 之前,我们先来回顾一下STM32 的GPIO 初始化(如果没有学过STM32 就不用回顾了),我们以最常见的STM32F103 为例来看一下STM32 的GPIO 初始化,示例代码如下:

示例代码8.1.1.1 STM32 GPIO 初始化
1 void 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.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 所示:

在这里插入图片描述

图8.1.2.1 I.MX6U GPIO 命名

从图8.1.2.1 可以看出,I.MX6ULL 的IO 分为两类:SNVS 域的和通用的,这两类IO 本质上都是一样的,我们就有下面的常用IO 为例,讲解一下I.MX6ULL 的IO 命名方式。

图8.1.2.1 中的形如“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”的就是GPIO 命名,命名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是GPIO 命名,比如:GPIO1_IO01、UART1_TX_DATA、JTAG_MOD 等等。I.MX6ULL 的GPIO 并不像STM32一样以PA0~ 15 这样命名,他是根据某个IO 所拥有的功能来命名的。比如我们一看到GPIO1_IO01 就知道这个肯定能做GPIO,看到UART1_TX_DATA 肯定就知道这个IO 肯定能做为UART1 的发送引脚。“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 GPIO1_IO00 复用

从图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 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 能复用什么外设的话可以直接查阅《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 参考手册》第30 章“Chapter 30: 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 寄存器

从图8.1.4.1 中可以看出,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 也是个寄存器,寄存器地址为0X020E02E8。这也是个32 位寄存器,但是只用到了其中的低17 位,在看这写位的具体含义之前,先来看一下图8.1.4.2 所示的GPIO 功能图:

在这里插入图片描述
图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 所示:

在这里插入图片描述
表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 所示:

在这里插入图片描述
表8.1.4.2 速度配置

DSE(bit5:3):对应图8.1.4.2 中的DSE,当IO 用作输出的时候用来设置IO 的驱动能力,总共有8 个可选选项,数值越大电阻越小驱动能力越大,如表8.1.4.3 所示:

在这里插入图片描述
表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 的默认电平如何设置等等,所以我们接着继续看。

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 的工作方向的,即输入/输出,如果是输出就向DR寄存器写数据,如果是输入就去读DR寄存器的值。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所示:

在这里插入图片描述
表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 对应的电平。

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 所示:

在这里插入图片描述

表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.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 就灭。

1. 程序编写

在这里插入图片描述

①、使能时钟

使能时钟。CCGR0–CCGR6这七个寄存器控制着I.MX6ULL所有外设时钟的使能,为了简单,设置CCGR0–CCGR6这七个寄存器全部为0xFFFFFFFF,相当于使能所有的外设时钟(前面汇编章节讲过了,这里的寄存器不是内部寄存器,而是RAM里面的寄存器。CPU不能直接访问RAM,所以通过汇编指令LDR、STR指令进行读写操作RAM里面的寄存器

汇编使能所有的外设时钟:

在这里插入图片描述

CCGR0:
在这里插入图片描述
CCGR1:
在这里插入图片描述
CCGR2:
在这里插入图片描述
CCGR3:
在这里插入图片描述

CCGR4:
在这里插入图片描述
CCGR5:
在这里插入图片描述
CCGR6:
在这里插入图片描述

②、配置 GPIO1_I003 PIN的复用为GPIO

将IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器的bit3-0,设置为0101,这样GPIO_IO03就复用为GPIO
在这里插入图片描述
汇编实现:
在这里插入图片描述

③、配置 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03的电器属性

设置寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03的电器属性,包括摆率,速度,驱动能力,开漏,上下拉等等
在这里插入图片描述
在这里插入图片描述

上表中寄存器每一位的功能作用:
bit0: 压摆率
bit2-1: 保留的,没有用
bit5-3: 驱动能力,数值越大,驱动能力越强
bit7-6: 速度
bit10-8: 保留的,没有用
bit11:开路输出(打开、关闭)
bit12:使能上拉还是保持
bit15-14:上下拉阻值

汇编代码实现:
在这里插入图片描述

用计算器计算二进制转十六进制的计算方法:
在这里插入图片描述

④、配置GPIO功能,设置输入输出模式

设置GPIO1_GDIR寄存器bit3为1(GPIO1_IO03和LED灯连接),也就是设置为输出模式
在这里插入图片描述
在这里插入图片描述
汇编实现:
在这里插入图片描述

⑤、设置GPIO_DR寄存器的bit3,1表示输出高电平,0表示输出低电平

在这里插入图片描述

在这里插入图片描述
汇编实现:
在这里插入图片描述

整个汇编代码(.s文件)

.global _start @全局标号

_start:
    /*使能所有外设时钟 */
    LDR R0 , =0x020c4068  @CCGR0   --》这是汇编的注释格式
    LDR R1 , =0xffffffff  @要想CCGR0写入的数据
    STR R1 , [R0]         @将R1的值写入到R0中
    
    LDR R0 , =0x020c406c  @CCGR1
    STR R1 ,[R0]

    LDR R0 , =0x020c4070  @CCGR1
    STR R1 ,[R0]

    LDR R0 , =0x020c4074  @CCGR1
    STR R1 ,[R0]

    LDR R0 , =0x020c4078  @CCGR1
    STR R1 ,[R0]

    LDR R0 , =0x020c407c  @CCGR1
    STR R1 ,[R0]

    LDR R0 , =0x020c4080  @CCGR1
    STR R1 ,[R0]

    /*配置 GPIO_I003  PIN的复用为GPIO
    * IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0101 =5
    * IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器的地址为0x020E_0068
    */
    LDR R0 , =0x020E0068  @IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
    LDR R1 , =0x5          @要写入的数据
    STR R1 , [R0]          @将5写入IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03

    /*配置 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03的电器属性
    * IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03的地址时0x020E_02F4
    * bit0 :    0 低速率
    * bit5-3:   110 R0/6  驱动能力
    * bit7-6:   10 100MHz速度
    * bit11:    0 关闭开路输出
    * bit12:    1 使能pull/keeper
    * bit15-14: 00 100K下拉
    * bit16:    0 关闭hys    
    * 向寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03写入 0x10b0
    */

    LDR R0 , = 0x020E02F4
    LDR R1 , = 0x10b0
    STR R1 ,[R0]

    /*设置GPIO功能
     *设置GPIO1_GDIR寄存器  设置GPIO1_GPIO03为输出
     *寄存器GPIO_GDIR的地址是  0x0209C004
     * 设置GPIO1_GDIR寄存器bit3为1也就是GPIO1_GPIO03为输出
     */
    LDR R0 , = 0x0209C004
    LDR R1 , = 0x8
    STR R1 ,[R0]

    /*打开LED,也就是设置GPIO1_GPIO03为0 
     *GPIO1_DR 寄存器地址为0x0209C000
    */

    LDR R0 , = 0x0209C000
    LDR R1 , =0
    STR R1 ,[R0]

loop:       @ 给一个死循环 让CPU只执行这些已知的指令
    b loop  @ b 是跳转的意思


2. 程序编译

交叉编译器的准备

我们是要编译出在 ARM 开发板上运行的可执行文件,所以要使用交叉编译器 arm-linux-gnueabihf-gcc 来编译。

交叉编译链的安装参考另外一篇博文:交叉编译链的安装

代码编译过程

本试验就一个 leds.s 源文件,所以编译比较简单。

在这里插入图片描述
I.MX6ULL内部ROM我们用不了,所有的Cortax-A系列芯片内部都没有flash,所以代码无法存到内部flash里面,只能放到RAM里面了。在这里插入图片描述

1.把 .s 文件编译成 .o文件

在这里插入图片描述

arm-linux-gnueabihf-gcc -g -c led.s -o led.o

在这里插入图片描述

其中“-g”选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字,这里我们指定 led.s 编译完成以后的文件名字为 leds.o。执行上述命令以后就会编译生成一个 leds.o 文件

2.把 .o文件链接成.elf格式的可执行文件(链接起始地址)

arm-linux-gnueabihf-ld 用来将众多的.o 文件链接到一个指定的位置(起始地址)。

我们在学习SMT32 的时候基本就没有听过“链接”这个词,我们一般用MDK(即keil软件) 编写好代码,然后点击“编译”,MDK 或者IAR 就会自动帮我们编译好整个工程,最后再点击“下载”就可以将代码下载到开发板中。这是因为链接这个操作MDK 或者IAR 已经帮你做好了,后面我就以MDK 为例给大家讲解。大家可以打开一个STM32 的工程,然后编译一下,肯定能找到很多.o 文件,如图所示:
在这里插入图片描述
图中的这些.o 文件肯定会被MDK 链接到某个地址去,如果使用MDK 开发STM32的话肯定对下图所示界面很熟悉:
在这里插入图片描述
图中左侧的IROM1 我们都知道是设置STM32 芯片的ROM 起始地址和大小的,右边的IRAM1 是设置STM32 芯片的RAM 起始地址和大小的。其中0X08000000 就是STM32 内部ROM (flash)的起始地址,编译出来的指令肯定是要从0X08000000 这个地址开始存放(存储)的。对于STM32 来说0X08000000 就是它的链接地址,图中的这些.o 文件就是这个链接地址开始依次存放,最终生成一个可以下载的hex 或者bin 文件,我们可以打开.map 文件查看一下这些文件的链接地址,在MDK 下打开一个工程的.map 文件方法如下图所示:
在这里插入图片描述
图中的.map 文件就详细的描述了各个.o 文件都是链接到了什么地址,如下图所示:
在这里插入图片描述
从图中就可以看出STM32 的各个.o 文件所处的位置,起始位置是0X08000000(上面keil截图里设置好的)。由此可以得知,我们用MDK 开发STM32 的时候也是有链接的,只是这些工作MDK 都帮我们全部做好了,我们不用关心而已。但是我们在Linux 下用交叉编译器开发ARM 的是时候就需要自己处理这些了。

代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。比如 I.MX6U 支持 SD 卡、EMMC、NAND 启动,因此代码可以存储到 SD 卡、EMMC 或者 NAND 中,但是要运行的话就必须将代码从 SD 卡、EMMC 或者NAND 中拷贝到其运行地址(链接地址)处,“存储地址”和“运行地址”可以一样。

裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,这个链接地址可以在 I.MX6U 的内部 128KB RAM 中(0X900000~0X91FFFF),也可以在外部的 DDR中。

我们把链接地址都放在 DDR中(因为内部RAM太小了),链接起始地址为 0X87800000I.MX6U-ALPHA 开发板的DDR 容量有两种:512MB 和256MB,起始地址都为 0X80000000,只不过 512MB 的终止地址为 0X9FFFFFFF,而 256MB 容量的终止地址为 0X8FFFFFFF之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址也是 0X87800000,这样我们统一使0X87800000 这个链接地址,不容易记混

链接起始地址就是代码运行的起始地址,有的时候也是保存代码的起始地址。

要使用DDR,必须要初始化DDR,对于I.MX来说bin文件不能直接运行,需要添加一个头部,这个头部信息包含了DDR的初始化参数,I.MX系列SOC内部boot room会从SD卡,EMMC等外置存储中读取头部信息,然后初始化DDR,并且将bin文件拷贝到指定的地方。
Bin的运行地址一定要和链接起始地址一致。位置无关代码除外。

确定了链接地址以后就可以使用 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 文件

在这里插入图片描述

led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,还需要将 led.elf 文件转换为.bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具了。

3.arm-linux-gnueabihf-objcopy 格式转换将.elf转换成bin文件

arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为led.bin 文件,命令如下:

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。上述命令执行完成以后,工程目录如图

在这里插入图片描述
至此我们终于等到了想要的东西-— led.bin文件。

4.arm-linux-gnueabihf-objdump (对elf 文件进行)反汇编

大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:

arm-linux-gnueabihf-objdump -D led.elf > led.dis

上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件
在这里插入图片描述
在这里插入图片描述
可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出到我们的代码已经链接到了以 0X87800000 为起始地址的区域。

5.整理成makefile文件

上面的这些命令一条条的敲太麻烦啦!而且这些代码有问题的话还要回去一直改代码来回编译很麻烦。我们可以把它整理成一个Makefile文件,一次性完成。

是用“touch”命令在工程根目录下创建一个名为“Makefile”的文件,如图

在这里插入图片描述

创建好 Makefile 文件以后就需要根据 Makefile 语法编写 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 命令前一定要是tab键形成的四个空格

led.bin是目标
led.s是依赖的文件
clean清理生成的所有文件

创建好 Makefile 以后我们就只需要执行一次“make”命令即可完成编译

在这里插入图片描述

3. 烧写bin文件到SD卡中并运行

STM32烧写到内部FLASH。
6ULL支持SD卡、EMMC、NAND、NOR、SPI FLASH等启动。裸机例程我们选择把bin文件烧写到SD卡(底板上有TF卡槽)里面,烧录完插到板子上选择从SD卡启动这样比较方便,因为EMMC、NAND是焊在板子上的,虽然官方有烧录工具,没有SD卡方便。

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 卡中,否则代码是绝对运行不起来的。

而且对于I.MX而言,不能直接烧写bin文件,比如要先在bin文件前面添加头部(下一讲会讲到)。而完成这个工作,需要使用正点原子提供的imxdownload软件。

1.烧写的工具

正点原子提供的imxdownload源代码

在这里插入图片描述
imxdownload.c

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "imxdownload.h"

#define SHELLCMD_LEN	(200)
#define BIN_OFFSET		(3072)

/* 此宏指明是否打印u-boot.imx的IVT DCD表信息,不同的开发板其IVT和DCD
 * 表的数据是不同的,因此需要获取所使用的开发板的IVT和DCD表信息,最
 * 简单的方法就是读取开发板配套资料里面的u-boot.imx的前1KB数据,理论上
 * 应该读取3KB的数据,但是表信息远远没有3K这么多,因此读1KB即可 
 */
#define PRINT_TAB		0	
/*
 * 介绍: 此软件是针对NXP的IMX6U系列芯片的,软件用来烧写bin文件到SD卡里面,
 *        本软件会自动添加IVT、DCD等信息到原始的bin文件里面,主要用于裸机和uboot的烧写。
 * 使用方法: 1、编译好原始的二进制bin文件,如,u-boot.bin等,并将编译好的.bin文件和本
 *             软件放置到同一个目录下!!!!
 *        	2、执行命令sudo ./imxdownload <soucre_bin> <sd_device>
 *             如烧写u-boot.bin到/dev/sdd中即可使用如下所示命令:
 *             sudo ./imxdownload u-boot.bin /dev/sdd
 */

/*
 * 输出一些信息
 */
void message_print(void)
{	
	printf("I.MX6ULL bin download software\r\n");
	printf("Edit by:zuozhongkai\r\n");
	printf("Date:2019/6/10\r\n");
	printf("Version:V1.1\r\n");
	printf("log:V1.0 initial version,just support 512MB DDR3\r\n");
	printf("    V1.1 and support 256MB DDR3\r\n");
}

int main(int argc, char *argv[])
{
	FILE *fp;
	unsigned char *buf;
	unsigned char *cmdbuf;
	int nbytes, filelen;
	int i = 0, j = 0;
	int ddrsize = 0; /* 0为512MB,1为256MB,2为128MB...... */

	message_print();

	if((argc != 3) && (argc != 4)){
		printf("Error Usage! Reference Below:\r\n");
		printf("sudo ./%s <-512m or -256m> <source_bin> <sd_device>\r\n", argv[0]);
		return -1;
	}

	/* 查找参数,获取DDR容量 */
	for(i = 0; i < argc; i++)
	{
		char *param = argv[i];
		if(param[0] != '-')
			continue;
		if(strcmp(param, "-256m") == 0) 		/* 256MB */
			ddrsize = 1;
		else if(strcmp(param, "-512m") == 0)	/* 512MB */
			ddrsize = 0;
	}
	if(argc == 3)	/* 三个参数,也就是不输入DDR容量的话默认为512MB */
		ddrsize = 0;

	/* 打开bin文件 */
	fp = fopen(argv[1], "rb"); /* 以二进制只读方式打开bin文件 */
	if(fp == NULL){
		printf("Can't Open file %s\r\n", argv[1]);
		return -1;
	}
	
	/* 获取bin文件长度 */
	fseek(fp, 0L, SEEK_END);
	filelen = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	printf("file %s size = %dBytes\r\n", argv[1], filelen);
	
	/* 读取bin文件到缓冲区buf中 */
	buf = malloc(filelen + BIN_OFFSET);
	if(buf == NULL){
		printf("Mem Malloc Failed!\r\n");
		fclose(fp);
		return -1;
	}
	memset(buf, 0, filelen + BIN_OFFSET); /* 清零 */
	/* 读取bin源码文件 */
	fread(buf + BIN_OFFSET, 1, filelen, fp);

	/* 关闭文件 */
	fclose(fp);

#if PRINT_TAB
	printf("IVT DCD Table:\r\n");
	for(i = 0; i < 1024/32; i++){
		for(j = 0; j < 8; j++)
		{
			printf("0X%08X,",*(int *)(buf + BIN_OFFSET + (((i * 8) + j) * 4)));
		}
		printf("\r\n");
	}	
	free(buf);
	return 0;
#endif
	
	/* 添加IVT DCD等表信息到bin文件里面 */
	if(ddrsize == 0) {		/* 512MB */
		printf("Board DDR SIZE: 512MB\r\n");
		memcpy(buf, imx6_512mb_ivtdcd_table, sizeof(imx6_512mb_ivtdcd_table));
	}
	else if (ddrsize == 1) {	/* 256MB */
		printf("Board DDR SIZE: 256MB\r\n");
		memcpy(buf, imx6_256mb_ivtdcd_table, sizeof(imx6_256mb_ivtdcd_table));
	}

	/* 现在我们已经在buf中构建好了可以用于下载的bin文件,将buf中的数据保存到
	 * 到一个文件中,文件命名为load.imx
	 */
	printf("Delete Old load.imx\r\n");
	system("rm -rf load.imx");		/* 先删除旧的load.imx文件	*/
	
	printf("Create New load.imx\r\n");
	system("touch load.imx");		/* 创建新的load.imx文件		*/
	fp = fopen("load.imx", "wb");	/* 打开laod.imx				*/
	if(fp == NULL){
		printf("Cant't Open load.imx!!!\r\n");
		free(buf);
		return -1;
	}
	nbytes = fwrite(buf, 1, filelen + BIN_OFFSET, fp);
	if(nbytes != (filelen + BIN_OFFSET)){
		printf("File Write Error!\r\n");
		free(buf);
		fclose(fp);
		return -1;
	}
	free(buf);
	fclose(fp);	
	
	/* 构建烧写的shell命令 */
	cmdbuf = malloc(SHELLCMD_LEN);
	sprintf(cmdbuf, "sudo dd iflag=dsync oflag=dsync if=load.imx of=%s bs=512 seek=2",argv[2]);	
	printf("Download load.imx to %s  ......\r\n", argv[2]);
	
	/* 执行上面的shell命令 */
	system(cmdbuf);
	free(cmdbuf);
	return 0;	
}

imxdownload.h

#ifndef _IMXDOWNLOAD_H
#define _IMXDOWNLOAD_H
/* IMX6U IVT DCD表信息  暂时定义为1K Bytes,此表是读取的u-boot.imx前1K Bytes
 * imx6_ivedcd_table[9]是指明代码长度的,本应该根据实际的代码长度来修改
 * 这里为了方便,就直接定义为2M Bytes,即
 */

const int imx6_512mb_ivtdcd_table[256] = {
0X402000D1,0X87800000,0X00000000,0X877FF42C,0X877FF420,0X877FF400,0X00000000,0X00000000,
0X877FF000,0X00200000,0X00000000,0X40E801D2,0X04E401CC,0X68400C02,0XFFFFFFFF,0X6C400C02,
0XFFFFFFFF,0X70400C02,0XFFFFFFFF,0X74400C02,0XFFFFFFFF,0X78400C02,0XFFFFFFFF,0X7C400C02,
0XFFFFFFFF,0X80400C02,0XFFFFFFFF,0XB4040E02,0X00000C00,0XAC040E02,0X00000000,0X7C020E02,
0X30000000,0X50020E02,0X30000000,0X4C020E02,0X30000000,0X90040E02,0X30000000,0X88020E02,
0X30000C00,0X70020E02,0X00000000,0X60020E02,0X30000000,0X64020E02,0X30000000,0XA0040E02,
0X30000000,0X94040E02,0X00000200,0X80020E02,0X30000000,0X84020E02,0X30000000,0XB0040E02,
0X00000200,0X98040E02,0X30000000,0XA4040E02,0X30000000,0X44020E02,0X30000000,0X48020E02,
0X30000000,0X1C001B02,0X00800000,0X00081B02,0X030039A1,0X0C081B02,0X0B000300,0X3C081B02,
0X44014801,0X48081B02,0X302C4040,0X50081B02,0X343E4040,0X1C081B02,0X33333333,0X20081B02,
0X33333333,0X2C081B02,0X333333F3,0X30081B02,0X333333F3,0XC0081B02,0X09409400,0XB8081B02,
0X00080000,0X04001B02,0X2D000200,0X08001B02,0X3030331B,0X0C001B02,0XF3526B67,0X10001B02,
0X630B6DB6,0X14001B02,0XDB00FF01,0X18001B02,0X40172000,0X1C001B02,0X00800000,0X2C001B02,
0XD2260000,0X30001B02,0X23106B00,0X40001B02,0X4F000000,0X00001B02,0X00001884,0X90081B02,
0X00004000,0X1C001B02,0X32800002,0X1C001B02,0X33800000,0X1C001B02,0X31800400,0X1C001B02,
0X30802015,0X1C001B02,0X40800004,0X20001B02,0X00080000,0X18081B02,0X27020000,0X04001B02,
0X2D550200,0X04041B02,0X06100100,0X1C001B02,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000
};

const int imx6_256mb_ivtdcd_table[256] = {
0X402000D1,0X87800000,0X00000000,0X877FF42C,0X877FF420,0X877FF400,0X00000000,0X00000000,
0X877FF000,0X00076000,0X00000000,0X40E801D2,0X04E401CC,0X68400C02,0XFFFFFFFF,0X6C400C02,
0XFFFFFFFF,0X70400C02,0XFFFFFFFF,0X74400C02,0XFFFFFFFF,0X78400C02,0XFFFFFFFF,0X7C400C02,
0XFFFFFFFF,0X80400C02,0XFFFFFFFF,0XB4040E02,0X00000C00,0XAC040E02,0X00000000,0X7C020E02,
0X30000000,0X50020E02,0X30000000,0X4C020E02,0X30000000,0X90040E02,0X30000000,0X88020E02,
0X30000C00,0X70020E02,0X00000000,0X60020E02,0X30000000,0X64020E02,0X30000000,0XA0040E02,
0X30000000,0X94040E02,0X00000200,0X80020E02,0X30000000,0X84020E02,0X30000000,0XB0040E02,
0X00000200,0X98040E02,0X30000000,0XA4040E02,0X30000000,0X44020E02,0X30000000,0X48020E02,
0X30000000,0X1C001B02,0X00800000,0X00081B02,0X030039A1,0X0C081B02,0X04000000,0X3C081B02,
0X3C013C01,0X48081B02,0X38324040,0X50081B02,0X28304040,0X1C081B02,0X33333333,0X20081B02,
0X33333333,0X2C081B02,0X333333F3,0X30081B02,0X333333F3,0XC0081B02,0X09409400,0XB8081B02,
0X00080000,0X04001B02,0X2D000200,0X08001B02,0X3030331B,0X0C001B02,0XF352433F,0X10001B02,
0X630B6DB6,0X14001B02,0XDB00FF01,0X18001B02,0X40172000,0X1C001B02,0X00800000,0X2C001B02,
0XD2260000,0X30001B02,0X23104300,0X40001B02,0X47000000,0X00001B02,0X00001883,0X90081B02,
0X00004000,0X1C001B02,0X32800002,0X1C001B02,0X33800000,0X1C001B02,0X31800400,0X1C001B02,
0X30802015,0X1C001B02,0X40800004,0X20001B02,0X00080000,0X18081B02,0X27020000,0X04001B02,
0X2D550200,0X04041B02,0X06100100,0X1C001B02,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
};

#endif

2.将 imxdownload 放在工程目录下(led.bin同一个文件夹下)

在这里插入图片描述

3.imxdownload 可执行权限

chmod   777  imxdownload

在这里插入图片描述

4.确定要烧写的 SD 卡。

准备一张新的 SD(TF)卡,确保 SD 卡里面没有数据,因为我们在烧写代码的时候可能会格式化 SD 卡,入下图所示:

在这里插入图片描述

Ubuntu 下所有的设备文件都在目录“/dev”里面,所以插上 SD 卡以后也会出现在“/dev”里面,其中存储设备都是以“/dev/sd”开头的。我们要先看一下不插 SD 卡的时候电脑都有哪些存储设备,以防插入 SD 卡以后分不清谁是谁。输入如下所示命令:

ls  /dev/sd*

在这里插入图片描述
从图中可以看到当前电脑有/dev/sda、/dev/sda1

使用读卡器将 SD 卡插到电脑,一定要确保 SD 卡是挂载到了 Ubuntu 系统中,而不是 Windows下。SD 卡挂载到电脑以后,VMware 桌面会出现boot图标(Ubuntu18.04)

在这里插入图片描述
再次查看:

ls  /dev/sd*

在这里插入图片描述
这里我们的Ubuntu多出了dev/sdb和/dev/sdb1,/dev/sdb 是我的 SD 卡,/dev/sdb1 是 SD 卡的第一个分区,我们把bin文件烧写到/dev/sdb1里面

确定好SD 卡以后我们就可以使用软件 imxdownload 向 SD 卡烧写 led.bin 文件了。

5.向 SD 卡烧写 bin 文件

使用 imxdownload 向 SD 卡烧写 led.bin 文件,命令格式如下:

./imxdownload <.bin file> <SD Card>

其中.bin 就是要烧写的.bin 文件,SD Card 就是你要烧写的 SD 卡,比如我的电脑使用如下命令烧写 led.bin 到/dev/sdb1 中:

./imxdownload leds.bin /dev/sdb1

在这里插入图片描述
烧写的最后一行会显示烧写大小、用时和速度,比如 led.bin 烧写到 SD 卡中的大小是 3.2KB,用时 0.0160821s,烧写速度是 201KB/s。注意这个烧写速度,如果这个烧写速度在几百 KB/s 以下那么就是正常烧写。

如果这个烧写速度大于几十 MB/s、甚至几百 MB/s 那么肯定是烧写失败了!

解决方法就是重新插拔 SD 卡,一般出现这种情况,重新插拔 SD 卡基本没啥用,只有重启Ubuntu,至于原因,我也不清楚。

烧写完成以后会在当前工程目录下生成一个 load.imx 的文件,如图

在这里插入图片描述
load.imx 这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容,在 leds.bin 文件前面添加了一些数据头以后生成的最终烧写到 SD 卡里面的就是这个 load.imx 文件,而非leds.bin

6.代码验证

代码已经烧写到了 SD 卡中了,接下来就是将 SD 卡插到开发板的 SD 卡槽中,然后设置拨码开关为 SD 卡启动,拨码开关设置如图:

在这里插入图片描述

设置好以后按一下开发板的复位键,如果代码运行正常的话 LED0 就会被点亮,如图:

在这里插入图片描述

LED0 被正常点亮,可能 LED0 之前会有一点微亮,那是因为 I.MX6U的 IO 默认电平可能让 LED0 导通了,但是 IO 的默认配置内部可能有很大的电阻,所以电流就很小,导致 LED0 微亮。当裸机点灯例程运行起来、配置好 IO 以后就不会有这个问题,LED0 就很亮了,这个过程大概两秒时间。

这两秒钟I.MX6ULL内部做了什么操作?
1、主芯片(底板)启动后内部bootroom运行,检测拨码开关设置的是从SD卡启动代码;
2、从SD卡读代码,读取到SD卡裸机例程代码内部头信息去配置外设,比如DDR;
3、链接脚本,内部bootroom把bin文件拷贝到DDR的0x87800000地址里。内部bootroom怎么知道链接脚本?是因为bin文件头部包含了文件的大小、拷到哪里去、要初始化的外设等信息(下一讲介绍);
4、最终运行,所以有2秒延时灯才亮。

其他

在这里插入图片描述
关于上述第三点:
并不是正点原子画的板子有问题,恩智浦自己官方的开发板也不行,官方开发板手册上是这么介绍的,8960和JTAG连接的几根线中间有个0欧电阻,如果想使用JTAG必须把板子上的0欧贴片电阻拆了,很麻烦。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值