Skip to content

概述

官方基础工程已预置 WS2812 驱动(components/ws2812,TIM1_CH4 PWM + DMA);ledstrip 调色工具已在创建 MCP 工具第 5 章注册。本章接线并实测 AI 控制彩灯——灯带默认熄灭,由 AI 控制点亮ledstrip 工具开关/调色/调亮度)。

WS2812 灯带的每颗灯珠内含芯片,数据线串行级联。其数据信号需要非常精确的时序(800kHz 数据率),因此使用 TIM1_CH4 的 PWM + DMA 生成——这正是 CubeMX 中配置 TIM1 PWM CH4 的原因。


🎯本页目标使用官方预置的 WS2812 驱动,新增 ledstrip 工具,实现 AI 控制灯带开关、颜色与亮度(默认熄灭,AI 控制点亮)。
🧰前置条件① 完成 [创建 MCP 工具](./create-mcp-tool-win) ② WS2812B 灯带(建议先接 10~30 颗)。
🔗相关章节本章为教程最后一章,完成后可参考 [示例工程](/9mod-mcpboard/examples) 深入学习。
确认官方已预置 WS2812 模块

官方基础工程已预置 WS2812 驱动(components/ws2812,含 color_mode 色彩工具),无需复制。从零工程起步时才需要:

New-Item -ItemType Directory -Force components\ws2812
Copy-Item AiPi-SCBB\WS2812\axk_ws2812.c components\ws2812\
Copy-Item AiPi-SCBB\WS2812\axk_ws2812.h components\ws2812\
Copy-Item AiPi-SCBB\WS2812\color_mode.c components\ws2812\
Copy-Item AiPi-SCBB\WS2812\color_mode.h components\ws2812\
确认官方初始化为熄灭状态

官方基础工程已配置 CMake 与初始化。本项目灯带默认熄灭ws2812_modeTask 任务只做初始化与"全灭"动作,不跑动画——只有 AI 通过 ledstrip 工具控制时才点亮。代码(freertos.c ws2812_modeTask 的真实代码):

axk_ws2812_init(&ws2812);
/* 默认灯带熄灭:AI 控制(ledstrip 工具)时才点亮 */
axk_ws2812_set_all_pixels_color(0, 0, 0, 0.0f);
for (;;) {
  osDelay(pdMS_TO_TICKS(15));
}

驱动依赖的底层能力(Bsp/pwm_dma 已实现,内部调用 TIM1 的 DMA 通道):

void bsp_pwm_dma_init(uint16_t led_num);   // 分配灯珠缓冲(FreeRTOS 堆)
int  bsp_pwm_dma_with_num(void);           // 把颜色缓冲灌入 TIM1 PWM DMA
// 头文件中:
#define WS2812_MAX_NUM 60                 // 灯带最多 60 颗
// CODE0 / CODE1 = 0 码和 1 码的占空比计数(0.35us / 1.25us @ 90 计数 800kHz)

components/CMakeLists.txt 加入 ws2812 目录;确认 Bsp/CMakeLists.txt 已有 pwm_dma。

初始化(freertos.c ws2812_modeTask 的真实代码):

/* 定义灯带对象:60 颗灯 */
axk_ws2812_strip_t ws2812 = {.led_count = WS2812_MAX_NUM};

/* 初始化 */
axk_ws2812_init(&ws2812);

驱动 API(axk_ws2812.h 核验):

int  axk_ws2812_init(axk_ws2812_strip_t *strip);            // 初始化
void axk_ws2812_show_leds(void);                             // 刷新显示
void axk_ws2812_set_pixel_color(index, r, g, b);             // 设置第 index 颗灯的颜色
void axk_ws2812_set_all_pixels_color(r, g, b, brightness);   // ★ 全部灯一个颜色(0~255,亮度 0~1)
void axk_ws2812_set_pixel_color_hsv(index, h, s, v);         // HSV 模式
void axk_ws2812_set_led_count(uint8_t count);                // 修改灯珠数
接线

🧩 九章板未板载灯带——灯带是外接模块(DIN → PA11),接上即可用。供电注意见下方提示。

WS2812 灯带 接开发板
DIN(数据输入) PA11(TIM1_CH4)
VCC(电源) 5V(长灯带务必外部供电)
GND GND(必须共地,否则数据不识别)

⚠️ 供电注意:一颗 WS2812 全亮白光约 60mA,60 颗即 3.6A,小板载稳压扛不住。实验时只接 10~30 颗,或灯带单独接 5V 电源并与板子共地。

确认 ledstrip 工具已注册(第 5 章已完成)

ledstrip 调色工具的回调与注册已在创建 MCP 工具第 5 章完成。工具回调核心逻辑如下(对照理解):

本项目已有完整的 ledstrip 工具(freertos.c),支持开关/调色/亮度/查询四种模式,核心逻辑如下:

/* ---- LEDStrip MCP 工具回调(WS2812 灯带控制) ---- */
static uint8_t led_r = 255, led_g = 0, led_b = 0;
static uint8_t led_brightness = 50;

