天空星HC32F4A0开发板驱动0.96寸IIC OLED屏(SSD1306)移植指南
最近在天空星HC32F4A0开发板上做个小项目,需要接个屏幕显示点信息,就选了最常用的0.96寸OLED屏。这种小屏功耗低、显示清晰,用IIC接口接线也简单,但网上的例程大多是针对STM32的,直接拿来用肯定不行。折腾了一下午,总算把驱动移植成功了,这里把完整的移植过程分享给大家,手把手教你搞定。
1. 准备工作:了解你的OLED屏
在开始移植之前,咱们先搞清楚要驱动的对象是什么。这次用的是一块0.96英寸的单色OLED显示屏,核心驱动芯片是SSD1306。
这块屏有几个关键特点:
- 通信接口:IIC(也叫I2C),只需要两根线(SCL时钟线和SDA数据线)就能通信,非常节省IO口。
- 供电电压:3.3V,正好和天空星开发板的IO电平匹配,可以直接连接,不需要电平转换。
- 分辨率:128x64像素,虽然不大,但显示文字和简单图形完全够用。
- 物理接口:4个2.54mm间距的排针,分别是VCC(电源)、GND(地)、SCL(时钟)、SDA(数据)。
注意:不同厂家生产的0.96寸OLED屏,引脚顺序可能不一样!一定要确认你手上的屏幕引脚定义。最常见的顺序是:1脚GND,2脚VCC,3脚SCL,4脚SDA。接线前最好用万用表量一下。
2. 获取原始驱动代码
移植的第一步,得有“原材料”。我用的驱动代码是从卖家提供的资料里找到的,如果你手头没有,可以按照原始文章里提到的链接去下载。
拿到资料后,你会发现里面有个【OLED】文件夹,这就是我们要移植的核心。文件夹里通常包含这几个文件:
oled.c- OLED屏的驱动函数实现oled.h- 函数声明和宏定义oledfont.h- 字库数据(ASCII字符的点阵)- 可能还有
sys.h、delay.h等依赖文件
把这些文件先保存好,接下来我们要把它们“改造”成适合HC32F4A0的样子。
3. 创建工程并导入文件
首先,你得有一个能编译的天空星HC32F4A0基础工程。如果还没有,可以去官方或者立创开发板(LCKFB)的网站下载一个空白工程或者点灯例程,确保工程里至少有一个能用的毫秒级延时函数(比如delay_ms)。
然后,在你的工程目录下新建一个文件夹,比如叫OLED,把刚才那几个源文件(oled.c、oled.h、oledfont.h)复制进去。
接着,在你的IDE(比如Keil MDK)里,把oled.c添加到工程的源文件组,把oled.h和oledfont.h所在的路径添加到头文件包含路径里。这一步是告诉编译器:“我这里有新的代码要编译,头文件在这个地方找”。
4. 修改头文件依赖(解决第一个编译错误)
直接编译肯定会报错,因为原来的代码是为别的芯片写的。咱们按照报错信息,一个个来改。
第一步:替换系统头文件打开oled.h文件,找到类似#include "sys.h"的语句。在HC32的工程里,我们通常用官方的底层库头文件。把它改成:
#include "hc32_ll.h" // 替换原来的 sys.h这个头文件包含了HC32芯片的GPIO、时钟等底层寄存器的定义。
第二步:替换延时头文件打开oled.c文件,找到#include "delay.h"。在我们的工程里,延时函数可能定义在board.h或bsp_delay.h里。根据你的工程实际情况修改,比如:
#include "board.h" // 替换原来的 delay.h,假设延时函数在board.h中声明第三步:定义数据类型原来的代码可能用了u8、u16、u32这样的简写来定义变量类型。我们需要在oled.h里确保这些类型有定义。通常在HC32的工程里,这些类型已经在stdint.h里定义了(uint8_t、uint16_t、uint32_t)。为了兼容,我们可以在oled.h文件的开头(#include语句之后)加上:
#ifndef u8 #define u8 uint8_t #endif #ifndef u16 #define u16 uint16_t #endif #ifndef u32 #define u32 uint32_t #endif这样,如果其他地方没定义u8,我们就把它指向uint8_t,确保代码能认。
5. 硬件连接与引脚配置
这是移植最关键的一步:告诉单片机,OLED屏接在哪两个引脚上。
硬件连接:把OLED屏的4个引脚接到天空星开发板上:
- VCC-> 开发板的3.3V引脚
- GND-> 开发板的GND引脚
- SCL-> 我们选择的GPIO引脚(例如PB10)
- SDA-> 我们选择的GPIO引脚(例如PB11)
我选择了PB10和PB11,你可以根据自己板子的空闲情况选择任意两个GPIO,但最好避开已经用于特殊功能(如串口、JTAG)的引脚。
软件配置:我们需要修改代码,把引脚定义改成我们实际连接的。
- 找到并修改引脚定义文件。在原始驱动代码里,可能有一个
lcd_init.h或者直接在oled.h里有引脚定义。我们需要修改它。 - 将定义修改为HC32的格式。在
oled.h(或新建一个oled_conf.h)文件中,添加或修改如下代码:
//-----------------GPIO端口定义---------------- #define OLED_GPIO_PORT GPIO_PORT_B // 使用B端口 #define OLED_SCL_PIN GPIO_PIN_10 // SCL时钟线接PB10 #define OLED_SDA_PIN GPIO_PIN_11 // SDA数据线接PB11 //-----------------OLED端口操作宏定义---------------- // 这些宏定义了如何控制引脚输出高低电平 #define OLED_SCL_Clr() GPIO_ResetPins(OLED_GPIO_PORT, OLED_SCL_PIN) // SCL拉低 #define OLED_SCL_Set() GPIO_SetPins(OLED_GPIO_PORT, OLED_SCL_PIN) // SCL拉高 #define OLED_SDA_Clr() GPIO_ResetPins(OLED_GPIO_PORT, OLED_SDA_PIN) // SDA拉低 #define OLED_SDA_Set() GPIO_SetPins(OLED_GPIO_PORT, OLED_SDA_PIN) // SDA拉高这里用到的GPIO_SetPins和GPIO_ResetPins是HC32官方驱动库提供的函数,用于设置引脚输出高或低电平。
6. 重写OLED初始化函数(核心步骤)
原来的OLED_Init()函数里包含了GPIO初始化和SSD1306芯片的初始化序列。我们需要把GPIO初始化的部分,改成HC32库函数的写法。
打开oled.c,找到void OLED_Init(void)函数,将其内容替换为如下代码:
void OLED_Init(void) { stc_gpio_init_t stcGpioInit; // 定义GPIO初始化结构体 // 解除GPIO外设的写保护,允许配置 LL_PERIPH_WE(LL_PERIPH_GPIO); // 将结构体变量初始化为默认值 (void)GPIO_StructInit(&stcGpioInit); // 配置GPIO结构体参数 stcGpioInit.u16PinState = PIN_STAT_SET; // 初始输出高电平 stcGpioInit.u16PinDir = PIN_DIR_OUT; // 设置为输出模式 stcGpioInit.u16PullUp = PIN_PU_ON; // 使能内部上拉电阻(IIC总线通常需要上拉) // 初始化SCL引脚 (PB10) (void)GPIO_Init(OLED_GPIO_PORT, OLED_SCL_PIN, &stcGpioInit); // 初始化SDA引脚 (PB11) (void)GPIO_Init(OLED_GPIO_PORT, OLED_SDA_PIN, &stcGpioInit); // 给OLED一点上电稳定时间 delay_ms(200); // 以下是SSD1306芯片的初始化命令序列,这部分是通用的,不需要修改 OLED_WR_Byte(0xAE,OLED_CMD);// 关闭显示 OLED_WR_Byte(0x00,OLED_CMD);// 设置列地址低4位 OLED_WR_Byte(0x10,OLED_CMD);// 设置列地址高4位 OLED_WR_Byte(0x40,OLED_CMD);// 设置显示起始行 OLED_WR_Byte(0x81,OLED_CMD);// 对比度设置命令 OLED_WR_Byte(0xCF,OLED_CMD);// 对比度值 (0xCF) OLED_WR_Byte(0xA1,OLED_CMD);// 段重映射设置 (0xA1正常,0xA0左右翻转) OLED_WR_Byte(0xC8,OLED_CMD);// 扫描方向设置 (0xC8正常,0xC0上下翻转) OLED_WR_Byte(0xA6,OLED_CMD);// 正常显示 (0xA6正常,0xA7反色) OLED_WR_Byte(0xA8,OLED_CMD);// 多路复用率设置命令 OLED_WR_Byte(0x3f,OLED_CMD);// 多路复用率值 (0x3F对应64行) OLED_WR_Byte(0xD3,OLED_CMD);// 显示偏移命令 OLED_WR_Byte(0x00,OLED_CMD);// 显示偏移值 (0) OLED_WR_Byte(0xd5,OLED_CMD);// 显示时钟分频/振荡频率命令 OLED_WR_Byte(0x80,OLED_CMD);// 分频比,设置时钟为100帧/秒 OLED_WR_Byte(0xD9,OLED_CMD);// 预充电周期命令 OLED_WR_Byte(0xF1,OLED_CMD);// 预充电15个时钟,放电1个时钟 OLED_WR_Byte(0xDA,OLED_CMD);// COM引脚硬件配置命令 OLED_WR_Byte(0x12,OLED_CMD);// COM引脚配置值 (0x12) OLED_WR_Byte(0xDB,OLED_CMD);// VCOMH电平命令 OLED_WR_Byte(0x40,OLED_CMD);// VCOMH电平值 (0.77*VCC) OLED_WR_Byte(0x20,OLED_CMD);// 内存地址模式设置命令 OLED_WR_Byte(0x02,OLED_CMD);// 页地址模式 (0x02) OLED_WR_Byte(0x8D,OLED_CMD);// 电荷泵设置命令 OLED_WR_Byte(0x14,OLED_CMD);// 使能电荷泵 (0x14) OLED_WR_Byte(0xA4,OLED_CMD);// 全部点亮关闭 (0xA4) OLED_WR_Byte(0xA6,OLED_CMD);// 非反色显示 (0xA6) OLED_Clear(); // 清屏 OLED_WR_Byte(0xAF,OLED_CMD); // 最后,开启显示 }提示:初始化序列里那一长串
OLED_WR_Byte命令,是SSD1306芯片手册规定的,用来设置显示参数,比如对比度、扫描方向等。这部分代码是通用的,无论用什么单片机,只要驱动SSD1306,这些命令都一样,所以千万不要修改这些十六进制数。
7. 修改IIC延时函数
SSD1306的IIC通信时序对延时有一定要求。原来的驱动里有一个IIC_delay()函数,我们需要根据HC32的时钟速度来调整。
在oled.c文件里找到void IIC_delay(void)函数,它里面可能调用了一个微秒延时函数。我们需要把它改成我们工程里已有的微秒延时函数。假设我们的工程里有一个delay_us()函数:
void IIC_delay(void) { delay_us(15); // 延时大约15微秒,满足IIC时序要求 }这个延时值(15us)是一个经验值,保证了SCL时钟信号的频率在IIC协议允许的范围内(标准模式100kHz以内)。如果后续通信不稳定,可以适当调整这个值。
8. 编写主函数进行测试
所有底层修改完成后,就可以写个简单的程序来测试了。
打开你的main.c文件,在while(1)主循环之前初始化OLED,然后在循环里显示一些内容。
#include "board.h" #include "oled.h" // 包含OLED驱动头文件 int32_t main(void) { // 硬件初始化(时钟、延时等) board_init(); // 初始化OLED OLED_Init(); OLED_Clear(); // 清屏 while(1) { // 在屏幕不同位置显示不同大小的“ABC” OLED_ShowString(0, 0, (uint8_t *)"Hello HC32!", 8, 1); // 在(0,0)坐标,用8号字体显示 OLED_ShowString(0, 16, (uint8_t *)"OLED Test", 16, 1); // 在(0,16)坐标,用16号字体显示 // 刷新显示(将缓存数据写入OLED) OLED_Refresh(); // 延时一段时间 delay_ms(1000); // 可以在这里加个清屏,实现闪烁效果 // OLED_Clear(); // delay_ms(500); } }9. 编译、下载与调试
- 编译:点击编译按钮,确保0错误,0警告。
- 连接硬件:用杜邦线将OLED屏的SCL、SDA、VCC、GND分别连接到开发板你定义的引脚和3.3V、GND。务必检查连线是否正确,电源不要接反!
- 下载程序:通过调试器将程序下载到天空星HC32F4A0开发板。
- 上电观察:给开发板上电,你应该能看到OLED屏亮起,并显示“Hello HC32!”和“OLED Test”字样。
如果屏幕不亮:
- 检查电源和地线是否接好,用万用表量一下OLED屏的VCC和GND之间是否有3.3V电压。
- 检查SCL和SDA线是否接对,是否接触不良。
如果屏幕亮但无显示:
- 检查
OLED_Init()函数中的GPIO初始化部分,确认引脚配置是否正确。 - 用逻辑分析仪或示波器抓一下SCL和SDA的波形,看是否有IIC通信信号。如果没有,检查
IIC_delay函数和底层OLED_SCL_Clr/Set、OLED_SDA_Clr/Set宏定义是否正确操作了引脚。
按照上面这些步骤走下来,驱动移植基本就成功了。整个过程中最关键的其实就是两步:一是把GPIO的操作换成HC32的库函数方式,二是保证IIC的时序延时正确。以后要在天空星开发板上用其他IIC设备,比如温湿度传感器、陀螺仪,思路也是一样的。