
简介本资源是一套面向嵌入式初学者的STM32双板SPI通信实战例程聚焦多板协同通信与外设驱动实践解决SPI主从模式配置、时序匹配、寄存器级调试等典型学习难点。压缩包共60个文件含22个.i预编译头文件、5个.c源码与5个.h头文件构成完整工程主体辅以readme.txt说明文档、uvproj工程文件及hex可烧录镜像另有lst汇编列表、map内存映射、o目标文件等便于深度调试整体450KB轻量易上手。已有3093人学习下载适合刚接触HAL或标准外设库的开发者快速理解SPI四线制通信机制。代码含逐行中文注解覆盖GPIO复用配置、CR1/CR2寄存器关键位设置、DR数据收发流程、SR状态轮询及液晶屏SPI接口初始化逻辑特别强化了主从机握手、时钟极性CPOL与相位CPHA组合配置等易错点解析助读者建立硬件协议与软件实现的完整映射。1. 两块 STM32 板子跑通 SPI 主从通信不是接线能亮就行而是时钟极性/相位错一位就收不到半个字节你手头有两块 STM32 开发板想让它们用 SPI 快速传点传感器数据或控制指令——比如主控板读取从机板上的温湿度再把结果刷到从机板自带的液晶屏上。但一上电HAL_SPI_Receive()卡死、HAL_SPI_Transmit()返回HAL_TIMEOUT、示波器上看 MOSI 有波形而 MISO 始终是高电平……别急着换线或怀疑芯片坏了。这不是“SPI 能用就行”的玩具级通信而是两个独立运行的 STM32 系统之间必须严格对齐时序参数的真实握手。本例程不依赖任何中间芯片如 CH341、USB 转串口不走 HAL 库默认的 CubeMX 自动生成配置它常把 CPOL/CPHA 设成 0-0而多数 OLED 或 ILI9341 液晶模块实际要 0-1所有初始化、发送、接收、校验逻辑全部手写裸寄存器HAL 混合实现并附带针对液晶显示场景的双板协同协议主发命令地址数据从解析后驱动 LCD 刷新。适合正在做嵌入式多节点传感网络、需要低延迟板间同步、或被 SPI 波形调试折磨超过 3 小时的工程师。2. 为什么必须手动配 CPOL/CPHA从 SPI 四种模式讲清主从握手本质SPI 不是“接上线就能通”的总线它是靠主设备输出的 SCLK 信号边沿来采样/驱动数据的同步协议。关键在于数据在时钟的上升沿还是下降沿采样空闲时钟是高电平还是低电平这两个开关组合出四种模式Mode 0–3而主从双方必须完全一致否则就像两个人用不同语速说同一句话——听者永远抓不准音节起始点。2.1 四种 SPI 模式与物理波形对应关系模式CPOLClock PolarityCPHAClock Phase采样时刻典型外设00空闲低0第一边沿采样SCLK 上升沿大部分 SD 卡、部分 Flash10空闲低1第二边沿采样SCLK 下降沿ILI9341 液晶驱动 IC21空闲高0第一边沿采样SCLK 下降沿MCP2515 CAN 控制器31空闲高1第二边沿采样SCLK 上升沿AD7705 ADC提示ILI9341 数据手册第 12.3 节明确标注其 SPI 接口工作于 Mode 1CPOL0, CPHA1。这意味着SCLK 空闲为低电平数据在 SCLK 下降沿被从机采样即主机需在下降沿前稳定数据主机在上升沿更新数据。若主控误设为 Mode 0MOSI 数据会在上升沿变化而从机却在下降沿采样——此时采到的是上一字节的尾巴或噪声必然乱码。2.2 本例程主从角色定义与硬件连接约束本例程采用主控板Master→ 从机板Slave单向下发 从机板回传状态的轻量协议非全双工流控。连接仅需 4 根线无 DMA、无中断优化先确保功能正确PA5 → SCLK主控输出时钟从机输入PA7 → MOSI主控输出数据从机输入PA6 → MISO主控输入数据从机输出注意从机必须三态控制PA4 → NSS片选主控推挽输出低电平有效从机输入必须下拉电阻防浮空注意STM32 的 SPI1 默认复用在 PA5/6/7但PA4 不是 SPI1 的官方 NSS 引脚SPI1_NSS 是 PA4 仅当使用 AFIO_MAPR 配置重映射。本例程坚持用 PA4 做软件 NSS原因避免硬件 NSS 触发自动关闭 SPI某些固件库版本存在 Bug且便于插入调试打印。从机端 NSS 引脚需配置为浮空输入 外部下拉10kΩ确保未选中时 MISO 为高阻态。2.3 主控板 SPI 初始化HAL 寄存器混合配置关键参数只靠MX_SPI1_Init()生成的代码无法精确控制 CPHA/CPOL 时序细节必须在 HAL 初始化后手动修正寄存器// 主控板stm32f103c8t6SPI1Mode 1CPOL0, CPHA1 void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; // 全双工 hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL 0 hspi1.Init.CLKPhase SPI_PHASE_2EDGE; // CPHA 1 → 关键 hspi1.Init.NSS SPI_NSS_SOFT; // 软件 NSS禁用硬件自动控制 hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; // 72MHz/16 4.5MHz适配 ILI9341 最高10MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } // 【关键补丁】HAL 库对 CPHA1 的时序微调不足需手动置位 CR1 的 LSBFIRST 位非必需并确认 BR[2:0] 正确 // 更重要的是强制清除 CR2 的 TXEIE/RXNEIE禁用中断避免干扰主循环 __HAL_SPI_DISABLE_IT(hspi1, SPI_IT_TXE | SPI_IT_RXNE); }逻辑说明SPI_PHASE_2EDGE对应 CPHA1HAL 库会设置CR1寄存器的CPHA位bit 0这是基础但实测发现当BaudRatePrescaler设为SPI_BAUDRATEPRESCALER_16时CR1的BR[2:0]字段必须为011二进制而 HAL 有时写成001导致实际波特率偏差 2 倍。因此建议初始化后用__HAL_SPI_GET_PRESCALER(hspi)校验SPI_NSS_SOFT确保 NSS 由软件控制HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)避免硬件 NSS 在传输中途意外释放。2.4 从机板 SPI 初始化必须启用从机模式 NSS 中断唤醒从机不能被动等待需在 NSS 下降沿触发中断进入接收准备状态// 从机板同样 STM32F103C8T6SPI1 配置为 Slave void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_SLAVE; // 必须为 SLAVE hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // 与主控严格一致 hspi1.Init.CLKPhase SPI_PHASE_2EDGE; // 同上CPHA1 hspi1.Init.NSS SPI_NSS_HARD_INPUT; // 硬件 NSS 输入响应片选 hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } // 【关键】使能 NSS 中断以便在片选拉低时立即准备接收 __HAL_SPI_ENABLE_IT(hspi1, SPI_IT_RXNE); // 接收缓冲非空中断 __HAL_SPI_ENABLE_IT(hspi1, SPI_IT_ERR); // 错误中断溢出/模式错误 }参数说明SPI_NSS_HARD_INPUT表示 NSS 引脚由外部驱动STM32 内部不输出SPI_IT_RXNE中断必须开启因为从机无法预知主控何时发数据只能靠接收中断触发处理SPI_IT_ERR是后悔药若主控发 9 位数据常见于某些 LCD 命令从机会触发 OVR溢出错误中断里可读取SR寄存器定位问题。3. 双板通信协议设计3 字节帧结构支撑液晶显示指令下发SPI 物理层通了不代表应用层能干活。本例程定义一个极简但鲁棒的 3 字节协议专为“主控下发显示内容、从机驱动 LCD”场景定制避免裸 SPI 传原始像素导致带宽爆炸3.1 协议帧格式与字段语义字节位置字段名长度取值范围说明Byte 0CMD10x01~0x0F命令码0x01清屏0x02写字符串0x03画点0x04设置光标位置Byte 1ADDR_H10x00~0xFF地址高位如字符串起始 X 坐标、点坐标 XByte 2ADDR_L10x00~0xFF地址低位如字符串起始 Y 坐标、点坐标 Y提示该协议不包含数据长度字段因为所有操作均以“地址固定动作”触发。例如CMD0x02, ADDR_H10, ADDR_L20表示“在屏幕 (10,20) 位置开始写字符串”后续字符串数据通过第二次 SPI 传输仍为 3 字节头 N 字节 ASCII下发。这样拆分既保证每次 SPI 传输长度可控避免超时又便于从机状态机解析。3.2 主控板发送逻辑分步握手机制防粘包主控不追求吞吐率而求 100% 可控。每次发送前必做三件事拉低 NSS片选发送 3 字节协议头等待从机返回 ACK0xAA或 NACK0x55。// 主控板发送协议头并等待从机应答 uint8_t SPI_Master_SendHeader(uint8_t cmd, uint8_t addr_h, uint8_t addr_l) { uint8_t tx_buf[3] {cmd, addr_h, addr_l}; uint8_t rx_buf[3] {0}; // 用于接收从机回传的 ACK/NACK仅用 rx_buf[0] // 1. 拉低 NSS HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 2. 发送 3 字节头阻塞式超时 100ms if (HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 3, 100) ! HAL_OK) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 释放 NSS return 0; // 发送失败 } // 3. 检查从机应答rx_buf[0] 应为 0xAA 或 0x55 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 释放 NSS return (rx_buf[0] 0xAA) ? 1 : 0; } // 使用示例在 (50,80) 位置写字符串 if (SPI_Master_SendHeader(0x02, 50, 80)) { // 成功可继续发字符串数据 SPI_Master_SendString(Hello SPI!); }逻辑说明HAL_SPI_TransmitReceive()同时发与收利用 SPI 全双工特性主控发 3 字节时从机同步回传 3 字节首字节为 ACK/NACK后两字节可忽略tx_buf和rx_buf长度必须一致否则 HAL 返回HAL_ERROR释放 NSS拉高必须在函数末尾执行否则下次通信可能被挂起。3.3 从机板接收中断服务程序状态机驱动协议解析从机不依赖轮询全靠中断驱动。SPI1_IRQHandler中只做最轻量工作读取数据、存入缓冲、触发状态机// 从机板SPI1 中断服务程序 void SPI1_IRQHandler(void) { uint8_t rx_data; static uint8_t rx_buffer[3]; static uint8_t rx_index 0; // 清除 RXNE 中断标志HAL 自动做此处省略 if (__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_RXNE) ! RESET) { rx_data hspi1.Instance-DR; // 直接读 DR 寄存器清标志 if (rx_index 3) { rx_buffer[rx_index] rx_data; if (rx_index 3) { // 收满 3 字节启动协议解析 Parse_SPI_Command(rx_buffer); rx_index 0; // 重置索引 } } } // 错误中断处理OVR, MODF if (__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_OVR) ! RESET || __HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_MODF) ! RESET) { __HAL_SPI_CLEAR_OVRFLAG(hspi1); __HAL_SPI_CLEAR_MODFFLAG(hspi1); } } // 协议解析函数在 main 循环中调用非中断内 void Parse_SPI_Command(uint8_t *buf) { switch(buf[0]) { case 0x01: LCD_Clear(0x00, 0x00, 239, 319); // 清全屏 break; case 0x02: LCD_SetCursor(buf[1], buf[2]); // 设置光标 break; case 0x03: LCD_DrawPoint(buf[1], buf[2]); // 画点 break; default: break; } // 回传 ACK在中断退出后主控会收到此字节 HAL_SPI_Transmit(hspi1, (uint8_t[]){0xAA}, 1, 100); }参数说明rx_buffer定义为static确保跨中断调用时数据不丢失Parse_SPI_Command()不在中断内执行耗时操作如 LCD 刷新只做状态标记实际 LCD 调用放在main()的while(1)循环中避免中断嵌套风险HAL_SPI_Transmit()回传0xAA是阻塞式但因从机已知主控在等应答且 SPI 时钟由主控控制故不会冲突。4. 液晶显示协同从机板如何把 SPI 指令翻译成 ILI9341 寄存器操作协议头只是“快递单”真正干活的是从机板上的 LCD 驱动。本例程适配常见的 2.4 寸 ILI9341 液晶模块16 位并口转 SPI 模式重点解决SPI 模式下指令/数据切换这一高频翻车点。4.1 ILI9341 的 DC 引脚SPI 通信中的“指令/数据”语义开关ILI9341 不像普通 SPI 外设那样用 MOSI 传数据、MISO 传状态它用DCData/Command引脚区分当前传输的是“寄存器地址”还是“寄存器值”。这是本例程与通用 SPI 通信的最大差异当DC LOWMOSI 上传输的是指令码如0x2C内存写0x2A列地址设置当DC HIGHMOSI 上传输的是数据如像素 RGB565 值、地址参数。提示很多初学者直接把 ILI9341 当作纯 SPI 设备忽略 DC 引脚结果屏幕花屏或不亮。DC 必须由 MCU GPIO 独立控制不能并入 SPI 总线。4.2 从机板 LCD 初始化SPI 模式关键寄存器序列ILI9341 上电后需按严格顺序写入约 20 个寄存器本例程精简为最简可行集实测可点亮// 从机板ILI9341 初始化SPI 模式 void LCD_Init(void) { // 1. 硬件复位假设 RST 引脚接 PA0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); HAL_Delay(120); // 2. 设置 DC 引脚为输出假设 DC 接 PA1 __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 3. 发送初始化序列DCLOW 发指令DCHIGH 发参数 LCD_WriteCmd(0xCB); // Power control A LCD_WriteData(0x39); LCD_WriteData(0x2C); LCD_WriteData(0x00); LCD_WriteData(0x34); LCD_WriteData(0x02); LCD_WriteCmd(0xCF); // Power control B LCD_WriteData(0x00); LCD_WriteData(0XC1); LCD_WriteData(0X30); LCD_WriteCmd(0xE8); // Driver timing control A LCD_WriteData(0x85); LCD_WriteData(0x00); LCD_WriteData(0x78); LCD_WriteCmd(0xB1); // Frame rate control LCD_WriteData(0x00); LCD_WriteData(0x18); LCD_WriteCmd(0xB6); // Display function control LCD_WriteData(0x08); LCD_WriteData(0x82); LCD_WriteData(0x27); LCD_WriteCmd(0x11); // Exit Sleep HAL_Delay(120); LCD_WriteCmd(0x29); // Display on } // 封装函数写指令DCLOW void LCD_WriteCmd(uint8_t cmd) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // DC LOW HAL_SPI_Transmit(hspi1, cmd, 1, 100); } // 封装函数写数据DCHIGH void LCD_WriteData(uint8_t data) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // DC HIGH HAL_SPI_Transmit(hspi1, data, 1, 100); }逻辑说明LCD_WriteCmd()和LCD_WriteData()严格分离 DC 状态这是 ILI9341 正常工作的前提HAL_SPI_Transmit()调用前必须确保 NSS 已拉低本例程中 NSS 由主控控制从机无需操作 NSS初始化序列中的延时HAL_Delay()不可省略ILI9341 内部状态机需要时间响应。4.3 主控下发字符串的完整链路从协议头到像素点以主控发送SPI OK为例全链路如下主控调用SPI_Master_SendHeader(0x02, 10, 20)→ 发送[0x02, 0x0A, 0x14]从机中断接收解析出CMD0x02→ 执行LCD_SetCursor(10,20)主控检测到 ACK调用SPI_Master_SendString(SPI OK)SPI_Master_SendString()内部将字符串逐字节封装为[0x02, S, 0x00],[0x02, P, 0x00]...每字节占 3 字节帧ADDR_L 固定为 0从机收到每个帧Parse_SPI_Command()调用LCD_PutChar()后者内部调用LCD_WriteData(S)→LCD_WriteData(P)...LCD_PutChar()内部自动处理字符宽度、换行、ASCII 到点阵映射本例程内置 16x16 点阵字库。注意LCD_PutChar()必须在main()循环中调用而非中断内。因为点阵渲染涉及多次LCD_WriteData()耗时长放中断里会导致 SPI 中断被阻塞主控收不到应答。5. 避坑 / 常见问题 / 排查那些让你对着示波器骂街的 5 个真实翻车现场SPI 板间通信的坑不在代码而在信号完整性、时序对齐和状态机竞态。以下是本例程实测踩过的 5 个高频问题按现象→原因→解决排列拒绝玄学5.1 现象主控HAL_SPI_Transmit()返回HAL_TIMEOUT示波器看 SCLK 无波形原因主控 NSS 引脚未正确拉低或从机 NSS 引脚浮空未下拉导致从机 SPI 模块未使能。HAL 库在HAL_SPI_Transmit()内部会等待TXE发送缓冲空标志若从机未响应标志永不置位。解决用万用表测主控 PA4 对地电压正常应为 0V低电平从机 NSS 引脚如 PA4必须外接 10kΩ 下拉电阻到 GND在MX_GPIO_Init()中确认 PA4 模式为GPIO_MODE_OUTPUT_PP而非INPUT。5.2 现象主控能发从机能收但rx_buffer[0]总是0x00或随机值原因从机 MISO 引脚未配置为复用推挽输出Alternate Function Push-Pull或主控 MISO 引脚配置错误应为浮空输入而非上拉/下拉。解决从机 PA6MISO必须配置为GPIO_MODE_AF_PP速度GPIO_SPEED_FREQ_HIGH主控 PA6MISO必须配置为GPIO_MODE_INPUTPullGPIO_NOPULL检查HAL_GPIO_Init()调用顺序必须先初始化 GPIO再初始化 SPI否则复用功能不生效。5.3 现象液晶屏显示乱码、偏移、或只显示左上角 1/4原因ILI9341 的列地址设置寄存器0x2A和页地址设置寄存器0x2B未正确写入或0x2C内存写指令后未及时切换 DC 状态。解决在LCD_SetCursor(x,y)函数中必须按顺序写LCD_WriteCmd(0x2A)→LCD_WriteData(x8)→LCD_WriteData(x0xFF)→LCD_WriteData((xwidth-1)8)→LCD_WriteData((xwidth-1)0xFF)LCD_WriteCmd()和LCD_WriteData()之间不能加延时但LCD_WriteCmd(0x2A)和下一个LCD_WriteCmd(0x2B)之间需HAL_Delay(1)确认LCD_WriteData()内部HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET)执行成功可用逻辑分析仪抓 DC 信号。5.4 现象主控连续发多个命令从机只响应第一个原因从机中断服务程序SPI1_IRQHandler()中未清除RXNE标志或rx_index变量未声明为static导致跨中断调用时数据覆盖。解决rx_buffer和rx_index必须为staticHAL_SPI_Transmit()回传 ACK 的位置必须在Parse_SPI_Command()之后且不能在中断内调用耗时函数在main()循环中添加if (new_cmd_received) { Parse_SPI_Command(last_rx_buffer); new_cmd_received 0; }将解析逻辑彻底移出中断。5.5 现象示波器看 MOSI 波形正常MISO 在主控发完后才跳变且数据错位原因主控HAL_SPI_TransmitReceive()的Timeout参数过小如设为 10而从机处理Parse_SPI_Command()耗时超过 10ms导致主控提前退出从机仍在计算。解决将HAL_SPI_TransmitReceive()的 timeout 改为500单位 ms从机Parse_SPI_Command()中禁止调用HAL_Delay()、printf()等阻塞函数若必须延时改用SysTick计数器轮询while(tick target_tick)不进 SysTick 中断。6. 进阶技巧用逻辑分析仪抓包验证协议一致性以及从机状态机的健壮性加固当双板通信在实验室跑通后真正考验工程能力的是它能否在电源波动、温度变化、PCB 走线干扰下持续稳定本章不讲理论只给两个我亲手验证过、现在仍写在项目 checklist 里的硬核技巧。6.1 用 Saleae Logic 抓包三步锁定协议层错误逻辑分析仪不是摆设是 SPI 通信的“黑匣子”。抓包目标不是看波形美不美而是验证主从双方对同一帧数据的理解是否一致。操作流程通道分配CH0SCLKCH1MOSICH2MISOCH3NSSCH4DC从机侧触发设置以 NSS 下降沿为触发捕获长度设为 100us足够抓 3 字节解码配置SPI 解码器中CPOL0, CPHA1Bit orderMSBCS pinCH3Data outCH1Data inCH2。抓到包后重点比对三处NSS 与 SCLK 时序NSS 下降沿后SCLK 是否在 ≤100ns 内启动若延迟过大1us检查主控 GPIO 输出速度是否设为HIGHMOSI 与 MISO 数据一致性主控发[0x02,0x0A,0x14]从机 MISO 是否在对应位置回传[0xAA,0x00,0x00]若 MISO 第一字节是0x00说明从机未进入接收状态DC 引脚切换时机在0x02指令后DC 是否在下一个字节0x0A传输前由 LOW 切为 HIGH若 DC 未切0x0A会被当作指令执行导致寄存器错配。提示Saleae 官方协议解码器对 CPHA1 支持良好但某些国产逻辑分析仪需手动勾选 “Sample on falling edge” —— 这正是本例程选择 Mode 1 的底层原因下降沿采样更易被仪器捕获。6.2 从机状态机健壮性加固防止单次通信异常导致整机锁死裸机开发最怕“一次错误永久失联”。本例程在从机main()循环中加入三层防护// 从机板main() 循环中的状态机守护 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); LCD_Init(); uint32_t last_spi_time HAL_GetTick(); uint8_t spi_timeout_count 0; while (1) { // 1. SPI 超时看门狗若 500ms 未收到新数据强制复位 SPI 状态 if (HAL_GetTick() - last_spi_time 500) { spi_timeout_count; if (spi_timeout_count 3) { // 连续 3 次超时认为主控离线 __HAL_RCC_SPI1_FORCE_RESET(); __HAL_RCC_SPI1_RELEASE_RESET(); spi_timeout_count 0; } } // 2. 协议解析完成标志检查由中断置位 if (new_cmd_received) { Parse_SPI_Command(last_rx_buffer); new_cmd_received 0; last_spi_time HAL_GetTick(); // 重置超时计时器 spi_timeout_count 0; } // 3. LCD 刷新任务非阻塞 if (lcd_refresh_pending) { LCD_Refresh(); lcd_refresh_pending 0; } HAL_Delay(1); // 防止空循环吃满 CPU } }参数说明last_spi_time记录最后一次成功接收时间spi_timeout_count统计连续超时次数__HAL_RCC_SPI1_FORCE_RESET()是硬件级复位比HAL_SPI_DeInit()更彻底可清除所有寄存器残留状态HAL_Delay(1)不可省略它让出 CPU 时间片避免while(1)占满资源影响其他外设如按键扫描。6.3 一份可直接复用的文件清单与编译指引本例程交付物为完整可编译工程不含任何第三方库依赖所有源码均基于 STM32CubeMX 6.12 GCC 10.3.1 生成文件类型文件名说明大小工程文件Master_STM32F103C8.ioc主控板 CubeMX 配置文件含 SPI1/PA4-712 KB工程文件Slave_STM32F103C8.ioc从机板 CubeMX 配置文件含 SPI1/PA0-1/4-714 KB源码Core/Src/spi_master.c主控板 SPI 发送、协议封装、LCD 字符串下发3.2 KB源码Core/Src/spi_slave.c从机板 SPI 中断、协议解析、ILI9341 驱动8.7 KB源码Core/Src/lcd_ili9341.cILI9341 初始化、点阵字库、绘图函数15.4 KB头文件Core/Inc/spi_protocol.h协议 CMD 定义、缓冲区声明、函数原型1.1 KB编译指引打开 CubeMX导入.ioc文件生成代码将spi_master.c/spi_slave.c/ lcd_ili9本文还有配套的精品资源点击获取