由于需要做一些低功耗的东西,所以最近在尝试玩墨水屏。出于成本考虑(没钱的另一种委婉说法)从咸鱼淘到2块便宜的二手SES 2.66寸三色墨水屏,并使用micropython将其驱动起来,并用字库的方法显示中文。

一.屏幕的驱动

1.硬件连线

SES 2.66墨水屏

SES 2.66墨水屏带驱动小板

买到的屏幕是图上这个样子的,带驱动小板 ,驱动小板的作用是提供给MCU标准的SPI操作接口。

墨水屏与ESP8266的连接方式采用推荐的方式,见下图。

连接没问题就可以测试了。

2.屏幕测试

由于micropython没有这个屏幕的现有驱动,因此拿到手后先使用有现成驱动的arduino环境进行测试,确保屏幕和连线没有问题。

(1)直接刷现成固件测试

卖家提供了编译好的现成的固件,直接使用NodeMCU-PyFlasher刷进去,屏幕上就会有测试画面显示,这是最快的测试方法。现成固件(“2.66测试固件”)下载地址见文末。

(2)使用arduino编译固件测试

下面是arduino下驱动的方法。

arduino的安装不在此述,ESP8266开发板的安装网上有很多教程,由于网速实在很慢,所以我采用的是最懒的使用别人打好的方式安装的。使用的包(“8266_package_3.0.1_arduino.cn.exe“)下载地址见文末。

然后就是把微雪的驱动包导进去,导入方法见“墨水屏使用须知(SES2.66三色为例).docx”,下载地址见文末。导入后需要修改或替换SES2.66b的驱动文件(EPD_2in66b.cpp),文中有叙述,照做就是。

最后编译上传,就应该能看到屏幕有反应了。

二、编写micropython驱动

由于要使用micropython来驱动屏幕,而该屏幕的资料很少,因此我必须研究下arduino下面的驱动程序,以编写相应的python驱动程序。

驱动方法还是主要参考两个部分:一是ariduino下那个EDP_2in66b.cpp,二是微雪的驱动包。

1.初始化

(1)BUSY/RESET引脚状态

由于资料不全,所以我实测了一下,SES2.66b的BUSY引脚高电平为忙,低电平为空闲,所以程序中定义:BUSY = const(1)  # 0=idle, 1=busy。

RESET引脚是低电平使模块复位。并且,上电后程序拉低RESET引脚使模块复位后,BUSY引脚会一直处于高电平状态(忙),只有在后续写入POWER ON命令后,BUSY引脚才会变为低电平,这有点坑。

(2)SPI总线初始化

ESP8266只有两个SPI通道,0和1。其中0通道为内部FLASH使用,所以只能用SPI 1。

初始化时,如果不带波特率参数,SPI的总线速度会比较高,高到墨水屏不能接受。你可以测试一下这个:

>>> from machine import SPI
>>> s=SPI(1)
>>> s
HSPI(id=1, baudrate=80000000, polarity=0, phase=0)
>>> 

默认波特率是80000000。所以我们需要把波特率降低些,实测10000000可以正常:

e=EPD(spi=SPI(1,baudrate=10000000),cs=Pin(15),dc=Pin(4),rst=Pin(2),busy=Pin(5))
e.init()

(3)初始化命令

直接给出代码吧。

