STM32零基础驱动I2C OLED:Proteus仿真与SSD1306排错实战
2026/9/10 11:43:39 网站建设 项目流程

简介:这是零基础STM32入门自学教程系列的OLED显示篇,面向没有实物开发板、希望借助Proteus完成仿真的初学者。工程基于STM32F103C8T6,使用PB10、PB11两个IO通过I2C接口驱动0.96英寸四线制OLED,是省IO的典型方案。整套代码采用模块化设计,底层驱动可移植到51或其他嵌入式平台。压缩包共201个文件,主要为C源码、头文件及Proteus仿真工程文件(pdsprj),另有编译中间文件与Hex文件,包体仅4.99MB,便于快速下载。主程序保持极简风格,并对可省略语句做了注释说明,帮助读者抓住驱动OLED的核心语句。软件库支持升级,扩展性较好。已有4034人浏览学习,适合刚接触STM32和I2C通信的初学者动手实践。

1. 为什么零基础学 STM32,第一块屏幕要先选 I2C 的 0.96 寸 OLED

很多人的第一反应是用 0.96 寸 OLED 直接点亮,觉得“能亮就完事”。可当你在 Proteus 里把 SSD1306 驱动的 OLED 挂到 STM32 的 I2C1 上,事情就没那么简单:屏幕不亮、花屏、显示像素偏移,几乎都是时序和初始化配置的问题。Proteus 仿真最大的好处,是省掉接线的麻烦,坏处是任何一根“虚拟线”没接对、任何一个寄存器没写完,它都老老实实不给你显示。这恰恰适合零基础——因为错误是透明的,你能一步步查到问题出在哪。

I2C 接口的 OLED 只需要两根信号线(SCL、SDA)加电源和地,在 Proteus 里画完电路,用 STM32CubeMX 初始化 I2C,再用 HAL 库驱动 SSD1306 控制器,就能在软件仿真里看到完整的显示效果。这套流程不需要真实硬件,不需要逻辑分析仪,一台电脑全搞定。这一篇我会告诉你如何在 Proteus 中搭电路、配置 CubeMX、写驱动代码,以及最关键的:屏幕不亮时怎么用示波器看时序定位问题。看完你就能举一反三,之后写 SPI 接口 OLED 或其他 I2C 传感器都通用。

2. SSD1306 与 I2C 时序:先弄懂协议细节,再写代码

2.1 SSD1306 驱动的 I2C 接口,到底在传输什么

0.96 寸 OLED 屏的显示核心是 SSD1306 控制器,它内部有 128×64 像素的显存(GDDRAM),共 8 页(Page 0 到 Page 7),每页 128 字节,每字节对应一列 8 个像素的垂直排列。你通过 I2C 向 SSD1306 写入显示数据或控制命令,控制器就会对应刷新显存内容。

SSD1306 作为 I2C 从设备,地址由 SA0 引脚决定。很多 OLED 模组上 SA0 已经通过硬件固定,通常地址是 0x78(7 位地址 0x3C 左移一位)或 0x7A。Proteus 里的 OLED 模型默认地址一般是 0x78,也就是 7 位地址 0x3C。这一条务必记住,因为后面你会在代码里反复见到这个数字。

I2C 总线上的每次通信都遵循标准的起始条件、地址帧、数据帧、应答位、停止条件结构。但对于 SSD1306,还有一层额外的封装:在每个传输帧中,第一个数据字节必须是指定“后续数据是命令还是显存数据”的控制字节。控制字节的 bit7 是 Co 位(Continuation),bit6 是 D/C# 位(Data/Command)。当 D/C#=0 时,后续字节被解释为命令;D/C#=1 时,后续字节被写入 GDDRAM 显存。这就是为什么你在网上看到的 SSD1306 驱动代码里,会有一个 write_cmd 函数和一个 write_data 函数的根本原因。

2.2 I2C 起始与停止条件的时序细节

