你是一位具有20年工作经验的嵌入式 硬件工程师和嵌入式软件工程师,请你详细回答以下面试问题,越详细越好,最好让初学者也懂,牢记我的要求:
1.STM32是什么?它基于什么架构?2.ARM Cortex-M0, M3,M4,M7内核的主要区别是什么?
3.什么是CMSIS?它的作用是什么?
4.描述-下STM32从上电到开始执行main函数的过程。
5.什么是ISP和IAP?它们有何不同?
6.STM32的供电引脚(VDD, VDDA, VBAT等)分别有什么作用?
7.什么是时钟树?为什么它在STM32中很重要?8.列举STM32的主要时钟源(HSI, HSE,LSI,LSE, PLL)
9.如何配置PLL以得到最高的系统时钟?10.什么是GPIO?STM32的GPIO有几种工作模式?
11.推挽输出与开漏输出有什么区别?
12.如何将一个GPIO引脚配置为上拉输入模式?13.什么是复用功能?如何将一个PA9引脚配置为USART1_TX?
14.什么是NVIC?它在中断系统中起什么作用?
15.STM32的中断优先级是如何分组和管理的?
16.什么是EXTI?它如何工作?
17.SysTick定时器有什么用途?18.看门狗定时器是什么?独立看门狗和窗口看门狗有何区别?
19.如何从待机模式中唤醒STM32?
20.什么是位带操作?它有什么优势?
21.UART通信的基本原理是什么?
22.如何配置UART以实现115200的波特率?
23.UART如何通过中断方式接收不定长数据?
24.什么是DMA?它有什么好处?
25.如何配置UART使用DMA进行数据发送和接收?
26.SPI有几种工作模式?由什么信号决定?27.如何配置SPI为主机全双工模式?28.12C通信的起始信号和停止信号是如何定义的?
29.如何用软件模拟I2C时序?
30.12C从机地址是如何组成的?
31.STM32的硬件I2C在应用时需要注意什么?32.比较UART、SPI和I2C三种通信协议的特点和适用场景。
33.通用定时器有哪些主要功能?
34.如何配置定时器产生一个1kHz的PWM信号?35.PWM输出的频率和占空比由哪些寄存器决定?36.如何用定时器捕获一个外部脉冲的高电平宽度?
37.什么是定时器的编码器接口模式?如何使用?38.高级定时器(如TIM1)比通用定时器多了哪些功能?
39.如何用定时器触发一个ADC转换?40.ADC的分辨率是什么意思?STM32的ADC通常是多少位?
41.什么是ADC的规则通道和注入通道?
42.如何实现多通道ADC扫描转换?
43.ADC的采样时间如何影响转换结果?
44.如何校准ADC?
45.DAC的主要用途是什么?
46.比较器的工作原理是什么?
47.什么是实时时钟?如何配置RTC并产生一个闹
钟中断?
48.如何将数据备份到RTC的备份寄存器中?
49.芯片唯一ID有什么用途?
50.Flash存储器是如何组织的?什么是页,什么是扇区?
51.如何对内部Flash进行读写操作?
52.什么是选项字节?如何配置它?53.在STM32中如何实现一个简单的引导程序?54.如何将程序从内部Flash搬运到外部RAM中运行以提高速度?
55.什么是内存管理单元?哪些Cortex-M内核拥有它?
56.如何配置MPU以保护不同的内存区域?57.什么是DSP指令集?哪些STM32系列支持它?
58.如何用STM32实现一个简单的FFT?59.浮点单元是什么?哪些STM32系列拥有硬件FPU?
60.使用FPU需要注意什么?
61.以太网MAC和PHY的区别是什么?
62.如何配置LWIP协议栈以实现TCP通信?63.USB有哪几种传输类型?各自适用于什么场景?
64.如何将STM32配置为一个USB HID设备?65.如何将STM32配置为一个USB CDC设备(虚拟串口)?
66.CAN总线的基本帧、扩展帧和远程帧有什么区别?
67.如何配置CAN总线过滤器?
68.如何计算CAN总线的波特率?69.SDIO接口和SPI模式驱动SD卡有何区别?
70.如何通过FSMC接口驱动TFT液晶屏?
71.什么是LTDC?它有什么作用?
72.DCMI接口用于什么场景?
73.如何用STM32驱动一个WS2812B LED?
74.如何读取旋转编码器的值?
75.如何通过STM32驱动一个步进电机?
76.如何实现一个简单的PID控制器?77.在STM32上如何运行FreeRTOS?78.在FreeRTOS中,任务有哪几种状态?79.什么是信号量、互斥量和消息队列?80.如何避免任务间的优先级反转?81.什么是内存堆碎片?如何应对?82.如何使用STM32CubeMX生成初始化代码?83.STM32CubeMX中生成的HAL库和LL库有什么区别?
84.HAL库中的轮询、中断和DMA三种模式有何不同?
85.如何处理HAL库中的超时错误?
86.如何自定义HAL库的回调函数?
87.如何通过SWD接口调试STM32?
88.断点有哪几种类型?89.如何通过串口打印调试信息?90.如何使用ITM进行更高效的调试?91.如何测量代码的执行时间?92.如何配置STM32进入低功耗模式(睡眠、停
止、待机)?
93.在低功耗模式下,哪些外设可以继续工作?94.如何分析和优化STM32的功耗?95.在电路设计中,STM32的复位电路和晶振电路需要注意什么?
96.什么是去耦电容?为什么要尽可能靠近芯片电源引脚放置?
97.PCB布局布线时,模拟部分和数字部分应如何处理?98.程序跑飞了,可能的原因有哪些?99.如何利用看门狗来增强系统的可靠性?100.在项目开发中,如何进行STM32的固件升级?
下面给出一份"从裸机到RTOS、从硬件到软件、从原理到调试"的STM32百问百答
力求让初学者也能看懂 ,同时把20年项目经验 穿插在"注意事项""实战技巧"里。
篇幅较长,建议收藏后按序号速查。
(所有代码片段均用 HAL/LL 双语法给出,可直接拷贝验证)
1. STM32 是什么?基于什么架构?
-
STM32 是 ST 公司推出的 32 位 ARM Cortex-M 系列 MCU 的总商标。
-
内核架构:ARMv6-M(M0)、ARMv7-M(M3/M4/M7)。
-
片内集成 Flash、SRAM、外设总线、时钟、复位、调试模块等,"单片"即可跑系统。
2. Cortex-M0/M3/M4/M7 区别(一张表记)
表格
复制
| 特性 | M0 | M3 | M4 | M7 |
|---|---|---|---|---|
| 架构 | ARMv6-M | ARMv7-M | ARMv7E-M | ARMv7E-M |
| DSP | ❌ | ❌ | ✅ 单周期 MAC、SIMD | ✅ 双精度 MAC |
| FPU | ❌ | ❌ | 单精度可选 | 单/双精度可选 |
| MPU | 可选 | 标配 | 标配 | 标配 |
| Cache | ❌ | ❌ | ❌ | I/D Cache |
| 主频 | ≤48 MHz | ≤200 MHz | ≤240 MHz | ≤480 MHz |
| 中断数 | 1-32 | 1-240 | 1-240 | 1-240 |
| 指令集 | 56 条 | >200 条 | +DSP | +DSP+双精度 |
经验:做电机控制优先选 M4/M7,做超低功耗传感器节点选 M0+。
3. CMSIS 是什么?作用?
Cortex Microcontroller Software Interface Standard
-
ARM 制定的"中间层"API,统一寄存器定义、系统启动、DSP 库、RTOS 接口。
-
我们写代码时只要
#include "stm32f4xx.h",里面的寄存器名、NVIC 通道号、位定义全部来自 CMSIS,换芯片不换手册。
4. 上电 → main() 的 7 步(代码级)
-
取复位向量表(0x0800 0000 处 SP,0x0800 0004 处 PC)。
-
启动文件
startup_stm32fxxx.s:-
复制
.data从 Flash→RAM -
清零
.bss -
设置系统时钟
SystemInit() -
跳
__main→最终main()
-
-
若用 CubeMX,还会先跑
HAL_Init()配置 Flash 等待周期、SysTick 1 ms。
5. ISP vs IAP
表格
复制
| ISP | IAP | |
|---|---|---|
| 谁烧录 | 芯片出厂 BootROM | 用户自己程序 |
| 接口 | UART/USB/CAN 等固定引脚 | 任意接口(SD卡、以太网、无线) |
| 是否改 Boot 引脚 | 需要 BOOT0=1 | 不需要 |
| 典型场景 | 产线第一次烧空片 | 现场远程升级 |
6. 供电引脚
-
VDD:数字核、GPIO 1.8-3.6 V。
-
VDDA :ADC/DAC/PLL 模拟供电,必须 ≤ VDD+0.3 V。
-
VBAT:RTC+备份寄存器+32 kHz 振荡器,1.65-3.6 V,主电丢失仍可跑。
-
VREF+:ADC 参考,可外接精密 2.5 V/3.0 V;若悬空则等于 VDDA。
经验:VDDA 走线必须 LC 滤波,否则 ADC 噪声大 3-5 LSB。
7. 时钟树 & 重要性
STM32 把"时钟"做成树状 MUX,原因:
-
不同外设需要不同频率(ADC ≤36 MHz,USB 必须 48 MHz)。
-
低功耗场景可关闭某一支路。
-
出错时快速切换安全时钟。
8. 主要时钟源
-
HSI:内部 16 MHz RC,上电默认,±1 % 温漂。
-
HSE:外部 4-26 MHz 晶振,精度 10 ppm。
-
LSI:内部 32 kHz RC,看门狗/自动唤醒。
-
LSE:外部 32.768 kHz,RTC 专用。
-
PLL:乘/除法器,把 HSE/HSI 升到 400 MHz(H7)。
9. 配置最高系统时钟(以 F407 为例,168 MHz)
c
复制
// CubeMX 图形界面只需 3 步:
// 1) HSE 选 8 MHz 晶振
// 2) PLLSRC = HSE
// 3) N=336, P=2, Q=7 → 168 MHz
// 代码级:
RCC_OscInitTypeDef osc = {0};
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
osc.HSEState = RCC_HSE_ON;
osc.PLL.PLLState = RCC_PLL_ON;
osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
osc.PLL.PLLM = 8; // VCO 输入 1 MHz
osc.PLL.PLLN = 336; // VCO 输出 336 MHz
osc.PLL.PLLP = RCC_PLLP_DIV2; // 系统时钟 168 MHz
HAL_RCC_OscConfig(&osc);
10. GPIO 是什么?工作模式
General Purpose Input/Output ,双向引脚。
STM32 每脚 8 种模式:
-
输入浮空
-
输入上拉
-
输入下拉
-
模拟(ADC/DAC)
-
推挽输出
-
开漏输出
-
复用推挽
-
复用开漏
11. 推挽 vs 开漏
-
推挽 :内部上下管交替导通,能输出高/低电平,驱动 LED、USART。
-
开漏 :下管导通只能拉低,上管关闭,需外加上拉电阻,可做"线与"、电平转换(5 V→3.3 V)。
12. 上拉输入配置(寄存器级)
c
复制
// HAL
GPIO_InitTypeDef g = {0};
g.Pin = GPIO_PIN_0;
g.Mode = GPIO_MODE_INPUT;
g.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &g);
// LL
LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_0, LL_GPIO_MODE_INPUT);
LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_0, LL_GPIO_PULL_UP);
13. 复用功能 & PA9→USART1_TX
复用 :GPIO 不再做普通 IO,而交给片上外设。
步骤:
-
__HAL_RCC_GPIOA_CLK_ENABLE(); -
__HAL_RCC_USART1_CLK_ENABLE(); -
配置 PA9 为复用推挽,上拉,速度高。
-
选 AF7:
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
14. NVIC 作用
Nested Vectored Interrupt Controller,ARM 内核自带。
-
管理 240 条外设中断线,提供 4-16 级优先级。
-
负责抢占+尾链+迟到优化,中断响应 12 周期以内。
15. 中断优先级分组
Cortex-M 把 8 位优先级拆成 组优先级+子优先级 。
STM32 只用高 4 位,通过 NVIC_PriorityGroupConfig() 选 5 种分配方式:
例:Group2 → 2 位组+2 位子,共 4 组,每组 4 级。
经验:RTOS 场景全部设 Group4(全抢占),避免子优先级带来的反转。
16. EXTI 外部中断
-
把 GPIO 边沿映射到 NVIC 线 0-15。
-
配置流程:
SYSCFG→EXTIx选端口 → 上升/下降沿触发 → NVIC 使能 → 写 ISR 清挂起位。
17. SysTick 用途
-
24 位递减计数器,地址固定 0xE000E010。
-
HAL 拿它做 1 ms 时基;RTOS 拿它做时间片轮转。
-
也可自己摘出来做软件定时器。
18. 看门狗
-
独立看门狗 IWDG :内部 32 kHz LSI,一旦启动软件无法关闭,复位时间 0.1-32 s。
-
窗口看门狗 WWDG :APB1 时钟,必须在"窗口期"喂狗,提前或滞后都复位,可检测跑飞+时钟失效。
19. 从待机模式唤醒
待机电流 2 µA(L476)。
唤醒源:RTC 闹钟、WKUP 引脚上升沿、NRST、IWDG。
代码:
c
复制
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
HAL_PWR_EnterSTANDBYMode(); // 到此停机
// 唤醒后自动复位,重新执行 main()
20. 位带操作
Cortex-M 把 1 M 字节的 SRAM 和外设区映射到 32 M 字节别名区,一次写 1 bit 变成写 32 bit 的原子操作 。
优势:无需读-改-写,无竞争,速度快 ,在多任务共享标志时神器。
示例:
c
复制
#define BITBAND_SRAM(addr, bit) \
(*(__IO uint32_t*)(0x22000000 + ((addr)-0x20000000)*32 + bit*4))
BITBAND_SRAM(&flag, 7) = 1; // 原子置位 bit7
21. UART 原理
异步串行:起始位(低)+8 数据位+可选奇偶+停止位(高)。
双方约定波特率 ,允许 2-3 % 误差。
TTL 电平:0-3.3 V;外接 RS-232 需 MAX3232;外接 RS-485 需 SP3485。
22. 配置 115200 baud(F407,APB1=42 MHz)
c
复制
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart1);
// 寄存器级:USART_BRR = 42 MHz / 115200 ≈ 0x1A1
23. 中断接收不定长数据
3 种主流做法:
-
空闲中断(IDLE)+DMA:CubeMX 勾选 USART1 global interrupt,
在
USART1_IRQHandler()里检测__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE),清标志后计算DMA_RX_LEN = BUF_SIZE - __HAL_DMA_GET_COUNTER(hdma_usart1_rx)。 -
字符超时+循环缓冲。
-
特殊帧尾(
\n)中断拼接。
24. DMA 好处
-
无需 CPU 搬运,省 10-20 倍时间。
-
双缓冲可零拷贝串口流。
-
低功耗:DMA 跑时内核可睡。
25. UART+DMA 双工
c
复制
HAL_UART_Transmit_DMA(&huart1, txBuf, len);
HAL_UART_Receive_DMA(&huart1, rxBuf, BUF_LEN);
// 完成后分别进入 HAL_UART_TxCpltCallback / HAL_UART_RxCpltCallback
26. SPI 四种模式
由 CPOL (时钟极性)和 CPHA(时钟相位)决定:
-
Mode0: CPOL=0, CPHA=0 → 上升沿采样
-
Mode1: CPOL=0, CPHA=1 → 下降沿采样
-
Mode2: CPOL=1, CPHA=0
-
Mode3: CPOL=1, CPHA=1
经验:先读从机手册,90% flash/W25Qxx 支持 Mode0/3。
27. 配置 SPI 主机全双工
c
复制
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 84/4=21 MHz
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // Mode0
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
HAL_SPI_Init(&hspi1);
// NSS 建议软件管理:GPIO 输出片选
28. I²C 起/止信号
-
起始 S:SCL 高期间 SDA 下降沿。
-
停止 P:SCL 高期间 SDA 上升沿。
必须先把 SDA 设为开漏,外加上拉 4.7 kΩ。
29. 软件模拟 I²C(位 bang)
c
复制
void I2C_Delay(void) { for(int i=0;i<80;i++) __NOP(); }
void I2C_Start(void){
SDA_H; SCL_H; I2C_Delay();
SDA_L; I2C_Delay(); // 下降沿
SCL_L;
}
适合引脚不够用或硬件 I²C 有硅 bug 的系列(如 F103 部分批次)。
30. I²C 从机地址
7 位地址左移 1 位 + 读写位 → 8 位头字节。
例:MPU6050 地址 0x68,写 0xD0,读 0xD1。
10 位地址模式很少用。
31. 硬件 I²C 注意点
-
必须开模拟滤波器(ANOFF=0)。
-
时钟拉伸要允许。
-
多主机会死锁,加超时回调
HAL_I2C_ErrorCallback()里发停止信号恢复。 -
上电时 SDA/SCL 不要被外设拉高,否则锁死,需 Clock out 9 个脉冲 解锁。
32. UART vs SPI vs I²C
表格
复制
| 线数 | 速度 | 多主机 | 距离 | 成本 | |
|---|---|---|---|---|---|
| UART | 2(TX/RX) | Mbps 级 | ❌ | 中等 | 低 |
| SPI | 4(+CS) | 50+ Mbps | ❌ | 短 | 低 |
| I²C | 2 | 0.4/1/3.4 Mbps | ✅ | 短 | 最低 |
33. 通用定时器功能
-
时基中断
-
输入捕获(测脉冲宽/频率)
-
PWM 输出(带死区)
-
编码器接口
-
单脉冲模式
-
触发 ADC/DAC
34. 1 kHz PWM(F103,TIM2,72 MHz)
c
复制
TimHandle.Instance = TIM2;
TimHandle.Init.Prescaler = 72-1; // 1 MHz
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.Period = 1000-1; // 1 kHz
HAL_TIM_PWM_Init(&TimHandle);
sConfigOC.Pulse = 500; // 50 % 占空
HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfigOC, TIM_CHANNEL_1);
35. PWM 频率/占空寄存器
-
频率 = TIM_CLK / (PSC+1) / (ARR+1)
-
占空 = CCR / (ARR+1)
36. 捕获高电平宽度
c
复制
// 通道1捕获上升沿,通道2捕获下降沿,开启"直连"
__HAL_TIM_SET_CAPTUREPOLARITY(&htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
__HAL_TIM_SET_CAPTUREPOLARITY(&htim, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING);
// 在 CCR1/CCR2 中断里计算差值
width = (capture2 - capture1) * 1 us; // 假设 1 MHz 计数
37. 编码器接口
定时器把 TI1/TI2 当成正交信号,硬件自动加减计数 。
配置:
c
复制
sEncoderMode.EncoderMode = TIM_ENCODERMODE_TI12;
HAL_TIM_Encoder_Init(&htim, &sEncoderMode);
读 __HAL_TIM_GET_COUNTER(&htim) 即位置,零软件开销。
38. 高级定时器 TIM1 额外功能
-
6 通道 PWM,带互补输出(驱动三相全桥)
-
死区时间可编程(ns 级)
-
刹车输入(紧急关 PWM,保护 MOSFET)
-
重复计数器(更新事件可 N 次后才中断)
-
可生成中心对齐 PWM(对称,降低谐波)
39. 定时器触发 ADC
c
复制
ADC_ExternalTrigConvConfig(ADC1, ADC_EXTERNALTRIGCONV_T1_CC1, ENABLE);
ADC 的 JEXTEN/JEXTSEL 位选触发源,实现同步采样,用于电机电流环。
40. ADC 分辨率
分辨率 = 量化台阶数,12 位 → 4096 级。
STM32 主流 12 位,部分系列 16 位过采样(G4/H7)。
41. 规则通道 vs 注入通道
-
规则:正常扫描,最多 16 通道,结果进 DR。
-
注入 :高优先级"打断"规则组 ,最多 4 通道,结果进 JDR,适合电机相电流双采样。
42. 多通道扫描
c
复制
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
...
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuf, N*Ch);
43. 采样时间影响
采样时间越长,输入阻抗允许越大 ;否则电荷没建立,读数偏低。
公式:RAIN < (Tsamp / (Cadc * ln(2^(12+1)))) -- Radc
经验:源阻抗 >10 kΩ 必须加运放缓冲。
44. ADC 校准
c
复制
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
每颗芯片出厂增益/偏移不同,校准后典型 ENOB 提高 0.3-0.5 位。
45. DAC 用途
-
波形发生器(正弦、梯形)
-
参考电压
-
音频输出(带 DMA 双缓冲)
46. 比较器
模拟比较器 1 µs 内比较两路电压,输出可接定时器刹车、产生中断、唤醒 Stop 模式。
用于过流保护比 ADC 轮询快 10 倍。
47. RTC 闹钟
c
复制
RTC_TimeTypeDef sTime = {0};
sTime.Hours = 6;
RTC_SetTime(&hrtc, &sTime);
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
闹钟事件可唤醒待机,精度 1 ppm(外接 32.768 kHz LSE)。
48. 备份寄存器
c
复制
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x1234);
VBAT 不掉电,存 128 字节,可存掉电前状态、错误码。
49. 唯一 ID
96 位 UID,每颗芯片不同,可做:
-
加密密钥种子
-
USB 序列号
-
防抄板
50. Flash 组织
-
页 Page:最小擦除单位,F1 系列 1 KB/页,F4 16 KB/页。
-
扇区 Sector:F4 分 12 扇区,前 4 个 16 KB,接着 64 KB,最后 128 KB。
-
块 Bank:H7 双 Bank 可 RWW 读-写并行。
51. 内部 Flash 读写
c
复制
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR);
FLASH_Erase_Sector(FLASH_SECTOR_5, VOLTAGE_RANGE_3);
HAL_FLASH_Program(TYPEPROGRAM_WORD, addr, data);
HAL_FLASH_Lock();
必关中断,擦写时不能执行代码,可在 RAM 中跑。
52. 选项字节
存放:读保护 RDP、BOR 电压、独立看门狗是否硬件启动、复位模式。
修改:ST-Link Utility 或 HAL_FLASHEx_OBProgram()。
RDP 从 0xAA→0xCC 会全局擦除,慎用!
53. 简易 Bootloader(1 KB 搞定)
-
上电检查 GPIO 按键,按住进升级模式。
-
接收 UART 新固件(带 CRC)。
-
写 App 区(0x08004000 起)。
-
校验 OK 后跳
((void(*)())(0x08004000 + 4))();
中断向量表重定位:
SCB->VTOR = 0x08004000;
54. 程序搬至外部 RAM 运行
-
H7/F7 带 FMC/SDRAM,
在
startup.s里把.data/.bss链接到 0xC000 0000,系统启动时复制 RO 段→SDRAM,CPU 通过 I-Cache 取指,速度提升 2-3 倍。
注意 MPU 把 SDRAM 设为 Normal, Write-back, no write allocate,否则 Cache 一致性问题。
55. MMU & MPU
-
MMU :只有 Cortex-M33/M55/M7 可选 ,提供虚拟地址,跑 Linux 才需要。
-
MPU :所有 M3/M4/M7 都有,把 4 GB 地址划 8/16 区 ,设特权/缓存/执行禁止,防止野指针跑飞。
56. 配置 MPU 保护
c
复制
MPU_Region_InitTypeDef mpu = {0};
mpu.Enable = MPU_REGION_ENABLE;
mpu.BaseAddress = 0x20000000;
mpu.Size = MPU_REGION_SIZE_256KB;
mpu.AccessPermission = MPU_REGION_FULL_ACCESS;
mpu.TypeExtField = MPU_TEX_LEVEL0;
mpu.IsBufferable = 1;
mpu.IsCacheable = 1;
mpu.IsShareable = 0;
HAL_MPU_ConfigRegion(&mpu);
57. DSP 指令集
M4/M7 提供:
-
单周期 32×32→64 MAC
-
SIMD 16×2/8×4
-
硬件除法、开方
跑 FIR 比 M3 快 5-7 倍。
58. 用 STM32 做 FFT
c
复制
#include "arm_math.h"
#define N 1024
float32_t in[N], out[N];
arm_rfft_fast_instance_f32 S;
arm_rfft_fast_init_f32(&S, N);
arm_rfft_fast_f32(&S, in, out, 0);
arm_cmplx_mag_f32(out, mag, N/2);
开 FPU 后 1024 点仅需 0.8 ms(F407 168 MHz)。
59. FPU
Floating Point Unit ,单精度符合 IEEE-754。
M4F/M7/M33 有,M0/M3 无 。
编译选项:-mfloat-abi=hard -mfpu=fpv4-sp-d16。
60. 使用 FPU 注意
-
上电默认关,
SCB->CPACR |= (0xF << 20)使能。 -
中断里用浮点需
FPU->FPCCR |= FPU_FPCCR_ASPEN_Msk;保存上下文。 -
双精度变量会调用软件库,拖慢 100 倍 ,尽量用
float。
61. 以太网 MAC vs PHY
-
MAC:STM32 内部,负责帧打包、DMA、FIFO。
-
PHY :外部芯片(LAN8720、KSZ8081),负责 MII/RMII 串并转换、电压驱动。
-
通过 MDIO 接口 配置 PHY 寄存器,LED 灯、自协商。
62. 配置 LWIP TCP
-
CubeMX 选 ETH、LWIP,DMA 描述符长度 4。
-
low_level_init()里设置 MAC 地址。 -
tcp_new()→tcp_bind()→tcp_listen()→ 在 accept 回调里tcp_write()。
内存池改
LWIP_MEM_SIZE到 16 KB,否则大数据包丢包。
63. USB 传输类型
-
控制:枚举/命令,必支持。
-
中断:HID 鼠标/键盘,1 ms 轮询。
-
批量 :U 盘、CDC,带宽大、可延迟。
-
等时 :USB 音频/摄像头,固定带宽、无重传。
64. 配置 USB HID
CubeMX:
-
MiddleWares → USB_DEVICE → Class → HID。
-
报告描述符改 64 字节,实现双向通信。
-
在
USBD_HID_SendReport()发数据,在HID_OutEvent()收数据。
65. 配置 USB CDC 虚拟串口
CubeMX 选 CDC,PC 端免驱。
-
发送:
CDC_Transmit_FS(buf, len); -
接收:在
CDC_Receive_FS()回调里把数据抛给队列。
注意 Win10 首次需 INF,Linux/Mac 免驱。
66. CAN 帧类型
-
标准帧:11 位 ID,数据场 0-8 字节。
-
扩展帧:29 位 ID。
-
远程帧:无数据,用于请求。
67. CAN 过滤器
c
复制
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x321 << 5;
sFilterConfig.FilterMaskIdHigh = 0x7FF << 5;
HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
28 个过滤器,可组双 16 位,灵活屏蔽。
68. 计算 CAN 波特率
复制
CAN 位时间 = 1 + BS1 + BS2
波特率 = PCLK / (Prescaler * (1 + BS1 + BS2))
例:PCLK=42 MHz, Prescaler=6, BS1=11, BS2=2 → 42 M/6/(1+11+2)=500 kbit/s。
采样点 70-87.5 % 符合 ISO。
69. SDIO vs SPI 模式 SD 卡
表格
复制
| SDIO | SPI | |
|---|---|---|
| 速度 | 48 MHz, 24 MB/s | 25 MHz, 3 MB/s |
| 引脚 | 6 根 | 4 根 |
| 功耗 | 高 | 低 |
| 代码 | 需 DMA 多块 | 简单 |
70. FSMC 驱动 TFT
-
NOR/SRAM 模式,A0 接 RS 脚 ,写命令
*(__IO uint16_t*)(0x60000000)=cmd; -
读数据
data = *(volatile uint16_t*)(0x60000002);
时序配 10 ns 级,RGB565 刷满屏 20 ms。
71. LTDC
LCD-TFT Display Controller ,H7 自带,直接驱动 24 位 RGB 屏 ,最高 1024×768。
带两层图层、Alpha 混合、色键,无需 CPU 刷屏。
72. DCMI
Digital Camera Interface ,8-14 位并口,接 OV2640、MT9M114 ,支持 JPEG 压缩数据流,DMA 直接进 SRAM。
73. 驱动 WS2812B
单总线 800 kHz,时序 0: 400 ns 高 + 850 ns 低;1: 850 ns 高 + 400 ns 低 。
方案:
-
PWM+DMA(F4 用 TIM1,72 MHz 倍频)
-
SPI 移位(M0 用 24×8=192 位模拟一像素)
关中断,否则时序抖动颜色错。
74. 读旋转编码器
用 定时器编码器模式 或 A/B 相中断。
加 1 ms 软件消抖,防止机械抖动误判。
75. 驱动步进电机
c
复制
// 定时器 1 kHz 中断,四拍
const uint8_t seq[8] = {0x09,0x0A,0x06,0x05};
HAL_GPIO_WritePort(GPIOA, seq[stepIdx++] & 0xF);
电流大用 DRV8825,加斜率控制降噪。
76. PID 控制器
c
复制
typedef struct{
float Kp, Ki, Kd;
float err, integral, prev;
} PID;
float PID_Update(PID *p, float set, float fb){
float e = set - fb;
p->integral += e;
float out = p->Kp*e + p->Ki*p->integral + p->Kd*(e - p->prev);
p->prev = e;
return out;
}
采样周期固定,积分限幅+抗饱和。
77. 跑 FreeRTOS
-
CubeMX 勾选 FreeRTOS,选 CMSIS_V2。
-
默认堆 10 KB,任务栈 128 word。
-
生成后
osKernelStart()自动调vTaskStartScheduler()。
78. FreeRTOS 任务状态
-
Running
-
Ready
-
Blocked(等事件/队列)
-
Suspended(显式挂起)
-
Deleted(待清理)
79. IPC 机制
-
信号量:资源计数。
-
互斥量:优先级继承,防反转。
-
消息队列 :拷贝数据,解耦 ISR 与任务。
80. 避免优先级反转
-
用互斥量(自带优先级继承)。
-
天花板优先级:任务先升优先级再进临界区。
81. 内存堆碎片
-
使用 heap_4.c(带合并算法)。
-
静态分配
xTaskCreateStatic()。 -
应用层对象池。
82. CubeMX 生成代码
图形化配外设 → 自动生成:
-
时钟树
-
GPIO 初始化
-
HAL/LL 句柄
-
FreeRTOS 任务框架
不要把用户代码写到 "USER CODE BEGIN" 之外,否则重新生成被覆盖。
83. HAL vs LL
表格
复制
| HAL | LL | |
|---|---|---|
| 抽象度 | 高 | 寄存器级 |
| 可移植 | 好 | 差 |
| 效率 | 中 | 高(单周期) |
| 代码体积 | 大 | 小 |
中断频繁建议 LL+DMA。
84. HAL 三种模式
-
轮询:阻塞,简单。
-
中断:后台进行,需回调。
-
DMA :零拷贝,最省功耗。
85. 超时错误处理
c
复制
if(HAL_UART_Transmit(&huart1, buf, len, 100) != HAL_OK){
// 超时 100 ms
HAL_UART_Abort(&huart1);
}
86. 自定义 HAL 回调
在 stm32f4xx_hal_msp.c 里重写:
c
复制
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
if(huart->Instance==USART1) LED_Toggle();
}
87. SWD 调试
-
2 线:SWDIO + SWCLK,占脚少。
-
速度 4 MHz,下载 256 KB 仅需 2 s。
长线加 22 Ω 串阻,防信号反射。
88. 断点类型
-
软件断点 :改 Flash 插 0xBE00,数量无限制。
-
硬件断点 :内核 COMP 寄存器,M4 仅 6 个。
-
数据观察点 :监测某地址读写,抓野指针神器。
89. 串口打印调试
c
复制
#define DEBUG_UART &huart1
int __io_putchar(int ch){
HAL_UART_Transmit(DEBUG_UART, (uint8_t*)&ch, 1, 10);
return ch;
}
printf("Temp=%.2f\r\n", temp);
封装
macro开关,量产关闭减体积。
90. ITM 调试
SWO 引脚输出,0 开销 printf ,速度 2 Mbit/s。
Keil 里勾选 Use ITM,代码:
c
复制
ITM_SendChar('A');
可插 32 路虚拟端口,不占 UART。
91. 测量代码执行时间
c
复制
uint32_t t0 = DWT->CYCCNT;
run_my_algo();
uint32_t cycles = DWT->CYCCNT - t0;
float us = cycles / 168.0f; // 168 MHz
先开
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
92. 低功耗模式
表格
复制
| 模式 | 电流 | 唤醒源 | 上下文 |
|---|---|---|---|
| Sleep | mA 级 | 任何中断 | 保持 |
| Stop | 10-50 µA | EXTI、RTC、NRST | SRAM 保持 |
| Standby | 1-2 µA | RTC、WKUP、NRST | 丢失,复位 |
93. 低功耗下仍工作外设
-
LSE、LSI
-
RTC、独立看门狗
-
WKUP 引脚
-
部分 USART(低功耗 UART)
94. 功耗优化技巧
-
降主频:168 → 16 MHz,电流降 60 %。
-
关 GPIO 模拟输入:每脚省 20 µA。
-
用 Run-Stop 交替 ,占空比 1 % 平均 50 µA。
95. 复位 & 晶振电路
-
NRST 引脚 100 nF→地 ,10 kΩ→VDD ,防误复位。
-
HSE 晶振 8 MHz ,负载电容 12-20 pF ,走线差分、包地、远离高频。
96. 去耦电容
-
0.1 µF + 4.7 µF 并联,环路面积 < 1 cm²。
-
降低 高频瞬态电流 ,防止电源塌陷导致内部复位。
97. PCB 模拟/数字分区
-
模拟地 AGND 单独画,单点接地。
-
ADC 走线 远离数字高速线 ,包地+ guard ring。
-
供电 π 型滤波(磁珠 + 电容)。
98. 程序跑飞原因
-
数组越界
-
中断向量表未重定位
-
时钟未配置即跑 Flash
-
堆栈溢出
-
看门狗未喂
-
外设指针未开时钟
99. 看门狗增强可靠性
-
IWDG 在初始化早期就启动 ,窗口值 80 %。
-
关键任务置标志 ,主循环统一喂狗 ,可检测死循环。
100. 固件升级方案
-
Bootloader + 双 Bank(H7 硬件支持)。
-
YMODEM/XMODEM 串口升级。
-
USB DFU (Device Firmware Update),PC 免驱动。
-
OTA :Wi-Fi/4G 下 断点续传 + CRC32 + 版本回滚。
量产时 加密签名 (AES-CTR + ECDSA),防篡改。
------ 完 ------
祝面试通关,源码常伴!