《STM32深度100问:AI助教工程师的实战问答录》从入门到精通适用入门嵌入式软件初级工程师,筑牢基础,技术积累

  1. STM32是什么?它基于什么架构?
  2. ARM Cortex-M0,M3,M4,M7内核的主要区别是什么?
  3. 什么是CMSIS?它的作用是什么?
  4. 描述一下STM32从上电到开始执行main函数的过程
  5. STM32的供电引脚(VDD,VDDA,VBAT等)分别有什么作用?
  6. 什么是时钟树?为什么它在STM32中很重要?
  7. 列举STM32的主要时钟源(HSI, HSE,LSI,LSE,PLL)。
  8. 如何配置PLL以得到最高的系统时钟?
  9. 什么是GPIO?STM32的GPIO有几种工作模式?
  10. 推挽输出与开漏输出有什么区别?
  11. 如何将一个GPIO引脚配置为上拉输入模式?
  12. 什么是复用功能?如何将一个PA9引脚配置为USART1_TX?
  13. 什么是NVIC?它在中断系统中起什么作用?
  14. STM32的中断优先级是如何分组和管理的?
  15. 什么是EXTI?它如何工作?
  16. SysTick定时器有什么用途?
  17. 看门狗定时器是什么?独立看门狗和窗口看门狗有何区别?
  18. 如何从待机模式中唤醒STM32?
  19. 什么是位带操作?它有什么优势?
  20. 什么是ISP和IAP?它们有何不同?
  21. 通用定时器有哪些主要功能?
  22. 如何配置定时器产生一个1kHz的PWM信号?
  23. PWM输出的频率和占空比由哪些寄存器决定?
  24. 如何用定时器捕获一个外部脉冲的高电平宽度?
  25. 什么是定时器的编码器接口模式?如何使用?
  26. 高级定时器(如TIM1)比通用定时器多了哪些功能?
  27. ADC的分辨率是什么意思?STM32的ADC通常是多少位?
  28. 什么是ADC的规则通道和注入通道?
  29. 如何实现多通道ADC扫描转换?
  30. ADC的采样时间如何影响转换结果?
  31. 如何校准ADC?
  32. 如何用定时器触发一个ADC转换?
  33. DAC的主要用途是什么?
  34. 比较器的工作原理是什么?
  35. 什么是实时时钟?如何配置RTC并产生一个闹钟中断?
  36. 如何将数据备份到RTC的备份寄存器中?
  37. 芯片唯一ID有什么用途?
  38. Flash存储器是如何组织的?什么是页,什么是扇区?
  39. 如何对内部Flash进行读写操作?
  40. 什么是选项字节?如何配置它?
  41. UART通信的基本原理是什么?
  42. 如何配置UART以实现115200的波特率?
  43. UART如何通过中断方式接收不定长数据?
  44. 什么是DMA?它有什么好处?
  45. 如何配置UART使用DMA进行数据发送和接收?
  46. SPI有几种工作模式?由什么信号决定?
  47. 如何配置SPI为主机全双工模式?
  48. I2C通信的起始信号和停止信号是如何定义的?
  49. I2C从机地址是如何组成的?
  50. 如何用软件模拟I2C时序?
  51. STM32的硬件I2C在应用时需要注意什么?
  52. 比较UART、SPI和I2C三种通信协议的特点和适用场景。
  53. CAN总线的基本帧、扩展帧和远程帧有什么区别?
  54. 如何配置CAN总线过滤器?
  55. 如何计算CAN总线的波特率?
  56. SDIO接口和SPI模式驱动SD卡有何区别?
  57. 如何通过FSMC接口驱动TFT液晶屏?
  58. 什么是LTDC?它有什么作用?
  59. DCMI接口用于什么场景?
  60. 如何用STM32驱动一个WS2812B LED?
  61. 如何读取旋转编码器的值?
  62. 如何通过STM32驱动一个步进电机?
  63. 如何实现一个简单的PID控制器?
  64. 在STM32上如何运行FreeRTOS?
  65. 在FreeRTOS中,任务有哪几种状态?
  66. 什么是信号量、互斥量和消息队列?
  67. 如何避免任务间的优先级反转?
  68. 什么是内存堆碎片?如何应对?
  69. 如何使用STM32CubeMX生成初始化代码?
  70. STM32CubeMX中生成的HAL库和LL库有什么区别?
  71. HAL库中的轮询、中断和DMA三种模式有何不同?
  72. 如何处理HAL库中的超时错误?
  73. 如何自定义HAL库的回调函数?
  74. 如何配置STM32进入低功耗模式(睡眠、停止、待机)?
  75. 在低功耗模式下,哪些外设可以继续工作?
  76. 如何分析和优化STM32的功耗?
  77. 如何通过SWD接口调试STM32?
  78. 断点有哪几种类型?
  79. 如何通过串口打印调试信息?
  80. 如何使用ITM进行更高效的调试?
  81. 如何测量代码的执行时间?
  82. 在电路设计中,STM32的复位电路和晶振电路需要注意什么?
  83. 什么是去耦电容?为什么要尽可能靠近芯片电源引脚放置?
  84. PCB布局布线时,模拟部分和数字部分应如何处理?
  85. 程序跑飞了,可能的原因有哪些?
  86. 如何利用看门狗来增强系统的可靠性?
  87. 在项目开发中,如何进行STM32的固件升级?
  88. 如何配置STM32实现一个简单的引导程序?
  89. 什么是内存管理单元?哪些Cortex-M内核拥有它?
  90. 如何配置MPU以保护不同的内存区域?
  91. 什么是DSP指令集?哪些STM32系列支持它?
  92. 如何用STM32实现一个简单的FFT?
  93. 浮点单元是什么?哪些STM32系列拥有硬件FPU?
  94. 使用FPU需要注意什么?
  95. 以太网MAC和PHY的区别是什么?
  96. 如何配置LWIP协议栈以实现TCP通信?
  97. USB有哪几种传输类型?各自适用于什么场景?
  98. 如何将STM32配置为一个USB HID设备?
  99. 如何将STM32配置为一个USB CDC设备(虚拟串口)?
  100. 如何用软件模拟I2C时序?

一、STM32基础架构篇

1. STM32是什么?它基于什么架构? STM32是意法半导体(ST)推出的32位微控制器系列 ,基于ARM Cortex-M内核 的"豪华套装"。它把CPU、RAM、Flash、定时器、ADC、通信外设 全部集成在一个芯片里,是嵌入式领域的"瑞士军刀"

架构

  • 内核 :ARM Cortex-M0/M0+/M3/M4/M7/M33(哈佛结构Thumb-2指令集

  • 总线AHB (高性能总线,连内存)+ APB(外设总线,连UART/SPI)

  • 存储Flash存程序 (0x0800 0000),RAM存数据(0x2000 0000)

底层真相 :STM32只是ARM内核的"装修公司",ST加了外设驱动和生态


2. ARM Cortex-M0, M3, M4, M7内核的主要区别是什么?

表格

复制

内核 性能 DSP FPU MPU Cache 应用场景
M0 0.9 DMIPS/MHz 超低功耗简单控制
M3 1.25 DMIPS/MHz 平衡主流
M4 1.25 DMIPS/MHz 可选 需要DSP电机控制
M7 2.14 DMIPS/MHz 双精度 I/D Cache 高性能GUI

关键差异

  • M4DSP指令单周期乘法累加MAC ),适合FFT、FIR

  • M76级流水线双精度FPUL1 Cache跑满400MHz

  • 中断 :M0有4级优先级 ,M3/M4/M7有8-256级NVIC

选型够用就好M3覆盖80%场景M7是性能怪兽


3. 什么是CMSIS?它的作用是什么? CMSIS(Cortex Microcontroller Software Interface Standard) 是ARM定的 "嵌入式开发普通话"统一各厂商的接口

作用

  1. 硬件抽象core_cm3.h 定义NVIC、SysTick、SCB 寄存器,代码跨芯片

  2. 启动文件startup_stm32f103xe.s 统一中断向量表,**堆栈初始化 **

  3. ** DSP库 **: ** arm_math.h **, ** FFT、矩阵运算 优化过的汇编 **

  4. ** RTOS接口 **: ** CMSIS-RTOS **, ** 统一FreeRTOS、RTX **

** 底层 :CMSIS就是 寄存器定义+启动文件 没有它,每家厂商各说各话 **。

** 使用 #include "stm32f1xx.h"** 间接包含cmsis_armcc.h直接用NVIC_EnableIRQ()


4. 描述一下STM32从上电到开始执行main函数的过程 这是STM32的"** 蝶变 "过程, 从硬件到软件 **:

  1. ** 上电复位 :VDD稳定, NRST引脚拉高 时钟起振 **

  2. ** BOOT引脚采样 BOOT0=0 从主Flash启动 **(0x0800 0000)

  3. ** 取中断向量表 :从0x0800 0000 初始栈指针SP ,从0x0800 0004 Reset_Handler地址 **

  4. ** 执行Reset_Handler startup.s **):

    • ** 初始化SP **

    • ** 复制.data段**到RAM **(Flash→RAM)

    • ** 清零.bss段 **(RAM初始0)

    • ** 调用SystemInit()**配置时钟(72MHz)

    • ** 调用__libc_init_array()**初始化C++静态对象

  5. ** 调用main() **:进入用户代码

** 关键 Reset_Handler是C环境的"接生婆"**,**SystemInit是时钟的"调音师"**。


5. 什么是ISP和IAP?它们有何不同? ** ISP(In-System Programming) 通过串口/USB下载 用系统Bootloader 不依赖用户代码 **。

** IAP(In-Application Programming) 用户程序自己写Flash 自我更新 **。

** 对比 **:

表格

复制

特性 ISP IAP
** 触发 ** ** BOOT0=1 上电 ** ** 程序运行时**
**代码 ** ** ST内置Bootloader ** ** 用户写Bootloader+APP**
接口 USART1/USB 任意接口WiFiSD卡
**安全性 ** ** 低 谁都能下载 ** ** 高 可加密验证 **
用途 开发调试 **产品OTA升级 **

** ISP流程 **: ** BOOT0拉高+复位→STM32进入Bootloader→串口发0x7F握手→下载APP **

** IAP流程 **: ** APP收到OTA指令→下载固件到备份区→校验→改启动标志→复位→Bootloader搬运到运行区 **

** 注意**:** ISP会清空Flash**,** IAP要小心别跑飞**,**否则变砖**。


二、时钟与电源篇

6. STM32的供电引脚(VDD, VDDA, VBAT等)分别有什么作用?

  • VDD数字电源1.7V~3.6V给CPU、GPIO、外设供电

  • VDDA模拟电源给ADC、DAC、运放供电必须<=VDD噪声要滤干净

  • VBAT电池备份电源1.8V~3.6V主电源断时给RTC、备份寄存器供电电流<1μA

  • VREF+ADC参考电压可接VDDA或外部精密源决定ADC精度

  • VSS/VSSA数字/模拟地单点接地防干扰