from micropython import const
from time import sleep_ms
import ustruct
import math# Display resolution
EPD_WIDTH  = const(152)
EPD_HEIGHT = const(296)# Display commands
PANEL_SETTING                     = const(0x00)
POWER_OFF                         = const(0x02)
POWER_ON                          = const(0x04)
BOOSTER_SOFT_START                = const(0x06)
DEEP_SLEEP                        = const(0x07)
DATA_START_TRANSMISSION_1         = const(0x10)
DISPLAY_REFRESH                   = const(0x12)
DATA_START_TRANSMISSION_2         = const(0x13)
VCOM_AND_DATA_INTERVAL_SETTING    = const(0x50)
TCON_RESOLUTION                   = const(0x61)
VCM_DC_SETTING_REGISTER           = const(0x82)
UNKNOWN_CMD                       = const(0x92)# Display orientation
ROTATE_0   = const(0)
ROTATE_90  = const(1)
ROTATE_180 = const(2)
ROTATE_270 = const(3)#BUSY = const(0)  # 0=busy, 1=idle
BUSY = const(1)  # 0=idle, 1=busy
#rstPin-->low=active
#dc------>low=command
#cs------>low=activeclass EPD:def __init__(self,spi,cs,dc,rst,busy):self.spi = spiself.cs = csself.dc = dcself.rst = rstself.busy = busyself.cs.init(self.cs.OUT, value=1)self.dc.init(self.dc.OUT, value=0)self.rst.init(self.rst.OUT, value=0)self.busy.init(self.busy.IN)self.width = EPD_WIDTHself.height = EPD_HEIGHTself.rotate = ROTATE_0def _command(self,command,data=None):self.dc(0)self.cs(0)self.spi.write(bytearray([command]))self.cs(1)if data is not None:self._data(data)def _data(self, data):self.dc(1)self.cs(0)self.spi.write(data)self.cs(1)def init(self):self.reset()self._command(BOOSTER_SOFT_START,b'\x17\x17\x17')# BOOSTER_SOFT_STARTself._command(POWER_ON)self.wait_until_idle()self._command(PANEL_SETTING, b'\xCF') # (296x160, LUT from register, B/W/R run both LU1 LU2, scan up, shift right, bootster on) KW-BF   KWR-AF    BWROTP 0fself._command(VCOM_AND_DATA_INTERVAL_SETTING, b'\x77')self._command(TCON_RESOLUTION,b'\x98\x01\x28')self._command(VCM_DC_SETTING_REGISTER, b'\x0A')#self._command(PLL_CONTROL, b'\x3A') # 3A 100HZ   29 150Hz 39 200HZ    31 171HZ#self._command(POWER_SETTING, b'\x03\x00\x2b\x2b\x09') # VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR#self._command(BOOSTER_SOFT_START, b'\x07\x07\x17')#self._command(POWER_OPTIMIZATION, b'\x60\xA5')#self._command(POWER_OPTIMIZATION, b'\x89\xA5')#self._command(POWER_OPTIMIZATION, b'\x90\x00')#self._command(POWER_OPTIMIZATION, b'\x93\x2A')#self._command(POWER_OPTIMIZATION, b'\x73\x41')#self._command(VCM_DC_SETTING_REGISTER, b'\x12')#self._command(VCOM_AND_DATA_INTERVAL_SETTING, b'\x87') # define by OTP#self.set_lut()#self._command(PARTIAL_DISPLAY_REFRESH, b'\x00')#self.turnOnDisplay()def reset(self):self.rst(0)sleep_ms(200)self.rst(1)sleep_ms(200)        def wait_until_idle(self):while self.busy.value() == BUSY:print("waiting for busy...")sleep_ms(50)        def turnOnDisplay(self):self._command(DISPLAY_REFRESH)self.wait_until_idle()def display_frame(self, frame_buffer_black, frame_buffer_red):#self._command(TCON_RESOLUTION, ustruct.pack(">HH", EPD_WIDTH, EPD_HEIGHT))#写分辨率,已在init中,不用再写if (frame_buffer_black != None):self._command(DATA_START_TRANSMISSION_1)#写黑白sleep_ms(2)for i in range(0, self.width * self.height // 8):self._data(bytearray([frame_buffer_black[i]]))#print(i)sleep_ms(2)self._command(UNKNOWN_CMD);#???if (frame_buffer_red != None):self._command(DATA_START_TRANSMISSION_2)#写红色sleep_ms(2)for i in range(0, self.width * self.height // 8):self._data(bytearray([frame_buffer_red[i]]))sleep_ms(2)self._command(UNKNOWN_CMD);#???self.turnOnDisplay()

