10浏览
查看: 10|回复: 10

[ESP8266/ESP32] 【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

[复制链接]
原标题
【花雕动手做】从零实现飞书 AI 控舵机:ESP32-S3 的 MimiClaw 嵌入式实践
——从“会发光”到“会动”,让你的嵌入式 AI Agent 拥有物理交互能力



引言

在上一篇文章中,我们已实现通过飞书发送“红”“绿”“蓝”等指令,控制 ESP32‑S3 板载的 WS2812 RGB LED 切换颜色,并完成了多色呼吸灯效果的开发。但静态灯效始终缺乏“灵性”,若能让 AI Agent 实现物理运动——比如控制舵机旋转,便能解锁自动开门、机械臂抓取、摄像头云台调控等更丰富的物理交互场景。

这里以 SG90 微型舵机为核心,详细讲解如何在 MimiClaw 框架下添加舵机控制工具,通过飞书自然语言指令(如“舵机0”“舵机90”“舵机180”)实现舵机远程精准控制。全文提供完整可运行代码,适配嵌入式 AI 爱好者、物联网开发者学习参考,手把手带你实现从“静态显示”到“动态交互”的突破。


【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图1

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

一、舵机控制原理

1.1 SG90 舵机简介

SG90 是一款应用广泛的微型伺服电机,重量仅 9g,扭矩约 1.6kg·cm,凭借小巧的体积和稳定的性能,常被用于机器人关节、智能小车转向、门禁锁控制等场景。它采用三线制接口,各线路功能明确,便于接线操作:

- 红色:电源接口,必须接入 5V 电压(不可用 3.3V,否则供电不足)

- 棕色/黑色:接地接口(GND),需与开发板共地

- 橙色/黄色:PWM 信号接口,用于接收控制指令

1.2 控制信号原理

SG90 舵机通过 50Hz 的 PWM 信号(周期固定为 20ms)实现角度控制,脉宽(高电平持续时间)与舵机旋转角度存在明确的对应关系,具体如下:

- 脉宽 0.5ms → 舵机旋转至 0°(最小角度)

- 脉宽 1.5ms → 舵机旋转至 90°(中间角度)

- 脉宽 2.5ms → 舵机旋转至 180°(最大角度)

ESP32‑S3 内置 LEDC(LED PWM 控制器),可轻松生成 50Hz、14 位精度的 PWM 波形,我们只需将舵机角度换算为对应的占空比,即可实现精准角度控制。

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图1

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图2

回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

三、MimiClaw 中添加舵机控制工具

MimiClaw 框架项目结构清晰,添加舵机控制功能只需完成三步:创建舵机驱动文件、注册工具、添加自然语言指令映射。全程基于框架规范开发,无需修改核心代码,便于后续扩展和维护。

3.1 创建头文件 tool_servo.h

在项目 tools/ 目录下新建头文件 tool_servo.h,用于声明舵机初始化、角度控制相关函数,代码如下:

  1. // tools/tool_servo.h
  2. #ifndef TOOL_SERVO_H
  3. #define TOOL_SERVO_H
  4. #include "esp_err.h"
  5. #ifdef __cplusplus
  6. extern "C" {
  7. #endif
  8. // 舵机初始化(指定控制引脚)
  9. esp_err_t tool_servo_init(int pin);
  10. // 舵机角度设置(0-180°)
  11. esp_err_t tool_servo_set_angle(int angle, char *output, size_t output_len);
  12. // 工具执行函数(解析JSON输入,调用对应功能)
  13. esp_err_t tool_servo_execute(const char *json_input, char *output, size_t output_len);
  14. #ifdef __cplusplus
  15. }
  16. #endif
  17. #endif // TOOL_SERVO_H
复制代码


回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

3.2 实现源文件 tool_servo.c

