这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【分享开发笔记,赚取电动螺丝刀】OLED刷新终极优化

共5条 1/1 1 跳转至

【分享开发笔记,赚取电动螺丝刀】OLED刷新终极优化

工程师
2025-03-01 14:08:44     打赏

【前言】

今天我在做Telink TL7218开发板中,使用ssd1306进行显示。在使用中,进行了一些代码的优化,现记录如下。

【SSD1306常见刷新方式】

1、按页刷新:将屏幕垂直方向划分为多个页(一般 8 页,每页 8 行像素),每次刷新一个页的数据,逐页进行操作。这种方式在处理小部分数据更新时较为灵活,适合只更新部分屏幕内容的场景。 2、整屏刷新:一次性将整个屏幕的显示数据发送给 SSD1306,这种方式适合需要快速更新整个屏幕内容的情况。 3、区域刷新:只刷新屏幕上指定的某个区域,可减少数据传输量,提高刷新效率,适用于局部内容频繁更新的场景。

在1,2种方式中,如果是整屏刷新,那么按整屏刷新的方式是要快于按页来刷新的,分析如下:

按页刷新的代码:

// 按页刷新逻辑
    for (uint8_t page = 0; page < 8; page++) {
        // 设置页地址
        OLED_WR_Byte(0xB0 + page, OLED_CMD);
        // 设置列地址低 4 位
        OLED_WR_Byte(0x00, OLED_CMD);
        // 设置列地址高 4 位
        OLED_WR_Byte(0x10, OLED_CMD);

        // 向当前页写入数据,这里假设要写入的数据全为 0x00
        for (uint8_t col = 0; col < 128; col++) {
            OLED_WR_Byte(0x00, OLED_DATA);
        }
    }

整屏刷新的代码:

// 一次性发送整屏数据
void SSD1306_FullScreenRefresh(uint8_t *data) {
    // 设置起始地址
    OLED_WR_Byte(0x21, 0); // 设置列地址
    OLED_WR_Byte(0x00, 0); // 起始列地址
    OLED_WR_Byte(SSD1306_WIDTH - 1, 0); // 结束列地址
    OLED_WR_Byte(0x22, 0); // 设置页地址
    OLED_WR_Byte(0x00, 0); // 起始页地址
    OLED_WR_Byte(SSD1306_PAGE_COUNT - 1, 0); // 结束页地址

    // 发送整屏数据
    for (int i = 0; i < SSD1306_WIDTH * SSD1306_PAGE_COUNT; i++) {
        OLED_WR_Byte(data[i], 1);
    }
}

两个函数中,我们可以明显的看到按页刷新,需要分8次向SSD1306写入设置地址,再发送一页即128位数据。如果是整屏的刷新,那么我们只需要设置一次起始地址。而在发送命令中,是一次写入两字字节的命令,显然占用的时间是多于整屏刷新的时间的。

【两个不同刷新方式的初始设置】

1. 内存寻址模式设置 按页初始化:通常会将内存寻址模式设置为页寻址模式,使用 0x20 命令并跟上 0x02 来选择页寻址模式。在页寻址模式下,数据是按页进行组织和传输的,每次只能操作一个页的数据。 整屏刷新:一般会将内存寻址模式设置为水平寻址模式,使用 0x20 命令并跟上 0x00 来选择水平寻址模式。在水平寻址模式下,数据可以从左到右、从上到下依次填充整个屏幕,适合一次性发送整屏数据。 2. 数据传输方式 按页初始化:在按页初始化时,数据传输是按页进行的,每次只处理一个页的数据。需要先设置页地址和列地址,然后逐列写入该页的数据,处理完一页后再处理下一页。 整屏刷新:整屏刷新则是一次性将整个屏幕的数据发送给 SSD1306。需要先设置好起始列地址、结束列地址、起始页地址和结束页地址,然后连续发送整个屏幕的数据。

【示列代码的对比】

按页初始化代码示例

