esp8266_TFTST7735语音识别UI界面虚拟小助手

发布于:2024-12-21 ⋅ 阅读:(8) ⋅ 点赞:(0)

一 实现思路

摘要:esp8266wifi,HTTP通信,TFT_espI库使用,语音交互系统,从0开始自定义UI界面,防低帧不爆刷

1 项目简介

功能与实现思路

1.1 项目效果

集成了ESP8266开发板,语音识别模块,TFT显示屏和小喇叭,实现了一个语音交互系统,拥有图标菜单和4个交互界面。完全使用语音口令操作,效率很高。有以下基础功能:

(1) 计划管理
这是最主要的功能,可以在TFT屏幕上查看每天、每周和每月的计划安排,随时随地的查看下一步要做的事情,不再需要翻阅复杂的计划表。其中存入了周,月,日的具体计划,esp8266会联网获取时间,到时间了自动更新下一步计划内容,且仅在发生变化时更新屏幕,不会爆刷形成低帧率。.看一眼便知道当前时间段应该做什么事

(2) 提醒功能
目前设置了五个固定时间点提醒喝水,通过蜂鸣器的振动提示。任何固定时间需要提醒的事件,都可以加在里面,形成一个提醒清单。

(3) 打卡功能
这一功能是为了帮助自己,每天高效做一件事,每天坚持一个最重要的习惯,可以设置打卡内容。

(4) 特殊功能
这里暂时显示一些重要的事件,比如生日纪念日或节气,或从网络获取一些信息,预留后续开发

本项目最大的特点是,设计了一个UI框架,把一个TFT屏幕划分为4个界面去利用。不论是直接使用它的计划功能,还是用于修改,都很方便。之所以不用手机app,是因为容易分散注意力,嵌入式产品的一大有点就是功能性强,不掺杂娱乐事项。

1.2 实现方式

(1) esp8266中
编写主程序即界面框架和功能实现,4个UI界面内容划分在不同的模块中编写,主函数中仅设计界面交互方初始化必备信息,并且把wifi与HTTP功能也单独划分,主函数逻辑清晰,所有修改接在各自模块中进行。并连接蜂鸣器,提醒事件时会触发蜂鸣器发声。

(2) 天问block中
在这里,我们主要使用其控制引脚电平的功能,听到说某个 UI界面的名称,通过喇叭说出回复语并控制对应引脚电平发生变化。

这是一个集成好的模块,使用简便,并且官方软件是图形化积木编程(可以随时查看c代码),不过积木封装的不太行,容易遇到各种稀奇古怪的bug。该语音模块主要使用方式为:听到固定语音指令后,使用预设好的声音,说出预设好的回复词。或是听到指令,控制引脚电平发生改变。该模块本身也可以接入其它简单模块,诸如oled,tft,温湿度,数码管超声波等等,但bug仍比较多,debug还得翻看积木的c代码,只建议简单使用。最大优点仍然在其快捷的语音交互上

(3) 交互原理
天问asr_pro模块检测到说出了指令——>asr_pro引脚电平变化(GPIO output)——>连接esp82266与asr_pro引脚——>esp8266检测到引脚电平变化(GPIO Iutput)——>切换UI界面/执行其它功能

本质是通过引脚电平信号变化串联二者功能,但对电平稳定要求较高,否则容易波动。(之所以只做4个UI界面,是因为esp8266引脚不不够了,连接TFT就占据了5个个。也可以考虑使用RXTX进行通讯)


2 项目构成

软硬件环境,代码逻辑结构,与事项项目中,会遇到的所有问题与解决方案

2.1 软硬件环境

(1) 软件环境
上传程序:arduino,天问block,
图片转c数组:lcd-image-converter(底部项目文件链接含此软件)
字体编辑:processing

(2) 所需硬件
esp8266(cp2102)+电机扩展板
天问语音识别模块:ASR_PRO
TFTst7735_RGB128*160——8引脚
小喇叭,蜂鸣器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 完整流程总结(重点整合)

