【2026 AI硬件开发学习必看系列】通过MCPAI语音助手加上温湿度测量播报功能

前言

本文是一篇实战教程,旨在引导您通过自定义 MCP(Model Context Protocol) 工具,结合语音指令控制硬件设备,实现智能环境监测。

LS26Arcs-mini 开发板二次开发,展示如何通过语音触发 I2C 通信,驱动外接温湿度传感器,并实时返回环境数据。

演示视频https://docs2.listenai.com/zz/11585.mp4?shortId=BYshUSGvT

无论您是物联网开发者、嵌入式爱好者,还是希望将语音交互能力融入硬件项目的创客,本教程都将带您一步步构建一个完整的“语音—I2C—传感器数据”软硬件联动系统。您将学习到如何配置 MCP 工具、编写 I2C 传感器驱动,并实现从语音指令到实时数据采集的无缝交互。

使用的温湿度传感器是 AHT10 ,参考手册下载链接:https://docs2.listenai.com/zz/11282.pdf?shortId=BYshUSGvT

  • 目标:

当您说出“查询环境温湿度”,Arcs-mini 将通过 I2C(PA04/PA05)读取温湿度传感器数据,并语音回复当前温度值。实操之前,请确保已根据文档开发环境搭建与烧录 | 聆思文档中心 搭建开发环境。

固件下载

如果您不想重新编译代码而希望直接体验本固件,可点击下载。

固件下载链接:

https://docs2.listenai.com/zz/11821.lpk?shortId=BYshUSGvT

