stm32/debug/adc-oled-debug.md

STM32F103 ADC+OLED 调试复盘:从黑屏到出数的两座大山

知识库 · STM32 📅 初版 2026-09-08

STM32F103 ADC+OLED 调试复盘:从黑屏到出数的两座大山

2026-09-08 实测验证:PA5 光敏电阻 ADC 采集 → OLED(SH1106)第一行居中显示 zhangshaohau + 实时 ADC 值,遮挡光敏电阻数值随之变化。一晚上连踩两个「程序没跑起来」的大坑,完整记录排查路径、寄存器级根因与一套可复用的调试方法论。

#一、元信息

字段值
芯片/平台STM32F103C8T6(HAL 库,ADC 用寄存器级操作,ST-Link 烧录)
接口类型ADC(PA5=ADC1_IN5,单次转换、软件触发、12 位右对齐)+ OLED(SH1106,软件 I2C:SCL=PB6,SDA=PB7)
验证状态✅ 已验证(2026-09-08)
验证环境洋桃1号开发板 / Keil MDK(ARMCC)/ ST-Link / 2026-09-08
驱动方式寄存器级 ADC(adc.c),软件 I2C + 帧缓冲 + 页寻址 OLED(oled.c),printf 重定向至 USART1 调试打印

#二、接线表

信号MCU 引脚模块引脚备注
光敏电阻 ADCPA5(ADC1_IN5)分压输出模拟输入,CNF=01/MODE=00
OLED SCLPB6OLED SCL软件 I2C 时钟线
OLED SDAPB7OLED SDA软件 I2C 数据线
OLED 电源3.3V / GNDOLED VCC / GND必须共地
调试串口PA9(TX)/ PA10(RX)USB-TTLUSART1,115200,8N1

#三、踩坑清单

  1. printf 半主机卡死(OLED 黑屏):main.c 缺少 struct __FILE { int handle; }; 和 FILE __stdout; 两个 ARMCC 声明时,printf 走半主机 _sys_open 永久等待 → main() 从未执行 → OLED 初始化从未运行 → 黑屏。
  2. ADC 软件触发必须 EXTTRIG=1 + EXTSEL=111:EXTTRIG=0 时写 SWSTART 无效,EOC 永不置位,读值恒为 0xFFFF(65535)。
  3. SWD 突然连不上目标:先重新插拔 ST-Link(USB 端)/ 冷启动目标板,再怀疑程序占用 PA13/PA14;期间可用 FlyMcu 串口烧录绕行。
  4. OLED 显示 5535:是 65535(0xFFFF)被显示函数截断,ADC 修复返回真实值后自动正常,无需处理。
  5. 诊断打印要限制次数:用 static uint8_t diag_cnt 只打印 3 次,避免一直刷屏没法复制。

#四、问题一:OLED 一直黑屏

#4.1 现象

OLED 全黑,串口无任何输出。反复断电重启、调初始化顺序、加上电延时均无效。

#4.2 排查过程:先硬件后软件

  1. 烧录官方示例 hex → OLED 显示正常、ADC 有值 → 硬件(OLED 模块、I2C 接线、供电)没问题,问题在软件;
  2. 软件方面优先怀疑「程序是否真的跑起来了 / 初始化是否成功」;
  3. 用调试器定位:OpenOCD 连接 ST-Link 后 halt 程序,读 PC 寄存器:
txt[stm32f1x.cpu] halted due to debug-request, current mode: Thread
pc: 0x080001e4   ← reset halt 后(复位向量,正常)
pc: 0x080005c8   ← resume 跑 1.5s 后 halt(异常!)

对照 Keil 生成的 Demo00.map 查找 0x080005c8:

txt_sys_open  0x080005b9   Thumb Code  20  sys_io.o(.text)

PC 停在 _sys_open(C 库函数),说明程序卡在 C 库底层调用。

#4.3 根因:printf 半主机(semihosting)卡死

  • main.c 里虽然写了 fputc(发往 USART1),但缺少 ARMCC 标准库重定向必需的两个声明:
txtstruct __FILE { int handle; };
FILE __stdout;
  • 缺少它们时,printf 不会调用 fputc,而是走默认半主机模式,进入 C 库的 _sys_open 等待调试器响应;
  • 脱机运行时没有调试器响应,_sys_open 永久等待 → main() 从未执行 → OLED 初始化从未运行 → 黑屏。

