基于 ESP32-S3 的四博 AI 台灯方案:摄像头拍照答题、语音大模型、双目屏与生活陪伴系统设计

基于 ESP32-S3 的四博 AI 台灯方案:摄像头拍照答题、语音大模型、双目屏与生活陪伴系统设计

1. 方案定位

四博 AI 台灯可以理解为"四博 AI 智能音箱方案"的多模态升级版。

传统 AI 音箱主要完成:

复制代码
复制代码
语音唤醒 → 语音识别 → 大模型对话 → TTS 播放

升级为 AI 台灯后,系统需要增加:

复制代码
复制代码
摄像头采集
拍照答题
双目屏表情
台灯 PWM 调光
触摸交互
震动反馈
舵机动作
生活陪伴
儿童学习辅助

因此,本方案的核心不再是单纯"语音音箱",而是:

复制代码
复制代码
ESP32-S3 + Camera + I2S Audio + LCD/Dual Eyes + Touch + Light PWM + Servo + Cloud AI

四博资料中,ESP32-S3 系列主要面向音视频/AI 市场,支持摄像头、LCD、外挂 PSRAM 等应用方向;四博 AI 开发宝典中也包含 AI-S3、双目、多模态开发板、小智 AI 对接等内容,适合作为 AI 台灯、拍照答题机、桌面陪伴机器人等产品的基础平台。


2. 系统整体架构

2.1 硬件架构

复制代码
复制代码
                    ┌──────────────────────┐
                    │      云端 AI 服务      │
                    │ ASR / LLM / VLM / TTS │
                    └──────────▲───────────┘
                               │ HTTPS/WebSocket
                               │
┌──────────────┐       ┌───────┴────────┐       ┌──────────────┐
│   摄像头      │──────▶│   ESP32-S3      │──────▶│ 双目屏 / LCD  │
│ OV2640/GC系列 │ DVP   │ ESPS3-32 N16R8  │ SPI   │ 表情/答案显示 │
└──────────────┘       └───────┬────────┘       └──────────────┘
                               │
        ┌──────────────┬───────┼────────┬──────────────┐
        │              │       │        │              │
   ┌────▼────┐   ┌─────▼───┐ ┌─▼──┐ ┌──▼───┐     ┌────▼────┐
   │ 麦克风   │   │ 喇叭功放 │ │触摸 │ │台灯PWM│     │舵机/震动 │
   │ I2S/PDM │   │ I2S/DAC │ │GPIO │ │LEDC  │     │PWM/GPIO │
   └─────────┘   └─────────┘ └────┘ └──────┘     └─────────┘

2.2 软件模块

复制代码
复制代码
main
├── app_main.c
├── board
│   ├── board_config.h
│   └── pin_config.h
├── camera
│   ├── app_camera.c
│   └── app_camera.h
├── audio
│   ├── app_audio.c
│   ├── wakeup.c
│   ├── recorder.c
│   └── tts_player.c
├── ai
│   ├── ai_event.c
│   ├── ai_protocol.c
│   ├── ai_http_vision.c
│   └── ai_ws_client.c
├── ui
│   ├── eye_anim.c
│   ├── answer_page.c
│   └── lvgl_port.c
├── lamp
│   ├── lamp_pwm.c
│   └── lamp_scene.c
├── motor
│   ├── servo_ctrl.c
│   └── vibration.c
├── touch
│   └── touch_key.c
└── net
    ├── wifi_manager.c
    └── blufi_config.c

3. 推荐硬件配置

3.1 标准版本

复制代码
复制代码
主控:ESP32-S3 / ESPS3-32
Flash:16MB
PSRAM:2MB / 8MB
摄像头:OV2640 / GC0308 / GC2145
音频:I2S 麦克风 + I2S DAC / Codec
功放:NS4150 / 8002 / 外置 Class-D
屏幕:0.71 寸双目屏 / 1.28 寸双目屏 / 2.0 寸 LCD
触摸:电容触摸 IC 或 GPIO 触摸按键
灯光:PWM 恒流驱动
动作:SG90 舵机 / 震动马达
联网:Wi-Fi,后续可扩展 4G

