STM32 学习笔记:OLED 调试工具与 Keil 在线调试

STM32 学习笔记:OLED 调试工具与 Keil 在线调试

对应课程:[4-1] OLED 调试工具

课堂录音:2026-08-30 18_06 记录 OLED&Keil调试_原文.docx

老师 PPT:第 37~40 页

芯片/板卡:STM32F103C8T6 最小系统板

OLED:0.96 寸、128×64、四针 I²C 版本

开发方式:标准外设库、软件模拟 I²C、Keil + ST-Link 在线调试

整理日期:2026-08-30

本节边界:课堂目标是学会调用老师提供的 OLED 模块并认识 Keil 调试工具;OLED 控制器原理、完整驱动和 I²C 协议将在后续课程展开。

1. 老师的整条思路

单片机不像电脑那样天然拥有屏幕和控制台。如果程序中的变量、执行位置和寄存器状态完全不可见,出错后就很难定位。因此老师先介绍三种调试方式,再选择 OLED 作为之后课程常用的现场显示工具,最后演示 Keil 在线调试。
#mermaid-svg-DEPrLt1jYF6tiXH8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DEPrLt1jYF6tiXH8 .error-icon{fill:#552222;}#mermaid-svg-DEPrLt1jYF6tiXH8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DEPrLt1jYF6tiXH8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .marker.cross{stroke:#333333;}#mermaid-svg-DEPrLt1jYF6tiXH8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DEPrLt1jYF6tiXH8 p{margin:0;}#mermaid-svg-DEPrLt1jYF6tiXH8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .cluster-label text{fill:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .cluster-label span{color:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .cluster-label span p{background-color:transparent;}#mermaid-svg-DEPrLt1jYF6tiXH8 .label text,#mermaid-svg-DEPrLt1jYF6tiXH8 span{fill:#333;color:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .node rect,#mermaid-svg-DEPrLt1jYF6tiXH8 .node circle,#mermaid-svg-DEPrLt1jYF6tiXH8 .node ellipse,#mermaid-svg-DEPrLt1jYF6tiXH8 .node polygon,#mermaid-svg-DEPrLt1jYF6tiXH8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .rough-node .label text,#mermaid-svg-DEPrLt1jYF6tiXH8 .node .label text,#mermaid-svg-DEPrLt1jYF6tiXH8 .image-shape .label,#mermaid-svg-DEPrLt1jYF6tiXH8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-DEPrLt1jYF6tiXH8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .rough-node .label,#mermaid-svg-DEPrLt1jYF6tiXH8 .node .label,#mermaid-svg-DEPrLt1jYF6tiXH8 .image-shape .label,#mermaid-svg-DEPrLt1jYF6tiXH8 .icon-shape .label{text-align:center;}#mermaid-svg-DEPrLt1jYF6tiXH8 .node.clickable{cursor:pointer;}#mermaid-svg-DEPrLt1jYF6tiXH8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .arrowheadPath{fill:#333333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DEPrLt1jYF6tiXH8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DEPrLt1jYF6tiXH8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DEPrLt1jYF6tiXH8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DEPrLt1jYF6tiXH8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .cluster text{fill:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 .cluster span{color:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DEPrLt1jYF6tiXH8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DEPrLt1jYF6tiXH8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-DEPrLt1jYF6tiXH8 .icon-shape,#mermaid-svg-DEPrLt1jYF6tiXH8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DEPrLt1jYF6tiXH8 .icon-shape p,#mermaid-svg-DEPrLt1jYF6tiXH8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DEPrLt1jYF6tiXH8 .icon-shape .label rect,#mermaid-svg-DEPrLt1jYF6tiXH8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DEPrLt1jYF6tiXH8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DEPrLt1jYF6tiXH8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DEPrLt1jYF6tiXH8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 为什么需要调试输出
比较三种调试方式
认识 OLED 和接口
按接线图连接硬件
导入现成 OLED 驱动
调用显示函数
进入 Keil Debug
单步/断点/变量/寄存器

录音时间 课堂内容
00:07--01:25 本节目标、演示现象、为什么需要调试工具
01:25--03:18 串口、显示屏、Keil 三种调试方法
03:18--04:45 OLED 特点、规格和常见版本
04:45--06:46 四针/七针接口、坐标和驱动函数
06:46--09:58 接线、导入驱动、修改 SCL/SDA 引脚
09:58--12:16 字符、字符串和各种进制数字显示
12:16--17:16 Keil 在线调试:运行控制、断点、变量和寄存器

2. 三种常用调试方式

图 1 老师 PPT 第 37 页:串口调试、显示屏调试和 Keil 调试模式。

2.1 为什么"能观察"如此重要

调试的核心不是盯着代码猜,而是获取证据:

  • 程序执行到哪里了?
  • 某个 if 条件是否成立?
  • 变量当前是什么值?
  • GPIO、定时器等寄存器是否按预期配置?
  • 程序是没有运行,还是硬件没有产生现象?

三种方法提供的观察窗口不同:

方法 观察位置 优点 局限 适合场景
串口调试 电脑串口助手 信息量大,可记录数据、画曲线 需要串口硬件与上位机 大量日志、长期采样、曲线分析
OLED 调试 设备本机屏幕 脱离电脑、直接、可持续刷新 屏幕小,占用引脚和程序空间 现场显示少量关键变量
Keil 在线调试 Keil Debug 窗口 可单步、断点、看变量和寄存器 通常需要调试器;停机会改变实时行为 追踪执行路径、查配置错误

这三种方式不是互相替代,而是互补。常见组合是:

  • OLED 显示运行时最重要的 2~5 个状态;
  • 串口连续输出详细数据;
  • Keil 在异常位置断住,检查当时的变量和寄存器。

2.2 没有调试工具时怎样缩小问题

老师还提到两种最基础的方法:

  • 删减/二分定位:先注释部分程序,确认恢复正常后,再逐步加入代码,缩小故障范围;
  • 对照法:找一份已验证的工程,对比接线、配置、执行顺序和现象。

这两种方法的本质也是"控制变量",避免一次面对整个工程。


3. OLED 是什么

图 2 老师 PPT 第 38 页:0.96 寸 OLED 的特点、规格和不同显示颜色版本。

OLEDOrganic Light Emitting Diode,中文为有机发光二极管。OLED 像素能够主动发光,不需要传统液晶屏那样的背光层,因此具有功耗低、响应快、视角宽、轻薄等特点。

本课程模块的关键参数:

项目 参数
屏幕尺寸 0.96 寸
分辨率 128×64 像素
供电 模块标称约 3~5.5 V;本工程使用 3.3 V
通信版本 四针 I²C 或七针 SPI
本节使用 四针 I²C 版本
字符坐标 4 行 × 16 列,每字符 8×16 像素

白色、蓝色和黄蓝双色主要是发光颜色不同。常见黄蓝屏通常是上方固定黄色、下方固定蓝色,并不是任意像素可自由切换颜色。


4. 四针 I²C 与七针 SPI 接口

图 3 老师 PPT 第 39 页:四针 I²C OLED 与七针 SPI OLED 的接口。

4.1 四针 I²C 版本

引脚 含义 本工程连接
GND 电源地 GND
VCC 模块电源 3.3 V
SCL 串行时钟 PB8
SDA 串行数据 PB9

课堂驱动使用 GPIO 软件模拟 I²C,因此 SCL/SDA 可以换到其他普通 GPIO;换引脚时,必须同时修改引脚宏和初始化代码。

4.2 七针 SPI 版本

除了 GND/VCC,通常还具有:

  • D0:SPI 时钟;
  • D1:SPI 数据;
  • RES:复位;
  • DC:命令/数据选择;
  • CS:片选。

四针驱动不能直接复制给七针模块使用,必须选择源码包中对应的版本。

4.3 软件模拟通信意味着什么

本工程没有调用 STM32 的硬件 I²C 外设,而是由普通 GPIO 按顺序改变 SCL/SDA 电平,模拟通信时序:
#mermaid-svg-ITcJy6ZQqORhKSDW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ITcJy6ZQqORhKSDW .error-icon{fill:#552222;}#mermaid-svg-ITcJy6ZQqORhKSDW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ITcJy6ZQqORhKSDW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ITcJy6ZQqORhKSDW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ITcJy6ZQqORhKSDW .marker.cross{stroke:#333333;}#mermaid-svg-ITcJy6ZQqORhKSDW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ITcJy6ZQqORhKSDW p{margin:0;}#mermaid-svg-ITcJy6ZQqORhKSDW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW .cluster-label text{fill:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW .cluster-label span{color:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW .cluster-label span p{background-color:transparent;}#mermaid-svg-ITcJy6ZQqORhKSDW .label text,#mermaid-svg-ITcJy6ZQqORhKSDW span{fill:#333;color:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW .node rect,#mermaid-svg-ITcJy6ZQqORhKSDW .node circle,#mermaid-svg-ITcJy6ZQqORhKSDW .node ellipse,#mermaid-svg-ITcJy6ZQqORhKSDW .node polygon,#mermaid-svg-ITcJy6ZQqORhKSDW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ITcJy6ZQqORhKSDW .rough-node .label text,#mermaid-svg-ITcJy6ZQqORhKSDW .node .label text,#mermaid-svg-ITcJy6ZQqORhKSDW .image-shape .label,#mermaid-svg-ITcJy6ZQqORhKSDW .icon-shape .label{text-anchor:middle;}#mermaid-svg-ITcJy6ZQqORhKSDW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ITcJy6ZQqORhKSDW .rough-node .label,#mermaid-svg-ITcJy6ZQqORhKSDW .node .label,#mermaid-svg-ITcJy6ZQqORhKSDW .image-shape .label,#mermaid-svg-ITcJy6ZQqORhKSDW .icon-shape .label{text-align:center;}#mermaid-svg-ITcJy6ZQqORhKSDW .node.clickable{cursor:pointer;}#mermaid-svg-ITcJy6ZQqORhKSDW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ITcJy6ZQqORhKSDW .arrowheadPath{fill:#333333;}#mermaid-svg-ITcJy6ZQqORhKSDW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ITcJy6ZQqORhKSDW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ITcJy6ZQqORhKSDW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ITcJy6ZQqORhKSDW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ITcJy6ZQqORhKSDW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ITcJy6ZQqORhKSDW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ITcJy6ZQqORhKSDW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ITcJy6ZQqORhKSDW .cluster text{fill:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW .cluster span{color:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ITcJy6ZQqORhKSDW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ITcJy6ZQqORhKSDW rect.text{fill:none;stroke-width:0;}#mermaid-svg-ITcJy6ZQqORhKSDW .icon-shape,#mermaid-svg-ITcJy6ZQqORhKSDW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ITcJy6ZQqORhKSDW .icon-shape p,#mermaid-svg-ITcJy6ZQqORhKSDW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ITcJy6ZQqORhKSDW .icon-shape .label rect,#mermaid-svg-ITcJy6ZQqORhKSDW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ITcJy6ZQqORhKSDW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ITcJy6ZQqORhKSDW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ITcJy6ZQqORhKSDW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} OLED显示函数
写命令/写数据
软件I2C发送字节
GPIO改变PB8/PB9
OLED控制器接收
显存与像素变化