在 tools/ 目录下新建源文件 tool_servo.c,实现头文件中声明的函数,基于 LEDC 控制器完成 PWM 信号生成、角度与占空比换算等核心逻辑,代码如下(含详细注释):

  1. // tools/tool_servo.c
  2. #include "tool_servo.h"
  3. #include "driver/ledc.h"
  4. #include "esp_log.h"
  5. #include "cJSON.h"
  6. #include <string.h>
  7. // 日志标签,便于调试
  8. static const char *TAG = "TOOL_SERVO";
  9. // 舵机控制引脚(全局变量,记录当前初始化引脚)
  10. static int s_servo_pin = -1;
  11. // LEDC通道配置结构体
  12. static ledc_channel_config_t s_ledc_channel = {0};
  13. // 舵机初始化状态标志
  14. static bool s_is_initialized = false;
  15. /**
  16. * @brief  角度转占空比(14位分辨率)
  17. * @param  angle 舵机目标角度(0-180°)
  18. * @return 对应的PWM占空比
  19. */
  20. static uint32_t angle_to_duty(int angle) {
  21.     // 角度范围限制(防止超出舵机可控范围)
  22.     if (angle < 0) angle = 0;
  23.     if (angle > 180) angle = 180;
  24.     // 14位分辨率最大占空比(2^14 - 1 = 16383)
  25.     const uint32_t duty_max = (1 << LEDC_TIMER_14_BIT) - 1;
  26.     // 脉宽计算:0.5ms(0°)~2.5ms(180°)线性映射
  27.     float pulse_width_ms = 0.5f + (angle / 180.0f) * 2.0f;
  28.     // 占空比 = (脉宽 / 周期)* 最大占空比(周期20ms)
  29.     uint32_t duty = (uint32_t)((pulse_width_ms / 20.0f) * duty_max);
  30.     return duty;
  31. }
  32. /**
  33. * @brief  舵机初始化
  34. * @param  pin 舵机控制引脚(GPIO)
  35. * @return esp_err_t 初始化结果(ESP_OK为成功)
  36. */
  37. esp_err_t tool_servo_init(int pin) {
  38.     // 若已初始化且引脚相同,直接返回成功
  39.     if (s_is_initialized && s_servo_pin == pin) return ESP_OK;
  40.     // 若已初始化但引脚不同,先停止当前LEDC通道
  41.     if (s_is_initialized) {
  42.         ledc_stop(LEDC_LOW_SPEED_MODE, s_ledc_channel.channel, 0);
  43.     }
  44.     // LEDC定时器配置(50Hz,14位分辨率)
  45.     ledc_timer_config_t ledc_timer = {
  46.         .speed_mode = LEDC_LOW_SPEED_MODE,  // 低速模式
  47.         .timer_num = LEDC_TIMER_0,          // 使用定时器0
  48.         .duty_resolution = LEDC_TIMER_14_BIT,// 14位分辨率
  49.         .freq_hz = 50,                      // 频率50Hz(周期20ms)
  50.         .clk_cfg = LEDC_AUTO_CLK            // 自动时钟配置
  51.     };
  52.     esp_err_t err = ledc_timer_config(&ledc_timer);
  53.     if (err != ESP_OK) return err;
  54.     // LEDC通道配置(绑定引脚、定时器)
  55.     s_ledc_channel = (ledc_channel_config_t){
  56.         .gpio_num = pin,                    // 舵机控制引脚
  57.         .speed_mode = LEDC_LOW_SPEED_MODE,  // 与定时器模式一致
  58.         .channel = LEDC_CHANNEL_0,          // 使用通道0
  59.         .timer_sel = LEDC_TIMER_0,          // 绑定定时器0
  60.         .duty = 0,                          // 初始占空比0
  61.         .hpoint = 0                         // 高电平起始点
  62.     };
  63.     err = ledc_channel_config(&s_ledc_channel);
  64.     if (err != ESP_OK) return err;
  65.     // 更新初始化状态和引脚信息
  66.     s_servo_pin = pin;
  67.     s_is_initialized = true;
  68.     ESP_LOGI(TAG, "Servo initialized on GPIO %d", pin);
  69.     return ESP_OK;
  70. }
  71. /**
  72. * @brief  舵机角度设置
  73. * @param  angle 目标角度(0-180°)
  74. * @param  output 输出提示信息缓冲区
  75. * @param  output_len 缓冲区长度
  76. * @return esp_err_t 执行结果
  77. */
  78. esp_err_t tool_servo_set_angle(int angle, char *output, size_t output_len) {
  79.     // 检查舵机是否已初始化
  80.     if (!s_is_initialized) {
  81.         snprintf(output, output_len, "Error: Servo not initialized");
  82.         return ESP_ERR_INVALID_STATE;
  83.     }
  84.     // 角度转占空比
  85.     uint32_t duty = angle_to_duty(angle);
  86.     // 设置占空比并更新
  87.     ledc_set_duty(s_ledc_channel.speed_mode, s_ledc_channel.channel, duty);
  88.     ledc_update_duty(s_ledc_channel.speed_mode, s_ledc_channel.channel);
  89.     // 输出执行结果
  90.     snprintf(output, output_len, "Servo moved to %d degree", angle);
  91.     ESP_LOGI(TAG, "Angle %d → duty %lu", angle, duty);
  92.     return ESP_OK;
  93. }
  94. /**
  95. * @brief  工具执行入口(解析JSON输入,调用初始化和角度设置)
  96. * @param  json_input JSON格式输入(含pin和angle参数)
  97. * @param  output 输出提示信息缓冲区
  98. * @param  output_len 缓冲区长度
  99. * @return esp_err_t 执行结果
  100. */
  101. esp_err_t tool_servo_execute(const char *json_input, char *output, size_t output_len) {
  102.     // 解析JSON输入
  103.     cJSON *root = cJSON_Parse(json_input);
  104.     if (!root) {
  105.         snprintf(output, output_len, "Error: invalid JSON");
  106.         return ESP_ERR_INVALID_ARG;
  107.     }
  108.     // 获取pin和angle参数
  109.     cJSON *pin_obj = cJSON_GetObjectItem(root, "pin");
  110.     cJSON *angle_obj = cJSON_GetObjectItem(root, "angle");
  111.     // 检查参数合法性
  112.     if (!cJSON_IsNumber(pin_obj) || !cJSON_IsNumber(angle_obj)) {
  113.         snprintf(output, output_len, "Error: need pin and angle (0-180)");
  114.         cJSON_Delete(root);
  115.         return ESP_ERR_INVALID_ARG;
  116.     }
  117.     // 转换参数类型
  118.     int pin = (int)pin_obj->valuedouble;
  119.     int angle = (int)angle_obj->valuedouble;
  120.     // 释放JSON内存
  121.     cJSON_Delete(root);
  122.     // 初始化舵机
  123.     esp_err_t err = tool_servo_init(pin);
  124.     if (err != ESP_OK) {
  125.         snprintf(output, output_len, "Servo init failed on pin %", pin);
  126.         return err;
  127.     }
  128.     // 设置舵机角度
  129.     return tool_servo_set_angle(angle, output, output_len);
  130. }