从0开始搭建项目中,遇到的所有问题

(1) 功能逻辑图

在这里插入图片描述


(2) 接线
TFT esp8266
SCL d5
SDA d7
RST d4
DC d3
CS d8
BLK 背光,可不接
V 3.3v即可
G G
esp8266 天问语音模块
D0 A5
D6 A6
D1 A2
D2 A3
Tx 蜂鸣器IO
天问语音模块 接线
5v5 5v (3.3v容易带不动,因为有喇叭)
G G

(3) 使用esp8266控制TFT屏

一般我们从淘宝买的屏幕模块,会自带stm32的测试程序,或是arduino UNO板的测试样例和库,但这二者,前者空间较小存不了几个图,后者没有wifi功能,一般也不会送esp8266或者32的库和案例。

所以如何使用esp8266控制tft就是个问题,解决这个问题我们可以使用一个arduino中可以直接下载的库:TFT_espI,只需要预先修改User_Setup.h中的一些参数,根据自己屏幕的 驱动如st7735,型号,分辨率等等参数,取消掉一些注释即可使用。


(4)TFT_espI库配置方法

设置好参数和引脚,才可以驱动tft屏,打开库文件中的User_Setup.h,需要修改以下内容:

必要设置

  • step1:44-65行,找到自己tft的驱动型号,取消那一行的注释
    如我的: #define ST7735_DRIVER

  • step2:76-77行,根据自己的屏幕型号,选择RGB 或 BGR,取消其中一行的注释

  • step3: 85-89行,根据屏幕的宽和高型号,取消注释对应代码
    如128*160:#define TFT_WIDTH 128 #define TFT_HEIGHT 160

  • step4:在112行以下,找到自己开发板的型号与引脚分布,设置SPI与引脚的连接,
    ( esp8266 :167行以下,esp32: 209行以下)

白屏问题:如TFTST7735,128*160,仍需要在102-111行间,找到对应分辨率的一行取消注释(因为不是同一批制造的)
其余型号,如有问题需自己查找驱动部分代码注释查看解决
如碰到屏幕反色,将116或117其中一行取消注释

其余设置

  • 310-321 为库自带字体(一般不需改动)
  • 359-372 为SPI频率和触摸屏相关参数

有问题,多看英文注释翻译解释

注意esp8266在arduino中的引脚,以GPIO数字编号为,不是DX

这里是引用


(5) TFT_esp库常用代码详解

1 基本功能

TFT_eSPI tft = TFT_eSPI(); //初始化tft对象
tft.init();
tft.setRotation(0);    //屏幕旋转0123: 0 90 180 270
tft.fillScreen(TFT_BLACK);//清屏
tft.setSwapBytes(true);//不加容易颜色异常
tft.pushImage(0,0,128,160,test2);//背景图
tft.loadFont(HGY316); //加载自定义中文字体,设置字体大小对加载的字体无效

2 字体设置

tft.setCursor(0,0);   //改字体显示位置
tft.setCursor(0,0,a);  //第三参数选择自带字体样式:a=124678
tft.setTextSize(1);  //设置文本显示的大小,,对中文字体无效
tft.setTextFont(1);   //选择库自带字体:1,2, 4, 6, 7, 8
tft.setTextColor(TFT_GREEN, TFT_BLACK);//字体颜色,字体背景色
tft.println("周一");  //输出中文或字符串,draw没有自动换行

tft.drawChar('#', 100, 64, 2);  //输出字符(字符,x,y,大小)字符或ascii码都可以
tft.drawNumber(num, 0, 100, 4);  //输出数字
tft.drawString("zifucahun", 0, 80, 2);//上传字符串(坐标,大小)

3 常见字体颜色代码:

#define TFT_BLACK       0x0000      /*   0,   0,   0 */
#define TFT_NAVY        0x000F      /*   0,   0, 128 */
#define TFT_DARKGREEN   0x03E0      /*   0, 128,   0 */
#define TFT_DARKCYAN    0x03EF      /*   0, 128, 128 */
#define TFT_MAROON      0x7800      /* 128,   0,   0 */
#define TFT_PURPLE      0x780F      /* 128,   0, 128 */
#define TFT_OLIVE       0x7BE0      /* 128, 128,   0 */
#define TFT_LIGHTGREY   0xD69A      /* 211, 211, 211 */
#define TFT_DARKGREY    0x7BEF      /* 128, 128, 128 */
#define TFT_BLUE        0x001F      /*   0,   0, 255 */
#define TFT_GREEN       0x07E0      /*   0, 255,   0 */
#define TFT_CYAN        0x07FF      /*   0, 255, 255 */
#define TFT_RED         0xF800      /* 255,   0,   0 */
#define TFT_MAGENTA     0xF81F      /* 255,   0, 255 */
#define TFT_YELLOW      0xFFE0      /* 255, 255,   0 */
#define TFT_WHITE       0xFFFF      /* 255, 255, 255 */
#define TFT_ORANGE      0xFDA0      /* 255, 180,   0 */
#define TFT_GREENYELLOW 0xB7E0      /* 180, 255,   0 */
#define TFT_PINK        0xFE19      /* 255, 192, 203 */    
#define TFT_BROWN       0x9A60      /* 150,  75,   0 */
#define TFT_GOLD        0xFEA0      /* 255, 215,   0 */
#define TFT_SILVER      0xC618      /* 192, 192, 192 */
#define TFT_SKYBLUE     0x867D      /* 135, 206, 235 */
#define TFT_VIOLET      0x915C      /* 180,  46, 226 */
(6)TFT屏显示图片

1 想要显示图片,需要将图片转化为c数组,然后存入头文件中调用显示

显示图片函数为:
tft.pushImage(0,0,128,160,test2);//x1,y1-->x2,y2

2 值得一提,tft屏幕的坐标系与旋转问题:

在这里插入图片描述

3 把图片转化为数组步骤(使用lcd-image-converter):

1 准备好分辨率图,可以使用window画图软件在这里插入图片描述
2 导入图片
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3 设置参数
在这里插入图片描述
在这里插入图片描述
4 保存数组
在这里插入图片描述
头文件格式:

#pragma once
#include<pgmspace.h>

const uint8_t name[] PROGMEM ={  //name可自己设置
	//放生成的代码
}

4 本项目中计划内容的坐标分配:

在这里插入图片描述


(7) TFT屏显示汉字
  • 1 c/user/windows/fonts/想用的字体.ttf(或网上下)
    移动到TFT_espI库/tools/Creat_Smooth_Font/Creat_Font/data目录下

  • 2 下载processing软件,打开TFT_eSPI\Tools\Create_Smooth_Font\的
    Create_font.pde 进行编辑:
    -130行改为自己的ttf字体文件名
    -132行选中后缀ttf解注释
    -140行设置字体大小(使用中文库:在arduino中不可修改,只有在里可修改)

  • 3 准备好项目中使用的所有汉字,使用网页在线转换:汉字转Unicode编码
    然后可以在记事本中,把所有的\u替换为 ,\0x ,删除开头的逗号

  • 4 在Create_font.pde
    -330行下,把上一步的汉字Unicode编码,粘贴到specificUnicodes数组中去

  • 5 点击precessing 运行按钮,弹窗会显示所有生成的汉字和其余字符

  • 6 成功后,回到TFT_eSPI\Tools\Create_Smooth_Font\Create_font\FontFiles目录,
    把生成对的 字体名.h 头文件,粘贴到项目文件夹中,然后引用头文件就可以显示中文。

  tft.print("内容") 函数输出即可

该步可直接搜b站教程:如何使用TFT_espI库在tft屏上显示汉字
注:只有生成好的汉字,才能够在TFT屏上显示,否则乱码