优点是引脚选择灵活、便于入门;代价是占用 CPU,且本课程简化驱动没有检查从机应答。


5. 按接线图连接 OLED

图 4 配套工程 1-1 接线图/4-1 OLED显示屏.jpg:VCC/GND 接电源,SCL 接 PB8,SDA 接 PB9。

接线顺序建议:

  1. 断电确认模块针脚从左到右的实际标识;
  2. GND → GND
  3. VCC → 3.3 V
  4. SCL → PB8
  5. SDA → PB9
  6. 检查共地和电源极性后再上电。

5.1 为什么不用 GPIO 给 OLED 供电

课堂提到该小屏功耗较低,临时实验中可以让 GPIO 输出高低电平充当电源,但也明确说明这不规范。

正式设计应使用电源引脚供电,原因包括:

  • GPIO 有单脚和端口总电流限制;
  • GPIO 上电、复位和调试期间可能处于高阻态;
  • 程序错误可能让"电源脚"电平反转;
  • 负载启动电流和电源稳定性难以保证。

因此,PB6/PB7 若恰好与面包板供电跳线处于相同孔列,只要不把它们配置为输出,就不会主动与电源争用;但更清晰的接线仍是避开这种容易误解的位置。

5.2 开漏输出为什么需要上拉

源码把 PB8/PB9 配置为 GPIO_Mode_Out_OD

c 复制代码
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

开漏输出只能主动拉低;写 1 时是释放总线,必须由上拉电阻形成高电平。常见 OLED 模块板上已经带有 I²C 上拉,但不同模块不一定相同。若 SCL/SDA 始终上不去,应查看模块原理图或实测是否存在上拉。


6. 导入并配置 OLED 驱动模块

老师提供两套驱动:

text 复制代码
1-4 OLED驱动函数模块/
├─ 4针脚I2C版本/
│  ├─ OLED.c
│  ├─ OLED.h
│  └─ OLED_Font.h
└─ 7针脚SPI版本/
   ├─ OLED.c
   ├─ OLED.h
   └─ OLED_Font.h

本实验选择四针版本,把三个文件复制到工程 Hardware 文件夹并加入 Keil 的 Hardware 分组。

三个文件的职责:

文件 内容 是否通常需要修改
OLED.c GPIO、通信时序、初始化和显示函数 换引脚时修改顶部配置及初始化
OLED.h 对外显示函数声明 通常不改
OLED_Font.h ASCII 字符点阵字库 通常不改

6.1 换引脚要改哪两处

第一处是写引脚的宏:

c 复制代码
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

第二处是 OLED_I2C_Init() 中的时钟和引脚初始化:

c 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);

若 SCL/SDA 换到不同端口,还要分别开启对应端口时钟,不能只改两个宏。

6.2 字库是什么