复制代码


回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

3.3 在 tool_registry.c 中注册工具

工具注册是 MimiClaw 框架调用自定义功能的核心步骤,需在 tool_registry.c 中引入舵机工具头文件,并注册舵机控制工具,具体操作如下:

1. 在 tool_registry.c 顶部添加头文件引入:

#include "tool_servo.h"

2. 在 tool_registry_init 函数中添加工具注册代码(与其他工具注册代码放在一起):

  1. // 舵机控制工具注册
  2. mimi_tool_t servo_tool = {
  3.     .name = "servo_set",  // 工具名称(调用时需匹配)
  4.     .description = "Set servo angle. Input: {"pin":<GPIO>,"angle":0-180}",  // 工具描述
  5.     .input_schema_json = "{"type":"object","properties":{"pin":{"type":"integer"},"angle":{"type":"integer"}},"required":["pin","angle"]}",  // 输入参数格式
  6.     .execute = tool_servo_execute,  // 工具执行函数
  7. };
  8. register_tool(&servo_tool);  // 注册工具到框架
复制代码

注意:需确保 main/CMakeLists.txt 文件的 REQUIRES 字段中包含 esp_driver_ledc,否则编译时会出现“driver/ledc.h: No such file”错误,添加后即可正常编译。

回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

3.4 在 agent_loop.c 中添加自然语言预处理

