尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

树莓派3B+WiringPi驱动0.96寸OLED屏幕实战指南

树莓派3B+WiringPi驱动0.96寸OLED屏幕实战指南 简介面向树莓派3B与I2C外设开发爱好者这是一份使用WiringPi库通过IICI2C协议驱动0.96寸OLED屏幕的完整驱动资源。资源针对SSD1306/SH1106控制器包含可直接阅读和修改的C语言源码、对应的头文件、makefile构建脚本以及编译生成的.o目标和可执行文件共10个文件、压缩包仅19KB。读者可以借此快速掌握WiringPi的GPIO复用配置、I2C地址探测与通信流程并结合makefile理解嵌入式项目的编译链接逻辑。项目代码结构简洁适合嵌入式入门者对照学习也适合已在树莓派上做显示类DIY项目的开发者直接移植或二次开发。已有946人浏览学习内容涵盖OLED初始化、清屏、坐标设置及基础图形绘制思路能有效缩短从接线到显示首屏内容的调试路径。 最近在工作室翻出一块吃灰很久的0.96寸OLED屏幕随手把它接在树莓派3B上用WiringPi库通过IIC总线点亮并显示内容。整个过程从接线、开启系统接口、装库、写驱动到最后屏幕上稳定显示字符前后大概半小时搞定。如果你也想在树莓派上用最小的硬件成本做一个小屏幕显示项目这篇文章可以帮你少走不少弯路。0.96寸OLED的驱动芯片是SSD1306分辨率128x64IIC接口版本只需要4根线。树莓派3B搭配WiringPi库是很多老嵌入式玩家最熟悉的组合API极其简洁。两者组合起来非常适合做小型状态屏、传感器数据显示屏、甚至简单的菜单界面。而且这个项目代码量不大核心逻辑对于入门I2C协议、理解显示驱动设计都很有帮助。下文按方案选型、环境搭建、驱动实现、问题排查四个部分完整走一遍你可以直接照着复现。1. 方案选型为什么是WiringPi IIC 0.96寸OLED1.1 三种常见驱动方案的取舍树莓派驱动0.96寸OLED常见有三种方式用Python的smbus2库、用C语言的WiringPi库、直接操作/dev/i2c-1设备文件做ioctl。Python方案代码量最少但由于解释器效率问题高频刷新时CPU占用明显偏高而且依赖第三方包在无网络环境下装包比较麻烦。直接ioctl的C方案性能最好但所有寄存器操作都要自己封装开发效率低。WiringPi正好处在中间它的I2C接口把底层的open、read、write和ioctl全包好了拿到文件描述符就能按SSD1306时序写数据既有C语言的性能又有接近Python的易用性。虽然WiringPi的作者在2019年之后就不再维护这个库但对树莓派3B这颗SoC来说WiringPi的引脚映射和I2C驱动非常稳定。实际使用中我并没有遇到因为停止维护而带来的问题。如果你用的是树莓派5或者更新内核的板子那确实建议另选方案但在3B上WiringPi仍然是性价比很高的选择。1.2 硬件接线与引脚确认硬件准备清单如下树莓派3B主板系统可以为老版Raspbian或新版Raspberry Pi OS。0.96寸OLED屏幕务必买4针IIC版本屏幕背面有SCL/SDA/VCC/GND四个焊盘。母对母杜邦线4根。接线很简单OLED的VCC接树莓派物理引脚13.3VGND接物理引脚6SCL接物理引脚5也就是BCM编号的GPIO3SDA接物理引脚3也就是BCM编号的GPIO2。有两点必须强调。第一VCC一定接3.3V不要图省事接到5V。SSD1306的I2C引脚不是5V tolerant长期接5V轻则屏幕发热重则烧掉驱动芯片。第二树莓派3B的I2C总线上已经板载了1.8k欧姆上拉电阻不需要额外并联上拉电阻直接飞线就能正常工作。我第一次做的时候还特意加了上拉纯属多此一举。1.3 软件环境与版本说明软件方面我用的是Raspberry Pi OS with desktopBullseye Legacy版内核5.10。不同版本的树莓派OS在开启I2C的方式上有细微差异下面会专门说明。如果拿到的是一份打包好的源码zip解压后通常会看到oled.c、oled.h、demo.c和Makefile这几个文件。我在实测中也用到了这样一个简单的工程结构。需要安装的软件包有gcc、git、i2c-tools和WiringPi。gcc在官方系统镜像里一般自带git没有就sudo apt install giti2c-tools是调试利器WiringPi建议从GitHub拉最新源码编译。如果apt下载速度慢先把系统软件源换成国内镜像源安装速度会快很多。2. 环境搭建开启I2C与安装WiringPi2.1 用raspi-config开启I2C接口树莓派默认关闭I2C接口这是很多人接好线、写完代码却发现没反应的头号原因。开启方式很简单sudo raspi-config进入后选择Interface Options再选I2C弹出的对话框选是然后重启。如果喜欢直接改配置也可以手动编辑/boot/config.txtsudo nano /boot/config.txt找到或添加一行dtparami2c_armon保存退出后重启然后检查内核是否挂载了I2C设备ls /dev/i2c*正常情况下会看到/dev/i2c-1。树莓派3B上只有这一个用户可用的I2C总线对应物理引脚3和5那一组。命令输出里如果还有别的i2c设备编号那是内部总线用于HDMI和摄像头跟屏幕没有关系也不要去操作它们。2.2 安装WiringPi库并验证WiringPi的安装是整个流程里坑最多的一步。老版本Raspbian里直接sudo apt install wiringpi就能装好但Debian 11之后的官方源已经把它移除了直接apt会提示找不到软件包。实测最稳的方案是从GitHub拉源码编译cd /tmp git clone https://github.com/WiringPi/WiringPi.git cd WiringPi ./build sudo ldconfig编译完成后运行gpio -v如果输出了WiringPi的版本信息就说明安装成功。GitHub仓库目前维护的版本是2.61在树莓派3B上跑过老版本2.52也跑过功能上没有差异。这里有一个容易让人困惑的细节做纯I2C显示时不一定要调用wiringPiSetup()来初始化引脚映射因为wiringPiI2CSetup内部走的是内核i2c-dev接口不依赖wiringPi的引脚编号模式。很多人习惯性地先调用wiringPiSetup()其实不写也一样能跑。2.3 用i2cdetect探测OLED地址这一步是验证硬件连接和总线状态的关键。先安装i2c-toolssudo apt install i2c-tools然后扫描总线sudo i2cdetect -y 1如果硬件接线和I2C开启都正常扫描结果里会看到一个地址0x3c。这就是SSD1306在I2C模式下的默认器件地址。要注意这个地址不是绝对固定的取决于屏幕背面的地址选择电阻。有一小部分模块的地址是0x3d。判断方法很简单扫描出现哪个就用哪个代码里替换掉就行。提示如果扫描结果全是英文横线“--”先别怀疑代码99%的情况是接线没接牢、VCC/GND接反或者I2C根本没开启。把这几个点重新检查一遍地址一般就出来了。3. 驱动实现SSD1306初始化与WiringPi IIC读写3.1 SSD1306使用原理页地址与显存映射写代码之前必须理解SSD1306的显存组织方式。SSD1306内置了一块128x64 bit的显存也就是总共1024字节每个字节表示8个连续的像素点。这块显存被分成8页每页128字节第0页是屏幕最上面8行第7页是屏幕最下面8行。当你想让屏幕上某个点亮起来并不是直接告诉芯片第几行第几列而是要计算出它属于第几页、在这一页内的字节偏移以及对应的bit位。这个特点让OLED的字符显示逻辑和普通终端完全不同。比如显示一个8x6的英文字符字模数组里是6个字节每个字节的bit从低到高分别代表该列8行的像素。理解了映射关系后面的绘制函数就不难写了。3.2 初始化序列那些命令都在干什么SSD1306上电后不会自动进入可显示状态必须按数据手册流程发送一串初始化命令。下面是一份实测通过的命令数组static const uint8_t oled_init_cmds[] { 0xAE, // display off 0xD5, 0x80, // 时钟分频因子 0xA8, 0x3F, // 复用率128x64用0x3F 0xD3, 0x00, // 显示偏移为0 0x40, // 起始行0 0x8D, 0x14, // 开启charge pump升压电路 0x20, 0x00, // 内存地址模式水平 0xA1, // 列段重映射X方向从左往右 0xC8, // COM扫描方向Y方向从上往下 0xDA, 0x12, // COM引脚配置 0x81, 0xCF, // 对比度207 0xD9, 0xF1, // 预充电周期 0xDB, 0x40, // VCOMH电压 0xA4, // 关闭全屏点亮模式 0xA6, // 正常显示 0xAF // display on };最容易导致屏幕不亮的是charge pump这一步。0x8D 0x14表示开启内部升压电路对没有外部升压电路的模块来说漏了这一条屏幕就永远是黑屏。另外0xA1和0xC8控制显示方向如果屏幕内容反了或上下颠倒就调整这两个字节。3.3 WiringPi的I2C函数封装WiringPi下操作I2C第一步是拿到设备文件描述符int fd wiringPiI2CSetup(0x3C); if (fd 0) { printf(I2C open failed\n); return -1; }拿到fd后向SSD1306发数据要遵循它的I2C传输格式每次传输的第一个字节是控制字节0x00表示后面是命令0x40表示后面是数据。建议直接用系统write发送比WiringPi封装好的wiringPiI2CWriteReg8更高效因为可以把控制字节和多个数据字节拼在一次调用里发出去#include unistd.h #include stdint.h static void oled_write(int fd, uint8_t *buf, int len) { if (write(fd, buf, len) ! len) { printf(I2C write error\n); } } static void oled_cmd(int fd, uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; oled_write(fd, buf, 2); } static void oled_data(int fd, uint8_t data) { uint8_t buf[2] {0x40, data}; oled_write(fd, buf, 2); }传一整帧数据时可以构造一个首字节为0x40、后面跟128个数据字节的数组一次write出去。我这里实测下来批量写入比逐字节调用WiringPi函数快将近一倍逻辑也更清晰。3.4 帧缓冲与字符显示实际工程里我不建议每次画点都直接往I2C总线写。每画一个点要发3条命令加1个数据刷一个128x64画面要跑几千次I2C传输效率太低。更合理的做法是在内存里维护一个8x128字节的帧缓冲先在缓冲里做所有绘图操作最后把整块缓冲一次性同步到OLEDuint8_t framebuffer[8][128]; // 8页每页128字节 void fb_set_pixel(int x, int y, int on) { if (x 0 || x 128 || y 0 || y 64) return; uint8_t page y / 8; uint8_t bit 1 (y % 8); if (on) framebuffer[page][x] | bit; else framebuffer[page][x] ~bit; } void oled_flush(int fd) { for (int page 0; page 8; page) { oled_cmd(fd, 0xB0 page); // 设置页地址 oled_cmd(fd, 0x00); // 列地址低4位 oled_cmd(fd, 0x10); // 列地址高4位 uint8_t buf[129]; buf[0] 0x40; memcpy(buf[1], framebuffer[page], 128); write(fd, buf, 129); } }显示字符的本质是取字模拷贝到buffer。一般英文字模用8x6或8x8格式网络上可以下载到ASCII字模数组。下面是一个基于帧缓冲的字符串绘制函数假设起点的y坐标是8的倍数这样不会跨页逻辑更简单static const uint8_t font6x8[][6] { // 以字符A为例实际数组包含ASCII码0x20到0x7F {0x00, 0x7c, 0x12, 0x12, 0x7c, 0x00}, }; void draw_string(int x, int y, const char *str) { int page y / 8; while (*str) { for (int i 0; i 6; i) { uint8_t c font6x8[*str - 0x20][i]; framebuffer[page][x i] c; } x 6; str; } }汉字的16x16字模原理完全一样只是每个字符有32字节绘制时把页和列的偏移相应放大即可。字模数组可以用PCtoLCD2002这类取模软件生成取模方式选择“列行式、正向”生成的数据才和SSD1306的页地址模式对得上。4. 编译实测与坑点排查4.1 编译运行与效果验证如果源码包里带了Makefile直接在目录里执行make就能编译出可执行文件。如果没有Makefile手动编译也很简单gcc -o oled_demo oled_demo.c -lwiringPi注意一定要加-lwiringPi链接库。运行的时候因为要访问/dev/i2c-1设备普通用户可能没有权限我一般直接加sudosudo ./oled_demo运行正常的话屏幕先整屏点亮一下自检然后第一行显示英文第二行显示数字和自定义字符。我在稳定供电下实测显示内容没有闪烁、没有残影连续运行几个小时后屏幕也只是微温。工程代码的main函数逻辑大致是打开I2C设备、初始化SSD1306、清屏、写字符串、刷新显存、循环延时。如果你想把刷新做成动画只要循环里反复改buffer并调用oled_flush就行不需要关屏幕。4.2 常见问题速查表我把这个项目里容易出现的问题整理成了速查表按顺序排查能解决大部分情况。现象原因解决办法i2cdetect扫描不到地址I2C未开启、接线错误、模块损坏重跑raspi-config开启I2C重插杜邦线换一块已知正常的模块测试扫描到0x3c但屏幕全黑初始化序列漏了charge pump检查是否发送0x8D 0x14确认代码执行到了0xAF扫描到地址但屏幕有噪点供电电压不稳或电源纹波大VCC接3.3V并和树莓派共地换短杜邦线显示内容上下颠倒COM扫描方向不对把0xC8改成0xC0显示内容左右镜像列段重映射不对把0xA1改成0xA0运行程序报Permission denied当前用户没权限访问I2C设备sudo执行或将用户加入i2c组画面刷新很慢逐字节调用WiringPi写接口改成批量write一次发整页128字节4.3 几个容易被忽略的细节最后说几个容易被忽略、但实际影响很大的细节。第一程序里第一次write失败时先确认wiringPiI2CSetup返回的fd是否合法。我遇到过一次open失败的情况排查很久结果系统里另一个进程占用了I2C总线杀掉进程就好了。第二不要在主循环里频繁发0xAE关显示和0xAF开显示来做闪烁动画。SSD1306的这些命令有内部时序要求过于频繁地开关屏幕容易出现闪烁甚至花屏。要做动画效果直接改帧缓冲内容就行。第三屏幕长时间显示静态内容OLED面板会有轻微像素老化这是物理特性和驱动代码无关。做长期显示项目时可以在程序里加一个屏保逻辑每隔一段时间把内容整体平移几个像素能延缓残影。最后聊点个人体会。树莓派3B虽然是好几年前的产物但做这类轻量显示控制项目性能依然绰绰有余。我后来把OLED驱动封装成了独立的oled.c和oled.h换其他树莓派型号时只需要改I2C设备号和屏幕地址代码主体基本不用动。调试时我习惯先i2cdetect确认地址再跑显示程序两步分开走能省下大量定位问题的时间。这个方案后续还可以扩展成中文菜单、传感器数据面板甚至桌面歌词屏一块不到十块钱的屏幕能玩出不少花样。本文还有配套的精品资源点击获取
返回列表