1. 项目概述:1.3英寸OLED (B)模块的深度探索
最近在捣鼓一个小玩意儿,一块1.3英寸的OLED显示屏,型号后缀带个“(B)”。这玩意儿在创客圈和嵌入式开发里挺火的,尤其是配合ESP32、STM32这些MCU做状态显示、数据监控或者小游戏机。你可能也搜过“ESP32S3获取时间并显示在OLED屏幕”或者“STM32驱动OLED”这类关键词,没错,我们今天聊的就是这个。这块屏的核心驱动芯片通常是SH1106或者SSD1306,通过SPI或I2C这两种最常用的串行通信协议和你的主控芯片“对话”。别看它尺寸小,分辨率一般也就128x64或128x32,但显示效果清晰、对比度高、功耗低,是很多便携式设备或UI原型的不二之选。无论你是刚接触嵌入式显示的初学者,还是想优化现有驱动代码的老手,这篇文章都会带你从硬件连接到软件驱动,从协议原理到实战避坑,完整地走一遍。我会尽量用“说人话”的方式,把SPI和I2C那点事讲清楚,并分享我在实际项目中调试SH1106驱动时积累的一些“血泪”经验。
2. 核心硬件与通信协议选型解析
2.1 OLED模块与驱动芯片SH1106探秘
你拿到手的这块“1.3inch OLED (B)”,其核心是一块有机发光二极管面板,而让它能听你指挥的,是藏在模块背面的一颗驱动芯片——很大概率是SH1106。为什么是它?因为市面上1.3英寸、128x64分辨率的OLED模块,SH1106和SSD1306是两大主流。它们功能相似,但存在细微差异。SH1106内部自带升压电路,能自己产生驱动OLED所需的高电压(通常大于7V),这意味着你的系统只需要提供3.3V或5V的逻辑电源即可,简化了外部电路设计。而有些SSD1306版本可能需要外部提供VCC电压。
仔细看模块背面,通常会有一排引脚:VCC、GND、SCL、SDA、RES、DC、CS。如果是SPI接口,还会看到D0(SCLK)和D1(MOSI)。这里的关键是“RES”(复位)和“DC”(数据/命令选择)引脚,它们是正确驱动屏幕的命门。RES用于硬件复位驱动芯片,确保从一个已知状态开始;DC引脚则告诉芯片,你接下来发送的一个字节是命令(如设置显示区域、对比度)还是数据(要显示的点阵信息)。把这两个引脚的控制逻辑搞错,屏幕大概率会“装死”不亮。
2.2 SPI vs I2C:为你的项目选择最佳“跑道”
模块通常支持SPI和I2C两种模式,通过焊接模块背面的电阻(0欧姆贴片)来选择。这是一个关键的选择,直接影响你的接线复杂度和刷新速度。
I2C模式:只需要两根线——SCL(时钟线)和SDA(数据线),再加上VCC和GND,最少四根线就能驱动。它通过设备地址(SH1106通常是0x78或0x7A)来寻址,支持总线上挂载多个设备。听起来很美好,对吧?但它的缺点是速度相对较慢,标准模式100kbps,快速模式400kbps。当你需要快速刷新全屏动画或大量数据时,I2C可能会成为瓶颈。而且,I2C总线需要上拉电阻,通常阻值在4.7kΩ到10kΩ之间。为什么需要上拉?因为I2C协议中,SCL和SDA线都是“开漏输出”,即芯片只能把线拉低(到GND),不能主动拉高(到VCC)。上拉电阻的作用就是当没有设备拉低线路时,将线路电平恢复到高电平(VCC),这是实现“线与”逻辑和总线仲裁的基础。如果模块本身没集成,你得自己在主控板这边加上。
SPI模式:需要更多的线——至少SCLK(时钟)、MOSI(主设备输出,数据线)、DC、CS(片选)、RES,加上电源线。它的优势是速度快,可以轻松跑到几兆甚至几十兆比特每秒,全屏刷新毫无压力。SPI是全双工通信,但OLED驱动芯片通常只接收数据,所以MISO(主设备输入)线一般不需要接。SPI有四种模式(CPOL和CPHA的组合),指的是时钟极性(空闲时高电平还是低电平)和时钟相位(在时钟的第一个边沿还是第二个边沿采样数据)。对于SH1106/SSD1306,最常用的是Mode 0(CPOL=0, CPHA=0),即时钟空闲时为低电平,在时钟的上升沿采样数据。你必须在初始化代码里把SPI配置对这个模式。
选择建议:如果你的项目对显示速度要求不高,且MCU的IO口非常紧张,选I2C。如果你需要流畅的动画、快速刷新,或者MCU的IO口还算充裕,强烈建议使用SPI。我个人在大多数项目里偏好SPI,因为速度优势明显,调试起来时序问题也更少。
2.3 电平转换与电源考量:一个容易被忽视的坑
你的MCU(比如STM32、ESP32)逻辑电平是3.3V,而OLED模块可能标注支持3.3V/5V。虽然SH1106芯片本身通常是宽电压设计,但直接连接时仍需注意。
- 电源:如果MCU和OLED都使用3.3V供电,直接连接即可。如果OLED模块接5V,而MCU是3.3V,那么通信信号线(SCL/SDA或SCLK/MOSI/DC/CS/RES)绝对不能直接接5V!这可能会损坏MCU的IO口。你需要进行3.3V到5V的电平转换。
- 电平转换方案:对于I2C,由于其开漏特性,有一种简单的“二极管搭接”法:在SDA和SCL线上,从MCU侧(3.3V)各串联一个二极管(正极接MCU)到模块侧,模块侧的上拉电阻接到5V。这样,MCU拉低时,能通过二极管将总线拉低;MCU输出高电平时,由于二极管截止,总线被5V上拉电阻拉至高电平。但这种方法有压降,且速度受限。更可靠的方法是使用专用的双向电平转换芯片(如TXB0104)。
- 电源噪声:OLED在刷新时电流会有瞬间变化,最好在模块的VCC和GND引脚之间并联一个10uF的电解电容和一个0.1uF的陶瓷电容,以滤除电源噪声,防止屏幕出现闪烁或乱码。
3. 驱动实现与软件架构深度剖析
3.1 底层通信函数封装:构建稳定的传输层
无论你用HAL库、LL库还是直接寄存器操作,第一步都是封装好最底层的字节发送函数。这是所有上层显示功能的基石。
对于SPI,你需要两个基本函数:写命令和写数据。它们的区别仅在于DC引脚的电平。
// 假设已初始化好SPI外设和GPIO #define OLED_DC_PIN GPIO_PIN_2 #define OLED_DC_PORT GPIOA #define OLED_CS_PIN GPIO_PIN_3 #define OLED_CS_PORT GPIOA void OLED_WriteCommand(uint8_t cmd) { HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET); // DC置低,表示命令 HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); // CS置低,选中设备 HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); // CS置高,取消选中 } void OLED_WriteData(uint8_t dat) { HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET); // DC置高,表示数据 HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &dat, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PORT, GPIO_PIN_SET); }注意,有些SPI驱动库(如HAL)内部有锁机制(HAL_SPI_Lock),这是为了防止多线程或中断环境下对SPI总线的并发访问造成数据错乱。在单线程简单应用中你可能感觉不到,但在复杂系统中需要留意。
对于I2C,每次传输都需要带上设备地址,并且通常在命令或数据字节前加一个“控制字节”。这个控制字节的bit0就代表了DC引脚的功能:0为命令,1为数据。
#define OLED_I2C_ADDR 0x78 // 7位地址,左移一位后为0xF0 void OLED_I2C_WriteByte(uint8_t type, uint8_t data) { uint8_t buf[2]; buf[0] = (type == 0) ? 0x00 : 0x40; // 控制字节:0x00写命令,0x40写数据 buf[1] = data; HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDR, buf, 2, HAL_MAX_DELAY); }这里有个常见坑点:I2C的地址。SH1106的7位地址可能是0x78或0x7A(取决于模块设计)。HAL库的HAL_I2C_Master_Transmit函数需要传入的是7位地址左移一位后的值(即带上读写位)。所以如果7位地址是0x78,传入的地址参数就是0x78 << 1 = 0xF0。很多驱动失败都是地址不对导致的。
3.2 初始化序列与内存管理:唤醒你的屏幕
封装好底层读写后,下一步就是发送一系列初始化命令,配置驱动芯片的工作模式。这个过程是严格遵循数据手册的。以下是一个SH1106的典型初始化序列(基于SPI,I2C只需替换写函数):
void OLED_Init(void) { // 1. 硬件复位(如果接了RESET引脚) HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET); HAL_Delay(100); // 保持低电平至少几个毫秒 HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET); HAL_Delay(100); // 等待复位稳定 // 2. 发送初始化命令序列 OLED_WriteCommand(0xAE); // 关闭显示(Display OFF) OLED_WriteCommand(0xD5); // 设置显示时钟分频比/振荡器频率 OLED_WriteCommand(0x80); // 建议值 OLED_WriteCommand(0xA8); // 设置多路复用率(MUX Ratio) OLED_WriteCommand(0x3F); // 对于64行屏幕,值是63(0x3F) OLED_WriteCommand(0xD3); // 设置显示偏移(Display Offset) OLED_WriteCommand(0x00); // 无偏移 OLED_WriteCommand(0x40); // 设置显示起始行(Set Display Start Line) // ... 更多命令,如设置充电泵、内存地址模式、扫描方向、COM引脚配置等 OLED_WriteCommand(0x8D); // 电荷泵设置(Charge Pump Setting) OLED_WriteCommand(0x14); // 使能电荷泵(必须,否则屏幕不亮!) OLED_WriteCommand(0xA1); // 段重映射设置(Segment Re-map),0xA1左右反置,0xA0正常 OLED_WriteCommand(0xC8); // COM输出扫描方向(COM Output Scan Direction),0xC8上下反置,0xC0正常 OLED_WriteCommand(0xDA); // COM引脚硬件配置 OLED_WriteCommand(0x12); // 对于64行屏幕,通常是0x12 OLED_WriteCommand(0x81); // 设置对比度控制(Contrast Control) OLED_WriteCommand(0xCF); // 对比度值,范围0x00~0xFF OLED_WriteCommand(0xD9); // 设置预充电周期(Pre-charge Period) OLED_WriteCommand(0xF1); // 建议值 OLED_WriteCommand(0xDB); // 设置VCOMH电压倍率(VCOMH Deselect Level) OLED_WriteCommand(0x40); // 建议值 OLED_WriteCommand(0xA4); // 关闭全局显示开启(Disable Entire Display On) OLED_WriteCommand(0xA6); // 设置正常显示(Normal Display),0xA7为反色显示 OLED_WriteCommand(0xAF); // 开启显示(Display ON) }关键点解析:
- 电荷泵(0x8D, 0x14):这是最容易遗漏的一步!SH1106需要内部升压电路工作才能驱动OLED像素点。如果不发送
0x8D后跟0x14这个序列,屏幕就是一片漆黑,但通信却是正常的,会让你排查很久。 - 显示起始行(0x40):这个命令可以用来实现滚屏效果。
- 段重映射和COM扫描方向:
0xA1/0xA0和0xC8/0xC0这两组命令决定了屏幕的显示方向。如果你发现显示的内容是镜像或颠倒的,调整这两个命令即可,无需修改你的显示数据。 - 内存地址模式:SH1106支持页地址模式、水平地址模式和垂直地址模式。最常用的是页地址模式(Page Addressing Mode)。屏幕被水平分为8个“页”(Page),每页8行(bit),对应一个字节的高度。128列(Segment)则对应字节的宽度。所以整个64行高的屏幕,可以看作8页 x 128列的一个字节数组。
3.3 显存管理与刷新策略:高效更新的核心
驱动芯片内部有一个GDDRAM(图形显示数据RAM),我们的所有画点、画线操作,最终都是修改这个内存中的一个位(bit),然后通过刷新命令将整个内存内容更新到屏幕上。
我们需要在MCU端建立一个显存缓冲区(Frame Buffer),大小通常是128 x 64 / 8 = 1024字节(对于128x64屏幕)。所有绘图函数都先修改这个缓冲区,修改完成后,再调用一次OLED_Refresh()函数,将整个缓冲区一次性发送到OLED的GDDRAM。
刷新函数实现:
uint8_t OLED_DisplayBuffer[8][128]; // 二维数组,8页,每页128列 void OLED_Refresh(void) { for (uint8_t page = 0; page < 8; page++) { OLED_WriteCommand(0xB0 + page); // 设置页地址(Page Address) OLED_WriteCommand(0x00); // 设置列地址低4位(Column Address Low) OLED_WriteCommand(0x10); // 设置列地址高4位(Column Address High) // 以上三条命令将指针定位到当前页的第一列 for (uint8_t col = 0; col < 128; col++) { OLED_WriteData(OLED_DisplayBuffer[page][col]); } } }这种“双缓冲”策略(MCU端缓冲区 + 驱动芯片GDDRAM)是通用的做法。它的好处是避免了频繁的、零碎的对OLED芯片的访问,减少了通信开销,并且可以实现局部刷新(只更新改变的部分)以进一步提升效率。例如,如果你只更新了屏幕上一小块区域的文字,理论上可以只刷新对应的几页和几列数据,而不是全屏1024字节。
4. 高级功能实现与图形库构建
4.1 基础绘图函数:从点到面
有了显存缓冲区,实现基本绘图函数就是操作这个缓冲区里的位。
画点函数:这是所有图形的基础。
void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t mode) { // 参数检查:x:0~127, y:0~63 if (x >= 128 || y >= 64) return; uint8_t page = y / 8; // 计算在哪一页 uint8_t bit_pos = y % 8; // 计算在页内的哪一位 uint8_t bit_mask = 1 << bit_pos; if (mode) { // 画亮(置1) OLED_DisplayBuffer[page][x] |= bit_mask; } else { // 画暗/擦除(清0) OLED_DisplayBuffer[page][x] &= ~bit_mask; } }画线、画矩形、画圆:这些都可以基于画点函数,使用经典的计算机图形学算法(如Bresenham算法)来实现。这里以画水平线为例,展示一种更高效的方式,直接操作字节:
void OLED_DrawHLine(uint8_t x1, uint8_t x2, uint8_t y, uint8_t mode) { if (y >= 64) return; uint8_t page = y / 8; uint8_t bit_mask = 1 << (y % 8); uint8_t fill_byte = mode ? bit_mask : 0; for (uint8_t x = x1; x <= x2 && x < 128; x++) { if (mode) { OLED_DisplayBuffer[page][x] |= bit_mask; } else { OLED_DisplayBuffer[page][x] &= ~bit_mask; } } }对于填充矩形,可以优化为按页填充连续的列,大幅减少循环和判断次数。
4.2 字体显示与取模技术:让屏幕“说话”
显示文字是OLED最常用的功能之一。我们需要字模数据。获取字模通常有两种方式:
- 使用现成的取模软件:如PCtoLCD2002、取字模等。选择正确的格式至关重要。对于SH1106这种页地址模式,通常使用“纵向取模,字节倒序”或类似的设置。取模软件会为每个汉字(16x16点阵)生成32字节数据,为每个ASCII字符(8x16点阵)生成16字节数据。
- 使用嵌入式字体库:如U8g2库、LVGL内置字体。这些库功能强大,但可能占用较多ROM资源。
自定义字模显示函数:
// 显示一个16x16的汉字 void OLED_ShowChinese(uint8_t x, uint8_t y, const uint8_t *font_ptr) { uint8_t page = y / 8; uint8_t bit_offset = y % 8; uint8_t col, temp_byte; for (uint8_t i = 0; i < 16; i++) { // 16列 col = x + i; if (col >= 128) break; temp_byte = font_ptr[i]; // 写入当前页 if (bit_offset == 0) { OLED_DisplayBuffer[page][col] = temp_byte; } else { OLED_DisplayBuffer[page][col] |= (temp_byte << bit_offset); // 如果跨页,还需要写入下一页 if (page < 7) { OLED_DisplayBuffer[page + 1][col] |= (temp_byte >> (8 - bit_offset)); } } temp_byte = font_ptr[i + 16]; // 汉字下半部分 // 处理下半部分(对于16x16,下半部分从第16字节开始) if (bit_offset == 0) { OLED_DisplayBuffer[page + 1][col] = temp_byte; } else { // 下半部分也需要考虑跨页 OLED_DisplayBuffer[page + 1][col] |= (temp_byte << bit_offset); if (page + 1 < 7) { OLED_DisplayBuffer[page + 2][col] |= (temp_byte >> (8 - bit_offset)); } } } }这个函数考虑了字符显示起始位置Y坐标不是8的倍数时的跨页问题,这是很多简单驱动库忽略的地方,会导致显示字符时上下错位或缺失。
4.3 动画与实时数据刷新:打造动态界面
要实现流畅的动画(如时钟秒针跳动、波形图滚动),关键在于局部刷新和避免屏幕撕裂。
- 局部刷新:只更新屏幕上发生变化的部分区域。例如一个数字从“1”变成“2”,你只需要重画这个数字所在的矩形区域,而不是刷新整个屏幕。这需要你记录每个UI元素的位置和大小。
- 避免撕裂:在刷新显存的过程中,屏幕正在逐行扫描显示,如果此时显存数据被修改,可能会看到上半部分是旧帧,下半部分是新帧的“撕裂”现象。对于OLED,一个简单有效的方法是在两次完整刷新之间进行显存更新。即:在
OLED_Refresh()函数开始时,可以发送命令关闭显示(0xAE),刷新完所有数据后再开启显示(0xAF)。虽然会带来极短的闪烁,但保证了帧完整性。对于要求高的动画,可以使用“双缓冲区”切换,但这需要驱动芯片支持(SH1106通常不支持硬件双缓冲),或者在MCU端开辟两个缓冲区,在一个缓冲区绘制下一帧的同时,另一个缓冲区正在被刷新到屏幕。
实时时钟显示示例(伪代码):
char time_str[20]; uint8_t last_second = 255; void OLED_UpdateTime(void) { get_current_time(&hour, &min, &sec); // 从RTC或网络获取时间 if (sec != last_second) { // 只有秒数变化时才刷新 last_second = sec; sprintf(time_str, "%02d:%02d:%02d", hour, min, sec); // 1. 清除时间显示区域(比如用背景色填充一个矩形) OLED_FillRect(40, 20, 80, 30, 0); // 假设坐标和大小 // 2. 在新的时间字符串位置显示 OLED_ShowString(40, 20, time_str, 1); // 3. 局部刷新时间区域(需要实现局部刷新函数) OLED_PartialRefresh(40, 20, 80, 30); } }5. 实战调试与深度问题排查实录
5.1 上电无显示:系统性排查流程
这是新手遇到最多的问题。屏幕一片漆黑,但代码好像都对了。别慌,按以下步骤排查:
电源与接线检查:
- 电压:用万用表测量模块VCC和GND之间的电压,确保在3.3V或5V(根据你的连接)。
- 接线:逐根检查杜邦线是否松动、接触不良。SPI模式下,检查CS引脚是否被正确拉低选中。I2C模式下,检查上拉电阻是否已接(通常4.7kΩ到10kΩ)。
- 复位信号:如果接了RESET引脚,确保上电后有一个从低到高的跳变过程。可以尝试在初始化代码最前面主动拉低100ms再拉高。
通信协议与配置验证:
- I2C地址:使用逻辑分析仪或I2C扫描代码(很多开发环境有例程)检查总线上设备的地址是否正确。确认你代码中使用的地址是7位地址左移一位后的值。
- SPI模式与相位:用逻辑分析仪抓取SPI的CLK和MOSI波形。确认时钟空闲电平(CPOL)和采样边沿(CPHA)是否符合SH1106的Mode 0要求。这是SPI驱动中最常见的错误。
- 时序:检查通信速度是否过快。尝试降低SPI的波特率(如降到1MHz以下)或I2C的时钟速度(如标准模式100kHz)。
初始化序列确认:
- 电荷泵:这是重中之重!必须确认初始化序列中包含了
0x8D, 0x14(或0x8D, 0x10,具体看数据手册)这两条命令。没有它,屏幕绝对不亮。 - 对比度:尝试发送命令
0x81, 0xFF将对比度调到最大。 - 显示开关:确认最后执行了
0xAF(开启显示)命令。
- 电荷泵:这是重中之重!必须确认初始化序列中包含了
软件逻辑排查:
- 缓冲区数据:在调用刷新函数后,检查你的显存缓冲区
OLED_DisplayBuffer里是否真的有非零数据。可以尝试用memset将缓冲区全部填充为0xFF,然后刷新,看屏幕是否全亮。 - 刷新函数:单步调试或添加打印信息,确认
OLED_Refresh函数确实被调用,并且循环发送了1024字节的数据。
- 缓冲区数据:在调用刷新函数后,检查你的显存缓冲区
5.2 显示错乱、花屏、残影问题分析
如果屏幕亮了,但显示的内容不对,问题可能出在数据映射或刷新逻辑上。
镜像或颠倒:这是由段重映射(0xA0/0xA1)和COM扫描方向(0xC0/0xC8)命令控制的。如果你发现文字是反的,尝试修改这两个命令的组合。通常
0xA0和0xC0是正常方向。垂直错位或重叠:
- 跨页计算错误:这是字体显示函数中最容易出错的地方。仔细检查你的
OLED_ShowChar或OLED_ShowChinese函数中,当起始Y坐标不是8的整数倍时,对字节的移位和跨页合并计算是否正确。上面的示例代码提供了参考。 - 页地址设置错误:在刷新函数中,设置页地址的命令是
0xB0 + page,其中page范围是0~7。确保没有越界。
- 跨页计算错误:这是字体显示函数中最容易出错的地方。仔细检查你的
花屏/雪花点:
- 电源噪声:在OLED模块的电源引脚附近增加一个100uF的电解电容并联一个0.1uF的陶瓷电容,可以有效滤除干扰。
- SPI总线干扰:如果SPI总线旁边有电机、继电器等大电流设备,可能会引入噪声。尽量缩短连线,使用双绞线,或尝试降低SPI速度。
- 显存未清空:在初始化或清屏时,确保将整个
OLED_DisplayBuffer数组清零,并且执行了完整的刷新。
残影:
- OLED屏幕在长时间显示静态图像后切换时,有时会留下淡淡的“鬼影”。这不是硬件故障,而是OLED材料的特性。可以通过定期轻微移动显示内容(如像素偏移几个点),或者在全黑和全白之间进行几次刷新来减轻。在软件上,确保在显示新内容前,彻底清除旧区域的显存数据。
5.3 性能优化与资源管理技巧
在资源有限的MCU(如STM32C8T6)上驱动OLED,需要精打细算。
减少全局刷新:这是最有效的优化。只刷新屏幕上发生变化的“脏矩形区域”。你需要维护一个区域更新列表或标记。
使用硬件SPI和DMA:
- 硬件SPI:相比软件模拟SPI(Bit-Banging),硬件SPI不占用CPU资源,速度极快且稳定。务必使用硬件SPI。
- DMA传输:对于全屏刷新(1024字节),使用DMA可以将数据从内存搬运到SPI数据寄存器,CPU在此期间可以处理其他任务。这对于实现流畅动画至关重要。配置DMA时,注意设置数据宽度为字节,内存地址自增,外设地址不变(指向SPI->DR寄存器)。同时要处理好DMA传输完成中断,以便在传输结束后拉高CS片选信号。
精简字体库:
- 只包含项目需要的字符(ASCII码、常用汉字),可以显著减少ROM占用。使用自定义的取模数组,而不是完整的字库文件。
- 考虑使用高度更小的字体(如6x8像素)来显示次要信息。
显存缓冲区优化:
- 对于单色OLED,每个像素只用1 bit表示。如果你的MCU RAM极其紧张(只有几KB),可以考虑不使用全尺寸的1024字节缓冲区,而是采用“即时渲染”方式,画一个点就发送一个点。但这会严重降低性能并增加通信负载,只作为最后的手段。
5.4 与特定平台结合的注意事项
- ESP32-S3:ESP32的SPI主机驱动非常灵活,但要注意引脚映射。除了默认的HSPI和VSPI,几乎任何GPIO都可以作为SPI引脚。使用
spi_bus_initialize和spi_device_interface_config_t进行配置时,注意设置正确的clock_speed_hz、mode(0)、queue_size等参数。获取网络时间(NTP)并显示是ESP32的常见应用,注意处理好Wi-Fi连接、NTP请求和时区转换的逻辑,并在时间更新时只刷新时间显示区域。 - STM32 HAL库:HAL库的
HAL_SPI_Transmit在某些情况下可能会因为超时或错误状态而卡住。确保在调用前SPI处于就绪状态。对于DMA驱动,要小心配置DMA中断优先级,避免与其他高优先级中断冲突。 - CubeMX配置:使用CubeMX生成代码时,仔细检查SPI或I2C的配置参数。对于SPI,确认“Data Size”是8位,“CPOL”是Low,“CPHA”是1 Edge(即Mode 0)。对于I2C,确认时钟速度、自己的地址(如果是从机)等设置正确。
驱动一块小小的OLED屏幕,涉及硬件接口、通信协议、软件架构和性能优化的方方面面。从最初的“点不亮”到最终实现流畅的UI,这个过程充满了挑战,但解决问题的乐趣和成就感也正是嵌入式开发的魅力所在。希望这篇长文能帮你绕过我当年踩过的那些坑,更顺畅地让你的1.3英寸OLED (B)屏幕亮起来,并展现出你想要的精彩内容。记住,耐心和细致的逻辑分析,是调试硬件最好的工具。