基本原理
硬件I2C相比于软件I2C,就是通过HAL库自动生成的I2C代码,使用专门的函数实现I2C协议代替手动去设置协议
首先打开Cubemx,进行时钟等基本配置,这里就不做过多赘述,然后我们直接打开I2C,不需要做其他配置(如果你想加快刷新率可以将Speed Modes改成Fast)
配置完成后我们生成代码,然后将OLED驱动库导入,导入的方法这里也不过多赘述
导入完成后我们只需要
#include”oled.h”
然后初始化oled
OLED_Init();
之后就可以使用函数驱动OLED了
接下来我们开始本章的重点,OLED驱动库的原理解析
打开oled.c的文件
我们可以找到传输函数的实例:
void OLED_WR_CMD(uint8_t cmd)
{
HAL_I2C_Mem_Write(&hi2c1 ,0x78,0x00,I2C_MEMADD_SIZE_8BIT,&cmd,1,0x100);
}
&hi2c1: 这是 I2C 外设的句柄,用于指定使用的 I2C 总线。
0x78: 这是 OLED 显示屏的 I2C 地址,指定要通信的设备。
0x00: 这是 OLED 显示屏的内存地址,指定要写入数据的位置。在这里,它是命令地址,用于发送命令给 OLED 显示屏。
I2C_MEMADD_SIZE_8BIT: 这是内存地址的大小,指定地址的精度为 8 位。
&cmd: 这是指向要发送的命令数据的指针。在这里,它是 uint8_t 类型的变量 cmd 的地址。
1: 这是要写入的数据的字节数,这里是一个字节的命令。
0x100: 这是超时参数,指定在发生超时之前等待的时间。
了解了传输函数,我们只需要简单了解初始化显示范围的设定,就可以理解OLED驱动库的原理,还是在oled的源文件中
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
OLED_WR_CMD(0xB0 + y);
OLED_WR_CMD(0x10 + ((x & 0xF0) >> 4));
OLED_WR_CMD(0x00 + (x & 0x0F));
}
这是设置显示位置的函数,其中0xB0,0x10,0x00定义了0.96寸屏幕的初始像素点,不同屏幕的初始像素点不同,比如通过测试0.42寸的屏幕的初始像素点为0xB3,0x2B,0x1C,然后通过过传递X和Y来和初始位置进行相加操作来设置显示的像素点。(网上找的代码是用了“或”运算符导致“0xB0 | 1”和“0xB1 | 1”是同一个值,坑死我了)
(如果想要自定义初始位置,可以自己去调整这三个数,其中0xB0每加1,则y初始像素点下移8像素,0x10(x的高位)每加1,则x的初始像素点右移8像素,0x01(x的低位每加1,则x的初始像素点右移1像素))
然后我们需要了解一下初始化显示点的机制,设置初始点需要设置其页地址和列地址,其中页地址就是Y,列地址就是X,而页地址是每八个像素点算一页,也就是说,0.96寸的OLED是64X128分辨率,那就有8页(0~7)(也就是说0xB0和0xB1这两个像素点之间隔了8个像素)
我们结合show_char的函数来分析一下其原理:
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_Set_Pos((Column - 1) * 8, (Line - 1) * 2); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WR_DATA(F8X16[Char - ' '][i]); //显示上半部分内容
}
OLED_Set_Pos((Column - 1) * 8, (Line -1) * 2+1 ); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WR_DATA(F8X16[Char - ' '][i + 8]); //显示下半部分内容
}
}
首先假设我们line和column传进来是1和1,那么第一个初始点设置的代码为:
OLED_Set_Pos((Column - 1) * 8, (Line - 1) * 2);
也就是传进去set_pos函数的x和y都为0
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
OLED_WR_CMD(0xB0 + y);
OLED_WR_CMD(0x10 + ((x & 0xF0) >> 4));
OLED_WR_CMD(0x00 + (x & 0x0F));
}
然后在这个函数内,首先OLED_WR_CMD(0xB0 + y)对0xB0和y进行一个加运算,传进来多少,就往下多少页,比如现在传进来的y是0,那么就加0页,还是0xB0,就是初始位置。
然后在这个函数内,首先OLED_WR_CMD(0xB0 + y)对0xB0和y进行一个加运算,传进来多少,就往下多少页,比如现在传进来的y是0,那么就加0页,还是0xB0,就是初始位置。
然后对于x的值我们用了两次传输,这是因为x是0~128,而y只是0~7,所以x需要八位二进制数来表示,那我们就需要将其分开来传输,分为高四位和低四位, OLED_WR_CMD(0x10 + ((x & 0xF0) >> 4));
其中0xF0转换为二进制数就是11110000,和x进行与操作,那么低四位就被置零,高四位保持不变,然后因为数据传输是从右往左的,所以我们要将与完的数据右移四位也就是变成0000xxxx,其中“xxxx”是传进来的x的高四位二进制数,这样我们就能将数据发出去了。
对于第四位同理,不过因为第四位x是与0x0F也就是00001111进行与操作,那么与完之后就不需要右移可以之间发送。
以上我们就完成了对显示位置的自定义
接下来只需要让对应的像素点亮起来就行了
for (i = 0; i < 8; i++)
{
OLED_WR_DATA(F8X16[Char - ' '][i]); //显示上半部分内容
}
这段代码的作用就是让对应的像素点亮起来
首先我们要了解一下字模库
我们打开oledfont.h
const unsigned char F8X16[][16]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//sp /0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! /1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" /2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# /3
}
这是只截取了非常一小部分的8X16字模库,也就是0.96寸oled适配的字模
其中我们定义了一个字符二维数组F8X16[][16],其中第二维是我们限制为16,也就是字模的列数,例如第二行是“!”的字模,它一共由16个元素组成,因为我们的所以字模都是由16个元素组成,那么我们只需要更改第一维的数字,就可以选择显示不同的字符,比如循环显示从F8X16[3][0]到F8X16[3][16]可以显示字符“#”
但是由于我们的y每一页只有8像素,而F8X16,需要在8X16的分辨率显示,那么我吗就只能将字模分为上半部分和下半部分分别显示
for (i = 0; i < 8; i++)
{
OLED_WR_DATA(F8X16[Char - ' '][i]); //显示上半部分内容
}
这段代码就是只让其显示前八个元素,显示下半部分内容同理
以上我们就完成的OLED的显示
这样我们的一个OLED显示的库就完成了,我们只需要做到字符的显示,那么之后的数字,有符号数字,二进制数,十六进制数,字符串等,都可以通过一些运算和循环调用字符显示来完成。
作者:石珂鸣
云FAE请选择您要咨询的方向,专业工程师为您服务!咨询客服