🤖 硬件实战:从零手搓一个会聊天的AI小机器人

⚠️ 开局长文警告:本文约8000字,是一份从硬件选型、焊接组装、环境搭建、代码编写到最终部署的保姆级全流程指南。你需要准备约一个周末的时间,以及200-300元的预算。过程就像拼一个高级乐高,既有动手的乐趣,也有编程的成就感。建议收藏后分次阅读,或直接跳转你感兴趣的章节。

📑 目录

  1. 开篇:我们要做一个什么样的机器人?
  2. 第一章:硬件“购物车”——百元级物料清单
  3. 第二章:动手“拼积木”——硬件组装与接线
  4. 第三章:搭建“大脑”运行环境——软件与系统准备
  5. 第四章:注入“灵魂”——核心代码逐行详解
  6. 第五章:开机!测试与调优
  7. 第六章:还能怎么玩?——进阶创意与扩展
  8. 避坑指南与常见问题

1. 开篇:我们要做一个什么样的机器人?

想象一下,你有一个巴掌大的小盒子,插上电,对它说:“今天天气怎么样?”它就能用清脆的声音回答你。你问它数学题,它能算;你让它讲个笑话,它能逗你乐。它还能控制身边的灯和风扇——这就是我们要做的 “桌面AI语音助手机器人”

它的核心工作原理,可以用下面这张图来理解:

graph TD A[你说“开灯”] --> B[麦克风接收声音] B --> C[核心板进行语音识别] C --> D[得到文本“开灯”] D --> E[将文本发送给AI大模型] E --> F[AI理解意图并生成回复] F --> G{意图是控制设备吗?} G -- 是 --> H[核心板控制GPIO引脚] H --> I[继电器吸合,灯亮] G -- 否 --> J[生成自然语言回复] J --> K[核心板进行语音合成] K --> L[喇叭播放回复音频] L --> M[你听到“灯已打开”]

这个流程融合了硬件感知网络通信AI智能决策,是一个完美的物联网与AI结合入门项目 。

2. 第一章:硬件“购物车”——百元级物料清单

我们把硬件分成“大脑”、“感官”、“躯干”和“工具”四部分来准备。所有东西都能在淘宝或立创商城找到。

类别 名称 型号/说明 预估价格 作用
🧠 大脑 开发板 ESP32-S3-BOXESP32-Korvo 80-120元 核心!自带麦克风、喇叭、屏幕、Wi-Fi/蓝牙,专为AIoT设计。
👂 感官 麦克风阵列 开发板已集成 - 听你说话。
  扬声器 开发板已集成 - 播放AI的回答。
  小屏幕 开发板已集成(可选) - 显示状态或文字。
🦾 躯干 继电器模块 1路 5V 低电平触发 5元 机器人的“手”,用来控制220V家电。
  LED灯珠 & 电阻 红色LED,220欧姆电阻 1元 用于初步测试,更直观。
  杜邦线 公对公、母对母若干 5元 连接电路的“面条”。
🔧 工具 USB数据线 Type-C口 10元 供电和烧录程序。
  电脑 一台 - 写代码和编译。
  焊台/烙铁 可选,但推荐有 50元起 焊接更牢固可靠。

为什么选ESP32-S3?
因为它性能足够强(双核240MHz),内存够大(512KB SRAM,外接P SRAM),最重要的是,乐鑫官方提供了完整的语音唤醒语音识别框架(ESP-SR),让我们免于从零造轮子 。

3. 第二章:动手“拼积木”——硬件组装与接线

这一步就像给机器人连接神经和肌肉。

3.1 基础连接(LED测试)

我们先不用继电器,用一个LED来测试GPIO控制是否正常,更安全。

  1. 找到ESP32-S3的GPIO引脚:查看你开发板的说明书,找到一组通用的GPIO,比如GPIO2
  2. 连接LED
    • 将LED的长脚(阳极) 通过一个220欧姆的电阻,连接到GPIO2
    • 将LED的短脚(阴极) 直接连接到开发板的GND(地)引脚。
    • (如果使用杜邦线,可能需要一个面包板来中转。)

3.2 进阶连接(继电器控制)

理解LED控制后,我们可以换上继电器,控制真正的电器。

⚠️ 安全警告:继电器高压侧涉及220V市电,操作时必须断电!建议先完全在低压(5V)下测试逻辑,再接高压电。新手可跳过直接接市电,仅用继电器模块的指示灯验证功能。

  1. 连接继电器到ESP32
    • VCC -> 开发板 5V 引脚
    • GND -> 开发板 GND 引脚
    • IN -> 开发板 GPIO2 引脚
  2. 连接被控设备(如台灯)
    • 将台灯电源线剪断(务必拔掉插头!),露出火线和零线。
    • 火线剪断,两端分别接到继电器模块的COMNO端子。
    • COM(公共端)接来自电源插头的线,NO(常开端)接通往台灯的线。
    • 零线保持直连。

