STM32学习笔记(四)OLED显示屏与Keil调试

STM32 学习笔记(四):OLED 显示屏与 Keil 调试

本文以 STM32F103C8T6、0.96 寸 OLED 模块、STM32F10x 标准外设库和 Keil μVision 为例,学习显示屏的基本使用方法,并介绍 Keil 调试器的常用功能。

一、程序调试

单片机程序调试,常用的方法主要有三种:串口调试、显示屏调试和 Keil 调试。它们就像三种不同的"观察窗口":串口和显示屏适合观察程序运行时主动输出的信息,Keil 调试则可以暂停程序,查看它当前执行到了哪一步。

串口调试

串口调试是最常见的调试方法之一。单片机通过串口通信把变量值、运行状态或错误提示发送给电脑,电脑再使用串口助手显示这些信息。

在设计 PCB 时,常常会在板边预留串口引脚或调试接口。需要调试程序时,把单片机的串口与电脑连接起来,就可以在串口助手中观察程序运行状态。例如,可以输出当前传感器数值、按键状态或某个函数是否被执行。

串口调试的优点是运行过程中不会频繁暂停程序,适合观察连续变化的数据;缺点是需要额外的串口硬件和通信代码,而且输出过多的信息可能影响程序的实时性。

显示屏调试

显示屏调试是把单片机内部的调试信息直接显示在屏幕上,例如显示变量值、状态编号或传感器结果。这种方法不需要电脑一直连接在旁边,适合在脱离电脑的情况下观察设备运行状态。

不过,显示屏本身也需要初始化和驱动。如果显示屏驱动还没有调通,就不能把它作为唯一的调试手段,否则容易出现"为了调试显示屏,先要调试显示屏"的循环问题。

Keil 调试

Keil 调试需要借助 ST-Link 等硬件调试器。进入调试模式后,可以单步运行程序、设置断点、查看变量、寄存器、内存以及反汇编代码,还可以观察程序运行到哪一行后出现异常。

可以把它想象成给程序装上了"暂停键"和"慢放键":程序正常运行时速度很快,调试器可以让它停在某一行,再逐条观察每一步执行后的结果。


二、OLED 简介与驱动函数

OLED 简介

OLED 是 Organic Light Emitting Diode 的缩写,中文称为有机发光二极管显示屏。OLED 的每个像素都可以自行发光,不需要像普通液晶屏那样依靠背光,因此具有功耗较低、响应速度快、可视角度大、厚度较小等特点,常用于嵌入式设备显示数据。

0.96 寸 OLED 模块

课件中介绍的 0.96 寸 OLED 模块具有体积小、接口较少、使用方便等特点。常见参数如下:

  • 供电电压:常见模块标注为 3~5.5V,但实际仍要以模块说明书为准;
  • 通信协议:常见为 I2CSPI
  • 分辨率:128 × 64,表示横向有 128 个像素点,纵向有 64 个像素点;
  • 主控芯片:常见为 SSD1306 或兼容控制器,具体型号要以模块资料为准。

分辨率可以理解为屏幕上的"方格数量"。128 × 64 就是横向排列 128 列、纵向排列 64 行,总共 8192 个像素点。驱动程序通过通信协议把要显示的图形或字符写入 OLED 内部显存,屏幕再根据显存内容点亮对应像素。

OLED 硬件连接

4 针 OLED

4 针模块通常使用 I²C 通信,但仍要以模块丝印和驱动程序为准:

引脚 作用
GND 接地
VCC 接电源,课件中接 3V3
SCL I²C 时钟线
SDA I²C 数据线

I²C 的 SCL 是时钟线,SDA 是数据线。I²C 通常采用开漏输出,并需要上拉电阻;许多 OLED 模块已经在板上集成了上拉电阻,但不能默认所有模块都有,使用前应查看电路或模块说明。

7 针 OLED

7 针模块通常使用 SPI 通信:

引脚 作用
GND 接地
VCC 接电源,课件中接 3V3
D0 SPI 时钟线
D1 SPI 数据线
RES 复位信号
DC 数据/命令选择
CS 片选信号

SPI 模块比 I²C 模块多出复位、数据/命令选择和片选等控制信号。实际接线时必须确认模块型号和驱动程序,不能只根据引脚数量猜测通信协议,也不能把 SPI 模块的 D0/D1 与 I²C 模块的 SCL/SDA 混用。

OLED 驱动函数

不同 OLED 驱动库的函数名和参数含义可能略有差异。下面这些函数来自本次使用的驱动模块:

c 复制代码
OLED_Init();                         // 初始化 OLED
OLED_Clear();                        // 清屏
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);   // 显示二进制数字

通常,前两个参数表示显示位置,例如行号和列号;最后一个参数通常表示显示的位数。但这些参数的具体含义取决于所使用的 OLED 驱动库,使用前应查看 OLED.h 或函数定义。

常见调用步骤是:

c 复制代码
OLED_Init();
OLED_Clear();
OLED_ShowString(1, 1, "STM32");

一般先初始化 OLED,再根据需要清屏,最后调用显示函数输出字符、字符串或数字。部分驱动在初始化过程中已经完成清屏,因此 OLED_Clear() 是否必须调用,要以驱动程序实现为准。

本节先掌握 OLED 相关函数的基本用法,暂时不深入研究显示缓存、字库和通信时序。等到能够稳定显示内容后,再回头理解驱动函数内部是如何把字符转换成像素数据的。


三、实战演练

准备工作

把之前的工程复制一份,修改工程名称,然后删除上一节实验中不需要的函数和语句,使工程回到可以重新添加 OLED 驱动的状态。

打开程序源码,找到 OLED 驱动模块,把适合当前硬件的 4 针 OLED 版本文件复制到工程的 Hardware 文件夹中,并在 Keil 工程中添加对应的 .c 文件和 .h 文件,同时配置头文件路径。

打开 OLED.c。驱动文件中的大部分内容通常不需要修改,主要检查引脚宏定义和 GPIO 初始化部分。

修改引脚配置

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 的 SCL 接到了 PB8,SDA 接到了 PB9,那么宏定义中的端口和引脚就应当写成 PB8、PB9。如果改了硬件接线,宏定义也必须同步修改,否则程序会把数据发送到错误的引脚。

这份驱动使用的是软件模拟 I²C:程序通过 GPIO_WriteBit 手动改变 SCL 和 SDA 的电平,而不是直接调用 STM32 硬件 I²C 外设。PB8、PB9 只是本例选择的普通 GPIO,并不意味着所有 OLED 都必须接在这两个引脚上。

修改引脚初始化

c 复制代码
/*引脚初始化*/
void OLED_I2C_Init(void)
{
    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);
	
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

这里要把 SCL 和 SDA 两个引脚都初始化好,并且配置为开漏输出。开漏输出输出低电平时可以主动拉低总线,输出高电平时则释放总线,由上拉电阻把总线拉高,这正是 I²C 总线常用的电气方式。

初始化结束时把 SCL 和 SDA 写为 1,是为了让总线处于释放状态,避免程序刚开始运行时把总线强行拉低。若模块没有上拉电阻,还需要根据 I²C 速度和总线电容补充合适的外部上拉电阻。

剩余驱动代码通常不需要修改,可以直接调用。

实战演练

先初始化 OLED,再显示字符和字符串:

c 复制代码
/* Main */
int main()
{	 
	OLED_Init();
	
	OLED_ShowChar(1,1,'A');
	OLED_ShowString(1,3,"Real World?");
	
	while(1)
	{
		
	}
}

在这份驱动中,前两个参数表示显示位置,后面的参数表示显示内容。编译并下载后,如果屏幕坐标和驱动库采用常见的行列定义,第一行应显示 A Real World?。如果显示位置或字符间距不同,应以当前 OLED 驱动库的字库和坐标规则为准。

显示无符号和有符号十进制数字:

c 复制代码
OLED_ShowNum(2,1,114514,6);//无符号
OLED_ShowSignedNum(2,8,-6,1);//有符号

这两个函数用于显示十进制整数。前两个参数表示行和列,第三个参数是要显示的数字,第四个参数是显示长度。如果长度参数比数字实际位数长,通常会在前面补 0;如果长度参数比实际位数短,高位数字可能被截掉。

对于有符号数字,长度参数通常只计算数字部分,不包含负号,但具体规则仍要以驱动函数定义为准。显示内容较长时,还要注意列数不能超过屏幕范围。

显示十六进制数和二进制数:

c 复制代码
OLED_ShowHexNum(3,1,0xAA55,4);//显示十六进制
OLED_ShowBinNum(4,1,0xAA55,16);//显示二进制

这两个函数分别用于显示十六进制和二进制数字。传统 C 语言标准通常不支持直接写二进制字面量,因此可以先写十六进制数,再由函数内部逐位转换为二进制显示。这里的长度参数表示转换后要显示的二进制位数。

清除屏幕内容:

c 复制代码
OLED_Clear();

这个函数会清除屏幕上的全部内容。如果只想清除部分位置,可以在对应位置显示空格,但要注意空格的宽度必须覆盖原字符占用的显示区域。


四、Keil 调试

进入调试模式

点击工具栏上的"魔术棒"按钮,打开 Options for Target,进入 Debug 页面。这里通常有两种选择:

  • Use ST-Link Debugger:使用真实的 STM32 芯片和 ST-Link 硬件进行调试;
  • Use Simulator:使用 Keil 软件模拟器运行部分程序。

本章使用硬件调试,因此选择 Use ST-Link Debugger。在调试前要先编译工程,确认代码能够正常编译和链接,然后点击右上角放大镜中带有 d 的图标,进入调试模式。

软件模拟器不需要连接开发板,但它不能完整模拟真实 OLED、按键和传感器等外部硬件。要观察真实屏幕显示和 GPIO 电平,仍然需要使用开发板和 ST-Link。

调试界面

进入调试界面后,主窗口通常显示 C 语言源程序,上方或其他窗口可以显示编译后的汇编代码。寄存器窗口能够显示 CPU 和外设寄存器的当前值,状态标志位则反映处理器当前的运行状态。

调试工具栏中的按钮用于控制程序运行,常见功能包括:

  • 复位程序;
  • 全速运行;
  • 停止运行;
  • 单步运行;
  • 单步跳过函数内部执行;
  • 单步跳出当前函数;
  • 运行到光标所在位置。

不同版本的 Keil 图标位置可能略有不同,但把鼠标悬停在按钮上通常会显示功能名称。

在源代码窗口中,黄色箭头表示下一条即将执行的语句。程序暂停后,可以单步执行,观察变量、寄存器和屏幕状态如何变化。单步执行就像把视频逐帧播放,适合分析程序为什么进入了某个分支。

设置断点

可以单击代码左侧的深灰色区域设置断点,再点击全速运行。程序运行到断点所在位置后会自动暂停,黄色箭头会指向下一条待执行语句。

断点适合观察某个函数是否被调用、某个条件是否成立以及变量在某一时刻的值。硬件调试器的断点数量通常有限,调试时应优先设置在最关键的位置,不要一次设置过多断点。

常用观察窗口

调试界面还可以打开以下窗口:

  • 命令窗口:输入调试命令;
  • 反汇编窗口:查看 C 代码对应的汇编指令;
  • 符号或变量窗口:实时查看变量的值;
  • 内存窗口:查看指定地址中的数据;
  • 串口显示窗口:观察串口相关信息;
  • 逻辑分析仪:观察部分信号或变量随时间的变化。

调试时可以把源代码、变量窗口、寄存器窗口和反汇编窗口结合起来使用。例如,OLED 没有显示时,可以先在源代码中确认 OLED_Init() 是否执行,再查看 GPIOB 的配置寄存器和 SCL/SDA 输出状态,最后检查 I²C 时序和接线。


五、附录

下面汇总本文使用的代码,正文中的代码保留在对应章节,附录方便集中查看和复制。

c 复制代码
//main.c
/* 头文件声明区域 */
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"

/* 变量声明区域 */

/* Main */
int main()
{	 
	OLED_Init();
	
	OLED_ShowChar(1,1,'A');
	OLED_ShowString(1,3,"Real World?");
	
	OLED_ShowNum(2,1,114514,6);
	OLED_ShowSignedNum(2,8,-6,1);
	
	OLED_ShowHexNum(3,1,0xAA55,4);
	OLED_ShowBinNum(4,1,0xAA55,16);
	
	
	while(1)
	{
		
	}
}
相关推荐
盖世汤猿1 小时前
AUTOSAR BSW 全栈开发学习计划(35岁危机)
学习
达柳斯·绍达华·宁1 小时前
重读CV系列——&&3、Faster-RCNN学习
学习
weixin_431600441 小时前
做 Agent 会用到的 Node API(4):取消与 AbortController
前端·学习·ai·agent·ai编程
2301_809051142 小时前
STM32 GPIO控制器及其应用 学习笔记-2
笔记·stm32·学习
破晓单片机2 小时前
093、STM32项目分享开源:蓝牙智能晾衣架系统
stm32·单片机·嵌入式硬件·开源
影视飓风TIM2 小时前
C++ STL 完整入门笔记[9]:C++ 仿函数、priority_queue与STL算法底层笔记
数据结构·c++·笔记
依然鸣3 小时前
蓝桥杯多校模拟赛(第二场)题解
数据结构·c++·经验分享·学习·算法·图论
依然鸣3 小时前
蓝桥杯多校模拟赛 题解
数据结构·c++·经验分享·学习·算法·蓝桥杯
LccKyI3 小时前
C# 零基础学习 Day01|基础概念梳理 + 思维导图总结
开发语言·笔记·学习·c#