为实现飞书自然语言直接控制,需在 agent_loop.c 的 try_direct_command 函数中添加舵机指令匹配逻辑,让框架无需调用 LLM,即可快速响应固定指令。找到 try_direct_command 函数,在函数末尾添加以下代码:

  1. // 舵机控制指令匹配(支持中英文指令,灵活匹配格式)
  2. // 匹配“舵机0”“servo 0”
  3. if (strstr(content, "舵机0") != NULL || strstr(content, "servo 0") != NULL) {
  4.     tool_registry_execute("servo_set", "{"pin":16,"angle":0}", output, output_size);
  5.     return true;
  6. }
  7. // 匹配“舵机90”“servo 90”
  8. if (strstr(content, "舵机90") != NULL || strstr(content, "servo 90") != NULL) {
  9.     tool_registry_execute("servo_set", "{"pin":16,"angle":90}", output, output_size);
  10.     return true;
  11. }
  12. // 匹配“舵机180”“servo 180”
  13. if (strstr(content, "舵机180") != NULL || strstr(content, "servo 180") != NULL) {
  14.     tool_registry_execute("servo_set", "{"pin":16,"angle":180}", output, output_size);
  15.     return true;
  16. }
  17. // 更灵活的匹配(支持“舵机 90 度”“舵机转到90”等格式)
  18. if (strstr(content, "舵机") && (strstr(content, "0") || strstr(content, "90") || strstr(content, "180"))) {
  19.     if (strstr(content, "0")) {
  20.         tool_registry_execute("servo_set", "{"pin":16,"angle":0}", output, output_size);
  21.         return true;
  22.     } else if (strstr(content, "90")) {
  23.         tool_registry_execute("servo_set", "{"pin":16,"angle":90}", output, output_size);
  24.         return true;
  25.     } else if (strstr(content, "180")) {
  26.         tool_registry_execute("servo_set", "{"pin":16,"angle":180}", output, output_size);
  27.         return true;
  28.     }
  29. }
复制代码

说明:上述代码中固定使用 GPIO 16 作为舵机控制引脚,若实际接线时更换了引脚,只需将代码中的“16”替换为实际使用的引脚号即可。

回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

四、编译与测试

完成代码开发后,通过编译烧录、串口测试、飞书测试三个步骤,验证舵机控制功能是否正常,确保每一步都符合预期效果。

4.1 编译烧录
打开终端,进入项目根目录,执行以下命令完成编译、烧录和串口监控(需将 COM12 替换为实际串口号):
  1. # 清除之前的编译缓存
  2. idf.py fullclean
  3. # 编译项目
  4. idf.py build
  5. # 烧录固件并启动串口监控
  6. idf.py -p COM12 flash monitor
复制代码


4.2 串口手动测试
当串口监控启动后,等待 MimiClaw 框架初始化完成,出现“mimi> ”提示符时,输入以下指令,手动测试舵机控制功能:
  1. # 控制舵机转到0°(GPIO 16)
  2. tool_exec servo_set "{"pin":16,"angle":0}"
  3. # 控制舵机转到90°(GPIO 16)
  4. tool_exec servo_set "{"pin":16,"angle":90}"
  5. # 控制舵机转到180°(GPIO 16)
  6. tool_exec servo_set "{"pin":16,"angle":180}"
复制代码

输入指令后,观察舵机是否能准确转动到对应角度。若舵机无反应,需优先检查 5V 供电、共地连接和信号线接线是否正确。

4.3 飞书自然语言测试
确保 MimiClaw 机器人已成功连接飞书,在飞书聊天框中向机器人发送以下指令,测试自然语言控制效果:
  1. <p>- 发送“舵机0” → 舵机旋转至 0°</p><p>- 发送“舵机90” → 舵机旋转至 90°</p><p>- 发送“舵机180” → 舵机旋转至 180°</p>
