当前位置:首页 > 工业控制 > 电路设计项目集锦
[导读]我个人有时在准备入睡时会想很多问题,但起身拿起手机、暴露在蓝光下来获取答案,实在很烦人。如果能直接按下按钮,对着它说话,它就立刻告诉我答案,是不是会方便很多?就像一个会说话的魔幻8球一样?不需要屏幕?这能帮助我更好地入睡。

我个人有时在准备入睡时会想很多问题,但起身拿起手机、暴露在蓝光下来获取答案,实在很烦人。如果能直接按下按钮,对着它说话,它就立刻告诉我答案,是不是会方便很多?就像一个会说话的魔幻8球一样?不需要屏幕?这能帮助我更好地入睡。

但作为我的第二个项目,这个任务却相当困难。

部件

我的设备由一个用于控制扬声器的 MAX98357A、一个用于麦克风输入的 INMP441、一个 micro SD 读卡模块和一个按钮组成。焊接非常简单,只需将引脚连接到对应的元件即可。

接线

接线也十分简单。大多数非输入独占的 GPIO 引脚(具体取决于你使用的电路板)应该都能正常工作。但请注意“绑扎引脚”!对于更高级的电路板来说,这可能不是大问题,但对于我的 ESP32 Wroom 来说,这是一条中等大小的鱼。将其他组件连接到这些绑扎引脚上,会导致你的 ESP32 在编译代码时反复崩溃。如果你想查看原理图,请参考下方内容。

代码

这是项目中最难的部分。

一开始编写这个程序,感觉就像冷水浇在头上一样。

这是我第二个项目,完全不知道自己在做什么。

因此我迅速适应。AI和YouTube解释得非常好,所以我能理解。在每一个步骤中,我都仔细理解每一行代码,并且我会向你展示,详细讲解每一步。

需要注意的是,这段代码是我自己的代码和其他人的代码混合拼接而成的。作为我的第二个项目,也是我第一次认真尝试编程,我知道必须对部分代码进行整合才能完成。

然而,很少有人做过类似的事情,也没有人在网上分享过这类经验,所以很多我拼接的代码都是有缺陷的。凭借我自身的技术以及在这个项目中学到的经验,我自己动手修复了这些错误。

库文件

用于SD卡的SD、SPI和FS库;用于JSON请求的WiFi、HTTPClient和ArduinoJson;以及用于Base64编码/解码和i2s功能的原生库。

Google Gemini 代码

让我们从最简单的部分开始。我在制作自己的项目时,受到另一个Arduino项目的启发,于是使用了Google Gemini的代码。总体而言,我从这位人士那里得到的Gemini代码是有效的。这是一个很好的起点,帮助我开始学习。我通过这个实用的网站学习了HTTPclient库、WiFi库,以及API请求所需的JSON格式化知识。

我深入研究了代码注释,但基本原理是:该方法将字符串格式化为Google语音转文本(STT)API所需的参数,并去除不需要的字符,然后使用我的API密钥发送POST请求。如果调用成功,该方法会反序列化JSON响应,将返回对象赋值给response并返回。如果JSON调用失败,c_str()函数会明确告诉我们具体原因(参考本指南)。当API调用失败时,Google API会返回错误码,直接告知我们失败的原因。

Google云设置

Google Cloud非常直观。在本指南中,我不会展示如何获取我的API密钥,因为你可以找到任何关于如何注册和获取API密钥的教程。Google Cloud提供相同的密钥,可用于其所有服务。

I2S

这正是事情变得复杂起来的部分,起初让我感到有些困惑和畏惧,但通过网络和人工智能的帮助,我逐渐理解了给ESP32设置的参数以及它所控制的各个组件。

我首先查阅了关于I2S的基本概念、其功能以及使用方法。我最关心的主要问题在于I2S与我的硬件引脚之间的对应关系,特别是以下这些:

- LRC(左通道)、BCLK(或SCK)、DIN、GAIN、SD引脚。

接下来,我将逐一说明自己重点关注的I2S参数列表。

.将模式设置为TX(发送模式)和RX(接收模式),以输出音频并接收音频输入。

.channel_format 对LRC引脚尤为重要,该引脚接收信号以在左、右或左右两个通道上传输数据。左右与左右同时传输的区别在于音频是单声道还是立体声。