3.2 产品版本规划

复制代码
复制代码
基础版:AI 音箱 + 台灯
学习版:AI 音箱 + 台灯 + 摄像头拍题
陪伴版:AI 音箱 + 台灯 + 摄像头 + 双目屏 + 舵机
高配版:AI 音箱 + 台灯 + 摄像头 + 双目屏 + 4G 联网

4. 典型交互流程

4.1 拍照答题

复制代码
复制代码
用户:小博,帮我看一下这道题。

设备流程:
1. 唤醒词触发
2. 进入 listening 状态
3. ASR 识别到"拍照答题"意图
4. 摄像头采集 JPEG 图片
5. 图片通过 HTTP/WebSocket 上传云端
6. 云端视觉大模型识别题目
7. 云端大模型生成解题步骤
8. TTS 生成语音
9. 设备播放讲解
10. 双目屏显示思考/回答表情
11. 台灯切换学习模式

4.2 生活陪伴

复制代码
复制代码
用户:小博,我今天有点不开心。

设备流程:
1. 语音识别用户情绪
2. 大模型生成安慰类回复
3. 灯光切换暖光
4. 双眼显示微笑表情
5. 震动马达短震反馈
6. 舵机轻微点头
7. TTS 播放陪伴语音

5. ESP-IDF 工程初始化

复制代码
复制代码
idf.py set-target esp32s3
idf.py menuconfig

建议开启:

复制代码
复制代码
CONFIG_SPIRAM=y
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=16384
CONFIG_LWIP_TCP_WND_DEFAULT=32768

组件依赖示例:

复制代码
复制代码
idf_component_register(
    SRCS
        "app_main.c"
        "camera/app_camera.c"
        "ai/ai_http_vision.c"
        "ai/ai_protocol.c"
        "lamp/lamp_pwm.c"
        "motor/servo_ctrl.c"
        "motor/vibration.c"
        "touch/touch_key.c"
        "ui/eye_anim.c"
    INCLUDE_DIRS
        "."
        "camera"
        "ai"
        "lamp"
        "motor"
        "touch"
        "ui"
    REQUIRES
        esp_http_client
        esp-tls
        esp_wifi
        nvs_flash
        json
        driver
        esp_timer
)

6. 事件总线设计

AI 台灯建议使用事件驱动架构,所有动作通过统一事件调度。

复制代码
复制代码
typedef enum {
    AI_EVT_NONE = 0,
    AI_EVT_WAKEUP,
    AI_EVT_LISTEN_START,
    AI_EVT_LISTEN_STOP,
    AI_EVT_PHOTO_QA,
    AI_EVT_COMPANION_CHAT,
    AI_EVT_TTS_START,
    AI_EVT_TTS_STOP,
    AI_EVT_TOUCH,
    AI_EVT_ERROR,
} ai_event_id_t;

typedef struct {
    ai_event_id_t id;
    char text[256];
    void *data;
    size_t data_len;
} ai_event_t;
复制代码
复制代码
static QueueHandle_t s_ai_queue;

void ai_event_init(void)
{
    s_ai_queue = xQueueCreate(8, sizeof(ai_event_t));
}

esp_err_t ai_event_post(ai_event_id_t id, const char *text)
{
    ai_event_t evt = {
        .id = id,
        .data = NULL,
        .data_len = 0,
    };

    if (text) {
        snprintf(evt.text, sizeof(evt.text), "%s", text);
    }

    if (xQueueSend(s_ai_queue, &evt, pdMS_TO_TICKS(100)) != pdTRUE) {
        return ESP_FAIL;
    }

    return ESP_OK;
}

7. 摄像头初始化

复制代码
复制代码
#pragma once

#include "esp_err.h"
#include "esp_camera.h"

esp_err_t app_camera_init(void);
camera_fb_t *app_camera_capture(void);
void app_camera_release(camera_fb_t *fb);
复制代码
复制代码
#include "app_camera.h"
#include "esp_log.h"