接线示意图(低压侧)

         ESP32-S3
        +--------+
        |    5V  |-------------------+
        |        |                   |
        |   GND  |-------------------+
        |        |                   |
        |  GPIO2 |-------------------+
        +--------+                   |
                                     v
                                 +------------+
                                 |  继电器模块 |
                                 |  VCC   GND |
                                 |  IN        |
                                 +------------+

4. 第三章:搭建“大脑”运行环境——软件与系统准备

机器人的“大脑”需要操作系统和开发工具。

4.1 安装基础软件

  1. 安装Python:访问Python官网,下载并安装3.8以上版本。安装时务必勾选“Add Python to PATH”。
  2. 安装VS Code:一个强大的代码编辑器,免费。

4.2 配置ESP-IDF开发框架

这是乐鑫官方的开发环境,虽然配置稍复杂,但一步到位。

# 1. 克隆ESP-IDF(国内用户建议使用gitee镜像)
git clone -b v5.1.2 --recursive https://gitee.com/esp-idf/esp-idf.git

# 2. 运行安装脚本
cd esp-idf
./install.sh  # Linux/macOS
install.bat   # Windows

# 3. 激活环境(每次打开新终端都需要)
. ./export.sh  # Linux/macOS
export.bat     # Windows

4.3 获取AI模型与例程

乐鑫提供了整合语音和AI的示例项目,我们以其为基础进行修改。

# 进入你的工作目录,克隆示例仓库
git clone --recursive https://github.com/espressif/esp-box.git
cd esp-box/examples/assistant

5. 第四章:注入“灵魂”——核心代码逐行详解

现在进入最核心的编程部分。我们将创建一个项目,实现:语音唤醒 -> 语音识别 -> AI对话 -> 控制指令执行。

5.1 项目文件结构

your_ai_robot/
├── main/
│   ├── CMakeLists.txt
│   └── main.c              # 主程序入口
├── components/
│   └── ai_agent/          # AI代理组件
│       ├── CMakeLists.txt
│       ├── ai_agent.c
│       └── include/
├── model/                  # 存放语音唤醒和识别模型
└── sdkconfig              # 项目配置

5.2 主程序逻辑 (main/main.c)

#include <stdio.h>
#include "esp_log.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "ai_agent.h" // 我们自定义的AI代理头文件

// 定义LED/继电器控制的GPIO引脚
#define CONTROL_GPIO  GPIO_NUM_2

// 标签用于日志打印
static const char *TAG = "MAIN_ROBOT";

// GPIO初始化函数
static void gpio_init(void) {
    gpio_config_t io_conf = {};
    io_conf.pin_bit_mask = (1ULL << CONTROL_GPIO);
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.intr_type = GPIO_INTR_DISABLE;
    gpio_config(&io_conf);
    gpio_set_level(CONTROL_GPIO, 0); // 初始化为低电平(关闭)
    ESP_LOGI(TAG, "GPIO %d 初始化完成,初始状态:关闭", CONTROL_GPIO);
}

// 简单的本地命令处理函数(在AI回复前快速执行)
static bool handle_local_command(const char *text) {
    ESP_LOGI(TAG, "尝试处理本地命令: %s", text);
    // 判断是否包含控制指令
    if (strstr(text, "开灯") || strstr(text, "打开灯") || strstr(text, "turn on the light")) {
        gpio_set_level(CONTROL_GPIO, 1);
        ESP_LOGI(TAG, "执行命令:开灯");
        return true;
    }
    if (strstr(text, "关灯") || strstr(text, "关闭灯") || strstr(text, "turn off the light")) {
        gpio_set_level(CONTROL_GPIO, 0);
        ESP_LOGI(TAG, "执行命令:关灯");
        return true;
    }
    return false; // 不是本地命令,交给AI处理
}

void app_main(void) {
    ESP_LOGI(TAG, "🤖 我的AI机器人启动中...");

    // 1. 初始化硬件GPIO
    gpio_init();

    // 2. 初始化Wi-Fi(需要你先配置好Wi-Fi信息)
    // ... (Wi-Fi初始化代码,通常来自例程)

    // 3. 初始化语音唤醒和识别引擎
    // ... (语音初始化代码,来自ESP-SR例程)

    // 4. 初始化AI代理组件
    ai_agent_init();

    ESP_LOGI(TAG, "系统初始化完成!等待唤醒词或按键...");

    // 主循环
    while (1) {
        // 5. 等待语音唤醒(例如“嗨,乐鑫”)
        // 当唤醒函数返回时,表示检测到唤醒词

        // 6. 开始录音并识别
        // 识别结果会以文本形式得到,比如“今天天气怎么样”

        char *recognized_text = "开灯"; // 这里假设识别结果是“开灯”

        // 7. 先尝试处理本地命令
        if (handle_local_command(recognized_text)) {
            // 如果是本地命令,已经执行,可以直接合成语音回复“已开灯”
            // 然后跳过AI查询,继续等待下一次唤醒
            // ... (语音合成代码)
            continue;
        }

        // 8. 如果不是本地命令,则交给AI大模型处理
        char *ai_response = ai_agent_query(recognized_text);
        if (ai_response) {
            ESP_LOGI(TAG, "AI回复: %s", ai_response);
            // 9. 将AI回复的文字合成为语音,并通过喇叭播放
            // ... (语音合成代码)
            free(ai_response);
        }

        vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时,让出CPU
    }
}