OLED 不认识字符 'A' 的形状。OLED_Font.h 保存每个可见 ASCII 字符的点阵数据;OLED_ShowChar() 根据字符编码查表,再把 8×16 点阵逐字节写入 OLED。
#mermaid-svg-A4Bk66Iww260LZYW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-A4Bk66Iww260LZYW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-A4Bk66Iww260LZYW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-A4Bk66Iww260LZYW .error-icon{fill:#552222;}#mermaid-svg-A4Bk66Iww260LZYW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-A4Bk66Iww260LZYW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-A4Bk66Iww260LZYW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-A4Bk66Iww260LZYW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-A4Bk66Iww260LZYW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-A4Bk66Iww260LZYW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-A4Bk66Iww260LZYW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-A4Bk66Iww260LZYW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-A4Bk66Iww260LZYW .marker.cross{stroke:#333333;}#mermaid-svg-A4Bk66Iww260LZYW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-A4Bk66Iww260LZYW p{margin:0;}#mermaid-svg-A4Bk66Iww260LZYW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-A4Bk66Iww260LZYW .cluster-label text{fill:#333;}#mermaid-svg-A4Bk66Iww260LZYW .cluster-label span{color:#333;}#mermaid-svg-A4Bk66Iww260LZYW .cluster-label span p{background-color:transparent;}#mermaid-svg-A4Bk66Iww260LZYW .label text,#mermaid-svg-A4Bk66Iww260LZYW span{fill:#333;color:#333;}#mermaid-svg-A4Bk66Iww260LZYW .node rect,#mermaid-svg-A4Bk66Iww260LZYW .node circle,#mermaid-svg-A4Bk66Iww260LZYW .node ellipse,#mermaid-svg-A4Bk66Iww260LZYW .node polygon,#mermaid-svg-A4Bk66Iww260LZYW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-A4Bk66Iww260LZYW .rough-node .label text,#mermaid-svg-A4Bk66Iww260LZYW .node .label text,#mermaid-svg-A4Bk66Iww260LZYW .image-shape .label,#mermaid-svg-A4Bk66Iww260LZYW .icon-shape .label{text-anchor:middle;}#mermaid-svg-A4Bk66Iww260LZYW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-A4Bk66Iww260LZYW .rough-node .label,#mermaid-svg-A4Bk66Iww260LZYW .node .label,#mermaid-svg-A4Bk66Iww260LZYW .image-shape .label,#mermaid-svg-A4Bk66Iww260LZYW .icon-shape .label{text-align:center;}#mermaid-svg-A4Bk66Iww260LZYW .node.clickable{cursor:pointer;}#mermaid-svg-A4Bk66Iww260LZYW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-A4Bk66Iww260LZYW .arrowheadPath{fill:#333333;}#mermaid-svg-A4Bk66Iww260LZYW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-A4Bk66Iww260LZYW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-A4Bk66Iww260LZYW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A4Bk66Iww260LZYW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-A4Bk66Iww260LZYW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A4Bk66Iww260LZYW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-A4Bk66Iww260LZYW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-A4Bk66Iww260LZYW .cluster text{fill:#333;}#mermaid-svg-A4Bk66Iww260LZYW .cluster span{color:#333;}#mermaid-svg-A4Bk66Iww260LZYW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-A4Bk66Iww260LZYW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-A4Bk66Iww260LZYW rect.text{fill:none;stroke-width:0;}#mermaid-svg-A4Bk66Iww260LZYW .icon-shape,#mermaid-svg-A4Bk66Iww260LZYW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A4Bk66Iww260LZYW .icon-shape p,#mermaid-svg-A4Bk66Iww260LZYW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-A4Bk66Iww260LZYW .icon-shape .label rect,#mermaid-svg-A4Bk66Iww260LZYW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A4Bk66Iww260LZYW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-A4Bk66Iww260LZYW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-A4Bk66Iww260LZYW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 字符 'A'
减去空格得到字库索引
取出16字节点阵
上半8字节写一页
下半8字节写下一页
屏幕形成8×16字符

本节只需要会调用,不要求重新编写字库和底层通信。


7. OLED 坐标和显示函数

图 5 老师 PPT 第 40 页:4×16 字符坐标及 OLED 驱动函数。

7.1 字符坐标

老师把 128×64 像素屏按照 8×16 字符划分为:

  • 行号 1~4,从上到下;
  • 列号 1~16,从左到右。

这是驱动函数提供的字符坐标,不是 OLED 原生像素坐标。底层换算关系为:

c 复制代码
Y = (Line - 1) * 2;   // 一个16像素高字符占OLED的两页
X = (Column - 1) * 8; // 一个字符宽8像素

7.2 函数速查

函数 作用 关键参数
OLED_Init() 初始化并清屏 使用任何显示函数前调用一次
OLED_Clear() 清空全屏
OLED_ShowChar() 显示一个字符 行、列、字符
OLED_ShowString() 显示字符串 起始行、列、字符串
OLED_ShowNum() 显示无符号十进制数 数值、数字长度
OLED_ShowSignedNum() 显示带正负号十进制数 数值、绝对值数字长度
OLED_ShowHexNum() 显示十六进制数 数值、十六进制位数
OLED_ShowBinNum() 显示二进制数 数值、二进制位数

7.3 课堂完整示例

c 复制代码
#include "stm32f10x.h"
#include "OLED.h"

int main(void)
{
    OLED_Init();

    OLED_ShowChar(1, 1, 'A');
    OLED_ShowString(1, 3, "HelloWorld!");
    OLED_ShowNum(2, 1, 12345, 5);
    OLED_ShowSignedNum(2, 7, -66, 2);
    OLED_ShowHexNum(3, 1, 0xAA55, 4);
    OLED_ShowBinNum(4, 1, 0xAA55, 16);

    while (1)
    {
    }
}

结果对应:

text 复制代码
第1行:A HelloWorld!
第2行:12345 -66
第3行:AA55
第4行:1010101001010101

源码勘误:OLED_ShowHexNum(3, 1, 0xAA55, 4) 的实参是 0xAA55,但其行尾注释写成了 0xA5A5;实际显示应以实参为准,即 AA55

7.4 Length 到底表示什么

