参考了notmine 的博客 :SSD1306(OLED驱动芯片)指令详解(https://blog.csdn.net/notMine/article/details/79317782)
今天在写ESP8266的OLED屏幕驱动的时候,发现了一个奇怪的问题,在调用画点程序画一条直线的时候,直线变成了连续的断点,直接画一幅图的时候确是正常的,看了SSD1306的手册之后,发现了问题的所在,原来是驱动程序有误。
设置内存地址模式(Set Memory Addressing Mode)一共有三种:页地址模式, 水平地址模式和垂直地址模式,需要使用命令将地址模式设置为以上三种之一。我拿到官方的驱动上选用的是页地址模式,下面引用上面提到的参考博客的其中一部分。
页地址模式(A[1:0]=10b)
当处于此模式时, 在GDDRAM访问后(读/写), 列地址指针将自动增加1。如果列地址指针到达列终止地址, 列地址指针将复位到列起始地址, 但页地址指针不会改变。
为了访问GDDRAM中下一页的内容, 用户必须设置新的页地址和列地址。页地址模式下页以及列地址指针的行为如下图所示
通常在页地址模式下访问GDDRAM, 需要如下步骤来定义起始RAM访问指针指向:
-通过命令(B0h-B7h)设置目标显示位置页起始地址
-通过命令(00h-0Fh)设置列起始地址低位
-通过命令(10h-1Fh)设置列起始地址高位
例如, 如果页地址是B2h, 列地址低位是03h, 列地址高位是10h, 起始列将为PAGE2的SEG3, GDDRAM访问指针的指向如下图所示
水平地址模式(A[1:0]=00b)
当处于此模式时, 在GDDRAM访问后(读/写), 列地址指针将自动增加1。如果列地址指针到达列终止地址, 列地址指针将复位到列起始地址, 且页地址指针将自动增加1。
水平地址模式下页以及列地址指针的行为如下图所示, 如果列地址指针和页地址指针都到达各自的终止地址时, 他们都将复位到各自的起始地址。(图中虚线)
垂直地址模式(A[1:0]=01b)
当处于此模式时, 在GDDRAM访问后(读/写), 页地址指针将自动增加1。如果页地址指针到达页终止地址, 页地址指针将复位到页起始地址, 且列地址指针将自动增加1。
垂直地址模式下页以及列地址指针的行为如下图所示, 如果列地址指针和页地址指针都到达各自的终止地址时, 他们都将复位到各自的起始地址。(图中虚线)
通常在(垂直/水平)地址模式下访问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;
}
进行修改之后发现 使用画点程序连续画一条时 显示的直线是正常的。
因为在网上看到很多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)