STM32+OLED屏(软件IIC+位带+帧缓冲区)刷新速率优化(四)

这篇具有很好参考价值的文章主要介绍了STM32+OLED屏(软件IIC+位带+帧缓冲区)刷新速率优化(四)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

       1.1   STM32+OLED屏初始化(一) 

       1.2  STM32+OLED屏显示字符串、汉字、图片(二)

       1.3  STM32+OLED屏多级菜单显示(三)

 1.优化刷新速率

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

软件iic速率,stm32,嵌入式硬件,单片机

        之前的屏幕刷新主要是对屏幕的某一页的一个区间读写,如果一个屏幕要写两个或者两个以上的字符时,就要调用多次寻址操作,但是如果在我们在单片机中构建一个和SSD1306芯片的GRAM等大的SRAM内存,直接在单片机的SRAM数组上写,最后再SRAM数组的数据一次性写到SSD1306中,就只用调用一次写函数,可以大大提高芯片的读写操作。 

        构建帧缓冲区后,再优化软件IIC,使用stm32位带操作,直接操作GPIO管脚的高低电平,更快速简洁,位带是一种在单片机中使用的技术,它可以将单个位(bit)与一个特定的内存地址关联起来。 

2.帧缓冲区

        就如先前所说, 屏幕刷新主要是对屏幕的某一页的一个区间读写,但是每写一个字符就必须要寻址一次,当要写十个字符就必须要寻址十次,不仅效率不高还非常麻烦,为了解决这一麻烦提高工作效率,可以在单片机内部构建一个和OLED屏幕SSD1306芯片的GRAM等大的SRAM缓冲区(在单片机内部构建缓冲区数组),因为是为刷新帧率而存在的数组也称为帧缓冲区或显存。有了这片缓冲区就不用频繁的寻址了,如果要写十个字符就直接在帧缓冲区中操作,等这十个字符写好后再一次性把缓冲区的数据写入屏幕的GRAM中,这种操作只需要一次寻址,大大提高工作效率。按照正点原子的说法:

软件iic速率,stm32,嵌入式硬件,单片机

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

 软件iic速率,stm32,嵌入式硬件,单片机软件iic速率,stm32,嵌入式硬件,单片机 

2.1 构建帧缓冲区与画点函数

        构建一个缓冲数组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;
}

2.2 在帧缓冲区绘制一个字符

        写入字符和之前一样,画点函数和写数据函数(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;
			}
		}
	}
}

2.3 在帧缓冲区绘制一串字符

        写入字符串可以照搬,和没有显存之前相差无异,只是在后面加上一个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;
		}
	}
}

 2.4 在帧缓冲区绘制一个汉字

        在原有的基础上,和写入字符一样,偏移八位即可。

/**
  * @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); 
			}
		}
	}
}

2.5 在帧缓冲区绘制一张图片

        图片也一样,照猫画虎,直接写入。

/**
  * @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); 
			}
		}
	}
}

 2.6 在帧缓冲区绘制一条直线

        再加一个画直线函数,此函数做了简化处理,只绘制水平线和垂直线,没有正/反显。

/**
  * @brief  OLED在帧缓冲区绘制直线
  * @param  x1,y1 起点坐标
  * @param  x2,y2 终点坐标
  * @retval 此函数做了简化处理,只绘制水平线和垂直线
  */
void OLED_Framebuffer_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{	
	uint16_t delta_x, delta_y, direction, a;
	
	//1、计算坐标增量
	delta_x = x2 - x1;
	delta_y = y2 - y1;
	
	//2、判断是否满足绘制条件(水平线、垂直线)
	if(delta_x && delta_y) return;//表示两个自增都不为0
	else if(!(delta_x || delta_y)) return;//表示两个自增都为0
	//当delta_y=0时,表示水平线;当delta_x=0时,表示垂直线
	if(delta_y == 0) { 
		direction = delta_x;
		a = x1;
	}
	else if(delta_x == 0) { 
		direction = delta_y;
		a = y1;
	}
	
	//3、开始绘制
	for(int i=a; i<direction+a; i++) {
		if(delta_x == 0) OLED_Framebuffer_DrawPoint(x1, i, 1);	
		else OLED_Framebuffer_DrawPoint(i, y1, 1);
	}
} 