设计要点VDDA和VSSA之间接10nF+1μF电容PCB上走线要粗


7. 什么是时钟树?为什么它在STM32中很重要? 时钟树是"时钟的分配网络"像水厂的管网把时钟源分频后送到各外设

重要性

  1. 性能72MHz主频但UART要115200波特率必须分频

  2. 功耗不用外设就关时钟省电

  3. 同步ADC和TIM要同步同根时钟源

  4. 灵活USB必须48MHz可PLL分频得到

STM32F103时钟树路径

复制

复制代码
HSE(8MHz) → PLL×9 → SYSCLK(72MHz) 
           ↓
       AHB(72MHz) → APB1(36MHz) → UART2/3/I2C
              ↓
          APB2(72MHz) → UART1/SPI1/TIM1/ADC

配置SystemInit() 里设RCC_CFGR 寄存器,CubeMX图形化配


8. 列举STM32的主要时钟源(HSI, HSE,LSI,LSE,PLL)。

  • HSI(High Speed Internal)内部RC振荡器8MHz精度±1%启动快但温漂大

  • HSE(High Speed External)外部晶振4-16MHz精度高(±10ppm)PLL源首选

  • LSI(Low Speed Internal)内部低速RC40kHz给IWDG/RTC精度差

  • LSE(Low Speed External)32768Hz晶振RTC专用精度高

  • PLL(Phase-Locked Loop)锁相环倍频HSE×9=72MHzSYSCLK源

选型HSE+PLL=高性能HSI=低成本LSE=RTC必备


9. 如何配置PLL以得到最高的系统时钟? STM32F103最高72MHzPLL倍频公式SYSCLK = HSE × PLLMUL

寄存器配置

c

复制

复制代码
// HSE=8MHz, PLLMUL=9, SYSCLK=72MHz
RCC->CR |= RCC_CR_HSEON;  // 开HSE
while(!(RCC->CR & RCC_CR_HSERDY));  // 等稳定

RCC->CFGR &= ~RCC_CFGR_PLLMULL;  // 清零
RCC->CFGR |= RCC_CFGR_PLLMULL9;  // 9倍频

RCC->CFGR |= RCC_CFGR_PLLSRC;  // PLL源=HSE
RCC->CR |= RCC_CR_PLLON;  // 开PLL
while(!(RCC->CR & RCC_CR_PLLRDY));  // 等锁定

RCC->CFGR |= RCC_CFGR_SW_PLL;  // SYSCLK=PLL
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);  // 确认

CubeMX输入HSE频率选PLL倍频自动算SYSCLK图形化配分频

注意PLL输出必须<=72MHz超频会死机


10. 什么是GPIO?STM32的GPIO有几种工作模式? GPIO(General Purpose Input/Output)通用输入输出引脚STM32的"手脚"

8种模式

  1. 输入模式

    • 浮空输入不上下拉电平不定适合按键

    • 上拉输入内部电阻拉VDD默认高

    • 下拉输入内部电阻拉GND默认低

    • 模拟输入连ADC禁用数字

  2. 输出模式

    • 推挽输出0/1都能推驱动强通用

    • 开漏输出只能拉低需外上拉I2C用

  3. 复用功能推挽/开漏的变体连UART/SPI

  4. 复用功能+上拉/下拉如USART_TX上拉,防悬空

配置

c

复制

复制代码
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

11. 推挽输出与开漏输出有什么区别?

表格

复制

特性 推挽(Push-Pull) 开漏(Open-Drain)
** 结构 ** ** P-MOS+N-MOS 都能导通 ** ** 只有N-MOS 只能拉低 **
** 输出 ** ** 0和1都能输出 强驱动 ** ** 只能输出0 1靠上拉 **
** 功耗 ** ** 低 不耗电 ** ** 高 上拉电阻耗电 **
** 速度 ** ** 快 边沿陡 ** ** 慢 RC充电 **
** 应用 ** ** LED 继电器 ** ** I2C **、 ** 线与逻辑 **

** 底层 :推挽是 图腾柱结构 开漏是"开集电极"的CMOS版 **。

** I2C必须用开漏 ,因为 多设备"线与"仲裁**,**推挽会短路**。


12. 如何将一个GPIO引脚配置为上拉输入模式? 寄存器写法

c

复制

复制代码
// PA0上拉输入
GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);  // 清零
GPIOA->CRL |= GPIO_CRL_CNF0_1;  // 上拉/下拉模式
GPIOA->BSRR = GPIO_BSRR_BS0;    // 上拉(BR0是下拉)

HAL库写法

c

复制

复制代码
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // IPU=Input Pull-Up
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

底层BSRR寄存器高16位BR复位低16位BS置位原子操作


13. 什么是复用功能?如何将一个PA9引脚配置为USART1_TX? 复用功能(Alternate Function)是 "引脚的多路复用"一个引脚可当GPIO/UART/SPI

AF映射表 :在DatasheetAlternate Function 章节查,PA9默认是USART1_TX

配置

c

复制

复制代码
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

// 2. PA9复用推挽
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 3. 配置USART1
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);

底层AFIO_MAPR寄存器 配置复用映射STM32F103默认映射F103C8T6的PB6/PB7也可map到USART1


14. 什么是NVIC?它在中断系统中起什么作用? NVIC(Nested Vectored Interrupt Controller)Cortex-M的"中断大管家"管理所有中断优先级和响应

作用

  1. 优先级管理8~256级可编程优先级数值越小越优先

  2. 嵌套支持高优先级能打断低优先级ISR

  3. 向量表存储每个ISR入口地址硬件自动跳转

  4. 中断使能/禁用ISER/ICER寄存器

  5. 挂起/清除ISPR/ICPR寄存器

寄存器操作

c

复制

复制代码
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;  // USART1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 抢占优先级0(最高)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

底层NVIC在SCS(System Control Space)0xE000E000系统空间


15. STM32的中断优先级是如何分组和管理的? 优先级分组抢占优先级 vs 子优先级的划分。

分组值SCB->AIRCRPRIGROUP[10:8]5种分法

表格

复制

分组 抢占 子优先级 总级数 场景
0 0位 4位 16 无嵌套全子优先级
1 1位 3位 2×8 **1级抢占 **
2 2位 2位 4×4 ** 平衡 **
3 3位 1位 8×2 ** 多抢占 **
4 4位 0位 16 ** 全抢占**,**无子优先级**

配置

c

复制

复制代码
// 分组2:2位抢占,2位子优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

// 某中断:抢占=1,子=2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;

抢占打断别的ISR子优先级同抢占时排队

推荐分组2(4×4)够用且灵活


16. 什么是EXTI?它如何工作? EXTI(External Interrupt)外部中断/事件控制器让GPIO能触发中断

工作原理

  • 19条线线0~15GPIO Pin0~Pin15PA0/PB0...共用线0 ),线16=PDD线17=RTC闹钟 , **线18=USB唤醒 **

** 配置步骤**:

c

复制

复制代码
// 1. 使能AFIO时钟(映射用)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

// 2. PA0映射到EXTI0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

// 3. EXTI配置
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;  // 线0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;  // 中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  // 上升沿
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

// 4. NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_Init(&NVIC_InitStructure);

** 注意 PA0和PB0不能同时用EXTI0 **, ** 一次只能选一个 **。


** 17. SysTick定时器有什么用途? ** ** SysTick是Cortex-M内核的"送奶工" 24位递减计数器 专给OS用 **。

** 用途 **:

  1. ** 系统节拍 **: ** FreeRTOS的心跳 **, ** 1ms中断一次 **,xTaskIncrementTick()

  2. ** 延时函数 **: ** HAL_Delay() while(delay--)不精确, SysTick准 **

  3. ** 性能测试 **: ** 测代码执行时间 **,start=SysTick->VAL; func(); end=SysTick->VAL;

** 配置 **:

c

复制

复制代码
// 72MHz,1ms中断
SysTick_Config(SystemCoreClock / 1000);  // 72000

void SysTick_Handler(void) {
    HAL_IncTick();  // HAL的ms计数+1
    xPortSysTickHandler();  // FreeRTOS节拍
}

** 寄存器 SysTick在0xE000E010 内核空间 所有Cortex-M都有 **。


** 18. 看门狗定时器是什么?独立看门狗和窗口看门狗有何区别? ** ** 看门狗是"系统监工" 程序跑飞时复位 **。

** 独立看门狗IWDG **:

  • ** 独立RC时钟 **(40kHz), ** CPU停它也走 **

  • ** 12位递减计数器 **, ** 喂狗写0xAAAA 超0复位 **

  • ** 简单 **, ** 防死循环 **, ** 时间窗固定 **

** 窗口看门狗WWDG **:

  • ** APB1时钟 **, ** 7位递减 **, ** 可配时间窗 **

  • ** 太早喂也复位 **, ** 防跑飞代码意外喂狗 **

  • ** 有EWI中断 **( ** 计到0x40提前预警 **)

区别

表格

复制

特性 IWDG WWDG
** 时钟 ** ** LSI独立 ** ** PCLK1依赖 **
** 时间窗 ** ** 无 ** ** 有 **(太早太晚都复位)
** 精度 ** ** 低 **(RC不准) ** 高 **
** 场景 ** ** 终极防护 ** ** 精确时间监控 **

配置

c

复制

复制代码
// IWDG:1秒超时
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_64);  // 40kHz/64=625Hz
IWDG_SetReload(625);  // 1秒
IWDG_ReloadCounter();  // 喂狗
IWDG_Enable();

// WWDG:100ms窗,50~100ms可喂
WWDG_Enable(0x7F);  // 启动,计数=127
WWDG_SetPrescaler(WWDG_Prescaler_8);  // 36MHz/8=4.5MHz
WWDG_SetWindowValue(0x5F);  // 窗口值=95,约50ms后
WWDG_EnableIT();  // 开EWI中断

19. 如何从待机模式中唤醒STM32? 待机模式是最低功耗(2μA)只有RTC和备份寄存器活着

唤醒源

  1. NRST复位按键复位

  2. RTC闹钟RTC_ALRRTC_WAKEUP

  3. WKUP引脚PA0(WKUP)上升沿

  4. IWDG复位喂狗失败

配置

c

复制

复制代码
// 1. 使能PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

// 2. 使能WKUP引脚(PA0)
PWR_WakeUpPinCmd(ENABLE);

// 3. 进入待机
PWR_EnterSTANDBYMode();  // 此函数不返回,唤醒后复位

RTC闹钟唤醒

c

复制

复制代码
// RTC配置闹钟
RTC_SetAlarm(RTC_GetCounter() + 10);  // 10秒后
RTC_ITConfig(RTC_IT_ALR, ENABLE);  // 使能闹钟中断
RTC_WaitForLastTask();

// 进待机
PWR_EnterSTANDBYMode();

注意待机唤醒=复位PC从0开始不是从WFI后面


20. 什么是位带操作?它有什么优势? 位带是Cortex-M的"硬件外挂"把一个位映射成一个字原子操作

