ws2812b效果研究之一 cylon

ws2812b效果研究之一 cylon

名字来源于经典科幻系列《太空堡垒卡拉狄加》中机器人的眼部扫描效果。这个效果通常表现为灯光在LED灯带上来回移动,像一只眼睛在扫描一样。其实感觉就是流水灯的效果
平台是atmega 2560,三个引脚分别是vcc,gnd和信号引脚
对应于arduino中的fastled库中的cylon例子

简单的版本(跑马灯)

#include <FastLED.h>

#define NUM_LEDS 100      // LED灯数量
#define DATA_PIN 2       // 连接到LED数据引脚的Arduino引脚

CRGB leds[NUM_LEDS];     // 定义LED数组

void setup() {
  //FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);  // 初始化LED灯带
  FastLED.addLeds<WS2812,DATA_PIN,RGB>(leds,NUM_LEDS);
}

void loop() {
  cylonEffect(255, 0, 0, 10);  // 红色的Cylon效果,亮度255,延时50ms
}

void cylonEffect(uint8_t r, uint8_t g, uint8_t b, uint8_t wait) {
  // 从左到右逐个点亮LED
  for (int i = 2; i < NUM_LEDS-2; i++) {
    leds[i-2] = CRGB(r/3, g, b);
    leds[i-1] = CRGB(r/2, g, b);
    leds[i] = CRGB(r, g, b);  // 设置当前LED的颜色
    leds[i+1] = CRGB(r/2, g, b);
    leds[i+2] = CRGB(r/3, g, b);
    FastLED.show();           // 更新显示
    delay(wait);              // 延时以控制移动速度
    leds[i-2] = CRGB(0, 0, 0);
    leds[i-1] = CRGB(0, 0, 0);
    leds[i] = CRGB(0, 0, 0);  // 关闭当前LED以实现“流动”效果
    leds[i+1] = CRGB(0, 0, 0);
    leds[i+2] = CRGB(0, 0, 0);
  }
#if 1
  // 从右到左逐个点亮LED
  for (int i = NUM_LEDS - 1; i >= 0; i--) {
    leds[i] = CRGB(r, g, b);
    FastLED.show();
    delay(wait);
    leds[i] = CRGB(0, 0, 0);
  }
  #endif
}

上述其实可以实现两个效果,如果单程那就是“发射”或者“流水”,如果加上反向那就是“循环”或者“震动”

好看的版本(梦幻)


/// @file    Cylon.ino
/// @brief   实现一个单个LED来回移动的动画效果(Larson扫描器效果)
/// @example Cylon.ino

#include <FastLED.h>  // 包含FastLED库,用于控制LED灯带

// 定义灯带上的LED数量
#define NUM_LEDS 108 

// 定义数据引脚(DATA_PIN)和时钟引脚(CLOCK_PIN)
// 对于像WS2812这样的LED芯片,只需要定义DATA_PIN;
// 如果使用SPI协议的LED芯片(如LPD8806),还需要定义CLOCK_PIN。
#define DATA_PIN 2
#define CLOCK_PIN 13  // 这里CLOCK_PIN不适用于WS2812,但定义在此方便解释

// 定义LED数组,用于存储灯带中每个LED的颜色状态
CRGB leds[NUM_LEDS];

void setup() { 
	// 初始化LED灯带
	// 使用FastLED库中的addLeds函数来设置灯带类型(WS2812)、数据引脚(DATA_PIN)和颜色顺序(RGB)
	FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS);
	
	// 设置灯带的亮度,范围为0到255,这里设置为最大亮度255
	FastLED.setBrightness(255);
}

// 定义一个函数fadeall,用于逐渐减弱所有LED的亮度,产生淡出效果
void fadeall() { 
	for(int i = 0; i < NUM_LEDS; i++) { 
		leds[i].nscale8(250);  // 使用nscale8函数将LED亮度缩放到原亮度的250/255
	} 
}

void loop() { 
	static uint8_t hue = 0;  // 定义一个静态变量hue,表示色调值(0到255),用于控制颜色变化
	
	// 第一部分:LED从左到右逐个点亮
	for(int i = 0; i < NUM_LEDS; i++) {
		// 设置第i个LED的颜色为当前色调hue(使用HSV颜色空间),并将色调值hue递增
		// CHSV函数的参数依次为色调(hue)、饱和度(255)、亮度(255)
		leds[i] = CHSV(hue++, 255, 255);
		
		// 更新LED显示,将颜色数据发送到灯带
		FastLED.show(); 
		
		// 将所有LED的亮度逐渐减弱,形成淡出效果
		fadeall();
		
		// 延时10毫秒,用于控制LED移动的速度
		delay(10);
	}

	// 第二部分:LED从右到左逐个点亮
	for(int i = NUM_LEDS - 1; i >= 0; i--) {
		// 设置第i个LED的颜色为当前色调hue(使用HSV颜色空间),并将色调值hue递增
		leds[i] = CHSV(hue++, 255, 255);(此处通过算数符号重载,将CHSV类转换成了RGB然后给到了leds[i],注意leds[i]的数据类型为CRGB类)
		
		// 更新LED显示,将颜色数据发送到灯带
		FastLED.show();
		
		// 将所有LED的亮度逐渐减弱,形成淡出效果
		fadeall();
		
		// 延时10毫秒,用于控制LED移动的速度
		delay(10);
	}
}

