外观
STM32 蓝牙温度报警模块 — 代码阅读文档
主控:STM32F103C8T6 | HAL 库 | CMake + arm-none-eabi-gcc | 文档版本对应仓库当前状态
本文档面向新接手本项目的开发者,按"自顶向下"顺序讲解:先看懂整体架构与主循环,再逐层下钻到各驱动模块,最后汇总设计亮点与注意事项。建议配合源码 app_main.c 一同阅读。
1. 项目概述
本项目是一个双路温度采集 + 蓝牙上报 + 本地报警的嵌入式模块,核心功能:
- 同时采集 DS18B20(数字温度传感器) 与 NTC 热敏电阻(模拟 ADC) 两路温度,并在 1.8 寸 TFT 屏本地显示;
- 两种工作模式,靠长按任一按键切换:
- 报警模式(ALARM):可设置上/下阈值;DS18B20 温度越限时蜂鸣器报警;
- 监控模式(NORMAL):仅实时显示,按键单击/双击无效;
- 报警模式下 SW1 调上阈值、SW2 调下阈值(单击 + / 双击 −),并保证
下阈值 < 上阈值; - 通过 蓝牙串口透传(USART1) 以 NDJSON(每行一个 JSON)协议上报温度、接收设置指令(阈值、采样间隔、传感器选择、查询)。
阈值比较以 DS18B20 为准;NTC 温度仅用于显示与可选上报。完整需求见 需求.md,蓝牙上位机协议见 BT_UART/文档/需求文档.md。
2. 硬件资源与引脚映射
引脚定义集中在 main.h(L59-L77),GPIO 初始化在 gpio.c。
| 外设 | 引脚 | 端口 | 模式 | 说明 |
|---|---|---|---|---|
| 蓝牙 USART1 | TX=PA9, RX=PA10 | — | AF | 透传串口,9600 8N1 |
| 蜂鸣器 BEEP | PA6 | GPIOA | 推挽输出 | 高电平发声(有源) |
| DS18B20 DQ | PA2 | GPIOA | 开漏输出(运行时由驱动改配) | 1-Wire,外接 4.7K 上拉 |
| NTC ADC | PA1 | GPIOA | 模拟输入 | ADC1_IN1,分压中点 |
| LCD CS | PA3 | GPIOA | 推挽(高速) | SPI 片选 |
| LCD RES | PA4 | GPIOA | 推挽输出 | 复位 |
| LCD SCL | PA5 | — | SPI1_SCK | 硬件 SPI,仅发送 |
| LCD SDA | PA7 | — | SPI1_MOSI | |
| LCD DC | PB5 | GPIOB | 推挽(高速) | 数据/命令选择 |
| LCD BL | PB6 | GPIOB | 推挽输出 | 背光 |
| SW1 | PB8 | GPIOB | EXTI 下降沿 + 内部上拉 | 按下为低(active low) |
| SW2 | PA0 | GPIOA | EXTI 下降沿 + 内部上拉 | 按下为低(active low) |
| VREFINT | — | 内部通道 | ADC_CHANNEL_VREFINT | 用于校准 VDDA |
时钟:HSE 8MHz × PLL9 = 72MHz;ADC 时钟 = PCLK2/6 = 12MHz(见
main.c的SystemClock_Config,L117-L157)。
3. 目录结构与分层架构
STM32_TEMP_BT_PROJECT/
├── Core/ # CubeMX 生成:main、外设初始化、HAL 配置
│ ├── Src/main.c # 入口:初始化 + while(1){ app_task(); }
│ ├── Src/gpio.c # 所有 GPIO 配置(含 SW1/SW2 的 EXTI)
│ └── Src/tim.c # TIM1 = 1ms 基准中断
├── App/ # 应用层(与硬件解耦的业务逻辑)
│ ├── app_main.c # ★ 核心:状态机、显示、按键、采样调度
│ └── bt_protocol.c # 蓝牙 NDJSON 协议:行缓冲、解析、组帧发送
├── BSP/ # 板级驱动层(每类外设一个文件)
│ ├── bsp_ntc.c # NTC 测温(ADC + VREFINT 校准 + R-T 查表)
│ ├── bsp_ds18b20.c # DS18B20 1-Wire bit-bang 驱动
│ ├── hw_key.c # ★ 实际使用的按键驱动(10ms 节拍)
│ ├── bsp_key.c # 另一套按键驱动(当前未使用,见 §11)
│ ├── bsp_lcd.c # LCD 绘图/字符 API(基于 lcd_interface 抽象)
│ ├── bsp_lcdinit.c # ST7735 初始化序列 + 0.96/1.8 寸注册函数
│ ├── bsp_lcdfont.h # ASCII / 汉字字库
│ ├── bsp_beep.c # 蜂鸣器开关
│ └── bsp_dwt.c # DWT 周期计数器 → 微秒级延时
├── Drivers/ # STM32 HAL + CMSIS
├── CMakeLists.txt # 构建脚本(含 NTC 10K/100K 规格切换)
└── 需求.md # 项目设计需求1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
分层关系(依赖方向自上而下):
main.c ──> App/app_main ──┬──> BSP/hw_key, bsp_ntc, bsp_ds18b20, bsp_beep
├──> BSP/bsp_lcd ──> bsp_lcdinit ──> HAL SPI
└──> App/bt_protocol ──> HAL UART
全部 BSP ──> HAL(adc/spi/usart/tim/gpio)──> 寄存器1
2
3
4
2
3
4
应用层只调用 BSP 提供的语义化接口(如 bsp_ntc_read(&t)),不直接碰寄存器;唯一例外是 DS18B20/NTC 驱动内部为时序/性能需要直接操作 GPIO/ADC 寄存器。
4. 启动流程与主循环
4.1 启动
main.c(L68-L111)标准 HAL 流程:
HAL_Init() → SystemClock_Config(72MHz)
→ MX_GPIO_Init / MX_ADC1_Init / MX_SPI1_Init / MX_USART1_Init / MX_TIM1_Init
→ app_init() // 应用层接管
→ while(1) app_task();1
2
3
4
2
3
4
app_init()(L398-L429)完成应用层全部初始化,顺序值得注意:
- 清空显示缓存
s_cache; bsp_dwt_init()—— 必须最先,后续 DS18B20 微秒延时时序依赖它;bsp_ds18b20_init()/bsp_ntc_init()/bsp_beep_init();- 按键句柄初始化(SW1=PB8, SW2=PA0,按下电平 =
GPIO_PIN_RESET); - LCD 注册并初始化(方向 1 = 竖屏 128×160),绘制静态界面;
bt_protocol_init()—— 使能 USART1 接收中断;HAL_TIM_Base_Start_IT(&htim1)—— 启动 1ms 定时中断;- 启动首次 DS18B20 转换(下次采样才能读到有效结果)。
4.2 主循环 app_task()(L431-L461)
采用协作式轮询(无 RTOS),单轮内按固定优先级处理 5 类事务:
app_task() 每轮:
┌─① 按键:若有 10ms 节拍标志 或 EXTI 按下标志 → Key_Scanf() 扫描两键
│ process_key_events() 消费事件 → 切模式 / 调阈值
├─② 蓝牙:bt_protocol_poll() 解析已完成的一行
│ handle_bt_cmds() 分发指令
├─③ 蜂鸣器:handle_beep(now) 越限则按 200ms 半周期闪烁
├─④ 显示:display_step() ★ 每轮只重绘 1 个内容变化的区域
└─⑤ 采样:距上次 ≥ interval_ms → sample_and_report() 读传感器 + 上报1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
设计要点:长任务(采样、整屏刷新)被拆分或限速,保证按键/蓝牙这类需要及时响应的事务不会被长时间阻塞。
5. 应用层 app_main 详解
5.1 关键状态变量
| 变量 | 含义 |
|---|---|
s_mode | 当前模式:APP_MODE_ALARM(默认) / APP_MODE_NORMAL |
s_ds_temp / s_ntc_temp | 两路温度缓存 |
s_ds_valid | DS18B20 是否已读到有效值(未连传感器时显示 --、不报警) |
s_thr_hi / s_thr_lo | 上/下阈值(初值 35.0 / 10.0) |
s_interval_ms | 采样/上报周期(默认 1000ms) |
s_sensor | 蓝牙上报用传感器选择(0=DS18B20 默认,1=NTC) |
s_cache[REG_CNT][20] | 各显示区域的上次字符串,用于变化检测 |
5.2 显示分块刷新机制(亮点)
屏幕被划分为 5 个逻辑区域(REG_* 枚举,L26):
| 索引 | 区域 | 位置 |
|---|---|---|
REG_DS | DS18B20 温度 | (6,42) 大字 24 号 |
REG_NTC | NTC 温度 | (40,70) |
REG_MODE | 当前模式 | (0,92) |
REG_THR | 阈值 / Alarm OFF | (0,110) |
REG_STAT | 状态行(OK/ALARM/Monitoring) | (0,130) |
display_step()(L236-L250)每轮只重绘第一个内容发生变化的区域(对比 s_cache),重绘后立即 return:
- 为什么这么做? SPI 写屏是慢操作,整屏重绘会阻塞主循环数十毫秒,导致按键漏扫。分块后单轮最大开销 = 一个区域的填充+绘字,可控。
- 代价是:若多个区域同时变化,需要连续几轮才能全部刷新——在 1Hz 采样场景下完全可接受。
fmt_local()(L53-L100)是自实现的浮点转字符串函数(支持 1~2 位小数、负数、四舍五入)。刻意不使用 printf("%f"),以避免引入庞大的浮点 printf 代码(节省 Flash)。
5.3 阈值调节与互斥约束
四个调节函数(app_main.c L113-L134),核心是保证 lo + step ≤ hi:
thr_hi_down():hi不能低于lo + step;thr_lo_up():lo不能高于hi - step;- 上下界统一钳位在
[-40, 125](APP_THRESH_MIN/MAX); - 步进
0.5℃(APP_THRESH_STEP)。
5.4 按键事件处理 process_key_events()(L263-L290)
| 事件 | SW1(上阈值) | SW2(下阈值) | 任一键 |
|---|---|---|---|
| 长按 | — | — | 切换模式(优先判定,处理后立即 return) |
| 单击(报警模式) | 上阈值 +0.5 | 下阈值 +0.5 | — |
| 双击(报警模式) | 上阈值 −0.5 | 下阈值 −0.5 | — |
| 单击/双击(监控模式) | 无效 | 无效 | — |
长按判定优先级最高:一旦识别到长按,立即切换模式并清除两键事件,避免与单击/双击冲突。
5.5 蓝牙指令处理 handle_bt_cmds()(L293-L329)
| 指令 | 行为 |
|---|---|
BT_CMD_SET_THRESHOLD | 设置 lo/hi,强制 lo < hi,并自动切到报警模式 |
BT_CMD_SET_INTERVAL | 设置采样间隔(最小 200ms) |
BT_CMD_SET_SENSOR | 切换上报传感器(字符串匹配 NTC / DS18B20) |
BT_CMD_QUERY | 回送一帧 config(当前阈值/间隔/传感器) |
5.6 蜂鸣器报警 handle_beep()(L332-L347)
- 仅在 报警模式 + DS18B20 有效 时参与;
- DS18B20 温度
> hi或< lo→ 蜂鸣器以 200ms 半周期闪烁(bsp_beep_set((now/200)&1)); - 否则关闭。报警判定只看 DS18B20,符合需求。
5.7 周期采样与上报 sample_and_report()(L350-L370)
bsp_ds18b20_read() → 读上次转换结果(失败则保持旧值/无效)
bsp_ds18b20_start() → 启动下一次转换(流水线,约 750ms 后才有结果)
bsp_ntc_read() → 即时采样
bt_send_data(所选传感器温度, ...) → 蓝牙上报一帧 data1
2
3
4
2
3
4
DS18B20 采用"读旧值 + 启动新转换"的流水线模式,避免主循环阻塞 750ms 等待转换完成。首次有效结果在启动后第二次采样周期才出现。
6. 蓝牙通信协议 bt_protocol
bt_protocol.h / bt_protocol.c。NDJSON(每帧以 \n 结尾),9600 8N1。
6.1 接收链路(中断 + 行缓冲 + 主循环解析)
USART1_RX 中断
│ HAL_UART_RxCpltCallback:逐字节写入 s_rx_buf[]
│ 遇 '\n' → s_rx_buf 置 '\0',置位 s_line_ready
▼
主循环 bt_protocol_poll()
│ 关中断 memcpy 整行到 s_line(避免与中断竞争)
│ parse_line():手写 JSON 定位键值 → 填充 s_pending,置位 s_pending_valid
▼
handle_bt_cmds() → bt_protocol_get_cmd() 取出消费1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
要点:
- 单字节中断接收,主循环以
\n切分整行; - 缓冲区满(128B)时直接丢弃重来,容错不崩溃;
- JSON 解析完全手写(
json_find/parse_float/parse_uint/parse_string),不依赖sscanf/json库; __disable_irq()保护行缓冲拷贝,防止中断半途改写。
6.2 上行帧(同样手写组帧)
| 帧 | 触发 | 示例 |
|---|---|---|
data | 每个 interval_ms | {"type":"data","temp":25.63,"sensor":"DS18B20","ts":123}\n |
config | 收到 query | {"type":"config","thresholdMin":10.0,"thresholdMax":35.0,"interval":1000,"sensor":"DS18B20"}\n |
注:协议文档里提到的
alarm/ack帧当前未实现——上位机已自行根据阈值判定报警,故 MCU 端省略。
7. BSP 驱动层
7.1 NTC 测温 bsp_ntc.c ★(你当前打开的文件)
硬件:3.3V → 上拉电阻 R(10K) → 中点(PA1) → NTC(下) → GND。NTC 型号 B3950-10K(或编译宏 NTC_R100K 选 100K)。
采样与换算流程(bsp_ntc_read(),L112-L143):
1. ntc_adc_read_channel(ADC_CHANNEL_1, 4) → 4 次平均得 adc_ntc
2. ntc_adc_read_channel(ADC_CHANNEL_VREFINT, 4) → 4 次平均得 adc_vref
3. vdda = 1.20V × 4096 / adc_vref (用内部基准反推实际 VDDA)
4. v_ntc = adc_ntc × vdda / 4096
5. r_ntc = R × v_ntc / (vdda - v_ntc) (分压反解)
6. 查表 ntc_r_to_temp(r_ntc/1000 Ω→KΩ) 线性插值 → 温度 ℃1
2
3
4
5
6
2
3
4
5
6
关键设计点:
采样时间 239.5 周期:内部 VREFINT 通道源阻抗高,需要长采样(>17.1μs),NTC 通道共用同一配置也无妨;
VREFINT 校准:STM32F103 出厂未校准 VREFINT,代码用典型值 1.20V。但实际上——如源码注释所指出——对纯分压测量,
vdda在第 5 步会数学上约去:r_ntc = R × (adc_ntc/4096) / (1 - adc_ntc/4096) = R × adc_ntc / (4096 - adc_ntc)1即最终阻值是纯比例测量,与 VDDA 真实值无关。读 VREFINT 在此主要起"ADC 正常工作"的健全性检查(
adc_vref==0时返回失败)。R-T 查表 + 线性插值(
ntc_r_to_temp(),L78-L104):-40~100℃ 共 29 个点,5℃ 步进。表为温度升序、阻值降序;查表时定位包围区间[i, i+1]做线性插值;超出量程则钳位到端点;10K 与 100K 共用 B3950 曲线:仅 R25 不同,故 100K 表各阻值 = 10K 表 ×10,由编译宏切换(见 §10)。
7.2 DS18B20 bsp_ds18b20.c
通过 GPIO 软件 bit-bang 驱动 1-Wire,DQ=PA2。
- 引脚模式:
bsp_ds18b20_init()(L34-L39)运行时把 PA2 改配为开漏输出 50MHz(直接写GPIOA->CRL),覆盖 CubeMX 默认的推挽配置。开漏输出"1"= 释放总线(被上拉拉高),输出"0"= 拉低;读 IDR 即得真实总线电平——单引脚双向复用; - 时序基准:依赖
bsp_dwt.c的bsp_delay_us()(基于 DWT 周期计数器,72MHz → 1μs = 72 拍); - 关键时序(均在
__disable_irq/__enable_irq临界区内执行,防止中断打断破坏时序):- 复位:拉低 480μs → 释放 → 70μs 后采样(低=有器件)→ 再等 410μs;
- 写 1:拉低 6μs + 释放 64μs;写 0:拉低 60μs + 释放 10μs;
- 读:拉低 4μs + 释放 8μs + 采样 + 等 60μs;
- 温度读取:读 scratchpad 前 2 字节(低字节在前),12 位分辨率,步进 0.0625℃;
- 流水线:
bsp_ds18b20_start()只发 SkipROM + ConvertT 即返回,不等待 750ms 转换完成。
7.3 按键 hw_key.c ★(实际使用)
⚠️ BSP 目录下有两套按键驱动:
hw_key.c(app_main 实际调用)与bsp_key.c(未使用,见 §11)。本节讲hw_key.c。
节拍来源:TIM1 1ms 中断 → 每 10ms 置位 s_key_scan_flag → 主循环调用 Key_Scanf()(L16-L83)。SW1/SW2 另配了 EXTI 下降沿中断,按下即额外置位 s_key_press_flag,让主循环立即补一次扫描,不必等下一个 10ms 节拍(提升响应速度)。
识别逻辑(计数式状态机,单位均为"10ms 节拍数"):
| 参数 | 宏 | 值 | 实际时间 |
|---|---|---|---|
| 去抖 | KEY_DEBOUNCE_TIME | 2 | 20ms |
| 双击窗口 | KEY_DOUBLE_PRESS_TIME | 40 | 400ms |
| 长按 | KEY_LONG_PRESS_TIME | 100 | 1000ms |
- 按下:累加
press_time,达到去抖阈值记一次有效按下(press_count++);继续累加,超过长按阈值 → 产生KEY_EVENT_LONG(仅一次,靠long_flag屏蔽); - 释放:进入双击倒计时(
release_time从 40 递减);倒计时归零时按press_count判定:1 = 单击,2 = 双击; - 事件写入
key->event,主循环用Key_GetEvent()读、Key_ClearEvent()清。
优点是逻辑紧凑、不依赖
HAL_GetTick()(纯计数);代价是必须保证Key_Scanf()被稳定地每 10ms 调一次,否则计时失准——这也是 EXTI 唤醒扫描存在的原因。
7.4 LCD 显示 bsp_lcd.c + bsp_lcdinit.c
抽象层设计(bsp_lcdinit.h L23-L33):lcd_interface 结构体内嵌 lcd_parameter(引脚/SPI 句柄/方向/尺寸)+ 一组函数指针(lcd_write_data8/16、lcd_write_reg、lcd_address_set、lcd_init 等)。register_1_80_lcd() 把 1.8 寸 ST7735 的实现绑定到这些指针;上层 bsp_lcd.c 的绘图 API(fill/drawLine/showChar/showString/汉字)只通过函数指针操作,与具体面板解耦——同一套绘图代码可驱动 0.96 寸或 1.8 寸屏。
当前配置:1.8 寸(ENABLE_1_8_LCD),方向 1(竖屏 128×160),SPI1 仅发送。
注意点:
lcd_1_80_address_set()(L278-L302)对列地址加了LCD_1_80_OFFSET_X = 2的偏移——本面板可见区相对 GRAM 有 2 列偏移,不加会导致左侧 2 列被遮挡、右侧 2 列花屏;- 原作者在
address_set末尾放过HAL_Delay(1),已移除并注释说明(ST7735 经 SPI 同步传输无需延时,那个延时会显著拖慢每次绘制); lcd_fill的xend/yend是排他坐标(不含),内部转包含式xend-1;- 字符显示支持 12/16/24/32 四种字号;汉字按 GBK 编码查
tfont12/16/24/32字库,未命中则显示?。
7.5 蜂鸣器 bsp_beep.c
最薄的驱动:bsp_beep_on/off/set(),即 HAL_GPIO_WritePin(PA6)。闪烁节奏由应用层 handle_beep() 用系统时间分频实现。
7.6 DWT 微秒延时 bsp_dwt.c
使能 Cortex-M3 的 DWT 跟踪单元与 CYCCNT 周期计数器,bsp_delay_us() 用 (DWT_CYCCNT - start) < ticks 忙等。比 SysTick 计数更精准、且不占用中断。DS18B20 1-Wire 时序的基石。
8. 中断与时基
| 中断源 | 优先级 | 用途 |
|---|---|---|
USART1_IRQn | 5(最高) | 蓝牙逐字节接收 |
EXTI0_IRQn (SW2/PA0) | 10 | 按键按下唤醒扫描 |
EXTI9_5_IRQn (SW1/PB8) | 10 | 按键按下唤醒扫描 |
TIM1_UP_IRQn | 11(最低) | 1ms 基准,10ms 置位按键扫描标志 |
优先级数字越小越高。UART 最高是为了不丢字节;TIM1 最低是因为它只产生"软节拍",晚几毫秒无影响。HAL 默认 4 位抢占优先级分组。
TIM1 时基(tim.c L43-L49):预分频 71、周期 999 → 72MHz / 72 / 1000 = 1kHz = 1ms。回调 HAL_TIM_PeriodElapsedCallback()(L374-L386)用静态计数器 div10 每 10 次置位一次 s_key_scan_flag,把 1ms 转成 hw_key 需要的 10ms 节拍。
9. 关键数据流
温度采集 → 显示 + 上报:
DS18B20 ─read─> s_ds_temp ─┬─> REG_DS 显示
NTC ─read─> s_ntc_temp ┼─> REG_NTC 显示
└─> selected_temp() ─> bt_send_data() ─USART1─> 蓝牙 ─> 上位机1
2
3
2
3
越限报警:
s_ds_temp ┐
s_thr_hi/lo┤ > 比较 > 闪烁驱动 BEEP(PA6)
s_ds_valid ┘
(仅报警模式生效)1
2
3
4
2
3
4
蓝牙下行设置 → 状态:
上位机 JSON ─USART1─> s_rx_buf ─\n─> s_line ─parse─> s_pending ─get_cmd─> handle_bt_cmds
→ s_thr_lo/hi / s_interval_ms / s_sensor / 回送 config1
2
2
10. 构建配置 CMakeLists.txt
工具链:arm-none-eabi-gcc,C11,CMake ≥ 3.22,CubeMX 生成的
cmake/stm32cubemx子目录提供 HAL 与启动文件;用户源码在
target_sources中显式列出(App + BSP 各 .c 文件);NTC 规格切换(L83-L89):
bashcmake -DNTC_RATING=10K ... # 默认,10K NTC + 10K 分压 cmake -DNTC_RATING=100K ... # 100K NTC + 100K 分压,定义宏 NTC_R100K1
2该宏控制
bsp_ntc.c中NTC_SERIES_R_OHM与查表数据,从而一套代码适配两种硬件;输出 HEX 也会带_NTC10K/_NTC100K后缀区分(POST_BUILD 用 objcopy 生成)。
11. 设计亮点与注意事项
亮点
- 分层清晰:App / BSP / HAL 三层,应用层完全不碰寄存器;
- 显示分块刷新:用空间(缓存表)换时间,保证主循环响应性;
- DS18B20 流水线采样:读旧值 + 启动新转换,避免阻塞;
- NTC 纯比例测量:巧妙利用
vdda在分压反解中约去,对电源精度不敏感; - 手写 JSON / 浮点格式化:规避
printf("%f")与 JSON 库,省 Flash; - EXTI 唤醒 + 定时扫描双保险:按键既准又快;
- 编译期切换 NTC 规格:一套源码两套硬件。
需要注意 / 潜在改进
存在两套按键驱动:
bsp_key.c(基于HAL_GetTick()的时间戳状态机,设计为放 SysTick 中断)当前未被任何代码引用,app_main 实际用的是hw_key.c(计数式)。bsp_key.c属于死代码,建议删除或明确标注保留原因,以免误用。默认模式为 ALARM:
app_main.c(L428)s_mode = APP_MODE_ALARM。需求 §2 的措辞("任意按键长按进入报警模式")暗示监控模式为入口,当前实现以报警模式启动。功能上长按可双向切换、不影响使用,但与需求字面描述略有出入,确认是否需要改为默认 NORMAL。VREFINT 校准对阻值计算是冗余的:如 §7.1 所述,最终阻值为纯比例量,
vdda被约去。当前读取 VREFINT 主要用作"ADC 工作正常"的健全性检查。若想真正发挥 VREFINT 作用,可用它换算 NTC 分压点的绝对电压(但本项目只需温度,无此需求)。DS18B20 临界区:1-Wire bit-bang 期间
__disable_irq()会阻塞所有中断(含 UART 接收)。单次位操作 < 100μs,9 字节读取总临界时间可控,但 9600 波特下 UART 字节间隔约 1ms,理论上存在极小概率的接收抖动风险——当前实测稳定,若未来提高波特率需复核。stm32f1xx_it.c中的EXTI0_IRQHandler / EXTI9_5_IRQHandler / TIM1_UP_IRQHandler / USART1_IRQHandler需正确调用 HAL 处理函数并最终走到本项目的回调(HAL_GPIO_EXTI_Callback/HAL_TIM_PeriodElapsedCallback/HAL_UART_RxCpltCallback)。这些回调都写在app_main.c(L372-L395)与bt_protocol.c(L323-L347)中——阅读中断链路时注意跨文件追踪。
附:快速代码索引
| 想了解… | 看哪里 |
|---|---|
| 主循环在干什么 | app_task()(L431-L461) |
| 模式怎么切换 | process_key_events()(L263-L290) |
| 温度怎么算出来 | bsp_ntc_read()(L112-L143) / bsp_ds18b20_read()(L127-L148) |
| 按键怎么识别单击/双击/长按 | Key_Scanf()(L16-L83) |
| 蓝牙收到的 JSON 怎么解析 | parse_line()(L176-L218) |
| 屏幕为什么不闪烁/卡顿 | display_step()(L236-L250) |
| 报警什么时候响 | handle_beep()(L332-L347) |
| 1ms / 10ms 节拍怎么来的 | HAL_TIM_PeriodElapsedCallback()(L374-L386) + tim.c |