映射公式

  • SRAM位带0x2200 0000 + (字节地址-0x2000 0000)×32 + 位号×4

  • 外设位带0x4200 0000 + (字节地址-0x4000 0000)×32 + 位号×4

优势

  1. 原子性读-改-写一气呵成不被中断撕裂

  2. 简洁BITBAND->PA_ODR8 = 1; GPIOA->ODR |= (1<<9); 安全

  3. 效率无锁无临界区开销

代码

c

复制

复制代码
// 位带别名区定义
#define BITBAND_PERIPH(addr, bit) \
  *(volatile uint32_t *)(PERIPH_BB_BASE + ((addr - PERIPH_BASE) << 5) + (bit << 2))

// 使用
#define LED_PIN  9
#define PA_ODR   (&GPIOA->ODR)

BITBAND_PERIPH(PA_ODR, LED_PIN) = 1;  // 亮
BITBAND_PERIPH(PA_ODR, LED_PIN) = 0;  // 灭

注意Cortex-M0/M0+无位带用LDREX/STREX模拟


三、通信协议篇

21. UART通信的基本原理是什么? UART是 "异步串口"两根线(TX/RX)全双工, **"说"和"听"同时进行 **。

** 一帧格式 **:

复制

复制代码
[起始位0] [数据位8位] [校验位] [停止位1] [空闲1]
   1bit      8bits     0/1bit    1bit      ...

** 原理 **:

  1. ** 波特率约定 双方约定速度 如115200 **

  2. ** 起始同步 起始位下降沿 接收方同步时钟 **

  3. ** 采样 16倍过采样 中间7-8-9三点投票 **

  4. ** 停止结束 **: ** 停止位高电平 帧间隔 **

** 底层 TX移位寄存器→引脚 RX引脚→移位寄存器 UART_SR状态寄存器标志收发完成


22. 如何配置UART以实现115200的波特率? 波特率公式UARTDIV = PCLK / (16 × 波特率)

PCLK

  • USART1:APB2=72MHz

  • USART2/3:APB1=36MHz

计算USART1DIV = 72M / (16×115200) = 39.0625

  • DIV_Mantissa = 39(整数)

  • **DIV_Fraction = 0.0625×16 = 1 **(小数)

** 寄存器配置 **:

c

复制

复制代码
// USART1, PCLK2=72MHz, 115200
USART1->BRR = (39 << 4) | 1;  // 0x0271

USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;  // 使能+收发

HAL库

c

复制

复制代码
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);

误差余数0.0625误差0.16%<2%没问题


23. UART如何通过中断方式接收不定长数据? ** 关键:接收空闲中断(IDLE)** + **DMA或RingBuffer **。

** 方案1:IDLE中断 **:

c

复制

复制代码
void USART1_IRQHandler(void) {
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
        // 1. 清IDLE标志
        uint16_t dummy = USART1->DR;
        
        // 2. 读取接收到的长度
        uint16_t len = UART_RX_BUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
        
        // 3. 处理数据
        process_uart_data(uart_rx_buf, len);
        
        // 4. 重新启动DMA
        DMA_Cmd(DMA1_Channel5, DISABLE);
        DMA1_Channel5->CNDTR = UART_RX_BUF_SIZE;
        DMA_Cmd(DMA1_Channel5, ENABLE);
    }
}

// 启动时
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);  // 使能IDLE中断

方案2:DMA+定时器

  • DMA收数据定时器10ms超时超时认为帧结束

方案3:字符超时

  • 每收到一个字节重载定时器超时则帧结束

优势IDLE中断是STM32硬件特性一帧结束自动触发CPU负担轻


24. 什么是DMA?它有什么好处? DMA(Direct Memory Access)"数据搬运工" , **不占用CPU 外设和内存直接传数据 **。

** 好处 **:

  1. ** 解放CPU 传数据时CPU干别的 或多睡会儿 **

  2. ** 高速 速度=外设总线速度 不经过CPU **

  3. ** 连续 批量传输 不中断 **

** STM32F103 DMA **:

  • ** 7通道 优先级可设 高/中/低 **)

  • ** 源/目的 外设→内存 内存→外设 内存→内存**

例子USART1->DRRAM收到100字节再中断CPU不用每字节干预

配置

c

复制

复制代码
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart_rx_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  // 外设→内存
DMA_InitStructure.DMA_BufferSize = 100;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  // 外设地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  // 正常模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  // 非内存到内存
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, ENABLE);

25. 如何配置UART使用DMA进行数据发送和接收? 发送DMA

c

复制

复制代码
// 1. 使能DMA和USART时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

// 2. 配置DMA发送通道(USART1_TX是DMA1_Channel4)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  // 内存→外设
DMA_InitStructure.DMA_BufferSize = len;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);

// 3. 使能USART的DMA发送
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

// 4. 启动DMA
DMA_Cmd(DMA1_Channel4, ENABLE);

// 5. 等待发送完成
while(DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC4);

接收DMA

c

复制

复制代码
// DMA1_Channel5是USART1_RX
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  // 外设→内存
DMA_InitStructure.DMA_BufferSize = RX_BUF_SIZE;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);

USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
DMA_Cmd(DMA1_Channel5, ENABLE);

关键DMA通道固定USART1_TX=CH4,USART1_RX=CH5查手册DMA请求映射表


26. SPI有几种工作模式?由什么信号决定? 4种模式由CPOL(时钟极性)和CPHA(时钟相位)决定

表格

复制

模式 CPOL CPHA 空闲电平 采样边沿
0 0 0 ** ** 上升沿采样 **
1 0 1 下降沿采样
2 1 0 下降沿采样
3 1 1 上升沿采样

**配置 **:

c

复制

复制代码
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;  // 空闲低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 第一个边沿采
// 模式0

** 由从设备决定 主设备必须匹配 W25Qxx Flash用模式0/3 TFT屏模式0 **。


**27. 如何配置SPI为主机全双工模式? **

c

复制

复制代码
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);

// 2. PA5/SCK, PA6/MISO, PA7/MOSI复用推挽
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // SCK/MOSI推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // MISO上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 3. SPI配置
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;  // 主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  // 8位
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;  // 模式0
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;  // 软件片选
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;  // 36MHz
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  // 高位先传
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);

// 4. 软件片选
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  // PA4=CS
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_4);  // CS高,不选中

收发

c

复制

复制代码
uint8_t SPI_RW(uint8_t data) {
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);  // 等发送空
    SPI_I2S_SendData(SPI1, data);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);  // 等接收非空
    return SPI_I2S_ReceiveData(SPI1);
}

28. I2C通信的起始信号和停止信号是如何定义的? 起始(START)SCL高电平时,SDA从高→低跳变 停止(STOP): **SCL高电平时,SDA从低→高跳变 **

复制

复制代码
SCL  ────┐          ┌─────────────
       │          │
       └──────────┘
SDA  ─────┐     ┌─────
       │     │
       └─────┘
       ↑START  ↑STOP

** 时序要求 tSU;STA > 4.7μs 标准模式 ), 建立时间 **, ** SDA必须在SCL高前拉低 **。

** 软件模拟 **:

c

复制

复制代码
void I2C_Start(void) {
    I2C_SDA_H;
    I2C_SCL_H;
    delay_us(5);
    I2C_SDA_L;  // SCL高时SDA下降
    delay_us(5);
    I2C_SCL_L;
}

void I2C_Stop(void) {
    I2C_SDA_L;
    I2C_SCL_H;
    delay_us(5);
    I2C_SDA_H;  // SCL高时SDA上升
    delay_us(5);
}

** 注意 起始后SDA只准在SCL低时变 高时保持稳定 **。


**29. 如何用软件模拟I2C时序? ** ** 用GPIO模拟 **, ** 开漏+上拉 SCL推挽 **。

** 代码框架**:

c

复制

复制代码
// SCL推挽
#define I2C_SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_6)
#define I2C_SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_6)

// SDA开漏,读取输入
#define I2C_SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_7)  // 释放,由上拉拉高
#define I2C_SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_7)
#define I2C_SDA_READ GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7)

void I2C_WBYTE(uint8_t byte) {
    for(int i=0; i<8; i++) {
        I2C_SCL_L;
        if(byte & 0x80) I2C_SDA_H;
        else I2C_SDA_L;
        delay_us(5);
        I2C_SCL_H;  // SCL上升沿,从机采样
        delay_us(5);
        byte <<= 1;
    }
    I2C_SCL_L;
}

uint8_t I2C_RBYTE(void) {
    uint8_t byte = 0;
    for(int i=0; i<8; i++) {
        I2C_SCL_H;
        delay_us(5);
        byte = (byte << 1) | I2C_SDA_READ;  // 读SDA
        I2C_SCL_L;
        delay_us(5);
    }
    return byte;
}

优势硬件I2C有bug时(F103早期版)软件模拟更可靠


** 30. I2C从机地址是如何组成的? ** 7位地址 = 4位固定 + 3位可配A0/A1/A2引脚

** 格式**:

[7位地址 << 1] | [R/W位]

  • addr = (slave_addr << 1) | 0( **0xA0 **)

  • ** 读 addr = (slave_addr << 1) | 1 0xA1 **)

** 例子 AT24C02地址0xA0 **, ** A0/A1/A2接地 **, ** 7位=0b1010000 **。

** 10位地址 扩展 **):

  • ** 两字节 **:11110 + 地址高2位 + R/W地址低8位

** 配置 手册查地址 左移1位再或R/W **。


** 31. STM32的硬件I2C在应用时需要注意什么? ** F103的硬件I2C有"暗病"注意事项

  1. ** 时钟配置 APB1时钟必须>2MHz 否则I2C死锁 **

  2. ** AFIO时钟 必须使能RCC_APB2ENR_AFIOEN 否则无法映射 **

  3. ** 时序配置 : ** I2C clock speed 标准100kHz 快速400kHz CCR寄存器

  4. ** 事件标志清除 **: ** 读SR1→读SR2 顺序错标志不清 **

  5. ** 死锁处理 **: ** BERR总线错误 AF应答失败 必须软件复位I2C **

  6. ** DMA配合 **: ** 大数据用DMA **, ** CPU不阻塞 **

  7. ** 上拉电阻 **: ** 必须外接4.7kΩ **, ** 内部上拉弱 **

** 死锁复位**:

c

复制

复制代码
void I2C_Reset(void) {
    I2C_Cmd(I2C1, DISABLE);
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);  // 复位
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
    I2C_Cmd(I2C1, ENABLE);
}

推荐 : **F103用硬件I2C F4/H7用I2C+DMA 稳定高效 **。


** 32. 比较UART、SPI和I2C三种通信协议的特点和适用场景。 **

表格

复制

协议 线数 速度 距离 主从 优点 缺点 场景
** UART ** ** 2 (TX/RX) ** ** 慢 **(115200bps) ** 长 **(15m) ** 无主 ** ** 简单 全双工** 异步、无流控易丢 调试、长距离
SPI 4+ (CLK/MOSI/MISO/CS) (10Mbps) (<1m) 一主多从 全双工高速 费线无仲裁 TFT屏、Flash
I2C 2 (SDA/SCL) (400kHz) (<1m) 多主 省线有仲裁 开漏上拉 EEPROM、传感器