Length 决定显示多少位,不是自动测量数值长度:

  • OLED_ShowNum(2, 1, 12345, 5)12345
  • OLED_ShowNum(2, 1, 12345, 3)345,高位被截掉;
  • OLED_ShowNum(2, 1, 66, 4)0066
  • OLED_ShowSignedNum(..., -66, 2) 中的 2 只表示绝对值两位,符号另占一列。

显示内容必须满足 Column + 占用列数 - 1 <= 16,否则会超出当前行。

7.5 为什么二进制实参写成十六进制

课程所用的旧版 C 编译环境不支持 0b1010... 二进制整数字面量,所以传给 OLED_ShowBinNum() 的数仍写成十六进制 0xAA55,函数负责把它按二进制逐位显示。

准确性补充:较新的 C23 标准已经加入二进制整数字面量;但本课程的 Keil/ARM C 工程应以当前编译器实际支持为准,使用十六进制最稳妥。

7.6 更新变量时为什么会"覆盖刷新"

固定在同一坐标重复显示相同宽度的数据,新点阵会覆盖旧点阵,适合观察变量:

c 复制代码
OLED_ShowNum(1, 1, Count, 5);

如果新内容比旧内容短,旧字符可能残留。解决方法是固定显示宽度、先显示空格覆盖局部区域,或调用 OLED_Clear() 清整屏。频繁全屏清除可能产生闪烁,调试时通常优先局部覆盖。


8. Keil 在线调试的准备工作

课堂选择 ST-Link 硬件在线调试:

  1. STM32 正确供电;
  2. ST-Link 的 SWDIO/SWCLK/GND/3.3V 连接正确;
  3. 工程 Options for Target → Debug 选择对应 ST-Link 调试器;
  4. 先编译,确保没有错误;
  5. 点击带 d 的放大镜图标进入 Debug;
  6. 调试结束后退出 Debug,才能正常修改并重新编译代码。

Keil 也可能提供软件模拟器,但它无法可靠还原所有具体 STM32 外设和外部 OLED 行为。本节 GPIO/OLED 程序应优先使用真实芯片在线调试。


9. 控制程序怎样运行

进入 Debug 后,黄色箭头指向下一条将要执行的语句

操作 含义 什么时候用
Reset 复位目标 从启动流程重新观察
Run 全速运行 跑到断点或持续运行
Stop 暂停 没有断点或程序一直运行时手动停止
Step Into 单步进入 想看函数内部怎样执行
Step Over 单步越过 把当前函数调用视作一步
Step Out 跳出当前函数 已经进入函数但不想继续逐行看
Run to Cursor 运行到光标 快速到达指定源码行

9.1 复位后为什么没有直接到 main()

复位后 CPU 首先从启动文件中的复位处理函数开始:
#mermaid-svg-W1EHs4NkvHgjRglb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W1EHs4NkvHgjRglb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W1EHs4NkvHgjRglb .error-icon{fill:#552222;}#mermaid-svg-W1EHs4NkvHgjRglb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W1EHs4NkvHgjRglb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W1EHs4NkvHgjRglb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W1EHs4NkvHgjRglb .marker.cross{stroke:#333333;}#mermaid-svg-W1EHs4NkvHgjRglb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W1EHs4NkvHgjRglb p{margin:0;}#mermaid-svg-W1EHs4NkvHgjRglb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W1EHs4NkvHgjRglb .cluster-label text{fill:#333;}#mermaid-svg-W1EHs4NkvHgjRglb .cluster-label span{color:#333;}#mermaid-svg-W1EHs4NkvHgjRglb .cluster-label span p{background-color:transparent;}#mermaid-svg-W1EHs4NkvHgjRglb .label text,#mermaid-svg-W1EHs4NkvHgjRglb span{fill:#333;color:#333;}#mermaid-svg-W1EHs4NkvHgjRglb .node rect,#mermaid-svg-W1EHs4NkvHgjRglb .node circle,#mermaid-svg-W1EHs4NkvHgjRglb .node ellipse,#mermaid-svg-W1EHs4NkvHgjRglb .node polygon,#mermaid-svg-W1EHs4NkvHgjRglb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W1EHs4NkvHgjRglb .rough-node .label text,#mermaid-svg-W1EHs4NkvHgjRglb .node .label text,#mermaid-svg-W1EHs4NkvHgjRglb .image-shape .label,#mermaid-svg-W1EHs4NkvHgjRglb .icon-shape .label{text-anchor:middle;}#mermaid-svg-W1EHs4NkvHgjRglb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W1EHs4NkvHgjRglb .rough-node .label,#mermaid-svg-W1EHs4NkvHgjRglb .node .label,#mermaid-svg-W1EHs4NkvHgjRglb .image-shape .label,#mermaid-svg-W1EHs4NkvHgjRglb .icon-shape .label{text-align:center;}#mermaid-svg-W1EHs4NkvHgjRglb .node.clickable{cursor:pointer;}#mermaid-svg-W1EHs4NkvHgjRglb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W1EHs4NkvHgjRglb .arrowheadPath{fill:#333333;}#mermaid-svg-W1EHs4NkvHgjRglb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W1EHs4NkvHgjRglb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W1EHs4NkvHgjRglb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W1EHs4NkvHgjRglb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W1EHs4NkvHgjRglb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W1EHs4NkvHgjRglb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W1EHs4NkvHgjRglb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W1EHs4NkvHgjRglb .cluster text{fill:#333;}#mermaid-svg-W1EHs4NkvHgjRglb .cluster span{color:#333;}#mermaid-svg-W1EHs4NkvHgjRglb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W1EHs4NkvHgjRglb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W1EHs4NkvHgjRglb rect.text{fill:none;stroke-width:0;}#mermaid-svg-W1EHs4NkvHgjRglb .icon-shape,#mermaid-svg-W1EHs4NkvHgjRglb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W1EHs4NkvHgjRglb .icon-shape p,#mermaid-svg-W1EHs4NkvHgjRglb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W1EHs4NkvHgjRglb .icon-shape .label rect,#mermaid-svg-W1EHs4NkvHgjRglb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W1EHs4NkvHgjRglb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W1EHs4NkvHgjRglb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W1EHs4NkvHgjRglb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 复位向量
Reset_Handler
SystemInit
C运行库初始化
main
用户初始化
while主循环