下载后,可以按照文档恢复出厂固件&升级固件教程 | 聆思文档中心(https://docs2.listenai.com/x/IMbN1kL5H) 进行烧录

代码示例

源码下载:apps.zip(https://docs2.listenai.com/zz/11820.zip?shortId=BYshUSGvT)

下载后,将其替换 arcs_mini 项目的 apps 文件夹

diff 文件下载链接:mcp_tool_AHT10.diff(https://docs2.listenai.com/zz/11587.diff?shortId=BYshUSGvT)

下载后,可以通过命令git apply ./mcp_tool_AHT10.diff 应用更改

一、硬件连接

AHT10 连接 Arcs_mini 的扩展 GPIO 接口

PA02 --> VIN

PA03 --> GND

PA04 --> SCL

PA05 --> SDA

二、初始化 温湿度计

1.新增文件

在项目下目录apps/arcs-mini/services目录下添加文件service_aht10.c service_aht10.h,然后修改 CMakeLists.txt 文件使新增文件能够参与编译

service_aht10.c2.根据业务选择需要使用的外设

下表截取自数据手册的APPENDIX章节

  • 本文档演示使用 PA04 PA05 I2C0 外设, 可以找到 GPIOA_04 GPIOA_04I2C0 外设对应 Function 8

3.新增 温湿度计 驱动

  • apps/arcs-mini/services/service_aht10.c 文件增加如下代码:

#include "service_aht10.h"
#include "IOMuxManager.h"
#include "Driver_I2C.h"
#include "lisa_log.h"
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"

#define TAG "aht10"

#define AHT10_I2C_ADDR              (0x38)
#define AHT10_CMD_INIT              (0xE1)
#define AHT10_CMD_MEASURE           (0xAC)
#define AHT10_CMD_SOFT_RESET        (0xBA)

#define SERVICE_AHT10_SDA_PIN       (5U)
#define SERVICE_AHT10_SCL_PIN       (4U)

#define AHT10_POWERON_DELAY_MS      (40)
#define AHT10_INIT_DELAY_MS         (20)
#define AHT10_RESET_DELAY_MS        (400)
#define AHT10_MEASURE_DELAY_MS      (80)
#define AHT10_MAX_RETRY             (3)

static void *g_i2c_dev = NULL;
static volatile uint32_t g_i2c_event = 0;
static uint8_t g_aht10_addr = AHT10_I2C_ADDR;
static uint8_t g_sda_pin = 0xFF;
static uint8_t g_scl_pin = 0xFF;

static void aht10_i2c_cb(uint32_t event, void *workspace)
{
    (void)workspace;
    g_i2c_event |= event;
}

static void aht10_delay_ms(uint32_t ms)
{
    vTaskDelay(ms);
}

static int aht10_wait_event(void)
{
    uint32_t timeout = 3000; // 3s timeout
    while (g_i2c_event == 0 && timeout--) {
        vTaskDelay(1);
    }
    return (g_i2c_event == 0) ? -1 : 0;
}


static int aht10_i2c_write(uint8_t addr, const uint8_t *data, uint32_t len)
{
    LISA_LOGI(TAG, "I2C write: addr=0x%02X, len=%d", addr, len);
    if (data && len > 0) {
        LISA_LOGI(TAG, "  data[0]=0x%02X", data[0]);
        if (len > 1) LISA_LOGI(TAG, "  data[1]=0x%02X", data[1]);
        if (len > 2) LISA_LOGI(TAG, "  data[2]=0x%02X", data[2]);
    }

    g_i2c_event = 0;
    int32_t ret = I2C_MasterTransmit(g_i2c_dev, addr, (uint8_t *)data, len, 0);
    LISA_LOGI(TAG, "I2C_MasterTransmit returned: %d", ret);

    if (aht10_wait_event() != 0) {
        LISA_LOGE(TAG, "I2C write timeout");
        return -1;
    }

    return 0;
}

static int aht10_i2c_read(uint8_t addr, uint8_t *data, uint32_t len)
{
    g_i2c_event = 0;
    I2C_MasterReceive(g_i2c_dev, addr, data, len, 0);

    if (aht10_wait_event() != 0) {
        LISA_LOGE(TAG, "I2C read timeout");
        return -1;
    }

    return 0;
}

int service_aht10_init(void)
{
    uint8_t sda_pin = SERVICE_AHT10_SDA_PIN;
    uint8_t scl_pin = SERVICE_AHT10_SCL_PIN;

    LISA_LOGI(TAG, "AHT10 I2C initialization starting, SDA=%d, SCL=%d", sda_pin, scl_pin);

    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, sda_pin, CSK_IOMUX_FUNC_ALTER8);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, scl_pin, CSK_IOMUX_FUNC_ALTER8);

    g_sda_pin = sda_pin;
    g_scl_pin = scl_pin;

    g_i2c_dev = I2C0();
    if (!g_i2c_dev) {
        LISA_LOGE(TAG, "I2C0 device not found");
        return -1;
    }

    I2C_Initialize(g_i2c_dev, aht10_i2c_cb, NULL);
    I2C_PowerControl(g_i2c_dev, CSK_POWER_FULL);
    I2C_Control(g_i2c_dev, CSK_I2C_TRANSMIT_MODE, 0);
    I2C_Control(g_i2c_dev, CSK_I2C_BUS_SPEED, CSK_I2C_BUS_SPEED_STANDARD);
    I2C_Control(g_i2c_dev, CSK_I2C_BUS_CLEAR, 0);

    aht10_delay_ms(AHT10_POWERON_DELAY_MS);


    LISA_LOGI(TAG, "AHT10 I2C initialized, device addr=0x%02X", g_aht10_addr);
    return 0;
}

int service_aht10_read_data(float *humidity, float *temperature)
{
    if (!humidity || !temperature || !g_i2c_dev) {
        return -1;
    }

    uint8_t status = 0;
    uint8_t data[6] = {0};

    // Step 1: Read status byte to check calibration bit[3]
    LISA_LOGI(TAG, "Reading status byte...");
    if (aht10_i2c_read(g_aht10_addr, &status, 1) != 0) {
        LISA_LOGE(TAG, "Failed to read status byte");
        return -1;
    }

    uint8_t calibrated = (status >> 3) & 0x01;
    uint8_t busy = (status >> 7) & 0x01;
    LISA_LOGI(TAG, "Status: 0x%02X, calibrated=%d, busy=%d", status, calibrated, busy);

    // Step 2: Check calibration bit[3], send init command if not calibrated
    if (calibrated == 0) {
        LISA_LOGI(TAG, "Sensor not calibrated, sending init command (0xE1 0x08 0x00)...");
        uint8_t init_cmd[3] = {AHT10_CMD_INIT, 0x08, 0x00};
        if (aht10_i2c_write(g_aht10_addr, init_cmd, 3) != 0) {
            LISA_LOGE(TAG, "Init command write failed");
            return -1;
        }
        aht10_delay_ms(AHT10_INIT_DELAY_MS);
        LISA_LOGI(TAG, "Init command sent and delayed");
    } else {
        LISA_LOGI(TAG, "Sensor already calibrated, skip init");
    }

    // Step 3: Send measure command (0xAC 0x33 0x00)
    LISA_LOGI(TAG, "Sending measure command (0xAC 0x33 0x00)...");
    uint8_t measure_cmd[3] = {AHT10_CMD_MEASURE, 0x33, 0x00};
    if (aht10_i2c_write(g_aht10_addr, measure_cmd, 3) != 0) {
        LISA_LOGE(TAG, "Measure command write failed");
        return -1;
    }

    // Step 4: Wait 100ms for measurement
    LISA_LOGI(TAG, "Waiting 100ms for measurement...");
    aht10_delay_ms(100);

    // Step 5: Poll status byte, check busy bit[7]
    for (int retry = 0; retry < AHT10_MAX_RETRY; retry++) {
        if (aht10_i2c_read(g_aht10_addr, &status, 1) != 0) {
            LISA_LOGW(TAG, "Failed to read status in poll, retry %d", retry + 1);
            aht10_delay_ms(AHT10_MEASURE_DELAY_MS);
            continue;
        }

        busy = (status >> 7) & 0x01;
        LISA_LOGI(TAG, "Poll status: 0x%02X, busy=%d", status, busy);

        if (busy == 0) {
            // Step 6: Data ready, read 6 bytes
            LISA_LOGI(TAG, "Data ready, reading 6 bytes...");
            if (aht10_i2c_read(g_aht10_addr, data, 6) != 0) {
                LISA_LOGE(TAG, "Failed to read measurement data");
                return -1;
            }

            // Parse humidity from bytes 1-3 (20-bit value)
            uint32_t raw_humi = ((uint32_t)(data[1]) << 12) | ((uint32_t)(data[2]) << 4) | ((data[3] & 0xF0) >> 4);
            *humidity = (float)raw_humi * 100.0f / 1048576.0f;

            // Parse temperature from bytes 3-5 (20-bit value)
            uint32_t raw_temp = ((uint32_t)(data[3] & 0x0F) << 16) | ((uint32_t)(data[4]) << 8) | data[5];
            *temperature = (float)raw_temp * 200.0f / 1048576.0f - 50.0f;

            LISA_LOGI(TAG, "Humidity: %.2f%%, Temperature: %.2f°C", *humidity, *temperature);
            return 0;
        }

        // Still busy, wait and retry
        if (retry < AHT10_MAX_RETRY - 1) {
            LISA_LOGI(TAG, "Measurement still busy, polling retry %d/%d", retry + 1, AHT10_MAX_RETRY);
            aht10_delay_ms(AHT10_MEASURE_DELAY_MS);
        }
    }

    LISA_LOGE(TAG, "Timeout waiting for measurement to complete");
    return -1;
}

int service_aht10_get_temperature_humidity(float *humidity, float *temperature)
{
    if (!humidity || !temperature) {
        return -1;
    }

    // 调用 AHT10 驱动读取温湿度
    int ret = service_aht10_read_data(humidity, temperature);
    if (ret != 0) {
        LOGE("Failed to read AHT10 sensor data, ret=%d", ret);
        return -1;
    }

    LOGI("Sensor data - Humidity: %.2f%%, Temperature: %.2f°C",
          *humidity, *temperature);

    return 0;
}

  • apps/arcs-mini/services/service_aht10.h 文件增加如下代码:

#ifndef SERVICE_AHT10_H
#define SERVICE_AHT10_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief 初始化 AHT10 传感器(I2C)
 * 使用服务内固定引脚进行初始化。
 *
 * @return 0 成功, 非0 失败
 */
int service_aht10_init(void);

/**
 * @brief 读取当前湿度和温度
 *
 * @param humidity 输出湿度百分比(0-100)
 * @param temperature 输出温度(摄氏度)
 * @return 0 成功, 非0 失败
 */
int service_aht10_read_data(float *humidity, float *temperature);

/**
 * @brief 读取温湿度传感器数据
 * @param humidity 湿度输出指针
 * @param temperature 温度输出指针
 * @return 0成功,-1失败
 */
int service_aht10_get_temperature_humidity(float *humidity, float *temperature);

#ifdef __cplusplus
}
#endif