I2C 在空闲时 SCL 和 SDA 都被上拉电阻拉高。起始条件(START)定义为:在 SCL 为高电平期间,SDA 从高跳变到低。停止条件(STOP)定义为:在 SCL 为高电平期间,SDA 从低跳变到高。这里有个新手特别容易犯的时序理解错误——数据位的变化必须发生在 SCL 为低电平期间,而起始/停止条件则相反,发生在 SCL 为高电平期间。如果你在写模拟 I2C 的代码时忘了这一规则,就会出现第一个字节就发送失败的情况。

在 Proteus 中,你可以放置虚拟逻辑分析仪或直接在 I2C Debugger 里观察通信波形,这一点比真实硬件还要方便。当屏幕不亮时,我最先检查的就是这段起始条件有没有正确产生,通常问题就出在 SCL 和 SDA 的时序先后顺序上。

SCL 频率方面,标准模式是 100kHz,快速模式是 400kHz。STM32 的硬件 I2C 可以配置到 400kHz,但 SSD1306 在 Proteus 仿真中的最佳表现一般在 100k~200kHz 之间。如果你把时钟配到 400kHz,仿真模型偶尔会出现响应波动——这不是它真实硬件不支持,而是 Proteus 的模型在高速下时序容易出错。稳妥起见,CubeMX 里把 I2C 时钟设为 100kHz。

2.3 SSD1306 初始化序列:每一行命令的作用

写完时序和地址,接着就是初始化。SSD1306 上电之后不会自动进入可显示状态,必须发送一连串初始化命令。这一步是零基础最容易失败的地方——漏一条、错一条,屏幕要么全黑、要么显示乱码。一个典型的最小初始化序列如下:

命令字节含义是否必须
0xAE关闭显示必须(先关再配置)
0x20, 0x00设置内存寻址模式为水平建议
0xB0设置页地址为 Page 0必须
0x00, 0x10设置列地址低位和高位为 0必须
0x40设置显示起始行必要
0x81, 0xCF设置对比度可选
0xA1设置段重映射(列地址 127 映射到 SEG0)必须,否则左右镜像
0xC8设置 COM 扫描方向(重映射)必须,否则上下翻转
0xA6正常显示(非反色)建议
0xA8, 0x3F设置多路复用比 1/64 duty必须
0xD3, 0x00显示偏移为 0必须
0xD5, 0x80设置时钟分频因子和振荡器频率建议
0x8D, 0x14使能电荷泵必须(软件里常被漏掉)
0xAF打开显示必须

你在网上看到的各种驱动代码,初始化序列大同小异,核心差异就在 0xA1/0xC8 这两个方向设置上。如果 Proteus 里点亮的 OLED 显示是镜像的,问题一定出在这里。另外,0x8D 0x14 是电荷泵使能,漏掉它屏幕会一直黑着——这个问题在仿真里不会像真实硬件那样有明显的电流变化,更难察觉。

3. Proteus 电路搭建与 CubeMX 初始化:从零到能跑的最小环境

3.1 在 Proteus 中添加 STM32F103C8 和 OLED 元件

Proteus 8 Professional 自带 STM32F103C8 模型和 SSD1306 OLED 仿真模型。打开新工程后,从元件库中分别搜索并放置:

  • STM32F103C8(LQFP48 封装,内核 Cortex-M3)
  • OLED_SSD1306 或 OLED 0.96 I2C 模型(具体名称取决于 Proteus 版本,可以搜 I2C OLED)

放置后,检查 OLED 模型的原理图引脚定义。不同版本的 Proteus OLED 模型引脚标注可能有差异,常见的有两种:一种是直接引出 VCC、GND、SCL、SDA;另一种是带 SA0 地址选择引脚。如果有 SA0,接 GND 对应地址 0x78,接 VCC 对应 0x7A。

电路连接关系非常简单,一共 5 根线:

  • OLED VCC → STM32 的 3.3V 电源输出引脚
  • OLED GND → STM32 的 GND
  • OLED SCL → STM32 的 PB6(I2C1_SCL)
  • OLED SDA → STM32 的 PB7(I2C1_SDA)
  • 可选:在 SCL 和 SDA 上各加一个 4.7kΩ 上拉电阻到 3.3V。Proteus 内部模型有时已经内置上拉,但加上更接近真实硬件习惯

