Winstar图形和文本模式显示

图形显示器(包括OLED类型的图形显示器,在市场上由Winstar最多代表)相对于小写字母的需求要低得多,使用它们的出版物也要少得多。 同时,由于缺乏与预定图案的字体表的绑定,图形OLED显示器提供了获得满足各种需求的人体工程学显示设备的最佳方法。 而且,事实证明,与文本模式相比,WS0010控制器中的图形模式更容易启动并且工作更稳定。

在继续考虑实际的图形显示之前,我们将考虑常绿问题,以及打开WS0010控制器的文本模式的问题,该问题得到了意想不到的明显解决方案(哦,我的眼睛在哪里!)。

解决WS0010文本模式问题


众所周知,Winstar系列显示器在初始化期间存在稳定性问题。 顺便说一句,事实证明,这并不是“该死的中国人”所独有的:我很困难地获得的Newhaven Display 16x2的样本位于地球的另一侧,在外部是Winstar的完整副本,除了某些铭文的位置和斑点上的公司名称(相同的形状和字体):

图片

这些显示器包含数据手册中所述的某些“ LCD可比”控制器,其行为与中文完全相同,并且具有相同的缺点。 显然,您不应该花时间去检查其他公司,例如Midas:从此出版物看来,没有国际合作就不可能做到。 全球化的经济规则!

文本模式的困难体现在以下事实:启动时(例如,重新启动或手动重置控制控制器的程序时),显示屏上可能会出现垃圾,第0行和第1行会随机更改位置。 实验表明,它不依赖于包含方法(8位或4位)。 当需要定期重新启动软件时,例如通过Watchdog-timer,此问题尤其严重。

问题的一部分是对电源的整洁态度(来自单独的来源,而绝非来自USB Arduino),以及在启动控制程序后通过关闭和打开显示器来进行单独的重新引导(请参阅作者先前的出版物 )。 事实证明,这些行的作者并不是唯一提出类似问题的解决方案的人:LuquidCrystal附加组件WinstarOLED的作者还包括一个特殊的pw_pin,在程序启动时显示功率会失真。

但这当然是所有措施的主动和一半。 SeregaB遇到了一种激进的方式(请参见在easyelectronics.ru上的出版物-感谢Tomasina的提示)。 实际上,他提出了一个完全不同的任务:学习如何仅使用图形模式而不是文本模式。 尝试在模式之间进行切换时,他很快发现“ 切换到图形模式是正常的,并且从图形切换为”文本”非常笨拙 。 然后他想起“ 很久以前,当DSh仍打印在纸上时,在HD44780上的某些DSh中,我读到只有在屏幕关闭时才可以进行切换模式 。” 而且有效。

从引用的出版物中,我将在这里简单地重现两个切换过程,以使其稍稍适应以与LuquidCrystal一起使用(此处的类实例称为OLED1)。

切换到图形模式:
OLED1.command(0x08);//  OLED1.command(0x1F);//   OLED1.command(0x01);//    (..  clear()) OLED1.command(0x08|0x04);//  

切换至文字模式:
  OLED1.command(0x08);//  OLED1.command(0x17);//    OLED1.command(0x01);//    (..  clear()) OLED1.command(0x04 | 0x08);//  

正如我们将在后面看到的那样,实际上并不需要第一个过程:WS0010从半踢切换到图形模式,只需向其发送0x1F命令即可。 但是第二个命令序列的确如此。 对于样本,使用LuquidCrystal以这种形式将其直接包含在草图中:
 void reset_textmode() //     { OLED1.command(0x08);//  OLED1.command(0x17);//    OLED1.command(0x01);//    OLED1.command(0x04 | 0x08);//  } 

然后在启动库后立即在安装程序中调用此函数:

  . . . . . OLED1.begin(16,2); //16  2  reset_textmode(); //  clear() . . . . . 

如果在此之前插入一些延迟(500),则演示将变得非常明显:按下屏幕上的Arduino板的重置按钮后,照常出现垃圾,但只有一小段时间:触发该函数后,屏幕被清除,所有行都恢复原位。

