☰
SSD1322灰度OLED驱动实战:STM32初始化与DMA刷新指南
2026/10/21 23:59:33 网站建设 项目流程

简介:本资源是一套面向嵌入式开发初学者与STM32项目实践者的SSD1322 OLED显示屏驱动工程,专为STM32F103系列MCU设计,解决OLED图形界面开发中控制器初始化、像素绘制与显示刷新等核心问题。压缩包共78个文件,含33个头文件(.h)用于接口定义与寄存器配置、32个源文件(.c)实现底层驱动逻辑与图形函数、8个汇编启动文件(.s)及Keil工程配置文件(.uvprojx/.uvoptx),总大小291KB,结构完整,可直接导入Keil uVision编译运行。已有790人学习下载,适用于智能仪表、小型HMI、便携设备等需单色高对比度显示的嵌入式场景。读者可获得已验证的SPI通信适配代码、128×64分辨率帧缓冲管理、基础绘图API(点/线/矩形)、对比度调节与显示开关控制等功能模块,并可通过修改Display.c与Mode.h快速扩展字符库或动画效果。 最近这个周末,我基本都耗在一个从资源站拉回来的压缩包上,名字就叫SSD1322驱动.rar,后面还拖着一串_OLED SSD1322_SSD1322 STM32_ssD1322程序_ssd1322_ssd13这样的标签。解压之前我以为又是一个 SSD1306 的变种,结果打开一看,完全不是一回事:这是一颗支持 16 级灰度的 OLED 驱动芯片,分辨率可以到 256x64,而不是我们熟悉的 0.96 寸小屏那种单色页面。如果你之前只玩过 SSD1306,第一次碰 SSD1322 大概率会卡在初始化看不懂、花屏不知道怎么排查、显示图片方向不对这几个坎上。

这篇文章就从一个真实的驱动压缩包出发,把 SSD1322 这颗芯片、它和 SSD1306 的区别、STM32 上的接线方式、初始化序列、取模方法、常见坑位全部串起来,最后给出一套可以直接抄作业的驱动思路。适合已经能用 HAL 库点亮普通 OLED、想更进一步玩灰度屏或者做仪表类界面的朋友,哪怕你是跟着江科大视频刚入门 STM32 的萌新,只要跟着步骤走,也能把它调出来。

1. 这块屏和这个驱动包,到底解决什么问题

先搞清楚 SSD1322 和 SSD1306 到底差在哪。SSD1306 是 128x64 单色屏,每个像素只有亮和灭两个状态,数据写入按页来,一页 8 行,单片机往显存里填 0 或 1 就行。SSD1322 则是 256x64 像素,支持 16 级灰度,内部有一个 128x64x4bit 的显存区域,通过 4bit 表示一个像素的亮度等级,所以显示效果比单色屏细腻得多,可以做类似黑白照片的界面,也能通过不同灰度做立体感很强的仪表盘。当然代价是数据量上来了:单色屏整屏 1KB 显存,SSD1322 在 4bit 模式下整屏要 8192 字节,如果按像素展开写,SPI 速度不够的话帧率会很难看。

这个压缩包的价值就在这里。网上很多 SSD1322 驱动包里的程序结构其实都是类似的,核心就是三部分:初始化序列、写命令/写数据接口、清屏和显示图片函数。打开之后通常能看到oled.c、oled.h、spi.c、gui.c、font.c,有的还带一个oled.bmp.c,里面放着一张已经取模好的图片数组。初始化序列那几十行代码是全网通用的,不同厂商模组差异不大,主要区别在 pin 脚映射和数据扫描方向。把这三部分吃透了,你拿到手的不只是一个能亮的程序,而是一套以后换屏、换主控都能复用的驱动模板。

不过我要先泼一盆冷水:别指望这个压缩包解压出来直接编译就能用。大概率会遇到这几个问题:接线引脚和你手里的模组对不上、SPI 模式不对导致花屏、初始化序列里有个0xFD 0x12命令被注释掉导致屏幕完全不亮、取模方向和驱动配置不一致导致图形上下颠倒。下面我一个个拆开讲。

