欢迎来到本项目,这是一份面向中文社区的系统、全面且贴近实战的嵌入式软件开发学习路线和知识点总结。涵盖范围包括 C/C++、嵌入式开发、驱动开发、计算机网络原理、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链等相关知识点。收集全网相关书籍和面试经历分享给各位。
2,704
0 commits
updated Jun 28, 2025
嵌入式软件学习路线图 · C语言 / RTOS / Linux驱动 · 从入门到进阶
欢迎来到本项目,这里拥有系统、全面且贴近实战的2025年嵌入式软件开发学习路线和知识点总结。
涵盖包括C语言、驱动开发、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链等嵌入式软件开发所需知识点
以及嵌入式开发相关的必读书籍、面试题以及面经。
跟随目录点击选择想学习的部分即可跳转至相应知识点。
爆肝两周只为给大家呈现最新开源免费的嵌入式软件学习资料!无任何套路!
能够帮助到你的话请点个star收藏一下推给更多有需要的人就是最大鼓励,不胜感激!
类型 变量名 [= 初始值];int count = 10;
float temperature;
char c = 'A';
int, short, long, long long, unsignedfloat, doublecharunsigned int u = 100;
long long big_number = 12345678900LL;
常用 C 关键字解释如下:
| 关键字 | 说明 |
|---|---|
const | 定义只读变量 |
volatile | 防止编译器优化,常用于寄存器 |
static | 变量作用域或函数仅在本文件可见 |
extern | 声明外部变量/函数 |
typedef | 为数据类型取别名 |
static int counter = 0; // 内部链接
extern int g_value; // 声明外部变量
volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问
10, 3.14, 'a', "abc"#define 或 const 定义 #define PI 3.14159
const int MAX_SIZE = 100;
void func(int a, int b) {
int sum = a + b; // sum存储在栈上
// ...
} // 函数返回时,sum和参数a、b自动释放
malloc / free 手动分配和释放void createArray() {
int* arr = (int*)malloc(10 * sizeof(int)); // 从堆分配内存
if (arr != NULL) {
arr[0] = 100; // 使用堆内存
// ...
}
free(arr); // 手动释放内存
}
栈 vs 堆的对比
| 特性 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 分配方式 | 自动(由编译器管理) | 手动(如 malloc/free 或 new/delete) |
| 生命周期 | 函数调用期间自动创建和销毁 | 程序员控制,需手动释放 |
| 内存空间 | 连续、有限(通常几 MB) | 不连续、较大(受限于物理内存) |
| 访问速度 | 快(通过寄存器快速访问) | 慢(通过指针间接访问) |
| 内存碎片 | 不存在(先进后出结构) | 可能产生(频繁分配和释放) |
| 使用场景 | 局部变量、函数调用栈帧 | 动态数据结构、跨函数共享数据 |
指针的作用: 可以通过指针间接访问内存
指针变量定义语法: 数据类型 * 变量名;
示例:
int main() {
//1、指针的定义
int a = 10; //定义整型变量a
//指针定义语法: 数据类型 * 变量名 ;
int * p;
//指针变量赋值
p = &a; //指针指向变量a的地址
cout << &a << endl; //打印数据a的地址
cout << p << endl; //打印指针变量p
//2、指针的使用
//通过*操作指针变量指向的内存
cout << "*p = " << *p << endl;
system("pause");
return 0;
}
指针变量和普通变量的区别
总结1: 我们可以通过 & 符号 获取变量的地址
总结2:利用指针可以记录地址
总结3:对指针变量解引用,可以操作指针指向的内存
提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?
示例:
int main() {
int a = 10;
int * p;
p = &a; //指针指向数据a的地址
cout << *p << endl; //* 解引用
cout << sizeof(p) << endl;
cout << sizeof(char *) << endl;
cout << sizeof(float *) << endl;
cout << sizeof(double *) << endl;
system("pause");
return 0;
}
总结:所有指针类型在32位操作系统下是4个字节,64位操作系统为8个字节
空指针:指针变量指向内存中编号为0的空间
用途: 初始化指针变量
注意: 空指针指向的内存是不可以访问的
示例1:空指针
int main() {
//指针变量p指向内存地址编号为0的空间
int * p = NULL;
//访问空指针报错
//内存编号0 ~255为系统占用内存,不允许用户访问
cout << *p << endl;
system("pause");
return 0;
}
野指针:指针变量指向非法的内存空间
示例2:野指针
int main() {
//指针变量p指向内存地址编号为0x1100的空间
int * p = (int *)0x1100;
//访问野指针报错
cout << *p << endl;
system("pause");
return 0;
}
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
const修饰指针有三种情况
const修饰指针 --- 常量指针
const修饰常量 --- 指针常量
const既修饰指针,又修饰常量
示例:
int main() {
int a = 10;
int b = 10;
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确
//const既修饰指针又修饰常量
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误
system("pause");
return 0;
}
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
核心概念
作用: 利用指针访问数组中元素
示例:
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int * p = arr; //指向数组的指针
cout << "第一个元素: " << arr[0] << endl;
cout << "指针访问第一个元素: " << *p << endl;
for (int i = 0; i < 10; i++)
{
//利用指针遍历数组
cout << *p << endl;
p++;
}
system("pause");
return 0;
}
作用: 利用指针作函数参数,可以修改实参的值(和前边形参相反)
示例:
//值传递
void swap1(int a ,int b)
{
int temp = a;
a = b;
b = temp;
}
//地址传递
void swap2(int * p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main() {
int a = 10;
int b = 20;
swap1(a, b); // 值传递不会改变实参
swap2(&a, &b); //地址传递会改变实参
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
int (*fp)(int) 表示指向返回 int 的函数的指针案例描述: 封装一个函数,利用冒泡排序,实现对整型数组的升序排序
例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
示例:
//冒泡排序函数
void bubbleSort(int * arr, int len) //int * arr 也可以写为int arr[]
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//打印数组函数
void printArray(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}
int main() {
int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
int len = sizeof(arr) / sizeof(int);
bubbleSort(arr, len);
printArray(arr, len);
system("pause");
return 0;
}
a + b, x++, p[i]+, -, *, /, %, &&, ||, !, ==, !=if, for, while, switch类型 数组名[大小];int arr[5] = {1, 2, 3, 4, 5};
char str[10] = "Hello";
char str1[] = "Hello"; // 自动添加 '\0'
char str2[6] = {'H','e','l','l','o','\0'}; // 手动指定
// 需包含头文件 <string.h>
strlen(str); // 计算长度(不含 '\0')
strcpy(dest, str); // 拷贝
strcmp(a, b); // 比较字符串
strcat(a, b); // 将 b 拼接到 a 后面
#include <string.h>
char msg[20];
strcpy(msg, "Hi"); // msg: "Hi"
strcat(msg, " there"); // msg: "Hi there"
定义:
结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
使用场景:
表示传感器、外设状态、网络包头等复杂数据
多变量统一传参,提升代码组织性
语法:
struct StructName {
int id;
float value;
char name[20];
};
示例:
struct SensorData {
int id;
float temperature;
char location[20];
};
struct SensorData s1 = {1, 36.5, "room_1"};
printf("Sensor: %d, Temp: %.1f\n", s1.id, s1.temperature);
定义:
共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
使用场景:
节省内存:如嵌入式协议帧解析
多种数据格式的重解释
语法:
union UnionName {
int i;
float f;
char c;
};
示例:
union Data {
int i;
float f;
};
union Data d;
d.i = 10;
printf("i = %d\n", d.i);
d.f = 3.14;
printf("f = %.2f\n", d.f); // 修改 f 会破坏 i
注意事项:
占用内存大小为最大成员的大小
修改一个成员后,其他成员的值不可预测
定义:
枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。
使用场景:
定义状态机状态
表示设备运行模式、错误码
语法:
enum Color { RED, GREEN, BLUE };
enum Color color = GREEN;
示例:
enum State {
STATE_IDLE,
STATE_ACTIVE,
STATE_ERROR = 100, // 可手动赋值
STATE_SLEEP
};
enum State current = STATE_ACTIVE;
printf("State: %d\n", current); // 输出 1
特性:
默认从 0 开始递增
可强制设定起始值
定义:
位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。
使用场景:
配置寄存器映射
网络协议比特位字段解析
内存空间紧张场景
语法:
struct Flags {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 2;
};
示例:
struct Flags f;
f.ready = 1;
f.error = 0;
f.mode = 3; // 占2位,最大为11(二进制)即3
printf("ready = %d, mode = %d\n", f.ready, f.mode);
注意事项:
位域不能取地址(&f.ready 不合法)
字段数值不能超过位数范围(2^n - 1)
与具体编译器实现密切相关(跨平台需小心)
#define LED_PIN (1 << 2)
PORT |= LED_PIN; // 置位
PORT &= ~LED_PIN; // 清零
PORT ^= LED_PIN; // 翻转
const(只读限定符)const int a = 10;
void print(const char* msg); // msg 不可修改
volatile(防止优化)volatile int *reg = (int *)0x40021000; // 硬件寄存器访问
static(静态变量/内部链接)static int count = 0; // 静态变量,函数调用间保留值
extern(外部变量声明)extern int global_var;
register(提示变量存放寄存器)register int speed;
auto(默认局部变量)auto int a = 10; // 一般可省略 auto
| 存储类型 | 生命周期 | 作用域 | 关键字 |
|---|---|---|---|
| 栈(stack) | 函数调用期间 | 局部变量 | auto |
| 静态区 | 程序全程 | 局部/全局 | static |
| 堆(heap) | 手动管理 | 全局 | malloc/free |
| 寄存器 | 函数调用期间 | 局部 | register |
gcc -E main.c -o main.i # 预处理
gcc -S main.c -o main.s # 编译为汇编
gcc -c main.c -o main.o # 汇编为目标文件
gcc main.o -o main # 链接生成可执行文件
CC = gcc
TARGET = app
OBJS = main.o utils.o
$(TARGET): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c $< -o $@
clean:
rm -f *.o $(TARGET)
| 参数 | 含义 |
|---|---|
-Wall | 开启所有警告 |
-g | 含调试信息 |
-O2 | 优化等级 |
-I | 头文件路径 |
-L/-l | 库路径和链接 |
-D | 宏定义 |
gdb ./main
(gdb) break main
(gdb) run
(gdb) next / step
(gdb) print var
(gdb) continue
int result;
__asm__ __volatile__ (
"movl $5, %%eax;"
"movl $3, %%ebx;"
"addl %%ebx, %%eax;"
"movl %%eax, %0;"
: "=r"(result)
:
: "%eax", "%ebx"
);
printf("result = %d\n", result); // 输出 8
相邻元素两两比较,把最大的“冒”到最后。
数据量小、逻辑简单、嵌入式环境友好
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int swapped = 0;
for (int j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j+1]) {
int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
swapped = 1;
}
}
if (!swapped) break; // 优化:已排序
}
}
每轮从未排序区间中选择最小值放到前面。
嵌入式设备中内存访问代价高,交换少
void selection_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int min = i;
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[min])
min = j;
}
if (min != i) {
int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
}
}
}
每次将一个元素插入到已排序部分的合适位置。
数据量小、数据基本有序时表现好
void insertion_sort(int arr[], int n) {
for (int i = 1; i < n; ++i) {
int key = arr[i], j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
分治法。选定基准,左边小于它,右边大于它。
高性能需求、数据量较大(慎用递归栈)
int partition(int arr[], int low, int high) {
int pivot = arr[high], i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] < pivot) {
++i;
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}
int t = arr[i+1]; arr[i+1] = arr[high]; arr[high] = t;
return i + 1;
}
void quick_sort(int arr[], int low, int high) {
if (low < high) {
int p = partition(arr, low, high);
quick_sort(arr, low, p - 1);
quick_sort(arr, p + 1, high);
}
}
分治法。将数组分成两半排序后合并。
追求稳定排序、高精度处理、实时传感器数据等(需额外内存)
void merge(int arr[], int l, int m, int r) {
int n1 = m-l+1, n2 = r-m;
int L[n1], R[n2];
for (int i = 0; i < n1; ++i) L[i] = arr[l+i];
for (int j = 0; j < n2; ++j) R[j] = arr[m+1+j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2)
arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void merge_sort(int arr[], int l, int r) {
if (l < r) {
int m = (l + r) / 2;
merge_sort(arr, l, m);
merge_sort(arr, m+1, r);
merge(arr, l, m, r);
}
}
利用堆结构(大根堆),反复取出最大元素构造有序序列。
嵌入式中对最值处理(最大温度等)、优先级调度
void heapify(int arr[], int n, int i) {
int largest = i, l = 2*i + 1, r = 2*i + 2;
if (l < n && arr[l] > arr[largest]) largest = l;
if (r < n && arr[r] > arr[largest]) largest = r;
if (largest != i) {
int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t;
heapify(arr, n, largest);
}
}
void heap_sort(int arr[], int n) {
for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i);
for (int i = n-1; i > 0; i--) {
int t = arr[0]; arr[0] = arr[i]; arr[i] = t;
heapify(arr, i, 0);
}
}
| 排序算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 | 嵌入式适用性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(1) | ✅ | ✅(小数据) |
| 选择排序 | O(n²) | O(1) | ❌ | ✅ |
| 插入排序 | O(n²) | O(1) | ✅ | ✅(近似有序) |
| 快速排序 | O(n log n) | O(log n) | ❌ | ⚠️(递归栈) |
| 归并排序 | O(n log n) | O(n) | ✅ | ⚠️(额外内存) |
| 堆排序 | O(n log n) | O(1) | ❌ | ✅ |
定义:
特点:
| 模块 | 功能说明 |
|---|---|
| MCU | 核心控制器,如 ARM Cortex-M |
| Flash/SRAM | 存储程序代码与运行数据 |
| 外设 | GPIO、UART、SPI、I2C、ADC、PWM 等 |
| 传感器 | 温湿度、光照、姿态等 |
| 通信模块 | WiFi、BLE、LoRa、CAN、NB-IoT 等 |
| 电源管理 | 电池、LDO、DC-DC,支持低功耗模式 |
+--------------------------+
| MCU |
| +----------------------+ |
| | Flash / RAM | |
| | GPIO / UART / ADC | |
| +----------------------+ |
+-----------|--------------+
|
+------+------+
| 外设 / 传感器 |
+-------------+
嵌入式系统的硬件构成是一个有机整体,各模块协同工作实现特定功能。以下从核心组件到通信架构进行深度解析:
| 接口 | 特点 | 典型应用 |
|---|---|---|
| UART | 全双工,异步,2线(TX/RX) | 调试信息输出、与模块通信(如GPS) |
| SPI | 全双工,同步,4线(SCK/MOSI/MISO/CS) | 高速数据传输(如OLED屏幕) |
| I2C | 半双工,同步,2线(SCL/SDA) | 多设备通信(如连接多个传感器) |
| CAN | 差分信号,抗干扰强,多主模式 | 汽车电子(如ECU间通信) |
┌─────────────────────────────────────────┐
│ 应用层 │
│ (用户逻辑:如温湿度采集、数据处理) │
├─────────────────────────────────────────┤
│ 驱动层 │
│ (传感器驱动、通信协议栈、外设控制) │
├─────────────────────────────────────────┤
│ 硬件层 │
│ MCU ── 存储器 ── 外设 ── 传感器 ── 通信 │
└─────────────────────────────────────────┘
SPI与I2C的区别:
如何选择合适的通信协议:
低功耗设计的关键策略:
main()。Reset_Handler:
LDR R0, =_estack ; 设置栈顶地址
MOV SP, R0
BL SystemInit ; 时钟初始化
BL main ; 跳转到主函数
Reset_Handler.data 和 .bss 段SystemInit()(通常配置系统时钟)main() 函数arm-none-eabi-gcc:编译器。arm-none-eabi-ld:链接器。arm-none-eabi-objcopy:格式转换工具(如生成.bin/.hex文件)。MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K // 只读,可执行
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K // 可读可写可执行
}
r:可读,w:可写,x:可执行。ORIGIN:起始地址,LENGTH:大小。SECTIONS
{
.text : { *(.text) } > FLASH // 代码段放入Flash
.data : { *(.data) } > RAM AT > FLASH // 已初始化数据运行时在RAM,加载时在Flash
.bss : { *(.bss) } > RAM // 未初始化数据放入RAM
.stack : { . = . + 0x1000; } > RAM // 栈空间(1KB)
.heap : { . = . + 0x2000; } > RAM AT > RAM // 堆空间(2KB)
}
.text:存储程序代码(如函数体)。.data:存储已初始化的全局变量(如int a = 10;)。.bss:存储未初始化的全局变量(如int b;),运行前自动清零。.stack:栈空间,用于局部变量和函数调用。.heap:堆空间,用于动态内存分配(如malloc)。.mysection : { KEEP(*(.mysection)) } > RAM // 保留特定段,不被链接器优化
.isr_vector : {
. = ALIGN(4);
KEEP(*(.isr_vector)) // 中断向量表必须位于Flash起始地址
. = ALIGN(4);
} > FLASH
地址范围 大小 描述
0x00000000-0x1FFFFFFF 512MB 代码区(可映射到Flash/SRAM/系统内存)
0x20000000-0x2001FFFF 128KB SRAM(运行时数据)
0x40000000-0x5FFFFFFF 512MB 外设寄存器(APB/AHB总线)
0xE0000000-0xE00FFFFF 1MB 系统控制空间(NVIC、SysTick等)
0x08000000-0x08000100 中断向量表
0x08000100-0x08080000 程序代码(.text)
0x08080000-0x08088000 已初始化数据(.data加载时位置)
0x20000000-0x20000100 已初始化数据(.data运行时位置)
0x20000100-0x20000200 未初始化数据(.bss)
0x20000200-0x20001200 栈空间(向下增长)
0x20001200-0x20010000 堆空间(向上增长)
#define GPIOA_BASE 0x40020000
#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00)) // 模式寄存器
GPIOA_MODER |= 0x01; // PA0设为输出模式
源代码(.c/.cpp) → 预处理器 → 编译器 → 汇编代码(.s) → 汇编器 → 目标文件(.o)
#include、#define等指令。多个目标文件(.o) + 库文件(.a/.lib) → 链接器 → 可执行文件(.elf) → 格式转换器 → 固件文件(.bin/.hex)
arm-none-eabi-objdump -d main.elf # 生成反汇编代码
arm-none-eabi-size main.elf # 显示各段大小
输出示例:
text data bss dec hex filename
1234 56 789 2079 81F main.elf
.debug_info : { *(.debug_info) } > RAM // 将调试信息放入RAM
KEEP防止符号被优化:
.vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
.data和.bss的区别:
.data存储已初始化的全局变量,占用Flash和RAM;.bss存储未初始化的全局变量,仅占用RAM(运行前自动清零)。如何减小代码体积:
链接脚本中AT关键字的作用:
AT指定段的加载地址,如.data > RAM AT > FLASH表示:
芯片数据手册(Datasheet)和参考手册(Reference Manual)是开发嵌入式系统时的核心参考材料,了解外设功能、寄存器地址、时钟结构、中断号、引脚复用等关键信息。
| 部分 | 说明 |
|---|---|
| Features | 简要功能描述,例如内核型号、Flash/RAM 容量、外设数量等 |
| Block Diagram | 芯片整体结构图 |
| Pinout / Alternate Function | 引脚定义和复用功能说明 |
| Electrical Characteristics | 电源、电压、电流、温度范围等参数 |
| Memory Map | 内存地址映射(Flash、SRAM、外设地址区间) |
| Peripherals | 每个外设的寄存器结构与配置方法 |
RCC_APB2ENR,快速定位外设时钟控制相关信息。向量表是处理器在启动时用来获取异常/中断服务函数入口地址的数组,通常放在 Flash 起始地址(如 0x0800 0000)或 RAM 中。
每个项为一个 函数指针,比如:
typedef void(*ISR_Handler)(void);
const ISR_Handler vector_table[] __attribute__((section(".isr_vector"))) = {
(ISR_Handler)&_estack, // 初始栈顶指针
Reset_Handler, // Reset
NMI_Handler, // NMI
HardFault_Handler, // HardFault
...
};
1. 修改中断处理函数指针:
__attribute__((section(".vector_table")))
void (*my_vector_table[])(void) = {
... // 自定义中断处理函数
};
2. 使用 SCB->VTOR(Vector Table Offset Register)更改向量表地址:
#include "core_cm4.h"
SCB->VTOR = (uint32_t)my_vector_table;
注意:新表地址必须对齐 0x100(最低 8 位为 0)
启动方式决定系统复位后,从哪块内存的地址开始执行程序,通常与 Boot Mode 管脚或 Boot 配置位有关。
0x0800 0000)加载向量表与指令0x2000 0000)启动| 项目 | ROM 启动 | RAM 启动 |
|---|---|---|
| 程序位置 | 编译链接到 Flash 区域 | 编译链接到 RAM 区域 |
| 向量表位置 | 默认在 Flash | 需手动配置向量表并设置 SCB->VTOR |
| 应用场景 | 量产版本、正常运行 | Bootloader、自举加载、调试 |
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。 以下从底层原理到实践应用进行深度扩展:
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
#define READ_BIT(REG, BIT) ((REG) & (BIT))
#define TOGGLE_BIT(REG, BIT) ((REG) ^= (BIT))
// 同时设置PA5、PA6为输出(MODER[13:12]=01, MODER[11:10]=01)
GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
// 示例:配置PA0为上升沿触发中断
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0; // 选择PA0
EXTI->IMR |= EXTI_IMR_IM0; // 使能中断线0
EXTI->RTSR |= EXTI_RTSR_TR0; // 上升沿触发
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); // 设置中断优先级
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能NVIC中断
波特率 = 系统时钟 / (16 * USARTDIV)// 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 处理接收到的数据
process_data(rx_buffer, rx_length);
// 重新开启接收中断
HAL_UART_Receive_IT(&huart1, rx_buffer, 1);
}
}
// 配置ADC1扫描模式,采样通道0、1、2
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 3; // 3个转换通道
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 配置DMA双缓冲区模式
hdma_adc.Instance = DMA2_Stream0;
hdma_adc.Init.BufferSize = 2; // 双缓冲区
hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
// ...其他配置
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
// HAL_UART_Transmit()函数原型
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 重写回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 发送完成后的处理
}
}
| 特性 | HAL | LL |
|---|---|---|
| 抽象程度 | 高 | 低 |
| 代码体积 | 大 | 小 |
| 执行效率 | 低 | 高 |
| 学习难度 | 低 | 高 |
HSE (8MHz) → PLLM=8 → VCO输入=1MHz → PLLN=360 → VCO输出=360MHz → PLLP=2 → 系统时钟=180MHz
// 示例:使用原子操作传递数据
volatile uint32_t g_flag __attribute__((aligned(4)));
void EXTI0_IRQHandler(void) {
__disable_irq();
g_flag = 1; // 原子写操作
__enable_irq();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
// 查看GPIOA_MODER寄存器值
uint32_t moder_value = GPIOA->MODER;
printf("GPIOA_MODER = 0x%08X\n", moder_value);
HAL与LL库的选择标准:
I2C通信中ACK/NACK的作用:
ADC采样时间对精度的影响:
DMA与CPU直接传输的优缺点:
本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
RTOS(Real-Time Operating System)是用于嵌入式设备中的轻量级操作系统,能提供任务调度、时间管理、资源管理等功能。
特点:
确定性(Determinism):
可抢占内核(Preemptive Kernel):
RTOS vs 裸机系统
| 特性 | 裸机系统 | RTOS(实时操作系统) |
|---|---|---|
| 任务管理 | 单任务 / 前后台系统 | 多任务并发,支持任务优先级 |
| 资源分配 | 手动管理 | 自动调度和资源管理 |
| 实时响应 | 依赖主循环结构 | 确定性调度,响应更稳定 |
| 开发难度 | 低(适合简单系统) | 高(需理解调度机制、堆栈管理) |
主流 RTOS 对比
| RTOS | 开源 | 应用领域 | 特点 |
|---|---|---|---|
| FreeRTOS | ✅ | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
| RT-Thread | ✅ | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
| μC/OS | ⚠️ 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
| VxWorks | ❌ | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
// 创建任务示例
void vTaskFunction(void *pvParameters) {
for (;;) {
// 任务代码
vTaskDelay(pdMS_TO_TICKS(100)); // 释放CPU
}
}
// 任务创建
xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
调度器选择 超时/事件发生
就绪 ───────────→ 运行 ←─────────── 阻塞
↑ │ │
│ └─── 调用vTaskDelay │
│ │
└─────── 调用vTaskSuspend ┘
或挂起API
抢占式调度:
时间片轮转:
// 相对延时(从调用开始计算)
vTaskDelay(pdMS_TO_TICKS(100));
// 绝对延时(固定周期执行)
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(100);
for (;;) {
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 周期性任务代码
}
// 创建并启动定时器
TimerHandle_t xTimer = xTimerCreate(
"Timer", // 定时器名称
pdMS_TO_TICKS(1000), // 周期1秒
pdTRUE, // 周期模式
(void *)0, // 定时器ID
vTimerCallback // 回调函数
);
xTimerStart(xTimer, 0);
// 定时器回调函数
void vTimerCallback(TimerHandle_t xTimer) {
// 定时任务代码
}
// 创建队列
QueueHandle_t xQueue = xQueueCreate(5, sizeof(int)); // 5个int元素
// 发送消息(阻塞100ms)
int value = 100;
xQueueSend(xQueue, &value, pdMS_TO_TICKS(100));
// 接收消息(永久等待)
int received_value;
xQueueReceive(xQueue, &received_value, portMAX_DELAY);
// 创建二值信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
// 任务中获取信号量
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
// 获得信号量,执行临界区代码
}
// 中断中释放信号量
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// 创建计数信号量(初始值=3,最大值=3)
SemaphoreHandle_t xPrinterSemaphore = xSemaphoreCreateCounting(3, 3);
// 任务中请求打印机
if (xSemaphoreTake(xPrinterSemaphore, portMAX_DELAY) == pdTRUE) {
// 获得打印机,执行打印任务
vPrintTask();
// 释放打印机
xSemaphoreGive(xPrinterSemaphore);
}
// 创建互斥锁
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// 高优先级任务H
void vTaskHigh(void *pvParameters) {
for (;;) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
// 临界区代码
xSemaphoreGive(xMutex); // 释放锁
}
}
// 低优先级任务L
void vTaskLow(void *pvParameters) {
for (;;) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
// 执行长时间操作(此时被中优先级任务M抢占)
xSemaphoreGive(xMutex); // 释放锁
}
}
// 定义消息结构体
typedef struct {
uint8_t command;
uint32_t data;
void (*callback)(void);
} Message_t;
// 创建消息队列(最多5个消息)
QueueHandle_t xMessageQueue = xQueueCreate(5, sizeof(Message_t));
// 发送消息
Message_t xMessage = {
.command = 0x01,
.data = 100,
.callback = vProcessCallback
};
xQueueSend(xMessageQueue, &xMessage, portMAX_DELAY);
// 接收消息
Message_t xReceivedMessage;
if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) {
// 处理消息
vProcessMessage(&xReceivedMessage);
}
| 特性 | 普通队列 | 消息队列 |
|---|---|---|
| 数据类型 | 固定大小字节块 | 支持结构体、指针等复杂数据类型 |
| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
// 创建事件组
EventGroupHandle_t xEventGroup = xEventGroupCreate();
// 任务1:设置事件位0
xEventGroupSetBits(xEventGroup, 0x01);
// 任务2:等待事件位0和1都置位
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
0x03, // 等待位0和1
pdTRUE, // 等待后清除位
pdTRUE, // 等待所有位
portMAX_DELAY // 永久等待
);
// 使用静态内存创建任务
StaticTask_t xTaskBuffer;
StackType_t xStack[256];
xTaskCreateStatic(
vTaskFunction, // 任务函数
"Task1", // 任务名称
256, // 栈大小
NULL, // 参数
2, // 优先级
xStack, // 静态栈
&xTaskBuffer // 静态任务控制块
);
// 可能导致碎片的错误模式
void vTask(void *pvParameters) {
for (;;) {
char *pcBuffer = (char *)pvPortMalloc(100);
// 使用缓冲区...
vPortFree(pcBuffer); // 释放后可能产生碎片
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// 预先分配对象池
static uint8_t xObjectPool[10][100]; // 10个100字节的对象
static BaseType_t xObjectAvailable[10] = {1}; // 标记可用状态
uint8_t *pvGetObject(void) {
for (int i = 0; i < 10; i++) {
if (xObjectAvailable[i]) {
xObjectAvailable[i] = 0;
return &xObjectPool[i][0];
}
}
return NULL;
}
void *pvBuffer = pvPortMalloc(100);
if (pvBuffer == NULL) {
// 内存分配失败处理
vHandleMemoryError();
}
void vCriticalFunction(void) {
taskENTER_CRITICAL();
// 临界区代码(禁止中断)
taskEXIT_CRITICAL();
}
// 创建互斥锁
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// 获取锁
xSemaphoreTake(xMutex, portMAX_DELAY);
// 临界区代码
xSemaphoreGive(xMutex); // 释放锁
| 参数 | 描述 | 示例值 |
|---|---|---|
configUSE_PREEMPTION | 是否使用抢占式调度 | 1(启用) |
configTICK_RATE_HZ | 系统滴答频率(Hz) | 1000(1ms) |
configMAX_PRIORITIES | 最大任务优先级数 | 5 ~ 32 |
configMINIMAL_STACK_SIZE | 最小任务栈大小(以字为单位) | 128(STM32) |
configSUPPORT_DYNAMIC_ALLOCATION | 是否支持动态内存分配 | 1(支持) |
vTaskStartScheduler(); Cortex-M3/M4 上下文切换示例(PendSV处理函数)
PendSV_Handler:
CPSID I ; 关中断
MRS R0, PSP ; 获取进程栈指针
CBZ R0, PendSV_NoSave ; 首次调用直接切换
; 保存寄存器到当前任务栈
SUBS R0, R0, #0x20 ; 调整栈指针
STM R0, {R4-R11} ; 保存R4-R11
LDR R1, =pxCurrentTCB ; 获取当前任务指针
LDR R1, [R1] ; 加载任务控制块地址
STR R0, [R1] ; 保存新的栈指针
PendSV_NoSave:
LDR R0, =pxCurrentTCB ; 获取当前任务指针
LDR R1, [R0] ; 加载当前任务控制块
LDR R0, [R1, #4] ; 加载下一个任务控制块
STR R0, [R0] ; 更新当前任务指针
LDR R0, [R0] ; 加载新任务栈指针
LDM R0, {R4-R11} ; 恢复寄存器
MSR PSP, R0 ; 更新进程栈指针
ORR LR, LR, #0x04 ; 设置返回标志
CPSIE I ; 开中断
BX LR ; 返回
// 获取任务运行时信息
void vTaskList(char *pcWriteBuffer);
// 示例输出:
// TaskName State Priority Stack Num
// Task1 Running 2 128 1
// Task2 Blocked 1 256 2
// 计算CPU使用率(需配置configGENERATE_RUN_TIME_STATS=1)
uint32_t ulHighFrequencyTimerTicks;
vTaskGetRunTimeStats(&ulHighFrequencyTimerTicks);
// 检查任务栈剩余空间
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
[Bootloader] → [Kernel] → [Root File System] → [User Application]
Power On →
BootROM →
Bootloader (SPL/U-Boot) →
Load & Decompress Kernel →
Kernel 初始化 →
挂载 RootFS →
启动 init →
Shell / App
setenv bootargs console=ttyS0 root=/dev/mmcblk0p2
tftp 0x80008000 zImage
bootz 0x80008000 - 0x83000000
.dts(源文件)、.dtsi(包含文件)、.dtb(二进制)uart1: serial@40011000 {
compatible = "vendor,uart";
reg = <0x40011000 0x400>;
interrupts = <5>;
status = "okay";
};
make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
| 命令 | 功能说明 |
|---|---|
ls -l | 列出文件详细信息 |
cd /path | 进入目录 |
cp source dest | 拷贝文件/目录 |
mv old new | 移动/重命名文件 |
rm -rf dir | 删除文件或目录 |
mkdir name | 创建新目录 |
find / grep | 搜索文件/内容 |
| 命令 | 功能说明 |
|---|---|
chmod 755 file | 修改权限(rwx) |
chown user:group | 更改文件拥有者 |
sudo | 以管理员身份执行命令 |
whoami / id | 查看当前用户信息 |
| 命令 | 功能说明 |
|---|---|
ps / top | 查看运行进程 |
kill PID | 杀死某个进程 |
htop | 进阶图形化进程管理工具 |
nice, renice | 修改进程优先级 |
| 命令 | 功能说明 |
|---|---|
ping | 测试网络连通性 |
ifconfig / ip | 配置 IP、MAC |
netstat -anp | 查看网络连接状态 |
scp, rsync | 文件远程复制 |
ssh user@host | SSH 登录远程系统 |
| 命令 | 功能说明 |
|---|---|
mount / umount | 挂载 / 卸载设备 |
df -h | 查看磁盘空间使用情况 |
lsblk, blkid | 查看块设备信息 |
| `dmesg | tail` |
| 工具 | 说明 |
|---|---|
apt, opkg, yum | 安装 / 卸载软件包 |
dpkg -i pkg.deb | 安装本地 deb 包 |
#!/bin/sh 或 #!/bin/bash:脚本头部声明chmod +x script.sh#!/bin/bash
for i in {1..5}
do
echo "Test $i"
done
| 命令/工具 | 说明 |
|---|---|
make | 使用 Makefile 构建 |
arm-linux-gcc | 使用交叉编译器编译 |
file a.out | 查看可执行文件平台架构 |
[硬件设备] ←→ [总线] ←→ [Device] ←→ [Driver] ←→ [内核]
struct file_operations fops = {
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
};
int major = register_chrdev(0, "mydev", &fops);
platform_deviceplatform_driverof_match_table 匹配设备树节点probe/remove 等接口/
├── bin/ → 常用命令
├── sbin/ → 系统工具
├── etc/ → 配置文件
├── dev/ → 设备节点
├── lib/ → 库文件
├── proc/ → 内核虚拟文件系统
├── sys/ → 设备/驱动信息
├── usr/ → 用户软件
├── tmp/ → 临时目录
└── home/ → 用户主目录
export CROSS_COMPILE=arm-linux-
gdb-multiarch vmlinux
target remote :1234
| 工具 | 用途 |
|---|---|
| GDB | 程序级调试 |
| strace | 跟踪系统调用 |
| dmesg | 内核日志查看 |
| ldd | 查看依赖的库文件 |
| top / htop | 查看系统资源使用情况 |
| lsmod/insmod | 加载/查看内核模块 |
| 平台 | 特点 |
|---|---|
| Raspberry Pi | 社区活跃,支持 Linux 全栈 |
| Allwinner / Rockchip | 国产主控,适配良好 |
| BeagleBone | 支持 PRU、实时协处理器 |
| STM32MP1 | 支持 Linux + Cortex-M 协同 |
每个组件仅拥有完成任务所需的最小权限(如 MPU 配置)。
多层次安全机制(如安全启动 + 通信加密 + 运行时防护)。
系统在异常情况下自动进入安全状态(如看门狗复位)。
保证启动时加载的固件是可信的
BootROM → 加载并验证一级Bootloader → 加载并验证二级Bootloader → 加载并验证应用固件
每个阶段只信任经过上一阶段验证的代码。
// 简化的签名验证伪代码
bool VerifyFirmwareSignature(uint8_t *firmware, uint32_t size, uint8_t *signature) {
// 1. 从OTP读取可信根公钥
const uint8_t *trusted_public_key = GetTrustedPublicKey();
// 2. 计算固件哈希值
uint8_t calculated_hash[32];
SHA256(firmware, size, calculated_hash);
// 3. 使用公钥解密签名获取原始哈希
uint8_t decrypted_hash[32];
RSA_PKCS1_Verify(trusted_public_key, signature, decrypted_hash);
// 4. 比较哈希值
return (memcmp(calculated_hash, decrypted_hash, 32) == 0);
}
// 启用读保护(RDP)
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef obInit = {0};
obInit.OptionType = OPTIONBYTE_RDP;
obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
HAL_FLASHEx_OBProgram(&obInit);
HAL_FLASH_OB_Lock();
// 配置安全/非安全区域
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 配置SRAM为安全区域
MPU_InitStruct.Number = MPU_REGION_0;
MPU_InitStruct.BaseAddress = 0x20000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = DISABLE;
MPU_InitStruct.IsShareable = ENABLE;
MPU_InitStruct.IsCacheable = DISABLE;
MPU_InitStruct.IsBufferable = DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
| RDP 级别 | 保护效果 | 可逆性 |
|---|---|---|
| Level 0 | 无保护(默认) | 是 |
| Level 1 | 禁止调试接口,Flash 只能运行不能读取 | 降级会擦除所有 Flash |
| Level 2 | 永久禁止调试接口和 Flash 读取 | 不可逆 |
将线性代码转换为基于状态机的结构,增加逆向难度。
用等效指令序列替换关键操作(如a+b替换为a-(-b))。
// 配置MPU保护关键数据区
void ConfigureMPU(void) {
// 使能MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
// 配置区域0保护关键代码区
MPU_Region_InitTypeDef MPU_InitStruct = {0};
MPU_InitStruct.Number = MPU_REGION_0;
MPU_InitStruct.BaseAddress = 0x08000000; // Flash起始地址
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO; // 特权可读写,用户只读
MPU_InitStruct.DisableExec = DISABLE;
MPU_InitStruct.IsShareable = DISABLE;
MPU_InitStruct.IsCacheable = DISABLE;
MPU_InitStruct.IsBufferable = DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
}
| 类别 | 示例 | 存储位置 |
|---|---|---|
| 密钥 | TLS 私钥、加密密钥 | 安全 SRAM |
| 敏感算法 | 密码验证、加密函数 | 安全代码区 |
| 安全服务 | OTA 签名验证、证书管理 | 安全任务 |
// 从非安全代码调用安全服务
__attribute__((section(".nonsecure_call")))
uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param2) {
// 通过SVC指令切换到安全模式
__asm("SVC #0");
// 返回值通过R0传递
}
嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
// 基本 UART 初始化
USART1->BRR = 0x1A1; // 设置波特率
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 使能收发
// TCP 客户端基础逻辑 (伪代码)
socket();
connect();
send();
recv();
| 分类 | 范围 | 含义 |
|---|---|---|
| 1xx | 100~199 | 接收中,继续处理 |
| 2xx | 200~299 | 请求成功 |
| 3xx | 300~399 | 重定向或更多操作 |
| 4xx | 400~499 | 客户端错误 |
| 5xx | 500~599 | 服务器错误 |
部分状态码示例:
Connection: keep-alive)GET /hello.htm HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: */*
Connection: Keep-Alive
... 其他头部字段
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 158
Server: Apache
Date: Sun, 14 Jun 2025 10:00:00 GMT
<html>...</html>
HTTPS = HTTP + TLS/SSL 加密
// mbed TLS配置PSK
mbedtls_ssl_config_set_psk(&ssl_conf,
psk, // 预共享密钥
psk_length,
identity, // 身份标识
strlen(identity));
// 验证服务器证书链
int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
// 检查证书有效期
if (mbedtls_x509_crt_check_validity(crt, time(NULL)) != 0) {
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
// 检查证书颁发者
if (!mbedtls_x509_crt_verify(crt, trusted_certs, NULL, NULL, flags, NULL, NULL)) {
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
return 0;
}
// 检测SWD/JTAG调试接口
bool IsDebuggerAttached(void) {
// 读取DBGMCU_IDCODE寄存器
uint32_t idcode = DBGMCU->IDCODE;
// 检查调试使能位
return ((DBGMCU->CR & (DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY)) != 0);
}
// 常量时间比较(防止时序攻击)
bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
uint8_t result = 0;
for (size_t i = 0; i < len; i++) {
result |= a[i] ^ b[i];
}
return (result == 0);
}
| 层级 | 协议/组件 | 功能说明 |
|---|---|---|
| 应用层 | HTTP, MQTT, CoAP, DNS | 面向用户的协议 |
| 传输层 | TCP, UDP | 数据传输可靠性与端口管理 |
| 网络层 | IP, ICMP, ARP | 地址与路由 |
| 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
| 特性 | TCP | UDP |
|---|---|---|
| 是否连接 | 是(面向连接) | 否(无连接) |
| 是否可靠 | 是(有重传、确认) | 否(可能丢包) |
| 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
| 开销 | 较大(握手、窗口等) | 较小(直接发送) |
recv(), send()close()// TCP 客户端示例
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(12345);
server.sin_addr.s_addr = inet_addr("192.168.1.10");
connect(sock, (struct sockaddr*)&server, sizeof(server));
send(sock, "Hello", strlen("Hello"), 0);
recv(sock, buffer, sizeof(buffer), 0);
close(sock);
gethostbyname() 等ping 实现通信测试支持远程更新嵌入式系统的固件版本
Flash布局:
+-------------------+ 0x08000000
| Bootloader |
+-------------------+ 0x08010000
| Application Slot A|
+-------------------+ 0x08040000
| Application Slot B|
+-------------------+ 0x08070000
| Configuration Area|
+-------------------+
typedef enum {
OTA_IDLE, // 空闲状态
OTA_CHECKING, // 检查更新
OTA_DOWNLOADING, // 下载中
OTA_VERIFYING, // 校验中
OTA_READY, // 准备重启
OTA_UPGRADING, // 升级中
OTA_FAILED // 升级失败
} OTA_State_t;
// OTA状态机处理函数
void OTA_Process(void) {
switch (ota_state) {
case OTA_IDLE:
if (check_update_flag) {
ota_state = OTA_CHECKING;
vCheckForUpdate();
}
break;
case OTA_DOWNLOADING:
if (download_complete) {
ota_state = OTA_VERIFYING;
vVerifyFirmware();
} else if (download_error) {
ota_state = OTA_FAILED;
vHandleError(DOWNLOAD_ERROR);
}
break;
// 其他状态处理...
}
}
// 启动时验证应用完整性
bool ValidateApplication(uint32_t start_address) {
// 检查向量表签名
uint32_t *vector_table = (uint32_t *)start_address;
if (vector_table[0] == 0xFFFFFFFF) { // 检查栈顶指针是否有效
return false;
}
// 计算应用哈希并验证
uint8_t calculated_hash[32];
SHA256((uint8_t *)start_address, APPLICATION_SIZE, calculated_hash);
// 从配置区获取预期哈希
uint8_t *expected_hash = GetExpectedHash();
return (memcmp(calculated_hash, expected_hash, 32) == 0);
}
// 主程序
int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();
// 检查主应用是否有效
if (ValidateApplication(APPLICATION_SLOT_A_ADDRESS)) {
// 跳转到主应用
JumpToApplication(APPLICATION_SLOT_A_ADDRESS);
} else if (ValidateApplication(APPLICATION_SLOT_B_ADDRESS)) {
// 主应用无效,尝试从备份应用启动
JumpToApplication(APPLICATION_SLOT_B_ADDRESS);
} else {
// 两个应用都无效,进入恢复模式
EnterRecoveryMode();
}
}
| 问题 | 解答 |
|---|---|
| MQTT 断线如何重连? | 设置心跳机制与 reconnect 回调逻辑 |
| OTA 更新失败怎么办? | 回退机制 + 双镜像分区设计 |
| CoAP 和 MQTT 有何区别? | CoAP 基于 UDP,适合低功耗设备;MQTT 基于 TCP,稳定性好 |
JTAG 与 SWD 接口对比
| 特性 | JTAG | SWD |
|---|---|---|
| 引脚数 | 5 线(TMS、TCK、TDI、TDO、TRST) | 2 线(SWDIO、SWCLK) |
| 速度 | 中低速(典型 1-10MHz) | 高速(可达 50MHz 以上) |
| 占用资源 | 高(需多个 GPIO) | 低(仅 2 个 GPIO) |
| 级联能力 | 支持多设备(通过 TAP 控制器) | 不支持级联 |
| 适用场景 | 复杂芯片调试(如 FPGA) | 嵌入式 MCU(如 STM32) |
// 使能SWD接口(禁用JTAG以释放GPIO)
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14; // SWDIO, SWCLK
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_SWJ;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
关键命令详解:
# 1. 启动OpenOCD(连接ST-Link与目标MCU)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
# 2. 启动GDB并加载ELF文件
arm-none-eabi-gdb path/to/firmware.elf
# 3. 连接到OpenOCD服务器
(gdb) target remote :3333 # 默认端口3333
# 4. 下载程序到Flash
(gdb) load
# 5. 复位并暂停CPU
(gdb) monitor reset halt
# 6. 设置断点
(gdb) break main # 在main()函数入口设置断点
(gdb) break MyFunction # 在自定义函数设置断点
(gdb) break file.c:123 # 在文件file.c的第123行设置断点
# 7. 执行控制
(gdb) continue # 继续执行
(gdb) next # 单步执行(不进入函数)
(gdb) step # 单步执行(进入函数)
(gdb) finish # 运行到当前函数结束
# 8. 查看变量
(gdb) print myVariable # 打印变量值
(gdb) p &myArray[0] # 打印数组地址
(gdb) x/10i $pc # 查看当前执行的10条汇编指令
# 9. 查看寄存器
(gdb) info registers # 查看所有寄存器
(gdb) p $r0 # 查看特定寄存器(如R0)
逻辑分析仪:用于捕捉数字信号波形,分析通信协议(如 I2C, SPI)。
逻辑分析仪典型场景:
示波器:查看模拟信号、电压、电流变化。对调试电源问题、干扰、PWM波形等极为重要。
示波器关键参数:
// 配置TIM3输出PWM(频率1kHz,占空比50%)
TIM_HandleTypeDef htim3;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
htim3.Init.Period = 1000 - 1; // 1MHz / 1000 = 1kHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim3);
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
使用示波器测量:频率应为 1kHz,高电平时间 500μs(占空比 50%)。
printf() 输出信息到串口查看程序执行流程。vTraceEnable() 开启追踪。// 自定义跟踪点示例
#define TRACE_TASK_SWITCH() do { \
uint32_t current_task = (uint32_t)pxCurrentTCB; \
uint32_t timestamp = xTaskGetTickCount(); \
vTraceStoreEvent(EVENT_TASK_SWITCH, timestamp, current_task); \
} while(0)
#define configUSE_TRACE_FACILITY 1 // 启用跟踪功能
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 启用统计功能
#define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
| 模式 | 唤醒时间 | 功耗 | 保留内容 |
|---|---|---|---|
| Sleep | 数 μs | 几 mA | CPU 寄存器、SRAM 内容 |
| Stop | 几十 μs | 几 μA | SRAM 内容、部分寄存器 |
| Standby | 几 ms | 几十 nA | 仅备份寄存器(如 RTC) |
// 禁用未使用的外设时钟
__HAL_RCC_GPIOA_CLK_DISABLE(); // 禁用GPIOA时钟
__HAL_RCC_SPI1_CLK_DISABLE(); // 禁用SPI1时钟
// 仅在需要时启用外设
void vReadSensor(void) {
__HAL_RCC_I2C1_CLK_ENABLE(); // 启用I2C时钟
// 读取传感器数据
__HAL_RCC_I2C1_CLK_DISABLE(); // 读取完成后禁用时钟
}
// 配置RTC闹钟唤醒(每10秒唤醒一次)
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
RTC_AlarmTypeDef sAlarm = {0};
sTime.Hours = 0;
sTime.Minutes = 0;
sTime.Seconds = 0;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 1;
sDate.Year = 0;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sAlarm.AlarmTime = sTime;
sAlarm.Alarm = RTC_ALARM_A;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
// 进入Standby模式
HAL_PWR_EnterSTANDBYMode();
// 记录内存分配/释放情况
void *pvPortMalloc( size_t xWantedSize ) {
void *pvReturn = NULL;
vTaskSuspendAll();
{
// 记录分配信息(如分配地址、大小、时间)
pvReturn = prvHeapAllocateMemory( xWantedSize );
vRecordMemoryAllocation(pvReturn, xWantedSize);
}
xTaskResumeAll();
return pvReturn;
}
static uint32_t ulLastInterruptTime = 0;
#define DEBOUNCE_TIME 50 // 50ms
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
uint32_t ulCurrentTime = xTaskGetTickCount();
if (ulCurrentTime - ulLastInterruptTime > DEBOUNCE_TIME) {
// 处理有效中断
vProcessButtonPress();
ulLastInterruptTime = ulCurrentTime;
}
}
采用Conventional Commits规范:
<类型>[可选范围]: <描述>
[可选正文]
[可选脚注]
feat:新功能fix:修复Bugdocs:文档更新style:代码格式调整(不影响功能)refactor:代码重构test:添加或修改测试chore:构建或辅助工具的变动使用语义化版本(SemVer)打标签:
# 创建标签
git tag v1.0.0
# 推送标签到远程
git push origin v1.0.0
# 查看所有标签
git tag -l
CC = arm-none-eabi-gcc
CFLAGS = -Wall -O2 -mcpu=cortex-m4 -mthumb
LDFLAGS = -Tstm32f4.ld
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
TARGET = firmware.elf
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
cmake_minimum_required(VERSION 3.10)
project(EmbeddedProject C)
# 设置交叉编译工具链
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
# 添加编译选项
add_compile_options(
-mcpu=cortex-m4
-mthumb
-mfloat-abi=hard
-mfpu=fpv4-sp-d16
-Wall
-Wextra
-Os
)
# 添加链接选项
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
# 添加源文件
file(GLOB_RECURSE SOURCES "src/*.c" "drivers/*.c")
# 添加可执行文件
add_executable(${PROJECT_NAME}.elf ${SOURCES})
# 添加目标文件
add_custom_target(${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
DEPENDS ${PROJECT_NAME}.elf
)
name: Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc-arm-none-eabi cmake ninja-build
- name: Configure CMake
run: cmake -B build -G Ninja
- name: Build
run: cmake --build build
- name: Run tests
run: |
cd build
ctest --output-on-failure
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'make clean all'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
stage('Code Coverage') {
steps {
sh 'make coverage'
}
post {
always {
junit 'build/test-results/*.xml'
publishCoverage adapters: [coberturaAdapter('build/coverage/coverage.xml')]
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'make deploy'
}
}
}
}
+----------------------+
| 应用层 |
| (业务逻辑、算法) |
+----------------------+
| 服务层 |
| (任务管理、事件) |
+----------------------+
| 驱动层 |
| (硬件抽象、BSP) |
+----------------------+
| 硬件层 |
| (MCU、外设) |
+----------------------+
project/
├── app/ # 应用层
│ ├── main.c # 主程序入口
│ ├── modules/ # 功能模块
│ │ ├── sensor/ # 传感器处理
│ │ ├── comm/ # 通信处理
│ │ └── control/ # 控制逻辑
│ └── config/ # 配置文件
├── services/ # 服务层
│ ├── task_mgr/ # 任务管理器
│ ├── event/ # 事件系统
│ └── utils/ # 工具函数
├── drivers/ # 驱动层
│ ├── bsp/ # 板级支持包
│ ├── hal/ # 硬件抽象层
│ └── periph/ # 外设驱动
└── build/ # 构建系统
├── cmake/ # CMake配置
└── Makefile # Makefile
// bsp_led.h
#ifndef BSP_LED_H
#define BSP_LED_H
#include <stdint.h>
typedef enum {
LED_RED,
LED_GREEN,
LED_BLUE
} led_t;
typedef enum {
LED_OFF,
LED_ON,
LED_TOGGLE
} led_state_t;
// 初始化LED
void bsp_led_init(void);
// 设置LED状态
void bsp_led_set(led_t led, led_state_t state);
#endif
// bsp_led.c (STM32实现)
#include "bsp_led.h"
#include "stm32f4xx_hal.h"
// LED GPIO定义
#define LED_RED_PIN GPIO_PIN_14
#define LED_RED_PORT GPIOG
#define LED_GREEN_PIN GPIO_PIN_13
#define LED_GREEN_PORT GPIOG
#define LED_BLUE_PIN GPIO_PIN_15
#define LED_BLUE_PORT GPIOG
void bsp_led_init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO时钟
__HAL_RCC_GPIOG_CLK_ENABLE();
// 配置GPIO引脚
GPIO_InitStruct.Pin = LED_RED_PIN | LED_GREEN_PIN | LED_BLUE_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
// 默认关闭所有LED
HAL_GPIO_WritePin(LED_RED_PORT, LED_RED_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_BLUE_PORT, LED_BLUE_PIN, GPIO_PIN_RESET);
}
void bsp_led_set(led_t led, led_state_t state) {
GPIO_TypeDef *port;
uint16_t pin;
// 根据LED类型选择GPIO
switch (led) {
case LED_RED:
port = LED_RED_PORT;
pin = LED_RED_PIN;
break;
case LED_GREEN:
port = LED_GREEN_PORT;
pin = LED_GREEN_PIN;
break;
case LED_BLUE:
port = LED_BLUE_PORT;
pin = LED_BLUE_PIN;
break;
default:
return;
}
// 设置LED状态
switch (state) {
case LED_OFF:
HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
break;
case LED_ON:
HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
break;
case LED_TOGGLE:
HAL_GPIO_TogglePin(port, pin);
break;
}
}
// spi_interface.h (抽象接口)
#ifndef SPI_INTERFACE_H
#define SPI_INTERFACE_H
#include <stdint.h>
typedef struct {
// 初始化SPI
void (*init)(uint32_t baudrate);
// 发送数据
void (*send)(const uint8_t *data, uint32_t length);
// 接收数据
void (*receive)(uint8_t *data, uint32_t length);
// 发送并接收数据
void (*transfer)(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);
} spi_interface_t;
// 获取SPI接口实例
const spi_interface_t* spi_get_interface(void);
#endif
// spi_stm32.c (STM32实现)
#include "spi_interface.h"
#include "stm32f4xx_hal.h"
static SPI_HandleTypeDef hspi1;
static void spi_init(uint32_t baudrate) {
// 配置SPI参数
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
// 根据波特率计算分频系数
uint32_t prescaler = SPI_BAUDRATEPRESCALER_2;
if (baudrate < 1000000) prescaler = SPI_BAUDRATEPRESCALER_128;
else if (baudrate < 2000000) prescaler = SPI_BAUDRATEPRESCALER_64;
else if (baudrate < 4000000) prescaler = SPI_BAUDRATEPRESCALER_32;
else if (baudrate < 8000000) prescaler = SPI_BAUDRATEPRESCALER_16;
else if (baudrate < 16000000) prescaler = SPI_BAUDRATEPRESCALER_8;
else if (baudrate < 32000000) prescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.BaudRatePrescaler = prescaler;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
// 初始化SPI
HAL_SPI_Init(&hspi1);
}
static void spi_send(const uint8_t *data, uint32_t length) {
HAL_SPI_Transmit(&hspi1, (uint8_t*)data, length, 1000);
}
static void spi_receive(uint8_t *data, uint32_t length) {
HAL_SPI_Receive(&hspi1, data, length, 1000);
}
static void spi_transfer(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length) {
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)tx_data, rx_data, length, 1000);
}
// SPI接口实现
static const spi_interface_t spi_impl = {
.init = spi_init,
.send = spi_send,
.receive = spi_receive,
.transfer = spi_transfer
};
// 获取SPI接口实例
const spi_interface_t* spi_get_interface(void) {
return &spi_impl;
}
Flash布局:
+-------------------+ 0x08000000
| Bootloader | (80KB)
+-------------------+ 0x08014000
| Application A | (448KB)
+-------------------+ 0x08084000
| Application B | (448KB)
+-------------------+ 0x08104000
| Configuration | (16KB)
+-------------------+
typedef enum {
OTA_IDLE, // 空闲状态
OTA_CHECKING, // 检查更新
OTA_DOWNLOADING, // 下载中
OTA_DOWNLOAD_PAUSED, // 下载暂停
OTA_VERIFYING, // 校验中
OTA_READY, // 准备重启
OTA_UPGRADING, // 升级中
OTA_FAILED // 升级失败
} ota_state_t;
typedef struct {
ota_state_t state;
uint32_t total_size;
uint32_t downloaded_size;
uint8_t progress;
char error_msg[64];
uint8_t firmware_hash[32];
} ota_context_t;
检查更新:
bool ota_check_update(void) {
// 从服务器获取版本信息
http_response_t response = http_get(UPDATE_SERVER_URL "/version");
if (response.status != 200) {
return false;
}
// 解析服务器版本
uint32_t server_version = parse_version(response.body);
uint32_t current_version = get_current_version();
// 比较版本
return (server_version > current_version);
}
下载固件:
void ota_download_firmware(void) {
// 打开固件下载URL
http_client_t client = http_open(UPDATE_SERVER_URL "/firmware.bin");
if (!client) {
ota_set_state(OTA_FAILED, "Failed to open URL");
return;
}
// 获取文件大小
uint32_t file_size = http_get_content_length(client);
ota_set_total_size(file_size);
// 开始下载
uint8_t buffer[512];
uint32_t bytes_received = 0;
uint32_t bytes_written = 0;
while ((bytes_received = http_read(client, buffer, 512)) > 0) {
// 写入到备份区
if (!flash_write(APPLICATION_B_ADDRESS + bytes_written, buffer, bytes_received)) {
ota_set_state(OTA_FAILED, "Flash write failed");
http_close(client);
return;
}
bytes_written += bytes_received;
ota_update_progress(bytes_written * 100 / file_size);
// 检查是否需要暂停
if (ota_should_pause()) {
http_close(client);
ota_set_state(OTA_DOWNLOAD_PAUSED, "Download paused");
return;
}
}
http_close(client);
ota_set_state(OTA_VERIFYING, "Verifying firmware");
}
验证与应用:
bool ota_verify_firmware(void) {
// 计算下载固件的哈希值
uint8_t calculated_hash[32];
calculate_firmware_hash(APPLICATION_B_ADDRESS, APPLICATION_SIZE, calculated_hash);
// 与服务器提供的哈希值比较
if (memcmp(calculated_hash, ota_get_expected_hash(), 32) != 0) {
return false;
}
// 验证向量表
uint32_t *vector_table = (uint32_t*)APPLICATION_B_ADDRESS;
if (vector_table[0] == 0 || vector_table[1] == 0) {
return false;
}
return true;
}
void ota_apply_update(void) {
// 设置升级标志
set_update_flag(1);
// 保存新固件版本
save_new_version(get_server_version());
// 重启系统
NVIC_SystemReset();
}
官网链接:
安装步骤:
Ctrl+Shift+X)验证安装:
打开VS Code,点击左下角的 PlatformIO Home 图标,若能正常打开则安装成功。
官网链接:
安装步骤:
验证安装:
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
官网链接:
安装步骤:
验证安装:
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
官网链接:
安装步骤:
Windows:
C:\openocd)bin 目录添加到系统环境变量Linux:
sudo apt-get install openocd # Ubuntu/Debian
sudo yum install openocd # CentOS/RHEL
macOS:
brew install open-ocd
验证安装:
在终端中运行 openocd --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
bin 目录添加到系统环境变量验证安装:
在终端中运行 arm-none-eabi-gdb --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
Windows:
Linux:
sudo apt-get install stlink-tools # Ubuntu/Debian
验证安装:
在终端中运行 st-info --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
Windows:
Linux:
sudo apt-get install cppcheck # Ubuntu/Debian
sudo yum install cppcheck # CentOS/RHEL
macOS:
brew install cppcheck
验证安装:
在终端中运行 cppcheck --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
Windows:
Linux:
sudo apt-get install clang-tidy # Ubuntu/Debian
macOS:
brew install llvm
验证安装:
在终端中运行 clang-tidy --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
docker run -d --name sonarqube -p 9000:9000 sonarqube
验证安装:
在浏览器中打开 http://localhost:9000,若能看到SonarQube界面则安装成功。
官网链接:
安装步骤:
git clone https://github.com/ThrowTheSwitch/Unity.git
src 目录添加到项目的头文件搜索路径验证安装:
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
官网链接:
安装步骤:
git clone https://github.com/ThrowTheSwitch/CMock.git
src 目录添加到项目的头文件搜索路径验证安装:
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
官网链接:
安装步骤:
git clone https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
cmake ..
make
sudo make install
验证安装:
创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。
| 工具 | 官网链接 | 安装指南 |
|---|---|---|
| VS Code | https://code.visualstudio.com/ | 直接下载安装包 |
| PlatformIO | https://platformio.org/ | VS Code扩展市场安装 |
| STM32CubeIDE | https://www.st.com/en/development-tools/stm32cubeide.html | 官网下载安装包 |
| CLion | https://www.jetbrains.com/clion/ | 官网下载安装包 |
| OpenOCD | http://openocd.org/ | 包管理器或预编译二进制包 |
| GDB | https://www.gnu.org/software/gdb/ | 随ARM GCC Toolchain安装 |
| ST-Link/V2 | https://www.st.com/en/development-tools/st-link-v2.html | 官网下载驱动 |
| CppCheck | https://cppcheck.sourceforge.io/ | 包管理器或安装包 |
| Clang-Tidy | https://clang.llvm.org/extra/clang-tidy/ | 随LLVM安装 |
| SonarQube | https://www.sonarqube.org/ | Docker容器或独立安装 |
| Unity | https://github.com/ThrowTheSwitch/Unity | 从GitHub下载源码 |
| CMock | https://github.com/ThrowTheSwitch/CMock | 从GitHub下载源码 |
| Google Test | https://github.com/google/googletest | CMake构建并安装 |
模型训练:
使用TensorFlow/Keras等工具在PC上训练模型。
# 简单MNIST模型示例
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)
模型量化:
将浮点模型转换为整数模型,减少内存占用和计算量。
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
模型部署:
将量化后的模型转换为C数组,集成到MCU项目中。
xxd -i model.tflite > model_data.cc
MCU推理:
使用TensorFlow Lite Micro框架在MCU上运行模型。
// 初始化解释器
tflite::MicroErrorReporter micro_error_reporter;
const tflite::ErrorReporter* error_reporter = µ_error_reporter;
const tflite::MicroOpResolver& op_resolver = MicroOpsResolver();
const tflite::SimpleTensorAllocator tensor_allocator(tensor_arena, kTensorArenaSize);
tflite::MicroInterpreter interpreter(model_data, model_data_len, op_resolver,
tensor_allocator, error_reporter);
// 运行推理
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
error_reporter->Report("Invoke failed\n");
}
| 模型 | 参数量 | 激活内存 | 准确率 | 推理时间(STM32H7) |
|---|---|---|---|---|
| MobileNetV1 | 4.2M | 16MB | 70.6% | 800ms |
| TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
| EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
# 使用x-cube-ai命令行工具转换模型
stm32ai generate -m model.h5 -o stm32ai_output
| 挑战 | 解决方案 |
|---|---|
| 内存受限 | 使用内存映射技术,模型分段加载 |
| 计算能力有限 | 优化算子实现,利用硬件加速指令 |
| 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
| 模型更新 | 设计OTA机制,支持模型动态更新 |
// 从加速度计读取数据
void read_accelerometer_data(float *data, size_t length) {
// 读取加速度计原始数据
int16_t raw_data[3];
accelerometer_read(raw_data);
// 转换为浮点数并归一化
for (int i = 0; i < 3; i++) {
data[i] = (float)raw_data[i] / 32768.0f;
}
}
// 运行AI模型进行活动识别
activity_t recognize_activity(float *sensor_data) {
// 准备模型输入
TfLiteTensor* input = interpreter->input(0);
memcpy(input->data.f, sensor_data, input->bytes);
// 执行推理
if (interpreter->Invoke() != kTfLiteOk) {
return ACTIVITY_UNKNOWN;
}
// 获取输出结果
TfLiteTensor* output = interpreter->output(0);
int activity_index = argmax(output->data.f, output->dims->data[0]);
return (activity_t)activity_index;
}
硬件信任根:
验证流程:
ROM → 验证Bootloader签名 → 验证应用固件签名 → 启动应用
选项字节配置:
// 启用读保护(RDP)
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef obInit;
obInit.OptionType = OPTIONBYTE_RDP;
obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
HAL_FLASHEx_OBProgram(&obInit);
HAL_FLASH_OB_Lock();
签名验证:
// 验证固件签名
bool verify_firmware_signature(const uint8_t *firmware, size_t size, const uint8_t *signature) {
// 从OTP读取公钥
const uint8_t *public_key = get_public_key_from_otp();
// 使用ECDSA验证签名
return ecdsa_verify(public_key, firmware, size, signature);
}
硬件连接:
STM32通过I2C/SPI与TPM芯片(如Infineon OPTIGA™ TPM SLB 9670)通信。
软件实现:
// TPM初始化
tpm_error_t tpm_init(void) {
// 初始化I2C接口
i2c_init(TPM_I2C_ADDRESS);
// 发送TPM启动命令
uint8_t startup_cmd[10] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44};
uint8_t response[20];
if (i2c_write(TPM_I2C_ADDRESS, startup_cmd, 10) != 0) {
return TPM_ERROR_COMMUNICATION;
}
// 读取响应
if (i2c_read(TPM_I2C_ADDRESS, response, 20) != 0) {
return TPM_ERROR_COMMUNICATION;
}
// 验证响应
if (response[6] == 0x00 && response[7] == 0x00) {
return TPM_SUCCESS;
} else {
return TPM_ERROR_INITIALIZATION;
}
}
// 生成密钥
tpm_error_t tpm_generate_key(uint8_t *key_handle, uint8_t *public_key) {
// 发送生成密钥命令
// ...
// 处理响应
// ...
return TPM_SUCCESS;
}
AI on MCU:
安全性:
实战案例:
本项目内容均来源于互联网公开资料,仅供学习交流使用,版权归原作者所有。
若原作者认为本项目引用内容存在侵权,请通过 issue 或邮件联系我,我们将在第一时间内删除或修正。
10 followers · starred Jul 2026
68 followers · starred Jul 2026
5 followers · starred Oct 2025
欢迎来到本项目,这是一份面向中文社区的系统、全面且贴近实战的嵌入式软件开发学习路线和知识点总结。涵盖范围包括 C/C++、嵌入式开发、驱动开发、计算机网络原理、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链等相关知识点。收集全网相关书籍和面试经历分享给各位。
2,704
0 commits
updated Jun 28, 2025
嵌入式软件学习路线图 · C语言 / RTOS / Linux驱动 · 从入门到进阶
欢迎来到本项目,这里拥有系统、全面且贴近实战的2025年嵌入式软件开发学习路线和知识点总结。
涵盖包括C语言、驱动开发、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链等嵌入式软件开发所需知识点
以及嵌入式开发相关的必读书籍、面试题以及面经。
跟随目录点击选择想学习的部分即可跳转至相应知识点。
爆肝两周只为给大家呈现最新开源免费的嵌入式软件学习资料!无任何套路!
能够帮助到你的话请点个star收藏一下推给更多有需要的人就是最大鼓励,不胜感激!
类型 变量名 [= 初始值];int count = 10;
float temperature;
char c = 'A';
int, short, long, long long, unsignedfloat, doublecharunsigned int u = 100;
long long big_number = 12345678900LL;
常用 C 关键字解释如下:
| 关键字 | 说明 |
|---|---|
const | 定义只读变量 |
volatile | 防止编译器优化,常用于寄存器 |
static | 变量作用域或函数仅在本文件可见 |
extern | 声明外部变量/函数 |
typedef | 为数据类型取别名 |
static int counter = 0; // 内部链接
extern int g_value; // 声明外部变量
volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问
10, 3.14, 'a', "abc"#define 或 const 定义 #define PI 3.14159
const int MAX_SIZE = 100;
void func(int a, int b) {
int sum = a + b; // sum存储在栈上
// ...
} // 函数返回时,sum和参数a、b自动释放
malloc / free 手动分配和释放void createArray() {
int* arr = (int*)malloc(10 * sizeof(int)); // 从堆分配内存
if (arr != NULL) {
arr[0] = 100; // 使用堆内存
// ...
}
free(arr); // 手动释放内存
}
栈 vs 堆的对比
| 特性 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 分配方式 | 自动(由编译器管理) | 手动(如 malloc/free 或 new/delete) |
| 生命周期 | 函数调用期间自动创建和销毁 | 程序员控制,需手动释放 |
| 内存空间 | 连续、有限(通常几 MB) | 不连续、较大(受限于物理内存) |
| 访问速度 | 快(通过寄存器快速访问) | 慢(通过指针间接访问) |
| 内存碎片 | 不存在(先进后出结构) | 可能产生(频繁分配和释放) |
| 使用场景 | 局部变量、函数调用栈帧 | 动态数据结构、跨函数共享数据 |
指针的作用: 可以通过指针间接访问内存
指针变量定义语法: 数据类型 * 变量名;
示例:
int main() {
//1、指针的定义
int a = 10; //定义整型变量a
//指针定义语法: 数据类型 * 变量名 ;
int * p;
//指针变量赋值
p = &a; //指针指向变量a的地址
cout << &a << endl; //打印数据a的地址
cout << p << endl; //打印指针变量p
//2、指针的使用
//通过*操作指针变量指向的内存
cout << "*p = " << *p << endl;
system("pause");
return 0;
}
指针变量和普通变量的区别
总结1: 我们可以通过 & 符号 获取变量的地址
总结2:利用指针可以记录地址
总结3:对指针变量解引用,可以操作指针指向的内存
提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?
示例:
int main() {
int a = 10;
int * p;
p = &a; //指针指向数据a的地址
cout << *p << endl; //* 解引用
cout << sizeof(p) << endl;
cout << sizeof(char *) << endl;
cout << sizeof(float *) << endl;
cout << sizeof(double *) << endl;
system("pause");
return 0;
}
总结:所有指针类型在32位操作系统下是4个字节,64位操作系统为8个字节
空指针:指针变量指向内存中编号为0的空间
用途: 初始化指针变量
注意: 空指针指向的内存是不可以访问的
示例1:空指针
int main() {
//指针变量p指向内存地址编号为0的空间
int * p = NULL;
//访问空指针报错
//内存编号0 ~255为系统占用内存,不允许用户访问
cout << *p << endl;
system("pause");
return 0;
}
野指针:指针变量指向非法的内存空间
示例2:野指针
int main() {
//指针变量p指向内存地址编号为0x1100的空间
int * p = (int *)0x1100;
//访问野指针报错
cout << *p << endl;
system("pause");
return 0;
}
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
const修饰指针有三种情况
const修饰指针 --- 常量指针
const修饰常量 --- 指针常量
const既修饰指针,又修饰常量
示例:
int main() {
int a = 10;
int b = 10;
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确
//const既修饰指针又修饰常量
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误
system("pause");
return 0;
}
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
核心概念
作用: 利用指针访问数组中元素
示例:
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int * p = arr; //指向数组的指针
cout << "第一个元素: " << arr[0] << endl;
cout << "指针访问第一个元素: " << *p << endl;
for (int i = 0; i < 10; i++)
{
//利用指针遍历数组
cout << *p << endl;
p++;
}
system("pause");
return 0;
}
作用: 利用指针作函数参数,可以修改实参的值(和前边形参相反)
示例:
//值传递
void swap1(int a ,int b)
{
int temp = a;
a = b;
b = temp;
}
//地址传递
void swap2(int * p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main() {
int a = 10;
int b = 20;
swap1(a, b); // 值传递不会改变实参
swap2(&a, &b); //地址传递会改变实参
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
int (*fp)(int) 表示指向返回 int 的函数的指针案例描述: 封装一个函数,利用冒泡排序,实现对整型数组的升序排序
例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
示例:
//冒泡排序函数
void bubbleSort(int * arr, int len) //int * arr 也可以写为int arr[]
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//打印数组函数
void printArray(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}
int main() {
int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
int len = sizeof(arr) / sizeof(int);
bubbleSort(arr, len);
printArray(arr, len);
system("pause");
return 0;
}
a + b, x++, p[i]+, -, *, /, %, &&, ||, !, ==, !=if, for, while, switch类型 数组名[大小];int arr[5] = {1, 2, 3, 4, 5};
char str[10] = "Hello";
char str1[] = "Hello"; // 自动添加 '\0'
char str2[6] = {'H','e','l','l','o','\0'}; // 手动指定
// 需包含头文件 <string.h>
strlen(str); // 计算长度(不含 '\0')
strcpy(dest, str); // 拷贝
strcmp(a, b); // 比较字符串
strcat(a, b); // 将 b 拼接到 a 后面
#include <string.h>
char msg[20];
strcpy(msg, "Hi"); // msg: "Hi"
strcat(msg, " there"); // msg: "Hi there"
定义:
结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
使用场景:
表示传感器、外设状态、网络包头等复杂数据
多变量统一传参,提升代码组织性
语法:
struct StructName {
int id;
float value;
char name[20];
};
示例:
struct SensorData {
int id;
float temperature;
char location[20];
};
struct SensorData s1 = {1, 36.5, "room_1"};
printf("Sensor: %d, Temp: %.1f\n", s1.id, s1.temperature);
定义:
共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
使用场景:
节省内存:如嵌入式协议帧解析
多种数据格式的重解释
语法:
union UnionName {
int i;
float f;
char c;
};
示例:
union Data {
int i;
float f;
};
union Data d;
d.i = 10;
printf("i = %d\n", d.i);
d.f = 3.14;
printf("f = %.2f\n", d.f); // 修改 f 会破坏 i
注意事项:
占用内存大小为最大成员的大小
修改一个成员后,其他成员的值不可预测
定义:
枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。
使用场景:
定义状态机状态
表示设备运行模式、错误码
语法:
enum Color { RED, GREEN, BLUE };
enum Color color = GREEN;
示例:
enum State {
STATE_IDLE,
STATE_ACTIVE,
STATE_ERROR = 100, // 可手动赋值
STATE_SLEEP
};
enum State current = STATE_ACTIVE;
printf("State: %d\n", current); // 输出 1
特性:
默认从 0 开始递增
可强制设定起始值
定义:
位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。
使用场景:
配置寄存器映射
网络协议比特位字段解析
内存空间紧张场景
语法:
struct Flags {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 2;
};
示例:
struct Flags f;
f.ready = 1;
f.error = 0;
f.mode = 3; // 占2位,最大为11(二进制)即3
printf("ready = %d, mode = %d\n", f.ready, f.mode);
注意事项:
位域不能取地址(&f.ready 不合法)
字段数值不能超过位数范围(2^n - 1)
与具体编译器实现密切相关(跨平台需小心)
#define LED_PIN (1 << 2)
PORT |= LED_PIN; // 置位
PORT &= ~LED_PIN; // 清零
PORT ^= LED_PIN; // 翻转
const(只读限定符)const int a = 10;
void print(const char* msg); // msg 不可修改
volatile(防止优化)volatile int *reg = (int *)0x40021000; // 硬件寄存器访问
static(静态变量/内部链接)static int count = 0; // 静态变量,函数调用间保留值
extern(外部变量声明)extern int global_var;
register(提示变量存放寄存器)register int speed;
auto(默认局部变量)auto int a = 10; // 一般可省略 auto
| 存储类型 | 生命周期 | 作用域 | 关键字 |
|---|---|---|---|
| 栈(stack) | 函数调用期间 | 局部变量 | auto |
| 静态区 | 程序全程 | 局部/全局 | static |
| 堆(heap) | 手动管理 | 全局 | malloc/free |
| 寄存器 | 函数调用期间 | 局部 | register |
gcc -E main.c -o main.i # 预处理
gcc -S main.c -o main.s # 编译为汇编
gcc -c main.c -o main.o # 汇编为目标文件
gcc main.o -o main # 链接生成可执行文件
CC = gcc
TARGET = app
OBJS = main.o utils.o
$(TARGET): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c $< -o $@
clean:
rm -f *.o $(TARGET)
| 参数 | 含义 |
|---|---|
-Wall | 开启所有警告 |
-g | 含调试信息 |
-O2 | 优化等级 |
-I | 头文件路径 |
-L/-l | 库路径和链接 |
-D | 宏定义 |
gdb ./main
(gdb) break main
(gdb) run
(gdb) next / step
(gdb) print var
(gdb) continue
int result;
__asm__ __volatile__ (
"movl $5, %%eax;"
"movl $3, %%ebx;"
"addl %%ebx, %%eax;"
"movl %%eax, %0;"
: "=r"(result)
:
: "%eax", "%ebx"
);
printf("result = %d\n", result); // 输出 8
相邻元素两两比较,把最大的“冒”到最后。
数据量小、逻辑简单、嵌入式环境友好
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int swapped = 0;
for (int j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j+1]) {
int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
swapped = 1;
}
}
if (!swapped) break; // 优化:已排序
}
}
每轮从未排序区间中选择最小值放到前面。
嵌入式设备中内存访问代价高,交换少
void selection_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int min = i;
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[min])
min = j;
}
if (min != i) {
int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
}
}
}
每次将一个元素插入到已排序部分的合适位置。
数据量小、数据基本有序时表现好
void insertion_sort(int arr[], int n) {
for (int i = 1; i < n; ++i) {
int key = arr[i], j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
分治法。选定基准,左边小于它,右边大于它。
高性能需求、数据量较大(慎用递归栈)
int partition(int arr[], int low, int high) {
int pivot = arr[high], i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] < pivot) {
++i;
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}
int t = arr[i+1]; arr[i+1] = arr[high]; arr[high] = t;
return i + 1;
}
void quick_sort(int arr[], int low, int high) {
if (low < high) {
int p = partition(arr, low, high);
quick_sort(arr, low, p - 1);
quick_sort(arr, p + 1, high);
}
}
分治法。将数组分成两半排序后合并。
追求稳定排序、高精度处理、实时传感器数据等(需额外内存)
void merge(int arr[], int l, int m, int r) {
int n1 = m-l+1, n2 = r-m;
int L[n1], R[n2];
for (int i = 0; i < n1; ++i) L[i] = arr[l+i];
for (int j = 0; j < n2; ++j) R[j] = arr[m+1+j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2)
arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void merge_sort(int arr[], int l, int r) {
if (l < r) {
int m = (l + r) / 2;
merge_sort(arr, l, m);
merge_sort(arr, m+1, r);
merge(arr, l, m, r);
}
}
利用堆结构(大根堆),反复取出最大元素构造有序序列。
嵌入式中对最值处理(最大温度等)、优先级调度
void heapify(int arr[], int n, int i) {
int largest = i, l = 2*i + 1, r = 2*i + 2;
if (l < n && arr[l] > arr[largest]) largest = l;
if (r < n && arr[r] > arr[largest]) largest = r;
if (largest != i) {
int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t;
heapify(arr, n, largest);
}
}
void heap_sort(int arr[], int n) {
for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i);
for (int i = n-1; i > 0; i--) {
int t = arr[0]; arr[0] = arr[i]; arr[i] = t;
heapify(arr, i, 0);
}
}
| 排序算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 | 嵌入式适用性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(1) | ✅ | ✅(小数据) |
| 选择排序 | O(n²) | O(1) | ❌ | ✅ |
| 插入排序 | O(n²) | O(1) | ✅ | ✅(近似有序) |
| 快速排序 | O(n log n) | O(log n) | ❌ | ⚠️(递归栈) |
| 归并排序 | O(n log n) | O(n) | ✅ | ⚠️(额外内存) |
| 堆排序 | O(n log n) | O(1) | ❌ | ✅ |
定义:
特点:
| 模块 | 功能说明 |
|---|---|
| MCU | 核心控制器,如 ARM Cortex-M |
| Flash/SRAM | 存储程序代码与运行数据 |
| 外设 | GPIO、UART、SPI、I2C、ADC、PWM 等 |
| 传感器 | 温湿度、光照、姿态等 |
| 通信模块 | WiFi、BLE、LoRa、CAN、NB-IoT 等 |
| 电源管理 | 电池、LDO、DC-DC,支持低功耗模式 |
+--------------------------+
| MCU |
| +----------------------+ |
| | Flash / RAM | |
| | GPIO / UART / ADC | |
| +----------------------+ |
+-----------|--------------+
|
+------+------+
| 外设 / 传感器 |
+-------------+
嵌入式系统的硬件构成是一个有机整体,各模块协同工作实现特定功能。以下从核心组件到通信架构进行深度解析:
| 接口 | 特点 | 典型应用 |
|---|---|---|
| UART | 全双工,异步,2线(TX/RX) | 调试信息输出、与模块通信(如GPS) |
| SPI | 全双工,同步,4线(SCK/MOSI/MISO/CS) | 高速数据传输(如OLED屏幕) |
| I2C | 半双工,同步,2线(SCL/SDA) | 多设备通信(如连接多个传感器) |
| CAN | 差分信号,抗干扰强,多主模式 | 汽车电子(如ECU间通信) |
┌─────────────────────────────────────────┐
│ 应用层 │
│ (用户逻辑:如温湿度采集、数据处理) │
├─────────────────────────────────────────┤
│ 驱动层 │
│ (传感器驱动、通信协议栈、外设控制) │
├─────────────────────────────────────────┤
│ 硬件层 │
│ MCU ── 存储器 ── 外设 ── 传感器 ── 通信 │
└─────────────────────────────────────────┘
SPI与I2C的区别:
如何选择合适的通信协议:
低功耗设计的关键策略:
main()。Reset_Handler:
LDR R0, =_estack ; 设置栈顶地址
MOV SP, R0
BL SystemInit ; 时钟初始化
BL main ; 跳转到主函数
Reset_Handler.data 和 .bss 段SystemInit()(通常配置系统时钟)main() 函数arm-none-eabi-gcc:编译器。arm-none-eabi-ld:链接器。arm-none-eabi-objcopy:格式转换工具(如生成.bin/.hex文件)。MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K // 只读,可执行
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K // 可读可写可执行
}
r:可读,w:可写,x:可执行。ORIGIN:起始地址,LENGTH:大小。SECTIONS
{
.text : { *(.text) } > FLASH // 代码段放入Flash
.data : { *(.data) } > RAM AT > FLASH // 已初始化数据运行时在RAM,加载时在Flash
.bss : { *(.bss) } > RAM // 未初始化数据放入RAM
.stack : { . = . + 0x1000; } > RAM // 栈空间(1KB)
.heap : { . = . + 0x2000; } > RAM AT > RAM // 堆空间(2KB)
}
.text:存储程序代码(如函数体)。.data:存储已初始化的全局变量(如int a = 10;)。.bss:存储未初始化的全局变量(如int b;),运行前自动清零。.stack:栈空间,用于局部变量和函数调用。.heap:堆空间,用于动态内存分配(如malloc)。.mysection : { KEEP(*(.mysection)) } > RAM // 保留特定段,不被链接器优化
.isr_vector : {
. = ALIGN(4);
KEEP(*(.isr_vector)) // 中断向量表必须位于Flash起始地址
. = ALIGN(4);
} > FLASH
地址范围 大小 描述
0x00000000-0x1FFFFFFF 512MB 代码区(可映射到Flash/SRAM/系统内存)
0x20000000-0x2001FFFF 128KB SRAM(运行时数据)
0x40000000-0x5FFFFFFF 512MB 外设寄存器(APB/AHB总线)
0xE0000000-0xE00FFFFF 1MB 系统控制空间(NVIC、SysTick等)
0x08000000-0x08000100 中断向量表
0x08000100-0x08080000 程序代码(.text)
0x08080000-0x08088000 已初始化数据(.data加载时位置)
0x20000000-0x20000100 已初始化数据(.data运行时位置)
0x20000100-0x20000200 未初始化数据(.bss)
0x20000200-0x20001200 栈空间(向下增长)
0x20001200-0x20010000 堆空间(向上增长)
#define GPIOA_BASE 0x40020000
#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00)) // 模式寄存器
GPIOA_MODER |= 0x01; // PA0设为输出模式
源代码(.c/.cpp) → 预处理器 → 编译器 → 汇编代码(.s) → 汇编器 → 目标文件(.o)
#include、#define等指令。多个目标文件(.o) + 库文件(.a/.lib) → 链接器 → 可执行文件(.elf) → 格式转换器 → 固件文件(.bin/.hex)
arm-none-eabi-objdump -d main.elf # 生成反汇编代码
arm-none-eabi-size main.elf # 显示各段大小
输出示例:
text data bss dec hex filename
1234 56 789 2079 81F main.elf
.debug_info : { *(.debug_info) } > RAM // 将调试信息放入RAM
KEEP防止符号被优化:
.vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
.data和.bss的区别:
.data存储已初始化的全局变量,占用Flash和RAM;.bss存储未初始化的全局变量,仅占用RAM(运行前自动清零)。如何减小代码体积:
链接脚本中AT关键字的作用:
AT指定段的加载地址,如.data > RAM AT > FLASH表示:
芯片数据手册(Datasheet)和参考手册(Reference Manual)是开发嵌入式系统时的核心参考材料,了解外设功能、寄存器地址、时钟结构、中断号、引脚复用等关键信息。
| 部分 | 说明 |
|---|---|
| Features | 简要功能描述,例如内核型号、Flash/RAM 容量、外设数量等 |
| Block Diagram | 芯片整体结构图 |
| Pinout / Alternate Function | 引脚定义和复用功能说明 |
| Electrical Characteristics | 电源、电压、电流、温度范围等参数 |
| Memory Map | 内存地址映射(Flash、SRAM、外设地址区间) |
| Peripherals | 每个外设的寄存器结构与配置方法 |
RCC_APB2ENR,快速定位外设时钟控制相关信息。向量表是处理器在启动时用来获取异常/中断服务函数入口地址的数组,通常放在 Flash 起始地址(如 0x0800 0000)或 RAM 中。
每个项为一个 函数指针,比如:
typedef void(*ISR_Handler)(void);
const ISR_Handler vector_table[] __attribute__((section(".isr_vector"))) = {
(ISR_Handler)&_estack, // 初始栈顶指针
Reset_Handler, // Reset
NMI_Handler, // NMI
HardFault_Handler, // HardFault
...
};
1. 修改中断处理函数指针:
__attribute__((section(".vector_table")))
void (*my_vector_table[])(void) = {
... // 自定义中断处理函数
};
2. 使用 SCB->VTOR(Vector Table Offset Register)更改向量表地址:
#include "core_cm4.h"
SCB->VTOR = (uint32_t)my_vector_table;
注意:新表地址必须对齐 0x100(最低 8 位为 0)
启动方式决定系统复位后,从哪块内存的地址开始执行程序,通常与 Boot Mode 管脚或 Boot 配置位有关。
0x0800 0000)加载向量表与指令0x2000 0000)启动| 项目 | ROM 启动 | RAM 启动 |
|---|---|---|
| 程序位置 | 编译链接到 Flash 区域 | 编译链接到 RAM 区域 |
| 向量表位置 | 默认在 Flash | 需手动配置向量表并设置 SCB->VTOR |
| 应用场景 | 量产版本、正常运行 | Bootloader、自举加载、调试 |
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。 以下从底层原理到实践应用进行深度扩展:
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
#define READ_BIT(REG, BIT) ((REG) & (BIT))
#define TOGGLE_BIT(REG, BIT) ((REG) ^= (BIT))
// 同时设置PA5、PA6为输出(MODER[13:12]=01, MODER[11:10]=01)
GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
// 示例:配置PA0为上升沿触发中断
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0; // 选择PA0
EXTI->IMR |= EXTI_IMR_IM0; // 使能中断线0
EXTI->RTSR |= EXTI_RTSR_TR0; // 上升沿触发
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); // 设置中断优先级
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能NVIC中断
波特率 = 系统时钟 / (16 * USARTDIV)// 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 处理接收到的数据
process_data(rx_buffer, rx_length);
// 重新开启接收中断
HAL_UART_Receive_IT(&huart1, rx_buffer, 1);
}
}
// 配置ADC1扫描模式,采样通道0、1、2
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 3; // 3个转换通道
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 配置DMA双缓冲区模式
hdma_adc.Instance = DMA2_Stream0;
hdma_adc.Init.BufferSize = 2; // 双缓冲区
hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
// ...其他配置
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
// HAL_UART_Transmit()函数原型
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 重写回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 发送完成后的处理
}
}
| 特性 | HAL | LL |
|---|---|---|
| 抽象程度 | 高 | 低 |
| 代码体积 | 大 | 小 |
| 执行效率 | 低 | 高 |
| 学习难度 | 低 | 高 |
HSE (8MHz) → PLLM=8 → VCO输入=1MHz → PLLN=360 → VCO输出=360MHz → PLLP=2 → 系统时钟=180MHz
// 示例:使用原子操作传递数据
volatile uint32_t g_flag __attribute__((aligned(4)));
void EXTI0_IRQHandler(void) {
__disable_irq();
g_flag = 1; // 原子写操作
__enable_irq();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
// 查看GPIOA_MODER寄存器值
uint32_t moder_value = GPIOA->MODER;
printf("GPIOA_MODER = 0x%08X\n", moder_value);
HAL与LL库的选择标准:
I2C通信中ACK/NACK的作用:
ADC采样时间对精度的影响:
DMA与CPU直接传输的优缺点:
本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
RTOS(Real-Time Operating System)是用于嵌入式设备中的轻量级操作系统,能提供任务调度、时间管理、资源管理等功能。
特点:
确定性(Determinism):
可抢占内核(Preemptive Kernel):
RTOS vs 裸机系统
| 特性 | 裸机系统 | RTOS(实时操作系统) |
|---|---|---|
| 任务管理 | 单任务 / 前后台系统 | 多任务并发,支持任务优先级 |
| 资源分配 | 手动管理 | 自动调度和资源管理 |
| 实时响应 | 依赖主循环结构 | 确定性调度,响应更稳定 |
| 开发难度 | 低(适合简单系统) | 高(需理解调度机制、堆栈管理) |
主流 RTOS 对比
| RTOS | 开源 | 应用领域 | 特点 |
|---|---|---|---|
| FreeRTOS | ✅ | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
| RT-Thread | ✅ | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
| μC/OS | ⚠️ 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
| VxWorks | ❌ | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
// 创建任务示例
void vTaskFunction(void *pvParameters) {
for (;;) {
// 任务代码
vTaskDelay(pdMS_TO_TICKS(100)); // 释放CPU
}
}
// 任务创建
xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
调度器选择 超时/事件发生
就绪 ───────────→ 运行 ←─────────── 阻塞
↑ │ │
│ └─── 调用vTaskDelay │
│ │
└─────── 调用vTaskSuspend ┘
或挂起API
抢占式调度:
时间片轮转:
// 相对延时(从调用开始计算)
vTaskDelay(pdMS_TO_TICKS(100));
// 绝对延时(固定周期执行)
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(100);
for (;;) {
vTaskDelayUntil(&xLastWakeTime, xFrequency);
// 周期性任务代码
}
// 创建并启动定时器
TimerHandle_t xTimer = xTimerCreate(
"Timer", // 定时器名称
pdMS_TO_TICKS(1000), // 周期1秒
pdTRUE, // 周期模式
(void *)0, // 定时器ID
vTimerCallback // 回调函数
);
xTimerStart(xTimer, 0);
// 定时器回调函数
void vTimerCallback(TimerHandle_t xTimer) {
// 定时任务代码
}
// 创建队列
QueueHandle_t xQueue = xQueueCreate(5, sizeof(int)); // 5个int元素
// 发送消息(阻塞100ms)
int value = 100;
xQueueSend(xQueue, &value, pdMS_TO_TICKS(100));
// 接收消息(永久等待)
int received_value;
xQueueReceive(xQueue, &received_value, portMAX_DELAY);
// 创建二值信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
// 任务中获取信号量
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
// 获得信号量,执行临界区代码
}
// 中断中释放信号量
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// 创建计数信号量(初始值=3,最大值=3)
SemaphoreHandle_t xPrinterSemaphore = xSemaphoreCreateCounting(3, 3);
// 任务中请求打印机
if (xSemaphoreTake(xPrinterSemaphore, portMAX_DELAY) == pdTRUE) {
// 获得打印机,执行打印任务
vPrintTask();
// 释放打印机
xSemaphoreGive(xPrinterSemaphore);
}
// 创建互斥锁
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// 高优先级任务H
void vTaskHigh(void *pvParameters) {
for (;;) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
// 临界区代码
xSemaphoreGive(xMutex); // 释放锁
}
}
// 低优先级任务L
void vTaskLow(void *pvParameters) {
for (;;) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
// 执行长时间操作(此时被中优先级任务M抢占)
xSemaphoreGive(xMutex); // 释放锁
}
}
// 定义消息结构体
typedef struct {
uint8_t command;
uint32_t data;
void (*callback)(void);
} Message_t;
// 创建消息队列(最多5个消息)
QueueHandle_t xMessageQueue = xQueueCreate(5, sizeof(Message_t));
// 发送消息
Message_t xMessage = {
.command = 0x01,
.data = 100,
.callback = vProcessCallback
};
xQueueSend(xMessageQueue, &xMessage, portMAX_DELAY);
// 接收消息
Message_t xReceivedMessage;
if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) {
// 处理消息
vProcessMessage(&xReceivedMessage);
}
| 特性 | 普通队列 | 消息队列 |
|---|---|---|
| 数据类型 | 固定大小字节块 | 支持结构体、指针等复杂数据类型 |
| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
// 创建事件组
EventGroupHandle_t xEventGroup = xEventGroupCreate();
// 任务1:设置事件位0
xEventGroupSetBits(xEventGroup, 0x01);
// 任务2:等待事件位0和1都置位
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
0x03, // 等待位0和1
pdTRUE, // 等待后清除位
pdTRUE, // 等待所有位
portMAX_DELAY // 永久等待
);
// 使用静态内存创建任务
StaticTask_t xTaskBuffer;
StackType_t xStack[256];
xTaskCreateStatic(
vTaskFunction, // 任务函数
"Task1", // 任务名称
256, // 栈大小
NULL, // 参数
2, // 优先级
xStack, // 静态栈
&xTaskBuffer // 静态任务控制块
);
// 可能导致碎片的错误模式
void vTask(void *pvParameters) {
for (;;) {
char *pcBuffer = (char *)pvPortMalloc(100);
// 使用缓冲区...
vPortFree(pcBuffer); // 释放后可能产生碎片
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// 预先分配对象池
static uint8_t xObjectPool[10][100]; // 10个100字节的对象
static BaseType_t xObjectAvailable[10] = {1}; // 标记可用状态
uint8_t *pvGetObject(void) {
for (int i = 0; i < 10; i++) {
if (xObjectAvailable[i]) {
xObjectAvailable[i] = 0;
return &xObjectPool[i][0];
}
}
return NULL;
}
void *pvBuffer = pvPortMalloc(100);
if (pvBuffer == NULL) {
// 内存分配失败处理
vHandleMemoryError();
}
void vCriticalFunction(void) {
taskENTER_CRITICAL();
// 临界区代码(禁止中断)
taskEXIT_CRITICAL();
}
// 创建互斥锁
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// 获取锁
xSemaphoreTake(xMutex, portMAX_DELAY);
// 临界区代码
xSemaphoreGive(xMutex); // 释放锁
| 参数 | 描述 | 示例值 |
|---|---|---|
configUSE_PREEMPTION | 是否使用抢占式调度 | 1(启用) |
configTICK_RATE_HZ | 系统滴答频率(Hz) | 1000(1ms) |
configMAX_PRIORITIES | 最大任务优先级数 | 5 ~ 32 |
configMINIMAL_STACK_SIZE | 最小任务栈大小(以字为单位) | 128(STM32) |
configSUPPORT_DYNAMIC_ALLOCATION | 是否支持动态内存分配 | 1(支持) |
vTaskStartScheduler(); Cortex-M3/M4 上下文切换示例(PendSV处理函数)
PendSV_Handler:
CPSID I ; 关中断
MRS R0, PSP ; 获取进程栈指针
CBZ R0, PendSV_NoSave ; 首次调用直接切换
; 保存寄存器到当前任务栈
SUBS R0, R0, #0x20 ; 调整栈指针
STM R0, {R4-R11} ; 保存R4-R11
LDR R1, =pxCurrentTCB ; 获取当前任务指针
LDR R1, [R1] ; 加载任务控制块地址
STR R0, [R1] ; 保存新的栈指针
PendSV_NoSave:
LDR R0, =pxCurrentTCB ; 获取当前任务指针
LDR R1, [R0] ; 加载当前任务控制块
LDR R0, [R1, #4] ; 加载下一个任务控制块
STR R0, [R0] ; 更新当前任务指针
LDR R0, [R0] ; 加载新任务栈指针
LDM R0, {R4-R11} ; 恢复寄存器
MSR PSP, R0 ; 更新进程栈指针
ORR LR, LR, #0x04 ; 设置返回标志
CPSIE I ; 开中断
BX LR ; 返回
// 获取任务运行时信息
void vTaskList(char *pcWriteBuffer);
// 示例输出:
// TaskName State Priority Stack Num
// Task1 Running 2 128 1
// Task2 Blocked 1 256 2
// 计算CPU使用率(需配置configGENERATE_RUN_TIME_STATS=1)
uint32_t ulHighFrequencyTimerTicks;
vTaskGetRunTimeStats(&ulHighFrequencyTimerTicks);
// 检查任务栈剩余空间
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
[Bootloader] → [Kernel] → [Root File System] → [User Application]
Power On →
BootROM →
Bootloader (SPL/U-Boot) →
Load & Decompress Kernel →
Kernel 初始化 →
挂载 RootFS →
启动 init →
Shell / App
setenv bootargs console=ttyS0 root=/dev/mmcblk0p2
tftp 0x80008000 zImage
bootz 0x80008000 - 0x83000000
.dts(源文件)、.dtsi(包含文件)、.dtb(二进制)uart1: serial@40011000 {
compatible = "vendor,uart";
reg = <0x40011000 0x400>;
interrupts = <5>;
status = "okay";
};
make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
| 命令 | 功能说明 |
|---|---|
ls -l | 列出文件详细信息 |
cd /path | 进入目录 |
cp source dest | 拷贝文件/目录 |
mv old new | 移动/重命名文件 |
rm -rf dir | 删除文件或目录 |
mkdir name | 创建新目录 |
find / grep | 搜索文件/内容 |
| 命令 | 功能说明 |
|---|---|
chmod 755 file | 修改权限(rwx) |
chown user:group | 更改文件拥有者 |
sudo | 以管理员身份执行命令 |
whoami / id | 查看当前用户信息 |
| 命令 | 功能说明 |
|---|---|
ps / top | 查看运行进程 |
kill PID | 杀死某个进程 |
htop | 进阶图形化进程管理工具 |
nice, renice | 修改进程优先级 |
| 命令 | 功能说明 |
|---|---|
ping | 测试网络连通性 |
ifconfig / ip | 配置 IP、MAC |
netstat -anp | 查看网络连接状态 |
scp, rsync | 文件远程复制 |
ssh user@host | SSH 登录远程系统 |
| 命令 | 功能说明 |
|---|---|
mount / umount | 挂载 / 卸载设备 |
df -h | 查看磁盘空间使用情况 |
lsblk, blkid | 查看块设备信息 |
| `dmesg | tail` |
| 工具 | 说明 |
|---|---|
apt, opkg, yum | 安装 / 卸载软件包 |
dpkg -i pkg.deb | 安装本地 deb 包 |
#!/bin/sh 或 #!/bin/bash:脚本头部声明chmod +x script.sh#!/bin/bash
for i in {1..5}
do
echo "Test $i"
done
| 命令/工具 | 说明 |
|---|---|
make | 使用 Makefile 构建 |
arm-linux-gcc | 使用交叉编译器编译 |
file a.out | 查看可执行文件平台架构 |
[硬件设备] ←→ [总线] ←→ [Device] ←→ [Driver] ←→ [内核]
struct file_operations fops = {
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
};
int major = register_chrdev(0, "mydev", &fops);
platform_deviceplatform_driverof_match_table 匹配设备树节点probe/remove 等接口/
├── bin/ → 常用命令
├── sbin/ → 系统工具
├── etc/ → 配置文件
├── dev/ → 设备节点
├── lib/ → 库文件
├── proc/ → 内核虚拟文件系统
├── sys/ → 设备/驱动信息
├── usr/ → 用户软件
├── tmp/ → 临时目录
└── home/ → 用户主目录
export CROSS_COMPILE=arm-linux-
gdb-multiarch vmlinux
target remote :1234
| 工具 | 用途 |
|---|---|
| GDB | 程序级调试 |
| strace | 跟踪系统调用 |
| dmesg | 内核日志查看 |
| ldd | 查看依赖的库文件 |
| top / htop | 查看系统资源使用情况 |
| lsmod/insmod | 加载/查看内核模块 |
| 平台 | 特点 |
|---|---|
| Raspberry Pi | 社区活跃,支持 Linux 全栈 |
| Allwinner / Rockchip | 国产主控,适配良好 |
| BeagleBone | 支持 PRU、实时协处理器 |
| STM32MP1 | 支持 Linux + Cortex-M 协同 |
每个组件仅拥有完成任务所需的最小权限(如 MPU 配置)。
多层次安全机制(如安全启动 + 通信加密 + 运行时防护)。
系统在异常情况下自动进入安全状态(如看门狗复位)。
保证启动时加载的固件是可信的
BootROM → 加载并验证一级Bootloader → 加载并验证二级Bootloader → 加载并验证应用固件
每个阶段只信任经过上一阶段验证的代码。
// 简化的签名验证伪代码
bool VerifyFirmwareSignature(uint8_t *firmware, uint32_t size, uint8_t *signature) {
// 1. 从OTP读取可信根公钥
const uint8_t *trusted_public_key = GetTrustedPublicKey();
// 2. 计算固件哈希值
uint8_t calculated_hash[32];
SHA256(firmware, size, calculated_hash);
// 3. 使用公钥解密签名获取原始哈希
uint8_t decrypted_hash[32];
RSA_PKCS1_Verify(trusted_public_key, signature, decrypted_hash);
// 4. 比较哈希值
return (memcmp(calculated_hash, decrypted_hash, 32) == 0);
}
// 启用读保护(RDP)
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef obInit = {0};
obInit.OptionType = OPTIONBYTE_RDP;
obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
HAL_FLASHEx_OBProgram(&obInit);
HAL_FLASH_OB_Lock();
// 配置安全/非安全区域
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 配置SRAM为安全区域
MPU_InitStruct.Number = MPU_REGION_0;
MPU_InitStruct.BaseAddress = 0x20000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = DISABLE;
MPU_InitStruct.IsShareable = ENABLE;
MPU_InitStruct.IsCacheable = DISABLE;
MPU_InitStruct.IsBufferable = DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
| RDP 级别 | 保护效果 | 可逆性 |
|---|---|---|
| Level 0 | 无保护(默认) | 是 |
| Level 1 | 禁止调试接口,Flash 只能运行不能读取 | 降级会擦除所有 Flash |
| Level 2 | 永久禁止调试接口和 Flash 读取 | 不可逆 |
将线性代码转换为基于状态机的结构,增加逆向难度。
用等效指令序列替换关键操作(如a+b替换为a-(-b))。
// 配置MPU保护关键数据区
void ConfigureMPU(void) {
// 使能MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
// 配置区域0保护关键代码区
MPU_Region_InitTypeDef MPU_InitStruct = {0};
MPU_InitStruct.Number = MPU_REGION_0;
MPU_InitStruct.BaseAddress = 0x08000000; // Flash起始地址
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO; // 特权可读写,用户只读
MPU_InitStruct.DisableExec = DISABLE;
MPU_InitStruct.IsShareable = DISABLE;
MPU_InitStruct.IsCacheable = DISABLE;
MPU_InitStruct.IsBufferable = DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
}
| 类别 | 示例 | 存储位置 |
|---|---|---|
| 密钥 | TLS 私钥、加密密钥 | 安全 SRAM |
| 敏感算法 | 密码验证、加密函数 | 安全代码区 |
| 安全服务 | OTA 签名验证、证书管理 | 安全任务 |
// 从非安全代码调用安全服务
__attribute__((section(".nonsecure_call")))
uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param2) {
// 通过SVC指令切换到安全模式
__asm("SVC #0");
// 返回值通过R0传递
}
嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
// 基本 UART 初始化
USART1->BRR = 0x1A1; // 设置波特率
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 使能收发
// TCP 客户端基础逻辑 (伪代码)
socket();
connect();
send();
recv();
| 分类 | 范围 | 含义 |
|---|---|---|
| 1xx | 100~199 | 接收中,继续处理 |
| 2xx | 200~299 | 请求成功 |
| 3xx | 300~399 | 重定向或更多操作 |
| 4xx | 400~499 | 客户端错误 |
| 5xx | 500~599 | 服务器错误 |
部分状态码示例:
Connection: keep-alive)GET /hello.htm HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: */*
Connection: Keep-Alive
... 其他头部字段
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 158
Server: Apache
Date: Sun, 14 Jun 2025 10:00:00 GMT
<html>...</html>
HTTPS = HTTP + TLS/SSL 加密
// mbed TLS配置PSK
mbedtls_ssl_config_set_psk(&ssl_conf,
psk, // 预共享密钥
psk_length,
identity, // 身份标识
strlen(identity));
// 验证服务器证书链
int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
// 检查证书有效期
if (mbedtls_x509_crt_check_validity(crt, time(NULL)) != 0) {
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
// 检查证书颁发者
if (!mbedtls_x509_crt_verify(crt, trusted_certs, NULL, NULL, flags, NULL, NULL)) {
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
return 0;
}
// 检测SWD/JTAG调试接口
bool IsDebuggerAttached(void) {
// 读取DBGMCU_IDCODE寄存器
uint32_t idcode = DBGMCU->IDCODE;
// 检查调试使能位
return ((DBGMCU->CR & (DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY)) != 0);
}
// 常量时间比较(防止时序攻击)
bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
uint8_t result = 0;
for (size_t i = 0; i < len; i++) {
result |= a[i] ^ b[i];
}
return (result == 0);
}
| 层级 | 协议/组件 | 功能说明 |
|---|---|---|
| 应用层 | HTTP, MQTT, CoAP, DNS | 面向用户的协议 |
| 传输层 | TCP, UDP | 数据传输可靠性与端口管理 |
| 网络层 | IP, ICMP, ARP | 地址与路由 |
| 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
| 特性 | TCP | UDP |
|---|---|---|
| 是否连接 | 是(面向连接) | 否(无连接) |
| 是否可靠 | 是(有重传、确认) | 否(可能丢包) |
| 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
| 开销 | 较大(握手、窗口等) | 较小(直接发送) |
recv(), send()close()// TCP 客户端示例
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(12345);
server.sin_addr.s_addr = inet_addr("192.168.1.10");
connect(sock, (struct sockaddr*)&server, sizeof(server));
send(sock, "Hello", strlen("Hello"), 0);
recv(sock, buffer, sizeof(buffer), 0);
close(sock);
gethostbyname() 等ping 实现通信测试支持远程更新嵌入式系统的固件版本
Flash布局:
+-------------------+ 0x08000000
| Bootloader |
+-------------------+ 0x08010000
| Application Slot A|
+-------------------+ 0x08040000
| Application Slot B|
+-------------------+ 0x08070000
| Configuration Area|
+-------------------+
typedef enum {
OTA_IDLE, // 空闲状态
OTA_CHECKING, // 检查更新
OTA_DOWNLOADING, // 下载中
OTA_VERIFYING, // 校验中
OTA_READY, // 准备重启
OTA_UPGRADING, // 升级中
OTA_FAILED // 升级失败
} OTA_State_t;
// OTA状态机处理函数
void OTA_Process(void) {
switch (ota_state) {
case OTA_IDLE:
if (check_update_flag) {
ota_state = OTA_CHECKING;
vCheckForUpdate();
}
break;
case OTA_DOWNLOADING:
if (download_complete) {
ota_state = OTA_VERIFYING;
vVerifyFirmware();
} else if (download_error) {
ota_state = OTA_FAILED;
vHandleError(DOWNLOAD_ERROR);
}
break;
// 其他状态处理...
}
}
// 启动时验证应用完整性
bool ValidateApplication(uint32_t start_address) {
// 检查向量表签名
uint32_t *vector_table = (uint32_t *)start_address;
if (vector_table[0] == 0xFFFFFFFF) { // 检查栈顶指针是否有效
return false;
}
// 计算应用哈希并验证
uint8_t calculated_hash[32];
SHA256((uint8_t *)start_address, APPLICATION_SIZE, calculated_hash);
// 从配置区获取预期哈希
uint8_t *expected_hash = GetExpectedHash();
return (memcmp(calculated_hash, expected_hash, 32) == 0);
}
// 主程序
int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();
// 检查主应用是否有效
if (ValidateApplication(APPLICATION_SLOT_A_ADDRESS)) {
// 跳转到主应用
JumpToApplication(APPLICATION_SLOT_A_ADDRESS);
} else if (ValidateApplication(APPLICATION_SLOT_B_ADDRESS)) {
// 主应用无效,尝试从备份应用启动
JumpToApplication(APPLICATION_SLOT_B_ADDRESS);
} else {
// 两个应用都无效,进入恢复模式
EnterRecoveryMode();
}
}
| 问题 | 解答 |
|---|---|
| MQTT 断线如何重连? | 设置心跳机制与 reconnect 回调逻辑 |
| OTA 更新失败怎么办? | 回退机制 + 双镜像分区设计 |
| CoAP 和 MQTT 有何区别? | CoAP 基于 UDP,适合低功耗设备;MQTT 基于 TCP,稳定性好 |
JTAG 与 SWD 接口对比
| 特性 | JTAG | SWD |
|---|---|---|
| 引脚数 | 5 线(TMS、TCK、TDI、TDO、TRST) | 2 线(SWDIO、SWCLK) |
| 速度 | 中低速(典型 1-10MHz) | 高速(可达 50MHz 以上) |
| 占用资源 | 高(需多个 GPIO) | 低(仅 2 个 GPIO) |
| 级联能力 | 支持多设备(通过 TAP 控制器) | 不支持级联 |
| 适用场景 | 复杂芯片调试(如 FPGA) | 嵌入式 MCU(如 STM32) |
// 使能SWD接口(禁用JTAG以释放GPIO)
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14; // SWDIO, SWCLK
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_SWJ;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
关键命令详解:
# 1. 启动OpenOCD(连接ST-Link与目标MCU)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
# 2. 启动GDB并加载ELF文件
arm-none-eabi-gdb path/to/firmware.elf
# 3. 连接到OpenOCD服务器
(gdb) target remote :3333 # 默认端口3333
# 4. 下载程序到Flash
(gdb) load
# 5. 复位并暂停CPU
(gdb) monitor reset halt
# 6. 设置断点
(gdb) break main # 在main()函数入口设置断点
(gdb) break MyFunction # 在自定义函数设置断点
(gdb) break file.c:123 # 在文件file.c的第123行设置断点
# 7. 执行控制
(gdb) continue # 继续执行
(gdb) next # 单步执行(不进入函数)
(gdb) step # 单步执行(进入函数)
(gdb) finish # 运行到当前函数结束
# 8. 查看变量
(gdb) print myVariable # 打印变量值
(gdb) p &myArray[0] # 打印数组地址
(gdb) x/10i $pc # 查看当前执行的10条汇编指令
# 9. 查看寄存器
(gdb) info registers # 查看所有寄存器
(gdb) p $r0 # 查看特定寄存器(如R0)
逻辑分析仪:用于捕捉数字信号波形,分析通信协议(如 I2C, SPI)。
逻辑分析仪典型场景:
示波器:查看模拟信号、电压、电流变化。对调试电源问题、干扰、PWM波形等极为重要。
示波器关键参数:
// 配置TIM3输出PWM(频率1kHz,占空比50%)
TIM_HandleTypeDef htim3;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
htim3.Init.Period = 1000 - 1; // 1MHz / 1000 = 1kHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim3);
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
使用示波器测量:频率应为 1kHz,高电平时间 500μs(占空比 50%)。
printf() 输出信息到串口查看程序执行流程。vTraceEnable() 开启追踪。// 自定义跟踪点示例
#define TRACE_TASK_SWITCH() do { \
uint32_t current_task = (uint32_t)pxCurrentTCB; \
uint32_t timestamp = xTaskGetTickCount(); \
vTraceStoreEvent(EVENT_TASK_SWITCH, timestamp, current_task); \
} while(0)
#define configUSE_TRACE_FACILITY 1 // 启用跟踪功能
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 启用统计功能
#define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
| 模式 | 唤醒时间 | 功耗 | 保留内容 |
|---|---|---|---|
| Sleep | 数 μs | 几 mA | CPU 寄存器、SRAM 内容 |
| Stop | 几十 μs | 几 μA | SRAM 内容、部分寄存器 |
| Standby | 几 ms | 几十 nA | 仅备份寄存器(如 RTC) |
// 禁用未使用的外设时钟
__HAL_RCC_GPIOA_CLK_DISABLE(); // 禁用GPIOA时钟
__HAL_RCC_SPI1_CLK_DISABLE(); // 禁用SPI1时钟
// 仅在需要时启用外设
void vReadSensor(void) {
__HAL_RCC_I2C1_CLK_ENABLE(); // 启用I2C时钟
// 读取传感器数据
__HAL_RCC_I2C1_CLK_DISABLE(); // 读取完成后禁用时钟
}
// 配置RTC闹钟唤醒(每10秒唤醒一次)
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
RTC_AlarmTypeDef sAlarm = {0};
sTime.Hours = 0;
sTime.Minutes = 0;
sTime.Seconds = 0;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 1;
sDate.Year = 0;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sAlarm.AlarmTime = sTime;
sAlarm.Alarm = RTC_ALARM_A;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
// 进入Standby模式
HAL_PWR_EnterSTANDBYMode();
// 记录内存分配/释放情况
void *pvPortMalloc( size_t xWantedSize ) {
void *pvReturn = NULL;
vTaskSuspendAll();
{
// 记录分配信息(如分配地址、大小、时间)
pvReturn = prvHeapAllocateMemory( xWantedSize );
vRecordMemoryAllocation(pvReturn, xWantedSize);
}
xTaskResumeAll();
return pvReturn;
}
static uint32_t ulLastInterruptTime = 0;
#define DEBOUNCE_TIME 50 // 50ms
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
uint32_t ulCurrentTime = xTaskGetTickCount();
if (ulCurrentTime - ulLastInterruptTime > DEBOUNCE_TIME) {
// 处理有效中断
vProcessButtonPress();
ulLastInterruptTime = ulCurrentTime;
}
}
采用Conventional Commits规范:
<类型>[可选范围]: <描述>
[可选正文]
[可选脚注]
feat:新功能fix:修复Bugdocs:文档更新style:代码格式调整(不影响功能)refactor:代码重构test:添加或修改测试chore:构建或辅助工具的变动使用语义化版本(SemVer)打标签:
# 创建标签
git tag v1.0.0
# 推送标签到远程
git push origin v1.0.0
# 查看所有标签
git tag -l
CC = arm-none-eabi-gcc
CFLAGS = -Wall -O2 -mcpu=cortex-m4 -mthumb
LDFLAGS = -Tstm32f4.ld
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
TARGET = firmware.elf
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
cmake_minimum_required(VERSION 3.10)
project(EmbeddedProject C)
# 设置交叉编译工具链
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
# 添加编译选项
add_compile_options(
-mcpu=cortex-m4
-mthumb
-mfloat-abi=hard
-mfpu=fpv4-sp-d16
-Wall
-Wextra
-Os
)
# 添加链接选项
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
# 添加源文件
file(GLOB_RECURSE SOURCES "src/*.c" "drivers/*.c")
# 添加可执行文件
add_executable(${PROJECT_NAME}.elf ${SOURCES})
# 添加目标文件
add_custom_target(${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
DEPENDS ${PROJECT_NAME}.elf
)
name: Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc-arm-none-eabi cmake ninja-build
- name: Configure CMake
run: cmake -B build -G Ninja
- name: Build
run: cmake --build build
- name: Run tests
run: |
cd build
ctest --output-on-failure
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'make clean all'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
stage('Code Coverage') {
steps {
sh 'make coverage'
}
post {
always {
junit 'build/test-results/*.xml'
publishCoverage adapters: [coberturaAdapter('build/coverage/coverage.xml')]
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'make deploy'
}
}
}
}
+----------------------+
| 应用层 |
| (业务逻辑、算法) |
+----------------------+
| 服务层 |
| (任务管理、事件) |
+----------------------+
| 驱动层 |
| (硬件抽象、BSP) |
+----------------------+
| 硬件层 |
| (MCU、外设) |
+----------------------+
project/
├── app/ # 应用层
│ ├── main.c # 主程序入口
│ ├── modules/ # 功能模块
│ │ ├── sensor/ # 传感器处理
│ │ ├── comm/ # 通信处理
│ │ └── control/ # 控制逻辑
│ └── config/ # 配置文件
├── services/ # 服务层
│ ├── task_mgr/ # 任务管理器
│ ├── event/ # 事件系统
│ └── utils/ # 工具函数
├── drivers/ # 驱动层
│ ├── bsp/ # 板级支持包
│ ├── hal/ # 硬件抽象层
│ └── periph/ # 外设驱动
└── build/ # 构建系统
├── cmake/ # CMake配置
└── Makefile # Makefile
// bsp_led.h
#ifndef BSP_LED_H
#define BSP_LED_H
#include <stdint.h>
typedef enum {
LED_RED,
LED_GREEN,
LED_BLUE
} led_t;
typedef enum {
LED_OFF,
LED_ON,
LED_TOGGLE
} led_state_t;
// 初始化LED
void bsp_led_init(void);
// 设置LED状态
void bsp_led_set(led_t led, led_state_t state);
#endif
// bsp_led.c (STM32实现)
#include "bsp_led.h"
#include "stm32f4xx_hal.h"
// LED GPIO定义
#define LED_RED_PIN GPIO_PIN_14
#define LED_RED_PORT GPIOG
#define LED_GREEN_PIN GPIO_PIN_13
#define LED_GREEN_PORT GPIOG
#define LED_BLUE_PIN GPIO_PIN_15
#define LED_BLUE_PORT GPIOG
void bsp_led_init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO时钟
__HAL_RCC_GPIOG_CLK_ENABLE();
// 配置GPIO引脚
GPIO_InitStruct.Pin = LED_RED_PIN | LED_GREEN_PIN | LED_BLUE_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
// 默认关闭所有LED
HAL_GPIO_WritePin(LED_RED_PORT, LED_RED_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_BLUE_PORT, LED_BLUE_PIN, GPIO_PIN_RESET);
}
void bsp_led_set(led_t led, led_state_t state) {
GPIO_TypeDef *port;
uint16_t pin;
// 根据LED类型选择GPIO
switch (led) {
case LED_RED:
port = LED_RED_PORT;
pin = LED_RED_PIN;
break;
case LED_GREEN:
port = LED_GREEN_PORT;
pin = LED_GREEN_PIN;
break;
case LED_BLUE:
port = LED_BLUE_PORT;
pin = LED_BLUE_PIN;
break;
default:
return;
}
// 设置LED状态
switch (state) {
case LED_OFF:
HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
break;
case LED_ON:
HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
break;
case LED_TOGGLE:
HAL_GPIO_TogglePin(port, pin);
break;
}
}
// spi_interface.h (抽象接口)
#ifndef SPI_INTERFACE_H
#define SPI_INTERFACE_H
#include <stdint.h>
typedef struct {
// 初始化SPI
void (*init)(uint32_t baudrate);
// 发送数据
void (*send)(const uint8_t *data, uint32_t length);
// 接收数据
void (*receive)(uint8_t *data, uint32_t length);
// 发送并接收数据
void (*transfer)(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);
} spi_interface_t;
// 获取SPI接口实例
const spi_interface_t* spi_get_interface(void);
#endif
// spi_stm32.c (STM32实现)
#include "spi_interface.h"
#include "stm32f4xx_hal.h"
static SPI_HandleTypeDef hspi1;
static void spi_init(uint32_t baudrate) {
// 配置SPI参数
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
// 根据波特率计算分频系数
uint32_t prescaler = SPI_BAUDRATEPRESCALER_2;
if (baudrate < 1000000) prescaler = SPI_BAUDRATEPRESCALER_128;
else if (baudrate < 2000000) prescaler = SPI_BAUDRATEPRESCALER_64;
else if (baudrate < 4000000) prescaler = SPI_BAUDRATEPRESCALER_32;
else if (baudrate < 8000000) prescaler = SPI_BAUDRATEPRESCALER_16;
else if (baudrate < 16000000) prescaler = SPI_BAUDRATEPRESCALER_8;
else if (baudrate < 32000000) prescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.BaudRatePrescaler = prescaler;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
// 初始化SPI
HAL_SPI_Init(&hspi1);
}
static void spi_send(const uint8_t *data, uint32_t length) {
HAL_SPI_Transmit(&hspi1, (uint8_t*)data, length, 1000);
}
static void spi_receive(uint8_t *data, uint32_t length) {
HAL_SPI_Receive(&hspi1, data, length, 1000);
}
static void spi_transfer(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length) {
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)tx_data, rx_data, length, 1000);
}
// SPI接口实现
static const spi_interface_t spi_impl = {
.init = spi_init,
.send = spi_send,
.receive = spi_receive,
.transfer = spi_transfer
};
// 获取SPI接口实例
const spi_interface_t* spi_get_interface(void) {
return &spi_impl;
}
Flash布局:
+-------------------+ 0x08000000
| Bootloader | (80KB)
+-------------------+ 0x08014000
| Application A | (448KB)
+-------------------+ 0x08084000
| Application B | (448KB)
+-------------------+ 0x08104000
| Configuration | (16KB)
+-------------------+
typedef enum {
OTA_IDLE, // 空闲状态
OTA_CHECKING, // 检查更新
OTA_DOWNLOADING, // 下载中
OTA_DOWNLOAD_PAUSED, // 下载暂停
OTA_VERIFYING, // 校验中
OTA_READY, // 准备重启
OTA_UPGRADING, // 升级中
OTA_FAILED // 升级失败
} ota_state_t;
typedef struct {
ota_state_t state;
uint32_t total_size;
uint32_t downloaded_size;
uint8_t progress;
char error_msg[64];
uint8_t firmware_hash[32];
} ota_context_t;
检查更新:
bool ota_check_update(void) {
// 从服务器获取版本信息
http_response_t response = http_get(UPDATE_SERVER_URL "/version");
if (response.status != 200) {
return false;
}
// 解析服务器版本
uint32_t server_version = parse_version(response.body);
uint32_t current_version = get_current_version();
// 比较版本
return (server_version > current_version);
}
下载固件:
void ota_download_firmware(void) {
// 打开固件下载URL
http_client_t client = http_open(UPDATE_SERVER_URL "/firmware.bin");
if (!client) {
ota_set_state(OTA_FAILED, "Failed to open URL");
return;
}
// 获取文件大小
uint32_t file_size = http_get_content_length(client);
ota_set_total_size(file_size);
// 开始下载
uint8_t buffer[512];
uint32_t bytes_received = 0;
uint32_t bytes_written = 0;
while ((bytes_received = http_read(client, buffer, 512)) > 0) {
// 写入到备份区
if (!flash_write(APPLICATION_B_ADDRESS + bytes_written, buffer, bytes_received)) {
ota_set_state(OTA_FAILED, "Flash write failed");
http_close(client);
return;
}
bytes_written += bytes_received;
ota_update_progress(bytes_written * 100 / file_size);
// 检查是否需要暂停
if (ota_should_pause()) {
http_close(client);
ota_set_state(OTA_DOWNLOAD_PAUSED, "Download paused");
return;
}
}
http_close(client);
ota_set_state(OTA_VERIFYING, "Verifying firmware");
}
验证与应用:
bool ota_verify_firmware(void) {
// 计算下载固件的哈希值
uint8_t calculated_hash[32];
calculate_firmware_hash(APPLICATION_B_ADDRESS, APPLICATION_SIZE, calculated_hash);
// 与服务器提供的哈希值比较
if (memcmp(calculated_hash, ota_get_expected_hash(), 32) != 0) {
return false;
}
// 验证向量表
uint32_t *vector_table = (uint32_t*)APPLICATION_B_ADDRESS;
if (vector_table[0] == 0 || vector_table[1] == 0) {
return false;
}
return true;
}
void ota_apply_update(void) {
// 设置升级标志
set_update_flag(1);
// 保存新固件版本
save_new_version(get_server_version());
// 重启系统
NVIC_SystemReset();
}
官网链接:
安装步骤:
Ctrl+Shift+X)验证安装:
打开VS Code,点击左下角的 PlatformIO Home 图标,若能正常打开则安装成功。
官网链接:
安装步骤:
验证安装:
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
官网链接:
安装步骤:
验证安装:
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
官网链接:
安装步骤:
Windows:
C:\openocd)bin 目录添加到系统环境变量Linux:
sudo apt-get install openocd # Ubuntu/Debian
sudo yum install openocd # CentOS/RHEL
macOS:
brew install open-ocd
验证安装:
在终端中运行 openocd --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
bin 目录添加到系统环境变量验证安装:
在终端中运行 arm-none-eabi-gdb --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
Windows:
Linux:
sudo apt-get install stlink-tools # Ubuntu/Debian
验证安装:
在终端中运行 st-info --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
Windows:
Linux:
sudo apt-get install cppcheck # Ubuntu/Debian
sudo yum install cppcheck # CentOS/RHEL
macOS:
brew install cppcheck
验证安装:
在终端中运行 cppcheck --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
Windows:
Linux:
sudo apt-get install clang-tidy # Ubuntu/Debian
macOS:
brew install llvm
验证安装:
在终端中运行 clang-tidy --version,若显示版本信息则安装成功。
官网链接:
安装步骤:
docker run -d --name sonarqube -p 9000:9000 sonarqube
验证安装:
在浏览器中打开 http://localhost:9000,若能看到SonarQube界面则安装成功。
官网链接:
安装步骤:
git clone https://github.com/ThrowTheSwitch/Unity.git
src 目录添加到项目的头文件搜索路径验证安装:
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
官网链接:
安装步骤:
git clone https://github.com/ThrowTheSwitch/CMock.git
src 目录添加到项目的头文件搜索路径验证安装:
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
官网链接:
安装步骤:
git clone https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
cmake ..
make
sudo make install
验证安装:
创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。
| 工具 | 官网链接 | 安装指南 |
|---|---|---|
| VS Code | https://code.visualstudio.com/ | 直接下载安装包 |
| PlatformIO | https://platformio.org/ | VS Code扩展市场安装 |
| STM32CubeIDE | https://www.st.com/en/development-tools/stm32cubeide.html | 官网下载安装包 |
| CLion | https://www.jetbrains.com/clion/ | 官网下载安装包 |
| OpenOCD | http://openocd.org/ | 包管理器或预编译二进制包 |
| GDB | https://www.gnu.org/software/gdb/ | 随ARM GCC Toolchain安装 |
| ST-Link/V2 | https://www.st.com/en/development-tools/st-link-v2.html | 官网下载驱动 |
| CppCheck | https://cppcheck.sourceforge.io/ | 包管理器或安装包 |
| Clang-Tidy | https://clang.llvm.org/extra/clang-tidy/ | 随LLVM安装 |
| SonarQube | https://www.sonarqube.org/ | Docker容器或独立安装 |
| Unity | https://github.com/ThrowTheSwitch/Unity | 从GitHub下载源码 |
| CMock | https://github.com/ThrowTheSwitch/CMock | 从GitHub下载源码 |
| Google Test | https://github.com/google/googletest | CMake构建并安装 |
模型训练:
使用TensorFlow/Keras等工具在PC上训练模型。
# 简单MNIST模型示例
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)
模型量化:
将浮点模型转换为整数模型,减少内存占用和计算量。
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
模型部署:
将量化后的模型转换为C数组,集成到MCU项目中。
xxd -i model.tflite > model_data.cc
MCU推理:
使用TensorFlow Lite Micro框架在MCU上运行模型。
// 初始化解释器
tflite::MicroErrorReporter micro_error_reporter;
const tflite::ErrorReporter* error_reporter = µ_error_reporter;
const tflite::MicroOpResolver& op_resolver = MicroOpsResolver();
const tflite::SimpleTensorAllocator tensor_allocator(tensor_arena, kTensorArenaSize);
tflite::MicroInterpreter interpreter(model_data, model_data_len, op_resolver,
tensor_allocator, error_reporter);
// 运行推理
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
error_reporter->Report("Invoke failed\n");
}
| 模型 | 参数量 | 激活内存 | 准确率 | 推理时间(STM32H7) |
|---|---|---|---|---|
| MobileNetV1 | 4.2M | 16MB | 70.6% | 800ms |
| TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
| EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
# 使用x-cube-ai命令行工具转换模型
stm32ai generate -m model.h5 -o stm32ai_output
| 挑战 | 解决方案 |
|---|---|
| 内存受限 | 使用内存映射技术,模型分段加载 |
| 计算能力有限 | 优化算子实现,利用硬件加速指令 |
| 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
| 模型更新 | 设计OTA机制,支持模型动态更新 |
// 从加速度计读取数据
void read_accelerometer_data(float *data, size_t length) {
// 读取加速度计原始数据
int16_t raw_data[3];
accelerometer_read(raw_data);
// 转换为浮点数并归一化
for (int i = 0; i < 3; i++) {
data[i] = (float)raw_data[i] / 32768.0f;
}
}
// 运行AI模型进行活动识别
activity_t recognize_activity(float *sensor_data) {
// 准备模型输入
TfLiteTensor* input = interpreter->input(0);
memcpy(input->data.f, sensor_data, input->bytes);
// 执行推理
if (interpreter->Invoke() != kTfLiteOk) {
return ACTIVITY_UNKNOWN;
}
// 获取输出结果
TfLiteTensor* output = interpreter->output(0);
int activity_index = argmax(output->data.f, output->dims->data[0]);
return (activity_t)activity_index;
}
硬件信任根:
验证流程:
ROM → 验证Bootloader签名 → 验证应用固件签名 → 启动应用
选项字节配置:
// 启用读保护(RDP)
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef obInit;
obInit.OptionType = OPTIONBYTE_RDP;
obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
HAL_FLASHEx_OBProgram(&obInit);
HAL_FLASH_OB_Lock();
签名验证:
// 验证固件签名
bool verify_firmware_signature(const uint8_t *firmware, size_t size, const uint8_t *signature) {
// 从OTP读取公钥
const uint8_t *public_key = get_public_key_from_otp();
// 使用ECDSA验证签名
return ecdsa_verify(public_key, firmware, size, signature);
}
硬件连接:
STM32通过I2C/SPI与TPM芯片(如Infineon OPTIGA™ TPM SLB 9670)通信。
软件实现:
// TPM初始化
tpm_error_t tpm_init(void) {
// 初始化I2C接口
i2c_init(TPM_I2C_ADDRESS);
// 发送TPM启动命令
uint8_t startup_cmd[10] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44};
uint8_t response[20];
if (i2c_write(TPM_I2C_ADDRESS, startup_cmd, 10) != 0) {
return TPM_ERROR_COMMUNICATION;
}
// 读取响应
if (i2c_read(TPM_I2C_ADDRESS, response, 20) != 0) {
return TPM_ERROR_COMMUNICATION;
}
// 验证响应
if (response[6] == 0x00 && response[7] == 0x00) {
return TPM_SUCCESS;
} else {
return TPM_ERROR_INITIALIZATION;
}
}
// 生成密钥
tpm_error_t tpm_generate_key(uint8_t *key_handle, uint8_t *public_key) {
// 发送生成密钥命令
// ...
// 处理响应
// ...
return TPM_SUCCESS;
}
AI on MCU:
安全性:
实战案例:
本项目内容均来源于互联网公开资料,仅供学习交流使用,版权归原作者所有。
若原作者认为本项目引用内容存在侵权,请通过 issue 或邮件联系我,我们将在第一时间内删除或修正。
10 followers · starred Jul 2026
68 followers · starred Jul 2026
5 followers · starred Oct 2025