因此在底层单步时会先看到启动文件和 SystemInit(),再进入 main()。如果只关心应用程序,可以使用"运行到光标"快速到达 main() 内指定行。

9.2 断点是什么

断点相当于在某行设置"自动停车点":

  1. 在源码左侧边栏点击设置断点;
  2. 点击 Run;
  3. CPU 全速执行;
  4. 执行到断点时暂停;
  5. 此时查看变量、调用路径和寄存器。

断点应放在"想验证是否执行到"和"状态刚刚发生变化"的位置,例如 if 分支内部或 GPIO 写操作之后。


10. 查看变量与寄存器

10.1 变量与结构体

Keil 可以在 Locals、Watch、Symbols 等窗口观察变量。若要持续观察某个变量,可把它加入 Watch。

结构体成员可以展开,例如:

text 复制代码
GPIO_InitStructure
├─ GPIO_Pin
├─ GPIO_Speed
└─ GPIO_Mode

单步执行三条赋值语句时,可以看到成员值逐项变化,这正好验证上一节讲过的"结构体用于打包配置参数"。

如果变量显示为 <not in scope>、无法观察或值看起来跳跃,先确认程序是否停在其作用域内,并考虑编译优化可能把变量放进寄存器、合并或删除。调试学习工程可使用较低优化等级。

10.2 外设寄存器

通过 Peripherals 菜单可以查看 GPIO 等外设寄存器。例如观察 PA0:

  • GPIOA_ODR 的 bit0 表示输出锁存值;
  • LED 闪烁程序改变 PA0 时,ODR0 会在 0/1 之间变化;
  • Keil 的寄存器窗口与真实 LED 现象共同验证程序是否正确输出。

#mermaid-svg-dnTP8dtvsa7MHgwH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dnTP8dtvsa7MHgwH .error-icon{fill:#552222;}#mermaid-svg-dnTP8dtvsa7MHgwH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dnTP8dtvsa7MHgwH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dnTP8dtvsa7MHgwH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dnTP8dtvsa7MHgwH .marker.cross{stroke:#333333;}#mermaid-svg-dnTP8dtvsa7MHgwH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dnTP8dtvsa7MHgwH p{margin:0;}#mermaid-svg-dnTP8dtvsa7MHgwH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH .cluster-label text{fill:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH .cluster-label span{color:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH .cluster-label span p{background-color:transparent;}#mermaid-svg-dnTP8dtvsa7MHgwH .label text,#mermaid-svg-dnTP8dtvsa7MHgwH span{fill:#333;color:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH .node rect,#mermaid-svg-dnTP8dtvsa7MHgwH .node circle,#mermaid-svg-dnTP8dtvsa7MHgwH .node ellipse,#mermaid-svg-dnTP8dtvsa7MHgwH .node polygon,#mermaid-svg-dnTP8dtvsa7MHgwH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dnTP8dtvsa7MHgwH .rough-node .label text,#mermaid-svg-dnTP8dtvsa7MHgwH .node .label text,#mermaid-svg-dnTP8dtvsa7MHgwH .image-shape .label,#mermaid-svg-dnTP8dtvsa7MHgwH .icon-shape .label{text-anchor:middle;}#mermaid-svg-dnTP8dtvsa7MHgwH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dnTP8dtvsa7MHgwH .rough-node .label,#mermaid-svg-dnTP8dtvsa7MHgwH .node .label,#mermaid-svg-dnTP8dtvsa7MHgwH .image-shape .label,#mermaid-svg-dnTP8dtvsa7MHgwH .icon-shape .label{text-align:center;}#mermaid-svg-dnTP8dtvsa7MHgwH .node.clickable{cursor:pointer;}#mermaid-svg-dnTP8dtvsa7MHgwH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dnTP8dtvsa7MHgwH .arrowheadPath{fill:#333333;}#mermaid-svg-dnTP8dtvsa7MHgwH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dnTP8dtvsa7MHgwH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dnTP8dtvsa7MHgwH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dnTP8dtvsa7MHgwH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dnTP8dtvsa7MHgwH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dnTP8dtvsa7MHgwH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dnTP8dtvsa7MHgwH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dnTP8dtvsa7MHgwH .cluster text{fill:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH .cluster span{color:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dnTP8dtvsa7MHgwH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dnTP8dtvsa7MHgwH rect.text{fill:none;stroke-width:0;}#mermaid-svg-dnTP8dtvsa7MHgwH .icon-shape,#mermaid-svg-dnTP8dtvsa7MHgwH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dnTP8dtvsa7MHgwH .icon-shape p,#mermaid-svg-dnTP8dtvsa7MHgwH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dnTP8dtvsa7MHgwH .icon-shape .label rect,#mermaid-svg-dnTP8dtvsa7MHgwH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dnTP8dtvsa7MHgwH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dnTP8dtvsa7MHgwH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dnTP8dtvsa7MHgwH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 程序执行GPIO写操作
GPIOA ODR位改变
输出驱动器改变PA0电平
LED亮灭
Keil寄存器窗口显示变化