选型调试用UART高速用SPI省线用I2C


33. 通用定时器有哪些主要功能? 通用定时器TIM2~TIM5"瑞士军刀"功能

  1. 定时/计数延时超时

  2. PWM输出4通道调电机/LED

  3. 输入捕获测脉冲宽度频率

  4. 输出比较翻转电平单脉冲

  5. 编码器接口电机测速

  6. 触发其他外设ADC/DAC

特性16位自动重装预分频1~655364个独立通道


**34. 如何配置定时器产生一个1kHz的PWM信号? ** ** 72MHz系统时钟 1kHz PWM **:

** 计算 **:

  • ** 周期=1ms 分频=72 计数值=1000 **

  • ** PSC=72-1=71 ARR=1000-1=999 **

** 代码 **:

c

复制

复制代码
// TIM2通道1,PA0输出PWM
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 999;  // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 71;  // PSC
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  // PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500;  // CCR=500,占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

TIM_Cmd(TIM2, ENABLE);

占空比TIM_SetCompare1(TIM2, duty); 0~999


35. PWM输出的频率和占空比由哪些寄存器决定? 频率ARR(自动重装载值) + PSC(预分频器) 占空比CCR(捕获比较值)

公式

复制

复制代码
PWM频率 = TIM_CLK / (PSC + 1) / (ARR + 1)
占空比 = CCR / (ARR + 1) × 100%

寄存器

  • TIMx_PSC预分频

  • TIMx_ARR周期

  • TIMx_CCRy通道y的比较值(y=1~4)

示例72MHzPSC=711MHzARR=9991kHzCCR=25025%占空比

动态调

c

复制

复制代码
TIM_SetAutoreload(TIM2, 999);  // 改频率
TIM_SetCompare1(TIM2, 250);    // 改占空比

36. 如何用定时器捕获一个外部脉冲的高电平宽度? 输入捕获+从模式复位

原理TI1FP1上升沿→捕获+复位计数器下降沿→捕获CCR2CCR2值=高电平时钟数

配置

c

复制

复制代码
// TIM2 TI1连外部脉冲
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;  // TI1
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);

TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;  // TI1FP2
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;  // 下降沿
TIM_ICInit(TIM2, &TIM_ICInitStructure);

// 从模式:复位模式,TI1FP1触发
TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);

TIM_Cmd(TIM2, ENABLE);

读取

c

复制

复制代码
uint32_t high_time = TIM_GetCapture2(TIM2);  // 高电平时间(时钟数)
float width_us = high_time / (SystemCoreClock / 1000000.0);

注意72MHz时钟,精度1μs捕获值16位,看是否溢出


37. 什么是定时器的编码器接口模式?如何使用? 编码器模式定时器的"外挂"硬件解码正交编码器信号A/B相)。

原理A相上升/下降沿+B相电平计数器自动加减不占用CPU

配置

c

复制

复制代码
// TIM2 CH1=PA0 (A相), CH2=PA1 (B相)
TIM_EncoderInterfaceConfig(TIM2, 
    TIM_EncoderMode_TI12,  // 两相都计数
    TIM_ICPolarity_Rising, // A相上升沿
    TIM_ICPolarity_Rising  // B相上升沿
);

TIM_Cmd(TIM2, ENABLE);

// 读位置
int32_t position = TIM_GetCounter(TIM2);  // 16位有符号

** 应用 **: ** 电机测速 位置反馈 机器人关节 **。

** 优势 硬件自动滤波 抗抖动 精度=编码器分辨率×4 **。


**38. 高级定时器(如TIM1)比通用定时器多了哪些功能? ** ** TIM1/TIM8是"豪华版" **:

  1. ** 重复计数器 更新N次才产生中断 适合步进电机 **

  2. ** 死区时间 互补PWM 插入死区 防上下管直通 **

  3. ** 刹车功能 外部故障信号 立即关PWM 保护电机 **

  4. ** 互补输出 CH1和CH1N反相 驱动H桥 **

  5. ** 触发ADC 多事件触发 同步采样 **

  6. ** 寄存器预装载 影子寄存器 **, ** 同步更新 **

** 场景 电机FOC控制 数字电源 需要保护的场合 **。

** 配置死区 **:

c

复制

复制代码
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_DeadTime = 0xFF;  // 死区时间
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);

39. 如何用定时器触发一个ADC转换? ADC外部触发, **TIMx_TRGO事件 **:

** 步骤 **:

  1. ** TIM配置为PWM模式 更新事件作为TRGO**

  2. ADC配置为外部触发TIMx_TRGO作为源

  3. TIM启动,每周期自动触发ADC

**TIM配置 **:

c

复制

复制代码
// TIM2 TRGO在更新时产生
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

** ADC配置 **:

c

复制

复制代码
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;  // TIM2触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;  // 上升沿
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);

** 应用 固定频率采样 电机电流采样 音频采集 **。


**40. ADC的分辨率是什么意思?STM32的ADC通常是多少位? ** ** 分辨率 = ADC能分辨的最小电压 **,Vmin = Vref / 2^n

STM32

  • F10312位( **0~4095 **)

  • ** F4/H7 12/16位可选 **

  • ** L4 12位 **

** 精度 ≠ 分辨率 **: ** 分辨率是理论值 , ** 精度看参考电压噪声

** 计算 **:

c

复制

复制代码
// Vref=3.3V,12位,测得2048
float voltage = 2048 * 3.3 / 4096;  // = 1.65V

** 优势 12位比10位(1024)精细4倍 能测0.8mV变化 **。


41. 什么是ADC的规则通道和注入通道? ** 规则通道 = 常规采样 **, ** 按顺序扫 **, ** 最多16个通道 注入通道 = 紧急采样 **, ** 可插队 , ** 最多4个

** 类比 规则=普通门诊 **, ** 注入=急诊 注入优先级高 **。

** 配置 **:

c

复制

复制代码
// 规则通道:CH1, CH2, CH3扫描
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_55Cycles5);

// 注入通道:CH4(紧急)
ADC_InjectedChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_13Cycles5);
ADC_InjectedSequencerLengthConfig(ADC1, 1);  // 1个注入通道

触发规则由软件/SYSCLK/TIM触发注入可TIM/外部引脚触发


42. 如何实现多通道ADC扫描转换? ** 步骤 **:

  1. ** 使能扫描模式 **:ADC_InitStructure.ADC_ScanConvMode = ENABLE;

  2. ** 配置多个规则通道 **: ** ADC_RegularChannelConfig() 指定序号 **

  3. ** 使能连续转换**:ADC_ContinuousConvModeCmd(ADC1, ENABLE);

  4. DMA搬运:**转换完自动填数组 **

** 代码 **:

c

复制

复制代码
#define ADC_CH_NUM 3
uint16_t adc_values[ADC_CH_NUM];

// 配置3个通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_55Cycles5);

// DMA配置
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  // 源固定
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_values;
DMA_InitStructure.DMA_BufferSize = ADC_CH_NUM;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  // 目标递增
DMA_Init(DMA1_Channel1, &DMA_InitStructure);

// ADC DMA使能
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 启动

结果adc_values[0]=CH1adc_values[1]=CH2adc_values[2]=CH3循环更新


43. ADC的采样时间如何影响转换结果? 采样时间 = **开关接通时间 让采样电容充电到输入电压 **。

** 公式 Tconv = 采样时间 + 12.5周期 12位转换需要**)

影响

  • ** 采样太短 电容没充满 读值偏小 误差大 **

  • ** 采样太长 转换慢 影响吞吐率 **

** 选择 **:

  • ** 低阻抗源 **( ** <1kΩ **) : ** 1.5周期 0.2μs **)

  • ** 高阻抗源 10kΩ **) : ** 55.5周期 7.7μs **)

  • ** 内部温度传感器 55.5周期 推荐 **)

** 配置 **:

c

复制

复制代码
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);

** 底层 ADC_SMPR1/SMPR2**寄存器,**3位/通道**。


44. 如何校准ADC? 校准 =消除内部电容失调上电后必须做

步骤

c

复制

复制代码
// 1. 等待ADC稳定
ADC_Cmd(ADC1, ENABLE);
delay_ms(2);

// 2. 启动校准
ADC_ResetCalibration(ADC1);  // 复位校准
while(ADC_GetResetCalibrationStatus(ADC1));  // 等完成

ADC_StartCalibration(ADC1);  // 开始校准
while(ADC_GetCalibrationStatus(ADC1));  // 等完成

// 3. 校准完成,可以转换

**校准值 **: ** ADC_DR 寄存器 读回校准结果 ST生产时校准过 用户可不复用 **。

** 注意 每次上电校准一次 温度变化大时重新校准 **。


45. DAC的主要用途是什么? DAC =**数模转换器 **, ** 数字→模拟电压 输出连续信号 **。

用途

  1. 信号发生器正弦波三角波方波

  2. 音频输出播放语音

  3. 控制电压调电机转速LED亮度

  4. 测试给外部电路提供参考电压

STM32F1032个DAC通道12位速度1Msps

配置

c

复制

复制代码
DAC_InitTypeDef DAC_InitStructure;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_Software;  // 软件触发
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;  // 使能输出缓冲
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_1, ENABLE);

// 输出电压
DAC_SetChannel1Data(DAC_Align_12b_R, 2048);  // 3.3V/2=1.65V

注意DAC输出是PA4/PA5负载能力 <5mA


46. 比较器的工作原理是什么? 比较器 =**1位ADC **, ** 比较两电压 输出0/1 **。

** 输入 **: ** IN+(正) IN-(负) 输出 **: ** OUT **

** 原理 IN+ > IN- → OUT=1(VDD) IN+ < IN- → OUT=0(GND) **

** STM32比较器 F3/L4有 F103无 **):

  • ** 迟滞 **: ** 防振荡 IN+在IN-附近抖动时不跳变 **

  • ** 滤波 **: ** 数字滤波 去毛刺 **

  • **唤醒 输出变1唤醒CPU **

应用过零检测电源掉电检测快速保护比ADC快)。


47. 什么是实时时钟?如何配置RTC并产生一个闹钟中断? RTC(Real-Time Clock)"万年历"掉电仍走VBAT供电)。

功能秒计数闹钟 、** wakeup 入侵检测 **。

** 配置 **:

c

复制

复制代码
// 1. 使能PWR和BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

// 2. 允许访问备份寄存器
PWR_BackupAccessCmd(ENABLE);

// 3. 配置LSE
RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);  // 等LSE稳定

// 4. RTC时钟源=LSE
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);

// 5. 等RTC同步
RTC_WaitForSynchro();

// 6. 配置闹钟
RTC_SetAlarm(RTC_GetCounter() + 10);  // 10秒后闹钟
RTC_ITConfig(RTC_IT_ALR, ENABLE);

// 7. NVIC
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_Init(&NVIC_InitStructure);