i2s_pin_config_t pin_config 是我用来指定哪个GPIO引脚向组件发送数据的地方。选择通用的GPIO引脚非常重要,且这些引脚不应被固定使用。

Google 文本转语音

这部分是我学习文本、语音以及中间所有技术环节时所用的内容。我最初从一个在线Arduino论坛中获取了现成的代码,并利用AI技术进行进一步开发。

起初我们看到相同的JSON和HTTP请求格式。但由于不再处理文本,我们必须将反序列化后的输出存储到缓冲区中。

然后我找到 b64 的起始位置,逐个字符遍历并存储到缓冲区中。

playAudio() 是一个辅助函数,它通过 I2S 将 b64 数据写入扬声器。音频以分段方式播放。

谷歌语音转文字

在通过尝试谷歌文本转语音功能积累了一定经验后,我意识到自己已经准备好用 AI 来实现语音识别部分了。我掌握了足够的知识,能够仔细分析输出结果,从而引导系统朝我期望的方向发展,保留有用的部分,并剔除冗余代码。

修复 SD 卡:

不知为何,SD.begin 函数会抛出错误,原因是我使用了 SD.begin,而该函数对我的代码和所使用的组件来说过于强大。SPI 使用的是简单的 4 根线,而 SD 却需要更多线路。

工作中的 SD 卡代码:

录音与按钮逻辑:

按钮逻辑很简单:按下并长按按钮开始录音,录音完成后松开按钮。我在按钮逻辑上曾遇到一些小问题,主要是因为 recordAudio() 的循环。代码随后将转录内容发送至 Google STT API,并返回语音的文本形式。

RecordingAudio:

录音开始前,需确保 myFile 可读且存在。文件检查通过后,我只写入 WAV 头部信息而不包含音频数据,然后开始录音。

recordAudio() 是一个循环方法,它实际上通过 i2s_read() 从麦克风录制音频。随后将原始音频编码写入文件。写入文件前会进行一个小的检查,确认是否真的在录制音频。如果 serial.printf() 语句输出的是 0,说明没有音频被录制。

录音结束后,会重新写入 WAV 文件并设置正确的数据大小,最后关闭文件。

写入 WAV 头部:

我已经详细介绍了 WAV 头部的内容。以下是其结构示例。头部定义了音频存储和播放的参数,位于文件开头处。

问题:

音频持续输出为纯静态声音。当我再次决定着手这个项目时,这将成为修复列表中的首要问题。

将音频发送至 Google STT:

向 STT API 发送音频的方式与我们在这次黑客松文章中见过的其他所有 API 请求相同,唯一的区别在于发送音频的方式。

我发送的是经过 base64 编码的字符串。

但我最大的挑战,花了我整整一个月(甚至更久),就是如何设置音频数据发送到API的音频配置。

这是代码中最棘手的部分,也是我始终无法解决的主要问题。我现在知道解决方法,稍后会透露。

学习如何处理内存分配是我遇到的最大难题。为了便于阅读,我删除了这部分代码,但其中仍有许多被注释掉的代码段。

存储base64Audio到JSON文档中的设置:

可以看到大量Serial.print()语句,这充分证明了我需要进行多少调试工作。

我取出了存储在SD卡中的myFile,并提取了其内部的音频内容(注意不要捕获wav文件头,否则会导致整个base64字符串出错)。

然后我检查是否有足够的内存来存储一段base64数据(通过print函数验证)。

接着,我逐块读取2048字节(或2048个b64字符),对每段进行编码,然后拼接成一个字符串,直到没有剩余的base64数据为止。最后将这个字符串返回并发送给API。

我的问题:

内存分配非常困难,因为我完全没意识到我的微控制器内存不足。我绞尽脑汁尝试优化内存,结果却始终没有足够的内存来处理Base64编码。我手头的ESP32 Wroom只有大约400KB的内存,而我在网上找到的代码所用的微控制器内存却高达1GB。

结果

Gemini思维的API功能正常,成功返回了AI输出。

文本转语音的API调用也成功,返回了音频,但扬声器仅播放了静态声音。

麦克风能够录音,但录下了静态噪音。API调用确实成功返回了文字转录内容和置信度!不过它只对静态音频返回了结果。

本文编译自hackster.io

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