韦东山 3-5 I.MX6ULL 的 LED 编程 · 学习笔记
所属课程:《ARM 架构与编程(基于 I.MX6ULL)》------裸机开发系列
所在章节:第三章 第一个程序(点亮 LED)
本节名称:3-5_IMX6ULL 的 LED 编程
原视频:https://video.100ask.net/p/t_pc/course_pc_detail/video/v_5f869f4fe4b06aff1a03fedf
整理日期:2026-09-28
摘要
本节把 3-4 的寄存器配置写成工程:汇编设栈、清 BSS 后跳入 C;C 用 volatile 指针封装寄存器,led_init 完成使能、复用、定向三步,led_ctl 位读写 DR 控制亮灭,delay 循环实现闪烁,经 Makefile 与 imxdownload 烧写验证。
目录
- 摘要
- [1. 本节在课程中的位置](#1. 本节在课程中的位置)
- [2. 工程总览:C 裸机程序的结构](#2. 工程总览:C 裸机程序的结构)
- [3. 启动文件 start.S:C 运行的两个前提](#3. 启动文件 start.S:C 运行的两个前提)
- [4. 驱动层 led.c:volatile 指针操作寄存器](#4. 驱动层 led.c:volatile 指针操作寄存器)
- [5. 应用层 main.c:闪烁逻辑](#5. 应用层 main.c:闪烁逻辑)
- [6. 构建、链接与烧写](#6. 构建、链接与烧写)
- [7. 常见误区](#7. 常见误区)
- [8. 本节结论与延伸](#8. 本节结论与延伸)
- [附录 A:术语速查](#附录 A:术语速查)
- [附录 B:自查清单](#附录 B:自查清单)
- 参考
1. 本节在课程中的位置
3-4 完成了"手册 → 寄存器地址与配置值"的推导,本节完成最后一环:把这些配置写成完整的汇编 + C 工程,编译烧录到开发板跑起来。核心新增知识点是"C 语言要跑在芯片上需要哪些准备工作"------栈、BSS 段、启动文件,以及工程化的文件组织(start.S / led.h / led.c / main.c / Makefile)。本节是裸机编程范式的定型课,后续所有裸机实验都沿用这套骨架。
2. 工程总览:C 裸机程序的结构
| 文件 | 职责 |
|---|---|
| start.S | 启动文件:设置栈指针、清除 BSS 段、跳转 main |
| led.h | 声明 led_init / led_ctl 接口 |
| led.c | 寄存器级驱动:初始化引脚、控制亮灭 |
| main.c | 应用逻辑:死循环 + 延时闪烁 |
| Makefile / 链接脚本 | 编译、链接到 DDR 起始地址 0x80000000 |
分工思想:汇编搭台,C 唱戏------汇编只做 C 干不了的事(栈、BSS),业务逻辑全部交给 C。
3. 启动文件 start.S:C 运行的两个前提
C 函数能执行的两个前提:有效的栈 (局部变量、函数调用需要)与干净的 BSS 段(未初始化全局变量需为 0)。
armasm
.text
.global _start @ 全局标号,链接器入口
_start:
ldr sp, =0x80200000 @ 设置栈指针(DDR 起始 + 2M)
bl clean_bss @ 清除 BSS 段
bl main @ 跳入 C 语言 main
halt:
b halt @ 原地循环,防止跑飞
clean_bss:
ldr r1, =__bss_start @ BSS 起始地址(链接脚本提供)
ldr r2, =__bss_end @ BSS 结束地址
mov r3, #0
clean:
str r3, [r1] @ 逐字清零
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr
要点:
- 栈为什么必须先设 :
bl main的返回地址压栈、main 里局部变量都在栈上,sp 无效则第一条 C 语句即崩; - 为什么清 BSS:程序烧在 SD/eMMC,加载到 DDR 时 BSS 段不占文件体积,内存里是随机值,必须软件清零,否则全局变量初值不可预期;
__bss_start/__bss_end由链接脚本符号提供,汇编与链接器在此协作。
部分版本课程还会在设栈前显式进入 SVC 模式(mrs/bic/orr/msr 操作 CPSR),保证处理器状态一致。
4. 驱动层 led.c:volatile 指针操作寄存器
韦东山例程(100ASK 板,LED 接 GPIO5_IO03,即 SNVS_TAMPER3 引脚复用):
c
static volatile unsigned int *CCM_CCGR1
= (volatile unsigned int *)(0x20C406C);
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
= (volatile unsigned int *)(0x2290014);
static volatile unsigned int *GPIO5_GDIR
= (volatile unsigned int *)(0x020AC000 + 0x4);
static volatile unsigned int *GPIO5_DR
= (volatile unsigned int *)(0x020AC000);
void led_init(void)
{
unsigned int val;
/* a. 使能 GPIO5 时钟: CCM_CCGR1[CG15] bit[31:30] = 11 */
*CCM_CCGR1 |= (3 << 30);
/* b. 复用为 GPIO5_IO03: MUX bit[3:0] = 0101 (ALT5) */
val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
val &= ~(0xf);
val |= (5);
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
/* c. 方向为输出: GPIO5_GDIR bit[3] = 1 */
*GPIO5_GDIR |= (1 << 3);
}
void led_ctl(int on)
{
if (on)
*GPIO5_DR &= ~(1 << 3); /* 低电平点亮 */
else
*GPIO5_DR |= (1 << 3); /* 高电平熄灭 */
}
三个代码级要点:
- volatile 不可省:告诉编译器该内存单元随时被硬件改变,禁止缓存到寄存器或优化掉重复读写------嵌入式驱动的第一铁律;
- 读-改-写 :
|=/&=保证不影响同寄存器其他位(3-2 铁律的落地); - static:把寄存器指针限定在本文件,避免符号污染。
引脚差异说明
100ASK 板的 LED 挂在 GPIO5_IO03 (SNVS 域引脚,复用寄存器在 0x2290014);正点原子等板卡常用 GPIO1_IO03。寄存器地址与组号因板而异,但四步配置方法完全同构------这正是 3-2 抽象出通用框架的价值:换板只改地址宏,不改流程。
5. 应用层 main.c:闪烁逻辑
c
#include "led.h"
void delay(volatile unsigned int d)
{
while (d--);
}
int main(void)
{
led_init();
while (1)
{
led_ctl(1); /* 点亮 */
delay(1000000);
led_ctl(0); /* 熄灭 */
delay(1000000);
}
return 0;
}
- delay 用软件空循环计数实现,无定时器参与------最简但精度差,依赖 CPU 主频;后续课程会改用 GPT 定时器;
led_ctl(1)点亮对应该板低电平亮的硬件事实(3-1 原理图结论);驱动层把极性封装起来,应用层只说"亮/灭"------这是最朴素的硬件抽象层(HAL)思想。
6. 构建、链接与烧写
start.S ──gcc -c──> start.o ─┐
├─ld -Ttext 0x80000000──> led.elf ──objcopy──> led.bin
main.c/led.c ──gcc -c──> *.o ─┘
- 链接地址 :
-Ttext 0x80000000指定代码放在 DDR 起始处,程序按此地址重定位执行; - objcopy 把 ELF 抽成纯二进制 led.bin;
- 烧写 :用 imxdownload 工具把 bin 加上 IVT 头部写入 SD 卡(
./imxdownload led.bin /dev/sdb),开发板拨码设为 SD 启动; - 上电后 BootROM 按 IVT 引导,把程序加载到 DDR 并跳转
_start,LED 开始闪烁。
7. 常见误区
- 忘写 volatile:开优化后 delay 被删、寄存器读写被合并,程序"看起来对但现象不对";
- 忘设栈:进入 main 即异常,且裸机无报错信息,是最隐蔽的坑;
- 忘清 BSS:全局变量随机初值,现象时好时坏;
- led_ctl 极性写反:不看原理图直接"写 1 点亮",结果语义颠倒;
- 把 led.bin 直接 dd 进 SD 卡:缺少 IVT 头部 BootROM 无法引导,必须经 imxdownload 等工具加工。
8. 本节结论与延伸
本节完成第三章点灯实验的最终闭环:原理图(3-1)→ 通用方法(3-2)→ 芯片寄存器(3-3/3-4)→ 可运行工程(3-5)。"汇编搭台、C 唱戏"的工程范式将贯穿后续所有裸机实验;volatile 指针、读-改-写、硬件抽象这些习惯会一直用到 Linux 驱动(届时换成 ioremap + readl/writel,思想不变)。
附录 A:术语速查
| 术语 | 含义 |
|---|---|
| BSS 段 | 存放未初始化全局变量的段,加载后需软件清零 |
| volatile | 告知编译器禁止优化该对象的读写 |
| 链接脚本 / -Ttext | 指定各段在内存中的布局与起始地址 |
| IVT | Boot ROM 识别的镜像头(引导向量表),SD 卡启动必需 |
| SNVS_TAMPER3 | SNVS 域引脚,复用 ALT5 后为 GPIO5_IO03 |
| imxdownload | 百问网烧写工具,为 bin 添加 IVT 头并写入 SD 卡 |
附录 B:自查清单
- 能说出 C 程序运行的两大前提及对应的汇编操作;
- 能解释 volatile 在本例中的两处必要性(寄存器、delay 计数);
- 能画出 start.S → main → led_init → led_ctl 的调用链;
- 能说明 __bss_start/__bss_end 由谁定义、谁使用;
- 能解释为什么 led.bin 不能直接 dd 进 SD 卡;
- 能说明本板 LED 极性并指出信息来源(原理图)。
参考
- 韦东山《ARM 架构与编程》3-1 ~ 3-4(本课程前置小节)
- 100ASK_IMX6ULL 裸板学习 LED 实验(中)------例程与代码分析(博客园)
- NXP i.MX 6ULL Applications Processor Reference Manual
- 100ASK I.MX6ULL 开发板原理图