十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OLED屏取模全攻略:从原理到实战,正确配置SSD1306

OLED屏取模全攻略:从原理到实战,正确配置SSD1306 做嵌入式或者电子DIY的朋友十有八九都绕不开一块0.96寸的OLED屏。这玩意儿个头小、功耗低、亮度和对比度都很漂亮接上I2C或者SPI两三种线就能亮起来非常适合用来做个温湿度计、桌面小摆件、传感器数据面板甚至是一些小型可穿戴设备的人机交互界面。但很多新手第一次上手OLED屏的时候最容易卡住的反而不在屏幕本身而是在“取模工具”这四个字上——文字能显示稍微带个图片或者图标就乱码或者取出来的数据显示明明是对的屏幕却怎么都不对劲。这篇文章不给你讲虚的我直接把我自己做显示驱动时踩过的坑、理清楚的原理、还有一套能直接抄作业的取模参数配置方案一次性讲明白。核心就两件事OLED屏怎么稳定地显示文字和图片以及取模工具到底该怎么用才能不出错。全文基于我这几年在单片机项目里用0.96寸I2C接口OLED屏、SSD1306驱动芯片的实战经验同时包括我在PCtoLCD2002、Img2Lcd等常用取模软件里摸出来的最佳参数组合适合刚接触单片机的学生、做DIY项目的爱好者也适合想把显示这块做得更稳的嵌入式开发新手。1. 显示之前先把OLED屏的工作原理搞明白很多人拿到屏幕第一反应是翻数据手册然后对着SSD1306那一大堆寄存器定义发懵。其实没必要OLED屏驱动的核心逻辑比你想的要简单得多只要把显存和像素的对应关系弄清楚后面所有的问题都是“翻译层”的事。1.1 128x64的屏幕其实是一块1KB的“画布”市面上最常见的0.96寸OLED屏分辨率为128x64意思是横向128个像素点纵向64个像素点。这128乘64一共是8192个像素而SSD1306驱动芯片内部有一块1KB的GRAM显存正好对应8192个bit每个bit就是一个像素——为1就是点亮为0就是不亮。这里值得注意的一个细节是SSD1306的显存不是按“像素矩阵”连续排列的而是按照“页Page”来组织的。64行像素被分成了8页每页8行也就是Page0对应第0到7行Page1对应第8到15行以此类推。每一页里显存地址的第0位对应这一页最上边的那一行一次写入一个字节8个bit实际控制的是某一页里的8个垂直像素。这个“页”的概念是整个OLED显示驱动里最关键的底层逻辑。你后面如果遇到“显示出来的东西整块错位、上半块跑到下半块去”这种诡异问题99%是对页地址和扫描方向的理解出了问题。我在后面“常见问题排查”里会专门展开这里先记住一句话屏幕的读取顺序不是从左到右逐行扫的而是从上到下分8页每页里再从左到右逐列处理。这个认知决定了取模工具里的参数该怎么选。1.2 I2C和SPI怎么选接口选型直接决定后续调试成本OLED模组常见的接口有I2C和SPI两种另外还有少数带并行接口的8080/6800模式但DIY场景下基本不碰。我自己的习惯是能用I2C就优先I2C除非你明确知道项目需要高速刷屏比如做动画或者视频播放才考虑SPI。I2C接口最大的优势是省引脚整块屏幕只需要接VCC、GND、SCL、SDA四根线。VCC一般支持3.3V到5V不过稳妥起见我都默认用3.3V供电因为SSD1306的逻辑电平是3.3V长期5V直供虽然多数模组自带稳压和电平转换但有些便宜板子扛不住。I2C的缺点也很明显标准模式下传输速率只有100kbps到400kbps一次写入一帧128x64的数据1KB要分很多次事务整屏刷新一帧粗略估算需要几十毫秒对静态显示场景完全够用但做动画就会明显感到卡顿。SPI接口的传输速率可以到10MHz以上刷屏速度快很多代价是多几根线CS、DC、RES、SCLK、MOSI共7根左右调试起来也稍微繁琐一点。我的建议是做静态显示、传感器面板、菜单页面选I2C做动态波形、简易动画、需要高频刷新的场景用SPI。关于I2C地址还有一个容易踩的小坑绝大多数0.96寸OLED模块的I2C地址是0x3C但如果你买的模块背面有一个标注为“AD0”的电阻或者焊盘把它改到另一个位置后地址会变成0x3D。驱动初始化之前先确认好自己板子的地址否则屏幕怎么都不亮排查半天最后发现是地址不对这种经历我估计不少人有。1.3 为什么非要取模工具屏幕不认识字符只认点阵数据接下来就是这篇文章的核心关键词取模工具。为什么显示一个“A”或者一张小图标要先去取模工具里跑一遍因为OLED屏的驱动芯片不内置字库也不会帮你解码图片它能识别的只有最原始的位图点阵数据。举个例子你想在屏幕上显示一个8x8像素的英文字符“A”这个字符长什么样你得先用一个8行8列的网格把它画出来点亮的格子记为1不亮的记为0。然后你把这些数据按字节打包送进SSD1306的显存。别人帮你把“A”画成了一个8字节的数组你把它填进显存屏幕就能正确显示。问题在于如果是16x16的汉字一个字符就要32个字节手工去画点阵根本不现实效率太低也太容易出错。取模工具干的事情就是把这个“手工画点阵、按字节打包”的过程自动化。它在电脑端软件里把你想显示的字符、汉字或者图片BMP格式等解析成一个C语言数组你把这个数组复制到单片机工程里通过驱动函数写进显存屏幕就能显示出来。可以说没有取模工具OLED屏的实用性会大打折扣而取模参数设置错误则是绝大多数显示异常问题的根源。2. 字模数据的底层结构与取模核心参数详解要说清楚取模工具怎么用先得把取模的“语言”讲明白。同样一个字在不同取模工具里、用不同参数设置生成的数组完全两样显示到屏幕上的效果也千差万别。只有理解了这些参数背后代表的数据排列逻辑你才能做到“任它工具怎么变我自有一套配置”。2.1 一个字模数组背后是一张行扫描的位图以最典型的16x16汉字点阵为例。一个汉字的显示区域是16像素宽、16像素高也就是256个bit。按每8个bit凑一个字节来算一共是32个字节。取模工具的工作流程是先把字符渲染成内存里的一张16x16位图然后按照你选择的“取模方式”逐行或者逐列扫描这张位图碰到一个点亮点就记1没点亮就记0最后把每8个点拼成一个十六进制字节输出成数组。听起来不复杂但这里有一个决定兼容性的关键点工具扫描位图的顺序是“从左到右、从上到下”逐行式还是“从上到下、从左到右”逐列式扫描出来的bit是“高位在前”排列每字节的bit7对应最左边还是“低位在前”排列这些细节不同生成的数组看起来就会完全不同。而单片机的显示驱动芯片读显存时也有它自己固定的读法。取模工具的扫描顺序如果和驱动芯片的显存写入顺序不一致结果就是显示乱码或者字形错乱。所以你在配置取模工具时真正要做的只有一件事让“取模工具生成数据的排列顺序”和“OLED驱动芯片扫描显存的顺序”严格对齐。谁能对上号谁就能正确显示。2.2 取模工具的5个关键参数到底在控制什么我以PCtoLCD2002这款老牌取模软件为例这也是国内玩单片机的人用得最多的一款工具。打开取模选项你通常能看到下面这些设置每一项都有明确意义。第一是“阴码”和“阳码”的选择。阴码表示“点亮为1、不亮为0”生成的数据里每个字节中值为1的位代表亮点阳码相反点亮为0、不亮为1。对OLED这种主动发光、写1点亮的屏幕来说选阴码是直觉上最自然的。阳码通常用于LCD之类需要背光透过、逻辑恰好反相的屏幕。如果你在OLED上选错了阴阳码字会显示成反白效果就是你看到的那种“文字周围一块方块笔画反而是空心的”。所以OLED屏请默认选阴码。第二是“逐行式”和“逐列式”。逐行式是先把第一行的8个点、16个点处理完再处理第二行逐列式是先把第一列的8个点处理完再处理第二列。这里就要回到第一部分说的SSD1306显存结构。SSD1306的显存按页组织每页8行一次写入一个字节。这意味着写入数据时每一字节正好对应一页里的8个垂直像素。所以那种“先纵向取8个点作为一字节、再右移一列继续”的逐列式恰好在某些驱动模式下和SSD1306的显存结构天然匹配。但我个人在PCtoLCD2002里用的其实是“逐行式”因为我的显示驱动函数会在应用层额外做一遍坐标映射把逐行取出的数据重新按页地址正确填入。这个没有绝对的对错只看你的驱动代码怎么写。第三是“顺向”和“逆向”。这控制的是取模方向即扫描是从左到右还是从右到左、从上到下还是从下到上。显示字体会不会“镜像”“倒立”就是这一项决定的。OLED用的坐标系统一般是原点在左上角X向右增长、Y向下增长所以我取模方向都是用“顺向”。第四是“C51格式”或“汇编格式”这个纯粹是输出格式。C51格式会把每一行的二进制转换成一个以0x开头的十六进制字节并且用逗号分隔方便直接粘贴到C语言数组里。汇编格式则是生成DB之类的汇编伪指令我们做单片机开发基本都用C51格式。第五是“每行显示数”和“字符宽度/高度”这一类参数。取字模前你需要设定待取字符的宽高比如8x8对应ASCII字符16x16对应汉字或者32x32、48x48做更大字号。别小看这一步宽度不匹配会导致字模数组长短不一驱动函数按固定长度去读取时就会读串数据显示出来就是花屏。2.3 一份我实测下来的通用取模参数配置上面讲了原理这里直接给结论省得你自己一个个试。我在0.96寸OLED、SSD1306驱动、I2C接口、使用标准U8g2库或者自己写底层驱动的情况下最常用的PCtoLCD2002配置是这样的点阵格式阴码取模走向逐行式取模走向顺向从左到右、从上到下输出数制十六进制输出格式C51格式。自定义字符的宽高按需设定ASCII用8x8或6x12汉字用16x16。这套配置搭配我自己写的驱动函数在OLED上实测显示完全正常没有任何乱码或错位。如果你用的是U8g2库其实U8g2自带的字体文件里已经内置了常用字符集不需要额外取模但遇到U8g2没有的特殊图标或生僻字你还是得自己取模那时参数同样按这套来。使用其他图形库时先确认库本身的数据写入顺序再微调“逐行/逐列”即可方向感其实就是数据怎么进显存取模就怎么生成。3. 文字取模实操从PCtoLCD2002到屏幕显示参数理解了下面就是动手环节。我用PCtoLCD2002完整走一遍从输入文字到生成代码、再到单片机里成功显示的过程。顺便把“中文字库太大怎么处理”这个新手必修课也讲了。3.1 文字取模的完整步骤第一步打开PCtoLCD2002在左侧输入框里输入你要显示的字符。如果只是单个ASCII字符可以直接输入字母或数字如果是汉字可以直接打字进去软件会按你指定的宽高去生成点阵。第二步菜单里选择“设置”里的“字模选项”把参数改成第一部分说的那套阴码、逐行式、顺向、十六进制、C51格式。需要注意取汉字前还得在“字符宽度/高度”里把宽和设置成16或者你需要的其它字号因为PCtoLCD2002取汉字时默认会带一个“自定义字符”的设置如果宽度和高度不匹配16x16生成的数组长度就不对。第三步点击“生成字模”左侧画布会显示取出来的点阵预览右侧则输出一串十六进制数组。比如显示汉字“温”生成的可能是这样的32字节数组0x02, 0x04, 0x0C, 0x18, 0x02, 0x3F, 0x02, 0x02, 0x02, 0x7F, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x08, 0x70第四步把右侧这些十六进制字节复制出来粘贴到你的工程里命名成一个数组比如const uint8_t hanzi_16x16_wen[] {...};。然后你的驱动函数只要把这个数组按16x16格式填到屏幕对应坐标就能显示“温”字。3.2 代码接入把字模数组画到屏幕上拿到字模数组下一步是把数据写进显存。我用的是自己封装的一套基于SSD1306的驱动接口核心是两个函数一个是设置光标位置的OLED_SetPos(x, y)它就是算出页地址和列地址然后发给SSD1306命令另一个是显存写入函数OLED_WR_Byte(data, DC)区分命令还是数据。显示16x16汉字的函数会这样写void OLED_ShowChinese(uint8_t x, uint8_t y, const uint8_t *font_data) { uint8_t i, j; for (i 0; i 2; i) // 汉字占16行分两页每页8行 { OLED_SetPos(x, y i * 8); // 定位到第i页的起始位置 for (j 0; j 16; j) // 每页写入16列 { OLED_WR_Byte(font_data[i * 16 j], OLED_DATA); } } }这段代码的思路是16x16汉字在逐行式取模下前16个字节对应上半部分第0到第7行16列的数据后16个字节对应下半部分第8到第15行。所以要分两页写每页写16个字节。如果直接把32个字节一次性按顺序灌进显存显存里第0页会出现16个字节第1页出现16个字节由于SSD1306自己会按页地址递增这样反而是对的——很多网上共享的驱动函数就是利用这个连续性一次地址设置后连续写32字节即可。对于8x16的ASCII字符同样的逻辑但数据长度是16字节分两页每页8列。你只需要定义一个通用函数根据宽高参数循环填入点阵即可。这里一个比较实用的习惯是把字模数据所在的坐标和宽高一并传入函数方便后续做滚动显示或者菜单高亮。3.3 中文显示的最大坑字库体积与自造字方案OLED屏显示中文最大的坑不是怎么取模而是字库怎么存。全量二级汉字字库大约6000多个汉字即使压缩编码后在单片机Flash里也要占几十KB甚至上百KB空间。对STM32F103这种128KB到512KB Flash的芯片来说还好但对一些2KB Flash的8位单片机比如STC15系列来说完全存不下。所以OLED中文显示的常规做法不是“内置全字库”而是“用多少就取多少”。把项目里用到的所有汉字先列出来比如“温度 湿度 时间 日期 设置 状态 开关”然后用取模工具把这些汉字全部取出来做一个自定义的小字库表。这个字库表可以做成结构体数组typedef struct { uint8_t Index[4]; // 汉字UTF-8编码或者GB2312内码 uint8_t Msk[32]; // 16x16点阵数据 } Chinese_Font_t; const Chinese_Font_t my_font[] { {温, {0x02,0x04,...}}, {度, {0x01,0x02,...}}, // ... };显示函数里用传入汉字的编码去遍历这个表找到匹配项后再把Msk取出来调用上面的显示函数。这样Flash占用只跟项目里实际用到的汉字个数成正比几个汉字才几百字节非常省空间。如果项目后期要增加汉字只需要在表里追加一项重编译即可完全不用改显示代码。你可能还会遇到一个问题PCtoLCD2002默认取的汉字编码和你单片机源文件里保存的编码未必一致。我建议统一用UTF-8编码保存源文件和字库表取模时在软件里选好编码格式PCtoLCD2002的“选项”里有编码选择否则会出现“取模取的明明是‘温’代码里对比的却是乱码”的尴尬问题。4. 图片取模实操从BMP图片到OLED画面文字显示只是开胃菜真正让OLED屏项目看起来有质感的是图片和自定义图标。比如开机Logo、一个电池图标、一个WiFi信号符号这些都可以通过图片取模来实现。但图片取模比文字取模多了一个坑图片本身是灰度的OLED只有亮和不亮两种状态必须要先做“二值化”处理。4.1 图片预处理三步走第一步是尺寸调整。OLED屏的分辨率是128x64你要显示的图片尺寸要么是128x64整幅要么是局部区块比如64x64、32x32、128x32等。如果你要整幅显示必须先把图片缩放成128x64像素否则取模工具会裁剪或者变形。第二步是格式转换。取模工具通常支持读取BMP文件有些新版工具也能直接读PNG、JPG但为了兼容性我习惯先把图片转成BMP格式。转换时务必选“单色位图”或“1位色深BMP”。如果原图是彩色取模工具内部的二值化算法往往不可控出来的效果时好时坏在画图软件里先把图转成黑白单色再导出BMP这是一个减少不确定性的好习惯。第三步是二值化阈值调整。单色位图就是每个像素只有0和1黑白分明。如果你的图片里有灰色过渡二值化后可能会出现大片噪点或者轮廓断裂。所以我一般在PS或画图工具里先把图片对比度拉高、亮度调到中间值让画面里尽量只有黑色和白色没有过多灰色过渡。也可以借助取模工具自带的“亮度/对比度”或“阈值”调节来实时预览取模效果。Img2Lcd这个工具左侧有预览窗口右边调阈值时能直接看到最终效果我在处理复杂图片时最喜欢用这个“所见即所得”的调节方式调到自己满意再点保存取模。4.2 图片取模的参数细节与常见方案图片取模工具我用得比较多的是Img2Lcd这也是网上流传很广的一款软件。打开Img2Lcd把预处理过的BMP图片拖进去右边会显示图片信息。关键参数设置如下最大宽度和高度分别填128和64或你实际显示区域的尺寸。颜色选择“单色”。“按位压缩”要勾选。最大颜色数填2就是黑白二值。还有一项“扫描方式”可以选择水平扫描或垂直扫描这同样要和你的驱动函数的显存写入顺序对应。Img2Lcd在“输出数据类型”里可以选择C语言数组也会直接生成一个.c文件或者复制数组的选项。生成的数组格式通常是每8个像素合成一个字节高位在前这样的数据结构和SSD1306的一页8行很匹配写入函数可以直接逐字节填入指定页和列。我实际处理过的做法是整幅128x64的图片Img2Lcd生成的数组长度大约是1024字节128x64/8正好对应1KB显存。在单片机里用一个OLED_DrawBMP(uint8_t x, uint8_t y, uint8_t width, uint8_t height, const uint8_t *bmp)函数来刷显存。这个函数内部按8行一页来循环每循环一页先设置页地址再设置列地址然后一次性写入width字节的数据。4.3 大图片显示的分页写入策略在I2C接口下一次传输的数据长度不宜过长SSD1306在标准I2C速率下一次连续写入如果超过32字节可能因为底层缓冲和时序问题出现丢数据。所以我的OLED_DrawBMP函数里不会一次性写1024字节而是每写一页128字节就重新设置一次页地址。更稳妥的分法是按32字节切块每写完32字节就重发一次地址确保数据传输不会太长。实际测试下来按页写入稳定性已经足够不需要切太碎。整幅图片刷新还有一个速度问题。I2C 400kHz模式下传输1KB显存数据加命令开销大约需要20到30毫秒。如果你只是开机显示一次Logo这个速度完全没问题。但如果想做菜单高亮、翻页动画这个速度会导致明显的闪烁感。这种情况下一个常用策略是使用显存缓冲区buffer先在内存里把一帧画面画好然后一次性刷到屏幕或者在屏幕上只更新变化的局部区域不整屏刷新。我后面会在常见问题部分再展开说。4.4 混合显示让图片和文字出现在同一画面实际项目里图片和文字通常不是割裂的而是同时出现在一个界面里。比如一个环境监测页面左上角显示一个小图标中间是温湿度数字底部是时间。这种情况下如果你用了显存缓冲区其实非常简单缓冲区对应整个128x64显存显示文字就是往缓冲区里填点阵显示图片就是往缓冲区里贴位图最后统一把缓冲区灌给屏幕。如果你没有用缓冲区而是直接写SSD1306显存那就要注意别让图片覆盖了文字区域。我在设计页面布局时通常会先规划一个“显示区域字典”比如“图标区x0-y0坐标0,0到32,32数据区坐标40,0到120,32时间区坐标0,40到120,64”。然后显示函数按区域操作。这样即使刷新时各区域独立更新也不会互相干扰。我自己的经验是OLED小屏项目最好还是引入一个“逻辑坐标显存缓冲”的抽象层哪怕只是256字节的局部缓冲。因为直接操作物理显存虽然简单但一旦页面复杂起来管理坐标和避让关系会非常痛苦。缓冲区一引入很多问题就变成了普通的画点、画线、填色问题顺序也不重要了。代价只是多占用一点RAM对大部分MCU来说完全可以接受。5. 完整项目实战文字、图片、动态图标一次搞定前面讲了一堆原理和函数这里我把它们串起来做一个最简单也最典型的“温湿度显示面板”屏幕上显示“温度 25.6C”文字同时显示一个温度计图标过几秒切换到“湿度 60%”页面显示一个水滴图标。这个项目规模不大但麻雀虽小五脏俱全正好把文字取模、图片取模、动态切换都涵盖进去。5.1 整体工程结构与取模素材准备这个项目基于STM32F103C8T6最小系统板OLED为0.96寸I2C接口DHT11温湿度传感器只是模拟数据源先随机填充来演示显示逻辑。工程目录大概这样的oled.c/oled.hSSD1306底层驱动包含初始化、设置坐标、写入命令/数据、清屏。bmp.c/bmp.h从Img2Lcd生成的图标点阵数据比如温度计图标、水滴图标、WiFi图标。font.c/font.h从PCtoLCD2002生成的汉字点阵数据包含“温度”“湿度”这几个字。main.c主逻辑负责按时间切换页面、刷新本地缓冲。取模素材准备阶段我先把要用的汉字“温度湿度时间日期”放进PCtoLCD2002按16x16、阴码、逐行式生成数组存进font.c。再把温度计图标和水滴图标分别做成32x32的BMP图片用Img2Lcd转成数组存进bmp.c。图片的格式统一为128x64整幅或局部区块驱动函数按区域写入。5.2 关键代码带缓冲的显示驱动示例我用一个128字节的局部缓冲来演示图片区块的写入方式。这个例子里的OLED_DrawBMP接收左上角坐标以及宽高计算好要覆盖的页数逐页写入。对于文字部分则调用OLED_ShowString往缓冲区里叠加。void OLED_Display_TempPage(void) { OLED_Clear(); // 清空显存缓冲 // 在左上角显示温度计图标32x32由Img2Lcd生成 OLED_DrawBMP(0, 0, 32, 32, temp_icon_bmp); // 第一行显示温度两个字 OLED_ShowChinese(40, 0, 温度); // 第二行显示数值字符串 OLED_ShowString(40, 20, 25.6 C, Font8x16); // 刷到物理显存 OLED_Refresh(); }OLED_Refresh这个函数是核心。它把缓存的128x64位图数据分8页刷进SSD1306。代码大概是这样的void OLED_Refresh(void) { uint8_t i; for (i 0; i 8; i) { OLED_SetPos(0, i); // 设置页地址为i列地址为0 for (uint16_t j 0; j 128; j) { OLED_WR_Byte(g_disp_buf[i * 128 j], OLED_DATA); } } }把整屏数据存到g_disp_buf[1024]这个数组里所有显示函数都往这个数组里填最后统一刷新。这样图片和文字叠加的时候顺序就无所谓了谁后画谁就显示在上层不会出现以前那种“我先写文字再写图片文字被冲掉一半”的问题。5.3 页面切换与动态图标实现页面切换的核心逻辑就是用一个状态机变量每隔3秒改变一次显示内容。这里要注意在I2C下刷新整屏要20到30毫秒如果刷得太频繁会明显感到闪烁。我实测一个比较舒服的切换节奏是文字信息从更新到刷新间隔至少200毫秒以上整屏页面切换间隔1到2秒人眼看起来流畅自然。动态图标实现的思路也很简单准备两帧不同的图标数据比如同一个图标但位置偏移1个像素或者图标下方加一条动态横线。每隔一定时间切换一次帧数据看起来就有动画效果。归根到底OLED屏的“动画”就是快速切换多帧位图受限于刷新率做局部小图标动画比全屏动画靠谱得多。在这个1500字节不到的小工程里文字取模、图片取模、页面管理、动态图标都覆盖了代码逻辑清晰也方便后续往里面塞菜单、按键、多级界面。做完这个小项目之后OLED屏在手里基本就没有秘密了。6. 常见问题排查与避坑实录最后这部分我把自己在OLED显示项目中遇到过的典型问题整理成一个速查表附带排查思路。这些问题在网上几乎每天都有人在问希望这份经验能帮你少走弯路。6.1 屏幕不亮、白屏或花屏先查这4个地方现象上电后屏幕完全没反应或者整个屏幕亮成白色或者显示一堆噪点。排查顺序依次是第一I2C地址。先确认你的模块是0x3C还是0x3D有些例程写死0x3C遇到0x3D的板子就白屏。第二接线。SCL接SCL、SDA接SDA别接反VCC和GND极性别反。第三供电。OLED模块如果和舵机、电机共用一个电源启动瞬间电压跌落可能让屏幕初始化失败最好单独用3.3V稳压供电。第四初始化时序。SSD1306上电后需要先执行一串初始化命令包括关闭显示、设置时钟分频、设置复用率、设置偏移、开启电荷泵等。有些例程没开启电荷泵Charge PumpOLED就会白屏或者不亮。这个是最常见的原因之一。6.2 显示乱码、字形错位几乎都是取模参数没对齐现象能显示内容但完全不是想要的形状笔画乱跳汉字上下部分错位或者镜像倒立。这种问题不用怀疑代码逻辑就是“数据顺序”和“显存写入顺序”不匹配。排查方法是先确认取模工具里用的是“逐行式”还是“逐列式”然后去看你驱动程序里OLED_SetPos(x, y)之后是横向写满一行再换页还是纵向写满一页再换列。前者对应逐列式取模后者对应逐行式取模。另外确认阴码/阳码是否选对选错会出现反白效果。我自己调试时有个笨但很有效的办法故意显示一张“从上到下、从左到右全是黑白相间条纹”的测试图片取模后如果显示出来也是横平竖直的条纹说明扫描方向对齐了如果显示出来是斜的、花的那就把“逐行/逐列”切换一下再试。几次下来就能定位到问题。6.3 图片显示太暗、全是噪点或者发灰现象图片显示出来黑糊糊一片细节丢光或者背景全是雪花点。原因基本在二值化环节。原图里有太多灰色过渡像素二值化算法把它们随机判成黑或者白就成了噪点。解决办法预处理图片时先转成纯黑白轮廓图用画图工具手动修一下边缘或者用Img2Lcd的阈值调节功能在预览窗口实时观察找到一个合适的阈值把噪点滤掉。另外如果图片颜色通道有问题比如BMP是24位但填了16位数据也会导致取模出来的数据完全错位记得统一按1位BMP导出。6.4 刷新慢、闪屏、拖影怎么优化显示性能现象整屏刷新时有明显闪烁像老式显示器刷新率不足或者画面有拖影。这个问题在I2C接口下尤其明显。优化思路有3个层次。第一缓冲优先。别每次只改一个像素就立刻写显存频繁的小事务非常浪费时间。应该先在RAM里把整帧画好需要显示时再一次性刷屏。第二局部刷新。如果你只是更新一个温度数值完全没有必要整屏刷。只把那块数字区域的坐标算出来调用一次OLED_SetPos再写入该区域的点阵数据即可。实际刷新面积从128x64降到32x16刷新时间可以缩短到原来的十分之一。第三换SPI。如果刷屏需求实在很高比如做示波器波形、动画界面直接把硬件换成SPI接口OLED速度提升立竿见影。这里还有一个值得说明的细节SSD1306支持“页地址模式”和“水平地址模式”。页地址模式下写完当前行最后一列后地址会自动滚到下一页的同一列水平地址模式下写完一行末尾后会自动跳转到下一行开头。这和取模工具的“逐行/逐列”设置直接相关。如果你用的是水平地址模式逐行式取模就会很顺手如果用的页地址模式逐列式取模可能更合适。建议初始化驱动时固定使用水平地址模式然后取模统一选逐行式这套组合我用了很久稳定。6.5 几个提升开发效率的小技巧最后分享几个我实际用下来很顺手的小技巧。第一在PCtoLCD2002里把常用符号、数字、字母做成一整张“ASCII字库表”一次性取出来作为固定文件放进工程以后做任何OLED项目都能直接复用。第二用Excel或者脚本批量把取模工具生成的长数组按行格式化方便阅读和排查。第三在代码里加一个调试模式可以通过按键切换显示“测试图”“纯白图”“纯黑图”快速确认屏幕硬件和接线是否正常。第四I2C总线上拉电阻一定要存在很多模块板载已经带上拉了但有些最小系统板需要你在SCL和SDA上各加一个4.7k到10k的上拉电阻否则数据线电平不稳定会出现时好时坏的随机花屏。OLED屏显示这件事说难不难说简单也不简单。硬件接线只是第一步真正的核心功夫全在“取模”和“显存对应关系”这两个环节上。把这个对应关系吃透了不管是文字、图片、图标还是动画无非都是往显存里填数据的排列组合问题。如果你照着这篇文章里的方法和参数配置去操作我相信绝大多数显示问题都能一次性解决。遇到问题的时候先别急着怀疑屏幕坏了回头检查一下取模参数和刷新策略多半问题就在那里。
返回列表