MicroPython+ESP32驱动ST7735 TFT实现动态数据可视化界面
1. 硬件准备与环境搭建
想要用ESP32驱动ST7735 TFT屏幕实现动态数据可视化,首先得把硬件环境搭建好。我刚开始玩这个的时候,最头疼的就是接线问题,一不小心接错就可能烧坏屏幕或者ESP32开发板。
ESP32开发板我推荐使用ESP32-WROOM-32,这个型号比较常见,引脚布局也相对标准。ST7735 TFT屏幕建议选择0.96寸或1.8寸的,分辨率160x80或128x160都比较适合做数据可视化。这两种尺寸的屏幕在MicroPython下驱动起来性能表现不错,刷新率足够显示动态数据。
接线是个技术活,我总结了一个比较稳妥的连接方案:
| ESP32引脚 | ST7735引脚 | 功能说明 |
|---|---|---|
| GPIO 18 | SCL | 时钟信号 |
| GPIO 23 | SDA | 数据信号 |
| GPIO 16 | RES | 复位信号 |
| GPIO 17 | DC | 数据/命令选择 |
| GPIO 5 | CS | 片选信号 |
| 3.3V | VCC | 电源正极 |
| GND | GND | 电源地线 |
实际接线时要注意,有些屏幕的BLK引脚是背光控制,可以接3.3V也可以接GPIO引脚来控制亮度。我第一次接的时候就忘了接这个引脚,结果屏幕一片漆黑,还以为屏幕坏了。
软件环境方面,需要先给ESP32刷入MicroPython固件。我习惯用esptool.py这个工具,刷机命令很简单:
esptool.py --port /dev/ttyUSB0 erase_flash
esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-micropython.bin
刷完固件后,用Thonny IDE或者VS Code+PyMakr插件来编写和上传代码。我个人更喜欢Thonny,因为它对MicroPython的支持很友好,可以直接在IDE里看到ESP32的文件系统,上传下载文件都很方便。
2. ST7735驱动库深度解析
驱动库是整个项目的核心,我花了不少时间研究ST7735的驱动原理。这个屏幕使用的是SPI接口通信,MicroPython通过machine模块的SPI类来控制数据传输。
初始化驱动库时有几个关键参数需要特别注意:
from machine import Pin, SPI
import st7735
spi = SPI(2, baudrate=10000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23))
tft = st7735.ST7735TFT(spi, dc=17, res=16, cs=5, size=(80, 160))
baudrate设置很重要,太高了会导致数据传输错误,太低了会影响刷新率。经过我的测试,10MHz是个比较稳定的值。polarity和phase这两个参数需要根据屏幕的数据手册来设置,大多数ST7735屏幕都是0和0。
驱动库的核心功能包括屏幕初始化、像素绘制、图像显示、文字渲染等。我在实际使用中发现,原生的驱动库有些地方还需要优化,特别是在显示动态数据时。
比如像素绘制函数,原始的实现是每次只画一个点,这样在绘制图表时效率很低。我改进后的版本支持批量绘制:
def draw_pixels_batch(self, positions, colors):
"""批量绘制像素点,提高绘制效率"""
if len(positions) != len(colors):
raise ValueError("位置和颜色数量不匹配")
# 设置绘制窗口
self._setwindowloc((0, 0), self._size)
# 准备数据缓冲区
buffer = bytearray(len(positions) * 2)
for i, color in enumerate(colors):
buffer[i*2] = (color >> 8) & 0xFF
buffer[i*2+1] = color & 0xFF
self._write(buffer)
这样的优化让动态数据刷新更加流畅,特别是在绘制实时曲线图时效果明显。
3. 动态数据可视化实战
动态数据可视化是这个项目最有趣的部分。我做过温湿度监测、网络时间显示、甚至简单的股票行情展示。关键是要掌握数据采集、处理和显示的完整流程。
以温湿度监测为例,首先需要连接DHT11或DHT22传感器:
import dht
from machine import Pin
sensor = dht.DHT11(Pin(15))
def read_sensor():
try:
sensor.measure()
temp = sensor.temperature()
humidity = sensor.humidity()
return temp, humidity
except:
return None, None
数据采集后,需要设计一个实时更新的图表显示。我常用的方法是双缓冲绘制:先在内存中绘制完整的图表,然后一次性更新到屏幕上。
class DynamicChart:
def __init__(self, tft, x, y, width, height):
self.tft = tft
self.x = x
self.y = y
self.width = width
self.height = height
self.data = []
self.max_data_points = width // 2 # 每两个像素一个数据点
def update(self, new_value):
if len(self.data) >= self.max_data_points:
self.data.pop(0)
self.data.append(new_value)
self._draw_chart()
def _draw_chart(self):
# 清空图表区域
self.tft.fillrect((self.x, self.y), (self.width, self.height), 0)
# 绘制坐标轴
self.tft.fillrect((self.x, self.y + self.height - 1), (self.width, 1), 0xFFFF)
self.tft.fillrect((self.x, self.y), (1, self.height), 0xFFFF)
# 绘制数据曲线
if len(self.data) > 1:
max_val = max(self.data) if max(self.data) > 0 else 1
for i in range(1, len(self.data)):
x1 = self.x + (i - 1) * 2
y1 = self.y + self.height - int(self.data[i - 1] / max_val * self.height)
x2 = self.x + i * 2
y2 = self.y + self.height - int(self.data[i] / max_val * self.height)
# 绘制线段
self._draw_line(x1, y1, x2, y2, 0xF800) # 红色曲线
这种实现方式可以实时显示数据变化趋势,刷新率能达到10-15fps,对于嵌入式设备来说已经很不错了。
4. 自定义字体与中文显示
中文显示是很多开发者遇到的难题,因为MicroPython默认不支持中文字体。我研究出了一套比较实用的解决方案。
首先需要制作中文字库,我推荐使用PCtoLCD2002这个工具。它支持各种点阵字体生成,可以导出MicroPython可以直接使用的字模数据。
制作字库的步骤很关键:
- 打开PCtoLCD2002,选择正确的字体和大小
- 设置输出格式为"阴码+逐行式+顺向+C51格式"
- 输入需要显示的汉字,生成字模数据
- 将字模数据转换为MicroPython字典格式
这是我常用的16x16中文字库结构:
font16x16 = {
'godata': 2, # 每行2个字节
'size': (16, 16),
'Data': {
21333: [0x10, 0x10, 0x08, 0x20, 0x04, 0x40, 0x3F, 0xF8, 0x21, 0x08, 0x21, 0x08, 0x3F, 0xF8, 0x21, 0x08,
0x21, 0x08, 0x3F, 0xF8, 0x01, 0x00, 0x01, 0x00, 0xFF, 0xFE, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00], # 单
39640: [0x02, 0x00, 0x01, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xE0, 0x08, 0x20, 0x08, 0x20, 0x0F, 0xE0,
0x00, 0x00, 0x7F, 0xFC, 0x40, 0x04, 0x4F, 0xE4, 0x48, 0x24, 0x48, 0x24, 0x4F, 0xE4, 0x40, 0x0C], # 高
}
}
显示中文时,需要先将字符串转换为Unicode编码,然后在字库中查找对应的字模数据:
def show_chinese(text, x, y, fg_color, bg_color):
for char in text:
char_code = ord(char)
if char_code in font16x16['Data']:
char_data = font16x16['Data'][char_code]
# 绘制字符
draw_char(x, y, char_data, fg_color, bg_color)
x += 16 # 移动到下一个字符位置
为了优化显示性能,我建议只包含需要显示的汉字,避免字库过大占用太多内存。在实际项目中,我通常只包含几十个常用汉字,这样既能满足显示需求,又不会占用太多资源。
5. 网络时间同步与界面设计
网络时间同步让我们的显示界面更加实用。ESP32可以连接WiFi,从NTP服务器获取准确的时间信息。
首先需要配置WiFi连接:
import network
import ntptime
def connect_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('正在连接WiFi...')
wlan.connect(ssid, password)
while not wlan.isconnected():
pass
print('网络配置:', wlan.ifconfig())
def sync_time():
try:
ntptime.settime()
print("时间同步成功")
except:
print("时间同步失败")
获取时间后,可以设计一个漂亮的时钟界面:
class ClockInterface:
def __init__(self, tft):
self.tft = tft
self.last_time = ""
def update(self):
current_time = self._get_formatted_time()
if current_time != self.last_time:
self._draw_clock(current_time)
self.last_time = current_time
def _get_formatted_time(self):
# 获取当前时间并格式化
import time
t = time.localtime()
return "{:02d}:{:02d}:{:02d}".format(t[3], t[4], t[5])
def _draw_clock(self, time_str):
# 清空显示区域
self.tft.fillrect((20, 50), (120, 30), 0)
# 绘制时间
self.tft.texts(time_str, (20, 50), 0xFFFF, 0, font16x16)
# 绘制装饰元素
self.tft.fillrect((20, 80), (120, 2), 0xFFFF) # 下划线
界面设计要考虑可视性和美观性。我通常使用深色背景配合高对比度的文字,这样在各种光照条件下都能清晰显示。重要的数据可以用大字体显示,次要信息用小字体。
6. 图像显示与优化技巧
图像显示能让界面更加生动。ST7735支持显示RGB565格式的图像,但需要先将图片转换为合适的格式。
我常用的图像处理流程:
- 用Python的PIL库处理图片
- 调整图片大小为屏幕分辨率
- 转换为RGB565格式
- 保存为二进制文件
from PIL import Image
def convert_image(input_path, output_path, size=(160, 80)):
img = Image.open(input_path)
img = img.resize(size)
img = img.convert('RGB')
with open(output_path, 'wb') as f:
for y in range(size[1]):
for x in range(size[0]):
r, g, b = img.getpixel((x, y))
color = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
f.write(color.to_bytes(2, 'big'))
显示图像时要注意内存使用。ESP32的内存有限,大图像需要分块读取和显示:
def show_image_optimized(path, x, y, width, height):
chunk_size = 512 # 每次读取512字节
with open(path, 'rb') as f:
for row in range(height):
# 计算当前行在文件中的位置
pos = row * width * 2
f.seek(pos)
# 读取一行数据
data = f.read(width * 2)
if not data:
break
# 设置显示窗口为当前行
tft._setwindowloc((x, y + row), (x + width - 1, y + row))
tft._write(data)
这种分块显示的方式大大降低了内存需求,即使显示大图像也不会导致内存不足。
7. 性能优化与内存管理
在ESP32上做数据可视化,性能优化很重要。我总结了几点实用经验:
首先是减少屏幕刷新区域。不需要每次都刷新整个屏幕,只刷新变化的部分:
def smart_refresh(prev_data, new_data):
"""智能刷新,只更新变化的部分"""
changed_indices = []
for i, (prev, new) in enumerate(zip(prev_data, new_data)):
if prev != new:
changed_indices.append(i)
# 只更新变化的数据点
for idx in changed_indices:
update_data_point(idx, new_data[idx])
其次是使用内存视图(memoryview)来减少内存拷贝:
def efficient_data_processing(data):
# 使用memoryview避免数据拷贝
mv = memoryview(data)
for i in range(0, len(mv), 2):
color = (mv[i] << 8) | mv[i+1]
# 处理颜色数据
另外,合理使用ESP32的硬件特性也能提升性能。比如使用硬件SPI而不是软件SPI:
# 使用硬件SPI
spi = SPI(1, baudrate=10000000, polarity=0, phase=0, sck=Pin(14), mosi=Pin(13))
# 而不是软件SPI
spi = SoftSPI(baudrate=10000000, polarity=0, phase=0, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
内存管理方面,要注意及时释放不再使用的资源。特别是在显示动态数据时,要避免内存泄漏:
class MemoryAwareDisplay:
def __init__(self):
self.buffers = []
def create_buffer(self, size):
# 创建缓冲区,但限制总数量
if len(self.buffers) > 5:
self.buffers.pop(0) # 移除最旧的缓冲区
buffer = bytearray(size)
self.buffers.append(buffer)
return buffer
这些优化技巧让我在ESP32上实现了流畅的动态数据可视化,即使同时显示多个数据源和图表也不会卡顿。
8. 实际应用案例分享
最后分享几个我实际做过的应用案例。第一个是环境监测站,显示温湿度、气压和空气质量数据:
class EnvironmentMonitor:
def __init__(self, tft):
self.tft = tft
self.sensors = {
'temperature': DHT11(Pin(15)),
'humidity': DHT11(Pin(15)),
'pressure': BMP280(I2C(0))
}
self.charts = {
'temp': DynamicChart(tft, 10, 20, 140, 40),
'humi': DynamicChart(tft, 10, 70, 140, 40)
}
def run(self):
while True:
temp = self.sensors['temperature'].read_temperature()
humi = self.sensors['humidity'].read_humidity()
self.charts['temp'].update(temp)
self.charts['humi'].update(humi)
# 显示当前数值
self.tft.fillrect((10, 120), (140, 20), 0)
self.tft.texts("T:{:.1f}C H:{:.1f}%".format(temp, humi),
(10, 120), 0xFFFF, 0, font16x16)
time.sleep(2)
第二个案例是网络状态显示器,实时显示WiFi信号强度和网络延迟:
class NetworkMonitor:
def __init__(self, tft):
self.tft = tft
self.wlan = network.WLAN(network.STA_IF)
def draw_network_info(self):
rssi = self.wlan.status('rssi')
quality = self._rssi_to_percentage(rssi)
# 绘制信号强度图标
self._draw_signal_icon(10, 10, quality)
# 显示IP地址
ip = self.wlan.ifconfig()[0]
self.tft.texts(ip, (40, 10), 0xFFFF, 0, font16x16)
def _draw_signal_icon(self, x, y, quality):
# 根据信号质量绘制不同数量的信号条
bars = max(1, min(4, quality // 25))
for i in range(4):
color = 0x07E0 if i < bars else 0x8410 # 绿色或灰色
height = (i + 1) * 3
self.tft.fillrect((x + i*4, y + 12 - height), (3, height), color)
这些案例展示了MicroPython+ESP32+ST7735组合的强大能力。无论是简单的数据展示还是复杂的交互界面,都能很好地实现。
在实际开发中,我还发现了一些小技巧:比如使用局部刷新来减少闪烁,合理布局元素来提高可读性,使用颜色编码来表示数据状态等。这些经验都是在多次项目中总结出来的,确实能提升用户体验。
最重要的是保持代码的可维护性和可扩展性。好的代码结构让后续的功能添加和bug修复都变得容易很多。我习惯把不同的功能模块分开,使用面向对象的方式组织代码,这样既清晰又便于重用。
更多推荐

所有评论(0)