到这里,屏幕操作基本上算是有反应了,init后应该能看到屏幕在闪烁。

是的,里面有个UNKNOW_CMD(0x92),我也不知道是什么命令,C语言板本得驱动文件里有,我就抄下来了。

(4)往屏幕上显示英文字符集

我使用的是micropython下的framebuff驱动方法。该方法的思想是在内存中开辟一块与显示屏分辨率相同的内存区域,要显示东西就往该内存块操作,操作完了直接用该内存块来整屏刷新即可。这样可以避免频繁直接操作屏幕,效率比较高。

framebuff使用时,先要创建一块buff内存块,然后创建framebuff对象,该对象只是提供了一些操作方法,方便了对之前创建的buff内存块的操作,其自身不占用buff内存块,所以,要送给显示器的数据实际上是buff内存块。在framebuff没有提供的方法之外,你也可以直接操作buff内存块,更改显示内容。

但由于micropython下的framebuff方法不完善,很多操作方法都没有,字体也仅限于8*8字体,所以也只能勉强用用而已,要显示大字体和中文,就得再想办法,这也是后文想描述的内容。

整屏刷新的代码在前面初始化部分已经贴出来了,就是def display_frame部分。

要特别说一下的是,由于内存实在有限,如果同时开辟黑色和红色两块显示buffer,ESP8266会当场摆烂,所以我只开了一块黑色的buffer来测试,改到ESP32平台的话,可以两块一起开。注意display_frame方法中:

self._command(DATA_START_TRANSMISSION_1)部分是写黑白显存

self._command(DATA_START_TRANSMISSION_2)部分是写红色显存

把初始化部分文件存为epaper2in66b1.py,然后做下面的测试。

先把代码贴出来:

from epaper2in66b1 import EPD
from machine import Pin,SPI
import framebuf
import micropython,gc# Display resolution
EPD_WIDTH  = const(152)
EPD_HEIGHT = const(296)
black      = const(0)
white      = const(1)
# Display orientation
ROTATE_0   = const(0)
ROTATE_90  = const(1)
ROTATE_180 = const(2)
ROTATE_270 = const(3)e=EPD(spi=SPI(1,baudrate=10000000),cs=Pin(15),dc=Pin(4),rst=Pin(2),busy=Pin(5))
e.init()buf_black=bytearray(EPD_WIDTH *EPD_HEIGHT//8)
frb_black=framebuf.FrameBuffer(buf_black,EPD_WIDTH,EPD_HEIGHT,framebuf.MONO_HLSB)frb_black.fill(white)
frb_black.text('Hello World',30,30,black)e.display_frame(buf_black)

OK,运行后你会在屏幕上看到一行黑色的hello world,字体是8*8的。

(5)显示中文

micropython下,中文是个很麻烦的事情,不过经过度娘了一堆文章研究后,得到一个比较好的方法,即使用unicode字模。

大神的文章在这里:https://www.cnblogs.com/juwan/p/13198330.html

大概的原理是这样:

micropython中,均使用的是utf-8编码,包括汉字,并且你改不了。

但是有个unicode编码方法,常用汉字的代码范围在0x4E00---0x9FA5,我们可以使用程序把汉字的utf-8编码转换为unicode编码,从而得到一个汉字的16位的unicode编码。如果我们有一个unicode汉字编码顺序的字模库,比如一个16*16的字模库,每个汉字占用的字模是固定的16*16/8=32字节,那么我们就可以根据汉字的unicode码,在字模库中查找出该字的字模数据,显示在屏幕上了。好在我们有这么一个工具,图中这个。

就可以创建一个unicode字模库。我试了下,16*16的字模库大小约2M,生成后我们存为font.dzk,上传到ESP8266根目录就可以了。

下面是转码和查字模的代码:


def encode_get_utf8_size(utf):if utf < 0x80:return 1if utf >= 0x80 and utf < 0xC0:return -1if utf >= 0xC0 and utf < 0xE0:return 2if utf >= 0xE0 and utf < 0xF0:return 3if utf >= 0xF0 and utf < 0xF8:return 4if utf >= 0xF8 and utf < 0xFC:return 5if utf >= 0xFC:return 6def encode_utf8_to_unicode(utf8):utfbytes = encode_get_utf8_size(utf8[0])if utfbytes == 1:unic = utf8[0]if utfbytes == 2:b1 = utf8[0]b2 = utf8[1]if ((b2 & 0xE0) != 0x80):return -1unic = ((((b1 << 6) + (b2 & 0x3F)) & 0xFF) << 8) | (((b1 >> 2) & 0x07) & 0xFF)if utfbytes == 3:b1 = utf8[0]b2 = utf8[1]b3 = utf8[2]if (((b2 & 0xC0) != 0x80) or ((b3 & 0xC0) != 0x80)):return -1unic = ((((b1 << 4) + ((b2 >> 2) & 0x0F)) & 0xFF) << 8) | (((b2 << 6) + (b3 & 0x3F)) & 0xFF)if utfbytes == 4:b1 = utf8[0]b2 = utf8[1]b3 = utf8[2]b4 = utf8[3]if (((b2 & 0xC0) != 0x80) or ((b3 & 0xC0) != 0x80) or ((b4 & 0xC0) != 0x80)):return -1unic = ((((b3 << 6) + (b4 & 0x3F)) & 0xFF) << 16) | ((((b2 << 4) + ((b3 >> 2)& 0x0F)) & 0xFF) << 8) | ((((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03)) & 0xFF)if utfbytes == 5:b1 = utf8[0]b2 = utf8[1]b3 = utf8[2]b4 = utf8[3]b5 = utf8[4]if (((b2 & 0xC0) != 0x80) or ((b3 & 0xC0) != 0x80) or ((b4 & 0xC0) != 0x80) or ((b5 & 0xC0) != 0x80)):return -1unic = ((((b4 << 6) + (b5 & 0x3F)) & 0xFF) << 24) | (((b3 << 4) + ((b4 >> 2) & 0x0F) & 0xFF) << 16) | ((((b2 << 2) + ((b3 >> 4) & 0x03)) & 0xFF) << 8) | (((b1 << 6)) & 0xFF)if utfbytes == 6:b1 = utf8[0]b2 = utf8[1]b3 = utf8[2]b4 = utf8[3]b5 = utf8[4]b6 = utf8[5]if (((b2 & 0xC0) != 0x80) or ((b3 & 0xC0) != 0x80) or ((b4 & 0xC0) != 0x80) or ((b5 & 0xC0) != 0x80) or ((b6 & 0xC0) != 0x80)):return -1unic = ((((b5 << 6) + (b6 & 0x3F)) << 24) & 0xFF) | (((b5 << 4) + ((b6 >> 2) & 0x0F) << 16) & 0xFF) | ((((b3 << 2) + ((b4 >> 4) & 0x03)) << 8) & 0xFF) | ((((b1 << 6) & 0x40) + (b2 & 0x3F)) & 0xFF)return unicdef draw_string(img,x,y,c,string,width,high,fonts,space=0):#draw_string(buf_black,0,20,black,2,b'中',16,16,unicode_dict)import framebufi=0pos=0while i<len(string):utfbytes=encode_get_utf8_size(string[i])print(i,string[i],utfbytes,string[i:i+utfbytes])tmp=encode_utf8_to_unicode(string[i:i+utfbytes])print(type(tmp),tmp)i+=utfbytespos+=1fonts.seek(tmp*int(high*width/8))#print(len(fonts.read(int(high*width/8))))#img.draw_font(x+(pos*s*(width+sapce)),y,width,high,fonts.read(int(high*width/8)),color=c)blitbuf=bytearray(int(width*high/8))zimo=fonts.read(int(high*width/8))for aa in range(int(width*high/8)):blitbuf[aa]=zimo[aa]print(blitbuf)blitfrmbuf=framebuf.FrameBuffer(blitbuf,width,high,framebuf.MONO_HLSB)img.blit(blitfrmbuf,x+(int(i/3)-1)*width,y)print("i=",i,"pos=",x+(i/3-1)*width)

前两段是大神写的utf-8转unicode代码,输出的是一个字符的unicode代码,比如“床”字的unicode编码是0x5E8A(十进制24202),然后我们就可以打开字模文件取字模了。

最后一段draw_string是我改的,目的是把汉字字模写到墨水屏的buffer里去。用到的是framebuff的blit方法。blit方法是在一块现有的buffer(墨水屏的buffer)上叠加另一块buffer(查出来的字模buffer)。

调用draw_string后再调用显示屏的display_frame,显示屏上就出现汉字了。

把该三段代码存为zhuanma.py,传到ESP8266根目录,就可以测试汉字显示了,测试代码如下:

from epaper2in66b1 import EPD
from machine import Pin,SPI
import framebuf
import micropython,gc# Display resolution
EPD_WIDTH  = const(152)
EPD_HEIGHT = const(296)
black      = const(0)
white      = const(1)
# Display orientation
ROTATE_0   = const(0)
ROTATE_90  = const(1)
ROTATE_180 = const(2)
ROTATE_270 = const(3)e=EPD(spi=SPI(1,baudrate=10000000),cs=Pin(15),dc=Pin(4),rst=Pin(2),busy=Pin(5))
e.init()buf_black=bytearray(EPD_WIDTH *EPD_HEIGHT//8)
frb_black=framebuf.FrameBuffer(buf_black,EPD_WIDTH,EPD_HEIGHT,framebuf.MONO_HLSB)frb_black.fill(white)
frb_black.text('Hello World',30,30,black)#以下为测试汉字显示的代码
import zhuanma
unicode_dict=open('/font.Dzk','rb')
zhuanma.draw_string(frb_black,20,80,white,b'床前明月光',16,16,unicode_dict)
#注意传给draw_string的是frameBuff对象,而不是直接的数组buffer,因为draw_string调用framBuff的blit方法,要求是frameBuff对象e.display_frame(buf_black)

效果如下图(忽略红色,红色是之前的测试没清除掉的,代码结果只有黑色):

最后,把文中所用的一些资料和软件网盘分享出来。

字模提取是未注册板本的,字模会有斜杠,涉及版权问题,要没有斜杠的字模库请私信我。

链接: https://pan.baidu.com/s/1VQFME6P3YyuV4YWqM1-DOQ?pwd=t6v6

使用micropython(ESP8266、ESP32)驱动SES 2.66寸墨水屏显示中文相关推荐

  1. esp8266时钟_ESP8266(Non-OS SDK) 驱动 waveshare 2.9 寸墨水屏(二)- 程序移植、修改与测试

    开始移植 上一篇文章简单介绍了了墨水屏原理.例程代码以及移植工作的可行性.这一步的目的是把前面在 STM32 跑的程序,完整地搬到 esp8266 上,达到相同的运行显示效果,Let's get st ...

  2. 利用CH340C制作MicroPython ESP8266,ESP32的下载器-改进型

    简 介: 本文给出了利用CH340C芯片制作ESP32,ESP8266下载器的方法,并进行了实测测试. 关键词: ESP32,CH340C,MicroPython,下载器 ▌01 ESP的MicroP ...

  3. 测试CH340C的功能,制作MicroPython ESP8266,ESP32下载器

    ▌01 CH340C USB-UART芯片 CH340C 是沁恒公司的USB-UART的转换芯片.在 CH340E USB转串口 IC测试电路 测试了CH340E的基本功能.为了制作 ESP8266以 ...

  4. 外设驱动(一)E-Paper墨水屏扫描显示原理

    嵌入式应用笔记 此系列作为产品过程中记录开源库移植和一些遇到的问题,包括有效资源.要点.函数.方法和一些使用心得. 移植 开源库移植(一)轻量级环形缓冲区LwRB 驱动 驱动应用(一)低功耗墨水屏硬件 ...

  5. 使用ESP8266让4.2inch e-paper墨水屏显示更多汉字

    一直以来一直想让不论是OLED还是墨水屏显示任意汉字,但都没能成功,主要原因是显示任意汉字就需要字库,但MCU容量有限,放不下全字符汉字库,加装汉字芯片或增加Falsh都需要硬件,还要接线挺麻烦的,最 ...

  6. 物联网开发笔记(84)- 使用Micropython开发ESP32开发板之控制LCD12864液晶屏和AHT10温度传感器

    一.目的 这一节我们学习如何使用我们的ESP32开发板来学习LCD12864液晶屏和AHT10温度传感器的操作.  二.环境 ESP32 + Thonny + LCD12864液晶屏 + AHT10温 ...

  7. 使用ESP8266驱动微雪e-paer2.13墨水屏

    使用ESP8266驱动微雪e-paer墨水屏 前些时候使用Arduino nona小板子,成功驱动了微雪e-paer2.13墨水屏,但由于Arduino nona小板子内存小.主频低,虽然可以显示,但 ...

  8. 1.54寸墨水屏模块 E-Paper电子纸显示屏STM32C8T6驱动代码以及调试过程

    目录 如何点亮屏幕? 例程代码拆分 DEV_Module_Init(); 模块引脚初始化 EPD_1IN54_V2_Init(); 寄存器初始化 EPD_1IN54_V2_Clear(); 清屏 EP ...

  9. 高通字库芯片GT20L16S1Y驱动 0.96寸 OLED 任意显示中文

    连续两个月的加班,给ODM客户生产温控器订单,今天终于顺利发货,对于工程师出身的我,终于可以对着电脑,消停几天,研究技术,分享技术了,闲话少说,直接进入正题: 半年前有个老客户介绍个中央某院的项目,我 ...

最新文章

  1. springboot多模块打包指定子模块环境配置文件
  2. 笔试分享 | 带你解读校招人工智能笔试题
  3. ZigBee TI ZStack CC2530 2.4 IAR软件版本
  4. LeetCode:62. 不同路径
  5. machine learning (5)---learning rate
  6. Matrix-Tree (生成树计数)
  7. 南开大学计算机考研2019分数线,2019年南开大学考研复试分数线已公布
  8. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_4-1.单机和分布式应用的登录检验讲解...
  9. Java实现Map集合二级联动
  10. vb红绿灯交通灯小程序
  11. TFS2010 - 强制撤销签出
  12. 基于边缘特征的二值化阈值选取方法--ywp125
  13. 【Panoramic stitching】并查集(disjoint set)结构及源码
  14. 计算机怎么看显卡内存容量,Win10系统显卡显存大小怎样查看?Win10查看显存大小的两种方法...
  15. 如何隐藏Android模拟器的虚拟按键
  16. C语言中EOF是什么?
  17. 国防军工企业信息化与信息安全概要
  18. C#简繁体转换方法(kernel32.dll)
  19. java计算机毕业设计智能旅游电子票务系统演示录像2020源码+mysql数据库+系统+部署+lw文档
  20. 【MATLAB】禁忌算法(TS)求解TSP问题

热门文章

  1. matlab 计算半波宽,半导体激光器半高宽(FWHM)计算(包含matlab仿真程序).docx
  2. STM32自平衡小车
  3. NLP-Job1 赛题理解(天池)
  4. winform 右键右下角小图标菜单
  5. 手机测试充电宝软件,共享充电宝软件哪个好用 一个APP帮你搞定
  6. Oracle 19c 19.10DBRU 最新补丁升级看这一篇就够了
  7. 鼠标移入图标显示二维码功能实现
  8. EUI-64格式生成
  9. 基于Ovito的团簇识别分析
  10. android 颜色值16进制转换int类型;