// 按页初始化函数
void SSD1306_PageInit() {
    // 基本初始化设置
    SSD1306_SendCommand(0xAE); // 关闭显示
    SSD1306_SendCommand(0x20); // 设置内存寻址模式
    SSD1306_SendCommand(0x02); // 页寻址模式
    SSD1306_SendCommand(0xB0); // 设置页地址
    SSD1306_SendCommand(0xC8); // 设置扫描方向
    SSD1306_SendCommand(0x00); // 设置列地址低 4 位
    SSD1306_SendCommand(0x10); // 设置列地址高 4 位
    SSD1306_SendCommand(0x40); // 设置显示起始行
    SSD1306_SendCommand(0x81); // 设置对比度
    SSD1306_SendCommand(0xFF); // 最大对比度
    SSD1306_SendCommand(0xA1); // 设置段重映射
    SSD1306_SendCommand(0xA6); // 设置正常显示
    SSD1306_SendCommand(0xA8); // 设置多路复用率
    SSD1306_SendCommand(0x3F); // 1/64 多路复用
    SSD1306_SendCommand(0xA4); // 恢复整体显示
    SSD1306_SendCommand(0xD3); // 设置显示偏移
    SSD1306_SendCommand(0x00); // 无偏移
    SSD1306_SendCommand(0xD5); // 设置时钟分频比/振荡器频率
    SSD1306_SendCommand(0xF0); // 设置分频比
    SSD1306_SendCommand(0xD9); // 设置预充电周期
    SSD1306_SendCommand(0x22); // 设置预充电周期
    SSD1306_SendCommand(0xDA); // 设置 COM 引脚硬件配置
    SSD1306_SendCommand(0x12); // 设置 COM 引脚硬件配置
    SSD1306_SendCommand(0xDB); // 设置 VCOMH 取消选择级别
    SSD1306_SendCommand(0x20); // 设置 VCOMH 取消选择级别
    SSD1306_SendCommand(0x8D); // 设置电荷泵
    SSD1306_SendCommand(0x14); // 启用电荷泵
    SSD1306_SendCommand(0xAF); // 开启显示
}

整屏刷新初始化代码示例

// 整屏刷新初始化函数
void SSD1306_FullScreenInit() {
    // 基本初始化设置
    SSD1306_SendCommand(0xAE); // 关闭显示
    SSD1306_SendCommand(0x20); // 设置内存寻址模式
    SSD1306_SendCommand(0x00); // 水平寻址模式
    SSD1306_SendCommand(0xB0); // 设置页地址
    SSD1306_SendCommand(0xC8); // 设置扫描方向
    SSD1306_SendCommand(0x00); // 设置列地址低 4 位
    SSD1306_SendCommand(0x10); // 设置列地址高 4 位
    SSD1306_SendCommand(0x40); // 设置显示起始行
    SSD1306_SendCommand(0x81); // 设置对比度
    SSD1306_SendCommand(0xFF); // 最大对比度
    SSD1306_SendCommand(0xA1); // 设置段重映射
    SSD1306_SendCommand(0xA6); // 设置正常显示
    SSD1306_SendCommand(0xA8); // 设置多路复用率
    SSD1306_SendCommand(0x3F); // 1/64 多路复用
    SSD1306_SendCommand(0xA4); // 恢复整体显示
    SSD1306_SendCommand(0xD3); // 设置显示偏移
    SSD1306_SendCommand(0x00); // 无偏移
    SSD1306_SendCommand(0xD5); // 设置时钟分频比/振荡器频率
    SSD1306_SendCommand(0xF0); // 设置分频比
    SSD1306_SendCommand(0xD9); // 设置预充电周期
    SSD1306_SendCommand(0x22); // 设置预充电周期
    SSD1306_SendCommand(0xDA); // 设置 COM 引脚硬件配置
    SSD1306_SendCommand(0x12); // 设置 COM 引脚硬件配置
    SSD1306_SendCommand(0xDB); // 设置 VCOMH 取消选择级别
    SSD1306_SendCommand(0x20); // 设置 VCOMH 取消选择级别
    SSD1306_SendCommand(0x8D); // 设置电荷泵
    SSD1306_SendCommand(0x14); // 启用电荷泵
    SSD1306_SendCommand(0xAF); // 开启显示
}