static const char *TAG = "app_camera";

#define CAM_PIN_PWDN     -1
#define CAM_PIN_RESET    -1
#define CAM_PIN_XCLK     15
#define CAM_PIN_SIOD     4
#define CAM_PIN_SIOC     5

#define CAM_PIN_D7       16
#define CAM_PIN_D6       17
#define CAM_PIN_D5       18
#define CAM_PIN_D4       12
#define CAM_PIN_D3       10
#define CAM_PIN_D2       8
#define CAM_PIN_D1       9
#define CAM_PIN_D0       11

#define CAM_PIN_VSYNC    6
#define CAM_PIN_HREF     7
#define CAM_PIN_PCLK     13

esp_err_t app_camera_init(void)
{
    camera_config_t config = {
        .pin_pwdn = CAM_PIN_PWDN,
        .pin_reset = CAM_PIN_RESET,
        .pin_xclk = CAM_PIN_XCLK,
        .pin_sccb_sda = CAM_PIN_SIOD,
        .pin_sccb_scl = CAM_PIN_SIOC,

        .pin_d7 = CAM_PIN_D7,
        .pin_d6 = CAM_PIN_D6,
        .pin_d5 = CAM_PIN_D5,
        .pin_d4 = CAM_PIN_D4,
        .pin_d3 = CAM_PIN_D3,
        .pin_d2 = CAM_PIN_D2,
        .pin_d1 = CAM_PIN_D1,
        .pin_d0 = CAM_PIN_D0,

        .pin_vsync = CAM_PIN_VSYNC,
        .pin_href = CAM_PIN_HREF,
        .pin_pclk = CAM_PIN_PCLK,

        .xclk_freq_hz = 20000000,
        .ledc_timer = LEDC_TIMER_0,
        .ledc_channel = LEDC_CHANNEL_0,

        .pixel_format = PIXFORMAT_JPEG,
        .frame_size = FRAMESIZE_VGA,
        .jpeg_quality = 12,
        .fb_count = 2,
        .fb_location = CAMERA_FB_IN_PSRAM,
        .grab_mode = CAMERA_GRAB_LATEST,
    };

    esp_err_t ret = esp_camera_init(&config);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "camera init failed: %s", esp_err_to_name(ret));
        return ret;
    }

    sensor_t *s = esp_camera_sensor_get();
    if (s) {
        s->set_brightness(s, 0);
        s->set_contrast(s, 1);
        s->set_saturation(s, 0);
        s->set_framesize(s, FRAMESIZE_VGA);
    }

    ESP_LOGI(TAG, "camera init ok");
    return ESP_OK;
}

camera_fb_t *app_camera_capture(void)
{
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        ESP_LOGE(TAG, "capture failed");
        return NULL;
    }

    ESP_LOGI(TAG, "capture ok, len=%d, size=%dx%d",
             fb->len, fb->width, fb->height);

    return fb;
}

void app_camera_release(camera_fb_t *fb)
{
    if (fb) {
        esp_camera_fb_return(fb);
    }
}

8. 拍照答题协议设计

设备上报:

复制代码
复制代码
{
  "type": "vision_qa",
  "device_id": "sibo_ai_lamp_001",
  "session_id": "20260513_001",
  "format": "jpeg",
  "prompt": "请识别图片中的题目,给出详细解题步骤,并用儿童可以理解的方式讲解。",
  "image_base64": "..."
}

云端返回:

复制代码
复制代码
{
  "code": 0,
  "type": "vision_qa_result",
  "answer": "这道题先看括号内的数字,再进行乘法计算......",
  "display_text": "解题步骤:1. 先算括号;2. 再算乘法;3. 最后检查结果。",
  "tts_url": "https://server.com/tts/001.mp3",
  "action": {
    "eye": "answering",
    "lamp": "study",
    "servo": "nod",
    "vibration": "short"
  }
}

9. 图片上传代码

