2025-06-16 14:03:58 +08:00
|
|
|
|
# 2025 嵌入式软件开发全景笔记 📘
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
2025-06-17 15:19:10 +08:00
|
|
|
|
欢迎来到本项目,这里将更新一份系统、全面且贴近实战的2025年嵌入式软件开发学习路线和知识点总结。
|
2025-06-17 16:59:45 +08:00
|
|
|
|
涵盖范围包括 C语言、驱动开发、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链等。
|
2025-06-16 14:11:41 +08:00
|
|
|
|
|
|
|
|
|
|
**目录**
|
2025-06-17 16:58:44 +08:00
|
|
|
|
* C语言基础
|
|
|
|
|
|
* [变量](./01-C语言基础与进阶/Readme.md#-变量variable)
|
|
|
|
|
|
* [数据类型](./01-C语言基础与进阶/Readme.md#-数据类型data-types)
|
|
|
|
|
|
* [关键字](./01-C语言基础与进阶/Readme.md#-关键字keywords)
|
|
|
|
|
|
* [常量](./01-C语言基础与进阶/Readme.md#-常量constant)
|
|
|
|
|
|
* [栈 和 堆(内存管理)](./01-C语言基础与进阶/Readme.md/#-栈和堆(内存管理))
|
|
|
|
|
|
* [栈](./01-C语言基础与进阶/Readme.md#栈-stack自动分配内存函数退出即释放)
|
|
|
|
|
|
* [堆](./01-C语言基础与进阶/Readme.md#堆-heap使用-malloc--free-手动分配和释放)
|
|
|
|
|
|
* [指针 / 指针数组 / 二级指针](./01-C语言基础与进阶/Readme.md/#-指针/指针数组/二级指针)
|
|
|
|
|
|
* [指针](./01-C语言基础与进阶/Readme.md#指针保存变量的地址)
|
|
|
|
|
|
* [指针数组](./01-C语言基础与进阶/Readme.md#指针数组数组中每个元素是一个指针)
|
|
|
|
|
|
* [二级指针](./01-C语言基础与进阶/Readme.md#二级指针指向指针的指针)
|
|
|
|
|
|
* [函数指针 / 函数指针数组](./01-C语言基础与进阶/Readme.md/#-函数指针/函数指针数组)
|
|
|
|
|
|
* [表达式、语句、运算符](./01-C语言基础与进阶/Readme.md/#-表达式、语句、运算符)
|
|
|
|
|
|
* [数组](./01-C语言基础与进阶/Readme.md#数组array)
|
|
|
|
|
|
* [字符串](./01-C语言基础与进阶/Readme.md#字符串string)
|
|
|
|
|
|
* [结构体](./01-C语言基础与进阶/Readme.md#结构体)
|
|
|
|
|
|
* [共用体](./01-C语言基础与进阶/Readme.md#共用体)
|
|
|
|
|
|
* [枚举](./01-C语言基础与进阶/Readme.md#枚举enumeration)
|
|
|
|
|
|
* [位域](./01-C语言基础与进阶/Readme.md#位域bit-field)
|
|
|
|
|
|
* [位操作](./01-C语言基础与进阶/Readme.md/#-位操作)
|
|
|
|
|
|
* [关键语义 & 修饰符](./01-C语言基础与进阶/Readme.md/#-关键语义&修饰符)
|
|
|
|
|
|
* [内存存储类型与生命周期](./01-C语言基础与进阶/Readme.md/#-内存存储类型与生命周期)
|
|
|
|
|
|
* [编译与调试基础](01-C语言基础与进阶/Readme.md/#-编译与调试基础)
|
|
|
|
|
|
* [排序算法](./01-C语言基础与进阶/Readme.md#排序算法)
|
2025-06-16 17:05:15 +08:00
|
|
|
|
* [冒泡排序](./01-C语言基础与进阶/Readme.md#冒泡排序bubble-sort)
|
|
|
|
|
|
* [选择排序](./01-C语言基础与进阶/Readme.md#选择排序selection-sort)
|
|
|
|
|
|
* [插入排序](./01-C语言基础与进阶/Readme.md#插入排序insertion-sort)
|
|
|
|
|
|
* [快速排序](./01-C语言基础与进阶/Readme.md#快速排序quick-sort)
|
|
|
|
|
|
* [归并排序](./01-C语言基础与进阶/Readme.md#归并排序merge-sort)
|
|
|
|
|
|
* [堆排序](./01-C语言基础与进阶/Readme.md#堆排序heap-sort)
|
2025-06-16 14:42:53 +08:00
|
|
|
|
* 嵌入式系统基础知识
|
2025-06-16 15:15:46 +08:00
|
|
|
|
* [嵌入式系统概览](./02-嵌入式系统基础知识/README.md#-嵌入式系统概览)
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [定义与特点](./02-嵌入式系统基础知识/README.md#-嵌入式系统定义与特点)
|
|
|
|
|
|
* [系统构成](./02-嵌入式系统基础知识/README.md#-系统构成mcu存储器传感器外设)
|
|
|
|
|
|
* [主流 MCU 简介](./02-嵌入式系统基础知识/README.md#-主流-mcu-简介)
|
|
|
|
|
|
* [架构与启动流程](./02-嵌入式系统基础知识/README.md#-架构与启动流程)
|
|
|
|
|
|
* [Cortex-M 内核结构](./02-嵌入式系统基础知识/README.md#-cortex-m-内核结构)
|
|
|
|
|
|
* [启动文件 Startup.s](./02-嵌入式系统基础知识/README.md#-启动文件-startups)
|
|
|
|
|
|
* [启动流程简要](./02-嵌入式系统基础知识/README.md#-启动流程简要)
|
|
|
|
|
|
* [编译器与链接器](./02-嵌入式系统基础知识/README.md#-编译器与链接器)
|
|
|
|
|
|
* [常用嵌入式工具链](./02-嵌入式系统基础知识/README.md#-常用嵌入式工具链)
|
|
|
|
|
|
* [链接脚本 (.ld) 示例](./02-嵌入式系统基础知识/README.md#-链接脚本-ld-示例)
|
|
|
|
|
|
* [存储器布局图(STM32 示例)](./02-嵌入式系统基础知识/README.md#-存储器布局图stm32-示例)
|
2025-06-16 17:14:50 +08:00
|
|
|
|
* [芯片数据手册阅读方法](./02-嵌入式系统基础知识/README.md#芯片数据手册阅读方法)
|
|
|
|
|
|
* [典型结构(以 STM32 为例)](./02-嵌入式系统基础知识/README.md#典型结构以-stm32-为例)
|
|
|
|
|
|
* [阅读技巧](./02-嵌入式系统基础知识/README.md#阅读技巧)
|
|
|
|
|
|
* [向量表的定义与重定向](./02-嵌入式系统基础知识/README.md#向量表的定义与重定向)
|
|
|
|
|
|
* [定义](/02-嵌入式系统基础知识/README.md#什么是向量表)
|
|
|
|
|
|
* [重定向](./02-嵌入式系统基础知识/README.md#重定向方法常用于-bootloader-或自定义中断)
|
|
|
|
|
|
* [ROM 启动 vs RAM 启动的差异](./02-嵌入式系统基础知识/README.md#rom-启动-vs-ram-启动的差异)
|
|
|
|
|
|
* [ROM 启动](./02-嵌入式系统基础知识/README.md#rom-启动flash-启动)
|
|
|
|
|
|
* [RAM 启动](./02-嵌入式系统基础知识/README.md#ram-启动)
|
|
|
|
|
|
* [使用差异](./02-嵌入式系统基础知识/README.md#使用差异)
|
2025-06-16 14:42:53 +08:00
|
|
|
|
* 驱动开发与外设编程
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [寄存器级开发](./03-驱动开发与外设编程/README.md#-寄存器级开发)
|
|
|
|
|
|
* [地址映射与寄存器偏移](./03-驱动开发与外设编程/README.md#-地址映射与寄存器偏移)
|
|
|
|
|
|
* [位操作技巧](./03-驱动开发与外设编程/README.md#-位操作技巧)
|
|
|
|
|
|
* [通用外设驱动](./03-驱动开发与外设编程/README.md#-通用外设驱动)
|
2025-06-16 22:24:38 +08:00
|
|
|
|
* [GPIO](./03-驱动开发与外设编程/README.md#-gpio通用输入输出)
|
|
|
|
|
|
* [UART / USART](./03-驱动开发与外设编程/README.md#-uart-usart)
|
|
|
|
|
|
* [SPI](./03-驱动开发与外设编程/README.md#-spi串行外设接口)
|
|
|
|
|
|
* [I2C](./03-驱动开发与外设编程/README.md#-i2c集成电路间总线)
|
|
|
|
|
|
* [ADC](./03-驱动开发与外设编程/README.md#-adc模拟-to-数字转换器)
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [RTC 实时时钟](./03-驱动开发与外设编程/README.md#-rtc-实时时钟)
|
|
|
|
|
|
* [复杂外设支持](./03-驱动开发与外设编程/README.md#-复杂外设支持)
|
|
|
|
|
|
* [DMA 控制器](./03-驱动开发与外设编程/README.md#-dma-控制器)
|
2025-06-16 22:24:38 +08:00
|
|
|
|
* [看门狗](./03-驱动开发与外设编程/README.md#-看门狗watchdog)
|
|
|
|
|
|
* [CAN](./03-驱动开发与外设编程/README.md#-can控制器局域网)
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [开发库 & 工具链](./03-驱动开发与外设编程/README.md#-开发库--工具链)
|
2025-06-16 22:24:38 +08:00
|
|
|
|
* [STM32 HAL ](./03-驱动开发与外设编程/README.md#-stm32-hal硬件抽象层)
|
|
|
|
|
|
* [STM32 LL ](./03-驱动开发与外设编程/README.md#-stm32-ll低层驱动)
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [STM32CubeMX](./03-驱动开发与外设编程/README.md#-stm32cubemx)
|
2025-06-16 22:24:38 +08:00
|
|
|
|
* [实战技巧](./03-驱动开发与外设编程/README.md#-实战技巧与常见问题)
|
2025-06-16 14:42:53 +08:00
|
|
|
|
* RTOS
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [RTOS 基础概念](./04-实时操作系统/README.md#-rtos-基础概念)
|
|
|
|
|
|
* [任务管理](./04-实时操作系统/README.md#-任务管理)
|
|
|
|
|
|
* [时间管理](./04-实时操作系统/README.md#-时间管理)
|
|
|
|
|
|
* [线程间通信](./04-实时操作系统/README.md#-线程间通信)
|
2025-06-16 17:26:01 +08:00
|
|
|
|
* [队列](./04-实时操作系统/README.md#队列queue)
|
|
|
|
|
|
* [信号量](./04-实时操作系统/README.md#信号量semaphore)
|
|
|
|
|
|
* [消息队列](./04-实时操作系统/README.md#消息队列Message-Queue)
|
|
|
|
|
|
* [事件组](./04-实时操作系统/README.md#事件组Event-Group)
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [资源管理](./04-实时操作系统/README.md#-资源管理)
|
|
|
|
|
|
* [FreeRTOS 配置与移植](./04-实时操作系统/README.md#-FreeRTOS-配置与移植)
|
|
|
|
|
|
* [实践应用场景](./04-实时操作系统/README.md#-实践应用场景)
|
2025-06-16 14:42:53 +08:00
|
|
|
|
* 嵌入式 Linux 开发基础
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [系统概览](./05-EmbeddedLinux/README.md#-嵌入式-linux-系统概览)
|
|
|
|
|
|
* [特点](./05-EmbeddedLinux/README.md#-嵌入式-linux-特点)
|
|
|
|
|
|
* [组成](./05-EmbeddedLinux/README.md#-系统组成)
|
|
|
|
|
|
* [启动流程详解](./05-EmbeddedLinux/README.md#-启动流程详解)
|
|
|
|
|
|
* [设备树](./05-EmbeddedLinux/README.md#-设备树device-tree)
|
2025-06-16 17:37:39 +08:00
|
|
|
|
* [常用 Linux 命令与开发工具](./05-EmbeddedLinux/README.md#常用-linux-命令与开发工具)
|
|
|
|
|
|
* [文件与目录管理](./05-EmbeddedLinux/README.md#文件与目录管理)
|
|
|
|
|
|
* [权限与用户管理](./05-EmbeddedLinux/README.md#权限与用户管理)
|
|
|
|
|
|
* [进程管理](./05-EmbeddedLinux/README.md#进程管理)
|
|
|
|
|
|
* [网络调试](./05-EmbeddedLinux/README.md#网络调试)
|
|
|
|
|
|
* [设备与文件系统](./05-EmbeddedLinux/README.md#设备与文件系统)
|
|
|
|
|
|
* [软件包管理](./05-EmbeddedLinux/README.md#软件包管理针对开发板所用-Linux)
|
|
|
|
|
|
* [Shell 脚本与自动化](./05-EmbeddedLinux/README.md#Shell-脚本与自动化)
|
|
|
|
|
|
* [交叉编译相关命令](./05-EmbeddedLinux/README.md#交叉编译相关命令Makefile-环境)
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [Linux 驱动开发模型](./05-EmbeddedLinux/README.md#-linux-驱动开发模型)
|
|
|
|
|
|
* [根文件系统构建](./05-EmbeddedLinux/README.md#-根文件系统构建)
|
|
|
|
|
|
* [工具链与调试手段](./05-EmbeddedLinux/README.md#-工具链与调试手段)
|
|
|
|
|
|
* [常见开发平台](./05-EmbeddedLinux/README.md#-常见开发平台)
|
2025-06-17 15:04:06 +08:00
|
|
|
|
* [嵌入式系统安全基础](./05-EmbeddedLinux/README.md#-嵌入式系统安全基础)
|
|
|
|
|
|
* [安全启动(Secure Boot)](./05-EmbeddedLinux/README.md#-安全启动secure-boot)
|
|
|
|
|
|
* [固件加密与防逆向](./05-EmbeddedLinux/README.md#-固件加密与防逆向)
|
|
|
|
|
|
* [权限隔离与防护](./05-EmbeddedLinux/README.md#-权限隔离与防护)
|
|
|
|
|
|
* [Bootloader 开发建议](./05-EmbeddedLinux/README.md#-bootloader-开发建议)
|
2025-06-16 14:42:53 +08:00
|
|
|
|
* 网络通信与物联网协议
|
2025-06-16 15:32:19 +08:00
|
|
|
|
* [串口通信与 Socket 通信](./06-NetworkIot/README.md#串口通信与Socket通信)
|
|
|
|
|
|
* [串口通信](./06-NetworkIot/README.md#串口通信)
|
|
|
|
|
|
* [Socket 网络通信](./06-NetworkIot/README.md#Socket网络通信)
|
|
|
|
|
|
* [无线通信协议](./06-NetworkIot/README.md#无线通信协议)
|
2025-06-16 15:37:39 +08:00
|
|
|
|
* [Wi-Fi](./06-NetworkIot/README.md#Wi-Fi)
|
|
|
|
|
|
* [BLE(蓝牙低功耗)](./06-NetworkIot#ble/README.md#蓝牙低功耗)
|
|
|
|
|
|
* [LoRa / ZigBee](.06-NetworkIot/README.md#lora--zigbee)
|
|
|
|
|
|
* [物联网协议栈](./06-NetworkIot/README.md#物联网协议栈)
|
|
|
|
|
|
* [MQTT](./06-NetworkIot/README.md#-mqtt)
|
2025-06-16 15:40:15 +08:00
|
|
|
|
* [HTTP/HTTPS](./06-NetworkIot/README.md#http--https)
|
2025-06-16 15:37:39 +08:00
|
|
|
|
* [CoAP/LwM2M](./06-NetworkIot/README.md#coap--lwm2m)
|
2025-06-17 15:04:06 +08:00
|
|
|
|
* [安全通信实践](/06-NetworkIot/README.md#-安全通信实践)
|
|
|
|
|
|
* [安全测试](/06-NetworkIot/README.md#-安全测试)
|
2025-06-16 17:54:52 +08:00
|
|
|
|
* [TCP/IP 协议栈基础与嵌入式实现](./06-NetworkIot/README.md#tcpip-协议栈基础与嵌入式实现)
|
|
|
|
|
|
* [TCP/IP 协议栈分层结构(四层模型)](./06-NetworkIot/README.md#tcpip-协议栈分层结构四层模型)
|
|
|
|
|
|
* [TCP 与 UDP 区别](./06-NetworkIot/README.md#tcp-与-udp-区别)
|
|
|
|
|
|
* [嵌入式 TCP/IP 协议栈组件](./06-NetworkIot/README.md#嵌入式-tcpip-协议栈组件)
|
|
|
|
|
|
* [嵌入式 TCP/IP 通信流程](./06-NetworkIot/README.md#嵌入式-tcpip-通信流程以-lwip-为例)
|
|
|
|
|
|
* [常用 API 示例](./06-NetworkIot/README.md#常用-api-示例lwip-bsd-socket)
|
|
|
|
|
|
* [DHCP / DNS / ICMP 说明](./06-NetworkIot/README.md#dhcp--dns--icmp-说明)
|
2025-06-16 15:37:39 +08:00
|
|
|
|
* [云平台接入 & OTA 实现](./06-NetworkIot/README.md#云平台接入--ota-实现)
|
|
|
|
|
|
* [云平台对接](./06-NetworkIot/README.md#云平台对接)
|
|
|
|
|
|
* [OTA 升级机制](./06-NetworkIot/README.md#OTA-升级机制)
|
2025-06-17 14:44:42 +08:00
|
|
|
|
* 调试与性能优化
|
|
|
|
|
|
* [常用调试工具](./07-Debug_Optimization#-常用调试工具)
|
|
|
|
|
|
* [JTAG / SWD 接口](/07-Debug_Optimization#-jtag--swd-接口)
|
|
|
|
|
|
* [GDB + OpenOCD 调试](./07-Debug_Optimization#-jtag--swd-接口)
|
|
|
|
|
|
* [逻辑分析仪 / 示波器](./07-Debug_Optimization#-jtag--swd-接口)
|
|
|
|
|
|
* [printf / 串口调试](./07-Debug_Optimization#-jtag--swd-接口)
|
|
|
|
|
|
* [断点调试](./07-Debug_Optimization#-jtag--swd-接口)
|
|
|
|
|
|
* [性能与功耗优化](./07-Debug_Optimization#-性能与功耗优化)
|
|
|
|
|
|
* [FreeRTOS Trace 与分析工具](./07-Debug_Optimization#-freertos-trace-与分析工具)
|
|
|
|
|
|
* [SystemView 分析工具](./07-Debug_Optimization#-systemview-分析工具)
|
|
|
|
|
|
* [STM32CubeMonitor](./07-Debug_Optimization#-stm32cubemonitor)
|
|
|
|
|
|
* [低功耗模式优化](./07-Debug_Optimization#-低功耗模式优化)
|
|
|
|
|
|
* [调试与优化实战案例](./07-Debug_Optimization#调试与优化实战案例)
|
2025-06-17 16:53:51 +08:00
|
|
|
|
* 项目实战与工具链
|
|
|
|
|
|
* [工程管理](./08-项目实战与工具链#-工程管理)
|
|
|
|
|
|
* [Git 版本控制](./08-项目实战与工具链#-git-版本控制)
|
|
|
|
|
|
* [Makefile、CMake 构建工具](./08-项目实战与工具链#-makefilecmake-构建工具)
|
|
|
|
|
|
* [Jenkins/GitHub Actions CI 流水线](./08-项目实战与工具链#-jenkinsgithub-actions-ci-流水线)
|
|
|
|
|
|
* [项目实践](/08-项目实战与工具链#-项目实践)
|
|
|
|
|
|
* [嵌入式应用框架设计](./08-项目实战与工具链#-嵌入式应用框架设计)
|
|
|
|
|
|
* [通用 BSP 构建](./08-项目实战与工具链#-通用-bsp-构建)
|
|
|
|
|
|
* [模块化驱动结构](./08-项目实战与工具链#-模块化驱动结构)
|
|
|
|
|
|
* [开发工具链](./08-项目实战与工具链#开发工具链安装指南)
|
|
|
|
|
|
* [VS Code + PlatformIO](./08-项目实战与工具链#vs-code--platformio)
|
|
|
|
|
|
* [STM32CubeIDE](./08-项目实战与工具链#stm32cubeide)
|
|
|
|
|
|
* [CLion](./08-项目实战与工具链#clion)
|
|
|
|
|
|
* [OpenOCD](./08-项目实战与工具链#openocd)
|
|
|
|
|
|
* [GDB](./08-项目实战与工具链#gdb)
|
|
|
|
|
|
* [ST-Link/V2](./08-项目实战与工具链#st-linkv2)
|
|
|
|
|
|
* [CppCheck](./08-项目实战与工具链#cppcheck)
|
|
|
|
|
|
* [Clang-Tidy](./08-项目实战与工具链#clang-tidy)
|
|
|
|
|
|
* [SonarQube](./08-项目实战与工具链#sonarqube)
|
|
|
|
|
|
* [Unity](./08-项目实战与工具链#unity)
|
|
|
|
|
|
* [CMock](./08-项目实战与工具链#cmock)
|
|
|
|
|
|
* [Google Test](./08-项目实战与工具链#google-test)
|
|
|
|
|
|
* 2025 嵌入式软件新趋势
|
|
|
|
|
|
* [AI on MCU / Edge AI](./09-2025_AI_on_MCU#-ai-on-mcu--edge-ai)
|
|
|
|
|
|
* [TinyML / TensorFlow Lite Micro](./09-2025_AI_on_MCU#-tinyml--tensorflow-lite-micro)
|
|
|
|
|
|
* [STM32 AI 开发套件](./09-2025_AI_on_MCU#-stm32-ai-开发套件)
|
|
|
|
|
|
* [模型量化与部署](./09-2025_AI_on_MCU#-模型量化与部署)
|
|
|
|
|
|
* [AI + 外设驱动融合案例](./09-2025_AI_on_MCU#-ai--外设驱动融合案例)
|
|
|
|
|
|
* [安全性](./09-2025_AI_on_MCU#-安全性)
|
|
|
|
|
|
* [TPM 安全芯片接入](./09-2025_AI_on_MCU#-tpm-安全芯片接入)
|
2025-06-17 15:04:06 +08:00
|
|
|
|
|
2025-06-17 17:22:43 +08:00
|
|
|
|
---
|
2025-06-17 17:23:41 +08:00
|
|
|
|
---
|
2025-06-17 17:22:43 +08:00
|
|
|
|
## 🔵 第一层:C/C++ 语言基础与进阶(必修)
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 变量 / 数据类型 / 关键字 / 常量
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 变量(Variable)
|
|
|
|
|
|
- 用于在程序中存储数据的具名内存区域。
|
|
|
|
|
|
- 声明格式:`类型 变量名 [= 初始值];`
|
|
|
|
|
|
```c
|
|
|
|
|
|
int count = 10;
|
|
|
|
|
|
float temperature;
|
|
|
|
|
|
char c = 'A';
|
|
|
|
|
|
```
|
|
|
|
|
|
- 局部变量:函数内声明,仅在函数内部可用。
|
|
|
|
|
|
- 全局变量:函数外声明,整个文件或项目中可见(根据作用域)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 数据类型(Data Types)
|
|
|
|
|
|
- **整型**:`int`, `short`, `long`, `long long`, `unsigned`
|
|
|
|
|
|
- **浮点型**:`float`, `double`
|
|
|
|
|
|
- **字符型**:`char`
|
|
|
|
|
|
- **派生类型**:指针、数组、结构体等
|
|
|
|
|
|
```c
|
|
|
|
|
|
unsigned int u = 100;
|
|
|
|
|
|
long long big_number = 12345678900LL;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 关键字(Keywords)
|
|
|
|
|
|
常用 C 关键字解释如下:
|
|
|
|
|
|
| 关键字 | 说明 |
|
|
|
|
|
|
|------------|------------------------------|
|
|
|
|
|
|
| `const` | 定义只读变量 |
|
|
|
|
|
|
| `volatile` | 防止编译器优化,常用于寄存器 |
|
|
|
|
|
|
| `static` | 变量作用域或函数仅在本文件可见 |
|
|
|
|
|
|
| `extern` | 声明外部变量/函数 |
|
|
|
|
|
|
| `typedef` | 为数据类型取别名 |
|
|
|
|
|
|
```c
|
|
|
|
|
|
static int counter = 0; // 内部链接
|
|
|
|
|
|
extern int g_value; // 声明外部变量
|
|
|
|
|
|
volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 常量(Constant)
|
|
|
|
|
|
- **字面常量**:如 `10`, `3.14`, `'a'`, `"abc"`
|
|
|
|
|
|
- **符号常量**:用 `#define` 或 `const` 定义
|
|
|
|
|
|
```c
|
|
|
|
|
|
#define PI 3.14159
|
|
|
|
|
|
const int MAX_SIZE = 100;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 栈 和 堆(内存管理)
|
|
|
|
|
|
#### 栈 (stack):自动分配内存,函数退出即释放。
|
|
|
|
|
|
1. 核心特性
|
|
|
|
|
|
- 自动分配与释放:由编译器自动管理,函数调用时分配栈帧,函数返回时自动释放。
|
|
|
|
|
|
- 后进先出(LIFO):类似一摞盘子,最后放入的最先取出。
|
|
|
|
|
|
- 高速访问:栈内存访问效率高(通常通过寄存器直接操作)。
|
|
|
|
|
|
- 空间有限:栈空间通常较小(如 Linux 默认 8MB),过大的局部变量可能导致栈溢出。
|
|
|
|
|
|
|
|
|
|
|
|
2. 存储内容
|
|
|
|
|
|
- 局部变量:函数内部定义的变量。
|
|
|
|
|
|
- 返回地址:函数执行完毕后返回的位置。
|
|
|
|
|
|
- 函数参数:调用函数时传递的参数。
|
|
|
|
|
|
- 寄存器值:保存调用前的寄存器状态,以便恢复。
|
|
|
|
|
|
|
|
|
|
|
|
3. 工作原理
|
|
|
|
|
|
- 栈指针(ESP):指向当前栈顶的内存地址。
|
|
|
|
|
|
- 栈帧(Stack Frame):每个函数调用在栈上分配的独立空间,包含局部变量和参数。
|
|
|
|
|
|
- 示例代码:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void func(int a, int b) {
|
|
|
|
|
|
int sum = a + b; // sum存储在栈上
|
|
|
|
|
|
// ...
|
|
|
|
|
|
} // 函数返回时,sum和参数a、b自动释放
|
|
|
|
|
|
```
|
|
|
|
|
|
4. 优缺点
|
|
|
|
|
|
- 优点:无需手动管理内存,速度快,不会内存泄漏。
|
|
|
|
|
|
- 缺点:生命周期固定(函数结束即释放),空间有限。
|
|
|
|
|
|
|
|
|
|
|
|
#### 堆 (heap):使用 `malloc` / `free` 手动分配和释放
|
|
|
|
|
|
1. 核心特性
|
|
|
|
|
|
|
|
|
|
|
|
- 手动分配与释放:使用malloc/calloc/realloc分配,free释放。
|
|
|
|
|
|
- 动态生命周期:内存块的生命周期由程序员控制,可跨函数使用。
|
|
|
|
|
|
- 碎片化问题:频繁分配和释放可能导致内存碎片,降低空间利用率。
|
|
|
|
|
|
- 慢速访问:需通过指针间接访问,效率低于栈。
|
|
|
|
|
|
|
|
|
|
|
|
2. 存储内容
|
|
|
|
|
|
- 动态分配的对象:如malloc返回的内存块。
|
|
|
|
|
|
- 大型数据结构:如数组、链表、树等需要动态调整大小的结构。
|
|
|
|
|
|
- 跨函数数据:需要在函数调用结束后继续存在的数据。
|
|
|
|
|
|
|
|
|
|
|
|
3. 工作原理
|
|
|
|
|
|
- 内存管理器:操作系统提供的堆管理器负责分配和回收内存。
|
|
|
|
|
|
- 空闲链表:堆管理器维护空闲内存块列表,分配时查找合适大小的块。
|
|
|
|
|
|
- 示例代码:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void createArray() {
|
|
|
|
|
|
int* arr = (int*)malloc(10 * sizeof(int)); // 从堆分配内存
|
|
|
|
|
|
if (arr != NULL) {
|
|
|
|
|
|
arr[0] = 100; // 使用堆内存
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
|
|
|
|
|
free(arr); // 手动释放内存
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
4. 常见函数
|
|
|
|
|
|
- malloc(size_t size):分配指定字节的内存,不初始化。
|
|
|
|
|
|
- calloc(size_t num, size_t size):分配内存并初始化为 0。
|
|
|
|
|
|
- realloc(void* ptr, size_t new_size):调整已分配内存的大小。
|
|
|
|
|
|
- free(void* ptr):释放内存,必须与malloc配对使用。
|
|
|
|
|
|
|
|
|
|
|
|
**栈 vs 堆的对比**
|
|
|
|
|
|
| 特性 | 栈(Stack) | 堆(Heap) |
|
|
|
|
|
|
|--------------|--------------------------------------|---------------------------------------------|
|
|
|
|
|
|
| 分配方式 | 自动(由编译器管理) | 手动(如 `malloc`/`free` 或 `new`/`delete`)|
|
|
|
|
|
|
| 生命周期 | 函数调用期间自动创建和销毁 | 程序员控制,需手动释放 |
|
|
|
|
|
|
| 内存空间 | 连续、有限(通常几 MB) | 不连续、较大(受限于物理内存) |
|
|
|
|
|
|
| 访问速度 | 快(通过寄存器快速访问) | 慢(通过指针间接访问) |
|
|
|
|
|
|
| 内存碎片 | 不存在(先进后出结构) | 可能产生(频繁分配和释放) |
|
|
|
|
|
|
| 使用场景 | 局部变量、函数调用栈帧 | 动态数据结构、跨函数共享数据 |
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 指针 / 指针数组 / 二级指针
|
|
|
|
|
|
#### 指针:保存变量的地址
|
|
|
|
|
|
1. 核心概念
|
|
|
|
|
|
|
|
|
|
|
|
- 指针是变量:存储的是内存地址,而非实际数据。
|
|
|
|
|
|
- 类型关联:指针类型必须与所指向变量的类型匹配(如int*指向int)。
|
|
|
|
|
|
- 间接访问:通过解引用操作符*访问或修改指向的内存。
|
|
|
|
|
|
2. 声明与初始化
|
|
|
|
|
|
```c
|
|
|
|
|
|
int num = 10; // 普通变量
|
|
|
|
|
|
int* ptr = # // 指针ptr指向num的地址
|
|
|
|
|
|
|
|
|
|
|
|
// 等价于两步:
|
|
|
|
|
|
int* ptr; // 声明指针
|
|
|
|
|
|
ptr = # // 初始化指针
|
|
|
|
|
|
```
|
|
|
|
|
|
3. 解引用操作
|
|
|
|
|
|
```c
|
|
|
|
|
|
printf("%d", *ptr); // 输出:10(访问指针指向的值)
|
|
|
|
|
|
*ptr = 20; // 修改指针指向的内存,num变为20
|
|
|
|
|
|
```
|
|
|
|
|
|
4. 指针运算
|
|
|
|
|
|
```c
|
|
|
|
|
|
int arr[3] = {1, 2, 3};
|
|
|
|
|
|
int* p = arr; // p指向arr[0]
|
|
|
|
|
|
printf("%d", *p); // 输出:1
|
|
|
|
|
|
p++; // 指针后移,指向arr[1]
|
|
|
|
|
|
printf("%d", *p); // 输出:2
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 指针数组:数组中每个元素是一个指针
|
|
|
|
|
|
1. 核心概念
|
|
|
|
|
|
|
|
|
|
|
|
- 数组本质:连续存储多个指针变量。
|
|
|
|
|
|
- 用途:常用于处理多个字符串或动态分配的内存块。
|
|
|
|
|
|
2. 声明与初始化
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 存储3个int指针的数组
|
|
|
|
|
|
int a = 10, b = 20, c = 30;
|
|
|
|
|
|
int* ptrArray[3] = {&a, &b, &c};
|
|
|
|
|
|
|
|
|
|
|
|
// 存储字符串常量的指针数组(每个元素是char*)
|
|
|
|
|
|
char* fruits[3] = {
|
|
|
|
|
|
"Apple",
|
|
|
|
|
|
"Banana",
|
|
|
|
|
|
"Cherry"
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
3. 访问与应用
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 访问指针数组中的值
|
|
|
|
|
|
printf("%d", *ptrArray[0]); // 输出:10(a的值)
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历字符串数组
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
|
printf("%s\n", fruits[i]); // 输出每个字符串
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
4. 典型场景
|
|
|
|
|
|
- 字符串处理:避免字符串拷贝,直接操作地址。
|
|
|
|
|
|
- 动态内存管理:存储多个动态分配的内存块(如malloc返回的指针)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 二级指针:指向指针的指针
|
|
|
|
|
|
1. 核心概念
|
|
|
|
|
|
|
|
|
|
|
|
- 双重间接性:二级指针存储的是一级指针的地址。
|
|
|
|
|
|
- 用途:常用于函数参数传递指针的地址,以便修改原始指针。
|
|
|
|
|
|
2. 声明与初始化
|
|
|
|
|
|
```c
|
|
|
|
|
|
int num = 100;
|
|
|
|
|
|
int* ptr = # // 一级指针指向num
|
|
|
|
|
|
int** ptr2 = &ptr; // 二级指针指向ptr
|
|
|
|
|
|
|
|
|
|
|
|
// 等价于:
|
|
|
|
|
|
int** ptr2;
|
|
|
|
|
|
ptr2 = &ptr;
|
|
|
|
|
|
```
|
|
|
|
|
|
3. 解引用操作
|
|
|
|
|
|
```c
|
|
|
|
|
|
printf("%d", **ptr2); // 输出:100(两次解引用)
|
|
|
|
|
|
**ptr2 = 200; // 修改num的值为200
|
|
|
|
|
|
```
|
|
|
|
|
|
4. 典型应用场景
|
|
|
|
|
|
|
|
|
|
|
|
(1)函数中修改指针
|
|
|
|
|
|
```c
|
|
|
|
|
|
void allocateMemory(int** p) {
|
|
|
|
|
|
*p = (int*)malloc(sizeof(int)); // 修改传入的指针
|
|
|
|
|
|
**p = 100; // 修改指针指向的值
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用
|
|
|
|
|
|
int* ptr = NULL;
|
|
|
|
|
|
allocateMemory(&ptr); // 传递指针的地址
|
|
|
|
|
|
printf("%d", *ptr); // 输出:100
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
(2)二维数组动态分配
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建3x3的二维数组
|
|
|
|
|
|
int** matrix = (int**)malloc(3 * sizeof(int*));
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
|
matrix[i] = (int*)malloc(3 * sizeof(int));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 访问元素
|
|
|
|
|
|
matrix[0][1] = 5; // 等价于*(*(matrix+0)+1) = 5
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 函数指针 / 函数指针数组
|
|
|
|
|
|
- **函数指针声明**:`int (*fp)(int)` 表示指向返回 `int` 的函数的指针
|
|
|
|
|
|
- **函数指针数组**:用于策略模式或注册多个处理函数
|
|
|
|
|
|
```c
|
|
|
|
|
|
int add(int a, int b) { return a + b; }
|
|
|
|
|
|
int sub(int a, int b) { return a - b; }
|
|
|
|
|
|
|
|
|
|
|
|
int (*ops[2])(int, int) = { add, sub };
|
|
|
|
|
|
printf("%d\n", ops[0](5, 3)); // 输出 8
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 表达式、语句、运算符
|
|
|
|
|
|
- 表达式:`a + b`, `x++`, `p[i]`
|
|
|
|
|
|
- 运算符:`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `!`, `==`, `!=`
|
|
|
|
|
|
- 语句:控制流程结构 `if`, `for`, `while`, `switch`
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 数组 / 字符串
|
|
|
|
|
|
#### 数组(Array)
|
|
|
|
|
|
- 数组是一组相同类型数据的有序集合,在内存中连续存储。
|
|
|
|
|
|
- 一维数组定义:`类型 数组名[大小];`
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
int arr[5] = {1, 2, 3, 4, 5};
|
|
|
|
|
|
char str[10] = "Hello";
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- 数组索引从 0 开始,访问方式如:arr[2] 表示第三个元素。
|
|
|
|
|
|
- 多维数组:int matrix[3][4]; 表示 3 行 4 列矩阵。
|
|
|
|
|
|
#### 字符串(String)
|
|
|
|
|
|
- 字符串是以空字符 '\0' 结尾的字符数组。
|
|
|
|
|
|
- 定义方式:
|
|
|
|
|
|
```c
|
|
|
|
|
|
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 后面
|
|
|
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
|
|
char msg[20];
|
|
|
|
|
|
strcpy(msg, "Hi"); // msg: "Hi"
|
|
|
|
|
|
strcat(msg, " there"); // msg: "Hi there"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 结构体 / 共用体 / 枚举 / 位域
|
|
|
|
|
|
#### 结构体
|
|
|
|
|
|
**定义:**
|
|
|
|
|
|
|
|
|
|
|
|
结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
|
|
|
|
|
|
**使用场景:**
|
|
|
|
|
|
- 表示传感器、外设状态、网络包头等复杂数据
|
|
|
|
|
|
|
|
|
|
|
|
- 多变量统一传参,提升代码组织性
|
|
|
|
|
|
|
|
|
|
|
|
语法:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
struct StructName {
|
|
|
|
|
|
int id;
|
|
|
|
|
|
float value;
|
|
|
|
|
|
char name[20];
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
示例:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 共用体
|
|
|
|
|
|
**定义:**
|
|
|
|
|
|
|
|
|
|
|
|
共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
|
|
|
|
|
|
**使用场景:**
|
|
|
|
|
|
- 节省内存:如嵌入式协议帧解析
|
|
|
|
|
|
|
|
|
|
|
|
- 多种数据格式的重解释
|
|
|
|
|
|
|
|
|
|
|
|
语法:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
union UnionName {
|
|
|
|
|
|
int i;
|
|
|
|
|
|
float f;
|
|
|
|
|
|
char c;
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
示例:
|
|
|
|
|
|
```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
|
|
|
|
|
|
```
|
|
|
|
|
|
注意事项:
|
|
|
|
|
|
- 占用内存大小为最大成员的大小
|
|
|
|
|
|
|
|
|
|
|
|
- 修改一个成员后,其他成员的值不可预测
|
|
|
|
|
|
|
|
|
|
|
|
#### 枚举(Enumeration)
|
|
|
|
|
|
**定义:**
|
|
|
|
|
|
|
|
|
|
|
|
枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。
|
|
|
|
|
|
|
|
|
|
|
|
**使用场景:**
|
|
|
|
|
|
- 定义状态机状态
|
|
|
|
|
|
|
|
|
|
|
|
- 表示设备运行模式、错误码
|
|
|
|
|
|
|
|
|
|
|
|
语法:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
enum Color { RED, GREEN, BLUE };
|
|
|
|
|
|
enum Color color = GREEN;
|
|
|
|
|
|
```
|
|
|
|
|
|
示例:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
enum State {
|
|
|
|
|
|
STATE_IDLE,
|
|
|
|
|
|
STATE_ACTIVE,
|
|
|
|
|
|
STATE_ERROR = 100, // 可手动赋值
|
|
|
|
|
|
STATE_SLEEP
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
enum State current = STATE_ACTIVE;
|
|
|
|
|
|
printf("State: %d\n", current); // 输出 1
|
|
|
|
|
|
```
|
|
|
|
|
|
特性:
|
|
|
|
|
|
- 默认从 0 开始递增
|
|
|
|
|
|
|
|
|
|
|
|
- 可强制设定起始值
|
|
|
|
|
|
|
|
|
|
|
|
#### 位域(Bit Field)
|
|
|
|
|
|
**定义:**
|
|
|
|
|
|
|
|
|
|
|
|
位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。
|
|
|
|
|
|
|
|
|
|
|
|
**使用场景:**
|
|
|
|
|
|
- 配置寄存器映射
|
|
|
|
|
|
|
|
|
|
|
|
- 网络协议比特位字段解析
|
|
|
|
|
|
|
|
|
|
|
|
- 内存空间紧张场景
|
|
|
|
|
|
|
|
|
|
|
|
语法:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
struct Flags {
|
|
|
|
|
|
unsigned int ready : 1;
|
|
|
|
|
|
unsigned int error : 1;
|
|
|
|
|
|
unsigned int mode : 2;
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
示例:
|
|
|
|
|
|
```c
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
- 与具体编译器实现密切相关(跨平台需小心)
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 位操作
|
|
|
|
|
|
- 嵌入式开发中用于设置寄存器位、控制硬件
|
|
|
|
|
|
```c
|
|
|
|
|
|
#define LED_PIN (1 << 2)
|
|
|
|
|
|
PORT |= LED_PIN; // 置位
|
|
|
|
|
|
PORT &= ~LED_PIN; // 清零
|
|
|
|
|
|
PORT ^= LED_PIN; // 翻转
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 关键语义 & 修饰符
|
|
|
|
|
|
#### `const`(只读限定符)
|
|
|
|
|
|
```c
|
|
|
|
|
|
const int a = 10;
|
|
|
|
|
|
void print(const char* msg); // msg 不可修改
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `volatile`(防止优化)
|
|
|
|
|
|
```c
|
|
|
|
|
|
volatile int *reg = (int *)0x40021000; // 硬件寄存器访问
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `static`(静态变量/内部链接)
|
|
|
|
|
|
```c
|
|
|
|
|
|
static int count = 0; // 静态变量,函数调用间保留值
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `extern`(外部变量声明)
|
|
|
|
|
|
```c
|
|
|
|
|
|
extern int global_var;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `register`(提示变量存放寄存器)
|
|
|
|
|
|
```c
|
|
|
|
|
|
register int speed;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### `auto`(默认局部变量)
|
|
|
|
|
|
```c
|
|
|
|
|
|
auto int a = 10; // 一般可省略 auto
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 内存存储类型与生命周期
|
|
|
|
|
|
|
|
|
|
|
|
| 存储类型 | 生命周期 | 作用域 | 关键字 |
|
|
|
|
|
|
|------------|-----------------|-------------------|--------------|
|
|
|
|
|
|
| 栈(stack) | 函数调用期间 | 局部变量 | auto |
|
|
|
|
|
|
| 静态区 | 程序全程 | 局部/全局 | static |
|
|
|
|
|
|
| 堆(heap) | 手动管理 | 全局 | malloc/free |
|
|
|
|
|
|
| 寄存器 | 函数调用期间 | 局部 | register |
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 编译与调试基础
|
|
|
|
|
|
|
|
|
|
|
|
#### C 编译四阶段(以 GCC 为例)
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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 # 链接生成可执行文件
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### Makefile 示例
|
|
|
|
|
|
```makefile
|
|
|
|
|
|
CC = gcc
|
|
|
|
|
|
TARGET = app
|
|
|
|
|
|
OBJS = main.o utils.o
|
|
|
|
|
|
|
|
|
|
|
|
$(TARGET): $(OBJS)
|
|
|
|
|
|
$(CC) -o $@ $^
|
|
|
|
|
|
|
|
|
|
|
|
%.o: %.c
|
|
|
|
|
|
$(CC) -c $< -o $@
|
|
|
|
|
|
|
|
|
|
|
|
clean:
|
|
|
|
|
|
rm -f *.o $(TARGET)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### GCC 编译参数
|
|
|
|
|
|
| 参数 | 含义 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `-Wall` | 开启所有警告 |
|
|
|
|
|
|
| `-g` | 含调试信息 |
|
|
|
|
|
|
| `-O2` | 优化等级 |
|
|
|
|
|
|
| `-I` | 头文件路径 |
|
|
|
|
|
|
| `-L`/`-l` | 库路径和链接 |
|
|
|
|
|
|
| `-D` | 宏定义 |
|
|
|
|
|
|
|
|
|
|
|
|
#### GDB 基础调试
|
|
|
|
|
|
```bash
|
|
|
|
|
|
gdb ./main
|
|
|
|
|
|
(gdb) break main
|
|
|
|
|
|
(gdb) run
|
|
|
|
|
|
(gdb) next / step
|
|
|
|
|
|
(gdb) print var
|
|
|
|
|
|
(gdb) continue
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 内联汇编
|
|
|
|
|
|
```c
|
|
|
|
|
|
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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 排序算法
|
|
|
|
|
|
### 冒泡排序(Bubble Sort)
|
|
|
|
|
|
|
|
|
|
|
|
#### 原理:
|
|
|
|
|
|
|
|
|
|
|
|
相邻元素两两比较,把最大的“冒”到最后。
|
|
|
|
|
|
|
|
|
|
|
|
#### 时间复杂度:
|
|
|
|
|
|
|
|
|
|
|
|
* 最坏/平均:O(n²)
|
|
|
|
|
|
* 最好:O(n)(加优化判断)
|
|
|
|
|
|
|
|
|
|
|
|
#### 适用场景:
|
|
|
|
|
|
|
|
|
|
|
|
数据量小、逻辑简单、嵌入式环境友好
|
|
|
|
|
|
|
|
|
|
|
|
#### 示例代码:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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; // 优化:已排序
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 选择排序(Selection Sort)
|
|
|
|
|
|
|
|
|
|
|
|
#### 原理:
|
|
|
|
|
|
|
|
|
|
|
|
每轮从未排序区间中选择最小值放到前面。
|
|
|
|
|
|
|
|
|
|
|
|
#### 时间复杂度:
|
|
|
|
|
|
|
|
|
|
|
|
* 所有情况:O(n²)
|
|
|
|
|
|
|
|
|
|
|
|
#### 适用场景:
|
|
|
|
|
|
|
|
|
|
|
|
嵌入式设备中内存访问代价高,交换少
|
|
|
|
|
|
|
|
|
|
|
|
#### 示例代码:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 插入排序(Insertion Sort)
|
|
|
|
|
|
|
|
|
|
|
|
#### 原理:
|
|
|
|
|
|
|
|
|
|
|
|
每次将一个元素插入到已排序部分的合适位置。
|
|
|
|
|
|
|
|
|
|
|
|
#### 时间复杂度:
|
|
|
|
|
|
|
|
|
|
|
|
* 最坏/平均:O(n²)
|
|
|
|
|
|
* 最好:O(n)
|
|
|
|
|
|
|
|
|
|
|
|
#### 适用场景:
|
|
|
|
|
|
|
|
|
|
|
|
数据量小、数据基本有序时表现好
|
|
|
|
|
|
|
|
|
|
|
|
#### 示例代码:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 快速排序(Quick Sort)
|
|
|
|
|
|
|
|
|
|
|
|
#### 原理:
|
|
|
|
|
|
|
|
|
|
|
|
分治法。选定基准,左边小于它,右边大于它。
|
|
|
|
|
|
|
|
|
|
|
|
#### 时间复杂度:
|
|
|
|
|
|
|
|
|
|
|
|
* 最坏:O(n²)
|
|
|
|
|
|
* 平均:O(n log n)
|
|
|
|
|
|
|
|
|
|
|
|
#### 适用场景:
|
|
|
|
|
|
|
|
|
|
|
|
高性能需求、数据量较大(慎用递归栈)
|
|
|
|
|
|
|
|
|
|
|
|
#### 示例代码:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 归并排序(Merge Sort)
|
|
|
|
|
|
|
|
|
|
|
|
#### 原理:
|
|
|
|
|
|
|
|
|
|
|
|
分治法。将数组分成两半排序后合并。
|
|
|
|
|
|
|
|
|
|
|
|
#### 时间复杂度:
|
|
|
|
|
|
|
|
|
|
|
|
* 所有情况:O(n log n)
|
|
|
|
|
|
|
|
|
|
|
|
#### 适用场景:
|
|
|
|
|
|
|
|
|
|
|
|
追求稳定排序、高精度处理、实时传感器数据等(需额外内存)
|
|
|
|
|
|
|
|
|
|
|
|
#### 示例代码:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 堆排序(Heap Sort)
|
|
|
|
|
|
|
|
|
|
|
|
#### 原理:
|
|
|
|
|
|
|
|
|
|
|
|
利用堆结构(大根堆),反复取出最大元素构造有序序列。
|
|
|
|
|
|
|
|
|
|
|
|
#### 时间复杂度:
|
|
|
|
|
|
|
|
|
|
|
|
* 所有情况:O(n log n)
|
|
|
|
|
|
|
|
|
|
|
|
#### 适用场景:
|
|
|
|
|
|
|
|
|
|
|
|
嵌入式中对最值处理(最大温度等)、优先级调度
|
|
|
|
|
|
|
|
|
|
|
|
#### 示例代码:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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) | ❌ | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
# 第二层:嵌入式系统基础知识
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 嵌入式系统概览
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 嵌入式系统定义与特点
|
|
|
|
|
|
|
|
|
|
|
|
**定义**:
|
|
|
|
|
|
- 专用性:针对特定任务优化,如汽车 ABS 防抱死系统仅负责刹车控制。
|
|
|
|
|
|
- 嵌入性:隐藏于设备内部,用户通常意识不到其存在(如微波炉中的控制系统)。
|
|
|
|
|
|
- 计算系统:包含硬件(处理器、传感器)和软件(固件、驱动)。
|
|
|
|
|
|
|
|
|
|
|
|
**特点**:
|
|
|
|
|
|
- 资源受限(低功耗、内存小)
|
|
|
|
|
|
- 实时性要求高
|
|
|
|
|
|
- 高可靠性与稳定性
|
|
|
|
|
|
- 通常运行裸机或 RTOS
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 系统构成(MCU、存储器、传感器、外设)
|
|
|
|
|
|
|
|
|
|
|
|
| 模块 | 功能说明 |
|
|
|
|
|
|
|------------|--------------------------------------|
|
|
|
|
|
|
| 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 | |
|
|
|
|
|
|
| +----------------------+ |
|
|
|
|
|
|
+-----------|--------------+
|
|
|
|
|
|
|
|
|
|
|
|
|
+------+------+
|
|
|
|
|
|
| 外设 / 传感器 |
|
|
|
|
|
|
+-------------+
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
嵌入式系统的硬件构成是一个有机整体,各模块协同工作实现特定功能。以下从核心组件到通信架构进行深度解析:
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
### MCU(微控制器):嵌入式系统的心脏
|
|
|
|
|
|
#### 1. **核心功能**
|
|
|
|
|
|
- **运算与控制**:执行程序指令,处理传感器数据,控制外设。
|
|
|
|
|
|
- **典型架构**:
|
|
|
|
|
|
- **ARM Cortex-M**:主流低功耗MCU(如STM32、Nordic nRF系列)。
|
|
|
|
|
|
- **RISC-V**:开源架构,灵活定制(如SiFive、平头哥玄铁系列)。
|
|
|
|
|
|
- **8051/AVR**:传统8位MCU,低成本(如Arduino Uno基于ATmega328P)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **关键参数**
|
|
|
|
|
|
- **主频**:决定运算速度(如STM32F103主频72MHz,STM32H7主频480MHz)。
|
|
|
|
|
|
- **内核位数**:8位(适合简单控制)、32位(主流)、64位(高性能应用)。
|
|
|
|
|
|
- **片上外设**:集成ADC、DAC、PWM等功能模块,减少外部芯片依赖。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 存储器:程序与数据的载体
|
|
|
|
|
|
#### 1. **Flash存储器**
|
|
|
|
|
|
- **功能**:存储程序代码(固件),掉电不丢失。
|
|
|
|
|
|
- **分类**:
|
|
|
|
|
|
- **NOR Flash**:读取速度快,支持XIP(就地执行),适合代码存储。
|
|
|
|
|
|
- **NAND Flash**:容量大、成本低,适合存储大量数据(如SSD、SD卡)。
|
|
|
|
|
|
- **典型应用**:
|
|
|
|
|
|
- MCU内置Flash(如STM32F407含1MB Flash)。
|
|
|
|
|
|
- 外部SPI Flash(如W25Q系列,用于存储文件系统或配置参数)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **SRAM(静态随机存取存储器)**
|
|
|
|
|
|
- **功能**:运行时数据存储(如变量、堆栈)。
|
|
|
|
|
|
- **特点**:速度快(纳秒级访问),但成本高、容量小。
|
|
|
|
|
|
- **容量配置**:
|
|
|
|
|
|
- 小型MCU:几KB~几十KB(如Arduino Uno含2KB SRAM)。
|
|
|
|
|
|
- 高性能MCU:数百KB~MB级(如STM32H7含1MB SRAM)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **其他存储类型**
|
|
|
|
|
|
- **EEPROM**:可擦写可编程只读存储器,适合存储少量关键参数(如设备ID)。
|
|
|
|
|
|
- **FRAM**:铁电随机存储器,读写速度快、寿命长(10^12次擦写),用于数据记录。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 外设接口:与外部世界的桥梁
|
|
|
|
|
|
#### 1. **GPIO(通用输入输出)**
|
|
|
|
|
|
- **功能**:数字信号输入/输出(如控制LED、读取按键状态)。
|
|
|
|
|
|
- **特性**:
|
|
|
|
|
|
- 可配置上拉/下拉电阻。
|
|
|
|
|
|
- 支持中断触发(如外部按键按下时唤醒MCU)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **通信接口**
|
|
|
|
|
|
| **接口** | **特点** | **典型应用** |
|
|
|
|
|
|
|----------|--------------------------|----------------------------------|
|
|
|
|
|
|
| **UART** | 全双工,异步,2线(TX/RX) | 调试信息输出、与模块通信(如GPS) |
|
|
|
|
|
|
| **SPI** | 全双工,同步,4线(SCK/MOSI/MISO/CS) | 高速数据传输(如OLED屏幕) |
|
|
|
|
|
|
| **I2C** | 半双工,同步,2线(SCL/SDA) | 多设备通信(如连接多个传感器) |
|
|
|
|
|
|
| **CAN** | 差分信号,抗干扰强,多主模式 | 汽车电子(如ECU间通信) |
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **模拟接口**
|
|
|
|
|
|
- **ADC(模拟-to-数字转换器)**:
|
|
|
|
|
|
- 将模拟信号(如电压、电流)转换为数字值。
|
|
|
|
|
|
- 精度通常为10~16位(如STM32的12位ADC,分辨率4096级)。
|
|
|
|
|
|
- **DAC(数字-to-模拟转换器)**:
|
|
|
|
|
|
- 将数字值转换为模拟电压输出(如音频信号生成)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 4. **定时控制**
|
|
|
|
|
|
- **PWM(脉冲宽度调制)**:
|
|
|
|
|
|
- 通过占空比控制输出电压平均值,用于电机调速、LED调光。
|
|
|
|
|
|
- 频率范围:几Hz~MHz(如舵机控制需50Hz PWM)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 传感器:感知物理世界的窗口
|
|
|
|
|
|
#### 1. **常见类型**
|
|
|
|
|
|
- **环境传感器**:
|
|
|
|
|
|
- **温湿度**:DHT22、SHT30(精度±0.3°C)。
|
|
|
|
|
|
- **光照**:BH1750(测量范围1~65535 lux)。
|
|
|
|
|
|
- **气压**:BMP280(海拔测量误差±1m)。
|
|
|
|
|
|
- **运动传感器**:
|
|
|
|
|
|
- **加速度计**:ADXL345(检测倾斜、振动)。
|
|
|
|
|
|
- **陀螺仪**:MPU6050(测量角速度,用于姿态解算)。
|
|
|
|
|
|
- **其他**:
|
|
|
|
|
|
- **气体传感器**:MQ-2(检测烟雾、液化气)。
|
|
|
|
|
|
- **红外传感器**:HC-SR501(人体感应)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **接口方式**
|
|
|
|
|
|
- **数字接口**:I2C(如SHT30)、SPI(如ADXL345)。
|
|
|
|
|
|
- **模拟接口**:输出电压值,需通过MCU的ADC转换(如模拟光照传感器)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 通信模块:连接万物的纽带
|
|
|
|
|
|
#### 1. **短距离通信**
|
|
|
|
|
|
- **WiFi**:
|
|
|
|
|
|
- 标准:802.11b/g/n(如ESP8266、ESP32)。
|
|
|
|
|
|
- 应用:智能家居(如智能插座)、数据上传至云端。
|
|
|
|
|
|
- **BLE(蓝牙低功耗)**:
|
|
|
|
|
|
- 传输距离:10~100m,功耗极低(如Nordic nRF52系列)。
|
|
|
|
|
|
- 应用:可穿戴设备(如心率带)、Beacon定位。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **中长距离通信**
|
|
|
|
|
|
- **LoRa**:
|
|
|
|
|
|
- 扩频技术,传输距离5~15km,低功耗(如Semtech SX1278)。
|
|
|
|
|
|
- 应用:物联网广域覆盖(如智能抄表)。
|
|
|
|
|
|
- **NB-IoT**:
|
|
|
|
|
|
- 蜂窝网络,覆盖全国,功耗极低(如Quectel BC660K)。
|
|
|
|
|
|
- 应用:远程监控(如井盖状态监测)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **工业总线**
|
|
|
|
|
|
- **CAN总线**:
|
|
|
|
|
|
- 传输速率:500kbps~1Mbps,抗干扰强。
|
|
|
|
|
|
- 应用:汽车电子(如车身控制模块)、工业自动化。
|
|
|
|
|
|
- **Modbus**:
|
|
|
|
|
|
- 主从协议,支持RS-232/RS-485,广泛用于工业设备通信。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 电源管理:续航与稳定性的保障
|
|
|
|
|
|
#### 1. **电源转换**
|
|
|
|
|
|
- **LDO(低压差线性稳压器)**:
|
|
|
|
|
|
- 优点:电路简单,输出纹波小(如AMS1117-3.3)。
|
|
|
|
|
|
- 缺点:效率低(输入输出压差越大,效率越低)。
|
|
|
|
|
|
- **DC-DC转换器**:
|
|
|
|
|
|
- 升压/降压,效率高(可达90%以上,如TPS62160)。
|
|
|
|
|
|
- 适合电池供电设备(如充电宝)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **低功耗设计**
|
|
|
|
|
|
- **休眠模式**:
|
|
|
|
|
|
- MCU进入休眠,关闭非必要外设,仅保留唤醒源(如RTC时钟)。
|
|
|
|
|
|
- 典型功耗:STM32L4系列休眠电流低至0.5μA。
|
|
|
|
|
|
- **动态电压调整**:
|
|
|
|
|
|
- 根据工作负载动态降低MCU电压(如ARM Cortex-M4的FlexPowerControl)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
### 系统集成与典型架构
|
|
|
|
|
|
#### 1. **最小系统**
|
|
|
|
|
|
- MCU + 晶振 + 复位电路 + 电源。
|
|
|
|
|
|
- 示例:STM32最小系统板(核心板)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **扩展架构**
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────┐
|
|
|
|
|
|
│ 应用层 │
|
|
|
|
|
|
│ (用户逻辑:如温湿度采集、数据处理) │
|
|
|
|
|
|
├─────────────────────────────────────────┤
|
|
|
|
|
|
│ 驱动层 │
|
|
|
|
|
|
│ (传感器驱动、通信协议栈、外设控制) │
|
|
|
|
|
|
├─────────────────────────────────────────┤
|
|
|
|
|
|
│ 硬件层 │
|
|
|
|
|
|
│ MCU ── 存储器 ── 外设 ── 传感器 ── 通信 │
|
|
|
|
|
|
└─────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **低功耗设计案例**
|
|
|
|
|
|
- **智能手环**:
|
|
|
|
|
|
- 平时MCU处于休眠,加速度计检测运动状态。
|
|
|
|
|
|
- 定时唤醒GPS模块采集位置数据,通过BLE上传手机。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 开发与调试工具
|
|
|
|
|
|
#### 1. **硬件工具**
|
|
|
|
|
|
- **开发板**:STM32 Nucleo、Arduino、ESP32 DevKit。
|
|
|
|
|
|
- **调试器**:ST-Link、J-Link(用于程序下载和调试)。
|
|
|
|
|
|
- **逻辑分析仪**:Saleae Logic(分析通信协议波形)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **软件工具**
|
|
|
|
|
|
- **IDE**:Keil MDK、STM32CubeIDE、Arduino IDE。
|
|
|
|
|
|
- **驱动配置**:STM32CubeMX(自动生成初始化代码)。
|
|
|
|
|
|
- **调试工具**:OpenOCD(开源调试协议)、GDB(调试器)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
### 面试高频问题
|
|
|
|
|
|
1. **SPI与I2C的区别**:
|
|
|
|
|
|
- SPI:高速(可达数十Mbps),4线,点对点;I2C:低速(标准100kbps),2线,支持多设备。
|
|
|
|
|
|
|
|
|
|
|
|
2. **如何选择合适的通信协议**:
|
|
|
|
|
|
- 短距离高速:SPI;多设备低速:I2C;长距离抗干扰:CAN;广域低功耗:LoRa/NB-IoT。
|
|
|
|
|
|
|
|
|
|
|
|
3. **低功耗设计的关键策略**:
|
|
|
|
|
|
- 休眠模式、动态电压调整、关闭非必要外设、使用低功耗通信协议(如BLE)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 架构与启动流程
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 Cortex-M 内核结构
|
|
|
|
|
|
|
|
|
|
|
|
- 32 位精简指令集(Thumb 指令集)
|
|
|
|
|
|
- 内建 NVIC(中断控制器)
|
|
|
|
|
|
- 支持两种堆栈:MSP(主栈)和 PSP(进程栈)
|
|
|
|
|
|
- 寄存器组:R0-R15、LR、PC、xPSR
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 启动文件 Startup.s
|
|
|
|
|
|
|
|
|
|
|
|
- 用汇编语言书写的启动文件,完成向量表定义、初始化堆栈、调用 `main()`。
|
|
|
|
|
|
|
|
|
|
|
|
```asm
|
|
|
|
|
|
Reset_Handler:
|
|
|
|
|
|
LDR R0, =_estack ; 设置栈顶地址
|
|
|
|
|
|
MOV SP, R0
|
|
|
|
|
|
BL SystemInit ; 时钟初始化
|
|
|
|
|
|
BL main ; 跳转到主函数
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 启动流程简要
|
|
|
|
|
|
|
|
|
|
|
|
1. MCU 上电 → 执行 `Reset_Handler`
|
|
|
|
|
|
2. 设置 SP(栈顶)
|
|
|
|
|
|
3. 初始化 `.data` 和 `.bss` 段
|
|
|
|
|
|
4. 调用 `SystemInit()`(通常配置系统时钟)
|
|
|
|
|
|
5. 跳转执行用户 `main()` 函数
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 编译器与链接器
|
|
|
|
|
|
|
|
|
|
|
|
### 嵌入式工具链详解
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **Keil MDK(Microcontroller Development Kit)**
|
|
|
|
|
|
- **特点**:
|
|
|
|
|
|
- 商业软件,支持ARM Cortex-M/R/A全系列处理器。
|
|
|
|
|
|
- 集成μVision IDE、ARM编译器、调试器,界面友好。
|
|
|
|
|
|
- 针对STM32等芯片提供Device Family Pack(DFP),简化外设配置。
|
|
|
|
|
|
- **应用场景**:
|
|
|
|
|
|
- 企业级产品开发(如医疗设备、工业控制)。
|
|
|
|
|
|
- 需高效调试功能(如硬件断点、实时变量监控)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **IAR EWARM(Embedded Workbench for ARM)**
|
|
|
|
|
|
- **特点**:
|
|
|
|
|
|
- 编译效率高,生成代码体积比GCC小10%-20%。
|
|
|
|
|
|
- 调试器支持高级功能(如指令级调试、功耗分析)。
|
|
|
|
|
|
- 跨平台支持(Windows、Linux、Mac)。
|
|
|
|
|
|
- **应用场景**:
|
|
|
|
|
|
- 对代码体积敏感的场景(如MCU Flash空间有限)。
|
|
|
|
|
|
- 汽车电子(符合ISO 26262功能安全标准)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **GCC (arm-none-eabi)**
|
|
|
|
|
|
- **特点**:
|
|
|
|
|
|
- 开源免费,基于GNU工具链(GCC、GDB、Binutils)。
|
|
|
|
|
|
- 跨平台支持,适合Linux开发者。
|
|
|
|
|
|
- 可通过命令行(CLI)集成到自动化构建流程(如Makefile、CMake)。
|
|
|
|
|
|
- **典型工具**:
|
|
|
|
|
|
- `arm-none-eabi-gcc`:编译器。
|
|
|
|
|
|
- `arm-none-eabi-ld`:链接器。
|
|
|
|
|
|
- `arm-none-eabi-objcopy`:格式转换工具(如生成.bin/.hex文件)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 链接脚本(.ld)深入解析
|
|
|
|
|
|
#### 1. **核心作用**
|
|
|
|
|
|
- **内存分区**:定义Flash、RAM等存储器区域的起始地址和大小。
|
|
|
|
|
|
- **段分配**:将代码段(.text)、数据段(.data)、BSS段(.bss)等映射到指定内存区域。
|
|
|
|
|
|
- **地址对齐**:确保关键数据(如中断向量表)位于特定地址。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **MEMORY 区块解析**
|
|
|
|
|
|
```ld
|
|
|
|
|
|
MEMORY
|
|
|
|
|
|
{
|
|
|
|
|
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K // 只读,可执行
|
|
|
|
|
|
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K // 可读可写可执行
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
- **属性说明**:
|
|
|
|
|
|
- `r`:可读,`w`:可写,`x`:可执行。
|
|
|
|
|
|
- `ORIGIN`:起始地址,`LENGTH`:大小。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **SECTIONS 区块解析**
|
|
|
|
|
|
```ld
|
|
|
|
|
|
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`)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 4. **特殊用法**
|
|
|
|
|
|
- **自定义段**:
|
|
|
|
|
|
```ld
|
|
|
|
|
|
.mysection : { KEEP(*(.mysection)) } > RAM // 保留特定段,不被链接器优化
|
|
|
|
|
|
```
|
|
|
|
|
|
- **指定中断向量表位置**:
|
|
|
|
|
|
```ld
|
|
|
|
|
|
.isr_vector : {
|
|
|
|
|
|
. = ALIGN(4);
|
|
|
|
|
|
KEEP(*(.isr_vector)) // 中断向量表必须位于Flash起始地址
|
|
|
|
|
|
. = ALIGN(4);
|
|
|
|
|
|
} > FLASH
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### STM32存储器布局详解
|
|
|
|
|
|
#### 1. **物理内存映射(以STM32F4为例)**
|
|
|
|
|
|
```
|
|
|
|
|
|
地址范围 大小 描述
|
|
|
|
|
|
0x00000000-0x1FFFFFFF 512MB 代码区(可映射到Flash/SRAM/系统内存)
|
|
|
|
|
|
0x20000000-0x2001FFFF 128KB SRAM(运行时数据)
|
|
|
|
|
|
0x40000000-0x5FFFFFFF 512MB 外设寄存器(APB/AHB总线)
|
|
|
|
|
|
0xE0000000-0xE00FFFFF 1MB 系统控制空间(NVIC、SysTick等)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **Flash区域详解**
|
|
|
|
|
|
- **起始地址**:0x08000000(实际代码从此处开始)。
|
|
|
|
|
|
- **典型布局**:
|
|
|
|
|
|
```
|
|
|
|
|
|
0x08000000-0x08000100 中断向量表
|
|
|
|
|
|
0x08000100-0x08080000 程序代码(.text)
|
|
|
|
|
|
0x08080000-0x08088000 已初始化数据(.data加载时位置)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **RAM区域详解**
|
|
|
|
|
|
- **起始地址**:0x20000000。
|
|
|
|
|
|
- **典型布局**:
|
|
|
|
|
|
```
|
|
|
|
|
|
0x20000000-0x20000100 已初始化数据(.data运行时位置)
|
|
|
|
|
|
0x20000100-0x20000200 未初始化数据(.bss)
|
|
|
|
|
|
0x20000200-0x20001200 栈空间(向下增长)
|
|
|
|
|
|
0x20001200-0x20010000 堆空间(向上增长)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4. **外设映射区(0x40000000起)**
|
|
|
|
|
|
- **GPIO控制器**:0x40020000-0x40025000(如GPIOA基址0x40020000)。
|
|
|
|
|
|
- **USART1**:0x40011000-0x40011400。
|
|
|
|
|
|
- **定时器(TIM2)**:0x40000000-0x40000400。
|
|
|
|
|
|
- **访问示例**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
#define GPIOA_BASE 0x40020000
|
|
|
|
|
|
#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00)) // 模式寄存器
|
|
|
|
|
|
|
|
|
|
|
|
GPIOA_MODER |= 0x01; // PA0设为输出模式
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 编译与链接流程
|
|
|
|
|
|
#### 1. **编译阶段**
|
|
|
|
|
|
```
|
|
|
|
|
|
源代码(.c/.cpp) → 预处理器 → 编译器 → 汇编代码(.s) → 汇编器 → 目标文件(.o)
|
|
|
|
|
|
```
|
|
|
|
|
|
- **关键步骤**:
|
|
|
|
|
|
- 预处理器处理`#include`、`#define`等指令。
|
|
|
|
|
|
- 编译器将代码转换为汇编语言。
|
|
|
|
|
|
- 汇编器生成机器码(目标文件)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **链接阶段**
|
|
|
|
|
|
```
|
|
|
|
|
|
多个目标文件(.o) + 库文件(.a/.lib) → 链接器 → 可执行文件(.elf) → 格式转换器 → 固件文件(.bin/.hex)
|
|
|
|
|
|
```
|
|
|
|
|
|
- **链接器工作**:
|
|
|
|
|
|
1. 合并所有目标文件的段(如.text、.data)。
|
|
|
|
|
|
2. 解析符号引用(如函数调用、全局变量)。
|
|
|
|
|
|
3. 根据链接脚本分配地址。
|
|
|
|
|
|
4. 生成最终可执行文件。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **烧录阶段**
|
|
|
|
|
|
- 工具:ST-Link、J-Link、OpenOCD等。
|
|
|
|
|
|
- 流程:将.bin/.hex文件写入MCU的Flash起始地址(如0x08000000)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 常见问题与调试技巧
|
|
|
|
|
|
#### 1. **链接错误**
|
|
|
|
|
|
- **符号未定义**:
|
|
|
|
|
|
- 原因:调用未实现的函数或使用未定义的变量。
|
|
|
|
|
|
- 解决:检查函数名拼写,确保目标文件包含该符号。
|
|
|
|
|
|
- **内存溢出**:
|
|
|
|
|
|
- 原因:代码或数据量超过Flash/RAM大小。
|
|
|
|
|
|
- 解决:优化代码,减少全局变量,或更换更大容量的MCU。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **调试工具**
|
|
|
|
|
|
- **反汇编工具**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
arm-none-eabi-objdump -d main.elf # 生成反汇编代码
|
|
|
|
|
|
```
|
|
|
|
|
|
- **查看内存分布**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
arm-none-eabi-size main.elf # 显示各段大小
|
|
|
|
|
|
```
|
|
|
|
|
|
输出示例:
|
|
|
|
|
|
```
|
|
|
|
|
|
text data bss dec hex filename
|
|
|
|
|
|
1234 56 789 2079 81F main.elf
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **链接脚本调试技巧**
|
|
|
|
|
|
- 添加自定义段:
|
|
|
|
|
|
```ld
|
|
|
|
|
|
.debug_info : { *(.debug_info) } > RAM // 将调试信息放入RAM
|
|
|
|
|
|
```
|
|
|
|
|
|
- 使用`KEEP`防止符号被优化:
|
|
|
|
|
|
```ld
|
|
|
|
|
|
.vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 面试高频问题
|
|
|
|
|
|
1. **.data和.bss的区别**:
|
|
|
|
|
|
- `.data`存储已初始化的全局变量,占用Flash和RAM;
|
|
|
|
|
|
- `.bss`存储未初始化的全局变量,仅占用RAM(运行前自动清零)。
|
|
|
|
|
|
|
|
|
|
|
|
2. **如何减小代码体积**:
|
|
|
|
|
|
- 使用IAR等优化能力更强的编译器。
|
|
|
|
|
|
- 移除无用代码(如未使用的函数)。
|
|
|
|
|
|
- 压缩常量数据(如图片、字体)。
|
|
|
|
|
|
|
|
|
|
|
|
3. **链接脚本中AT关键字的作用**:
|
|
|
|
|
|
- `AT`指定段的加载地址,如`.data > RAM AT > FLASH`表示:
|
|
|
|
|
|
- 运行时在RAM(0x20000000),但加载时从Flash(0x08080000)复制到RAM。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 芯片数据手册阅读方法
|
|
|
|
|
|
|
|
|
|
|
|
#### 为什么重要?
|
|
|
|
|
|
|
|
|
|
|
|
芯片数据手册(Datasheet)和参考手册(Reference Manual)是开发嵌入式系统时的核心参考材料,了解外设功能、寄存器地址、时钟结构、中断号、引脚复用等关键信息。
|
|
|
|
|
|
|
|
|
|
|
|
#### 典型结构(以 STM32 为例):
|
|
|
|
|
|
|
|
|
|
|
|
| 部分 | 说明 |
|
|
|
|
|
|
| --------------------------- | -------------------------------- |
|
|
|
|
|
|
| Features | 简要功能描述,例如内核型号、Flash/RAM 容量、外设数量等 |
|
|
|
|
|
|
| Block Diagram | 芯片整体结构图 |
|
|
|
|
|
|
| Pinout / Alternate Function | 引脚定义和复用功能说明 |
|
|
|
|
|
|
| Electrical Characteristics | 电源、电压、电流、温度范围等参数 |
|
|
|
|
|
|
| Memory Map | 内存地址映射(Flash、SRAM、外设地址区间) |
|
|
|
|
|
|
| Peripherals | 每个外设的寄存器结构与配置方法 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 阅读技巧:
|
|
|
|
|
|
|
|
|
|
|
|
* **先看 Block Diagram 和内存映射图**,了解系统架构。
|
|
|
|
|
|
* **按功能模块查阅**:比如用 UART,就查看 USART 章节。
|
|
|
|
|
|
* **关注寄存器描述表格**:查看寄存器地址、每个位的含义、读写属性(R/W)和复位值。
|
|
|
|
|
|
* **善用搜索关键词**:如 `RCC_APB2ENR`,快速定位外设时钟控制相关信息。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 向量表的定义与重定向
|
|
|
|
|
|
|
|
|
|
|
|
#### 什么是向量表?
|
|
|
|
|
|
|
|
|
|
|
|
向量表是处理器在启动时用来获取异常/中断服务函数入口地址的数组,通常放在 Flash 起始地址(如 `0x0800 0000`)或 RAM 中。
|
|
|
|
|
|
|
|
|
|
|
|
每个项为一个 **函数指针**,比如:
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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
|
|
|
|
|
|
...
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 重定向方法(常用于 Bootloader 或自定义中断):
|
|
|
|
|
|
|
|
|
|
|
|
**1. 修改中断处理函数指针:**
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
__attribute__((section(".vector_table")))
|
|
|
|
|
|
void (*my_vector_table[])(void) = {
|
|
|
|
|
|
... // 自定义中断处理函数
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**2. 使用 `SCB->VTOR`(Vector Table Offset Register)更改向量表地址:**
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
#include "core_cm4.h"
|
|
|
|
|
|
SCB->VTOR = (uint32_t)my_vector_table;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> 注意:新表地址必须对齐 0x100(最低 8 位为 0)
|
|
|
|
|
|
|
|
|
|
|
|
#### 应用场景:
|
|
|
|
|
|
|
|
|
|
|
|
* Bootloader 跳转到 App 时的向量表切换
|
|
|
|
|
|
* 定制中断处理逻辑
|
|
|
|
|
|
* 启动阶段将向量表从 Flash 重定向到 RAM(以支持运行时修改)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### ROM 启动 vs RAM 启动的差异
|
|
|
|
|
|
|
|
|
|
|
|
#### 启动方式的定义:
|
|
|
|
|
|
|
|
|
|
|
|
启动方式决定系统复位后,**从哪块内存的地址开始执行程序**,通常与 Boot Mode 管脚或 Boot 配置位有关。
|
|
|
|
|
|
|
|
|
|
|
|
#### ROM 启动(Flash 启动):
|
|
|
|
|
|
|
|
|
|
|
|
* CPU 从 Flash 起始地址(如 `0x0800 0000`)加载向量表与指令
|
|
|
|
|
|
* 适用于生产烧录版本
|
|
|
|
|
|
* 启动速度快,代码执行稳定
|
|
|
|
|
|
|
|
|
|
|
|
#### RAM 启动:
|
|
|
|
|
|
|
|
|
|
|
|
* CPU 从 SRAM 地址(如 `0x2000 0000`)启动
|
|
|
|
|
|
* 适用于调试、自定义 Bootloader 或通过 JTAG 加载代码场景
|
|
|
|
|
|
* 启动前通常需要拷贝一段程序到 RAM(由 Boot ROM、调试器或引导代码完成)
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用差异:
|
|
|
|
|
|
|
|
|
|
|
|
| 项目 | ROM 启动 | RAM 启动 |
|
|
|
|
|
|
| ----- | -------------- | --------------------- |
|
|
|
|
|
|
| 程序位置 | 编译链接到 Flash 区域 | 编译链接到 RAM 区域 |
|
|
|
|
|
|
| 向量表位置 | 默认在 Flash | 需手动配置向量表并设置 SCB->VTOR |
|
|
|
|
|
|
| 应用场景 | 量产版本、正常运行 | Bootloader、自举加载、调试 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 链接脚本修改示例(GCC):
|
|
|
|
|
|
|
|
|
|
|
|
* ROM 启动:
|
|
|
|
|
|
|
|
|
|
|
|
```ld
|
|
|
|
|
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
* RAM 启动:
|
|
|
|
|
|
|
|
|
|
|
|
```ld
|
|
|
|
|
|
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 🟠 第三层:驱动开发与外设编程
|
|
|
|
|
|
|
|
|
|
|
|
嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。
|
|
|
|
|
|
以下从底层原理到实践应用进行深度扩展:
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 寄存器级开发
|
|
|
|
|
|
#### 📌 地址映射与寄存器偏移
|
|
|
|
|
|
- **总线架构**:
|
|
|
|
|
|
- AHB/APB总线:STM32通过AHB(高级高性能总线)连接高速外设,APB(高级外设总线)连接低速外设。
|
|
|
|
|
|
- 示例:GPIOA位于AHB1总线,基地址0x40020000;USART1位于APB2总线,基地址0x40011000。
|
|
|
|
|
|
- **寄存器偏移**:
|
|
|
|
|
|
- 每个外设包含多个寄存器,通过基地址+偏移量访问。
|
|
|
|
|
|
- 示例:GPIOA_MODER(模式寄存器)偏移0x00,GPIOA_ODR(输出数据寄存器)偏移0x14。
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 位操作技巧
|
|
|
|
|
|
- **原子操作宏**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
#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))
|
|
|
|
|
|
```
|
|
|
|
|
|
- **多位置位/清零**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 同时设置PA5、PA6为输出(MODER[13:12]=01, MODER[11:10]=01)
|
|
|
|
|
|
GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 通用外设驱动
|
|
|
|
|
|
#### 📌 GPIO(通用输入输出)
|
|
|
|
|
|
- **模式配置**:
|
|
|
|
|
|
- 输入模式:浮空输入、上拉输入、下拉输入、模拟输入。
|
|
|
|
|
|
- 输出模式:推挽输出、开漏输出(需外部上拉)。
|
|
|
|
|
|
- 复用模式:用于SPI、I2C等外设功能。
|
|
|
|
|
|
- **中断配置步骤**:
|
|
|
|
|
|
1. 配置GPIO为输入模式。
|
|
|
|
|
|
2. 配置SYSCFG_EXTICR寄存器选择中断源。
|
|
|
|
|
|
3. 配置EXTI_IMR(中断屏蔽)、EXTI_RTSR(上升沿触发)/FTSR(下降沿触发)。
|
|
|
|
|
|
4. 在NVIC中使能并设置中断优先级。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 示例:配置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中断
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 UART/USART
|
|
|
|
|
|
- **波特率计算**:
|
|
|
|
|
|
- 公式:`波特率 = 系统时钟 / (16 * USARTDIV)`
|
|
|
|
|
|
- 示例:系统时钟72MHz,波特率115200,则USARTDIV = 72000000 / (16 * 115200) ≈ 39.0625。
|
|
|
|
|
|
- **中断接收实现**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 接收完成回调函数
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 SPI(串行外设接口)
|
|
|
|
|
|
- **模式配置**:
|
|
|
|
|
|
- 时钟极性(CPOL):0(空闲时SCLK为低)或1(空闲时SCLK为高)。
|
|
|
|
|
|
- 时钟相位(CPHA):0(第一个边沿采样)或1(第二个边沿采样)。
|
|
|
|
|
|
- 数据位宽:8位或16位。
|
|
|
|
|
|
- **主从模式区别**:
|
|
|
|
|
|
- 主模式:控制SCK时钟,负责发起通信。
|
|
|
|
|
|
- 从模式:接收SCK时钟,响应主设备请求。
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 I2C(集成电路间总线)
|
|
|
|
|
|
- **寻址方式**:
|
|
|
|
|
|
- 7位地址:0x00~0x7F,其中0x00为广播地址。
|
|
|
|
|
|
- 10位地址:扩展寻址,用于特殊设备。
|
|
|
|
|
|
- **多主竞争解决**:
|
|
|
|
|
|
- 通过SDA线的电平检测实现总线仲裁,先检测到SDA线被拉低的主设备退出竞争。
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 ADC(模拟-to-数字转换器)
|
|
|
|
|
|
- **采样时间配置**:
|
|
|
|
|
|
- 采样时间越长,转换结果越精确,但转换速度越慢。
|
|
|
|
|
|
- 示例:STM32F4的ADC采样时间可配置为3、15、28、56、84、112、144、480周期。
|
|
|
|
|
|
- **多通道扫描模式**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 配置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 控制器
|
|
|
|
|
|
- **通道选择**:
|
|
|
|
|
|
- 每个DMA控制器包含多个通道,不同外设对应不同通道。
|
|
|
|
|
|
- 示例:USART1_RX对应DMA2通道5,USART1_TX对应DMA2通道4。
|
|
|
|
|
|
- **双缓冲区模式**:
|
|
|
|
|
|
- 适合大数据量传输,一个缓冲区用于当前传输,另一个准备下一次传输。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 配置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;
|
|
|
|
|
|
// ...其他配置
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 看门狗(Watchdog)
|
|
|
|
|
|
- **独立看门狗(IWDG)**:
|
|
|
|
|
|
- 由专用低速时钟(LSI,约32kHz)驱动,即使主时钟故障仍能工作。
|
|
|
|
|
|
- 喂狗时间范围:典型值10ms~16s。
|
|
|
|
|
|
- **窗口看门狗(WWDG)**:
|
|
|
|
|
|
- 喂狗时间必须在窗口范围内(上限值~下限值),防止程序在异常状态下喂狗。
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 CAN(控制器局域网)
|
|
|
|
|
|
- **位时序配置**:
|
|
|
|
|
|
- 由同步段(SYNC_SEG)、传播时间段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)和相位缓冲段2(PHASE_SEG2)组成。
|
|
|
|
|
|
- 示例:波特率500kbps,系统时钟42MHz,位时序配置为:
|
|
|
|
|
|
```c
|
|
|
|
|
|
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);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 开发库 & 工具链
|
|
|
|
|
|
#### 📌 STM32 HAL(硬件抽象层)
|
|
|
|
|
|
- **HAL库架构**:
|
|
|
|
|
|
- 核心层:提供外设初始化、控制和状态检查函数。
|
|
|
|
|
|
- 回调函数:通过弱函数(weak)实现,用户可重写。
|
|
|
|
|
|
- 示例:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 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) {
|
|
|
|
|
|
// 发送完成后的处理
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 STM32 LL(低层驱动)
|
|
|
|
|
|
- **优势**:
|
|
|
|
|
|
- 代码体积更小,执行效率更高。
|
|
|
|
|
|
- 更接近寄存器操作,适合性能敏感场景。
|
|
|
|
|
|
- **与HAL对比**:
|
|
|
|
|
|
| **特性** | **HAL** | **LL** |
|
|
|
|
|
|
|----------------|--------------------------|--------------------------|
|
|
|
|
|
|
| 抽象程度 | 高 | 低 |
|
|
|
|
|
|
| 代码体积 | 大 | 小 |
|
|
|
|
|
|
| 执行效率 | 低 | 高 |
|
|
|
|
|
|
| 学习难度 | 低 | 高 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 📌 STM32CubeMX
|
|
|
|
|
|
- **时钟树配置**:
|
|
|
|
|
|
- 基于PLL(锁相环)生成系统时钟,需合理配置倍频系数和分频系数。
|
|
|
|
|
|
- 示例:配置系统时钟为180MHz:
|
|
|
|
|
|
```
|
|
|
|
|
|
HSE (8MHz) → PLLM=8 → VCO输入=1MHz → PLLN=360 → VCO输出=360MHz → PLLP=2 → 系统时钟=180MHz
|
|
|
|
|
|
```
|
|
|
|
|
|
- **中间件集成**:
|
|
|
|
|
|
- 支持FreeRTOS、LWIP、USB、File System等中间件一键配置。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 实战技巧与常见问题
|
|
|
|
|
|
#### 1. **外设初始化流程**
|
|
|
|
|
|
1. 使能外设时钟。
|
|
|
|
|
|
2. 配置GPIO复用功能(如需要)。
|
|
|
|
|
|
3. 配置外设参数(如波特率、采样时间)。
|
|
|
|
|
|
4. 使能外设。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **中断处理优化**
|
|
|
|
|
|
- 中断服务函数(ISR)应尽量简短,避免耗时操作。
|
|
|
|
|
|
- 关键数据传递使用原子操作或关中断保护。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 示例:使用原子操作传递数据
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **调试技巧**
|
|
|
|
|
|
- **寄存器查看**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 查看GPIOA_MODER寄存器值
|
|
|
|
|
|
uint32_t moder_value = GPIOA->MODER;
|
|
|
|
|
|
printf("GPIOA_MODER = 0x%08X\n", moder_value);
|
|
|
|
|
|
```
|
|
|
|
|
|
- **示波器检测**:
|
|
|
|
|
|
- 检测SPI/I2C总线波形,验证通信时序。
|
|
|
|
|
|
- 检测PWM波形,验证占空比和频率。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 面试高频问题
|
|
|
|
|
|
1. **HAL与LL库的选择标准**:
|
|
|
|
|
|
- 快速开发选HAL,性能敏感场景选LL;需平衡开发效率与代码体积。
|
|
|
|
|
|
|
|
|
|
|
|
2. **I2C通信中ACK/NACK的作用**:
|
|
|
|
|
|
- ACK(应答):接收方正确接收到数据,发送低电平。
|
|
|
|
|
|
- NACK(非应答):接收方无法继续接收,发送高电平。
|
|
|
|
|
|
|
|
|
|
|
|
3. **ADC采样时间对精度的影响**:
|
|
|
|
|
|
- 采样时间越长,对信号的积分效果越好,抗干扰能力越强,精度越高。
|
|
|
|
|
|
|
|
|
|
|
|
4. **DMA与CPU直接传输的优缺点**:
|
|
|
|
|
|
- 优点:释放CPU资源,实现高速数据传输。
|
|
|
|
|
|
- 缺点:配置复杂,占用总线带宽。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 🟣 第四层:实时操作系统(RTOS)
|
|
|
|
|
|
|
|
|
|
|
|
本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 RTOS 基础概念
|
|
|
|
|
|
|
|
|
|
|
|
### 什么是 RTOS?
|
|
|
|
|
|
**RTOS**(Real-Time Operating System)是用于嵌入式设备中的轻量级操作系统,能提供任务调度、时间管理、资源管理等功能。
|
|
|
|
|
|
|
|
|
|
|
|
**特点:**
|
|
|
|
|
|
- 确定性(Determinism):
|
|
|
|
|
|
- 任务执行时间可预测,如中断响应时间 ≤100μs。
|
|
|
|
|
|
- 对比:通用操作系统(如 Linux)强调吞吐量,不保证实时性。
|
|
|
|
|
|
|
|
|
|
|
|
- 可抢占内核(Preemptive Kernel):
|
|
|
|
|
|
- 高优先级任务可立即抢占低优先级任务。
|
|
|
|
|
|
- 示例:飞行控制系统中,传感器数据采集任务优先级高于显示任务。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 常见 RTOS
|
|
|
|
|
|
- FreeRTOS(开源、广泛使用)
|
|
|
|
|
|
- RT-Thread(国产开源,图形化支持强)
|
|
|
|
|
|
- CMSIS-RTOS(ARM 标准接口)
|
|
|
|
|
|
- Zephyr(Linux 基金会支持,适合物联网)
|
|
|
|
|
|
|
|
|
|
|
|
**RTOS vs 裸机系统**
|
|
|
|
|
|
| 特性 | 裸机系统 | RTOS(实时操作系统) |
|
|
|
|
|
|
|--------------|-----------------------------|---------------------------------|
|
|
|
|
|
|
| 任务管理 | 单任务 / 前后台系统 | 多任务并发,支持任务优先级 |
|
|
|
|
|
|
| 资源分配 | 手动管理 | 自动调度和资源管理 |
|
|
|
|
|
|
| 实时响应 | 依赖主循环结构 | 确定性调度,响应更稳定 |
|
|
|
|
|
|
| 开发难度 | 低(适合简单系统) | 高(需理解调度机制、堆栈管理) |
|
|
|
|
|
|
|
|
|
|
|
|
**主流 RTOS 对比**
|
|
|
|
|
|
| RTOS | 开源 | 应用领域 | 特点 |
|
|
|
|
|
|
|------------|----------|--------------------------|----------------------------------------------|
|
|
|
|
|
|
| FreeRTOS | ✅ | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
|
|
|
|
|
|
| RT-Thread | ✅ | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
|
|
|
|
|
|
| μC/OS | ⚠️ 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
|
|
|
|
|
|
| VxWorks | ❌ | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 任务管理
|
|
|
|
|
|
|
|
|
|
|
|
### 任务创建与内存布局
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建任务示例
|
|
|
|
|
|
void vTaskFunction(void *pvParameters) {
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
// 任务代码
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(100)); // 释放CPU
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 任务创建
|
|
|
|
|
|
xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
|
|
|
|
|
|
```
|
|
|
|
|
|
- 栈空间分配:
|
|
|
|
|
|
- 每个任务独立栈空间,需避免溢出(通过configCHECK_FOR_STACK_OVERFLOW检测)。
|
|
|
|
|
|
- 计算方法:任务局部变量大小 + 函数调用深度 × 最大寄存器保存数。
|
|
|
|
|
|
|
|
|
|
|
|
### 任务状态转换
|
|
|
|
|
|
```plaintext
|
|
|
|
|
|
调度器选择 超时/事件发生
|
|
|
|
|
|
就绪 ───────────→ 运行 ←─────────── 阻塞
|
|
|
|
|
|
↑ │ │
|
|
|
|
|
|
│ └─── 调用vTaskDelay │
|
|
|
|
|
|
│ │
|
|
|
|
|
|
└─────── 调用vTaskSuspend ┘
|
|
|
|
|
|
或挂起API
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 任务优先级与调度算法
|
|
|
|
|
|
- 抢占式调度:
|
|
|
|
|
|
- 基于任务优先级,高优先级任务可立即抢占当前运行任务。
|
|
|
|
|
|
- 实现:FreeRTOS 通过pxCurrentTCB指针指向当前任务控制块(TCB)。
|
|
|
|
|
|
|
|
|
|
|
|
- 时间片轮转:
|
|
|
|
|
|
- 同优先级任务按时间片轮流执行(由configTICK_RATE_HZ决定)。
|
|
|
|
|
|
- 示例:两个优先级相同的任务各执行 10ms。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 时间管理
|
|
|
|
|
|
|
|
|
|
|
|
### 任务延时实现
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 相对延时(从调用开始计算)
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
|
|
|
|
|
|
|
|
|
|
// 绝对延时(固定周期执行)
|
|
|
|
|
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
|
|
|
|
|
const TickType_t xFrequency = pdMS_TO_TICKS(100);
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
|
|
|
|
|
// 周期性任务代码
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 软件定时器
|
|
|
|
|
|
- 单次触发:执行一次后停止。
|
|
|
|
|
|
- 周期触发:按固定周期重复执行。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建并启动定时器
|
|
|
|
|
|
TimerHandle_t xTimer = xTimerCreate(
|
|
|
|
|
|
"Timer", // 定时器名称
|
|
|
|
|
|
pdMS_TO_TICKS(1000), // 周期1秒
|
|
|
|
|
|
pdTRUE, // 周期模式
|
|
|
|
|
|
(void *)0, // 定时器ID
|
|
|
|
|
|
vTimerCallback // 回调函数
|
|
|
|
|
|
);
|
|
|
|
|
|
xTimerStart(xTimer, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 定时器回调函数
|
|
|
|
|
|
void vTimerCallback(TimerHandle_t xTimer) {
|
|
|
|
|
|
// 定时任务代码
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 线程间通信
|
|
|
|
|
|
|
|
|
|
|
|
### 队列(Queue)
|
|
|
|
|
|
- 特性:
|
|
|
|
|
|
- 线程安全的 FIFO 缓冲区,支持阻塞读写。
|
|
|
|
|
|
- 最大长度和消息大小在创建时指定。
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建队列
|
|
|
|
|
|
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);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 信号量(Semaphore)
|
|
|
|
|
|
#### 二值信号量:
|
|
|
|
|
|
- 用于任务同步(如中断与任务通信)。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建二值信号量
|
|
|
|
|
|
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
|
|
|
|
|
|
|
|
|
|
|
|
// 任务中获取信号量
|
|
|
|
|
|
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
|
|
|
|
|
|
// 获得信号量,执行临界区代码
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 中断中释放信号量
|
|
|
|
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
|
|
|
|
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
|
|
|
|
|
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 计数信号量(共享资源数量)
|
|
|
|
|
|
- 核心概念
|
|
|
|
|
|
- 资源计数器:初始值为可用资源数量,用于控制对有限资源的访问。
|
|
|
|
|
|
- 操作规则:
|
|
|
|
|
|
- xSemaphoreTake():获取信号量时计数器减 1,若计数器为 0 则阻塞。
|
|
|
|
|
|
- xSemaphoreGive():释放信号量时计数器加 1,唤醒等待任务。
|
|
|
|
|
|
- 典型应用场景
|
|
|
|
|
|
- 多资源管理:如打印机池(假设有 3 台打印机)
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建计数信号量(初始值=3,最大值=3)
|
|
|
|
|
|
SemaphoreHandle_t xPrinterSemaphore = xSemaphoreCreateCounting(3, 3);
|
|
|
|
|
|
|
|
|
|
|
|
// 任务中请求打印机
|
|
|
|
|
|
if (xSemaphoreTake(xPrinterSemaphore, portMAX_DELAY) == pdTRUE) {
|
|
|
|
|
|
// 获得打印机,执行打印任务
|
|
|
|
|
|
vPrintTask();
|
|
|
|
|
|
// 释放打印机
|
|
|
|
|
|
xSemaphoreGive(xPrinterSemaphore);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
- 生产者——消费者缓冲区:用信号量跟踪缓冲区空 / 满状态。
|
|
|
|
|
|
#### 互斥信号量(用于资源保护)
|
|
|
|
|
|
- 核心特性
|
|
|
|
|
|
- 二值信号量的特例:初始值为 1,表示资源可用。
|
|
|
|
|
|
- 优先级继承:解决优先级反转问题(低优先级任务持有锁时临时提升其优先级)。
|
|
|
|
|
|
- 优先级反转示例
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建互斥锁
|
|
|
|
|
|
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); // 释放锁
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
- 问题:任务 L 持有锁时被任务 M 抢占,导致任务 H 无法执行(优先级反转)。
|
|
|
|
|
|
- 解决:启用优先级继承后,任务 L 持有锁时临时提升至任务 H 的优先级,避免被 M 抢占。
|
|
|
|
|
|
|
|
|
|
|
|
### 消息队列(Message Queue)
|
|
|
|
|
|
#### 与普通队列的区别
|
|
|
|
|
|
- 结构化数据传递:支持传递复杂数据类型(如结构体)。
|
|
|
|
|
|
- 指针传递优化:可传递数据指针而非数据本身,减少内存拷贝。
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用示例
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 定义消息结构体
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
#### 消息队列 vs 普通队列
|
|
|
|
|
|
| 特性 | 普通队列 | 消息队列 |
|
|
|
|
|
|
|--------------|----------------------------------|--------------------------------------------|
|
|
|
|
|
|
| 数据类型 | 固定大小字节块 | 支持结构体、指针等复杂数据类型 |
|
|
|
|
|
|
| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
|
|
|
|
|
|
| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 事件组(Event Group)
|
|
|
|
|
|
- 类似标志位,可用于多任务同步
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建事件组
|
|
|
|
|
|
EventGroupHandle_t xEventGroup = xEventGroupCreate();
|
|
|
|
|
|
|
|
|
|
|
|
// 任务1:设置事件位0
|
|
|
|
|
|
xEventGroupSetBits(xEventGroup, 0x01);
|
|
|
|
|
|
|
|
|
|
|
|
// 任务2:等待事件位0和1都置位
|
|
|
|
|
|
EventBits_t uxBits = xEventGroupWaitBits(
|
|
|
|
|
|
xEventGroup, // 事件组句柄
|
|
|
|
|
|
0x03, // 等待位0和1
|
|
|
|
|
|
pdTRUE, // 等待后清除位
|
|
|
|
|
|
pdTRUE, // 等待所有位
|
|
|
|
|
|
portMAX_DELAY // 永久等待
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 资源管理
|
|
|
|
|
|
|
|
|
|
|
|
### 内存管理方式
|
|
|
|
|
|
#### 静态分配(推荐)
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 使用静态内存创建任务
|
|
|
|
|
|
StaticTask_t xTaskBuffer;
|
|
|
|
|
|
StackType_t xStack[256];
|
|
|
|
|
|
|
|
|
|
|
|
xTaskCreateStatic(
|
|
|
|
|
|
vTaskFunction, // 任务函数
|
|
|
|
|
|
"Task1", // 任务名称
|
|
|
|
|
|
256, // 栈大小
|
|
|
|
|
|
NULL, // 参数
|
|
|
|
|
|
2, // 优先级
|
|
|
|
|
|
xStack, // 静态栈
|
|
|
|
|
|
&xTaskBuffer // 静态任务控制块
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
#### 动态分配(需要注意碎片与失败处理)
|
|
|
|
|
|
- 原因:频繁分配 / 释放不同大小的内存块,导致空闲内存分散。
|
|
|
|
|
|
- 示例:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 可能导致碎片的错误模式
|
|
|
|
|
|
void vTask(void *pvParameters) {
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
char *pcBuffer = (char *)pvPortMalloc(100);
|
|
|
|
|
|
// 使用缓冲区...
|
|
|
|
|
|
vPortFree(pcBuffer); // 释放后可能产生碎片
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 安全使用动态内存的原则
|
|
|
|
|
|
- 预分配固定大小块:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 预先分配对象池
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
- 检查分配结果:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void *pvBuffer = pvPortMalloc(100);
|
|
|
|
|
|
if (pvBuffer == NULL) {
|
|
|
|
|
|
// 内存分配失败处理
|
|
|
|
|
|
vHandleMemoryError();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 临界区保护
|
|
|
|
|
|
- 关中断:
|
|
|
|
|
|
```c
|
|
|
|
|
|
void vCriticalFunction(void) {
|
|
|
|
|
|
taskENTER_CRITICAL();
|
|
|
|
|
|
// 临界区代码(禁止中断)
|
|
|
|
|
|
taskEXIT_CRITICAL();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
- 互斥锁:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 创建互斥锁
|
|
|
|
|
|
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取锁
|
|
|
|
|
|
xSemaphoreTake(xMutex, portMAX_DELAY);
|
|
|
|
|
|
// 临界区代码
|
|
|
|
|
|
xSemaphoreGive(xMutex); // 释放锁
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 FreeRTOS 配置与移植
|
|
|
|
|
|
|
|
|
|
|
|
### 配置项(FreeRTOSConfig.h)
|
|
|
|
|
|
| 参数 | 描述 | 示例值 |
|
|
|
|
|
|
|-------------------------------|-----------------------------------|--------------------|
|
|
|
|
|
|
| `configUSE_PREEMPTION` | 是否使用抢占式调度 | `1`(启用) |
|
|
|
|
|
|
| `configTICK_RATE_HZ` | 系统滴答频率(Hz) | `1000`(1ms) |
|
|
|
|
|
|
| `configMAX_PRIORITIES` | 最大任务优先级数 | `5 ~ 32` |
|
|
|
|
|
|
| `configMINIMAL_STACK_SIZE` | 最小任务栈大小(以字为单位) | `128`(STM32) |
|
|
|
|
|
|
| `configSUPPORT_DYNAMIC_ALLOCATION` | 是否支持动态内存分配 | `1`(支持) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 移植步骤
|
|
|
|
|
|
1. 提供 SysTick 定时器实现
|
|
|
|
|
|
2. 提供上下文切换代码(汇编)
|
|
|
|
|
|
3. 编写启动任务入口函数 `vTaskStartScheduler()`
|
|
|
|
|
|
|
|
|
|
|
|
### 移植关键点
|
|
|
|
|
|
- 上下文切换实现(汇编):
|
|
|
|
|
|
```assembly
|
|
|
|
|
|
; 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 ; 返回
|
|
|
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 RTOS 调试与性能分析
|
|
|
|
|
|
|
|
|
|
|
|
### 调试工具与技术
|
|
|
|
|
|
- 任务状态查看:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 获取任务运行时信息
|
|
|
|
|
|
void vTaskList(char *pcWriteBuffer);
|
|
|
|
|
|
|
|
|
|
|
|
// 示例输出:
|
|
|
|
|
|
// TaskName State Priority Stack Num
|
|
|
|
|
|
// Task1 Running 2 128 1
|
|
|
|
|
|
// Task2 Blocked 1 256 2
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 性能指标分析
|
|
|
|
|
|
- CPU 使用率:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 计算CPU使用率(需配置configGENERATE_RUN_TIME_STATS=1)
|
|
|
|
|
|
uint32_t ulHighFrequencyTimerTicks;
|
|
|
|
|
|
vTaskGetRunTimeStats(&ulHighFrequencyTimerTicks);
|
|
|
|
|
|
```
|
|
|
|
|
|
- 任务堆栈深度:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 检查任务栈剩余空间
|
|
|
|
|
|
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 面试高频问题
|
|
|
|
|
|
|
|
|
|
|
|
#### RTOS 中任务与线程的区别:
|
|
|
|
|
|
- 任务是 RTOS 调度的基本单位,线程是操作系统调度的基本单位;RTOS 任务通常更轻量级。
|
|
|
|
|
|
|
|
|
|
|
|
#### 信号量与互斥锁的区别:
|
|
|
|
|
|
- 信号量可用于同步和资源计数,互斥锁专用于资源保护,支持优先级继承避免死锁。
|
|
|
|
|
|
|
|
|
|
|
|
#### 如何避免 RTOS 中的死锁:
|
|
|
|
|
|
- 按相同顺序获取锁,使用带超时的锁获取函数,避免嵌套锁。
|
|
|
|
|
|
|
|
|
|
|
|
#### FreeRTOS 任务优先级设置原则:
|
|
|
|
|
|
- 关键任务(如传感器采样)设高优先级,非关键任务(如显示更新)设低优先级。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🔹 实践应用场景
|
|
|
|
|
|
|
|
|
|
|
|
- 多任务协同:传感器数据采集 + 通信模块处理
|
|
|
|
|
|
- 响应式控制:定时器 + 外部中断 + 优先级控制
|
|
|
|
|
|
- 任务调度机制优化(任务嵌套/抢占/时间片轮转)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 🟢 第五层:嵌入式 Linux 开发基础
|
|
|
|
|
|
|
|
|
|
|
|
嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 嵌入式 Linux 系统概览
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 嵌入式 Linux 特点
|
|
|
|
|
|
|
|
|
|
|
|
- 可裁剪、可定制、模块化强
|
|
|
|
|
|
- 支持多种架构(ARM、MIPS、RISC-V 等)
|
|
|
|
|
|
- 社区支持强大(开源内核、驱动丰富)
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 系统组成
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
[Bootloader] → [Kernel] → [Root File System] → [User Application]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **Bootloader**:负责上电后硬件初始化、加载内核(如 U-Boot)
|
|
|
|
|
|
- **Kernel**:Linux 内核,管理硬件与系统资源
|
|
|
|
|
|
- **RootFS**:根文件系统,包含用户空间程序
|
|
|
|
|
|
- **应用层**:运行用户程序、脚本、服务等
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 启动流程详解
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 通用启动流程
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
Power On →
|
|
|
|
|
|
BootROM →
|
|
|
|
|
|
Bootloader (SPL/U-Boot) →
|
|
|
|
|
|
Load & Decompress Kernel →
|
|
|
|
|
|
Kernel 初始化 →
|
|
|
|
|
|
挂载 RootFS →
|
|
|
|
|
|
启动 init →
|
|
|
|
|
|
Shell / App
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 U-Boot(主流 Bootloader)
|
|
|
|
|
|
|
|
|
|
|
|
- 二阶段:SPL(初始化内存)+ U-Boot 本体
|
|
|
|
|
|
- 功能:串口输出、TFTP 下载、引导内核、环境变量配置等
|
|
|
|
|
|
- 命令示例:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
setenv bootargs console=ttyS0 root=/dev/mmcblk0p2
|
|
|
|
|
|
tftp 0x80008000 zImage
|
|
|
|
|
|
bootz 0x80008000 - 0x83000000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 设备树(Device Tree)
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 基本概念
|
|
|
|
|
|
|
|
|
|
|
|
- 描述硬件资源的结构化信息
|
|
|
|
|
|
- 独立于内核源码,提高可移植性
|
|
|
|
|
|
- 文件类型:`.dts`(源文件)、`.dtsi`(包含文件)、`.dtb`(二进制)
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 示例结构
|
|
|
|
|
|
|
|
|
|
|
|
```dts
|
|
|
|
|
|
uart1: serial@40011000 {
|
|
|
|
|
|
compatible = "vendor,uart";
|
|
|
|
|
|
reg = <0x40011000 0x400>;
|
|
|
|
|
|
interrupts = <5>;
|
|
|
|
|
|
status = "okay";
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 编译设备树
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 常用 Linux 命令与开发工具
|
|
|
|
|
|
|
|
|
|
|
|
### 文件与目录管理
|
|
|
|
|
|
| 命令 | 功能说明 |
|
|
|
|
|
|
|-------------------|------------------------------|
|
|
|
|
|
|
| `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` | 查看内核设备日志 |
|
|
|
|
|
|
|
|
|
|
|
|
### 软件包管理(针对开发板所用 Linux)
|
|
|
|
|
|
| 工具 | 说明 |
|
|
|
|
|
|
|--------------------|---------------------------------|
|
|
|
|
|
|
| `apt`, `opkg`, `yum` | 安装 / 卸载软件包 |
|
|
|
|
|
|
| `dpkg -i pkg.deb` | 安装本地 deb 包 |
|
|
|
|
|
|
|
|
|
|
|
|
### Shell 脚本与自动化
|
|
|
|
|
|
- `#!/bin/sh` 或 `#!/bin/bash`:脚本头部声明
|
|
|
|
|
|
- 脚本权限设置:`chmod +x script.sh`
|
|
|
|
|
|
- 示例:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
for i in {1..5}
|
|
|
|
|
|
do
|
|
|
|
|
|
echo "Test $i"
|
|
|
|
|
|
done
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 交叉编译相关命令(Makefile 环境)
|
|
|
|
|
|
|
|
|
|
|
|
| 命令/工具 | 说明 |
|
|
|
|
|
|
| --------------- | -------------- |
|
|
|
|
|
|
| `make` | 使用 Makefile 构建 |
|
|
|
|
|
|
| `arm-linux-gcc` | 使用交叉编译器编译 |
|
|
|
|
|
|
| `file a.out` | 查看可执行文件平台架构 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 Linux 驱动开发模型
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 驱动分层模型
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
[硬件设备] ←→ [总线] ←→ [Device] ←→ [Driver] ←→ [内核]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **总线(bus)**:如 platform、i2c、spi 总线
|
|
|
|
|
|
- **设备(device)**:描述具体外设
|
|
|
|
|
|
- **驱动(driver)**:实现对设备的控制逻辑
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 字符设备驱动框架
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
struct file_operations fops = {
|
|
|
|
|
|
.open = my_open,
|
|
|
|
|
|
.read = my_read,
|
|
|
|
|
|
.write = my_write,
|
|
|
|
|
|
.release = my_release,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
int major = register_chrdev(0, "mydev", &fops);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 平台驱动开发流程
|
|
|
|
|
|
|
|
|
|
|
|
1. 定义 `platform_device`
|
|
|
|
|
|
2. 编写并注册 `platform_driver`
|
|
|
|
|
|
3. 通过 `of_match_table` 匹配设备树节点
|
|
|
|
|
|
4. 实现 `probe/remove` 等接口
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 根文件系统构建
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 常见文件系统类型
|
|
|
|
|
|
|
|
|
|
|
|
- ext3/ext4:标准 Linux 文件系统
|
|
|
|
|
|
- squashfs:只读压缩文件系统,适合嵌入式
|
|
|
|
|
|
- initramfs:内存文件系统
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 文件系统布局(典型)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
/
|
|
|
|
|
|
├── bin/ → 常用命令
|
|
|
|
|
|
├── sbin/ → 系统工具
|
|
|
|
|
|
├── etc/ → 配置文件
|
|
|
|
|
|
├── dev/ → 设备节点
|
|
|
|
|
|
├── lib/ → 库文件
|
|
|
|
|
|
├── proc/ → 内核虚拟文件系统
|
|
|
|
|
|
├── sys/ → 设备/驱动信息
|
|
|
|
|
|
├── usr/ → 用户软件
|
|
|
|
|
|
├── tmp/ → 临时目录
|
|
|
|
|
|
└── home/ → 用户主目录
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 构建方式
|
|
|
|
|
|
|
|
|
|
|
|
- BusyBox + 自制文件结构
|
|
|
|
|
|
- Buildroot:快速构建定制系统
|
|
|
|
|
|
- Yocto:更灵活、工业级构建方案
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔹 工具链与调试手段
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 交叉编译工具链
|
|
|
|
|
|
|
|
|
|
|
|
- gcc-arm-linux-gnueabi
|
|
|
|
|
|
- arm-none-eabi-gcc
|
|
|
|
|
|
- 使用环境变量指定:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export CROSS_COMPILE=arm-linux-
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 📌 GDB 调试
|
|
|
|
|
|
|
|
|
|
|
|
- GDB Server + Remote Debug
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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 协同 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 嵌入式系统安全基础
|
|
|
|
|
|
1. 威胁模型分析
|
|
|
|
|
|
- 物理攻击:
|
|
|
|
|
|
- 探针访问调试接口(JTAG/SWD)读取 Flash 内容。
|
|
|
|
|
|
- 电压 / 时钟干扰导致程序异常(故障注入攻击)。
|
|
|
|
|
|
- 网络攻击:
|
|
|
|
|
|
- 中间人攻击(MITM)篡改通信数据。
|
|
|
|
|
|
- 恶意固件注入(利用未加密 OTA 通道)。
|
|
|
|
|
|
- 软件攻击:
|
|
|
|
|
|
- 缓冲区溢出执行恶意代码。
|
|
|
|
|
|
- 逆向工程获取算法逻辑(如加密密钥)。
|
|
|
|
|
|
|
|
|
|
|
|
2. 安全设计原则
|
|
|
|
|
|
- 最小权限原则:
|
|
|
|
|
|
|
|
|
|
|
|
每个组件仅拥有完成任务所需的最小权限(如 MPU 配置)。
|
|
|
|
|
|
|
|
|
|
|
|
- 防御纵深:
|
|
|
|
|
|
|
|
|
|
|
|
多层次安全机制(如安全启动 + 通信加密 + 运行时防护)。
|
|
|
|
|
|
|
|
|
|
|
|
- 故障安全:
|
|
|
|
|
|
|
|
|
|
|
|
系统在异常情况下自动进入安全状态(如看门狗复位)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 安全启动(Secure Boot)
|
|
|
|
|
|
|
|
|
|
|
|
> 保证启动时加载的固件是可信的
|
|
|
|
|
|
|
|
|
|
|
|
1. 基本原理
|
|
|
|
|
|
```plaintext
|
|
|
|
|
|
BootROM → 加载并验证一级Bootloader → 加载并验证二级Bootloader → 加载并验证应用固件
|
|
|
|
|
|
```
|
|
|
|
|
|
- 信任链传递:
|
|
|
|
|
|
|
|
|
|
|
|
每个阶段只信任经过上一阶段验证的代码。
|
|
|
|
|
|
|
|
|
|
|
|
2. 数字签名验证流程
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 简化的签名验证伪代码
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
3. STM32 Secure Boot 实现
|
|
|
|
|
|
- 选项字节配置:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 启用读保护(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();
|
|
|
|
|
|
```
|
|
|
|
|
|
- TrustZone 配置(适用于 STM32L5 等支持型号):
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 配置安全/非安全区域
|
|
|
|
|
|
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);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 固件加密与防逆向
|
|
|
|
|
|
|
|
|
|
|
|
1. **AES 加密固件**,防止泄露源码逻辑
|
|
|
|
|
|
|
|
|
|
|
|
- 加密流程:
|
|
|
|
|
|
- 开发阶段:使用工具链(如 GCC 插件)加密固件。
|
|
|
|
|
|
- 部署阶段:Bootloader 解密后加载到 RAM 执行。
|
|
|
|
|
|
- 密钥管理:
|
|
|
|
|
|
- 主密钥存储在 OTP(一次性可编程)区域。
|
|
|
|
|
|
- 会话密钥通过主密钥派生(如 AES-KDF)
|
|
|
|
|
|
|
|
|
|
|
|
2. Flash 读保护(RDP)
|
|
|
|
|
|
|
|
|
|
|
|
| RDP 级别 | 保护效果 | 可逆性 |
|
|
|
|
|
|
|------------|------------------------------------------|-------------------------|
|
|
|
|
|
|
| Level 0 | 无保护(默认) | 是 |
|
|
|
|
|
|
| Level 1 | 禁止调试接口,Flash 只能运行不能读取 | 降级会擦除所有 Flash |
|
|
|
|
|
|
| Level 2 | 永久禁止调试接口和 Flash 读取 | 不可逆 |
|
|
|
|
|
|
|
|
|
|
|
|
3. 代码混淆技术
|
|
|
|
|
|
- 控制流平坦化:
|
|
|
|
|
|
|
|
|
|
|
|
将线性代码转换为基于状态机的结构,增加逆向难度。
|
|
|
|
|
|
|
|
|
|
|
|
- 指令替换:
|
|
|
|
|
|
|
|
|
|
|
|
用等效指令序列替换关键操作(如a+b替换为a-(-b))。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 权限隔离与防护
|
|
|
|
|
|
|
|
|
|
|
|
1. MPU(内存保护单元)配置
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 配置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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
2. TrustZone 安全域隔离
|
|
|
|
|
|
- 安全资产分类:
|
|
|
|
|
|
|
|
|
|
|
|
| 类别 | 示例 | 存储位置 |
|
|
|
|
|
|
|------------|------------------------------|------------------|
|
|
|
|
|
|
| 密钥 | TLS 私钥、加密密钥 | 安全 SRAM |
|
|
|
|
|
|
| 敏感算法 | 密码验证、加密函数 | 安全代码区 |
|
|
|
|
|
|
| 安全服务 | OTA 签名验证、证书管理 | 安全任务 |
|
|
|
|
|
|
|
|
|
|
|
|
- 安全 / 非安全通信:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 从非安全代码调用安全服务
|
|
|
|
|
|
__attribute__((section(".nonsecure_call")))
|
|
|
|
|
|
uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param2) {
|
|
|
|
|
|
// 通过SVC指令切换到安全模式
|
|
|
|
|
|
__asm("SVC #0");
|
|
|
|
|
|
// 返回值通过R0传递
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 Bootloader 开发建议
|
|
|
|
|
|
|
|
|
|
|
|
- 通用功能:下载、校验、重启、回滚
|
|
|
|
|
|
- 支持双分区升级(Slot A / Slot B)
|
|
|
|
|
|
- 防止电量中断、写失败后的砖机风险
|
|
|
|
|
|
- 可设置升级标志位(Upgrade Flag)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 🔚 小结
|
|
|
|
|
|
|
|
|
|
|
|
嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 🟣 第六层:网络通信与物联网协议(Network & IoT)
|
|
|
|
|
|
|
|
|
|
|
|
本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 串口通信与Socket通信
|
|
|
|
|
|
|
|
|
|
|
|
### 串口通信
|
|
|
|
|
|
- 串口基础:波特率、校验位、停止位、数据位
|
|
|
|
|
|
- 应用:模块通信、调试信息输出
|
|
|
|
|
|
- 中断方式与 DMA 模式接收
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 基本 UART 初始化
|
|
|
|
|
|
USART1->BRR = 0x1A1; // 设置波特率
|
|
|
|
|
|
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 使能收发
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Socket网络通信
|
|
|
|
|
|
- Socket 基础:TCP/UDP 区别、连接建立过程
|
|
|
|
|
|
- 在 ESP32 等模块中使用 LWIP 实现 TCP 客户端/服务器
|
|
|
|
|
|
```c
|
|
|
|
|
|
// TCP 客户端基础逻辑 (伪代码)
|
|
|
|
|
|
socket();
|
|
|
|
|
|
connect();
|
|
|
|
|
|
send();
|
|
|
|
|
|
recv();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 无线通信协议
|
|
|
|
|
|
|
|
|
|
|
|
### Wi-Fi
|
|
|
|
|
|
- ESP32 支持 STA / AP 模式,常用于联网或热点传输
|
|
|
|
|
|
- SmartConfig、ESP-NOW、HTTP Server 应用
|
|
|
|
|
|
|
|
|
|
|
|
### BLE(蓝牙低功耗)
|
|
|
|
|
|
- GATT 协议模型:服务、特征值、通知机制
|
|
|
|
|
|
- 使用 nRF52、ESP32 等平台进行广播、连接和数据交互
|
|
|
|
|
|
|
|
|
|
|
|
### LoRa / ZigBee
|
|
|
|
|
|
- 长距离通信:适用于室外传感器
|
|
|
|
|
|
- 使用 Semtech SX1278 / ZigBee 模块通信帧格式解析
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 物联网协议栈
|
|
|
|
|
|
|
|
|
|
|
|
### MQTT
|
|
|
|
|
|
- 轻量级发布-订阅协议,常用于设备与云平台交互
|
|
|
|
|
|
- QoS 等级、主题结构、客户端连接
|
|
|
|
|
|
- 常见平台支持:EMQX、OneNet、阿里云 IoT
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### HTTP / HTTPS
|
|
|
|
|
|
|
|
|
|
|
|
#### HTTP 请求方法(Methods)
|
|
|
|
|
|
|
|
|
|
|
|
* **GET**:请求指定资源。常用于获取数据。
|
|
|
|
|
|
* **POST**:提交数据到服务器,如表单、上传。
|
|
|
|
|
|
* **PUT**:上传数据,通常是更新资源。
|
|
|
|
|
|
* **DELETE**:删除指定资源。
|
|
|
|
|
|
* **HEAD**:与 GET 类似,但不返回响应体。
|
|
|
|
|
|
* **OPTIONS**:返回服务器支持的请求方法。
|
|
|
|
|
|
* **TRACE**:诊断请求响应路径,回显请求报文。
|
|
|
|
|
|
* **CONNECT**:用于建立隧道(如 HTTPS 代理)。
|
|
|
|
|
|
|
|
|
|
|
|
#### HTTP 状态码
|
|
|
|
|
|
|
|
|
|
|
|
| 分类 | 范围 | 含义 |
|
|
|
|
|
|
| --- | -------- | -------- |
|
|
|
|
|
|
| 1xx | 100\~199 | 接收中,继续处理 |
|
|
|
|
|
|
| 2xx | 200\~299 | 请求成功 |
|
|
|
|
|
|
| 3xx | 300\~399 | 重定向或更多操作 |
|
|
|
|
|
|
| 4xx | 400\~499 | 客户端错误 |
|
|
|
|
|
|
| 5xx | 500\~599 | 服务器错误 |
|
|
|
|
|
|
|
|
|
|
|
|
**部分状态码示例:**
|
|
|
|
|
|
|
|
|
|
|
|
* 200 OK
|
|
|
|
|
|
* 201 Created
|
|
|
|
|
|
* 204 No Content
|
|
|
|
|
|
* 301 Moved Permanently
|
|
|
|
|
|
* 302 Found
|
|
|
|
|
|
* 304 Not Modified
|
|
|
|
|
|
* 400 Bad Request
|
|
|
|
|
|
* 401 Unauthorized
|
|
|
|
|
|
* 403 Forbidden
|
|
|
|
|
|
* 404 Not Found
|
|
|
|
|
|
* 500 Internal Server Error
|
|
|
|
|
|
* 502 Bad Gateway
|
|
|
|
|
|
|
|
|
|
|
|
#### HTTP 长连接与短连接
|
|
|
|
|
|
|
|
|
|
|
|
* **HTTP/1.0** 默认使用短连接(每次请求后断开)
|
|
|
|
|
|
* **HTTP/1.1** 默认使用长连接(`Connection: keep-alive`)
|
|
|
|
|
|
|
|
|
|
|
|
#### HTTP 请求报文格式
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
GET /hello.htm HTTP/1.1
|
|
|
|
|
|
Host: www.example.com
|
|
|
|
|
|
User-Agent: Mozilla/5.0
|
|
|
|
|
|
Accept: */*
|
|
|
|
|
|
Connection: Keep-Alive
|
|
|
|
|
|
... 其他头部字段
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### HTTP 响应报文格式
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
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 通信过程
|
|
|
|
|
|
|
|
|
|
|
|
HTTPS = HTTP + TLS/SSL 加密
|
|
|
|
|
|
|
|
|
|
|
|
#### 通信过程:
|
|
|
|
|
|
|
|
|
|
|
|
1. 客户端发起 HTTPS 请求(握手开始)
|
|
|
|
|
|
2. 服务器返回证书(含公钥)
|
|
|
|
|
|
3. 客户端验证证书合法性
|
|
|
|
|
|
4. 客户端生成随机对称密钥(用公钥加密传给服务器)
|
|
|
|
|
|
5. 双方使用对称密钥开始加密通信
|
|
|
|
|
|
|
|
|
|
|
|
#### 对称加密(加解密使用同一密钥)
|
|
|
|
|
|
|
|
|
|
|
|
* **DES**(56 位)
|
|
|
|
|
|
* **AES**(128/192/256 位)
|
|
|
|
|
|
|
|
|
|
|
|
#### 非对称加密(加密/解密使用公钥/私钥)
|
|
|
|
|
|
|
|
|
|
|
|
* **RSA**:支持加密、签名
|
|
|
|
|
|
* **DSA**:只支持签名(效率高,但不用于加解密)
|
|
|
|
|
|
|
|
|
|
|
|
#### 哈希算法(不可逆)
|
|
|
|
|
|
|
|
|
|
|
|
* **MD5**(128 位)
|
|
|
|
|
|
* **SHA-1**(160 位)
|
|
|
|
|
|
* **SHA-256**(256 位)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### CoAP / LwM2M
|
|
|
|
|
|
- 适合低功耗终端的简化协议,UDP 传输,可压缩
|
|
|
|
|
|
- 用于 NB-IoT、LwIP 等网络栈中
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 安全通信实践
|
|
|
|
|
|
1. TLS 握手优化
|
|
|
|
|
|
- 预共享密钥(PSK)模式:
|
|
|
|
|
|
减少证书验证开销,适合资源受限设备。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// mbed TLS配置PSK
|
|
|
|
|
|
mbedtls_ssl_config_set_psk(&ssl_conf,
|
|
|
|
|
|
psk, // 预共享密钥
|
|
|
|
|
|
psk_length,
|
|
|
|
|
|
identity, // 身份标识
|
|
|
|
|
|
strlen(identity));
|
|
|
|
|
|
```
|
|
|
|
|
|
2. 证书管理方案
|
|
|
|
|
|
- 证书存储:
|
|
|
|
|
|
- 根证书存储在安全 Flash 区域。
|
|
|
|
|
|
- 设备证书通过安全通道动态更新。
|
|
|
|
|
|
- 证书验证:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 验证服务器证书链
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 安全测试
|
|
|
|
|
|
|
|
|
|
|
|
1. 固件逆向分析
|
|
|
|
|
|
- 工具链:
|
|
|
|
|
|
- Ghidra:反编译二进制文件,生成 C 语言伪代码。
|
|
|
|
|
|
- IDA Pro:专业逆向工程工具,支持 ARM 架构。
|
|
|
|
|
|
- 防御措施:
|
|
|
|
|
|
- 固件加密:使用 AES-256 加密整个固件。
|
|
|
|
|
|
- 反调试机制:检测调试接口是否被连接。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 检测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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. 侧信道攻击防护
|
|
|
|
|
|
- 电源分析攻击:
|
|
|
|
|
|
通过测量设备功耗分析加密密钥。
|
|
|
|
|
|
- 防护措施:
|
|
|
|
|
|
常量时间实现:避免条件分支依赖密钥值。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 常量时间比较(防止时序攻击)
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## TCP/IP 协议栈基础与嵌入式实现
|
|
|
|
|
|
|
|
|
|
|
|
#### TCP/IP 协议栈分层结构(四层模型)
|
|
|
|
|
|
|
|
|
|
|
|
| 层级 | 协议/组件 | 功能说明 |
|
|
|
|
|
|
|----------------|----------------------------|----------------------------------|
|
|
|
|
|
|
| 应用层 | HTTP, MQTT, CoAP, DNS | 面向用户的协议 |
|
|
|
|
|
|
| 传输层 | TCP, UDP | 数据传输可靠性与端口管理 |
|
|
|
|
|
|
| 网络层 | IP, ICMP, ARP | 地址与路由 |
|
|
|
|
|
|
| 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### TCP 与 UDP 区别
|
|
|
|
|
|
|
|
|
|
|
|
| 特性 | TCP | UDP |
|
|
|
|
|
|
|----------------|----------------------------|----------------------------|
|
|
|
|
|
|
| 是否连接 | 是(面向连接) | 否(无连接) |
|
|
|
|
|
|
| 是否可靠 | 是(有重传、确认) | 否(可能丢包) |
|
|
|
|
|
|
| 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
|
|
|
|
|
|
| 开销 | 较大(握手、窗口等) | 较小(直接发送) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 嵌入式 TCP/IP 协议栈组件
|
|
|
|
|
|
|
|
|
|
|
|
- **LwIP(Lightweight IP)**
|
|
|
|
|
|
- 开源轻量级 TCP/IP 协议栈
|
|
|
|
|
|
- 支持 TCP/UDP/IP/DNS/DHCP 等
|
|
|
|
|
|
- 常用于 STM32、ESP32、RT-Thread 中
|
|
|
|
|
|
- **uIP(micro IP)**
|
|
|
|
|
|
- 更轻量,适合资源极小的 MCU
|
|
|
|
|
|
- **FreeRTOS+TCP**
|
|
|
|
|
|
- 与 FreeRTOS 配套的 TCP/IP 协议栈
|
|
|
|
|
|
- **Nut/Net、CycloneTCP**:其他常用协议栈
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 嵌入式 TCP/IP 通信流程(以 LwIP 为例)
|
|
|
|
|
|
|
|
|
|
|
|
1. **初始化网络接口**:配置 IP、MAC、网关
|
|
|
|
|
|
2. **创建 socket 套接字**:TCP 或 UDP
|
|
|
|
|
|
3. **建立连接 / 绑定端口**
|
|
|
|
|
|
4. **接收/发送数据**:`recv()`, `send()`
|
|
|
|
|
|
5. **关闭连接**:`close()`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 常用 API 示例(LwIP BSD socket)
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### DHCP / DNS / ICMP 说明
|
|
|
|
|
|
|
|
|
|
|
|
* **DHCP**:动态分配 IP(LwIP 可配置)
|
|
|
|
|
|
* **DNS**:域名解析,调用 `gethostbyname()` 等
|
|
|
|
|
|
* **ICMP**:如 `ping` 实现通信测试
|
|
|
|
|
|
|
|
|
|
|
|
#### 推荐阅读资料
|
|
|
|
|
|
|
|
|
|
|
|
* [LwIP 官方文档](https://savannah.nongnu.org/projects/lwip/)
|
|
|
|
|
|
* [FreeRTOS+TCP 文档](https://freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_TCP/index.html)
|
|
|
|
|
|
* [TCP/IP Illustrated (Vol 1)](https://book.douban.com/subject/1088054/)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 云平台接入 & OTA 实现
|
|
|
|
|
|
|
|
|
|
|
|
### 云平台对接
|
|
|
|
|
|
- 主流平台:阿里云 IoT、腾讯连连、OneNet、ThingsBoard
|
|
|
|
|
|
- 认证方式:三元组 / MQTT 密钥 / TLS 证书
|
|
|
|
|
|
|
|
|
|
|
|
### OTA 升级机制
|
|
|
|
|
|
> 支持远程更新嵌入式系统的固件版本
|
|
|
|
|
|
1. 双分区升级架构
|
|
|
|
|
|
|
|
|
|
|
|
```plaintext
|
|
|
|
|
|
Flash布局:
|
|
|
|
|
|
+-------------------+ 0x08000000
|
|
|
|
|
|
| Bootloader |
|
|
|
|
|
|
+-------------------+ 0x08010000
|
|
|
|
|
|
| Application Slot A|
|
|
|
|
|
|
+-------------------+ 0x08040000
|
|
|
|
|
|
| Application Slot B|
|
|
|
|
|
|
+-------------------+ 0x08070000
|
|
|
|
|
|
| Configuration Area|
|
|
|
|
|
|
+-------------------+
|
|
|
|
|
|
```
|
|
|
|
|
|
2. 升级状态机实现
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
// 其他状态处理...
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
3. 失败回滚机制
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 启动时验证应用完整性
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
#### ✅ OTA 流程核心步骤
|
|
|
|
|
|
|
|
|
|
|
|
1. 检查版本更新(HTTP/MQTT 下载 manifest)
|
|
|
|
|
|
2. 下载固件(二进制)
|
|
|
|
|
|
3. 存储到备份区(Backup Slot)
|
|
|
|
|
|
4. 校验 CRC/Hash / 签名
|
|
|
|
|
|
5. 设置 Bootloader 标志位并重启
|
|
|
|
|
|
6. Bootloader 引导进入新固件
|
|
|
|
|
|
7. 若失败则回滚(Fail-safe 机制)
|
|
|
|
|
|
|
|
|
|
|
|
#### ✅ 常用升级协议
|
|
|
|
|
|
|
|
|
|
|
|
- HTTP / HTTPS
|
|
|
|
|
|
- MQTT + Base64 二进制块传输
|
|
|
|
|
|
- CoAP(轻量级)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 推荐学习顺序
|
|
|
|
|
|
|
|
|
|
|
|
1. 学习 UART 通信与基本网络 socket 原理
|
|
|
|
|
|
2. 掌握 Wi-Fi / BLE 开发流程(推荐 ESP32/nRF52)
|
|
|
|
|
|
3. 理解 MQTT 协议与平台接入逻辑
|
|
|
|
|
|
4. 实践 OTA 升级流程,构建远程维护能力
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 📌 常见问题 FAQ
|
|
|
|
|
|
|
|
|
|
|
|
| 问题 | 解答 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| MQTT 断线如何重连? | 设置心跳机制与 reconnect 回调逻辑 |
|
|
|
|
|
|
| OTA 更新失败怎么办? | 回退机制 + 双镜像分区设计 |
|
|
|
|
|
|
| CoAP 和 MQTT 有何区别? | CoAP 基于 UDP,适合低功耗设备;MQTT 基于 TCP,稳定性好 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ⚡ 第七层:调试与性能优化
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 常用调试工具
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 JTAG / SWD 接口
|
|
|
|
|
|
|
|
|
|
|
|
- **JTAG**(Joint Test Action Group)标准调试接口,支持多设备级联。
|
|
|
|
|
|
- **SWD**(Serial Wire Debug)是 ARM Cortex 系列的简化调试协议,仅使用两根线(SWDIO, SWCLK),适用于资源受限设备。
|
|
|
|
|
|
|
|
|
|
|
|
**JTAG 与 SWD 接口对比**
|
|
|
|
|
|
| 特性 | JTAG | SWD |
|
|
|
|
|
|
|------------|----------------------------------------------|--------------------------------|
|
|
|
|
|
|
| 引脚数 | 5 线(TMS、TCK、TDI、TDO、TRST) | 2 线(SWDIO、SWCLK) |
|
|
|
|
|
|
| 速度 | 中低速(典型 1-10MHz) | 高速(可达 50MHz 以上) |
|
|
|
|
|
|
| 占用资源 | 高(需多个 GPIO) | 低(仅 2 个 GPIO) |
|
|
|
|
|
|
| 级联能力 | 支持多设备(通过 TAP 控制器) | 不支持级联 |
|
|
|
|
|
|
| 适用场景 | 复杂芯片调试(如 FPGA) | 嵌入式 MCU(如 STM32) |
|
|
|
|
|
|
|
|
|
|
|
|
- SWD 调试配置示例(STM32CubeMX):
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 使能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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 GDB + OpenOCD 调试
|
|
|
|
|
|
|
|
|
|
|
|
- **GDB**:GNU 调试器,支持断点、单步、查看变量等操作。
|
|
|
|
|
|
- **OpenOCD**:Open On-Chip Debugger,用于连接 GDB 和硬件调试接口(如 ST-Link)。
|
|
|
|
|
|
|
|
|
|
|
|
关键命令详解:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 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)。
|
|
|
|
|
|
- 逻辑分析仪典型场景:
|
|
|
|
|
|
- SPI 通信时序分析(验证 CPOL/CPHA 设置)。
|
|
|
|
|
|
- I2C 总线竞争检测(查看 ACK/NACK 信号)。
|
|
|
|
|
|
- UART 波特率校准(测量位宽计算实际波特率)。
|
|
|
|
|
|
|
|
|
|
|
|
- **示波器**:查看模拟信号、电压、电流变化。对调试电源问题、干扰、PWM波形等极为重要。
|
|
|
|
|
|
- 示波器关键参数:
|
|
|
|
|
|
- 带宽:至少为信号最高频率的 3-5 倍(如测量 1MHz PWM 需 5MHz 带宽)。
|
|
|
|
|
|
- 采样率:至少为信号最高频率的 10 倍(如 1MHz 信号需 10MSa/s 采样率)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 调试 PWM 信号示例:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 配置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 / 串口调试
|
|
|
|
|
|
|
|
|
|
|
|
- 常用 `printf()` 输出信息到串口查看程序执行流程。
|
|
|
|
|
|
- 可与 RTT(Real Time Transfer)配合实现非阻塞调试输出。
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 断点调试
|
|
|
|
|
|
|
|
|
|
|
|
- 在 IDE(如 STM32CubeIDE)中设置断点暂停程序运行,查看寄存器、内存、变量。
|
|
|
|
|
|
- 适合调试初始化流程、外设配置错误等问题。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 性能与功耗优化
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 FreeRTOS Trace 与分析工具
|
|
|
|
|
|
|
|
|
|
|
|
- 使用 FreeRTOS+Trace 工具(Percepio)记录任务切换、上下文切换、CPU 占用率。
|
|
|
|
|
|
- 可通过 `vTraceEnable()` 开启追踪。
|
|
|
|
|
|
- 跟踪点(Trace Point):
|
|
|
|
|
|
在关键代码位置插入记录函数(如任务切换、中断处理)。
|
|
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 自定义跟踪点示例
|
|
|
|
|
|
#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)
|
|
|
|
|
|
```
|
|
|
|
|
|
- 数据存储:
|
|
|
|
|
|
- 环形缓冲区:存储跟踪事件,避免内存溢出。
|
|
|
|
|
|
- 示例配置:
|
|
|
|
|
|
```c
|
|
|
|
|
|
#define configUSE_TRACE_FACILITY 1 // 启用跟踪功能
|
|
|
|
|
|
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 启用统计功能
|
|
|
|
|
|
#define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 SystemView 分析工具
|
|
|
|
|
|
|
|
|
|
|
|
- SEGGER 提供的实时系统分析工具。
|
|
|
|
|
|
- 与 FreeRTOS 集成,通过 SWO 接口获取任务执行时间、事件追踪等信息。
|
|
|
|
|
|
- 关键指标解读:
|
|
|
|
|
|
- 任务执行时间:各任务 CPU 占用百分比。
|
|
|
|
|
|
- 上下文切换频率:过高表示任务调度不合理。
|
|
|
|
|
|
- 中断响应时间:从中断触发到 ISR 执行的时间差。
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 STM32CubeMonitor
|
|
|
|
|
|
|
|
|
|
|
|
- ST 官方提供的可视化变量监控与数据图示工具。
|
|
|
|
|
|
- 可用于实时观察寄存器值、ADC 曲线、温度、电压等参数。
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 低功耗模式优化
|
|
|
|
|
|
|
|
|
|
|
|
#### Cortex-M 支持三种主要低功耗模式:
|
|
|
|
|
|
|
|
|
|
|
|
| 模式 | 唤醒时间 | 功耗 | 保留内容 |
|
|
|
|
|
|
|----------|-----------|----------|--------------------------------|
|
|
|
|
|
|
| Sleep | 数 μs | 几 mA | CPU 寄存器、SRAM 内容 |
|
|
|
|
|
|
| Stop | 几十 μs | 几 μA | SRAM 内容、部分寄存器 |
|
|
|
|
|
|
| Standby | 几 ms | 几十 nA | 仅备份寄存器(如 RTC) |
|
|
|
|
|
|
|
|
|
|
|
|
#### 优化技巧:
|
|
|
|
|
|
|
|
|
|
|
|
- 外设时钟管理:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 禁用未使用的外设时钟
|
|
|
|
|
|
__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 唤醒配置:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 配置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();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 调试与优化实战案例
|
|
|
|
|
|
1. 内存泄漏检测
|
|
|
|
|
|
- 静态检测工具:
|
|
|
|
|
|
- CppCheck:检查内存分配与释放是否匹配。
|
|
|
|
|
|
- Valgrind(需模拟器环境):检测动态内存问题。
|
|
|
|
|
|
- 自定义内存管理钩子:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 记录内存分配/释放情况
|
|
|
|
|
|
void *pvPortMalloc( size_t xWantedSize ) {
|
|
|
|
|
|
void *pvReturn = NULL;
|
|
|
|
|
|
vTaskSuspendAll();
|
|
|
|
|
|
{
|
|
|
|
|
|
// 记录分配信息(如分配地址、大小、时间)
|
|
|
|
|
|
pvReturn = prvHeapAllocateMemory( xWantedSize );
|
|
|
|
|
|
vRecordMemoryAllocation(pvReturn, xWantedSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
xTaskResumeAll();
|
|
|
|
|
|
return pvReturn;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
2. 中断风暴处理
|
|
|
|
|
|
- 问题现象:CPU 占用率 100%,系统无响应。
|
|
|
|
|
|
- 排查步骤:
|
|
|
|
|
|
- 使用调试器暂停 CPU,查看当前执行的代码(通常是某个 ISR)。
|
|
|
|
|
|
- 检查中断触发条件(如 GPIO 引脚是否抖动)。
|
|
|
|
|
|
- 添加中断计数统计:
|
|
|
|
|
|
- 解决方案:
|
|
|
|
|
|
- 添加软件消抖:
|
|
|
|
|
|
```c
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 面试高频问题
|
|
|
|
|
|
1. JTAG 与 SWD 的优缺点:
|
|
|
|
|
|
|
|
|
|
|
|
- JTAG:兼容性强,支持多设备级联,但占用引脚多;SWD:引脚少,速度快,适合嵌入式设备。
|
|
|
|
|
|
|
|
|
|
|
|
2. 如何优化 RTOS 系统的 CPU 使用率:
|
|
|
|
|
|
- 减少空闲任务 CPU 占用(通过configIDLE_SHOULD_YIELD配置)。
|
|
|
|
|
|
- 优化中断处理时间,避免长中断服务程序。
|
|
|
|
|
|
- 使用低功耗模式,在空闲时进入 Sleep/Stop 状态。
|
|
|
|
|
|
|
|
|
|
|
|
3. 调试时发现程序跑飞,如何定位问题:
|
|
|
|
|
|
|
|
|
|
|
|
- 设置看门狗定时器,捕获异常复位。
|
|
|
|
|
|
- 使用硬件断点,检查关键函数是否被正确调用。
|
|
|
|
|
|
- 添加断言(assert),验证关键条件。
|
|
|
|
|
|
|
|
|
|
|
|
4. 如何测量代码执行时间:
|
|
|
|
|
|
- 使用高精度定时器(如 STM32 的 DWT_CYCCNT)。
|
|
|
|
|
|
- SystemView 等工具通过 SWO 接口获取精确时间。
|
|
|
|
|
|
|
|
|
|
|
|
### 学习资源推荐
|
|
|
|
|
|
1. 调试工具文档:
|
|
|
|
|
|
- [GDB 官方文档](https://sourceware.org/gdb/documentation/)
|
|
|
|
|
|
- [OpenOCD 用户手册](https://link.wtturl.cn/?target=http%3A%2F%2Fopenocd.org%2Fdoc%2Fpdf%2Fopenocd.pdf&scene=im&aid=497858&lang=zh)
|
|
|
|
|
|
|
|
|
|
|
|
2. 性能分析教程:
|
|
|
|
|
|
- [FreeRTOS Trace 可视化指南](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_Trace/trace_introduction.html)
|
|
|
|
|
|
- [SEGGER SystemView 应用笔记](https://link.wtturl.cn/?target=https%3A%2F%2Fwww.segger.com%2Fproducts%2Fdevelopment-tools%2Fsystemview%2F&scene=im&aid=497858&lang=zh)
|
|
|
|
|
|
|
|
|
|
|
|
3. 低功耗设计指南:
|
|
|
|
|
|
- [STM32 低功耗应用手册](https://link.wtturl.cn/?target=https%3A%2F%2Fwww.st.com%2Fresource%2Fen%2Fapplication_note%2Fdm00071990-stm32-microcontroller-lowpower-modes-stmicroelectronics.pdf&scene=im&aid=497858&lang=zh)
|
|
|
|
|
|
- [Cortex-M 低功耗技术白皮书](https://developer.arm.com/documentation/100166/latest/)
|
|
|
|
|
|
|
|
|
|
|
|
4. 实践项目:
|
|
|
|
|
|
- 在 STM32 上实现功耗测量(使用外部电流表或内部 ADC 监测 VDD 电流)。
|
|
|
|
|
|
- 使用 SystemView 分析 FreeRTOS 任务调度行为。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# 📦 第八层:项目实战与工具链
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 工程管理
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 Git 版本控制
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **Git 分支策略**
|
|
|
|
|
|
- **主干分支(main/master)**:
|
|
|
|
|
|
永远代表可发布的稳定版本,仅接受通过CI/CD验证的代码。
|
|
|
|
|
|
- **开发分支(develop)**:
|
|
|
|
|
|
集成所有新功能的开发,是日常开发的基础分支。
|
|
|
|
|
|
- **特性分支(feature/*)**:
|
|
|
|
|
|
从develop分支创建,用于开发单个新功能或修复问题,完成后合并回develop。
|
|
|
|
|
|
- **发布分支(release/*)**:
|
|
|
|
|
|
从develop分支创建,用于准备发布版本,进行最后的测试和Bug修复。
|
|
|
|
|
|
- **热修复分支(hotfix/*)**:
|
|
|
|
|
|
从main分支创建,用于紧急修复生产环境问题,修复后合并回main和develop。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **提交规范**
|
|
|
|
|
|
采用Conventional Commits规范:
|
|
|
|
|
|
```
|
|
|
|
|
|
<类型>[可选范围]: <描述>
|
|
|
|
|
|
|
|
|
|
|
|
[可选正文]
|
|
|
|
|
|
|
|
|
|
|
|
[可选脚注]
|
|
|
|
|
|
```
|
|
|
|
|
|
- **常见类型**:
|
|
|
|
|
|
- `feat`:新功能
|
|
|
|
|
|
- `fix`:修复Bug
|
|
|
|
|
|
- `docs`:文档更新
|
|
|
|
|
|
- `style`:代码格式调整(不影响功能)
|
|
|
|
|
|
- `refactor`:代码重构
|
|
|
|
|
|
- `test`:添加或修改测试
|
|
|
|
|
|
- `chore`:构建或辅助工具的变动
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **标签管理**
|
|
|
|
|
|
使用语义化版本(SemVer)打标签:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 创建标签
|
|
|
|
|
|
git tag v1.0.0
|
|
|
|
|
|
|
|
|
|
|
|
# 推送标签到远程
|
|
|
|
|
|
git push origin v1.0.0
|
|
|
|
|
|
|
|
|
|
|
|
# 查看所有标签
|
|
|
|
|
|
git tag -l
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 Makefile、CMake 构建工具
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **Makefile 基础**
|
|
|
|
|
|
- **简单示例**:
|
|
|
|
|
|
```makefile
|
|
|
|
|
|
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)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **CMake 高级应用**
|
|
|
|
|
|
- **跨平台配置**:
|
|
|
|
|
|
```cmake
|
|
|
|
|
|
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
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 Jenkins/GitHub Actions CI 流水线
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **GitHub Actions 配置**
|
|
|
|
|
|
- **编译与测试工作流**:
|
|
|
|
|
|
```yaml
|
|
|
|
|
|
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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **Jenkins 集成**
|
|
|
|
|
|
- **构建脚本示例**:
|
|
|
|
|
|
```groovy
|
|
|
|
|
|
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'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 项目实践
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 嵌入式应用框架设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **分层架构**
|
|
|
|
|
|
```
|
|
|
|
|
|
+----------------------+
|
|
|
|
|
|
| 应用层 |
|
|
|
|
|
|
| (业务逻辑、算法) |
|
|
|
|
|
|
+----------------------+
|
|
|
|
|
|
| 服务层 |
|
|
|
|
|
|
| (任务管理、事件) |
|
|
|
|
|
|
+----------------------+
|
|
|
|
|
|
| 驱动层 |
|
|
|
|
|
|
| (硬件抽象、BSP) |
|
|
|
|
|
|
+----------------------+
|
|
|
|
|
|
| 硬件层 |
|
|
|
|
|
|
| (MCU、外设) |
|
|
|
|
|
|
+----------------------+
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **组件化设计**
|
|
|
|
|
|
- **核心组件**:
|
|
|
|
|
|
- 任务管理器:负责任务创建、调度和通信。
|
|
|
|
|
|
- 事件系统:处理异步事件和回调。
|
|
|
|
|
|
- 配置管理:加载和保存系统配置。
|
|
|
|
|
|
- 日志系统:分级日志记录和输出。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **代码结构示例**
|
|
|
|
|
|
```
|
|
|
|
|
|
project/
|
|
|
|
|
|
├── app/ # 应用层
|
|
|
|
|
|
│ ├── main.c # 主程序入口
|
|
|
|
|
|
│ ├── modules/ # 功能模块
|
|
|
|
|
|
│ │ ├── sensor/ # 传感器处理
|
|
|
|
|
|
│ │ ├── comm/ # 通信处理
|
|
|
|
|
|
│ │ └── control/ # 控制逻辑
|
|
|
|
|
|
│ └── config/ # 配置文件
|
|
|
|
|
|
├── services/ # 服务层
|
|
|
|
|
|
│ ├── task_mgr/ # 任务管理器
|
|
|
|
|
|
│ ├── event/ # 事件系统
|
|
|
|
|
|
│ └── utils/ # 工具函数
|
|
|
|
|
|
├── drivers/ # 驱动层
|
|
|
|
|
|
│ ├── bsp/ # 板级支持包
|
|
|
|
|
|
│ ├── hal/ # 硬件抽象层
|
|
|
|
|
|
│ └── periph/ # 外设驱动
|
|
|
|
|
|
└── build/ # 构建系统
|
|
|
|
|
|
├── cmake/ # CMake配置
|
|
|
|
|
|
└── Makefile # Makefile
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 通用 BSP 构建
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **设计原则**
|
|
|
|
|
|
- **硬件无关性**:上层代码不直接访问硬件寄存器。
|
|
|
|
|
|
- **可移植性**:相同功能代码可在不同硬件平台复用。
|
|
|
|
|
|
- **配置化**:通过配置文件而非修改代码适配不同硬件。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **BSP 实现示例**
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 模块化驱动结构
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **驱动分层**
|
|
|
|
|
|
- **硬件层**:直接操作寄存器的低级驱动。
|
|
|
|
|
|
- **抽象层**:提供统一接口的高级驱动。
|
|
|
|
|
|
- **适配层**:连接抽象层和硬件层的中间层。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **SPI驱动示例**
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 OTA 升级方案设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **双分区架构**
|
|
|
|
|
|
```
|
|
|
|
|
|
Flash布局:
|
|
|
|
|
|
+-------------------+ 0x08000000
|
|
|
|
|
|
| Bootloader | (80KB)
|
|
|
|
|
|
+-------------------+ 0x08014000
|
|
|
|
|
|
| Application A | (448KB)
|
|
|
|
|
|
+-------------------+ 0x08084000
|
|
|
|
|
|
| Application B | (448KB)
|
|
|
|
|
|
+-------------------+ 0x08104000
|
|
|
|
|
|
| Configuration | (16KB)
|
|
|
|
|
|
+-------------------+
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **OTA状态机**
|
|
|
|
|
|
```c
|
|
|
|
|
|
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;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **OTA流程**
|
|
|
|
|
|
1. **检查更新**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. **下载固件**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. **验证与应用**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
# 开发工具链安装指南
|
|
|
|
|
|
## 1. **IDE推荐**
|
|
|
|
|
|
|
|
|
|
|
|
### VS Code + PlatformIO
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [VS Code](https://code.visualstudio.com/)
|
|
|
|
|
|
- [PlatformIO](https://platformio.org/)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 下载并安装 [VS Code](https://code.visualstudio.com/Download)
|
|
|
|
|
|
2. 打开VS Code,点击左侧扩展图标(或按 `Ctrl+Shift+X`)
|
|
|
|
|
|
3. 搜索并安装 **PlatformIO IDE** 扩展
|
|
|
|
|
|
4. 安装完成后,重启VS Code
|
|
|
|
|
|
5. PlatformIO会自动安装所需的工具链和依赖
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
打开VS Code,点击左下角的 **PlatformIO Home** 图标,若能正常打开则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### STM32CubeIDE
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 访问官网,点击 **Get Software** 下载对应操作系统的安装包
|
|
|
|
|
|
2. 运行安装程序,按照向导完成安装
|
|
|
|
|
|
3. 安装过程中会自动下载并配置STM32CubeMX
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### CLion
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [CLion](https://www.jetbrains.com/clion/)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 下载并安装 [CLion](https://www.jetbrains.com/clion/download/)
|
|
|
|
|
|
2. 安装CMake和MinGW(Windows用户需要):
|
|
|
|
|
|
- CMake:从 [官网](https://cmake.org/download/) 下载并安装
|
|
|
|
|
|
- MinGW:推荐使用 [MSYS2](https://www.msys2.org/) 安装
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 2. **调试工具**
|
|
|
|
|
|
|
|
|
|
|
|
### OpenOCD
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [OpenOCD](http://openocd.org/)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
- **Windows**:
|
|
|
|
|
|
1. 从 [GNU MCU Eclipse](https://github.com/gnu-mcu-eclipse/openocd/releases) 下载预编译二进制包
|
|
|
|
|
|
2. 解压到指定目录(如 `C:\openocd`)
|
|
|
|
|
|
3. 将 `bin` 目录添加到系统环境变量
|
|
|
|
|
|
|
|
|
|
|
|
- **Linux**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
sudo apt-get install openocd # Ubuntu/Debian
|
|
|
|
|
|
sudo yum install openocd # CentOS/RHEL
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **macOS**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
brew install open-ocd
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
在终端中运行 `openocd --version`,若显示版本信息则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### GDB
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [GDB](https://www.gnu.org/software/gdb/)
|
|
|
|
|
|
- [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 下载并安装 [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
|
|
|
|
|
|
2. 将 `bin` 目录添加到系统环境变量
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
在终端中运行 `arm-none-eabi-gdb --version`,若显示版本信息则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ST-Link/V2
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [ST-Link](https://www.st.com/en/development-tools/st-link-v2.html)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
- **Windows**:
|
|
|
|
|
|
1. 从 [ST官网](https://www.st.com/en/development-tools/stsw-link004.html) 下载并安装ST-Link驱动
|
|
|
|
|
|
2. 安装完成后,将ST-Link/V2调试器连接到电脑
|
|
|
|
|
|
|
|
|
|
|
|
- **Linux**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
sudo apt-get install stlink-tools # Ubuntu/Debian
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
在终端中运行 `st-info --version`,若显示版本信息则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 3. **静态代码分析**
|
|
|
|
|
|
|
|
|
|
|
|
### CppCheck
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [CppCheck](https://cppcheck.sourceforge.io/)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
- **Windows**:
|
|
|
|
|
|
1. 从 [官网](https://cppcheck.sourceforge.io/) 下载安装包
|
|
|
|
|
|
2. 运行安装程序,按照向导完成安装
|
|
|
|
|
|
|
|
|
|
|
|
- **Linux**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
sudo apt-get install cppcheck # Ubuntu/Debian
|
|
|
|
|
|
sudo yum install cppcheck # CentOS/RHEL
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **macOS**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
brew install cppcheck
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
在终端中运行 `cppcheck --version`,若显示版本信息则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Clang-Tidy
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
- **Windows**:
|
|
|
|
|
|
1. 安装 [LLVM](https://releases.llvm.org/download.html)
|
|
|
|
|
|
2. Clang-Tidy会随LLVM一起安装
|
|
|
|
|
|
|
|
|
|
|
|
- **Linux**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
sudo apt-get install clang-tidy # Ubuntu/Debian
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **macOS**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
brew install llvm
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
在终端中运行 `clang-tidy --version`,若显示版本信息则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### SonarQube
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [SonarQube](https://www.sonarqube.org/)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 下载并安装 [Docker](https://www.docker.com/get-started)
|
|
|
|
|
|
2. 运行SonarQube容器:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
docker run -d --name sonarqube -p 9000:9000 sonarqube
|
|
|
|
|
|
```
|
|
|
|
|
|
3. 访问 [http://localhost:9000](http://localhost:9000),使用默认账号(admin/admin)登录
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
在浏览器中打开 [http://localhost:9000](http://localhost:9000),若能看到SonarQube界面则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 4. **单元测试**
|
|
|
|
|
|
|
|
|
|
|
|
### Unity
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [Unity](https://github.com/ThrowTheSwitch/Unity)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 从GitHub下载Unity源码:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git clone https://github.com/ThrowTheSwitch/Unity.git
|
|
|
|
|
|
```
|
|
|
|
|
|
2. 将 `src` 目录添加到项目的头文件搜索路径
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### CMock
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [CMock](https://github.com/ThrowTheSwitch/CMock)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 从GitHub下载CMock源码:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git clone https://github.com/ThrowTheSwitch/CMock.git
|
|
|
|
|
|
```
|
|
|
|
|
|
2. 将 `src` 目录添加到项目的头文件搜索路径
|
|
|
|
|
|
|
|
|
|
|
|
**验证安装**:
|
|
|
|
|
|
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Google Test
|
|
|
|
|
|
|
|
|
|
|
|
**官网链接**:
|
|
|
|
|
|
- [Google Test](https://github.com/google/googletest)
|
|
|
|
|
|
|
|
|
|
|
|
**安装步骤**:
|
|
|
|
|
|
1. 从GitHub下载Google Test源码:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git clone https://github.com/google/googletest.git
|
|
|
|
|
|
```
|
|
|
|
|
|
2. 使用CMake构建并安装:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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构建并安装 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# 第九层:2025 新趋势
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ AI on MCU / Edge AI
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 TinyML / TensorFlow Lite Micro
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **概念与优势**
|
|
|
|
|
|
- **TinyML**:将机器学习模型部署到资源受限的微控制器(MCU)上,实现边缘智能。
|
|
|
|
|
|
- **优势**:
|
|
|
|
|
|
- **低延迟**:本地处理数据,无需云端交互。
|
|
|
|
|
|
- **低功耗**:适合电池供电的物联网设备。
|
|
|
|
|
|
- **隐私保护**:敏感数据无需上传。
|
|
|
|
|
|
- **离线运行**:在网络中断时仍能工作。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **开发流程**
|
|
|
|
|
|
1. **模型训练**:
|
|
|
|
|
|
使用TensorFlow/Keras等工具在PC上训练模型。
|
|
|
|
|
|
```python
|
|
|
|
|
|
# 简单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)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. **模型量化**:
|
|
|
|
|
|
将浮点模型转换为整数模型,减少内存占用和计算量。
|
|
|
|
|
|
```python
|
|
|
|
|
|
converter = tf.lite.TFLiteConverter.from_keras_model(model)
|
|
|
|
|
|
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
|
|
|
|
|
tflite_model = converter.convert()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. **模型部署**:
|
|
|
|
|
|
将量化后的模型转换为C数组,集成到MCU项目中。
|
|
|
|
|
|
```bash
|
|
|
|
|
|
xxd -i model.tflite > model_data.cc
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
4. **MCU推理**:
|
|
|
|
|
|
使用TensorFlow Lite Micro框架在MCU上运行模型。
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 初始化解释器
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **性能指标**
|
|
|
|
|
|
| **模型** | **参数量** | **激活内存** | **准确率** | **推理时间(STM32H7)** |
|
|
|
|
|
|
|----------------|------------|--------------|------------|-------------------------|
|
|
|
|
|
|
| MobileNetV1 | 4.2M | 16MB | 70.6% | 800ms |
|
|
|
|
|
|
| TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
|
|
|
|
|
|
| EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 STM32 AI 开发套件
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **硬件平台**
|
|
|
|
|
|
- **STM32H7系列**:高性能MCU,支持DSP和FPU,适合运行复杂AI模型。
|
|
|
|
|
|
- **STM32L4+系列**:低功耗MCU,集成AI加速器,适合电池供电设备。
|
|
|
|
|
|
- **X-CUBE-AI扩展包**:提供模型转换工具和优化库。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **开发工具链**
|
|
|
|
|
|
1. **STM32CubeMX**:配置硬件和生成初始化代码。
|
|
|
|
|
|
2. **STM32Cube.AI**:将TensorFlow/PyTorch模型转换为STM32优化代码。
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 使用x-cube-ai命令行工具转换模型
|
|
|
|
|
|
stm32ai generate -m model.h5 -o stm32ai_output
|
|
|
|
|
|
```
|
|
|
|
|
|
3. **STM32CubeIDE**:集成开发环境,调试和优化AI应用。
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **性能优化**
|
|
|
|
|
|
- **硬件加速**:利用STM32的DSP、FPU和专用AI加速器(如STM32H7的Chrom-ART加速器)。
|
|
|
|
|
|
- **模型优化**:使用STM32Cube.AI的量化工具将模型压缩至8位或更少。
|
|
|
|
|
|
- **内存管理**:优化模型和中间数据的内存布局,减少RAM占用。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 模型量化与部署
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **量化技术**
|
|
|
|
|
|
- **权重量化**:将浮点权重转换为整数(通常8位或更少)。
|
|
|
|
|
|
- **激活量化**:运行时将输入/输出数据转换为整数。
|
|
|
|
|
|
- **混合精度**:对关键层使用更高精度,平衡准确率和性能。
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **部署挑战与解决方案**
|
|
|
|
|
|
| **挑战** | **解决方案** |
|
|
|
|
|
|
|------------------------|----------------------------------------------|
|
|
|
|
|
|
| 内存受限 | 使用内存映射技术,模型分段加载 |
|
|
|
|
|
|
| 计算能力有限 | 优化算子实现,利用硬件加速指令 |
|
|
|
|
|
|
| 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
|
|
|
|
|
|
| 模型更新 | 设计OTA机制,支持模型动态更新 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 AI + 外设驱动融合案例
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **智能传感器处理**
|
|
|
|
|
|
- **场景**:基于加速度计数据的活动识别。
|
|
|
|
|
|
- **实现**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 从加速度计读取数据
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **预测性维护**
|
|
|
|
|
|
- **场景**:基于振动传感器的电机故障预测。
|
|
|
|
|
|
- **实现**:
|
|
|
|
|
|
1. 采集振动数据并进行FFT变换。
|
|
|
|
|
|
2. 使用AI模型分析频谱特征,识别潜在故障。
|
|
|
|
|
|
3. 通过BLE将结果发送至云端。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 安全性
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 安全启动(Secure Boot)
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **原理与流程**
|
|
|
|
|
|
1. **硬件信任根**:
|
|
|
|
|
|
- 设备内置不可更改的私钥(存储在OTP中)。
|
|
|
|
|
|
- 用于验证第一个加载的软件组件(通常是Bootloader)。
|
|
|
|
|
|
|
|
|
|
|
|
2. **验证流程**:
|
|
|
|
|
|
```
|
|
|
|
|
|
ROM → 验证Bootloader签名 → 验证应用固件签名 → 启动应用
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **STM32实现**
|
|
|
|
|
|
- **选项字节配置**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 启用读保护(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();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **签名验证**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 验证固件签名
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 🔹 TPM 安全芯片接入
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. **TPM 2.0 概述**
|
|
|
|
|
|
- **功能**:
|
|
|
|
|
|
- 安全存储密钥
|
|
|
|
|
|
- 硬件级加密
|
|
|
|
|
|
- 平台身份验证
|
|
|
|
|
|
- 远程证明
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. **STM32与TPM集成**
|
|
|
|
|
|
- **硬件连接**:
|
|
|
|
|
|
STM32通过I2C/SPI与TPM芯片(如Infineon OPTIGA™ TPM SLB 9670)通信。
|
|
|
|
|
|
|
|
|
|
|
|
- **软件实现**:
|
|
|
|
|
|
```c
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. **应用场景**
|
|
|
|
|
|
- **安全启动增强**:使用TPM验证固件完整性。
|
|
|
|
|
|
- **安全通信**:TPM生成和存储TLS密钥,保护通信数据。
|
|
|
|
|
|
- **设备身份认证**:基于TPM的唯一密钥实现设备身份识别。 `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 🚀 实战案例
|
|
|
|
|
|
|
|
|
|
|
|
### 1. **工业设备预测性维护**
|
|
|
|
|
|
- **需求**:基于振动传感器数据预测设备故障。
|
|
|
|
|
|
- **实现**:
|
|
|
|
|
|
- 使用STM32H7采集振动数据。
|
|
|
|
|
|
- 部署TinyML模型进行实时分析。
|
|
|
|
|
|
- 通过TLS加密将结果发送至云端。
|
|
|
|
|
|
- 使用TPM确保数据完整性和设备身份安全。
|
|
|
|
|
|
|
|
|
|
|
|
### 2. **智能家居安全监控**
|
|
|
|
|
|
- **需求**:基于摄像头的人体检测与异常行为识别。
|
|
|
|
|
|
- **实现**:
|
|
|
|
|
|
- 使用STM32MP1微处理器运行轻量级CNN模型。
|
|
|
|
|
|
- 仅在检测到异常时唤醒系统并发送警报。
|
|
|
|
|
|
- 通过安全启动确保固件未被篡改。
|
|
|
|
|
|
- 使用TPM存储用户认证密钥。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 🔗 参考资源
|
|
|
|
|
|
|
|
|
|
|
|
1. **AI on MCU**:
|
|
|
|
|
|
- [TensorFlow Lite Micro](https://www.tensorflow.org/lite/microcontrollers)
|
|
|
|
|
|
- [STM32Cube.AI](https://www.st.com/en/embedded-software/x-cube-ai.html)
|
|
|
|
|
|
- [Edge Impulse](https://www.edgeimpulse.com/)
|
|
|
|
|
|
|
|
|
|
|
|
2. **安全性**:
|
|
|
|
|
|
- [PSA Certified](https://www.psacertified.org/)
|
|
|
|
|
|
- [mbed TLS](https://tls.mbed.org/)
|
|
|
|
|
|
- [TPM 2.0 Specification](https://trustedcomputinggroup.org/resource/tpm-library-specification/)
|
|
|
|
|
|
|
|
|
|
|
|
3. **实战案例**:
|
|
|
|
|
|
- [STMicroelectronics AI Demo](https://www.st.com/en/evaluation-tools/stm32ai-discovery.html)
|
|
|
|
|
|
- [ESP32 TinyML Examples](https://github.com/tensorflow/tflite-micro-arduino-examples)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AI与安全是2025年嵌入式领域的两大核心趋势。通过将AI算法部署到边缘设备,可实现实时智能决策,同时降低网络带宽和云端计算成本。而安全性则是保障设备和数据可信的基础,从安全启动到加密通信,再到TPM硬件级保护,构建多层次安全防护体系。在实际项目中,需根据具体需求选择合适的AI模型和安全方案,平衡性能、功耗和安全性。
|