目录
1. 目标
通过HAL库操作I2C,点亮OLED屏
2. 背景知识
2.1 I2C基础
I2C是一种同步半双工总线协议,具有主机和从机的区分
典型的I2C需要:数据线SDA,时钟线SCL
起始:SCL为高,SDA有下降沿。停止:SCL为高,SDA有上升沿
逻辑0:SCL为高,SDA稳定低电平。逻辑1:SCL为高,SDA稳定高电平
写数据流程:
起始 - 设备地址(7位)+操作(1位)+应答(1位) - 寄存器地址(8位)+应答(1位) - 数据(8位)+应答(1位) - 停止
读数据流程:
起始 - 设备地址(7位)+操作(1位)+应答(1位) - 寄存器地址(8位)+应答(1位) - 起始 - 设备地址(7位)+操作(1位) - 数据(8位)+应答(1位) - 停止
2.2 0.96寸OLED基础
可能有错,所以,这一小节可以跳过,只是出于文章的逻辑完整性放在这里,提供参考借鉴
0.96寸OLED屏幕像素为128x64
而实际控制OLED的是内部的SSD1315,SSD1315是一款集成了控制器的单芯片CMOS OLED/PLED驱动器,SSD1315可以直接从其内部的128 x 64位图形显示数据RAM(GDDRAM)显示数据
主机和OLED通过I2C交换数据,所以首先需要知道从机的地址,查阅手册,在写模式下,地址可以是 0111 1000 即0x78
对于操作,显然应该有命令和数据的区分,根据手册,区分应该是根据D/C#高低电平控制的,但实际上HAL库对这个字段的描述是MemAddress寄存器地址?不是很懂,这里
根据手册,结合代码,写入指令应该是0x00,数据应该是0x40 (D/C#高电平)
对于指令,可以参考代码注释和手册,这里就不过多叙述了
对于数据,需要知道数据如何存储(虽然也没有弄太清楚),结合手册,大概长这样。一共分为8页,每页可以划分为128SEG或8COM
在不重映射的前提下,大概是这样的,以第2页为例
然后写入数据大概有三种模式:页模式,水平模式和垂直模式
3 代码移植
显然,不是所有硬件都需要重复造轮子,所以,选用卖家代码为模板进行移植
代码为标准库+软件I2C,目标将其改写为HAL库+硬件I2C
3.1 改写为HAL库
先改写为HAL库检查代码是否能够正常运行
按照哪里标红改哪里的原则进行修改
先到oled.h中检查
删除sys.h后,先加入简单的宏定义
#include <stdio.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
再把标准库所写的宏定义进行改写
#include "main.h"
// 标准库
#define OLED_SCL_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_0)
#define OLED_SCL_Set() GPIO_SetBits(GPIOA,GPIO_Pin_0)
// HAL库
#define OLED_SCL_Clr() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET)
#define OLED_SCL_Set() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET)
接着在oled.c中检查
在OLED_init()中,对GPIO口进行了初始化,改为CubeMX初始化,GPIO口设置为输出模式
接下来把延时函数替换为HAL库
// 原始代码
delay_ms(200);
// HAL库
HAL_Delay(200);
最后回到main.c,添加测试代码进行测试
OLED_Init ();
OLED_ColorTurn(0);
OLED_DisplayTurn(0);
OLED_ShowString(0, 0, "Hello World !", 8, 1);
OLED_Refresh ();
烧录代码,我们可以惊奇的发现,显示屏显示Hello World ! ,说明代码不存在问题
3.2 改写为硬件I2C
通过分析,可以发现实际上对I2C的操作基于以下部分代码
一部分是在oled.h中的宏定义,另一部分是在oled.c中的函数
#define OLED_SCL_Clr() HAL_GPIO_WritePin(GPIOF,GPIO_PIN_0,GPIO_PIN_RESET)//SCL
#define OLED_SCL_Set() HAL_GPIO_WritePin(GPIOF,GPIO_PIN_0,GPIO_PIN_SET)
#define OLED_SDA_Clr() HAL_GPIO_WritePin(GPIOF,GPIO_PIN_1,GPIO_PIN_RESET)//DIN
#define OLED_SDA_Set() HAL_GPIO_WritePin(GPIOF,GPIO_PIN_1,GPIO_PIN_SET)
//延时
void IIC_delay (void);
//起始信号
void I2C_Start (void);
//结束信号
void I2C_Stop (void);
//写入一个字节
void Send_Byte (u8 dat);
//发送一个字节
void OLED_WR_Byte (u8 dat, u8 mode);
根据2.1 I2C基础知识,我们可以将代码流程进行梳理,并进行改写
/**
* @brief 通过I2C发送1字节数据
* @param dat 数据
* @param isData 是数据吗(数据/指令)
*/
void OLED_WR_Byte (u8 dat, u8 isData) {
u8 oledAddress = 0x78;
u8 dataAddress = 0x40;
u8 commondAddress = 0x00;
if (isData)
HAL_I2C_Mem_Write(&hi2c2, oledAddress, dataAddress, I2C_MEMADD_SIZE_8BIT, &dat, 1, 20);
else
HAL_I2C_Mem_Write(&hi2c2, oledAddress, commondAddress, I2C_MEMADD_SIZE_8BIT, &dat, 1, 20);
}
将软件I2C宏定义和函数删除后, 根据标红地方,该删删,该改为OLED_WR_Byte就改
至此,改写为硬件I2C就结束了,可以在CubeMX中更改I2C速率以享受高帧率体验
另外,在OLED_Refresh函数中会传输一个128字节的数据,可以先把数据放在一个数组里面,再一次性发送数组(128字节),这样帧率又可以提高亿点
4 流程略解
所有画图操作,本质上都是操作这个数组 u8 OLED_GRAM[144][8]; 其实这里128应该就够了
数组共128行,代表128个SEG;每行8列,每列代表一页中的8个COM(1字节8位)
只有在OLED_Refresh (void)或者void OLED_Clear (void)时,会将该数组通过I2C发送到OLED实现刷新
写入时是按照页模式写入,需一页一页写,所以在OLED_Refresh (void)中会将数组做一次转换再进行写入