对于 ESP32-S3,推荐优先使用二进制上传,减少 Base64 带来的 33% 数据膨胀。简单项目可以使用 Base64 JSON,量产项目更建议使用 multipart/form-data 或 WebSocket binary。

9.1 Base64 JSON 版本

复制代码
复制代码
#include "esp_http_client.h"
#include "mbedtls/base64.h"
#include "cJSON.h"
#include "esp_log.h"
#include <stdlib.h>
#include <string.h>

static const char *TAG = "vision_http";

#define VISION_API_URL "https://your-domain.com/api/v1/vision/qa"
#define DEVICE_ID      "sibo_ai_lamp_001"

static char *base64_encode(const uint8_t *input, size_t input_len)
{
    size_t out_len = 0;

    mbedtls_base64_encode(NULL, 0, &out_len, input, input_len);

    char *out = calloc(1, out_len + 1);
    if (!out) {
        return NULL;
    }

    int ret = mbedtls_base64_encode(
        (unsigned char *)out,
        out_len,
        &out_len,
        input,
        input_len
    );

    if (ret != 0) {
        free(out);
        return NULL;
    }

    out[out_len] = '\0';
    return out;
}

esp_err_t ai_send_vision_qa(camera_fb_t *fb, const char *prompt)
{
    if (!fb || !prompt) {
        return ESP_ERR_INVALID_ARG;
    }

    char *image_b64 = base64_encode(fb->buf, fb->len);
    if (!image_b64) {
        ESP_LOGE(TAG, "base64 encode failed");
        return ESP_ERR_NO_MEM;
    }

    cJSON *root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "type", "vision_qa");
    cJSON_AddStringToObject(root, "device_id", DEVICE_ID);
    cJSON_AddStringToObject(root, "format", "jpeg");
    cJSON_AddStringToObject(root, "prompt", prompt);
    cJSON_AddStringToObject(root, "image_base64", image_b64);

    char *body = cJSON_PrintUnformatted(root);

    cJSON_Delete(root);
    free(image_b64);

    if (!body) {
        return ESP_ERR_NO_MEM;
    }

    esp_http_client_config_t config = {
        .url = VISION_API_URL,
        .timeout_ms = 30000,
        .buffer_size = 4096,
        .buffer_size_tx = 4096,
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    if (!client) {
        free(body);
        return ESP_FAIL;
    }

    esp_http_client_set_method(client, HTTP_METHOD_POST);
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_post_field(client, body, strlen(body));

    esp_err_t ret = esp_http_client_perform(client);

    if (ret == ESP_OK) {
        int status = esp_http_client_get_status_code(client);
        ESP_LOGI(TAG, "vision qa status=%d", status);
    } else {
        ESP_LOGE(TAG, "http failed: %s", esp_err_to_name(ret));
    }

    esp_http_client_cleanup(client);
    free(body);

    return ret;
}

10. 云端 FastAPI 示例

复制代码
复制代码
from fastapi import FastAPI
from pydantic import BaseModel
import base64
import uuid

app = FastAPI()

class VisionQARequest(BaseModel):
    type: str
    device_id: str
    format: str
    prompt: str
    image_base64: str

def call_vlm(image_bytes: bytes, prompt: str) -> str:
    """
    这里可以接入:
    1. 小智服务
    2. 豆包视觉大模型
    3. 通义千问 VL
    4. ChatGPT 视觉模型
    5. 私有化 OCR + LLM 服务
    """
    return "识别到一道数学题。解题步骤:先分析题干,再列式计算,最后检查答案。"

def call_tts(text: str) -> str:
    task_id = str(uuid.uuid4())
    return f"https://your-domain.com/tts/{task_id}.mp3"

@app.post("/api/v1/vision/qa")
async def vision_qa(req: VisionQARequest):
    image_bytes = base64.b64decode(req.image_base64)

    answer = call_vlm(
        image_bytes=image_bytes,
        prompt=req.prompt
    )

    tts_url = call_tts(answer)

    return {
        "code": 0,
        "type": "vision_qa_result",
        "device_id": req.device_id,
        "answer": answer,
        "display_text": answer[:80],
        "tts_url": tts_url,
        "action": {
            "eye": "answering",
            "lamp": "study",
            "servo": "nod",
            "vibration": "short"
        }
    }

