这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【GROVECREATORKIT测评】5、综合4-遥感控制RGB

共1条 1/1 1 跳转至页

【GROVECREATORKIT测评】5、综合4-遥感控制RGB

高工
2026-10-04 20:31:03     打赏
一、硬件介绍(Hardware)

1.1 主控板

Arduino Nano RP2040 Connect
  • MCU:Raspberry Pi RP2040(双核 ARM Cortex-M0+,133 MHz)

  • 封装:Arduino Nano 经典尺寸(18 × 45 mm),兼容 Grove 扩展板

  • 工作电压:3.3 V(逻辑电平 3.3 V,与 Grove 模块同电平域)

  • 模拟输入:8 个 12-bit ADC(本次用 A2 / A3)

  • 数字 IO:20 个,全部支持 INPUT_PULLUP

  • 开发框架:ArduinoCore-mbed(PlatformIO platform = raspberrypi)

1.2 Grove 模块清单

模块型号 / 规格丝印Arduino 引脚号GPIO接口类型
拇指摇杆 X 轴Grove - Thumb Joystick(电位器)A2A2GPIO28模拟输入
拇指摇杆 Y 轴Grove - Thumb Joystick(电位器)A3A3GPIO29模拟输入
RGB LED 时钟 CIGrove - Chainable RGB LED V2.0(P9813)D66GPIO18数字输出(开漏)
RGB LED 数据 DIGrove - Chainable RGB LED V2.0(P9813)D77GPIO19数字输出(开漏)
模块细节① Grove - Thumb Joystick
  • 摇杆由两个 10 kΩ 电位器 + 1 个按键组成;

  • 静止 / 居中:X、Y 输出约 500(实际范围 200~800);

  • 推动方向:X、Y 输出 0~1023;

  • 按下按键时,X 被硬件强制置为 1023——所以仅凭 X 轴就能识别"按下",不需要额外的按键信号线。

② Grove - Chainable RGB LED V2.0
  • 内部驱动芯片:P9813(全彩 LED 驱动 + 256 级灰度);

  • 2 线协议:CI(时钟)+ DI(数据),开漏输出,模块本身不带板上拉——必须靠 MCU 端上拉(这里使用 RP2040 的内部上拉 + INPUT_PULLUP → OUTPUT 技巧);

  • 单线协议 32 bit / 灯:起始 32 bit 0 + 每灯 32 bit [flag][B][G][R] + 末尾 32 bit 0(末尾 0 即 latch);

  • 支持级联:ChainableLED leds(clkPin, dataPin, numLeds) 中 numLeds 即链上灯数。


1.3 接线总览

Arduino Nano RP2040 Connect
            ┌────────────────────────────────┐
            │                          A2  ──┼──► Joystick X(SIG)
            │                          A3  ──┼──► Joystick Y(SIG)
            │                          D6  ──┼──► RGB LED CI (时钟)
            │                          D7  ──┼──► RGB LED DI (数据)
            └────────────────────────────────┘
82844c90-b8eb-4a09-ac69-f95c547b3e35.png

二、软件设计(Software Design)

程序结构总览

PlatformIO Project (seeedtest)
├── platformio.ini
├── src/
│   └── main.cpp                ← 主程序
└── lib/
    └── ChainableLED/           ← 项目级 P9813 驱动库
        ├── ChainableLED.h
        └── ChainableLED.cpp
main.cpp 整体扁平结构:
main.cpp
├── 文件头注释(接线、行为、Joystick 极性说明)
├── 引脚 / 参数常量
├── 状态机 + 方向枚举 + "待更新" 标志位
├── 库对象(ChainableLED leds(6, 7, 1))
├── 摇杆读取工具(keyPressed / dirFromAxis)
├── onDirChanged()              ← 边沿触发加/减档
├── showColor() / flushColor()  ← 待更新标志位模型
├── setup()                     ← 引脚 + LED init + 串口
└── loop()
     ├── 1) 按键清零(写一次)
     ├── 2) 摇杆方向识别(NONE → INC/DEC 才 +1/-1 档)
     ├── 3) flushColor()        ← 出口检查 pendingUpdate,写一次后清零
     └── 4) 节流日志

