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。
相关推荐
深圳英康仕1 小时前
龙芯2K3000嵌入式工控机的技术拆解:算力、接口与国产系统适配
嵌入式硬件·工控机·工业计算机·国产工控机·龙芯2k3000
山木嵌入式1 小时前
STM32 UART串口通信协议与3种底层驱动实现(寄存器/标准库/HAL库)
stm32·单片机·串口·uart
Heartache boy1 小时前
野火STM32_HAL库版课程笔记-I2C介绍
笔记·stm32·单片机
Deitymoon1 小时前
STM32——OLED显示汉字
stm32·单片机·嵌入式硬件
狮驼岭的小钻风2 小时前
单片机启动流程与 .s 文件详解
单片机·嵌入式硬件
金色光环2 小时前
【DSP学习笔记】 F28335中断系统理解-基于普中DSP28335开发攻略
笔记·单片机·学习·dsp开发
iCxhust2 小时前
8086/8088单板机VSCode集中环境开发编译(第二版整理)
ide·vscode·嵌入式硬件·编辑器·嵌入式·微机原理·8086最小系统
时光の尘2 小时前
【嵌入式大厂面经】·IIC常见考点(持续更新中···)
arm开发·单片机·嵌入式硬件·mcu·物联网·iot
三佛科技-187366133972 小时前
AIP7550GD893.TR是什么芯片?200mA/30V低压差线性稳压器芯片分析
单片机·嵌入式硬件