原文发表于 CSDN(作者:hjz0810),发布日期 2026-04-12(首次发布 2026-03-29)。本文为转载备份,已征得作者同意。
1.设计原理
以Esp32单片机作为核心控制单元,通过外设驱动技术实现与WiFi通信模块、TFT彩屏显示模块、扬声器提示模块的联动。用户通过触摸屏或物理按键发起菜品选择、份数调整等操作,Esp32单片机实时采集这些操作指令,经数据格式化处理后,通过WiFi模块的无线通信机制基于TCP/IP协议将数据传输至手机APP;同时,单片机根据操作结果触发扬声器模块,按照预设音效逻辑发出选择成功、下单确认等语音提示,形成“操作-采集-处理-反馈”的硬件闭环。
采用WiFi模块建立Esp32单片机与手机APP之间的TCP长连接,确保数据传输的稳定性与实时性。TCP协议的面向连接特性可实现数据校验与重传机制,避免菜品信息、订单数据在传输过程中丢失或出错;同时支持离线缓存模式,当网络中断时,单片机将用户操作数据暂存于内置Flash,待网络恢复后自动同步至手机APP,保障点餐流程不中断。
2.系统框架
本系统采用“硬件终端层-通信传输层-软件应用层”三层架构设计,各层独立工作且协同联动,确保系统稳定性与扩展性,具体框架如下:
(1)硬件终端层核心控制与数据采集
作为用户直接操作载体与数据处理核心,包含以下模块:
核心控制模块:以Esp32单片机为核心,负责接收用户操作指令、驱动外设模块、处理数据格式、控制无线通信传输,是硬件终端的“中枢大脑”。
输入输出模块:
输入子模块:触摸屏+物理按键,支持菜品选择、份数增减、下单确认等操作输入,适配不同用户操作习惯;
输出子模块:TFT彩屏显示模块+语音播报,展示菜品分类、详细信息、操作指引、扬声器提示模块输出语音反馈音效。
无线通信模块:WiFi模块支持协议,负责与手机APP建立TCP长连接,实现数据双向传输。
数据存储模块:内置Flash存储单元,用于缓存离线数据、备份关键信息,支持断电数据保护。
(2)通信传输层数据交互桥梁
连接方式:硬件终端与手机APP的持续通信,支持数据实时同步与状态反馈。
数据格式:采用标准化格式封装数据,包含菜品ID、名称、单价、份数、订单状态、操作指令等字段,便于双方解析与处理。
传输逻辑:硬件终端→APP;APP→硬件终端;支持数据校验,确保传输数据准确性。
- 软件应用层
以手机APP为载体,实现数据处理、功能交互与信息展示,包含以下模块:
数据解析与核算模块:解析硬件终端传输的菜品数据,自动计算单个菜品费用与总消费金额,生成结构化账单。
菜品管理模块:提供图形化配置界面,支持商家对菜品信息进行“增、删、改、查”操作,数据实时同步至硬件终端。
订单管理模块:展示订单详情,支持订单提交确认、状态更新,并反馈至硬件终端。
离线缓存模块:同步硬件终端的离线数据,网络恢复后自动完成数据同步与订单提交,保障流程连续性。
3.硬件要求
基于系统功能与稳定性需求,硬件选型与设计需满足以下具体要求:
核心控制模块Esp32单片机:
型号要求:选用Esp32,具备丰富接口、串口;
性能要求:主频≥72MHz,Flash容量≥128KB,RAM容量≥20KB,满足多模块联动数据处理与存储需求;
驱动要求:支持外设模块化驱动开发,可实现对WiFi模块、彩屏、扬声器的精准控制。
无线通信模块:
通信参数:支持802.11b/g/n协议,工作频段2.4GHz,通信距离≥10米;
接口要求:支持串口与Esp32单片机通信,波特率可配置;
功能要求:支持长连接建立与维持,具备休眠唤醒功能,适配低功耗设计;支持网络重连机制,网络中断后自动尝试重连。
显示与交互模块:
TFT彩屏:分辨率≥320×240,显示色彩≥65K色,具备背光调节功能,适配餐厅不同光照环境;
物理按键:采用轻触式按键,至少包含“确认”“取消”“增减份数”“菜品分类切换”功能键,按键寿命≥10万次,响应灵敏无卡顿。
提示模块扬声器:
音效要求:输出音量≥60dB,支持3种预设音效;
驱动要求:通过Esp32单片机PWM输出控制,支持音效触发逻辑自定义。
硬件连接与抗干扰要求:
接口连接:Esp32与各模块采用标准化接口连接,接线简洁,接触可靠;
抗干扰设计:电源电路添加滤波电容,减少电压波动干扰;PCB布线采用地线隔离、关键信号线屏蔽设计,避免电磁干扰影响数据传输;
供电要求:支持5V直流供电,电源纹波≤50mV,确保各模块稳定工作。
数据存储要求:内置Flash存储容量≥512KB,支持关键数据自动备份,断电后数据保留时间≥1年。
4.软件要求
外设驱动开发:
实现TFT彩屏驱动:支持菜品分类页面、菜品详情页面、操作指引页面的显示与切换,支持触摸坐标识别与按键信号采集;
实现WiFi模块驱动:支持TCP长连接的建立、数据发送与接收,支持网络状态检测与离线缓存触发;
实现扬声器驱动:根据操作指令触发对应音效输出,支持音量调节。
数据处理功能:
支持用户操作指令解析,将指令转换为标准化数据格式;
支持离线数据管理:网络中断时将数据写入Flash,网络恢复后自动读取并同步;
支持低功耗控制:无操作时触发Esp32进入低功耗模式,WiFi模块进入休眠状态,有操作时唤醒。
稳定性要求:程序运行无死循环,模块驱动响应时间≤50ms,数据传输错误率≤0.1%。
一 、屏幕部分
由于0.96寸屏幕过于狭小,无法放下超过四个菜品名称,价格和总价,因此不再采用通用的0.96小屏幕,而是改为更大的ST7735S,有着两倍于原来的1.8寸大屏幕,和128乘160的全色RGB彩色像素,分别率和屏幕大小的提高将带来更高的视觉观感
但是,换了不常用的屏幕,意味着弃用了原有的好用,便捷的官方库,将面临各种莫名其妙的未知问题,首先,就是屏幕乱码问题,以往,在波特律动LED字模生成器中取模后,直接复制粘贴即可显示中文汉字,
而这次,中文汉字却变成了乱码
原因是这块屏幕是横向扫描,用以往的列行式就出现了乱码,采取逐行式取模
列行式
const uint8_t zh16x16[][36] = {
/* 0 小 */ {0xe5,0xb0,0x8f,0x00,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x20,0x40,0x80,0x00,0x00,0x08,0x04,0x03,0x00,0x00,0x40,0x80,0x7f,0x00,0x00,0x00,0x00,0x00,0x01,0x0e,0x00,},
/* 1 龙 */ {0xe9,0xbe,0x99,0x00,0x10,0x10,0x10,0x10,0x10,0xff,0x10,0x10,0xf0,0x10,0x11,0x16,0xd0,0x10,0x10,0x00,0x80,0x40,0x20,0x18,0x06,0x41,0x20,0x10,0x3f,0x44,0x42,0x41,0x40,0x40,0x78,0x00,},
/* 2 虾 */ {0xe8,0x99,0xbe,0x00,0x00,0xf8,0x08,0xff,0x08,0xf8,0x02,0x02,0x02,0xfe,0x02,0x42,0x82,0x02,0x02,0x00,0x20,0x63,0x21,0x1f,0x11,0x19,0x30,0x00,0x00,0xff,0x00,0x00,0x00,0x03,0x00,0x00,}
};
cpp逐行式
const uint8_t zh16x16[][36] = {
/* 0 小 */ {0xe5,0xb0,0x8f,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x88,0x08,0x88,0x10,0x88,0x20,0x84,0x20,0x84,0x40,0x82,0x40,0x81,0x40,0x80,0x00,0x80,0x00,0xa0,0x00,0x40,0x00,},
/* 1 龙 */ {0xe9,0xbe,0x99,0x00,0x20,0x04,0x20,0x08,0x20,0x08,0x20,0x00,0xff,0x7f,0x20,0x01,0x20,0x11,0x20,0x11,0x20,0x09,0x10,0x05,0x10,0x03,0x08,0x41,0x88,0x41,0x44,0x41,0x22,0x7e,0x01,0x00,},
/* 2 虾 */ {0xe8,0x99,0xbe,0x00,0x08,0x00,0xc8,0x7f,0x08,0x02,0x3e,0x02,0x2a,0x02,0x2a,0x02,0x2a,0x0a,0x2a,0x12,0x3e,0x22,0x0a,0x22,0x08,0x02,0x28,0x02,0x78,0x02,0x47,0x02,0x02,0x02,0x00,0x02,}
};
cpp这样,文字将不再乱码,但是,这个屏怪就怪在,采取了列行式后,一个字像是被从中间劈开一样,部首和剩余部分完全是反着的,比如小龙虾的“虾”字,变成了“下虫”样式,于是,有需要一个有效的函数算法,将字的偏旁部首对调实现一个字正确的显示
对调算法
// ====================================== 对调算法 ======================================
// 参数说明:
// x - 汉字左上角的X坐标
// y - 汉字左上角的Y坐标
// d - 指向16x16汉字字模数组的指针(每个汉字占32字节,2字节/行 × 16行)
// color - 汉字的显示颜色,默认使用全局文本颜色COLOR_TEXT
void drawCN(int x, int y, const uint8_t *d, uint16_t color = COLOR_TEXT) {
// 定义字模指针,指向传入的字模数据起始地址
const uint8_t *p = d;
// 遍历汉字的16行(16x16点阵,共16行像素)
for (int row = 0; row < 16; row++) {
// 核心:对调每行的左右两个字节
// 原字模每行顺序:[右半部分字节][左半部分字节]
// 修复后顺序:[左半部分字节][右半部分字节]
uint8_t byteLeft = p[row * 2 + 1]; // 取出原字模中该行的右字节,作为新的左半部分
uint8_t byteRight = p[row * 2]; // 取出原字模中该行的左字节,作为新的右半部分
// 绘制当前行的左半部分(8个像素)
for (int col = 0; col < 8; col++) {
// 从高位(0x80=10000000)到低位逐位检查字节的每一位
// 如果该位为1,就在对应坐标绘制一个像素点
if (byteLeft & (0x80 >> col)) {
tft.drawPixel(x + col, y + row, color);
}
}
// 绘制当前行的右半部分(8个像素)
for (int col = 0; col < 8; col++) {
// 右半部分的X坐标从x+8开始,接在左半部分后面
if (byteRight & (0x80 >> col)) {
tft.drawPixel(x + 8 + col, y + row, color);
}
}
}
}
cpp然而,再完成对调后,问题又双叒叕出现了,字终于是个字了,但是,居然是镜像的,举个例子,肉夹馍三个字,由于夹是中心对称的一个字,所以镜像后看起来没问题,而肉和摸两边不对称,看起来就像你在镜子面前摆一个字一样的效果,归根到底,还是字模出现的问题,需要再次在对调函数存在的情况下修改字模,当然,这一步交给ai可以直接实现,但为了不每次都问ai,所以有了一个外置算法,只要把在网页波特律动LED字模生成器生成的代码替换下面算法中的代码就能实现反镜像
反镜像外置算法
def reverse_byte(b):
"""将一个字节(0x00~0xFF)的位顺序反转"""
return int(f'{b:08b}'[::-1], 2)
def fix_mirror_array(hex_str):
"""
输入类似 "{0x80,0x00,0x80,0x00,...}" 的字符串
输出修正后的相同格式字符串
"""
# 提取所有十六进制数字
import re
nums = re.findall(r'0x[0-9a-fA-F]{2}', hex_str)
if not nums:
return hex_str # 没找到数据,原样返回
# 转换为整数列表
data = [int(x, 16) for x in nums]
# 每两个字节为一组(16位字模的一行)
# 1. 对每个字节独立位反转
reversed_bytes = [reverse_byte(b) for b in data]
# 2. 交换每行中的两个字节(原来高位在前,现在低位在前)
fixed = []
for i in range(0, len(reversed_bytes), 2):
if i+1 < len(reversed_bytes):
fixed.append(reversed_bytes[i+1])
fixed.append(reversed_bytes[i])
else:
fixed.append(reversed_bytes[i]) # 奇数个字节时保留最后一个
# 重新格式化为 C 数组字符串
hex_str_out = '{' + ','.join(f'0x{b:02x}' for b in fixed) + '}'
return hex_str_out
def process_text(text):
"""处理包含多个数组的文本,只替换 {...} 内的内容"""
import re
def replacer(match):
original = match.group(0)
return fix_mirror_array(original)
# 匹配所有 {...} 块
result = re.sub(r'\{[^}]*\}', replacer, text)
return result
# ========== 在这里粘贴你的镜像字模 ==========
input_data = """
{
/* 0 小 */ {0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x88,0x08,0x88,0x10,0x88,0x20,0x84,0x20,0x84,0x40,0x82,0x40,0x81,0x40,0x80,0x00,0x80,0x00,0xa0,0x00,0x40,0x00,},
/* 1 龙 */ {0x20,0x04,0x20,0x08,0x20,0x08,0x20,0x00,0xff,0x7f,0x20,0x01,0x20,0x11,0x20,0x11,0x20,0x09,0x10,0x05,0x10,0x03,0x08,0x41,0x88,0x41,0x44,0x41,0x22,0x7e,0x01,0x00,},
/* 2 虾 */ {0x08,0x00,0xc8,0x7f,0x08,0x02,0x3e,0x02,0x2a,0x02,0x2a,0x02,0x2a,0x0a,0x2a,0x12,0x3e,0x22,0x0a,0x22,0x08,0x02,0x28,0x02,0x78,0x02,0x47,0x02,0x02,0x02,0x00,0x02,}
}
"""
# ==========================================
if __name__ == '__main__':
result = process_text(input_data)
print(result)
python但考虑到一些人电脑没有python环境,这里再提供一版C语言代码
反镜像内置算法(C语言版本)
#include <stdint.h>
// 反转一个字节的位顺序(查表法,效率高)
static uint8_t reverse_byte(uint8_t b) {
static const uint8_t tab[16] = {
0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE,
0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF
};
return (tab[b & 0x0F] << 4) | tab[b >> 4];
}
/**
* 修正镜像字模(16位宽,高字节在前)
* @param font 字模数据指针(每个字模32字节,16行×2字节)
* @param num_chars 字模个数
*/
void fix_mirror_font(uint8_t *font, int num_chars) {
for (int c = 0; c < num_chars; c++) {
uint8_t *p = font + c * 32; // 当前字模起始地址
for (int row = 0; row < 16; row++) {
int idx = row * 2;
uint8_t high = p[idx]; // 原始高位
uint8_t low = p[idx + 1]; // 原始低位
// 位反转
high = reverse_byte(high);
low = reverse_byte(low);
// 交换并写回
p[idx] = low;
p[idx+1] = high;
}
}
}
cpp此代码优点在于可以放进代码中随程序运行自动调整,调用的时候只需要写
// 镜像字模数据(以“小龙虾”为例)
uint8_t mirror_font[] = {
/* 小 */ 0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x88,0x08,0x88,0x10,0x88,0x20,0x84,0x20,0x84,0x40,0x82,0x40,0x81,0x40,0x80,0x00,0x80,0x00,0xa0,0x00,0x40,0x00,
/* 龙 */ 0x20,0x04,0x20,0x08,0x20,0x08,0x20,0x00,0xff,0x7f,0x20,0x01,0x20,0x11,0x20,0x11,0x20,0x09,0x10,0x05,0x10,0x03,0x08,0x41,0x88,0x41,0x44,0x41,0x22,0x7e,0x01,0x00,
/* 虾 */ 0x08,0x00,0xc8,0x7f,0x08,0x02,0x3e,0x02,0x2a,0x02,0x2a,0x02,0x2a,0x0a,0x2a,0x12,0x3e,0x22,0x0a,0x22,0x08,0x02,0x28,0x02,0x78,0x02,0x47,0x02,0x02,0x02,0x00,0x02
};
int main() {
// 转换3个字模
fix_mirror_font(mirror_font, 3);
// 现在 mirror_font 中存储的就是正常顺序的字模了
// 可以继续使用或打印验证
return 0;
}
cpp至此,lcd屏经历了从
小屏显示不下,
大屏完全乱码,
文字完全错乱
文字镜像
到最终完成体
好的,屏幕项目到此完成,这里附上屏幕具体接线图
二、语音部分
语音部分比较简单,使用天问block软件开发和ASRPRO开发板,整个界面都是图形化配置
对应代码
#include "asr.h"
extern "C"{ void * __dso_handle = 0 ;}
#include "setup.h"
#include "HardwareSerial.h"
uint32_t snid;
void ASR_CODE();
//{speak:小蝶-清新女声,vol:4,speed:10,platform:haohaodada}
//{playid:10001,voice:欢迎光临,你可以手机扫码查看菜单或直接向我点菜}
//{playid:10002,voice:休眠模式}
/*描述该功能...
*/
void ASR_CODE(){
//本函数是语音识别成功钩子程序
//运行时间越短越好,
---
> ⚠️ **说明**: 本文在 CSDN 原文处被截断(原文末尾为"运行时间越短越好,"未完结)。此处保留已抓取到的内容,如需完整 ASRPRO 语音模块代码及后续章节,请参考 CSDN 原文。
DISCUSSION
评论
还没有评论。你可以留下第一条有价值的讨论。