登峰造极:一年嵌入式技术系统修炼完全指南
前言:为何需要一场"技术苦修"?
在嵌入式系统开发的浩瀚海洋中,初学者常感迷茫:C语言基础不牢,硬件操作如同隔靴搔痒,实时操作系统(RTOS)如空中楼阁,通信协议更是错综复杂。然而,现代嵌入式开发已不再是单一技术的较量,而是对开发者综合能力的严苛考验。从底层硬件寄存器配置,到上层应用协议栈的移植,再到系统级的性能优化,每一个环节都要求开发者具备扎实的理论功底和丰富的实战经验。
一周突击学习或许能让你窥见门径,但真正的技术沉淀需要时间的积累与打磨。本文将为您设计一套为期一年的嵌入式技术系统修炼计划。这不是一份简单的知识点清单,而是一套经过精心设计的、以实战为导向、以逻辑为核心、以闭环为目标的系统学习方案。我们不会浮于表面的概念背诵,而是深入每一个技术的底层原理,通过大量的代码实例、硬件调试和项目整合,让您在一年的时间内构建起完整、坚固的嵌入式技术知识体系。
学习核心原则:
- 重实战: 每一个知识点都必须有对应的代码验证。理论是地图,代码是脚步,只有双脚踩在泥土上,才能走出属于自己的路。
- 强逻辑: 拒绝死记硬背。理解"为什么"比知道"是什么"更重要。例如,理解指针运算的地址偏移逻辑,远比记住指针的写法更有价值。
- 做闭环: 从点亮一个LED灯,到通过串口打印数据,再到将数据上传至云端,形成完整的"感知-处理-传输-控制"闭环。只有将碎片化的知识串联起来,才能真正理解嵌入式系统的精髓。
准备好了吗?让我们开始这段为期一年的极限挑战。
第一阶段:筑基期(第1-3月)------ C语言核心筑基与硬件基础
第一月:C语言核心筑基------嵌入式开发的灵魂
C语言是嵌入式世界的通用语言。第一个月的目标不是泛泛地复习语法,而是直击嵌入式开发中最核心、最易出错、也最重要的部分:指针、内存、函数、预处理和中断基础。今天的每一个知识点,都将在后续的硬件开发中反复出现。
第1周:指针与内存------驾驭内存的"地址之魂"
在嵌入式系统中,我们操作的不是抽象的变量,而是具体的物理内存地址。指针,就是连接变量名与内存地址的桥梁。
1.1 指针运算:地址的舞蹈
指针的加减运算,其步长由指针所指向的数据类型决定。这是C语言中一个极其重要且易混淆的概念。
c
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p_int = arr; // p_int指向数组首地址,即 &arr[0]
char *p_char = (char*)arr; // p_char也指向同一地址,但类型为char*
printf("Address of arr[0]: %p\n", (void*)p_int);
printf("p_int + 1: %p\n", (void*)(p_int + 1)); // 移动 sizeof(int) 字节,通常为4
printf("p_char + 1: %p\n", (void*)(p_char + 1)); // 移动 sizeof(char) 字节,为1
// 指针运算在实际中的应用:遍历数组
for(int i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(p_int + %d) = %d\n", i, arr[i], i, *(p_int + i));
}
return 0;
}
深度解析:
p_int + 1的地址值比p_int大了4(在32位或64位系统中int通常为4字节)。这意味着指针运算自动处理了类型大小,让程序员可以逻辑地访问数组元素。p_char + 1只移动了1个字节。这种差异在操作硬件寄存器(按字节访问)或处理内存池时至关重要。- 应用场景: 在嵌入式系统中,我们常使用指针来遍历外设的缓冲区(如UART接收缓冲区),或直接操作连续的内存区域(如LCD显存)。
实战项目:实现一个简单的内存池管理器
c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define POOL_SIZE 1024
#define BLOCK_SIZE 32
#define BLOCK_COUNT (POOL_SIZE / BLOCK_SIZE)
typedef struct {
uint8_t pool[POOL_SIZE];
uint8_t used[BLOCK_COUNT];
} MemoryPool;
MemoryPool g_mempool;
void mempool_init(MemoryPool *mp) {
memset(mp->used, 0, sizeof(mp->used));
}
void* mempool_alloc(MemoryPool *mp) {
for(int i = 0; i < BLOCK_COUNT; i++) {
if(!mp->used[i]) {
mp->used[i] = 1;
return &mp->pool[i * BLOCK_SIZE];
}
}
return NULL; // 内存耗尽
}
void mempool_free(MemoryPool *mp, void *ptr) {
if(ptr == NULL) return;
uintptr_t addr = (uintptr_t)ptr;
uintptr_t base = (uintptr_t)mp->pool;
if(addr < base || addr >= base + POOL_SIZE) return;
int index = (addr - base) / BLOCK_SIZE;
if(index >= 0 && index < BLOCK_COUNT) {
mp->used[index] = 0;
}
}
int main() {
mempool_init(&g_mempool);
void *block1 = mempool_alloc(&g_mempool);
void *block2 = mempool_alloc(&g_mempool);
printf("Allocated block1 at %p, block2 at %p\n", block1, block2);
mempool_free(&g_mempool, block1);
printf("Freed block1\n");
void *block3 = mempool_alloc(&g_mempool);
printf("Allocated block3 at %p\n", block3);
return 0;
}
1.2 野指针与内存泄漏:内存管理的两大杀手
野指针: 指向"垃圾"内存的指针。其成因主要有三:
- 指针未初始化: 局部指针变量在栈上分配,其值是随机的。
- 指针指向的内存被释放: 调用
free(p)后,没有将p置为NULL,p变成了一个悬挂指针。 - 指针操作超出了变量作用域: 例如,函数返回了一个指向其内部局部变量的指针。
c
#include <stdio.h>
#include <stdlib.h>
int* create_dangling_pointer() {
int local_var = 100;
return &local_var; // 危险!函数返回后,local_var的内存被回收
}
int main() {
int *p;
// 场景1:未初始化
// printf("%d\n", *p); // 直接使用未初始化的指针,导致未定义行为(通常程序崩溃)
// 场景2:悬挂指针
int *ptr = (int*)malloc(sizeof(int));
*ptr = 10;
free(ptr);
// 此时ptr成为悬挂指针
// *ptr = 20; // 操作已释放的内存,极其危险!
ptr = NULL; // 正确做法:释放后立即置空
// 场景3:返回局部变量地址
int *dangling = create_dangling_pointer();
// *dangling = 200; // 危险!操作的是已被回收的栈空间
return 0;
}
内存泄漏: 动态分配的内存(通过malloc/calloc/realloc)在使用完毕后没有被释放,导致这片内存一直被占用,无法被系统重新利用。在长期运行的嵌入式系统中,内存泄漏是导致系统最终崩溃的主要原因。
c
#include <stdlib.h>
void memory_leak_example() {
int *leak = (int*)malloc(100 * sizeof(int));
// 忘记调用 free(leak);
// 函数返回后,指针leak消失,但分配的100个int大小的内存块仍被标记为"已使用"
// 如果这个函数被反复调用,可用内存将不断减少,最终耗尽
}
int main() {
while(1) {
memory_leak_example(); // 模拟一个持续泄漏的场景
// 在实际嵌入式系统中,这将导致系统在运行一段时间后崩溃
}
return 0;
}
实战原则:
- 谁分配,谁释放: 明确内存的所有权。如果函数内部分配了内存,要么在函数内释放,要么明确地将释放的责任交给调用者。
- 释放即置空:
free(ptr); ptr = NULL;可以防止后续代码误用。 - 使用工具: 在PC端开发时,使用Valgrind等工具检测内存泄漏;在嵌入式端,可以自己实现简单的内存池和跟踪机制。
实战项目:带内存跟踪的malloc/free包装器
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRACK_MEMORY 1
typedef struct {
void *ptr;
size_t size;
const char *file;
int line;
} MemRecord;
#define MAX_RECORDS 100
MemRecord g_records[MAX_RECORDS];
int g_record_count = 0;
void* track_malloc(size_t size, const char *file, int line) {
void *ptr = malloc(size);
if(ptr && TRACK_MEMORY && g_record_count < MAX_RECORDS) {
g_records[g_record_count].ptr = ptr;
g_records[g_record_count].size = size;
g_records[g_record_count].file = file;
g_records[g_record_count].line = line;
g_record_count++;
printf("[ALLOC] %p (%zu bytes) at %s:%d\n", ptr, size, file, line);
}
return ptr;
}
void track_free(void *ptr, const char *file, int line) {
if(ptr == NULL) return;
for(int i = 0; i < g_record_count; i++) {
if(g_records[i].ptr == ptr) {
printf("[FREE] %p (was %zu bytes) at %s:%d\n",
ptr, g_records[i].size, file, line);
// 移除记录
memmove(&g_records[i], &g_records[i+1],
(g_record_count - i - 1) * sizeof(MemRecord));
g_record_count--;
free(ptr);
return;
}
}
printf("[WARNING] Freeing untracked pointer %p at %s:%d\n", ptr, file, line);
free(ptr);
}
void print_memory_leaks(void) {
if(g_record_count == 0) {
printf("No memory leaks detected!\n");
return;
}
printf("\n=== MEMORY LEAKS DETECTED ===\n");
for(int i = 0; i < g_record_count; i++) {
printf("Leak %p (%zu bytes) allocated at %s:%d\n",
g_records[i].ptr, g_records[i].size,
g_records[i].file, g_records[i].line);
}
}
#define malloc(size) track_malloc(size, __FILE__, __LINE__)
#define free(ptr) track_free(ptr, __FILE__, __LINE__)
int main() {
int *a = (int*)malloc(sizeof(int) * 10);
int *b = (int*)malloc(sizeof(int) * 20);
free(a);
// 故意不释放 b,模拟内存泄漏
print_memory_leaks();
return 0;
}
1.3 结构体与联合体:数据组织的艺术
结构体(struct): 将不同类型的数据组合成一个逻辑单元。其内存大小是各成员大小之和(考虑内存对齐)。
c
#include <stdio.h>
#include <stdint.h>
// 定义一个结构体,用于描述一个传感器节点
typedef struct {
char id[5]; // 传感器ID,如 "S001"
float temperature; // 温度值
uint16_t humidity; // 湿度值
uint8_t status; // 状态:0-正常,1-异常
} SensorNode;
int main() {
SensorNode node1 = {"S001", 25.5, 65, 0};
SensorNode *p_node = &node1;
// 通过指针访问结构体成员,使用 -> 运算符
printf("Node ID: %s, Temp: %.1f, Humidity: %d\n",
p_node->id, p_node->temperature, p_node->humidity);
// 修改成员
p_node->temperature = 26.0;
printf("Updated Temp: %.1f\n", node1.temperature); // 直接访问,值已改变
// 结构体指针在嵌入式中的典型应用:传递外设句柄、消息队列数据等
return 0;
}
联合体(union): 所有成员共享同一块内存空间。其大小等于最大成员的大小。联合体常用于:
- 数据拆分与重组: 将一个32位整数拆分为4个字节,或将4个字节组合成一个32位整数。
- 协议解析: 对同一段内存,用不同的视角(结构体)进行解读,实现数据包的灵活解析。
c
#include <stdio.h>
#include <stdint.h>
// 联合体用于拆分32位数据
typedef union {
uint32_t word; // 32位整数值
uint8_t bytes[4]; // 按字节访问
} WordToBytes;
int main() {
WordToBytes converter;
converter.word = 0x12345678; // 假设小端模式,低地址存低字节
// 打印每个字节的值
// 在小端模式下,bytes[0] = 0x78, bytes[1]=0x56, bytes[2]=0x34, bytes[3]=0x12
printf("Byte0: 0x%02X\n", converter.bytes[0]);
printf("Byte1: 0x%02X\n", converter.bytes[1]);
printf("Byte2: 0x%02X\n", converter.bytes[2]);
printf("Byte3: 0x%02X\n", converter.bytes[3]);
// 实战应用:从串口接收到的4个字节,组合成一个float或int32_t
uint8_t rx_buffer[4] = {0xCD, 0xCC, 0x4C, 0x3F}; // 对应浮点数 0.8
WordToBytes rx_data;
for(int i=0; i<4; i++) rx_data.bytes[i] = rx_buffer[i];
printf("Reconstructed value: 0x%08X\n", rx_data.word);
// 注意:如果目标是浮点数,可以用另一个联合体成员 float f_val; 来解读
return 0;
}
实战项目:实现一个灵活的数据包解析器
c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 定义不同类型的数据包
typedef enum {
PKT_TYPE_TEMPERATURE = 0x01,
PKT_TYPE_HUMIDITY = 0x02,
PKT_TYPE_PRESSURE = 0x03,
PKT_TYPE_STATUS = 0x04
} PacketType;
// 使用联合体实现不同类型的数据包
typedef struct {
uint8_t type;
uint8_t length;
union {
float temperature;
uint16_t humidity;
uint32_t pressure;
uint8_t status;
uint8_t raw[4];
} data;
} Packet;
// 打包函数
void pack_temperature(Packet *pkt, float temp) {
pkt->type = PKT_TYPE_TEMPERATURE;
pkt->length = sizeof(float);
pkt->data.temperature = temp;
}
void pack_humidity(Packet *pkt, uint16_t hum) {
pkt->type = PKT_TYPE_HUMIDITY;
pkt->length = sizeof(uint16_t);
pkt->data.humidity = hum;
}
// 解析函数
void parse_packet(Packet *pkt) {
switch(pkt->type) {
case PKT_TYPE_TEMPERATURE:
printf("Temperature: %.2f°C\n", pkt->data.temperature);
break;
case PKT_TYPE_HUMIDITY:
printf("Humidity: %d%%\n", pkt->data.humidity);
break;
case PKT_TYPE_PRESSURE:
printf("Pressure: %d Pa\n", pkt->data.pressure);
break;
case PKT_TYPE_STATUS:
printf("Status: %s\n", pkt->data.status ? "Error" : "OK");
break;
default:
printf("Unknown packet type: 0x%02X\n", pkt->type);
}
}
int main() {
Packet pkt;
uint8_t buffer[sizeof(Packet)];
// 打包温度数据
pack_temperature(&pkt, 25.6);
memcpy(buffer, &pkt, sizeof(Packet));
// 模拟传输和接收
Packet rx_pkt;
memcpy(&rx_pkt, buffer, sizeof(Packet));
parse_packet(&rx_pkt);
// 打包湿度数据
pack_humidity(&pkt, 65);
memcpy(buffer, &pkt, sizeof(Packet));
memcpy(&rx_pkt, buffer, sizeof(Packet));
parse_packet(&rx_pkt);
return 0;
}
第2周:函数与预处理------代码组织的智慧
2.1 函数指针与回调:解耦与扩展的利器
函数指针是指向函数的指针。它可以作为参数传递给另一个函数,被调用的函数在运行时再决定调用哪个函数,这就是回调函数。
c
#include <stdio.h>
// 定义一个函数类型,它接收两个int,返回一个int
typedef int (*operation_func)(int, int);
// 实现几个具体的操作
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
// 这个函数接受一个函数指针作为参数,体现了"回调"思想
int calculate(int x, int y, operation_func op) {
printf("Calculating...\n");
return op(x, y); // 在运行时决定调用哪个函数
}
int main() {
int a = 10, b = 5;
printf("10 + 5 = %d\n", calculate(a, b, add));
printf("10 - 5 = %d\n", calculate(a, b, subtract));
printf("10 * 5 = %d\n", calculate(a, b, multiply));
return 0;
}
嵌入式应用场景:
- 硬件抽象层(HAL): 在写外设驱动时,我们可以定义一个通用的
SPI_Transmit函数,它接收一个函数指针作为参数,这个函数指针指向具体的硬件操作函数(如SPI1_SendByte或SPI2_SendByte)。这样,上层代码无需关心底层硬件差异。 - 定时器回调: 在RTOS或裸机系统中,定时器中断服务函数(ISR)通常只做最少的处理(如清中断标志、发送信号量),而真正的业务逻辑通过注册的回调函数在主循环或任务上下文中执行,从而保证ISR的快速响应。
- 排序/搜索算法: 在需要对不同结构体数组进行排序时,可以传递一个比较函数指针,实现算法的通用化。
实战项目:实现一个通用的硬件抽象层(HAL)框架
c
#include <stdio.h>
#include <stdint.h>
// 定义外设类型
typedef enum {
PERIPH_SPI1,
PERIPH_SPI2,
PERIPH_I2C1,
PERIPH_I2C2
} PeriphId;
// 定义传输回调函数类型
typedef void (*transmit_cb_t)(PeriphId id, uint8_t *data, uint32_t len);
typedef void (*receive_cb_t)(PeriphId id, uint8_t *data, uint32_t len);
// 外设操作结构体
typedef struct {
PeriphId id;
transmit_cb_t transmit;
receive_cb_t receive;
void *hal_driver; // 指向具体的外设驱动实例
} PeriphDriver;
// 具体的硬件实现
void spi1_transmit(PeriphId id, uint8_t *data, uint32_t len) {
printf("SPI1: Transmitting %d bytes\n", len);
// 实际硬件操作...
}
void spi1_receive(PeriphId id, uint8_t *data, uint32_t len) {
printf("SPI1: Receiving %d bytes\n", len);
// 实际硬件操作...
}
void i2c1_transmit(PeriphId id, uint8_t *data, uint32_t len) {
printf("I2C1: Transmitting %d bytes\n", len);
// 实际硬件操作...
}
// 通用外设操作函数
void periph_transmit(PeriphDriver *driver, uint8_t *data, uint32_t len) {
if(driver && driver->transmit) {
driver->transmit(driver->id, data, len);
}
}
// 注册外设驱动
void register_periph_driver(PeriphDriver *driver, PeriphId id,
transmit_cb_t tx_cb, receive_cb_t rx_cb) {
driver->id = id;
driver->transmit = tx_cb;
driver->receive = rx_cb;
}
int main() {
PeriphDriver spi1_driver;
PeriphDriver i2c1_driver;
register_periph_driver(&spi1_driver, PERIPH_SPI1, spi1_transmit, spi1_receive);
register_periph_driver(&i2c1_driver, PERIPH_I2C1, i2c1_transmit, NULL);
uint8_t data[] = {0x01, 0x02, 0x03};
periph_transmit(&spi1_driver, data, 3);
periph_transmit(&i2c1_driver, data, 3);
return 0;
}
2.2 宏定义的替换规则与条件编译:预处理的魔力
宏定义的本质是文本替换。 理解其替换规则,可以避免许多隐藏的陷阱。
c
#include <stdio.h>
// 简单的宏定义
#define PI 3.14159
// 带参数的宏 - 注意括号的重要性!
#define SQUARE(x) ((x) * (x))
// 如果写成 #define SQUARE(x) x*x,则 SQUARE(1+2) 会被展开为 1+2*1+2 = 5,而不是9
// 宏的副作用
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 5, y = 10;
int result = MAX(x++, y++);
// 展开后:((x++) > (y++) ? (x++) : (y++))
// 实际执行过程:比较5和10后,x变为6,y变为11,然后执行y++,最终y变为12,result=11
printf("result=%d, x=%d, y=%d\n", result, x, y); // 输出 result=11, x=6, y=12
// 正确的做法:使用内联函数或避免在宏参数中使用有副作用的表达式
return 0;
}
条件编译: 用于根据不同的条件编译不同的代码片段,是实现跨平台、调试信息控制和功能裁剪的核心手段。
c
#include <stdio.h>
// 定义宏来控制是否开启调试输出
#define DEBUG_ENABLE 1
int main() {
int sensor_value = 100;
#if DEBUG_ENABLE
printf("Debug: sensor_value = %d\n", sensor_value);
#endif
// 根据不同的硬件平台,选择不同的初始化函数
#if defined(STM32F103)
#include "stm32f103.h"
void hardware_init() { /* STM32F103 specific init */ }
#elif defined(STM32F407)
#include "stm32f407.h"
void hardware_init() { /* STM32F407 specific init */ }
#else
#error "No hardware platform defined!"
#endif
// 使用 #pragma once 或 #ifndef 防止头文件重复包含
return 0;
}
实战项目:实现一个跨平台的日志系统
c
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
// 日志级别定义
#define LOG_LEVEL_NONE 0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARNING 2
#define LOG_LEVEL_INFO 3
#define LOG_LEVEL_DEBUG 4
// 通过条件编译控制日志级别
#ifndef LOG_LEVEL
#define LOG_LEVEL LOG_LEVEL_INFO
#endif
// 日志输出目标
typedef enum {
LOG_OUT_CONSOLE,
LOG_OUT_UART,
LOG_OUT_FILE,
LOG_OUT_NETWORK
} LogOutput;
// 日志输出函数指针类型
typedef void (*log_output_func)(const char *msg);
// 根据条件编译选择不同的实现
#if defined(PLATFORM_PC)
#define LOG_NEWLINE "\r\n"
static void log_output_console(const char *msg) {
printf("%s", msg);
}
static log_output_func g_log_output = log_output_console;
#elif defined(PLATFORM_STM32)
#define LOG_NEWLINE "\r\n"
extern void uart_send_string(const char *str);
static void log_output_uart(const char *msg) {
uart_send_string(msg);
}
static log_output_func g_log_output = log_output_uart;
#else
#define LOG_NEWLINE "\n"
static void log_output_console(const char *msg) {
printf("%s", msg);
}
static log_output_func g_log_output = log_output_console;
#endif
// 带级别的日志宏
#if LOG_LEVEL >= LOG_LEVEL_ERROR
#define LOG_ERROR(fmt, ...) \
do { \
char buf[256]; \
snprintf(buf, sizeof(buf), "[ERROR] " fmt LOG_NEWLINE, ##__VA_ARGS__); \
g_log_output(buf); \
} while(0)
#else
#define LOG_ERROR(fmt, ...) ((void)0)
#endif
#if LOG_LEVEL >= LOG_LEVEL_WARNING
#define LOG_WARN(fmt, ...) \
do { \
char buf[256]; \
snprintf(buf, sizeof(buf), "[WARN] " fmt LOG_NEWLINE, ##__VA_ARGS__); \
g_log_output(buf); \
} while(0)
#else
#define LOG_WARN(fmt, ...) ((void)0)
#endif
#if LOG_LEVEL >= LOG_LEVEL_INFO
#define LOG_INFO(fmt, ...) \
do { \
char buf[256]; \
snprintf(buf, sizeof(buf), "[INFO] " fmt LOG_NEWLINE, ##__VA_ARGS__); \
g_log_output(buf); \
} while(0)
#else
#define LOG_INFO(fmt, ...) ((void)0)
#endif
#if LOG_LEVEL >= LOG_LEVEL_DEBUG
#define LOG_DEBUG(fmt, ...) \
do { \
char buf[256]; \
snprintf(buf, sizeof(buf), "[DEBUG] " fmt LOG_NEWLINE, ##__VA_ARGS__); \
g_log_output(buf); \
} while(0)
#else
#define LOG_DEBUG(fmt, ...) ((void)0)
#endif
// 断言宏,只在调试模式下启用
#ifdef DEBUG_ENABLE
#define ASSERT(expr) \
do { \
if(!(expr)) { \
LOG_ERROR("Assertion failed: %s at %s:%d", #expr, __FILE__, __LINE__); \
while(1); /* 死循环,等待调试器 */ \
} \
} while(0)
#else
#define ASSERT(expr) ((void)0)
#endif
int main() {
LOG_INFO("System starting up...");
LOG_DEBUG("Initializing peripherals...");
LOG_WARN("Low battery detected!");
LOG_ERROR("Sensor communication failed!");
int value = 100;
ASSERT(value > 0);
ASSERT(value < 50); // 这个断言会失败
return 0;
}
2.3 static/const/volatile 关键字的约束
-
static关键字:- 修饰局部变量: 将变量的生命周期延长至整个程序运行期,但作用域仍局限于其所在的函数。变量在静态存储区分配,且只被初始化一次。常用于实现函数内的状态保持。
- 修饰全局变量/函数: 限制其作用域仅在本文件内,实现信息隐藏和模块化。
-
const关键字:- 修饰变量,表示该变量的值不能被修改。在嵌入式系统中,常用于定义只读数据(如查找表、字符串常量),这些数据可以被存放在ROM中,节省RAM空间。
- 修饰指针:需要区分
const int *p(指向常量整数的指针,指针可变,指向的内容不可变)和int * const p(指向整数的常量指针,指针不可变,指向的内容可变)。
-
volatile关键字:- 告诉编译器,该变量可能被程序之外的因素(如硬件中断、其他线程、硬件寄存器)改变,因此每次访问都必须从内存中读取,而不能使用寄存器中的缓存值。
- 应用场景: 硬件寄存器、中断服务程序中修改的全局变量、多线程共享的变量。
c
#include <stdio.h>
#include <stdint.h>
// 静态局部变量:保持函数调用间的状态
int counter() {
static int count = 0; // 只初始化一次
return ++count;
}
// 文件作用域的静态变量,只能在本文件内访问
static int g_module_state = 0;
// 静态函数,只能在本文件内调用
static void internal_helper() {
printf("Internal helper called\n");
}
// 只读数据,通常放在ROM中
const uint16_t sine_table[] = {
0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000
};
// const指针示例
void const_pointer_example() {
int a = 10, b = 20;
const int *p1 = &a; // 指向常量的指针,不能通过p1修改a
// *p1 = 30; // 错误
p1 = &b; // 正确,可以改变指向
int * const p2 = &a; // 常量指针,不能改变指向
*p2 = 30; // 正确,可以修改内容
// p2 = &b; // 错误
const int * const p3 = &a; // 指向常量的常量指针,都不能修改
}
// volatile应用:硬件寄存器
#define GPIOA_BASE 0x40020000
#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR (*(volatile uint32_t*)(GPIOA_BASE + 0x14))
// 中断服务程序中修改的变量
volatile uint32_t g_sys_tick = 0;
void SysTick_Handler(void) {
g_sys_tick++; // 硬件中断修改,必须用volatile
}
void delay_ms(uint32_t ms) {
uint32_t start = g_sys_tick;
while((g_sys_tick - start) < ms) {
// 每次循环都必须重新读取g_sys_tick
// 如果不用volatile,编译器可能优化成只读一次
}
}
int main() {
// 配置GPIO
GPIOA_MODER = 0x40000000; // 硬件寄存器,必须用volatile
for(int i = 0; i < 5; i++) {
printf("Counter: %d\n", counter());
}
internal_helper();
printf("Sine value: %d\n", sine_table[5]);
return 0;
}
第3周:中断与基础------嵌入式系统的响应核心
3.1 中断处理流程:从硬件到软件
中断是嵌入式系统实现实时响应的核心机制。当中断发生时,CPU会暂停当前正在执行的程序,保存现场,跳转到中断服务程序(ISR)执行,执行完毕后再恢复现场,继续执行原程序。
c
#include <stdio.h>
#include <stdint.h>
#include <setjmp.h>
// 简化的中断处理框架
typedef struct {
uint32_t *stack_ptr; // 栈指针
uint32_t pc; // 程序计数器
uint32_t lr; // 链接寄存器
uint32_t regs[13]; // R0-R12
uint32_t psr; // 程序状态寄存器
} Context;
// 中断向量表(简化)
typedef void (*isr_handler_t)(void);
#define MAX_IRQ_NUM 128
isr_handler_t g_isr_table[MAX_IRQ_NUM];
// 中断使能/禁止
static uint32_t g_primask = 0;
void enable_irq(void) {
// 恢复之前的中断状态
__asm volatile("msr primask, %0" : : "r" (g_primask));
}
uint32_t disable_irq(void) {
uint32_t primask;
__asm volatile("mrs %0, primask" : "=r" (primask));
__asm volatile("cpsid i");
g_primask = primask;
return primask;
}
// 注册中断处理函数
void register_isr(uint32_t irq_num, isr_handler_t handler) {
if(irq_num < MAX_IRQ_NUM) {
g_isr_table[irq_num] = handler;
}
}
// 中断入口(汇编调用)
void irq_handler_entry(uint32_t irq_num) {
if(irq_num < MAX_IRQ_NUM && g_isr_table[irq_num]) {
g_isr_table[irq_num]();
}
}
// 示例:定时器中断处理
volatile uint32_t g_timer_ticks = 0;
void timer_isr(void) {
// 清中断标志(硬件操作)
// TIMER->SR &= ~TIMER_SR_UIF;
g_timer_ticks++;
printf("Timer ISR: tick %d\n", g_timer_ticks);
}
int main() {
// 注册定时器中断
register_isr(15, timer_isr);
// 使能中断
enable_irq();
// 模拟中断触发
for(int i = 0; i < 5; i++) {
// 模拟硬件触发中断
irq_handler_entry(15);
}
return 0;
}
3.2 临界区保护与原子操作
临界区是指访问共享资源(如全局变量、硬件寄存器)的代码段,必须保证其原子性执行,即不会被其他中断或任务打断。
实战项目:实现临界区保护机制
c
#include <stdio.h>
#include <stdint.h>
// 临界区保护宏
#define ENTER_CRITICAL() \
do { \
uint32_t primask; \
__asm volatile("mrs %0, primask" : "=r" (primask)); \
__asm volatile("cpsid i"); \
__asm volatile("" ::: "memory"); /* 编译器屏障 */ \
primask = primask; /* 防止未使用警告 */
#define EXIT_CRITICAL() \
__asm volatile("msr primask, %0" : : "r" (primask)); \
__asm volatile("" ::: "memory"); \
} while(0)
// 原子操作示例:计数器
volatile uint32_t g_counter = 0;
void increment_counter(void) {
ENTER_CRITICAL();
g_counter++; // 现在这个操作是原子的
EXIT_CRITICAL();
}
// 使用自旋锁实现更细粒度的保护
typedef struct {
volatile uint32_t lock;
} spinlock_t;
void spinlock_init(spinlock_t *lock) {
lock->lock = 0;
}
void spinlock_lock(spinlock_t *lock) {
while(1) {
ENTER_CRITICAL();
if(lock->lock == 0) {
lock->lock = 1;
EXIT_CRITICAL();
break;
}
EXIT_CRITICAL();
// 可以在这里添加一些延时或让出CPU
}
}
void spinlock_unlock(spinlock_t *lock) {
ENTER_CRITICAL();
lock->lock = 0;
EXIT_CRITICAL();
}
// 示例:使用自旋锁保护共享资源
spinlock_t g_resource_lock;
int g_shared_resource = 0;
void task1(void) {
spinlock_lock(&g_resource_lock);
// 访问共享资源
g_shared_resource++;
printf("Task1: resource = %d\n", g_shared_resource);
spinlock_unlock(&g_resource_lock);
}
void task2(void) {
spinlock_lock(&g_resource_lock);
// 访问共享资源
g_shared_resource += 2;
printf("Task2: resource = %d\n", g_shared_resource);
spinlock_unlock(&g_resource_lock);
}
int main() {
spinlock_init(&g_resource_lock);
// 模拟两个任务交替访问
for(int i = 0; i < 5; i++) {
task1();
task2();
}
return 0;
}
3.3 异常处理机制
嵌入式系统中的异常处理不仅包括硬件异常(如除零错误、内存访问错误),还包括软件层面的错误处理。
c
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
// 使用setjmp/longjmp实现简单的异常处理
jmp_buf g_exception_env;
typedef enum {
EXCEPTION_DIVIDE_BY_ZERO,
EXCEPTION_MEMORY_FAULT,
EXCEPTION_INVALID_PARAM,
EXCEPTION_CUSTOM
} ExceptionCode;
// 异常处理函数
void handle_exception(ExceptionCode code, const char *message) {
printf("Exception %d: %s\n", code, message);
longjmp(g_exception_env, code);
}
// 可能抛出异常的函数
int safe_divide(int a, int b) {
if(b == 0) {
handle_exception(EXCEPTION_DIVIDE_BY_ZERO, "Division by zero");
return 0;
}
return a / b;
}
// 内存访问检查
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if(ptr == NULL && size > 0) {
handle_exception(EXCEPTION_MEMORY_FAULT, "Memory allocation failed");
}
return ptr;
}
// 硬件异常处理(ARM Cortex-M示例)
void HardFault_Handler(void) {
// 保存异常现场
uint32_t *sp;
__asm volatile("mrs %0, psp" : "=r" (sp));
printf("Hard Fault! Stack pointer: %p\n", sp);
printf("PC: 0x%08X\n", sp[6]);
printf("LR: 0x%08X\n", sp[5]);
// 尝试恢复或重启
while(1) {
// 死循环,等待看门狗复位
}
}
void MemManage_Handler(void) {
printf("Memory Management Fault!\n");
while(1);
}
void BusFault_Handler(void) {
printf("Bus Fault!\n");
while(1);
}
void UsageFault_Handler(void) {
printf("Usage Fault!\n");
while(1);
}
// 自定义错误处理框架
typedef struct {
int error_code;
char error_msg[64];
void (*error_handler)(void);
} ErrorHandler;
ErrorHandler g_error_handlers[10];
int g_error_count = 0;
void register_error_handler(int code, const char *msg, void (*handler)(void)) {
if(g_error_count < 10) {
g_error_handlers[g_error_count].error_code = code;
strncpy(g_error_handlers[g_error_count].error_msg, msg, 63);
g_error_handlers[g_error_count].error_handler = handler;
g_error_count++;
}
}
void handle_error(int code) {
for(int i = 0; i < g_error_count; i++) {
if(g_error_handlers[i].error_code == code) {
printf("Error %d: %s\n", code, g_error_handlers[i].error_msg);
if(g_error_handlers[i].error_handler) {
g_error_handlers[i].error_handler();
}
return;
}
}
printf("Unknown error: %d\n", code);
}
// 示例错误处理
void uart_error_handler(void) {
printf("UART error: reinitializing...\n");
// 重新初始化UART
}
int main() {
// 注册错误处理器
register_error_handler(100, "UART buffer overflow", uart_error_handler);
register_error_handler(101, "I2C communication timeout", NULL);
// 使用setjmp/longjmp进行异常处理
int exception = setjmp(g_exception_env);
if(exception == 0) {
// 正常代码
int result = safe_divide(10, 0); // 这会抛出异常
printf("Result: %d\n", result);
void *ptr = safe_malloc(1000000000); // 可能会失败
free(ptr);
} else {
// 异常处理代码
printf("Caught exception: %d\n", exception);
printf("Recovering from exception...\n");
}
// 测试错误处理框架
handle_error(100);
return 0;
}
第二月:STM32/单片机实战------从理论到硬件
进入第二个月,我们将把C语言的知识应用到实际的硬件平台上。选择STM32作为学习平台,因为它有丰富的资料、强大的生态系统和广泛的应用场景。
第1周:外设驱动------寄存器与HAL库双视角
1.1 GPIO:最基础的输入输出
GPIO(General Purpose Input Output)是最基础的外设,控制LED、读取按键状态都离不开它。
c
// 寄存器版本:STM32F103 GPIO驱动
#include "stm32f10x.h"
// GPIO初始化函数
void GPIO_Init_Reg(GPIO_TypeDef* GPIOx, uint16_t pin, uint32_t mode) {
uint32_t pos = 0;
uint32_t tmp = 0;
// 计算引脚位置
for(pos = 0; pos < 16; pos++) {
if(pin == (1 << pos)) break;
}
if(pos < 8) {
// 低8位引脚,配置在CRL寄存器
tmp = GPIOx->CRL;
tmp &= ~(0xF << (pos * 4)); // 清除原有配置
tmp |= (mode << (pos * 4)); // 设置新配置
GPIOx->CRL = tmp;
} else {
// 高8位引脚,配置在CRH寄存器
pos -= 8;
tmp = GPIOx->CRH;
tmp &= ~(0xF << (pos * 4));
tmp |= (mode << (pos * 4));
GPIOx->CRH = tmp;
}
}
// 设置引脚输出
void GPIO_SetBits_Reg(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->BSRR = pin; // BSRR寄存器低16位用于置位
}
// 清除引脚输出
void GPIO_ResetBits_Reg(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->BRR = pin; // BRR寄存器用于复位
}
// 读取引脚输入
uint8_t GPIO_ReadInputBit_Reg(GPIO_TypeDef* GPIOx, uint16_t pin) {
return (GPIOx->IDR & pin) ? 1 : 0;
}
// 使用示例:LED闪烁
void LED_Blink_Reg(void) {
// 使能GPIOB时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
// 配置PB12为推挽输出,50MHz
GPIO_Init_Reg(GPIOB, GPIO_Pin_12, GPIO_Mode_Out_PP);
while(1) {
GPIO_SetBits_Reg(GPIOB, GPIO_Pin_12);
delay_ms(500);
GPIO_ResetBits_Reg(GPIOB, GPIO_Pin_12);
delay_ms(500);
}
}
// HAL库版本:更高级的抽象
#include "stm32f1xx_hal.h"
void LED_Blink_HAL(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置GPIO
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
while(1) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
HAL_Delay(500);
}
}
1.2 UART:串行通信的基础
UART是最常用的调试和通信接口之一。
c
// 寄存器版本:UART初始化
void UART_Init_Reg(uint32_t baudrate) {
// 使能USART1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;
// 配置PA9(TX)为复用推挽输出,PA10(RX)为浮空输入
GPIO_Init_Reg(GPIOA, GPIO_Pin_9, GPIO_Mode_AF_PP);
GPIO_Init_Reg(GPIOA, GPIO_Pin_10, GPIO_Mode_IN_FLOATING);
// 计算波特率分频
uint32_t temp = 0;
temp = (uint32_t)(72000000 / baudrate); // 假设系统时钟72MHz
USART1->BRR = temp;
// 配置数据位、停止位、校验位等
USART1->CR1 = USART_CR1_TE | USART_CR1_RE; // 使能发送和接收
USART1->CR2 = 0; // 1位停止位
USART1->CR3 = 0;
// 使能USART
USART1->CR1 |= USART_CR1_UE;
}
// 发送单个字符
void UART_SendChar_Reg(uint8_t ch) {
// 等待发送缓冲区空
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = ch;
}
// 发送字符串
void UART_SendString_Reg(const char *str) {
while(*str) {
UART_SendChar_Reg(*str++);
}
}
// 接收单个字符(阻塞)
uint8_t UART_ReceiveChar_Reg(void) {
// 等待接收完成
while(!(USART1->SR & USART_SR_RXNE));
return USART1->DR;
}
// HAL库版本:UART通信
#include "stm32f1xx_hal.h"
UART_HandleTypeDef huart1;
void UART_Init_HAL(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
}
// 中断方式发送
void UART_Send_IT(uint8_t *data, uint16_t size) {
HAL_UART_Transmit_IT(&huart1, data, size);
}
// 中断方式接收
void UART_Receive_IT(void) {
HAL_UART_Receive_IT(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
// 中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
// 发送完成,可以发送下一包数据
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
// 接收到数据,进行处理
process_received_data();
// 继续接收下一帧
HAL_UART_Receive_IT(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
}
1.3 SPI:高速同步串行接口
SPI常用于连接高速外设,如LCD、SD卡、传感器等。
c
// SPI初始化(寄存器版本)
void SPI_Init_Reg(void) {
// 使能SPI1和GPIO时钟
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN | RCC_APB2ENR_GPIOAEN | RCC_APB2ENR_GPIOBEN;
// 配置SPI引脚:SCK-PA5, MISO-PA6, MOSI-PA7
GPIO_Init_Reg(GPIOA, GPIO_Pin_5 | GPIO_Pin_7, GPIO_Mode_AF_PP);
GPIO_Init_Reg(GPIOA, GPIO_Pin_6, GPIO_Mode_IN_FLOATING);
// 配置CS引脚(使用PB12作为片选)
GPIO_Init_Reg(GPIOB, GPIO_Pin_12, GPIO_Mode_Out_PP);
GPIO_SetBits_Reg(GPIOB, GPIO_Pin_12); // 默认CS高电平
// 禁用SPI,进行配置
SPI1->CR1 = 0;
// 配置为主模式,波特率=fPCLK/256,时钟极性=0,相位=0,8位数据
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_0 | SPI_CR1_BR_1; // 分频器设置
SPI1->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM; // 软件管理NSS
// 使能SPI
SPI1->CR1 |= SPI_CR1_SPE;
}
// SPI发送接收一个字节
uint8_t SPI_TransferByte_Reg(uint8_t tx_data) {
// 等待发送缓冲区空
while(!(SPI1->SR & SPI_SR_TXE));
// 发送数据
SPI1->DR = tx_data;
// 等待接收完成
while(!(SPI1->SR & SPI_SR_RXNE));
// 返回接收到的数据
return SPI1->DR;
}
// SPI读写多个字节
void SPI_Transfer_Reg(uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len) {
for(uint32_t i = 0; i < len; i++) {
rx_buf[i] = SPI_TransferByte_Reg(tx_buf ? tx_buf[i] : 0xFF);
}
}
// HAL库版本:SPI通信
#include "stm32f1xx_hal.h"
SPI_HandleTypeDef hspi1;
void SPI_Init_HAL(void) {
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;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi1);
}
// SPI传输
void SPI_Transfer_HAL(uint8_t *tx_data, uint8_t *rx_data, uint16_t size) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // 拉低CS
HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, size, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); // 拉高CS
}
1.4 I2C:两线式串行总线
I2C常用于连接传感器、EEPROM等低速设备。
c
// I2C初始化(寄存器版本)
void I2C_Init_Reg(void) {
// 使能I2C1和GPIOB时钟
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
// 配置PB6(SCL)和PB7(SDA)为复用开漏输出
GPIO_Init_Reg(GPIOB, GPIO_Pin_6 | GPIO_Pin_7, GPIO_Mode_AF_OD);
// 禁用I2C进行配置
I2C1->CR1 = 0;
// 配置时钟控制寄存器
I2C1->CR2 = 36; // 外设时钟频率36MHz
// 配置时钟速率:标准模式100kHz
I2C1->CCR = 180; // CCR = 36MHz / (2 * 100kHz) = 180
// 配置上升时间
I2C1->TRISE = 37; // TRISE = (36MHz * 1us) / 1000 + 1 = 37
// 使能I2C
I2C1->CR1 |= I2C_CR1_PE;
}
// I2C起始条件
void I2C_Start_Reg(void) {
// 产生起始条件
I2C1->CR1 |= I2C_CR1_START;
// 等待起始条件发送完成
while(!(I2C1->SR1 & I2C_SR1_SB));
}
// I2C发送地址
void I2C_SendAddr_Reg(uint8_t addr, uint8_t direction) {
// 发送地址
I2C1->DR = (addr << 1) | (direction ? 1 : 0);
// 等待地址发送完成
while(!(I2C1->SR1 & I2C_SR1_ADDR));
// 清除ADDR标志
(void)I2C1->SR2;
}
// I2C发送一个字节
void I2C_SendByte_Reg(uint8_t data) {
I2C1->DR = data;
// 等待发送完成
while(!(I2C1->SR1 & I2C_SR1_TXE));
}
// I2C接收一个字节
uint8_t I2C_ReceiveByte_Reg(uint8_t ack) {
// 等待接收完成
while(!(I2C1->SR1 & I2C_SR1_RXNE));
uint8_t data = I2C1->DR;
// 配置应答
if(ack) {
I2C1->CR1 |= I2C_CR1_ACK;
} else {
I2C1->CR1 &= ~I2C_CR1_ACK;
}
return data;
}
// I2C停止条件
void I2C_Stop_Reg(void) {
I2C1->CR1 |= I2C_CR1_STOP;
}
// 写入EEPROM示例
void EEPROM_Write_Reg(uint16_t addr, uint8_t data) {
I2C_Start_Reg();
I2C_SendAddr_Reg(0x50, 0); // 写入地址
I2C_SendByte_Reg(addr >> 8); // 高字节地址
I2C_SendByte_Reg(addr & 0xFF); // 低字节地址
I2C_SendByte_Reg(data);
I2C_Stop_Reg();
// 等待写入完成
HAL_Delay(5);
}
1.5 ADC/DAC:模拟与数字的桥梁
ADC(模数转换器)和DAC(数模转换器)让单片机能够处理模拟信号。
c
// ADC初始化(寄存器版本)
void ADC_Init_Reg(void) {
// 使能ADC1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN;
// 配置PA0为模拟输入
GPIO_Init_Reg(GPIOA, GPIO_Pin_0, GPIO_Mode_AIN);
// 复位ADC
RCC->APB2RSTR |= RCC_APB2RSTR_ADC1RST;
RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC1RST;
// 配置ADC
ADC1->CR1 = 0; // 独立模式,单通道
ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_CAL; // 开启ADC和校准
// 等待校准完成
while(ADC1->CR2 & ADC_CR2_CAL);
// 设置采样时间
ADC1->SMPR1 = 0;
ADC1->SMPR2 = ADC_SMPR2_SMP0_0 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_2; // 239.5周期
}
// ADC读取(单次转换)
uint16_t ADC_Read_Reg(uint8_t channel) {
// 设置转换序列
ADC1->SQR3 = channel; // 只转换一个通道
// 开始转换
ADC1->CR2 |= ADC_CR2_SWSTART;
// 等待转换完成
while(!(ADC1->SR & ADC_SR_EOC));
// 返回转换结果
return ADC1->DR;
}
// DAC初始化
void DAC_Init_Reg(void) {
// 使能DAC时钟
RCC->APB1ENR |= RCC_APB1ENR_DACEN;
// 配置PA4为模拟输出
GPIO_Init_Reg(GPIOA, GPIO_Pin_4, GPIO_Mode_AIN);
// 使能DAC通道1
DAC->CR = DAC_CR_EN1;
}
// DAC输出
void DAC_Output_Reg(uint16_t value) {
// 输出12位值
DAC->DHR12R1 = value;
}
第2周:时序控制------定时器与PWM
2.1 定时器工作模式
STM32的定时器功能强大,可以用于计时、PWM生成、输入捕获、输出比较等。
c
// 基本定时器初始化(寄存器版本)
void TIM_Init_Reg(void) {
// 使能TIM2时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 配置预分频器:72MHz / 7200 = 10kHz
TIM2->PSC = 7199;
// 配置自动重装载值:10kHz / 1000 = 10Hz
TIM2->ARR = 999;
// 更新事件使能
TIM2->DIER |= TIM_DIER_UIE;
// 使能计数器
TIM2->CR1 |= TIM_CR1_CEN;
// 使能中断(NVIC配置)
NVIC_EnableIRQ(TIM2_IRQn);
}
// 定时器中断服务函数
void TIM2_IRQHandler(void) {
if(TIM2->SR & TIM_SR_UIF) {
TIM2->SR &= ~TIM_SR_UIF; // 清除中断标志
// 定时器溢出处理
g_sys_tick++;
}
}
// 通用定时器用于输入捕获(测量脉冲宽度)
void TIM_InputCapture_Init_Reg(void) {
// 使能TIM3时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// 配置PA6为复用功能,用于TIM3_CH1
GPIO_Init_Reg(GPIOA, GPIO_Pin_6, GPIO_Mode_IN_FLOATING);
// 配置TIM3为输入捕获模式
TIM3->PSC = 71; // 72MHz / 72 = 1MHz,1us计数
TIM3->ARR = 0xFFFF; // 最大计数值
// 配置通道1为输入捕获,上升沿触发
TIM3->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1通道配置为输入,映射到TI1
TIM3->CCER |= TIM_CCER_CC1E; // 使能捕获
// 使能捕获中断
TIM3->DIER |= TIM_DIER_CC1IE;
// 使能定时器
TIM3->CR1 |= TIM_CR1_CEN;
}
// 输入捕获中断处理
void TIM3_IRQHandler(void) {
if(TIM3->SR & TIM_SR_CC1IF) {
TIM3->SR &= ~TIM_SR_CC1IF;
uint16_t capture_value = TIM3->CCR1; // 读取捕获值
// 处理捕获数据...
}
}
2.2 PWM波形生成与电机控制
PWM(脉冲宽度调制)是控制电机、LED亮度等的常用技术。
c
// PWM初始化(寄存器版本)
void PWM_Init_Reg(void) {
// 使能TIM1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN | RCC_APB2ENR_IOPAEN;
// 配置PA8为复用推挽输出(TIM1_CH1)
GPIO_Init_Reg(GPIOA, GPIO_Pin_8, GPIO_Mode_AF_PP);
// 配置TIM1
TIM1->PSC = 71; // 72MHz / 72 = 1MHz
TIM1->ARR = 999; // 1MHz / 1000 = 1kHz PWM频率
// 配置通道1为PWM模式1
TIM1->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1
TIM1->CCMR1 |= TIM_CCMR1_OC1PE; // 使能预装载
TIM1->CCER |= TIM_CCER_CC1E; // 使能输出
TIM1->CCR1 = 500; // 占空比50%
// 使能自动重装载预装载
TIM1->CR1 |= TIM_CR1_ARPE;
// 使能主输出(高级定时器需要)
TIM1->BDTR |= TIM_BDTR_MOE;
// 使能定时器
TIM1->CR1 |= TIM_CR1_CEN;
}
// 设置PWM占空比(0-1000对应0%-100%)
void PWM_SetDuty_Reg(uint16_t duty) {
if(duty > 1000) duty = 1000;
TIM1->CCR1 = duty;
}
// 电机控制(H桥驱动)
typedef struct {
uint16_t pwm_duty; // PWM占空比
uint8_t direction; // 方向:0-正转,1-反转
uint8_t brake; // 刹车:0-运行,1-刹车
} MotorControl;
void Motor_Control(MotorControl *mc) {
if(mc->brake) {
// 刹车:两相下管导通
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // IN1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); // IN2
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET); // IN3
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // IN4
PWM_SetDuty_Reg(0);
} else {
if(mc->direction == 0) {
// 正转
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);
} else {
// 反转
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
}
PWM_SetDuty_Reg(mc->pwm_duty);
}
}
// PID控制算法
typedef struct {
float kp; // 比例系数
float ki; // 积分系数
float kd; // 微分系数
float target; // 目标值
float integral; // 积分累积
float prev_error; // 上一次误差
} PIDController;
void PID_Init(PIDController *pid, float kp, float ki, float kd) {
pid->kp = kp;
pid->ki = ki;
pid->kd = kd;
pid->target = 0;
pid->integral = 0;
pid->prev_error = 0;
}
float PID_Update(PIDController *pid, float current) {
float error = pid->target - current;
// 比例项
float p_term = pid->kp * error;
// 积分项(带积分限幅)
pid->integral += error;
if(pid->integral > 1000) pid->integral = 1000;
if(pid->integral < -1000) pid->integral = -1000;
float i_term = pid->ki * pid->integral;
// 微分项
float derivative = error - pid->prev_error;
float d_term = pid->kd * derivative;
pid->prev_error = error;
// 计算输出
float output = p_term + i_term + d_term;
// 输出限幅
if(output > 1000) output = 1000;
if(output < -1000) output = -1000;
return output;
}
第3周:系统调试------建立完整的硬件运行思维
3.1 最小系统板构建
最小系统板是学习嵌入式开发的基石,包含MCU、电源、晶振、复位电路和调试接口。
c
// 最小系统初始化代码
void System_Init(void) {
// 1. 配置系统时钟
SystemClock_Config();
// 2. 初始化调试接口(SWD)
__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
// 3. 初始化SysTick用于延时
HAL_Init();
// 4. 初始化LED指示灯(用于调试)
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 5. 指示系统启动
for(int i = 0; i < 3; i++) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
HAL_Delay(100);
}
}
3.2 LED流水灯与串口通信调试
这是嵌入式入门的经典项目,帮助理解GPIO控制和串口调试。
c
// LED流水灯
void LED_Running_Light(void) {
uint16_t leds[] = {GPIO_PIN_12, GPIO_PIN_13, GPIO_PIN_14, GPIO_PIN_15};
while(1) {
for(int i = 0; i < 4; i++) {
// 点亮当前LED
HAL_GPIO_WritePin(GPIOB, leds[i], GPIO_PIN_SET);
HAL_Delay(100);
// 熄灭上一个LED
if(i > 0) {
HAL_GPIO_WritePin(GPIOB, leds[i-1], GPIO_PIN_RESET);
}
}
// 熄灭最后一个LED
HAL_GPIO_WritePin(GPIOB, leds[3], GPIO_PIN_RESET);
}
}
// 串口调试系统
typedef struct {
uint8_t rx_buffer[256];
uint16_t rx_index;
uint8_t rx_complete;
} UART_Debug;
UART_Debug g_debug;
void UART_Debug_Init(void) {
// 初始化UART
UART_Init_HAL();
// 启动中断接收
HAL_UART_Receive_IT(&huart1, g_debug.rx_buffer, 1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
if(g_debug.rx_buffer[g_debug.rx_index] == '\r' ||
g_debug.rx_buffer[g_debug.rx_index] == '\n') {
g_debug.rx_buffer[g_debug.rx_index] = '\0';
g_debug.rx_complete = 1;
} else {
g_debug.rx_index++;
if(g_debug.rx_index >= sizeof(g_debug.rx_buffer)) {
g_debug.rx_index = 0;
}
}
// 继续接收
HAL_UART_Receive_IT(&huart1, &g_debug.rx_buffer[g_debug.rx_index], 1);
}
}
// 命令处理
void Process_Command(void) {
if(!g_debug.rx_complete) return;
if(strcmp((char*)g_debug.rx_buffer, "help") == 0) {
UART_SendString_HAL("Commands:\r\n");
UART_SendString_HAL(" help - Show this help\r\n");
UART_SendString_HAL(" led - Toggle LED\r\n");
UART_SendString_HAL(" temp - Read temperature\r\n");
} else if(strcmp((char*)g_debug.rx_buffer, "led") == 0) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12);
UART_SendString_HAL("LED toggled\r\n");
} else if(strcmp((char*)g_debug.rx_buffer, "temp") == 0) {
// 读取温度传感器
uint16_t adc_value = ADC_Read_Reg(0);
float voltage = adc_value * 3.3f / 4096.0f;
float temperature = (voltage - 0.76f) / 0.0025f; // 假设LM35
char buf[64];
sprintf(buf, "Temperature: %.1f°C\r\n", temperature);
UART_SendString_HAL(buf);
} else if(strlen((char*)g_debug.rx_buffer) > 0) {
UART_SendString_HAL("Unknown command\r\n");
}
g_debug.rx_complete = 0;
g_debug.rx_index = 0;
memset(g_debug.rx_buffer, 0, sizeof(g_debug.rx_buffer));
}
// 调试信息输出宏
#define DEBUG_PRINT(fmt, ...) \
do { \
char buf[128]; \
snprintf(buf, sizeof(buf), fmt, ##__VA_ARGS__); \
UART_SendString_HAL(buf); \
} while(0)
第二阶段:进阶期(第4-6月)------ RTOS与通信协议
第三月:RTOS实时操作系统------多任务处理的艺术
进入第三个月,我们将学习实时操作系统(RTOS),这是嵌入式系统从简单到复杂的关键飞跃。
第1周:核心架构------FreeRTOS任务管理
1.1 任务创建与调度算法
c
#include "FreeRTOS.h"
#include "task.h"
// 任务句柄
TaskHandle_t g_led_task_handle = NULL;
TaskHandle_t g_uart_task_handle = NULL;
TaskHandle_t g_sensor_task_handle = NULL;
// LED闪烁任务
void vLED_Task(void *pvParameters) {
// 初始化LED
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
while(1) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12);
vTaskDelay(pdMS_TO_TICKS(500)); // 延时500ms
}
}
// 传感器读取任务
void vSensor_Task(void *pvParameters) {
// 初始化ADC
ADC_Init_Reg();
while(1) {
uint16_t adc_value = ADC_Read_Reg(0);
float temperature = (adc_value * 3.3f / 4096.0f - 0.76f) / 0.0025f;
// 通过队列发送数据
xQueueSend(g_sensor_queue, &temperature, 0);
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒读取一次
}
}
// 任务优先级
#define LED_TASK_PRIORITY 1
#define UART_TASK_PRIORITY 2
#define SENSOR_TASK_PRIORITY 3
void RTOS_Init(void) {
// 创建LED任务
xTaskCreate(vLED_Task, "LED", configMINIMAL_STACK_SIZE, NULL,
LED_TASK_PRIORITY, &g_led_task_handle);
// 创建传感器任务
xTaskCreate(vSensor_Task, "Sensor", configMINIMAL_STACK_SIZE, NULL,
SENSOR_TASK_PRIORITY, &g_sensor_task_handle);
// 启动调度器
vTaskStartScheduler();
}
1.2 任务状态切换机制
c
// 任务状态管理示例
typedef enum {
TASK_RUNNING,
TASK_READY,
TASK_BLOCKED,
TASK_SUSPENDED
} TaskState;
// 模拟任务控制块
typedef struct {
char name[16];
TaskState state;
uint32_t priority;
uint32_t stack_used;
struct task_control_block *next;
} TaskControlBlock;
// 查看任务状态
void PrintTaskStatus(void) {
TaskControlBlock *current = g_task_list;
printf("Task Status:\r\n");
while(current) {
printf(" %s: ", current->name);
switch(current->state) {
case TASK_RUNNING: printf("Running\r\n"); break;
case TASK_READY: printf("Ready\r\n"); break;
case TASK_BLOCKED: printf("Blocked\r\n"); break;
case TASK_SUSPENDED: printf("Suspended\r\n"); break;
}
current = current->next;
}
}
// 挂起和恢复任务
void TaskControl(void) {
// 挂起LED任务
vTaskSuspend(g_led_task_handle);
printf("LED task suspended\r\n");
vTaskDelay(pdMS_TO_TICKS(2000));
// 恢复LED任务
vTaskResume(g_led_task_handle);
printf("LED task resumed\r\n");
// 删除传感器任务
vTaskDelete(g_sensor_task_handle);
printf("Sensor task deleted\r\n");
}
第2周:同步机制------任务间通信
2.1 信号量、消息队列、事件组
c
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
// 队列和信号量句柄
QueueHandle_t g_sensor_queue;
SemaphoreHandle_t g_uart_semaphore;
EventGroupHandle_t g_system_events;
// 队列数据
typedef struct {
uint32_t timestamp;
float temperature;
uint16_t humidity;
} SensorData;
// 事件位定义
#define EVENT_BIT_UART_RX (1 << 0)
#define EVENT_BIT_SENSOR_READ (1 << 1)
#define EVENT_BIT_TIMER (1 << 2)
// 任务间通信示例
void vTask_Producer(void *pvParameters) {
SensorData data;
while(1) {
// 读取传感器
data.timestamp = xTaskGetTickCount();
data.temperature = Read_Temperature();
data.humidity = Read_Humidity();
// 发送到队列(等待100ms)
if(xQueueSend(g_sensor_queue, &data, pdMS_TO_TICKS(100)) != pdTRUE) {
printf("Queue full!\r\n");
}
// 设置事件位
xEventGroupSetBits(g_system_events, EVENT_BIT_SENSOR_READ);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask_Consumer(void *pvParameters) {
SensorData data;
while(1) {
// 从队列接收数据(一直等待)
if(xQueueReceive(g_sensor_queue, &data, portMAX_DELAY) == pdTRUE) {
printf("Received: Time=%lu, Temp=%.1f, Hum=%d\r\n",
data.timestamp, data.temperature, data.humidity);
// 释放信号量,通知UART任务发送
xSemaphoreGive(g_uart_semaphore);
}
}
}
void vTask_UART(void *pvParameters) {
while(1) {
// 等待信号量
if(xSemaphoreTake(g_uart_semaphore, portMAX_DELAY) == pdTRUE) {
// 发送数据到UART
UART_SendString_HAL("Data sent!\r\n");
}
}
}
// 事件组同步
void vTask_EventWaiter(void *pvParameters) {
EventBits_t bits;
while(1) {
// 等待多个事件
bits = xEventGroupWaitBits(g_system_events,
EVENT_BIT_UART_RX | EVENT_BIT_SENSOR_READ,
pdTRUE, // 清除事件
pdFALSE, // 不等待所有事件
portMAX_DELAY);
if(bits & EVENT_BIT_UART_RX) {
printf("UART data received\r\n");
}
if(bits & EVENT_BIT_SENSOR_READ) {
printf("Sensor data ready\r\n");
}
}
}
void RTOS_Sync_Init(void) {
// 创建队列(最多10个元素)
g_sensor_queue = xQueueCreate(10, sizeof(SensorData));
// 创建二进制信号量
g_uart_semaphore = xSemaphoreCreateBinary();
// 创建事件组
g_system_events = xEventGroupCreate();
// 创建任务
xTaskCreate(vTask_Producer, "Producer", 256, NULL, 2, NULL);
xTaskCreate(vTask_Consumer, "Consumer", 256, NULL, 2, NULL);
xTaskCreate(vTask_UART, "UART", 256, NULL, 1, NULL);
xTaskCreate(vTask_EventWaiter, "Events", 256, NULL, 1, NULL);
}
2.2 死锁预防与优先级反转
c
// 死锁预防示例
typedef struct {
SemaphoreHandle_t mutex_a;
SemaphoreHandle_t mutex_b;
} ResourceManager;
ResourceManager g_resources;
void vTask_Deadlock_Risk1(void *pvParameters) {
while(1) {
// 按固定顺序获取锁(预防死锁)
xSemaphoreTake(g_resources.mutex_a, portMAX_DELAY);
printf("Task1: Got mutex A\r\n");
vTaskDelay(pdMS_TO_TICKS(100));
xSemaphoreTake(g_resources.mutex_b, portMAX_DELAY);
printf("Task1: Got mutex B\r\n");
// 使用资源...
xSemaphoreGive(g_resources.mutex_b);
xSemaphoreGive(g_resources.mutex_a);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask_Deadlock_Risk2(void *pvParameters) {
while(1) {
// 也按相同顺序获取锁
xSemaphoreTake(g_resources.mutex_a, portMAX_DELAY);
printf("Task2: Got mutex A\r\n");
vTaskDelay(pdMS_TO_TICKS(100));
xSemaphoreTake(g_resources.mutex_b, portMAX_DELAY);
printf("Task2: Got mutex B\r\n");
// 使用资源...
xSemaphoreGive(g_resources.mutex_b);
xSemaphoreGive(g_resources.mutex_a);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 优先级反转演示
void vTask_Low(void *pvParameters) {
SemaphoreHandle_t mutex = (SemaphoreHandle_t)pvParameters;
while(1) {
xSemaphoreTake(mutex, portMAX_DELAY);
printf("Low priority task: Got mutex\r\n");
// 长时间占用互斥量
vTaskDelay(pdMS_TO_TICKS(5000));
xSemaphoreGive(mutex);
printf("Low priority task: Released mutex\r\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask_Medium(void *pvParameters) {
while(1) {
// 中等优先级任务占用CPU
printf("Medium priority task: Running\r\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask_High(void *pvParameters) {
SemaphoreHandle_t mutex = (SemaphoreHandle_t)pvParameters;
while(1) {
vTaskDelay(pdMS_TO_TICKS(2000));
printf("High priority task: Waiting for mutex\r\n");
// 高优先级任务被低优先级任务阻塞(优先级反转)
xSemaphoreTake(mutex, portMAX_DELAY);
printf("High priority task: Got mutex\r\n");
vTaskDelay(pdMS_TO_TICKS(500));
xSemaphoreGive(mutex);
printf("High priority task: Released mutex\r\n");
}
}
// 使用互斥量(优先级继承)替代二值信号量防止优先级反转
void RTOS_Priority_Inversion_Prevention(void) {
SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); // 优先级继承
xTaskCreate(vTask_Low, "Low", 256, (void*)mutex, 1, NULL);
xTaskCreate(vTask_Medium, "Medium", 256, NULL, 2, NULL);
xTaskCreate(vTask_High, "High", 256, (void*)mutex, 3, NULL);
}
第3周:实战改造------裸机到多任务系统
3.1 裸机项目升级为多任务系统
将原来的裸机程序改造为RTOS多任务系统,实现数据采集、显示和通信的并行处理。
c
// 系统任务定义
#define TASK_SENSOR_PRIO 3
#define TASK_DISPLAY_PRIO 2
#define TASK_COMM_PRIO 2
#define TASK_LED_PRIO 1
// 任务句柄
TaskHandle_t g_sensor_task_h;
TaskHandle_t g_display_task_h;
TaskHandle_t g_comm_task_h;
TaskHandle_t g_led_task_h;
// 消息队列
QueueHandle_t g_sensor_data_queue;
QueueHandle_t g_display_queue;
QueueHandle_t g_comm_queue;
// 信号量
SemaphoreHandle_t g_display_sem;
SemaphoreHandle_t g_comm_sem;
// 传感器数据结构
typedef struct {
float temperature;
float humidity;
float pressure;
uint32_t timestamp;
} SensorData_t;
// 显示数据结构
typedef struct {
uint8_t line;
char text[32];
} DisplayData_t;
// 传感器采集任务
void vSensorTask(void *pvParameters) {
SensorData_t data;
while(1) {
// 读取传感器
data.temperature = read_temperature();
data.humidity = read_humidity();
data.pressure = read_pressure();
data.timestamp = xTaskGetTickCount();
// 发送到数据队列
xQueueSend(g_sensor_data_queue, &data, 0);
// 通知显示任务更新
xSemaphoreGive(g_display_sem);
// 采集周期:500ms
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 显示更新任务
void vDisplayTask(void *pvParameters) {
SensorData_t data;
DisplayData_t display_data;
char buffer[32];
while(1) {
// 等待显示信号量
xSemaphoreTake(g_display_sem, portMAX_DELAY);
// 从队列获取最新数据
while(xQueueReceive(g_sensor_data_queue, &data, 0) == pdTRUE) {
// 只保留最新数据
}
// 更新LCD显示
sprintf(buffer, "T:%.1f C", data.temperature);
display_data.line = 0;
strcpy(display_data.text, buffer);
xQueueSend(g_display_queue, &display_data, 0);
sprintf(buffer, "H:%.1f %%", data.humidity);
display_data.line = 1;
strcpy(display_data.text, buffer);
xQueueSend(g_display_queue, &display_data, 0);
sprintf(buffer, "P:%.1f hPa", data.pressure);
display_data.line = 2;
strcpy(display_data.text, buffer);
xQueueSend(g_display_queue, &display_data, 0);
// 通知通信任务发送数据
xSemaphoreGive(g_comm_sem);
}
}
// 通信任务
void vCommTask(void *pvParameters) {
SensorData_t data;
uint8_t tx_buffer[64];
while(1) {
// 等待通信信号量
xSemaphoreTake(g_comm_sem, portMAX_DELAY);
// 获取最新数据
xQueueReceive(g_sensor_data_queue, &data, 0);
// 打包数据
tx_buffer[0] = 0xAA; // 帧头
tx_buffer[1] = 0x55;
memcpy(&tx_buffer[2], &data.temperature, 4);
memcpy(&tx_buffer[6], &data.humidity, 4);
memcpy(&tx_buffer[10], &data.pressure, 4);
tx_buffer[14] = calculate_crc(tx_buffer, 14);
// 通过UART发送
HAL_UART_Transmit(&huart1, tx_buffer, 15, 100);
// 发送周期:1秒
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// LED心跳任务
void vLEDTask(void *pvParameters) {
while(1) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 系统初始化
void System_RTOS_Init(void) {
// 创建队列
g_sensor_data_queue = xQueueCreate(10, sizeof(SensorData_t));
g_display_queue = xQueueCreate(20, sizeof(DisplayData_t));
g_comm_queue = xQueueCreate(5, sizeof(SensorData_t));
// 创建信号量
g_display_sem = xSemaphoreCreateBinary();
g_comm_sem = xSemaphoreCreateBinary();
// 创建任务
xTaskCreate(vSensorTask, "Sensor", 256, NULL, TASK_SENSOR_PRIO, &g_sensor_task_h);
xTaskCreate(vDisplayTask, "Display", 512, NULL, TASK_DISPLAY_PRIO, &g_display_task_h);
xTaskCreate(vCommTask, "Comm", 256, NULL, TASK_COMM_PRIO, &g_comm_task_h);
xTaskCreate(vLEDTask, "LED", 128, NULL, TASK_LED_PRIO, &g_led_task_h);
// 启动调度器
vTaskStartScheduler();
}
第四月:通信协议精通------连接万物的桥梁
进入第四个月,我们将深入学习各种通信协议,从工业总线到物联网通信。
第1周:工业总线------Modbus/RS485与CAN
1.1 Modbus/RS485协议
Modbus是一种广泛应用于工业领域的串行通信协议,RS485是其最常用的物理层。
c
#include <stdint.h>
#include <string.h>
// Modbus功能码
#define MODBUS_FC_READ_COILS 0x01
#define MODBUS_FC_READ_DISCRETE_INPUT 0x02
#define MODBUS_FC_READ_HOLDING_REG 0x03
#define MODBUS_FC_READ_INPUT_REG 0x04
#define MODBUS_FC_WRITE_SINGLE_COIL 0x05
#define MODBUS_FC_WRITE_SINGLE_REG 0x06
#define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F
#define MODBUS_FC_WRITE_MULTIPLE_REGS 0x10
// Modbus异常码
#define MODBUS_EXC_ILLEGAL_FUNCTION 0x01
#define MODBUS_EXC_ILLEGAL_DATA_ADDR 0x02
#define MODBUS_EXC_ILLEGAL_DATA_VALUE 0x03
#define MODBUS_EXC_SLAVE_FAILURE 0x04
// Modbus帧结构
typedef struct {
uint8_t slave_addr; // 从站地址
uint8_t func_code; // 功能码
uint16_t reg_addr; // 寄存器地址
uint16_t reg_count; // 寄存器数量
uint8_t *data; // 数据
uint16_t data_len; // 数据长度
} ModbusFrame_t;
// CRC16校验算法
uint16_t Modbus_CRC16(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF;
for(uint16_t i = 0; i < len; i++) {
crc ^= data[i];
for(uint8_t j = 0; j < 8; j++) {
if(crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
// Modbus从站数据结构
typedef struct {
uint8_t slave_id;
uint16_t holding_regs[100]; // 保持寄存器
uint16_t input_regs[100]; // 输入寄存器
uint8_t coils[100]; // 线圈
uint8_t discrete_inputs[100]; // 离散输入
} ModbusSlave_t;
ModbusSlave_t g_modbus_slave;
// Modbus帧解析
void Modbus_ParseFrame(uint8_t *frame, uint16_t len) {
if(len < 4) return;
// 检查CRC
uint16_t recv_crc = frame[len-2] | (frame[len-1] << 8);
uint16_t calc_crc = Modbus_CRC16(frame, len-2);
if(recv_crc != calc_crc) {
printf("CRC error!\r\n");
return;
}
// 检查从站地址
if(frame[0] != g_modbus_slave.slave_id && frame[0] != 0) {
return;
}
uint8_t func = frame[1];
uint8_t *response;
uint16_t response_len = 0;
switch(func) {
case MODBUS_FC_READ_HOLDING_REG: {
uint16_t start_addr = (frame[2] << 8) | frame[3];
uint16_t reg_count = (frame[4] << 8) | frame[5];
if(start_addr + reg_count > 100) {
response = Modbus_Exception(frame[0], MODBUS_EXC_ILLEGAL_DATA_ADDR);
response_len = 3;
} else {
response = malloc(3 + reg_count * 2);
response[0] = frame[0];
response[1] = func;
response[2] = reg_count * 2;
for(uint16_t i = 0; i < reg_count; i++) {
response[3 + i*2] = (g_modbus_slave.holding_regs[start_addr + i] >> 8) & 0xFF;
response[4 + i*2] = g_modbus_slave.holding_regs[start_addr + i] & 0xFF;
}
response_len = 3 + reg_count * 2;
}
break;
}
case MODBUS_FC_WRITE_SINGLE_REG: {
uint16_t reg_addr = (frame[2] << 8) | frame[3];
uint16_t reg_value = (frame[4] << 8) | frame[5];
if(reg_addr >= 100) {
response = Modbus_Exception(frame[0], MODBUS_EXC_ILLEGAL_DATA_ADDR);
response_len = 3;
} else {
g_modbus_slave.holding_regs[reg_addr] = reg_value;
response = frame; // 回显请求帧
response_len = len;
}
break;
}
default:
response = Modbus_Exception(frame[0], MODBUS_EXC_ILLEGAL_FUNCTION);
response_len = 3;
break;
}
if(response) {
// 添加CRC并发送
uint16_t crc = Modbus_CRC16(response, response_len);
response[response_len] = crc & 0xFF;
response[response_len+1] = (crc >> 8) & 0xFF;
HAL_UART_Transmit(&huart1, response, response_len + 2, 100);
if(func != MODBUS_FC_WRITE_SINGLE_REG) free(response);
}
}
// 异常响应生成
uint8_t* Modbus_Exception(uint8_t slave_id, uint8_t exception_code) {
static uint8_t response[3];
response[0] = slave_id;
response[1] = exception_code | 0x80;
response[2] = exception_code;
return response;
}
// RS485方向控制
void RS485_SetDirection(uint8_t tx_enable) {
if(tx_enable) {
HAL_GPIO_WritePin(RS485_DE_RE_GPIO_Port, RS485_DE_RE_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(RS485_DE_RE_GPIO_Port, RS485_DE_RE_Pin, GPIO_PIN_RESET);
}
}
1.2 CAN总线协议
CAN总线具有高可靠性、实时性和错误处理能力,广泛应用于汽车电子和工业控制。
c
// CAN帧结构
typedef struct {
uint32_t id; // 标识符(11位标准ID或29位扩展ID)
uint8_t ide; // 标识符扩展位:0-标准帧,1-扩展帧
uint8_t rtr; // 远程帧:0-数据帧,1-远程帧
uint8_t dlc; // 数据长度(0-8)
uint8_t data[8]; // 数据
} CanFrame_t;
// CAN初始化(STM32 HAL库)
void CAN_Init_HAL(void) {
hcan.Instance = CAN1;
hcan.Init.Prescaler = 9; // 36MHz / 9 = 4MHz
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_13TQ;
hcan.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = DISABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE;
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
HAL_CAN_Init(&hcan);
// 配置过滤器
CAN_FilterTypeDef sFilterConfig;
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_FILTER_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
// 启动CAN
HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
}
// CAN发送
uint8_t CAN_Send(CanFrame_t *frame) {
CAN_TxHeaderTypeDef tx_header;
uint32_t tx_mailbox;
tx_header.ExtId = frame->id;
tx_header.IDE = frame->ide ? CAN_ID_EXT : CAN_ID_STD;
tx_header.RTR = frame->rtr ? CAN_RTR_REMOTE : CAN_RTR_DATA;
tx_header.DLC = frame->dlc;
tx_header.TransmitGlobalTime = DISABLE;
if(HAL_CAN_AddTxMessage(&hcan, &tx_header, frame->data, &tx_mailbox) != HAL_OK) {
return 0;
}
return 1;
}
// CAN接收回调
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CanFrame_t rx_frame;
CAN_RxHeaderTypeDef rx_header;
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_frame.data);
rx_frame.id = rx_header.ExtId;
rx_frame.ide = rx_header.IDE;
rx_frame.rtr = rx_header.RTR;
rx_frame.dlc = rx_header.DLC;
// 根据ID处理不同的消息
CAN_ProcessFrame(&rx_frame);
}
// CAN ID分配与管理
#define CAN_ID_MASTER 0x100
#define CAN_ID_SENSOR_1 0x200
#define CAN_ID_SENSOR_2 0x201
#define CAN_ID_ACTUATOR_1 0x300
#define CAN_ID_ACTUATOR_2 0x301
typedef struct {
uint32_t id;
uint32_t mask;
void (*handler)(CanFrame_t *frame);
} CanHandler_t;
CanHandler_t g_can_handlers[] = {
{CAN_ID_MASTER, 0x7FF, CAN_MasterHandler},
{CAN_ID_SENSOR_1, 0x7FF, CAN_SensorHandler},
{CAN_ID_SENSOR_2, 0x7FF, CAN_SensorHandler},
{CAN_ID_ACTUATOR_1, 0x7FF, CAN_ActuatorHandler},
};
void CAN_ProcessFrame(CanFrame_t *frame) {
for(int i = 0; i < sizeof(g_can_handlers)/sizeof(CanHandler_t); i++) {
if((frame->id & g_can_handlers[i].mask) == g_can_handlers[i].id) {
if(g_can_handlers[i].handler) {
g_can_handlers[i].handler(frame);
}
break;
}
}
}
// CAN错误处理
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) {
uint32_t error = HAL_CAN_GetError(hcan);
if(error & HAL_CAN_ERROR_EWG) {
printf("CAN: Error warning\r\n");
}
if(error & HAL_CAN_ERROR_EPV) {
printf("CAN: Error passive\r\n");
}
if(error & HAL_CAN_ERROR_BOF) {
printf("CAN: Bus off\r\n");
// 尝试恢复
HAL_CAN_Stop(hcan);
HAL_Delay(100);
HAL_CAN_Start(hcan);
}
if(error & HAL_CAN_ERROR_STF) {
printf("CAN: Stuff error\r\n");
}
if(error & HAL_CAN_ERROR_FOR) {
printf("CAN: Form error\r\n");
}
if(error & HAL_CAN_ERROR_ACK) {
printf("CAN: Acknowledge error\r\n");
}
}
第2周:物联网通信------ESP32与WiFi/MQTT
2.1 ESP32环境搭建
ESP32是一款集成了WiFi和蓝牙的芯片,非常适合物联网应用。
c
// ESP32环境配置(Arduino框架)
#include <WiFi.h>
#include <PubSubClient.h>
#include <HTTPClient.h>
// WiFi配置
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
// MQTT配置
const char* mqtt_server = "mqtt.broker.com";
const int mqtt_port = 1883;
const char* mqtt_user = "user";
const char* mqtt_pass = "password";
// MQTT主题
const char* topic_pub = "sensor/data";
const char* topic_sub = "actuator/control";
WiFiClient espClient;
PubSubClient client(espClient);
// WiFi连接
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
// MQTT回调函数
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
String message;
for(int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// 处理控制命令
if(String(topic) == topic_sub) {
if(message == "ON") {
digitalWrite(LED_BUILTIN, HIGH);
client.publish(topic_pub, "LED turned ON");
} else if(message == "OFF") {
digitalWrite(LED_BUILTIN, LOW);
client.publish(topic_pub, "LED turned OFF");
}
}
}
// MQTT重连
void mqtt_reconnect() {
while(!client.connected()) {
Serial.print("Attempting MQTT connection...");
// 创建客户端ID
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
// 连接MQTT服务器
if(client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {
Serial.println("connected");
// 订阅主题
client.subscribe(topic_sub);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
// 发送传感器数据
void publish_sensor_data(float temperature, float humidity) {
if(client.connected()) {
char payload[100];
sprintf(payload, "{\"temperature\":%.1f,\"humidity\":%.1f}",
temperature, humidity);
client.publish(topic_pub, payload);
Serial.println("Data published");
}
}
2.2 HTTP网络请求
HTTP协议是Web应用的基础,在物联网中常用于RESTful API通信。
c
// HTTP GET请求
void http_get_example() {
HTTPClient http;
http.begin("http://api.example.com/sensor/read");
int httpCode = http.GET();
if(httpCode > 0) {
if(httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
// 解析JSON数据
// 可以使用ArduinoJson库
}
} else {
Serial.printf("[HTTP] GET failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
// HTTP POST请求
void http_post_example(float temperature) {
HTTPClient http;
http.begin("http://api.example.com/sensor/data");
http.addHeader("Content-Type", "application/json");
char json[100];
sprintf(json, "{\"temperature\":%.1f,\"timestamp\":%lu}",
temperature, millis());
int httpCode = http.POST(json);
if(httpCode > 0) {
if(httpCode == HTTP_CODE_OK) {
String response = http.getString();
Serial.println(response);
}
} else {
Serial.printf("[HTTP] POST failed\n");
}
http.end();
}
// 带认证的HTTP请求
void http_authenticated_request() {
HTTPClient http;
http.begin("http://api.example.com/protected/data");
http.addHeader("Authorization", "Bearer your_access_token");
http.addHeader("Content-Type", "application/json");
int httpCode = http.GET();
if(httpCode == HTTP_CODE_OK) {
String response = http.getString();
Serial.println(response);
} else if(httpCode == HTTP_CODE_UNAUTHORIZED) {
Serial.println("Authentication failed");
// 刷新token
}
http.end();
}
2.3 MQTT协议深度解析
MQTT是一种轻量级的发布/订阅消息协议,非常适合物联网场景。
c
// MQTT质量等级(QoS)
// QoS 0: 最多一次(可能丢失)
// QoS 1: 至少一次(保证送达,可能重复)
// QoS 2: 恰好一次(保证送达且不重复)
// MQTT发布(QoS 1)
void mqtt_publish_qos1(const char* topic, const char* payload) {
// 使用QoS 1,需要消息ID
uint16_t msg_id = client.publish(topic, payload, true); // retained = true
Serial.printf("Published with msg_id: %d\n", msg_id);
}
// MQTT发布确认回调
void mqtt_publish_callback(uint16_t msg_id) {
Serial.printf("Publish acknowledged: %d\n", msg_id);
}
// MQTT遗嘱消息
void mqtt_set_will() {
// 设置遗嘱消息,当客户端意外断开时,服务器会发布这个消息
client.setWill("device/status", "offline", true, 0);
}
// 持久会话
void mqtt_persistent_session() {
// 设置clean session为false,服务器会保存订阅信息
client.setCleanSession(false);
// 连接时指定客户端ID,服务器会记住会话
client.connect("ESP32_Persistent_Client");
}
第三阶段:深化期(第7-9月)------ Linux系统编程
进入第七个月,我们将学习Linux系统编程,这是嵌入式Linux开发的基础。
第五月:Linux系统操作与Shell脚本
第1周:文件权限管理与进程调度
bash
#!/bin/bash
# 文件权限管理示例
# 查看文件权限
ls -l file.txt
# 输出: -rw-r--r-- 1 user group 1024 Jan 1 12:00 file.txt
# 权限位: 文件类型(1) 所有者权限(3) 组权限(3) 其他权限(3)
# 修改文件权限(符号方式)
chmod u+x file.txt # 添加所有者执行权限
chmod g-w file.txt # 移除组写权限
chmod o+r file.txt # 添加其他用户读权限
chmod a+x file.txt # 添加所有用户执行权限
# 修改文件权限(数字方式)
# r=4, w=2, x=1
chmod 755 file.txt # 所有者rwx,组r-x,其他r-x
chmod 644 file.txt # 所有者rw-,组r--,其他r--
# 修改文件所有者
chown newuser file.txt
chown newuser:newgroup file.txt
# 进程管理
# 查看进程
ps aux # 显示所有进程
ps -ef | grep python # 查找Python进程
# 进程优先级(nice值)
nice -n 10 ./program # 以较低优先级运行
renice 5 -p 1234 # 改变运行中进程的优先级
# 后台运行
./program & # 后台运行
nohup ./program & # 忽略挂起信号,退出终端后继续运行
jobs # 查看后台任务
fg %1 # 将后台任务调至前台
bg %1 # 将挂起的任务后台运行
# 进程监控
top # 动态查看进程
htop # 更友好的top
pkill process_name # 杀死进程
kill -9 1234 # 强制杀死进程
第2周:管道通信与信号处理
c
// 管道通信示例
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void pipe_example() {
int fd[2];
pid_t pid;
char buf[256];
// 创建管道
if(pipe(fd) == -1) {
perror("pipe");
return;
}
pid = fork();
if(pid == 0) {
// 子进程:写入数据
close(fd[0]); // 关闭读端
char *msg = "Hello from child!";
write(fd[1], msg, strlen(msg));
close(fd[1]);
} else if(pid > 0) {
// 父进程:读取数据
close(fd[1]); // 关闭写端
int n = read(fd[0], buf, sizeof(buf));
buf[n] = '\0';
printf("Parent received: %s\n", buf);
close(fd[0]);
wait(NULL);
}
}
// 命名管道(FIFO)
#include <sys/stat.h>
#include <fcntl.h>
void fifo_example() {
// 创建FIFO
mkfifo("/tmp/myfifo", 0666);
pid_t pid = fork();
if(pid == 0) {
// 写入进程
int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "Data through FIFO", 17);
close(fd);
} else {
// 读取进程
int fd = open("/tmp/myfifo", O_RDONLY);
char buf[256];
int n = read(fd, buf, sizeof(buf));
buf[n] = '\0';
printf("Read: %s\n", buf);
close(fd);
unlink("/tmp/myfifo");
wait(NULL);
}
}
// 信号处理
#include <signal.h>
volatile sig_atomic_t g_signal_received = 0;
void signal_handler(int sig) {
g_signal_received = sig;
printf("Received signal: %d\n", sig);
}
void signal_example() {
// 注册信号处理函数
signal(SIGINT, signal_handler); // Ctrl+C
signal(SIGTERM, signal_handler); // 终止信号
signal(SIGUSR1, signal_handler); // 用户自定义信号1
// 忽略信号
signal(SIGPIPE, SIG_IGN);
// 发送信号
kill(getpid(), SIGUSR1);
while(1) {
pause(); // 等待信号
if(g_signal_received == SIGINT) {
printf("Exiting...\n");
break;
}
}
}
// 更高级的信号处理(sigaction)
void sigaction_example() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; // 自动重启被中断的系统调用
sigaction(SIGINT, &sa, NULL);
}
第3周:Shell脚本自动化
bash
#!/bin/bash
# 嵌入式开发自动化脚本
# 变量定义
PROJECT_NAME="embedded_app"
BUILD_DIR="build"
SRC_DIR="src"
TOOLCHAIN="arm-none-eabi-"
MCU="STM32F103C8"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 打印函数
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查依赖
check_dependencies() {
print_info "Checking dependencies..."
# 检查编译器
if ! command -v ${TOOLCHAIN}gcc &> /dev/null; then
print_error "${TOOLCHAIN}gcc not found"
exit 1
fi
# 检查OpenOCD
if ! command -v openocd &> /dev/null; then
print_warn "OpenOCD not found, flashing may not work"
fi
print_info "All dependencies OK"
}
# 编译项目
compile_project() {
print_info "Compiling project..."
# 创建构建目录
mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR}
# 运行CMake
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/${MCU}.cmake
# 编译
make -j4
if [ $? -eq 0 ]; then
print_info "Compilation successful"
else
print_error "Compilation failed"
exit 1
fi
cd ..
}
# 烧录固件
flash_firmware() {
print_info "Flashing firmware..."
# 使用OpenOCD烧录
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "program ${BUILD_DIR}/${PROJECT_NAME}.hex verify reset exit"
if [ $? -eq 0 ]; then
print_info "Flashing successful"
else
print_error "Flashing failed"
fi
}
# 运行单元测试
run_tests() {
print_info "Running unit tests..."
# 编译测试
mkdir -p test_build
cd test_build
cmake ../test
make
# 运行测试
./unit_tests
if [ $? -eq 0 ]; then
print_info "All tests passed"
else
print_error "Some tests failed"
exit 1
fi
cd ..
}
# 生成文档
generate_docs() {
print_info "Generating documentation..."
# 使用Doxygen生成文档
doxygen Doxyfile
print_info "Documentation generated in docs/"
}
# 清理构建
clean() {
print_info "Cleaning build artifacts..."
rm -rf ${BUILD_DIR}
rm -rf test_build
rm -rf docs/html
print_info "Clean completed"
}
# 主函数
main() {
case "$1" in
build)
check_dependencies
compile_project
;;
flash)
flash_firmware
;;
test)
run_tests
;;
docs)
generate_docs
;;
clean)
clean
;;
all)
check_dependencies
compile_project
run_tests
generate_docs
flash_firmware
;;
*)
echo "Usage: $0 {build|flash|test|docs|clean|all}"
exit 1
;;
esac
}
# 脚本入口
main "$@"
第六月:高级编程------文件IO与多线程
第1周:深入文件IO操作
c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
// 标准文件IO
void std_file_io() {
FILE *fp = fopen("test.txt", "w+");
if(fp == NULL) {
perror("fopen");
return;
}
// 写入
fprintf(fp, "Hello, Embedded Linux!\n");
fputs("Another line\n", fp);
// 刷新缓冲区
fflush(fp);
// 定位
fseek(fp, 0, SEEK_SET);
// 读取
char buf[256];
while(fgets(buf, sizeof(buf), fp) != NULL) {
printf("%s", buf);
}
fclose(fp);
}
// 系统调用IO
void sys_file_io() {
int fd = open("test.txt", O_RDWR | O_CREAT, 0644);
if(fd < 0) {
perror("open");
return;
}
// 写入
char *msg = "System call IO\n";
write(fd, msg, strlen(msg));
// 定位到文件开头
lseek(fd, 0, SEEK_SET);
// 读取
char buf[256];
int n = read(fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("Read: %s", buf);
close(fd);
}
// 内存映射文件
void mmap_file() {
int fd = open("large_file.dat", O_RDWR | O_CREAT, 0644);
if(fd < 0) {
perror("open");
return;
}
// 设置文件大小
lseek(fd, 1024 * 1024 - 1, SEEK_SET);
write(fd, "", 1);
// 内存映射
void *map = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if(map == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
// 直接操作内存
sprintf((char*)map, "Data at offset 0");
sprintf((char*)map + 100, "Data at offset 100");
// 同步到文件
msync(map, 1024 * 1024, MS_SYNC);
// 取消映射
munmap(map, 1024 * 1024);
close(fd);
}
// 文件锁
void file_locking() {
int fd = open("shared.dat", O_RDWR | O_CREAT, 0644);
struct flock fl;
// 设置写锁
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0; // 整个文件
if(fcntl(fd, F_SETLKW, &fl) == -1) {
perror("fcntl lock");
return;
}
printf("Lock acquired, writing...\n");
write(fd, "Locked data", 11);
sleep(5);
// 释放锁
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl);
printf("Lock released\n");
close(fd);
}
第2周:多线程同步互斥
c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
// 互斥锁示例
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
int g_shared_counter = 0;
void* thread_increment(void *arg) {
for(int i = 0; i < 1000000; i++) {
pthread_mutex_lock(&g_mutex);
g_shared_counter++;
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
void mutex_example() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_increment, NULL);
pthread_create(&t2, NULL, thread_increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Counter: %d\n", g_shared_counter);
}
// 条件变量示例
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
int g_ready = 0;
void* producer(void *arg) {
sleep(2);
pthread_mutex_lock(&g_mutex);
g_ready = 1;
printf("Producer: Signal ready\n");
pthread_cond_signal(&g_cond);
pthread_mutex_unlock(&g_mutex);
return NULL;
}
void* consumer(void *arg) {
pthread_mutex_lock(&g_mutex);
while(!g_ready) {
printf("Consumer: Waiting...\n");
pthread_cond_wait(&g_cond, &g_mutex);
}
printf("Consumer: Got signal\n");
pthread_mutex_unlock(&g_mutex);
return NULL;
}
void cond_example() {
pthread_t prod, cons;
pthread_create(&cons, NULL, consumer, NULL);
pthread_create(&prod, NULL, producer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
}
// 读写锁示例
pthread_rwlock_t g_rwlock = PTHREAD_RWLOCK_INITIALIZER;
int g_data = 0;
void* reader(void *arg) {
for(int i = 0; i < 5; i++) {
pthread_rwlock_rdlock(&g_rwlock);
printf("Reader %ld: data = %d\n", (long)arg, g_data);
pthread_rwlock_unlock(&g_rwlock);
usleep(100000);
}
return NULL;
}
void* writer(void *arg) {
for(int i = 0; i < 3; i++) {
pthread_rwlock_wrlock(&g_rwlock);
g_data++;
printf("Writer: data = %d\n", g_data);
pthread_rwlock_unlock(&g_rwlock);
sleep(1);
}
return NULL;
}
void rwlock_example() {
pthread_t readers[5], writer_t;
for(long i = 0; i < 5; i++) {
pthread_create(&readers[i], NULL, reader, (void*)i);
}
pthread_create(&writer_t, NULL, writer, NULL);
for(int i = 0; i < 5; i++) {
pthread_join(readers[i], NULL);
}
pthread_join(writer_t, NULL);
}
// 信号量示例
sem_t g_semaphore;
#define BUFFER_SIZE 10
int g_buffer[BUFFER_SIZE];
int g_in = 0, g_out = 0;
void* producer_sem(void *arg) {
for(int i = 0; i < 20; i++) {
sem_wait(&g_semaphore); // 等待空闲槽位
g_buffer[g_in] = i;
printf("Produced: %d at %d\n", i, g_in);
g_in = (g_in + 1) % BUFFER_SIZE;
sem_post(&g_semaphore); // 释放一个槽位
usleep(100000);
}
return NULL;
}
void* consumer_sem(void *arg) {
for(int i = 0; i < 20; i++) {
sem_wait(&g_semaphore);
int data = g_buffer[g_out];
printf("Consumed: %d from %d\n", data, g_out);
g_out = (g_out + 1) % BUFFER_SIZE;
sem_post(&g_semaphore);
usleep(150000);
}
return NULL;
}
void semaphore_example() {
sem_init(&g_semaphore, 0, BUFFER_SIZE);
pthread_t prod, cons;
pthread_create(&prod, NULL, producer_sem, NULL);
pthread_create(&cons, NULL, consumer_sem, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
sem_destroy(&g_semaphore);
}
第3周:网络Socket编程
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
// TCP服务器
void tcp_server() {
int server_fd, client_fd;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
// 创建socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 设置socket选项
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
// 监听
listen(server_fd, 3);
printf("Server listening on port 8080\n");
// 接受连接
client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
printf("Client connected\n");
// 接收数据
read(client_fd, buffer, 1024);
printf("Received: %s\n", buffer);
// 发送响应
char *response = "Hello from server!";
send(client_fd, response, strlen(response), 0);
close(client_fd);
close(server_fd);
}
// TCP客户端
void tcp_client() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[1024] = {0};
// 创建socket
sock = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务器地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
// 连接服务器
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// 发送数据
char *message = "Hello from client!";
send(sock, message, strlen(message), 0);
printf("Message sent\n");
// 接收响应
read(sock, buffer, 1024);
printf("Received: %s\n", buffer);
close(sock);
}
// 多线程TCP服务器
typedef struct {
int client_fd;
struct sockaddr_in address;
} ClientInfo;
void* handle_client(void *arg) {
ClientInfo *info = (ClientInfo*)arg;
char buffer[1024];
printf("Handling client from %s:%d\n",
inet_ntoa(info->address.sin_addr),
ntohs(info->address.sin_port));
while(1) {
int n = read(info->client_fd, buffer, sizeof(buffer) - 1);
if(n <= 0) break;
buffer[n] = '\0';
printf("Received: %s\n", buffer);
// 回显
send(info->client_fd, buffer, n, 0);
}
close(info->client_fd);
free(info);
return NULL;
}
void multithread_server() {
int server_fd;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 10);
printf("Multi-thread server listening on port 8080\n");
while(1) {
ClientInfo *info = malloc(sizeof(ClientInfo));
info->client_fd = accept(server_fd, (struct sockaddr*)&info->address,
(socklen_t*)&addrlen);
pthread_t thread;
pthread_create(&thread, NULL, handle_client, info);
pthread_detach(thread); // 自动回收线程资源
}
close(server_fd);
}
// UDP服务器
void udp_server() {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
char buffer[1024];
socklen_t len;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(8080);
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
printf("UDP server listening on port 8080\n");
while(1) {
len = sizeof(cliaddr);
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&cliaddr, &len);
buffer[n] = '\0';
printf("Received from %s:%d: %s\n",
inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buffer);
sendto(sockfd, buffer, n, 0, (struct sockaddr*)&cliaddr, len);
}
close(sockfd);
}
// UDP客户端
void udp_client() {
int sockfd;
struct sockaddr_in servaddr;
char buffer[1024];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
char *message = "Hello UDP!";
sendto(sockfd, message, strlen(message), 0,
(struct sockaddr*)&servaddr, sizeof(servaddr));
printf("Message sent\n");
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
buffer[n] = '\0';
printf("Received: %s\n", buffer);
close(sockfd);
}
第四阶段:整合期(第10-12月)------ 项目实战与优化
第七月:项目整合------智能传感器采集系统
项目一:STM32 + FreeRTOS + Modbus + LCD显示
c
// 智能传感器采集系统主程序
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
// 任务优先级定义
#define TASK_SENSOR_PRIO 3
#define TASK_MODBUS_PRIO 2
#define TASK_LCD_PRIO 2
#define TASK_DATA_PROC_PRIO 1
// 全局队列和信号量
QueueHandle_t g_sensor_queue;
QueueHandle_t g_display_queue;
SemaphoreHandle_t g_modbus_sem;
// 传感器数据结构
typedef struct {
float temperature;
float humidity;
float pressure;
uint32_t timestamp;
uint8_t quality;
} SensorData;
// 显示数据结构
typedef struct {
uint8_t page;
char line1[20];
char line2[20];
char line3[20];
char line4[20];
} DisplayData;
// 传感器采集任务
void vSensorTask(void *pvParameters) {
SensorData data;
while(1) {
// 读取传感器
data.temperature = read_temperature();
data.humidity = read_humidity();
data.pressure = read_pressure();
data.timestamp = xTaskGetTickCount();
data.quality = 100; // 数据质量百分比
// 发送到队列
xQueueSend(g_sensor_queue, &data, 0);
// 通知Modbus任务数据已更新
xSemaphoreGive(g_modbus_sem);
// 采集周期:1秒
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 数据处理任务
void vDataProcessTask(void *pvParameters) {
SensorData data;
DisplayData display;
float avg_temp = 0, avg_hum = 0, avg_pres = 0;
int count = 0;
while(1) {
// 从队列获取数据
if(xQueueReceive(g_sensor_queue, &data, pdMS_TO_TICKS(100)) == pdTRUE) {
// 滑动平均滤波
avg_temp = (avg_temp * count + data.temperature) / (count + 1);
avg_hum = (avg_hum * count + data.humidity) / (count + 1);
avg_pres = (avg_pres * count + data.pressure) / (count + 1);
if(count < 10) count++;
// 更新显示数据
sprintf(display.line1, "Temp: %.1f C", avg_temp);
sprintf(display.line2, "Hum: %.1f %%", avg_hum);
sprintf(display.line3, "Pres: %.1f hPa", avg_pres);
sprintf(display.line4, "Quality: %d%%", data.quality);
xQueueSend(g_display_queue, &display, 0);
}
}
}
// LCD显示任务
void vLCDTask(void *pvParameters) {
DisplayData display;
while(1) {
if(xQueueReceive(g_display_queue, &display, portMAX_DELAY) == pdTRUE) {
// 更新LCD显示
LCD_Clear();
LCD_SetCursor(0, 0);
LCD_Print(display.line1);
LCD_SetCursor(0, 1);
LCD_Print(display.line2);
LCD_SetCursor(0, 2);
LCD_Print(display.line3);
LCD_SetCursor(0, 3);
LCD_Print(display.line4);
}
}
}
// Modbus通信任务
void vModbusTask(void *pvParameters) {
SensorData data;
uint8_t modbus_frame[256];
while(1) {
// 等待数据更新或外部请求
xSemaphoreTake(g_modbus_sem, pdMS_TO_TICKS(100));
// 获取最新数据
xQueuePeek(g_sensor_queue, &data, 0);
// 更新Modbus保持寄存器
g_modbus_slave.holding_regs[0] = (uint16_t)(data.temperature * 10);
g_modbus_slave.holding_regs[1] = (uint16_t)(data.humidity * 10);
g_modbus_slave.holding_regs[2] = (uint16_t)(data.pressure * 10);
g_modbus_slave.holding_regs[3] = data.quality;
g_modbus_slave.holding_regs[4] = data.timestamp & 0xFFFF;
g_modbus_slave.holding_regs[5] = (data.timestamp >> 16) & 0xFFFF;
// 处理Modbus请求
if(UART_DataAvailable()) {
int len = UART_Read(modbus_frame, sizeof(modbus_frame));
Modbus_ParseFrame(modbus_frame, len);
}
}
}
// 系统初始化
void System_Init(void) {
// 硬件初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
LCD_Init();
// 创建队列
g_sensor_queue = xQueueCreate(20, sizeof(SensorData));
g_display_queue = xQueueCreate(10, sizeof(DisplayData));
// 创建信号量
g_modbus_sem = xSemaphoreCreateBinary();
// 创建任务
xTaskCreate(vSensorTask, "Sensor", 256, NULL, TASK_SENSOR_PRIO, NULL);
xTaskCreate(vDataProcessTask, "Process", 512, NULL, TASK_DATA_PROC_PRIO, NULL);
xTaskCreate(vLCDTask, "LCD", 512, NULL, TASK_LCD_PRIO, NULL);
xTaskCreate(vModbusTask, "Modbus", 512, NULL, TASK_MODBUS_PRIO, NULL);
// 启动调度器
vTaskStartScheduler();
}
第八月:项目整合------物联网远程控制平台
项目二:ESP32 + WiFi + MQTT + 云服务
c
// 物联网远程控制平台
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
// WiFi配置
const char* ssid = "your_wifi";
const char* password = "your_password";
// MQTT配置
const char* mqtt_server = "mqtt.aliyun.com";
const int mqtt_port = 1883;
const char* mqtt_user = "your_username";
const char* mqtt_pass = "your_password";
// 设备ID
const char* device_id = "ESP32_001";
// MQTT主题
const char* topic_status = "device/status";
const char* topic_data = "device/data";
const char* topic_command = "device/command";
const char* topic_response = "device/response";
// 传感器引脚
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define RELAY_PIN 5
DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient client(espClient);
// 系统状态
typedef struct {
bool relay_state;
float temperature;
float humidity;
uint32_t last_update;
uint32_t last_heartbeat;
uint8_t wifi_rssi;
uint32_t uptime;
} SystemState;
SystemState g_state;
// WiFi连接
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
int retry = 0;
while(WiFi.status() != WL_CONNECTED && retry < 20) {
delay(500);
Serial.print(".");
retry++;
}
if(WiFi.status() == WL_CONNECTED) {
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
g_state.wifi_rssi = WiFi.RSSI();
} else {
Serial.println("WiFi connection failed");
}
}
// MQTT回调函数
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
// 解析JSON
DynamicJsonDocument doc(256);
deserializeJson(doc, payload, length);
if(String(topic) == topic_command) {
const char* command = doc["command"];
Serial.printf("Command: %s\n", command);
if(strcmp(command, "relay_on") == 0) {
digitalWrite(RELAY_PIN, HIGH);
g_state.relay_state = true;
send_response("relay_on", "success");
} else if(strcmp(command, "relay_off") == 0) {
digitalWrite(RELAY_PIN, LOW);
g_state.relay_state = false;
send_response("relay_off", "success");
} else if(strcmp(command, "get_status") == 0) {
send_status();
}
}
}
// 发送响应
void send_response(const char* command, const char* result) {
DynamicJsonDocument doc(128);
doc["device_id"] = device_id;
doc["command"] = command;
doc["result"] = result;
doc["timestamp"] = millis();
char buffer[128];
serializeJson(doc, buffer);
client.publish(topic_response, buffer);
}
// 发送状态
void send_status() {
DynamicJsonDocument doc(256);
doc["device_id"] = device_id;
doc["relay"] = g_state.relay_state;
doc["temperature"] = g_state.temperature;
doc["humidity"] = g_state.humidity;
doc["rssi"] = g_state.wifi_rssi;
doc["uptime"] = g_state.uptime;
char buffer[256];
serializeJson(doc, buffer);
client.publish(topic_status, buffer);
}
// 发送传感器数据
void send_sensor_data() {
DynamicJsonDocument doc(128);
doc["device_id"] = device_id;
doc["temperature"] = g_state.temperature;
doc["humidity"] = g_state.humidity;
doc["timestamp"] = millis();
char buffer[128];
serializeJson(doc, buffer);
client.publish(topic_data, buffer);
}
// MQTT重连
void mqtt_reconnect() {
while(!client.connected()) {
Serial.print("Attempting MQTT connection...");
if(client.connect(device_id, mqtt_user, mqtt_pass)) {
Serial.println("connected");
client.subscribe(topic_command);
send_status();
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
// 读取传感器
void read_sensors() {
g_state.temperature = dht.readTemperature();
g_state.humidity = dht.readHumidity();
if(isnan(g_state.temperature) || isnan(g_state.humidity)) {
Serial.println("Failed to read from DHT sensor");
g_state.temperature = 0;
g_state.humidity = 0;
}
}
// 心跳任务
void heartbeat_task(void *pvParameters) {
while(1) {
g_state.uptime = millis() / 1000;
g_state.wifi_rssi = WiFi.RSSI();
// 发送心跳
DynamicJsonDocument doc(128);
doc["device_id"] = device_id;
doc["status"] = "online";
doc["uptime"] = g_state.uptime;
char buffer[128];
serializeJson(doc, buffer);
client.publish(topic_status, buffer);
vTaskDelay(pdMS_TO_TICKS(30000)); // 30秒心跳
}
}
// 传感器任务
void sensor_task(void *pvParameters) {
while(1) {
read_sensors();
send_sensor_data();
vTaskDelay(pdMS_TO_TICKS(10000)); // 10秒采集一次
}
}
// 主程序
void setup() {
Serial.begin(115200);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
dht.begin();
g_state.relay_state = false;
g_state.uptime = 0;
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(mqtt_callback);
// 创建RTOS任务
xTaskCreatePinnedToCore(heartbeat_task, "Heartbeat", 2048, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(sensor_task, "Sensor", 2048, NULL, 2, NULL, 1);
}
void loop() {
if(!client.connected()) {
mqtt_reconnect();
}
client.loop();
// 让出CPU给RTOS
vTaskDelay(pdMS_TO_TICKS(10));
}
第九月:性能调优与项目优化
代码逻辑优化
c
// 优化前:多次函数调用
void process_data_before(uint8_t *data, uint32_t len) {
for(uint32_t i = 0; i < len; i++) {
uint8_t byte = data[i];
byte = transform1(byte);
byte = transform2(byte);
byte = transform3(byte);
data[i] = byte;
}
}
// 优化后:内联函数和循环展开
static inline uint8_t transform_all(uint8_t byte) {
byte = (byte ^ 0x55) + 0x33;
byte = (byte << 1) | (byte >> 7);
byte = ~byte;
return byte;
}
void process_data_after(uint8_t *data, uint32_t len) {
// 8路循环展开
uint32_t i = 0;
for(; i + 8 <= len; i += 8) {
data[i] = transform_all(data[i]);
data[i+1] = transform_all(data[i+1]);
data[i+2] = transform_all(data[i+2]);
data[i+3] = transform_all(data[i+3]);
data[i+4] = transform_all(data[i+4]);
data[i+5] = transform_all(data[i+5]);
data[i+6] = transform_all(data[i+6]);
data[i+7] = transform_all(data[i+7]);
}
for(; i < len; i++) {
data[i] = transform_all(data[i]);
}
}
内存占用分析
bash
#!/bin/bash
# 内存分析脚本
# 编译时输出内存映射
arm-none-eabi-gcc -Wl,-Map=output.map main.c -o program.elf
# 分析内存使用
arm-none-eabi-size program.elf
# 输出:
# text data bss dec hex filename
# 12345 1234 5678 19257 4b39 program.elf
# 使用objdump查看段信息
arm-none-eabi-objdump -h program.elf
# 分析栈使用(使用-fstack-usage编译选项)
arm-none-eabi-gcc -fstack-usage main.c -o program.elf
# 生成.su文件,包含每个函数的栈使用情况
# 使用Valgrind检测内存泄漏(在PC上)
valgrind --leak-check=full ./program
系统功耗优化
c
// STM32低功耗模式
void enter_low_power_mode(void) {
// 关闭不需要的外设时钟
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOC_CLK_DISABLE();
// 配置未使用的GPIO为模拟输入
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_All;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 进入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后重新初始化系统时钟
SystemClock_Config();
}
// 动态频率调整
void adjust_cpu_frequency(uint32_t frequency) {
// 根据负载调整CPU频率
uint32_t cpu_load = get_cpu_load();
if(cpu_load < 10) {
set_cpu_frequency(16000000); // 16MHz
} else if(cpu_load < 50) {
set_cpu_frequency(72000000); // 72MHz
} else {
set_cpu_frequency(168000000); // 168MHz
}
}
// 外设按需供电
void power_manage_peripherals(void) {
// 传感器按需供电
if(sensor_needed()) {
HAL_GPIO_WritePin(SENSOR_POWER_PIN, GPIO_PIN_SET);
HAL_Delay(10); // 等待传感器稳定
read_sensors();
HAL_GPIO_WritePin(SENSOR_POWER_PIN, GPIO_PIN_RESET);
}
// 通信模块按需供电
if(communication_needed()) {
HAL_GPIO_WritePin(WIFI_POWER_PIN, GPIO_PIN_SET);
wifi_connect();
send_data();
wifi_disconnect();
HAL_GPIO_WritePin(WIFI_POWER_PIN, GPIO_PIN_RESET);
}
}
第五阶段:巩固期(第10-12月)------ 知识复盘与职业发展
第十月:知识梳理与深度复盘
技术体系思维导图
markdown
# 嵌入式技术体系
## 基础层
- C语言
- 指针与内存管理
- 结构体/联合体
- 函数指针/回调
- 预处理/宏
- 关键字(static/const/volatile)
- 计算机体系结构
- CPU架构
- 内存层次
- 中断系统
- DMA
## 硬件层
- MCU架构
- ARM Cortex-M
- RISC-V
- 外设驱动
- GPIO/UART/SPI/I2C
- Timer/PWM/ADC/DAC
- 中断/NVIC
- 硬件设计
- 原理图阅读
- 最小系统设计
- 信号完整性
## 系统层
- RTOS
- FreeRTOS
- RT-Thread
- 任务调度
- 同步机制
- 内存管理
- Linux
- 文件系统
- 进程/线程
- 网络编程
- 驱动开发
## 协议层
- 工业总线
- Modbus
- CAN
- Profibus
- 无线通信
- WiFi/Bluetooth
- LoRa/NB-IoT
- Zigbee
- 应用协议
- MQTT/CoAP
- HTTP/WebSocket
## 应用层
- 物联网平台
- 阿里云IoT
- AWS IoT
- 腾讯云IoT
- 数据处理
- 滤波算法
- 数据融合
- 边缘计算
- 人机交互
- LCD/OLED
- 触摸屏
- 语音识别
第十一月:技术默写与面试准备
高频面试题总结
C语言部分:
- 指针和数组的区别
- 内存对齐的作用和实现
- volatile的使用场景
- 回调函数的实现原理
- 宏定义与函数的区别
STM32部分:
- 时钟树配置流程
- 中断优先级和嵌套
- DMA的工作原理
- 定时器的各种模式
- 寄存器与HAL库的优缺点
RTOS部分:
- 任务切换的实现原理
- 优先级反转及解决方案
- 消息队列和信号量的区别
- 互斥量和二值信号量的区别
- 死锁的四个必要条件
通信协议部分:
- Modbus帧结构
- CAN总线仲裁机制
- MQTT的QoS等级
- TCP三次握手和四次挥手
- I2C和SPI的区别
第十二月:职业规划与持续学习
学习资源推荐
书籍:
- 《深入理解计算机系统》
- 《ARM Cortex-M3权威指南》
- 《嵌入式实时操作系统:FreeRTOS原理与应用》
- 《Linux设备驱动程序》
- 《TCP/IP详解》
在线资源:
- GitHub开源项目
- 嵌入式相关论坛
- 厂商官方文档
- 技术博客(如本文)
职业发展路径
markdown
## 嵌入式工程师职业发展
### 初级工程师(1-3年)
- 熟练掌握C语言
- 能够独立开发STM32项目
- 理解RTOS基本概念
- 能够阅读原理图和PCB设计
### 中级工程师(3-5年)
- 深入理解操作系统原理
- 能够进行Linux驱动开发
- 掌握多种通信协议
- 具备系统架构设计能力
- 能够进行性能优化
### 高级工程师(5-8年)
- 系统级架构设计
- 复杂项目管理和团队协作
- 硬件软件协同设计
- 新技术研究和引入
- 技术文档撰写和知识传承
### 专家/架构师(8年以上)
- 产品技术路线规划
- 核心技术攻关
- 技术团队培养
- 行业标准参与
- 技术创新和专利
结语:持续学习,永不止步
一年的嵌入式技术学习计划即将结束,但这仅仅是技术生涯的开始。嵌入式技术领域日新月异,新的芯片、新的协议、新的架构不断涌现。作为嵌入式开发者,我们需要保持终身学习的态度,不断更新自己的知识体系。
最后,送给每一位嵌入式开发者的话:
- 实践出真知: 不要停留在阅读代码,动手写代码、调试硬件才能真正掌握技术。
- 追根溯源: 遇到问题,不要只满足于解决问题,要深入理解问题的本质。
- 保持好奇: 技术世界充满未知,保持好奇心去探索新的领域。
- 分享交流: 通过技术博客、开源项目、社区分享,与他人交流能加速成长。
愿你在嵌入式技术的道路上,不断攀登,终抵高峰!