资讯动态

自己动手用ESP32S3与大模型API打造智能AI语音助手

发布时间:2026/9/17 23:47:00 来源:尧图企业网站定制
自己动手做智能音箱这事我从大学就开始琢磨。市面上各种智能音箱虽然方便但作为一个技术人总希望它能干点“超纲”的事——换个更聪明的语音大模型接入自己的知识库甚至自定义唤醒词而不是每天被厂商的默认回答限制住。这个项目就是源于这种想法用一片ESP32S3开发板、一个几块钱的I2S麦克风、一台跑Python的电脑再加上当前很火的大语言模型API从零搭一个真正属于自己的智能AI语音助手。本文会把整个方案的选型思考、硬件连接、固件编写、Python后端、大模型接入、端到端联调和排错经验全部过一遍适合有嵌入式基础或Python基础、想接触AI应用开发的读者参考也适合先完整跟一遍再结合自己的需求慢慢深入。1. 项目整体设计与方案选型1.1 为什么偏偏选ESP32S3而不是ESP32、树莓派或者其他板子做语音助手硬件的选择会直接决定后面所有代码怎么写。我最早用过ESP32经典款试过类似的链路但录音和WiFi同时跑的时候偶尔会卡顿。ESP32S3的出现解决了我最头疼的两个问题算力和内存余量。ESP32S3用的是双核Xtensa LX7处理器主频最高能到240MHz虽然跟树莓派那种Linux系统完全不是一类东西但在MCU里算是顶流了。更重要的是ESP32S3内置了用于AI加速的向量指令PIE在做关键词识别这类轻量级神经网络推理时比老ESP32要快不少。如果你买的是8MB PSRAM、16MB Flash的版本跑乐鑫官方的ESP-SR语音识别框架也不会觉得内存紧张这在ESP32上是不敢想的。另外一个容易被忽略的因素是外设。ESP32S3集成I2S接口这对音频项目是决定性的。I2S可以直接连接数字麦克风不需要经过ADC信号质量比模拟采集稳定太多。同时它支持WiFi和BLE传输音频数据给后端处理完全够用。相比之下树莓派虽然算力强但整套下来成本贵几倍还要处理Linux系统的环境问题对“一个语音终端”来说有点杀鸡用牛刀。我的结论是ESP32S3的定位非常准它就是个为物联网终端设计、又刚好能满足轻量AI推理的芯片语音助手这个场景它比ESP32从容比树莓派轻量。1.2 技术架构拆解本地采集云端思考定下ESP32S3之后下一步是整体架构设计。我的方案可以概括成一句话本地采集音频云端大脑生成回复扬声器本地播放。具体来说数据流是这样的用户对着麦克风说话ESP32S3通过I2S接口把模拟的声音变成数字信号存成16kHz、16bit的单声道PCM/WAV数据。然后ESP32S3通过WiFi把这些音频数据POST到电脑上跑的Python后端。Python后端会做三件事第一调用语音识别ASR服务把音频转成文字第二把文字交给大语言模型LLM让模型生成回答文本第三用语音合成TTS把回答文字转成音频再通过网络传回ESP32S3由I2S功放和喇叭播放出来。为什么不让ESP32S3直接调大模型API不是不行而是体验会很差。ESP32S3的内存和Flash装不下通用的语音识别模型更装不下一个大语言模型。就算真的硬着头皮去调用HTTP接口签名算法、JSON解析、上下文管理这些逻辑写在Arduino代码里调试成本高得吓人。把Python后端放在中间最大的好处是职责清晰ESP32S3只管“采集和播放”所有AI相关的逻辑都集中在Python里后续换ASR服务、换大模型、优化提示词完全不用动固件。1.3 核心方案对比为什么这种组合最平衡做技术选型的时候我把市面上的常见方案列了个表格对比一圈之后就能很明显地看出各自的取舍方案成本智能化程度开发难度适合人群ESP32 纯本地轻量识别低只能识别几个预置命令中想玩嵌入式语音的入门者ESP32S3 云端ASR/LLM本方案较低几乎无限取决于大模型中想快速搭建完整语音助手的开发者树莓派 本地大模型高高但受限于模型大小较高追求完全离线的硬核玩家手机 现成语音助手中受制于厂商低普通用户我最终选择方案二是因为它在“开发成本和体验上限”之间找到了一个平衡点。纯本地方案的智能化水平太弱只能听懂“开灯”“关灯”这种命令词而且换个用户口音识别率就直接崩。树莓派本地跑大模型虽然能离线运行但一张好一点的开发板加散热外壳就得好几百部署和调试的时间和精力成本也高不少。把音频采集放到MCU上、把AI推理放到API侧是现阶段性价比最高的一种做法。2. 硬件准备与环境搭建2.1 材料清单入门成本不到两顿饭钱这个项目的硬件清单非常短除了开发板需要稍微选一下之外其他的都是通用模块。我按照实际使用体验列了一份参考清单ESP32S3开发板一块推荐买16MB Flash 8MB PSRAM的版本常见的DevKitC形状就行INMP441 I2S数字麦克风模块一个这是I2S麦克风里最常用的型号几块钱就能买到MAX98357A I2S功放模块一个3W功率直接驱动小喇叭3W或4Ω小喇叭一个外壳拆机的也行杜邦线若干、面包板一块或者直接用烙铁飞线5V/2A电源或者质量好的USB线给开发板供电如果不想自己接模块也可以直接买合宙ESP32S3板或者带麦克风的集成板比如Seeed XIAO ESP32S3 Sense把麦克风和SD卡都集成了省事很多。我当时用的是分体模块的方案好处是每一部分都可以单独替换排查对学习理解很有帮助。第一次做的话我建议也走分体路线虽然麻烦一点点但后面排查问题会顺手很多。2.2 先把线接对引脚对照与供电要点接线是整个项目里最机械也最容易翻车的环节。INMP441和MAX98357A都是I2S接口所以要占用两组I2S引脚。为了不让两个外设冲突我给它们分配了不同的GPIO。这里有一个非常重要的经验I2S的SCK和WS最好分别用独立的引脚千万别和开发板自带的Flash或PSRAM引脚冲突否则固件直接崩溃。我用的GPIO映射如下表所示外设I2S信号ESP32S3引脚INMP441SCK时钟GPIO 4INMP441WS左右声道选择GPIO 5INMP441SD数据输出GPIO 6INMP441L/R声道选择GNDMAX98357ABCLKGPIO 15MAX98357ALRCWSGPIO 16MAX98357ADINGPIO 17接线的时候有两点容易忽略第一INMP441的L/R引脚如果接GND它输出的就是左声道数据如果接VDD就输出右声道数据。一定要接地这样在I2S配置里设置成单声道读取时不会出现左右声道串数据的问题。第二MAX98357A的SD引脚是关断引脚悬空或者拉低会导致功放不工作所以即使不使用它也要把它接到VDD或者用一个GPIO拉高否则喇叭永远没声音。供电是个隐藏的大坑。INMP441和MAX98357A都需要3.3V供电但ESP32S3开发板上的3.3V稳压器输出能力有限同时给WiFi射频、麦克风和功放供电时电压很容易跌落导致WiFi掉线或者录音出现爆音。我的做法是功放和麦克风都用开发板3.3V供电但开发板必须接一个足够稳的5V输入不要用电脑前置USB口供电尽量用带屏蔽的短数据线或者直接上5V/2A适配器。2.3 Arduino IDE与Python环境一次性配好固件开发我选的是Arduino IDE理由很简单ESP32的Arduino生态非常成熟库管理方便对新手友好。你当然可以用乐鑫官方的ESP-IDF功能更强、控制更精细但学习曲线陡很多。先把Arduino路线跑通后续有性能瓶颈再切IDF也不迟。Arduino IDE建议安装最新2.x版本然后在“文件 - 首选项 - 附加开发板管理器网址”里填入乐鑫的ESP32板包地址https://espressif.github.io/arduino-esp32/package_esp32_index.json之后在“开发板管理器”里搜索esp32安装“esp32 by Espressif Systems”最新版。安装完成后在“开发板”里选择你手上的ESP32S3型号。注意有些开发板的USB转串口芯片和板载串口芯片不一样选完板子后还要在“端口”里选对设备否则下载固件时会报错“Failed to connect”。Python环境这块实际不需要装什么复杂的发行版直接从官网下载Python 3.10或3.11安装包就行。安装过程中有一句“Add Python to PATH”一定要勾选否则命令行里敲python会提示找不到命令。装完之后再给项目建一个虚拟环境这是Python项目的卫生习惯避免和系统环境互相污染python -m venv vassistant_env source vassistant_env/bin/activate # Windows下是 vassistant_env\Scripts\activate pip install fastapi uvicorn requests python-dotenv3. 让ESP32S3开口说话固件端开发3.1 I2S音频采集让麦克风变成可上传的WAV音频采集是固件里最关键的部分。INMP441通过I2S协议输出数据ESP32S3读取数据时要按照I2S外设的时序去配置。我直接给出一个实测稳定可用的配置这段代码在Arduino环境下可以直接用#include driver/i2s.h #define I2S_PORT_RX I2S_NUM_0 #define I2S_WS 5 #define I2S_SCK 4 #define I2S_SD 6 void i2s_init() { i2s_config_t i2s_config { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate 16000, .bits_per_sample I2S_BITS_PER_SAMPLE_16BIT, .channel_format I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags ESP_INTR_FLAG_LEVEL1, .dma_buf_count 8, .dma_buf_len 1024, .use_apll false, .tx_desc_auto_clear false, .fixed_mclk 0 }; i2s_pin_config_t pin_config { .bck_io_num I2S_SCK, .ws_io_num I2S_WS, .data_out_num I2S_PIN_NO_CHANGE, .data_in_num I2S_SD }; i2s_driver_install(I2S_PORT_RX, i2s_config, 0, NULL); i2s_set_pin(I2S_PORT_RX, pin_config); }这里有几个参数的含义要说清楚。sample_rate16000是16kHz采样率这是语音识别领域的事实标准绝大多数ASR服务都要求这个参数bits_per_sample16表示16bit位深channel_formatONLY_LEFT是因为我们把INMP441的L/R脚接到了GND它输出的是左声道数据只用一路声道可以省一半传输量。录音时I2S外设会把数据持续写入DMA缓冲区我们只需要循环读取就行。一个容易踩的坑是I2S读出来的原始数据是PCM格式如果直接发给ASR接口比如百度短语音识别需要封装成WAV或者原始PCM。如果接口要求传base64编码那就只对PCM数据base64编码如果要求带文件头就得自己在前面加上44字节的WAV头。很多项目的麦克风“没声音”其实不是硬件问题而是数据格式不对接口那边根本解析不出来。3.2 网络通信与上传逻辑把声音交给Python后端录音完成之后ESP32S3需要把音频数据交给Python后端。传输方式我建议用HTTP POST固件代码清晰、容易排查。我的逻辑是按下触发按键后开始录音3秒然后组装HTTP请求上传。#include WiFi.h #include HTTPClient.h const char* ssid 你的WiFi名; const char* password 你的WiFi密码; const char* serverUrl http://192.168.1.100:8000/voice; void uploadAudio(uint8_t* data, size_t len) { HTTPClient http; http.begin(serverUrl); http.addHeader(Content-Type, application/octet-stream); int httpCode http.POST(data, len); if (httpCode 0) { Serial.printf(Response code: %d\n, httpCode); } else { Serial.printf(Upload failed: %s\n, http.errorToString(httpCode).c_str()); } http.end(); }后端服务器地址写你电脑在局域网里的IP这个可以从路由器后台或者电脑的网络设置里查到。这里要注意ESP32S3和电脑必须在同一个网段下否则请求根本发不出去。上传之前我建议加一个简单的重试机制WiFi环境下偶发丢包太正常了直接放弃上传会让人误以为是麦克风坏了。还有一个很容易被忽略的细节不要在主循环里直接发几百KB的HTTP数据MCU会卡住好几秒。最好在录音完成之后用一个单独的任务Task去处理网络请求这样系统的其他功能指示灯、按键检测还能正常工作。Arduino环境下可以用FreeRTOS的xTaskCreatePinnedToCore创建任务把WiFi上传挂到核心0上。3.3 唤醒词怎么选按键触发起步ESP-SR进阶智能语音助手如果每次都要按键再说话总觉得不够“智能”。所以很多读者会问能不能像智能音箱那样喊一声唤醒词就激活能但有代价。最简单的方案是GPIO按键输入按下按键录音、松开停止并上传。我把这个方案称为“开发模式”它的价值在于先验证整条链路是否通把ASR、LLM、TTS跑通后再去考虑唤醒词问题定位会清晰得多。进阶方案是用乐鑫官方的ESP-SR框架它在ESP32S3上可以运行基于神经网络的唤醒词检测。Arduino环境下可以直接用“esp32_sr”库它自带了几个预置唤醒词比如中文的“你好小智”、英文的Hey ESP等。如果你想自定义唤醒词就得用乐鑫的WakeNet训练工具链在电脑上跑Python训练脚本生成模型然后转成C数组烧进固件。这条路比较长我建议先把功能链路跑通再回头来啃ESP-SR。4. Python语音后端与大语言模型接入4.1 FastAPI后端一台电脑搞定ASR/LLM/TTSPython后端是整个系统的“大脑”我用FastAPI来实现因为它写起来简单、自带接口文档、而且天然支持异步。后端只需要暴露一个HTTP接口POST /voice接收ESP32S3上传的音频内部依次调用ASR、LLM、TTS三个服务最后返回结果。核心代码框架如下from fastapi import FastAPI, UploadFile import asyncio app FastAPI() app.post(/voice) async def handle_voice(file: UploadFile): audio_bytes await file.read() # 第一步语音识别 text await asr_recognize(audio_bytes) # 第二步大模型生成回答 reply await llm_chat(text) # 第三步语音合成 audio_url await tts_synthesize(reply) return {text: text, reply: reply, audio_url: audio_url}ASR服务的选择很多。百度短语音识别、讯飞语音识别都有免费额度OpenAI的Whisper API也可以但国内访问受网络环境影响比较大。我当时用的某短语音识别接口它对音频格式要求是16kHz采样率、16bit位深、单声道PCM/WAV正好我们固件里录出来的格式就是这样的直接传过去不用转换。如果你更倾向离线方案也可以用sherpa-onnx在电脑上本地跑Whisper或者Paraformer模型虽然会占用一些CPU资源但胜在免费、隐私性好。调用外部API时我强烈建议把所有密钥放在.env文件里管理不要硬编码在代码中方便后续换服务商也方便自己维护。4.2 大模型选型从免费API到本地Ollama大模型是整个助手的“智商来源”。选型时首先要明确一点这不是选“最强模型”而是选“最合适的模型”。你要考虑两个关键指标——响应延迟和成本。一个几百亿参数的旗舰模型生成回复虽然质量高但思考和输出都要好几秒语音助手对话节奏就会变得非常拖沓反而是一些轻量级模型比如智谱AI的glm-4-flash免费开放响应速度还快对语音助手这种需要快速交互的场景非常合适。如果你有本地部署的偏好或者不想申请API也可以用Ollama在电脑上跑Qwen2.5-1.5B或3B这种小模型。一台16GB内存的普通电脑就能流畅运行虽然智能程度比不上云端的大模型但回答简单问题和日常对话绰绰有余。用Ollama的好处是本地运行、零成本、随时可以换模型缺点是你得保证电脑开着而且模型能力确实比云端主流模型要弱。实测下来做语音助手本地小模型回答“今天天气怎么样”这种问题还行讨论稍微专业的话题就开始跑偏了。调用大模型API的代码统一用OpenAI兼容格式就行这样可以快速切换不同服务商import os import requests from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(LLM_API_KEY) LLM_URL os.getenv(LLM_URL, https://open.bigmodel.cn/api/paas/v4/chat/completions) MODEL_NAME os.getenv(MODEL_NAME, glm-4-flash) def llm_chat(user_text, historyNone): history history or [] payload { model: MODEL_NAME, messages: history [{role: user, content: user_text}], temperature: 0.7 } headers {Authorization: fBearer {API_KEY}} resp requests.post(LLM_URL, jsonpayload, headersheaders, timeout30) resp.raise_for_status() return resp.json()[choices][0][message][content]4.3 提示词与上下文管理会聊天的关键是会设置人设大模型API接上之后很多人的第一版助手会“笨”得厉害。不是因为模型不行而是你少做了两件事设置系统提示词以及管理对话上下文。语音助手和人聊天不太一样用户是用耳朵在听回答所以大模型的回答不能像写议论文那样又长又绕。我的系统提示词是这样的你是一个名叫小智的智能语音助手。请用简洁、口语化的中文回答用户问题。 回答尽量控制在3句话以内每句话不超过20个字。不要使用markdown格式 不要说废话不要反问用户。这段提示词看似简单但作用非常大。它直接决定了大模型的输出风格避免了TTS念长文本时那种机械感。实测下来加了这段提示词之后整个对话的节奏立刻就顺了。上下文管理同样重要。大模型API本身是无状态的每次调用都是“失忆”的所以要在后端自己维护一个对话历史列表。每次用户说话之后把新内容追加到历史里再丢给大模型。历史不能无限堆积一般保留最近10轮就够了否则请求会越来越大延迟越来越高还会撑爆token上限。我的做法是在调用llm_chat前从历史里取最后10条消息传给大模型更早的对话直接丢弃。5. 端到端联调与实测记录5.1 从后端开始逐级验证不要一上来就连硬件联调最忌讳的就是“一把梭”——固件、后端、大模型全都没验证过直接变成一个整体出任何问题都不知道该查哪里。我的经验是从后端往前面逐级验证。把Python后端跑起来之后先用浏览器访问http://127.0.0.1:8000/docsFastAPI会自动生成一个交互式文档页面可以在里面直接测试接口。你可以在页面上传一个提前准备好的测试音频文件看ASR能不能返回正确文本。确定了ASR正常再试LLM能不能回复确定LLM正常再试TTS能不能生成语音。每一步都确认无误之后再拿ESP32S3实机操作。如果没有现成的测试音频可以用电脑录音软件录一段“你好今天天气怎么样”导出成WAV格式。注意导出的采样率要跟固件一致都是16kHz、16bit、单声道否则测试结果没有参考意义。5.2 整机实测响应延迟与识别效果链路全部跑通后我做了几轮整机实测。在同一个WiFi环境下使用3秒录音、glm-4-flash模型、Edge-TTS语音合成从按下唤醒键到喇叭播出回复平均耗时大约4.8秒。这个延迟分布大概是录音3秒这是固定成本 音频上传0.2秒 ASR识别0.6秒 大模型生成1.2秒 TTS合成0.5秒 音频下发和播放0.3秒。4.8秒的延迟在对话场景下还算能接受但谈不上流畅。如果想让体验更好有两个明显的优化方向。第一把录音时间从固定3秒改成动态VAD检测不说话就提前结束录音能省下1到2秒。第二大模型选择响应更快的模型或者把temperature参数调低生成一般会更稳定、更快。实测中glm-4-flash在1.2秒左右开始返回换更强模型会涨到3秒以上那体验就明显拖沓了。识别效果方面16kHz的INMP441在安静环境下中文普通话识别率很高。但有一个前提说话人要和麦克风保持一定距离太近容易爆音识别反而变差。如果环境嘈杂建议在固件里加一个简单的降噪或者在后端ASR服务里开启降噪开关效果立竿见影。5.3 延迟优化三板斧参数、模型、传输如果对响应延迟不满意可以从三个方向下手。第一参数层面降低录音时长、提高采样裁剪逻辑。很多人会以为采样率越高识别越准但对于ASR来说16kHz已经足够48kHz只会让文件变大、上传变慢识别精度并不会有明显提升。第二模型层面尽量选择轻量级、低延迟的大模型。可以用一个简单的对照方法在电脑上单独测试不同模型从post请求发出到拿到回复的时间哪个低于1秒就优先用哪个。第三传输层面用WebSocket替代HTTP。HTTP每次请求都要重新建立连接存在握手开销WebSocket可以在后端和ESP32S3之间维持一条长连接请求和响应都在一条通道上走虽然首版开发复杂一些但传输延迟确实更稳定。6. 常见问题与避坑指南6.1 问题速查表现象、原因、解决办法这个项目我做下来表面看是“接线调API”实际上碰到的坑都是一环扣一环的。我把踩过的问题整理成了一张速查表方便你直接对照排查现象可能原因解决办法电脑收不到ESP32S3上传的数据开发板和电脑不在同一网段确认开发板连接的是局域网WiFi且后端监听0.0.0.0麦克风录音全是爆音/杂音I2S时钟脚和数据脚接错仔细核对SCK、WS、SD三个脚的映射ASR完全识别不出内容录音格式和接口要求不匹配统一为16kHz、16bit、单声道PCM/WAV大模型回复特别慢选择的模型太大换成轻量模型如glm-4-flash、gpt-4o-miniTTS生成的语音播放不出声MAX98357A的SD脚未拉高把SD引脚接VDD或者用GPIO控制置高喇叭声音很小或发闷功放增益设置不当检查MAX98357A的GAIN脚调高增益到9dB或12dB固件上传后一直重启GPIO被Flash/PSRAM占用改引脚避开ESP32S3的保留引脚有一个最容易忽视的问题电脑的防火墙。FastAPI默认监听127.0.0.1只能本机访问ESP32S3当然连不上。必须启动时改成uvicorn app:app --host 0.0.0.0 --port 8000同时确保Windows防火墙没有拦截Python程序的入站连接。我因为这个问题在局域网联调时卡了快一个小时写出来给各位避个雷。6.2 调试心得与常见误区再多分享几个调试心得。第一硬件问题一定要用“替换法”排查。我曾经怀疑是INMP441坏了结果用电脑接上同一块麦克风录音一试发现模块是好的问题是GPIO配置错了。工具上最好准备一个USB转TTL串口模块可以直接在电脑上读串口日志信息量比开发板自带串口大很多。第二代码层面的错误日志一定要打全。Arduino固件里每一步WiFi连接、开始录音、开始上传、上传成功/失败都用Serial.println输出状态。Python后端更简单FastAPI的uvicorn本身就带请求日志但最好还是在每个处理步骤里加一条print这样ESP32S3上传到哪一步断了一眼就能看出来。第三不要一开始就追“智能”。先把按键触发版本跑通录下来的声音能被ASR识别成文字再谈大模型回答质量最后才考虑唤醒词、离线识别这些高级功能。我见过太多人在唤醒词上死磕两个星期结果语音识别那块从头到尾都没跑通过。做硬件项目最小可用版本的思路永远是最优解。这个内容后续还可以这样扩展给ESP32S3接上屏幕显示对话文本用Agent框架让语音助手具备调用工具的能力或者把整个链路容器化部署在NAS上。不过这些都是后话了先把第一版跑通再说。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价