所有教程由网友发布,仅供参考,请谨慎采纳。科创不对教程的科学性、准确性、可靠性负责。
Micropython+ESP32驱动墨水屏
XZH1002023/03/12原创 电子技术 IP:上海

一  开发板

开发板推荐选择NodeMCU-32s

捕获.jpg

micropython固件烧写请看:固件烧写与测试

(固件,Tonny,驱动和串口工具在文末附件里有)

二  墨水屏及驱动

墨水屏我使用的是微雪的2.9寸屏

捕获.jpg

也可以选择其他的,只要兼容微雪就行

然后是整个项目里最难的部分,写驱动。由于微雪官方给的驱动只有arduino和树莓派的,所以micropython的驱动需要自己编写。我写了大概快两周。

from micropython import const
from time import sleep_ms
import ustruct
from machine import Pin
import framebuf

EPD_WIDTH       = 128
EPD_HEIGHT      = 296

先导入需要的库并定义屏幕宽高

class EPD(framebuf.FrameBuffer):
    def __init__(self, spi, cs, dc, rst, busy):
	self.spi = spi
	self.cs = Pin(cs,Pin.OUT,value = 1)
	self.dc = Pin(dc,Pin.OUT,value = 0)
	self.rst = Pin(rst,Pin.OUT,value = 0)
	self.busy = Pin(busy,Pin.IN)
	self.width = EPD_WIDTH
	self.height = EPD_HEIGHT
	self.pages = self.height//8
	self.buffer = bytearray(self.width*self.pages)
	super().__init__(self.buffer, self.width, self.height,
                framebuf.MONO_HLSB)
	self.init()

创建EPD类并初始化(初始化函数在后面)


def reset(self):
    self.rst.value(1) 
    sleep_ms(20) 
    self.rst.value(0)
    sleep_ms(5)
    self.rst.value(1)
    sleep_ms(20)

然后是硬件重置函数,说白了就是给rst引脚一个低电平脉冲


def send_command(self, command):
    self.dc.value(0)
    self.cs.value(0)
    self.spi.write(command.to_bytes(1,'big'))
    self.cs.value(1)

def send_data(self, data):
    self.dc.value(1)
    self.cs.value(0)
    self.spi.write(data.to_bytes(1,'big'))
    self.cs.value(1)

然后是写入数据和命令

写入命令或数据前通过dc脚表示输入的是命令还是数据,然后cs脚低电平表示有效,通过spi写入


def ReadBusy(self):
    while self.busy == 1:      #  0: idle, 1: busy
	sleep_ms(10)

由于墨水屏刷新需要一段时间,所以需要是否忙,如果busy脚是高电平就需要等待


def TurnOnDisplay(self):
    self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
    self.send_data(0xc7)
    self.send_command(0x20) # MASTER_ACTIVATION
    self.ReadBusy()

def TurnOnDisplay_Partial(self):
    self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
    self.send_data(0x0F)
    self.send_command(0x20) # MASTER_ACTIVATION
    self.ReadBusy()

全局刷新和局部刷新的函数


def SetLut(self, lut):
    self.send_command(0x32)
    for i in range(0, 153):
        self.send_data(lut[i])
    self.ReadBusy()
    
def SetLut_by_host(self, lut):
    self.SetLut(lut)
    self.send_command(0x3f)
    self.send_data(lut[153])
    self.send_command(0x03);	# gate voltage
    self.send_data(lut[154])
    self.send_command(0x04);	# source voltage
    self.send_data(lut[155])	# VSH
    self.send_data(lut[156])	# VSH2
    self.send_data(lut[157])	# VSL
    self.send_command(0x2c);	# VCOM
    self.send_data(lut[158])

设置lut表,lut表在附件里的驱动有,作用是设置全刷还是局刷模式


def SetWindow(self, x_start, y_start, x_end, y_end):
    self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
    # x point must be the multiple of 8 or the last 3 bits will be ignored
    self.send_data((x_start>>3) & 0xFF)
    self.send_data((x_end>>3) & 0xFF)
    self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
    self.send_data(y_start & 0xFF)
    self.send_data((y_start >> 8) & 0xFF)
    self.send_data(y_end & 0xFF)
    self.send_data((y_end >> 8) & 0xFF)

def SetCursor(self, x, y):
    self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
    # x point must be the multiple of 8 or the last 3 bits will be ignored
    self.send_data((x >> 3) & 0xFF)  
    self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
    self.send_data(y & 0xFF)
    self.send_data((y >> 8) & 0xFF)
    self.ReadBusy()

这两个函数主要作用是设置写那一块区域的图像缓存,说白了就是设置你的图片显示在哪里


def init(self):
    # EPD hardware init start     
    self.reset()
    
    self.ReadBusy()
    self.send_command(0x12)  #SWRESET
    self.ReadBusy() 
    
    self.send_command(0x01) #Driver output control      
    self.send_data(0x27)
    self.send_data(0x01)
    self.send_data(0x00)
       
    self.send_command(0x11) #data entry mode       
    self.send_data(0x03)
    
    self.SetWindow(0, 0, self.width-1, self.height-1)
    
    self.send_command(0x21) #  Display update control
    self.send_data(0x00)
    self.send_data(0x80)
        
    self.SetCursor(0, 0)
    self.ReadBusy()
    self.SetLut_by_host(self.WS_20_30)
	# EPD hardware init end
	return 0

初始化函数,具体流程图可以在墨水屏的说明书中找到

捕获.jpg

