韦东山《ARM 架构与编程 · 基于 IMX6ULL》快速入门路线
面向对象:已具备 51 单片机(STC89C52)与 STM32(Cortex-M3,江协科技教程)基础的学习者
核心思路:以旧带新、以寄存器窥全机 ------ 用你已经会的"配寄存器点灯"当船,渡到 ARMv7-A 裸机开发的岸
关键提醒:这门课不是"再学一遍单片机",而是把 STM32 的经验往下拆一层,看清 CPU、总线、地址空间、程序加载的真实面目
目录
- [0. 这份路线图怎么用](#0. 这份路线图怎么用)
- [1. 课程全貌:十章知识地图](#1. 课程全貌:十章知识地图)
- [2. 前置能力盘点:你有的 vs 你缺的](#2. 前置能力盘点:你有的 vs 你缺的)
- [2.1 迁移对照表(51/STM32 → IMX6ULL)](#2.1 迁移对照表(51/STM32 → IMX6ULL))
- [2.2 必须补的三个知识缺口](#2.2 必须补的三个知识缺口)
- [3. 环境准备(半天内搞定)](#3. 环境准备(半天内搞定))
- [3.1 硬件清单](#3.1 硬件清单)
- [3.2 软件与工具链](#3.2 软件与工具链)
- [3.3 与 Keil 时代的思维切换](#3.3 与 Keil 时代的思维切换)
- [4. 三阶段速通路线(总计 4--6 周)](#4. 三阶段速通路线(总计 4–6 周))
- [5. 逐章精讲与"快进/必看"标注](#5. 逐章精讲与"快进/必看"标注)
- [第 2 章 嵌入式概念及硬件组成](#第 2 章 嵌入式概念及硬件组成)
- [第 3 章 第一个程序(IMX6ULL 点灯)](#第 3 章 第一个程序(IMX6ULL 点灯))
- [第 4 章 ARM 架构](#第 4 章 ARM 架构)
- [第 5 章 第 1 个程序深度解析](#第 5 章 第 1 个程序深度解析)
- [第 6 章 使用按键控制 LED](#第 6 章 使用按键控制 LED)
- [第 7 章 串口 UART 编程](#第 7 章 串口 UART 编程)
- [第 8 章 keil_gcc_Makefile](#第 8 章 keil_gcc_Makefile)
- [第 9 章 代码重定位](#第 9 章 代码重定位)
- [第 10 章 异常与中断](#第 10 章 异常与中断)
- [6. 核心难点攻坚](#6. 核心难点攻坚)
- [6.1 用寄存器点灯"看见"计算机如何工作(本书精华)](#6.1 用寄存器点灯"看见"计算机如何工作(本书精华))
- [6.2 Cortex-M3 与 ARMv7-A 的本质差异](#6.2 Cortex-M3 与 ARMv7-A 的本质差异)
- [6.3 汇编与 C 的边界:启动代码](#6.3 汇编与 C 的边界:启动代码)
- [6.4 代码重定位与链接脚本(全课最难)](#6.4 代码重定位与链接脚本(全课最难))
- [6.5 异常向量表与 GIC 中断](#6.5 异常向量表与 GIC 中断)
- [7. 建议时间表(4 周冲刺版 / 6 周稳健版)](#7. 建议时间表(4 周冲刺版 / 6 周稳健版))
- [8. 调试与验证手段](#8. 调试与验证手段)
- [9. 与后续课程的衔接](#9. 与后续课程的衔接)
- [10. 常见问题 FAQ](#10. 常见问题 FAQ)
- [11. 一页速查:寄存器点灯最小代码](#11. 一页速查:寄存器点灯最小代码)
0. 这份路线图怎么用
三条原则:
| 原则 | 含义 | 具体做法 |
|---|---|---|
| 旧知识加速 | 讲 GPIO、UART、中断"是什么"的部分直接快进 | 只精听"IMX6ULL 上怎么做"的差异部分 |
| 新概念慢炖 | 地址空间、重定位、异常向量、GIC 必须逐帧看 | 每节课后自己复现一遍,不看讲稿敲代码 |
| 动手 > 看懂 | 裸机开发最大的坑都在"想当然" | 每个实验必须落地到开发板上出效果 |
推荐节奏:先用半天通读本文档 → 按 [第 4 节](#先用半天通读本文档 → 按 第 4 节 三阶段推进 → 卡壳时回查 第 6 节) 三阶段推进 → 卡壳时回查 [第 6 节](#先用半天通读本文档 → 按 第 4 节 三阶段推进 → 卡壳时回查 第 6 节)。
1. 课程全貌:十章知识地图
课程本质是一条**"从会用到懂原理"**的递进链:
外设层(会点灯)
第2章 概念/硬件组成 → 第3章 寄存器点灯 → 第6章 按键
↓
架构层(懂指令) 第7章 UART
第4章 ARM架构与汇编 → 第5章 反汇编/存储布局 ↓
↓
工程层(懂编译链接) ↓
第8章 gcc/Makefile → 第9章 代码重定位 → 第10章 异常与中断
| 章节 | 标题 | 视频数 | 难度 | 定位 |
|---|---|---|---|---|
| 1 | 资料说明与下载 | --- | ★ | 环境搭建 |
| 2 | 嵌入式概念及硬件组成 | 2 | ★ | 概念铺垫(MCU/MPU/AP 的区别) |
| 3 | 第一个程序(IMX6ULL 点灯) | 5 | ★★ | 全课基石:GPIO 寄存器操作 |
| 4 | ARM 架构 | 7 | ★★★★ | 分水岭:寄存器组 + 汇编指令集 |
| 5 | 第 1 个程序深度解析 | 5 | ★★★★ | 反汇编、字节序、C 与汇编的对应 |
| 6 | 使用按键控制 LED | 1 | ★★ | 输入方向配置(快进) |
| 7 | 串口 UART 编程 | 3 | ★★★ | 第一个"有反馈"的调试通道 |
| 8 | keil_gcc_Makefile | 8 | ★★★★ | 脱离 IDE,理解编译链接全过程 |
| 9 | 代码重定位 | 7 | ★★★★★ | 全课最硬:链接脚本、段、BSS |
| 10 | 异常与中断 | 10 | ★★★★★ | 异常向量表、CPU 模式、GIC |
总计约 48 个视频,按 30--40 分钟/集估算,视频净时长 25--30 小时;配合动手实操,总投入约 60--80 小时。
2. 前置能力盘点:你有的 vs 你缺的
2.1 迁移对照表(51/STM32 → IMX6ULL)
| 能力项 | 你在 51/STM32 上已掌握 | IMX6ULL 上的变化 | 迁移难度 |
|---|---|---|---|
| GPIO 点灯 | 配 P1 口 / GPIO_InitStruct | 多了一步"引脚复用(IOMUX)+ 时钟使能(CCM)" | ★★ 需注意 |
| 时钟系统 | 51 无;STM32 RCC 使能 | 七个 PLL,要查 CCM 寄存器手动配 | ★★★ |
| 中断 | NVIC,IRQHandler |
无 NVIC,改用 GIC + 异常向量表 | ★★★★ |
| 启动流程 | 硬件自动跳到 0x0000_0000 / 复位向量 | 需手写启动汇编 + 链接脚本 | ★★★★★ |
| 编译工具 | Keil MDK 一键编译 | arm-linux-gcc + Makefile 手动链接 | ★★★★ |
| 下载方式 | STC-ISP / ST-Link | imxdownload 制作 SD 卡镜像 | ★★ |
| C 语言运行环境 | 自动完成 | 必须自己清 BSS、重定位、设栈 | ★★★★★ |
| 汇编 | 基本没用过 | 指令集 + 寻址方式 + 现场保存 | ★★★★★ |
2.2 必须补的三个知识缺口
- ARMv7-A 寄存器组:Cortex-A7 有 37 个寄存器、9 种 CPU 模式,而 Cortex-M3 只有 R0--R15 + PSR。这是理解异常/中断与现场保存的前提。
- 存储器的"段"概念 :
.text / .rodata / .data / .bss各自的运行位置与加载位置不同------这是第 9 章重定位的全部理由。 - 编译---汇编---链接---加载"四步链" :搞不清
gcc → as → ld → objcopy各产出什么文件,后面必崩。
好消息 :你学过的 51 直接操作寄存器(
P1 = 0xFE)、STM32 的位带操作/寄存器宏定义,都是这门课的直系前置。韦东山讲"寄存器就是内存里某个固定地址上的一个可读写单元",你其实早就用过,只是没上升为世界观。
3. 环境准备(半天内搞定)
3.1 硬件清单
| 项目 | 建议 | 说明 |
|---|---|---|
| 开发板 | 韦东山 IMX6ULL Pro / Mini(100ASK_IMX6ULL) | 课程代码与之严格对应,建议直接买同款 |
| 存储卡 | 8--16 GB Class10 microSD | 裸机程序从 SD 卡启动 |
| 读卡器 | USB 读卡器 | 烧写镜像 |
| 串口模块 | CH340 / CP2102 USB-TTL | 三线接法:TX↔RX、RX↔TX、GND↔GND |
| 网线 | 可选 | 后续 NFS 挂载内核时必需 |
| 电源 | 5V/2A DC | 别用电脑 USB 口带不动 |
3.2 软件与工具链
| 类别 | 工具 | 备注 |
|---|---|---|
| 交叉编译 | arm-linux-gnueabihf-gcc(韦东山提供 Windows 版工具链) |
装完把 bin 加入 PATH,arm-linux-gnueabihf-gcc -v 验证 |
| 汇编器/链接器 | 随工具链自带的 as / ld / objcopy / objdump |
objdump -d 是反汇编主力 |
| 编辑器 | VS Code(推荐)或 CodeBlocks | 第 8-1 讲会讲"Keil 背后的命令" |
| 烧录 | imxdownload(韦东山提供) |
./imxdownload xxx.bin /dev/sdX |
| 串口终端 | MobaXterm / SecureCRT / putty | 波特率 115200-8-N-1 |
| 调试(进阶) | OpenOCD + GDB / J-Link | 早期可用"点灯+串口打印"代替 |
3.3 与 Keil 时代的思维切换
| 维度 | Keil(你熟悉的) | 裸机 gcc(本课) |
|---|---|---|
| 编译入口 | 点 Build 按钮 | make |
| 启动文件 | Keil 自带 startup_stm32.s |
自己写 start.S |
| 链接地址 | 图形界面里填 0x0800_0000 | 自己写 led.lds 链接脚本 |
| 下载地址 | ST-Link 自动烧 Flash | 手动把 .bin 摆放到 SD 卡指定偏移 |
| 全局变量初值 | 自动从 Flash 拷到 RAM | 自己写拷贝循环(重定位) |
| 未初始化变量 | 自动清零 | 自己清 BSS 段 |
一句话:STM32 帮你做完的事,IMX6ULL 裸机课要你自己做一遍------这正是这门课的价值所在。
4. 三阶段速通路线(总计 4--6 周)
阶段一:点亮世界(约 1 周)------先跑通,别纠结原理
| 顺序 | 章节 | 目标 | 必做实验 |
|---|---|---|---|
| 1 | 第 1 章 | 下载资料,装上工具链 | 编译并烧录官方示例 |
| 2 | 第 2 章 | 分清 MCU/MPU/AP,认识板子硬件框图 | 对着原理图找到 LED 与对应引脚 |
| 3 | 第 3 章 3-1~3-5 | 用寄存器点亮 LED | 手写 GPIO 配置代码,改寄存器观察现象 |
| 4 | 第 6 章 | 按键读入 + 判断 | 按键控制 LED 亮灭 |
| 5 | 第 7 章 | UART 收发 | 串口打印 "Hello IMX6ULL",形成调试手段 |
阶段验收 :能脱离 IDE,用命令行编译出 .bin,烧到 SD 卡,板上 LED 按你的代码闪烁,串口能打印自己想打印的话。
阶段二:看懂世界(约 2 周)------最关键的认知跃升
| 顺序 | 章节 | 目标 | 必做实验 |
|---|---|---|---|
| 6 | 第 4 章 4-1~4-7 | ARM 寄存器组 + 汇编指令分类 | 用汇编模拟器单步执行,观察寄存器变化 |
| 7 | 第 5 章 5-1~5-5 | C 与汇编的对应关系、反汇编 | 用 objdump -d 反汇编自己的 led.c,逐行对照 |
| 8 | 第 5-5 讲 | 纯汇编点灯 | 把 LED 代码全部改用汇编写一遍 |
阶段验收 :给你一段 C 代码,你能说出编译器大概会生成什么样的汇编;能用 objdump 找到 main 函数的入口地址;理解"函数调用就是保存 LR + 跳转"。
阶段三:掌控世界(约 2--3 周)------工程化与系统级
| 顺序 | 章节 | 目标 | 必做实验 |
|---|---|---|---|
| 9 | 第 8 章 8-1~8-8 | 编译链接全过程 + Makefile | 为点灯程序写一个能用的 Makefile,支持 make clean |
| 10 | 第 9 章 9-1~9-7 | 段、链接脚本、重定位、清 BSS | 写链接脚本,实现带初始值全局变量的正确运行 |
| 11 | 第 10 章 10-1~10-10 | 异常向量表、CPU 模式、SVC、GIC | 实现 GPIO 中断,按键触发中断处理函数 |
阶段验收:能解释"为什么全局变量初值存在 Flash 里而运行时在 RAM 里";能自己写出异常向量表;按键能通过 GIC 触发中断。
5. 逐章精讲与"快进/必看"标注
第 2 章 嵌入式概念及硬件组成
| 视频 | 建议 | 要点 |
|---|---|---|
| 2-1 处理器的区分 MCU_MPU_AP | 必看 | 你以前玩的 STM32 是 MCU(无 MMU、跑裸机/RTOS);IMX6ULL 是 MPU/AP(有 MMU、跑 Linux)。这是"为什么 STM32 不用重定位而这里要"的根源 |
| 2-2 嵌入式系统硬件组成 | 必看 | SoC 内部框图:CPU 核、总线矩阵、外设控制器、DDR 控制器。建立"CPU 通过总线访问所有外设"的模型 |
第 3 章 第一个程序(IMX6ULL 点灯)
| 视频 | 建议 | 要点 |
|---|---|---|
| 3-1 硬件知识_LED 原理图 | 必看 | 学会读原理图:LED 接在哪根 GPIO 上、高电平还是低电平点亮 |
| 3-2 GPIO 引脚操作方法概述 | 快进 | 通用 GPIO 概念你已在 STM32 学过,2 倍速 |
| 3-3 IMX6ULL 的 GPIO 操作方法 | 必看 | 三种方法:库函数 / 寄存器 / 汇编。重点看"寄存器三件套" |
| 3-4 IMX6ULL 的 LED 寄存器操作 | 全课重点 | 使能时钟 → 配置复用 → 配置电气属性 → 设方向 → 写数据 |
| 3-5 IMX6ULL 的 LED 编程 | 必看 | 完整代码落地 |
IMX6ULL GPIO 五步法(务必背下来):
① CCM_CCGRx 使能 GPIO 模块时钟(不用就断电,省电设计)
② IOMUXC_SW_MUX_CTL_PAD_x 把物理引脚复用为 GPIO 功能(ALT5)
③ IOMUXC_SW_PAD_CTL_PAD_x 配置驱动能力、上下拉、压摆率
④ GPIOx_GDIR 设方向(1=输出,0=输入)
⑤ GPIOx_DR 写电平(输出)/ 读电平(输入)
对比记忆 :STM32 里
__HAL_RCC_GPIOA_CLK_ENABLE()+GPIO_Init()帮你做完了 ①②③④,这里全部变成手动写寄存器。你不是在学新东西,是在看 HAL 库的底牌。
第 4 章 ARM 架构
| 视频 | 建议 | 要点 |
|---|---|---|
| 4-1 地址空间_RISC 与 CISC | 必看 | 统一编址 vs 独立编址;ARM 是 RISC(加载/存储架构,运算必须经寄存器) |
| 4-2 ARM 内部寄存器 | 核心必看 | R0--R15、CPSR、SPSR;R13=SP、R14=LR、R15=PC。理解 LR 是理解函数返回与中断现场的关键 |
| 4-3 ARM 汇编 | 慢看+手抄 | MOV / LDR / STR / ADD / B / BL / CMP / BEQ 等 |
| 4-4 ARM 汇编模拟器 | 必看 | 单步执行看寄存器值变化,这是最有性价比的学习工具 |
| 4-5 内存访问指令 | 必看 | LDR/STR、多寄存器 LDM/STM、寻址方式 |
| 4-6 数据处理指令 | 必看 | 算术/逻辑/移位/比较,条件执行是 ARM 特色 |
| 4-7 跳转指令 | 必看 | B / BL / BX / BLX,区分"跳过去还回不回来" |
这一章的坎 :视频密度大、信息量猛。建议每看一节就打开模拟器敲 5 行汇编单步跑一遍,比看三遍视频都管用。
第 5 章 第 1 个程序深度解析
| 视频 | 建议 | 要点 |
|---|---|---|
| 5-1 编程知识_进制 | 快进 | 已掌握 |
| 5-2 编程知识_字节序_位操作 | 必看 | 小端序(低字节存低地址);位操作是寄存器编程基本功 |
| 5-3 汇编_反汇编_机器码 | 必看 | 用 objdump 看机器码,建立"源码 ↔ 机器码"的映射 |
| 5-4 C 与汇编深入分析 | 全课精华 | 函数调用过程、栈帧、参数传递(R0--R3)、返回值 |
| 5-5 纯汇编点灯 | 必看 | 把 C 全部换成汇编,理解 C 只是"人写的方便话" |
核心收获:C 语言里每一行代码,最终都是"几条 ARM 指令 + 几个内存访问"。
第 6 章 使用按键控制 LED
| 视频 | 建议 | 要点 |
|---|---|---|
| 6-1 使用按键控制 LED | 1.5 倍速 | 只新增"输入方向配置 + 读 DR 寄存器"。注意按键消抖------输入方向、上下拉配置 |
第 7 章 串口 UART 编程
| 视频 | 建议 | 要点 |
|---|---|---|
| 7-1 硬件知识_UART 硬件介绍 | 快进 | 波特率、起始/停止位、奇偶校验------STM32 已学过 |
| 7-2 IMX6ULL_UART 操作 | 必看 | 时钟源(PLL3 分频)、UCR1--UCR4、UFCR、UBIR/UBMR 定波特率 |
| 7-3 IMX6ULL_UART 编程 | 必看 | 实现 putchar / puts / printf 重定向 |
工程价值:串口是裸机阶段唯一的"printf 调试"。做完这章,后面所有调试都轻松一个量级。
第 8 章 keil_gcc_Makefile
| 视频 | 建议 | 要点 |
|---|---|---|
| 8-1 KEIL_IDE 背后的命令 | 必看 | 看懂 Keil 输出窗口的那些命令行,你就"脱敏"了 |
| 8-2 安装 windows-gcc 工具_CodeBlocks | 快进 | 照做即可 |
| 8-3 gcc 编译过程详解 | 必看 | 预处理 → 编译 → 汇编 → 链接,四步分别产出 .i / .s / .o / .elf |
| 8-4 Makefile 的引入及规则 | 必看 | 目标:依赖 + Tab + 命令 |
| 8-5 Makefile 语法 | 必看 | 变量、通配符、模式规则 %.o: %.c |
| 8-6 Makefile 函数 | 了解 | wildcard、patsubst,能看懂即可 |
| 8-7 Makefile 实例 | 必看 | 抄一份改一份 |
| 8-8 改造裸机程序的 Makefile | 必看 | 让第 3 章的点灯程序脱离 IDE 编译 |
推荐的核心 Makefile 骨架(背下来):
makefile
CROSS = arm-linux-gnueabihf-
CC = $(CROSS)gcc
LD = $(CROSS)ld
OBJCOPY = $(CROSS)objcopy
OBJDUMP = $(CROSS)objdump
CFLAGS = -Wall -O2 -nostdlib -ffreestanding
LDFLAGS = -T led.lds
objs := start.o led.o
led.bin: $(objs)
$(LD) $(LDFLAGS) -o led.elf $^
$(OBJCOPY) -O binary -S led.elf $@
$(OBJDUMP) -D -m arm led.elf > led.dis
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.S
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.o *.elf *.bin *.dis
第 9 章 代码重定位
| 视频 | 建议 | 要点 |
|---|---|---|
| 9-1 段的概念_重定位的引入 | 必看 | .text / .rodata / .data / .bss 各自性质 |
| 9-2 重定位要做的事情 | 必看 | 把数据的加载地址 搬到运行地址 |
| 9-3 链接脚本使用与分析 | 核心必看 | SECTIONS { . = 0x87800000; .text {...} .data {...} .bss {...} } |
| 9-4 数据段重定位 | 必看 | 写汇编循环搬运 .data |
| 9-5 清除 BSS 段 | 必看 | .bss 只占空间不留数据,运行前必须清零 |
| 9-6 代码段重定位 | 必看 | 理解位置无关码与相对跳转 |
| 9-7 重定位的纯 C 函数实现 | 必看 | 用 C 完成,但要注意函数本身必须已在正确位置运行 |
这是全课最抽象的一章,也是最值钱的一章。 参考链接脚本:
lds
SECTIONS
{
. = 0x87800000;
.text : { *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
第 10 章 异常与中断
| 视频 | 建议 | 要点 |
|---|---|---|
| 10-1 异常与中断概念引入 | 必看 | 异常种类、处理流程(保存现场 → 处理 → 恢复现场) |
| 10-2 ARM 架构中异常与中断的处理 | 必看 | 异常向量表(8 个入口)、向量地址基址 |
| 10-3 异常处理深入分析_保存现场 | 核心必看 | 用 STMFD SP!, {R0-R12, LR} 保存,LDMFD 恢复 |
| 10-4 CPU 模式_状态与寄存器 | 必看 | User/FIQ/IRQ/SVC/Abort/Undef/System 模式与各自的 SP/LR |
| 10-5 ARM_Thumb 指令集示例 | 了解 | Thumb 是 16 位编码,省空间 |
| 10-6 实战_未定义指令异常 | 必看 | 亲手触发异常,看向量表是否生效 |
| 10-7 实战_SVC 异常 | 必看 | SVC 是"主动陷入",Linux 系统调用就靠它 |
| 10-8 中断的硬件框架 | 必看 | 外设 → GIC → CPU,两级控制器 |
| 10-9 GIC 介绍与编程 | 必看 | 分发器 + CPU 接口;使能、优先级、目标 CPU、确认/结束中断 |
| 10-10 实战_GPIO 中断编程 | 必看 | 按键触发 GPIO 中断,完整跑通 |
异常向量表样板:
asm
.text
.global _start
_start:
b reset @ 0x00 复位
b undef_handler @ 0x04 未定义指令
b svc_handler @ 0x08 软中断
b prefetch_handler
b data_handler
nop @ 0x14 保留
b irq_handler @ 0x18 IRQ
b fiq_handler @ 0x1C FIQ
6. 核心难点攻坚
6.1 用寄存器点灯"看见"计算机如何工作(本书精华)
韦东山讲这个例子的厉害之处,在于它把整个计算机的工作模型压缩成了 5 行代码:
c
*(volatile unsigned int *)0x020C406C |= (3 << 26); /* 使能 GPIO1 时钟 */
*(volatile unsigned int *)0x020E0068 = 0x5; /* 引脚复用为 GPIO1_IO03 */
*(volatile unsigned int *)0x020E02F4 = 0x10B0; /* 电气属性配置 */
*(volatile unsigned int *)0x0209C004 |= (1 << 3); /* 方向:输出 */
*(volatile unsigned int *)0x0209C000 &= ~(1 << 3); /* 输出低电平 → 点亮 */
它让你"看见"了什么:
| 现象 | 揭示的本质 |
|---|---|
0x020C406C 这个数字 |
地址空间:整个 SoC 就是一片编了号的"储物柜",外设和内存共用同一套编号(统一编址) |
| 往地址写 1 或 0 | 总线:CPU 发地址 → 地址译码 → 选中某个外设 → 写数据线 → 寄存器值改变 |
| LED 亮了 | 外设寄存器不是抽象概念,就是触发器/锁存器的集合,值改变 → 电压改变 → 硬件动作 |
volatile 不能省 |
编译器不知道这个"内存"会自己变,会被优化掉 |
| 为什么先使能时钟 | 寄存器本身也要耗电,SoC 把每个模块的时钟做成开关 |
一句话:"程序"就是"往哪些地址按什么顺序写什么数" 。指令也是被放在某个地址上的数据,CPU 一遍遍地"取指 → 译码 → 执行"。当你理解了 LED 是被一个数字控制的,你就理解了 CPU 是被一堆数字控制的。
建议动手:把上面 5 个地址的数值改成错误的值(比如去掉时钟使能),观察现象;再在串口打印这些寄存器的读回值。"改一个数,看一个现象"是这门课最高效的学习方法。
6.2 Cortex-M3 与 ARMv7-A 的本质差异
| 维度 | STM32(Cortex-M3) | IMX6ULL(Cortex-A7) |
|---|---|---|
| 指令集 | Thumb-2 为主 | ARM + Thumb,混合 |
| 寄存器组 | 16 个通用寄存器,一套 | 37 个寄存器,按模式分组 |
| 特权/异常 | Thread/Handler 两模式 | 7 种特权模式 + User |
| 中断控制器 | NVIC(核内) | GIC(核外) |
| 中断入口 | 向量表自动跳转,硬件压栈 | 手写向量表 + 手动保存现场 |
| 启动 | 硬件自动完成 | 手写 start.S |
| 内存管理 | 无 MMU(可选 MPU) | 有 MMU,Linux 必需 |
| 编译器 | armcc / arm-none-eabi-gcc | arm-linux-gnueabihf-gcc |
关键结论 :Cortex-M 系列是"对程序员友好的简化版 ARM",Cortex-A 系列是"完整版"。你在 STM32 上"白嫖"的那些自动机制,是 ARM 架构里本来就该程序员做的事。
6.3 汇编与 C 的边界:启动代码
start.S 是裸机程序真正的第一行代码,典型结构:
asm
.global _start
_start:
/* 1. 设置栈指针(不同模式各设一份) */
ldr sp, =0x80200000
/* 2. 可选:关看门狗、初始化时钟 */
/* 3. 重定位 .data(第 9 章内容) */
/* 4. 清 BSS */
/* 5. 跳转到 C 的 main */
bl main
为什么必须用汇编写这一段? 因为 C 语言的运行前提就是"栈已就绪、全局变量已就位"------而这个前提在 C 代码里没法自己建立(先有鸡还是先有蛋)。
6.4 代码重定位与链接脚本(全课最难)
三个必须想清楚的问题:
| 问题 | 答案 |
|---|---|
| 为什么需要重定位? | 程序运行地址与存储地址不一致:代码存在 SD 卡/Flash 的某处,但必须搬到 RAM 的 0x87800000 才能高速运行 |
| 哪些段要搬? | .data 要从加载地址搬到运行地址;.bss 不用搬但必须清零;.text 若位置无关可原地或在 NOR 上直接跑 |
| 怎么搬? | 在链接脚本里定义 __data_load_addr 与 __data_start/__data_end 符号,汇编里循环 LDR/STR 搬运 |
调试口诀:现象是"全局变量初值全是 0 或乱码" → 十有八九是重定位没做或做错;现象是"未初始化变量有随机值" → BSS 没清。
6.5 异常向量表与 GIC 中断
中断处理四步(一定要形成肌肉记忆):
① 硬件:外设产生中断信号 → GIC 分发器记录 → CPU 接口仲裁 → 拉高 IRQ
② CPU:跳转到向量表 0x18 处的 irq_handler(此时自动切到 IRQ 模式,LR 已被设为返回地址)
③ 软件:lr -= 4 → 保存现场(STMFD)→ 读 GIC 的 IAR 得到中断号 → 分发处理 → 写 GIC 的 EOIR 结束中断 → 恢复现场(LDMFD)→ `subs pc, lr, #4` 返回
④ 关键细节:记得清外设自己的中断标志位(GPIOx_ISR),否则中断会反复触发
GIC 编程要点:
GICD_ISENABLERn:使能某个中断号GICD_IPRIORITYRn:设置优先级(数值越小优先级越高)GICD_ITARGETSRn:指定目标 CPUGICC_PMR/GICC_CTLR:CPU 接口允许的中断优先级与总开关GICC_IAR读中断号 →GICC_EOIR写回同一值表示处理完毕
7. 建议时间表(4 周冲刺版 / 6 周稳健版)
| 周次 | 冲刺版(每天 3--4 h) | 稳健版(每天 2 h) |
|---|---|---|
| 第 1 周 | 第 1--3 章 + 第 6、7 章:点灯、按键、串口全部跑通 | 第 1--3 章,点灯吃透 |
| 第 2 周 | 第 4 章 + 第 5 章:汇编与反汇编 | 第 6、7 章 + 第 4 章前半 |
| 第 3 周 | 第 8 章 + 第 9 章:Makefile 与重定位 | 第 4 章后半 + 第 5 章 |
| 第 4 周 | 第 10 章 + 综合项目 | 第 8、9 章 |
| 第 5--6 周 | --- | 第 10 章 + 综合项目 |
综合验收项目(强烈建议做,能串起 90% 的知识点):
用自己写的启动文件 + 链接脚本 + Makefile,实现"按键触发 GPIO 中断 → 中断里通过 UART 打印按键次数 → 同时 LED 闪烁"。全部代码不允许用任何现成库。
8. 调试与验证手段
| 手段 | 用途 | 使用要点 |
|---|---|---|
| LED | 最原始也最可靠 | 程序"跑没跑到这里"的探针 |
| 串口打印 | 主力调试通道 | 先保证 putchar 能用 |
objdump -d |
看反汇编 | arm-linux-gnueabihf-objdump -d led.elf |
readelf -S |
看段布局与地址 | 验证链接脚本是否生效 |
| 汇编模拟器 | 学指令阶段 | 单步看寄存器/内存变化 |
| OpenOCD + GDB | 断点、单步 | 进阶,重定位调不出来时救命 |
| 万用表/示波器 | 硬件层验证 | 怀疑原理图或接线时用 |
排错顺序(黄金流程):先确认程序是否运行(点灯)→ 再确认初始化顺序(时钟→复用→方向)→ 再看地址与位是否正确 → 最后才怀疑硬件。
9. 与后续课程的衔接
| 本课知识点 | 后续用途 |
|---|---|
| 寄存器操作 + 时钟配置 | U-Boot 的 board_init_f、时钟树初始化 |
| 链接脚本 + 重定位 | U-Boot / Linux 内核的 vmlinux.lds,理解内核启动 |
| 异常向量 + GIC | Linux 中断子系统、驱动中的 request_irq |
| Makefile / gcc | 内核 Kconfig + Makefile 构建体系 |
| 串口驱动 | console 与 early_printk |
学完这门课,你就拥有了**"从裸机到操作系统"的最后一公里**,之后学 U-Boot 移植、内核裁剪、驱动开发都会顺得多。
10. 常见问题 FAQ
Q1:我没学过汇编,能直接上手吗?
不能跳。第 4、5 章是全课地基。但可以先用 C 写点灯(第 3 章),建立成就感,再回头补汇编。
Q2:能不能不用 Makefile,继续用 IDE?
建议不用。第 8 章的价值不在"会用 Makefile",而在"理解编译链接过程"------这是第 9 章的入场券。
Q3:SD 卡烧录老是没反应?
90% 是这三点:.bin 文件没生成(objcopy 漏了)、烧录偏移不对(韦东山镜像需放在特定扇区)、开发板拨码开关没设成 SD 卡启动。
Q4:重定位看不懂怎么办?
先把链接脚本里的符号用 readelf -s 打印出来,在纸上画出"Flash 里怎么摆、RAM 里怎么摆",再回来看代码。画图比看视频有效十倍。
Q5:只学"快速通道"跳过第 9、10 章行不行?
可以,但会止步于"会点亮外设"。想走 Linux 驱动路线,第 9、10 章省不掉。
Q6:需要同时学 STM32 和 IMX6ULL 吗?
不必。STM32 是你的"已知锚点",用它对照理解差异即可,不需要在这门课上再复习 STM32。
11. 一页速查:寄存器点灯最小代码
start.S
asm
.text
.global _start
_start:
ldr sp, =0x80200000 @ 设置栈
bl main @ 跳转 C 入口
halt:
b halt
led.c
c
#define CCM_CCGR1 (*(volatile unsigned int *)0x020C406C)
#define IOMUXC_MUX_IO03 (*(volatile unsigned int *)0x020E0068)
#define IOMUXC_PAD_IO03 (*(volatile unsigned int *)0x020E02F4)
#define GPIO1_GDIR (*(volatile unsigned int *)0x0209C004)
#define GPIO1_DR (*(volatile unsigned int *)0x0209C000)
static void led_init(void)
{
CCM_CCGR1 |= (3 << 26); /* 使能 GPIO1 时钟 */
IOMUXC_MUX_IO03 = 0x5; /* 复用为 GPIO1_IO03 */
IOMUXC_PAD_IO03 = 0x10B0; /* 电气属性 */
GPIO1_GDIR |= (1 << 3); /* 输出 */
}
static void delay(volatile int n) { while (n--); }
int main(void)
{
led_init();
while (1) {
GPIO1_DR &= ~(1 << 3); /* 低电平:点亮 */
delay(1000000);
GPIO1_DR |= (1 << 3); /* 高电平:熄灭 */
delay(1000000);
}
return 0;
}
编译烧录三步
bash
make # 生成 led.bin
./imxdownload led.bin /dev/sdX # 烧到 SD 卡
# 插卡上电,串口 115200 观察,LED 闪烁
附:学习心态提醒
裸机开发的挫败感几乎都来自同一件事:"代码看起来没错,但板上没反应" 。这时候不要重看视频,要用现象反推原因 ------LED 不亮,就先用串口确认程序跑到了哪一句;串口没输出,就先用 LED 确认程序有没有进 main。
能"看见"程序在哪一步断掉,比看懂任何一个知识点都重要。 这门课真正的收获不是 10 章知识,而是你从此拥有了"从硅片到代码"的完整视野。
文档版本:v1.0 | 适用课程:韦东山《ARM 架构与编程------基于 IMX6ULL》