经过上述代码的对比,应该非常之清楚了。

【整屏发送代码的优化】

以TL7218的发送为例:

// 一次性发送整屏数据
void SSD1306_FullScreenRefresh(uint8_t *data) {
    // 设置起始地址
    OLED_WR_Byte(0x21, 0); // 设置列地址
    OLED_WR_Byte(0x00, 0); // 起始列地址
    OLED_WR_Byte(SSD1306_WIDTH - 1, 0); // 结束列地址
    OLED_WR_Byte(0x22, 0); // 设置页地址
    OLED_WR_Byte(0x00, 0); // 起始页地址
    OLED_WR_Byte(SSD1306_PAGE_COUNT - 1, 0); // 结束页地址

    // 发送整屏数据
    for (int i = 0; i < SSD1306_WIDTH * SSD1306_PAGE_COUNT; i++) {
        OLED_WR_Byte(data[i], 1);
    }
}

我们使用整屏发送修改为一次性传输:

发送数之前,发送的第一个数据为0x40,那么我们需要重新创建一个数据,这个数组为1025长度,即整屏1024字数据+1个命令。

uint8_t tx_buff[1025]={0x00};
tx_buff[0] = 0x40;//发送数据的指令码
for(int i= 0; i<1024;i++){
  tx_buff[i+1] = data[i];
  }
 i2c_master_write(SSD1306_ADDR, tx_buff, 1025);

这次的话,又相比

OLED_WR_Byte(data[i], 1);

又要快了不少。

【优化方式2】

使用for来拷贝数据,还是有提升的空间的。我这次使用memcpy来优化,由于是我们是将tx_buff的第2次开始拷贝数据,所以我将for替换为:

memcpy(&tx_buff[1], data, 1024);

这样速度是不是又快了许多,而且代码也会整洁许多。

【终级优化】

在优化方式2中,我们还需要重新申请一个1025的数组,这样对于资源紧张的MCU还是有非常大的开销的。因此我继续进行了优化。

在我们代码初始化时,我们创建了一个全局数组:

uint8_t OLED_buffer[1024] = {0};

用于做为ssd1306的显存,我们在画点画线等等,都是在这个数组里进行操作,最后我们刷新的,直接更新这个数组到ssd1306上。

而我们在整屏刷新时,再申请一个tx_buff[1025]的数组,这个两个数组唯一的不同就是在OLED_buffer最前面添加了一个0x40的写数据的命令。因此,大有改进的空间,我想办法把他们两个数组指向同一个内存空间。根据这样的方法,我做了如下的优化:

1、申请一个Flash_OLED_Buffer[1025]的数组,让我们发送整屏数据时,地址指向这个首地址,然后,我再申请一个指针,让他的地址指向Flash_OLED_Buffer地址+1,这样我们就可以不需要在发送整屏数据时再申请一个局部变量了。节约了内存空间中,又提升了速度。

终级代码:

uint8_t Flash_OLED_Buffer[1025] = {0};  //做为发送整屏数据的缓存
uint8_t * OLED_buffer = Flash_OLED_Buffer+1 ;  //做为显存的空间

//发送代码
Flash_OLED_Buffer[0] = 0x40; //发送数据的指令
i2c_master_write(SSD1306_ADDR, Flash_OLED_Buffer, SSD1306_WIDTH * OLED_PAGE_SIZE +1);

【小结】

结合ssd1306的特性,以及对内存地址的理解,可以对刷新做出非常好的优化。

这只是我个人的一些建议,如有不足,还希望大佬们批评指正。




关键词: SSD1306     刷新方式     内存指针    

专家
2025-03-01 20:34:11     打赏
2楼

感谢分享


专家
2025-03-01 20:35:43     打赏
3楼

感谢分享


专家
2025-03-01 20:38:42     打赏
4楼

感谢分享


专家
2025-03-03 08:51:56     打赏
5楼

感谢分享


共5条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]