
简介面向嵌入式开发者和智能硬件爱好者这份资料包以STM32F103VET6为核心围绕蓝牙2.0寸TFT智能手表完整呈现软硬件协同设计。压缩包共1075个文件、约36.99MB以C/H源码、Altium原理图为主包含schdoc、uvproj等工程文件以及PDF说明、BMP屏幕素材和编译中间文件便于直接查看电路连接、阅读代码并复现项目。目前已有292人学习下载。软件源码划分了Bootloader、FreeRTOS驱动、GUI库、蓝牙通信库和应用层功能模块覆盖从开机初始化到通知显示的全流程源码文件展示了模块化分层设计思路硬件原理图则细致描绘TFT屏接口、蓝牙模块UART连接、电源管理与电池充电电路。无论是学习STM32外设配置、TFT屏幕驱动还是研究低功耗蓝牙通信和嵌入式GUI开发这套资料都能提供可对照分析的完整参考适合毕业设计、课程项目或产品原型二次开发有助于系统掌握嵌入式系统、蓝牙通信和TFT显示技术。1. 拿到“STM32F103VET6 蓝牙 2.0寸TFT智能手表”资料后先要看懂这三件事一套智能手表的软硬件资料拆开来看无非三块主控、屏幕、无线。STM32F103VET6 是意法半导体的主流 M3 芯片LQFP-100 封装512KB Flash、64KB RAM引脚多到用不完2.0 寸 TFT 彩屏决定显示方案的复杂度蓝牙模块负责和手机打交道。而 ALTIUM 原理图加上软件源码恰恰把这三块串成了一条完整的链路——从原理图里看供电和引脚分配从源码里找 GPIO 和串口配置两边一对整块表的工作方式就清晰了。这个标题对三类人最有用拿它做毕设或课程设计的学生想低成本攒一块可穿戴设备的工程师以及想从硬件到固件完整走一遍开发流程的爱好者。你能拿到的是一份可以打开、编译、烧录、改动的起点而不是一块只能看不能碰的成品。下面我按自己拿到这类资料后的惯用顺序来讲先看原理图再调屏幕再通蓝牙最后谈调试技巧。2. ALTIUM 原理图阅读与工程结构先别急着改代码2.1 用 altium designer 打开工程前的版本检查标题里写的是 ALTIUM 原理图这通常意味着文件可能是.SchDoc加.PcbDoc也可能是 AD17 之后的工程格式。第一步不是双击而是先看文件夹里有几个文件、扩展名是什么、有没有 Library 或 Doc 子目录。常见做法是先把 ZIP 解开到不含中文的路径例如D:\watch_project\然后打开 AD 时选择File → Open定位到项目文件.PrjPcb或.PrjScr。这里有个高频坑AD 版本太老打不开新工程或版本太新导致老工程库丢失。altium designer 安装教程里反复提到的兼容性问题在这个场景下非常现实。如果打开后原理图里全部是绿色问号或飞线乱飞那不是图坏了是库没加载。处理方式是在Projects面板里右键工程名选Add Existing to Project把同目录下的.SchLib和.PcbLib文件手动挂上。要是还是缺封装直接在原理图编辑器里按Tools → Footprint Manager批量检查每个元件的 Footprint 是否指向实际存在的封装。我一般还会顺手做一次编译检查Project → Compile PCB Project。编译产生的Messages面板里如果有 Error先看是不是引脚冲突或者重复标号如果是 Warning比如悬空网络或未连接的电源引脚可以后处理。这一步能省掉后面调试硬件时大量“为什么这个脚没反应”的排查时间。2.2 原理图里的关键网络电源轨、TFT 接口、蓝牙串口这块表的原理图再简单也逃不过几个固定区域。拿到图后不要从上往下扫而是按功能块跳着看。先找电源。智能手表用锂电典型电压 3.7V经过 LDO 或 DC-DC 降到 3.3V 供给 STM32F103VET6 和屏幕。原理图上通常能看到VBAT、3V3、5V这些电源标号。注意 MCU 的 VDDA 和 VDD 是否分开供电VDDA 有没有单独的滤波电容这直接决定 ADC 采样的稳定性。再看主控。STM32F103VET6 是 100 脚封装PA0-PA15、PB0-PB15、PC0-PC15 基本全引出。原理图里重点看 BOOT0 和 BOOT1 的接法以及 NRST 是否有上拉电容和复位按键。下载接口通常引出 SWD 的 PA13/PA14或者 UART1 的 PA9/PA10。接着是 TFT 液晶屏。2.0 寸彩屏的接口方式要么是 4 线 SPI要么是 8080 并口。从原理图的网络标号上就能分辨SPI 接口会有SCLK、MOSI、MISO可选、CS、DC或RS、RST、BL并口则是一片连续的DB0-DB15。绝大多数低成本 2.0 寸屏走 SPI引脚少刷新率也够用。最后是蓝牙模块。常见接法是串口透传模块的 TX 接 MCU 的 RX模块的 RX 接 MCU 的 TXVCC 接 3.3V 或 5V取决于模块底板是否带稳压GND 共地。有的模块还有 STATE 引脚用于指示连接状态这个脚如果引出来了可以接到 MCU 的一个 GPIO 上做连接状态检测。把这些区域扫完后花五分钟建一张引脚对照表这是后续读源码最重要的索引功能模块STM32F103VET6 引脚网络标号说明TFT 屏幕PA5TFT_SCLKSPI 时钟TFT 屏幕PA7TFT_MOSISPI 数据TFT 屏幕PA4TFT_CS片选低有效TFT 屏幕PA2TFT_DC数据/命令选择TFT 屏幕PA3TFT_RST复位低有效蓝牙模块PA9BT_TX接模块 TX蓝牙模块PA10BT_RX接模块 RX指示灯PB5LED_STATUS连接状态指示这张表不一定是实际项目里的但阅读方式是一样的把原理图里的网络标号和源码里的宏定义对齐PIN 对不上就改代码或改图二选一。2.3 从原理图到 PCB 的检查点封装、丝印与阻抗如果你的目的是直接打板而不是单纯读图那要额外看几个地方。电池座和 USB 座这类大件的封装对不对——很多 Altium 封装库里 2.0 间距和 2.54 间距混用丝印一放大就看出来了。晶振旁边的负载电容值对不对8MHz 晶振配 20pF 电容是常规操作但板上如果空间紧张电容离晶振太远会导致起振不稳。蓝牙天线区域不要走地铜也不要放任何走线。2.4G 频段的信号对天线净空区很敏感这一点在原理图上看不出来但 PCB 上必须留出来。陶瓷天线下方所有层都要净空馈线长度尽量控制在 1/4 波长以内。提示如果你从没画过这类的板子别一上来就追求四层。这个项目两层板完全够用主要信号线适当加粗电源线至少 0.5mm晶振走线等长且远离排针即可。3. TFT 彩屏驱动先点亮背光再谈刷屏效率3.1 识别 2.0 寸屏幕的控制器型号并初始化2.0 寸 240×320 的 TFT“彩屏”常用的控制器有两颗ILI9341 和 ST7789V。两颗芯片的初始化序列是同一个套路先把寄存器值写进数组再按“软件复位 → 关闭显示 → 设置参数 → 开显示”的顺序刷进去。源码里通常以静态数组的形式存放这些命令格式大同小异先发命令字节再发数据。判断控制器型号最直接的办法是读 ID 寄存器。ILI9341 读0xD3能拿到三个字节的 IDST7789V 读0x04返回控制器版本。下面这段代码演示了通过 SPI 读取 ID 的过程uint32_t LCD_ReadID(void) { uint32_t id 0; LCD_CS_RESET(); // 片选拉低 LCD_DC_HIGH(); // 数据模式 SPI1_SendByte(0xD3 1 | 1); // 写入读命令 D3h LCD_DC_LOW(); // 切回命令模式 SPI1_SendByte(0x00); // 发送占位字节等待控制器响应 id SPI1_ReceiveByte() 16; // 第一个字节是 ID 高 8 位 id | SPI1_ReceiveByte() 8; // 中间字节 id | SPI1_ReceiveByte(); // 低字节 LCD_CS_SET(); // 片选拉高 return id; }这段代码的关键在于命令/数据位的切换时机。SPI 接口的 TFT 屏把DC引脚当作区分“当前字节是命令还是数据”的信号读 ID 时必须先以数据模式发送读命令的高位再切回命令模式让控制器响应。片选拉低后整个时序必须紧凑不能在中间插入 delay否则控制器会判定一次传输结束。SPI1_SendByte和SPI1_ReceiveByte是对裸 SPI 外设的封装实际项目中建议直接用SPI_I2S_SendData和SPI_I2S_ReceiveData。注意 STM32F103 的 SPI 接收寄存器是呆滞读的要人为往发送寄存器写 0xFF 才能触发一次时钟周期否则读回来的永远是 0x00。3.2 初始化序列里的关键寄存器设置ILI9341 的初始化数组动辄上百个字节但真正决定画面质量的只有几个寄存器。我一般在拿到现成源码后不会全盘照抄而是按需求裁剪。核心寄存器有这么几组。0xC0 - Power Control 1内部参考电压和 LCD 驱动电压设置默认值通常在 0x23 左右适当调高可以改善偏色但会增加功耗。0xC5 - VCOM Control 1VCOM 电压调整屏幕暗角或整体偏暗时优先查这个。0x36 - Memory Access Control控制扫描方向对应横屏/竖屏切换。这个寄存器里的三位MY、MX、MV决定坐标原点调试时就旋转方向不对改这里不要去改画点函数里的坐标映射。0x3A - Pixel Format Set0x55表示 16 位 RGB5650x66表示 18 位 RGB666。RGB565 每个像素两个字节刷屏数据量小是默认选择。下面给一个裁剪后的最小初始化序列static void ILI9341_InitSequence(void) { LCD_Reset(); LCD_WriteCmd(0x01); LCD_Delay(120); // 软件复位 LCD_WriteCmd(0x11); LCD_Delay(120); // 退出睡眠模式 LCD_WriteCmd(0x36); LCD_WriteData(0x48); // 扫描方向横屏 LCD_WriteCmd(0x3A); LCD_WriteData(0x55); // RGB56516位色深 LCD_WriteCmd(0xC0); LCD_WriteData(0x23); // 电源控制 LCD_WriteCmd(0xC1); LCD_WriteData(0x10); // 电源控制 LCD_WriteCmd(0xC5); LCD_WriteData(0x3E); // VCOM 电压 LCD_WriteCmd(0xC7); LCD_WriteData(0x86); // VCOM 偏移 LCD_WriteCmd(0x29); LCD_Delay(20); // 打开显示 }这段代码里没有用延时超过 200ms 的地方因为复位后的 120ms 延时是控制器要求的硬性时间缩短会导致初始化不稳定。0x36设置成0x48是横屏加上 BGR 颜色顺序如果你的屏显示偏红或偏蓝把最后的0x08位翻转即可——RGB 和 BGR 不对所有颜色都会错乱。3.3 画点、矩形填充与显存窗口的换算初始化完成后所有绘图操作都建立在“设置窗口 → 写入像素数据”这个模型上。ILI9341 和 ST7789V 都有 CASET列地址设置和 RASET行地址设置命令先框定一块矩形区域再连续填充像素数据效率和直接逐点画天差地别。下面是标准实现void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCmd(0x2A); // 列地址设置 LCD_WriteData(x0 8); LCD_WriteData(x0 0xFF); LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteCmd(0x2B); // 行地址设置 LCD_WriteData(y0 8); LCD_WriteData(y0 0xFF); LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteCmd(0x2C); // 开始写入显存 } void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t pixel_count (x1 - x0 1) * (y1 - y0 1); LCD_SetWindow(x0, y0, x1, y1); for (uint32_t i 0; i pixel_count; i) { LCD_WriteData(color 8); // 高字节在前 LCD_WriteData(color 0xFF); // 低字节在后 } }坐标计算逻辑在 2.0 寸屏上有个容易踩的坑控制器内部的列地址范围是 0-239行地址范围是 0-319但屏幕实际的安装方向可能旋转了 180 度。如果你的填充矩形出现对角线方向偏移不用怀疑代码逻辑去改0x36寄存器的值把扫描方向反过来即可。刷整屏的时间算一下240×320×2 字节 153.6KBSPI 时钟 9MHz 时约 170ms。这个速度对静态表盘没有任何问题但菜单滑动动画就会卡出明显的残影。要提速就得走 DMA。3.4 用 SPI DMA 把刷屏从“卡顿”变成“顺滑”F103 系列有 DMA1 和 DMA2SPI1 的 TX 请求挂在 DMA1 通道 3 上。配置一次 DMA 传输把显存数据从内存自动搬到 SPI 数据寄存器CPU 就不再参与每个字节的搬运刷一屏 153.6KB 数据的时间几乎可以压到 50ms 以内。void LCD_DMA_Config(void) { DMA_InitTypeDef dma_init; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); dma_init.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; // 目标SPI1 数据寄存器 dma_init.DMA_MemoryBaseAddr (uint32_t)dma_buffer; // 源内存缓冲 dma_init.DMA_DIR DMA_DIR_PeripheralDST; // 内存到外设 dma_init.DMA_BufferSize 0; dma_init.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma_init.DMA_MemoryInc DMA_MemoryInc_Enable; dma_init.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; dma_init.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; dma_init.DMA_Mode DMA_Mode_Normal; dma_init.DMA_Priority DMA_Priority_High; dma_init.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, dma_init); }实际调用时把要刷的像素数据按 RGB565 格式拼进dma_buffer然后执行DMA_SetCurrDataCounter(DMA1_Channel3, length); DMA_Cmd(DMA1_Channel3, ENABLE);等DMA_GetFlagStatus(DMA1_FLAG_TC3)置位就说明搬完了。这个方案里 DMA 搬运的源地址一定要是物理连续的内存F103 的 SRAM 没有缓存一致性概念不需要担心 cache 问题但堆上分配的缓冲区要保证大到能容纳一行的像素240×2480 字节一次搬一行或一屏分几次搬都行。提示开了 DMA 之后SPI_I2S_SendData这种轮询发送函数就不能和 DMA 混用了切换回轮询模式前必须先关 DMA 请求并把 SPI 的 TXE 标志清干净否则会出现发一半卡死的现象。4. 蓝牙模块配置与 STM32 串口对接从 AT 指令到数据透传4.1 模块选型、接线顺序和“hc05蓝牙模块连接不上”的排查资料里标题只写“蓝牙”没有指定模块型号但这类项目中使用率最高的还是 HC-05 和 JDY-31。HC-05 自带底板有 KEY 引脚用于进入 AT 模式LED 闪烁频率表示连接状态JDY-31 体积更小没有 KEY 引脚配置方式用“上电前按住按键”来区分。两块模块的串口参数默认都是 9600-8-N-1可以直接替换使用。接线顺序固定为四条线模块 VCC 接开发板 5VHC-05 板载稳压需要 5V 输入接 3.3V 会出现串口有响应但配对不上的问题、GND 共地、模块 TX 接 MCU 的 RX、模块 RX 接 MCU 的 TX。如果你的 MCU 板上没有 5V 引脚那就只能用 3.3V但要注意 HC-05 的 RX 引脚不能直接接 5V 逻辑电平而 STM32F103 的引脚在开漏输出模式下是可以兼容 3.3V 的注意别把两个电平混接。“hc05蓝牙模块连接不上”这个热搜词里的问题十有八九出在 AT 模式没进对。进入 AT 模式的方法是按住模块上的按键再给模块上电此时串口助手发AT返回OK模块串口波特率会变成 38400部分版本是 9600这时候才能用 AT 指令配置。如果你已经接好了 STM32 却收不到任何返回先断掉 MCU 的 TX 到模块 RX 这条线用 USB 转 TTL 单独连模块验证排除 MCU 程序占用了串口的问题。4.2 主从机角色、配对密码与波特率配置AT 模式里最常用到的命令是角色设置、配对码设置和串口参数。下面是完整表格AT 指令功能示例返回值AT测试通信OKATROLE0设为从机OKATROLE1设为主机设备可被搜索OKATPSWD1234设置配对密码OKATUART115200,0,0设置串口波特率为 115200无校验1 停止位OKATNAMEWatchBT修改蓝牙名称OKATCMODE1任意地址连接模式OKATADDR查询模块地址ADDR: 1234:56:789012配置主从方案要分两种场景。手表只和手机通信一端固定从机、手机做主机够用如果要做两个模块互相通信手表和遥控器之间那就一个设ROLE0、一个设ROLE1两边PSWD一致CMODE0指定地址连接主机端用ATBIND从机地址绑定目标模块上电后自动回连。从机在CMODE0模式下不会主动搜索手机能不能搜到它取决于ROLE1可被发现的从机状态是否正确。波特率这里有一个容易忽略的点模块默认 9600MCU 配置的串口波特率必须和模块一致。如果你用 115200 配置了模块但 MCU 的初始化代码还是9600数据收发时会出现频繁的乱码或丢字节。改完ATUART后必须重新上电命令才会真正生效。4.3 中断接收 超时判定避免粘包和半包STM32 与蓝牙模块之间是 UART只要 MCU 的波特率和模块一致连接稳定后数据就会源源不断进入接收寄存器。但“数据到了中断里”和“数据能组成一条完整消息”是两回事裸机开发里最常见的错误是逐字节处理结果一帧多字节的数据刚收一半就被拿去解析程序逻辑直接乱掉。我的做法是开一个环形缓冲中断里只负责丢数据进缓冲主循环里做超时判定。超时判定规则相邻两个字节到达时间间隔大于 20ms 就认为一包数据已经结束从缓冲里取出一整包来处理。代码如下#define RX_BUF_SIZE 256 static uint8_t rx_buf[RX_BUF_SIZE]; static volatile uint16_t rx_write 0; static volatile uint16_t rx_read 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART2); uint16_t next (rx_write 1) % RX_BUF_SIZE; if (next ! rx_read) { // 缓冲未满才写入 rx_buf[rx_write] data; rx_write next; } } } int16_t UART_GetPacket(uint8_t *packet, uint16_t max_len, uint32_t timeout_ms) { uint32_t start HAL_GetTick(); uint16_t pkt_len 0; while (pkt_len max_len) { if (rx_read ! rx_write) { packet[pkt_len] rx_buf[rx_read]; rx_read (rx_read 1) % RX_BUF_SIZE; start HAL_GetTick(); // 收到一个字节就重置计时 } if (HAL_GetTick() - start timeout_ms) { break; // 超时判定一包结束 } } return (pkt_len 0) ? (int16_t)pkt_len : -1; }timeout_ms的取值跟波特率相关9600 波特率下一个字节接近 1ms两字节间隔超过 5ms 就很可疑是两条独立消息115200 下间隔甚至可以压到 2ms。建议设 20ms 作为一个折中值。这段逻辑里的环形缓冲把中断上下文和主循环解耦了即使解析数据耗时较长也不会丢中断里进来的字节代价是多花一点 RAM。与这个配套的是状态机解析。数据包里如果带帧头比如0xAA 0x55和帧尾0x0D 0x0A就按帧拆包如果不带格式那超时判定就是唯一的拆包手段。两种方式可以叠加先等超时再查帧头帧尾都不满足就丢弃整个包。4.4 蓝牙测距的取舍RSSI 做接近检测比做精确测距更实际热搜词里出现“蓝牙测距”不是偶然做手表的人总想让手机靠近时自动切表盘或解锁。但基于经典蓝牙的 RSSI 测距在室内环境非常不稳定——信号强度受人体遮挡、金属结构、无线环境干扰影响极大2.4G 频段上任何 WiFi 路由器都可能让 RSSI 跳变 10dB 以上。测出来的距离误差在 1 米到 3 米之间浮动是常态拿来判断“手机在不在 1 米范围内”都勉强。如果真想用距离做触发条件建议用阈值判断而不是连续测距。比如手机端 App 每 500ms 上报一次 RSSI 值手表端取最近 10 次的平均值只有平均值低于 -70dBm 且持续 3 秒才判定“手机远离”用迟滞逻辑避免临界点抖动。这在工程上比任何距离公式都可靠代价是不能得到一个精确的厘米级距离数值。注意如果你的项目要求毫米级测距那就不是普通蓝牙模块能干的事了得走 UWB 或者带测距功能的蓝牙 5.1 方向成本和复杂度都上一个台阶。5. 调试顺序与源码定位技巧5.1 先点亮背光再点亮屏幕最后连蓝牙在一套陌生源码里上手调试最忌讳的是上来就编译整个工程然后试图把每个功能都跑通。我的调试顺序固定为四步每一步都有明确的通过标准。第一步编译工程下载到板子用示波器或万用表确认 3.3V 和 GND 之间没有短路电源指示灯常亮。第二步让 MCU 输出一个 GPIO 高电平去控制屏幕背光如果背光能亮说明 MCU 主频、GPIO 初始化和下载链路都是通的。第三步跑屏幕初始化代码让屏幕显示纯色这一步过了说明 SPI 通信和初始化寄存器没问题。第四步打开蓝牙模块的 AT 模式用串口助手直接发指令确认模块本身是好的再把 MCU 串口接上做透传。每一步失败时先缩小范围背光不亮查电源和背光引脚纯色不对查 SPI 引脚和0x36扫描方向AT 指令无返回查模块供电、串口交叉接线和波特率。不要跳步跳步只会让问题叠加到时候分不清是屏幕没通还是蓝牙没通。5.2 用 grep 定位源码里的关键宏定义拿到软件源码后整个工程可能有一两百个文件挨个看效率太低。我一般直接在源码目录下用grep搜关键字把引脚定义、屏幕初始化、蓝牙处理函数的位置一次性摸出来。grep -rn GPIO_Pin_5\|GPIO_Pin_7 Src/ grep -rn ILI9341\|ST7789V Src/ grep -rn USART2\|USART1 Src/ grep -rn RCC_APB2Periph_SPI1 Src/搜出来的结果先看头文件通常main.h或bsp.h里会集中放引脚宏。这些宏的命名习惯一般是LCD_CS_GPIO_Port、LCD_DC_Pin、BT_TX_Pin之类的组合找到之后和你在原理图里整理的引脚表逐一对齐。如果发现源码里的引脚和原理图不一致优先以源码为准改原理图或飞线因为重新编译的工程量远小于改板子的工程量。5.3 低功耗处理手表掉电快的三个常规原因和对应做法智能手表常用的方案是保留一块常显表盘但 F103 在 72MHz 全速运行时的电流不低纯靠一颗 200mAh 电池撑不了太久。常规做法是把系统做成分时休眠空闲时进入 STOP 模式RTC 闹钟或外部按键唤醒唤醒后只刷新屏幕局部区域。void Enter_StopMode(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // 关闭不需要的外设时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, DISABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, DISABLE); // 所有未用的 GPIO 配置为模拟输入避免悬空漏电 GPIO_InitTypeDef gpio; gpio.GPIO_Mode GPIO_Mode_AIN; gpio.GPIO_Pin GPIO_Pin_All; GPIO_Init(GPIOA, gpio); GPIO_Init(GPIOB, gpio); GPIO_Init(GPIOC, gpio); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 醒来后需要重新配置系统时钟 SystemInit(); }进入 STOP 模式后电流能压到微安级但代价是唤醒后所有外设都要重新初始化。屏幕内容会保留在控制器内部显存里但 SPI 引脚状态会失效所以唤醒后第一步是重新配置 SPI 和 GPIO再按需刷新表盘局部。按键唤醒用 WAKEUP 引脚PA0或 RTC 闹钟中断两者都行PA0 是空闲态的默认唤醒源代码简单但只能从外部触发做不到定时唤醒。5.4 一张表格收掉高频调试故障把这段时间最容易踩的问题集中列出来备查用。现象可能原因排查顺序背光不亮背光引脚 GPIO 配置错误或供电缺失先量引脚电压再查原理图网络屏幕白屏SPI 时钟极性/相位不对或初始化序列未完成调整 CPOL/CPHA复位后加足延时屏幕颜色错乱RGB/BGR 顺序不对改0x36寄存器第 3 位蓝牙搜不到模块未进入可发现模式置ROLE1重新上电AT 无返回串口接线交叉错误或波特率不匹配先用 USB 转 TTL 单独测试模块数据乱码波特率不一致或时钟配置错误核对RCC_GetClocksFreq()后的实际串口波特率休眠后无法唤醒唤醒引脚未配置或 STOP 模式下时钟丢失检查 PWR 时钟是否使能WAKEUP 引脚电平是否反转把这一轮走完屏幕能亮、蓝牙能连、手机能收数据你就有了一个可以继续往上堆功能的基础。剩下的表盘 UI 设计、图标素材、背光亮度曲线调整都只是在这套框架里加内容的活。还有一点值得花时间做把原理图里的每个网络标号和源码里的每个宏定义建立对应关系文档这比任何代码注释都耐用。本文还有配套的精品资源点击获取