#endif // SERVICE_AHT10_H

三、增加功能测试代码

1. 修改文件

  • 引入头文件,在 apps/arcs-mini/main.c 中顶部添加代码:

#include "service_aht10.h"

  • 调用初始化函数,在 apps/arcs-mini/main.c 中的 main() 函数中添加代码:

service_aht10_init();

  • 按键触发温度获取,在 main.c 中的 button_changed() 函数中添加代码:

/* 四击: 读取并打印温湿度 */
float humidity = 0.0f;
float temperature = 0.0f;
extern int service_aht10_get_temperature_humidity(float *humidity, float *temperature);
int ret = service_aht10_get_temperature_humidity(&humidity, &temperature);
if (ret == 0) {
    LISA_LOGI(TAG, "四击: 当前温度: %.2f°C, 湿度: %.2f%%", temperature, humidity);
} else {
    LISA_LOGE(TAG, "四击: 读取温湿度失败");
}

2.测试功能

  • 按4下电源键,观察串口日志,预期应该有如下日志:

如果没有预期日志可以检查硬件连接是否正确

四、端侧注册 MCP 工具

1.新增文件

  • apps/arcs-mini/mcp_tools 文件夹下添加文件 mcp_tool_aht10.c
  • 修改 apps/arcs-mini/mcp_tools/CMakeLists.txt 添加代码: mcp_tool_aht10.c

