四博 AI 智能音箱 4G S3 版本技术方案
下面这版更偏技术方案 + 原型开发说明 + 可落地代码骨架,适合放到方案书、技术推广文档、客户交流材料中。代码以ESP-IDF / ESP32-S3风格写,重点突出四博方案的工程结构、联网切换、远场拾音、实时打断、MCP 扩展、屏幕异显和客户系统接入。
四博 AI 智能音箱 4G S3 版本技术方案
1. 方案概述
四博 AI 智能音箱 4G S3 版本,是基于ESP32-S3 主控平台 + 远场语音前端 + Wi-Fi / 蓝牙 / 4G 三模联网 + 屏幕显示 + AI 大模型云端服务 + MCP 扩展协议构建的一套 AI 语音硬件方案。
该方案面向 AI 音箱、AI 学习机、AI 故事机、AI 桌宠、AI 玩具、智能中控、语音控制终端等产品,可实现:
1. 5 米范围远距离拾音、唤醒、打断和对话 2. 空气炸锅、风扇、厨房电器等高噪音环境下稳定响应 3. 支持唤醒词打断、实时打断、AI 播报中插话 4. 支持修改唤醒词,适合品牌定制 5. 支持 Wi-Fi、蓝牙、4G 三种联网方式 6. 支持一个屏幕、两个屏幕、双屏异显 7. 支持四博小助手配网、知识库、声音克隆、自建大模型 8. 支持 MCP / UART 接入客户自己的控制系统 9. 基于 ESP32-S3,方便客户进行二次开发《四博智联 AI 开发宝典》中,四博 AI-Speaker 开发板已经明确支持“四博小助手”小程序、克隆、知识库、自建大模型和 MCP;同时文档中也给出了 S3 AI-Speaker 的 ESP-IDF 工程配置、编译、烧录和 BluFi 配网流程,可作为本方案的软件基础。
2. 系统总体架构
┌───────────────────────────────────────────────┐ │ 客户业务系统 / AI 云服务 │ │ │ │ ASR 语音识别 │ │ LLM 大模型 │ │ TTS 语音合成 │ │ 知识库 / 题库 / 内容平台 │ │ 设备管理 / OTA / 会员系统 │ └──────────────────────▲────────────────────────┘ │ │ WebSocket / HTTPS / MQTT │ ┌──────────────────────┴────────────────────────┐ │ 四博 AI 智能音箱 4G S3 设备端 │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ ESP32-S3 应用主控层 │ │ │ │ - FreeRTOS 任务调度 │ │ │ │ - 音频采集 / 播放控制 │ │ │ │ - 屏幕 UI / LVGL │ │ │ │ - 网络状态管理 │ │ │ │ - WebSocket AI 会话 │ │ │ │ - OTA 升级 │ │ │ │ - MCP / UART 扩展 │ │ │ └─────────────────────────────────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │ │ Wi-Fi 联网 │ │ 蓝牙联网/配网 │ │ 4G 联网 │ │ │ └─────────────┘ └─────────────┘ └──────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │ │ 远场麦克风 │ │ 语音唤醒/打断 │ │ I2S 功放 │ │ │ └─────────────┘ └─────────────┘ └──────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │ │ 主屏显示 │ │ 副屏表情显示 │ │ 外设控制 │ │ │ └─────────────┘ └─────────────┘ └──────────┘ │ └───────────────────────────────────────────────┘3. 硬件方案定义
3.1 推荐硬件组成
主控平台: - ESP32-S3 / 四博 S3R8 系列方案 联网方式: - Wi-Fi - 蓝牙上网 / 蓝牙配网 - 4G 模块 语音输入: - 远场麦克风 - 可选双麦 / 阵列麦克风 - 离线语音前端 / VB6824 类语音芯片 语音输出: - I2S Codec - 功放 - 4Ω 3W 喇叭 显示部分: - 单屏 LCD - 双屏 LCD - 支持双屏异显 扩展接口: - UART - I2C - SPI - GPIO - USB - 电池接口 - 充电管理《开发宝典》中 AI-C5 方案已包含 4G 模组、喇叭、咪头、电池和屏幕等硬件配置,并支持 Wi-Fi 与 4G 模式切换,这部分可以作为 4G 版本智能音箱的硬件设计参考。
4. 软件任务架构
设备端建议按 FreeRTOS 多任务方式组织:
app_main ├── board_init_task ├── network_manager_task ├── audio_capture_task ├── voice_wakeup_task ├── ai_session_task ├── tts_player_task ├── display_task ├── mcp_uart_task ├── ota_task └── key_event_task4.1 FreeRTOS 任务划分
typedef enum { SYS_EVT_NONE = 0, SYS_EVT_NET_CONNECTED, SYS_EVT_NET_DISCONNECTED, SYS_EVT_WAKEUP_DETECTED, SYS_EVT_REALTIME_INTERRUPT, SYS_EVT_ASR_START, SYS_EVT_ASR_END, SYS_EVT_TTS_START, SYS_EVT_TTS_END, SYS_EVT_MCP_FRAME, SYS_EVT_OTA_START, SYS_EVT_OTA_DONE, } sys_event_t; typedef enum { AI_STATE_BOOTING = 0, AI_STATE_NET_CONFIG, AI_STATE_CONNECTING, AI_STATE_IDLE, AI_STATE_LISTENING, AI_STATE_THINKING, AI_STATE_SPEAKING, AI_STATE_INTERRUPTED, AI_STATE_UPGRADING, } ai_state_t; static QueueHandle_t g_sys_evt_queue; static ai_state_t g_ai_state = AI_STATE_BOOTING; static void post_sys_event(sys_event_t evt) { if (g_sys_evt_queue) { xQueueSend(g_sys_evt_queue, &evt, 0); } }5. 主程序代码骨架
#include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "esp_log.h" #include "nvs_flash.h" static const char *TAG = "SIBO_AI_SPEAKER"; void board_power_init(void); void board_gpio_init(void); void display_init(void); void audio_codec_init(void); void microphone_init(void); void speaker_init(void); void voice_frontend_init(void); void network_manager_init(void); void ai_protocol_init(void); void mcp_uart_init(void); void ota_service_init(void); void network_manager_task(void *arg); void ai_session_task(void *arg); void audio_capture_task(void *arg); void tts_player_task(void *arg); void display_task(void *arg); void mcp_uart_task(void *arg); void key_event_task(void *arg); void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret != ESP_OK) { ESP_LOGW(TAG, "NVS 初始化失败,尝试擦除后重新初始化"); nvs_flash_erase(); ESP_ERROR_CHECK(nvs_flash_init()); } ESP_LOGI(TAG, "四博 AI 智能音箱 4G S3 版本启动"); g_sys_evt_queue = xQueueCreate(16, sizeof(sys_event_t)); board_power_init(); board_gpio_init(); display_init(); audio_codec_init(); microphone_init(); speaker_init(); voice_frontend_init(); network_manager_init(); ai_protocol_init(); mcp_uart_init(); ota_service_init(); xTaskCreate(network_manager_task, "network_manager", 4096, NULL, 6, NULL); xTaskCreate(audio_capture_task, "audio_capture", 8192, NULL, 7, NULL); xTaskCreate(ai_session_task, "ai_session", 8192, NULL, 6, NULL); xTaskCreate(tts_player_task, "tts_player", 4096, NULL, 5, NULL); xTaskCreate(display_task, "display_task", 4096, NULL, 4, NULL); xTaskCreate(mcp_uart_task, "mcp_uart", 4096, NULL, 5, NULL); xTaskCreate(key_event_task, "key_event", 2048, NULL, 4, NULL); ESP_LOGI(TAG, "系统任务创建完成"); }6. 三模联网设计
6.1 联网状态机
开机 ↓ 读取上一次联网模式 ↓ 优先尝试 Wi-Fi ↓ 失败 尝试蓝牙上网 / 蓝牙配网 ↓ 失败 尝试 4G 模块 ↓ 成功 建立 WebSocket AI 会话 ↓ 进入 AI 待机状态6.2 网络模式定义
typedef enum { NET_MODE_WIFI = 0, NET_MODE_BT, NET_MODE_4G, } net_mode_t; typedef enum { NET_STATUS_IDLE = 0, NET_STATUS_CONNECTING, NET_STATUS_CONNECTED, NET_STATUS_FAILED, } net_status_t; typedef struct { net_mode_t mode; net_status_t status; int retry; bool ip_ready; bool ai_connected; } net_ctx_t; static net_ctx_t g_net_ctx = { .mode = NET_MODE_WIFI, .status = NET_STATUS_IDLE, .retry = 0, .ip_ready = false, .ai_connected = false, };6.3 网络切换逻辑
static void net_switch_next_mode(void) { if (g_net_ctx.mode == NET_MODE_WIFI) { g_net_ctx.mode = NET_MODE_BT; ESP_LOGI("NET", "切换到蓝牙上网模式"); } else if (g_net_ctx.mode == NET_MODE_BT) { g_net_ctx.mode = NET_MODE_4G; ESP_LOGI("NET", "切换到 4G 上网模式"); } else { g_net_ctx.mode = NET_MODE_WIFI; ESP_LOGI("NET", "切换到 Wi-Fi 上网模式"); } g_net_ctx.status = NET_STATUS_IDLE; g_net_ctx.retry = 0; g_net_ctx.ip_ready = false; g_net_ctx.ai_connected = false; }6.4 网络管理任务
static bool wifi_start_connect(void); static bool bt_network_start(void); static bool modem_4g_start(void); static bool ai_ws_connect(void); static bool network_is_ip_ready(void); void network_manager_task(void *arg) { while (1) { switch (g_net_ctx.status) { case NET_STATUS_IDLE: g_net_ctx.status = NET_STATUS_CONNECTING; if (g_net_ctx.mode == NET_MODE_WIFI) { ESP_LOGI("NET", "开始 Wi-Fi 联网"); wifi_start_connect(); } else if (g_net_ctx.mode == NET_MODE_BT) { ESP_LOGI("NET", "开始蓝牙上网"); bt_network_start(); } else { ESP_LOGI("NET", "开始 4G 联网"); modem_4g_start(); } break; case NET_STATUS_CONNECTING: if (network_is_ip_ready()) { ESP_LOGI("NET", "网络已获取 IP"); g_net_ctx.status = NET_STATUS_CONNECTED; g_net_ctx.ip_ready = true; post_sys_event(SYS_EVT_NET_CONNECTED); } else { g_net_ctx.retry++; if (g_net_ctx.retry > 15) { ESP_LOGW("NET", "联网超时"); g_net_ctx.status = NET_STATUS_FAILED; } } break; case NET_STATUS_CONNECTED: if (!g_net_ctx.ai_connected) { ESP_LOGI("NET", "开始连接 AI WebSocket"); if (ai_ws_connect()) { g_net_ctx.ai_connected = true; ESP_LOGI("NET", "AI 服务连接成功"); } else { ESP_LOGW("NET", "AI 服务连接失败"); g_net_ctx.status = NET_STATUS_FAILED; } } break; case NET_STATUS_FAILED: ESP_LOGW("NET", "当前网络模式失败,准备切换"); post_sys_event(SYS_EVT_NET_DISCONNECTED); net_switch_next_mode(); break; } vTaskDelay(pdMS_TO_TICKS(1000)); } }7. 4G 模块 AT 指令控制
7.1 UART 初始化
#include "driver/uart.h" #define MODEM_UART_PORT UART_NUM_1 #define MODEM_UART_TX_PIN 17 #define MODEM_UART_RX_PIN 18 #define MODEM_UART_BAUD 115200 #define MODEM_BUF_SIZE 1024 static void modem_uart_init(void) { uart_config_t uart_config = { .baud_rate = MODEM_UART_BAUD, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, }; uart_driver_install(MODEM_UART_PORT, MODEM_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(MODEM_UART_PORT, &uart_config); uart_set_pin(MODEM_UART_PORT, MODEM_UART_TX_PIN, MODEM_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); ESP_LOGI("4G", "4G UART 初始化完成"); }7.2 AT 指令发送与等待
static bool modem_send_cmd_wait(const char *cmd, const char *expect, int timeout_ms) { uint8_t rxbuf[MODEM_BUF_SIZE] = {0}; uart_flush(MODEM_UART_PORT); uart_write_bytes(MODEM_UART_PORT, cmd, strlen(cmd)); int len = uart_read_bytes( MODEM_UART_PORT, rxbuf, sizeof(rxbuf) - 1, pdMS_TO_TICKS(timeout_ms) ); if (len <= 0) { ESP_LOGW("4G", "AT 无响应: %s", cmd); return false; } rxbuf[len] = 0; ESP_LOGI("4G", "AT 返回: %s", (char *)rxbuf); if (strstr((char *)rxbuf, expect)) { return true; } return false; }7.3 4G 模块就绪检测
static bool modem_4g_check_ready(void) { if (!modem_send_cmd_wait("AT\r\n", "OK", 1000)) { ESP_LOGE("4G", "4G 模块无响应"); return false; } modem_send_cmd_wait("ATE0\r\n", "OK", 1000); if (!modem_send_cmd_wait("AT+CPIN?\r\n", "READY", 3000)) { ESP_LOGE("4G", "SIM 卡未就绪"); return false; } modem_send_cmd_wait("AT+CSQ\r\n", "OK", 1000); if (!modem_send_cmd_wait("AT+CREG?\r\n", "OK", 1000)) { ESP_LOGW("4G", "查询注册状态失败"); } if (!modem_send_cmd_wait("AT+CGATT?\r\n", "+CGATT: 1", 3000)) { ESP_LOGE("4G", "4G 网络未附着"); return false; } ESP_LOGI("4G", "4G 网络已就绪"); return true; }7.4 4G 联网入口
static bool modem_4g_start(void) { modem_uart_init(); if (!modem_4g_check_ready()) { g_net_ctx.status = NET_STATUS_FAILED; return false; } /* * 实际项目中这里有两种实现: * 1. 使用 4G 模块内部 TCP/WebSocket/HTTP AT 指令 * 2. 使用 PPP/ECM,把 4G 模块作为 ESP-IDF 网络接口 * * 推荐量产方案: * 如果项目已有 WebSocket/HTTP 代码,优先做 PPP/ECM, * 这样上层 AI 会话层不用区分 Wi-Fi 还是 4G。 */ g_net_ctx.status = NET_STATUS_CONNECTED; g_net_ctx.ip_ready = true; return true; }8. 语音前端与实时打断
8.1 AI 状态机
static void ai_set_state(ai_state_t state) { g_ai_state = state; switch (state) { case AI_STATE_BOOTING: ESP_LOGI("AI", "状态:启动中"); display_show_status("启动中"); break; case AI_STATE_NET_CONFIG: ESP_LOGI("AI", "状态:配网中"); display_show_status("请使用小程序配网"); break; case AI_STATE_CONNECTING: ESP_LOGI("AI", "状态:联网中"); display_show_status("联网中"); break; case AI_STATE_IDLE: ESP_LOGI("AI", "状态:空闲"); display_show_status("你好小智"); display_show_expression("idle"); break; case AI_STATE_LISTENING: ESP_LOGI("AI", "状态:聆听中"); display_show_status("正在聆听"); display_show_expression("listening"); break; case AI_STATE_THINKING: ESP_LOGI("AI", "状态:思考中"); display_show_status("正在思考"); display_show_expression("thinking"); break; case AI_STATE_SPEAKING: ESP_LOGI("AI", "状态:说话中"); display_show_status("正在回答"); display_show_expression("speaking"); break; case AI_STATE_INTERRUPTED: ESP_LOGI("AI", "状态:已打断"); display_show_status("已打断"); display_show_expression("interrupt"); break; case AI_STATE_UPGRADING: ESP_LOGI("AI", "状态:升级中"); display_show_status("升级中"); break; } }8.2 唤醒词打断逻辑
static bool g_tts_playing = false; static bool g_realtime_interrupt_enable = true; void voice_on_wakeup_detected(void) { ESP_LOGI("VOICE", "检测到唤醒词"); if (g_ai_state == AI_STATE_SPEAKING && g_tts_playing) { ESP_LOGI("VOICE", "AI 播报中检测到唤醒词,执行唤醒词打断"); audio_player_stop(); ai_ws_send_interrupt(); g_tts_playing = false; ai_set_state(AI_STATE_INTERRUPTED); vTaskDelay(pdMS_TO_TICKS(100)); } ai_set_state(AI_STATE_LISTENING); ai_ws_send_listen_start(); audio_capture_start(); }8.3 实时打断逻辑
void voice_on_realtime_speech_detected(void) { if (!g_realtime_interrupt_enable) { return; } if (g_ai_state == AI_STATE_SPEAKING && g_tts_playing) { ESP_LOGI("VOICE", "检测到用户插话,执行实时打断"); audio_player_stop(); ai_ws_send_interrupt(); g_tts_playing = false; ai_set_state(AI_STATE_INTERRUPTED); vTaskDelay(pdMS_TO_TICKS(100)); ai_set_state(AI_STATE_LISTENING); audio_capture_start(); ai_ws_send_listen_start(); } }8.4 高噪音场景模式
针对空气炸锅、抽油烟机、风扇等高噪音环境,可以增加场景模式:
typedef enum { NOISE_MODE_NORMAL = 0, NOISE_MODE_KITCHEN, NOISE_MODE_FAN, NOISE_MODE_MUSIC, } noise_mode_t; static noise_mode_t g_noise_mode = NOISE_MODE_NORMAL; void audio_set_noise_mode(noise_mode_t mode) { g_noise_mode = mode; switch (mode) { case NOISE_MODE_NORMAL: ESP_LOGI("AUDIO", "普通环境模式"); voice_frontend_set_agc_level(1); voice_frontend_set_vad_threshold(40); break; case NOISE_MODE_KITCHEN: ESP_LOGI("AUDIO", "厨房高噪音模式"); voice_frontend_set_agc_level(2); voice_frontend_set_vad_threshold(65); voice_frontend_enable_noise_suppression(true); break; case NOISE_MODE_FAN: ESP_LOGI("AUDIO", "风扇噪音模式"); voice_frontend_set_agc_level(2); voice_frontend_set_vad_threshold(60); voice_frontend_enable_noise_suppression(true); break; case NOISE_MODE_MUSIC: ESP_LOGI("AUDIO", "背景音乐模式"); voice_frontend_set_agc_level(1); voice_frontend_set_vad_threshold(70); voice_frontend_enable_aec(true); break; } }9. AI WebSocket 会话层
9.1 会话事件
typedef enum { AI_WS_EVT_CONNECTED = 0, AI_WS_EVT_DISCONNECTED, AI_WS_EVT_ASR_TEXT, AI_WS_EVT_LLM_TEXT, AI_WS_EVT_TTS_AUDIO, AI_WS_EVT_TTS_DONE, AI_WS_EVT_ERROR, } ai_ws_evt_t;9.2 WebSocket 初始化骨架
#include "esp_websocket_client.h" static esp_websocket_client_handle_t g_ws = NULL; static void ai_ws_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: ESP_LOGI("AI_WS", "WebSocket 已连接"); ai_set_state(AI_STATE_IDLE); break; case WEBSOCKET_EVENT_DISCONNECTED: ESP_LOGW("AI_WS", "WebSocket 已断开"); ai_set_state(AI_STATE_CONNECTING); break; case WEBSOCKET_EVENT_DATA: ESP_LOGI("AI_WS", "收到 AI 数据,长度=%d", data->data_len); ai_ws_parse_message(data->data_ptr, data->data_len); break; case WEBSOCKET_EVENT_ERROR: ESP_LOGE("AI_WS", "WebSocket 错误"); break; } } static bool ai_ws_connect(void) { esp_websocket_client_config_t ws_cfg = { .uri = "wss://your-ai-server.example.com/xiaozhi/v1/", .reconnect_timeout_ms = 5000, .network_timeout_ms = 10000, }; g_ws = esp_websocket_client_init(&ws_cfg); if (!g_ws) { return false; } esp_websocket_register_events( g_ws, WEBSOCKET_EVENT_ANY, ai_ws_event_handler, NULL ); esp_websocket_client_start(g_ws); return true; }9.3 发送监听开始 / 打断事件
static void ai_ws_send_json(const char *json) { if (!g_ws || !esp_websocket_client_is_connected(g_ws)) { ESP_LOGW("AI_WS", "WebSocket 未连接,发送失败"); return; } esp_websocket_client_send_text(g_ws, json, strlen(json), portMAX_DELAY); } void ai_ws_send_listen_start(void) { ai_ws_send_json( "{" "\"type\":\"listen\"," "\"state\":\"start\"" "}" ); } void ai_ws_send_listen_stop(void) { ai_ws_send_json( "{" "\"type\":\"listen\"," "\"state\":\"stop\"" "}" ); } void ai_ws_send_interrupt(void) { ai_ws_send_json( "{" "\"type\":\"interrupt\"," "\"reason\":\"barge_in\"" "}" ); }9.4 发送音频数据
void ai_ws_send_audio_frame(const uint8_t *audio, size_t len) { if (!g_ws || !esp_websocket_client_is_connected(g_ws)) { return; } esp_websocket_client_send_bin( g_ws, (const char *)audio, len, portMAX_DELAY ); }10. 音频采集任务
#define AUDIO_FRAME_SIZE 640 static bool g_audio_capture_running = false; void audio_capture_start(void) { g_audio_capture_running = true; } void audio_capture_stop(void) { g_audio_capture_running = false; } void audio_capture_task(void *arg) { uint8_t audio_frame[AUDIO_FRAME_SIZE]; while (1) { if (!g_audio_capture_running) { vTaskDelay(pdMS_TO_TICKS(20)); continue; } int len = microphone_read(audio_frame, sizeof(audio_frame)); if (len > 0) { /* * 可在此处加入: * 1. AEC * 2. AGC * 3. NS 降噪 * 4. VAD 端点检测 * 5. Opus 编码 */ ai_ws_send_audio_frame(audio_frame, len); } if (voice_frontend_is_speech_end()) { ESP_LOGI("AUDIO", "检测到语音结束"); audio_capture_stop(); ai_ws_send_listen_stop(); ai_set_state(AI_STATE_THINKING); } vTaskDelay(pdMS_TO_TICKS(10)); } }11. TTS 播放任务
typedef struct { uint8_t *data; size_t len; } tts_audio_packet_t; static QueueHandle_t g_tts_queue; void tts_player_init(void) { g_tts_queue = xQueueCreate(8, sizeof(tts_audio_packet_t)); } void tts_enqueue_audio(uint8_t *data, size_t len) { tts_audio_packet_t pkt = { .data = data, .len = len, }; xQueueSend(g_tts_queue, &pkt, portMAX_DELAY); } void tts_player_task(void *arg) { tts_audio_packet_t pkt; while (1) { if (xQueueReceive(g_tts_queue, &pkt, portMAX_DELAY)) { if (!g_tts_playing) { g_tts_playing = true; ai_set_state(AI_STATE_SPEAKING); } speaker_write(pkt.data, pkt.len); free(pkt.data); } } }AI 返回 TTS 结束时:
void ai_on_tts_done(void) { ESP_LOGI("TTS", "TTS 播放结束"); g_tts_playing = false; ai_set_state(AI_STATE_IDLE); }12. MCP / UART 扩展协议
《开发宝典》中,四博 AT+MCP 协议通过 UART 实现语义到控制指令的映射,标准串口参数为115200、8N1、无校验、1 位停止位。模块收到合法 AT 指令后返回 OK;通过AT+ADDMCP可把自然语言映射成 MCU 可执行的二进制控制帧。
12.1 MCP 场景设计
语音:“切换到 4G 上网” 动作:ESP32-S3 切换 4G 网络模式 语音:“打开厨房模式” 动作:启用高噪声拾音参数 语音:“声音调到 80” 动作:设置音量为 80 语音:“关闭屏幕” 动作:LCD 背光关闭 语音:“打开副屏表情” 动作:副屏显示 AI 表情 语音:“开始配网” 动作:进入 BluFi / SoftAP 配网模式12.2 MCP UART 初始化
#define MCP_UART_PORT UART_NUM_2 #define MCP_UART_TX_PIN 41 #define MCP_UART_RX_PIN 42 #define MCP_UART_BAUD 115200 #define MCP_RX_BUF_SIZE 512 static void mcp_uart_init(void) { uart_config_t uart_config = { .baud_rate = MCP_UART_BAUD, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, }; uart_driver_install(MCP_UART_PORT, MCP_RX_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(MCP_UART_PORT, &uart_config); uart_set_pin(MCP_UART_PORT, MCP_UART_TX_PIN, MCP_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); ESP_LOGI("MCP", "MCP UART 初始化完成"); }12.3 发送 AT 指令
static void mcp_send_line(const char *line) { uart_write_bytes(MCP_UART_PORT, line, strlen(line)); } static void mcp_register_commands(void) { mcp_send_line("AT\r\n"); // 音量控制,返回一个音量参数 V mcp_send_line( "AT+ADDMCP=1,set_volume,设置音箱音量,F1,1,V\r\n" ); // 厨房高噪声模式,固定返回命令 mcp_send_line( "AT+ADDMCP=0,set_kitchen_mode,打开厨房高噪音模式,2,F2,01\r\n" ); // 网络切换,N=1 WiFi,N=2 蓝牙,N=3 4G mcp_send_line( "AT+ADDMCP=1,switch_network,切换联网方式,F3,1,N\r\n" ); // 屏幕亮度 mcp_send_line( "AT+ADDMCP=1,set_brightness,设置屏幕亮度,F4,1,B\r\n" ); // 表情切换 mcp_send_line( "AT+ADDMCP=1,set_expression,设置副屏表情,F5,1,E\r\n" ); // 进入配网模式 mcp_send_line( "AT+ADDMCP=0,start_config,开始配网,2,F6,01\r\n" ); }12.4 MCP 帧格式解析
《开发宝典》中 MCP 设备帧格式为:
0x55 0xAA LEN CMD DATA... 0xAA 0x55代码解析:
typedef struct { uint8_t len; uint8_t cmd; uint8_t data[32]; } mcp_frame_t; static bool mcp_parse_frame(const uint8_t *buf, int len, mcp_frame_t *out) { if (len < 5) { return false; } if (buf[0] != 0x55 || buf[1] != 0xAA) { return false; } uint8_t payload_len = buf[2]; if (len < payload_len + 4) { return false; } if (buf[payload_len + 2] != 0xAA || buf[payload_len + 3] != 0x55) { return false; } out->len = payload_len; out->cmd = buf[3]; int data_len = payload_len - 1; if (data_len > 0) { memcpy(out->data, &buf[4], data_len); } return true; }12.5 MCP 指令处理
static void mcp_handle_frame(const mcp_frame_t *frame) { switch (frame->cmd) { case 0xF1: ESP_LOGI("MCP", "设置音量: %d", frame->data[0]); audio_set_volume(frame->data[0]); display_show_status("音量已调整"); break; case 0xF2: ESP_LOGI("MCP", "打开厨房高噪声模式"); audio_set_noise_mode(NOISE_MODE_KITCHEN); display_show_status("厨房模式"); break; case 0xF3: ESP_LOGI("MCP", "切换网络: %d", frame->data[0]); if (frame->data[0] == 1) { net_set_mode(NET_MODE_WIFI); } else if (frame->data[0] == 2) { net_set_mode(NET_MODE_BT); } else if (frame->data[0] == 3) { net_set_mode(NET_MODE_4G); } break; case 0xF4: ESP_LOGI("MCP", "设置屏幕亮度: %d", frame->data[0]); display_set_brightness(frame->data[0]); break; case 0xF5: ESP_LOGI("MCP", "设置副屏表情: %d", frame->data[0]); display_set_expression_by_id(frame->data[0]); break; case 0xF6: ESP_LOGI("MCP", "进入配网模式"); ai_set_state(AI_STATE_NET_CONFIG); start_blufi_config(); break; case 0xFC: /* * 开发宝典中提到: * 如果模块发出 55 AA 01 FC AA 55, * MCU 需要重启 AI 模组并重新发送 MCP 映射。 */ ESP_LOGW("MCP", "AI 模组请求恢复,重启并重发 MCP"); ai_module_reset(); vTaskDelay(pdMS_TO_TICKS(1000)); mcp_register_commands(); break; default: ESP_LOGW("MCP", "未知 MCP CMD: 0x%02X", frame->cmd); break; } }12.6 MCP UART 任务
void mcp_uart_task(void *arg) { uint8_t rxbuf[128]; mcp_frame_t frame; mcp_register_commands(); while (1) { int len = uart_read_bytes( MCP_UART_PORT, rxbuf, sizeof(rxbuf), pdMS_TO_TICKS(100) ); if (len > 0) { if (mcp_parse_frame(rxbuf, len, &frame)) { mcp_handle_frame(&frame); } else { rxbuf[len] = 0; ESP_LOGI("MCP", "UART 文本返回: %s", (char *)rxbuf); } } vTaskDelay(pdMS_TO_TICKS(10)); } }13. 单屏 / 双屏 / 异显设计
13.1 显示模式定义
typedef enum { DISPLAY_MODE_SINGLE = 0, DISPLAY_MODE_DUAL_SAME, DISPLAY_MODE_DUAL_DIFF, } display_mode_t; typedef enum { DISPLAY_MAIN = 0, DISPLAY_SUB, } display_id_t; static display_mode_t g_display_mode = DISPLAY_MODE_DUAL_DIFF;13.2 显示内容分工
单屏模式: - 网络状态 - AI 状态 - 音量 - 时间 - 对话文本 - 天气 - OTA 状态 双屏同显: - 两个屏幕显示相同 UI 双屏异显: - 主屏:文本、菜单、设置、状态 - 副屏:AI 表情、眼睛动画、语音波形、状态图标13.3 LVGL 显示刷新骨架
void display_show_status(const char *text) { lcd_select(DISPLAY_MAIN); lv_label_set_text(ui_status_label, text); } void display_show_ai_text(const char *text) { lcd_select(DISPLAY_MAIN); lv_label_set_text(ui_ai_text_label, text); } void display_show_expression(const char *expr) { if (g_display_mode == DISPLAY_MODE_SINGLE) { return; } lcd_select(DISPLAY_SUB); if (strcmp(expr, "idle") == 0) { lv_img_set_src(ui_expression_img, &img_idle); } else if (strcmp(expr, "listening") == 0) { lv_img_set_src(ui_expression_img, &img_listening); } else if (strcmp(expr, "thinking") == 0) { lv_img_set_src(ui_expression_img, &img_thinking); } else if (strcmp(expr, "speaking") == 0) { lv_img_set_src(ui_expression_img, &img_speaking); } else if (strcmp(expr, "interrupt") == 0) { lv_img_set_src(ui_expression_img, &img_interrupt); } }13.4 根据 AI 状态刷新 UI
void display_update_by_state(ai_state_t state) { switch (state) { case AI_STATE_IDLE: display_show_status("待机中"); display_show_ai_text("你好,我在这里"); display_show_expression("idle"); break; case AI_STATE_LISTENING: display_show_status("聆听中"); display_show_ai_text("请说话..."); display_show_expression("listening"); break; case AI_STATE_THINKING: display_show_status("思考中"); display_show_ai_text("正在理解你的问题"); display_show_expression("thinking"); break; case AI_STATE_SPEAKING: display_show_status("回答中"); display_show_expression("speaking"); break; case AI_STATE_INTERRUPTED: display_show_status("已打断"); display_show_ai_text("请继续说"); display_show_expression("interrupt"); break; case AI_STATE_NET_CONFIG: display_show_status("配网模式"); display_show_ai_text("请使用四博小助手配网"); break; default: break; } }14. 按键逻辑设计
结合四博设备常见操作,可以定义:
单击:切换聆听 / 空闲 双击:切换联网模式 / 进入唤醒词升级模式 三击:进入配网模式 长按:开关机 五击:进入素材 / 调试模式14.1 按键事件处理
typedef enum { KEY_EVT_SINGLE_CLICK = 0, KEY_EVT_DOUBLE_CLICK, KEY_EVT_TRIPLE_CLICK, KEY_EVT_LONG_PRESS, KEY_EVT_FIVE_CLICK, } key_evt_t; void key_handle_event(key_evt_t evt) { switch (evt) { case KEY_EVT_SINGLE_CLICK: if (g_ai_state == AI_STATE_IDLE) { ai_set_state(AI_STATE_LISTENING); audio_capture_start(); ai_ws_send_listen_start(); } else if (g_ai_state == AI_STATE_LISTENING) { audio_capture_stop(); ai_ws_send_listen_stop(); ai_set_state(AI_STATE_IDLE); } break; case KEY_EVT_DOUBLE_CLICK: net_switch_next_mode(); display_show_status("切换联网方式"); break; case KEY_EVT_TRIPLE_CLICK: ai_set_state(AI_STATE_NET_CONFIG); start_blufi_config(); break; case KEY_EVT_LONG_PRESS: display_show_status("准备关机"); board_power_off(); break; case KEY_EVT_FIVE_CLICK: display_show_status("进入调试模式"); enter_debug_mode(); break; } }15. OTA 升级设计
开发宝典中 S3 AI-Speaker 工程可配置 OTA 地址,设备重新编译烧录后可连接自己的后端服务;开发宝典中也提到,如果对工程编译环境不熟悉,可使用四博在线烧录平台体验固件。
15.1 OTA 检测流程
设备启动 ↓ 获取当前固件版本 ↓ 请求 OTA 服务 ↓ 比较服务器版本 ↓ 如有新版本,下载固件 ↓ 校验固件 ↓ 写入 OTA 分区 ↓ 重启进入新固件15.2 OTA 配置示例
#define FIRMWARE_VERSION "1.0.0" #define OTA_CHECK_URL "https://your-domain.com/v1/ota/check" void ota_service_check(void) { char post_data[256]; snprintf(post_data, sizeof(post_data), "{" "\"model\":\"sibo_ai_speaker_s3_4g\"," "\"version\":\"%s\"," "\"chip\":\"esp32s3\"" "}", FIRMWARE_VERSION ); ESP_LOGI("OTA", "检查 OTA: %s", post_data); /* * 实际实现: * 1. HTTP POST 到 OTA 服务 * 2. 获取版本号和固件 URL * 3. 调用 esp_https_ota */ }16. 客户系统接入方式
16.1 方式一:客户云平台接入
四博 AI 音箱 ↓ WebSocket / HTTPS 客户 AI 网关 ↓ 客户 ASR / LLM / TTS / 知识库 ↓ 返回音频 / 文本 / 控制命令适合:
教育学习机 AI 故事机 客户自有内容平台 会员制 AI 服务 企业知识库问答16.2 方式二:客户 MCU 接入
客户 MCU ↑↓ UART / MCP 四博 AI 音箱 S3 主控 ↑↓ Wi-Fi / 蓝牙 / 4G AI 云端适合:
传统家电 玩具主控 灯控系统 机器人主控 智能中控设备16.3 方式三:客户 App / 小程序接入
客户 App / 小程序 ↓ 客户服务器 ↓ 设备管理 / AI 配置 / 内容下发 ↓ 四博 AI 音箱适合:
品牌客户 电商成品客户 需要用户体系的客户 需要素材管理的客户 需要知识库配置的客户17. 工程配置示例
17.1 sdkconfig.defaults.board
# 目标芯片 CONFIG_IDF_TARGET="esp32s3" # 蓝牙与 BluFi 配网 CONFIG_BT_ENABLED=y CONFIG_USE_BLUFI_NET_CONFIGURING=y # Wi-Fi CONFIG_ESP_WIFI_ENABLED=y # 4G CONFIG_USE_4G_NETWORK=y CONFIG_MODEM_UART_BAUD=115200 # 音频 CONFIG_USE_AUDIO_CODEC=y CONFIG_USE_I2S_SPEAKER=y CONFIG_USE_MICROPHONE=y # AEC / 实时打断 CONFIG_USE_DEVICE_AEC=y CONFIG_USE_SERVER_AEC=n CONFIG_USE_REALTIME_INTERRUPT=y # 显示 CONFIG_USE_LCD_DISPLAY=y CONFIG_USE_DUAL_LCD_DISPLAY=y CONFIG_USE_DIFFERENT_DISPLAY=y # MCP CONFIG_USE_MCP_UART=y CONFIG_MCP_UART_BAUD=115200 # OTA CONFIG_OTA_URL="https://your-domain.com/v1/ota/" # AI 服务 CONFIG_AI_WEBSOCKET_URL="wss://your-domain.com/xiaozhi/v1/"17.2 Kconfig 菜单示例
menu "Sibo AI Speaker S3 4G" config BOARD_TYPE_SIBO_AI_SPEAKER_S3_4G bool "Sibo AI Speaker S3 4G" default y config USE_WIFI_NETWORK bool "Enable Wi-Fi network" default y config USE_BT_NETWORK bool "Enable Bluetooth network" default y config USE_4G_NETWORK bool "Enable 4G network" default y config USE_REALTIME_INTERRUPT bool "Enable realtime barge-in" default y config USE_WAKEUP_INTERRUPT bool "Enable wake word interrupt" default y config USE_DUAL_LCD_DISPLAY bool "Enable dual LCD display" default y config USE_MCP_UART bool "Enable MCP UART" default y endmenu18. 编译与烧录流程
《开发宝典》中,四博 S3 方案使用 ESP-IDF 工程方式开发,S3 AI-Speaker 章节给出了克隆工程、配置工程、编译、合并固件和烧录流程;C5 章节也说明,C5 编译建议使用 ESP-IDF 5.5。
# 1. 进入 ESP-IDF 环境 . ~/esp/esp-idf/export.sh # 2. 设置目标芯片 idf.py set-target esp32s3 # 3. 打开配置菜单 idf.py menuconfig # 4. 编译工程 idf.py build # 5. 合并固件 idf.py merge-bin # 6. 烧录并查看日志 idf.py -p COMx flash monitor19. 技术卖点总结
19.1 远场语音能力
- 5 米范围内可唤醒 - 5 米范围内可打断 - 5 米范围内可连续对话 - 高噪声环境下仍能稳定响应 - 适合厨房、客厅、儿童房、展厅等复杂环境19.2 打断能力
- 支持唤醒词打断 - 支持实时打断 - 支持 AI 播报过程中插话 - 支持修改唤醒词 - 支持品牌专属唤醒词19.3 三模联网
- Wi-Fi:家庭和固定场景 - 蓝牙:手机辅助联网和快速配网 - 4G:户外、移动、无 Wi-Fi 场景19.4 显示能力
- 支持单屏显示 - 支持双屏显示 - 支持双屏异显 - 可显示 AI 表情、状态、文本、天气、时间、音量、网络状态19.5 二次开发能力
- 基于 ESP32-S3 - 基于 ESP-IDF - 支持 UART / MCP - 支持 WebSocket / HTTPS - 支持客户云平台 - 支持客户 App / 小程序 - 支持自定义知识库和自建大模型20. 技术推广版结尾
四博 AI 智能音箱 4G S3 版本,是一套面向量产和二次开发的 AI 语音硬件底座。它不仅支持传统 AI 音箱的语音问答能力,还进一步集成了远距离拾音、高噪音环境识别、唤醒词打断、实时打断、唤醒词修改、Wi-Fi / 蓝牙 / 4G 三模联网、单屏 / 双屏 / 异显显示、MCP 语义控制和客户系统接入能力。
对于客户来说,四博方案可以快速完成从技术原型到成品量产的落地。客户既可以直接使用四博现有 AI 音箱方案,也可以在 ESP32-S3 平台上接入自己的云服务、App、小程序、知识库、内容系统、控制协议和品牌唤醒词,从而形成自有品牌的 AI 智能音箱、AI 学习机、AI 桌宠或 AI 中控产品。
四博提供的不只是一个 AI 音箱硬件,而是一套可联网、可打断、可显示、可扩展、可量产、可接入客户系统的 AI 智能硬件完整方案。