11. 台灯 PWM 控制

复制代码
复制代码
#include "driver/ledc.h"
#include "esp_log.h"

#define LAMP_PWM_GPIO       38
#define LAMP_LEDC_MODE      LEDC_LOW_SPEED_MODE
#define LAMP_LEDC_TIMER     LEDC_TIMER_1
#define LAMP_LEDC_CHANNEL   LEDC_CHANNEL_1
#define LAMP_PWM_FREQ       5000
#define LAMP_DUTY_RES       LEDC_TIMER_13_BIT

void lamp_pwm_init(void)
{
    ledc_timer_config_t timer = {
        .speed_mode = LAMP_LEDC_MODE,
        .timer_num = LAMP_LEDC_TIMER,
        .duty_resolution = LAMP_DUTY_RES,
        .freq_hz = LAMP_PWM_FREQ,
        .clk_cfg = LEDC_AUTO_CLK,
    };

    ledc_timer_config(&timer);

    ledc_channel_config_t channel = {
        .gpio_num = LAMP_PWM_GPIO,
        .speed_mode = LAMP_LEDC_MODE,
        .channel = LAMP_LEDC_CHANNEL,
        .timer_sel = LAMP_LEDC_TIMER,
        .duty = 0,
        .hpoint = 0,
    };

    ledc_channel_config(&channel);
}

void lamp_set_brightness(uint8_t percent)
{
    if (percent > 100) {
        percent = 100;
    }

    uint32_t max_duty = (1 << 13) - 1;
    uint32_t duty = max_duty * percent / 100;

    ledc_set_duty(LAMP_LEDC_MODE, LAMP_LEDC_CHANNEL, duty);
    ledc_update_duty(LAMP_LEDC_MODE, LAMP_LEDC_CHANNEL);
}

void lamp_scene_study(void)
{
    lamp_set_brightness(85);
}

void lamp_scene_companion(void)
{
    lamp_set_brightness(45);
}

void lamp_scene_sleep(void)
{
    lamp_set_brightness(10);
}

12. 舵机点头动作

复制代码
复制代码
#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define SERVO_GPIO          39
#define SERVO_MODE          LEDC_LOW_SPEED_MODE
#define SERVO_TIMER         LEDC_TIMER_2
#define SERVO_CHANNEL       LEDC_CHANNEL_2
#define SERVO_FREQ_HZ       50
#define SERVO_DUTY_RES      LEDC_TIMER_14_BIT

static uint32_t servo_angle_to_duty(int angle)
{
    if (angle < 0) {
        angle = 0;
    }

    if (angle > 180) {
        angle = 180;
    }

    const uint32_t max_duty = (1 << 14) - 1;
    float pulse_ms = 0.5f + ((float)angle / 180.0f) * 2.0f;

    return (uint32_t)(max_duty * pulse_ms / 20.0f);
}

void servo_init(void)
{
    ledc_timer_config_t timer = {
        .speed_mode = SERVO_MODE,
        .timer_num = SERVO_TIMER,
        .duty_resolution = SERVO_DUTY_RES,
        .freq_hz = SERVO_FREQ_HZ,
        .clk_cfg = LEDC_AUTO_CLK,
    };

    ledc_timer_config(&timer);

    ledc_channel_config_t channel = {
        .gpio_num = SERVO_GPIO,
        .speed_mode = SERVO_MODE,
        .channel = SERVO_CHANNEL,
        .timer_sel = SERVO_TIMER,
        .duty = servo_angle_to_duty(90),
        .hpoint = 0,
    };

    ledc_channel_config(&channel);
}

void servo_set_angle(int angle)
{
    uint32_t duty = servo_angle_to_duty(angle);
    ledc_set_duty(SERVO_MODE, SERVO_CHANNEL, duty);
    ledc_update_duty(SERVO_MODE, SERVO_CHANNEL);
}

