上一家中,我们学习了如何在屏幕上显示一张图片,那怎me显示动态图片呢?其实原理很简单,我们把一个GIF动态图片,通过工具分成若干张图片,然后快速的切换显示,就可以看到动起来的效果。让我们一起来试试吧。
一、目的
在我们的240x240的oled屏幕上显示动态图片
二、环境
ESP32 + 240x240的oled彩色屏幕+ Thonny IDE + 几根杜邦线
接线方式请看上一节,此处不再重复赘述。
三、准备GIF图片
我们在百度上直接搜索你想要的动态图片即可,找到后下载到本地。
四、格式转换
我们下载一个图片格式转换工具,叫做:优速图片格式转换器
下载地址1:优速图片格式转换器免费版_优速图片格式转换器免费版下载_优速图片格式转换器V2.0.3-华军软件园
下载地址2:
优速图片格式转换器-优速图片格式转换器免费下载安装_优速办公软件
下载后,安装,然后打开,按如下步骤操作:
然后我们就得到若干张图片:
接下来我们需要对图片进行裁剪,变为240x240的大小。
五、图片裁剪
我们先下载一个图片批量裁剪的工具,叫做:优速图片格式转换器
哈哈。。。还是上面的那个工具,功能强大哈。。。就是免费的有水印,囧。。。。
然后按照如下方法,进行操作:
处理后的240x240图片:
然后我们需要将图片转化为二进制文件。
我们用上一节给大家那个代码就可以,忘记了没,我们一起再来复习下哈:
上代码喽,客观慢用,哈。。。
# img_to_binary.py
import struct
import numpy as np
from PIL import Image # PIL就是pillow库
def color565(r, g, b):
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3
def main():
for i in range(1, 9):
img = Image.open("./images/PuBu{}.jpg".format(i))
# print(img.format, img.size, img.mode)
img_data = np.array(img) # 240行240列有3个 240x240x3
with open("./images/PuBu{}.dat".format(i), "wb") as f:
for line in img_data:
for dot in line:
f.write(struct.pack("H", color565(dot[0], dot[1], dot[2]))[::-1])
if __name__ == '__main__':
main()
我们在电脑上打开pycharm,运行上面代码,就会得到.dat文件
然后我们需要将这些dat文件,转送到开发板上
七、dat文件传送
还记得怎么从电脑上将文件传到开发板中吗?看一下上一节内容,想一想哦。。。
忘了没关系我们一起看一下,首先我们需要早开发板上开启一个TCP的服务端,用来接收数据。在Thonny IDE中运行以下代码:
# recv_img_dat.py 在MicroPython上启动TCP服务器,接收数据
import time
import network
import machine
import socket
def do_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('WIFI名字', 'WIFI密码') # WIFI名称和密码
i = 1
while not wlan.isconnected():
print("正在链接...{}".format(i))
i += 1
time.sleep(1)
print('network config:', wlan.ifconfig())
# 0. 链接wifi
do_connect()
# 1. 创建TCP套接字
server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定本地信息
server_s.bind(("", 8080))
# 3. 设置为被动的
server_s.listen(128)
print("等待对方链接...")
# 4. 等待客户端链接
new_s, client_info = server_s.accept()
print("等待对方发送图片数据...")
# 3. 创建文件,接收数据
for i in range(1, 9):
with open("PuBu{}.dat".format(i), "wb") as f:
for j in range(240):
# 3.1 接收数据
data = new_s.recv(480) # 240*2=480 一行有240个点,每个点有2个字节
# 3.2 写到文件
f.write(data)
print("接收完毕{}".format(i))
print("全部数据接收完毕")
# 7. 关闭套接字
new_s.close()
server_s.close()
然后我们在电脑上的pycharm上运行以下代码,给开发板发送dat文件:
# send_img_dat_to_esp.py 发送二进制文件到开发板中
from socket import *
# 1. 创建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 2. 链接服务器
tcp_client_socket.connect(("192.168.0.100", 8080)) # ESP32开发板的IP地址和端口号
# 2. 打开文件,发送数据
for i in range(1, 9):
with open("./images/PuBu{}.dat".format(i), "rb") as f:
for j in range(240):
# 3.1 写到文件
data = f.read(480)
# 3.2 接收数据
tcp_client_socket.send(data) # 240*2=480 一行有240个点,每个点有2个字节
print("发送完毕{}".format(i))
print("所有数据发送完毕")
# 7. 关闭套接字
tcp_client_socket.close()
电脑发送完成:
开发板接收完成:
开发板接收完数据,我们接下来就可以想办法显示啦。。。。
八、动态图片显示
在Thonny上新建一个文件show_img.py:
# show_img.py 显示图片代码
from machine import Pin, SPI
import st7789_new
import time
tft = st7789_new.ST7889_Image(SPI(2, 80000000), dc=Pin(2), cs=Pin(5), rst=Pin(15))
tft.fill(st7789_new.color565(0, 0, 0)) # 背景设置为黑色
# 因为用到了14张图片,所以这里创建14个文件对象
f_list = [open("PuBu{}.dat".format(i), "rb") for i in range(1, 9)]
def show_img():
while True:
for f in f_list: # 遍历14个文件,显示图片
f.seek(0)
for row in range(0, 240, 24):
buffer = f.read(11520)
tft.show_img(0, row, 239, row+24, buffer)
show_img()
PS:记得导入屏幕驱动哦,没有驱动屏幕无法显示哈,驱动同上一节一样哈,这里在贴一下:
import ustruct
import utime
_NOP = const(0x00)
_SWRESET = const(0x01)
_RDDID = const(0x04)
_RDDST = const(0x09)
_SLPIN = const(0x10)
_SLPOUT = const(0x11)
_PTLON = const(0x12)
_NORON = const(0x13)
_INVOFF = const(0x20)
_INVON = const(0x21)
_DISPOFF = const(0x28)
_DISPON = const(0x29)
_CASET = const(0x2A)
_RASET = const(0x2B)
_RAMWR = const(0x2C)
_RAMRD = const(0x2E)
_PTLAR = const(0x30)
_COLMOD = const(0x3A)
_MADCTL = const(0x36)
_FRMCTR1 = const(0xB1)
_FRMCTR2 = const(0xB2)
_FRMCTR3 = const(0xB3)
_INVCTR = const(0xB4)
_DISSET5 = const(0xB6)
_GCTRL = const(0xB7)
_VCOMS = const(0xBB)
_FRCTR2 = const(0xC6)
_D6H = const(0xD6)
_PWCTRL1 = const(0xD0)
_GATECTRL = const(0xE4)
_PWCTR1 = const(0xC0)
_PWCTR2 = const(0xC1)
_PWCTR3 = const(0xC2)
_PWCTR4 = const(0xC3)
_PWCTR5 = const(0xC4)
_VMCTR1 = const(0xC5)
_RDID1 = const(0xDA)
_RDID2 = const(0xDB)
_RDID3 = const(0xDC)
_RDID4 = const(0xDD)
_PWCTR6 = const(0xFC)
_GMCTRP1 = const(0xE0)
_GMCTRN1 = const(0xE1)
def color565(r, g, b):
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3
class DummyPin:
"""A fake gpio pin for when you want to skip pins."""
OUT = 0
IN = 0
PULL_UP = 0
PULL_DOWN = 0
OPEN_DRAIN = 0
ALT = 0
ALT_OPEN_DRAIN = 0
LOW_POWER = 0
MED_POWER = 0
HIGH_PWER = 0
IRQ_FALLING = 0
IRQ_RISING = 0
IRQ_LOW_LEVEL = 0
IRQ_HIGH_LEVEL = 0
def __call__(self, *args, **kwargs):
return False
init = __call__
value = __call__
out_value = __call__
toggle = __call__
high = __call__
low = __call__
on = __call__
off = __call__
mode = __call__
pull = __call__
drive = __call__
irq = __call__
class Display:
_PAGE_SET = None
_COLUMN_SET = None
_RAM_WRITE = None
_RAM_READ = None
_INIT = ()
_ENCODE_PIXEL = ">H"
_ENCODE_POS = ">HH"
_DECODE_PIXEL = ">BBB"
def __init__(self, width, height):
self.width = width
self.height = height
self.init()
def init(self):
"""Run the initialization commands."""
for command, data in self._INIT:
self._write(command, data)
def _block(self, x0, y0, x1, y1, data=None):
"""Read or write a block of data."""
self._write(self._COLUMN_SET, self._encode_pos(x0, x1))
self._write(self._PAGE_SET, self._encode_pos(y0+80, y1+80))
if data is None:
size = ustruct.calcsize(self._DECODE_PIXEL)
return self._read(self._RAM_READ, (x1 - x0 + 1) * (y1 - y0 + 1) * size)
self._write(self._RAM_WRITE, data)
def _encode_pos(self, a, b):
"""Encode a postion into bytes."""
return ustruct.pack(self._ENCODE_POS, a, b)
def _encode_pixel(self, color):
"""Encode a pixel color into bytes."""
return ustruct.pack(self._ENCODE_PIXEL, color)
def _decode_pixel(self, data):
"""Decode bytes into a pixel color."""
return color565(*ustruct.unpack(self._DECODE_PIXEL, data))
def pixel(self, x, y, color=None):
"""Read or write a pixel."""
if color is None:
return self._decode_pixel(self._block(x, y, x, y))
if not 0 <= x < self.width or not 0 <= y < self.height:
return
self._block(x, y, x, y, self._encode_pixel(color))
def fill_rectangle(self, x, y, width, height, color):
"""Draw a filled rectangle."""
x = min(self.width - 1, max(0, x))
y = min(self.height - 1, max(0, y))
w = min(self.width - x, max(1, width))
h = min(self.height - y, max(1, height))
self._block(x, y, x + w - 1, y + h - 1, b'')
chunks, rest = divmod(w * h, 512)
print("color:", color)
pixel = self._encode_pixel(color)
print("decode:", pixel)
if chunks:
data = pixel * 512
for count in range(chunks):
self._write(None, data)
if rest:
self._write(None, pixel * rest)
def fill(self, color=0):
"""Fill whole screen."""
self.fill_rectangle(0, 0, self.width, self.height, color)
def hline(self, x, y, width, color):
"""Draw a horizontal line."""
self.fill_rectangle(x, y, width, 1, color)
def vline(self, x, y, height, color):
"""Draw a vertical line."""
self.fill_rectangle(x, y, 1, height, color)
def blit_buffer(self, buffer, x, y, width, height):
"""Copy pixels from a buffer."""
if (not 0 <= x < self.width or
not 0 <= y < self.height or
not 0 < x + width <= self.width or
not 0 < y + height <= self.height):
raise ValueError("out of bounds")
self._block(x, y, x + width - 1, y + height - 1, buffer)
class DisplaySPI(Display):
def __init__(self, spi, dc, cs=None, rst=None, width=1, height=1):
self.spi = spi
self.cs = cs
self.dc = dc
self.rst = rst
if self.rst is None:
self.rst = DummyPin()
if self.cs is None:
self.cs = DummyPin()
self.cs.init(self.cs.OUT, value=1)
self.dc.init(self.dc.OUT, value=0)
self.rst.init(self.rst.OUT, value=1)
self.reset()
super().__init__(width, height)
def reset(self):
self.rst(0)
utime.sleep_ms(50)
self.rst(1)
utime.sleep_ms(50)
def _write(self, command=None, data=None):
if command is not None:
self.dc(0)
self.cs(0)
self.spi.write(bytearray([command]))
self.cs(1)
if data:
self.dc(1)
self.cs(0)
self.spi.write(data)
self.cs(1)
def _read(self, command=None, count=0):
self.dc(0)
self.cs(0)
if command is not None:
self.spi.write(bytearray([command]))
if count:
data = self.spi.read(count)
self.cs(1)
return data
class ST7789(DisplaySPI):
"""
A simple driver for the ST7789-based displays.
>>> from machine import Pin, SPI
>>> import st7789
>>> display = st7789.ST7789(SPI(1), dc=Pin(12), cs=Pin(15), rst=Pin(16))
>>> display = st7789.ST7789R(SPI(1, baudrate=40000000), dc=Pin(12), cs=Pin(15), rst=Pin(16))
>>> display.fill(0x7521)
>>> display.pixel(64, 64, 0)
"""
_COLUMN_SET = _CASET
_PAGE_SET = _RASET
_RAM_WRITE = _RAMWR
_RAM_READ = _RAMRD
_INIT = (
(_SWRESET, None),
(_SLPOUT, None),
(_COLMOD, b"\x55"), # 16bit color
(_MADCTL, b"\x08"),
)
def __init__(self, spi, dc, cs, rst=None, width=240, height=240):
super().__init__(spi, dc, cs, rst, width, height)
def init(self):
super().init()
cols = ustruct.pack(">HH", 0, self.width)
rows = ustruct.pack(">HH", 0, self.height)
# ctr2p= ustruct.pack(">BBBBB", b"\x1F\x1F\x00\x33\x33")
ctr2p= b"\x1F\x1F\x00\x33\x33"
# ctr1p= ustruct.pack(">BB", b"\xA4\xA1")
ctr1p= b"\xA4\xA1"
# e0p= ustruct.pack(">BBBBBBBBBBBBBB", b"\xF0\x08\x0E\x09\x08\x04\x2F\x33\x45\x36\x13\x12\x2A\x2D")
e0p= b"\xF0\x08\x0E\x09\x08\x04\x2F\x33\x45\x36\x13\x12\x2A\x2D"
# e1p= ustruct.pack(">BBBBBBBBBBBBBB", b"\xF0\x0E\x12\x0C\x0A\x15\x2E\x32\x44\x39\x17\x18\x2B\x2F")
e1p= b"\xF0\x0E\x12\x0C\x0A\x15\x2E\x32\x44\x39\x17\x18\x2B\x2F"
# gatep= ustruct.pack(">BBB", b"\x1d\x00\x00")
gatep= b"\x1d\x00\x00"
for command, data in (
(_CASET, cols),
(_RASET, rows),
(_FRMCTR2,ctr2p),
(_GCTRL, b"\x00"),
(_VCOMS, b"\x36"),
(_PWCTR3, b"\x01"),
(_PWCTR4, b"\x13"),
(_PWCTR5, b"\x20"),
(_FRCTR2, b"\x13"),
(_D6H, b"\xA1"),
(_PWCTRL1, ctr1p),
(_GMCTRP1, e0p),
(_GMCTRN1, e1p),
(_GATECTRL, gatep),
(_INVON, None),
(_NORON, None),
(_DISPON, None),
(_MADCTL, b"\xc0"), # Set rotation to 0 and use RGB
):
self._write(command, data)
class ST7889_Image(ST7789):
def _set_columns(self, start, end):
if start <= end:
self._write(_CASET, self._encode_pos(start, end))
def _set_rows(self, start, end):
if start <= end:
self._write(_RASET, self._encode_pos(start, end))
def _set_window(self, x0, y0, x1, y1):
"""
x0: x起始位置
y0: y起始位置
x1: x结束位置
y1: y结束位置
"""
self._set_columns(x0, x1)
self._set_rows(y0, y1)
self._write(_RAMWR)
def show_img(self, x0, y0, x1, y1, img_data):
self._set_window(x0, y0 + 80, x1, y1 + 80)
self._write(None, img_data)
九、显示效果
十、小结
看下文件结构,电脑端文件:
ESP32开发板端文件:
所有文件我给大家打个包,需要吸取哈。。。
链接: https://pan.baidu.com/s/1mEnhsUE2dS_0TAqT-jkMFA 提取码: 8rpy 复制这段内容后打开百度网盘手机App,操作更方便哦。。。