void RTC_IRQHandler(void) {
    if(RTC_GetITStatus(RTC_IT_ALR) != RESET) {
        RTC_ClearITPendingBit(RTC_IT_ALR);
        printf("Alarm!\r\n");
    }
}

时间设置RTC_SetCounter(Unix时间)1970年1月1日0点起秒数


48. 如何将数据备份到RTC的备份寄存器中? 备份寄存器BKP =20个16位寄存器VBAT供电掉电保存

用途存用户配置校准参数

代码

c

复制

复制代码
// 1. 使能时钟+访问
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);

// 2. 写入
BKP_WriteBackupRegister(BKP_DR1, 0x1234);  // DR1存0x1234

// 3. 读取
uint16_t data = BKP_ReadBackupRegister(BKP_DR1);

注意主电源断后,VBAT必须供电否则数据丢失


49. 芯片唯一ID有什么用途? 唯一ID=96位工厂烧录全球唯一地址0x1FFFF7E8

用途

  1. ** 产品序列号 : ** 设备身份证

  2. 加密ID+密钥生成许可证

  3. 防克隆ID不匹配不运行

  4. 追踪生产/维修记录

读取

c

复制

复制代码
uint16_t *uid = (uint16_t *)0x1FFFF7E8;
printf("UID: %04X %04X %04X\r\n", uid[0], uid[1], uid[2]);

注意ID不是随机数可预测加密要加盐


50. Flash存储器是如何组织的?什么是页,什么是扇区? STM32F103 Flash=512KB=64页×2KB(每页)

页(Page)最小擦除单位2KB(中容量)擦除时间20ms

扇区(Sector)大容量芯片(F103ZE)=128KB×4扇区擦除时间1s

**组织 **:

复制

复制代码
地址          内容
0x0800 0000  页0(2KB)
0x0800 0800  页1
...
0x0807 F800  页63

** 选项字节**:** 0x1FFFF800**,**配置读保护、写保护、用户数据**。

注意Flash寿命10万次频繁写要磨损均衡


51. 如何对内部Flash进行读写操作? **步骤:解锁→擦→写→锁 **。

** 代码 **:

c

复制

复制代码
// 1. 解锁Flash
FLASH_Unlock();

// 2. 擦除页(必须整页)
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
FLASH_ErasePage(0x08008000);  // 擦页1

// 3. 写半字(16位)
FLASH_ProgramHalfWord(0x08008000, 0x1234);  // 地址必须2字节对齐

// 4. 上锁
FLASH_Lock();

// 5. 读取
uint16_t data = *(uint16_t *)0x08008000;

注意写前必须擦擦后全0xFF只能写0不能写1


52. 什么是选项字节?如何配置它? 选项字节 =**Flash的"配置页" 存读保护、写保护、看门狗模式、用户数据 **。

** 地址 **: ** 0x1FFFF800 OB_RDR **), ** 16字节 **。

** 位定义 **:

  • ** RDP **: ** 读保护 , ** 0xA5=不保护0xXX=保护

  • USER看门狗、复位模式

  • WRP0~WRP3写保护每4页1位

配置

c

复制

复制代码
// 1. 解锁OB
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_OPTVERR);

// 2. 解锁选项字节
FLASH->OPTKEYR = 0x45670123;  // KEY1
FLASH->OPTKEYR = 0xCDEF89AB;  // KEY2

// 3. 修改OB
FLASH->OB_RDR = 0xA5;  // 解除读保护

// 4. 启动OB修改
FLASH->CR |= FLASH_CR_OPTSTRT;
while(FLASH->CR & FLASH_CR_OPTSTRT);  // 等完成

// 5. 复位生效
NVIC_SystemReset();

**CubeProgrammer 图形化改OB 简单安全 **。


** 53. 在STM32中如何实现一个简单的引导程序?** **Bootloader=小系统+搬APP **。

** 设计 **:

  1. ** 启动判断 : ** 按键/标志Bootloader or APP

  2. 通信USART/YModem协议接收固件

  3. 写Flash按页擦写

  4. 跳转APP关中断→设SP→设PC =APP地址+4

代码

c

复制

复制代码
// 1. 启动判断
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) {  // 按键按下
    run_bootloader();
} else {
    jump_to_app();
}

// 2. 跳转APP
typedef void (*pAppEntry)(void);
void jump_to_app(void) {
    uint32_t app_addr = 0x08004000;  // APP在页2
    
    // 检查栈顶合法性(RAM范围)
    if(((*(uint32_t *)app_addr) & 0x2FFE0000) == 0x20000000) {
        __disable_irq();  // 关中断
        SCB->VTOR = app_addr;  // 重定位向量表(F4+)
        pAppEntry app_entry = (pAppEntry)*(uint32_t *)(app_addr + 4);
        __set_MSP(*(uint32_t *)app_addr);  // 设主栈指针
        app_entry();  // 跳转
    }
}

// 3. YModem接收
void ymodem_recv(void) {
    uint8_t buf[1024];
    while(1) {
        uart_recv(buf, 1024);
        if(buf[0] == SOH) {  // 开始头
            FLASH_ProgramPage(addr, buf+3);
            addr += 1024;
        }
    }
}

** 协议 YModem CRC校验**,**断点续传**。


54. 如何将程序从内部Flash搬运到外部RAM中运行以提高速度? F103无外部RAMF4/H7有FSMC/FMC

步骤

  1. ** 链接脚本 **: ** .text段放外部RAM 启动文件搬运 **

  2. ** 启动文件 : ** 复制代码到外部RAM 跳转执行

** LD文件**:

ld

复制

复制代码
MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
  RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K
  EXT_RAM (rxw) : ORIGIN = 0x60000000, LENGTH = 8M  // FSMC地址
}

SECTIONS
{
  .ext_code :
  {
    *(.ext_text*)
  } > EXT_RAM AT > FLASH  // 运行时载外部RAM,镜像存Flash
}

搬运

c

复制

复制代码
// 启动时
void CopyExtCode(void) {
    extern uint32_t _siext_text, _sext_text, _svextext;
    memcpy(&_sext_text, &_siext_text, &_sext_text - &_svextext);
}

** 实际 内部Flash 72MHz足够 外部RAM慢 一般只放数据 不放代码 **。


** 55. 什么是内存管理单元?哪些Cortex-M内核拥有它? ** ** MMU(Memory Management Unit) **= ** 虚拟内存 **, ** 地址转换 **, ** Linux需要 **。

** Cortex-M 无MMU 只有MPU(Memory Protection Unit) M3/M4/M7/M33有 **)。

MPU =**内存保护 **, ** 分8区 **, ** 设权限 防越界 **。

** MMU vs MPU **:

  • ** MMU 虚拟→物理 **, ** 页表 **, ** 支持Linux **(Cortex-A)

  • ** MPU 物理保护 **, ** 无虚拟 **, ** 裸机/RTOS **

** STM32 MPU **, ** 配置见问题57 **。


56. 如何配置MPU以保护不同的内存区域? **MPU=系统保镖 **。

** 配置 **:

c

复制

复制代码
MPU_Region_InitTypeDef MPU_InitStruct;
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x20000000;  // RAM
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;  // 全访问
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = 0;  // 区0
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;  // 可执行
HAL_MPU_ConfigRegion(&MPU_InitStruct);

// 使能MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

** 栈保护 **: ** 栈底设MPU区 **, ** 大小0KB 访问违规触发Fault **。


**57. 什么是DSP指令集?哪些STM32系列支持它? ** ** DSP指令 **= ** ARM的"数学加速包" 单指令多数据 **。

**指令 **:

  • ** SMLABB/SMLABT 16×16+32累加 **, ** MAC 乘累加 **)

  • ** SMULxy 16×16→32 滤波**

  • ** MLA** = **1周期乘加 **, ** C语言几十周期 **

** 支持 **:

  • ** Cortex-M4/M7/M33 **: ** 全DSP **

  • ** 系列**:** F4/H7/G4**,** 标志=** __ARM_FEATURE_DSP

使用

c

复制

复制代码
#include "arm_math.h"

// FFT要用DSP库
arm_rfft_fast_instance_f32 fft_inst;
arm_rfft_fast_init_f32(&fft_inst, 1024);
arm_rfft_fast_f32(&fft_inst, input, output, 0);  // 硬件加速

效果 :**FFT提速3-5倍 电机FOC必备 **。


58. 如何用STM32实现一个简单的FFT? **步骤 **:

  1. ** 采集数据 ADC采样1024点 **

  2. ** 加窗 汉宁窗 **, ** 防频谱泄露 **

  3. ** 调用DSP库 **: ** arm_rfft_fast_f32 **

  4. ** 算幅值 sqrt(real²+imag²)**

代码

c

复制

复制代码
#define FFT_SIZE 1024
float32_t input[FFT_SIZE];
float32_t output[FFT_SIZE];
float32_t fft_mag[FFT_SIZE/2];

// 1. ADC采样填充input
for(int i=0; i<FFT_SIZE; i++) {
    input[i] = (float32_t)read_adc();
}

// 2. 加汉宁窗
for(int i=0; i<FFT_SIZE; i++) {
    input[i] *= 0.5 * (1 - cosf(2*PI*i/(FFT_SIZE-1)));
}

// 3. FFT
arm_rfft_fast_instance_f32 fft_inst;
arm_rfft_fast_init_f32(&fft_inst, FFT_SIZE);
arm_rfft_fast_f32(&fft_inst, input, output, 0);

// 4. 算幅值
arm_cmplx_mag_f32(output, fft_mag, FFT_SIZE/2);

// 5. 找峰值频率
uint32_t max_index = 0;
arm_max_f32(fft_mag, FFT_SIZE/2, &max_val, &max_index);
float freq = max_index * Fs / FFT_SIZE;  // Fs=采样率

资源F103 M3无DSP,FFT慢F4/H7用DSP快


59. 浮点单元是什么?哪些STM32系列拥有硬件FPU? FPU(Floating Point Unit)=硬件浮点加速器单精度/双精度指令

支持

  • Cortex-M4F/M7单精度FPUfloat

  • Cortex-M7双精度FPUdouble

  • 系列F4/H7/G4/L4+

使用

c

复制

复制代码
// 编译选项:-mfloat-abi=hard -mfpu=fpv4-sp-d16

float a = 1.23f, b = 4.56f;
float c = a * b;  // 单周期完成,不用软件库

性能F103软件浮点=100周期F4硬件=1周期,**快100倍 **。


60. 使用FPU需要注意什么? 注意

  1. ** 编译选项 **: ** makefile **, ** -mfloat-abi=hard -mfpu=fpv4-sp-d16 **

  2. ** 中断保存 : ** FPU寄存器 **S0-S31 **, ** 进出中断要压栈 **( ** 自动 FPCCR寄存器 **)

  3. ** RTOS 任务切换要保存FPU上下文 FreeRTOS需配置**

    c

    复制

    复制代码
    // FreeRTOSConfig.h
    #define configUSE_TASK_FPU_SUPPORT 1
  4. 功耗FPU开=+5mA不用时关

    c

    复制

    复制代码
    SCB->CPACR |= (0xF << 20);  // 使能FPU

