先介绍一下OLED
OLED被称为有机激光二极管显示,OLED采用有机材料涂层和玻璃基板,当有电流通过时有机材料就会发光,所以OLED具有自发光特性,不需要背光源的特点。显示的原理是将图像或文字分解为一组组小点,并通过控制每个点的亮度或颜色来显示信息。这些小点通常是很小的正方形,并且由有机发光二极管组成。
常用的点阵大小有12x12、14x14、16x16、8x16等,每个点用0或1表示,0代表没有点,1代表有点。这样,每个汉字就可以通过一个二进制数组来表示。
OLED屏像素分辨率
0.96寸OLED是128x64像素的分辨率,我们可以理解为:水平方向分布了128个像素点,垂直方向分布了64个像素点。而驱动芯片在点亮像素点的时候,是以8个像素点为单位的。我们在画点的时候坐标Y的取值为0-7,坐标X的取值为0-127.
上代码,在STM32F103C8T6上测试的,按照下方建好.c文件和.h文件,配置里面加上stm32f10x_i2c.c,直接复制
/***************************** main.c 文件 ******************************/
#include "stm32f10x.h"
#include "oled.h"
#include "font.h"
int main(void) {
// 系统时钟初始化(默认使用内部8MHz RC时钟)
SystemInit();
// 初始化I2C外设(使用I2C1,PB6-SCL,PB7-SDA)
I2C_Config();
// 初始化OLED显示屏
OLED_Init();
// 在屏幕上显示内容
OLED_PrintString(0, 0, "Hello STM32!");
OLED_PrintString(2, 3, "OLED TEST");
// 刷新显示
OLED_Refresh();
while (1) {
// 主循环
}
}
/***************************** oled.h 文件 ******************************/
#ifndef __OLED_H
#define __OLED_H
#include "stm32f10x.h"
// OLED参数定义
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_PAGES (OLED_HEIGHT/8) // 8行每页
// 函数声明
void I2C_Config(void);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_Refresh(void);
void OLED_PrintChar(uint8_t x, uint8_t y, char ch);
void OLED_PrintString(uint8_t x, uint8_t y, const char *str);
#endif
/***************************** oled.c 文件 ******************************/
#include "oled.h"
#include "font.h"
#include <string.h>
// OLED I2C地址(左移1位后的地址)
#define OLED_ADDRESS 0x78 // 7位地址0x3C << 1
// 命令/数据标识
#define OLED_CMD 0x00
#define OLED_DATA 0x40
// 显示缓冲区(128x64像素,按页组织)
static uint8_t oled_buffer[OLED_WIDTH][OLED_PAGES];
/*----------------------------------------------------------
I2C配置函数
功能:配置I2C1外设,使用PB6(SCL)和PB7(SDA)
------------------------------------------------------------*/
void I2C_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// 2. 配置GPIO
// PB6 - I2C1_SCL
// PB7 - I2C1_SDA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 3. 配置I2C参数
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 50%占空比
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机地址不使用
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 使能ACK
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHz标准模式
I2C_Init(I2C1, &I2C_InitStructure);
// 4. 使能I2C
I2C_Cmd(I2C1, ENABLE);
}
/*----------------------------------------------------------
I2C写单个字节函数
参数:type - 命令/数据标识
data - 要发送的数据
------------------------------------------------------------*/
static void OLED_I2C_WriteByte(uint8_t type, uint8_t data) {
// 等待总线空闲
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
// 发送起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址(写模式)
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送控制字节(命令/数据标识)
I2C_SendData(I2C1, type);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 发送实际数据
I2C_SendData(I2C1, data);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 生成停止条件
I2C_GenerateSTOP(I2C1, ENABLE);
}
/*----------------------------------------------------------
发送命令函数
参数:cmd - 要发送的命令字节
------------------------------------------------------------*/
void OLED_WriteCmd(uint8_t cmd) {
OLED_I2C_WriteByte(OLED_CMD, cmd);
}
/*----------------------------------------------------------
发送数据函数
参数:data - 要发送的数据字节
------------------------------------------------------------*/
void OLED_WriteData(uint8_t data) {
OLED_I2C_WriteByte(OLED_DATA, data);
}
/*----------------------------------------------------------
OLED初始化函数
功能:发送初始化序列,配置显示参数
------------------------------------------------------------*/
void OLED_Init(void) {
// 关闭显示
OLED_WriteCmd(0xAE);
// 设置时钟分频和振荡频率
OLED_WriteCmd(0xD5);
OLED_WriteCmd(0x80); // 建议值
// 设置多路复用率
OLED_WriteCmd(0xA8);
OLED_WriteCmd(0x3F); // 1/64 duty
// 设置显示偏移
OLED_WriteCmd(0xD3);
OLED_WriteCmd(0x00); // 无偏移
// 设置起始行地址
OLED_WriteCmd(0x40);
// 启用内部电荷泵
OLED_WriteCmd(0x8D);
OLED_WriteCmd(0x14); // 启用电荷泵
// 设置内存地址模式
OLED_WriteCmd(0x20);
OLED_WriteCmd(0x00); // 水平地址模式
// 段重映射设置
OLED_WriteCmd(0xA1); // 列地址127映射到SEG0
// COM输出扫描方向
OLED_WriteCmd(0xC8); // 从COM63扫描到COM0
// COM引脚硬件配置
OLED_WriteCmd(0xDA);
OLED_WriteCmd(0x12); // 备用COM引脚配置
// 设置对比度
OLED_WriteCmd(0x81);
OLED_WriteCmd(0xCF); // 对比度值
// 预充电周期设置
OLED_WriteCmd(0xD9);
OLED_WriteCmd(0xF1); // Phase1 = 1 DCLK, Phase2 = 15 DCLK
// VCOMH电压设置
OLED_WriteCmd(0xDB);
OLED_WriteCmd(0x40); // VCOMH = 0.83×VCC
// 全部像素点亮(测试模式)
OLED_WriteCmd(0xA4); // 正常显示
// 显示反转设置
OLED_WriteCmd(0xA6); // 正常显示(0正常,1反转)
// 开启显示
OLED_WriteCmd(0xAF);
// 清屏并刷新
OLED_Clear();
OLED_Refresh();
}
/*----------------------------------------------------------
清屏函数
功能:将显示缓冲区全部清零
------------------------------------------------------------*/
void OLED_Clear(void) {
memset(oled_buffer, 0, sizeof(oled_buffer));
}
/*----------------------------------------------------------
刷新显示函数
功能:将缓冲区内容发送到OLED显示
------------------------------------------------------------*/
void OLED_Refresh(void) {
for (uint8_t page = 0; page < OLED_PAGES; page++) {
// 设置页地址
OLED_WriteCmd(0xB0 | page);
// 设置列地址低位(0x00)
OLED_WriteCmd(0x00);
// 设置列地址高位(0x10)
OLED_WriteCmd(0x10);
// 发送该页全部128列的数据
for (uint8_t col = 0; col < OLED_WIDTH; col++) {
OLED_WriteData(oled_buffer[col][page]);
}
}
}
/*----------------------------------------------------------
显示单个字符函数
参数:x - 水平位置(0-15,每个字符占8像素宽)
y - 垂直页位置(0-7)
ch - 要显示的ASCII字符
------------------------------------------------------------*/
void OLED_PrintChar(uint8_t x, uint8_t y, char ch) {
// 参数检查
if (x >= OLED_WIDTH/8 || y >= OLED_PAGES) return;
// 获取字符点阵数据(ASCII从32开始)
const uint8_t *font_ptr = &font_8x8[(ch - 32) * 8];
// 将点阵数据写入缓冲区
for (uint8_t i = 0; i < 8; i++) {
oled_buffer[x * 8 + i][y] = font_ptr[i];
}
}
/*----------------------------------------------------------
显示字符串函数
参数:x - 起始水平位置(0-15)
y - 起始垂直页位置(0-7)
str - 要显示的字符串
------------------------------------------------------------*/
void OLED_PrintString(uint8_t x, uint8_t y, const char *str) {
while (*str) {
OLED_PrintChar(x, y, *str++);
x++;
// 换行处理
if (x >= OLED_WIDTH/8) {
x = 0;
y++;
if (y >= OLED_PAGES) y = OLED_PAGES - 1;
}
}
}
/***************************** font.h 文件 ******************************/
#ifndef __FONT_H
#define __FONT_H
// 8x8 ASCII字库(仅包含部分字符)
const uint8_t font_8x8[95][8] = {
// 空格 (ASCII 32)
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// '!' (33)
{0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00},
// 更多字符数据...
// '0' (48)
{0x3E,0x7F,0x63,0x63,0x63,0x63,0x7F,0x3E},
// '1' (49)
{0x18,0x38,0x58,0x18,0x18,0x18,0x18,0x7E},
// 添加其他必要字符...
// 'A' (65)
{0x18,0x3C,0x66,0x66,0x7E,0x66,0x66,0x66},
// 'B' (66)
{0x7E,0x33,0x33,0x3E,0x33,0x33,0x33,0x7E},
// 补充剩余字符...
};
#endif
代码结构说明:
-
main.c - 主程序入口
- 初始化系统时钟
- 配置I2C外设
- 初始化OLED并显示测试内容
-
oled.h - OLED驱动头文件
- 包含参数定义和函数声明
- 定义显示尺寸和内存组织方式
-
oled.c - OLED驱动实现
- 包含硬件驱动层函数和显示逻辑
- 使用I2C协议与OLED通信
- 维护显示缓冲区并实现刷新机制
-
font.h - 字库文件
- 包含ASCII字符的8x8点阵数据
- 每个字符使用8字节表示(垂直列)
关键函数详细说明:
-
I2C_Config()
- 配置I2C1的GPIO引脚(PB6/PB7)
- 设置I2C时钟速度为100kHz
- 使能I2C外设
-
OLED_I2C_WriteByte()
- 实现I2C通信基本操作
- 包含完整的I2C时序控制:
- 起始条件
- 地址发送
- 数据传输
- 停止条件
-
OLED_Init()
- 发送SSD1306初始化命令序列
- 配置显示参数:
- 时钟分频
- 多路复用率
- 内存地址模式
- 对比度设置等
-
OLED_Refresh()
- 将缓冲区内容按页刷新到OLED
- 使用水平地址模式逐列更新
-
OLED_PrintChar()
- 将ASCII字符转换为点阵数据
- 支持字符定位显示
- 自动处理字库偏移(ASCII从32开始)
硬件连接说明:
OLED引脚 STM32连接 ------------------------ VCC 3.3V GND GND SCL PB6 (I2C1_SCL) SDA PB7 (I2C1_SDA)
使用注意事项:
- 字库需要完整实现ASCII 32-126字符
- 实际显示坐标系统:
- X轴:0-15(每个字符8像素宽)
- Y轴:0-7(每页8行)
- 修改显示内容后必须调用
OLED_Refresh()
- 可以通过修改
oled_buffer
直接操作像素