前言※
之前女友生日的时候,打算做一个情侣摆件送给她作为生日礼物,发现 B 站挺多人实现过类似的摆件而且不少大佬也开源了,但完全符合我需求的好像没有。作为生日礼物怎么能用别人的东西来复刻呢?于是查找资料后决定从头开始自己做一遍。
最终成品图:
项目已开源至 GitHub: https://github.com/dvai/ESP-32-Couple-ornaments
准备※
计划实现的功能※
作为情侣摆件,我期望完成以下功能:
- 显示天气
- 番茄钟
- 显示日期时间
- 恋爱倒数日
- 拉屎提醒
- 定时早上好
- 发送小心心
材料※
材料 | 数量 |
---|---|
ESP-32(CP2102) | 2 |
1.44 寸彩屏(ST 7735S) | 2 |
micro-usb 线 | 2 |
UV 胶水 | 1 |
分光棱镜(25.4mm*25.4mm) | 2 |
热熔枪、电烙铁等工具 | 若干 |
杜邦线 | 若干 |
部署环境※
因为对 python 比较熟悉,所以选择使用 MicroPython 作为开发语言,IDE 自然就使用了 Thonny
,这款 IDE 可直接在该软件中实现给 ESP32 单片机刷 MicroPython 固件,以及实时预览 ESP32 的文件系统,比较方便开发。
安装 Thonny 后,因为 ESP32 中没有 MicroPython 的运行环境,所以需要向 ESP32 刷入 MicroPython 固件,固件可以在官网下载:MicroPython。详细安装教程可以参考这篇文章: MicroPython ESP32 环境搭建 | 极客侠 GeeksMan 。
安装驱动※
ESP32 与电脑连接的也需要相应的驱动,否则 ESP32 插在电脑上会没有反应,需要到对应驱动芯片的官网下载驱动,我购买的是 CP2102,可以去这里下载:CP210x USB to UART Bridge VCP Drivers - Silicon Labs。
如果是 CH340 芯片则可以在 半导小芯 中查询芯片型号【ch340】,找到该芯片的制造商江苏沁恒股份有限公司的官网,找到对应的驱动程序安装即可:CH340IR.EXE 。
驱动屏幕※
1.44 寸是由 ST 7735s 驱动,首先在驱动代码中定义好针脚,我的定义如下:
ESP32 IO | TFT | 说明 |
---|---|---|
GND | GND | GND |
3V3 | 3V3 | 3V3 |
18 | SCL | 时钟线 |
23 | SDA | 数据线 |
4 | RST | 复位线 |
2 | DC | 数据命令选择线 |
5 | CS | 片选 |
19 | BLK | 背光 |
在 main.py
中同样定义一下针脚连接:
spi = SPI(2, baudrate=40000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23), miso=None)
tft=TFT(spi,2,4,5)
tft.initr()
tft.rgb(False)
tft.fill(TFT.BLACK)
准备工作到此结束,理论上来说现在已经可以开始写代码了。
实施※
具体的业务代码不过多赘述,下面介绍一下遇到的几个难点。
屏幕显示中文※
如果想要在屏幕上显示中文,有两种方法:
1. 使用中文字体库,想要使用中文字体库需要烧录支持中文字体库的固件,但是字库文件较大;
2. 使用取模软件,对用到的字体进行取模,然后在代码中按一定逻辑读取;
但我的项目中需要的中文字符其实很少,所以用取模软件将需要的字符保存即可。使用的软件是 PCtoLCD2002
,相应的设置如下:
这里我们要输出 16*16 点阵上的字符,而英文字符只占中文字符一半的空间,这是因为英文字母,符号,数字这些通用字符都是半角(一字符占用一个标准的字符位置),汉字是全角(一个字符占用两个标准字符位置)。因此,我们显示的汉字也是分开显示的。
行列式的显示逻辑是从第一行开始向右取 8 个点作为一个字节,然后从第二行开始向右取 8 个点作为第二个字节……依此类推。生成的这些十六进制数,每一个都表示这一行 8 个点的逻辑状态,比如 0x10
,转换成二进制就是 0b10000
,如果不足 8 位,我们就在他的前面补 0,0b10000
与 0b00010000
对计算机来说并没有区别。
将取模的数组放入 myFont.py
最终形成的字库文件类似这样:
full_angle_16 = {"Width": 16, "Height": 16, "Data": {
"叶": [0x00, 0xFC, 0x04, 0x04, 0xFC, 0x00, 0x40, 0x40, 0x40, 0x40, 0xFF, 0x40, 0x40, 0x40, 0x40, 0x00,
0x00, 0x0F, 0x04, 0x04, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,], # 叶 0
"老": [
0x20, 0x20, 0x24, 0x24, 0x24, 0x24, 0xBF, 0x64, 0x24, 0x34, 0x28, 0x24, 0x22, 0x20, 0x20, 0x00,
0x10, 0x08, 0x04, 0x02, 0x3F, 0x45, 0x44, 0x44, 0x42, 0x42, 0x42, 0x41, 0x78, 0x00, 0x00, 0x00,], # 老 1
"师": [
0x00, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x02, 0xE2, 0x22, 0x22, 0xFE, 0x22, 0x22, 0xE2, 0x02, 0x00,
0x00, 0x87, 0x40, 0x30, 0x0F, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xFF, 0x08, 0x10, 0x0F, 0x00, 0x00,], # 师 2
"东": [
0x00, 0x08, 0x88, 0x48, 0x28, 0x18, 0x0F, 0xE8, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
0x00, 0x20, 0x11, 0x09, 0x05, 0x41, 0x81, 0x7F, 0x01, 0x01, 0x05, 0x09, 0x11, 0x20, 0x00, 0x00,], # 东 3
# ……
}
}
写一个方法对这个字库文件调用:
# 显示字符(中英文皆可),取模方式为列行式
def display_text(tft,pos,text,color,font):
for ch in range(len(text)):
fontw = font["Width"]
fonth = font["Height"]
countw = ceil(fontw/8)# 表示有几组行
counth = ceil(fonth/8) # 表示有几组列
charData = bytearray(font["Data"][text[ch]])
buf = bytearray(2 *fonth * fontw)
parts=[]
for numh in range(1,counth+1):
parts.append(charData[(8*countw)*(numh-1):(8*countw) * numh])
for q in range(fontw):
c = parts[numh-1][q]
for numw in range(1,countw+1):
for r in range((8*numw)*(numh-1),(8*numw) * numh):
if c & 0x01:
coordinate = 2*(r * fontw + q)
buf[coordinate] = color >> 8
buf[coordinate + 1] = color & 0xFF
c >>= 1
tft.image(pos[0]+fontw*ch+1, pos[1], (pos[0]+fontw*ch+1) + fontw - 1, pos[1] + fonth - 1, buf)
在需要的地方进行调用即可,例如 display_text(tft,(24,15),"早上好",tft.WHITE,font)
,屏幕上会从坐标(24,15)开始显示"早上好"字符。
两个单片机之间通信※
单片机之间的通信主要依靠 MQTT 协议。MQTT 是通过 topic
、 msg
在不同客户端发布服务的通信协议。
可以用 EMQX 提供的 MQTT 服务在本地搭建服务器
- 服务器: 下载 EMQX
- windows 客户端:MQTTX:跨平台 MQTT 5.0 桌面客户端工具
我在一台具有公网 IP 的服务器上部署了 EMQX,在 esp32 中即可使用 MQTT.py 库,通过服务器与另一个单片机进行通信。写代码的时候可以使用 MQTTX 客户端测试。
示例代码:
wifi_connect(ssid, password)
def sub_cb(topic, msg): # 回调函数,收到服务器消息后会调用这个函数
if topic != None:
display_img(tft,(0,0),(64,40),"/imgs/bg_1.dat")
# 1. 联网
# 2. 创建 mqt
c = MQTTClient("umqtt_client", "127.0.0.1") # 建立一个 MQTT 客户端
c.set_callback(sub_cb) # 设置回调函数
c.connect() # 建立连接
c.subscribe(b"tv") # 监控 tv 这个主题
c.publish(b"tv",b"你好") # 发布消息
while True:
c.check_msg()
time.sleep(1)
打印外壳※
首先确定单片机和屏幕的尺寸,我粗略测量了下尺寸:
屏幕:
外:4.5 * 2.95
内:3.6 * 2.9
高:0.4
显示区域:29*29
单片机:
长:5.5
宽:2.7
高:0.6
USB 接口: 3*8
棱镜:26*26
然后使用 Sketch up 绘制外壳,将模型文件导出后,可以在嘉立创下单助手里进行 3D 打印,打印出的效果还不错:
组装焊接※
最后就是组装焊机过程,以后如果还要做类似的东西还是画电路板吧,这个飞线飞的我想死,盒子差点放不下。
结语※
其实这个情侣摆件已经完成好久了,只不过近期才有时间把文章整理一下,目前依然正常。因为对嵌入式开发一窍不通的情况下几乎是从头开始制作(包括代码、素材的绘制、外壳的建模等),前前后后花了两个多月的时间才做好。
但探索的过程也算有趣,是一次不错的体验。