陷阱混用hard和soft floatABI不兼容链接失败


**61. 以太网MAC和PHY的区别是什么? **

  • ** MAC(Media Access Control) **: ** 数据链路层 **, ** 帧收发 CRC校验 DMA **, ** STM32内置 **

  • ** PHY(Physical Layer) **: ** 物理层 **, ** 模拟信号 编码 RJ45接口 **, ** 外部芯片(如DP83848) **

** 关系 MAC→MII/RMII接口→PHY→网线 **。

**配置 **: ** PHY地址 **( ** 0/1 ) , 自动协商 Speed/Duplex **。


**62. 如何配置LWIP协议栈以实现TCP通信? ** ** LWIP=LightWeight IP **, ** 嵌入式TCP/IP **。

** 步骤**:

  1. CubeMX :** 使能ETH 配置PHY 选LWIP **

  2. ** 代码**:

    c

    复制

    复制代码
    // 初始化
    MX_LWIP_Init();
    
    // 创建TCP服务器
    struct tcp_pcb *pcb = tcp_new();
    tcp_bind(pcb, IP_ADDR_ANY, 8080);
    pcb = tcp_listen(pcb);
    tcp_accept(pcb, tcp_accept_callback);
  3. ** 回调**:

    c

    复制

    复制代码
    err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) {
        tcp_recv(newpcb, tcp_recv_callback);
        return ERR_OK;
    }

内存:**F103不够用 **, ** F4/H7推荐 **, ** RAM至少64KB **。


63. USB有哪几种传输类型?各自适用于什么场景?

表格

复制

类型 方向 速度 可靠性 场景
** 控制 **(Control) 双向 ** 满速 ** ** 高 ** ** 枚举 ** 、配置
批量(Bulk) 单向 ** 慢/高速 ** ** 高** U盘打印机
中断(Interrupt) 单向 ** 满速 ** ** 高 ** HID鼠标键盘
等时(Isochronous) 单向 ** 高速 ** ** 低 ** ** 音频 ** 、视频

批量带宽大可重传等时实时性强丢包不重传


**64. 如何将STM32配置为一个USB HID设备? ** ** HID(Human Interface Device) **= ** 免驱 **, ** 鼠标键盘 **。

** 步骤 **:

  1. ** CubeMX **: ** USB_DEVICE→HID **, ** 生成 **

  2. ** 报告描述符 **: ** 定义数据格式 8字节报表 **

  3. ** 发送数据 **:

    c

    复制

    复制代码
    uint8_t hid_data[8] = {0};  // x, y, buttons
    hid_data[1] = 10;  // x移动
    USBD_HID_SendReport(&hUsbDeviceFS, hid_data, 8);

报表描述符

c

复制

复制代码
0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x02,        // Usage (Mouse)
0xA1, 0x01,        // Collection (Application)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x05, 0x09,        //     Usage Page (Buttons)
0x19, 0x01,        //     Usage Minimum (0x01)
0x29, 0x03,        //     Usage Maximum (0x03)

优势无需驱动Windows/Linux即插即用


**65. 如何将STM32配置为一个USB CDC设备(虚拟串口)? ** ** CDC(Communication Device Class) **= ** 虚拟COM **, ** 电脑认作串口 **。

** 步骤**:

  1. CubeMX :** USB_DEVICE→CDC 生成 **

  2. ** 代码自动 : ** USBD_CDC_SetTxBuffer() USBD_CDC_TransmitPacket()

  3. 接收: ** CDC_Receive_FS()回调 **

发送

c

复制

复制代码
uint8_t data[] = "Hello USB\r\n";
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, data, strlen(data));
USBD_CDC_TransmitPacket(&hUsbDeviceFS);

应用 : **USB转串口 调试神器 速度12Mbps **。


** 66. CAN总线的基本帧、扩展帧和远程帧有什么区别? **

表格

复制

帧类型 ID位数 用途 帧格式
** 基本帧 ** ** 11位 ** ** 标准通信 ** ** 0x000~0x7FF **
** 扩展帧 ** ** 29位 ** ** 大量设备 ** ** 0x00000000~0x1FFFFFFF **
** 远程帧 ** ** 11/29位 ** ** 请求数据 ** ** RTR=1 无数据场 **

扩展帧 :**ID=29位=11位基ID+18位扩展ID IDE位=1 **。

** 远程帧 **: ** 无数据 帧类型RTR=1 请求对方发数据 **。

** 仲裁 基本帧优先级>扩展帧 ID小优先 **)。


67. 如何配置CAN总线过滤器? **过滤器=硬件分拣器 **, ** 只收关心的ID **。

模式

  • ** 列表模式 精确匹配 收4个ID **

  • ** 掩码模式 模糊匹配 收一类ID **

**代码 **:

c

复制

复制代码
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x123 << 5;  // ID左移5位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x7FF << 5;  // 掩码:所有位匹配
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;  // 掩码模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;  // 16位
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);

**收0x100~0x1FF一类ID **:

复制

复制代码
Mask = 0x700  // 高3位关心
Id   = 0x100  // 基地址

**68. 如何计算CAN总线的波特率? ** ** 波特率公式 **: Bitrate = APB1CLK / (BRP × (1 + BS1 + BS2))

** 参数 **:

  • **BRP(波特率预分频) **: ** 1~1024 **

  • ** BS1(时间段1) **: ** 1~16Tq **

  • ** BS2(时间段2) **: ** 1~8Tq **

  • ** 采样点 **= ** (1 + BS1) / (1 + BS1 + BS2) 推荐85% **

** 计算 APB1=36MHz 目标250kbps **:

复制

复制代码
BRP=9, BS1=8, BS2=3
Bitrate = 36M / (9 × (1+8+3)) = 36M / 108 = 250k

** 配置 **:

c

复制

复制代码
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Prescaler = 9;

**69. SDIO接口和SPI模式驱动SD卡有何区别? **

表格

复制

特性 SDIO(4位) SPI(1位)
** 速度 ** ** 快 48MHz **) ** 慢 20MHz **)
** 引脚 ** ** CLK/CMD/D0-D3 ** ** CLK/MOSI/MISO/CS **
** 协议 ** ** SDIO协议 ** ** SPI协议 **
** CPU ** ** DMA ** 中断/DMA
** 功耗 ** ** 高 ** ** 低 **
** 适用 ** ** 高速 ** ** 低速/省线 **

SDIO:**F4/H7有 **, ** F103无 **, ** 用SPI模式 **。


**70. 如何通过FSMC接口驱动TFT液晶屏? **

继续你的回答

编辑

复制

分享

STM32关键技术问答汇总

  1. ** 什么是LTDC?它有什么作用? ** ** LTDC(LCD-TFT Display Controller) STM32F4/H7的"显卡" 直接驱动RGB接口的液晶屏 **, ** 无需SPI/I2C **。

** 作用 **:

  • ** 并口 **: ** 24位RGB数据线 时钟 HSYNC VSYNC **, ** DE **

  • ** 显存 **: ** 在RAM中划一块 framebuffer **, ** LTDC自动刷 **

  • ** 图层 **: ** 2层叠加 **, ** 透明度混合 **, ** 硬件加速 **

  • ** 分辨率 **: ** 最大1024×768 **( ** H7可到4K **)

** 配置 **:

c

复制

复制代码
// LTDC时序配置(800×480屏)
LTDC_InitTypeDef LTDC_InitStruct;
LTDC_InitStruct.LTDC_HSPolarity = LTDC_HSPolarity_AL;  // HSYNC低有效
LTDC_InitStruct.LTDC_VSPolarity = LTDC_VSPolarity_AL;
LTDC_InitStruct.LTDC_DEPolarity = LTDC_DEPolarity_AL;
LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IPC;
LTDC_InitStruct.LTDC_HorizontalSync = 48 - 1;
LTDC_InitStruct.LTDC_VerticalSync = 3 - 1;
LTDC_InitStruct.LTDC_AccumulatedHBP = 48 + 88 - 1;
LTDC_InitStruct.LTDC_AccumulatedVBP = 3 + 32 - 1;
LTDC_InitStruct.LTDC_AccumulatedActiveW = 48 + 88 + 800 - 1;
LTDC_InitStruct.LTDC_AccumulatedActiveH = 3 + 32 + 480 - 1;
LTDC_InitStruct.LTDC_TotalWidth = 48 + 88 + 800 + 40 - 1;
LTDC_InitStruct.LTDC_TotalHeigh = 3 + 32 + 480 + 13 - 1;
LTDC_Init(&LTDC_InitStruct);

** 显存 **: ** 在SDRAM中划800×480×4字节(RGB888) **。


72. DCMI接口用于什么场景? ** DCMI(Digital Camera Interface) STM32的"摄像头接口" **, ** 并口 **, ** 8/10/12/14位数据线 **。

** 场景 **: ** 接摄像头 **( ** OV2640 OV5640 **), ** 图像采集 条码扫描 机器视觉 **。

** 信号**:** PIXCLK像素时钟 VSYNC帧同步 HSYNC行同步 DATA[0..13] **。

**配置 **:

c

复制

复制代码
DCMI_InitTypeDef DCMI_InitStructure;
DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_Continuous;  // 连续采集
DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;    // 硬件同步
DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising;      // 上升沿采样
DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low;
DCMI_InitStructure.DCMI_VSPolarity = DCMI_VSPolarity_Low;
DCMI_InitStructure.DCMI_DataExtendedMode = DCMI_DataExtendedMode_8b;  // 8位数据
DCMI_Init(&DCMI_InitStructure);

DMADCMI+DMA2, ** 直接存SDRAM **, ** 不占用CPU **。


73. 如何用STM32驱动一个WS2812B LED? ** ** WS2812B="时序怪物" ,**0/1靠400ns/800ns脉宽区分 必须单周期精度 **。

**方案1:GPIO+NOP延时 F103够用 **):

c

复制

复制代码
#define NOP __NOP()

void ws2812_send_byte(uint8_t byte) {
    for(int i=0; i<8; i++) {
        if(byte & 0x80) {  // 1码:800ns高
            GPIO_SetBits(GPIOA, GPIO_Pin_0);
            NOP; NOP; NOP;  // 12个NOP≈800ns@72MHz
            // 共15个NOP
            GPIO_ResetBits(GPIOA, GPIO_Pin_0);
        } else {  // 0码:400ns高
            GPIO_SetBits(GPIOA, GPIO_Pin_0);
            NOP; NOP;  // 6个NOP≈400ns
            GPIO_ResetBits(GPIOA, GPIO_Pin_0);
        }
        byte <<= 1;
    }
}

方案2:SPI DMA:**SPI波特率=2.4MHz **, ** 00111100B=1码 **, ** 00011100B=0码 **。

** 方案3:PWM+DMA **: ** TIM2 PWM **, ** DMA更新CCR 自动序列 **。

** 要点 **: ** 关中断 防时序被打断 **。


