STM32 SPI通信协议详解:从原理到实战,掌握嵌入式开发核心技能

发布时间:2026/7/30 7:52:24
STM32 SPI通信协议详解:从原理到实战,掌握嵌入式开发核心技能 1. 项目概述为什么SPI是嵌入式开发的“瑞士军刀”在嵌入式开发领域尤其是基于STM32这类主流MCU的项目中与外设打交道是家常便饭。无论是驱动一块LCD屏幕、读取一个传感器数据还是与另一个微控制器交换信息你都需要一种可靠、高效的通信方式。在众多通信协议中SPISerial Peripheral Interface因其简单、高速、全双工的特性成为了工程师们最得力的“瑞士军刀”之一。它不像I2C那样需要复杂的地址管理和应答机制也不像UART那样依赖精确的波特率匹配SPI的核心思想就是“主从同步时钟说话”这使得它在需要高速数据流的场景下表现尤为出色。我接触过很多刚上手STM32的朋友他们往往对GPIO、定时器、中断这些基础概念掌握得不错但一到SPI、I2C这类通信外设就有点犯怵。其实SPI的硬件逻辑非常直观。想象一下你主设备要和一个人从设备快速传递一堆小纸条数据。你需要做的是首先你喊一声他的名字拉低片选线CS告诉他“注意我要开始跟你说话了”。然后你手里拿着一个节拍器时钟线SCK每“哒”一声你就递给他一张纸条通过MOSI线发送一位数据同时他也递给你一张纸条你通过MISO线接收一位数据。这样一送一收同时进行效率自然高。等所有纸条传完你再喊一声“好了”拉高片选线CS对话结束。这个过程里时钟节奏极性CPOL和相位CPHA、纸条的大小数据位宽通常是8位都是你们事先约定好的。理解了这幅画面SPI就不再是寄存器手册里冰冷的比特位了。对于STM32开发者而言掌握SPI意味着你能轻松驾驭从简单的EEPROM、Flash存储芯片到复杂的陀螺仪、加速度计、无线模块如NRF24L01、甚至高分辨率的TFT液晶屏等一系列外设。无论是使用STM32CubeMX进行图形化配置还是直接撸寄存器透彻理解SPI的工作机制都是打通任督二脉的关键一步。接下来我将从一个实际项目开发者的角度带你从原理到实战彻底吃透STM32的SPI通信。2. SPI核心原理与STM32的硬件实现2.1 SPI协议的四根线与时序模式SPI协议在物理上通常由四根线构成这是其经典模式也是我们最需要厘清的基础SCK (Serial Clock) 时钟信号线由主设备产生和控制。所有的数据收发都严格跟随这个时钟的边沿进行这是SPI“同步”二字的由来。MOSI (Master Out Slave In) 主设备输出从设备输入。数据从主设备流向从设备。MISO (Master In Slave Out) 主设备输入从设备输出。数据从从设备流回主设备。CS/SS (Chip Select / Slave Select) 片选信号线由主设备控制。通常低电平有效主设备通过拉低某条特定的CS线来选中与之通信的从设备。这是实现一主多从架构的关键。很多初学者会困惑于SPI的四种模式。这四种模式其实是由时钟极性CPOL和时钟相位CPHA两个参数组合而成的CPOL (Clock Polarity) 时钟空闲状态的电平。CPOL0表示SCK在空闲时为低电平CPOL1则表示空闲时为高电平。CPHA (Clock Phase) 数据采样的时钟边沿。CPHA0表示在SCK的第一个边沿如果CPOL0就是上升沿CPOL1就是下降沿采样数据CPHA1则表示在第二个边沿采样数据。这四种模式Mode 0, 1, 2, 3必须与从设备的数据手册要求严格匹配否则通信必然失败。一个快速记忆和示波器观察的方法是Mode 0 (CPOL0, CPHA0)是最常见的模式空闲时SCK为低数据在SCK的上升沿被采样。当你用示波器抓取一个已知的SPI设备比如一个SPI Flash的波形时对照着看就能立刻确定其模式。2.2 STM32 SPI外设的架构与关键寄存器STM32的SPI外设是一个相当复杂的数字状态机但我们可以把它抽象成几个核心部分来理解。首先它包含发送和接收两个独立的移位寄存器以及对应的数据寄存器DR。当你往DR寄存器写入数据时硬件会自动将其加载到发送移位寄存器并在SCK的驱动下一位一位地从MOSI线移出。同时MISO线上的数据也会被一位一位地移入接收移位寄存器当收到一个完整的数据帧比如8位后硬件会自动将其转移到DR寄存器供CPU读取并可能产生接收完成中断。其次STM32的SPI支持丰富的时钟配置。你可以通过预分频器对APB总线时钟进行分频得到所需的SCK频率。这里有个关键点SPI的时钟频率并非越高越好必须考虑从设备支持的最高时钟频率以及PCB布线的长度。过高的速率会导致信号完整性变差通信出错。通常对于普通传感器1-10MHz是一个安全的范围对于高速存储器可能需要跑到几十MHz。控制寄存器CR1/CR2是我们配置SPI模式、数据大小、主从模式、硬件NSS管理等的地方。而状态寄存器SR里的标志位如TXE发送缓冲区空、RXNE接收缓冲区非空、BSY总线忙则是我们编写查询式或中断式驱动时判断通信状态的核心依据。注意STM32的SPI外设功能强大但不同系列如F1, F4, H7在细节上会有差异。例如F1系列可能不支持高于16位的数据帧而F4/H7系列支持。在跨系列移植代码时务必仔细核对参考手册。2.3 软件片选 vs. 硬件片选灵活性与效率的权衡STM32的SPI外设支持硬件NSSNSS即片选管理。当配置为主模式且硬件NSS使能时你可以将某个GPIO通常是SPI接口的NSS引脚专门用作硬件片选。在通信开始时硬件会自动拉低该引脚通信结束时自动拉高。这听起来很美好解放了CPU。但在实际项目中我几乎无一例外地选择使用“软件片选”。所谓软件片选就是任意指定一个普通的GPIO引脚不一定是SPI接口的NSS引脚来控制从设备在通信前后手动拉低和拉高它。为什么这么做原因有三灵活性一个SPI接口常常需要挂载多个从设备。硬件NSS通常只有一个引脚无法直接支持多设备。而软件片选你可以用任意多个GPIO轻松实现一主多从。时序控制有些“脾气古怪”的从设备需要在两次数据传输之间保持片选有效或者在数据帧之间要求片选有一个短暂的高电平脉冲。硬件NSS的自动行为很难满足这些特殊时序而软件控制则随心所欲。调试便利在调试时你可以随时在代码中插入延时或断点观察片选信号的变化更容易定位问题是出在SPI配置上还是出在片选时序上。硬件NSS更适合单一、标准的从设备且对CPU占用率有极致要求的场景。但对于大多数应用软件片选的额外开销微乎其微带来的灵活性却是巨大的。因此在STM32CubeMX中配置时我通常将“NSS”设置为“Disable”或“Software”然后自己在代码中管理GPIO。3. 从零构建STM32 SPI驱动CubeMX配置与HAL库实战3.1 使用STM32CubeMX进行图形化配置STM32CubeMX极大地简化了外设初始化过程。假设我们要配置一个SPI1为主机与一个SPI Flash通信Mode 0, 8位数据。引脚分配在“Pinout Configuration”标签页找到SPI1。将其模式设置为“Full-Duplex Master”。此时CubeMX会自动分配MOSIPA7、MISOPA6、SCKPA5引脚。NSS引脚我们不用保持为“Disable”。参数配置在SPI1的配置窗口中Basic Parameters:Clock Prescaler: 时钟预分频。根据你的主频和从设备要求选择。例如主频72MHz选择8分频则SCK为9MHz。初期调试建议选大一点的分频如128分频约562.5KHz稳定后再提高。Data Size: 选择“8 bits”。First Bit: 选择“MSB First”。绝大多数设备都是先传输最高位。Configuration Parameters:CPOL: 选择“Low”。对应Mode 0或Mode 1具体看CPHA。CPHA: 选择“1 Edge”。CPOLLow, CPHA1 Edge 即 Mode 0。NSS: 选择“Disable”。CRC Calculation: 禁用除非你的从设备要求CRC校验。生成代码配置好时钟树等其他参数后生成代码。CubeMX会自动生成MX_SPI1_Init()函数完成GPIO和SPI外设的初始化。3.2 HAL库SPI收发函数深度解析HAL库提供了不同模式的SPI传输函数理解它们的区别至关重要。阻塞式PollingHAL_SPI_Transmit()HAL_SPI_Receive()HAL_SPI_TransmitReceive()。工作原理函数内部通过循环查询状态寄存器如TXE, RXNE, BSY直到整个数据传输完成或超时期间CPU被完全占用。适用场景简单的、非实时的、数据量小的单次传输。代码简单但效率低会阻塞整个程序。示例读取一个设备的ID号。uint8_t tx_data 0x9F; // 假设是Flash的读ID命令 uint8_t rx_data[3] {0}; HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET); // 拉低片选 HAL_SPI_Transmit(hspi1, tx_data, 1, 100); // 发送命令 HAL_SPI_Receive(hspi1, rx_data, 3, 100); // 接收3字节ID HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET); // 拉高片选中断式InterruptHAL_SPI_Transmit_IT()HAL_SPI_Receive_IT()HAL_SPI_TransmitReceive_IT()。工作原理函数启动传输后立即返回传输完成后会触发SPI全局中断在中断服务程序HAL_SPI_IRQHandler()中调用对应的回调函数如HAL_SPI_TxCpltCallback()。适用场景中等数据量传输且系统有其他任务需要处理。提高了CPU利用率但需要处理中断和回调编程稍复杂。注意事项在回调函数中进行片选控制等后续操作要小心避免重入问题。DMA式Direct Memory AccessHAL_SPI_Transmit_DMA()HAL_SPI_Receive_DMA()HAL_SPI_TransmitReceive_DMA()。工作原理DMA控制器在后台搬运数据完全解放CPU。传输完成后通过DMA中断或SPI的TXE/RXNE事件配合DMA来通知CPU。适用场景大数据量、高速率、实时性要求高的传输如驱动SPI LCD刷屏、音频流传输。这是性能最高的方式。配置要点需要在CubeMX中使能SPI对应的DMA通道TX和RX通常需要分开的通道并设置数据流方向、优先级等。实操心得对于大多数传感器读写阻塞式足够简单可靠。一旦涉及连续、大数据传输比如每秒更新一次全屏LCD务必考虑DMA。我曾在一个项目中用阻塞式SPI刷屏导致系统明显卡顿切换到DMA后流畅无比。HAL库的DMA函数虽然封装得好但一定要理清__HAL_LINKDMA宏的作用它把SPI句柄和DMA句柄关联起来。3.3 封装通用的SPI设备读写层直接在每个业务函数里调用HAL库函数并管理片选会导致代码重复且混乱。一个好的实践是封装一个设备驱动层。以SPI Flash为例// spi_flash.c static SPI_HandleTypeDef *hspi_flash; // 指向初始化好的SPI句柄如hspi1 static GPIO_TypeDef* cs_port; static uint16_t cs_pin; void SPI_FLASH_Init(SPI_HandleTypeDef *hspi, GPIO_TypeDef* port, uint16_t pin) { hspi_flash hspi; cs_port port; cs_pin pin; HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_SET); // 初始化为高电平不选中 } static void cs_low(void) { HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_RESET); } static void cs_high(void) { HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_SET); } uint8_t SPI_FLASH_ReadByte(void) { uint8_t dummy 0xFF, value; // SPI是全双工发送dummy时钟以读取数据 HAL_SPI_TransmitReceive(hspi_flash, dummy, value, 1, 100); return value; } void SPI_FLASH_WriteByte(uint8_t data) { HAL_SPI_Transmit(hspi_flash, data, 1, 100); } // 封装一个更高级的读数据函数 HAL_StatusTypeDef SPI_FLASH_ReadData(uint32_t addr, uint8_t *pData, uint32_t size) { uint8_t cmd 0x03; // 读数据命令 cs_low(); HAL_SPI_Transmit(hspi_flash, cmd, 1, 10); // 发送24位地址假设Flash是24位地址 uint8_t addr_buf[3] {(addr16)0xFF, (addr8)0xFF, addr0xFF}; HAL_SPI_Transmit(hspi_flash, addr_buf, 3, 10); HAL_SPI_Receive(hspi_flash, pData, size, 1000); // 读取数据 cs_high(); return HAL_OK; }这样上层应用只需要调用SPI_FLASH_ReadData这样的函数无需关心底层的片选、命令发送细节代码清晰且可复用。4. 高级应用与性能优化DMA、多从机与错误处理4.1 使用DMA驱动SPI LCD的实战案例驱动像ILI9341这类SPI接口的LCD刷屏是典型的高带宽需求场景。以320x240的16位色屏为例一帧数据量为3202402153600字节。用阻塞式传输CPU会被完全占用数十毫秒。使用DMA是唯一的选择。CubeMX配置关键点在SPI配置中使能DMA。为SPI_TX添加一个DMA流Stream方向设为“Memory To Peripheral”。模式可以是“Normal”单次或“Circular”循环用于连续刷新。根据你的LCD驱动IC要求可能需要将数据拆分为命令和数据分别发送。通常我们需要一个DC数据/命令引脚来控制当前发送的是命令还是数据。内存中的数据格式要匹配LCD的要求。例如ILI9341通常使用RGB565格式那么你的帧缓冲区uint16_t frame_buffer[320*240]中的每一个16位整数就对应一个像素的颜色。代码实现要点// 准备一块帧缓冲区 uint16_t lcd_frame_buffer[LCD_WIDTH * LCD_HEIGHT]; // 填充缓冲区例如画一个红色屏幕 for(int i0; iLCD_WIDTH*LCD_HEIGHT; i) { lcd_frame_buffer[i] 0xF800; // RGB565 红色 } // 设置LCD的显示区域GRAM地址 LCD_SetWindow(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1); // 启动DMA传输 HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET); // 设置为数据模式 HAL_SPI_Transmit_DMA(hspi2, (uint8_t*)lcd_frame_buffer, sizeof(lcd_frame_buffer));踩坑记录这里有一个大坑HAL_SPI_Transmit_DMA期望的数据长度单位是字节。如果你的帧缓冲区是uint16_t类型sizeof计算出来的是字节数没问题。但如果你直接传递元素个数就会导致只发送一半的数据。务必检查长度参数。DMA传输完成回调你需要在HAL_SPI_TxCpltCallback()回调函数中进行后续处理比如通知图形引擎下一帧已准备好或者重新启动循环DMA传输以实现动画。4.2 一主多从SPI系统的设计与片选管理当需要连接多个SPI从设备时比如一个温度传感器、一个Flash、一个无线模块硬件连接上有两种常见方式独立片选推荐每个从设备独占一组MOSI/MISO/SCK但片选CS引脚各自独立。主设备通过控制不同的GPIO来选中目标设备。这种方式布线简单逻辑清晰通信互不干扰。缺点是占用GPIO较多。菊花链Daisy-Chain所有从设备的MOSI、MISO、SCK分别并联上一个设备的MISO接下一个设备的MOSI形成一个环。主设备只用一个CS控制所有设备。数据在主设备发出后依次通过所有从设备移位寄存器最后又回到主设备。这种方式节省GPIO但协议复杂所有设备必须支持菊花链模式且通信效率较低一次传输要穿过所有设备。对于独立片选方式软件管理的核心是一个片选引脚表和一个选择函数typedef struct { GPIO_TypeDef* port; uint16_t pin; bool is_selected; } SPI_Device_t; SPI_Device_t spi_devices[] { {FLASH_CS_GPIO_Port, FLASH_CS_Pin, false}, {SENSOR_CS_GPIO_Port, SENSOR_CS_Pin, false}, // ... 更多设备 }; void SPI_SelectDevice(uint8_t dev_id) { // 首先取消选中所有设备 for(int i0; isizeof(spi_devices)/sizeof(spi_devices[0]); i) { if(spi_devices[i].is_selected) { HAL_GPIO_WritePin(spi_devices[i].port, spi_devices[i].pin, GPIO_PIN_SET); spi_devices[i].is_selected false; } } // 选中目标设备 if(dev_id sizeof(spi_devices)/sizeof(spi_devices[0])) { HAL_GPIO_WritePin(spi_devices[dev_id].port, spi_devices[dev_id].pin, GPIO_PIN_RESET); spi_devices[dev_id].is_selected true; // 可选短暂延时等待从设备准备好 HAL_Delay(1); } }在每次与特定设备通信前调用SPI_SelectDevice(DEV_FLASH)即可。务必保证在通信间隔只有一个设备的片选是有效的。4.3 SPI通信中的错误处理与稳定性提升SPI通信并非总是那么可靠尤其是在长线、高速或干扰环境下的应用中。HAL库通过状态寄存器提供了一些错误标志我们需要妥善处理。常见错误标志OVR (Overrun) 接收溢出。当CPU或DMA没有及时读取接收数据寄存器DR而新的数据已经到来时就会发生溢出。这是最常见的错误之一。解决方法在接收数据量大的应用中确保使用DMA或及时的中断服务程序来读取数据。发生OVR后SPI外设可能会被锁定需要先清除错误标志读SR和DR寄存器有时甚至需要重新初始化SPI。MODF (Mode Fault) 模式错误。在多主机配置中当NSS引脚被意外拉低另一个主机试图占用总线时发生。在单一主机系统中如果配置了硬件NSS管理但接线有问题也可能发生。通常我们使用软件片选可以避免此问题。CRCERR (CRC Error) CRC校验错误。如果使能了硬件CRC计算接收到的CRC与计算值不匹配时会触发。在HAL库中的处理 HAL库的中断和DMA传输函数其返回值HAL_StatusTypeDef就包含了错误信息。此外当错误发生时会进入HAL_SPI_ErrorCallback()回调函数。void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if(__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_OVR)) { // 处理溢出错误清除标志可能需要重置缓冲区或重新初始化 __HAL_SPI_CLEAR_OVRFLAG(hspi); // 严重错误时可以考虑重新初始化SPI // HAL_SPI_DeInit(hspi); // MX_SPI1_Init(); // 重新初始化 } // ... 检查其他错误标志 }提升稳定性的实战技巧上拉电阻 在SCK、MOSI、MISO线上连接4.7k-10k的上拉电阻到VCC可以增强信号驱动能力特别是在总线空闲时保持确定的电平减少干扰。阻抗匹配与布线 对于高速SPI10MHzPCB布线要考虑阻抗匹配尽量缩短走线长度避免过孔MOSI/MISO/SCK线尽量等长并远离高频噪声源。电源去耦 主设备和从设备的电源引脚附近必须放置足够且靠近的0.1uF陶瓷去耦电容这是保证数字电路稳定工作的基石。软件超时与重试 在所有HAL库的阻塞式函数调用中设置一个合理的超时时间。对于关键通信可以封装一个带重试机制的发送函数。HAL_StatusTypeDef SPI_Send_With_Retry(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint8_t retries) { HAL_StatusTypeDef status; for(uint8_t i0; iretries; i) { status HAL_SPI_Transmit(hspi, pData, Size, 100); if(status HAL_OK) { return HAL_OK; } HAL_Delay(5); // 重试前稍作等待 // 可选在这里进行一些错误恢复如重置SPI } return status; // 返回最后一次错误 }5. 调试技巧与常见问题排查实录5.1 硬件调试示波器与逻辑分析仪的使用当SPI通信不成功时“眼见为实”是最好的方法。一台示波器或逻辑分析仪是必不可少的调试工具。连接与触发将探头的通道分别连接到SCK、MOSI、MISO和你使用的片选CS线上。将触发源设置为CS信号的下降沿通信开始。这样你就能稳定地捕获到一次完整的SPI通信波形。观察要点片选时序CS拉低后是否在SCK开始之前有足够的建立时间t_SUCS拉高前最后一个SCK边沿之后是否有足够的保持时间t_HD这些时间参数需要查阅从设备的数据手册。时钟极性与相位对照抓取的波形确认CPOL和CPHA是否与从设备要求一致。看SCK空闲电平CPOL以及数据是在SCK的哪个边沿稳定CPHA。数据内容对照你发送的命令和数据看MOSI线上的波形是否正确。同时看MISO线上从设备的回复是否正确。逻辑分析仪软件通常能直接解析出十六进制或二进制数据非常方便。信号质量观察波形是否有严重的过冲、振铃或边沿过于缓慢上升/下降时间过长。这可能是阻抗不匹配或驱动能力不足的表现。我曾遇到一个案例读取SPI Flash ID总是返回0xFF。用示波器一看发现MOSI线上有命令波形但MISO线一直是高电平。最后排查发现是Flash芯片的供电引脚虚焊。没有示波器这种问题可能要排查好几天。5.2 软件调试从初始化到数据收发的检查清单如果硬件连接和波形看起来都正常问题可能出在软件配置上。可以按照以下清单逐一核对初始化检查[ ] SPI外设时钟是否使能__HAL_RCC_SPI1_CLK_ENABLE()[ ] GPIO时钟是否使能GPIO是否被正确复用为SPI功能CubeMX通常会自动配置[ ] SPI初始化参数波特率、数据位宽、CPOL、CPHA、NSS设置是否与从设备100%匹配[ ] 如果使用DMADMA时钟是否使能DMA流/通道是否配置正确内存和外围地址是否无误通信过程检查[ ] 片选信号CS是否在通信前被正确拉低通信后被正确拉高[ ] 发送的数据格式字节序MSB/LSB First是否正确很多设备要求MSB First。[ ] 对于需要先发送命令、再发送/接收数据的设备命令和数据之间是否有足够的延时有些设备处理命令需要时间。[ ] 接收数据时是否使用了正确的函数HAL_SPI_Receive内部会发送dummy数据0xFF来产生时钟。如果你需要先发送一个命令字节再接收应该使用HAL_SPI_TransmitReceive或者先Transmit命令再Receive数据。[ ] 是否检查了HAL函数的返回值特别是超时错误HAL_TIMEOUT。5.3 典型问题与解决方案速查表下表总结了我遇到过的常见SPI问题及其排查思路问题现象可能原因排查步骤与解决方案完全无通信MISO一直为高/低1. 硬件连接错误断线、虚焊2. 从设备未上电或损坏3. 片选信号错误始终未选中4. SPI外设或GPIO时钟未使能1. 用万用表检查连通性。2. 检查从设备电源电压和电流。3. 用示波器确认CS信号是否有跳变。4. 在调试器中查看RCC相关寄存器。能发送但接收数据全为0x00或0xFF1. MISO线接反或接触不良2. 从设备未正确响应模式不对、命令错误3. 接收函数使用错误如该用TransmitReceive却用了Receive1. 交换MISO/MOSI线测试仅限测试。2. 用示波器同时抓MOSI和MISO看从设备是否有数据输出。3. 核对从设备数据手册的命令格式和时序。通信不稳定偶尔出错1. 时钟频率过高信号质量差2. 电源噪声大3. 软件上缺少错误处理和重试机制4. DMA缓冲区溢出或访问冲突1. 降低SPI波特率再测试。2. 检查电源纹波加强去耦。3. 在代码中加入CRC校验或重发逻辑。4. 检查DMA配置确保缓冲区大小足够且内存地址对齐。DMA传输卡住不进回调函数1. DMA传输未成功启动配置错误2. 传输过程中发生错误如OVR3. 中断优先级冲突导致DMA中断被屏蔽1. 单步调试检查HAL_SPI_Transmit_DMA返回值。2. 在HAL_SPI_ErrorCallback中设置断点。3. 检查NVIC配置确保DMA/SPI中断优先级合理且全局中断已开启。多从设备干扰1. 片选管理不当同时选中了多个设备2. 总线负载过重上拉电阻阻值不合适1. 确保通信逻辑是“选中A-与A通信-取消选中A-选中B-...”。2. 尝试减小上拉电阻值如从10k改为4.7k增强驱动能力。最后分享一个让我记忆深刻的调试经历。当时一个SPI接口的六轴传感器MPU6500读数偶尔会跳变。用示波器看波形完美降低速率也无改善。最后发现是MCU和传感器共用了一个LDO供电当MCU的无线模块发射时电流骤增导致LDO输出电压有微小跌落影响了传感器的模拟电路。解决方法很简单给传感器单独加一颗LDO或者使用更大功率、响应更快的电源芯片。这个问题提醒我们嵌入式调试不能只盯着数字逻辑电源完整性往往是隐藏的杀手。