在 Proteus 中连线完成后,双击 STM32 芯片,加载你后面用 Keil 编译出来的 HEX 文件。然后点击左下角的运行按钮,观察 OLED 屏幕是否有反应。这里有一个注意点:在 CubeMX 初始化完成、代码尚未写出时,不需要急着加载 HEX,先把电路搭好,后面一步步来。

3.2 用 STM32CubeMX 配置 I2C1,生成 Keil 工程

打开 STM32CubeMX,新建工程选择芯片 STM32F103C8Tx。在 Pinout & Configuration 标签页里,按以下步骤配置:

  1. 找到 PB6,选择 I2C1_SCL;找到 PB7,选择 I2C1_SDA。
  2. 左侧栏点击 Connectivity → I2C1,在 Parameter Settings 里把 I2C Speed Mode 改为 Standard Mode(100kHz)。
  3. Clock Speed 保持默认的 100000 Hz 即可。
  4. 若使用外部晶振(HSE),需要配置 RCC 并选择 HSE 为 Crystal/Ceramic Resonator。零基础建议直接用内部时钟 HSI,省去晶振起振问题。在 Clock Configuration 标签页里,把 System Clock Mux 设为 HSI 内部时钟,系统时钟设 64MHz 或 72MHz 都行,I2C 外设时钟会自动分频。
  5. 生成工程时,Toolchain/IDE 选择 MDK-ARM V5,Code Generator 里勾选 Generate peripheral initialization as a pair of ‘.c/.h’ files。

生成工程后,打开 Keil,在 main.c 的 user code 区域中添加自己的代码。注意:CubeMX 生成的 I2C 初始化代码已经完整放在 i2c.c 中,包含 MX_I2C1_Init() 函数,你不需要手写寄存器配置。

3.3 在 Keil 中配置包含路径和编译选项

新建驱动文件时,我建议把 SSD1306 的代码单独拆成 oled.c 和 oled.h,不要全部塞进 main.c,之后维护和移植会方便很多。在 Keil 中把这两个文件添加到项目里,并把头文件路径加入 C/C++ 选项卡的 Include Paths。这些代码你把核心函数敲进去之后,编译的第一关是语法,第二关是链接问题,如果报 undefined symbol,多半是源文件没添加进工程。

编译成功后会生成 HEX 文件,默认在工程的 Objects 或 Listings 目录下。在 Proteus 中双击 STM32F103C8,在 Program File 一栏选择这个 HEX 文件,点击运行。如果屏幕亮起且正确显示,说明电路和初始化都是对的;如果黑屏,进入第 5 章的排错流程。

4. HAL 库驱动 OLED:核心读写函数与显示封装

4.1 基于 HAL 库的 I2C 读写基础函数

STM32 HAL 库提供了三个最常用的 I2C 函数,分别用于发送数据、接收数据和发送带寄存器地址的数据:

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);

对于 SSD1306,我们只需要用前两个函数。特别要注意DevAddress参数——HAL 库要求传入的是 8 位地址(包含读写位),也就是 0x78 而不是 0x3C。这个细节困扰过很多人:用 0x3C 作为参数时,屏幕上没有任何反应,HAL 库会一直返回 HAL_BUSY 或 HAL_ERROR。

4.2 SSD1306 写命令和写数据的完整实现

SSD1306 的 I2C 通信格式是:先发从机地址(0x78),再发控制字节(0x00 表示命令,0x40 表示数据),最后发命令或数据本体。用 HAL 库实现,核心代码只有几行:

// oled.c #include "oled.h" #include "i2c.h" #define OLED_ADDR 0x78 // 8位地址,即SA0=0时的地址,注意不是0x3C // 写命令:control byte = 0x00 void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2]; buf[0] = 0x00; // 控制字节:D/C#=0,表示后续是命令 buf[1] = cmd; HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100); } // 写数据:control byte = 0x40 void OLED_WriteData(uint8_t data) { uint8_t buf[2]; buf[0] = 0x40; // 控制字节:D/C#=1,表示后续是显存数据 buf[1] = data; HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100); } // 写一整块数据(用于连续发送显存内容) void OLED_WriteDataBuffer(uint8_t *data, uint16_t len) { uint8_t buf[len + 1]; buf[0] = 0x40; memcpy(&buf[1], data, len); HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, len + 1, 100); }

