0.96寸IIC单色OLED屏(SSD1306)驱动移植与显示应用实战

最近在做一个需要小型显示屏的项目,手头正好有一块0.96寸的OLED屏,驱动芯片是SSD1306,接口是IIC。这种小屏功耗低、体积小,显示效果也不错,非常适合用在嵌入式设备上做状态显示或者简单的人机交互。但网上的驱动代码五花八门,移植到自己的开发板上总会出现各种问题,比如引脚不对、时序不对、显示乱码等等。

今天我就以“地奇星”开发板为例,手把手带你把这套OLED驱动移植过去,并且实现字符、汉字、图形甚至图片的显示。整个过程我会把每一步的原理和注意事项讲清楚,让你不仅能“抄作业”,更能理解背后的逻辑,以后换别的开发板也能自己搞定。

1. 认识我们的“小屏幕”:硬件与接线

在开始写代码之前,咱们先得把硬件搞清楚。这块0.96寸OLED屏的核心是SSD1306驱动芯片,它通过IIC总线和我们单片机通信。

1.1 屏幕关键参数

根据资料,这块屏的主要参数如下:

参数项 规格说明
工作电压 3.3V (非常重要,接5V可能会烧坏!)
工作电流 约9mA (非常省电)
模块尺寸 27.3mm x 27.8mm (非常小巧)
分辨率 128 x 64 像素 (单色,每个像素点只有亮或灭两种状态)
驱动芯片 SSD1306
通信协议 IIC (只需要两根线)
管脚数量 4 Pin (2.54mm间距排针)

四个引脚分别是:GND (地), VCC (电源,接3.3V), SCL (IIC时钟线), SDA (IIC数据线)。

注意:一定要确认你的开发板IO口是3.3V电平的。如果是5V单片机,需要在IIC线上加电平转换电路,或者寻找支持5V的屏幕型号,否则可能通信失败甚至损坏屏幕。

1.2 与开发板连接

接下来就是把屏幕和“地奇星”开发板连起来。连接非常简单,就像接四根导线:

OLED屏幕引脚 连接到开发板
GND 任意GND引脚
VCC 3.3V电源引脚
SCL P407 (这是IIC的时钟线,你可以根据实际情况换到其他IO)
SDA P408 (这是IIC的数据线,需和SCL配对使用)

这里我选择P407和P408作为IIC引脚,你完全可以根据自己板子的资源分配情况,换成其他任意两个GPIO口,只要在代码里改一下宏定义就行。IIC通信对时序要求严格,但对引脚没有特殊要求,普通IO口模拟即可。

硬件连接好之后,咱们就可以进入软件部分了。

2. 驱动移植:把代码“搬”到你的工程里

移植驱动,说白了就是把别人写好的、能在别的板子上跑的代码,修改成能在你的板子上跑。这个过程一般分三步:拿代码、改引脚、调时序。

2.1 获取驱动源码

首先,你需要拿到OLED的驱动源码。通常包含以下几个关键文件:

  1. oled.holed.c:核心驱动文件,包含初始化、画点、显示字符等函数。
  2. oledfont.h:字库文件,里面存放了显示ASCII字符和汉字需要的点阵数据。
  3. 可能还有bmp.h之类的图片数据文件。

你可以从提供的资料链接下载,或者从其他开源项目(如正点原子、野火等)的例程中找到针对SSD1306的驱动。把oled.h, oled.c, oledfont.h这三个文件复制到你自己的工程目录下,比如/User/OLED/文件夹里。

然后在你的IDE(如Keil, IAR等)中,将这些文件添加到工程里。以Keil为例,在项目窗口右键点击你的工程分组,选择“Add Existing Files to Group...”,然后找到并添加这几个.c.h文件。

2.2 修改引脚配置(最关键的一步)

这是移植的核心。原始驱动代码里,控制SCL和SDA的宏定义是针对原作者板子的,我们必须改成自己板子的引脚。

打开oled.h文件,找到下面这部分的代码:

//-----------------OLED端口定义----------------
#define SCREEN_SCL_PIN             BSP_IO_PORT_04_PIN_07 //SCL
#define SCREEN_SDA_PIN             BSP_IO_PORT_04_PIN_08 //SDA

#define OLED_SCL_Clr()  R_IOPORT_PinWrite(&g_ioport_ctrl, SCREEN_SCL_PIN, 0)//SCL
#define OLED_SCL_Set()  R_IOPORT_PinWrite(&g_ioport_ctrl, SCREEN_SCL_PIN, 1)

#define OLED_SDA_Clr()  R_IOPORT_PinWrite(&g_ioport_ctrl, SCREEN_SDA_PIN, 0)//DIN
#define OLED_SDA_Set()  R_IOPORT_PinWrite(&g_ioport_ctrl, SCREEN_SDA_PIN, 1)