行为模型

输入行为
摇杆 X 推过死区一次红色 +1 档(0..15)
摇杆 Y 推过死区一次绿色 +1 档(0..15)
摇杆按键红绿清零、LED 全灭
红绿同档同灯叠加显示(黄 = 红 + 绿)
关键语义:
  • 推过死区那一刻立刻 +1 档(边沿触发,不延迟);

  • 保持不动不重复触发(必须物理回中再推一次才能再加);

  • 蓝色通道固定 0(题目要求"双色")。

关键算法:边沿触发 + 待更新标志位

边沿触发的状态机
static void onDirChanged(uint8_t &v, Dir cur, Dir &last) {
  // 1) 摇杆在死区中心:清零状态机
  if (cur == DIR_NONE) {
    if (last != DIR_NONE) last = DIR_NONE;
    return;
  }
  // 2) cur == last(保持同一方向):不触发
  if (cur == last) return;
  // 3) cur != last 且 cur != NONE:NONE -> 非 NONE 上升沿 → 改档 + 置位
  last = cur;
  if (cur == DIR_INC) v = (v >= LEVELS - 1) ? LEVELS - 1 : v + 1;
  else                v = (v == 0)         ? 0          : v - 1;
  pendingUpdate = true;
}
  • 死区:raw ∈ [420, 580] 视为 DIR_NONE(中心);

  • 方向:raw < 420 → DIR_DEC;raw > 580 → DIR_INC;

  • 抖动防护:摇杆 ADC 在死区边缘跳变时(580/582/580…),只要没真正回到 NONE,last 一直被锁在 INC/DEC,绝不会重复触发。

待更新标志位模型
static void showColor() {
  pendingUpdate = true;          // 只置标志,不直接写总线
}
static void flushColor() {
  if (!pendingUpdate) return;    // 没新数据:什么都不做(LED 锁在原状态)
  pendingUpdate = false;         // 先清零再写
  leds.setColorRGB(0, r, g, 0);  // 真正写一次
}
数据流:
摇杆方向变化 → onDirChanged 改 level → pendingUpdate = true
按键清零    → level = 0 → showColor() → pendingUpdate = true
loop() 出口 → flushColor() → 检查标志、写一次、清零
优势:
  • 每帧最多写一次 LED;

  • 状态没变完全不写总线(P9813 latch 不会被打断);

  • 判断"何时写"集中在 loop() 出口一处。

库实现关键点(ChainableLED.cpp)