该函数的工作方式是这样的,但为方便起见,我在升级后的LiquidCrystalRus_OLED.cpp库文件中替换了LiquidCrystalRus :: clear()函数的内容,该文件先前已通过此命令序列进行了讨论(提醒您可以从作者的网站下载 )。 无需等待命令在库中执行,因此,出于可靠性考虑,在每个命令之后,都会以库的常规样式插入100μs的延迟。 在使用LiquidCrystalRus_OLED的此变体的草图中,在安装开始时,有必要调用clear()函数,同时清除屏幕。
注意事项
清洁屏幕存在一个问题:在命令表的数据表中,“当fsp或fosc = 250KHz时”,0x01命令可以持续长达6.2 ms。 在特定的控制器中实际上是什么样的“ fsp或fosc”,它们太懒了以至于无法编写,但是无论如何,即使是兆赫兹,该命令的延迟也应该是很大的(LiquidCrystal的作者提到了这一点)。 但是,实际上,如果根本没有延迟,那么清洁团队就会为自己工作。 因此我听不懂,但按照著名的编程规则行事:“行之有效-请勿触摸!”。

现在,让我们最后处理图形模式。

文本显示中的图形模式WEH001602


首先,我尝试将WEH001602BG的文本显示切换为图形模式。 请注意,图形化的100x16和文本(20x2配置,16x2仅具有较少的水平点)显示具有相同的矩阵,在熟悉情况下,仅文本显示由间隔隔开。 这严重限制了在文本显示中使用图形模式,甚至在图形中使用更多的文本模式。 但是要测试其工作原理,可以使用其中任何一个。

根据以下方案,显示器与DS1307一起连接到了Arduino Nano:
图片

按照相同的方案,将来我们将连接图形显示。 必要时,图中的灰色表示第二个显示器的连接。

要切换到图形模式,可以使用上一部分中改进的过程,但是一个命令中的一个简单功能可以起作用:
 . . . . . #define LCD_SETGRAPHICMODE 0x1f LiquidCrystal lcd(9, 4, 8, 7, 6, 5); void setGraphicMode(){ lcd.command(LCD_SETGRAPHICMODE); } . . . . . 

我们在这里不需要任何俄罗斯表格,因此,使用了标准(非直形)LiquidCrystal,它可以在图形模式下完美运行。 为了避免调试所有库选项,在同时包含文本和图形显示的情况下,对于每个库我都使用自己的库(对于文本升级的Rus_OLED,对于图形常规而言)。 在这种情况下,根据上图,除E输出引脚外,仍然可以连接到相同的控制器脚。

此外,我部分使用了提到的WinstarOLED库的作者的成就(就我而言,LuquidCrystal的附加组件本身是不完整的,因此不实用)。 他介绍了一种用于设置图形光标的便捷功能(有关最大x值的原始错误已在此处修复):
 void setGraphicCursor( uint8_t x, uint8_t y ){ if( 0 <= x && x <= 99 ){ lcd.command(LCD_SETDDRAMADDR | x); } if( 0 <= y && y <= 1 ){ lcd.command(LCD_SETCGRAMADDR | y); } } 

在LiquidCrystal库中定义了常数LCD_SETDDRAMADDR。 与文本显示一样,100x16的显示也分为两行,即0和1,因为y在这里只能取两个值。 并且水平坐标x在0到99之间变化。使用lcd.write()命令发送一个字节,该字节的各个位确定长度为8点的垂直线的发光位置。 上一行中最左侧的位置的坐标为0,0,下端最右侧的坐标为99,1。 此外,最低点将对应于最低有效位,最低点将对应于最高位。

为了方便对图像进行编码,我画了一个盘子,您可以在其中快速手动创建所需的代码。 当然,对于完整的字体表,建议使用特殊的编辑器(其中至少有100万种不同程度的业余活动),但是具有所需位顺序的10位数字可以更快地手动处理,特别是因为自动创建的字体通常仍然需要手工完成。 根据上述内容,一个字形(例如,数字2字体10x16)将被编码如下:

图片