(8) wif联网与HTTP获取时间

事先注意:上传代码前,一定查看清楚自己的模块到底是什么型号
若是CP2102,则对应NodeMCU板载ESP-12E ( 4MB Flash) WIFI模组
在arduino中一旦选择错误,wif功能基本报废,且波特率也对不上
cp2102:应设115200,若设为9600烧录代码会很慢

1 给出cp2102的arduino工具参数图:

在这里插入图片描述

2 wifi连接基本操作
led灯显示是否连接成功,闪烁表示正在连接,长亮表示连接成功

#include <ESP8266WiFi.h>
const char *ssid = "....";
const char *pass = "....";

int led=14; //wifi连接指示灯D5连接led
void setup() {
  
  Serial.begin(115200);
  WiFi.mode(WIFI_STA); //使用STA模式用8266去连接wifi
  WiFi.begin(ssid, pass);
  
  //等待wifi连接结果
  while (WiFi.status()!=WL_CONNECTED) {
     bool is=digitalRead(led);//巧妙反转指示灯
     digitalWrite(led, !is);
     Serial.println("....");
     delay(500);
  }digitalWrite(led, 1);//成功常亮

  Serial.println("WiFi 已连接");
  Serial.println("IP地址为:");
  Serial.println(WiFi.localIP());

}

3 HTTP获取网络时间
使用网址为:http://quan.suning.com/getSysTime.do
显示格式为:{"sysTime2":"2024-12-20 07:59:14","sysTime1":"20241220075914"}

#include <ESP8266HTTPClient.h>
HTTPClient http;
String GetURL="http://quan.suning.com/getSysTime.do"; //获取链接
String res;//回应
void setup() {
  //需提前连接wifi
  
  Serial.begin(115200)
  //http连接
  http.setTimeout(5000);//预启动连接,不加也行
  http.begin(GetURL);//网址
}
void loop() {
  //连接
  int httpCode = http.GET();//一个数值,表示连接情况
  if(httpCode>0){
    Serial.printf("[HTTP] GET... code: %d\n", httpCode);
      if (httpCode == HTTP_CODE_OK){   //是否是一个成功的请求
        //读取响应内容
        res = http.getString();//得到固定内容
        Serial.println(res);
        //可使用substring(a,b)--:显示s[a]~s[b-1]的字符串内容,截取时间
        delay(300);
      }
  }else{
       Serial.println("HTTP Get ERROR");
  }  http.end();//关闭连接
}

4 因为没有周几的情况,故本项目使用zeller公式,根据年月日计算出周几

在这里插入图片描述

注意:

实测定时器内置定时器Tricker库法:
-若开机只获取一次时间,然后采用tricker定时器库,每几秒联网更新一次时间
这样容易反复重启,可能tricker要求调用函数精简,而网络获取不稳定,需要1-2s
或是其它的冲突问题,开发板容易无限重启,最后仍采用在loop中重复获网络时间的方案


3 代码实现

代码模块功能介绍与注意事项

3.1 不同UI界面

项目共有5个界面,可想而知人如果全把代码写在主程序,修改非常不遍,故根据功能将不同的界面写入不同的.c和.h文件,由主函数统一调用,主函数只负责调用写的方法,每个UI的美化与内容设计,都在各自模块中中进行,见下头文件方法:

(1) 计划内容wifi_HTTP 头文件:

/*
1 计划界面设计:月,日,周 计划设计函数
2 功能接口函数,便于修改
3 wifi+http获取网络时间
*/
//日期结构体
typedef struct {
  int year;
  int mon;//月
  int day;//日
  int wk;//周
  int h;//小时 0-24
  int m;//分 0-60
  int s;//秒0-60
}DateTypedef;

//tft屏上显示月,日,周 计划信息
void Get_monthPlan(DateTypedef x);//显示月初事项
void Get_weekPlan(DateTypedef x);//显示周信息 
void Get_dayPlan(DateTypedef x);//显示日计划
void Get_bottomtime();//显示底部时间数据

