发作品签到
专业版

【完整教程】语音控制墨水屏摆件【STM32 FreeRTOS】

工程标签

1.6w
0
0
104

简介

一个离线语音桌面助手,使用墨水屏幕显示,功能有: 1.记录重要事件(距离上一次写日记、阅读、泡菜坛子加水、花浇水已经多少天了) 2.记录重要日期(恋爱时常、多久没吃海底捞了) 3.显示时间、日期、图片

简介:一个离线语音桌面助手,使用墨水屏幕显示,功能有: 1.记录重要事件(距离上一次写日记、阅读、泡菜坛子加水、花浇水已经多少天了) 2.记录重要日期(恋爱时常、多久没吃海底捞了) 3.显示时间、日期、图片
星火计划2025

开源协议

CC BY-NC-SA 4.0

(未经作者授权,禁止转载)
创建时间:2024-11-14 13:29:25更新时间:2025-04-14 14:47:37

描述

xiaotian_Epaper

一款语音控制的墨水屏桌面助手

具备时间显示、倒计时事件显示、累积事件显示、语音控制功能

一款动动嘴就能交互的极简风桌面摆件!

组成: 墨水屏、stm32、vc02语音模块、实时时钟

使用软件: cubeide1.16.1、lceda专业版、solidworks2024、keyshot13

完整资料: 已上传github-Xiaotian_Epaper,gitee-Xiaotian_Epaper,oshwhub-电路设计,b站视频

更新日志:

  1. 2025.4.13:添加scene3红黑显示demo,为”时间时加一“添加边界限制;更新了3D结构文件,删除了需要内嵌螺母的我外壳版本;pcb完善了丝印;
  2. 2025.2.11:首次烧录时根据系统编译时间自动更新时钟芯片时间
  3. 2025.1.18:更新bom表中黑白红墨水屏器件链接,更新程序,添加二次开发教程
  4. 2025.1.5:添加pcb焊接建议,bom表中增加了我使用的下载器购买链接
  5. 2024.12.28:添加黑白红墨水屏供选择,版本v0.0.2
  6. 2024.12.13:首次上传,版本v0.0.1

IMG_20250413_093734.jpg

IMG_20250413_093706.jpg

微信图片_20250413101005.jpg

文件结构

序号文件名描述
1Hardware硬件设计
2Firmware固件文件
3Software软件程序
4CAD_Model模型文件
5Docs文档
6Tools附带工具
7img输出图片
8REDME.assetsREDME中的图片

控制指令

语音控制指令集

命令词回复语备注
请介绍你自己你好我叫小天 我是一个桌面助手
你可以通过语音和我进行交互
你可以说浇过花了 浇过水了 更改时间 更换图片
有什么需要尽管给我说
需唤醒
你好小天|小天小天随时待命唤醒词
写完日记了记录每一天免唤醒
看完书了日拱一卒功不唐捐免唤醒
加过水了下次早点浇免唤醒
坛子加过水了美味需要时间酝酿免唤醒
吃了海底捞发工资了吗又吃海底捞免唤醒
更改时间想要调整时间你可以说时间年加一或者时间年减一需唤醒
时间年加一需唤醒
时间年减一需唤醒
时间月加一需唤醒
时间月减一需唤醒
时间日加一需唤醒
时间日减一需唤醒
时间时加一需唤醒
时间时减一需唤醒
时间分加一需唤醒
时间分减一需唤醒
更换图片已经更换照片免唤醒

器件选型

墨水屏Epaper

分辨率296x128像素 、2.9寸、黑白

刷新方式:全屏刷新/局部刷新(4灰阶不支持局刷)

刷新时间:全刷3秒,局刷0.6秒

据说无故障刷新次数为100w次,我设置每分钟刷新一次,那这样算,能够用两年,想想也还有点短

image-20241114215437317.png

stm32f103cbt6

72M主频,基本外设丰富,特别的是128k flash,对于当前所选墨水屏分辨率来说