void ChainableLED::setColorRGB(uint8_t led, uint8_t r, uint8_t g, uint8_t b) {  sendZeros();                                  // 起始 32 bit 0  for (uint8_t i = 0; i < _numLeds; ++i) {      // 每灯 32 bit    if (i == led) sendColor(r, g, b);    else          sendColor(0, 0, 0);  }  sendZeros();                                  // 末尾 32 bit 0(latch)  digitalWrite(_clkPin, LOW);  delayMicroseconds(80);                        // latch ≥ 24 µs  digitalWrite(_clkPin,  HIGH);                 // 进 idle  digitalWrite(_dataPin, HIGH); }
  • 起始 32×0 让所有 P9813 走出 reset;

  • 末尾 32×0 + CI LOW 80µs = latch(让 P9813 把 shift register 锁存到输出);

  • init() 先 INPUT_PULLUP → OUTPUT,让引脚默认电平稳定为 HIGH,避免开漏线浮空。

时间参数

参数值含义
DEAD_LO420死区下界(raw < 此值视为 DIR_DEC)
DEAD_HI580死区上界(raw > 此值视为 DIR_INC)
KEY_X_RAW1000X ≥ 此值视为按键按下
LEVELS16红/绿各 16 档
LEVEL_STEP17灰度档位(255 / 15)
LOG_INTERVAL_MS150串口日志节流
主循环 delay10 ms采样周期

三、功能展示

eepw-4.gif

附:全部代码
#include "ChainableLED.h"

ChainableLED::ChainableLED(uint8_t clkPin, uint8_t dataPin, uint8_t numLeds) {
  _clkPin  = clkPin;
  _dataPin = dataPin;
  _numLeds = numLeds;
}

void ChainableLED::init() {
  pinMode(_clkPin,  INPUT_PULLUP);
  pinMode(_dataPin, INPUT_PULLUP);
  delayMicroseconds(20);
  pinMode(_clkPin,  OUTPUT);
  pinMode(_dataPin, OUTPUT);
  digitalWrite(_clkPin,  LOW);
  digitalWrite(_dataPin, LOW);
  delayMicroseconds(50);

  setColorRGB(0, 0, 0, 0);
}

void ChainableLED::sendByte(uint8_t b) {
  for (uint8_t i = 0; i < 8; ++i) {
    if (b & 0x80) digitalWrite(_dataPin, HIGH);
    else          digitalWrite(_dataPin, LOW);
    delayMicroseconds(1);
    digitalWrite(_clkPin, HIGH);
    delayMicroseconds(1);
    digitalWrite(_clkPin, LOW);
    b <<= 1;
  }
  delayMicroseconds(1);
}
void ChainableLED::sendZeros() {
  sendByte(0x00);
  sendByte(0x00);
  sendByte(0x00);
  sendByte(0x00);
}

void ChainableLED::sendColor(uint8_t r, uint8_t g, uint8_t b) {
  uint8_t prefix = 0xC0;
  if ((b & 0x80) == 0) prefix |= 0x20;
  if ((b & 0x40) == 0) prefix |= 0x10;
  if ((g & 0x80) == 0) prefix |= 0x08;
  if ((g & 0x40) == 0) prefix |= 0x04;
  if ((r & 0x80) == 0) prefix |= 0x02;
  if ((r & 0x40) == 0) prefix |= 0x01;

  sendByte(prefix);
  sendByte(b);
  sendByte(g);
  sendByte(r);
}

void ChainableLED::setColorRGB(uint8_t led, uint8_t r, uint8_t g, uint8_t b) {
  if (led >= _numLeds) return;
  sendZeros();
  for (uint8_t i = 0; i < _numLeds; ++i) {
    if (i == led) sendColor(r, g, b);
    else          sendColor(0, 0, 0);
  }

  sendZeros();
  digitalWrite(_clkPin, LOW);
  delayMicroseconds(80);

  digitalWrite(_clkPin,  HIGH);
  digitalWrite(_dataPin, HIGH);
}

// HSL -> RGB 转换 + 写入(保留以匹配官方 API)
void ChainableLED::setColorHSL(uint8_t led, float hue, float sat, float lum) {
  if (hue < 0.0f) hue = 0.0f;
  if (hue > 1.0f) hue = 1.0f;
  if (sat < 0.0f) sat = 0.0f;
  if (sat > 1.0f) sat = 1.0f;
  if (lum < 0.0f) lum = 0.0f;
  if (lum > 1.0f) lum = 1.0f;

  uint8_t r, g, b;

  if (sat == 0.0f) {
    uint8_t v = (uint8_t)(lum * 255.0f);
    r = g = b = v;
  } else {
    float h = hue * 6.0f;
    int   i = (int)h;
    float f = h - i;
    float p = lum * (1.0f - sat);
    float q = lum * (1.0f - sat * f);
    float t = lum * (1.0f - sat * (1.0f - f));
    float rv, gv, bv;
    switch (i % 6) {
      case 0: rv = lum; gv = t;   bv = p; break;
      case 1: rv = q;   gv = lum; bv = p; break;
      case 2: rv = p;   gv = lum; bv = t; break;
      case 3: rv = p;   gv = q;   bv = lum; break;
      case 4: rv = t;   gv = p;   bv = lum; break;
      default: rv = lum; gv = p;  bv = q; break;
    }
    r = (uint8_t)(rv * 255.0f);
    g = (uint8_t)(gv * 255.0f);
    b = (uint8_t)(bv * 255.0f);
  }

  setColorRGB(led, r, g, b);
}





关键词: GROVE     测评     遥感    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]