拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ESP32-S3端云架构实战:打造稳定可迭代的AI陪伴设备

我身边常有人拿 ESP32-S3 问我说想做 AI 陪伴设备但不知道从哪下手。最典型的两条歪路一是打算在板子上硬塞一个能聊的大模型结果固件刚起来就 OOM连 WiFi 握手都过不去二是彻底放弃端侧逻辑把开发板当成一个“物理按键的网页版聊天输入框”一断网就全部瘫痪。我自己做这块设备时把重点放在“端云架构”的合理切分上ESP32-S3 在端侧负责音频、视觉、显示这类硬件交互云上跑大模型和 AI Agent 层负责复杂对话和长期记忆。这篇文章会从硬件选型、端侧固件结构、云侧服务、通信协议到 OTA 演进完整拉一遍我们的设计过程。适合手里有 ESP32-S3 开发板、想做一个真正稳定可迭代的 AI 硬件项目的开发者参考。1. 为什么锚定 ESP32-S3先看清这块芯片的真实边界1.1 与手机、树莓派和纯云方案对比它赢在哪里做 AI 硬件设备很多人会纠结到底用 ESP32-S3、树莓派还是直接调云 API。我的结论是ESP32-S3 不是万能的但在“陪伴设备”这个场景下它是性价比和工程复杂度的平衡点。手机方案看似现成但问题是它不是一个“独立设备”。你得处理 App 常驻后台、屏幕熄不熄、系统杀进程、蓝牙断连这些事做出来更像一个手机配件而不是陪伴机器人。树莓派性能确实强但功耗高、启动慢、结构大更麻烦的是掉电容易坏 SD 卡。纯云端方案则把设备的命运全押在网络和服务器上离线时连本地语音唤醒都做不了交互体验非常断裂。ESP32-S3 是双核 Xtensa LX7最高 240MHz带 512KB SRAM 和可选的 8MB PSRAM最关键是内置向量指令能跑轻量的神经网络推理。这意味着它可以做到随时待机、毫秒级唤醒、离线工作部分功能、体积小、功耗低。做一个桌面陪伴设备这个芯片是非常合理的选择。1.2 我最终确定的硬件清单与引脚规划说句实话ESP32-S3 的型号非常多选错了后续会很痛苦。我的建议是直接买带 8MB PSRAM 的版本因为摄像头帧缓存、音频缓冲、TTS 解码都极度依赖 PSRAM。没有 PSRAM 的版本做不了带摄像头的 AI 视觉项目。以下是这个项目的硬件清单模块型号接口作用主控ESP32-S3-WROOM-1 N16R8-4MB flash 8MB PSRAM麦克风INMP441I2S采集用户语音功放喇叭MAX98357A 3W 小喇叭I2S播放 TTS 语音摄像头OV2640DVP 并行接口拍照与图像上行显示屏ST7789 1.3 寸 IPSSPI显示表情与状态电池3.7V 18650/聚合物电池充电管理 IC移动使用电平转换3.3V-5V 模块-兼容部分外设引脚分配上我踩过不少坑最常见的坑就是 GPIO 冲突。比如 GPIO0 是 boot 引脚接了按键之后某些外设初始化会干扰启动。我最终稳定使用的引脚分配是I2S 麦克风SCKGPIO4WSGPIO5SDGPIO6I2S 功放BCLKGPIO15LRCGPIO16DINGPIO17ST7789 屏幕SCLKGPIO21MOSIGPIO22CSGPIO10DCGPIO11RSTGPIO12BLGPIO13OV2640 摄像头使用标准的 DVP 引脚组PCLKGPIO18XCLKGPIO19VSYNCGPIO38HREFGPIO39SDAGPIO40SCLGPIO41电池电压检测GPIO35接电阻分压1.3 功耗粗账陪伴设备的续航底线这块相对容易被忽略但我建议别等做完了才后悔。设备端功耗是架构层面的约束——如果上云唤醒词检测功耗太高整个“随时待命”的设计就会失效。实测下来这个设备在不同的运行状态下的功耗大概是状态电流对应场景Deep Sleep~150uA无活动休眠语音唤醒待机~30mAWakeNet 监听中屏幕亮WiFi 连接~90mA显示界面、云端在线持续录音上传~160mA对话中拍照上传~220mA视觉交互配 2000mAh 电池待机能撑 3~5 天持续对话大约 4~6 小时。如果你做的是桌面设备这个续航足够日常使用了。这里的心得是优先用 ESP-IDF 的 Power Management 框架它能在降频和保留实时响应之间自动切换。2. 端云分工的最先一刀哪些留在端侧哪些必须上云2.1 陪伴场景对延迟、隐私与成本的三角约束端云架构不是“能往云端丢就往云端丢”也不是“尽量在本地算完”。真正要看你设备的场景需求。AI 陪伴设备有三条绕不过的约束。第一是延迟。对话是强实时交互如果每次唤醒后要 3 秒才能听到回声用户马上会觉得这是个残次品。本地方可承担高实时性的部分把网络延时的不可控因素挡在关键路径之外。第二是隐私。陪伴设备会放在卧室、书桌上用户可能随时说什么敏感内容。如果所有声音都无脑传云端不仅用户体验差隐私合规也是个雷。最好的做法是端侧先做 VAD 和本地唤醒只有用户明确开始对话时语音才上行。第三是成本。大模型 API 调用不是免费的而陪伴场景是高频交互。如果每次设备随意说的一句话都要跑一轮大模型你的账单会被快速打爆。端侧用规则引擎过滤掉无意义的杂音云端只处理真正有意向的请求。2.2 最终的职责边界表我把系统划分为三层边界每一层都有明确职责层级职责典型实现端侧即时响应层唤醒词、LED/表情、按键、本地命令WakeNet、LVGL端侧感知层VAD、音频采集、拍照、姿态传感器I2S、DVP、IMU云侧智能层LLM 对话、工具调用、长期记忆、个性化FastAPI AI Agent云侧服务层TTS 生成、推送通知、任务调度流式 TTS、MQTT设备端永远不直接拼长文本大模型推理而是把“感知到的上下文”整理成简洁的事件发给云端。云端返回的也不是一长串文本而是结构化指令要说什么、要不要拍照、要不要执行某个工具。2.3 一次完整交互的时序链路如果读者想理解这套架构我建议先记住一条完整的时序链路。下面是我项目中一次普通对话的流程用户说“小伴小伴”。ESP32-S3 上的 WakeNet 识别到唤醒词拉高系统主频开始 VAD。用户继续说话VAD 检测到起始点ESP32-S3 开始采集 16kHz/16bit 单声道 PCM 音频。检测到语音结束点设备把这段音频数据编码后通过 MQTT/WebSocket 上传到云端。云端服务调用语音识别得到文本。AI Agent 层把这个文本和当前上下文交给大模型大模型决定是调用工具还是直接回复。如果回复是“我可以看看桌面”Agent 层下发一个“take_photo”指令。ESP32-S3 执行拍照将 JPEG 上传云上视觉模型理解图像内容再生成最终回复。云端 TTS 生成语音通过 WebSocket 返回音频流ESP32-S3 播放。这整个过程里设备端是“感知 执行”的角色云端是“理解 决策”的角色。边界清楚之后任何一层要升级都不需要推翻另外一层。3. 设备端软件让 ESP32-S3 同时扛住音频、图像与联网3.1 用 ESP-IDF 还是 Arduino我的选择与理由ESP32-S3 可以用 Arduino 框架快速上手也可以用乐鑫官方的 ESP-IDF 做深度开发。就 AI 陪伴设备这种多外设、多任务、需要精细内存控制的场景我更推荐 ESP-IDF。原因有三一是 ESP-IDF 对音频、摄像头、WiFi 这三大件有更完整的原生驱动二是它的内存管理能力更强可以用heap_caps_malloc指定把大块 buffer 放在 PSRAM避免 SRAM 不足三是 OTA、NVS、电源管理等关键能力在 ESP-IDF 中是第一公民Arduino 总是隔一层。当然如果你只是做原型验证Arduino 也可以。但我的项目已经跑到量产迭代阶段所以整个端侧固件都是基于 ESP-IDF v5.x 写的。3.2 音频子系统的实现细节音频是陪伴设备最核心的交互通道。ESP32-S3 自带两个 I2S 控制器可以同时跑音频输入和输出。我在输入侧使用 INMP441 MEMS 麦克风输出侧使用 MAX98357A D 类功放。输入流的典型配置是i2s_config_t i2s_in_config { .mode 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, };这里的关键参数是 DMA buffer。dma_buf_len 1024、dma_buf_count 8意味着总共 8 个 1024 帧的缓冲区。因为 16kHz 采样率下每个 buffer 约 64ms 音频8 个 buffer 可以提供约 512ms 的 FIFO 深度足以吸收 Wi-Fi 峰值延迟。如果 buffer 设置太小会发生 DMA overrun音频会断断续续如果过大端到端延迟又会增加。唤醒词检测我用的是乐鑫的 ESP-SR WakeNet。它可以在 ESP32-S3 上跑不需要很大的 RAM。唤醒之后才进入轻量级 VAD 循环只有检测到人说话才开始真正上传录音。这里有个容易忽略的细节不要直接把整段麦克风 PCM 数据推上云带宽和云成本都扛不住。16kHz/16bit 单声道是 32KB/s如果用户说 10 秒的话就是 320KB 数据。要在端侧做 VAD 切割只上传语音有效区间并且在靠近尾部加 300ms padding避免尾部字被切断。3.3 摄像头拍照与图像压缩上报设备的视觉能力我用 OV2640 实现。OV2640 是 200 万像素的 DVP 摄像头在 ESP32-S3 上要用esp32-camera驱动库。我在架构里特意把它设计成“按需拍照”而不是持续视频流。一来陪伴场景不需要实时视频二来持续视频流会占满上传带宽和云端的视觉处理成本。拍照触发有两种方式用户直接说“看看我这边”或者 AI Agent 判断当前场景需要图像信息。调用拍照时我先把帧缓冲区指向 PSRAMcamera_config_t config { .pin_pwdn -1, .pin_reset -1, .pin_xclk 19, .pin_sccb_sda 40, .pin_sccb_scl 41, .pin_d7 48, // ... 省略其他引脚 .frame_size FRAMESIZE_UXGA, .jpeg_quality 12, .fb_count 2, .grab_mode CAMERA_GRAB_WHEN_EMPTY, .fb_location CAMERA_FB_IN_PSRAM, };这里必须注意fb_location设为 PSRAM。UXGA 分辨率的 RGB565 帧 buffer 要占用 3MB 以上如果放到内部 SRAM 会直接崩溃。实际使用时我几乎不保存原始 RGB而是直接把 JPEG 数据打包传输到云端这样单帧只有 100KB 左右上传很快。3.4 任务划分与事件循环设计ESP32-S3 是双核架构我把它跑成“一个主控核 一个通信核”的分工模式核0跑 WiFi/MQTT/HTTP 网络栈。这个核上的任务允许阻塞因为网络操作天然有等待。核1跑音频、摄像头、显示以及主事件循环。这些任务必须保持低延迟不能因为网络抖动而卡顿。任务间通信不要用复杂的锁尽量用队列。比如音频采样任务把 PCM 数据块扔进队列上传任务从队列里取数据。队列深度设为 16如果队列满就丢最旧的数据块而不是直接阻塞录音这样能保证语音采集的连续性。屏幕上我显示的是一个简单的卡通表情用 ST7789 驱动。表情变化通过主事件循环接收云端指令来触发。注意 SPI 总线和 I2S 在同时工作时要避免同频干扰最好把屏幕刷新和音频数据搬移放在不同核上。4. 云端服务把“陪伴”组织成有状态的 AI Agent4.1 网关层设备接入与会话管理云端最外层是一个网关服务负责设备接入认证、WebSocket/MQTT 连接维护、会话管理。我这里用的是 Python FastAPI 写的一个轻量网关设备通过 MQTT 上报事件通过 WebSocket 接收流式 TTS 音频。每个设备有一个唯一 ID首次配网时从云端换取 token。后续所有请求头都带这个 token服务端校验后才允许建立长连接。设备上线后服务端会为它创建一个 session 对象session 里保存当前对话的上下文指针、用户偏好、最近操作历史。设备上行消息格式我设计得很精简{ device_id: desk_01, type: audio_upload, session_id: s_8f3a, duration_ms: 3200, audio: base64 }这里 audio 字段用的是 base64 编码的 PCM 或 Opus 数据。虽然 base64 会增加约 33% 的传输量但好处是 JSON 调试非常方便一个工具链就能通吃整个链路。如果你对功耗和流量极其敏感也可以改成二进制分帧协议不过初期调试成本会高很多。4.2 大模型服务的接入与参数调校网关收到语音后先调用 ASR语音识别把音频转文本然后把文本交给大模型服务。我的云端项目里大模型部分兼容 OpenAI 格式的 API所以可以灵活切换不同供应商哪个服务质量好、价格合适就换哪个。对话能力的核心提示词设计很关键。陪伴设备的大模型 system prompt 要短角色要明确还要规定输出格式。我们采用“行为指令 工具调用”的提示词结构你是一个桌面 AI 陪伴助手名字叫“小伴”。 你的职责 - 用简短、温暖、自然的语气和用户聊天 - 如果用户请求涉及当前环境调用 visual_understand 工具理解图像 - 如果用户请求涉及时间和任务调用 reminder 工具 - 不要长篇大论回复不超过 50 字 - 如果意识到用户情绪低落适当给出共情回复大模型的temperature我设在 0.7让它保留一点随机性和亲切感。max_tokens设成 256防止模型偶尔抽风生成一大段废话导致 TTS 都处理不过来。4.3 工具调用与结构化输出我强烈建议在陪伴设备这类场景中不要直接让大模型输出自然语言给 TTS而是先输出结构化指令再由服务端转成自然语言。原因是大模型直接生成的自然语言经常包含无法读出来的符号、重复词、语气词TTS 会很僵硬。我的做法是用 function calling 机制。大模型先决定调用哪个工具比如photo、reminder、weather工具执行完再把结果注入到下一轮 prompt。云上工具集的实现示例def execute_tool(name, args): if name take_photo: # 下发拍照指令到设备等待 JPEG 返回 photo device.capture_once() caption vision_model.describe(photo) return {photo: photo, caption: caption} elif name create_reminder: return reminder_service.create(args[text], args[time])工具调用的响应再喂回大模型生成最终用户可见的回复文本。这个过程虽然多了一次模型往返但换来的是输出质量的巨大提升。4.4 记忆与个性化陪伴感的来源如果设备每次对话都是“失忆”的那根本谈不上陪伴。我做了两层记忆短期会话记忆和长期用户画像。短期会话记忆保存在 Redis 里存最近 20 轮对话摘要每轮对话后由一个小模型把关键信息压缩成一句话。长期用户画像则存用户的偏好、作息、重要事件。比如用户说过“我周三早上有晨会”这个信息会被 extract 出来进入用户 profile。下次用户问“明天早上我有什么安排”模型就可以结合提醒事项直接回答。记忆的数据模型大概是{ user_id: u_123, preferences: {lights: nonono, voice: gentle}, facts: [ {content: 用户是前端工程师, ts: 1699999999}, {content: 用户有一只猫叫豆豆, ts: 1700000000} ], last_topic: 工作压力 }5. 端云通信协议与容错设备联网不只是连上 Wi-Fi5.1 MQTT、HTTP 与 WebSocket 在链路中的分工通信层是整个端云架构里最容易“看起来全通实际坑一堆”的部分。我在项目中用三种协议各管一段协议用途理由MQTT设备事件上报、状态心跳、云端下发指令轻量支持断线重连QoS 可靠WebSocket流式 TTS 音频回传低延迟双向流式适合音频HTTPS固件 OTA、配置拉取、一次性图片上传简单可靠有大文件传输能力MQTT 只承担小于 1KB 的控制消息比如“设备上线”“唤醒词触发”“拍照指令”。音频流和图像流不走 MQTT否则 QoS 重传会把整个链路堵死。5.2 消息协议设计让设备端不用理解自然语言设备端收到的云端指令必须足够结构化。我定义了一个action枚举设备端固件只需要对这个枚举做分发不需要理解自然语言{ action: speak, params: { text: 好的我来看一下你的桌面。, tts_url: https://..., duration_ms: 2300 } }常见的 action 有speak、take_photo、set_led、set_screen、start_upload、do_ota。设备端就是一个轻量的 action dispatcher收到什么执行什么。这样做的好处是云端升级对话逻辑时设备固件完全不用变甚至可以在不重新烧录的情况下新增一种云端临时指令。5.3 断网、弱网与重连一套降级策略设备永远在线是一个美好的愿望真实环境里 5G/Wi-Fi 总会抖。我设计了三档降级策略完全在线全功能使用。网络闪烁语音能唤醒本地显示“网络不稳定”云端请求正常发出但超时时间降为 3 秒如果超时设备本地播放“网络不太好我等下再回答你”。完全离线设备进入本地回退模式只能播放一些预置音频、显示时钟、充当普通摆件。绝不尝试反复重试导致死机。重连逻辑有一个容易被忽视的细节Wi-Fi 断线后不要立即重连。ESP32-S3 底层自带重连机制如果应用层再叠加一层重连会出现两个线程同时抢连接的情况。我的做法是应用层只监听WIFI_EVENT_STA_DISCONNECTED一旦触发就停止所有上行任务清理 socket5 秒后手动触发一次esp_wifi_connect()。重连成功后再按顺序恢复 MQTT、WebSocket。5.4 安全与合规的做法设备与云端之间我用 TLS 加密。因为 ESP32-S3 的硬件加速支持 AES 和 SHATLS 握手并不算太慢实测大概 1~2 秒完全能接受。设备端存储 token 用 NVS 分区加密不能明文存放在 flash 里。用户语音数据上传时网关层只保留匿名化的 session id不直接关联真实手机号。用户注销时云端要能一键删除该用户的所有语音文件和记忆 profile。这些能力要在架构设计时留好接口不要等上线后被要求整改时才补。6. 可持续演进OTA、配置与接口版本的工程化6.1 ESP32-S3 的双 OTA 分区与回滚机制“可持续演进”是我这个项目给自己定的硬指标设备必须能远程升级固件并且升级失败后能自动回滚。ESP32-S3 支持 OTA但前提是分区表要专门设计。我的分区表partitions.csv定义# Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x5000 otadata, data, ota, 0xe000, 0x2000 app0, app, ota_0, 0x10000, 0x200000 app1, app, ota_1, 0x210000, 0x200000 storage, data, fat, 0x410000, 0x1f0000这里关键是app0和app1两个 OTA 分区各占 2MB。当前运行的固件在 app0新固件下载到 app1校验通过后重启bootloader 切换到 app1。如果新固件启动后 30 秒内没有上报“启动成功”系统会触发 watchdog 回滚到上一个版本。这等于给设备装了一个安全降落伞我可以放心大胆地推送新功能。6.2 云端 API 的版本演进策略固件能升云端接口也得能平滑演进。我踩过最大的坑是某个接口返回的 JSON 加了新字段结果老固件不识别直接解析报错。从那之后我所有的上下行 JSON 都带schema_version字段。设备端解析器碰到不认识的版本会忽略未知字段只解析自己认识的字段。云端加字段永远算 minor change不允许破坏老字段语义。设备状态上报也一样。device_state结构体初期只有battery、wifi_rssi后来加了temperature、uptime。只要新字段是可选的老固件就不会出问题。如果实在要做 breaking change我宁愿新增一个 action而不是改旧 action 的语义。6.3 数据回流与模型迭代闭环一个端云系统的价值在于能不断变聪明。设备每次交互的匿名化数据会回流到云端数据湖包括唤醒成功率、VAD 误触发率、云端 ASR 置信度、用户明确表达的“满意/不满意”情绪、工具调用失败次数。这些数据我每两周产出一份指标报表用来指导三件事调整唤醒词灵敏度如果 VAD 误触发太多就提高置信度阈值。优化提示词如果用户频繁说“不是你问的这个”说明大模型的追问方式有问题。新增功能如果用户在特定时间段密集问某一类问题就考虑做成快捷指令。整个闭环让设备不只是“能用”而是“越用越准”。这也是我说的“可持续演进”的真正含义。7. 最后说几个我们踩过的坑7.1 音频失真的根源竟然在电源最开始调试音频时只要音量开大喇叭里就有“滋滋”的底噪。用示波器一看I2S 数据线上的信号在喇叭峰值时产生毛刺。根因是 MAX98357A 瞬时电流拉低了 3.3V 电源轨导致 I2S 逻辑电平抖动。解决方案很简单功放电源单独走 5V 供电并且在电源输入端加 470uF 电容。这个问题不遇到真想不到所以建议大家在画板或接线时功放供电从一开始就单独一路。7.2 摄像头 OOM 崩溃不是代码问题是 buffer 位置问题运行一个小时左右设备偶尔会重启。查 log 发现是esp_camera_fb_get()返回 NULL。排查半天发现我把 frame buffer 分配在了内部 RAM虽然设置了fb_count2但 UXGA 的 JPEG 帧在复杂场景下可能瞬时膨胀直接把内部 SRAM 挤爆。把fb_location改成 PSRAM 后问题彻底消失。另外一个隐藏问题grab_mode要选CAMERA_GRAB_WHEN_EMPTY如果选CAMERA_GRAB_LATEST在高分辨率下 DMA 会持续占用带宽导致 WiFi 吞吐量下降。7.3 WiFi 中断和音频 DMA 抢 CPU在双核上WiFi 协议栈的中断频率非常高尤其是传输大数据包时。如果音频 DMA 中断也跑在同一核会出现音频卡顿。解决方法是把 WiFi 固定在核0音频搬移循环固定在核1并通过IRAM_ATTR标记中断回调。ESP32-S3 两个核是独立的中断控制器合理分配核间任务后整体稳定度提升了一个量级。7.4 云端超时导致的假死现象设备初期经常出现“喊它没反应”的假死状态但 RSSI 显示 WiFi 正常。查 log 发现设备在等待云端返回 TTS 音频时线程被recv()阻塞而这期间唤醒词回调无法执行。后来我加了一个 8 秒的看门狗定时器如果 WebSocket 超过 8 秒没有收到任何数据就强制断开重连。核心教训是任何阻塞调用都不能是永久阻塞的硬性超时是设备稳定性的红线。7.5 MQTT QoS 的误用说实话项目初期我把所有下行指令都设成 QoS 2觉得这样最可靠。结果是网络一抖动消息重传堆积设备端队列直接堵死。后来我把 MQTT 分成两个 topiccmd/control用 QoS 1适合拍照、播放这类要可靠送达的指令event/report用 QoS 0因为状态上报可以丢弃下一次上报会覆盖。这样既保证了关键指令的可靠性又避免了队列拥塞。这套端云架构从原型到现在已经稳定跑了几个月。我个人最大的体会是做 AI 硬件真正难的不是把某个模型跑在某块板子上而是把“设备端感知-云侧决策-设备端执行”这条链路做成一个可以持续升级的闭环。ESP32-S3 不是最强的算力平台但它让我用很低的功耗和成本把云侧那些大模型的智能带进了真实桌面。如果你也在做类似项目建议从最小的音频交互闭环开始先把唤醒、录音、上传、回复这条路跑通再往上面加视觉和记忆。每一步都保持端云边界清晰未来演进就不会被一段写死的逻辑卡住。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门