本文是一篇实战教程,旨在引导你通过自定义 MCP(Model Context Protocol)工具,结合语音指令控制硬件设备。我们将以 Arcs-mini 开发板为例,展示如何通过语音触发串口通信,最终在扩展 GPIO 接口输出串口信息。
无论你是物联网开发者、嵌入式爱好者,还是希望将语音交互能力接入硬件项目的创客,本教程都将带你一步步实现一个完整的 “语音—串口—GPIO” 软硬件联动示例。
你将学习到如何配置 MCP 工具、编写串口控制逻辑,并实现语音到硬件的无缝控制。
本文以
PA07PA08作为示例,可以根据业务需要修改成PA04PA05或者是其他引脚。
这具体取决于引脚功能表,如下图
实操之前,请确保已根据文档《Arcs-Mini 开发环境搭建指南》 搭建开发环境。
如果您不想重新编译代码而希望直接体验本固件,可点击下载。
固件下载链接:
mcp_tool_uart1.lpk
下载后,可以按照文档《恢复出厂固件 & 升级固件教程》 进行烧录
如果您想直接查看所有代码更爱,可点击下载。
diff 文件下载链接:mcp_tool_uart1.diff
下载后,将其放到 arcs_mini 项目根目录,然后执行可命令
git apply ./mcp_tool_uart1.diff应用更改
在arcs_mini项目下的apps/arcs_mini/services 目录下添加service_uart1.c service_uart1.h,
修改同目录下CMakeLists.txt 文件,添加语句 service_uart.c
使新增文件能够参与编译,如下

