本文主要是介绍ws2812b效果研究之一 cylon,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
效果
- 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) {// 从左到右逐个点亮LEDfor (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// 从右到左逐个点亮LEDfor (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,这里设置为最大亮度255FastLED.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上面去。
这篇关于ws2812b效果研究之一 cylon的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!