这里的BSP_IO_PORT_04_PIN_07BSP_IO_PORT_04_PIN_08是“地奇星”开发板对P407和P408引脚的宏定义。R_IOPORT_PinWrite是这个开发板库函数里控制引脚高低电平的函数。

你需要根据自己使用的开发板和单片机型号来修改:

  • 如果你用的也是“地奇星”开发板,并且接的正是P407和P408,那么这部分代码可以不用改。
  • 如果你用的是STM32,你可能需要改成类似GPIO_PIN_6这样的定义,并且将R_IOPORT_PinWrite替换成HAL库的HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET),或者标准库的GPIO_SetBits(GPIOB, GPIO_Pin_6)
  • 如果你用的是Arduino,那就更简单了,直接digitalWrite(SCL_PIN, HIGH)

原则就是:找到你开发环境下控制某个GPIO输出高电平(1)和低电平(0)的方法,然后替换掉上面的四个宏定义。

2.3 修改延时函数

IIC通信对时序有严格要求,所以驱动里用到了微秒级延时delay_us。在oled.h的开头,我们看到它用开发板的软件延时函数R_BSP_SoftwareDelay来实现了各种延时:

#ifndef delay_ms
#define delay_ms(x)   R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MILLISECONDS)
#endif
#ifndef delay_1ms
#define delay_1ms(x)  R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MILLISECONDS)
#endif
#ifndef delay_us
#define delay_us(x)   R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MICROSECONDS)
#endif
#ifndef delay_1us
#define delay_1us(x)  R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MICROSECONDS)
#endif

同样,你需要根据你的平台替换这些延时函数。例如在STM32的HAL库中,delay_us通常可以用HAL_Delay()(毫秒级)配合系统滴答定时器自己封装一个。在无操作系统的环境下,经常用空循环来实现微秒延时,但要注意不同主频的CPU循环次数需要调整。

提示:IIC时序对delay_us的精度要求不是特别高,差一点通常也能工作。如果屏幕初始化失败但接线和引脚确认无误,可以尝试稍微调整IIC_delay函数中的延时时间(比如从3微秒调到5微秒)。

完成以上两步,驱动层的主要修改就完成了。接下来我们看看怎么使用这些驱动函数来显示内容。

3. 驱动解析与应用:让屏幕“活”起来

移植好驱动后,oled.c里提供了丰富的函数。咱们挑几个最常用的,看看它们是怎么工作的,以及怎么用。

3.1 核心机制:显存与刷新

SSD1306芯片内部有一块对应的显示缓存(GRAM)。我们所有画点、画线、写字的操作,实际上都是在修改单片机内存中的一个二维数组OLED_GRAM[144][8](这个数组大小是128x64比特的另一种组织方式)。

当你调用OLED_DrawPoint(10, 20, 1)在坐标(10,20)画一个亮点时,程序会计算这个点对应OLED_GRAM数组中的哪一位,并将其置1。注意,这时候屏幕本身还没有变化!

必须调用OLED_Refresh()函数,这个函数才会把整个OLED_GRAM数组的数据,通过IIC总线,一股脑地发送到SSD1306芯片内部的GRAM中,屏幕才会更新显示。这种“先修改缓存,再统一刷新”的方式效率很高。

所以,一个典型的显示流程是:

OLED_Init();          // 1. 初始化屏幕
OLED_Clear();         // 2. 清空缓存(全黑)
OLED_DrawPoint(...);  // 3. 在缓存上画图
OLED_ShowString(...); // 4. 在缓存上写字
OLED_Refresh();       // 5. 将缓存内容刷到屏幕,真正显示出来

3.2 基础显示函数怎么用

oled.h中声明了很多函数,我们来看看几个最重要的:

1. 初始化与清屏

void OLED_Init(void);   // 初始化OLED,必须最先调用
void OLED_Clear(void);  // 清屏(全黑)
void OLED_DisPlay_On(void);  // 开启显示
void OLED_DisPlay_Off(void); // 关闭显示(省电模式)

2. 画图函数

  • OLED_DrawPoint(u8 x, u8 y, u8 t): 在(x,y)坐标画点,t=1点亮,t=0熄灭。坐标x范围0~127,y范围0~63。
  • OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2,u8 mode): 从(x1,y1)到(x2,y2)画直线。
  • OLED_DrawCircle(u8 x,u8 y,u8 r): 以(x,y)为圆心,画半径为r的圆。
  • OLED_ShowPicture(u8 x,u8 y,u8 sizex,u8 sizey,u8 BMP[],u8 mode): 显示一张位图。BMP[]是一个数组,存放了图片的点阵数据。

