I.MX6U-汇编裸机点灯
一、I.MX6U寄存器介绍
和STM32类似,要控制某个IO时,需要对其进行一些必要的操作,如:
- GPIO时钟使能
- GPIO功能复用配置
- GPIO模式配置(速度、上下拉等)
- 对GPIO进行控制
对此,需要找到I.MX6C中和IO相关的配置寄存器,并对其进行操作。
1.1、I.MX6U的IO介绍
I.MX6U中和IO相关的描述在:
《I.MX6ULL 参考手册 Chapter 32: IOMUX Controller(IOMUXC)》
1.1.1、I.MX6U的IO划分
在I.MX6U中,将IO分成了两个区域:
![]()
![]()
即 IOMUXC_SNVS、IOMUXC。
对于IOMUXC :
- 通常负责主处理器I/O引脚的复用功能。
- 控制大多数普通I/O引脚,如GPIO、UART、SPI等接口的复用和电气属性。
- 这些引脚通常用于通用的输入/输出功能和外设连接。
对于 IOMUXC_SNVS :
- 特别负责SNVS(System Non-Volatile Storage)域的I/O引脚。
- SNVS域通常包含与安全、电源管理和非易失性存储相关的功能。
- IOMUXC_SNVS控制的引脚可能包括tamper检测、RTC(实时时钟)功能、唤醒事件等专用功能的引脚。
- 这些引脚可能在低功耗模式下仍然活跃,并且在处理器的安全启动和电源管理方面起着关键作用。
对于点灯而言,暂时不需要考虑IOMUXC_SNVS。
1.1.2、I.MX6U的IO命名
在STM32中,GPIO明明为 GPIOx_PIN_n ,表明了这个是哪一个组的哪一个引脚的IO。
而在 I.MX6U 中,对于IO的寄存器有如下定义:


前缀形如:IOMUXC_SW_MUX、IOMUXC_SW_PAD,后面跟上相同的CRL_PAD_XXXX_XXXX。
IOMUXC_SW_MUX 代表了这个寄存器用于配置这个IO的复用功能(GPIO、UART等)、IOMUXC_SW_PAD 代表了这个寄存器用于配置这个IO的电气特性(驱动强度、速度、上下拉等)。
最后的 CRL_PAD_XXXX_XXXX 里面的xxxx_xxxx就代表了这个IO的命名了,比如:CTL_PAD_UART1_TX_DATA 就代表了这个UART1_TX_DATAIO,从名字上也可以看出其可以作为串口使用。
1.2、I.MX6U的IO相关配置寄存器
1.2.1、IO复用-IOMUXC_SW_MUX_xxx
以1.1.2节提到的 SW_MUX_CTL_PAD_UART1_TX_DATA 为例,也可以将其配置为普通的GPIO。
此寄存器文档描述如下:

可以看到在 MUX_MODE 这一项中,有可以将其配置为 GPIO1_IO16 的描述。
如果想直接查看所有的IO复用关系,可以查看
《IMX6ULL 参考手册》 第 4 章“Chapter 4 External Signals and Pin Multiplexing”
其中的ALTx就代表了复用寄存器中 MUX_MODE 的一种模式。
1.2.2、IO配置-IOMUXC_SW_PAD_xxx
以1.1.2节提到的 IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA 为例,其对应的配置寄存器为: IOMUXC_SW_PAD_CTL_PAD_UART1_TX_DATA:

图片中未展示完全,里面有多种配置项,这个寄存器只用到了其Bit0~Bit16:

| 功能位 | 描述 |
|---|---|
HYS (Bit16) | 对应功能图中的HYS,用来使能迟滞比较器。0:关闭 1:开启 |
PUS (Bit15<14>14>) | 用来设置上下拉电阻 00:100K下拉 01:47K上拉 10:100K上拉 11:22K上拉 |
PUE (Bit13) | 当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器(图中未给出) 0:使用状态保持(仅输入时有效,当外部电路断电以后此 IO 口可以保持住以前的状态) 1:使用上下拉 |
PKE (Bit12) | 用来使能或者禁止PUE(上下拉/状态保持器)功能。0:禁止 PUE 功能1:使能 PUE 功能 |
ODE (Bit11) | IO输出时有效,用来禁止或者使能开路输出。 0:禁止 1:使能 |
SPEED (Bit7<6>6>) | 控制IO引脚速度 00:低速 50M 01:中速 100M 10:中速 100M (没写错,和上面01是一样的) 11:最大速度 200M |
DSE (Bit5<3>3>) | 当 IO 用作输出的时候用来设置 IO 的驱动能力 000:输出驱动关闭 001:R0(3.3V 下 R0 是 260Ω, 1.8V 下 R0 是 150Ω,接 DDR 的时候是 240Ω ) 010:R0/2 011:R0/3 100:R0/3 101:R0/5 110:R0/6 111:R0/7 |
SRE (Bit0) | 设置压摆率 0:低压摆率 1:高压摆率 压摆率就是 IO 电平跳变所需要的时间,比如从 0 到 1 需要多少时间,时间越小波形就越陡,说明压摆率越高 |
1.2.3、GPIO功能配置
根据本章节目的,要将IO配置为GPIO的功能,因此就需要对GPIO进行配置,涉及输入/输出方向、输出控制等寄存器。相关资料位于:
《IMX6UL 参考手册》 第 28章“Chapter 28 General Purpose Input/Ouput(GPIO)”

