1 项目和设计思路介绍
内容
- 项目目标:在 MAX32655FTHR 板上展示“双核协作 + 人机交互 + 可视化灯效”,让新手
能一眼理解 CM4 启动 RV32、RV32 处理交互与灯效的完整链路。
- 系统分工:CM4 侧仅做启动加载与提示信息;RV32 侧专注按键扫描与 RGB 灯效渲染,
体现“主核负责系统启动、协处理核负责应用逻辑”的清晰边界。
- 设计原则:
- 可见性:用 RGB LED 作为直观输出,按钮作为直观输入。
- 可解释性:关键算法(软件 PWM、按键去抖、彩虹/呼吸状态机)都有可调参数与易
读流程。
- 易修改:将“模式、速度、颜色”等定义为常量数组,方便快速改造。
- 关键流程:上电后 CM4 打印提示并启动 RV32;RV32 初始化外设后进入主循环,执
行“PWM 输出 + 按键采样 + 灯效更新”的节拍调度。
2 硬件介绍(控制器及使用外设)
内容
- 控制器:MAX32655(双核 SoC)
- CM4(Arm Cortex-M4F):复位后最先运行,负责启动 RV32。
- RV32(RISC-V):运行应用逻辑(按键读取 + RGB 灯效)。
- 开发板:MAX32655FTHR
- 板载 DAPLink(MAX32625):提供 SWD 调试与 UART0 虚拟串口。
- USB 供电即可运行。
- 本项目使用的外设与引脚
- RGB LED(D1):P0.18(R) / P0.19(G) / P0.26(B)
- BUTTON1/2:P0.2 / P0.3
- UART0:P0.0(RX) / P0.1(TX),用于串口日志输出
- 其他说明
- 灯效输出使用 GPIO 软件 PWM 实现亮度变化。
- 通过 DAPLink 的虚拟串口观察运行状态(115200 8-N-1)。
3 软件代码介绍(主要代码片段)
主要代码片段(扩展版)
1) CM4 启动 RV32(m4/src/main.c)
printf("ARM: RV_ARM_Loader\r\n");
MXC_Delay(MXC_DELAY_SEC(2));
MXC_SYS_RISCVRun();
说明:CM4 侧只负责提示与启动 RV32,业务逻辑全部交给 RV32;启动后 CM4 进入空循
环,不参与灯效与交互。
2) RV32 初始化与可调参数(riscv/src/main.c)
static const uint32_t kPwmPeriodUs = 2000; // 500Hz
static const uint32_t kButtonSampleUs = 5000; // 5ms
static const uint32_t kEffectTickUs = 20000; // 20ms
说明:三组时间常量分别控制 PWM 频率、按键采样周期、灯效刷新节拍;Board_Init()
负责 GPIO/LED/按键的板级初始化,Console_Init() 确保串口可用。
3) 颜色/亮度处理基础(gamma + 缩放)
static uint8_t gamma_u8(uint8_t x)
{
uint16_t xx = (uint16_t)x * (uint16_t)x;
return (uint8_t)((xx + 127u) / 255u);
}
static rgb8_t rgb_scale(rgb8_t c, uint8_t scale)
{
rgb8_t out = {
.r = (uint8_t)(((uint16_t)c.r * scale + 127u) / 255u),
.g = (uint8_t)(((uint16_t)c.g * scale + 127u) / 255u),
.b = (uint8_t)(((uint16_t)c.b * scale + 127u) / 255u),
};
return out;
}
说明:gamma_u8() 做简易 gamma≈2.0 的视觉校正,让低亮度变化更平滑;rgb_scale()
用于呼吸亮度的比例缩放,避免溢出并做四舍五入。
4) 彩虹颜色轮(HSV→RGB 简化版)
static rgb8_t color_wheel(uint16_t hue)
{
rgb8_t c = { 0, 0, 0 };
uint16_t h = hue % 1536u;
uint8_t region = (uint8_t)(h >> 8);
uint8_t offset = (uint8_t)(h & 0xFF);
switch (region) {
case 0: c.r = 255; c.g = offset; c.b = 0; break;
case 1: c.r = (uint8_t)(255 - offset); c.g = 255; c.b = 0; break;
case 2: c.r = 0; c.g = 255; c.b = offset; break;
case 3: c.r = 0; c.g = (uint8_t)(255 - offset); c.b = 255; break;
case 4: c.r = offset; c.g = 0; c.b = 255; break;
default: c.r = 255; c.g = 0; c.b = (uint8_t)(255 - offset); break;
}
return c;
}
说明:将 hue 划分为 6 个 256 级分段(总 1536 级),每段线性插值一个颜色分量,
用极少运算实现平滑彩虹过渡。
5) 事件驱动的软件 PWM(riscv/src/main.c)
uint32_t on_r = ((uint32_t)rgb.r * period_us) / 255u;
uint32_t on_g = ((uint32_t)rgb.g * period_us) / 255u;
uint32_t on_b = ((uint32_t)rgb.b * period_us) / 255u;
if (on_r > 0) { LED_On(LED_RED); } else { LED_Off(LED_RED); }
if (on_g > 0) { LED_On(LED_GREEN); } else { LED_Off(LED_GREEN); }
if (on_b > 0) { LED_On(LED_BLUE); } else { LED_Off(LED_BLUE); }
uint32_t off_r = (on_r > 0 && on_r < period_us) ? on_r : period_us;
uint32_t off_g = (on_g > 0 && on_g < period_us) ? on_g : period_us;
uint32_t off_b = (on_b > 0 && on_b < period_us) ? on_b : period_us;
uint32_t elapsed = 0;
while (1) {
uint32_t next = period_us;
if (off_r < next) next = off_r;
if (off_g < next) next = off_g;
if (off_b < next) next = off_b;
if (next == period_us) break;
if (next > elapsed) { MXC_Delay(next - elapsed); }
if (off_r == next) { LED_Off(LED_RED); off_r = period_us; }
if (off_g == next) { LED_Off(LED_GREEN); off_g = period_us; }
if (off_b == next) { LED_Off(LED_BLUE); off_b = period_us; }
elapsed = next;
}
if (elapsed < period_us) { MXC_Delay(period_us - elapsed); }
说明:将每个通道的“关灯时刻”作为事件进行调度:同一周期内只关心“下一次关断时
间”,避免三路计时器并行,实现清晰的分段延时。
6) 按键去抖(移位寄存器法)
static bool button_filter_pressed_event(button_filter_t *btn, bool
raw_pressed)
{
btn->history = (uint8_t)((btn->history << 1) | (raw_pressed ? 1u : 0u));
if (btn->history == 0xFF && !btn->stable_pressed) {
btn->stable_pressed = true;
return true;
}
if (btn->history == 0x00 && btn->stable_pressed) {
btn->stable_pressed = false;
}
return false;
}
说明:用 8 位移位寄存器记录连续采样结果:8 个 1 才触发“按下事件”;8 个 0 才认
为释放,能有效滤除按键抖动。
7) 主循环节拍调度(PWM → 按键 → 灯效)
rgb8_t pwm_rgb = {
.r = gamma_u8(target.r),
.g = gamma_u8(target.g),
.b = gamma_u8(target.b),
};
rgb_pwm_cycle(pwm_rgb, kPwmPeriodUs);
now_us += kPwmPeriodUs;
if (now_us - last_button_sample_us >= kButtonSampleUs) {
last_button_sample_us += kButtonSampleUs;
bool b1_event = button_filter_pressed_event(&btn1, PB_Get(BUTTON1) ?
true : false);
bool b2_event = button_filter_pressed_event(&btn2, PB_Get(BUTTON2) ?
true : false);
/* ... */
}
if (now_us - last_effect_tick_us >= kEffectTickUs) {
last_effect_tick_us += kEffectTickUs;
/* ... */
}
说明:以 PWM 周期作为“最小时间片”,用 now_us 累计运行时间;按键采样与灯效更新
分别用“上次时间戳 + 周期”方式调度,避免漂移。
8) 彩虹/呼吸状态机(节拍更新)
if (mode == EFFECT_RAINBOW) {
uint16_t step = rainbow_steps[rainbow_speed_level - 1];
hue = (uint16_t)((hue + step) % 1536u);
target = color_wheel(hue);
} else if (mode == EFFECT_BREATH) {
breath_level += (int16_t)(breath_dir * breath_step);
/* ... */
target = rgb_scale(kBreathColors[breath_idx], (uint8_t)breath_level);
}
说明:彩虹模式通过色相步进遍历颜色轮,速度由 rainbow_steps 定义;呼吸模式用三
角波控制亮度(breath_level 与 breath_dir),再用 rgb_scale() 叠加颜色。
主要代码片段
CM4 启动 RV32(m4/src/main.c)
printf("ARM: RV_ARM_Loader\r\n");
MXC_Delay(MXC_DELAY_SEC(2));
MXC_SYS_RISCVRun();
说明:CM4 侧只负责提示与启动 RV32,业务逻辑全部交给 RV32。
事件驱动的软件 PWM(riscv/src/main.c)
static void rgb_pwm_cycle(rgb8_t rgb, uint32_t period_us)
{
uint32_t on_r = ((uint32_t)rgb.r * period_us) / 255u;
uint32_t on_g = ((uint32_t)rgb.g * period_us) / 255u;
uint32_t on_b = ((uint32_t)rgb.b * period_us) / 255u;
...
}
说明:用“事件调度”的方式在一个周期内统一处理 RGB 的开关时刻,避免为每个通道单
独计时,逻辑清晰且易解释。
按键去抖(移位寄存器法)
static bool button_filter_pressed_event(button_filter_t *btn, bool
raw_pressed)
{
btn->history = (uint8_t)((btn->history << 1) | (raw_pressed ? 1u : 0u));
if (btn->history == 0xFF && !btn->stable_pressed) {
btn->stable_pressed = true;
return true;
}
if (btn->history == 0x00 && btn->stable_pressed) {
btn->stable_pressed = false;
}
return false;
}
说明:连续 8 次采样稳定才认定为“按下”,能有效滤除抖动。
彩虹/呼吸状态机(节拍更新)
if (mode == EFFECT_RAINBOW) {
hue = (uint16_t)((hue + step) % 1536u);
target = color_wheel(hue);
} else if (mode == EFFECT_BREATH) {
breath_level += (int16_t)(breath_dir * breath_step);
...
target = rgb_scale(kBreathColors[breath_idx], (uint8_t)breath_level);
}
说明:灯效以固定 tick 更新,保证动画节奏稳定;参数可调,易于演示和拓展。
4 硬件实现功能展示

展示要点
- 上电后串口打印:
- ARM: RV_ARM_Loader
- RV32: MAX32655FTHR RGB 灯效演示
- BUTTON1: 切换模式 | BUTTON2: 调整参数
- 默认模式为“纯色”,BUTTON2 依次切换预置颜色(含关灯)。
- 按下 BUTTON1 切换到“彩虹”模式,LED 连续渐变;BUTTON2 切换速度(慢/中/快)。
- 再次按 BUTTON1 切换到“呼吸”模式,LED 亮度循环变化;BUTTON2 切换呼吸颜色(红/
绿/蓝/白)。