**74. 如何读取旋转编码器的值? ** ** 编码器=正交AB相 用定时器编码器模式 **。

** 电路 **: ** A相接TIM2_CH1 B相接TIM2_CH2 **。

代码

c

复制

复制代码
// TIM2编码器模式
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, 
                           TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

TIM_Cmd(TIM2, ENABLE);

// 读值(有符号)
int16_t encoder_val = TIM_GetCounter(TIM2);  // 正转+1,反转-1

// 清0
TIM_SetCounter(TIM2, 0);

** 防抖 **: ** 硬件编码器模式自动滤毛刺 **, ** 或用GPIO中断+状态机 **。

** 应用 **: ** 音量调节 菜单选择 电机位置反馈 **。


75. 如何通过STM32驱动一个步进电机? **步进电机=脉冲驱动 **, ** 每脉冲转1.8° **。

** 驱动方式 **:

  1. ** GPIO直接驱动 **: ** 电流<20mA **, ** 只能驱微型步进 **

  2. ** ULN2003 **: ** 达林顿管阵列 电流500mA **, ** 5V步进 **

  3. ** A4988/TMC2209 **: ** 专用驱动 电流2A **, ** 细分 **

** 代码(四相八拍) **:

c

复制

复制代码
uint8_t phase_seq[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09};

void step_motor(int steps) {
    static uint8_t idx = 0;
    for(int i=0; i<abs(steps); i++) {
        if(steps > 0) idx++;  // 正转
        else idx--;           // 反转
        idx %= 8;
        GPIO_Write(GPIOA, phase_seq[idx]);  // 输出到PA0-PA3
        delay_ms(2);  // 速度控制
    }
}

** 速度 **: ** delay_ms小 **→ ** 转速快 但 torque下降 需加减速曲线 **。

** 加减速 **: ** S曲线 **, ** 起步慢→加速→匀速→减速→停止 **。


** 76. 如何实现一个简单的PID控制器? ** ** PID=Proportional-Integral-Derivative **, ** 反馈控制经典算法 **。

** 公式**:U(t) = Kp×e(t) + Ki×∫e(t)dt + Kd×de(t)/dt

代码

c

复制

复制代码
typedef struct {
    float Kp, Ki, Kd;
    float setpoint;
    float integral;
    float last_error;
    float output_limit;
} pid_t;

float pid_calc(pid_t *pid, float measured) {
    float error = pid->setpoint - measured;
    
    // P
    float P = pid->Kp * error;
    
    // I
    pid->integral += error;
    float I = pid->Ki * pid->integral;
    
    // D
    float D = pid->Kd * (error - pid->last_error);
    pid->last_error = error;
    
    // 输出
    float output = P + I + D;
    
    // 限幅
    if(output > pid->output_limit) output = pid->output_limit;
    if(output < -pid->output_limit) output = -pid->output_limit;
    
    return output;
}

// 使用
pid_t motor_pid = {Kp: 1.0, Ki: 0.1, Kd: 0.01, setpoint: 1000, output_limit: 255};
while(1) {
    float speed = read_encoder();
    float pwm = pid_calc(&motor_pid, speed);
    set_motor_pwm(pwm);
    delay_ms(10);
}

** 调参 : ** Ziegler-Nichols法 , ** 先Kp振荡 再Ki消除静差**→**最后Kd抑制超调 **。


** 77. 在STM32上如何运行FreeRTOS? ** ** 步骤 **:

  1. ** 移植 **: ** source/portable/GCC/ARM_CM3 **, ** port.c **+ ** heap_4.c **

  2. ** 中断 **: ** SysTick中断 **→ ** xPortSysTickHandler() **

  3. ** 配置 FreeRTOSConfig.h **

    c

    复制

    复制代码
    #define configTICK_RATE_HZ 1000
    #define configTOTAL_HEAP_SIZE (8 * 1024)
  4. 创建任务

    c

    复制

    复制代码
    void task1(void *pv) {
        while(1) {
            GPIO_ToggleBits(GPIOA, GPIO_Pin_0);
            vTaskDelay(pdMS_TO_TICKS(500));  // 延时500ms
        }
    }
    
    int main(void) {
        SystemInit();
        xTaskCreate(task1, "LED", 128, NULL, 1, NULL);
        vTaskStartScheduler();  // 启动调度器
    }

** 启动 vTaskStartScheduler() → ** 创建空闲任务→ ** 启动第一个任务 **。


78. 在FreeRTOS中,任务有哪几种状态? 4种状态

  1. ** 就绪(Ready) 万事俱备,只欠CPU 等调度 **

  2. ** 运行(Running) 正在执行 **

  3. ** 阻塞(Blocked) 等事件**,**挂起 **, ** 不占CPU **

  4. ** 暂停(Suspended) vTaskSuspend() 人工暂停 不调度 **

** 转换 **:

复制

复制代码
Running → Blocked: vTaskDelay()/xSemaphoreTake()
Blocked → Ready: 超时 or 事件到
Running → Ready: 时间片到 or 高优先级就绪
Ready → Running: 调度器选择
Running → Suspended: vTaskSuspend()
Suspended → Ready: vTaskResume()

**79. 什么是信号量、互斥量和消息队列? **

  • ** 信号量(Semaphore) **= ** 资源计数 N个资源 拿-1,放+1 **

    c

    复制

    复制代码
    SemaphoreHandle_t sem = xSemaphoreCreateCounting(3, 3);  // 3个资源
    xSemaphoreTake(sem, portMAX_DELAY);  // 拿
    xSemaphoreGive(sem);  // 放
  • ** 互斥量(Mutex) **= ** 二值信号量+优先级继承 保护临界区 **

    c

    复制

    复制代码
    SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
    xSemaphoreTake(mutex, portMAX_DELAY);
    shared_var++;
    xSemaphoreGive(mutex);
  • ** 消息队列(Queue) **= ** 数据缓冲 先进先出 **

    c

    复制

    复制代码
    QueueHandle_t queue = xQueueCreate(10, sizeof(uint32_t));
    xQueueSend(queue, &data, 100);
    xQueueReceive(queue, &data, portMAX_DELAY);

区别信号量计数互斥量防优先级反转队列传数据


80. 如何避免任务间的优先级反转? ** 优先级反转 **: ** 低优先任务持锁 高优先级饿死 **。

** 解决 **:

  1. 互斥量FreeRTOS自动优先级继承低优先级临时升

  2. 优先级天花板锁的优先级=最高可能拿锁的任务

  3. 无锁设计消息队列代替共享变量 ->同步用队列

  4. 关中断临界区极短<5行代码

最佳用互斥量简单有效


81. 什么是内存堆碎片?如何应对? 堆碎片=malloc/free后,内存千疮百孔无法分配连续大块

**应对 **:

  1. ** 不用malloc **: ** 静态分配 **

  2. ** 内存池 固定大小**,**无碎片 **

    c

    复制

    复制代码
    #define BLOCK_SIZE 64
    uint8_t mempool[BLOCK_SIZE * 10];
  3. ** TLSF分配器 **: ** 嵌入式专用 碎片率低 **

  4. ** 定期整理 重启复位 **

  5. ** MPU保护 检测越界 **, ** HardFault **

** RTOS **: ** FreeRTOS的heap_4.c有碎片合并 **。


** 82. 如何使用STM32CubeMX生成初始化代码? ** ** 步骤 **:

  1. ** 新建项目 **: ** 选芯片型号 **

  2. ** 时钟树 **: ** HSE输入8M PLL×9=72M **

  3. ** 外设 **: ** 点UART1(Asynchronous) 自动引脚PA9/PA10 **

  4. **参数 **: ** Baud=115200 NVIC使能 **

  5. ** 项目设置 Toolchain=MDK-ARM **, ** 包名 **

  6. ** 生成代码 GENERATE CODE **

  7. ** Keil打开 **: ** 在main.c的USER CODE区写业务 **

** 优势 **: ** 10分钟搭框架 **, ** 不查手册 引脚自动检查冲突 **。


**83. STM32CubeMX中生成的HAL库和LL库有什么区别? **

表格

复制

特性 HAL库 LL库
** 抽象度 ** ** 高 ** ** 低 寄存器级 **)
**函数名 ** ** 长 **( HAL_UART_Transmit ** 短 **( LL_USART_Transmit
** 代码量 ** ** 大 ** ** 小 **
**可读性 ** ** 好 ** ** 接近寄存器 **
** 性能 ** ** 低 检查+状态机 **) ** 高 **
** 移植性 ** ** 好 ** ** 差 **

** 选型 **: ** 新手用HAL 熟手用LL 追求性能混合用 **。

** HAL代码量 **: ** 10KB+ **, ** LL只有几百B **。


84. HAL库中的轮询、中断和DMA三种模式有何不同?

表格

复制

模式 CPU占用 实时性 吞吐 适用
** 轮询 ** ** 100% ** ** 差 ** ** 低 ** ** 简单测试 **
** 中断 ** ** 中 ** ** 好 ** ** 中 ** ** 实时响应 **
** DMA ** ** <5% ** ** 极好 ** ** 高 ** ** 大数据流 **

** 轮询 HAL_UART_Transmit() while等标志 CPU干等**。

** 中断 **:HAL_UART_Transmit_IT(), ** 发完进回调 CPU干别的 **。

** DMA **:HAL_UART_Transmit_DMA(), **后台传 **, ** 完成回调 **。

** 推荐 **: ** 中断/DMA为主 轮询仅调试 **。


85. 如何处理HAL库中的超时错误? HAL_TIMEOUT =** 轮询超时间 **,HAL_MAX_DELAY=0xFFFFFFFF

** 处理 **:

  1. ** 检查返回值 **:

    c

    复制

    复制代码
    if(HAL_UART_Transmit(&huart1, data, len, 1000) != HAL_OK) {
        // 超时或错误
        Error_Handler();
    }
  2. ** 调超时值 根据波特率算 100字节@115200≈10ms **

  3. ** 中断/DMA **: ** 不用超时 **, ** 异步通知 **

  4. ** 错误回调 **: ** 注册 HAL_UART_ErrorCallback() **

** 底层**:HAL_GetTick()计时,** SysTick中断++uwTick**。


** 86. 如何自定义HAL库的回调函数? ** ** 回调=中断服务后通知应用 **。

** 方式1:弱函数重定义 **:

c

复制

复制代码
// stm32f1xx_hal_uart.c中有__weak void HAL_UART_RxCpltCallback()
// 在main.c中重新定义
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if(huart->Instance == USART1) {
        // 处理
        HAL_UART_Receive_IT(&huart1, rx_buf, 1);  // 重新启动
    }
}

** 方式2:注册回调 F4/H7支持 **):

c

复制

复制代码
HAL_UART_RegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID, my_rx_callback);

** 推荐 方式1简单 直接覆盖 **。


87. 如何通过SWD接口调试STM32? ** SWD接口=2根线**: ** SWDIO+SWCLK **。

** 连接 **:

复制

