Cubemx+硬件I2C驱动OLED屏幕
华工RobotIC实验室
2023-12-21
MCU
基本原理硬件I2C相比于软件I2C,就是通过HAL库自动生成的I2C代码,使用专门的函数实现I2C协议代替手动去设置协议 首先打开Cubemx,进行时钟等基本配置,这里就不做过多赘述,然后我们直接打开I2C,不需要做其他配置(如果你想加快刷新率可以将Speed Modes改成Fast) 配置完成后我们生成代码,然后将OLED驱动库导入,导入的方法这里也不过多赘述 导入完成后我们只需要#include”oled.h”然后初始化oledOLED_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都为0void 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.hconst 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显示的库就完成了,我们只需要做到字符的显示,那么之后的数字,有符号数字,二进制数,十六进制数,字符串等,都可以通过一些运算和循环调用字符显示来完成。作者:石珂鸣