本文是一篇实战教程,旨在引导您通过自定义 MCP(Model Context Protocol) 工具,结合语音指令控制硬件设备,实现智能环境监测。
我们将以 Arcs-mini 开发板为核心,展示如何通过语音触发 I2C 通信,驱动外接温湿度传感器,并实时返回环境数据。
无论您是物联网开发者、嵌入式爱好者,还是希望将语音交互能力融入硬件项目的创客,本教程都将带您一步步构建一个完整的“语音—I2C—传感器数据”软硬件联动系统。您将学习到如何配置 MCP 工具、编写 I2C 传感器驱动,并实现从语音指令到实时数据采集的无缝交互。
使用的温湿度传感器是 AHT10 ,参考手册下载链接:ASAIR-AHT10.pdf
实操之前,请确保已根据文档《Arcs-Mini 开发环境搭建指南》 搭建开发环境。
如果您不想重新编译代码而希望直接体验本固件,可点击下载。
固件下载链接:
mcp_tool_aht10.lpk
下载后,可以按照文档《恢复出厂固件 & 升级固件教程》 进行烧录
源码下载:apps.zip
下载后,将其替换 arcs_mini 项目的
apps文件夹
diff 文件下载链接:mcp_tool_AHT10.diff
下载后,可以通过命令
git apply ./mcp_tool_AHT10.diff应用更改
AHT10 连接 Arcs_mini 的扩展 GPIO 接口
PA02 --> VIN
PA03 --> GND
PA04 --> SCL
PA05 --> SDA

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

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

PA04 PA05 I2C0 外设, 可以找到 GPIOA_04 GPIOA_04 的 I2C0 外设对应 Function 8apps/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
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, "四击: 读取温湿度失败");
}
如果没有预期日志可以检查硬件连接是否正确

apps/arcs-mini/mcp_tools 文件夹下添加文件 mcp_tool_aht10.capps/arcs-mini/mcp_tools/CMakeLists.txt 添加代码: mcp_tool_aht10.c
在 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);
./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

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