2.7 在帧缓冲区绘制一个矩形 

        利用直线函数,画矩形

/**
  * @brief  OLED在帧缓冲区绘制矩形
  * @param  x1、x2 行位置
  * @param  y1、y2 列位置
  * @retval 无
  */
void OLED_Framebuffer_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
	OLED_Framebuffer_DrawLine(x1, y1, x2, y1);
	OLED_Framebuffer_DrawLine(x1, y1, x1, y2);
	OLED_Framebuffer_DrawLine(x1, y2, x2, y2);
	OLED_Framebuffer_DrawLine(x2, y1, x2, y2);
}

 2.8 在帧缓冲区绘制一个圆形

        最后再画一个圆形。

/**
  * @brief  OLED在帧缓冲区绘制圆
  * @param  x0、x0 圆心
  * @param  radius 列位置
  * @param  mode 显示模式:1--正显  0--反显
  * @retval 无
  */
void OLED_Framebuffer_DrawCircle(uint16_t x0, uint16_t y0, uint16_t radius, uint8_t mode)
{
	int a, b, di;
	a = 0;
	b = radius;	  
	di = 3-(radius<<1);             //判断下个点位置的标志
	while(a <= b) {
		OLED_Framebuffer_DrawPoint(x0+a, y0-b, mode);             //5
 		OLED_Framebuffer_DrawPoint(x0+b, y0-a, mode);             //0           
		OLED_Framebuffer_DrawPoint(x0+b, y0+a, mode);             //4               
		OLED_Framebuffer_DrawPoint(x0+a, y0+b, mode);             //6 
		OLED_Framebuffer_DrawPoint(x0-a, y0+b, mode);             //1       
 		OLED_Framebuffer_DrawPoint(x0-b, y0+a, mode);             
		OLED_Framebuffer_DrawPoint(x0-a, y0-b, mode);             //2             
		OLED_Framebuffer_DrawPoint(x0-b, y0-a, mode);             //7     	         
		a++;  
		if(di<0) di += 4*a+6;	//Bresenham画圆算法   
		else {
			di += 10+4*(a-b);   
			b--;
		}
	}
}

 3.数据写入

        虽然在帧缓冲区中绘制好了图像,但是并没有写到屏幕上,所有接下来的一步就是将帧缓冲区的数据写入屏幕上,可以直接使用之前写好的IIC通信协议将数据写入屏幕,不过之前的IIC通信协议高低电平的转变需要找到对应的位使用位运算或逻辑运算来实现,大大增加了单片机的负担,为了解决这样麻烦引入了位带操作。

3.1 位带操作 

        位带(Bit Band)是一种用于嵌入式系统中内存优化的技术。位带访问使得单个位的访问操作变得更加高效,有助于提高嵌入式系统的性能和资源利用率。STM32微控制器上的位带区是一种特殊的内存区域,用于对单个位进行读写操作。位带区在8位和16位的外设寄存器中使用,让STM32也可以像51单片机一样IO口置高置低,可以提供更高的速度和更灵活的位操作能力。在Cortex-M3权威指南中5.5节,有详细介绍:

软件iic速率,stm32,嵌入式硬件,单片机

        位带区的主要优势是可以通过使用C语言的位带操作符(例如*(bitband_base + bit_number))来直接对单个位进行操作,而无需进行读取-修改-写入的操作。这种直接访问位的方式可以提高代码的执行速度和效率,特别是在对多个位进行操作时。使用位带操作的公式:

软件iic速率,stm32,嵌入式硬件,单片机

//把“位带地址+位序号”转换成别名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr & 0xFFFFF)<<5)+(bitnum<<2))

//把该地址转换成一个指针
#define MEM_ADDR(addr) *((volatile unsigned long *) (addr))
    
