目录
[一.引脚复用功能+引脚电气属性(IOMUXC 32)](#一.引脚复用功能+引脚电气属性(IOMUXC 32))
1.IOMUXC_SW_MUX_CTL_PAD:引脚复用功能
[二.GPIO模块(GPIO 28)](#二.GPIO模块(GPIO 28))
[三.时钟模块(CCM 18)](#三.时钟模块(CCM 18))
背景介绍
本文开始在VScode上使用IMX6ULL以真机进行学习,注意,创建的.S文件在本篇学习时注意大写,在编译时会进行部分预处理操作,小写则.s文件会跳过预处理阶段。
芯片原理图参考如下。



其中:
-
SOC IMX6ULL NXP(核心处理器) :这是整块板子的"大脑"。NXP(恩智浦)的 IMX6ULL 基于 ARM Cortex-A7 架构,主频通常在 528MHz 或 800MHz。
-
DDR3 512MB(运行内存 / RAM) :相当于电脑的内存条 。它是掉电丢失的。当 Linux 系统启动时,内核(kernel)会被加载到这块 DDR3 里运行,它负责临时存放程序运行时产生的大量数据。
-
EMMC nand flash 8GB(存储 / ROM) :相当于电脑的硬盘。它是掉电不丢失的。用来永久存放系统镜像(uboot、kernel、设备树、rootfs文件系统)和用户程序。
-
蜂鸣器贴纸有"+"号,表示有源蜂鸣器,上电即可工作
-
左下角BOOT拨码开关设置启动模式,将1,7位拉高则开机。
底板原理图
其中两列引脚通过板子的两列白色插座接入核心板,故核心板自然会将插座的引脚接入SOC内部

该芯片GPIO分为5组:GPIO1、GPIO2、GPIO3、GPIO4、GPIO5.若为图中LED0所在的GPIO 3则表示默认为GPIO1组(注意中间有空格)

其中,DCDC表示直流供电,LED1是物理设备编号,LED0则是引脚工作名称,由原理图可知被接入到GPIO1组的03号引脚,显然右边接入高电平,控制LED0输出低电平则灯亮,故本文后续点灯操作将围绕此展开。
一.引脚复用功能+引脚电气属性(IOMUXC 32)
点灯第一步,先配置引脚复用功能以及电气属性
**1.IOMUXC_SW_MUX_CTL_PAD:**引脚复用功能

SION为控制输入输出功能,而当采用GPIO功能时,输入输出默认打开,而当采用其他七种模式时默认关闭;MUX_MODE为引脚复用的功能,易知,共有八种模式,而该引脚默认为0101,即GPIO1_IO03功能
2.IOMUXC_SW_PAD_CTL_PAD:电气属性

HYS:仅输入功能使用,用于过滤波形,整形。(点灯为输出功能,故暂不使用)
PUS:仅输入可用,控制上拉下拉电阻。
PUE:仅输入可用,控制引脚使用上拉还是保持电压
PKE:打开PUE的开关,为1表示PUE上拉保持功能开
ODE:控制输出模式,默认为0,即推挽输出。
漏级开漏输出,引脚是高阻态,可以输出低电平,但无法输出高电平,除非外部接入上拉电阻;
推挽输出:驱动能力强,既可输出高电平,也可输出低电平

SPEED:控制输出速度,合适即可,高速EMI电磁干扰会增加功耗
DSE:驱动能力,表示驱动多强的外部设备,电阻越小,电流越大,驱动变强,但同时功耗变大,芯片发烫。(给高电平有效,对点灯无用,默认即可)
SRE:从低电平变为高电平需要时间,压摆率越高曲线越陡(适用高速设备,USB,CAN,网口)

对于点灯而言,电气属性设置为默认值即可,由图易知,默认值0X10B0,配置之后该引脚则为GPIO功能。
3.代码
cpp
_led_init:
// 配置引脚复用功能:GPIO1_IO03
ldr r0, =0x020E0068
mov r1, #0x05
str r1, [r0]
// 配置引脚电气属性:默认属性(0x10B0)
ldr r0, =0x020E02F4
ldr r1, =0x10B0
str r1, [r0]
二.GPIO模块(GPIO 28)
1.DR

GPIOx_DR:控制第几组的GPIO引脚
DR:控制引脚高低电平,例如,本篇要点亮的LED灯为GPIO1组的IO03引脚,则让bit3置0,灯亮
2.GDIR

GDIR(GPIO direction register):GPIO方向寄存器,即控制输入/输出模式,对应LED,GPIO_03显然该寄存器应置1
3.PSR

PSR:只读,读取引脚电平,(1为高,0为低电平),同时DR也可读
4.ICR与EDGE

ICR1:配置引脚中断方式的寄存器,从0-31,一共32位,每两位控制一个引脚的中断触发方式,寄存器编号即引脚编号,显然要配置GPIO03号引脚则应配置6.7位.对应具体设置位依次为低电平触发,高电平触发,上升沿触发,下降沿触发。
ICR1为0-15位引脚,ICP2为16-31位引脚。

ICR2原理同上,此外,EDGE寄存器则可控制为双边沿触发

5.IMR

IMR:控制中断是否屏蔽,显然要将LED灯对应的bit3为置1,表示开中断
6.ISR

ISR:查询引脚是否产生中断,1表示产生中断
7.总结
GPIOx_DR:向GPIOx组某个引脚写0或1(低电平/高电平)
GPIOx_GDIR:设置GPIOx组某个引脚方向(输入0和输出1)
GPIOx_PSR(只读):读取GPIOx组某个引脚的状态
GPIOxICR:设置GPIOx组某个引脚中断触发方式(低电平、高电平、下降沿、上升沿)
GPIOx_IMR:设置GPIOx组某个引脚中断屏蔽/允许
GPIOx_ISR:用来查询GPIOx组某个引脚是否产生中断
GPIOx_EDGE_SEL:设置中断触发方式为双边沿触发
以上则为GPIO模块的相关寄存器解析,对于点灯功能而言无需中断,此处了解即可,后续开发将会详细介绍。点灯仅需设置GPIOx_DR与GPIOx_GDIR寄存器,将引脚置低电平且设置为输出方向。


8.代码
cpp
_led_init:
// 配置引脚方向(GDIR):输出模式
ldr r0, =0x0209C004
ldr r1, [r0]
orr r1, r1, #(1 << 3)
str r1, [r0]
bx lr
_led_on:
//给DR bit3位低电平
ldr r0, =0x0209C000
ldr r1, [r0]
bic r1, r1, #(1 << 3)
str r1, [r0]
bx lr
三.时钟模块(CCM 18)
1.简介
外设工作需要时钟,不同外设工作频率不同,需要的时钟频率则也不同,故点灯则也需设置对应时钟频率。


右侧为外设,cg(clock gate)为控制外设开关,ARM处理器中,为了保证低功耗,每个外设门都是关闭的,要实现点灯,则首先打开对应的cg开关。


CCGRx即为控制外设cg的寄存器,每两位控制一个外设,置11即为开启。为方便后续开发,将CCM_CCGR0-CCM_CCGR6都置为全1,即0xFFFF FFFF,表示打开所有外设时钟门。
2.代码
cpp
_enable_clocks:
// CCM_CCGR0 clock enable
ldr r0, =0x020C4068
ldr r1, =0xFFFFFFFF
str r1, [r0]
// CCM_CCGR1 clock enable
ldr r0, =0x020C406C
str r1, [r0]
// CCM_CCGR2 clock enable
ldr r0, =0x020C4070
str r1, [r0]
// CCM_CCGR3 clock enable
ldr r0, =0x020C4074
str r1, [r0]
// CCM_CCGR4 clock enable
ldr r0, =0x020C4078
str r1, [r0]
// CCM_CCGR5 clock enable
ldr r0, =0x020C407C
str r1, [r0]
// CCM_CCGR6 clock enable
ldr r0, =0x020C4080
str r1, [r0]
bx lr
四.全局代码

系统初次复位时在SVC模式,复位异常(位于0x00地址),此时若执行一条跳转指令到复位异常函数处,则可完成一些初始化工作。此外SVC模式仅在刚复位时使用,此后大多时候在system进行工作,故再不做过多阐述。(可参考上篇启动代码的学习)
异常复位函数
cpp
.global _start
_start:
ldr pc, =_reset_handler
ldr pc, =_undefine_handler
ldr pc, =_software_handler
ldr pc, =_prefetch_abort_handler
ldr pc, =_data_abort_handler
ldr pc, =_reserved_handler
ldr pc, =_irq_handler
ldr pc, =_fiq_handler
_fiq_handler:
ldr pc, =_fiq_handler
_irq_handler:
ldr pc, =_irq_handler
_reserved_handler:
ldr pc, =_reserved_handler
_data_abort_handler:
ldr pc, =_data_abort_handler
_prefetch_abort_handler:
ldr pc, =_prefetch_abort_handler
_software_handler:
ldr pc, =_software_handler
_undefine_handler:
ldr pc, =_undefine_handler
finished:
b finished
复位函数:
其中 _enable_clocks,_led_init, _led_on函数均以在上述中实现。
cpp
_reset_handler:
// 设置irq模式
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x12
msr cpsr, r0
// 设置irq模式下的栈(32M)
ldr sp, =0x86000000
// 设置system模式,相比user模式多了特权模式
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x1F
msr cpsr, r0
// 设置system模式下的栈
ldr sp, =0x84000000
// 打开所有外设时钟门
bl _enable_clocks
// 初始化led
bl _led_init
// led亮
bl _led_on
b finished

上篇由于是仿真学习,使用的是芯片内部的RAM,而本芯片SOC内部的RAM不允使用,故使用DDR3的内存,而该芯片规定该内存起始地址为0X8000 0000,大小512MB,即到0XA000 0000,在这中间可自行根据七种异常栈分配内存。
五.环境搭建
1.安装FileZilla与SSH服务器
由于在Ubuntu中运行VScode比较卡顿,故选择在Windows上的VScode进行代码编写,再将代码工程传输到Ubuntu中进行编译下载。首先为了方便从Windows和Ubuntu之间的文件传输,我们需要在Windows下安装FileZilla,该软件下载地址如下:https://www.filezilla.cn/download。 FileZilla作为客户端,Ubuntu作为服务端。

该软件底层通过采用应用层的FTP协议实现文件传输。

操作过程如下:

此外还需要在Ubuntu下安装SSH服务器: sudo apt-get install vsftpd ,并配置文件: sudo vi
/etc/vsftpd.conf 。使能本地功能和写入功能:

最后,执行 sudo /etc/init.d/vsftpd restart刷新,使配置文件生效
2.Ubuntu
1.设置NAT模式(分配了虚拟IP,只要Windows能上网,Ubuntu就能上网)

cpp
sudo /etc/init.d/networking restart
//执行此代码重启网络
-
桥接模式 :虚拟机就像你家里的另一台独立电脑。它和你的 Windows 宿主机平起平坐,共用同一个真实路由器(你之前连的手机热点)。
-
NAT模式 :虚拟机就像一个住在你 Windows 宿主机里面的"租客"。它通过宿主机代理上网,外部网络(手机热点)根本看不见它。
| 对比维度 | 桥接模式(VMnet0) | NAT模式(VMnet8) |
|---|---|---|
| IP地址来源 | 由外部路由器 分配(你之前是手机热点给的 10.138.48.x) |
由VMware虚拟路由器 分配(现在是 192.168.85.x) |
| 网络地位 | 和宿主机(Windows)是平等的同学 | 是宿主机的下属 |
| 宿主机通信 | 直接通过局域网通信(但受限于外部路由器) | 通过宿主机虚拟网卡 192.168.85.1 通信 |
| 外网通信 | 直接暴露在局域网中,占用一个外部IP | 经过宿主机 NAT(网络地址转换)转发,共用宿主机IP |
| 外部设备访问虚拟机 | 能(如果局域网没做隔离,比如你之前手机热点的问题) | 不能直接访问(除非在VMware里设置端口转发) |
| 虚拟机访问宿主机 | 能(但需要依赖外部路由器的配置) | 能(直接 ping 192.168.85.1 即可) |
2.安装ARM编译器
由于Ubuntu位X86架构,gcc为对应的编译指令,而要在ARM芯片上运行,则需要安装对应ARM架构的编译器 。

将该编译器拷贝至Ubuntu的家目录下,再切换至根目录的/usr/local目录下,创建arm文件夹,再将家目录的编译器安装包复制于当前arm文件夹,sudo mv ~/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz .再解压压缩包 sudo tar -xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz。解压后会产生对应文件夹,进入其中的bin目录,其中为arm编译的工具链,pwd打印出当前目录路径,复制保存,再sudo vim ~/.bashrc进入配置文件,配置刚才复制的路径如下,最后sudo reboot重启虚拟机即可,最后arm-linux-gnueabihf-gcc -v查看安装编译器版本

3.编译
家目录下创建imx6ull/led_asm(对应的芯片文件夹保存编译文件),通过FileZilla上传VScode上写的start.S文件至此,编译三步骤:
- 编译:arm-linux-gnueabihf-gcc -g -c start.S -o start.o,
- 其中"-g"选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。"-c"选项是编译源 文件,但是不链接。"-o"选项是指定编译产生的文件名字,这里我们指定 start.s 编译完成以后的文件名字为 start.o。执行上述命令以后就会编译生成一个start.o 文件
- 链接:arm-linux-gnueabihf-ld -Ttext 0X87800000 start.o -o start.elf
- 本次课程中,我们所有的裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程 序会将可执 行文件拷贝 到链接地址 处,这个链 接地址可以 在 I.MX6U 的内部 128KBRAM 中 (0X900000~0X91FFFF),也可以在外部的 DDR 中。本次课程中所有裸机例程的链接地址都在 DDR中,链 接起始地址为 0X87800000。I.MX6U-ALPHA 开发板的 DDR 容量有两种:512MB 和256MB,起始地址都为 0X80000000,只不过 512MB 的终止地址为0X9FFFFFFF,而 256MB 容量的终止地址为 0X8FFFFFFF。之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000, 这样我们统一使用 0X87800000 这个链接地址,不容易记混。
- 格式转换:arm-linux-gnueabihf-objcopy -O binary -S -g start.elf start.bin
- 我们需要用它将 start.elf 文件转换为start.bin 文件 。上述命令中,"-O"选项指定以什么格式输出,后面的"binary"表示以二进制格式输出,选项"-S"表示不要复制源文件中的重定位信息和符号信息,"-g"表示不复制源文件中的调试信息。上述命令执行完成以后,就会在工程目录下多一个 start.bin 文件。至此我们终于等到了想要的东西---start.bin 文件
- 反汇编:arm-linux-gnueabihf-objdump -D led.elf > led.dis(可不用)
- 大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,当然这个工作目前对于我们不是必须的。一般来说可以将 elf 文件反汇编。上述代码中的"-D"选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 start.dis 文件。我们可以打开这个文件,查看其中的内容可以发 现这里面都是汇编代码。注意通过 start.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以 0X87800000 为起始地址的区域。
4.烧录
装入SD卡,插入读卡器,连接到虚拟机,

ls /dev/sd*查看已有设备,
例如我这里看到的结果是/dev/sda /dev/sda1 /dev/sda2 /dev/sda3 四个文件,分别代 表Ubuntu下的磁盘和三个分区

然后将imxdownload文件上传至led_asm,执行chmod 0777 imxdownload给予权限

使用命令 ./imxdownload start.bin /dev/sdb烧录,注意不要写为 . /imxdownload led.bin /dev/sda, sda是系统磁盘,会造成Ubuntu损坏.

当出现此界面时下载成功,注意下载速度为KB/S,若为MB/S单位则下载失败,拔卡重启虚拟机再插卡, 注意保证读卡器没有损坏,部分廉价读卡器在插入时易因为静电烧坏。
若是插入新读卡器则可能会出现自动挂载情况,输入lsblk查看,例如,sdb挂载到boot与rootfs文件。

执行下面指令卸载挂载,因为 Linux 系统为了安全, 不允许对已经挂载的分区进行 dd 裸写入 。如果不卸载,你执行烧录会直接报 Device or resource busy 错误。
cpp
sudo umount /dev/sdb1
sudo umount /dev/sdb2
//或者一句
sudo umount /dev/sdb*
最后再进行烧录,看到进度条跑完、提示烧录成功后。然后安全弹出读卡器,把 SD 卡插回 i.MX6ULL 开发板,拨到 SD 卡启动模式,上电!
cpp
sudo ./imxdownload start.bin /dev/sdb
至此,灯亮了,路也就清晰了,前路漫漫亦灿灿,与所有在嵌入式路上摸黑前行的朋友共勉。