基于ESP32制作一个桌面情侣摆件

前言

之前女友生日的时候,打算做一个情侣摆件送给她作为生日礼物,发现 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 IOTFT说明
GNDGNDGND
3V33V33V3
18SCL时钟线
23SDA数据线
4RST复位线
2DC数据命令选择线
5CS片选
19BLK背光

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,0b100000b00010000 对计算机来说并没有区别。

将取模的数组放入 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 是通过 topicmsg 在不同客户端发布服务的通信协议。

可以用 EMQX 提供的 MQTT 服务在本地搭建服务器

我在一台具有公网 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 打印,打印出的效果还不错:

组装焊接

最后就是组装焊机过程,以后如果还要做类似的东西还是画电路板吧,这个飞线飞的我想死,盒子差点放不下。

结语

其实这个情侣摆件已经完成好久了,只不过近期才有时间把文章整理一下,目前依然正常。因为对嵌入式开发一窍不通的情况下几乎是从头开始制作(包括代码、素材的绘制、外壳的建模等),前前后后花了两个多月的时间才做好。

但探索的过程也算有趣,是一次不错的体验。

“您的支持是我持续分享的动力”

微信收款码
微信
支付宝收款码
支付宝

目录