//网络功能
void WiFiHTTP_init();
void Update_http();//并更新时间数值

(2) 提醒 与 UI界面头文件:

/*
1 功能:U1-4界面设计
2 提醒事件设计,如:喝水,日期提醒
*/

//弹出提醒函数,可扩展类似功能
void Remind_water(DateTypedef x);//每日定时喝水,弹出几s

//仅查看UI界面函数
void Remind_menu();//UI1
void Remind_warn();//UI2
void Remind_habit(DateTypedef x); //UI3
void Remind_special(DateTypedef x);//UI4

3.2 UI切换方法

功能逻辑为,天问语音模块检测到口令后给对应引脚一个高电平(正常为低),所以在loop中检测对应引脚变化即可。使用UI表示每个界面的编号,0-4,0为计划界面在swith的default分支,1234分别对应:菜单,提醒,打卡,特殊。

值得注意的是:Loop中不要放刷新屏幕数,否则会爆刷,屏幕帧率会极端低,效果很差。而这里采用了标记法,记录上一次屏幕UI编号本次编号,只有两次编号不同的时候,说明进行了界面切换,此时才刷新屏幕,然后及时更新两个标记就可以吗,大大减少刷新次数,增加稳定性。


void loop() {
  Get_freshUI();//更新UI编号,显示不同界面
  switch (UI){
    case 1:     //U1菜单
      Remind_menu();
      UI=0;
      break;
    case 2:     //U2提醒
      Remind_warn();
      UI=0;
      break;
    case 3:    //U3打卡
      Remind_habit(wifi_date);
      UI=0;
      break;
    case 4:   //U4特殊
      Remind_special(wifi_date);
      UI=0;
      break;
    default:  //计划界面
      Get_dayPlan(wifi_date);
      Get_weekPlan(wifi_date); 
      Get_monthPlan(wifi_date);
      Get_bottomtime();//多久更新一次
      UI=0;//默认为计划界面
  }
}
//获取当前口令下的UI编号
int tem=0;//标记上一次UI界面的编号牌
void Get_freshUI(){
  //不同的口令,在语音模块设置对应引脚电平为高
  //esp8266读取到高电平信号,更新界面UI值,显示不同界面
  if(digitalRead(u1_pin)==HIGH)
    UI=1;
  if(digitalRead(u2_pin)==HIGH)
    UI=2;
  if(digitalRead(u3_pin)==HIGH)
    UI=3;
  if(digitalRead(u4_pin)==HIGH)
    UI=4;
  if(tem!=UI){
     tft.fillScreen(TFT_BLACK);//清屏
     tem=UI;  //仅在切换不同界面时更新一次,防低帧爆闪
  }
}

3.3 LooP函数中的内容与优先级

(1) 网络更新需要放在这里,获取网络时间应立即对本地结构体时间尽心修改
(2) 像闹钟,纪念日,喝水,这种固定时间的时间,需要弹出立刻提醒的,应放在界面切换之上,时间到立刻弹出一段时间,然后自动退出即可。
(3) 更新界面编号的函数,应随时检测引脚电平变化确定口令的有效性

void loop() {
  Update_http();//从网上获取与更新本地时间
  Get_freshUI();//更新UI编号,显示不同界面
  digitalWrite(u5_pin,HIGH);//我的蜂鸣器低电平触发
  Remind_water(wifi_date);//提醒喝水,所有弹出提醒事件,都是高优先级,不在swith中

  switch (UI){
    case 1:     //U1菜单
      Remind_menu();
      UI=0;
      break;
    case 2:     //U2提醒
      Remind_warn();
      UI=0;
      break;
    case 3:    //U3打卡
      Remind_habit(wifi_date);
      UI=0;
      break;
    case 4:   //U4特殊
      Remind_special(wifi_date);
      UI=0;
      break;
    default:  //计划界面
      Get_dayPlan(wifi_date);
      Get_weekPlan(wifi_date); 
      Get_monthPlan(wifi_date);
      Get_bottomtime();//多久更新一次
      UI=0;//默认为计划界面
  }

  //测试:快速模拟 时分秒,查看计划
  // wifi_date.m++;
  // if(wifi_date.s==60){ wifi_date.s=0; wifi_date.m++;}
  // if(wifi_date.m==60){ wifi_date.m=0; wifi_date.h++;}
  // if(wifi_date.h==24){ wifi_date.h=0; wifi_date.wk++; wifi_date.day++;}
  // if(wifi_date.wk==8) { wifi_date.wk=1; }
}

