发帖
8 1 1

基于 Ai-WV01-32S+STM32移植 emMCP 实现 AI 语音控制点灯

云烟
中级会员

6

主题

4

回帖

408

积分

中级会员

积分
408
小安AI 841 8 2026-1-8 14:42:18
[i=s] 本帖最后由 云烟 于 2026-1-8 14:42 编辑 [/i]

基于 Ai-WV01-32S+STM32移植 emMCP 实现 AI 语音控制点灯

前言

emMCP(Easy MCU MCP)是 AI-Thinker 推出的轻量级 UART-MCP 适配库,旨在降低 MCU 对接 AI 语音模组的开发门槛。
该库封装了 UART-MCP 协议的核心交互逻辑,提供简洁的 API 接口,开发者无需深入理解复杂的协议细节,
即可快速实现 MCU 与 AI 语音模组的通信,完成 AI 语音控制外设的开发。
帮助开发者快速使用 MCU 接入 AI 模型进行 MCP 交互。官方详细介绍
本文将以 STM32F103C8T6(HAL 库)为例,完整讲解 emMCP 库的移植、配置流程,并最终实现AI语音对话控制 LED 灯亮灭的功能,适合有一定基础 STM32 HAL 库开发经验的开发者参考。

一、准备工作

1.1 硬件清单

硬件名称 数量 备注
STM32F103C8T6开发板 1 核心控制MCU
Ai-WV01-32S AI 语音模组 1 需刷入小安 AI V2.8 测试固件
ST-Link 烧录器 1 烧录 STM32 程序
扬声器 1 连接 AI 模组,播放语音反馈
麦克风 1 连接 AI 模组,采集语音指令
LED 灯 1 被控外设(示例用 PB5 引脚)
杜邦线 若干 硬件连接

1.2 软件清单

软件/资源 获取地址
emMCP 源码 GitHub Gitee
小安 AI V2.8 测试固件 点击下载

前置要求

  • 已完成 STM32 USART1(用于log打印调试) 和USART2 的 HAL 库配置(开启 DMA 接收、RxEvent 中断),确保串口收发功能正常;
  • 已完成 Ai-WV01-32S 模组的固件烧录,并验证模组可正常唤醒(默认唤醒词:“你好小安”);
  • 熟悉 STM32 HAL 库基础操作(GPIO、UART、DMA、中断)。

二、emMCP 源码获取与目录解析

  • 建议直接通过 Git 克隆源码仓库,也可点击链接跳转直接下载 ZIP包:
#GitHub
git clone https://github.com/Ai-Thinker-Open/emMCP.git
#Gitee
git clone https://gitee.com/Ai-Thinker-Open/emMCP.git
  • 下载好的文件 目录结构如下
emMCP
├── example               ------> 示例代码文件加
│   ├── STM32F10xRTOS_MCP ------> STM32F103 的 CMake 示例
│   └── ...
├── port                  ------> emMCP 移植接口文件夹
│   ├── uartPort.h        ------> emMCP 移植接口头文件
│   └── uartPort.c        ------> emMCP 移植接口源文件
└── uart-mcp              ------> emMCP 串口通讯文件夹
    ├──cJSON              ------> cJSON 库
    │  ├── cJSON.h        ------> cJSON 头文件
    │  └── cJSON.c        ------> cJSON 源文件
    ├── emMCP.h           ------> emMCP 头文件
    └── emMCP.c           ------> emMCP 源文件

三、emMCP 库移植到 STM32 工程

  1. emMCP 源码中的 portuart-mcpAPP文件夹复制到你的 STM32 工程目录下(建议新建 emMCP文件夹统一存放),工程目录示例:

01.png

  1. 打开IDE编辑工程关联上述文件夹中的 .c.h文件,本章使用VScode,涉及插件安装以及配置可参考官方文章: 点击跳转

02.png

四、接口移植配置

  • 本节提示 :记得在配置接口函数时 #include相应的头文件哦
  1. 打开 port/uartPort.c 文件,找到函数在函数 int uartPortSendData(char *data, int len)
    当中调用你的串口发送函数,将 datalen 作为参数传入。例如:
#include "uartPort.h"
#include "stm32f1xx_hal.h"

// 全局UART句柄(需与实际配置的串口一致,示例为USART2)
extern UART_HandleTypeDef huart2;

/**
 * @brief 串口发送函数接口
 *
 * @param data
 * @param len
 * @return int
 */
int uartPortSendData(char *data, int len) {
  
  if (data == NULL || len <= 0) {
    return -1;
  }
  // 在此处实现串口发送函数
  return HAL_UART_Transmit(&huart2, (uint8_t *)data, len, 100); // 返回发送状态
}
  1. 内存管理函数的实现,在 port/port.h 文件中,将内存管理函数在宏 #define emMCP_malloc #define emMCP_free 中实现,例如:

05.png

  • 注意:若工程启用了 RTOS,可替换为 RTOS 的内存管理函数(如pvPortMalloc/vPortFree)
  1. 延时函数的实现,在 port/port.h 文件中,将延时函数在宏 #define emMCP_delay 中实现,例如:
    06.png
  • 注意:若工程启用了 RTOS,可替换为 RTOS 的osDelay
  1. 在 MCU 的串口接收函数当中调用 int uartPortRecvData(char *data, int len)的接收函数,emMCP 的接收逻辑需要在 STM32 串口接收中断中触发,推荐使用 DMA+RxEvent 中断方式(避免数据丢失)例如:
#include "emMCP.h"
#include "uartPort.h"

#define UART_RXBUFF_MAX 256
emMCP_t emMCP;
uint8_t rxBuffer[UART_RXBUFF_MAX] = {0};
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
  if (huart == &huart2) {
    //调用emMCP接收函数
    uartPortRecvData((char *)rxBuffer, Size);
    HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rxBuffer, UART_RXBUFF_MAX);
    __HAL_DMA_DISABLE_IT(huart->hdmarx, DMA_IT_HT);
  }
}
  1. main.cmain函数中,初始化串口 DMA接收:
// 其他初始化(时钟、GPIO、UART等)完成后
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rxBuffer, UART_RXBUFF_MAX);
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT); // 禁用半传输中断
  1. main.c中初始化emMCP,并在主循环 while中调用 emMCP_TickHandle()例如:
static emMCP_t emMCP_dev;
int main(void)
{
    //其他初始化函数

	emMCP_Init(&emMCP_dev);
    while(1)
	{
		emMCP_TickHandle(10);

        //其他任务
	}
}

6.完成上述步骤后,编译项目,确保零错误零警告,按照以下步骤验证是否移植成功:

  • 下载程序到MCU
  • 等待小安初始化成功
  • 对AI模组说:“你好小安”
  • 得到AI模组回复后观察串口助手输出,得到以下类似输出,说明emMCP成功移植到MCU上:
[DEBUG] emMCP_EventCallback:99: emMCP_EventCallback: event:10,type:4,param:2.WakeUP
[DEBUG] emMCP_EventCallback:99: emMCP_EventCallback: event:11,type:4,param:3.Sleep

四、AI 语音控制点灯功能开发配置

  1. 需要在 main初始化配置emMCP结构体参数;详细的各函数参数配置描述可以参考 官方详细介绍 这里直接给出我的配置如下:
emMCP_tool_t relay;//创建工具
int main(void)
{
  // 其他初始化代码
  // 初始化MCP
  emMCP_SetAiWakeUp(10);//唤醒10s
  emMCP_Init(&emMCP);
  relay.name = "继电器";//工具名称,保持唯一性
  relay.description = "用来控制继电器的开关";//工具的功能描述
  relay.inputSchema.properties[0].name = "enable";//属性指令,AI 通过这个指令发送命令
  relay.inputSchema.properties[0].description = "控制继电器,打开:true,关闭为:false,查询为null";  //指令描述,AI 通过这个描述理解指令
  relay.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN;//指令类型,AI 通过这个类型发送相对应的数据
  relay.setRequestHandler = emMCP_SetRelayHandler;//设置控制回调
  relay.checkRequestHandler = emMCP_GetRelayHandler;//设置查询回调
  emMCP_AddToolToToolList(&relay);   // 添加工具到工具列表
  emMCP_RegistrationTools(); // 注册工具到小安AI
  while (1)
  {
    emMCP_TickHandle(10);
  }
}

2.配置核心控制回调函数 emMCP_SetRelayHandler,emMCP_GetRelayHandler,该部分为控制效果的实际体现,注意你实际LED灯对应的引脚。我的配置如下:

//控制回调函数
static void emMCP_SetRelayHandler(void *arg)
{
    // 接收到的数据
  cJSON *param = (cJSON *)arg;
  // 控制继电器
  cJSON *enable = cJSON_GetObjectItem(param, "enable");//获取继电器命令   
  if (enable != NULL) {
    if (enable->valueint == 1) {
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
    } else if (enable->valueint == 0) {
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
    }
    emMCP_ResponseValue(emMCP_CTRL_OK); //返回控制成功
  }else {
    emMCP_ResponseValue(emMCP_CTRL_ERROR); //返回控制失败
  }
}
//查询回调函数
static void emMCP_GetRelayHandler(void *arg){}

五、功能测试

  1. 编译烧录程序到 STM32,确保硬件连接正常;
  2. 唤醒 AI 模组:“你好小安”(模组回应 “你好呀”);
  3. 发送语音指令:
    “打开继电器” → PB5 引脚输出低电平,LED 点亮;
    “关闭继电器 → PB5 引脚输出高电平,LED 熄灭;
  4. 模组会语音反馈 “已打开 LED 灯”/“已关闭 LED 灯”,同时串口输出指令日志。
  • 串口输出的日志如下:

07.png

  • 实际效果:

动图演示效果.gif

──── 1人觉得很赞 ────

使用道具 举报

真棒!
2026-1-8 22:15:39
好厉害!
好厉害!
2026-1-9 14:57:32
太棒了~!
2026-1-9 15:20:45
这个设备已经开始卖了吗,还是自己打的板呀,记得莫工发过这个图来的。
2026-1-12 10:23:11
WT_0213 发表于 2026-1-9 15:20
这个设备已经开始卖了吗,还是自己打的板呀,记得莫工发过这个图来的。

目前还没开卖,我这属于是版本前瞻调试
2026-1-13 17:50:12
云烟 发表于 2026-1-12 10:23
目前还没开卖,我这属于是版本前瞻调试

真棒呀
真不错,值得一学
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 30708 个
  • 话题数: 44755 篇