可以看出,和GPIO配置相关的寄存器有:
| 寄存器 | 描述 |
|---|---|
数据寄存器-DR | 寄存器大小32位用于控制数据的输出,每个寄存器控制着一组GPIO,因此最大一组 可以控制32个GPIO。和STM32的ODR寄存器一样。 0:输出低电平 1:输出高电平 |
方向寄存器-GDIR | 此寄存器用来设置某个 IO 的工作方向,是输入还是输出。同样的,每个 IO 对应一个位 0:输入模式 1:输出模式 |
GPIO 状态寄存器-PSR | PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状态, 也就是 GPIO 的高低电平值。功能和输入状态下的DR`寄存器一样。 0:输入了低电平 1:输入了高电平 |
中断控制寄存器-ICR1、ICR2 | ICR1用于配置低16个GPIO,ICR2 用于配置高 16 个 GPIO。 每个IO占两位,用来配置中断的触发方式,和 STM32 的中断很类似: 00:低电平触发 01:高电平触发 10:上升沿触发 11:下降沿触发 |
中断禁止和使能控制寄存器-IMR | 也是一个 GPIO 对应一个位, IMR 寄存器用来控制 GPIO 的中断(ICRx) 禁止和使能0:禁止 1:使能 |
中断状态寄存器-ISR | 也是一个 GPIO 对应一个位,只要某个 GPIO 的中断发生,那么ISR 中相应的位就会被置 1。 |
边沿中断设置寄存器-EDGE_SEL | 也是一个 GPIO 对应一个位,这个寄存器会覆盖ICRx的配置(无效ICRx),只要设置为1,就表示此IO设置了对应的 GPIO 是上升沿和下降沿(双边沿)触发。 0:关闭 1:双边沿触发 |
1.2.4、GPIO时钟使能
I.MX6U 的系统时钟参考:
《I.MX6UL 参考手册》第 18 章“Chapter 18: ClockController Module(CCM)”
这一章主要讲解 I.MX6U 的时钟系统,很复杂,先只 看 一 下 CCM 里 面 的 外 设 时 钟 使 能 寄 存 器。

CCM里面有 CCM_CCGR0~CCM_CCGR6 7个寄存器,以 CCM_CCGR0 为例:

可以看到里面 CG15 代表了 gpio2 的时钟使能控制。还包括了其他外设的时钟控制。
每一项都占据了两个Bit位:

- 00:所有模式下都关闭外设时钟
- 01:只有在运行模式下打开外设时钟,等待模式和停止模式下均关闭外设时钟
- 10:未使用(保留)
- 11:除了停止模式以外,其他所有模式下时钟都打开
因此,如果我们要打开 GPIO2 的外设时钟,那么只需要设置 CCM_CCGR0 的 bit31 和 bit30 都为 1 即可。
二、硬件原理图

![]()
由此可知,只需要拉低IO,LED即可点亮。
三、代码编写
/************************************************************************* This source code file is the property of MengHang.Li.* The contents of this file are intended for non-commercial use only.* No individual or organization may use this file for commercial purposes* without the explicit written permission of MengHang.Li.** This source code is provided "as is" without any warranties. The author* or copyright holder shall not be liable for any consequences, direct or* indirect, arising from the use of this source code.** Copyright Notice: (C) 2024-2025, MengHang.Li.** File: led.s* Author: LMH* Creation Date: 2024.09.22************************************************************************** LED 使用IO:GPIO1_IO03*************************************************************************/
/** @group 外设时钟控制寄存器 - CCM_CCGRx * @{ */.equ CCM_CCGR0_ADDR, 0x020C4068.equ CCM_CCGR1_ADDR, 0x020C406C.equ CCM_CCGR2_ADDR, 0x020C4070.equ CCM_CCGR3_ADDR, 0x020C4074.equ CCM_CCGR4_ADDR, 0x020C4078.equ CCM_CCGR5_ADDR, 0x020C407C.equ CCM_CCGR6_ADDR, 0x020C4080/** * @} */
/** * @brief LED IO 复用控制寄存器 - IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 */.equ LED_MUX_CTRL_ADDR, 0x020E0068
/** * @brief LED IO 配置寄存器 - IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 */.equ LED_PAD_CTRL_ADDR, 0x020E02F4
/** * @brief LED IO 数据寄存器 - GPIO1_DR */.equ LED_GPIO_DR_ADDR, 0x0209C000
/** * @brief LED IO 方向寄存器 - GPIO1_GDIR */.equ LED_GPIO_GDIR_ADDR, 0x0209C004
/** * @brief LED IO 亮灭控制掩码 */.equ LED_STATUS_ADDR, 0x00000008
/** * @brief 声明全局_start */.global _start
/********************************************************************* Function Name : _start* Input : xxx* Output : xxx* Returns : xxx* Description : 裸机代码启动入口* Note : 2024.09.22 By LMH**********************************************************************/_start: /* 1、使能 CCM_CCGRx 时钟*/ LDR R0, =0xFFFFFFFF
LDR R1, =CCM_CCGR0_ADDR STR R0, [R1] LDR R1, =CCM_CCGR1_ADDR STR R0, [R1] LDR R1, =CCM_CCGR2_ADDR STR R0, [R1] LDR R1, =CCM_CCGR3_ADDR STR R0, [R1] LDR R1, =CCM_CCGR4_ADDR STR R0, [R1] LDR R1, =CCM_CCGR5_ADDR STR R0, [R1] LDR R1, =CCM_CCGR6_ADDR STR R0, [R1]
/* 2、LED IO 复用为GPIO(Bit 0 ~ 23)*/ LDR R1, =0x00000005 LDR R2, =LED_MUX_CTRL_ADDR STR R1, [R2]
/* 3、LED IO 配置 @ bit 16:0 HYS关闭 @ bit [15:14]: 00 默认下拉 @ bit [13]: 0 kepper功能 @ bit [12]: 1 pull/keeper使能 @ bit [11]: 0 关闭开路输出 @ bit [7:6]: 10 速度100Mhz @ bit [5:3]: 110 R0/6驱动能力 @ bit [0]: 0 低转换率 */ LDR R1, =0x000010B0 LDR R2, =LED_PAD_CTRL_ADDR STR R1, [R2]
/* 4、LED IO 方向设置为输出*/ LDR R1, =0x0000008 LDR R2, =LED_GPIO_GDIR_ADDR STR R1, [R2]
/* 5、点亮LED 拉低有效*/ LDR R1, =0x00000000 LDR R2, =LED_GPIO_DR_ADDR STR R1, [R2]
/********************************************************************* Function Name : loop* Input : xxx* Output : xxx* Returns : xxx* Description : 死循环* Note : 2024.09.22 By LMH**********************************************************************/loop: b loop因为暂时不知道GPIO外设运行会和哪些时钟有关,这里先全部打开。
四、代码编译
4.1、编译
首先,肯定要将.s代码生成.o代码:
arm-linux-gnueabihf-gcc -nostdlib -o led led.o这里生成时使用了 -nostdlib,原因是因为这是裸机开发,在代码中定义了 _start 标签,如果不加入不使用标准库的选项,会导致编译报错。

这里就将 -nostdlib 给去掉了,结果编译报错说 _start 重复定义,还有找到到 main 等。
同样我们可以加上-g 项,表示生成调试信息,这样就可通过GDB进行调试。
如:
arm-linux-gnueabihf-gcc -nostdlib -g -o led led.o这里的 -o 用于指定编译产生的文件名字
4.2、链接
编译完成后,需要执行链接操作,就是将生成的.o文件链接到某个地址上去。如Keil:

配置的就是代码存放地址。
这里链接时要弄清楚两个概念:
-
存储地址
就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。
-
运行地址
就是代码运行的时候所处的地址,这个在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。
比如 I.MX6U 支持 SD 卡、 EMMC、 NAND 启动,因此代码可以存储到 SD 卡、 EMMC 或者 NAND 中,但是要运行的话就必须将代码从 SD 卡、 EMMC 或者NAND 中拷贝到其运行地址(链接地址)处,“存储地址”和“运行地址”可以一样,比如STM32 的存储起始地址和运行起始地址都是 0X08000000。
以烧写到SD卡为例,
上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,这个链接地址可以在 I.MX6U 的内部 128KB RAM 中 (0X900000~0X91FFFF),也可以在外部的 DDR 中。
这里选择将例程的链接地址都在 DDR中,链接起始地址为 0X87800000。之所以选择 0X87800000 这个地址是因为后面 Uboot 其链接地址就是 0X87800000,这样统一使用 0X87800000 这个链接地址,不容易记混。
确定好链接地址后,就可以执行链接操作:
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf- arm-linux-gnueabihf-ld :就是链接指令
- -Ttext:指定链接地址
- -o:指定链接生成的elf 文件名
执行后,即可链接完成。
4.3、转码
链接后生成的 led.elf 文件并不是最终下载并执行的文件,而是需要将其转换为 .bin 文件。
用到的指令是:arm-linux-gnueabihf-objcopy
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin- -O:指定以什么格式输出,后面的“ binary”表示以二进制格式输出
- -S:表示不要复制源文件中的重定位信息和符号信息
- -g:表示不复制源文件中的调试信息
执行后,即可在当前路径下生成 led.bin 文件。
![]()
4.4、反汇编
大多数情况下都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
arm-linux-gnueabihf-objdump -D led.elf > led.dis- -D:表示反汇编所有段
指令执行后,将在路径下生成一个 led.dis 文件,可以用文本编辑器将其打开查看

可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。
过 led.dis 这个反汇编文件可以明显的看出代码已经链接到了以 0X87800000 为起始地址的区域。
五、Mackfile生成
根据第4章节步骤,编写Mackfile 文件:
led: led.o # 命令1:编译生成led文件,-nostdlib 用于指定不需要标准库 # 因为代码中的_start 会和标准库中的冲突,会报错,裸机开发时必须指定 arm-linux-gnueabihf-gcc -nostdlib -o led led.o
# 命令2:链接代码,将代码放在0x87800000中运行,生成elf文件 arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
# 命令3:将elf文件转译成.bin文件 arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
# 命令3:反汇编,用于检查代码正确性 arm-linux-gnueabihf-objdump -D led.elf > led.dis
%.o: %.s arm-linux-gnueabihf-gcc -c $<
clean: rm *.o rm led rm led.bin rm led.elf rm led.dis创建好后,执行一次mack操作即可。
六、代码烧写
I.MX6U 支持从外置的 NOR Flash、 NAND Flash、 SD/EMMC、 SPI NOR Flash 和 QSPI Flash 这些存储介质中启动,所以可以将代码烧写到这些存储介质中中。在这些存储介质中,除了 SD 卡以外,其他的一般都是焊接到了板子上的,没法直接烧写。因此调试阶段一般烧写到 SD 卡上。
但是==不能直接将 .bin 文件放在 SD 卡中,因为存放的位置,NXP是有详细规定的!==不按照规定烧写,代码就无法运行。
可以查阅:
《IMX6UL 参考手册》第 8 章“Chapter 8 System Boot”
就是专门讲解 I.MX6U 启动。
这里先暂时使用正点原子提供的 imxdownload 烧录工具。先将imxdownload 复制到代码路径下
首先将 SD 卡 通过读卡器等插入Ubuntu 电脑。使用 lsblk 查看路径:

确定插入的U盘,这里是sdb
然后执行:
ls /dev/sd*![]()
可以看到确实有一个 /dev/sdb的路径。
然后将刚刚复制的 imxdownload 文件,使用chmod 指令,给与执行权限
chmod 777 imxdownload具备执行权限的文件,其符号是绿色显示的:
![]()
最后执行 imxdownload 命令:
./imxdownload led.bin /dev/sd
注意最后的下载速度,如果这个烧写速度大于几十 MB/s、甚至几百 MB/s 那么肯定是烧写失败了。重新拔插或重试。
自此,代码就烧录完成。
七、代码验证
插入SD 卡,拨动拨码开关,使其从 sd 卡启动。上电,观察现象。
记录:
拨前:10100110
拨后:10000010
现象:

红灯亮起,操作成功。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!