3.4 随时间滚动的计划事项

(1) 首先我们的时间结构体,存取的是整数值
而非简单的截取显示一段字符串,所以对于网络获取的数据,应对字符串进行准换,保留为整型数据

String s;
s[i]-'0' 可得到数字字符代表的整数值
然后通过十进制计算出具体值保留即可(乘以若干10

(2)涉及时间区间,需要判断一个时间是否在某个区间内,用反向判断更简单:

typedef struct {
  int year;
  int mon;//月
  int day;//日
  int wk;//周
  int h;//小时 0-24
  int m;//分 0-60
  int s;//秒0-60
}DateTypedef;
//判断当前时间是否在[m1:n1]~[m2:n2]之间,含区间端点
int Set_section(DateTypedef x,int m1,int n1,int m2,int n2){
	if(x.h<m1||x.h>m2) return 0; //小时超界 
	else if(x.h==m1&&x.m<n1)  return 0; //不到区间左端点 
	else if(x.h==m2&&x.m>n2)  return 0;  //超过区间右端点
	else{
		return 1; 
	} 
}

(3)最后一个问题
每一步计划的更新,应该是在某一秒到达计划时间后,更新一次,而不是反复的刷新屏幕,那样会降低帧率,效果极差,同上,我们依旧使用标记法,这次是对所有的计划事件编号,两个标记,本次时间与上次事件(万能的flag)

如不刷新屏幕,会有字体重叠显现出现

/*
周计划显示
*/
int int wk_s=1,wk_n=0; //st上一次计划编号,now本次计划编号
void Get_weekPlan(DateTypedef x){
  tft.setTextColor(TFT_GREEN);
	switch (x.wk){
		case 1:
      wk_n=1;
      locadt(0,60,"周一"); //封装的显示函数,坐标与内容一起设置,简化代码
      locadt(0,80,"今日内务");
      locadt(0,100,"耳鼻,清灰,耳机");
		    break;
		case 2:
     wk_n=2;
      locadt(0,60,"周二");
      locadt(0,80,"今日内务");
      locadt(0,100,"洗澡,大扫除");
		    break;
		case 3:
		default:
      locadt(0,100,"ERROR!");
	}
	if(wk_s!=wk_n){ //同理,防爆刷
    tft.fillRect(0, 60, 128, 120, TFT_BLACK);//部分刷新屏幕
    wk_s=wk_n;
  }
}

3.5 天问代码

仅需要注意:语音标识ID不会自懂更新
在这里插入图片描述


重点内容便是上述部分,还有数不清对的小功能,便不再详细描述,项目文件链接中包含全部的程序,开源供大家学习交流。
项目难度不大,代码和结构半小时就编好了,但各种模块的坑,真的是一个接一个,在此综合的整合一下所有问题,希望对需要的人有所帮助,节省时间。硬件学习并不是件很难的事,只不过麻烦而已,请不要丧失你的信心,加油各位


二 展示

1 图片

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


2 视频

✨太一·庚辰✨赛博少女助手,esp8266+TFT+语音识别


3 下载

3.1 项目下载

项目完整文件

3.2 arduino esp8266开发板离线包

esp8266+32开发板包

3.3 processin下载

processing官网

3.4 天问BLOCK下载

天问官网