5.3 AI代理组件 (components/ai_agent/ai_agent.c)

这是连接大模型的关键。我们以使用百度千帆API为例。

#include <string.h>
#include "esp_http_client.h"
#include "esp_log.h"
#include "cJSON.h"
#include "ai_agent.h"

static const char *TAG = "AI_AGENT";
// 你的API Key和Secret Key,从百度千帆控制台获取
static const char *API_KEY = "YOUR_API_KEY";
static const char *SECRET_KEY = "YOUR_SECRET_KEY";
static char ACCESS_TOKEN[256] = {0};

// 1. 获取Access Token
static bool obtain_access_token(void) {
    esp_http_client_config_t config = {
        .url = "https://aip.baidubce.com/oauth/2.0/token",
        .method = HTTP_METHOD_POST,
    };
    char post_data[256];
    snprintf(post_data, sizeof(post_data),
             "grant_type=client_credentials&client_id=%s&client_secret=%s",
             API_KEY, SECRET_KEY);

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_set_post_field(client, post_data, strlen(post_data));
    esp_http_client_set_header(client, "Content-Type", "application/x-www-form-urlencoded");

    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        int status_code = esp_http_client_get_status_code(client);
        if (status_code == 200) {
            int content_len = esp_http_client_get_content_length(client);
            char *buffer = malloc(content_len + 1);
            esp_http_client_read(client, buffer, content_len);
            buffer[content_len] = '\0';

            cJSON *root = cJSON_Parse(buffer);
            if (root) {
                cJSON *token = cJSON_GetObjectItem(root, "access_token");
                if (cJSON_IsString(token)) {
                    strncpy(ACCESS_TOKEN, token->valuestring, sizeof(ACCESS_TOKEN) - 1);
                    ESP_LOGI(TAG, "Access Token 获取成功");
                }
                cJSON_Delete(root);
            }
            free(buffer);
        }
    }
    esp_http_client_cleanup(client);
    return (ACCESS_TOKEN[0] != '\0');
}

// 2. 初始化AI代理
void ai_agent_init(void) {
    if (obtain_access_token()) {
        ESP_LOGI(TAG, "AI代理初始化成功");
    } else {
        ESP_LOGE(TAG, "AI代理初始化失败,请检查网络和API密钥");
    }
}

// 3. 向大模型发送查询并获取回复
char *ai_agent_query(const char *user_input) {
    if (ACCESS_TOKEN[0] == '\0') {
        ESP_LOGE(TAG, "未获取到有效的Access Token");
        return NULL;
    }

    char url[512];
    snprintf(url, sizeof(url),
             "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=%s",
             ACCESS_TOKEN);

    // 构造请求的JSON数据
    cJSON *request = cJSON_CreateObject();
    cJSON_AddStringToObject(request, "messages", user_input);
    // 可以添加更多参数,如model、temperature等
    char *post_data = cJSON_PrintUnformatted(request);
    cJSON_Delete(request);

    esp_http_client_config_t config = {
        .url = url,
        .method = HTTP_METHOD_POST,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_set_post_field(client, post_data, strlen(post_data));
    esp_http_client_set_header(client, "Content-Type", "application/json");

    char *ai_response = NULL;
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK && esp_http_client_get_status_code(client) == 200) {
        int len = esp_http_client_get_content_length(client);
        char *buffer = malloc(len + 1);
        esp_http_client_read(client, buffer, len);
        buffer[len] = '\0';

        // 解析返回的JSON,提取AI回复内容
        cJSON *root = cJSON_Parse(buffer);
        if (root) {
            // 根据千帆API的实际返回结构解析,这里是一个示例
            cJSON *result = cJSON_GetObjectItem(root, "result");
            if (cJSON_IsString(result)) {
                ai_response = strdup(result->valuestring); // 复制字符串
                ESP_LOGI(TAG, "成功获取AI回复");
            }
            cJSON_Delete(root);
        }
        free(buffer);
    } else {
        ESP_LOGE(TAG, "AI查询请求失败");
    }

    esp_http_client_cleanup(client);
    free(post_data);
    return ai_response; // 调用者需要负责释放这个内存
}

6. 第五章:开机!测试与调优

6.1 编译与烧录

在项目根目录下,执行以下命令:

# 1. 设置目标芯片
idf.py set-target esp32s3

# 2. 打开配置菜单(进行关键配置)
idf.py menuconfig

menuconfig中,你需要配置:

  • Example Configuration -> 输入你的Wi-Fi SSID和密码。
  • `Component config

参考来源

 

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