在Keil MDK中实现OLED屏显示控制(SSD1306+STM32)
你想了解的是在Keil MDK开发环境下,通过代码完成OLED屏(以最常用的SSD1306驱动芯片、I2C接口为例)的显示控制,核心是先搭建Keil工程环境,再实现I2C底层时序、SSD1306初始化和显示功能函数,最终完成字符/数字/字符串的显示。下面从工程准备到代码实战,一步步教你实现。
一、前置准备
1.1 硬件与环境要求
- 硬件:STM32单片机(以STM32F103C8T6为例)、0.96英寸I2C接口OLED屏(SSD1306驱动)、杜邦线;
- 软件:Keil MDK-ARM V5.x(需安装对应STM32的Device Pack);
- 基础工程:已搭建好STM32F103的Keil工程(包含系统初始化、延时函数)。
1.2 硬件接线(I2C接口)
OLED屏4个核心引脚与STM32的接线如下(可自定义GPIO,后续代码对应修改即可):
| OLED引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V | 供电(禁止接5V) |
| GND | GND | 共地 |
| SCL | PB6 | I2C时钟线 |
| SDA | PB7 | I2C数据线 |
二、Keil工程中添加OLED驱动代码
2.1 新建驱动文件
在Keil工程中新建两个文件:oled.h(头文件)和oled.c(源文件),添加到工程目录并纳入编译。
2.2 实现I2C底层驱动(软件模拟,新手友好)
软件I2C无需依赖STM32硬件I2C外设,仅通过GPIO模拟时序,兼容性更强,先在oled.c中实现:
#include "oled.h"
#include "delay.h" // 需提前实现us/ms级延时函数
// ************************* 引脚定义(可根据实际修改)*************************
#define OLED_SCL_PIN GPIO_Pin_6
#define OLED_SCL_PORT GPIOB
#define OLED_SDA_PIN GPIO_Pin_7
#define OLED_SDA_PORT GPIOB
// 引脚电平控制宏
#define OLED_SCL_H() GPIO_SetBits(OLED_SCL_PORT, OLED_SCL_PIN)
#define OLED_SCL_L() GPIO_ResetBits(OLED_SCL_PORT, OLED_SCL_PIN)
#define OLED_SDA_H() GPIO_SetBits(OLED_SDA_PORT, OLED_SDA_PIN)
#define OLED_SDA_L() GPIO_ResetBits(OLED_SDA_PORT, OLED_SDA_PIN)
/**
* @brief 初始化OLED的I2C引脚
* @param 无
* @retval 无
*/
void OLED_I2C_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
// 使能GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置SCL/SDA为推挽输出
GPIO_InitStruct.GPIO_Pin = OLED_SCL_PIN | OLED_SDA_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(OLED_SCL_PORT, &GPIO_InitStruct);
// 初始电平置高
OLED_SCL_H();
OLED_SDA_H();
}
/**
* @brief 软件I2C起始信号(时序核心)
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_SDA_H();
OLED_SCL_H();
delay_us(2); // 延时保证时序稳定,需确保delay_us函数精准
OLED_SDA_L(); // SDA在SCL高电平时拉低,产生起始信号
delay_us(2);
OLED_SCL_L();
}
/**
* @brief 软件I2C停止信号
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_SDA_L();
OLED_SCL_H();
delay_us(2);
OLED_SDA_H(); // SDA在SCL高电平时拉高,产生停止信号
delay_us(2);
OLED_SCL_L();
}
/**
* @brief 软件I2C发送一个字节
* @param dat:待发送的8位数据
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t dat)
{
uint8_t i;
for(i=0; i<8; i++)
{
OLED_SCL_L();
delay_us(2);
// 高位先行,逐位发送
if(dat & 0x80) OLED_SDA_H();
else OLED_SDA_L();
dat <<= 1;
delay_us(2);
OLED_SCL_H();
delay_us(2);
}
// 省略应答检测(新手简化版)
OLED_SCL_L();
delay_us(2);
OLED_SDA_H();
delay_us(2);
OLED_SCL_H();
delay_us(2);
OLED_SCL_L();
}
/**
* @brief 向OLED发送指令
* @param cmd:指令字节
* @retval 无
*/
void OLED_SendCmd(uint8_t cmd)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); // SSD1306 I2C设备地址(0x3C<<1,部分屏为0x38)
OLED_I2C_SendByte(0x00); // 0x00表示后续是指令
OLED_I2C_SendByte(cmd);
OLED_I2C_Stop();
}
/**
* @brief 向OLED发送显示数据
* @param dat:像素数据字节
* @retval 无
*/
void OLED_SendData(uint8_t dat)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); // 设备地址
OLED_I2C_SendByte(0x40); // 0x40表示后续是数据
OLED_I2C_SendByte(dat);
OLED_I2C_Stop();
}
2.3 实现OLED初始化与坐标设置
在oled.c中继续添加SSD1306初始化函数(配置显示参数)和坐标设置函数:
/**
* @brief OLED屏初始化(SSD1306核心配置)
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
delay_ms(100); // 上电延时,确保芯片稳定
OLED_I2C_Init(); // 初始化I2C引脚
// SSD1306初始化指令集
OLED_SendCmd(0xAE); // 关闭显示
OLED_SendCmd(0x00); // 设置列起始地址低4位
OLED_SendCmd(0x10); // 设置列起始地址高4位
OLED_SendCmd(0x40); // 设置显示起始行
OLED_SendCmd(0xB0); // 设置页地址(Y轴,每页8行)
OLED_SendCmd(0x81); // 开启对比度调节
OLED_SendCmd(0xFF); // 对比度最大值(0~255)
OLED_SendCmd(0xA1); // 段重映射(正常显示,0xA0为左右翻转)
OLED_SendCmd(0xA6); // 正常显示(0xA7为反显)
OLED_SendCmd(0xA8); // 设置多路复用率
OLED_SendCmd(0x3F); // 64行显示
OLED_SendCmd(0xC8); // 扫描方向(正常显示,0xC0为上下翻转)
OLED_SendCmd(0xD3); // 设置显示偏移
OLED_SendCmd(0x00); // 偏移量0
OLED_SendCmd(0xD5); // 设置时钟分频因子
OLED_SendCmd(0x80); // 默认值
OLED_SendCmd(0xD9); // 设置预充电周期
OLED_SendCmd(0xF1);
OLED_SendCmd(0xDA); // 设置COM引脚配置
OLED_SendCmd(0x12);
OLED_SendCmd(0xDB); // 设置VCOMH电压
OLED_SendCmd(0x40);
OLED_SendCmd(0x8D); // 启用电荷泵(必须开启,否则无显示)
OLED_SendCmd(0x14);
OLED_SendCmd(0xAF); // 开启显示
}
/**
* @brief 设置OLED显示坐标
* @param x:列坐标(0~127)
* @param y:页坐标(0~7,每页对应8行)
* @retval 无
*/
void OLED_SetPos(uint8_t x, uint8_t y)
{
OLED_SendCmd(0xB0 + y); // 设置页地址
OLED_SendCmd(((x & 0xF0) >> 4) | 0x10); // 列地址高4位
OLED_SendCmd(x & 0x0F); // 列地址低4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t x, y;
for(y=0; y<8; y++) // 遍历8个页
{
OLED_SetPos(0, y);
for(x=0; x<128; x++) // 遍历128列
{
OLED_SendData(0x00); // 写入0,关闭所有像素
}
}
}
2.4 实现字符/字符串显示函数
添加8×16点阵ASCII字模(截取数字/字母部分)和显示函数,在oled.c中补充:
// 8×16 ASCII字模表(数字0-9,可扩展字母/符号)
const unsigned char OLED_F8x16[] = {
0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00, // 0
0x00,0x00,0x00,0x70,0x08,0x08,0x08,0x70,0x00,0x00,0x00,0x70,0x08,0x08,0x08,0x70, // 1
0x00,0x00,0x7C,0x02,0x01,0x02,0x7C,0x00,0x00,0x00,0x7C,0x02,0x01,0x02,0x7C,0x00, // 2
// 其余数字/字母字模可自行补充
};
/**
* @brief 显示单个字符(8×16点阵)
* @param x:起始列(0~127)
* @param y:起始页(0~7,单个字符占2页)
* @param c:待显示字符(ASCII码)
* @retval 无
*/
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t c)
{
uint8_t i;
c -= '0'; // 以数字0为基准偏移(显示字母需调整偏移量)
// 显示上半部分(第1页)
OLED_SetPos(x, y);
for(i=0; i<8; i++)
{
OLED_SendData(OLED_F8x16[c*16 + i]);
}
// 显示下半部分(第2页)
OLED_SetPos(x, y+1);
for(i=8; i<16; i++)
{
OLED_SendData(OLED_F8x16[c*16 + i]);
}
}
/**
* @brief 显示字符串
* @param x:起始列
* @param y:起始页
* @param str:字符串指针
* @retval 无
*/
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str)
{
while(*str != '\0')
{
OLED_ShowChar(x, y, *str);
x += 8; // 每个字符占8列
if(x > 120) // 超出屏幕宽度则换行
{
x = 0;
y += 2;
}
str++;
}
}
2.5 头文件oled.h声明
在oled.h中添加函数声明和必要的头文件,方便主函数调用:
#ifndef __OLED_H
#define __OLED_H
#include "stm32f10x.h"
#include "stdint.h"
// 函数声明
void OLED_I2C_Init(void);
void OLED_SendCmd(uint8_t cmd);
void OLED_SendData(uint8_t dat);
void OLED_Init(void);
void OLED_SetPos(uint8_t x, uint8_t y);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t c);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str);
#endif
三、Keil中编写主函数测试
在main.c中调用OLED驱动函数,实现显示测试:
#include "stm32f10x.h"
#include "oled.h"
#include "delay.h" // 需确保delay_init、delay_ms、delay_us已实现
int main(void)
{
// 系统初始化
SystemInit(); // 配置STM32主频(72MHz)
delay_init(); // 初始化延时函数
OLED_Init(); // 初始化OLED屏
OLED_Clear(); // 清屏
while(1)
{
// 显示固定字符串
OLED_ShowString(0, 0, "Keil OLED Test");
// 显示数字(示例:计数递增)
static uint32_t cnt = 0;
OLED_ShowString(0, 2, "Count:");
// 拼接数字为字符串(或扩展OLED_ShowNum函数)
uint8_t num_str[6];
sprintf((char*)num_str, "%05d", cnt);
OLED_ShowString(40, 2, num_str);
cnt++;
if(cnt > 99999) cnt = 0;
delay_ms(100); // 100ms刷新一次
}
}
四、Keil工程编译与下载
- 检查工程:确保
oled.c已添加到Keil工程的Source Group中,头文件路径正确; - 编译工程:点击Keil工具栏的
Build或Rebuild,解决语法错误(如缺少延时函数、GPIO定义错误); - 下载程序:连接调试器(J-Link/ST-Link),点击
Download将程序下载到STM32; - 验证效果:上电后OLED屏显示“Keil OLED Test”和递增的数字,说明控制成功。
五、常见问题排查(Keil环境下)
- 编译报错“undefined reference to delay_us”:
- 原因:未实现延时函数,或未添加到工程;
- 解决:补充
delay.c/delay.h,实现基于SysTick的delay_us和delay_ms。
- 下载后OLED无显示:
- 检查Keil调试配置:确认Debug标签页中调试器、Core Clock配置正确;
- 检查I2C地址:将
OLED_I2C_SendByte(0x78)改为0x38测试(部分屏地址为0x1C<<1); - 检查代码烧录:Keil中点击
View->Watch & Call Stack Window,确认OLED_Init执行完成。
- 显示乱码:
- 原因:字模点阵与显示函数不匹配,或Keil编译器优化等级过高;
- 解决:将Keil优化等级改为
-O0(Options for Target->Optimization),核对字模点阵格式。
浙公网安备 33010602011771号