3. 显示字符与字符串 这是最常用的功能。

void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 size1, u8 mode);
void OLED_ShowString(u8 x, u8 y, u8 *chr, u8 size1, u8 mode);
void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size1, u8 mode);
void OLED_ShowChinese(u8 x, u8 y, u8 num, u8 size1, u8 mode);
  • x, y: 显示起始的左上角坐标。
  • chr*chr: 要显示的字符或字符串。
  • size1: 字体大小,支持8(6x8), 12(6x12), 16(8x16), 24(12x24)。注意,这里参数是字符高度,宽度通常是高度的一半。
  • mode: 0表示反色显示(黑底白字),1表示正常显示(白底黑字,OLED上是亮底暗字)。
  • num (在ShowChinese中): 不是汉字本身,而是汉字在字库数组Hzk1, Hzk2等中的索引号。你需要事先在oledfont.h里定义好你的字库,并知道每个汉字的编号。

3.3 实战:编写你的显示程序

现在,我们参考提供的例程,在main函数或者你的应用任务里写一个显示流程。下面我写一个简单的例子,演示如何显示一段文字和数字:

#include "oled.h"

int main(void)
{
    // 系统初始化...
    HAL_Init();
    SystemClock_Config();
    // ... 其他外设初始化

    // 1. 初始化OLED
    OLED_Init();
    OLED_ColorTurn(0); //0正常显示,1 反色显示
    OLED_DisplayTurn(0); //0正常显示 1 屏幕翻转显示

    // 2. 清屏
    OLED_Clear();

    // 3. 在缓存上绘制内容
    OLED_ShowString(0, 0, "Hello OLED!", 16, 1); // 在(0,0)显示16像素高的字符串
    OLED_ShowString(0, 20, "Voltage:", 16, 1);
    OLED_ShowNum(70, 20, 330, 3, 16, 1); // 在(70,20)显示3位数字330
    OLED_ShowString(100, 20, "mV", 16, 1);

    // 画一条分割线
    OLED_DrawLine(0, 40, 127, 40, 1);

    // 显示一个汉字(假设“测”字在字库中的索引是10)
    OLED_ShowChinese(0, 48, 10, 16, 1);
    OLED_ShowString(18, 48, "Test OK!", 16, 1);

    // 4. 最关键的一步:刷新到屏幕
    OLED_Refresh();

    while(1)
    {
        // 你可以在这里动态更新显示内容,比如刷新传感器数值
        // 注意:每次更新后都要调用OLED_Refresh()
    }
}

4. 调试与常见问题排查

移植过程中,最容易出问题的地方就几个,咱们来盘点一下:

问题1:屏幕完全不亮,没有任何反应。

  • 检查电源:首先用万用表量一下VCC和GND之间是不是3.3V。确认屏幕供电正常。
  • 检查接线:确认SCL、SDA、GND、VCC四根线没有接错、没有虚焊。
  • 检查初始化:确保OLED_Init()函数被正确调用。可以在OLED_WR_Byte函数里设置断点,看是否有IIC数据发出。

问题2:屏幕亮但显示乱码、花屏。

  • 检查IIC时序:这是最常见的原因。可能是延时delay_us不准确。尝试增大或减小oled.c文件中IIC_delay函数里的延时值。
  • 检查显存刷新:确认在修改显示内容后,调用了OLED_Refresh()。只画点不刷新,屏幕是不会变的。
  • 检查字库:如果只是汉字显示乱码而英文正常,那很可能是oledfont.h里的汉字点阵数据不对,或者OLED_ShowChinese函数中使用的字库索引num不对。

问题3:显示内容错位或镜像。

  • 使用旋转函数OLED_DisplayTurn(1)可以让屏幕显示旋转180度。OLED_ColorTurn(1)可以反色显示。如果你发现显示是反的,可以调用这两个函数调整。

问题4:编译通不过,提示引脚函数未定义。

  • 检查宏定义替换:回头仔细检查oled.h里关于OLED_SCL_Set, OLED_SDA_Clr等宏定义,是否替换成了你当前平台正确的GPIO控制语句。确保相关的GPIO时钟和模式初始化在别处已经完成(通常在主函数初始化阶段)。

最后,把工程编译、下载到开发板,上电。如果一切顺利,你应该能看到OLED屏上清晰地显示出你设定的文字和图形。这个过程虽然会遇到一些小麻烦,但成功点亮的那一刻,成就感还是满满的。这套驱动函数功能比较全,掌握了基本显示后,你完全可以尝试用它来画个简单的图形界面,或者实时显示传感器数据,让你的嵌入式项目更加生动。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