2. 硬件连接:点亮之前先把每个引脚对齐

2.1 SSD1322 模组的常见引脚定义

STM32 驱动 SSD1322,首先要学会看模组丝印。常见 2.4 寸、2.8 寸、3.12 寸 SSD1322 模组引脚大同小异,一般包括:VCI、VDD、VLSS(或 GND)、VCOMH、VCOML、BS0、BS1、BS2、SCLK、SDIN、D/C(或 A0)、CS、RES、Busy。其中 VCI 是面板驱动电压,有些模组需要 5V 输入,有些可以 3.3V;VDD 是逻辑电压,通常 3.3V;VLSS 接地。Busy 脚有些模组引出来,有些没有,用到时通常接单片机 IO 读取忙状态,但 4 线 SPI 模式下不接也能跑。

最容易被忽略的是 BS0、BS1、BS2 这三个接口选择脚。SSD1322 支持 8 位 6800/8080 并行、4 线 SPI、3 线 SPI 等模式,具体走哪种接口由 BS0/BS1/BS2 的电平组合决定。驱动包里的代码默认是 4 线 SPI,那就必须把这三个脚按 4 线 SPI 拉高或拉低。如果你手里的模组把 BS 脚固定死在并行模式,那 SPI 怎么调都不会亮,这种情况下要么飞线改电平,要么干脆走并行接口。这里没有统一的电平表,因为不同厂商模组封装不一样,正确姿势是翻你的模组数据手册,搜 “BS0 BS1 BS2 interface selection” 表格,对着表格设置。

2.2 供电、上电时序和电平匹配

SSD1322 对供电时序比较敏感,官方手册建议 VCI 上电后经过一段延时,再让 RES 引脚从低变高完成复位,之后才能发命令。如果上来就发命令,IC 可能处于锁定状态,命令全部被忽略。很多驱动包里的OLED_Init函数一开始会摆一段 HAL_Delay,比如上电后延时 50ms,然后 RES 拉低 10ms、拉高 10ms,再延时 100ms,这个顺序最好别省。我之前偷懒去掉这个延时,结果屏幕偶尔亮偶尔不亮,加了之后稳得很。

电平方面需要注意,STM32F103 的普通 IO 是 3.3V 电平,SSD1322 逻辑部分 VDD 也是 3.3V,直接连没问题。但如果你用的是 5V 供电的 Arduino 或者 STM32 的 5V 容忍脚,和 VDD=3.3V 的模组之间就需要确认电平兼容,尤其 SCLK、SDIN 这些信号线,输入电压超过 VDD+0.3V 就可能损坏 IC。最稳妥的办法是选 VDD=3.3V 的模组,主控用 3.3V 供电,信号线直连。SCLK 和 SDIN 串个小电阻(比如 100Ω)也可以,能降低噪声和过冲。

2.3 以 STM32F103 SPI1 为例的接线表

我这次实验用的是 STM32F103C8T6,SPI1 外设。SPI1_SCK 在 PA5,SPI1_MOSI 在 PA7,CS 我随便选了 PA4,D/C 选了 PA1,RES 选了 PA0。如果你手里的主控不是 SPI1,记住一个原则:只要你的 SPI 主机能输出模式匹配的时钟和数据,CS、D/C、RES 用普通 GPIO 控制就行,不一定非要挂到 SPI 复用引脚上。4 线 SPI 默认不需要 MISO,因为 SSD1322 不往回传数据(Busy 是独立引脚),所以 SPI1_MISO 脚可以空着。

实际的接线表格长这样:

SSD1322 引脚STM32F103 引脚说明
VCI5V 或 3.3V(按模组要求)面板电源,注意看丝印
VDD3.3V逻辑电源
VLSS/GNDGND共地
SCLKPA5SPI1_SCK
SDINPA7SPI1_MOSI
CSPA4片选,低有效
D/CPA1低电平发命令,高电平发数据
RESPA0复位,低有效
BS0/BS1/BS2按模组手册接必须设置为 4 线 SPI 模式