一张全屏照片大小=296x128/8=4.7k,即使考虑到用上freeRTOS也可以无压力存20来张图像,并且往往无需全屏图像,对于个人开发来说完全够用了

vc02语音模块

一款低成本纯离线语音识别模组,简单说就是写好对答语句,说特定语句执行特定功能,这里使用时,说出特定语句发送串口指令到主控进行任务控制。支持150条本地指令离线识别,支持RTOS轻量级系统,具有丰富的外围接口,以及简单友好的客制化工具,该项目放弃了按键交互,完全使用语音模块进行交互,想干嘛动动嘴就行,君子动口不动手!

DS1302

一款时钟芯片,用于离线时间记录,确保断电后设备仍然能保持时间

软件框架

使用cubeide经过初步封装后的freeRTOS,建立了四个任务init、show、run、read,分别执行初始化、显示、主要运行计算、读取任务

为了降低不同任务抢夺共享资源,用了两个无限时间等待的信号量确保run任务正常运行,用了两个事件组分别控制时钟芯片的数据读写和墨水屏的刷新显示

软件框架图.drawio-1734048330920-3.png

模块数据流

列出硬件组成中模块的电气连接及数据流向

硬件信号流.drawio-1734048262367-1.png

3D设计及安装

巧妙地外壳和屏幕壳体设计,使用Bambu的0.12mm打印刚好将屏幕壳体嵌入到外壳中,在底部使用两颗平头螺丝固定,表面外壳看不出什么安装的痕迹,对于审美的一点点强迫症,舒适了!

keyshot.8.jpg
安装需要准备9以下零件,选购链接已经在BOM表Xiaotian_Epaper\1.Hadware\BOM清单2024-12-14.xlsx中给出

2月12日.png

PCB焊接建议

电容电阻使用的0603封装,需要用到加热台烙铁,焊接不当需要拆除的话也需要用到热风枪,这里给一些手工焊接的建议,如果采用SMT焊接可以不用看

  1. 手工焊接建议开钢网,当然想纯白嫖也可以不开,我没开也焊接成功了,就是难度稍微高一点
  2. 首先用加热台焊接元器件最多的一面,但注意不要一开始就把引脚密集的typeC和stm32主控芯片贴上去,因为引脚过于密集,但凡锡膏多加一点就会短接,后续用烙铁不好分开,有的同学可能说用助焊剂容易分开,但我以前用的时候发现助焊剂加热时间久了会导电短路,有点恐怖。我实际发现比较好的方法是在加热台上等锡膏都融化了,用镊子把粘连的锡抚顺了,然后再放上typeC和stm32主控,成功率提高很多!
  3. 焊接好了可以先连接typeC测试串口,能检测到串口一般说明最难的主控和typeC焊接没什么问题,有问题的话先检查用的线是不是只能充电没有信号线,确定是焊接问题,就需要用热风枪吹热拆下来然后再焊接了

焊接成品如下,手工抹的锡膏,表面会有一些锡球不那么美观。

b16dd6a19d504836f2a4427307bea4e.jpg

二次开发教程

不同墨水屏选择

文件路径:"Xiaotian_Epaper\3.Software\Xiaotian_Epaper\Libraries\include\EPAPER.h"

//选择屏幕类型,共适配了两种类型的显示屏,bom表中有列出链接,可以根据自己选择来做选择
#define black_white_red	//使用黑白红墨水屏
//#define black_white			//使用黑白墨水屏

自己创建新的场景(以主图为例)

1.定义场景名称,路径:"Xiaotian_Epaper\3.Software\Xiaotian_Epaper\Libraries\include\myfreertos.h"

// 可以参考定义自己的场景
#define scene1						//显示场景1
//#define scene2						//显示场景2

2.在**init_show()**函数中初始化场景,路径"Xiaotian_Epaper\3.Software\Xiaotian_Epaper\Libraries\mytasks\task_show.c"

