
无线语音演示器
简介
教室里的投影仪和智慧黑板,虽然把电脑的屏幕放大了,但是使用的时候却还要跑到电脑旁边,那么你有没有想过把电脑的鼠标和键盘像激光翻页笔一样放在手中?一起来看看吧!
简介:教室里的投影仪和智慧黑板,虽然把电脑的屏幕放大了,但是使用的时候却还要跑到电脑旁边,那么你有没有想过把电脑的鼠标和键盘像激光翻页笔一样放在手中?一起来看看吧!开源协议
:GPL 3.0
描述
一、作品简介
教室里的投影仪和智慧黑板,虽然把电脑的屏幕放大了,但是使用的时候却还要跑到电脑旁边,那么你有没有想过把电脑的鼠标和键盘像激光翻页笔一样放在手中?一起来看看吧!
本作品通过整合空中鼠标功能以及语音输入实现将鼠标和键盘的功能融合为一体,有点类似于激光翻页笔的升级。
二、作品功能
1.空中鼠标,即通过发送端的MPU6050将人手部的动作转换为屏幕上光标的动作;
动图展示的是空中鼠标功能以及使用旋转编码器模拟鼠标滚轮
2.按键输入,发送端通过识别发送端按键的动作并将其发送给接收端,接收端将其编码为对应的鼠标或键盘上的USB的HID值发送到电脑端实现按键功能
动图展示的是将按键设置为截屏键,配合空中鼠标功能实现图片内容标记
3.语音输入,通过按键输入并利用电脑端输入法的快捷键功能实现语音输入功能开启,此时发送端再利用无线音频传输(OPUS编解码)将数据发送至接收端,进而上传至电脑端用于语音输入的识别。关闭时再由发送端使用按键输入功能将电脑端的语音输入功能关闭即可。
动图展示的是语音输入,使用的是windows自带的语音识别(开启方式:windows徽标键+H)
4.屏幕显示以及按键键值自定义,通过LVGL搭配外部FLASH实现按键对应键盘的键值可以自定义并且支持掉电不丢失,此外也支持电量显示、充电提示、充电完成提示、低电量提醒。
动图展示的是电量显示以及键值自定义操作
5.电源管理,使用TP4056实现锂电池充电以及为充电提示、充电完成提示提供依据;使用电源切换电路实现USB电源与电池电源自动切换;采用电池电量芯片获得电池的相对充电状态为电量显示与低电量提醒提供依据。
动图展示的是充电状态(屏幕显示+LED 1S 间隔闪烁(充满电变常亮))
三、硬件设计(V1.1版本)
由于本作品的无线传输使用2.4G模块实现,所以在硬件上分为发送端(即手持端)与接收端(即插在电脑USB端口上)。
发送端
上图为发送端的硬件框图
1.电源自动切换电路参考:史上最详解!外置USB供电与内置锂电池供电自动切换电路 - 知乎
2.充电提示信号是指将TP4056的充电指示引脚引出到单片机端,从而让单片机能够检测到充电指示信号,
本作品中是采用的电源切换电路的输出进行的上拉,但是由于这个输出会随着USB的插拔由拨动所以建议使用单片机进行3.3V上拉,但是在实际测试中本作品中的此设计并未受到电压波动的影响。
3.线性稳压器选择TPS7A2033PDBVR是因为其压降低且噪声小,压降低可以确保充分利用3.7V锂电池的电量,噪声小也可以尽量减少电源对2.4G射频模块的影响,
本作品中线性稳压器的输入电源是电源切换电路的输出,其在USB的插拔过程中会产生变化(5V->4.2V/4.2V->5V),
在实际测试中电源后级的MCU等并不会因此而产生死机等问题。
4.旋转编码器按键、三向拨轮按键、常规按键在连接MCU时注意确保这些引脚若同时开启外部中断不会冲突,因为MCU加入低功耗模式后需要使用外部中断唤醒MCU
5.MCU采集旋转编码器的数据时需要开启定时器,若MCU处于睡眠模式,定时器的中断可以将其唤醒,
但是本作品为了尽可能的降低MCU功耗所以采用的停机模式,此时只能使用外部中断唤醒,单纯依靠定时器中断无法唤醒MCU
所以为了解决此问题,本作品采用的方法是将调试接口的PA9与编码器的CHA/B引脚使用飞线连接(因为后来才意识到这个问题所以未在电路图画出)
这样的话若拨动编码器由于CHA/B引脚会有电平变化,再将PA9设置为外部中断模式捕获这些变化
从而实现拨动编码器也可以唤醒停机状态下的MCU了。
6.其中F412核心板采用的是自制的F4核心板(支持STM32F401RCT6、STM32F412RET6),发送端采用的是STM32F412RET6,其中包括一个0.96寸的OLED.
7.其余电路均按照芯片手册的典型应用电路绘制。
发送端实物示意图
发送端装配爆炸图
接收端
上图为接收端的硬件框图
1.接收端正常工作时插在电脑的USB接口上,所以接收端直接从电脑的USB端口取电。
2.其中F401核心板采用的是自制的F4核心板(支持STM32F401RCT6、STM32F412RET6),接收端采用的是STM32F01RCT6
接收端装配爆炸图
四、软件设计
发送端交互方案
1.旋转编码器按键与旋转编码器
屏幕开启显示时,作为LVGL中的交互设备
屏幕关闭显示时,作为一个普通按键以及鼠标滚轮
2.充电指示灯常亮的两种情况
(1)电量过低,进入低电量模式,直到处于充电状态
(2)充电完成,单击辅助按键会看到显示充电完成字样
3.充电指示灯以大约1HZ的频率闪烁的两种情况
(1)正在充电,单击辅助按键会看到正在充电的显示
(2)程序进入硬件错误中断,如果是使用的源代码,则此情况非软件问题,一般是发送端的BTB连接器的连接有问题导致的。因为代码中的LVGL使用的图片以及字体等存放在外部FLASH,使用QPSI的内存映射访问,所以如果BTB连接器的连接有问题会导致,开启内存映射后因为访问无效内存地址进入硬件错误中断。
以下是发送端的具体操作说明
操作类型 |
开启动作 |
关闭动作 |
功能效果 |
普通按键操作 |
单击 |
无 |
按键的键值对应键盘上某个/多个按键被单击或LVGL界面中的确认键被单击(仅编码器按键支持) |
特殊按键操作 |
设置按键单击 |
无 |
进入显示模式,显示电量百分比以及充电状态5S |
特殊按键操作 |
设置按键双击 |
无 |
进入设置模式,可以通过交互改变普通按键对应的键值 |
特殊按键操作 |
交互按键单击 |
无 |
鼠标左键单击 |
特殊按键操作 |
交互按键长按 |
抬起交互按键 |
开启空中鼠标功能(适合仅移动屏幕光标的场景) |
特殊按键操作 |
交互按键单击+长按 |
抬起交互按键 |
鼠标左键长按并开启空中鼠标功能(适合拖拽、标记等场景) |
特殊按键操作 |
交互按键双击 |
无 |
鼠标左键双击 |
特殊按键操作 |
语音按键单击 |
无 |
鼠标右键单击 |
特殊按键操作 |
语音按键长按 |
抬起语音按键 |
发送端采集的音频转换为文字输入在电脑上的输入框中 |
特殊按键操作 |
语音按键双击 |
无 |
鼠标中键单击 |
旋转编码器操作 |
顺时针/逆时针拨动编码器 |
停止拨动编码器 |
相当于鼠标滚轮顺时针/逆时针滚动或LVGL界面中的更换焦点选项 |
充电反馈 |
插入USB充电器 |
拔出USB充电器 |
显示进入充电状态的画面5S,充电指示灯以1HZ频率闪烁 |
发送端软件设计
软件采用hal库进行开发,由于软件中涉及到的空中飞鼠功能以及无线音频传输,计算量大且对实时性要求较高,并且整体任务不多所以采用裸机编程的形式
为了让软件各部分功能分配更加明确,运行更加有序。
故将软件分为工作模式、设置模式、显示模式、休眠模式共四种模式。
以工作模式为主进行模式切换,若要进入设置模式和显示模式则应等待工作模式完成所有任务进入空闲状态
模式切换框图
工作模式运行框图
1.其中麦克风与姿态角转鼠标指针偏移量功能(即空中飞鼠功能)的开启与关闭由按键处理进行管理
2.此时编码器被用作模拟鼠标滚轮
3.按键的扫描使用的是周期为20ms的定时器中断,识别按键动作是在中断中进行的,但是对于动作的响应是在按键处理部分进行处理的(即中断外),
这样的好处就是不用担心耗费太多中断时间以及不用担心函数的重入问题,但是需要注意必须保证即使在最坏的情况下也应该让按键动作对应的响应在下次动作前得到处理,否则先前的按键动作的响应将被忽略。
3.鼠标与键盘数据采用2.4G发送队列(FIFO队列)进行发送是因为这两种数据最注重准确性与完整性,实时性得不到保证最多也只是让用户感觉反应有些慢,但是若是不准确或者少发了一个数据则会极大的影响用户体验。
并且使用发送队列可以让需要发送的数据进行组包发送从而降低总体发送时间,并且当数据总量多时由于一次仅发一帧数据耗时大约1ms左右,所以对于此模式的整体运行不会产生重大影响
4.音频编码数据并没有加入2.4G发送队列而是直接使用2.4G进行发送,是因为音频流数据最注重实时性,如果实时性得不到保证,即使发送的数据全都是对的也无济于事。
5.音频编码的数据包对应的时间长度为20ms,在工作模式的运行期间音频编码耗时15ms左右,发送一帧音频编码数据耗时1ms左右,即使加上另外两个较为耗时的任务:MPU6050数据处理的时间(大约1ms)、2.4G发送任务(1ms,因为最多发送一帧数据)。
总体时间长度也在20ms以内,所以语音输入与传输即使与空中飞鼠功能同时开启也不会有实时性的问题。
6.当开启空中飞鼠功能时,MPU6050的数据输出速率为100HZ,即10ms每次,根据上述结论使用空中飞鼠功能时若同时开启语音输入则空中飞鼠的功能可能会有一些影响,但在实际测试中发现并没有太大的影响。
空中鼠标运行框图
语音输入运行框图
设置模式运行框图
1.此时编码器以及编码器按键均用于LVGL输入,编码器数值读取依赖于按键扫描定时器。
2.由于LVGL使用的字体等数据放置在外部闪存通过QSPI的内存映射模式读取,所以将用户自定义数据存储到外部FLASH时要先关闭内存映射再写入然后再打开内存映射。
显示模式运行框图
1.读取电量计测量的电池电量,通过TP4056的充电指示引脚获取当前充电状态。
2.期间产生的编码器数值变化全部忽略,仅检测退出动作相关。
休眠模式运行框图
此模式的源码中保留了三种模式
0.不使用低功耗模式,仅检测系统是否有中断产生从而退出休眠模式
1.使用MCU的睡眠模式,检测到中断就退出休眠模式
2.使用MCU的停机模式,检测到外部中断就退出休眠模式
其中源码中默认使用模式2,并且硬件上需要自行焊接一个飞线
若不想飞线则可以使用模式1,因为编码器使用了定时器的输入捕获中断可以唤醒睡眠模式
若是需要调试可以切换到模式0,避免低功耗模式下无法调试的问题(在MCU的手册上看到好像支持低功耗模式下的调试,所以可以自行添加开启低功耗模式下的调试语句)
以上是四种模式的具体运行,下面是作者觉得需要详细描述的部分,与理解软件无太大关系,感兴趣可以看看。
1.软件中使用的OPUS编解码以及LVGL都需要动态内存分配,为了方便管理所以都是用标准库的malloc进行操作的,所以需要设置程序的堆空间足够大防止动态内存分配失败带来的问题。
因为OPUS编解码以及LVGL中可能会有一些函数对函数栈的需求较大,所以栈区也要适当调大。如果软件容易进入硬件错误中断则首先应排查堆与栈空间的分配是否合理。其次再考虑数组越界等问题。
之前再CSDN上发现的一个文章讲的是如何定位进入硬件错误中断的原因。链接:stm32进入HardFault的异常定位方法
2.OLED屏幕只在设置模式以及显示模式开启时使用(使用期间保持亮度设置为最低,此时屏幕开启会增加2mA左右功耗),其他时间保持关闭状态(对应的I2C外设也关闭)以期得到最低的功耗。
3.I2S麦克风数据的采集使用的是循环DMA,虽然保证了使用I2S读取数据期间可以不使用CPU,但是由于需要对音频数据进行OPUS编码所以音频数据的采集与编码不能在同一块内存区域进行,否则若编码期间发生了音频数据写入则编码的正确性无法得到保证,所以软件中采用了多个缓冲区从而将音频数据的采集与编码隔离开,大致原理如下。
4.如何将用户的自定义数据保存至外部闪存,
4.1使用另一个程序将数据先烧写到外部闪存,然后在下载本程序进行使用;但是总感觉有些繁琐
4.2在本程序中增加烧写步骤,复位后先将数据烧写至外部闪存,这种方法比上一种方式简洁,但是由于程序不知道先前是否烧写过,所以每次复位都会使用默认数据对先前数据进行覆盖
所以本程序中通过使用C语言中的__TIME__等语句将程序的编译日期与时间添加进自定义数据中,在程序中增加如下逻辑,
若检测到外部闪存中的编译日期与时间与MCU的程序中保存的一致(每次编译都会改变),则证明已经提前烧写过,所以直接读取即可。(对应第一次烧写后,复位之后的情况)
若不一致,则证明未烧写过,先烧写再读取即可。(对应第一次烧写的情况)
5.由于空中飞鼠功能需要MPU6050达到100hz以上的姿态角输出速率此时MPU6050与MCU的功耗都较大,MPU6050是因为陀螺仪与加速度计与DMP都开启,MCU是因为要将MPU6050输出的四元数数据转换为欧拉角涉及很多浮点运算而且运算频率相当高(100HZ),如果不采取措施MCU的发热甚至用手都能摸出来,
故在程序中采取关闭陀螺仪不使用的X轴、不开启空中飞鼠功能时将MPU6050的数据速出速率降低到1hz,并且MCU端完全屏蔽MPU6050的中断。从而降低MCU端以及MPU6050的功耗
6.对于如何进入显示模式以及设置模式程序中提供两种方式的支持(需要自己在程序中修改宏定义切换,"TX_V1_1\BSP\bsp_user_data.h"中的#define USER_DATA_WAKEUP_OLED_TAP_DOUBLE)
6.1使用辅助按键的单击进入显示模式,双击进入设置模式;使用MPU6050的感应点击功能(正面单击)触发自定义快捷键(默认为屏幕截图)。
6.2使用MPU6050的感应点击功能,正面单击进入显示模式,双击进入设置模式;使用辅助按键触发自定义快捷键(默认为屏幕截图)。
但是后来安装外壳后,发现感应点击功能识别准确度比较低,且MPU6050的中断输出不支持只输出感应点击数据,只能降低至1HZ左右的中断频率,所以这个功能相当于放弃了
7.空中飞鼠功能依赖于将发送端上的MPU6050测量的姿态角转换为对应的鼠标指针偏移量的操作,但是这个转换的系数又应该怎样得到?
从0开始慢慢尝试?
首先我们可以知道常用的屏幕像素分布为1920*1080,
其次人的腕关节的活动范围以握持遥控器对准屏幕为准横向大约为-70~90,纵向大约为-30~30(单位均为角度),参考链接:多图展示四肢七大关节的活动范围
各自取一个较小且好计算的值,故应为横向-40~40以及纵向-20~20,
根据以上分析可以得到两个系数
1920px/80°=24px/°
1080px/40°=27px/°
即对于横向移动,手腕移动一度,屏幕上的鼠标指针应移动24个像素。
对于纵向移动,手腕移动一度,屏幕上的鼠标指针应移动27个像素。
所以将横向或纵向的姿态角变化量*上面的系数即可得到屏幕上的鼠标指针应移动的像素数
我承认我有赌的成分,因为数据的计算并不严格,但是在实际测试中效果还可以,哈哈哈!
8.程序中使用了多个按键,并且要识别按键的长按、单击、单击+长按、双击等,所以为了简化程序的编写使用了一个开源的按键库
FlexibleButton 是一个基于 C 语言的小巧灵活的按键处理库。
其对按键数几乎不做限制,但是其将按下分为两种情况即短按与长按,然后是单击与双击,在本程序中略显繁琐,且本程序仅区分长按与单击或双击(因为短按和长按的区别不太好把握所以不识别),
为了改善这种情况,我将开源库的代码中的按键事件识别部分进行了修改,使得能够识别长按、长按、单击、单击+长按、双击....理论上对于连击次数无任何限制,并且支持任意连击+长按的动作。
这样就能更适合本程序的应用。
下图中,左侧为源码中的事件识别逻辑,右边是修改过后的。(绘图若有错误,请多见谅)
接收端软件设计
软件采用HAL库进行开发,主要任务就是将发送端发送过来的数据,解析为对应的鼠标和键盘数据以及音频数据,通过USB上传至电脑。运行框图如下
其中将HID数据上传至USB的动作,是先将数据写入上传队列,然后再从队列中读取数据上传至电脑,队列相关代码与发送端基本一致。
音频数据则是使用与发送端相近的缓冲区策略进行上传,即接收后直接写入缓冲区,USB需要读取音频数据时也直接从缓冲区读取。写入与读取相隔离。
无线数据包的设计
鼠标以及键盘相关的HID数据参照USB的HID数据格式进行发送
//数据包总长小于等于32字节
无线数据包格式:
数据包ID+(数据长度)+数据...+ 数据包ID+(数据长度)+数据 .....
一包数据可以有多个数据包,但是如果是音频数据则不再添加HID数据
鼠标//数据长度固定为4,所以不再发送数据包长度
数据包ID: 1 //0000 0001
自定义控制:
报告ID的前六位用来指示鼠标左键分别是单击/双击/长按,
例如:
数据包ID等于0000 0001 则若数据中出现鼠标左键按键按下/抬起,则代表长按/抬起,
数据包ID等于0100 0001 则若数据中出现鼠标左键按键按下,则代表单击,
数据包ID等于1000 0001 则若数据中出现鼠标左键按键按下,则代表双击,
键盘//数据长度固定为3,所以不再发送数据包长度//键盘数据均认为是对应按键单击//第二个字节为保留字节无意义所以不通过无线通信进行传输
数据包ID: 2// //0000 0010
编码音频流
数据包ID: 3// //0000 0011
数据长度: 不定长,但需要限制为可以单次传输完成
其中鼠标数据中的按键单击以及双击等,通过标志位的形式通知接收端此数据为单击等,此时接收端接收到消息会自动将其拓展为对应的HID数据
比如:发送端发送一个鼠标左键按下数据并指示此数据为双击,则接收端接收到数据后会自动将其拓展为按下+抬起+按下+抬起的数据从而达到实现双击的效果。
使用此设计既借鉴了HID的数据格式又通过数据包ID的前6位防止了冗余数据的无线传输,提高了无线传输的效率。
不足以及改进建议
1.编码器使用外部中断唤醒MCU的问题,可以在电路板上将飞线加入,避免外部飞线的各种问题(具体讨论再硬件设计处)
2.核心板+底板的设计是由于我有核心板所以那样画电路板,会更方便一些。如果将核心板与底板放在一块板子上会提高整体的可靠程度。
3.发送端在V1.1版本中是分为发送端主体与电源管理两部分,但在绘制完成后发现如果两部分合并的话不仅可以提高整体的可靠程度并且3D外壳绘制也会更加方便。(此时可以单独用一个小板子防止充电的Type-C接口)
对了最好加一个电池的开关,这样方便关闭电池的电源。
4.当前发送端的耗电量有些大,300mah的电池即使不使用也只能坚持一天半。经过分析后发现功耗主要来源是MPU6050,因为程序中为了保持空中飞鼠更快的启动速度,所以MPU6050需要一直工作,其工作时的电流约为3mA左右,假设使用300mah的电池,300/3=100h,即使在理想状态下电池输出电量为300mah,并且只让MPU6050消耗电量,也只能维持100小时。(记住这不是可以用这么长时间,而是把它放在那里这么长时间后自己就没电了)
所以在降低功耗方面重点应从MPU6050着手,采用LSM6DS3等现代化一点的唤醒测量单元,普遍可以将功耗降低至1ma以内。可以极大的提高续航时间。还有可以给外部传感器等增加一个电子开关,不使用时直接断电。
5.如果不手动将电池拔出则电池中的电池保护板会在2.5V左右断开电源,以避免过放,当然还要减去LDO的压降(140mv左右),所以可以得到给器件供电的最低电压为2.4-0.14=2.26V.
所以需要确定系统中的器件的工作电压范围,防止在低于其工作电压的范围下工作,需要注意的有以下几部分
1. 2.4G模块(安信可NF-03(si24r1))
2.MPU6050
3.麦克风(inmp441)
4.OLED(ssd1312)
其中OLED点亮屏幕需要充电泵调节器(要不然达不到屏幕的工作电压即VCC)所以oled电路中将VDD与VBAT均连接在3.3V电源上,所以实际限制为3.0V-3.5V
5.电量计芯片(MAX17048)
6.W25Q64
7.STM32F412RET6
由此可见只有部分芯片可以在接近电池过放的电压下工作,所以当检测电池电量过低的情况下应该暂停使用并使用指示灯指示电量过低,避免电压过低的情况下使用器件。
在充电后重新启动。
其他注意事项
BTB连接器注意事项
其中使用的F4核心板是我开源的另一个STM32F4核心板(BTB连接器)项目,需注意接收端使用的是合高为6.5mm的BTB连接器,而发送端为了降低厚度,选择了合高为3.0mm的BTB连接器。
购买链接:高精密镀金0.5MM间距双槽板对板btb连接器8-100P 对插高度3.0-8.5
规格如下图:
发送端电路注意事项
由于需要在停机模式下通过编码器唤醒MCU故需要将编码器与PA9飞线连接。详细原因请参考:硬件设计——》发送端——》5.
飞线示意图
发送端安装注意事项
电源开关(1pin的编码开关)是后来加的并未在电路板上添加(自己手焊上去的,见下图)
发送端的板子有一部分没有支撑,可能会导致按按键的时候板子容易变形,可以在没有支撑的部分用一些纸用作支撑
相关链接
哔哩哔哩视频链接
F4核心板开源链接:
(本工程使用的均为V1.2版本)
STM32F4核心板(BTB连接器版) - 立创开源硬件平台
gitee代码链接
部分代码(仅包含测试及下载用的代码)请查看:无线语音演示器 · 镯毁/立创开源作品代码 - 码云 - 开源中国
全部代码(包含用到的所有代码)请查看:无线语音演示器: 笔者的毕业设计软件总文件夹,包括本设计的所有工程源码(驱动编写、代码测试、等等)
TX_V1_1的代码由于需要使用外部FLASH下载算法,故请查看文件夹中的代码下载文件夹查看如何正确操作
参考链接
〖星繁宇动〗Spotlight 罗技演示笔体检,非评测_哔哩哔哩_bilibili
【Audio】I2S传输PCM音频数据分析总结(一)_i2s 双声道数据-CSDN博客
Audio-音频传输接口(I2S、PCM、PDM)_i2s音频接口-CSDN博客
stm32 USB系列-组合设备_哔哩哔哩_bilibili
【USB】STM32模拟USB鼠标_模拟usb输出鼠标信号-CSDN博客
STM32 USB复合设备(VCP虚拟串口+HID键盘)详解_usb vcp-CSDN博客
HID报表描述符(目前最全的解析,也是USB最复杂的描述符)_hid描述符-CSDN博客
【STM32+cubemx】0017 HAL库开发:usb HID鼠标设备实现_usb设备构建 hal库-CSDN博客
USB 协议分析之 HID 设备_usb hid-CSDN博客
通过 KEIL 制作 QSPI 接口的外部 Flash 下载算法 - STM32团队 ST意法半导体中文论坛
手把手系列--编写Keil MDK 外部FLASH下载算法_mdk使用下载算法-CSDN博客
【快速入门 LVGL】-- 1、STM32 工程移植 LVGL-CSDN博客
LVGL roller 项过多导致选项异常问题_lvgl中roller显示异常-CSDN博
SquareLine studio设计LVGL工程,移植到stm32的过程_squareline 移植 stm32-CSDN博客
基于 LVGL 使用 SquareLine Studio 快速设计 UI 界面_lvgl界面设计器-CSDN博客
FlexibleButton: 灵活的按键处理库(Flexible Button)| 按键驱动
STM32使用QUADSPI读写外部Nor Flash(以W25Q64为例)_stm32 quadspi-CSDN博客
STM32 QSPI Flash 退出内存映射模式的方法_qspi内存映射-CSDN博客
使用STM32的I2S协议读取麦克风INMP441-CSDN博客
设计图

BOM


评论