嵌入式 考点总结

覆盖实验:实验1 搭建开发环境 | 实验2 GPIO与ARM裸机开发 | 实验3 中断 | 实验4 Uboot与Linux系统 | 实验5 设备驱动
覆盖课件:CH1 嵌入式系统基础 | CH2 ARM硬件平台 | CH3 交叉开发环境


📗 CH1 嵌入式系统基础

1. 嵌入式系统定义 ⭐⭐

IEEE定义: 嵌入式系统是”用于控制、监视或者辅助操作机器和设备的装置”(devices used to control, monitor, or assist the operation of equipment, machinery or plants)。

通俗定义: 嵌入式系统是以应用为中心,以计算机技术为基础,并且软硬件可裁剪,适用于应用系统对功能、可靠性、成本、体积功耗有严格要求的专用计算机系统。

嵌入式系统本身是一个相对模糊的定义。一个手持的MP3和一个PC104工控计算机都可以认为是嵌入式系统。


2. 嵌入式系统特点 ⭐⭐⭐

特点 说明
专用性 为固定用途而定制
可裁剪 资源有限,够用即可
可靠性 长期工作,故障率低
实时性 强实时 / 弱实时
低功耗 首要目标不是高性能而是低功耗

3. PC与嵌入式系统对比 ⭐⭐

硬件对比:

对比项 PC 嵌入式系统
CPU Intel 和 AMD ARM、MIPS、Motorola
内存 内存条 内存芯片
存储设备 硬盘 Flash 芯片
输入设备 键盘、鼠标 按键、触摸屏
输出设备 显示器 LCD、无、控制设备
接口 芯片

软件对比:

对比项 PC 嵌入式系统
引导代码 主板的 BIOS BootLoader(要移植)
OS Windows、Linux WindowsCE、VxWorks、Linux(要移植)
驱动程序 OS自带或下载 自己开发或移植
开发环境 本机开发和调试 借助服务器进行交叉编译
仿真器 不需要 需要

4. 嵌入式系统组成结构 ⭐⭐

1
2
3
嵌入式系统 = 硬件系统 + 软件系统
├── 硬件系统 = 嵌入式处理器 + 最小系统 + 接口电路
└── 软件系统 = 嵌入式OS + 应用软件
  • 最小系统: 处理器 + 最少的外围电路(时钟、复位、电源)
  • 简单嵌入式系统: 硬件 + 监控程序(无OS)
  • 复杂嵌入式系统: 硬件 + 嵌入式OS + 驱动 + 应用

5. 嵌入式处理器分类 ⭐⭐⭐

类型 全称 特点 典型代表
MPU Micro Processor Unit 功能强,需外部扩展存储器和I/O ARM系列、MIPS系列、PowerPC
MCU Micro Controller Unit 片内集成CPU+RAM+Flash+I/O 8051系列、STM32系列
DSP Digital Signal Processor 专用于数字信号处理 TI的TMS320系列
SoC System on Chip 系统级芯片,高度集成 RK3399、Exynos4412

MCU基本含义: 在一块芯片上集成了CPU、存储器(RAM/ROM)、定时器/计数器及多种I/O接口的比较完整的系统。


6. 嵌入式操作系统 ⭐⭐

OS 特点
VxWorks 稳定,异常处理强,商业系统
Windows CE 界面漂亮
μC/OS-II 最佳学习型,开源,实时性好
嵌入式Linux 开源,可裁剪,应用广泛
Android / iOS 移动智能终端
HarmonyOS 华为全场景分布式OS

嵌入式Linux(Embedded Linux)是指对Linux经过小型化裁剪后,能够固化在容量为几百KB到几十MB的存储芯片中,应用于特定嵌入式系统的专用Linux操作系统。


7. 交叉开发环境 ⭐⭐⭐

交叉开发(Cross Developing): 在PC机(宿主机/上位机)上完成程序的编辑、编译、链接等工作;程序的运行在嵌入式设备(目标机)上。

环境 组成
宿主机 PC + Linux + 交叉编译工具链
目标机 实验箱 + 引导程序 + 嵌入式操作系统

使用交叉开发的原因: 嵌入式系统的硬件资源有限,编译所需资源大、耗时长,且嵌入式设备编辑不方便。

在线调试方式:

  • JTAG(Joint Test Action Group):IBM和TI公司提出
  • BDM(Background Debugging Method):Motorola公司提出

8. GCC编译过程 ⭐⭐⭐

C源程序 hello.c 编译成可执行文件,要依次经过四个阶段:

1
hello.c →[预处理 -E]→ hello.i →[编译 -S]→ hello.S →[汇编 -c]→ hello.o →[链接]→ a.out
阶段 选项 功能
预处理 -E 处理 #include 头文件、#define 宏替换,生成 .i 文件
编译 -S 语法检查 → 转换为汇编语言,生成 .s 文件
汇编 -c 汇编代码 → 目标代码(机器码),生成 .o 文件
链接 目标代码 + 库文件 → 可执行文件

GCC编译过程演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 预处理 → 编译 → 汇编 → 链接(一步步执行)
gcc -E hello.c -o hello.i # 预处理:展开头文件、替换宏
gcc -S hello.i -o hello.s # 编译:转换为汇编代码
gcc -c hello.s -o hello.o # 汇编:生成目标文件
gcc hello.o -o hello # 链接:生成可执行文件

# 一步到位(默认输出 a.out)
gcc hello.c
./a.out

# 指定输出文件名
gcc hello.c -o hello
./hello

GCC多文件编译实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* exam1.c */
int sum(int n) {
int temp = 0, i;
for(i = 1; i <= n; i++)
temp += i;
return temp;
}

/* exam2.c */
#include <stdio.h>
main() {
int m;
extern int sum(int sum);
scanf("%d", &m);
printf("The sum is %d", sum(m));
}
1
2
gcc exam1.c exam2.c -o exam    # 多个源文件编译为一个可执行文件
./exam

GCC编译选项:

选项 功能 示例
-o FileName 指定输出文件名(默认 a.out gcc test.c -o test
-c 只编译生成目标文件 .o gcc -c test.c
-g 包含标准调试信息 gcc -g test.c -o test
-O 优化编译,提高执行效率 gcc -O test.c -o test
-l 链接库文件(如 -lpthread gcc pthread.c -lpthread -o pthread

GCC优化效果对比:

1
2
3
4
5
6
7
8
9
# 不优化
gcc optimize.c -o op_1
time ./op_1
# real 0m4.203s

# 优化编译
gcc -O optimize.c -o op_2
time ./op_2
# real 0m1.064s ← 速度提升约4倍

GCC链接库示例(多线程程序):

1
2
3
4
5
6
# 错误:未指定线程库
gcc pthread.c -o pthread
# 报错:undefined reference to `pthread_create'

# 正确:使用 -l 指定链接 libpthread 库
gcc pthread.c -lpthread -o pthread

多文件编译: gcc exam1.c exam2.c -o exam


9. Buildroot与SDK层次架构 ⭐

Buildroot: Linux平台上构建嵌入式Linux系统的框架,由大量Makefile脚本和Kconfig配置文件构成。可通过 make menuconfig 配置,编译出完整系统(Uboot + kernel + rootfs + 应用程序)。

SDK层次架构:

1
2
3
4
5
6
7
8
9
┌─────────────────────────┐
│ Applications(应用层) │ 具体产品功能及交互逻辑
├─────────────────────────┤
│ Libraries(中间件层) │ 系统基础库、第三方开源库、API接口
├─────────────────────────┤
│ Kernel(内核层) │ Linux 4.4 内核
├─────────────────────────┤
│ Bootloader(引导层) │ Uboot
└─────────────────────────┘

编译命令:

1
2
3
./build.sh uboot    # 编译Uboot
./build.sh kernel # 编译内核
./build.sh rootfs # 编译根文件系统

📘 CH2 ARM硬件平台

1. ARM处理器概述 ⭐⭐

ARM处理器特点:

  • 功耗低(以mW计,首要目标不是高性能而是低功耗)
  • 集成丰富的外设接口(尽可能做到SoC)
  • 对实时多任务有很强的支持能力

典型ARM系列:

体系结构 典型内核 特点
ARMv4T ARM7TDMI 3级流水线,无Cache无MMU
ARMv5 ARM920T 5级流水线,16KB Cache,有MMU
ARMv7-A Cortex-A8/A9/A15 有Cache有MMU
ARMv7-M Cortex-M3 高速公路ETC读卡机
ARMv8 Cortex-A53/A72 64位(RK3399使用)

2. ARM处理器工作模式 ⭐⭐⭐

ARM处理器有 7种 工作模式:

模式 缩写 说明
用户模式 usr 正常程序执行模式,不能直接切换到其他模式
快速中断模式 fiq 高速数据传输或通道处理
外部中断模式 irq 通用中断处理
管理模式 svc 操作系统使用的保护模式(复位后默认进入)
中止模式 abt 虚拟内存或存储器保护
未定义指令模式 und 支持硬件协处理器的软件仿真
系统模式 sys 运行具有特权的操作系统任务(ARMv4以上)

其中 usr 为非特权模式,其余6种为特权模式fiq、irq、svc、abt、und 有独立的寄存器组,称为异常模式


3. ARM寄存器 ⭐⭐⭐

通用寄存器: R0~R15

寄存器 别名 用途
R0~R3 参数传递 / 返回值 / 临时变量
R4~R11 局部变量(被调用函数需保存)
R12 ip 过程间临时寄存器(子程序调用时的scratch寄存器)
R13 SP 栈指针(Stack Pointer)
R14 LR 链接寄存器(Link Register),保存函数返回地址
R15 PC 程序计数器(Program Counter)

状态寄存器:

寄存器 用途
CPSR 当前程序状态寄存器,保存条件标志、中断禁止位、当前处理器模式
SPSR 程序状态保存寄存器,异常发生时保存CPSR的副本(5种异常模式各有1个SPSR)

CPSR中关键位:低5位为模式位(如SVC=0x13),bit6为FIQ禁止位,bit7为IRQ禁止位


4. ARM指令分类 ⭐⭐⭐

数据处理指令

指令 功能 示例
MOV 数据传送 MOV R0, #0xFF
MVN 数据取反传送 MVN R0, #0
ADD 加法 ADD R0, R1, R2
SUB 减法 SUB R0, R1, #5
AND 按位与 AND R0, R0, #0xFF
ORR 按位或 ORR R0, R0, #0x01
BIC 位清零 BIC R0, R0, #0x1F
EOR 按位异或 EOR R0, R0, R0
CMP 比较(设置CPSR标志,不保存结果) CMP R1, #0
MRS 读状态寄存器 MRS R0, CPSR
MSR 写状态寄存器 MSR CPSR_c, R0

跳转指令

指令 功能 示例
B 无条件跳转 B Label
BL 带链接跳转(调用子程序,返回地址存入LR) BL main
BX 跳转到寄存器中的地址(常用于函数返回) BX LR
BEQ 条件跳转(Z=1时跳转) BEQ Label

存储器访问指令(LDR/STR系列)

ARM除了寄存器(R0~R15)外没有别的存储单元。所有外围模块都看作是ARM的不同地址单元。

指令 位数 功能 示例
LDR 32位(字) 从存储器加载到寄存器 LDR R3, [R4]
LDRB 8位(字节) 加载字节,高24位清零 LDRB R3, [R1, #8]
LDRH 16位(半字) 加载半字,高16位清零 LDRH R3, [R1, R2]
STR 32位(字) 从寄存器存储到存储器 STR R3, [R1], #8
STRB 8位(字节) 存储低8位 STRB R3, [R1]
STRH 16位(半字) 存储低16位 STRH R3, [R1]

LDR寻址方式:

方式 语法 说明
零偏移 LDR Rd, [Rn] Rn即为地址
前索引偏移 LDR Rd, [Rn, #0x04]! 先更新地址,再访问(!表示写回Rn)
后索引偏移 LDR Rd, [Rn], #0x04 先访问,再更新地址
程序相对偏移 LDR Rd, label 基于PC的偏移(加载标号地址)

批量加载/存储指令(LDM/STM):

堆栈类型 后缀 说明
满递减堆栈 FD 栈满时地址递减(常用
空递减堆栈 ED 栈空时地址递减
满递增堆栈 FA 栈满时地址递增
空递增堆栈 EA 栈空时地址递增
1
2
STMFD SP!, {R0, R4-R12, LR}   @ 多寄存器入栈(保存现场)
LDMFD SP!, {R0, R4-R12, PC} @ 多寄存器出栈(恢复现场)

PUSH = STMFD SP!POP = LDMFD SP!(PUSH/POP是STMFD/LDMFD的别名)

数据交换指令:

指令 功能
SWP 字交换:R2↔[R3]
SWPB 字节交换:R2低8位↔[R3]字节

5. RK3399 SoC详解 ⭐⭐⭐

RK3399 架构:

  • Big.Little架构:双核 Cortex-A72(大核)+ 四核 Cortex-A53(小核),64位
  • CPU频率 > 1.8GHz(大核簇)
  • L1 Cache:A72 各48KB I + 32KB D;A53 各32KB I + 32KB D
  • L2 Cache:大核簇1024KB,小核簇512KB
  • 内部SRAM:总计192KB(启动时bootrom使用4KB)

RK3399 GPIO四步设置(以 GPIO0_A2 引脚为例):

步骤 名称 寄存器地址 操作
1 使能时钟 0xFF750000 + 0x0104 PMUCRU_CLKGATE_CON1 置位19,使能GPIO0时钟
2 设置功能模式 0xFF310000 + 0x0000 PMUGRF_GPIO0A_IOMUX,第21:20位设为11(GPIO模式)
3 设置方向 0xFF720000 + 0x0004 GPIO_SWPORTA_DDR,第2位置1=输出,0=输入
4 设置电平 0xFF720000 + 0x0000 GPIO_SWPORTA_DR,第2位置1=高电平,0=低电平

完整ARM汇编代码(四步合一):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
.global _start
_start:
bl led_test
ret

halt:
b halt

/*************************************************/
led_test:
/* 第一步:使能GPIO0时钟 */
mov x0, #0xFF750000 @ PMUCRU基地址
mov x1, #0xFF750000
ldr w1, [x1, #0x0104] @ 读取 PMUCRU_CLKGATE_CON1
orr w1, w1, #0x80000 @ 第19位置1,使能GPIO0时钟
str w1, [x0, #0x0104] @ 写回寄存器

/* 第二步:设置GPIO0为GPIO功能模式 */
mov x0, #0xFF310000 @ PMUGRF基地址
mov x1, #0xFF310000
ldr w1, [x1, #0x0000] @ 读取 PMUGRF_GPIO0A_IOMUX
orr w1, w1, #0x300000 @ 第21:20位设为11(GPIO模式)
str w1, [x0, #0x0000] @ 写回寄存器

/* 第三步:设置GPIO0_A2为输出方向 */
mov x0, #0xFF720000 @ GPIO0基地址
mov x1, #0xFF720000
ldr w1, [x1, #0x0004] @ 读取 GPIO_SWPORTA_DDR
orr w1, w1, #0x04 @ 第2位置1(输出)
str w1, [x0, #0x0004] @ 写回寄存器

/* 第四步:设置GPIO0_A2输出高电平 */
mov x0, #0xFF720000
mov x1, #0xFF720000
ldr w1, [x1, #0x0000] @ 读取 GPIO_SWPORTA_DR
orr w1, w1, #0x04 @ 第2位置1(高电平)
str w1, [x0, #0x0000] @ 写回寄存器

b halt

RK3399 C语言LED控制程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "led.h"

static void led_delay(void) {
volatile unsigned long int i, j;
for(i = 10; i > 0; i--)
for(j = 10000; j > 0; j--);
}

int main(void) {
led_int();
led_status(0);
while(1) {
led_status_led0(1); led_delay(); led_delay();
led_status_led0(0); led_delay(); led_delay();
led_status_led1(1); led_delay(); led_delay();
led_status_led1(0); led_delay(); led_delay();
led_status_led2(1); led_delay(); led_delay();
led_status_led2(0); led_delay(); led_delay();
led_status_led3(1); led_delay(); led_delay();
led_status_led3(0); led_delay(); led_delay();
}
return 0;
}

GPIO操作的核心是**”读-改-写”**:先从寄存器读取当前值(ldr),修改特定位(orr),再写回寄存器(str)。

Exynos4412 GPIO寄存器组织方式(对比):

寄存器类型 命名规则 功能
端口控制寄存器 GPA0CON ~ GPZCON 配置引脚复用功能
端口数据寄存器 GPA0DAT ~ GPZDAT 读/写引脚电平
端口上拉寄存器 GPA0PUD ~ GPZPUD 配置上拉/下拉电阻
驱动能力寄存器 GPA0DRV ~ GPZDRV 配置驱动强度

📙 CH3 交叉开发环境(BootLoader与Linux系统)

1. BootLoader概述 ⭐⭐⭐

BootLoader定义: 系统加电后运行的第一段代码(通常从地址 0x00000000 开始执行),核心任务是启动操作系统。相当于PC中的 BIOS + MBR

BootLoader通用结构:

阶段 语言 功能
Stage1 汇编 硬件设备初始化 → 为Stage2准备RAM → 复制Stage2到RAM → 设置堆栈 → 跳转Stage2入口
Stage2 C语言 初始化硬件 → 检测内存映射 → 将kernel和rootfs从Flash读到RAM → 设置启动参数 → 引导内核

常用BootLoader: U-Boot(Universal BootLoader)、Blob、ARMboot、RedBoot、vivi


2. U-Boot目录结构 ⭐⭐

目录 存放内容
arch 体系结构相关代码(arm、x86等)
board 核心板相关支持文件(samsung、rockchip等)
common U-Boot命令(bootm、tftp等)
configs 各板配置文件(rk3399_defconfig
driver 驱动程序(网卡、串口、USB等)
fs 文件系统支持(ext4、nfs、jffs2等)
include 头文件(硬件平台支持、系统配置等)
net 网络协议栈(bootp、tftp、rarp)
tools 工具(mkimage 生成U-Boot镜像)

配置命令:

1
2
make rk3399_defconfig    # 加载默认配置
make menuconfig # 图形化配置

3. Linux内核启动流程 ⭐⭐

Image的生成过程:

1
vmlinux(原始内核,~23MB)→[objcopy去掉符号表]→ Image(纯内核镜像,~18MB)

Linux启动流程:

1
2
3
4
5
arch/arm64/kernel/head.S(汇编入口)
→ start_kernel()(init/main.c)
→ rest_init()
→ kernel_init()(启动init进程)
→ 用户空间(加载rootfs中的init脚本)

vmlinux.lds 是内核链接脚本,决定了各段在内存中的位置


4. 根文件系统(rootfs) ⭐⭐⭐

rootfs 与 kernel 的关系:

  • 根文件系统和内核是完全独立的两个部分
  • 单独的Linux内核没法正常工作,必须搭配根文件系统
  • rootfs包含:内核模块(.ko)、/etc/fstab(自动挂载列表)、/etc/inittab(init配置)、应用程序、库文件等

Linux关键目录文件:

文件 用途
/etc/fstab 自动挂载的文件系统列表
/etc/inittab init进程的配置文件
/bin 系统必备命令(cp、ls、mount、rm)
/sbin 系统管理命令(insmod、fdisk、reboot)

Linux文件系统格式(补充):

类型 格式
传统硬盘 ext2、ext3、ext4、XFS
闪存 JFFS2、YAFFS
网络 NFS
虚拟/特殊 swap、procfs、sysfs、debugfs、devfs

📗 实验1 搭建开发环境

1. eAIOT实验平台 ⭐

平台组成:

  • 左侧:高性能嵌入式主板(核心板),采用瑞芯微 RK3399(64位ARM SoC),标配 4GB 内存 + 16GB 闪存
  • 右侧:无线传感器网络扩展板(Zigbee、BLE、LORA、NB-IoT、RFID、10+传感器)
  • 核心板以金手指形式插入底板,板载 2×2 MIMO 双天线 WiFi 模组

支持运行的OS: Android 8.1、Ubuntu 18.04、Armbian、Buildroot、Linux

开机流程(按顺序):

  1. 将各类天线、网线正确接入
  2. 将 Type-C 数据线插入 debug 或 download 口
  3. 确保主板电源开关处于靠左关闭状态
  4. 插入白色 12V 电源插头
  5. 向右拨动电源开关启动系统

⚠️ 请严格按照以上步骤开机,减少瞬时电涌对主板接口的损害。Type-C 接口较为脆弱,请勿暴力抽拽。


2. Linux文件系统 ⭐⭐

文件系统类型:

文件系统 特点
ext2 早期文件系统,非日志文件系统,已不推荐
ext3 在 ext2 基础上发展,日志文件系统,完全兼容 ext2
ext4 在 ext3 基础上发展,性能和可靠性更佳,向下兼容 ext3 和 ext2

Linux根目录重要文件夹:

目录 用途
/bin 二进制可执行命令文件
/sbin 系统命令
/root 超级用户 root 的根目录
/home 普通用户默认目录
/boot 系统内核和启动文件
/dev 设备文件
/etc 系统管理配置文件
/lib 系统运行所需库文件
/proc 虚拟目录,保存系统信息和进程信息
/tmp 临时文件,所有用户可读写
/var 变化文件,如日志
/usr 应用程序和库文件
/sys 系统设备和文件层次结构

查询当前文件系统版本:df -T -h


3. 常用Shell命令 ⭐⭐

Shell命令格式: command -options [argument]

基础命令:

命令 语法示例 功能
ls ls -l /home 列出目录内容(-l详细列表,-a显示隐藏文件)
cd cd /home/xuelitec 切换目录(cd ~回用户目录,cd ..返回上级)
pwd pwd 显示当前所在目录的完整路径
uname uname -a 显示系统信息(-a显示全部内核信息)
sudo sudo apt-get install xxx 以超级用户权限执行命令
man man ls 查看命令手册(按 q 退出)
df df -T -h 查看磁盘使用情况(-T显示文件系统类型,-h人性化显示)

文件操作命令:

命令 语法示例 功能
touch touch test1.c 创建空文件 / 更新文件时间戳
mkdir mkdir -p dir/subdir 创建目录(-p递归创建多级目录)
rm rm -rf dir/ 删除文件(-r递归删除目录,-f强制)
rmdir rmdir empty_dir/ 删除空目录(目录非空会报错)
cp cp file1.c /home/backup/ 复制文件
mv mv oldname.c newname.c 移动文件 / 重命名
chmod chmod 755 test1.c 修改文件权限
find find /home -name "*.c" 按条件查找文件
grep grep "include" main.c 在文件中搜索字符串

压缩解压命令:

命令 语法示例 功能
tar tar -zxvf file.tar.gz 解压 .tar.gz-zgzip,-x解压,-v显示过程,-f指定文件)
tar tar -cvzf dir.tar.gz dir/ 压缩为 .tar.gz-c创建)
zip zip archive.zip file1 file2 压缩为 .zip
unzip unzip archive.zip 解压 .zip

文件权限详解(chmod):

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查询权限
ls -l test1.c
# 输出:-rwxr-xr-- 1 user group 1024 May 7 10:00 test1.c
# [-][rwx][r-x][r--]
# | | | |
# | | | └─ 其他用户:只读(r--)
# | | └─────── 同组用户:读+执行(r-x)
# | └───────────── 所有者:读+写+执行(rwx)
# └──────────────── 文件类型(-普通文件,d目录)

# 修改权限 - 数字法
chmod 755 test1.c # rwxr-xr-x(7=rwx, 5=r-x, 5=r-x)
chmod 644 test1.c # rw-r--r--(6=rw-, 4=r--, 4=r--)

4. 开发环境搭建 ⭐

Ubuntu和Windows文件互传: FTP服务

NFS服务安装:

1
sudo apt-get install nfs-kernel-server

SSH服务安装:

1
sudo apt-get install openssh-server

交叉编译工具链: arm-linux-gnueabihf-gcc(用于编译裸机程序)/ aarch64-linux-gnu-gcc(用于编译64位程序)


📘 实验2 GPIO与ARM裸机开发

1. ARM处理器与GPIO ⭐⭐⭐

RK3399 SoC 双处理器:

  • Cortex-A53(64位,小核)
  • Cortex-A72(64位,大核)

GPIO操作步骤(裸机):

  1. 使能GPIO时钟
  2. 设置GPIO复用功能(配置MUX寄存器)
  3. 设置GPIO方向(输入/输出)
  4. 设置GPIO输出电平(高低)

RK3399关键寄存器:

  • PMUGRF_GPIO0A_IOMUX(地址 0xFF310000):GPIO复用功能配置
  • GPIO0_SWPORTA_DR:GPIO数据寄存器(写数据)
  • GPIO0_SWPORTA_DDR:GPIO方向寄存器(0=输入,1=输出)

2. ARM启动文件(start.S) ⭐⭐⭐

start.S完成的主要功能:

  1. 设置异常向量表(Exception Vector)
  2. 关闭 IRQ、FIQ,设置 SVC 模式
  3. 关闭 L1 cache,设置 L2 cache,关闭 MMU
  4. 根据 OM 引脚确定启动方式
  5. 在 SoC 内部 SRAM 中设置栈
  6. 执行 lowlevel_init 函数(初始化系统时钟、SDRAM、串口等)
  7. 设置 SDRAM 中的栈

⚠️ 异常向量表必须从 0x000000000xFFFF0000 开始,每条向量占4字节


3. ARM汇编与C语言混合编程 ⭐⭐

关键汇编指令:

指令 语法示例 功能
LDR LDR R0, =0xFF310000 将地址/数据加载到寄存器(=表示伪指令,加载立即数到R0)
STR STR R0, [R1] 将寄存器的值存储到内存地址
MOV MOV R0, #0xFF 将立即数移动到寄存器
BL BL main 带链接的跳转(调用函数,将返回地址存入LR)
B B loop 无条件跳转(不保存返回地址)
BX BX LR 跳转到寄存器中的地址(常用于函数返回,LR存放返回地址)
PUSH PUSH {R0, R1, LR} 将寄存器压栈(保存现场)
POP POP {R0, R1, PC} 从栈中弹出寄存器(恢复现场,PC为程序计数器)
MRS MRS R0, CPSR 读取状态寄存器CPSR到通用寄存器
MSR MSR CPSR_c, R0 写入通用寄存器的值到状态寄存器

汇编中调用C函数: 使用 BL main 跳转到C语言入口点,函数返回时通过 BX LR 回到汇编

实际应用示例(start.S中设置SVC模式并关中断):

1
2
3
4
5
MRS R0, CPSR          @ 读取当前状态寄存器
BIC R0, R0, #0x1F @ 清除低5位(模式位)
ORR R0, R0, #0x13 @ 设置为SVC模式(0x13 = 10011)
ORR R0, R0, #0xC0 @ 关闭IRQ(bit 7)和FIQ(bit 6)
MSR CPSR_c, R0 @ 写回CPSR

栈设置与调用C函数示例:

1
2
3
LDR SP, =0x00200000   @ 设置栈指针SP(SDRAM顶部往下)
BL main @ 调用C语言main函数
B . @ 如果main返回则死循环

4. 交叉编译与烧写 ⭐⭐

编译步骤:

1
2
3
4
5
6
7
8
# 编译 start.S
arm-linux-gnueabihf-gcc -c start.S -o start.o
# 编译 main.c
arm-linux-gnueabihf-gcc -c main.c -o main.o
# 链接
arm-linux-gnueabihf-ld -Ttext 0x00000000 start.o main.o -o led.elf
# 生成bin文件
arm-linux-gnueabihf-objcopy -O binary led.elf led.bin

烧写命令(Uboot下):

1
2
3
4
# 下载到内存
loadx 0x00200000 # 使用xmodem协议
# 执行
go 0x00200000

Makefile编写要点:

  • CROSS_COMPILE = arm-linux-gnueabihf-
  • 使用 -march=armv7-a 指定架构
  • 链接脚本 .ld 指定各段在内存中的位置

5. Makefile 编写 ⭐⭐⭐

Makefile 是工程管理工具,通过依赖关系自动判断哪些文件需要重新编译,大幅提高编译效率。

基本语法

1
2
目标(target): 依赖(prerequisites)
[Tab] 命令(command)

规则:

  • 目标:要生成的文件,或是一个”伪目标”(如 clean
  • 依赖:生成目标所依赖的文件列表
  • 命令:生成目标的具体操作(必须用 Tab 开头

变量与自动变量

Makefile变量分为三类:

变量/符号 含义 示例
$(CC) 编译器 CC = arm-linux-gnueabihf-gcc
$(LD) 链接器 LD = arm-linux-gnueabihf-ld
$(OBJCOPY) 格式转换工具 OBJCOPY = arm-linux-gnueabihf-objcopy
$@ 当前目标名 led.bin: led.elf$@ = led.bin
$< 第一个依赖名 led.o: led.S$< = led.S
$^ 所有依赖名(空格分隔) app: a.o b.o$^ = a.o b.o
?= 若未定义则赋值 CROSS_COMPILE ?= arm-linux-gnueabihf-

Makefile预定义变量(Make自带,可直接引用):

变量 默认值 用途
AR ar 库文件维护程序
AS as 汇编程序
CC cc C编译器
CPP $(CC) -E C预编译器
CXX g++ C++编译器
RM rm -f 文件删除命令
CFLAGS C编译器选项
LDFLAGS 链接器选项

常用编译变量

变量 用途 常用值
CFLAGS C编译选项 -march=armv7-a -mfloat-abi=hard -mfpu=neon
LDFLAGS 链接选项 -Ttext 0x00000000 -Tlink.ld
CROSS_COMPILE 交叉编译前缀 arm-linux-gnueabihf-
TARGET 最终目标文件名 led.bin

伪目标 .PHONY

1
2
3
.PHONY: clean     # 声明 clean 为伪目标,避免目录下有同名文件时 make 不执行
clean:
rm -f *.o *.elf *.bin

为什么需要 .PHONY 若目录下存在一个名为 clean 的文件,make clean 会认为目标已最新而跳过。.PHONY 告诉 make 无论是否有同名文件都执行该规则。

裸机开发完整 Makefile 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 交叉编译工具链前缀
CROSS_COMPILE = arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
CFLAGS = -march=armv7-a -marm -Wall

TARGET = led
OBJS = start.o main.o

# 默认目标(第一个目标)
all: $(TARGET).bin

# 链接:将 .o 文件链接为 .elf
$(TARGET).elf: $(OBJS)
$(LD) -Ttext 0x00000000 $^ -o $@

# 生成二进制文件
$(TARGET).bin: $(TARGET).elf
$(OBJCOPY) -O binary $< $@

# 编译 .S → .o
%.o: %.S
$(CC) -c $(CFLAGS) $< -o $@

# 编译 .c → .o
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

# 伪目标:清理
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET).elf $(TARGET).bin

内核模块 Makefile(实验5 用)

1
2
3
4
5
6
7
8
9
10
# 内核模块 Makefile(放在模块源码同一目录)
KDIR := /lib/modules/$(shell uname -r)/build # 内核源码目录

obj-m += chrdev.o # 指定要编译为模块的目标

all:
$(MAKE) -C $(KDIR) M=$(PWD) modules # 进入内核目录编译模块

clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean # 清理

关键点: 内核模块 Makefile 与普通 Makefile 不同,obj-m 表示编译为模块,-C 指定内核源码目录,M=$(PWD) 指定模块源码目录。

RK3399实际交叉编译 Makefile(课件原版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# RK3399 裸机交叉编译 Makefile(带链接脚本)
PREFIX = aarch64-linux-gnu-
CC = $(PREFIX)gcc
LD = $(PREFIX)ld
AR = $(PREFIX)ar
OBJCOPY = $(PREFIX)objcopy
OBJDUMP = $(PREFIX)objdump

# 添加 GCC 运行时库
PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc

rk3399.bin: start.S
$(CC) -nostdlib -g -c -o start.o start.S
$(LD) -Trk3399.lds -g start.o -o rk3399_elf $(PLATFORM_LIBS)
$(OBJCOPY) -O binary -S rk3399_elf rk3399.bin
$(OBJDUMP) -D -m arm rk3399_elf > rk3399.dis

clean:
rm -f rk3399.dis rk3399.bin rk3399_elf *.o

与简化版Makefile的区别: 使用 -nostdlib 不链接标准库(裸机无OS)、-Trk3399.lds 指定链接脚本控制内存布局、OBJDUMP 生成反汇编文件用于调试

Make 常用命令

命令 功能
make 执行 Makefile 中第一个目标(通常是 all
make clean 执行 clean 目标,清理编译产物
make -j4 4个线程并行编译,加快速度
make V=1 显示详细的编译命令(调试用)

📙 实验3 中断

1. 中断基本概念 ⭐⭐⭐

中断定义: CPU在执行程序的过程中,当出现某些突发事件时,CPU暂停当前程序,转去处理该事件,处理完毕后再返回原来被中断的位置继续执行。

中断类型:

类型 来源 特点
IRQ 外设中断 普通中断,可屏蔽
FIQ 快速中断 优先级高于IRQ,用于高速数据传输
SWI/SVC 软件中断 用户态切换到内核态
Reset 复位 最高优先级
Undefined Instruction 未定义指令 执行未定义指令时触发
Data Abort 数据访问异常 数据读写出错
Prefetch Abort 指令预取异常 指令读取出错

2. RK3399 GPIO中断实验 ⭐⭐

GPIO中断配置步骤:

  1. 使能GPIO时钟
  2. 设置GPIO复用功能为GPIO模式
  3. 设置GPIO方向为输入
  4. 配置GPIO中断触发方式(上升沿/下降沿/高电平/低电平)
  5. 使能GPIO中断
  6. 注册中断服务函数(ISR)

按键消抖: 软件延时消抖(约10~20ms)


3. ARM异常处理 ⭐⭐

异常向量表:

地址偏移 异常类型
0x00 Reset
0x04 Undefined Instruction
0x08 SWI/SVC
0x0C Prefetch Abort
0x10 Data Abort
0x14 未使用
0x18 IRQ
0x1C FIQ

中断处理流程:

  1. 保存现场(将寄存器压栈)
  2. 执行中断服务程序(ISR)
  3. 恢复现场(寄存器出栈)
  4. 中断返回

4. GIC中断控制器 ⭐⭐

GIC(Generic Interrupt Controller)功能:

  • 中断优先级管理
  • 中断路由(分发到哪个CPU核心)
  • 中断屏蔽与使能
  • 中断状态跟踪(active/pending/inactive)

📕 实验4 Uboot与Linux系统

1. RK3399启动过程 ⭐⭐⭐

完整启动流程:

1
上电复位 → romcode → BL1(SRAM)→ BL2(DDR)→ OS Kernel → Rootfs

详细步骤:

  1. RK3399系统上电复位后,Cortex-A53从地址 0xFFFF0000 执行romcode
  2. romcode依次按以下顺序查找介质上的ID BLOCK信息:
    • SPI NOR FLASH → SPI NAND FLASH → eMMC → SD/MMC → USB
  3. romcode功能:查找ID BLOCK → 确定启动方式 → 初始化硬件 → 将BL1加载到内部SRAM
  4. BL1执行DDR初始化,将BL2复制到DDR,跳转到BL2
  5. BL2初始化硬件和软件,将OS复制到DDR,跳转到OS
  6. 启动操作系统

2. Bootloader (Uboot) ⭐⭐⭐

Bootloader定义: 系统启动后、OS内核运行之前运行的一段小程序,用于初始化硬件设备、建立内存映射,为调用OS内核准备好环境。

Bootloader两种操作模式:

模式 说明
启动加载模式(自主模式) 从固态存储设备将OS加载到RAM运行,无需用户介入。是产品发布时的正常工作模式
下载模式 通过串口/网络从主机下载内核映像和根文件系统。用于第一次安装和系统更新,提供命令行接口

BL1阶段(Stage1,汇编实现):

步骤 功能
1 硬件设备初始化
2 为加载Stage2准备RAM空间
3 复制Stage2到RAM空间
4 设置堆栈(为执行C语言代码做准备)
5 跳转到Stage2的C入口点

BL2阶段(Stage2,C语言实现):

步骤 功能
1 初始化本阶段使用的硬件设备
2 检测系统内存映射(memory map)
3 将内核映像和根文件系统从Flash读到RAM
4 为内核设置启动参数
5 调用内核

3. Uboot源码启动流程 ⭐⭐

start.S 文件(BL1阶段):

  1. 指定Uboot入口
  2. 设置异常向量
  3. 关闭IRQ、FIQ,设置SVC模式
  4. 关闭L1 cache,设置L2 cache,关闭MMU
  5. 根据OM引脚确定启动方式
  6. 在SoC内部SRAM中设置栈
  7. 执行 lowlevel_init(初始化系统时钟、SDRAM、串口)
  8. 设置开发板供电锁存
  9. 设置SDRAM中的栈

_main 函数(BL1→BL2过渡):

  1. 将Uboot从存储介质复制到SDRAM(BL2加载到RAM并跳转)
  2. 设置并开启MMU
  3. 在SDRAM中合适位置设置栈
  4. 清除BSS段,BL1阶段完毕

board_init_r 函数(BL2阶段):

  1. 规划Uboot内存使用
  2. 遍历调用 init_sequence 初始化函数数组
  3. 初始化堆管理器
  4. 初始化SD/MMC控制器
  5. 环境变量重定位
  6. 目标机硬件设备初始化
  7. 控制台初始化
  8. 网卡芯片初始化
  9. 进入主循环 main_loop(出现Uboot提示符)

4. Linux内核 ⭐⭐

Linux内核主要功能:

功能 说明
进程管理 进程创建/销毁,调度策略,处理器资源共享,进程间通信
内存管理 虚拟地址空间,地址映射,多进程安全共享内存
文件管理 虚拟文件系统(VFS),支持ext3/ext4等数十种文件系统
设备管理 设备驱动程序,每种外设对应特定驱动代码
网络管理 网络协议栈 + 网络设备驱动程序

Linux内核目录结构:

目录 内容
arch 架构相关代码(ARM、x86等)
block 块设备(SD卡、eMMC、NAND、硬盘等)
drivers 驱动程序(drivers/i2cdrivers/gpio等)
fs 文件系统
init 初始化代码(含 main.cstart_kernel
ipc 进程间通信
kernel 调度器等核心代码
mm 内存管理
net 网络协议栈

Linux内核启动顺序:

1
内核引导 → 运行init → 系统初始化 → 建立终端 → 用户登录系统

内核编译命令:

1
2
3
4
5
6
7
# 编译uboot
sudo ./build.sh uboot
# 生成:rk3399_loader_v1.22.119.bin, trust.img, uboot.img

# 编译内核
sudo ./build.sh kernel
# 生成:kernel.img, resource.img, boot.img

内核配置命令:

1
2
3
# 加载实验平台默认配置
make lpkt030_linux_defconfig # 或 rk3399_defconfig
# 编译后在 arch/arm64/boot/ 目录下生成 Image 镜像

📓 实验5 设备驱动(一)

1. 应用程序与驱动的关系 ⭐⭐⭐

调用流程:

1
应用程序(用户空间) → C库函数(open/read/write) → 系统调用 → 内核空间 → 驱动程序
  • 应用程序运行在用户空间
  • 驱动程序运行在内核空间
  • 用户空间不能直接操作内核,必须通过系统调用陷入内核空间
  • open、close、write、read 等函数由 C 库提供

2. file_operations 结构体 ⭐⭐⭐

定义位置: include/linux/fs.h

file_operations 是Linux内核驱动操作函数集合

成员 功能
owner 拥有该结构体的模块指针,设为 THIS_MODULE
read 读取设备文件
write 向设备文件写入数据
open 打开设备文件
release 关闭设备文件(对应应用层 close

开发字符设备驱动最主要的工作就是实现 file_operations 中的函数


3. 驱动模块加载与卸载 ⭐⭐⭐

驱动运行方式:

  • 编译进Linux内核:内核启动时自动运行
  • 编译为模块(.ko):内核启动后使用命令加载(调试时推荐)

模块注册函数:

1
2
module_init(xxx_init);   // 注册模块加载函数,insmod时调用xxx_init
module_exit(xxx_exit); // 注册模块卸载函数,rmmod时调用xxx_exit

模块加载命令:

1
2
3
insmod drv.ko        # 加载指定模块
modprobe drv.ko # 加载模块(自动处理依赖)
rmmod drv.ko # 卸载模块

4. 字符设备注册与注销 ⭐⭐

1
2
3
4
5
6
// 注册字符设备
register_chrdev(major, name, fops);
// major: 主设备号; name: 设备名; fops: file_operations指针

// 注销字符设备
unregister_chrdev(major, name);

设备号:

  • 每个设备有一个设备号,分为主设备号和次设备号
  • 应用程序通过设备号使用设备驱动
  • 主设备号:标识驱动程序(同类驱动共用)
  • 次设备号:标识具体设备实例

创建设备文件:

1
mknod /dev/chrdev c 200 0    # c=字符设备 200=主设备号 0=次设备号

5. 内核空间与用户空间数据传递 ⭐⭐⭐

两个空间不能直接互相访问,必须使用专用函数:

1
2
3
4
5
// 内核空间 → 用户空间
unsigned long copy_to_user(void *to, const void *from, unsigned long count);

// 用户空间 → 内核空间
unsigned long copy_from_user(void *to, const void *from, unsigned long count);
参数 含义
to 传递的目标地址
from 传递的起始地址
count 传递的数据长度
返回值 未成功传递的数据长度

6. 地址映射(MMU) ⭐⭐⭐

MMU(Memory Manage Unit)功能:

  1. 虚拟空间到物理空间的映射
  2. 内存保护,设置访问权限和缓冲特性

关键概念:

  • 虚拟地址(VA):CPU访问的地址
  • 物理地址(PA):实际硬件地址
  • Linux内核启动时初始化MMU,CPU访问的都是虚拟地址

地址映射函数:

1
2
3
4
5
// 物理地址 → 虚拟地址
void __iomem *ioremap(unsigned long phys_addr, size_t size);

// 释放映射
void iounmap(volatile void __iomem *addr);

示例:

1
2
3
4
// 映射
static void __iomem *PMUGRF_GPIO0A_IOMUX = ioremap(PMUGRF_GPIO0A_IOMUX_BASE, 4);
// 卸载时取消映射
iounmap(PMUGRF_GPIO0A_IOMUX);

7. I/O内存访问函数 ⭐⭐

ARM体系下只有I/O内存(无I/O端口概念)

读操作函数:

函数 位数
readb(addr) 8-bit
readw(addr) 16-bit
readl(addr) 32-bit

写操作函数:

函数 位数
writeb(value, addr) 8-bit
writew(value, addr) 16-bit
writel(value, addr) 32-bit

寄存器操作示例:

1
2
3
4
// 读-改-写操作
val = readl(PMUGRF_GPIO0A_IOMUX); // 读取寄存器
val |= (3 << (4+16)) | (0 << 4); // 修改特定位
writel(val, PMUGRF_GPIO0A_IOMUX); // 写回寄存器

8. 字符设备驱动开发完整步骤 ⭐⭐

  1. 编写驱动代码:实现 file_operations 中的 open/release/read/write
  2. 编写Makefile:使用内核构建系统(KDIR指向内核源码目录)
  3. 编译驱动make 生成 .ko 模块文件
  4. 编译测试程序aarch64-linux-gnu-gcc chrdevApp.c -o chrdevApp
  5. 加载模块insmod chrdev.ko
  6. 创建设备文件mknod /dev/chrdev c 200 0
  7. 运行测试./chrdevApp

📝 重点题型总结

简答题:嵌入式系统定义与特点

定义要点: 以应用为中心、计算机技术为基础、软硬件可裁剪、专用计算机系统
特点记忆: 专裁可实低(专用性、可裁剪、可靠性、实时性、低功耗)

简答题:嵌入式处理器分类

MPU(微处理器)需外扩存储器;MCU(微控制器)片上集成CPU+RAM+Flash+I/O;DSP(数字信号处理器)专用于信号处理;SoC(片上系统)高度集成

简答题:ARM处理器工作模式

7种模式: usr(用户)、fiq(快速中断)、irq(外部中断)、svc(管理/复位默认)、abt(中止)、und(未定义指令)、sys(系统)
特权模式: 除usr外的6种;异常模式: fiq、irq、svc、abt、und(各有独立SPSR)

简答题:ARM寄存器

R13=SP(栈指针)、R14=LR(链接寄存器,保存返回地址)、R15=PC(程序计数器)
CPSR(当前状态寄存器)、SPSR(异常模式下保存CPSR副本,5种异常模式各1个)

程序分析题:ARM汇编指令

掌握指令功能识别:MOV(传送)、ADD/SUB(加减)、AND/ORR/BIC/EOR(位运算)、CMP(比较,设标志)、LDR/STR(存储器访问)、B/BL/BX(跳转)、MRS/MSR(状态寄存器访问)、PUSH/POP(栈操作)
LDR寻址辨析: 零偏移[Rn]、前索引[Rn,#off]!、后索引[Rn],#off

简答题:交叉开发环境

宿主机(PC + Linux + 交叉编译工具链)负责编辑编译;目标机(实验箱 + BootLoader + OS)负责运行
在线调试: JTAG(IBM/TI)、BDM(Motorola)

简答题:GCC编译过程

四阶段: 预处理(-E→.i) → 编译(-S→.s) → 汇编(-c→.o) → 链接(→可执行文件)

简答题:rootfs与kernel的关系

完全独立的两个部分。 单独内核无法工作,必须搭配rootfs(包含内核模块、/etc/fstab、/etc/inittab、应用程序、库文件等)

简答题:RK3399启动过程

记忆线索: 上电 → romcode(0xFFFF0000) → 查ID BLOCK(SPI NOR→NAND→eMMC→SD→USB) → BL1(SRAM, DDR初始化) → BL2(DDR, 加载OS) → 启动OS

简答题:Bootloader两种模式

启动加载模式 = 自主模式,从存储设备加载OS,产品发布用;下载模式 = 从主机下载,首次安装和更新用

简答题:BL1/BL2阶段功能

**BL1(汇编)**:硬件初始化 → 准备RAM → 复制BL2 → 设栈 → 跳转C入口
**BL2(C)**:初始化硬件 → 检测内存 → 读内核/根文件系统 → 设启动参数 → 调用内核

简答题:Linux内核主要功能

记忆:进程、内存、文件、设备、网络

简答题:内核空间与用户空间数据传递

copy_to_user = 内核→用户;copy_from_user = 用户→内核;两个空间不能直接访问

简答题:地址映射

ioremap = 物理地址→虚拟地址;iounmap = 释放映射;因为MMU开启后CPU只能访问虚拟地址

简答题:file_operations结构体

是Linux内核驱动操作函数集合,包含 open/read/write/release 等函数指针

简答题:设备号与设备文件

mknod /dev/chrdev c 200 0:c=字符设备,200=主设备号,0=次设备号;应用通过设备号使用驱动


💡 考试提示:

  • 嵌入式系统定义与特点(专用性、可裁剪、可靠性、实时性、低功耗)可能考填空/简答
  • 嵌入式处理器分类(MPU/MCU/DSP/SoC)需区分
  • ARM工作模式(7种,特别是usr/svc/irq/fiq)可能考简答或选择
  • ARM寄存器(R13=SP, R14=LR, R15=PC, CPSR/SPSR)必考
  • ARM指令(MOV/ADD/SUB/CMP/LDR/STR/B/BL/BX/PUSH/POP/MRS/MSR)必考程序分析
  • LDR寻址方式(零偏移、前索引、后索引)可能考辨析
  • RK3399启动过程(romcode地址、ID BLOCK查找顺序)必考简答
  • RK3399 GPIO四步(CRU→GRF→方向→电平)必考
  • Bootloader两种模式和BL1/BL2功能必考简答
  • GCC编译过程(预处理→编译→汇编→链接)及编译选项(-c/-o/-g/-O/-l)可能考
  • Linux内核五大功能必考简答
  • 字符设备驱动开发步骤(加载/卸载、注册/注销、数据传递、地址映射)为实验5重难点
  • Makefile编写(变量、自动变量、伪目标、内核模块Makefile)可能考简答或写Makefile
  • 根文件系统与内核的独立性可能考简答
  • ARM异常向量表(7种异常)需记忆地址偏移