在线更新存在一定延迟;高频翻转时,窗口不一定显示每一次瞬时变化。若点击 Stop,CPU 暂停,外设行为也可能随之停止;重新 Run 后继续执行。


11. 一套实用的调试顺序

遇到"程序没有现象"时,可以按下面顺序排查:
#mermaid-svg-DJlQtia76NmffIxz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DJlQtia76NmffIxz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DJlQtia76NmffIxz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DJlQtia76NmffIxz .error-icon{fill:#552222;}#mermaid-svg-DJlQtia76NmffIxz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DJlQtia76NmffIxz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DJlQtia76NmffIxz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DJlQtia76NmffIxz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DJlQtia76NmffIxz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DJlQtia76NmffIxz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DJlQtia76NmffIxz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DJlQtia76NmffIxz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DJlQtia76NmffIxz .marker.cross{stroke:#333333;}#mermaid-svg-DJlQtia76NmffIxz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DJlQtia76NmffIxz p{margin:0;}#mermaid-svg-DJlQtia76NmffIxz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DJlQtia76NmffIxz .cluster-label text{fill:#333;}#mermaid-svg-DJlQtia76NmffIxz .cluster-label span{color:#333;}#mermaid-svg-DJlQtia76NmffIxz .cluster-label span p{background-color:transparent;}#mermaid-svg-DJlQtia76NmffIxz .label text,#mermaid-svg-DJlQtia76NmffIxz span{fill:#333;color:#333;}#mermaid-svg-DJlQtia76NmffIxz .node rect,#mermaid-svg-DJlQtia76NmffIxz .node circle,#mermaid-svg-DJlQtia76NmffIxz .node ellipse,#mermaid-svg-DJlQtia76NmffIxz .node polygon,#mermaid-svg-DJlQtia76NmffIxz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DJlQtia76NmffIxz .rough-node .label text,#mermaid-svg-DJlQtia76NmffIxz .node .label text,#mermaid-svg-DJlQtia76NmffIxz .image-shape .label,#mermaid-svg-DJlQtia76NmffIxz .icon-shape .label{text-anchor:middle;}#mermaid-svg-DJlQtia76NmffIxz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DJlQtia76NmffIxz .rough-node .label,#mermaid-svg-DJlQtia76NmffIxz .node .label,#mermaid-svg-DJlQtia76NmffIxz .image-shape .label,#mermaid-svg-DJlQtia76NmffIxz .icon-shape .label{text-align:center;}#mermaid-svg-DJlQtia76NmffIxz .node.clickable{cursor:pointer;}#mermaid-svg-DJlQtia76NmffIxz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DJlQtia76NmffIxz .arrowheadPath{fill:#333333;}#mermaid-svg-DJlQtia76NmffIxz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DJlQtia76NmffIxz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DJlQtia76NmffIxz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DJlQtia76NmffIxz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DJlQtia76NmffIxz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DJlQtia76NmffIxz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DJlQtia76NmffIxz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DJlQtia76NmffIxz .cluster text{fill:#333;}#mermaid-svg-DJlQtia76NmffIxz .cluster span{color:#333;}#mermaid-svg-DJlQtia76NmffIxz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DJlQtia76NmffIxz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DJlQtia76NmffIxz rect.text{fill:none;stroke-width:0;}#mermaid-svg-DJlQtia76NmffIxz .icon-shape,#mermaid-svg-DJlQtia76NmffIxz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DJlQtia76NmffIxz .icon-shape p,#mermaid-svg-DJlQtia76NmffIxz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DJlQtia76NmffIxz .icon-shape .label rect,#mermaid-svg-DJlQtia76NmffIxz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DJlQtia76NmffIxz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DJlQtia76NmffIxz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DJlQtia76NmffIxz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

