ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ESP32-S3 MicroPython实战:对接小智AI-01与DeepSeek/Qwen大模型

ESP32-S3 MicroPython实战:对接小智AI-01与DeepSeek/Qwen大模型 1. 为什么要在ESP32-S3上跑语音助手而不是买个成品音箱我手头这块ESP32-S3开发板买回来大半年了一直躺在元件盒里吃灰。直到上个月想给书房做个能语音控制灯光和查询天气的小终端翻出来重新折腾。市面上的智能音箱便宜是便宜但你想让它接自己的大模型API、想改唤醒词、想让它控制自定义的GPIO设备基本没戏。成品音箱的固件是封闭的云端服务也是厂商绑定你只能在它允许的范围内玩。ESP32-S3这颗芯片有意思的地方在于它自带向量指令加速240MHz双核512KB SRAM加8MB PSRAM我用的这款是N16R8版本跑MicroPython绰绰有余。更关键的是它支持Wi-Fi和蓝牙5.0做网络语音交互的硬件底子完全够用。小智AI-01这个语音助手方案在创客圈子里讨论度挺高核心思路是把语音识别和语义理解放到云端本地只负责音频采集、唤醒词检测和网络通信这样对MCU的算力要求就降下来了。我选MicroPython而不是ESP-IDF的原因很简单开发迭代快。用C写一个HTTP请求加JSON解析改一行代码要重新编译烧录等半天MicroPython直接文件系统拖进去就生效调试效率差好几倍。当然代价是运行效率低一些但对于语音助手这种不是硬实时的场景完全能接受。这篇文章适合三类人看手里有ESP32-S3开发板想找个实用项目练手的想给自己的硬件接大模型能力但不知道从哪下手的以及已经在用MicroPython但没试过音频网络综合项目的。我会把整个对接过程拆开讲清楚包括硬件接线、固件烧录、音频采集、API调用、DeepSeek和Qwen的配置差异以及我踩过的那些坑。2. 硬件选型与接线别小看麦克风和功放的选择2.1 ESP32-S3开发板的版本差异市面上ESP32-S3开发板版本很多我建议至少选带8MB PSRAM的型号。原因在于音频缓冲和JSON解析都需要内存MicroPython本身也要占一部分4MB版本跑起来会很紧张。我用的这块是ESP32-S3-DevKitC-1 N16R816MB Flash加8MB PSRAM空间很充裕。另外注意USB接口类型。有些板子是Type-C有些是Micro-USB还有的需要外接USB转TTL模块。我建议直接买Type-C接口的版本烧录和供电一根线搞定省事。板子上的USB接口分两种模式UART模式和JTAG模式烧录MicroPython固件时需要用UART模式这个后面会细说。2.2 麦克风模块INMP441还是MSM261数字麦克风我试过两款INMP441和MSM261S4030H0。两者都是I2S接口接线方式基本一样。INMP441便宜好买但底噪稍微大一点MSM261信噪比更好价格贵几块钱。如果你对语音识别准确率要求高建议直接上MSM261。接线方面I2S数字麦克风一般需要五根线麦克风引脚ESP32-S3引脚说明VDD3.3V供电GNDGND共地SCKGPIO 14位时钟WSGPIO 15字选择SDGPIO 32数据输出这里有个坑要注意INMP441的SD引脚是开漏输出需要外接一个10K上拉电阻到3.3V否则数据线一直是低电平读出来全是噪声。MSM261内部有上拉不需要额外加。我第一次用INMP441的时候没加上拉电阻调试了两个小时以为是代码问题最后拿示波器一看数据线根本没波形。2.3 功放与喇叭MAX98357A的用法输出端我用的是MAX98357A功放模块I2S输入直接推一个4欧3W的小喇叭。接线比麦克风还简单功放引脚ESP32-S3引脚说明VIN5V供电3.3V也能响但音量小GNDGND共地BCLKGPIO 27位时钟LRCGPIO 26左右声道时钟DINGPIO 25数据输入MAX98357A有个好处是它自带DAC和功放不需要额外的解码芯片。但它有个SD引脚关断控制悬空时默认开启拉低就静音。如果你想像我一样用GPIO控制静音可以把SD接到一个空闲的GPIO上。注意麦克风和功放的I2S时钟是独立的ESP32-S3有两个I2S外设可以同时使用。但MicroPython的I2S驱动对双工模式支持不太好我建议用两个独立的I2S实例一个收一个发不要试图用同一个实例做全双工。3. MicroPython固件烧录与环境搭建3.1 固件下载与烧录工具选择MicroPython官网有ESP32-S3的通用固件但我建议用带SPIRAM支持的版本文件名里通常带spiram字样。下载下来是一个.bin文件大概2MB左右。烧录工具我用的是esptool命令行操作pip install esptool esptool.py --chip esp32s3 --port /dev/ttyUSB0 erase_flash esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 ESP32_GENERIC_S3-SPIRAM_OCT-20240602-v1.23.0.binWindows下端口号一般是COM3、COM4之类的在设备管理器里看。Mac下是/dev/tty.usbserial-*或者/dev/tty.wchusbserial*。烧录的时候有个细节ESP32-S3需要手动进入下载模式。按住BOOT键不放点一下RST键然后松开BOOT键这时候板子就进入下载模式了。如果你用的是带自动下载电路的板子比如官方DevKitCesptool会自动触发不需要手动按键。3.2 Thonny还是mpremote烧录完固件后连接开发板的方式有两种Thonny IDE或者mpremote命令行工具。Thonny对新手友好有文件管理器和REPL交互窗口mpremote更适合自动化脚本和批量操作。我平时两个都用开发调试用Thonny因为可以实时看REPL输出部署的时候用mpremote批量传文件mpremote connect /dev/ttyUSB0 fs cp main.py :main.py mpremote connect /dev/ttyUSB0 fs cp config.py :config.py mpremote connect /dev/ttyUSB0 reset3.3 必备的MicroPython库ESP32-S3的MicroPython固件里已经内置了machine、network、time这些基础库。但音频处理和HTTP请求需要额外的东西urequests发HTTP请求固件里可能没有需要手动传一个上去json解析API返回的数据固件内置audio或machine.I2S音频采集和播放固件内置urequests这个库比较特殊它不在标准固件里你需要从MicroPython的官方库仓库下载一个urequests.py文件然后用Thonny或者mpremote传到板子上。我试过用socket自己封装HTTP请求代码量大概多三倍没必要。4. 小智AI-01的通信协议拆解4.1 整体数据流小智AI-01的架构其实不复杂核心流程是这样的本地麦克风持续采集音频做VAD语音活动检测检测到有效语音后把音频数据编码成PCM或者Opus格式通过WebSocket或者HTTP POST把音频数据发到小智AI-01的服务端服务端做语音识别把文字交给大模型处理大模型返回文字回复服务端做TTS合成音频数据回传给ESP32-S3通过功放播放整个链路里ESP32-S3只负责第1、2、3、6步语音识别、语义理解、语音合成都放在云端。这也是为什么用MicroPython能跑起来的原因——本地计算量很小。4.2 WebSocket还是HTTP小智AI-01支持两种通信方式WebSocket和HTTP短连接。WebSocket的优点是延迟低适合实时对话HTTP的优点是实现简单MicroPython的urequests直接就能用。我一开始用的是HTTP方式每次录音2秒发一个POST请求等返回。实测下来延迟大概3到5秒主要是网络往返和云端处理时间。后来换成WebSocket延迟降到1.5秒左右但MicroPython的WebSocket库不太稳定偶尔会断连。如果你只是做简单的语音指令比如开灯关灯HTTP方式完全够用。如果想做连续对话建议上WebSocket。4.3 音频格式的选择小智AI-01服务端接受的音频格式主要是PCM和Opus。PCM是无压缩的16位采样、16kHz采样率、单声道每秒数据量是32KB。Opus压缩后大概每秒4KB但MicroPython没有内置Opus编码器需要自己移植C库比较麻烦。我建议直接用PCM虽然数据量大一点但实现简单。ESP32-S3的Wi-Fi带宽足够传32KB/s没什么压力。唯一需要注意的是内存缓冲录2秒音频需要64KB的缓冲区8MB PSRAM完全放得下。5. DeepSeek与Qwen的API配置差异5.1 两个平台的基本信息小智AI-01本身是一个语音交互框架它后端可以接不同的大模型。我试了DeepSeek和Qwen两个平台配置方式有差异但整体思路一样。对比项DeepSeekQwenAPI地址api.deepseek.comdashscope.aliyuncs.com认证方式Bearer TokenBearer Token请求格式JSONJSON流式输出支持支持免费额度有有5.2 在MicroPython里构造请求MicroPython的urequests库用法和Python的requests很像但功能少一些。构造一个DeepSeek的请求大概是这样import urequests import json url https://api.deepseek.com/v1/chat/completions headers { Content-Type: application/json, Authorization: Bearer sk-你的API密钥 } data { model: deepseek-chat, messages: [ {role: system, content: 你是一个语音助手回答要简短。}, {role: user, content: 今天天气怎么样} ], max_tokens: 100 } response urequests.post(url, headersheaders, datajson.dumps(data)) result response.json() print(result[choices][0][message][content]) response.close()Qwen的请求格式几乎一样只是URL和model名字不同url https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions data { model: qwen-turbo, messages: [...] }5.3 密钥管理和安全注意事项API密钥绝对不能硬编码在main.py里尤其是如果你打算把代码分享出去。我的做法是单独建一个config.py文件里面放密钥和Wi-Fi密码然后在.gitignore里排除这个文件。# config.py WIFI_SSID 你的Wi-Fi名称 WIFI_PASSWORD 你的Wi-Fi密码 DEEPSEEK_API_KEY sk-xxxxxxxx QWEN_API_KEY sk-xxxxxxxx然后在main.py里from config import *。这样分享代码的时候只分享main.py就行密钥不会泄露。提示MicroPython的REPL里输入的任何内容都会被记录在历史里如果你在REPL里直接粘贴了密钥记得用CtrlD软重启后清一下历史。更安全的做法是永远不在REPL里输入密钥只通过文件系统写入。6. 音频采集与播放的实操细节6.1 I2S初始化的参数计算I2S的初始化参数如果设错了录出来的声音要么是噪音要么速度不对。关键参数有三个采样率、位深、声道数。采样率我设的是16000Hz这是语音识别的标准采样率。位深16位单声道。I2S的时钟频率计算公式是BCLK 采样率 × 位深 × 声道数 × 2对于16000Hz、16位、单声道BCLK 16000 × 16 × 1 × 2 512000Hz。这个时钟频率ESP32-S3的I2S外设能轻松支持。MicroPython里初始化I2S的代码from machine import I2S, Pin i2s_in I2S( 0, sckPin(14), wsPin(15), sdPin(32), modeI2S.RX, bits16, formatI2S.MONO, rate16000, ibuf8192 )ibuf是内部缓冲区大小我设的8192字节大概能存0.25秒的音频。如果你发现录音有丢帧可以把这个值调大。6.2 录音缓冲区的管理录音的时候不能一直往内存里存否则几秒钟就把RAM撑爆了。我的做法是录2秒就停把数据发出去然后清空缓冲区重新录。2秒的PCM数据是64000字节加上JSON封装和HTTP头一个请求大概70KB。import struct def record_audio(duration_ms2000): buffer bytearray(64000) bytes_read 0 while bytes_read len(buffer): chunk i2s_in.readinto(buffer[bytes_read:]) if chunk: bytes_read chunk return buffer这里有个细节readinto返回的是实际读取的字节数可能小于请求的大小。所以要用循环确保读满整个缓冲区。6.3 播放回传音频播放比录音简单因为数据长度是已知的。初始化一个TX模式的I2S实例然后把音频数据写进去i2s_out I2S( 1, sckPin(27), wsPin(26), sdPin(25), modeI2S.TX, bits16, formatI2S.MONO, rate16000, ibuf8192 ) def play_audio(data): i2s_out.write(data)注意TX和RX要用不同的I2S实例编号0和1否则会冲突。7. 踩坑实录那些让我熬夜的调试问题7.1 Wi-Fi连接不稳定导致请求超时最开始我用的是HTTP短连接每次请求都要重新建立TCP连接。ESP32-S3的Wi-Fi在2.4GHz频段周围路由器多了会有干扰。我遇到的情况是前几次请求正常跑十几分钟后就频繁超时。排查过程先用ping测试网络延迟发现延迟波动很大从20ms到500ms都有。然后换了一个Wi-Fi信道从信道6换到信道11情况好转但没根治。最后发现是urequests每次请求都新建socket没有复用连接。改成WebSocket长连接后问题基本消失。如果你坚持用HTTP建议在请求之间加一个短延迟并且设置合理的超时时间response urequests.post(url, headersheaders, datajson.dumps(data), timeout10)7.2 JSON解析内存不足MicroPython的json.loads在解析大JSON时会占用大量内存。DeepSeek返回的完整响应大概2到3KB解析的时候需要额外分配差不多两倍的内存。如果PSRAM没启用很容易报MemoryError。解决办法有两个一是确保固件启用了SPIRAM支持二是在请求里加max_tokens限制返回长度。我设的100返回的文本大概几十个字解析起来很轻松。7.3 麦克风采集到的全是噪音这个问题困扰我最久。代码逻辑没问题I2S初始化参数也检查了好几遍但录出来的音频就是沙沙的噪音。排查步骤先用示波器看SD引脚发现没有数据波形确认是硬件问题检查INMP441的接线发现SD引脚没有上拉电阻加了一个10K电阻到3.3V数据波形出来了但录出来的声音还是很小检查发现麦克风的L/R引脚接错了应该接GND选择左声道这两个问题叠加在一起让我多花了整整一个晚上。所以如果你用INMP441务必确认SD引脚有上拉L/R引脚接地。7.4 API返回401错误401是认证失败原因通常是密钥不对或者请求头格式错了。我遇到过一次是因为复制密钥的时候多带了一个空格。还有一次是因为Authorization头写成了authorizationHTTP头字段名是大小写不敏感的但MicroPython的urequests在某些版本里对大小写处理有问题。建议在代码里加一个错误处理if response.status_code ! 200: print(请求失败状态码, response.status_code) print(返回内容, response.text)这样出问题的时候能快速定位。8. 从能跑到好用几个值得做的优化8.1 加入本地唤醒词检测一直录音上传太费流量也费电。我在本地加了一个简单的能量检测只有当麦克风采集到的音频能量超过阈值时才开始正式录音。这样待机的时候几乎不耗流量。def get_energy(buffer): total 0 for i in range(0, len(buffer), 2): sample struct.unpack(h, buffer[i:i2])[0] total abs(sample) return total // (len(buffer) // 2) # 主循环 while True: chunk i2s_in.read(1024) if get_energy(chunk) 500: audio record_audio(2000) # 发送处理阈值500是我实测下来的经验值安静环境下能量大概在100到200说话时能到1000以上。你可以根据实际环境调整。8.2 用流式输出降低延迟DeepSeek和Qwen都支持流式输出stream模式也就是模型生成一个字就返回一个字不用等全部生成完。这样首字延迟能从3秒降到1秒以内。但MicroPython处理流式响应比较麻烦因为urequests不支持流式读取。我的做法是用socket直接发HTTP请求然后逐行读取响应import socket import ssl s socket.socket() s.connect((api.deepseek.com, 443)) s ssl.wrap_socket(s) # 手动构造HTTP请求...这种方式代码量大但延迟改善明显。如果你对响应速度要求高值得折腾。8.3 音频数据的压缩PCM数据量太大如果网络条件不好可以考虑用ADPCM压缩。MicroPython的audio模块支持ADPCM编码压缩比大概是4:1音质损失可以接受。from audio import ADPCM adpcm ADPCM() compressed adpcm.encode(pcm_data)不过ADPCM需要服务端也支持解码小智AI-01的服务端是否支持要看你用的具体版本。我试过用ADPCM识别准确率比PCM低一些但流量省了75%。9. 关于成本和实用性的几点个人体会DeepSeek和Qwen的API都是按token计费的语音助手这种场景每次请求大概消耗100到200个token成本很低。我算过一笔账每天用50次一个月下来费用不到两块钱。当然这是在不考虑语音识别和TTS费用的情况下如果小智AI-01的服务端把这些都包了那整体成本会更低。实际用下来ESP32-S3跑MicroPython做语音助手响应延迟在1.5到3秒之间比市面上的智能音箱慢一些但胜在完全可控。你可以随便换模型、改提示词、加自定义功能。我后来给它加了一个查快递的功能直接调快递100的API比问智能音箱方便多了。如果你也想动手做我的建议是先把HTTP短连接的版本跑通确认音频采集和API调用都没问题再去折腾WebSocket和流式输出。一上来就搞最复杂的方案很容易卡在某个细节上失去耐心。硬件方面麦克风和功放的钱别省INMP441加MAX98357A这套组合不到三十块钱但体验比板载麦克风好太多。
返回列表