所有这些都被写入以下形式的二维数组中:
 const byte Data2[2][10]={{0x06,0x07,0x03,0x03,0x03,0x83,0xc3,0x63,0x3f,0x1e}, {0xf0,0xf8,0xcc,0xc6,0xc3,0xc1,0xc0,0xc0,0xc0,0xc0}}; 

对于每个数字0-9,将创建一个单独的此类数组:Data0,Data1,Data2,依此类推。 对于手表,除了数字外,您还需要一个双点。 可以将其缩短:
 const byte DataDP[2][2]={{0x70,0x70}, {0x1c,0x1c}};//  

由于控制器不知道如何在图形模式下“闪烁”,因此有必要以编程方式使冒号闪烁。 您可以简单地通过在相应位置显示零来消除双点,但是为了均匀起见,我制作了一个单独的数组
 const byte DataDPclr[2][2]={{0x00,0x00}, {0x00,0x00}};// .  

为了显示每个数字并分别显示一个双点,编写了一个单独的函数:
 void draw2 (byte x/* */) // “2” { for (byte i = x; i<x+10; i++){ setGraphicCursor(i, 0); lcd.write(Data2[0][ix]); setGraphicCursor(i, 1); lcd.write(Data2[1][ix]);} } 

所有函数都相同,但是使用不同的数组,并且对于双点,还使用了循环的其他限制。 事实证明,就代码量而言,这并不是太经济(请参阅下文中的更多内容),但是它很清楚并且很容易纠正错误。 在输出阶段考虑字符之间的间隙,指示相应的位置( RTClib库用于读取时钟):
 void loop() { DateTime clock = RTC.now(); if (clock.second()!=old_second) { uint8_t values; values=clock.hour()/10; //  drawValPos(values,0); values=clock.hour()%10; //  drawValPos(values,12); values=clock.minute()/10; //  drawValPos(values,28); values=clock.minute()%10; //end   drawValPos(values,40); if (clock.second()%2) drawDP(24); else drawDPclr(24); old_second=clock.second(); }//end if clocksecond } 

十位数字(每个20字节)将占用200字节的内存-约占其体积的10%(宽字体为16x16(如下面的示例所示,全部为16%))。 此大小的完整单语字体以及数字,不考虑各种标点符号和特殊字符。 字符,包含62个(英语)到74个(不带E的俄语)字符,该值将占用ATmega328 RAM的几乎一半。 因此,必须取消每个字符分别具有数组和输出函数的技巧,并按预期进行操作。 也就是说,字体应保留在程序存储器中,并通过PROGMEM下载,所有字形模式应安排为单个字体数组,并加载以在单个表中按符号号输出。 否则,将没有足够的内存,并且程序代码将膨胀到无法控制的容量。 这里我们不再赘述,因为在我们简单的示例中并不需要所有这些-每次我们都将被限制为一小部分严格必要的字符。

由于GraphicOLED_DC1307草图的全文很大,我不带它;您可以在这里下载。 resetOLED功能保存在文本中,该功能会在控制器重新启动时(通过pwrPin D2)扭曲显示功率,但由于不需要它,因此可以安全地将其删除。 程序的结果如图所示:

图片

不幸的是,不能同时停留在文本和图形模式下,因此,如果要使用剩余的空间,则必须绘制自己的字体(每行中有大约7个5x7字体的字符剩余空间)。

图形显示WEG010016A


最后,当订购的图形显示WEG010016AL到达时,我首先尝试将其输入文本模式以查看结果。

为了检查文本模式,下载了以前的出版物中描述的使用外部温度传感器模拟日历时钟显示的程序。 结果让我记得不同的Winstar显示器相对于连接器可以有不同的方向(在这种情况下,WEG010016A的顶部有一个连接器,上面是文本WEH001602B,在底部有一个连接器,在侧面有C型):

图片

我们将进一步处理显示方向,但现在我们将看到发生了什么。 但这并没有带来什么好处:文本模式(当然,在本文的开头已经讨论过配备拐杖的模式)可以完美地工作,但是实际上,由于字符之间没有空格,因此没有任何意义。 因此,我们不会拖延它,而是继续考虑图形模式。

图形模式安装过程本身与上面针对文本版本讨论的过程相同。 如果在屏幕顶部有连接器,则仍然需要处理显示器的翻转。 当然,您可以翻转显示屏,但是在我看来连接器朝下的位置似乎更加自然和方便。 另外,当使用侧面带有连接器的类型时,您可能需要将连接器指向右侧而不是左侧。 对于上下颠倒的方向,有必要对图像进行变换-即交换第一个和最后一个水平位置,线条,并反转构成数组的字节的位顺序(在这种情况下,最低有效位将对应于较低点)。

由于我已经为前一种情况绘制了十位数,因此对于最后一项任务,仍然需要引入程序反转过程:
 byte reverse(byte x) { byte result=0,i; for(i=0;i<8;i++) { if (x & (1 << i)) { result |= 1 << (7-i); } } return result; } 

您可以通过更改setGraphicCursor函数来更改水平坐标和垂直线的顺序:
 void setGraphicCursor( uint8_t x, uint8_t y ){ if( 0 <= x && x <= 99 ){ lcd.command(LCD_SETDDRAMADDR | (99-x)); } if( 0 <= y && y <= 1 ){ lcd.command(LCD_SETCGRAMADDR | (1-y)); } } 

每个数字的数组的输出函数保持不变,仅添加位反转:
 void draw2 (byte x/* */) // 2 { for (byte i = x; i<x+10; i++){ setGraphicCursor(i, 0); byte b=reverse(Data2[0][ix]); lcd.write(b); setGraphicCursor(i, 1); b=reverse(Data2[1][ix]); lcd.write(b);} } 

从此处下载GraphicOLED_DC1307_100x16手表的输出的完整草图,并且WEG010016AL显示屏的结果如图所示:

图片

但是在这张照片中,WEG010016CG显示屏上的字体不同(16x16)(显示屏也颠倒了):

图片

如果您通过手动更改位的顺序来重新创建字体,则无需执行相反操作,并且程序将运行得更快(尽管眼前没有明显的延迟)。 但是给定的位翻转过程在任何情况下都非常有用-显示各种图片。 例如,从一个向上和向右的箭头,您可以以编程方式一次获得四个方向。
箭头图
图像和箭头代码(表中的坐标和位根据WEG010016AL显示器连接器的下部位置而反转,请参见上文):

图片
 const byte DataATR[2][8]={{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x01,0x02,0x04,0x28,0x30,0x78,0x60,0x80}}; 

多向箭头输出功能:
 . . . . . void drawSW (byte x) //   (  ) { for (byte i = x; i<x+8; i++){ setGraphicCursor(i, 0); lcd.write(DataATR[0][ix]); setGraphicCursor(i, 1); lcd.write(DataATR[1][ix]);} } void drawNW (byte x) //   (  ) {//   : for (byte i = x; i<x+8; i++){ setGraphicCursor(i, 0); byte b=reverse(DataATR[1][ix]); lcd.write(b); setGraphicCursor(i, 1); b=reverse(DataATR[0][ix]); lcd.write(b);} } void drawNE (byte x) //   (  ) {//  ,    for (byte i = x; i<x+8; i++){ setGraphicCursor(i, 0); byte b=reverse(DataATR[1][7-(ix)]); lcd.write(b); setGraphicCursor(i, 1); b=reverse(DataATR[0][7-(ix)]); lcd.write(b);} } void drawSE (byte x) //   (  ) {//   for (byte i = x; i<x+8; i++){ setGraphicCursor(i, 0); lcd.write(DataATR[0][7-(ix)]); setGraphicCursor(i, 1); lcd.write(DataATR[1][7-(ix)]);} } . . . . . 

下图显示了用于显示速度和风向传感器的空白程序的结果。 如您所见,事实证明,将不同大小的字体与图片一起实现非常简单:

图片

总之,我将添加一个非常有趣的库,用于使用SPI在图形和文本模式下使用WS0010。 在文本中,它主要复制液晶(还有什么您能想到的?),在图形中,它具有绘制图形基元,内置字体(较厚的字体,如我的字体和通常的5x7)等功能。

Source: https://habr.com/ru/post/zh-CN402517/


All Articles