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。
相关推荐
半杯咖啡半行码6 小时前
STM32之ARM通用汇编指令详解:从指令格式到寻址方式
汇编·arm开发·stm32
创界工坊工作室7 小时前
22-基于STM32的汽车防盗系统设计
stm32·单片机·嵌入式硬件·proteus
雨田言炎9 小时前
STM32之RCC系统时钟控制(芯片的心脏控制器)
stm32·单片机·嵌入式硬件
李永奉9 小时前
日常使用的锂电池上,新、旧锂电池的差异分析
单片机·嵌入式硬件·物联网·智能手机·电脑
额额额对了9 小时前
IMX6ULL 杂项设备 LED 驱动
stm32·单片机·嵌入式硬件
长春的KAKA10 小时前
Proxmark3 Iceman分支CLI命令总表
嵌入式硬件
半杯咖啡半行码11 小时前
STM32开发之IIC通信详解:从协议基础到FM24CL04实战
stm32·单片机·嵌入式硬件
硅基手札11 小时前
【I2C 技术系列 05】I2C 多主仲裁与时钟同步
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
szxinmai主板定制专家12 小时前
面向虚拟电厂协调控制:RK3588+FPGA+AI边缘控制器设计思路与算力评估
人工智能·嵌入式硬件·fpga开发·机器人·jetson·zynq·振动采集
Zw-awa12 小时前
DO 和 AO 到底怎么选:比较器替你决定了什么
stm32