在智能硬件项目中,RGB LED 经常承担"状态表达"的角色。单色 LED 只能表达亮和灭,而 RGB LED 可以通过红、绿、蓝三路光的混合,表达更多设备状态。例如白色短亮可以表示设备启动,蓝色呼吸可以表示待机,绿色闪烁可以表示任务完成,红色长闪可以表示错误,橙色闪烁可以表示普通警告。
本实验基于 CanMV K210 使用 PWM 控制 RGB 三色 LED。程序将物理引脚 6、7、8 分别作为红色、绿色、蓝色通道,通过三个 PWM 对象输出不同占空比,让 RGB LED 显示指定颜色。代码支持 RGB 元组设置、十六进制颜色设置、全局亮度缩放、平滑渐变、呼吸灯、警告闪烁、成功提示、错误提示和设备状态演示。这个实验不只是让灯变颜色,而是把颜色当成智能硬件的人机反馈语言。
| 学习目标 | 说明 |
|---|---|
| 理解 PWM 控制 | 认识 PWM 占空比如何影响 LED 亮度 |
| 掌握 RGB 混色 | 通过红、绿、蓝三路亮度组合显示不同颜色 |
| 学会亮度缩放 | 使用全局亮度参数控制整体亮度,避免灯光过亮 |
| 理解灯效封装 | 将渐变、呼吸、闪烁和状态提示封装成可复用函数 |
| 建立状态灯思维 | 使用不同颜色和闪烁节奏表达设备运行状态 |
文章目录
理论基础
RGB LED 可以理解成把红色 LED、绿色 LED 和蓝色 LED 封装在一起的发光器件。程序分别控制 R、G、B 三个通道的亮度,就可以混合出不同颜色。红色通道亮度高时显示偏红,绿色通道亮度高时显示偏绿,蓝色通道亮度高时显示偏蓝;红色和绿色同时亮起可以形成黄色,绿色和蓝色同时亮起可以形成青色,红色和蓝色同时亮起可以形成紫色,三路同时亮起可以形成白色。
普通 GPIO 输出只有高电平和低电平,适合控制 LED 亮灭,但不适合控制亮度变化。PWM 的作用就是通过快速开关输出形成不同占空比,让 LED 看起来具有不同亮度。占空比越高,对应通道越亮;占空比越低,对应通道越暗。本实验中的 rgb_to_duty() 函数,就是把 0~255 的颜色值转换成 0~100 的 PWM 占空比。
RGB LED 常见结构包括共阴极和共阳极。共阴极 RGB LED 通常是数值越大越亮,三路颜色通道输出较高占空比时,对应颜色增强。共阳极 RGB LED 的亮灭逻辑相反,数值越大不一定越亮。代码中通过 COMMON_ANODE 参数适配这两类模块,默认按共阴极方式处理。如果实际现象出现颜色反向、熄灭异常或亮灭逻辑不符合预期,可以修改这个参数。
下面的流程图从电路控制角度展示整个实验链路。程序并不是直接"设置颜色",而是先把颜色值拆成 R、G、B 三路亮度,再转换成 PWM 占空比,最后由三个引脚分别控制 RGB LED 的三个颜色通道。
#mermaid-svg-YkcOAqKJVXpchYCh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YkcOAqKJVXpchYCh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YkcOAqKJVXpchYCh .error-icon{fill:#552222;}#mermaid-svg-YkcOAqKJVXpchYCh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YkcOAqKJVXpchYCh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YkcOAqKJVXpchYCh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YkcOAqKJVXpchYCh .marker.cross{stroke:#333333;}#mermaid-svg-YkcOAqKJVXpchYCh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YkcOAqKJVXpchYCh p{margin:0;}#mermaid-svg-YkcOAqKJVXpchYCh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YkcOAqKJVXpchYCh .cluster-label text{fill:#333;}#mermaid-svg-YkcOAqKJVXpchYCh .cluster-label span{color:#333;}#mermaid-svg-YkcOAqKJVXpchYCh .cluster-label span p{background-color:transparent;}#mermaid-svg-YkcOAqKJVXpchYCh .label text,#mermaid-svg-YkcOAqKJVXpchYCh span{fill:#333;color:#333;}#mermaid-svg-YkcOAqKJVXpchYCh .node rect,#mermaid-svg-YkcOAqKJVXpchYCh .node circle,#mermaid-svg-YkcOAqKJVXpchYCh .node ellipse,#mermaid-svg-YkcOAqKJVXpchYCh .node polygon,#mermaid-svg-YkcOAqKJVXpchYCh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YkcOAqKJVXpchYCh .rough-node .label text,#mermaid-svg-YkcOAqKJVXpchYCh .node .label text,#mermaid-svg-YkcOAqKJVXpchYCh .image-shape .label,#mermaid-svg-YkcOAqKJVXpchYCh .icon-shape .label{text-anchor:middle;}#mermaid-svg-YkcOAqKJVXpchYCh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YkcOAqKJVXpchYCh .rough-node .label,#mermaid-svg-YkcOAqKJVXpchYCh .node .label,#mermaid-svg-YkcOAqKJVXpchYCh .image-shape .label,#mermaid-svg-YkcOAqKJVXpchYCh .icon-shape .label{text-align:center;}#mermaid-svg-YkcOAqKJVXpchYCh .node.clickable{cursor:pointer;}#mermaid-svg-YkcOAqKJVXpchYCh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YkcOAqKJVXpchYCh .arrowheadPath{fill:#333333;}#mermaid-svg-YkcOAqKJVXpchYCh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YkcOAqKJVXpchYCh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YkcOAqKJVXpchYCh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YkcOAqKJVXpchYCh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YkcOAqKJVXpchYCh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YkcOAqKJVXpchYCh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YkcOAqKJVXpchYCh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YkcOAqKJVXpchYCh .cluster text{fill:#333;}#mermaid-svg-YkcOAqKJVXpchYCh .cluster span{color:#333;}#mermaid-svg-YkcOAqKJVXpchYCh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YkcOAqKJVXpchYCh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YkcOAqKJVXpchYCh rect.text{fill:none;stroke-width:0;}#mermaid-svg-YkcOAqKJVXpchYCh .icon-shape,#mermaid-svg-YkcOAqKJVXpchYCh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YkcOAqKJVXpchYCh .icon-shape p,#mermaid-svg-YkcOAqKJVXpchYCh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YkcOAqKJVXpchYCh .icon-shape .label rect,#mermaid-svg-YkcOAqKJVXpchYCh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YkcOAqKJVXpchYCh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YkcOAqKJVXpchYCh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YkcOAqKJVXpchYCh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-YkcOAqKJVXpchYCh .code>*{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-YkcOAqKJVXpchYCh .code span{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-YkcOAqKJVXpchYCh .code tspan{fill:#1F3B6D!important;}#mermaid-svg-YkcOAqKJVXpchYCh .pwm>*{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-YkcOAqKJVXpchYCh .pwm span{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-YkcOAqKJVXpchYCh .pwm tspan{fill:#1F5C3A!important;}#mermaid-svg-YkcOAqKJVXpchYCh .led>*{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-YkcOAqKJVXpchYCh .led span{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-YkcOAqKJVXpchYCh .led tspan{fill:#7A4A00!important;}#mermaid-svg-YkcOAqKJVXpchYCh .config>*{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-YkcOAqKJVXpchYCh .config span{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-YkcOAqKJVXpchYCh .config tspan{fill:#7A2740!important;} 颜色指令
RGB 元组 / HEX 颜色 / 状态灯函数
颜色解析
R / G / B 三路数值
亮度缩放
BRIGHTNESS 全局亮度
PWM 占空比转换
rgb_to_duty()
CanMV K210
物理引脚 6 / 7 / 8
RGB LED
红色 / 绿色 / 蓝色通道
灯光效果
混色 / 渐变 / 呼吸 / 闪烁 / 状态提示
COMMON_ANODE
共阳极适配
Timer + PWM
定时器与 PWM 输出
从这条链路可以看出,RGB LED 实验不只是普通亮灯实验的升级版。它同时涉及 PWM 输出、颜色值换算、亮度比例控制、灯效节奏设计和设备状态映射。后续做联网状态灯、AI 识别结果提示、传感器阈值报警、设备运行反馈时,都可以复用这种思路。
硬件设施
本实验真正使用到的硬件对象是 CanMV K210 开发板和一个 RGB LED。软件侧主要使用 machine.Timer、machine.PWM 和 time。代码没有使用 LCD、按键、蜂鸣器、摄像头或传感器,因此这些模块不作为本节重点。RGB LED 的核心控制方式是 PWM,程序通过不同通道的占空比控制红、绿、蓝三种颜色的亮度,再由三色混合形成不同颜色效果。
接线关系可以先通过下面这张图建立整体印象。CanMV K210 使用物理引脚 6、7、8 分别连接 RGB LED 的红色、绿色、蓝色通道,公共端需要根据 RGB LED 类型连接到对应电源或地线。如果使用的是裸 RGB LED,建议给每个颜色通道串联合适的限流电阻;如果使用的是 RGB LED 模块,通常模块板上已经集成限流电阻。

| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,并通过 PWM 输出控制 RGB LED |
| RGB LED | 彩色显示对象 | 通过红、绿、蓝三路通道混合显示不同颜色 |
| 物理引脚 6 | 红色通道输出 | 对应 RED_PIN,用于控制 RGB LED 的红色亮度 |
| 物理引脚 7 | 绿色通道输出 | 对应 GREEN_PIN,用于控制 RGB LED 的绿色亮度 |
| 物理引脚 8 | 蓝色通道输出 | 对应 BLUE_PIN,用于控制 RGB LED 的蓝色亮度 |
machine.Timer |
PWM 定时器资源 | 为 PWM 输出提供定时器和通道配置 |
machine.PWM |
PWM 控制模块 | 通过频率和占空比控制每个颜色通道的输出强度 |
time |
延时控制模块 | 通过 time.sleep_ms() 控制颜色保持、闪烁和渐变速度 |
实验中使用的 RGB LED 模块如下。接线时需要重点确认 R、G、B 三个颜色输入端和公共端,不同模块的引脚排列可能不同,不能只根据线的颜色判断通道。若红色、绿色、蓝色显示顺序和程序不一致,优先检查接线顺序,再考虑修改代码中的 RED_PIN、GREEN_PIN、BLUE_PIN。

| 物理引脚 | PWM 对象 | 代码变量 | 对应硬件 | 说明 |
|---|---|---|---|---|
| 6 | pwm_r |
RED_PIN |
RGB LED 红色通道 | 负责控制红色亮度 |
| 7 | pwm_g |
GREEN_PIN |
RGB LED 绿色通道 | 负责控制绿色亮度 |
| 8 | pwm_b |
BLUE_PIN |
RGB LED 蓝色通道 | 负责控制蓝色亮度 |
| 公共端 | 无 | COMMON_ANODE 相关 |
RGB LED 公共引脚 | 共阴极接 GND,共阳极接 VCC,具体以模块标识为准 |
完成接线后的整体状态如下。检查时重点关注三个位置:RGB LED 的 R/G/B 通道是否接到代码指定引脚,公共端连接方式是否和模块类型一致,程序中的 COMMON_ANODE 是否匹配实际 RGB LED 类型。接线正确后,程序运行会依次出现颜色轮播、平滑渐变、呼吸灯和状态灯演示效果。

| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 基础颜色轮播 | 红、绿、蓝、黄、紫、青、白依次显示 | 颜色顺序错误时检查 R/G/B 接线 |
| 平滑颜色渐变 | 不同颜色之间逐步过渡 | 渐变突兀时可增加 steps 或减小 delay_ms |
| RGB 呼吸灯 | 指定颜色由暗变亮,再由亮变暗 | 亮度异常时检查 BRIGHTNESS 和 COMMON_ANODE |
| 设备启动提示 | 白色短亮 | 不亮时检查公共端和 PWM 引脚 |
| 待机状态提示 | 蓝色呼吸 | 颜色不对时检查蓝色通道接线 |
| 运行状态提示 | 蓝色和青色之间平滑变化 | 绿色或蓝色通道异常会导致颜色偏差 |
| 警告与错误提示 | 橙色或红色闪烁 | 红色通道异常会影响警告显示 |
软件代码
本实验代码围绕 RGB LED 的 PWM 控制展开。程序先定义红、绿、蓝三个物理引脚,再创建三个 PWM 输出对象。后续所有颜色显示、渐变、呼吸灯和状态提示,都基于 set_rgb() 统一控制三路颜色通道。代码还增加了亮度缩放和共阳极适配变量,便于在不同 RGB LED 模块之间调整。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能正常运行基础程序 |
| CanMV 固件 | 提供 machine.Timer 和 machine.PWM |
固件环境需要支持当前 PWM 写法 |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口终端能看到状态打印信息 |
machine.Timer |
创建 PWM 所需定时器资源 | 三个颜色通道分别绑定到不同通道 |
machine.PWM |
输出 PWM 波形 | 通过 duty() 修改 RGB LED 亮度 |
time |
控制灯效节奏 | 控制颜色保持、闪烁间隔和渐变速度 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CanMV K210 RGB-LED 实验 Demo
功能:
1. 使用 PWM 控制 RGB 三色 LED
2. 支持 HEX 颜色、RGB 颜色两种设置方式
3. 支持亮度缩放,避免灯光过亮
4. 支持颜色切换、渐变过渡、呼吸灯、警告闪烁、设备状态灯
5. 通过不同颜色表达智能硬件状态
"""
from machine import Timer, PWM
import time
# =========================
# 硬件配置区
# =========================
RED_PIN = 6
GREEN_PIN = 7
BLUE_PIN = 8
PWM_FREQ = 2000 # PWM 频率
BRIGHTNESS = 0.35 # 全局亮度,范围 0.0 ~ 1.0
# 大多数普通 RGB LED 为共阴极:数值越大越亮
# 若实际现象为颜色反向或熄灭异常,可改为 True
COMMON_ANODE = False
# =========================
# PWM 初始化
# =========================
timer_r = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
timer_g = Timer(Timer.TIMER0, Timer.CHANNEL1, mode=Timer.MODE_PWM)
timer_b = Timer(Timer.TIMER0, Timer.CHANNEL2, mode=Timer.MODE_PWM)
pwm_r = PWM(timer_r, freq=PWM_FREQ, duty=0, pin=RED_PIN)
pwm_g = PWM(timer_g, freq=PWM_FREQ, duty=0, pin=GREEN_PIN)
pwm_b = PWM(timer_b, freq=PWM_FREQ, duty=0, pin=BLUE_PIN)
# =========================
# 常用颜色定义
# =========================
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
CYAN = (0, 255, 255)
PURPLE = (255, 0, 255)
WHITE = (255, 255, 255)
ORANGE = (255, 80, 0)
COLOR_LIST = [
RED,
GREEN,
BLUE,
YELLOW,
PURPLE,
CYAN,
WHITE
]
# =========================
# 工具函数
# =========================
def clamp(value, min_value=0, max_value=255):
"""限制数值范围"""
if value < min_value:
return min_value
if value > max_value:
return max_value
return value
def rgb_to_duty(value):
"""
将 0~255 的颜色值转换为 0~100 的 PWM 占空比
"""
value = clamp(value)
duty = int(value / 255 * 100 * BRIGHTNESS)
if COMMON_ANODE:
duty = 100 - duty
return duty
def hex_to_rgb(color):
"""
将 0xRRGGBB 颜色转换为 RGB 元组
"""
r = (color & 0xFF0000) >> 16
g = (color & 0x00FF00) >> 8
b = color & 0x0000FF
return r, g, b
# =========================
# 基础控制函数
# =========================
def set_rgb(r, g, b):
"""
设置 RGB LED 颜色
"""
pwm_r.duty(rgb_to_duty(r))
pwm_g.duty(rgb_to_duty(g))
pwm_b.duty(rgb_to_duty(b))
def set_hex_color(color):
"""
通过十六进制颜色设置 RGB LED
示例:set_hex_color(0xFF0000)
"""
r, g, b = hex_to_rgb(color)
set_rgb(r, g, b)
def led_off():
"""
关闭 RGB LED
"""
set_rgb(0, 0, 0)
def show_color(color, delay_ms=800):
"""
显示指定颜色一段时间
"""
r, g, b = color
set_rgb(r, g, b)
time.sleep_ms(delay_ms)
# =========================
# 灯效函数
# =========================
def color_cycle(delay_ms=700):
"""
基础颜色轮播
"""
for color in COLOR_LIST:
show_color(color, delay_ms)
def fade_to_color(start_color, end_color, steps=40, delay_ms=20):
"""
从一种颜色平滑渐变到另一种颜色
"""
start_r, start_g, start_b = start_color
end_r, end_g, end_b = end_color
for i in range(steps + 1):
r = int(start_r + (end_r - start_r) * i / steps)
g = int(start_g + (end_g - start_g) * i / steps)
b = int(start_b + (end_b - start_b) * i / steps)
set_rgb(r, g, b)
time.sleep_ms(delay_ms)
def smooth_color_cycle():
"""
平滑颜色渐变轮播
"""
colors = [RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, RED]
for i in range(len(colors) - 1):
fade_to_color(colors[i], colors[i + 1], steps=45, delay_ms=18)
def breathing_light(color=BLUE, cycles=3):
"""
单色呼吸灯
"""
global BRIGHTNESS
old_brightness = BRIGHTNESS
r, g, b = color
for _ in range(cycles):
for level in range(0, 101, 4):
BRIGHTNESS = level / 100 * old_brightness
set_rgb(r, g, b)
time.sleep_ms(25)
for level in range(100, -1, -4):
BRIGHTNESS = level / 100 * old_brightness
set_rgb(r, g, b)
time.sleep_ms(25)
BRIGHTNESS = old_brightness
led_off()
def blink_color(color, times=3, on_ms=180, off_ms=180):
"""
指定颜色闪烁
"""
r, g, b = color
for _ in range(times):
set_rgb(r, g, b)
time.sleep_ms(on_ms)
led_off()
time.sleep_ms(off_ms)
def warning_flash():
"""
警告闪烁:红色快速闪烁
"""
blink_color(RED, times=6, on_ms=120, off_ms=120)
def success_flash():
"""
成功提示:绿色闪烁
"""
blink_color(GREEN, times=2, on_ms=200, off_ms=160)
def error_flash():
"""
错误提示:红色长闪
"""
blink_color(RED, times=3, on_ms=450, off_ms=200)
def standby_light():
"""
待机状态:蓝色呼吸灯
"""
breathing_light(BLUE, cycles=2)
def running_light():
"""
运行状态:青色平滑渐变
"""
fade_to_color(BLUE, CYAN, steps=35, delay_ms=18)
fade_to_color(CYAN, BLUE, steps=35, delay_ms=18)
def device_status_demo():
"""
智能硬件状态灯演示
"""
print("设备启动:白色短亮")
show_color(WHITE, 500)
led_off()
time.sleep_ms(300)
print("待机状态:蓝色呼吸")
standby_light()
print("运行状态:蓝青渐变")
running_light()
print("任务完成:绿色提示")
success_flash()
print("普通警告:橙色提示")
blink_color(ORANGE, times=4, on_ms=150, off_ms=150)
print("错误状态:红色提示")
error_flash()
# =========================
# 主循环
# =========================
def loop():
while True:
print("基础颜色轮播")
color_cycle()
print("平滑颜色渐变")
smooth_color_cycle()
print("RGB 呼吸灯")
breathing_light(PURPLE, cycles=2)
print("智能硬件状态演示")
device_status_demo()
print("演示结束,暂停 2 秒")
led_off()
time.sleep_ms(2000)
# =========================
# 程序入口
# =========================
if __name__ == "__main__":
try:
loop()
except KeyboardInterrupt:
led_off()
print("程序已停止,RGB LED 已关闭")
这段程序可以分成三个层级。底层是 PWM 初始化和占空比转换,负责把颜色值变成真实的 PWM 输出;中间层是 set_rgb()、set_hex_color()、led_off() 和 show_color(),负责提供统一的颜色控制接口;上层是颜色轮播、渐变、呼吸灯、闪烁提示和设备状态演示,负责把灯光变化变成可理解的设备反馈。
硬件配置区集中管理 RED_PIN、GREEN_PIN、BLUE_PIN、PWM_FREQ、BRIGHTNESS 和 COMMON_ANODE。后续更换接线、调节亮度或适配共阳极 RGB LED 时,只需要修改配置区,不需要进入每个灯效函数中逐个查找。set_rgb() 是最核心的接口,后面的颜色轮播、渐变、呼吸和状态灯都围绕它展开。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
clamp() |
限制颜色数值范围 | 防止 RGB 数值超出 0~255 导致占空比异常 |
rgb_to_duty() |
将 RGB 颜色值转换为 PWM 占空比 | 控制单个颜色通道的实际亮度 |
hex_to_rgb() |
将十六进制颜色拆成 RGB 元组 | 支持 0xRRGGBB 方式设置颜色 |
set_rgb() |
设置 RGB LED 三路颜色值 | RGB LED 显示指定混合颜色 |
set_hex_color() |
使用十六进制颜色设置 LED | 通过 0xFF0000 这类颜色值控制灯光 |
led_off() |
关闭 RGB LED | 三路颜色通道全部熄灭 |
show_color() |
显示指定颜色并保持一段时间 | LED 固定显示某种颜色 |
color_cycle() |
基础颜色轮播 | 红、绿、蓝、黄、紫、青、白依次显示 |
fade_to_color() |
两种颜色之间平滑过渡 | LED 从一种颜色逐步渐变到另一种颜色 |
smooth_color_cycle() |
多色平滑渐变轮播 | RGB LED 在多种颜色之间连续过渡 |
breathing_light() |
单色呼吸灯 | 指定颜色由暗变亮,再由亮变暗 |
blink_color() |
指定颜色闪烁 | LED 按设置次数和时间间隔闪烁 |
warning_flash() |
警告闪烁 | 红色快速闪烁,模拟警告提示 |
success_flash() |
成功提示 | 绿色短闪,模拟任务完成反馈 |
error_flash() |
错误提示 | 红色长闪,模拟错误状态反馈 |
standby_light() |
待机状态灯 | 蓝色呼吸,表达设备处于待机状态 |
running_light() |
运行状态灯 | 蓝色与青色之间平滑渐变 |
device_status_demo() |
智能硬件状态灯演示 | 用不同颜色和节奏表达启动、待机、运行、完成、警告、错误 |
loop() |
主循环调度 | 持续播放颜色轮播、渐变、呼吸灯和状态灯演示 |
device_status_demo() 更接近真实项目中的状态灯逻辑。设备启动时白色短亮,待机时蓝色呼吸,运行时蓝青渐变,任务完成时绿色闪烁,普通警告时橙色闪烁,错误状态时红色长闪。这里的重点不是颜色本身,而是将程序状态映射成灯光语言。后续只要把真实设备状态变量接入这些函数,就可以把 RGB LED 变成简单但有效的人机反馈模块。
扩展应用
RGB LED 实验常见问题主要集中在颜色不对、亮度异常、灯不亮、闪烁不稳定和共阴共阳接反等方面。排查时应围绕引脚接线、PWM 输出、占空比转换和 LED 类型逐步确认,不需要扩展到当前代码没有使用的模块。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| RGB LED 完全不亮 | 开发板供电异常、程序没有运行、引脚接线错误、公共端未连接 | 检查程序是否启动,确认 RGB LED 公共端和 R/G/B 三路是否正确连接 |
| 显示颜色与代码不一致 | 红、绿、蓝通道接线顺序错误 | 运行 color_cycle() 观察红绿蓝是否对应,必要时调整 RED_PIN、GREEN_PIN、BLUE_PIN 或实际接线 |
| 颜色亮灭逻辑反了 | RGB LED 模块可能是共阳极结构 | 将 COMMON_ANODE = False 改成 COMMON_ANODE = True 后重新运行 |
| 灯光太亮刺眼 | BRIGHTNESS 参数过高 |
降低全局亮度,例如从 0.35 调整为 0.2 |
| 渐变不够平滑 | steps 太少或 delay_ms 太大 |
增加渐变步数,适当减小每一步延时 |
| 呼吸灯亮度没有恢复 | 程序异常中断在亮度变化过程中 | 重新运行程序,或在中断后重新设置 BRIGHTNESS |
| 某一种颜色始终不亮 | 对应颜色通道接线异常、LED 管脚损坏或 PWM 引脚不匹配 | 单独执行 set_rgb(255, 0, 0)、set_rgb(0, 255, 0)、set_rgb(0, 0, 255) 定位异常通道 |
| 串口打印正常但灯效没有变化 | PWM 引脚与实际连接不一致 | 根据硬件连接重新修改 RED_PIN、GREEN_PIN、BLUE_PIN |
| 灯光闪烁异常 | 接线松动、公共端连接不稳定或模块供电不足 | 重新检查公共端、供电和三路颜色通道连接 |
RGB LED 的价值在于用很少的硬件资源表达丰富状态。相比单色 LED,RGB LED 不仅能亮灭,还能用颜色、闪烁频率、渐变节奏和呼吸效果表达不同含义。当前代码已经把颜色设置、渐变、闪烁、呼吸和状态演示封装成函数,后续只需要根据设备状态调用不同函数,就能把 RGB LED 变成智能硬件的状态提示窗口。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 设备启动提示 | 启动时调用 show_color(WHITE, 500),用白色短亮表示系统开始运行 |
可结合开机自检结果显示不同启动状态 |
| 待机状态显示 | 使用 standby_light() 让蓝色灯光持续呼吸 |
可扩展为低功耗待机、等待联网、等待任务输入等状态 |
| 任务运行反馈 | 使用 running_light() 在蓝色和青色之间渐变 |
可根据任务进度改变渐变速度或颜色范围 |
| 任务完成提示 | 调用 success_flash() 让绿色短闪 |
可用于拍照完成、识别完成、上传完成等场景 |
| 异常报警提示 | 使用 error_flash() 或 warning_flash() 表示错误和警告 |
后续可扩展蜂鸣器,实现声光报警 |
| 教学演示实验 | 通过颜色变化观察 PWM、占空比、RGB 混色和函数封装 | 可把抽象参数变化转化为可见灯光效果 |
| 智能硬件状态灯 | 使用 device_status_demo() 建立颜色与设备状态的对应关系 |
可接入传感器、摄像头识别结果或网络状态变量 |
| 氛围灯效果 | 使用 smooth_color_cycle() 进行平滑颜色轮播 |
可扩展更多颜色表和自定义主题色 |
从工程角度看,这段代码已经具备较好的复用性。引脚、频率、亮度和共阳极配置集中在硬件配置区,颜色定义集中在常量区,底层 PWM 控制统一封装到 set_rgb(),业务层状态提示则由 device_status_demo() 组织。后续增加更多状态时,不需要重新理解 PWM 初始化,只需要新增颜色函数或在状态演示函数中增加调用逻辑。这样的结构适合继续扩展为按键切换灯效、传感器阈值报警、AI 摄像头识别状态提示或网络连接状态显示。
总结
本实验通过 CanMV K210 使用 PWM 控制 RGB LED,完成了三色通道控制、RGB 颜色混合、HEX 颜色解析、亮度缩放、共阳极适配、颜色渐变、呼吸灯和设备状态灯封装。代码从三个颜色通道出发,把硬件层面的 PWM 占空比转换成可观察的颜色变化,再通过函数封装形成更接近真实项目的状态提示逻辑。
RGB LED 实验很适合作为硬件编程进阶案例。普通 GPIO 只能表达高低电平,而 PWM 可以表达连续变化的亮度;单色 LED 只能表达亮灭,而 RGB LED 可以通过颜色组合表达更丰富的设备状态。后续课程可以继续扩展到按键切换灯效、蜂鸣器报警、LCD 显示状态、传感器数据采集、AI 摄像头识别结果提示等方向。只要把程序中的状态变化映射到合适的颜色和节奏,RGB LED 就能成为智能硬件项目中简单直观的反馈系统。