#4.4 解决

在 main.c 的 USER CODE PV 区补上声明,printf 走 fputc → USART1:

txt/* ARMCC 标准库 printf 重定向必需:使 printf 走 fputc 而非半主机(_sys_open) */
struct __FILE { int handle; };
FILE __stdout;

这也解释了「最初版能亮、加了打印的版本全黑」——最初版没有 printf,没触发半主机调用。

#五、问题二:ADC 值恒为 65535

#5.1 现象

OLED 显示数字 5535(65535 的截断显示),串口打印 ADC=65535(=0xFFFF,即超时返回值)。用手遮挡光敏电阻,数值不变。

#5.2 排查过程:读寄存器逐项对照

在 main 循环里用串口打印 ADC 状态寄存器(限制只打 3 次避免刷屏):

txtADC=65535 SR=0x0 CR2=0x1 SQR3=0x5 SMPR2=0x38000 CRL=0x44448888
寄存器值解读
SQR30x5规则序列通道 = 5(ADC1_IN5)✅
SMPR20x38000SMP5=111=239.5 采样周期 ✅
CRL(PA5)0x4CNF=01 模拟输入、MODE=00 输入 ✅
CR20x1ADON=1 已上电、CAL=0 校准完成 ✅
SR0x0EOC 从未置位,转换从未完成 ❌

配置全部正确,但转换不完成 → 怀疑触发方式。查官方示例 adc.c,关键在:

txtADC_ExternalTrigConv_None = 0x000E0000   // EXTSEL = 7(无外部触发源)
...
ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 实际置 EXTTRIG|SWSTART

#5.3 根因:STM32F1 的软件触发规则

  • F1 规定:EXTTRIG=0 时写 SWSTART 无效,转换永远不会启动;
  • 软件触发必须:EXTTRIG=1 + EXTSEL=111(无外部触发),然后写 SWSTART;
  • 我的代码只写了 SWSTART(EXTTRIG=0),所以 EOC 永不置位。

#5.4 解决

adc.c 两处修改:

txt/* 初始化:EXTSEL=111 无外部触发(软件触发必需,同官方 None) */
ADC1->CR2 |= ADC_CR2_EXTSEL;

/* 启动转换:EXTTRIG=1 + SWSTART(同官方 ADC_SoftwareStartConvCmd) */
ADC1->CR2 |= ADC_CR2_EXTTRIG | ADC_CR2_SWSTART;

#六、调试方法论总结

定位问题按这个顺序,能最快收敛:

  1. 先排除硬件:烧录官方/已知正常的 hex,若功能正常 → 硬件没问题,锁定软件;
  2. 软件问题优先怀疑「初始化/启动是否成功」:程序跑起来了吗?→ 串口打印「进入 main」、初始化前后打点;或用调试器 halt 看 PC 停在哪(本案例 PC 停在 _sys_open 一锤定音);
  3. 外设不工作读寄存器:把 SR / CR / 配置寄存器打出来,与参考手册、官方示例逐位对照;
  4. 一次只改一个变量:改一个点 → 编译烧录 → 验证 → 再改下一个;
  5. 保留诊断通道:UART 打印是最低成本的调试手段,但注意 printf 重定向配置要正确。

本次两个 bug 都属于「初始化/启动层面」(printf 半主机、ADC 软件触发),都是程序其实没执行到预期逻辑,而不是逻辑写错——这正说明「先确认跑起来没」的价值。

#七、最终验证

  • OLED 点亮,第一行居中显示 zhangshaohau;
  • ADC 值实时刷新,遮挡光敏电阻数值变小,光照增强数值变大;
  • 串口输出 ADC=xxxx,与 OLED 一致。

#八、参考库/链接

  • STM32F103 参考手册(RM0008):ADC 章节 EXTSEL / EXTTRIG / SWSTART 说明
  • 洋桃1号官方例程:D:\学习资料\【用户资料】洋桃1号开发板(2)\B5-教学视频示例程序\1-2-28-光敏电阻ADC读取程序\
  • OLED(SH1106)驱动档案:/kb/stm32/display/oled-sh1106
  • ARMCC printf 重定向 / 半主机(semihosting)说明:Keil 帮助文档 uVision Help → C Library

#九、更新日志

📝共 1 条记录
日期类型更新内容
2026-09-08新增首次建档(由博客文章迁移至知识库)