复制代码


测试成功后,串口日志会输出类似以下内容,说明指令被预处理直接捕获并执行,响应速度极快(延迟小于 0.5 秒):
  1. I (132426) TOOL_SERVO: Servo initialized on GPIO 16
  2. I (132426) TOOL_SERVO: Angle 0 → duty 409
  3. I (132436) agent: Direct command matched, executing tool and responding
复制代码


回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

五、效果展示

飞书指令、舵机动作与串口输出的占空比对应关系如下表所示,可直观查看控制效果,验证舵机是否精准响应指令:

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图1

整个控制链路延迟小于 0.5 秒,且无需调用 LLM API,不消耗 API 额度,适合长期稳定使用。


六、常见问题与解决方法

在实操过程中,可能会遇到舵机不转、抖动、角度不准等问题,以下是常见问题的原因分析及解决方法,帮助快速排查故障:

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图2

回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 1 小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

七、通过飞书自然语言控制 SG90 舵机实验场景图与视频记录

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图1

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图3

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图4

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图5

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图2


【【花雕动手做】从零实现飞书 AI 控舵机:ESP32-S3 的 MimiClaw 嵌入式实践——让你的嵌入式 AI Agent 拥有物理交互能力#迷你小龙虾】

https://www.bilibili.com/video/BV15sDvBDEX7/?share_source=copy_web




回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 半小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

八、扩展思路

本文实现的舵机控制功能可灵活扩展,结合 MimiClaw 框架的特性,可实现更丰富的应用场景,以下是几个实用的扩展方向:

1. 多角度预设

在 try_direct_command 函数中添加更多角度指令,如“舵机45”“舵机135”,同时调整 angle_to_duty 函数,支持 0-180° 任意角度的精准控制,满足更多场景需求。

2. 多舵机控制

ESP32‑S3 拥有多组 LEDC 通道,可为每个舵机分配不同的 LEDC 通道和 GPIO 引脚,修改 tool_servo.c 代码,支持通过 JSON 参数指定舵机引脚,实现多舵机同步控制(如机器人多关节联动)。

3. 传感器联动

结合超声波传感器、温湿度传感器等外设,实现智能化控制:例如通过超声波传感器检测距离,自动控制舵机旋转调整摄像头角度;通过温湿度传感器检测环境温度,自动控制舵机驱动开窗器。

4. 定时任务

利用 MimiClaw 框架的 cron_add 工具,设置定时任务,让舵机在固定时间执行指定动作,例如每天固定时间转动舵机,实现自动宠物喂食器、定时开关门等功能。

5. 平滑运动

在 tool_servo_set_angle 函数中添加步进循环逻辑,让舵机从当前角度逐步过渡到目标角度,实现缓动效果,避免角度突变导致的舵机抖动,提升控制体验。


九、总结

本文详细讲解了在 MimiClaw 嵌入式 AI Agent 框架中添加 SG90 舵机控制功能的完整流程,从舵机原理、硬件接线,到代码开发、工具注册、自然语言映射,再到测试与故障排查,全程贴合实战场景,提供可直接运行的代码和清晰的操作步骤。

通过本文的实践,你的 ESP32‑S3 开发板将不再局限于“智能灯泡”的角色,而是升级为能够执行物理动作的 AI 代理。核心实现逻辑可复用至步进电机、继电器、舵机阵列等其他外设,为机器人、智能家居、自动化设备开发提供了清晰的思路和基础。

项目源码基于 MimiClaw 二次开发,欢迎 fork 和贡献。若在实现过程中遇到任何问题,欢迎在评论区留言交流,共同探讨嵌入式 AI 与物联网的实践技巧。

本文为“花雕学编程”、“花雕动手做”系列博客之一,聚焦嵌入式 AI Agent 与物联网的交叉实践,后续将带来更多实战案例,敬请关注。



回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 半小时前

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图3

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图1

【花雕动手做】ESP32-S3 + MimiClaw 实战:飞书控制 SG90 舵机图2
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail