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

STM32——软件IIC显示字符

一、整体说明这是一套STM32 软件模拟 I2C 驱动 0.96 寸 OLEDSSD1306的完整工程代码。特点纯软件 I2C不占用硬件 I2C 外设引脚SCLPB0、SDAPB1驱动芯片SSD1306支持清屏、显示字符、显示汉字、显示图片二、文件总览整个工程由3 个核心文件组成oledr.c——驱动核心最重要本文重点讲解oledr.h—— 宏定义、函数声明codetab.h—— 字库ASCII、汉字、图片取模数据三、oledr.c超详细逐模块讲解博客核心模块 1延时函数软件 I2C 必须靠延时控制时序// 微秒级延时粗略延时满足I2C时序即可 static void delay_us(unsigned char num) { unsigned char i; while(num--) { i 10; while(i--); } } // 毫秒级延时用于初始化等待、上电稳定 static void delay_ms(unsigned int ms) { unsigned int x,y; for(x ms;x 0;x--) for(y12000;y0;y--); }作用delay_us控制 I2C 通信时序保证 SDA/SCL 变化稳定delay_msOLED 上电需要稳定时间初始化前必须等待模块 2GPIO 初始化软件 I2C 最重要一步static void OLED_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 开 GPIOB AFIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // 模式通用开漏输出软件I2C必须用这个 GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_OD; // 引脚PB0(SCL) PB1(SDA) GPIO_InitStruct.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; // 速度50MHz GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); // 初始状态SCL1SDA1I2C 总线空闲状态 OLED_SCLK_Set(); OLED_SDIN_Set(); }关键知识点必须开 AFIO 时钟模式必须是Out_OD开漏输出硬件 I2C 用AF_OD软件 I2C 必须用通用开漏I2C 空闲状态 SCL1SDA1模块 3I2C 起始信号通信的 “开场白”static void OLED_IIC_Start(void) { OLED_SCLK_Set(); // SCL1 OLED_SDIN_Set(); // SDA1 delay_us(1); // 稳定 OLED_SDIN_CLR(); // SDA 从 1→0产生起始信号 delay_us(1); OLED_SCLK_CLR(); // 拉低时钟准备传输数据 }I2C 起始信号规则标准SCL 保持高电平时SDA 由高变低 → 起始信号OLED 检测到这个信号才知道 “要开始通信了”。模块 4I2C 停止信号通信的 “结束语”static void OLED_IIC_Stop(void) { OLED_SDIN_CLR(); delay_us(1); OLED_SCLK_Set(); delay_us(1); OLED_SDIN_Set(); // SDA 从 0→1产生停止信号 delay_us(1); }规则SCL 高电平时SDA 由低变高 → 停止信号模块 5I2C 等待 ACK 应答确认 OLED 收到数据static unsigned char IIC_Wait_Ack(void) { unsigned char ack; OLED_SCLK_CLR(); delay_us(1); OLED_SDIN_Set(); delay_us(1); OLED_SCLK_Set(); delay_us(1); // 读取 SDA 电平 if(OLED_READ_SDIN()) ack IIC_NO_ACK; // 无应答 else ack IIC_ACK; // 有应答 OLED_SCLK_CLR(); delay_us(1); return ack; }作用每发 1 个字节OLED 必须拉低 SDA 表示“我收到了”。如果无应答 → 通信失败。模块 6I2C 发送一个字节核心收发函数static void Write_IIC_Byte(unsigned char IIC_Byte) { unsigned char i; for(i0;i8;i) // 一个字节8位 { OLED_SCLK_CLR(); // 拉低时钟准备放数据 delay_us(1); // 从最高位开始发送 if(IIC_Byte 0x80) OLED_SDIN_Set(); else OLED_SDIN_CLR(); IIC_Byte 1; // 左移准备下一位 delay_us(1); OLED_SCLK_Set(); // 上升沿发送数据 delay_us(1); } OLED_SCLK_CLR(); delay_us(1); IIC_Wait_Ack(); // 等待应答 }原理I2C 发送数据规则SCL 低 → 放数据SCL 高 → 数据被读取高位先发模块 7发送命令 / 发送数据OLED 指令规则发送命令设置 OLED亮度、地址、模式等static void Write_IIC_Command(unsigned char IIC_Command) { OLED_IIC_Start(); Write_IIC_Byte(0x78); // OLED 地址 Write_IIC_Byte(0x00); // 0x00 写命令 Write_IIC_Byte(IIC_Command); OLED_IIC_Stop(); }发送数据显示内容点亮像素static void Write_IIC_Data(unsigned char IIC_Data) { OLED_IIC_Start(); Write_IIC_Byte(0x78); // OLED 地址 Write_IIC_Byte(0x40); // 0x40 写数据 Write_IIC_Byte(IIC_Data); OLED_IIC_Stop(); }超级重点0x00 写命令0x40 写数据0x78 OLED 设备地址这是 SSD1306 严格规定的通信格式模块 8写命令 / 写数据 封装函数void OLED_WR_Byte(unsigned char data, unsigned char cmd) { if(cmd) Write_IIC_Data(data); // cmd1 → 数据 else Write_IIC_Command(data);// cmd0 → 命令 }模块 9设置光标坐标决定在哪里显示void OLED_Set_Pos(unsigned char x,unsigned char y) { OLED_WR_Byte(0xb0y,OLED_CMD); // 设置页地址Y OLED_WR_Byte((x0x0f),OLED_CMD); // 列地址低4位 OLED_WR_Byte(((x0xf0)4)|0x10,OLED_CMD); // 列地址高4位 }OLED 128×64 分为8 页 × 128 列一页 8 行所以 Y 方向 0~7X 方向 0~127模块 10开显示 / 关显示void OLED_Display_On(void) { OLED_WR_Byte(0x8D,OLED_CMD); OLED_WR_Byte(0x14,OLED_CMD); // 开启电荷泵 OLED_WR_Byte(0xAF,OLED_CMD); // 开显示 } void OLED_Display_Off(void) { OLED_WR_Byte(0x8D,OLED_CMD); OLED_WR_Byte(0x10,OLED_CMD); // 关闭电荷泵 OLED_WR_Byte(0xAE,OLED_CMD); // 关显示 }模块 11清屏函数void OLED_Clear(void) { unsigned char i,n; for(i0;i8;i) // 8页 { OLED_WR_Byte(0xb0i,OLED_CMD); OLED_WR_Byte(0x00,OLED_CMD); OLED_WR_Byte(0x10,OLED_CMD); for(n0;n128;n) // 128列 OLED_WR_Byte(0,OLED_DATA); // 全部写0 } }模块 12显示一个 ASCII 字符void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr) { unsigned char c 0,i 0; c chr - ; // 偏移量计算 if(SIZE 16) // 16×8大小字符 { OLED_Set_Pos(x,y); for(i0;i8;i) OLED_WR_Byte(F8X16[c*16i],OLED_DATA); OLED_Set_Pos(x,y1); for(i0;i8;i) OLED_WR_Byte(F8X16[c*168i],OLED_DATA); } else // 6×8大小 { OLED_Set_Pos(x,y); for(i0;i6;i) OLED_WR_Byte(F6x8[c][i],OLED_DATA); } }原理一个 16×8 字符占2 页先写上 8 行再写下 8 行数据来自codetab.h字库模块 13OLED 初始化最关键必须按顺序发void OLED_Init(void) { OLED_GPIO_Init(); delay_ms(200); OLED_WR_Byte(0xAE,OLED_CMD); // 关显示 OLED_WR_Byte(0x00,OLED_CMD); // 列低地址 OLED_WR_Byte(0x10,OLED_CMD); // 列高地址 OLED_WR_Byte(0x40,OLED_CMD); // 起始行 OLED_WR_Byte(0xB0,OLED_CMD); // 页地址 OLED_WR_Byte(0x81,OLED_CMD); OLED_WR_Byte(0xFF,OLED_CMD); // 亮度 OLED_WR_Byte(0xA1,OLED_CMD); // 左右反转 OLED_WR_Byte(0xA6,OLED_CMD); // 正常显示 OLED_WR_Byte(0xA8,OLED_CMD); OLED_WR_Byte(0x3F,OLED_CMD); // 64行 OLED_WR_Byte(0xC8,OLED_CMD); // 上下反转 OLED_WR_Byte(0xD3,OLED_CMD); OLED_WR_Byte(0x00,OLED_CMD); OLED_WR_Byte(0xD5,OLED_CMD); OLED_WR_Byte(0x80,OLED_CMD); OLED_WR_Byte(0xD9,OLED_CMD); OLED_WR_Byte(0xF1,OLED_CMD); OLED_WR_Byte(0xDA,OLED_CMD); OLED_WR_Byte(0x12,OLED_CMD); OLED_WR_Byte(0xDB,OLED_CMD); OLED_WR_Byte(0x40,OLED_CMD); OLED_WR_Byte(0x8D,OLED_CMD); OLED_WR_Byte(0x14,OLED_CMD); OLED_WR_Byte(0xAF,OLED_CMD); // 开显示 OLED_Clear(); OLED_Set_Pos(0,0); }作用按 SSD1306 官方要求初始化时钟驱动路数内存模式扫描方向对比度开启电荷泵必须开否则不亮四、oledr.h头文件#ifndef _OLEDR_H #define _OLEDR_H #include stm32f10x.h // 1. I2C 引脚操作宏定义 #define OLED_SCLK_Set() GPIO_SetBits(GPIOB, GPIO_Pin_0) // SCL1 #define OLED_SCLK_CLR() GPIO_ResetBits(GPIOB, GPIO_Pin_0) // SCL0 #define OLED_SDIN_Set() GPIO_SetBits(GPIOB, GPIO_Pin_1) // SDA1 #define OLED_SDIN_CLR() GPIO_ResetBits(GPIOB, GPIO_Pin_1) // SDA0 #define OLED_READ_SDIN() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) // 读SDA电平 // 2. 命令/数据标志位 #define IIC_ACK 0 // 应答信号 #define IIC_NO_ACK 1 // 非应答信号 #define OLED_CMD 0 // 写命令 #define OLED_DATA 1 // 写数据 // 3. 字库选择 #define SIZE 16 // 字体大小1616*8, 其他6*8 // 4. 函数声明 void OLED_WR_Byte(unsigned char data, unsigned char cmd); void OLED_Set_Pos(unsigned char x,unsigned char y); void OLED_Display_On(void); void OLED_Display_Off(void); void OLED_Clear(void); void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr); void OLED_Init(void); #endif详细解释1. 防止头文件重复包含#ifndef _OLEDR_H #define _OLEDR_H ... #endif作用防止一个.c文件多次包含同一个头文件导致重复定义报错。所有正式的 C 语言头文件都必须这么写。2. 包含 STM32 库文件#include stm32f10x.h作用引入 STM32 的标准库让编译器认识GPIO_SetBits、GPIO_InitTypeDef等函数和结构体。没有这一行所有 GPIO 操作都无法编译。3. I2C 引脚操作宏核心#define OLED_SCLK_Set() GPIO_SetBits(GPIOB, GPIO_Pin_0) #define OLED_SCLK_CLR() GPIO_ResetBits(GPIOB, GPIO_Pin_0) #define OLED_SDIN_Set() GPIO_SetBits(GPIOB, GPIO_Pin_1) #define OLED_SDIN_CLR() GPIO_ResetBits(GPIOB, GPIO_Pin_1) #define OLED_READ_SDIN() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)作用把 STM32 复杂的库函数简化成一句话SCLK_Set()→ 让 PB0 输出高电平SCLK_CLR()→ 让 PB0 输出低电平SDIN_Set()→ 让 PB1 输出高电平SDIN_CLR()→ 让 PB1 输出低电平READ_SDIN()→ 读取 PB1 现在是高电平还是低电平为什么要写宏原来代码要写GPIO_ResetBits(GPIOB, GPIO_Pin_0);现在只需要写OLED_SCLK_CLR();代码更简洁、可读性更强、移植更方便。硬件对应PB0 I2C_SCL时钟线PB1 I2C_SDA数据线4. 命令 / 数据标志#define OLED_CMD 0 // 写命令 #define OLED_DATA 1 // 写数据OLED 通信规则给0→ 发送命令设置屏幕、亮度、坐标给1→ 发送数据显示内容、点亮像素点5. 字体大小定义#define SIZE 16SIZE16→ 使用16×8大小的 ASCII 字符两行高度不是 16 → 使用6×8大小的 ASCII 字符一行高度6. 函数声明void OLED_Init(void); void OLED_Clear(void); void OLED_ShowChar(...); ...作用告诉编译器这些函数在oledr.c里实现这样main.c包含头文件后就可以直接调用这些函数。五、main.c主函数详细解释#include stm32f10x.h #include main.h #include stdio.h #include oledr.h // 毫秒级延时函数 void delay(uint16_t time) { uint16_t i 0; while(time--) { i 12000; while(i--); } } extern const unsigned char BMP2[]; int main(void) { // 中断分组本工程暂时没用 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 1. 初始化 OLED OLED_Init(); // 2. 等待 2 秒 delay(2000); // 3. 清屏全部熄灭 OLED_Clear(); // 4. 显示字符 OLED_ShowChar(30,2,O); OLED_ShowChar(38,2,K); // 死循环程序停在这里 while(1) { } }1. 头文件包含#include stm32f10x.h #include main.h #include oledr.hstm32f10x.hSTM32 核心库oledr.hOLED 驱动头文件必须包含否则不能用 OLED 函数2. 延时函数void delay(uint16_t time) { uint16_t i 0; while(time--) { i 12000; while(i--); } }作用毫秒级粗略延时用于初始化后等待屏幕稳定、方便观察效果3. 主函数入口int main(void) {程序从这里开始执行4. OLED 初始化最重要OLED_Init();内部执行初始化 PB0、PB1 为开漏输出发送一系列 SSD1306 初始化命令打开屏幕电荷泵必须开否则不亮默认清屏5. 延时 2 秒delay(2000);等待屏幕稳定防止刚上电就操作导致异常6. 清屏OLED_Clear();把屏幕所有点都写 0 →全部熄灭7. 显示字符OLED_ShowChar(30,2,O); OLED_ShowChar(38,2,K);格式OLED_ShowChar(x, y, 字符)x30→ 水平第 30 列y2→ 垂直第 2 页一页 8 行功能在屏幕上显示OK
http://www.zskr.cn/news/1315690.html