复制代码
ST-Link → STM32
VCC → VDD
GND → GND
SWDIO → PA13
SWCLK → PA14
NRST → NRST(可选)

** Keil配置 **: ** Debug→ST-Link SW模式 **, ** Flash Download勾选Reset and Run **。

** OpenOCD **:

bash

复制

复制代码
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg
gdb-multiarch -ex "target remote localhost:3333" firmware.elf

** 优势 **: ** 2线省引脚 **, ** 调试速度快 **, ** 可下载 **。


** 88. 断点有哪几种类型? **

  1. ** 软件断点 **: ** 修改Flash为BKPT指令 **, ** 无限个 **, ** 需改代码 **

  2. 硬件断点: ** 调试器硬件支持 **, ** 6个 **, ** 不改代码 **, ** 可在ROM设 **

  3. ** 数据断点(Watchpoint) 变量读写断 4个 **

  4. 条件断点表达式满足才断i==100

  5. 临时断点断一次自动删除

Keil用法 : **F9软件断点 Debug→Breakpoints→Data Watchpoint **。


89. 如何通过串口打印调试信息? ** 串口printf=嵌入式"看门狗" **。

** 方案1:重定向fputc **

c

复制

复制代码
int fputc(int ch, FILE *f) {
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
    return ch;
}
// 直接用printf

**方案2:DMA后台发送 **:

c

复制

复制代码
void debug_print(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsnprintf(tx_buf, TX_SIZE, fmt, args);
    va_end(args);
    HAL_UART_Transmit_DMA(&huart1, tx_buf, strlen(tx_buf));
}

** 注意 : ** ISR中禁止printf ),用环形队列


**90. 如何使用ITM进行更高效的调试? ** ** ITM(Instrumentation Trace Macrocell) **= ** Cortex-M的"硬件printf" 通过SWO引脚输出 **, ** 不占用UART **。

**配置 **:

  1. ** 使能Trace **:

    c

    复制

    复制代码
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    ITM->LAR = 0xC5ACCE55;  // 解锁
    ITM->TER = 0x1;  // 使能端口0
  2. ** 输出函数 **:

    c

    复制

    复制代码
    void ITM_SendChar(char ch) {
        while(ITM->PORT[0].u32 == 0);  // 等FIFO空
        ITM->PORT[0].u8 = ch;
    }
  3. ** Keil调试 **: ** Debug→Settings→Trace→Enable **

  4. ** 查看 Debug→printf Viewer **

** 优势**:** 速度=SWO时钟/10**,** <1μs延迟**,**ISR可用**。


**91. 如何测量代码的执行时间? ** ** 方法1:DWT周期计数器 Cortex-M3/M4/M7 **):

c

复制

复制代码
// 初始化
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;  // 清零
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;  // 使能

// 测量
uint32_t start = DWT->CYCCNT;
func();  // 被测函数
uint32_t cycles = DWT->CYCCNT - start;
float us = cycles / (SystemCoreClock / 1000000.0);

** 方法2:GPIO翻转+示波器 最准 **):

c

复制

复制代码
GPIO_SetBits(GPIOA, GPIO_Pin_0);
func();
GPIO_ResetBits(GPIOA, GPIO_Pin_0);

** 方法3:SysTick差值 **:

c

复制

复制代码
uint32_t start = HAL_GetTick();
func();
uint32_t ms = HAL_GetTick() - start;

** 注意 优化级别影响 -O0和-O3差10倍 **。


** 92. 如何配置STM32进入低功耗模式(睡眠、停止、待机)? ** **睡眠=CPU停,外设运行 **:

c

复制

复制代码
__WFI();  // 等中断

**停止=PLL/HSE停 **:

c

复制

复制代码
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
// 唤醒后需重新配时钟
SystemInit();

** 待机=只剩RTC **:

c

复制

复制代码
PWR_EnterSTANDBYMode();  // 不返回,唤醒复位

** 配置停止模式 **:

c

复制

复制代码
// 1. 使能PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

// 2. 使能WKUP(PA0)
PWR_WakeUpPinCmd(ENABLE);

// 3. 进入
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);  // WFI等中断
// 或PWR_STOPEntry_WFE(事件)

** 功耗 **: ** Run=50mA , ** Sleep=5mA Stop=50μA,**Standby=2μA **。


**93. 在低功耗模式下,哪些外设可以继续工作? **

表格

复制

模式 运行外设 唤醒源
** 睡眠 ** ** 所有外设 ** ** 任何中断 **
** 停止 ** ** RTC ** 、** IWDG ** ** EXTI RTC闹钟 **
** 待机 ** ** RTC ** 、 ** BKP ** ** WKUP引脚 **、 ** RTC闹钟 **

** 注意 停止模式下,GPIO状态保持 复用时钟停 **。


**94. 如何分析和优化STM32的功耗? ** ** 分析 **:

  1. ** 万用表 测电源电流 **

  2. ** STM32CubeMonitor-Power **: ** PC软件 实时监控 **

  3. ** 示波器 **: ** 电流探头 看瞬时功耗 **

** 优化 **:

  1. ** 降频 **: ** 72MHz→24MHz 功耗降50% **

  2. ** 关外设 **: ** RCC_APBxPeriphClockCmd **, ** 不用就关 **

  3. ** GPIO配置 **: ** 输入上拉/下拉 不浮空 输出低 **

  4. **低功耗模式 **: ** Sleep/Stop **

  5. ** 外设时钟 **: ** LSI/LSE比HSE省 **

** 实测 **: ** F103 STOP模式 功耗从50mA→50μA **。


** 95. 在电路设计中,STM32的复位电路和晶振电路需要注意什么? ** ** 复位电路 **:

  • RC延时 :**R=10kΩ,C=100nF 上电延时>1ms **(NRST低电平复位

  • 按键并联10nF电容消抖

  • STLinkNRST接SWD调试器可复位

** 晶振电路 **:

  • **HSE 8MHz **, ** 负载电容12pF±10% 靠近OSC_IN/OSC_OUT 走线短 **

  • **LSE 32768Hz **, ** 6pF电容 配二级管防静电 **

  • ** 匹配 晶振CL=负载电容= (C1×C2)/(C1+C2) + PCB杂散 **

** 反面教材 **: ** 晶振离芯片10cm 不起振 电容错=频偏 **。


**96. 什么是去耦电容?为什么要尽可能靠近芯片电源引脚放置? ** ** 去耦电容=电源滤波+电荷储备 **, ** 滤高频噪声 **。

** 原理**:** 电源线有电感**,** 负载突变时电压跌落**,**电容瞬时供电**。

** 配置 **: ** VDD引脚 **→ ** 100nF陶瓷电容+1μF钽电容 **, ** 距离<5mm **。

** 布局**:** 每对VDD/VSS放1个100nF**,** 晶振底下不放**,**铺地**。

** 实测 不去耦 GPIO翻转时电源纹波200mV 去耦后<20mV **。


**97. PCB布局布线时,模拟部分和数字部分应如何处理? ** ** 分区隔离 **, ** 单点接地 **。

** 布局 **:

  1. ** 分区 ADC/DAC/运放放一区 MCU数字放一区 **

  2. ** 隔离 **: ** 0Ω电阻或磁珠 数字地≠模拟地 **

  3. ** 星型接地 单点汇到电源地 防地弹 **

  4. ** 去耦 模拟电源独立LDO LC滤波 **

  5. ** 走线 模拟线短而粗 屏蔽 **, ** 远离数字线 **

** 反面 数字信号穿过模拟区 ADC跳码 **。


** 98. 程序跑飞了,可能的原因有哪些? ** ** 跑飞=PC指向无效地址 **。

** 原因 **:

  1. **栈溢出 **: ** 函数嵌套太深 数组越界 **

  2. ** 野指针 未初始化 释放后使用 **

  3. ** 中断向量 **: ** 未定义ISR 进HardFault **

  4. ** 电源 电压不稳 复位不良 **

  5. ** 晶振 不起振 PLL失锁 **

  6. ** 看门狗 没喂狗 频繁复位 **

  7. ** EMI 干扰 引脚误触发 **

  8. ** 锁 读保护 **, ** IWDG锁 **

** 调试 : ** HardFault_Handler 中 ** 读LR 栈回溯**→**定位PC **。


** 99. 如何利用看门狗来增强系统的可靠性? ** ** 策略 **:

  1. ** 独立看门狗 防死循环 **

    c

    复制

    复制代码
    IWDG_Enable();
    IWDG_ReloadCounter();  // 主循环喂
  2. ** 窗口看门狗 防跑飞代码喂狗 **

    c

    复制

    复制代码
    WWDG_Enable(0x7F);
    // 在窗口期喂
    if(HAL_GetTick() - last_tick < 50) {
        WWDG_SetCounter(0x7F);
    }
  3. ** 任务看门狗 **: ** 每个任务喂独立Flag 守护进程检查 **

  4. ** 应用喂狗 关键操作后喂 不是定时器中断喂 **

** 最佳实践 复杂逻辑后喂 耗时操作分段喂 喂狗点=健康检查点 **。


**100. 在项目开发中,如何进行STM32的固件升级? ** ** OTA流程 **:

  1. ** Bootloader分区 **: ** 0x08000000(16KB) **

  2. ** APP分区 **: ** 0x08004000(剩余空间) **

  3. ** 升级流程 **:

    • APP通过** USART/WiFi 下载新固件→ 存0x08008000 备份区 **)

    • ** 校验CRC **

    • ** 写Flash标志 new_fw_available**→**复位**

    • Bootloader看标志→**搬0x08008000→0x08004000 跳APP **

** 安全措施 **:

  • ** 双备份 **: ** A/B分区 升级失败回滚 **

  • ** 签名验证 RSA256 防篡改 **

  • ** 断电保护 **: ** 写元数据双备份 原子操作 **

** 工具**:** IAP通过YModem协议**,**SecureCRT发送**。


总结 :STM32是"** 乐高积木 ", 搭好框架 填满细节 调试优化 产品落地**。这100问是"** 武功秘籍 ", 勤练则成 **。

相关推荐
+VX:Fegn089539 分钟前
计算机毕业设计|基于springboot + vue二手交易管理系统(源码+数据库+文档)
数据库·vue.js·spring boot
西游音月39 分钟前
(11)功能实现:Qt实战项目之新建db文件
数据库
V***u45343 分钟前
sql实战解析-sum()over(partition by xx order by xx)
数据库·sql
修己xj44 分钟前
使用Docker Compose轻松部署达梦数据库
数据库
合方圆~小文1 小时前
智能变焦球机:全方位监控升级新标杆
数据库·人工智能·前端框架
二宝1521 小时前
黑马商城day10-Redis面试篇
数据库·redis·面试
FPGA_小田老师1 小时前
FPGA基础知识(十六):Xilinx Block Memory IP核完全指南(1)--核心定位与基础配置
fpga开发·存储·block ram·block rom
xiegwei1 小时前
spring security oauth2 集成异常处理
数据库·spring·spring security
siriuuus1 小时前
带你了解 Redis —— 基础知识总结
数据库·redis·缓存