void servo_nod_once(void)
{
    servo_set_angle(75);
    vTaskDelay(pdMS_TO_TICKS(250));

    servo_set_angle(105);
    vTaskDelay(pdMS_TO_TICKS(250));

    servo_set_angle(90);
}

13. 震动反馈

复制代码
复制代码
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define VIBRATION_GPIO  40

void vibration_init(void)
{
    gpio_config_t cfg = {
        .pin_bit_mask = 1ULL << VIBRATION_GPIO,
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };

    gpio_config(&cfg);
    gpio_set_level(VIBRATION_GPIO, 0);
}

void vibration_short(void)
{
    gpio_set_level(VIBRATION_GPIO, 1);
    vTaskDelay(pdMS_TO_TICKS(120));
    gpio_set_level(VIBRATION_GPIO, 0);
}

14. 双目屏状态机

复制代码
复制代码
typedef enum {
    EYE_IDLE = 0,
    EYE_WAKEUP,
    EYE_LISTENING,
    EYE_THINKING,
    EYE_ANSWERING,
    EYE_SMILE,
    EYE_SLEEP,
    EYE_ERROR,
} eye_state_t;

void eye_set_state(eye_state_t state)
{
    switch (state) {
    case EYE_IDLE:
        // show idle image
        break;

    case EYE_WAKEUP:
        // show wakeup animation
        break;

    case EYE_LISTENING:
        // show listening animation
        break;

    case EYE_THINKING:
        // show thinking animation
        break;

    case EYE_ANSWERING:
        // show speaking animation
        break;

    case EYE_SMILE:
        // show smile image
        break;

    case EYE_SLEEP:
        // show sleep image
        break;

    case EYE_ERROR:
        // show error image
        break;

    default:
        break;
    }
}

动作映射建议:

复制代码
复制代码
void apply_ai_action(const char *eye, const char *lamp, const char *servo, const char *vibration)
{
    if (strcmp(eye, "thinking") == 0) {
        eye_set_state(EYE_THINKING);
    } else if (strcmp(eye, "answering") == 0) {
        eye_set_state(EYE_ANSWERING);
    } else if (strcmp(eye, "smile") == 0) {
        eye_set_state(EYE_SMILE);
    }

    if (strcmp(lamp, "study") == 0) {
        lamp_scene_study();
    } else if (strcmp(lamp, "companion") == 0) {
        lamp_scene_companion();
    } else if (strcmp(lamp, "sleep") == 0) {
        lamp_scene_sleep();
    }

    if (strcmp(servo, "nod") == 0) {
        servo_nod_once();
    }

    if (strcmp(vibration, "short") == 0) {
        vibration_short();
    }
}

15. 主任务调度

复制代码
复制代码
static void ai_main_task(void *arg)
{
    ai_event_t evt;

    while (1) {
        if (xQueueReceive(s_ai_queue, &evt, portMAX_DELAY) == pdTRUE) {
            switch (evt.id) {
            case AI_EVT_WAKEUP:
                eye_set_state(EYE_WAKEUP);
                lamp_scene_companion();
                break;

            case AI_EVT_LISTEN_START:
                eye_set_state(EYE_LISTENING);
                break;

            case AI_EVT_PHOTO_QA: {
                eye_set_state(EYE_THINKING);
                lamp_scene_study();

                camera_fb_t *fb = app_camera_capture();
                if (fb) {
                    ai_send_vision_qa(
                        fb,
                        "请识别图片中的题目,给出详细步骤,并用儿童能理解的方式讲解。"
                    );
                    app_camera_release(fb);
                } else {
                    eye_set_state(EYE_ERROR);
                }

                break;
            }

            case AI_EVT_COMPANION_CHAT:
                eye_set_state(EYE_SMILE);
                lamp_scene_companion();
                vibration_short();
                break;

            case AI_EVT_TTS_START:
                eye_set_state(EYE_ANSWERING);
                break;

            case AI_EVT_TTS_STOP:
                eye_set_state(EYE_IDLE);
                break;

            case AI_EVT_TOUCH:
                eye_set_state(EYE_SMILE);
                vibration_short();
                servo_nod_once();
                break;

            default:
                break;
            }
        }
    }
}
复制代码
复制代码
void app_main(void)
{
    nvs_flash_init();

    ai_event_init();

    wifi_manager_init();
    lamp_pwm_init();
    vibration_init();
    servo_init();
    touch_key_init();
    app_camera_init();
    audio_init();
    display_init();

    xTaskCreate(ai_main_task, "ai_main_task", 8192, NULL, 5, NULL);

    ai_event_post(AI_EVT_WAKEUP, NULL);
}