//位带的实现---GPIOA/B/C IDR/ODR
#define PAin(bitnum)  MEM_ADDR(BITBAND(GPIOA_BASE + 0x08, bitnum))//GPIOA的IDR的第bitnum位的地址
#define PAout(bitnum) MEM_ADDR(BITBAND(GPIOA_BASE + 0x0C, bitnum))//GPIOA的ODR的第bitnum位的地址
#define PBin(bitnum)  MEM_ADDR(BITBAND(GPIOB_BASE + 0x08, bitnum))//GPIOB的IDR的第bitnum位的地址
#define PBout(bitnum) MEM_ADDR(BITBAND(GPIOB_BASE + 0x0C, bitnum))//GPIOB的ODR的第bitnum位的地址
#define PCin(bitnum)  MEM_ADDR(BITBAND(GPIOC_BASE + 0x08, bitnum))//GPIOB的IDR的第bitnum位的地址
#define PCout(bitnum) MEM_ADDR(BITBAND(GPIOC_BASE + 0x0C, bitnum))//GPIOB的ODR的第bitnum位的地址

在软件IIC中使用位带操作有以下好处:

  1. 简化代码:通过使用位带,可以直接对单个位进行操作,而不需要使用位运算或逻辑运算来实现。这样可以简化代码,提高代码的可读性和可维护性。

  2. 提高性能:由于位带操作是直接对单个位进行操作,而不需要对整个字节进行读取和写入,因此可以提高代码的执行效率和速度。

  3. 减少内存占用:使用位带可以减少对内存的占用,因为每个位都可以单独进行操作,而不需要为每个位都分配一个字节的内存空间。

  4. 方便调试:使用位带可以方便地对单个位进行调试和监测,可以更容易地定位和解决问题。

3.2 软件IIC使用位带操作

        从下面的宏定义可以看出来,使用位带操作和不使用位带操作有明显的区别。

//使用位带区域直接读写
#define IIC_W_SCL PBout(6)
#define IIC_W_SDA PBout(7)
#define IIC_R_SDA PBin(7)
//读写
read = IIC_R_SDA;
IIC_W_SDA = 1;

//不使用位带区域直接读写
#define IIC_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)(x))
#define IIC_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)(x))
#define IIC_R_SDA()  GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_7)
//读写
read = IIC_R_SDA();
IIC_W_SDA(1);

        软件IIC运行代码,IIC的起始信号、停止信号和应答信号和以前一样,读写也同样,不过为了简化代码,把应答信号放到写入函数内部去了。在之前的测试中,产生电平信号时不加延时也可以,但是使用位带操作后好像不行了,例如:在产生起始信号和停止信号时,直接操作GPIO管脚的高低电平,但是因为跳变太快必须得加2us的延时,不然无法产生可识别的时序,从而无法产生起始信号和停止信号。

/**
  * @brief  软件IIC起始信号
  * @param  无
  * @retval 无
  * @explain IIC开启需要在SCL(1)为高的时候拉低SDA(0)
  */
void MyIIC_Start(void)
{	
	IIC_W_SDA = 1;
	IIC_W_SCL = 1;
	delay_us(2);
	IIC_W_SDA = 0;
	delay_us(2);
	IIC_W_SCL = 0;//钳住总线
}

/**
  * @brief  软件IIC停止信号
  * @param  无
  * @retval 无
  * @explain IIC结束需要在SCL(1)为高的时候拉高SDA(1)
  */
void MyIIC_Stop(void)
{
	IIC_W_SDA = 0;
	IIC_W_SCL = 1;
	delay_us(2);
	IIC_W_SDA = 1;
	delay_us(2);
}

/**
  * @brief  软件IIC发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  * @explain 主机在SCL(0)低的时候只会发送一位,从机在SCL(1)为高的时候一次也只接收一位
  */
void MyIIC_SendByte(uint8_t Byte)
{
	uint8_t i;
	
	for (i=0; i<8; i++) {
		IIC_W_SCL = 0;//主机开始写
		delay_us(2);
		IIC_W_SDA = (Byte<<i & 0x80) ? 1 : 0;
		delay_us(2);
		IIC_W_SCL = 1;//从机开始读
		delay_us(2);
	}
	IIC_W_SCL = 0;//钳住总线
	delay_us(2);
	//额外的时钟,不处理应答
	IIC_W_SDA = 0;
	delay_us(2);
	IIC_W_SCL = 1;
	delay_us(2);
	IIC_W_SCL = 0;//钳住总线
}