确认供电、共地和接线
确认工程能编译下载
在main入口或初始化后设断点
能到达断点吗
检查启动、下载、复位和死循环
单步检查分支和函数调用
Watch查看关键变量
Peripherals核对RCC/GPIO寄存器
用OLED或串口观察连续运行数据
缩小到具体软件或硬件环节

这套顺序把问题分成三层:

  • 程序是否真的运行;
  • 软件状态是否符合预期;
  • 寄存器和外部硬件是否真正产生了对应结果。

12. 易错点与转写校正

  1. 转写中的 OLD/OLG/ORD 均指 OLED
  2. 转写中的 KO/Q/kill 多数指 Keil
  3. SCL/SDA 是四针 I²C 版本的时钟与数据线;七针版本使用 SPI 信号。
  4. 本工程默认 SCL=PB8SDA=PB9,换端口必须同步修改宏、时钟和初始化引脚。
  5. 软件 I²C 使用开漏输出,高电平依赖上拉电阻。
  6. OLED_Init() 必须先调用,随后才能可靠使用显示函数。
  7. 行列参数是 1 起始的字符坐标,不是 0 起始的像素坐标。
  8. Length 是强制显示位数;太短会截高位,太长会补前导零。
  9. 源码十六进制示例实参是 0xAA55,行尾注释误写为 0xA5A5
  10. OLED 通过 GPIO 临时供电不规范,正式项目应使用稳定电源。
  11. 黄色箭头表示下一条将执行的语句,而不是已经执行完的语句。
  12. Debug 模式中不能按正常流程直接修改并重新编译;应先退出调试。
  13. 寄存器实时窗口有刷新延迟,不适合观察每一次高速翻转。

13. 复习自测

  1. 串口、OLED、Keil 调试分别适合观察什么?
  2. 为什么单片机学习需要让变量和执行过程"可见"?
  3. 四针 OLED 的四根线分别是什么?
  4. 为什么软件 I²C 的 SCL/SDA 可以换到普通 GPIO?
  5. PB8/PB9 配置为开漏输出时,高电平由什么产生?
  6. OLED.cOLED.hOLED_Font.h 分别负责什么?
  7. OLED_ShowSignedNum(2, 7, -66, 2) 总共占用几列?
  8. OLED_ShowNum(..., 12345, 3) 为什么只显示 345
  9. 复位后为什么会先看到 Reset_HandlerSystemInit()
  10. Step Into、Step Over、Step Out 有什么区别?
  11. 断点、Watch 和 Peripherals 三种工具分别帮助确认什么?
  12. 为什么高速变化的寄存器不一定能在 Keil 窗口中逐次看到?

14. 资料定位

内容 资料位置
三种调试方式 老师 PPT 第 37 页;录音 01:25--03:18
OLED 特点与规格 老师 PPT 第 38 页;录音 03:18--04:45
四针/七针硬件接口 老师 PPT 第 39 页;录音 04:45--05:18
OLED 坐标和函数 老师 PPT 第 40 页;录音 05:18--06:4609:58--12:16
面包板接线 配套工程 1-1 接线图/4-1 OLED显示屏.jpg;录音 06:46--08:14
四针与七针驱动包 程序源码/STM32Project-有注释版/1-4 OLED驱动函数模块
本节可运行工程 程序源码/STM32Project-有注释版/4-1 OLED显示屏
软件 I²C、显示函数实现 4-1 OLED显示屏/Hardware/OLED.c
对外函数声明 4-1 OLED显示屏/Hardware/OLED.h
字库数据 4-1 OLED显示屏/Hardware/OLED_Font.h
Keil 在线调试操作 录音 12:16--17:16
GPIO 输出模式、ODR STM32F10xxx参考手册(中文).pdf 第 108~110、115 页

15. 一句话总结

OLED 让运行数据在设备端可见,Keil Debug 让执行过程、变量和寄存器在开发端可见;把两者结合起来,就能从"程序是否执行"一直追踪到"硬件是否产生预期现象"。

相关推荐
EthanChou202019 分钟前
AES67协议笔记
前端·笔记·es6
prog_610319 分钟前
【笔记】cllama:搜罗万象当LLM api server测qwen3.8:flash-next
笔记·大语言模型·agent·vibe-coding·qwen3.8-flash
TinyMemory21 分钟前
Java 面向对象核心入门(六):this 关键字完全解析
java·笔记·面向对象·java新手·this关键字
淬炼之火26 分钟前
笔记:Visually-Guided Policy Optimization for Multimodal Reasoning
人工智能·笔记·算法·机器学习·语言模型·自然语言处理
STLearner32 分钟前
KDD 2026 | (2月轮)时空数据(Spatial-Temporal)论文总结时空(交通)预测,轨迹数据挖掘(表示,生成)
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
一条破秋裤42 分钟前
STM32 学习笔记:中断系统与 EXTI 外部中断
笔记·stm32·学习
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 21
java·spring boot·笔记
jianqiang.xue1 小时前
低功耗产品架构专项优化:电池供电设备休眠、唤醒、漏电流、待机功耗极致优化
stm32·单片机·mcu·物联网·架构·esp32
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】WebSocket & SSE 协议
网络·c++·websocket·网络协议·学习·面试