16. Touch 触发拍照

复制代码
复制代码
#include "driver/gpio.h"

#define TOUCH_GPIO  3

static void IRAM_ATTR touch_isr_handler(void *arg)
{
    ai_event_t evt = {
        .id = AI_EVT_PHOTO_QA,
    };

    xQueueSendFromISR(s_ai_queue, &evt, NULL);
}

void touch_key_init(void)
{
    gpio_config_t cfg = {
        .pin_bit_mask = 1ULL << TOUCH_GPIO,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_NEGEDGE,
    };

    gpio_config(&cfg);

    gpio_install_isr_service(0);
    gpio_isr_handler_add(TOUCH_GPIO, touch_isr_handler, NULL);
}

17. 产品化建议

17.1 拍照答题优化

复制代码
复制代码
1. 摄像头优先输出 JPEG,减少 ESP32-S3 编码压力
2. 图片分辨率建议 VGA / SVGA 起步
3. 拍题场景建议增加补光灯
4. 云端做 OCR + VLM 双通道识别
5. 回答不要只给答案,要给步骤
6. 儿童场景需要加入内容安全过滤

17.2 音频链路优化

复制代码
复制代码
1. 麦克风使用 I2S/PDM
2. 本地做 VAD,减少无效上传
3. 语音唤醒可以使用离线唤醒芯片或本地唤醒模型
4. TTS 音频建议使用 Opus 或 MP3
5. 播放端用 ringbuffer,避免网络抖动导致卡顿

17.3 量产建议

复制代码
复制代码
1. ESP32-S3 建议使用带 PSRAM 版本
2. 摄像头排线需要注意 EMI 和结构固定
3. 台灯 PWM 频率建议高于 2kHz,避免可视频闪
4. 舵机电源和主控电源需要隔离或单独稳压
5. 喇叭功放走线远离摄像头和 Wi-Fi 天线
6. 儿童产品需注意隐私、拍照提示和家长授权

18. 总结

四博 AI 台灯方案的核心价值,是把 AI 音箱从"语音交互终端"升级为"视觉、语音、表情、灯光、动作一体化的多模态陪伴终端"。

技术上,ESP32-S3 负责本地设备控制和多媒体采集,云端负责视觉识别、大模型推理和语音合成。产品上,它可以覆盖 AI 学习台灯、AI 拍学机、AI 儿童陪伴机器人、AI 桌面宠物、AI 智能音箱等多个方向

相关推荐
Jmayday1 小时前
RNN案例之:人名分类器
人工智能·rnn·深度学习·nlp
企业架构师老王1 小时前
开源还是商用?跨境电商自动运营Agent的选型对比与开发实践
人工智能·ai·开源·自动化
陈天伟教授1 小时前
UI-TARS Desktop
人工智能·ui
花椒技术1 小时前
AI 协同开发落地复盘:1 小时生成首版后,为什么 Review 和修正又花了 2-3 天
前端·人工智能·架构
ygw_2 小时前
Claude code的使用教程
人工智能
:mnong2 小时前
QuoteApp Skills技能设计理念与技巧总结
人工智能·cad
昇腾CANN2 小时前
5月14号直播丨多模态生成技术优化实践第二期--并行和Cache篇
人工智能·昇腾·cann