3.3 OLED写入命令/数据函数

        直接搬入。

/**
  * @brief  OLED 写命令
  * @param  Command 写入命令
  * @retval 无
  * @explain 无
  */
void OLED_Write_Command(uint8_t Command)
{
	MyIIC_Start();
	MyIIC_SendByte(0x78);  	//写入从机地址
	MyIIC_SendByte(0x00);  	//写入命令
	MyIIC_SendByte(Command); //写命令
	MyIIC_Stop();      		//发送停止信号
}
 
/**
  * @brief  OLED 写数据
  * @param  Data 写入数据
  * @retval 无
  * @explain 无
  */
void OLED_Write_Data(uint8_t Data)
{
	MyIIC_Start();
	MyIIC_SendByte(0x78);  	//写入从机地址
	MyIIC_SendByte(0x40);  	//写入命令
	MyIIC_SendByte(Data);   //写命令
	MyIIC_Stop();      		//发送停止信号
}

 3.4 OLED的各种命令

        这里介绍一下OLED的三种寻址方式,页寻址方式水平寻址方式垂直寻址方式,通过写入命令选择相应的寻址方式,因为页寻址方式和水平寻址方式两者编码相通,所有比较常用的,垂直寻址方式用于一些特殊的场所。

        在页地址模式(页寻址方式)下,读写SSD1306的GRAM后,列地址指针自动增加1。如果列地址指针到达列结束地址,则列地址指针将被重置为列起始地址,而页面地址指针将不会被更改。用户必须设置新的页面和列地址,才能访问下一页RAM内容(只用设置一个光标就可以写一整页,换页要另外设置起始光标)。页面寻址模式的Page和Col的移动顺序如下所示:

软件iic速率,stm32,嵌入式硬件,单片机

        在列地址模式(水平寻址方式)下,读写SSD1306的GRAM后,列地址指针自动增加1。如果列地址指针到达列结束地址,则将列地址指针重置为列起始地址,页地址指针增加1。水平寻址模式下的页面和列地址点的移动顺序如下所示(设置一个初始位置从头写到尾,适用于写整个屏幕)。当列和页面地址指针都到达结束地址时,指针将重置为列起始地址和页面起始地址。请看下图:

软件iic速率,stm32,嵌入式硬件,单片机

        在行地址模式(垂直寻址方式)下,读写SSD1306的GRAM后,页面地址指针自动增加1。如果页面地址指针到达页面结束地址,则页面地址指针重置为页面起始地址,列地址指针增加1。垂直寻址模式的页面和列地址点的移动顺序如下所示。当列和页面地址指针都到达结束地址时,指针将重置为列起始地址和页面起始地址(同样从头写到尾,适用于写整个屏幕)。

软件iic速率,stm32,嵌入式硬件,单片机

设置寻址模式命令与写入位置命令:

        在25个命令后加上这8个命令,或者直接修改前面的设置内存寻址模式,就只用添加后面六个命令了。 

软件iic速率,stm32,嵌入式硬件,单片机

static uint8_t OLED_Command[31] = 
{
	0xAE, //关闭显示
	0xD5, //设置时钟分频因子,震荡频率
	0x80, //[3:0],分频因子;[7:4],震荡频率
	0xA8, //设置驱动路数
	0X3F, //默认 0X3F(1/64) 
	0xD3, //设置显示偏移
	0X00, //默认为 0 
	0x40, //设置显示开始行 [5:0],行数. 
	0x8D, //电荷泵设置
	0x14, //bit2,开启/关闭
	0x20, //设置内存地址模式
	0x00, //[1:0],00列地址模式; 01行地址模式; 10页地址模式; 默认10;
	0xA1, //段重定义设置,bit0:0,0->0; 1,0->127;
	0xC8, //设置 COM 扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
	0xDA, //设置 COM 硬件引脚配置
	0x12, //[5:4]配置
	0x81, //对比度设置
	0xEF, //1~255;默认 0X7F (亮度设置,越大越亮)
	0xD9, //设置预充电周期
	0xf1, //[3:0],PHASE 1;[7:4],PHASE 2;
	0xDB, //设置 VCOMH 电压倍率
	0x30, //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
	0xA4, //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
	0xA6, //设置显示方式;bit0:1,反相显示;0,正常显示
	0xAF, //开启显示
	
	0x21, //设置列地址起始位置与终点位置
	0x00,
	0x7F,//0x00~0x7F = 0~127
	0x22,//设置页地址起始位置与终点位置
	0x00,
	0x07,//0x00~0x07 = 0~7
};

