当前位置: 首页 > news >正文

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。
http://www.jsqmd.com/news/798678/

相关文章:

  • 用Yii2快速构建微服务RESTful API全攻略
  • 41《CAN总线报文周期、抖动与实时性分析》
  • 后端开发必看:设计高并发系统时,如何估算你的RTT和时延带宽积?
  • 别再死记硬背公式了!用Python代码实战理解无人机姿态的三种表示法(欧拉角、DCM、四元数)
  • 实时交通+天气+限行政策+司机疲劳度四维融合——Gemini重构Google Maps路线决策逻辑(仅限首批200家ISV开放调用)
  • 5分钟搞定专业神经网络图:Draw.io开源模板库终极指南
  • 如何自定义查询历史记录面板的展示风格_时间轴样式设计
  • 2026年谷歌广告投放机构怎么选?5家头部平台多维横向实测解析 - GEO优化
  • Pearcleaner:macOS系统清理的终极免费工具,彻底告别应用残留问题
  • OpenSCENARIO实战:从标准到场景的构建指南
  • 低精度SIMD脉冲神经网络引擎L-SPINE设计与优化
  • S7-1200 Modbus TCP 通信客户端指令块 MB_CLIENT
  • 避坑指南:CPAL脚本中diagGetRespPrimitiveByte提取诊断响应数据的正确姿势
  • 专业媒体数字化转型:从EE Times改版看响应式设计与内容生态构建
  • AMD收购赛灵思:异构计算时代下的战略整合与行业格局重塑
  • Honey Select 2终极优化指南:HS2-HF Patch完整解决方案
  • 阿里巴巴Qwen模型深度整合淘宝:对话式购物取代搜索,优化移动端购物体验
  • 第一次接触浏览器的LocalStorage
  • 从标注到训练:用Labelme+Anaconda搞定YOLO/UNet数据集全流程(以车辆检测为例)
  • 别再傻傻分不清了!UE5材质节点ActorPosition与ObjectPosition实战避坑指南
  • CoQA 数据集介绍
  • Vue3 监听器 watch 监听不到数组长度变化?深度解析数组响应式避坑指南.txt
  • 2026年华为mate80新手机会预装一些如咸鱼的第三方软件吗?靠谱吗?
  • 技术产品设计:如何避免复杂性暴露与响应缓慢导致用户体验灾难
  • #33 Agent 的可观测性:日志、追踪、监控与性能分析(LangSmith、Wandb)
  • 深入MFGTool2:拆解I.MX6U双阶段烧录原理,从BootStrap到Updater的完整流程分析
  • 从2012 CES看技术演进:移动计算、物联网与生态博弈
  • UniApp引导页从开发到上线的完整避坑指南:我用Swiper组件踩过的那些雷
  • 从原子到应用:下一代AI计算的跨学科融合与硬件革新
  • 2026制造业线上推广公司技术与效果评估报告:五大优选品牌解析 - GEO优化