SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)

参考了notmine 的博客 :SSD1306(OLED驱动芯片)指令详解(https://blog.csdn.net/notMine/article/details/79317782)

 

今天在写ESP8266的OLED屏幕驱动的时候,发现了一个奇怪的问题,在调用画点程序画一条直线的时候,直线变成了连续的断点,直接画一幅图的时候确是正常的,看了SSD1306的手册之后,发现了问题的所在,原来是驱动程序有误。

SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)_第1张图片

 设置内存地址模式(Set Memory Addressing Mode)一共有三种:页地址模式, 水平地址模式和垂直地址模式,需要使用命令将地址模式设置为以上三种之一。我拿到官方的驱动上选用的是页地址模式,下面引用上面提到的参考博客的其中一部分。


页地址模式(A[1:0]=10b)
当处于此模式时, 在GDDRAM访问后(读/写), 列地址指针将自动增加1。如果列地址指针到达列终止地址, 列地址指针将复位到列起始地址, 但页地址指针不会改变。
为了访问GDDRAM中下一页的内容, 用户必须设置新的页地址和列地址。页地址模式下页以及列地址指针的行为如下图所示SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)_第2张图片

通常在页地址模式下访问GDDRAM, 需要如下步骤来定义起始RAM访问指针指向:
-通过命令(B0h-B7h)设置目标显示位置页起始地址
-通过命令(00h-0Fh)设置列起始地址低位
-通过命令(10h-1Fh)设置列起始地址高位

例如, 如果页地址是B2h, 列地址低位是03h, 列地址高位是10h, 起始列将为PAGE2的SEG3, GDDRAM访问指针的指向如下图所示
SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)_第3张图片

水平地址模式(A[1:0]=00b)
当处于此模式时, 在GDDRAM访问后(读/写), 列地址指针将自动增加1。如果列地址指针到达列终止地址, 列地址指针将复位到列起始地址, 且页地址指针将自动增加1。
水平地址模式下页以及列地址指针的行为如下图所示, 如果列地址指针和页地址指针都到达各自的终止地址时, 他们都将复位到各自的起始地址。(图中虚线)
SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)_第4张图片

垂直地址模式(A[1:0]=01b)
当处于此模式时, 在GDDRAM访问后(读/写), 页地址指针将自动增加1。如果页地址指针到达页终止地址, 页地址指针将复位到页起始地址, 且列地址指针将自动增加1。
垂直地址模式下页以及列地址指针的行为如下图所示, 如果列地址指针和页地址指针都到达各自的终止地址时, 他们都将复位到各自的起始地址。(图中虚线)
SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)_第5张图片

通常在(垂直/水平)地址模式下访问GDDRAM, 需要如下步骤来定义起始RAM访问指针指向:
-通过命令(21h)设置目标显示位置列起始地址以及列终止地址
-通过命令(22h)设置目标显示位置页起始地址以及页终止地址

OLED设置内存地址模式的方法如下:

    oled_write_cmd(0x20);    // Set Memory Addressing Mode (20h)
    oled_write_cmd(0x02);    // Set 页地址模式(A[1:0]=10b) 水平地址模式(A[1:0]=00b) 垂直地址模式(A[1:0]=01b)

 

根据这个文档,我发现库中提供的设置点坐标的函数是有错误的 

错误的oled_set_pos函数如下:

esp_err_t oled_set_pos(uint8_t x_start, uint8_t y_start)
{
    oled_write_cmd(0xb0 + y_start);
    oled_write_cmd(((x_start & 0xf0) >> 4) | 0x10);
    oled_write_cmd((x_start & 0x0f) | 0x01);
    return ESP_OK;
}

从手册中可以看出 在页地址模式下访问GDDRAM, 需要
-通过命令(B0h-B7h)设置目标显示位置页起始地址
-通过命令(00h-0Fh)设置列起始地址低位
-通过命令(10h-1Fh)设置列起始地址高位

由此可见设置列起始地址低位时候 范围是 00H-0FH ,而驱动中却把最低位强制置1了,这样并不符合手册中设置坐标的方式,会让最后一位不是1的自动跳到下一位,也就是上图出现的隔一个点才显示一次的问题。

所以正确的oled_set_pos函数应该是这样的:

esp_err_t oled_set_pos(uint8_t x_start, uint8_t y_start)
{
    oled_write_cmd(0xb0 + y_start);
    oled_write_cmd(((x_start & 0xf0) >> 4) | 0x10);
    oled_write_cmd((x_start & 0x0f) );
    return ESP_OK;
}

进行修改之后发现 使用画点程序连续画一条时 显示的直线是正常的。

SSD1306(OLED驱动芯片) 设置内存地址模式(Set Memory Addressing Mode)(20h)_第6张图片

因为在网上看到很多OLED屏幕的驱动程序都把最低位强制置1了,我也想知道这些驱动中把最低位置1的理由,我把发现的问题写在这里,也希望了解的人能够和我进行交流。

 

另外 除了页地址模式 我还写了 水平地址模式的驱动

(垂直/水平)地址模式下访问GDDRAM, 需要如下步骤来定义起始RAM访问指针指向:
-通过命令(21h)设置目标显示位置列起始地址以及列终止地址
-通过命令(22h)设置目标显示位置页起始地址以及页终止地址

在需要设置本次写入的坐标时 需要如下操作

    oled_write_cmd(0x21);//COLUMNADDR
    oled_write_cmd(x轴起点);
    oled_write_cmd(x轴终点);

    oled_write_cmd(0x22);//PAGEADDR
    oled_write_cmd(y轴起点);
    oled_write_cmd(y轴终点);

 

下面是水平地址模式下 显示一张图片的源代码 是基于Arduino修改而来的


// Set 水平地址模式(A[1:0]=00b)
void display(void) 
{

    if(dispaly_image_buffer==NULL)
    {
        DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not init display\n");
        return;
    }

#ifdef OLED_BACK_IMAGE_ENABLE

    if(dispaly_image_buffer_back==NULL)
    {
        DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not BACK init display\n");
        return;
    }
        
    uint8_t minBoundY = UINT8_MAX;
    uint8_t maxBoundY = 0;

    uint8_t minBoundX = UINT8_MAX;
    uint8_t maxBoundX = 0;

    uint8_t x, y;

    // Calculate the Y bounding box of changes
    // and copy buffer[pos] to buffer_back[pos];
    for (y = 0; y < (displayHeight / 8); y++) {
        for (x = 0; x < displayWidth; x++) {
          uint16_t pos = x + y * displayWidth;
          if (dispaly_image_buffer[pos] != dispaly_image_buffer_back[pos]) {
            minBoundY = _min(minBoundY, y);
            maxBoundY = _max(maxBoundY, y);
            minBoundX = _min(minBoundX, x);
            maxBoundX = _max(maxBoundX, x);
          }
          dispaly_image_buffer_back[pos] = dispaly_image_buffer[pos];
        }
    }

    // If the minBoundY wasn't updated
    // we can savely assume that buffer_back[pos] == buffer[pos]
    // holdes true for all values of pos
    if (minBoundY == UINT8_MAX) return;

    oled_write_cmd(0x21);//COLUMNADDR
    oled_write_cmd(minBoundX);
    oled_write_cmd(maxBoundX);

    oled_write_cmd(0x22);//PAGEADDR
    oled_write_cmd(minBoundY);
    oled_write_cmd(maxBoundY);

    printf("minx:%d maxx:%d miny:%d maxy:%d\n",minBoundX,maxBoundX,minBoundY,maxBoundY);
    for (y = minBoundY; y <= maxBoundY; y++) {
        for (x = minBoundX; x <= maxBoundX; x++) {
           oled_write_byte(dispaly_image_buffer[x + y * displayWidth]);//x + y * displayWidth
        }
    }

#else
    // No double buffering
    oled_write_cmd(0x21);//COLUMNADDR
    oled_write_cmd(0x0);
    oled_write_cmd(0x7F);

    oled_write_cmd(0x22);//PAGEADDR
    oled_write_cmd(0x0);

    oled_write_cmd(0x7);

    for (uint16_t i=0; i

为OLED画一个点而纠结的原因也正在于是用了上面第一种驱动方式,使用这个函数,能最低限度的与OLED通信,节省了SPI通信的时间, 它可以做到画一个点只需要改动一个字节。

 

完整的代码我会在后续更新到我的Github上,欢迎访问~

你可能感兴趣的:(ESP,Github)