2.添加 MCP 工具

apps/arcs-mini/mcp_tools/mcp_tool_aht10.c 文件下添加代码:

#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include <string.h>
#include <stdio.h>
#include "service_aht10.h"

#define TAG "mcp_tool_aht10"


/**
 * @brief 温湿度传感器工具的列表信息创建函数
 * @param name 工具名称
 * @return 创建的cJSON对象,失败返回NULL
 */
static cJSON *mcp_tool_aht10_list(const char *name)
{
    // 创建默认工具信息,描述温湿度查询功能
    cJSON *tool = mcp_tool_list_info_create_default(name, "获取环境温湿度传感器数据,包括当前温度(摄氏度)和湿度(百分比)");
    if (!tool) {
        return NULL;
    }

    // 该工具无入参,无需添加property
    return tool;
}

/**
 * @brief 温湿度传感器工具的调用处理函数
 * @param id 工具ID
 * @param name 工具名称
 * @param args 调用参数(该工具无参数)
 * @return 调用结果的cJSON对象,失败返回NULL
 */
static cJSON *mcp_tool_aht10_call(const char *id, const char *name, cJSON *args)
{
    float humidity = 0.0f;
    float temperature = 0.0f;

    // 读取传感器数据
    int ret = service_aht10_get_temperature_humidity(&humidity, &temperature);
    if (ret != 0) {
        LOGE("Read sensor data failed");

        // 创建错误响应
        cJSON *result = mcp_tool_call_result_create(name);
        if (!result) {
            return NULL;
        }

        cJSON *content_array = cJSON_CreateArray();
        cJSON *content_item = cJSON_CreateObject();
        cJSON_AddStringToObject(content_item, "type", "text");
        cJSON_AddStringToObject(content_item, "text", "读取传感器数据失败,请检查设备连接");
        cJSON_AddItemToArray(content_array, content_item);
        cJSON_AddItemToObject(result, "content", content_array);
        cJSON_AddBoolToObject(result, "isError", 1);

        return result;
    }

    // 构建成功响应
    cJSON *result = mcp_tool_call_result_create(name);
    if (!result) {
        return NULL;
    }

    // 拼接温湿度结果文本
    char result_msg[256];
    snprintf(result_msg, sizeof(result_msg),
            "当前环境温度为 %.1f°C,湿度为 %.1f%%", temperature, humidity);

    cJSON *content_array = cJSON_CreateArray();
    cJSON *content_item = cJSON_CreateObject();
    cJSON_AddStringToObject(content_item, "type", "text");
    cJSON_AddStringToObject(content_item, "text", result_msg);
    cJSON_AddItemToArray(content_array, content_item);
    cJSON_AddItemToObject(result, "content", content_array);
    cJSON_AddBoolToObject(result, "isError", 0);

    return result;
}

MCP_TOOL_DEFINE(sensor_temperature_humidity, mcp_tool_aht10_list, mcp_tool_aht10_call);

3.测试 MCP 工具

编译

./build.sh -S  ./apps/arcs-mini

烧录
 adb shell recovery
adb push res/arcs-mini/ap.bin /RAW/NAND/40000
adb push res/arcs-mini/tone.bin /RAW/NAND/100000
adb push res/arcs-mini/wake_word.bin /RAW/NAND/200000
adb push res/arcs-mini/respak.bin /RAW/NAND/400000
adb push build/arcs-mini.bin /RAW/NAND/600000
adb shell reboot hard

预期效果
  • 唤醒小聆,然后说 "环境温度湿度多少?" ,小聆回复环境温度就成功了

至此, MCP 工具已经可以调用温湿度计查询环境温度了

总结和信息补充

MCP协议在拓展智能硬件功能时带来很大便利性,不仅可以让智能硬件可以快捷的调用互联网服务,也可以让外设和感应器等外设接入更简单。

更多智能硬件接MCP的方式和示例会陆续分享,有需求的朋友可以直接关注或在评论区留言,我们会持续分享相关操作示例。

本文操作示例中使用的硬件是LS26Arcs-mini大模型开发板支持二次开发做更多个性化功能和DIY改造,需要了解硬件详细信息可以参考:https://docs2.listenai.com/x/IPiXdnAJg

如果还想进阶学习更多离线AI示例上手Zephyr 开发可以选择CSK6大模型视觉语音开发套件硬件详细信息可以参考:https://docs2.listenai.com/x/CNCwAs0Dv

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