前三篇几乎已经完成了OLED屏显示的全部内容,当然,还缺少一些精致的图形,这方面可以自己动手做一些喜欢的图形,也可以移植一下大佬们图形库,并不是很复杂所有就不多赘述了。这一篇主要是优化屏幕的刷新速率,让OLED的刷新率尽可能的快一些,也就是经常说的高帧,优化刷新速率分为两篇,分别用软件IIC和硬件IIC,这一篇是软件IIC。

之前的屏幕刷新主要是对屏幕的某一页的一个区间读写,如果一个屏幕要写两个或者两个以上的字符时,就要调用多次寻址操作,但是如果在我们在单片机中构建一个和SSD1306芯片的GRAM等大的SRAM内存,直接在单片机的SRAM数组上写,最后再SRAM数组的数据一次性写到SSD1306中,就只用调用一次写函数,可以大大提高芯片的读写操作。
构建帧缓冲区后,再优化软件IIC,使用stm32位带操作,直接操作GPIO管脚的高低电平,更快速简洁,位带是一种在单片机中使用的技术,它可以将单个位(bit)与一个特定的内存地址关联起来。
就如先前所说, 屏幕刷新主要是对屏幕的某一页的一个区间读写,但是每写一个字符就必须要寻址一次,当要写十个字符就必须要寻址十次,不仅效率不高还非常麻烦,为了解决这一麻烦提高工作效率,可以在单片机内部构建一个和OLED屏幕SSD1306芯片的GRAM等大的SRAM缓冲区(在单片机内部构建缓冲区数组),因为是为刷新帧率而存在的数组也称为帧缓冲区或显存。有了这片缓冲区就不用频繁的寻址了,如果要写十个字符就直接在帧缓冲区中操作,等这十个字符写好后再一次性把缓冲区的数据写入屏幕的GRAM中,这种操作只需要一次寻址,大大提高工作效率。按照正点原子的说法:

单片机有无构建帧缓冲区示意图:

构建一个缓冲数组OLED_GRAM[8][128](实际上是SRAM)为帧缓冲区,之后所有的显示操作都放在这个帧缓冲区中,修改完成图形数据后,再一次性将单片机内部的OLED_GRAM写入SSD1306的GRAM中。
//构建OLED帧缓冲区
uint8_t OLED_GRAM[64/8][128];
构建帧缓冲区后,就面临两个问题,1、如何在帧缓冲区写数据?2如何将帧缓冲区的数据写入OLED屏幕上?在帧缓冲区中写数据可以使用画点函数,把帧缓冲区的数据写入SSD1306的GRAM中使用IIC通信。
先说在帧缓冲区写数据,编写画点函数,对帧缓冲区写入数据。保存当前要写入的页面中八个点,再修改其中要修改的点,最后把修改好的八个点写入帧缓冲区,就修改了其中一个点了。画点函数对每次对帧缓冲区的一个点进行写0和1,需要那个点亮起就对该点写1,需要那个点灭掉就对该点写0,但是如果把该写1的地方写0,把该写0的地方写1,那么就会出现截然相反的效果,这就是帧缓冲区反显操作,这种操作并不需要对模块写入反显命令就可以实现。
/**
* @brief OLED帧缓冲画点函数
* @param x 行位置
* @param y 列位置
* @param mode 显示模式:1--正显 0--反显
* @retval 无
* @explain 在帧缓冲区任意位置正/反显示一个点
*/
void OLED_Framebuffer_Drawpoint(uint8_t x, uint8_t y, uint8_t mode)
{
uint8_t page, line, temp = 0;
if(x>128 || y>64) return;//超出屏幕范围保护
page = y/8; // y方向8个字节 8Byte*8Bit = 64Bit y坐标除以8得要操作的Byte位
line = y%8; // y方向8个字节 8Byte*8Bit = 64Bit y坐标取余8得要操作的Bit位
temp = OLED_GRAM[page][x];
if(mode) temp |= 1<<line;
else temp &= ~(1<<line);
OLED_GRAM[page][x] = temp;
}
写入字符和之前一样,画点函数和写数据函数(OLED_Write_Data()) 有部分差异,写数据函数(OLED_Write_Data()) 是一次写入8 bit,画点函数一次只写入一个点,所以在我们写入字符时要在之前的基础上加上八次循环,再相应的偏移八位即可,mode表示正/反显。
/**
* @brief OLED在帧缓冲绘制一个字符
* @param x 行位置
* @param y 列位置
* @param Fontsize 字体大小
* @param Char 要显示的一个字符
* @param mode 显示模式:1--正显 0--反显
* @retval 无
*/
void OLED_Framebuffer_DrawChar(uint8_t x, uint8_t y, uint16_t Fontsize, const char Char, uint8_t mode)
{
uint8_t i, j, k;
uint8_t temp;
for(k=0; k<Fontsize/8; k++) {
switch(Fontsize) {
case 8:{
for(i=0; i<6; i++){//字宽为6
temp = Ascii_6x8[Char - ' '][i+k*6];
for (j=0; j<8; j++) {//画8个点
if(mode) OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 1:0);
else OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 0:1);
}
}
break;
}
case 16: {
for(i=0; i<8; i++) {//字宽为8
temp = Ascii_8x16[Char - ' '][i+k*8];
for (j=0; j<8; j++) {//画8个点
if(mode) OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 1:0);
else OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 0:1);
}
}
break;
}
case 24: {
for(i=0; i<12; i++) {//字宽为12
temp = Ascii_12x24[Char - ' '][i+k*12];
for (j=0; j<8; j++) {//画8个点
if(mode) OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 1:0);
else OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 0:1);
}
}
break;
}
}
}
}
写入字符串可以照搬,和没有显存之前相差无异,只是在后面加上一个mode表示正/反显。
/**
* @brief OLED在帧缓冲绘制字符串
* @param x 行位置
* @param y 列位置
* @param Fontsize 字体大小
* @param String 显示字符串,
* @param mode 显示模式:1--正显 0--反显
* @retval 无
*/
#include <string.h>
void OLED_Framebuffer_DrawString(uint8_t x, uint8_t y, uint16_t Fontsize, const char* String, uint8_t mode)
{
uint8_t i, len;
len = strlen(String);
for(i=0; i<len; i++) {
switch(Fontsize) {
case 8:OLED_Framebuffer_DrawChar(x+i*6, y, Fontsize, String[i], mode);
break;
case 16:OLED_Framebuffer_DrawChar(x+i*8, y, Fontsize, String[i], mode);
break;
case 24:OLED_Framebuffer_DrawChar(x+i*12, y, Fontsize, String[i], mode);
break;
}
}
}
在原有的基础上,和写入字符一样,偏移八位即可。
/**
* @brief OLED在帧缓冲区绘制汉字
* @param x 行位置
* @param y 列位置
* @param Chinese 汉字,
* @param mode 显示模式:1--正显 0--反显
* @retval 无
*/
void OLED_Framebuffer_DrawChinese(uint8_t x, uint8_t y, uint8_t Chinese, uint8_t mode)
{
uint8_t i, j, k;
uint8_t temp;
for(k=0; k<2; k++) {
for (i=0; i<16; i++) {//字宽为24
temp = Chinese_16x16[Chinese][i+k*16];
for (j=0; j<8; j++) {//画8个点
if(mode) OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 1:0);
else OLED_Framebuffer_Drawpoint(x+i, y+j+k*8, (temp>>j & 0x01) ? 0:1);
}
}
}
}
图片也一样,照猫画虎,直接写入。
/**
* @brief OLED显示图片
* @param mode 显示模式:1--正显 0--反显
* @retval 无
* @explain 无
*/
void OLED_Framebuffer_DrawImageBMG(uint8_t mode)
{
uint8_t i, j, k;
uint8_t temp;
for(k=0; k<8; k++) {
for (i=0; i<128; i++) {
temp = ImageBMG64x128[i+k*128];
for (j=0; j<8; j++) {//画8个点
if(mode) OLED_Framebuffer_Drawpoint(i, j+k*8, (temp>>j & 0x01) ? 1:0);
else OLED_Framebuffer_Drawpoint(i, j+k*8, (temp>>j & 0x01) ? 0:1);
}
}
}
}