接完之后不要急着通电。先用万用表测一下 VCI 和 GND 之间有没有短路,很多模组焊接、挤压后容易短路,一上电烧的不是屏幕就是单片机。另外 CS 和 D/C 不能悬空,悬空时电平不确定,初始化很可能跑飞;驱动包里如果没做上拉,建议代码初始化之前也把这两个引脚的电平状态明确设置好。

3. 驱动程序的架构:初始化、写命令、刷屏是怎么配合的

3.1 压缩包里常见文件结构和核心函数定位

解压SSD1322驱动.rar之后,核心文件通常就是oled.c和oled.h。oled.c里一般有OLED_WR_CMD、OLED_WR_DATA、OLED_Init、OLED_Clear、OLED_ShowPicture这几个函数。如果驱动包还带了 GUI,那oled.c会再提供OLED_SetPixel、OLED_Line、OLED_Circle这类画图函数。我们的目标是先把底层跑通,别一上来就折腾 GUI。

OLED_WR_CMD和OLED_WR_DATA是驱动的地基。它们两个长得几乎一样,唯一的区别是 D/C 引脚的电平。写命令时 D/C 拉低,写数据时 D/C 拉高。SPI 传输本身没差别,都是往 SPI 外设的数据寄存器里塞一个字节。一个典型实现:

void OLED_WR_CMD(uint8_t cmd) { OLED_DC_LOW(); OLED_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); OLED_CS_HIGH(); } void OLED_WR_DATA(uint8_t dat) { OLED_DC_HIGH(); OLED_CS_LOW(); HAL_SPI_Transmit(&hspi1, &dat, 1, HAL_MAX_DELAY); OLED_CS_HIGH(); }

注意 CS 是在每次传输前后拉低拉高,还是在整个数据块传输期间一直拉低,两种写法都有人用。我实测只要驱动包自身保持一致,两种都能跑。但如果后面要接 DMA 批量刷新,建议把 CS 控制从单字节函数里拿出来,变成“开始传输时拉低一整块数据、传完再拉高”,不然 DMA 中断处理会很啰嗦。

3.2 SSD1322 初始化序列逐条拆解

SSD1322 的初始化序列是重点,也是最容易出问题的地方。网上流传的初始化代码基本源于数据手册的参考序列,我在驱动包里拿到的版本和常见的 SSD1306 驱动对比了一下,发现多了好几条指令。下面挑关键的逐条说明。

