以 LS26(Arcs-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_04 的 I2C0 外设对应 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
● 引入头文件,在 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, "四击: 读取温湿度失败");
}
● 按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);
● 在终端按下列顺序执行编译、进入烧录模式、烧录、重启命令:(请确保已经根据文档开发环境搭建与烧录 | 聆思文档中心 下载烧录脚本并已安装 ADB 工具)
./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的方式和示例会陆续分享,有需求的朋友可以直接关注或在评论区留言,我们会持续分享相关操作示例。
本文操作示例中使用的硬件是LS26(Arcs-mini)大模型开发板,支持二次开发做更多个性化功能和DIY改造,需要了解硬件详细信息可以参考:https://docs2.listenai.com/x/IPiXdnAJg
如果还想进阶学习更多离线AI示例和上手Zephyr 开发,可以选择CSK6大模型视觉语音开发套件,硬件详细信息可以参考:https://docs2.listenai.com/x/CNCwAs0Dv
我要赚赏金