void init_show(){

	//****
	
#ifdef scene1
#ifdef black_white_red
	EPD_WhiteScreen_Black();
#else
	EPD_WhiteScreen_White();
#endif
	EPD_W21_Init();
	//white
	EPD_Dis_Part(0, 0, gImage1_white, 296, 128, NEG);
	//right
	EPD_Dis_string(186, 0, time, 32, NEG);
	EPD_Dis_string(200, 32, date, 16, NEG);
	EPD_Dis_Part(210, 48, gImage_love, 32, 32, POS);
	EPD_Dis_string(210 + 32, 56, itoa(date_diff(Time_start, Time_now),temp_itoa,10), 16, NEG);
	EPD_Dis_Part(210, 80, gImage_haidi, 32, 32, NEG);
	EPD_Dis_string(210 + 32, 88, itoa(date_diff(Time_haidilao, Time_now),temp_itoa,10), 16, NEG);
	//middle
	EPD_Dis_Part(85, 0, photo1, 110, 128, POS);
	//	left
	EPD_Dis_power(0, 0, (date_diff(event_power[0], Time_now)>4)?0:4-date_diff(event_power[0], Time_now));
	EPD_Dis_power(0, 32, (date_diff(event_power[1], Time_now)>4)?0:4-date_diff(event_power[1], Time_now));
	EPD_Dis_power(0, 64, (date_diff(event_power[2], Time_now)>4)?0:4-date_diff(event_power[2], Time_now));
	EPD_Dis_power(0, 96, (date_diff(event_power[3], Time_now)>4)?0:4-date_diff(event_power[3], Time_now));
	EPD_Dis_Part(60, 0, gImage_riji, 32, 32, NEG);
	EPD_Dis_Part(60, 32, gImage_dushu, 32, 32, POS);
	EPD_Dis_Part(60, 64, gImage_tanzi, 32, 32, POS);
	EPD_Dis_Part(60, 96, gImage_jiaoshui, 32, 32, POS);
#endif

	//****

}

3.在update_show()函数中定义更新逻辑,路径"Xiaotian_Epaper\3.Software\Xiaotian_Epaper\Libraries\mytasks\task_show.c"
图片数组在"Xiaotian_Epaper\3.Software\Xiaotian_Epaper\Libraries\EPD_W21\EPAPER.c"中定义,在"Xiaotian_Epaper\3.Software\Xiaotian_Epaper\Libraries\include\EPAPER.h"中使用extern命令进行外部声明,可参考现有图片数组更改

注:图片数组取模具体操作参考后文中工具使用部分

void update_show()
{
    
	//****
	
#ifdef scene1
	//white
	EPD_Dis_Part(0, 0, gImage1_white, 296, 128, NEG);
	//right
	EPD_Dis_string(186, 0, time, 32, NEG);
	EPD_Dis_string(200, 32, date, 16, NEG);
	EPD_Dis_Part(210, 48, gImage_love, 32, 32, POS);
	EPD_Dis_string(210 + 32, 56, itoa(date_diff(Time_start, Time_now),temp_itoa,10), 16, NEG);
	EPD_Dis_Part(210, 80, gImage_haidi, 32, 32, NEG);
	EPD_Dis_string(210 + 32, 88, itoa(date_diff(Time_haidilao, Time_now),temp_itoa,10), 16, NEG);
	//middle
	EPD_Dis_Part(85, 0, photo1, 110, 128, POS);
	//	left
	EPD_Dis_power(0, 0, (date_diff(event_power[0], Time_now)>4)?0:4-date_diff(event_power[0], Time_now));
	EPD_Dis_power(0, 32, (date_diff(event_power[1], Time_now)>4)?0:4-date_diff(event_power[1], Time_now));
	EPD_Dis_power(0, 64, (date_diff(event_power[2], Time_now)>4)?0:4-date_diff(event_power[2], Time_now));
	EPD_Dis_power(0, 96, (date_diff(event_power[3], Time_now)>4)?0:4-date_diff(event_power[3], Time_now));
	EPD_Dis_Part(60, 0, gImage_riji, 32, 32, NEG);
	EPD_Dis_Part(60, 32, gImage_dushu, 32, 32, POS);
	EPD_Dis_Part(60, 64, gImage_tanzi, 32, 32, POS);
	EPD_Dis_Part(60, 96, gImage_jiaoshui, 32, 32, POS);
#endif

	//****

}