void OLED_Init(void) { // 上电延时 + 复位 HAL_Delay(100); OLED_RES_HIGH(); HAL_Delay(10); OLED_RES_LOW(); HAL_Delay(10); OLED_RES_HIGH(); HAL_Delay(100); OLED_WR_CMD(0xFD); OLED_WR_DATA(0x12); // 解锁命令锁定寄存器 OLED_WR_CMD(0xAE); // 关闭显示 OLED_WR_CMD(0xA8); OLED_WR_DATA(0x3F); // 设置 multiplex ratio = 64 OLED_WR_CMD(0xA0); OLED_WR_DATA(0x51); // 重映射配置 OLED_WR_CMD(0xA1); OLED_WR_DATA(0x00); // 起始行 OLED_WR_CMD(0xA2); OLED_WR_DATA(0x00); // 显示偏移 OLED_WR_CMD(0xA4); // 正常显示,关闭全亮 OLED_WR_CMD(0xAB); OLED_WR_DATA(0x01); // 开启内部 VDD 稳压器 OLED_WR_CMD(0xB1); OLED_WR_DATA(0xE0); // 相位 1/相位 2 时长 OLED_WR_CMD(0xB3); OLED_WR_DATA(0xF0); // 时钟分频 OLED_WR_CMD(0x81); OLED_WR_DATA(0x7F); // 对比度 OLED_WR_CMD(0xB8); OLED_WR_DATA(0x03); // 灰度查找表 OLED_WR_CMD(0xBC); OLED_WR_DATA(0x08); // 预充电电压 OLED_WR_CMD(0xBE); OLED_WR_DATA(0x07); // VCOMH 电压 OLED_WR_CMD(0xAF); // 开启显示 HAL_Delay(50); }

先看0xFD 0x12,这条的作用是给 IC 解锁。SSD1322 有个命令锁机制,如果 IC 处于锁定状态,后续命令都不会被接受。很多 ssd1306 转过来的朋友移植代码时容易漏掉这条,结果屏幕死活不亮。再看0xAB 0x01,这条是使能内部 VDD 稳压器。如果你的模组 VDD 引脚没有外部供电,而是靠内部稳压出来,这条漏掉也是白屏。然后0x81设置对比度,0x7F 是个比较中性的初始值,后面觉得亮度不够可以现场调大,比如 0xFF;但注意对比度拉太满且刷新率高的话,长时间显示同一画面容易有烧屏风险。

0xA0 0x51这个重映射配置是 SSD1322 最让人头大的地方。它决定了列地址和物理像素的对应关系、数据 Nibble 的排列顺序、以及左右上下扫描方向。0x51 是很多标准 256x64 模组上比较通用的一组配置,但并不是所有模组都认。如果初始化之后出现图形左右对调、上下颠倒、或者一个像素的亮度和数据对不上,先改这里,改成其他值试试,比如 0x42、0x51 的变体、0x60 等,直到显示方向正确。这个踩坑过程我没法用一张表说死,因为模组厂商的层压方向不一样,必须靠实测。

另外还有一个细节,0xB8后面的数据是灰度查找表相关配置,不同厂商给的参考值五花八门,有的写 0x03,有的写 0x00,有的还会带一串 16 字节的 Gamma 曲线数据。我的建议是:如果你手里的驱动包能正常显示,就保留原包的值;如果换了模组出现灰度层次很怪异的情况,优先找这个模组厂商的参考代码覆盖初始化序列。

3.3 显示一张图的完整数据流程

初始化完之后,显示图片的流程比 SSD1306 更接近传统 LCM。SSD1306 是用页地址模式,把显示分成 8 页,每页 8 行;SSD1322 则是完全的行列窗口模式,先设置列地址范围,再设置行地址范围,然后连续写数据。列地址是 0 到 127,行地址是 0 到 63。这里要特别提醒:列地址 0 到 127 不是你以为的 0 到 127 像素列,而是底层的显存地址单元。在 4bit 灰度模式下,每个地址单元对应两个相邻显示像素,一个字节里高 4 位和低 4 位分别表示这两个像素的灰度值。

对应到代码上,设置窗口和发送图片数据的函数如下:

void OLED_SetWindow(uint8_t col_start, uint8_t col_end, uint8_t row_start, uint8_t row_end) { OLED_WR_CMD(0x15); // 设置列地址范围 OLED_WR_DATA(col_start & 0x7F); OLED_WR_DATA(col_end & 0x7F); OLED_WR_CMD(0x75); // 设置行地址范围 OLED_WR_DATA(row_start & 0x3F); OLED_WR_DATA(row_end & 0x3F); } void OLED_Clear(uint8_t dat) { OLED_SetWindow(0, 127, 0, 63); OLED_DC_HIGH(); OLED_CS_LOW(); for (uint16_t i = 0; i < 128 * 64; i++) { // 8192 字节 HAL_SPI_Transmit(&hspi1, &dat, 1, HAL_MAX_DELAY); } OLED_CS_HIGH(); }

发送图片的核心逻辑就是先把窗口设到全屏,然后连续写入图片数组。列地址在写入过程中会自动递增,到窗口右边界后回卷到左边界且行地址加 1,所以一整屏数据 8192 字节可以一路写下去,不需要中途再次设置窗口。这个自动回卷行为给 DMA 批量刷新提供了便利,后面第 6 部分会细说。

3.4 把驱动包移植到自己的 STM32 工程

拿到驱动包之后最直接的做法不是直接编译,而是先找到oled.h里的引脚宏定义,改成自己板子的引脚。在 HAL 库工程里,常见的宏如下:

#define OLED_RES_PIN GPIO_PIN_0 #define OLED_RES_PORT GPIOA #define OLED_DC_PIN GPIO_PIN_1 #define OLED_DC_PORT GPIOA #define OLED_CS_PIN GPIO_PIN_4 #define OLED_CS_PORT GPIOA

然后把HAL_SPI_Transmit(&hspi1, ...)里的句柄换成你自己工程的hspi变量名。如果驱动包里用的不是 HAL 库而是标准外设库,差别主要在 SPI 发送函数,标准库写法是:

while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, cmd);

其余初始化逻辑可以原样保留。移植完成后先做一个最低限度的验证:调用OLED_Init(),然后全屏写 0x0F,看屏幕是不是均匀显示最高亮度,再全屏写 0x00 清屏。如果这两步都正常,说明底层 SPI 和初始化没问题,再往上层加图片和 GUI。反过来,如果全屏 0x0F 出现亮暗不均、横向条纹,先别查代码,多半是供电或者 SPI 时钟线的信号完整性问题。

4. 灰度显示和取模方向:别让图片上下颠倒

4.1 灰度模式下的数据组织方式

SSD1322 的灰度是 4bit,所以一个像素的亮度范围是 0 到 15,0 是纯黑,15 是最亮。从 0x00 到 0x0F,数值越大越亮。当你向显存写一个字节时,这个字节被拆成两个像素的灰度。比如你写0x12,高 4 位0x1是第一个像素的低灰度,低 4 位0x2是第二个像素稍亮一点的灰度。因此图片数据天生是“一个字节管两个像素”的状态,取模软件必须按这个数据组织方式来生成数组。

如果你使用的图片取模工具,比如 Image2Lcd 或者 PCtoLCD2002,需要设置成灰度输出模式。以 Image2Lcd 为例,打开图片后要设置输出灰度、最大宽度和高度设为 256 和 64,扫描方向选择从左到右、从上到下,输出格式选 C 语言数组,每个点灰度 4bit。这样生成的数组里,第一个字节高 4 位是图片左上角第一个像素,低 4 位是它右边相邻的那个像素,第二个字节就是第三、第四个像素,以此类推。

4.2 取模方向要和 Remap 配置对应上

这里有个必须实测的点:取模软件的扫描方向必须和你初始化里0xA0重映射配置的实际显示方向一致。如果你的驱动包用的0xA0 0x51是从左到右、从上到下扫描,那取模也按从左到右、从上到下;如果初始化配置里用了奇奇怪怪的 Remap 值,屏幕显示方向可能反了,图片内容也就跟着反了。

调整方向不用重新取模,优先调驱动初始化里的 Remap,或者调取模软件的扫描顺序,看哪个方便。我在实际调试中遇到一次图片左右镜像、一次上下颠倒,最后都是通过修改取模软件的扫描方向解决,没有动驱动里的 0x51,因为改 0x51 可能影响后续所有绘图函数的坐标对应关系。如果你用的是自己写画点函数画线画圆,那更不建议瞎改 Remap,保持驱动和取模同一套方向体系,然后靠软件层面调整坐标映射。

4.3 显示中文字符和简单界面的思路

SSD1322 不像 SSD1306 那样一个字节正好是一列 8 个点,所以字符显示的思路要调整。最朴素的办法是先把字符点阵按灰度 1 和 0 展开,然后逐点填充到显存。具体步骤:先在内存里准备好一个 128x64 的缓冲数组(4bit 模式下是 8192 字节),所有绘图操作都写进这个缓冲区,最后调用一次刷新函数把缓冲区整体推到屏幕。这种“显存缓冲 + 整体刷新”的做法比直接一字节一字节操作 SSD1322 的列地址窗口要省心得多,尤其是做界面的时候。

缓冲区大小 8KB,在 STM32F103 上放进全局数组没问题,但注意芯片内部 RAM 只有 20KB,如果工程里还有其他大缓冲区,要留意别爆内存。F103C8T6 的 20KB RAM 应付 8KB 显存缓冲区还能接受,再多个摄像头帧缓冲就危险了。你也可以把缓冲区砍成半个屏,或者只在变更区域刷新,但代码复杂度会上来,新手阶段先用整屏缓冲最稳。

5. 常见问题排查:白屏、花屏、亮度不对,逐个击破

5.1 白屏完全没反应

白屏是最常见也最让人恼火的问题。按照经验,先按“供电 → 复位 → 命令锁 → 内部稳压 → 对比度 → 接线”这个顺序排查。首先确认 VCI 和 VDD 都正常,模组有没有发烫,电流是否异常。然后确认 RES 上电后有完成一次低脉冲复位,可以用示波器或者逻辑分析仪看 RES 波形;没有示波器就在代码里故意拉高拉低几次,一般能解决。接着检查初始化序列是否包含0xFD 0x12解锁命令,以及0xAB 0x01开启内部 VDD 稳压器。这两个命令漏一个,屏幕就是纯白或纯黑。

如果这些都没问题,再看 CS、D/C 两个引脚是否接反。CS 接错的话,SPI 数据根本没进芯片;D/C 接反的话,命令被当数据、数据被当命令,屏幕行为会非常诡异。最后检查一下 SPI 发送函数里有没有把hspi1句柄传错,比如工程里同时初始化了 SPI1 和 SPI2,代码里写的却是&hspi2,这种低级错误我也犯过。

5.2 花屏、乱码、图形左右颠倒

花屏的排查重点在 SPI 模式和数据方向。SSD1322 的 SPI 模式可以是 mode 0 或 mode 3,不同模组驱动的锁存沿可能不同。如果你发现屏幕上出现规律的雪花点、或者相同的画面重复出现,先试一下 SPI 初始化里SPI_CPOL和SPI_CPHA的组合,STM32 上的操作就是在这两个参数上做排列组合,哪个能正常显示就用哪个。另外确认 SDIN 是不是接在了主控的 MOSI 上,别接到 MISO,这种错误花屏表现也非常典型。

图形左右颠倒一般是 Remap 配置和取模方向不一致。左右对调时改取模软件的水平扫描方向,上下颠倒改垂直扫描方向,或者尝试调整0xA0后面的 Remap 参数。不要同时改两处,一次只改一个变量,改完重新显示看效果,这样能快速定位是驱动问题还是数据问题。

5.3 亮度不足、残影、闪烁

亮度不足先查对比度寄存器0x81,初始值 0x7F 可以往上调,0xFF 是最亮。如果调到最亮还是觉得暗,检查0xBC预充电电压和0xBEVCOMH 电压,这两个值影响 OLED 像素的充电程度。但注意并不是越大越好,VCOMH 过高可能出现“鬼影”,就是切换画面后上一帧的残影还留在屏幕上。

闪烁问题优先查0xB3时钟分频和刷新率。如果 SPI 速度太低、刷一帧需要几十毫秒,那屏幕刷新率上不去,人眼就会看到闪烁。DMA 刷新可以明显改善这个现象,因为主循环不再阻塞在 SPI 发送上,屏幕刷完一帧立刻能刷下一帧。还一种情况是电源纹波太大,特别是用廉价 DC-DC 给模组供电时,OLED 亮度会跟着电源波动,这时候在 VCI 和 GND 之间并一个 10uF 和 100nF 电容,通常能压下来。

5.4 问题排查速查表

现象可能原因处理办法
全屏白或全屏黑未解锁命令锁检查 0xFD 0x12
全屏白或全屏黑内部 VDD 稳压器未开启检查 0xAB 0x01
全屏白或全屏黑RES 复位时序不对上电后加延时再拉高
花屏、雪花SPI 模式不匹配切换 CPOL/CPHA 组合
图形左右颠倒Remap 或取模扫描方向不一致修改取模方向或 0xA0 参数
图形上下颠倒行扫描方向反了修改取模垂直方向
亮度不足对比度太低调大 0x81 的值
残影明显预充电或 VCOMH 设置偏高调低 0xBC 或 0xBE
画面闪烁SPI 刷新率不够启用 DMA,提高 SPI 分频
偶尔花屏电源纹波、CS 浮空加电容,代码里显式控制 CS

6. 性能优化:用 DMA 把刷新率提上去,别让主循环卡死

6.1 为什么阻塞式刷新不可取

用HAL_SPI_Transmit一字节一字节发送,或者在 for 循环里反复调用发送函数,是非常慢的。STM32F103 的 SPI1 最高 18MHz,一帧 8192 字节,理论上最快也要 8192 * 8 / 18MHz ≈ 3.6ms。但 HAL 库每次发送都有函数调用开销、标志位等待,实际测下来一帧可能要 10ms 以上,这意味着刷新率只有 100Hz 不到,而且整个 MCU 都被这一个 for 循环占死,没法同时做按键扫描、数据处理、界面逻辑。

解决方案是用 SPI DMA 传输。DMA 能把内存里的图片数据自动搬到 SPI 发送寄存器,中间不需要 CPU 干预。这样刷新 8KB 数据只需要启动一次 DMA 传输,发完触发一次中断,主循环可以继续干别的事。我把这部分的代码贴出来,基本思路是先初始化 SPI 的 DMA 发送通道,然后刷新时设置窗口、启动 DMA、等待完成标志。

// 前提:在 CubeMX 里给 SPI1 添加 DMA 发送通道 extern DMA_HandleTypeDef hdma_spi1_tx; volatile uint8_t oled_transfer_done = 1; void OLED_Refresh(uint8_t *buffer) { while (oled_transfer_done == 0); // 等上一帧发完 OLED_SetWindow(0, 127, 0, 63); OLED_DC_HIGH(); OLED_CS_LOW(); oled_transfer_done = 0; HAL_SPI_Transmit_DMA(&hspi1, buffer, 8192); // 在 HAL_SPI_TxCpltCallback 里置 oled_transfer_done = 1 } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { oled_transfer_done = 1; OLED_CS_HIGH(); } }

注意这里有个细节:CS 不要在整个 DMA 传输期间一直拉低,而是在 DMA 发送完成中断里拉高,否则下一次刷新如果提前启动,CS 时序会乱。另外 DMA 缓冲区必须保持有效,不能在函数返回后缓冲区地址失效,所以要么用全局数组,要么用静态数组,不能在栈上临时申请 8KB 缓冲区。

6.2 帧率和显存策略的取舍

如果启用 DMA 后还想进一步提高帧率,可以考虑两个方向。一是降低刷新数据量,使用 4bit 灰度模式时如果只是显示简单界面,可以隔行刷新或者只刷新变化区域;二是提高 SPI 时钟分频,F103 的 SPI1 挂 APB2 总线,PCLK2 36MHz,SPI 时钟可以设到 18MHz,再往上需要超频或者换更强主控,比如 STM32F4 系列,SPI 能到 42MHz 甚至更高,刷新一帧 8KB 只需要不到 2ms,配合 DMA 就能跑出 30fps 以上的动画效果。

显存策略上,整屏缓冲是最省心的。如果你对内存敏感,可以改成 4bit 灰度下的“双半屏缓冲”,一个 4KB 缓冲存上半屏,另一个 4KB 缓冲存下半屏,刷新完上半屏再刷下半屏。不过个人建议新手不要为了省 RAM 把代码搞复杂,STM32F103 的 20KB RAM 放 8KB 显存并不紧张,等界面复杂到真的缺内存时再优化不迟。

6.3 还有哪些可以扩展的方向

驱动跑通之后,可以往几个方向扩展。一是加一个简单的灰度字体库,把常用 ASCII 字符点阵按 4bit 灰度展开,做一个简单的文本显示函数。二是用 SSD1322 的灰度特性做开机 Logo、电池电量、波形曲线这些界面,效果比单色 OLED 好很多。三是如果手头有多余的 SPI Flash,可以把图片数组放到外部 Flash 里,需要显示时再读出来,省点内部 Flash 空间,因为一张 8192 字节的图片数组放内部 Flash 还是有点心疼的。

我个人的体会是,SSD1322 不像 SSD1306 那样“一把就能点亮”,它更需要你耐心地把初始化序列、Remap 方向、取模方式、刷新方式这几个环节逐一理顺。一旦理顺了,它的灰度显示能力带来的界面质感是普通单色屏远远给不了的。最后再分享一个小技巧:如果你在调试时发现图片整体灰度偏亮或偏暗,不要急着改代码,直接把0x81后面的对比度值拿来调,这个寄存器是灰度屏的“全局亮度旋钮”,比改任何一个其他参数都直观。

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

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

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

立即咨询