STM32——OLED显示图片

前言

很多小伙伴在用 STM32 驱动 0.96 寸 I2C OLED(SSD1306)时,汉字显示没问题,但图片花屏、只显示一小部分、画面错位、完全不显示,核心原因就两个:

  1. 不懂 SSD1306 页寻址存储原理
  2. 图片绘制函数循环逻辑写错、页号不刷新、传参混淆「行数」和「页数」。

本文基于标准库 STM32F103,从零讲解 OLED 图片显示底层原理、PCtoLCD2002 取模配置、错误原因分析、修正后完整源码,附带逐行注释,新手看完直接上手,代码可直接编译跑通,适合收藏发博客。

一、硬件与基础原理

1. 硬件配置

  • 主控:STM32F103 标准库
  • 屏幕:0.96 寸 I2C 接口 128×64 OLED(SSD1306)
  • 引脚:PB6=SCL 、PB7=SDA
  • 通信:I2C 400K 快速模式

2. SSD1306 核心页寻址原理(必懂)

OLED 分辨率:128 列 × 64 行 SSD1306 不按普通行寻址,而是按页 (Page) 管理

  • 1 页 = 8 个像素行
  • 整个屏幕 64 行 = 总共 8 页
  • 每页固定 128 列
  • 一张全屏 128×64 图片,固定需要:8页 × 128字节 = 1024 字节

关键点:代码里控制上下位置用页号 0~7不是直接填 64,这是 90% 新手出错的根源。

3. PCtoLCD2002 图片取模固定配置

图片的话,需要转成bmp格式

和汉字取模配置一致,必须严格照搬,否则图片旋转错位:

  • 点阵格式:阴码
  • 取模方式:列行式
  • 取模走向:逆向(低位在前)
  • 图片尺寸:128×64
  • 输出格式:C 语言数组

二、完整工程源码

1、 oled.c 新增正确图片绘制函数

复制代码
/**
 * @brief  OLED 12864 图片绘制函数
 * @param  x0: 起始列坐标
 * @param  y0: 起始页号
 * @param  x1: 结束列(全屏固定128)
 * @param  y1: 结束页(全屏固定8页)
 * @param  BMP: 128*64 图片取模数组
 * @note   SSD1306 1页=8行,64行共8页
 */
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,const unsigned char BMP[])
{
	unsigned int j = 0;     // 图片数组偏移下标
	unsigned char x,y;      // x:列循环  y:页循环
	
	// 外层循环:逐页绘制 0~7共8页
	for(y = y0; y < y1; y++)
	{
		// 关键:每画完一页,重新定位当前页坐标
		OLED_Setpos(x0, y);
		
		// 内层循环:当前页从左到右写满128列
		for(x = x0; x < x1; x++)
		{
			WriteData(BMP[j]);  // 写入一个像素字节
			j++;                // 数组下标后移
		}
	}
}

2、 codetab.h 图片数组定义

数组开头加 const,防止内存溢出数据乱码:

复制代码
#ifndef _CODETAB_H
#define _CODETAB_H

// 128*64 OLED 全屏图片取模数组
const unsigned char BMP1[] =
{
	0X22,0X01,0X57,0X00,0X40,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0X80,0X40,0X40,0XE0,0X60,
0X20,0X70,0XF0,0X08,0X08,0X04,0X04,0X04,0X06,0X02,0X02,0X02,0X02,0X82,0XFE,0XFE,
0X00,0X0E,0X0A,0X08,0X0C,0X0C,0X1C,0X1C,0X18,0X18,0X30,0X30,0X60,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X1C,0X30,
0X67,0X99,0XA0,0X40,0X40,0XFF,0XF0,0X00,0X00,0X03,0X0E,0X0C,0X28,0XC8,0X88,0X08,
0X0C,0X0C,0X0C,0X06,0X07,0X07,0X03,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X0F,0XCF,0XFC,0XF8,0X7C,0X3C,
0X1C,0X3E,0X1F,0X1F,0X8C,0X8E,0X46,0X07,0X07,0X1E,0XFC,0XF0,0XC0,0X80,0X00,0X80,
0X80,0XC0,0XE0,0X30,0X18,0X06,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X03,0X07,0XFC,0XF0,0XF3,0XFF,0XFF,0XFD,0XF8,0XF8,0XF8,
0XFC,0XFC,0XFE,0XFF,0X7F,0X7F,0X3F,0X1F,0X07,0X03,0X01,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X01,
0X01,0X01,0X03,0X03,0X01,0X01,0X01,0X01,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
};

#endif

3、 main.c 主函数调用

复制代码
#include "stm32f10x.h"
#include "main.h"
#include "stdio.h"
#include "sg90.h"
#include "oled.h"

// 简易软件延时
void delay(uint16_t time)  
{
	uint16_t i = 0;
	while(time --)
	{
		i = 12000;
		while(i --);
	}
}

// 声明外部图片数组
extern const unsigned char BMP1[];

int main(void)
{
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	I2C_Configuration();   // I2C初始化
	OLED_Init();           // OLED初始化
	delay(2000);           // 等待屏幕稳定
	
	OLED_Fill(0XFF);       // 全屏亮屏测试
	delay(2000);
	OLED_Fill(0X00);       // 全屏清屏
	delay(2000);
	
	while(1)
	{
		// 全屏图片显示:起始列0 起始页0 结束列128 结束页8
		OLED_DrawBMP(0, 0, 128, 8, BMP1);
	}
}

三、函数逐行深度解析

复制代码
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,const unsigned char BMP[])
{
	unsigned int j = 0;
	unsigned char x,y;
	
	// 外层循环:遍历每一页 0~7
	for(y = y0; y < y1; y++)
	{
		// 关键:每一页都重新设置坐标,切换页号
		OLED_Setpos(x0, y);
		
		// 内层循环:当前页从左到右写满128列
		for(x = x0; x < x1; x++)
		{
			WriteData(BMP[j]);
			j++;
		}
	}
}
  1. j:记录图片数组当前读到的位置,依次往后取模数据;
  2. 外层 for(y):控制页切换,从第 0 页一直写到第 7 页;
  3. OLED_Setpos(x0, y):每画一页必须重新定位,这是修复花屏的核心;
  4. 内层 for(x):同一页内,从左到右逐列写入像素数据;
  5. 全屏调用固定传参:OLED_DrawBMP(0,0,128,8,BMP1)

四、使用注意事项

  1. 图片取模必须和本驱动匹配:阴码、列行式、逆向
  2. 12864 图片数组必须 1024 字节,多一个少一个都会错位;
  3. 图片数组一定要加 const,存储在 Flash 避免内存溢出;
  4. 调用时结束页填 8,不是 64,分清「页」和「像素行」;
  5. 不要在循环里频繁清屏,避免屏幕闪烁。

五、总结

OLED 图片显示乱码、花屏的根本原因就三点:

  • 不懂页寻址,不会逐页刷新坐标;
  • 函数循环逻辑错误,固定页号不切换;
  • 取模配置不匹配、数组不加 const。
相关推荐
蓝斯49714 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip15 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程15 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工16 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip17 小时前
CAN与CANopen的本质区别
单片机
lilihewo17 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET18 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院19 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰19 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥20 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5