3.5 OLED初始化

        OLED初始化需要写入命令和写入数据两个函数,两个函数和之前一样,虽然这两个函数已经可以完成了现在所需要的操作,但是还是不够快,重复的发送地址和指令太浪费时间了,需要进行一次优化,直接将所有数据一次性全部写入。

//写命令
void OLED_Write_Command(uint8_t Command)
//写数据
void OLED_Write_Data(uint8_t Data);

         第一篇介绍IIC通信协议时说道,放在SDA行上的每个字节必须是8位长。每次传输可以传输的字节数不受限制。每个字节后面必须有一个确认位。首先使用最重要位(MSB)传输数据。如果从属服务器不能接收或传输另一个完整的数据字节,直到它执行了一些其他功能,例如服务一个内部中断,它可以保持时钟线SCL低,以迫使主服务器进入等待状态。当从服务器准备处理另一个字节并释放时钟线SCL时,继续数据传输。

软件iic速率,stm32,嵌入式硬件,单片机

        所以得到一个新的写数据函数,可以将帧缓冲区是数据一次性写入SSD1306中。 

/**
  * @brief  OLED 将帧缓冲区的数据一次性写入
  * @param  无
  * @retval 无
  * @explain 无
  */
void OLED_Write_Datarray(void)
{
	MyIIC_Start();
	MyIIC_SendByte(0x78);  	//写入从机地址
	MyIIC_SendByte(0x40);  	//写入命令
	for(int i=0; i<8; i++)
	    for(int j=0; j<128; j++)
	        MyIIC_SendByte(OLED_GRAM[i][j]);
	MyIIC_Stop();      		//发送停止信号
}

        还是标准的OLED屏幕初始化函数。 

/**
  * @brief  OLED初始化函数
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	//1、上电延时
	delay_ms(200);
	
	//2、软件IIC初始化
	MyIIC_Init();
	
	//3、写入OLED初始化命令
	for(int i=0; i<31; i++) 
		OLED_Write_Command(OLED_Command[i]);

	//4、清屏
	OLED_Clear();
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	OLED_Write_Datarray();
}
        更新屏幕和帧缓冲区清屏。
/**
  * @brief  OLED屏幕显示更新
  * @param  无
  * @retval 无
  * @explain 把帧缓冲区OLED_GRAM的内容更新到显示屏上
  */
void OLED_Update_FramebufferShow(void)
{
	OLED_Write_Datarray();
}

/**
  * @brief  OLED帧缓冲清屏
  * @param  无
  * @retval 无
  */
void OLED_Framebuffer_Clear(void)
{  
	uint8_t i, j;
	for (i = 0; i < 8; i++) {
		for(j = 0; j < 128; j++) {
			OLED_GRAM[i][j] = 0x00;
		}
	}
}

4.多级目录

        多级目录与之前的代码差别不大,可以直接照搬。

4.1 设计菜单界面

        菜单界面的设计。

/**
  * @brief  菜单界面函数
  * @param  无
  * @retval 无
  */
void Menu_Interface(void)
{
	char buff[50];
	
	sprintf(buff, "Date:%04d/%02d/%02d", 
	RTC_timedate.tm_year, RTC_timedate.tm_mon, RTC_timedate.tm_mday);
	OLED_Framebuffer_DrawString(10, 36,  8, buff, 1);
	sprintf(buff, "time:%02d:%02d:%02d", 
	RTC_timedate.tm_hour, RTC_timedate.tm_min, RTC_timedate.tm_sec);
	OLED_Framebuffer_DrawString(6,  8,  16, buff, 1);
}

4.2 设计功能选项界面

        因为在编写画点函数时用了正/反显的功能,所以这里可以省去">>",直接用正/反显更加美观。

/**
  * @brief  功能界面函数
  * @param  无
  * @retval 无
  */