相关文章:

  • 工业算力服务器一体机:智能制造的硬核算力底座
  • VS Code CircuitPython扩展实战:嵌入式开发环境搭建与高效调试指南
  • 【Git】常用命令:commit提交,push推送,merge,branch添加分支
  • 告别命令行!ESP32安全启动V2的图形化实战:Flash下载工具配置Secure Boot全记录
  • 2026.5.18-要闻
  • 当RRT*遇见CNN:一份给路径规划新手的‘开箱即用’指南与避坑心得
  • NotebookLM评论反馈功能全链路拆解(从Prompt响应延迟到语义锚定失效的7个致命断点)
  • OpenEuler桌面化踩坑实录:从黑屏登录界面到完美远程访问,我的xfce+xrdp配置全记录
  • Equalizer APO完整指南:免费系统级音频均衡器从零开始
  • 算法工程师简历封神指南:项目细节 + 论文 / 竞赛成果缺一不可
  • ECC 从安装到精通
  • 外部半流式图算法:大规模图数据处理新突破
  • 给排水设计新人必看:如何用SWMM快速搭建一个‘麻雀虽小五脏俱全’的练习模型?
  • 利用taotoken为开源ai agent项目hermes提供稳定后端
  • 3个让你工作效率翻倍的macOS窗口管理技巧:Topit如何解决多任务处理的烦恼
  • 从密码学RSA到区块链:二次剩余(Cipolla算法)在CTF和加密实战中的妙用
  • 2026年八大上门服务预约小程序:解锁高效生活新体验
  • Godot实战(一)—— 用C#构建2D躲避游戏的核心机制
  • 不止是图像采集:基于RK3588 NPU和FPGA,如何给Cameralink相机注入AI灵魂(附目标跟踪/电子稳像实战)
  • 植物树枝叶片果实检测数据集7220张VOC+YOLO格式
  • AI为编程赋能增效:从“古法编程”到氛围编程的范式革命
  • MD5是哈希,不是加密,防君子不防小人
  • RISC-V vs MIPS:同为RISC,指令集设计哲学与编码格式有何不同?
  • PSI5协议:汽车传感器同步通信的基石
  • 高层次综合设计算法-常见问题记录(一)
  • Linux Ext 调度器的 BPF 程序集成:用户态与内核态的交互
  • 避开这些坑!ZYNQ裸机下PS+PL双网口LWIP调试常见问题与解决方案
  • FcaNet:从频域视角重构通道注意力,超越GAP的单一信息瓶颈
  • 用Python和nilmtk库,5分钟上手非侵入式用电分析(附实战代码)
  • FDE(前沿部署工程师):AI时代年薪百万的新贵,到底值不值得冲?