这个效果算法的核心是:每次通过hsv色彩空间点亮下一个灯,注意灯的hue值增加,这样的话,可以使得每个灯的颜色变化没那么突然,然后把所有的灯的亮度都降低(其实只处理当前点亮的灯可以节省一些处理时间,算法才是最优的)

算法逻辑再次解释:
处理第i个灯数据最高亮度,饱和度增加,也就是CHSV(hue++, 255, 255);
发送亮灯命令,使其亮灯。
所有灯的亮度均降低一些(缩放到原亮度的250/255)此处并不会再次发送命令让其亮,待到下次循环处理下一个灯的饱和度时候才会再次亮灯。

这里提供一个hsv转rgb的参考函数

#include <stdint.h>

void hsv_to_rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t* red, uint8_t* green, uint8_t* blue) {
    uint8_t region, remainder, p, q, t;

    if (saturation == 0) {
        // 如果饱和度为0,颜色为灰色(无色),即 R = G = B = V
        *red = value;
        *green = value;
        *blue = value;
        return;
    }

    // 计算色调所在的区域(色环被分为6个区域)
    region = hue / 43;
    remainder = (hue - (region * 43)) * 6;

    // 计算中间值
    p = (value * (255 - saturation)) >> 8;
    q = (value * (255 - ((saturation * remainder) >> 8))) >> 8;
    t = (value * (255 - ((saturation * (255 - remainder)) >> 8))) >> 8;

    // 根据当前的色调区域,计算最终的 RGB 值
    switch (region) {
        case 0:
            *red = value;
            *green = t;
            *blue = p;
            break;
        case 1:
            *red = q;
            *green = value;
            *blue = p;
            break;
        case 2:
            *red = p;
            *green = value;
            *blue = t;
            break;
        case 3:
            *red = p;
            *green = q;
            *blue = value;
            break;
        case 4:
            *red = t;
            *green = p;
            *blue = value;
            break;
        default:
            *red = value;
            *green = p;
            *blue = q;
            break;
    }
}

后续考虑到这个函数使用极其频繁, 于是乎考虑到使用内嵌汇编和查找表进行函数优化

#include <stdint.h>

// 函数原型
void hsv_to_rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t* red, uint8_t* green, uint8_t* blue);

// 查找表定义:每个条目包含三个字段,分别是 RGB 通道的分配顺序
const uint8_t lookup_table[6][3] = {
    {0, 1, 2}, // case 0: red = value, green = t, blue = p
    {1, 0, 2}, // case 1: red = q, green = value, blue = p
    {2, 0, 1}, // case 2: red = p, green = value, blue = t
    {2, 1, 0}, // case 3: red = p, green = q, blue = value
    {1, 2, 0}, // case 4: red = t, green = p, blue = value
    {0, 2, 1}  // case default: red = value, green = p, blue = q
};

void hsv_to_rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t* red, uint8_t* green, uint8_t* blue) {
    uint8_t region, remainder, p, q, t;

    // 优化:直接用位移操作代替除法
    region = hue >> 5;  // hue / 32 (接近于原始代码中的 hue / 43)
    remainder = (hue & 31) << 3;  // hue % 32 * 8,原始 remainder 计算优化

    // 计算中间值
    p = (value * (255 - saturation)) >> 8;

    // 使用内嵌汇编优化 q 和 t 的计算
    __asm__ volatile (
        "mul %[temp1], %[sat], %[rem]\n\t" // temp1 = saturation * remainder
        "mul %[temp2], %[sat], %[comp_rem]\n\t" // temp2 = saturation * (255 - remainder)
        "rsb %[temp3], %[value], #255\n\t" // temp3 = 255 - value

        "mla %[q], %[temp1], %[temp3], %[value]\n\t" // q = value - (temp1 * temp3) / 256
        "mla %[t], %[temp2], %[temp3], %[value]\n\t" // t = value - (temp2 * temp3) / 256

        : [q] "=r" (q), [t] "=r" (t)
        : [value] "r" (value), [sat] "r" (saturation), [rem] "r" (remainder), [comp_rem] "r" (255 - remainder)
        : "cc", "memory"
    );

    // 从查找表中获取 RGB 分量的分配顺序
    const uint8_t* lookup = lookup_table[region];

    uint8_t rgb[3];
    rgb[lookup[0]] = value;
    rgb[lookup[1]] = t;
    rgb[lookup[2]] = p;

    *red = rgb[0];
    *green = rgb[1];
    *blue = rgb[2];
}

那么大体上这个效果相关的技术细节就都处理完成了。后续将其移植到stm32上面去。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值