static void ledstrip_set_handler(void *arg) {
  cJSON *root = (cJSON *)arg;
  cJSON *mode_item = cJSON_GetObjectItem(root, "mode");
  log_info("[ledstrip] called, mode=%s", mode_item && cJSON_IsString(mode_item) ? mode_item->valuestring : "(null)");
  if (mode_item && cJSON_IsString(mode_item)) {
    const char *m = mode_item->valuestring;
    if (strcmp(m, "on") == 0) {
      led_on = true;
      axk_ws2812_set_all_pixels_color(led_r, led_g, led_b, led_brightness / 100.0f);
    } else if (strcmp(m, "off") == 0) {
      led_on = false;
      axk_ws2812_set_all_pixels_color(0, 0, 0, 0.0f);
    } else if (strcmp(m, "set") == 0) {
      cJSON *r = cJSON_GetObjectItem(root, "r");
      cJSON *g = cJSON_GetObjectItem(root, "g");
      cJSON *b = cJSON_GetObjectItem(root, "b");
      if (r && cJSON_IsNumber(r)) led_r = (uint8_t)r->valueint;
      if (g && cJSON_IsNumber(g)) led_g = (uint8_t)g->valueint;
      if (b && cJSON_IsNumber(b)) led_b = (uint8_t)b->valueint;
      cJSON *br = cJSON_GetObjectItem(root, "brightness");
      if (br && cJSON_IsNumber(br)) led_brightness = (uint8_t)br->valueint;
      log_info("[ledstrip] set: r=%d g=%d b=%d br=%d", led_r, led_g, led_b, led_brightness);
      led_on = true;
      axk_ws2812_set_all_pixels_color(led_r, led_g, led_b, led_brightness / 100.0f);
    } else if (strcmp(m, "query") == 0) {
      char rsp[128];
      snprintf(rsp, sizeof(rsp), "{\"mode\":\"%s\",\"on\":%s,\"r\":%d,\"g\":%d,\"b\":%d,\"brightness\":%d}", "set", led_on ? "true" : "false", led_r, led_g, led_b, led_brightness);
      emMCP_ResponseValue(rsp);
      return;
    } else { emMCP_ResponseValue(emMCP_CTRL_ERROR); return; }
    emMCP_ResponseValue(emMCP_CTRL_OK);
    return;
  }
  emMCP_ResponseValue(emMCP_CTRL_ERROR);
}

/* 注册(4 个属性:r/g/b/brightness,注册方式见 [创建 MCP 工具](./create-mcp-tool-win)) */
memset(&t, 0, sizeof(t));
t.name = "ledstrip";
t.description = "WS2812灯带控制工具,RGB颜色(r/g/b:0-255)、可选mode(on/off)、brightness(0-100)";
t.inputSchema.properties[0].name = "r";
t.inputSchema.properties[0].description = "红色分量 0-255";
t.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_NUMBER;
t.inputSchema.properties[1].name = "g";
t.inputSchema.properties[1].description = "绿色分量 0-255";
t.inputSchema.properties[1].type = MCP_SERVER_TOOL_TYPE_NUMBER;
t.inputSchema.properties[2].name = "b";
t.inputSchema.properties[2].description = "蓝色分量 0-255";
t.inputSchema.properties[2].type = MCP_SERVER_TOOL_TYPE_NUMBER;
t.inputSchema.properties[3].name = "brightness";
t.inputSchema.properties[3].description = "亮度 0-100";
t.inputSchema.properties[3].type = MCP_SERVER_TOOL_TYPE_NUMBER;
t.setRequestHandler = ledstrip_set_handler;
t.checkRequestHandler = ledstrip_check_handler;
emMCP_AddToolToToolList(&t);

为什么灯带默认熄灭ws2812_modeTask 任务不跑动画(与官方例程的渐变动画不同,本项目已移除),上电灯带即灭;AI 调用 ledstrip 工具(mode 为 on/set)时点亮并保持颜色,mode=off 时熄灭。led_on 标志记录 AI 控制状态,供查询(mode=query)使用。

测试
  1. 编译烧录。上电灯带默认熄灭(本项目已移除默认动画);用串口或 AI 控制点亮后,说明驱动正常。

  2. 串口手动测试(USART2,115200):

    # 全灯变纯红,亮度 50%
    {"role":"AI","msgType":"MCP","data":{"name":"ledstrip","args":{"mode":"set","r":255,"g":0,"b":0,"brightness":50}}}
    # 关灯
    {"role":"AI","msgType":"MCP","data":{"name":"ledstrip","args":{"mode":"off"}}}
    # 查询状态
    {"role":"AI","msgType":"MCP","data":{"name":"ledstrip","args":{"mode":"query"}}}
    
  3. AI 语音测试:说 “把灯带调成蓝色,亮度 30%”“关闭灯带”“把灯调成暖黄色”(暖黄 ≈ r=255, g=180, b=50)。

    AI 语音控制灯带

故障排查:

故障 排查
灯带不亮 DIN 是否接 PA11;灯带 GND 是否共地;检查 TIM1_CH4 的 DMA(DMA1_Channel4,半字、内存→外设)与中断配置
长灯带暗/闪 供电不足,外部单独 5V 供电并共地