void Function_Interface1(void)
{
	OLED_Framebuffer_DrawString(0, 0,  16, "String",   0);
	OLED_Framebuffer_DrawString(0, 16, 16, "Chinese",  1);
	OLED_Framebuffer_DrawString(0, 32, 16, "ImageBMG", 1);
	OLED_Framebuffer_DrawString(0, 48, 16, "Graphics", 1);
}
void Function_Interface2(void)
{
	OLED_Framebuffer_DrawString(0, 0,  16, "String",   1);
	OLED_Framebuffer_DrawString(0, 16, 16, "Chinese",  0);
	OLED_Framebuffer_DrawString(0, 32, 16, "ImageBMG", 1);
	OLED_Framebuffer_DrawString(0, 48, 16, "Graphics", 1);
}
void Function_Interface3(void)
{
	OLED_Framebuffer_DrawString(0, 0,  16, "String",   1);
	OLED_Framebuffer_DrawString(0, 16, 16, "Chinese",  1);
	OLED_Framebuffer_DrawString(0, 32, 16, "ImageBMG", 0);
	OLED_Framebuffer_DrawString(0, 48, 16, "Graphics", 1);
}
void Function_Interface4(void)
{
	OLED_Framebuffer_DrawString(0, 0,  16, "String",   1);
	OLED_Framebuffer_DrawString(0, 16, 16, "Chinese",  1);
	OLED_Framebuffer_DrawString(0, 32, 16, "ImageBMG", 1);
	OLED_Framebuffer_DrawString(0, 48, 16, "Graphics", 0);
}

 4.3 设计执行功能界面

        执行功能,同样无区别。

/**
  * @brief  功能设置界面函数
  * @param  设置有三种状态
  * @retval 无
  */
void Function_Interface5(void)
{
	OLED_Framebuffer_DrawString(0, 0,  8,  "ABCD", 1);
	OLED_Framebuffer_DrawString(0, 8,  16, "ABCD", 1);
	OLED_Framebuffer_DrawString(0, 24, 24, "ABCD", 1);
}
void Function_Interface6(void)
{   
	for(int i=0; i<4; i++) {
		OLED_Framebuffer_DrawChinese(32+i*16, 24, i, 1);// 点
	}

}
void Function_Interface7(void)
{
	OLED_Framebuffer_DrawImageBMG(0);
}
void Function_Interface8(void)
{
	OLED_Framebuffer_DrawRectangle(16,16,112,48);
	OLED_Framebuffer_DrawCircle(64,32,16);
	OLED_Framebuffer_DrawCircle(64,32,15);
}

 4.4 设计界面任务调度表

        任务调度表,同样无区别。

uint8_t taskIndex = 0;	//初始任务
//任务调度表
Menu_table_t taskTable[] =
{
    //菜单界面函数 -- 一级界面
    {0, 1, 0, 1, Menu_Interface}, 
    //功能界面函数 -- 二级界面
    {1, 5, 2, 0, Function_Interface1},
    {2, 6, 3, 0, Function_Interface2},
    {3, 7, 4, 0, Function_Interface3},
	{4, 8, 1, 0, Function_Interface4},
	//功能设置界面函数 -- 三级界面
	{5, 5, 5, 1, Function_Interface5},
	{6, 6, 6, 2, Function_Interface6},
	{7, 7, 7, 3, Function_Interface7},
	{8, 8, 8, 4, Function_Interface8},
};

5.主函数

        最后,就是主函数,在执行函数下面加一个显存刷新函数,就可以完成显示。

int main(void)
{
	delay_Init();//延时函数初始化
	USART1_Init();//串口函数初始化
	TIME2_Init();//定时器辅助串口接收初始化
	RTC_Init();//实时时钟初始化
	TIME4_Init();//定时器辅助按键实现长短按初始化
	
	LED_Init();
	KEY_EXTI_Init();
	PWM_Init();
	OLED_Init();
	
	printf("标准库 -- 位带+帧缓冲区优化刷新速率\r\n");
	while(1) {
		if(g_APPTOIN_MUTEX) {
			taskTable[taskIndex].Current_Operation();//执行函数
			OLED_Update_FramebufferShow();//更新帧缓冲区数据到OLED显示
		}
	}
}

        经过测试发现屏幕的刷新是12帧,好像更慢了。。。哈哈

软件iic速率,stm32,嵌入式硬件,单片机

效果演示:

OLED优化刷新速率

源码分享:

链接:https://pan.baidu.com/s/1CXnW-ZW7TqTC_2xbZyo9Ew?pwd=jgtz 
提取码:jgtz文章来源地址https://www.toymoban.com/news/detail-825084.html

到了这里,关于STM32+OLED屏(软件IIC+位带+帧缓冲区)刷新速率优化(四)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32 软件IIC 控制OLED 显示屏

    需要看原理图了

    2024年02月09日
    浏览(49)
  • STM32(HAL库)软件IIC驱动OLED

    目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置  2.1.2 RCC配置 2.2 软件IIC引脚配置 2.3 项目生成  3、KEIL端程序整合 3.1 OLED驱动添加 3.3 主函数代 3.4 效果展示 本文通过STM32F103C8T6单片机(HAL库)通过软件IIC方式对OLED进行驱动。 2.1.1 SYS配置  2.1.2 RCC配置 首先在建立Ha

    2024年02月14日
    浏览(50)
  • STM32移植u8g2玩转oled 用软件iic实现驱动oled

    移植u8g2到stm 这里是驱动iic 这里可以驱动u8g2 具体作用后续学习 下面就基本上可以驱动成功 最主要是修改后的u8g2的图

    2024年02月11日
    浏览(53)
  • 【Linux】理解缓冲区

    我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关! C接口的函数被打印了两次系统接口前后只是打印了一次:和fork函数有关,fork会创建子进程。在创建子进程的时候,数据会被处理成两份,父子进程发生写时拷

    2024年01月23日
    浏览(52)
  • Redis 缓冲区

    缓冲区的应用场景 : 客户端与服务器端的通信时,暂存客户端发送的命令数据,或暂存服务器端返给客户端的数据结果 主从节点间进行数据同步时,暂存主节点接收的写命令和数据 缓冲区 : 避免客户端和服务器端的请求发送和处理速度不匹配 服务器给每个连接的客户端都准

    2024年02月07日
    浏览(69)
  • 8.缓冲区管理

    双缓冲区:TC+M 假设初始状态缓冲区1满,缓冲区2空,工作区为空。 刚开始缓冲区2为空,所以设备可以向缓冲区2中冲入数据耗时T,另一方面刚开始缓冲区1中是满的,所以刚开始就可以把缓冲区1中的数据传送到工作区中,M时刻工作区被充满,CPU就开始处理数据耗时C,处理完

    2024年02月11日
    浏览(41)
  • 理解缓冲区

    对于这样的代码,首先可以肯定的是 printf 语句先于 sleep 执行,既然如此那么就应该是先打印语句然后进行休眠,下面看看结果: 但这里却是先休眠以后再打印语句,这是因为存在一个叫缓冲区的东西,当我们要向外设写入数据(让显示器显示就是向显示器写入数据)时会将

    2023年04月25日
    浏览(72)
  • 【Linux】文件缓冲区

    提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念,今天我们不妨深入理解什么是文件缓冲区 通过自己实现库中的一些文件操作函数更加深入的理解文件缓冲区 自定义实现

    2024年02月10日
    浏览(57)
  • C/C++缓冲区

    什么是缓冲区? 程序和磁盘文件之间不能直接交换数据,必须通过内存中一个被称为文件缓冲区的区域来中转。ANSIC标准规定,系统会自动为每个正在使用的文件在内存中开辟一个缓冲区,缓冲区的大小随机器而异。 缓冲区有什么作用? 假设我们在家中休息看电视吃零食,

    2024年02月15日
    浏览(49)
  • 【Linux】深入理解缓冲区

    目录 什么是缓冲区 为什么要有缓冲区 缓冲区刷新策略 缓冲区在哪里  手动设计一个用户层缓冲区 缓冲区本质上一块内存区域,用来保存临时数据。 缓冲区在各种计算任务中都广泛应用,包括输入/输出操作、网络通信、图像处理、音频处理等。 这块内存区域是由 谁提供的

    2024年02月15日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包