def SetFrameMemory(self,image_buffer,x,y,w,h):
	# x point must be the multiple of 8 or the last 3 bits will be ignored
	x = x & 0xF8
	w = w & 0xF8

	if image_buffer==[] or x < 0 or w < 0 or y < 0 or h < 0:
		return

	if (x + w >= self.width):
		x_end = self.width - 1
	else:
		x_end = x + w - 1

	if (y + h >= self.height):
		y_end = self.height - 1
	else:
		y_end = y + h - 1

	self.SetWindow(x, y, x_end, y_end)
	self.SetCursor(x, y)
	self.send_command(0x24)
		
	for j in range(0, int(y_end - y + 1)):
		for i in range(0, int((x_end - x + 1) / 8)):
			self.send_data(image_buffer[i + j * int(w / 8)])

def SetFrameMemory_Partial(self,image_buffer,x,y,w,h):
	if image_buffer==[] or x < 0 or w < 0 or y < 0 or h < 0:
		return
		
	x &= 0xF8
	w &= 0xF8
		
	if (x + w >= self.width):
		x_end = self.width - 1
	else:
		x_end = x + w - 1

	if (y + h >= self.height):
		y_end = self.height - 1
	else:
		y_end = y + h - 1

	self.rst.value(0)
	sleep_ms(5)
	self.rst.value(1)
	sleep_ms(5)
		
	self.SetLut(self._WF_PARTIAL_2IN9)
	self.send_command(0x37)
	self.send_data(0x00)
	self.send_data(0x00)
	self.send_data(0x00)
	self.send_data(0x00)
	self.send_data(0x00)
	self.send_data(0x40)
	self.send_data(0x00)
	self.send_data(0x00)
	self.send_data(0x00)
	self.send_data(0x00)

	self.send_command(0x3C) #BorderWavefrom
	self.send_data(0x80)

	self.send_command(0x22)
	self.send_data(0xC0)
	self.send_command(0x20)
	sleep_ms(100)
	#self.ReadBusy()
		
	self.SetWindow(x, y, x_end, y_end)
	self.SetCursor(x, y)
	self.send_command(0x24)
	for j in range(0,int(y_end - y + 1)):
		for i in range(0,int((x_end - x + 1) / 8)):
			self.send_data(image_buffer[i + j * int(w / 8)])

def SetFrameMemory_Base(self, image_buffer):
	self.SetWindow(0, 0,self.width - 1,self.height - 1)
	self.SetCursor(0, 0)
	self.send_command(0x24)
	for i in range(0, self.width/8*self.height):
		self.send_data(image_buffer[i])
	self.send_command(0x26)
	for i in range(0, self.width/8*self.height):
		self.send_data(image_buffer[i])

设置帧内存,分别对应全刷,局刷和显示背景,背景图片必须是296*128像素的

注意:这里缩进由于复制出问题了

这是清空帧内存,类似于清屏

def ClearFrameMemory(self,color):
    self.SetWindow(0, 0,self.width - 1,self.height - 1)
    self.SetCursor(0, 0)
    self.send_command(0x24)
    for i in range(0,self.width/8*self.height):
    self.send_data(color)

这个color输入的必须是0xFF或0x00(前者是全部刷白,后者是全部刷黑)

睡眠模式:长时间不刷新需要调为睡眠模式

def sleep(self):
    self.send_command(0x10) # DEEP_SLEEP_MODE
    self.send_data(0x01)

注意:唤醒后需要重新进行初始化

到这里驱动就写完了,毫不夸张的说,网上能找到的驱动很少,功能也没这么全(基本上都不支持局刷),但刚入门编写驱动难度较大,大家可以根据这套编写其他尺寸墨水屏的驱动。毕竟授之以鱼不如授之以渔。

三  显示图片

捕获.jpg

墨水屏显示的内容必须转成十六进制形式,需要用到图片取模软件(附件里也有)

首先用Windows自带的画图创一个128*296大小的图片,画完后另存为.bmp(单位色图)格式。

捕获.jpg

打开附件里的PCtoLCD2002,先要设置输出格式

捕获.jpg

提示:请仔细检查设置的内容和上图是否一致,否则可能导致报错或显示错误

然后点击文件->打开,打开前面保持的图像点击生成字模,然后在micropython设备中新建一个bg_XXXXXXX,将输出的内容放在一个列表里

捕获.jpg

新建XXXXXXX

from machine import SPI,Pin
from e2in9 import EPD
from bg_data import BG_DATA

spi=SPI(1,32000,sck=Pin(14),mosi=Pin(13))
display=EPD(spi,22,21,17,16)

display.SetFrameMemory_Base(BG_DATA)
display.TurnOnDisplay()

运行,这时屏幕上应该已经显示出了内容

f0b23016c484ebd27a2e2a1852b4f3fb.mp4  点击下载

然后试试局刷

5d01ffd4c26e90f2d6fae196e977f14f.mp4  点击下载

原来图片的残影怎么出来了???

总体来说算成功了


四 进阶内容:墨水屏时钟摆件

TI.jpg


资料:

attachment icon epaper.zip 23.63MB ZIP 267次下载


来自:电子信息 / 电子技术严肃内容:教程/课程
1
8
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
Ma3.02的守望
1年9个月前 IP:安徽
917975

楼主用墨水屏的刷屏帧率大概能到多少呢?无明显残影的情况下。盖格计一类的省电仪器,搭配墨水屏也是不错的组合。


残影是墨水屏的固有问题,好像是需要间隔刷白屏才能完全消除,网上看过一些新工艺墨水屏可以把残影控制的比较好。

引用
评论(1)
4
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
XZH100
进士 机友 笔友
文章
13
回复
80
学术分
0
2022/03/20注册,2时43分前活动

奇迹师

奇迹总是一时命运总是漫长

XXXXXXXXXXXXXeor.space/eden8888/

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:上海
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}