常见问题与踩坑提示

🔧 灯带完全不亮
原因:① DIN 没接 PA11 ② 没共地 ③ 供电不足
解决:① DIN→PA11 ② 灯带 GND 与板子必须共地 ③ 短灯带直接 5V,长灯带独立供电

🔧 只有前几颗灯亮/颜色不对
原因:① 数据方向接反(接成 DOUT) ② 灯珠数设置不符
解决:① 接 DIN 不是 DOUT ② led_count 与实际灯珠数一致(默认 60)

🔧 灯带发暗/闪烁
原因:供电不足
解决:一颗全亮白光约 60mA,60 颗 3.6A;长灯带必须外部 5V 供电并与板子共地

🔧 调色后颜色不准(红绿颠倒)
原因:驱动里 GRB 顺序
解决:WS2812 数据顺序是 G-R-B(本项目驱动已按此处理);如果换了其他型号灯珠(如 SK6812 是 RGB),需改驱动里的字节顺序

🔧 log_error: Failed to allocate memory for WS2812 buffer(或 Failed to start DMA transmission 刷屏)
原因标准 malloc 堆不够——20KB RAM 里标准堆只有约 2144 字节,60 颗灯缓冲需 2882 字节
解决必须用 pvPortMalloc 从 FreeRTOS 堆分配(见下方"WS2812 缓冲内存"说明);同时确认 configTOTAL_HEAP_SIZE ≥ 6656

🔧 灯带能开关但调色无效(颜色不变)
原因:① 工具没注册 ② 参数没传对
解决:① 看日志 mcp-tool sent ② 确认 AI 发的参数带 mode=setr/g/b(数值型),用串口手动测试指令验证

🔧 3.3V 驱动部分灯带不识别
原因:部分灯带需要 5V 逻辑电平
解决:在 PA11 与 DIN 之间加电平转换(或选 3.3V 兼容灯带);本项目 PA11 为开漏输出 + 上拉方案,多数灯带可用

🔧 关闭后再开,颜色不是上次的
原因:断电后状态丢失
解决:属正常现象;led_r/led_g/led_b 变量在 RAM 里,掉电即失。如需记忆需写 Flash/EEPROM

🔧 60 颗灯全亮时板子复位
原因:电流过大导致 5V 掉电
解决:减小 led_count、降低亮度、外部供电,三选一

灯带测试安全起点

新手测试建议:10 颗灯、亮度 30% 起步。{"mode":"set","r":255,"g":0,"b":0,"brightness":30} 先确认单色,再玩彩虹/呼吸,最后再上长灯带+外部供电。


⚠️ 必读:WS2812 缓冲内存分配(20KB RAM 的内存账)

现象:日志刷屏 Failed to start DMA transmission;换灯数后变成 WS2812 buffer not allocated (malloc failed);随后温湿度/调色等工具也间歇性失败。

根因:STM32F103 只有 20KB RAM,本项目已用 92%。内存分两个堆:

大小谁在用
标准堆(malloc2144 字节cJSON 消息解析(emMCP 工具调用的参数解析全靠它)+ 其他 malloc
FreeRTOS 堆(pvPortMalloc6656 字节(configTOTAL_HEAP_SIZEemMCP 工具注册 + WS2812 缓冲

WS2812 缓冲 = 灯数 × 24 × 2 字节:60 颗 = 2882 字节 > 标准堆 2144。如果 bsp_pwm_dma_init 用标准 malloc 分配,必然失败——DMA 拿 NULL 地址启动报错刷屏;更隐蔽的是 cJSON 挤在 2144 字节里,长消息(带中文参数的调色/查询)解析分配失败 → 工具静默失败。

修复Bsp/pwm_dma/stm32f10x_pwm_dma.c 用 FreeRTOS 堆分配缓冲:

c
/* bsp_pwm_dma_init:malloc → pvPortMalloc */
buffer_ptr = (uint16_t *)pvPortMalloc((ws2812_max_led_num * 24 + 1) * sizeof(uint16_t));
if (buffer_ptr == NULL) {
  log_error("Failed to allocate memory for WS2812 buffer");
}

/* bsp_pwm_dma_deinit:free → vPortFree */
vPortFree(buffer_ptr);

同时 bsp_pwm_dma_with_num() 开头加空指针防护(DMA 不拿 NULL 地址启动):

c
if (buffer_ptr == NULL) {
  log_error("WS2812 buffer not allocated (malloc failed)");
  return -1;
}

📌 内存账小结:标准堆 2144 字节必须留给 cJSON;WS2812(2882B)与 emMCP 工具(约 1~2KB)共用 FreeRTOS 堆 6656。想再加外设/功能时,先算内存账——RAM 余量只有约 1.5KB(92% 使用率)。


结语

至此九个教程全部完成。你已经掌握 SCBB 模块移植五件套:复制模块 → 接 BSP 宏 → 加 CMake → 初始化 → 注册 MCP 工具。下一步可以尝试:给工具加更多参数(如继电器定时)、自定义 OLED 页面、或移植到其他 STM32 芯片。

下一步

Released under the MIT License. Build Time 2026-08-07 22:59:19