参数说明:

  • buf[0] = 0x00buf[0] = 0x40是 SSD1306 协议的控制字节,0x00 表示命令,0x40 表示数据。注意这里的 0x40 不是 I2C 地址,跟之前地址 0x78 没有直接关系。
  • HAL_I2C_Master_Transmit的最后一个参数是超时时间 100ms。I2C 在仿真中出现阻塞时,HAL 库会在超时后返回 HAL_BUSY,你可以在 OLED_WriteCmd 中检查返回值并做异常处理。一个简单的做法是包装一层重试机制:
void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] = {0x00, cmd}; HAL_StatusTypeDef status; status = HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100); if (status != HAL_OK) { // 常见原因:I2C 通信失败,可在此处加调试断点观察 } }

在实际开发中,把每次 I2C 通信的返回值检查都写清楚,屏幕上不亮时能省很多定位时间。Proteus 仿真出现 HAL_BUSY 的概率高于真实硬件,原因多半是多个 I2C 从设备地址冲突或 SCL/SCL 配置错误。

4.3 显存映射与全屏刷新函数

SSD1306 的显示不是逐像素写入,而是按页写入。前面提到它有 8 页,每页 128 字节。要在 (x, y) 坐标显示一个像素,需要先把坐标转换成页地址和字节位:

#define OLED_WIDTH 128 #define OLED_HEIGHT 64 #define OLED_PAGE_NUM 8 // 全屏显存缓存,1920字节约等于2KB,F103C8的20KB SRAM完全够用 uint8_t oledBuffer[OLED_PAGE_NUM][OLED_WIDTH]; // 在缓冲区中画一个像素,x 0-127,y 0-63 void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t isOn) { if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return; // 越界保护 if (isOn) { oledBuffer[y / 8][x] |= (1 << (y % 8)); } else { oledBuffer[y / 8][x] &= ~(1 << (y % 8)); } } // 将整个缓冲内容写入 SSD1306,分8页,每页128字节 void OLED_Refresh(void) { uint8_t page; for (page = 0; page < OLED_PAGE_NUM; page++) { OLED_WriteCmd(0xB0 + page); // 设置页地址 OLED_WriteCmd(0x00); // 列地址低4位=0 OLED_WriteCmd(0x10); // 列地址高4位=0 OLED_WriteDataBuffer(oledBuffer[page], OLED_WIDTH); } }

这段代码有两个关键点。第一,0xB0 + page是页寻址命令,范围是 0xB0 到 0xB7,对应 8 页,不小心把页地址算错会出现“内容错位”或“只有前两行亮”的现象。第二,每页写完 128 字节后,SSD1306 内部的列地址会自动加一,所以不需要逐字节发送列地址命令,但前提是把内存寻址模式设置为 0x20 0x00(水平模式)。

有了 OLED_DrawPixel 和 OLED_Refresh,显示字符和汉字的本质就是拼像素。英文字符集一般是 6×8 或 8×16 的点阵,汉字是 16×16 或更大。把点阵数据存成 const 数组,按字节逐位调用 OLED_DrawPixel 即可。这里不展开讲字库生成,但我会建议你在网上找现成的取模工具,用“列行式”或“纵向取模”的方式生成点阵数据,与上面的画点函数配合最自然。

4.4 软硬件 I2C 的选择:HAL 库硬 I2C 与 GPIO 模拟的取舍

我介绍的这套方案用的是 STM32 硬件 I2C(I2C1 外设),这是最省 CPU 的方式。但如果你在 Proteus 里仿真报了 HAL_BUSY 或者时序响应异常,也可以改用 GPIO 模拟 I2C——用任意两个引脚手动拉高拉低实现时序。零基础阶段我反而建议两条路都走一遍:硬 I2C 理解外设工作原理,模拟 I2C 反过来加强信号时序概念。

GPIO 模拟 I2C 的核心也很简单,就是按照协议去翻转电平:

#define OLED_SCL_PIN GPIO_PIN_6 #define OLED_SDA_PIN GPIO_PIN_7 #define OLED_SCL_PORT GPIOB #define OLED_SDA_PORT GPIOB void OLED_I2C_Start(void) { HAL_GPIO_WritePin(OLED_SDA_PORT, OLED_SDA_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(OLED_SCL_PORT, OLED_SCL_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(OLED_SDA_PORT, OLED_SDA_PIN, GPIO_PIN_RESET); // SCL高时,SDA由高变低 HAL_GPIO_WritePin(OLED_SCL_PORT, OLED_SCL_PIN, GPIO_PIN_RESET); } void OLED_I2C_Stop(void) { HAL_GPIO_WritePin(OLED_SDA_PORT, OLED_SDA_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(OLED_SCL_PORT, OLED_SCL_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(OLED_SDA_PORT, OLED_SDA_PIN, GPIO_PIN_SET); // SCL高时,SDA由低变高 } void OLED_I2C_SendByte(uint8_t byte) { for (int i = 0; i < 8; i++) { if (byte & 0x80) HAL_GPIO_WritePin(OLED_SDA_PORT, OLED_SDA_PIN, GPIO_PIN_SET); else HAL_GPIO_WritePin(OLED_SDA_PORT, OLED_SDA_PIN, GPIO_PIN_RESET); byte <<= 1; HAL_GPIO_WritePin(OLED_SCL_PORT, OLED_SCL_PIN, GPIO_PIN_SET); // SCL拉高,数据被采样 HAL_GPIO_WritePin(OLED_SCL_PORT, OLED_SCL_PIN, GPIO_PIN_RESET); } }

注意这段模拟代码里没有延时函数,实际运行时要根据 SCL 频率添加几微秒的延时,否则在仿真里波形容易失真。用HAL_Delay(1)太粗,应该用for循环空转或DWT->CYCCNT做精密延时。模拟 I2C 与硬 I2C 各有适用场景:硬 I2C 代码少、效率高,但配置有讲究;模拟 I2C 不受引脚复用限制,任意 GPIO 都能用,但 CPU 占用高。零基础阶段建议先跑通硬 I2C,再用模拟 I2C 加深理解。

5. Proteus 仿真的关键排错清单:屏幕不亮、花屏、乱码逐个击破

5.1 用 Proteus 的 I2C Debugger 和虚拟示波器定位协议问题

Proteus 自带的 I2C Debugger(在虚拟仪器面板中)可以直接挂在 SCL 和 SDA 总线上,显示所有经过的 I2C 通信报文,包括起始位、地址、数据、应答位。这是你排错时最强大的工具。把 I2C Debugger 连接到电路后,运行仿真,观察它捕获的报文。如果软件运行后 Debugger 里空无一物,说明主设备根本没有发起通信,问题出在 CubeMX 的引脚配置或代码中。

虚拟示波器(OSCILLOSCOPE)也值得接上。把通道分别挂到 SCL 和 SDA 上,观察波形是否符合 I2C 协议的基本要求:数据位是否在 SCL 低电平时变化、起始/停止条件形状是否正确、SCL 频率是否符合预期。出现同一条总线上同时有两个主设备在抢线、或者 SDA 被意外拉死成低电平的情况,波形上都会露出端倪——比如 SDA 长时间保持低电平时,通常是从设备(OLED)不产生应答位,总线被某个字节卡死。

5.2 HAL_BUSY 与 HAL_ERROR:三种常见原因和修法

错误返回 HAL_BUSY 或 HAL_ERROR 是 Proteus 仿真中最常见的现象。我总结了排序靠前的三种原因:

原因现象解决方法
地址参数错用 0x3CI2C Debugger 能看到主机发起通信,但没有应答位将 DevAddress 改为 0x78,确保使用的是 8 位地址
SCL/SDA 引脚配置成普通 GPIO波形完全不存在,I2C Debugger 无数据检查 CubeMX 中 PB6/PB7 是否被配置为 I2C1 复用功能,而不是普通输出
上拉电阻缺失或接错电压SDA 波形变为缓慢的锯齿波,高低电平不分明在 SCL 和 SDA 对 3.3V 各接 4.7kΩ 电阻

你还要注意一点:Proteus 的仿真速度和真实硬件不同,运行速度过快时 I2C 时序容易丢位。把仿真运行速度调到 1x(而不是优先最快速度),再观察 OLED 是否正常。这是一个反直觉但真实存在的坑。

5.3 屏幕亮但显示内容错位:页地址和列地址的导航问题

如果 OLED 能点亮、能显示,但显示的内容出现 8 像素的垂直偏移或错位,通常不是 I2C 通信问题,而是页地址定位出错。SSD1306 的寻址范围是 Page 0 到 Page 7,列地址是 0 到 127。刷新数据时顺序是:写页地址命令 → 写列地址命令 → 连续写 128 字节。如果你在 OLED_Refresh 中把页地址从 0 写到 7,列地址每次都回归 0,显示内容就应该严格对齐。出现整体上下错位,检查0xB0 + page是否正确;出现左右错位,检查列地址高低位命令是否写反(0x00 低 4 位,0x10 高 4 位)。

5.4 显示镜像:段重映射与 COM 扫描方向配置

左右镜像的根因是段重映射寄存器(0xA0/A1),上下镜像的根因是 COM 扫描方向(0xC0/C8)。这是 SSD1306 初始化序列里两个最容易配错的地方。0xA1 表示列地址 127 映射到 SEG0 引脚,0xA0 则相反;0xC8 表示 COM 从 N-1 扫描到 0,0xC0 则相反。如果你的 OLED 显示的内容左右颠倒,把 0xA1 改成 0xA0;上下颠倒,把 0xC8 改成 0xC0。在 Proteus 里这两个方向都有对应的仿真效果,一改就能看到差别,比真机调试还直观。

6. 进阶玩法:在帧循环中绘制动态图形,并验证刷新率对显示的影响

仿真环境跑通后,可以挑战一个进阶任务:在 OLED 上动态显示一个正弦波或简单的图形动画。这个任务看起来简单,实际考核的是显存更新策略与刷新机制的配合。对于帧率、系统时钟和 I2C 带宽之间的关系,零基础阶段不需要严格计算,但要理解一个事实:64×64 像素的一个全屏矩形区域,每帧需要发送 64×64/8 = 512 字节的显存数据,加上控制字节和地址命令,实际传输量约 600 字节,在 100kHz 的 I2C 总线上耗时约 48ms,最高只能跑 20 帧左右——这还没算上 CPU 绘制时间。这就是为什么很多 OLED 动态显示看起来“卡顿”,因为 I2C 带宽就是瓶颈。

一个常见的优化技巧是局部刷新:只在变化区域绘制并发送对应页的数据,避免整屏刷新。比如做一个 64×64 的跳动的方块,只需要刷新顶部 8 行对应的那页数据,传输量立刻缩小到原来的 1/8。实现方式是修改 OLED_Refresh 函数,传入起始页和终止页参数,而不是每次全刷 8 页。这在工业仪表、桌面摆件这类低刷新率应用中是够用的;如果将来你要在 OLED 上播放超过 30fps 的动画,建议改用 SPI 接口的屏幕,或者到带 DMA 的高速屏上实现。

最后你可以做一个 1 秒的计数器动画,显示在 OLED 上,验证每秒刷新时没有闪烁和拖尾。如果屏幕有残影,往往不是 OLED 本身的问题,而是显存清零不完全——上一帧的像素没有被正确覆盖。检查 OLED_DrawPixel 的关闭像素分支是否正常生效,确保清屏时所有缓存字节被赋为 0x00,再从 SSD1306 的清屏命令(0x00 到 0x7F 地址写入 0x00)走一遍。用 Proteus 做这个验证比真实硬件更方便,因为虚拟屏幕可以无限次重复仿真,不会损坏,你能把每一个状态转换都看清楚,不必担心反复烧录对硬件寿命的影响。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询