SH1106驱动OLED12864完整指南:IIC接线、列偏移与踩坑实录

SH1106驱动OLED12864完整指南:IIC接线、列偏移与踩坑实录 简介这份资源围绕1.3英寸OLED12864显示屏的SH1106驱动开发面向嵌入式初学者与STM32、C51开发者解决IIC接口下屏幕初始化、寻址与显示控制的实现问题。压缩包共210个文件约5.43MB包含大量C/H源码、工程配置文件、SH1106_V2.3.pdf与1.30-IIC.pdf数据手册、OLED12864SH1106显示地址表xlsx以及编译生成的hex固件等可覆盖从原理阅读、代码移植到烧录验证的完整流程。资料同时提供STM32与C51两套平台示例便于对照学习不同MCU上的驱动移植方法SH1106数据手册详细列出指令集、电气特性与引脚定义显示地址表则有助于快速定位像素内存映射。已有1403人学习下载适合正在调试OLED显示或希望深入理解IIC协议与显示控制器协同工作的开发者。 玩单片机的人很难绕开OLED屏0.96寸那批小屏几乎被SSD1306统治教程一搜一大把。但你如果买的是1.3寸、或者商家标着“兼容SSD1306”的模组点亮后问题就来了画面整体左偏两个像素、最左边冒出两根常亮竖线、或者干脆白屏——这时候大概率遇到的是SH1106。这篇博文不讲空泛理论直接把SH1106驱动OLED12864的完整流程拆给你看包括IIC接线、上拉电阻、协议细节、驱动移植以及我踩过的坑和排查手段。建议手里正好有SH1106模块、想把它用起来的兄弟收藏着慢慢看。1. 硬件连接与最开始的几个坑1.1 四根线怎么接市面上常见的OLED12864模块IIC接口就是四根线VCC、GND、SCL、SDA。VCC绝大多数接3.3V除非模块丝印明确写了支持5V否则老老实实接3.3V。我见过有人图省事直接怼5V结果模块烧了那个味道确实不好闻。SCL接主控的I2C时钟引脚SDA接数据引脚。如果你用的是STM32F103C8T6硬件I2C1的默认引脚是PB6SCL、PB7SDA用软件IIC模拟时任意两个GPIO都可以但建议选两个挨着的引脚方便布线。需要注意有些模块背面的BS0、BS1电阻决定接口模式如果你的模块是SPI/IIC两用版要先确认电阻位置让模块处于IIC模式否则SCL和SDA怎么接都不亮。再说一个经常被忽略的点RES复位脚。很多IIC模块把RES在板上拉不用管。但如果你买的模块把RES引出来了必须先给它一个复位脉冲或者至少保证是高电平不然芯片一直处于复位状态IIC地址发烂了也不会应答。1.2 上拉电阻别拍脑袋IIC总线是开漏结构高电平全靠上拉电阻“拉”上去。这个电阻取多大直接决定波形上升沿快不快通信稳不稳定。常见的取值是4.7kΩ但这不是万能答案。IIC标准规范里快速模式400kHz要求上升时间不超过300ns总线电容按100pF估算用公式tr 0.8473 × R × C算一下4.7kΩ大约是398ns已经超了换成2.2kΩ大约是186ns满足要求。所以我的建议是标准模式100kHz4.7kΩ~10kΩ都行快速模式400kHz建议2.2kΩ杜邦线超过20cm把上拉电阻往小了调1kΩ也可以试电源3.3V和5V都适用上述范围5V时电阻稍微大一点问题不大很多模块板上已经带了上拉电阻直接接主控就能工作。如果你发现屏幕偶尔花屏、波形边沿太缓可以在模块外部再并联一个2.2kΩ或者4.7kΩ实测对稳定性有帮助。另外STM32内部上拉弱得很别指望靠它带400kHz该外接就外接。1.3 IIC还是SPI先看需求再选SPI驱动OLED12864需要SCLK、MOSI、CS、DC四根线速度能到10MHz以上IIC只要两根线速度最快400kHz。一帧画面128×641024字节IIC 400kHz下理论传输时间约23ms还要算上ACK位和起始停止条件SPI 10MHz下只要1ms左右。如果你只是做个温湿度显示、小电压表、设备状态屏IIC完全够用省下的两根引脚能干很多事。如果你要跑动画、做简易示波器、或者刷新率要求很高老老实实选SPI或者并行接口。很多模块本身SPI/IIC都支持购买时问清楚驱动也要对应。2. IIC通信机制看懂时序才能排查2.1 起始条件、停止条件、ACK怎么理解IIC通信可以理解成“主机打节拍、从机应答”。SCL是时钟线主机控制节奏SDA是数据线双方在SCL高电平期间读数据。一个完整的写操作流程是这样起始条件SCL高电平期间SDA由高变低→ 发送地址字节 → 从机拉低SDA应答ACK→ 发送控制字节 → 应答 → 发送命令或数据 → 应答 → 停止条件SCL高电平期间SDA由低变高。这里要特别注意ACK每发送完8个bit第9个时钟周期由从机把SDA拉低表示“我收到了”。如果你用逻辑分析仪看波形发现第9个时钟上SDA没有被拉低说明从机压根没响应别再怀疑命令对不对了先查地址、供电和接线。对OLED来说IIC模式下发送的数据有个控制字节0x00表示后面跟的是命令0x40表示后面跟的是数据。很多驱动库内部就是靠这个区分“写命令”和“写数据”的比如我之前见过有人用HAL_I2C_Mem_Write去操作OLED把0x00当寄存器地址结果命令和数据全乱套白屏了半天。OLED不像MPU6050或EEPROM那样有寄存器地址它就是靠控制字节区分这是个很容易忽略的差异点。2.2 地址0x3C和0x78的关系IIC通信里每个从机都有自己的地址。SH1106的7位从机地址是0x3C这个“7位地址”在数据手册里写的是0111100。但实际发送的时候地址字节还要拼上读写方向位所以表示方式数值说明7位从机地址0x3C数据手册、传感器库里常见8位写地址0x78实际发送的地址字节写方向8位读地址0x79读方向OLED基本用不到HAL库的HAL_I2C_Master_Transmit函数里DevAddress参数要求填8位地址也就是0x78。如果你代码里写的是0x3C从机收到的实际上是0x1C二进制00111100地址对不上从机不会应答。我见过很多新手在这里卡了半小时最后才发现是地址左移没搞清楚。反过来如果你看到别人代码里写#define OLED_ADDR 0x3C那多半他调用的时候做了OLED_ADDR 1。两套写法都对怕就怕混着抄。我的建议是统一按8位地址0x78写省心。2.3 软件IIC和硬件IIC怎么选软件IIC就是用GPIO模拟时序好处是引脚随便选、代码移植性好、出问题了容易排查。缺点是速度慢而且如果主循环里有中断打断时序通讯容易被干扰。硬件IIC用MCU内部外设速度快、CPU不用管位时序但配置复杂一点STM32F103的硬件I2C在某些情况下还会遇到总线锁死的问题后面会讲。我的建议是第一次点亮SH1106这种模块先用软件IIC验证硬件连接和代码逻辑点亮了再切硬件IIC做性能优化。软件IIC只要把SCL拉高拉低、SDA按位输出逻辑非常直观出问题也好定位。很多开源库比如u8g2的软件IIC实现稳定得很跑个静态屏绰绰有余。3. SH1106驱动移植从初始化到显示3.1 和SSD1306的差异别直接套用SSD1306和SH1106都是12864 OLED的常用驱动芯片指令集高度相似以至于很多人把SSD1306的例程直接拷过来结果显示内容偏了两列。原因在于SSD1306的GRAM是128×64而SH1106的GRAM是132×64物理上多出4列实际可见的128列从GRAM第2列开始。也就是说写入GRAM第0列和第1列的数据在屏幕上根本看不见最左边会留下两条“盲区竖线”。解决思路就一句话写列地址时整体偏移2列。比如要往屏幕左边第一列写数据实际要设置GRAM列地址为2。很多成熟的驱动库内部已经处理了这个偏移但自己移植时很容易漏掉。另外一个差异是SSD1306有水平/垂直寻址模式SH1106虽然也支持设置寻址模式的命令但实际应用中最常用的还是页地址模式每页8行共8页列地址在写数据时自动递增。所以“能不能直接用SSD1306代码”这个问题答案是有条件地能初始化序列换成SH1106的列地址偏移2处理掉剩下的命令和数据写入逻辑基本通用。3.2 初始化序列与每条指令含义下面是我在STM32F103上实测可用的SH1106初始化序列逐条加了注释static const uint8_t OLED_InitCmds[] { 0xAE, // 关闭显示 0xD5, 0x80, // 显示时钟分频/振荡频率 0xA8, 0x3F, // 多路复用比1/64 duty 0xD3, 0x00, // 显示偏移为0 0x40, // 起始行0 0x8D, 0x14, // 开启电荷泵 0x20, 0x02, // 内存寻址模式页寻址 0xA1, // 段重映射配合0xC8使用 0xC8, // COM扫描方向反向 0xDA, 0x12, // COM引脚硬件配置 0x81, 0xCF, // 对比度0xCF是常用亮度 0xD9, 0xF1, // 预充电周期 0xDB, 0x40, // VCOMH电压 0xA4, // 输出跟随RAM内容 0xA6, // 正常显示不是反显 0xAF // 打开显示 };初始化完毕之后如果发现左右镜像比如字是反的把0xA1改成0xA0上下镜像则把0xC8改成0xC0。OLED模块走线不同这两个参数经常要按实物调不是固定不变的。对比度0xCF大约是中上亮度环境光暗可以降一点省电环境光强可以调到0xFF试试。3.3 核心代码命令/数据发送与坐标设置先搞定最底层的IIC发送。使用STM32CubeMX配置I2C1模式I2C速度为400kHz或100kHz都行。SH1106的IIC最高支持400kHz但杜邦线质量差时降速到100kHz更容易稳定。#define OLED_I2C_ADDR 0x78 // 8位写地址注意不是0x3C static void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; HAL_I2C_Master_Transmit(hi2c1, OLED_I2C_ADDR, buf, 2, 100); } static void OLED_WriteData(uint8_t dat) { uint8_t buf[2] {0x40, dat}; HAL_I2C_Master_Transmit(hi2c1, OLED_I2C_ADDR, buf, 2, 100); }控制字节0x00表示命令0x40表示数据。如果一次要发送多个连续的命令可以把0x00放在数组第一位后面直接跟一串命令字节写成一次HAL_I2C_Master_Transmit发送效率更高。同样写一整行显示数据时也可以0x40开头后面跟上128个数据字节。设置页地址和列地址的函数关键在于偏移2列void OLED_SetPos(uint8_t page, uint8_t col) { col 2; // SH1106列偏移不同SSD1306 OLED_WriteCmd(0xB0 page); // 设置页地址 0~7 OLED_WriteCmd(0x00 | (col 0x0F)); // 低列地址 OLED_WriteCmd(0x10 | ((col 4) 0x0F)); // 高列地址 }col参数是屏幕上的列号0~127函数内部自动加2这样上层代码就不用操心GRAM偏移的问题了。page是页号0~7每页对应8行像素。3.4 显存缓冲与批量刷新提速我强烈建议在RAM里开一块128×8字节的显存缓冲所有绘制操作先改缓冲最后统一刷新到屏幕。STM32F103C8T6有20KB RAM1KB缓冲完全不算事。uint8_t OLED_GRAM[8][128]; // 页优先存储 void OLED_Refresh(void) { uint8_t buf[129]; buf[0] 0x40; // 数据控制字节 for (uint8_t page 0; page 8; page) { OLED_SetPos(page, 0); memcpy(buf[1], OLED_GRAM[page], 128); HAL_I2C_Master_Transmit(hi2c1, OLED_I2C_ADDR, buf, 129, 100); } }这里有个提速细节不要每个字节都单独发一次IIC事务最好一次发129个字节1个控制字节128个数据字节一页一次事务整屏只要8次事务。如果每个字节都调一次HAL_I2C_Master_Transmit一次事务要经历START、地址、ACK、STOP一系列开销整屏1024字节刷下来至少要几十毫秒肉眼能看到明显闪烁。改成按页批量发送后刷新一屏可以做到10毫秒以内IIC在400kHz下静态显示非常流畅。如果没有缓冲也可以直接边算边往GRAM写省掉1KB内存适合RAM紧张的单片机比如51、AVR。但那样画图、反显、局部更新都比较费劲所以我建议大内存MCU一律用缓冲方案。3.5 画点、清屏、显示字符有了缓冲画点函数就非常简单void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t color) { if (x 128 || y 64) return; uint8_t page y 3; uint8_t bit y 0x07; if (color) OLED_GRAM[page][x] | (1 bit); else OLED_GRAM[page][x] ~(1 bit); } void OLED_Clear(void) { memset(OLED_GRAM, 0x00, sizeof(OLED_GRAM)); // OLED_Refresh(); // 由调用方决定何时刷新 }清屏不一定要立刻刷新可以等整个画面绘制完再一次刷新避免中间过程闪屏。显示字符时把取模好的字模数组按页写入即可比如显示一个6×8或8×16的ASCII字符在缓冲里把对应位置的字节填进去刷新后就能看到。中文显示更大一点通常16×16像素需要两页取模软件生成数组后照着同样的思路填充代码量也不大。4. 常见问题排查与效率技巧4.1 白屏、花屏分别怎么看白屏是最常见的故障。我的排查顺序是先量VCC是否是3.3V再量SCL和SDA有没有波形接着确认地址是不是0x78最后看RES引脚电平。如果SCL和SDA有波形但屏幕一点反应没有十有八九是地址不对或者模块实际不是SH1106。花屏分成两种一种是上电瞬间的随机乱码那是因为GRAM上电内容是随机的初始化之后先调用一次OLED_Clear再刷新基本能解决。另一种是显示过程中出现横线、杂点多半是电源纹波大、杜邦线太长、或者上拉电阻过大导致波形边沿太缓把上拉电阻调小、线剪短再试试。4.2 列偏移和镜像问题显示内容整体左移两列、或者最左侧有两条竖线几乎是SH1106驱动最典型的坑。原因就是3.1节说的132列GRAM和128列可见区域的问题。处理办法就是在设置列地址时偏移2也就是我代码里OLED_SetPos里的col 2。如果你是从SSD1306代码改过来的可以先检查这里有没有加偏移。镜像问题也很常见。屏幕字符整体反过来把段重映射从0xA1改成0xA0屏幕上下颠倒把COM扫描方向从0xC8改成0xC0。这两个指令可以组合不同模块的走线布局不一样以实物效果为准。改完记得重新初始化。4.3 I2C总线卡死与复位恢复STM32F103的硬件I2C有个老毛病在某些情况下SDA被从机拉低总线处于“锁死”状态HAL_I2C_Master_Transmit一直超时。大多数时候不是芯片bug而是启动时序或异常处理没做好。我的处理办法是初始化I2C前先把GPIO设为开漏输出手动给SCL/SDA几个脉冲把可能处于半通信状态的外设复位在HAL_I2C_Master_Transmit调用后检查返回值如果超时就调用HAL_I2C_DeInit重新初始化再继续发。并且所有发送函数的Timeout不要设成HAL_MAX_DELAY否则一旦总线卡住程序就死在里面出不来。设置成100~200ms就够了。另外如果用的是软件IIC这种情况基本不会发生因为GPIO模拟时序不受I2C外设状态机限制这也是我建议新手先软件IIC起步的原因。4.4 用逻辑分析仪精准定位排查IIC问题逻辑分析仪是神器。十几块钱的8通道逻辑分析仪配PulseView软件就很好用。接线很简单通道0接SCL通道1接SDAGND和模块共地。抓完波形后先看有没有正确的起始条件再解开码器看地址字节。如果显示帧里的地址是0x3C而不是0x78说明代码里地址没左移或传参传错了。接着看第9个时钟的ACKSDA有没有被从机拉低。如果每个字节都没有ACK回头查供电和地址如果有ACK但显示不对再检查控制字节是不是0x00命令和0x40数据以及命令内容有没有发错。这套流程走一遍基本能把问题缩小到具体某一层。4.5 问题速查表现象可能原因解决方案白屏、完全不亮供电异常、RES拉低、地址错误量电压检查RES确认地址0x78上电花屏GRAM随机值初始化后先清屏再刷新内容左移两列未处理SH1106列偏移列地址设置里加偏移2左右镜像段重映射方向不对0xA1改0xA0上下颠倒COM扫描方向不对0xC8改0xC0显示闪烁严重逐字节IIC传输按页批量发送1控制字节128数据字节刷新偶尔花屏上拉电阻过大、线太长换2.2kΩ电阻缩短杜邦线程序卡死在发送函数硬件I2C总线锁死、无超时设超时检查返回值I2C重新初始化不显示但总线有波形芯片是SSD1306或地址不同确认模块型号按对应驱动芯片初始化最后说点个人体会驱动这类屏幕大部分时间不是难在寄存器而是难在地址和偏移这种细节上。我建议新手第一次画驱动板先把OLED单独接到最小系统板上点亮确认是SH1106还是SSD1306再决定下一步否则后面所有代码都建立在错误假设上。手里有SH1106模组的把文中的初始化序列、列偏移和地址对照存一份基本可以少踩两小时坑。本文还有配套的精品资源点击获取