3.语音控制指令自定义
vc02的语音控制可以在安信可平台进行编辑并生成固件,每条控制指令对应有一个串口发送动作,编辑功能还需要再stm32源码中对应修改

image-20250413142411475.png

image-20250413142601448.png

工具使用

使用取模软件制作图片数组

首先了解2.9寸墨水屏显示尺寸为296*128,x方向可按1像素平移,而y方向只能按8像素平移(例如设置y=0,实际y=0;设置y=1~7,实际y=7)。以封面为例,主图大小为100x128px,所有小图标为32x32px,二次开发可参考该大小进行布局

显示布局图.png

工具路径:xiaotian_Epaper\6.Tools\CharacterMatrix 3.0.2.3.exe
由于厂家给的墨水屏demo是按照x方向排列的,现在需要以y方向排列,因此需要按照以下步骤对图片进行镜像和旋转

图片取模.png
得到字模二进制数据后拷贝到文件对应位置即可

图片取模2.png

vc02烧录工具教程参考

工具路径:xiaotian_Epaper\6.Tools\hummingbird-m-production-tool\UniOneDownloadTool.exe

固件路径:xiaotian_Epaper\2.Firmware\uni_app_release.bin

字模压缩

游程编码压缩字模大小

考虑到嵌入式设备的flash容量有限,对保存的ASCII字模进行一定压缩处理,大大缩减了字模数组所占存储

取模软件取到的字模如下,在进行存储时,黑色区域存储为0x00,那么字模中就有大片的0x00存在,那么你一定能想到一种简单的思路对字模进行压缩。

如“0000000000”=”10个0“,这个思路叫做游程编码,其核心思想是通过减少重复数据的存储来实现压缩。在数据序列中,连续出现的相同元素组成的一段被称为一个“游程”。编码时,每个游程被替换为一个表示游程长度和游程值的二元组。例如,在字符串“AAAABBBCCDAA”中,“AAAA”是一个游程,长度为4,可以表示为(4, 'A')。

image-20241213121832314.png

1.num-char压缩方式

“AAAABBBCCDAA” =》 (4, 'A')(3, 'B')(2, 'C')(1, 'D')(2, 'A')

这种方式的压缩率大概是81.89%

image-20241123112642943.png

2.只压缩0x00的方式

“00000AAAABBBCCDAA” =》 (5, 0x00)"AAAABBBCCDAA"

这种方式压缩率达到51.19%不过对应的编码和解码就麻烦一点,这里自己写了一个xiaotian_Epaper\6.Tools\ed_codem.m文件进行处理,项目也采用该方式进行字模压缩

image-20241124135917699.png

字模制作过程:

先在字模工具中制作完成->生成.c文件用vscode打开->正则表达式进行替换,删除不必要的注释内容,替换成可输入到matlab中的形式->拷贝到ed_codem.m文件中进行运算->拷贝到c程序中

字模数据处理所用正则表达式:

/\*.*?\*/			搜索所有/*  */括起来的内容 
,(?![^,]*,)			选中所有行最后一个逗号
\n{2}				选中连续的两个换行符

最后

难度不是很高的一个项目,想锻炼一下贴片焊接、学习stm32设计开发、freeRTOS的同学都可以来试试,我会持续完善更新,期待大家交作业!
觉得不错的话,求个点赞+收藏+关注!

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
暂无数据
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1)
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航