./arcs_mini/arcs-sdk/drivers/lisa_uart/lisa_uart.h下API实现简单的串口功能
实现目标的代码如下:
编写service_uart1.c如下:
/*
* Copyright (c) 2025, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file service_uart1.c
* @brief UART1 回显示例服务
*
* 示例流程:
* 1. 初始化 UART1 并配置接收缓冲区;
* 2. 使能接收后,由接收任务直接阻塞在 lisa_uart_read_sync() 上等待数据;
* 3. 收到数据后,通过同步发送接口原样回射。
*
* 该示例适合演示在业务层封装一个简单的 UART echo 服务。
*/
#define LOG_TAG "sample"
#include <lisa_log.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "lisa_device.h"
#include "lisa_uart.h"
#include "IOMuxManager.h"
#include "FreeRTOS.h"
#include "task.h"
#include "service_uart1.h"
#define UART_DEVICE "uart1"
/* UART1 默认引脚映射(ARCS_MINI: PA7=TX, PA6=RX) */
#define UART1_TX_PAD CSK_IOMUX_PAD_A
#define UART1_TX_PIN 7
#define UART1_RX_PAD CSK_IOMUX_PAD_A
#define UART1_RX_PIN 8
#define UART1_FUNC CSK_IOMUX_FUNC_ALTER3
/* 配置 UART1 的引脚复用;如板级连线变化,可在此处调整。 */
void lisa_uart1_pinmux()
{
IOMuxManager_PinConfigure(UART1_TX_PAD, UART1_TX_PIN, UART1_FUNC);
IOMuxManager_PinConfigure(UART1_RX_PAD, UART1_RX_PIN, UART1_FUNC);
LISA_LOGI(LOG_TAG, "uart1 pinmux configured: TX=PA%02d RX=PA%02d", UART1_TX_PIN, UART1_RX_PIN);
}
int service_uart1_send(const uint8_t *buf, uint32_t len)
{
if (buf == NULL || len == 0) {
LISA_LOGE(LOG_TAG, "Error: invalid buffer/length");
return -1;
}
lisa_device_t *uart_dev = lisa_device_get(UART_DEVICE);
if (!lisa_device_ready(uart_dev)) {
LISA_LOGE(LOG_TAG, "Error: UART device not ready");
return -1;
}
/* 对外提供一个简单的阻塞发送接口,适合日志或小包发送场景。 */
int ret = lisa_uart_write_sync(uart_dev, buf, len, 5000);
if (ret < 0) {
LISA_LOGE(LOG_TAG, "Error: Send failed %d", ret);
return ret;
}
return ret;
}
/* 接收任务:直接阻塞读取串口数据,收到后立即回射。 */
static void service_uart1_rx_task(void *arg)
{
lisa_device_t *uart_dev = (lisa_device_t *)arg;
if (!uart_dev) {
vTaskDelete(NULL);
return;
}
uint8_t buf[256];
while (1) {
/*
* 直接阻塞在驱动的同步读接口上。
* 底层会通过内部信号量等待 RX 中断/空闲中断,这里不再额外套一层应用信号量。
*/
int r = lisa_uart_read_sync(uart_dev, buf, sizeof(buf));
if (r > 0) {
/* 将本次收到的数据原样发回,形成 echo 效果。 */
service_uart1_send(buf, (uint32_t)r);
} else if (r < 0) {
LISA_LOGW(LOG_TAG, "UART read failed: %d", r);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
vTaskDelete(NULL);
}
static void service_uart1_task(void *arg)
{
(void)arg;
static const uint8_t startup_msg[] = "UART1 echo ready on PA07/PA06 @115200\r\n";
LISA_LOGI(LOG_TAG, "=== LISA UART echo (high-level API) example ===");
/* 获取并检查 UART1 设备句柄。 */
lisa_device_t *uart_dev = lisa_device_get(UART_DEVICE);
if (!lisa_device_ready(uart_dev)) {
LISA_LOGE(LOG_TAG, "Error: UART device not ready");
vTaskDelete(NULL);
return;
}
/*
* 使用默认参数配置串口:115200、8N1、中断模式。
* 这里额外打开 Ping-Pong 接收缓冲区,否则 rx_enable() 后不会持续搬运接收数据,
* echo 场景下也就收不到 RX_READY / RX_TIMEOUT 事件。
*/
lisa_uart_config_t config = LISA_UART_CONFIG_DEFAULT();
config.rx_buf_config.buffer_count = 2;
config.rx_buf_config.buffer_size = 256;
if (lisa_uart_configure(uart_dev, &config) != 0) {
LISA_LOGE(LOG_TAG, "Error: UART configure failed");
vTaskDelete(NULL);
return;
}
/* 先使能接收,再由 rx 任务通过同步读接口阻塞取数。 */
if (lisa_uart_rx_enable(uart_dev) != 0) {
LISA_LOGE(LOG_TAG, "Error: Failed to enable RX");
vTaskDelete(NULL);
return;
}
/* 单独创建接收任务,专门负责阻塞读取和回射。 */
BaseType_t xret = xTaskCreate(service_uart1_rx_task, "uart1_rx", 1024, uart_dev, 6, NULL);
if (xret != pdPASS) {
LISA_LOGE(LOG_TAG, "Error: Failed to create rx task");
vTaskDelete(NULL);
return;
}
/* 主动发送一条启动消息,方便用串口工具直接确认 TX 是否正常。 */
if (service_uart1_send(startup_msg, sizeof(startup_msg) - 1) < 0) {
LISA_LOGW(LOG_TAG, "UART1 startup banner send failed");
}
LISA_LOGI(LOG_TAG, "UART ready: send via service_uart1_send(buf,len). Echo enabled.");
/* 初始化完成后当前任务退出,串口收发由 rx 任务继续工作。 */
vTaskDelete(NULL);
}
int service_uart1_init(void)
{
/* 通过独立初始化任务完成设备配置,避免阻塞调用方。 */
BaseType_t ret = xTaskCreate(service_uart1_task, "uart1_task", 1024, NULL, 5, NULL);
if (ret != pdPASS) {
LISA_LOGE(LOG_TAG, "Error: Failed to create uart1 task");
return -1;
}
return 0;
}
编写service_uart1.h如下:
#ifndef SERVICE_UART1_H
#define SERVICE_UART1_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int service_uart1_init(void);
int service_uart1_send(const uint8_t *buf, uint32_t len);
#ifdef __cplusplus
}
#endif
#endif // SERVICE_UART1_H
apps/arcs-mini/main.c 中顶部添加代码:
#include "service_uart1.h"
/apps/arcs_mini/main.c 的 main() 函数,添加代码: service_uart1_init();

./build.sh -S ./apps/arcs-mini && ./res/arcs-mini/adb_dowanload.sh


PA07 --> RxGND --> GNDPA08 --> Tx

至此,扩展 GPIO 接口已经成功应用,接下来将讲解如何通过 MCP 工具调用该接口
/apps/arcs_mini/mcp_tools 文件夹下添加文件 mcp_tool_uart1.c修改同目录下CMakeLists.txt 文件,添加语句 service_uart.c
使新增文件能够参与编译,如下

/apps/arcs_mini/mcp_tools/mcp_tool_uart1.c 文件下添加代码:#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "service_uart1.h"
#define TAG "mcp_tool_uart1"
static cJSON *uart1_result(const char *name, const char *text, bool is_error)
{
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
if (!content_array || !content_item) {
cJSON_Delete(content_array);
cJSON_Delete(content_item);
cJSON_Delete(result);
return NULL;
}
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", text);
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", is_error);
return result;
}
/**
* @brief UART发送工具的列表信息创建函数
* @param name 工具名称
* @return 创建的cJSON对象,失败返回NULL
*/
static cJSON *mcp_tool_uart1_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name, "串口发送工具,支持将用户指定文本通过 UART1 发送。");
if (!tool) {
return NULL;
}
cJSON *content_property = cJSON_CreateObject();
cJSON_AddStringToObject(content_property, "type", "string");
cJSON_AddStringToObject(content_property, "description",
"要发送到 UART1 的文本。例如:用户说“串口发送hello”,则 content=hello。");
mcp_tool_info_add_json_property(tool, "content", content_property, true);
return tool;
}
/**
* @brief UART发送工具的调用处理函数(核心:发送Hello World!)
* @param id 工具ID
* @param name 工具名称
* @param args 调用参数(无参数)
* @return 调用结果的cJSON对象,失败返回NULL
*/
static cJSON *mcp_tool_uart1_call(const char *id, const char *name, cJSON *args)
{
(void)id;
const cJSON *content_json = mcp_tool_call_args_get(args, "content");
const char *send_msg = NULL;
int sent_len = 0;
if (!content_json || !cJSON_IsString(content_json) || !content_json->valuestring || content_json->valuestring[0] == '\0') {
return uart1_result(name, "参数 content 不能为空。", true);
}
send_msg = content_json->valuestring;
sent_len = service_uart1_send((const uint8_t *)send_msg, (uint32_t)strlen(send_msg));
if (sent_len < 0) {
LOGE("UART1 send failed: %d", sent_len);
return uart1_result(name, "UART1 发送失败。", true);
}
if ((uint32_t)sent_len != strlen(send_msg)) {
LOGE("UART1 partial send: expect=%u actual=%d", (unsigned int)strlen(send_msg), sent_len);
return uart1_result(name, "UART1 发送不完整。", true);
}
LOGI("UART1 sent(%d): %s", sent_len, send_msg);
char result_msg[256];
snprintf(result_msg, sizeof(result_msg), "UART1 已发送:%s", send_msg);
return uart1_result(name, result_msg, false);
}
MCP_TOOL_DEFINE(uart1_send, mcp_tool_uart1_list, mcp_tool_uart1_call);
./build.sh -S ./apps/arcs-mini && ./res/arcs-mini/adb_dowanload.sh


PA07 --> RxGND --> GNDPA08 --> Tx

至此, MCP 工具已经可以调用扩展 GPIO 接口,接下来将讲解如何在接入自定义编排应用情况下,通过 MCP 工具调用调用扩展 GPIO 接口