FastLED可以实现二维点阵LED的显示效果
**FastLED** 完全可以实现二维点阵(矩阵)LED的显示效果!虽然 FastLED 原生设计主要用于一维 LED 灯带控制,但通过合理的 **坐标映射** 和 **逻辑处理**,可以轻松将一维灯带数据转换为二维矩阵的显示效果。以下是具体实现方法和示例:---
### **1. 硬件连接与布局**
二维点阵的 LED 通常以网格形式排列(如 8x8、16x16 等),常见连接方式有两种:
* **蛇形排列(Snake Layout)**:
LED 灯带在行间交替方向(例如,第一行从左到右,第二行从右到左),以缩短走线。
* **逐行排列(Row-by-Row)**:
每行 LED 独立连接,方向一致。
需要根据实际硬件布局编写 **坐标映射函数**。
---
### **2. 核心步骤**
#### **(1) 定义二维矩阵参数**
```
#define MATRIX_WIDTH16// 矩阵宽度(列数)
#define MATRIX_HEIGHT 8 // 矩阵高度(行数)
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) // LED总数
CRGB leds;
```
#### **(2) 坐标映射函数**
将二维坐标 `(x, y)` 转换为一维 LED 索引。
**示例代码(蛇形排列)**:
**cpp**
复制
下载
```
uint16_t XY(uint8_t x, uint8_t y) {
if (y % 2 == 0) {
// 偶数行:从左到右
return y * MATRIX_WIDTH + x;
} else {
// 奇数行:从右到左
return y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x);
}
}
```
#### **(3) 控制二维点阵**
通过 `XY(x, y)` 函数直接操作指定坐标的 LED:
```
// 在坐标 (3, 5) 显示红色
leds = CRGB::Red;
// 在坐标 (0, 0) 到 (4, 4) 绘制绿色方块
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
leds = CRGB::Green;
}
}
```
---
### **3. 示例效果实现**
#### **(1) 滚动文字/图案**
通过偏移坐标实现水平或垂直滚动:
```
void loop() {
static int offset = 0;
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// 计算滚动后的坐标(水平滚动)
int shiftedX = (x + offset) % MATRIX_WIDTH;
// 根据图案显示颜色(示例:间隔点亮)
leds = (x % 2 == 0) ? CRGB::Blue : CRGB::Black;
}
}
FastLED.show();
offset++;
delay(100);
}
```
#### **(2) 动态波纹效果**
结合二维坐标计算动态亮度:
```
void loop() {
static uint8_t wave = 0;
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// 计算距离中心的波纹强度
float dx = x - MATRIX_WIDTH/2;
float dy = y - MATRIX_HEIGHT/2;
uint8_t brightness = 255 * (sin8(sqrt(dx*dx + dy*dy) + wave) / 255;
leds = CHSV(128, 255, brightness);
}
}
FastLED.show();
wave += 5;
delay(30);
}
```
---
### **4. 高级技巧**
#### **(1) 使用二维数组缓存**
为简化逻辑,可先将颜色数据存储在二维数组,再批量写入 LED:
```
CRGB buffer;
// 更新缓冲区
buffer = CRGB::Purple;
// 将缓冲区写入实际 LED
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
leds = buffer;
}
}
FastLED.show();
```
#### **(2) 优化性能**
* **并行刷新**:如果使用多个 LED 控制器(如多个 WS2812B 灯带),可分割矩阵并并行驱动。
* **颜色预计算**:对动态效果预先计算颜色值,减少实时计算量。
---
### **5. 完整示例:8x8 矩阵显示彩虹波浪**
```
#include <FastLED.h>
#define MATRIX_WIDTH8
#define MATRIX_HEIGHT 8
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT)
CRGB leds;
// 坐标映射(蛇形排列)
uint16_t XY(uint8_t x, uint8_t y) {
return (y % 2 == 0) ? (y * MATRIX_WIDTH + x) : (y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x));
}
void setup() {
FastLED.addLeds<WS2812B, 6>(leds, NUM_LEDS);
}
void loop() {
static uint8_t hue = 0;
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// 根据坐标生成色相和明度波浪
uint8_t ledHue = hue + x * 10 + y * 5;
uint8_t brightness = sin8(x * 20 + y * 20 + hue);
leds = CHSV(ledHue, 255, brightness);
}
}
FastLED.show();
hue += 2;
delay(20);
}
```
---
### **注意事项**
* **硬件布局**:务必确认 LED 的物理排列方式(蛇形/逐行),否则坐标会错乱。
* **性能限制**:大型矩阵(如 32x32)可能因刷新率不足导致卡顿,需优化代码或使用更高性能控制器(如 ESP32)。
通过 FastLED 的灵活性和二维坐标映射,你可以轻松实现复杂的动态图案、游戏甚至低分辨率动画!
static uint8_t wave = 0;
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// 计算距离中心的波纹强度
float dx = x - MATRIX_WIDTH/2;
float dy = y - MATRIX_HEIGHT/2;
uint8_t brightness = 255 * (sin8(sqrt(dx*dx + dy*dy) + wave) / 255);
leds = CHSV(128, 255, brightness);
}
}
FastLED.show();
wave += 5;
delay(300);
彩红效果:
static uint8_t hue = 0;
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// 根据坐标生成色相和明度波浪
uint8_t ledHue = hue + x * 10 + y * 5;
uint8_t brightness = sin8(x * 20 + y * 20 + hue);
leds = CHSV(ledHue, 255, brightness);
}
}
FastLED.show();
hue += 2;
delay(20);
#include <BluetoothSerial.h>
#include <FastLED.h>
#include <math.h>
struct Polar { float radius; float angle_radians; };
Polar cartesianToPolar(float x, float y) {
return {
sqrt(x*x + y*y),
atan2(y, x)
};
}
#define MATRIX_WIDTH30// 矩阵宽度(列数)
#define MATRIX_HEIGHT 15 // 矩阵高度(行数)
#define NUM_LEDS (MATRIX_WIDTH * MATRIX_HEIGHT) // LED总数
#define LED_PIN 15 // 数据线连接的GPIO引脚
//#define NUM_LEDS 450// LED数量
#define BRIGHTNESS 100// 亮度(0-255)
CRGB leds;
CRGB red_c = CRGB(255, 0, 0); // 红色
CRGB green_c = CRGB(0, 255, 0); // 绿色
CRGB blue_c = CRGB(0, 0, 255); // 蓝色
CRGB black_c = CRGB(0, 0, 0); // 黑色(关闭LED)
CRGB white_c = CRGB(255, 255, 255);// 白色(全亮度)
CRGB yellow_c = CRGB(255, 255, 0);// 黄色
CRGB qing_c = CRGB(0, 255, 255);// 青色
CRGB pin_c = CRGB(255, 0, 255);// 品红
CRGB cheng_c = CRGB(255, 165, 0);// 橙色
CRGB zi_c = CRGB(128, 0, 128);// 紫色
CRGB fen_c = CRGB(255, 192, 203);// 粉色
int flag=0;
//===================================================
// 定义主色和辅助色的HSV范围(H:0-255对应0-360度)
const uint8_t MAIN_HUE = 0; // 主色Hue(例如红色)
const uint8_t ACCENT_HUE = 32;// 辅助色Hue(例如橙色)
const uint8_t HUE_RANGE = 16; // 主辅色之间的过渡范围
// 动态参数
uint8_t baseBrightness = 128; // 基础亮度
uint8_t brightnessJitter = 50;// 亮度波动范围
uint8_t saturation = 200; // 饱和度(0-255)
uint8_t spreadRatio = 50; // 主色占比(%)
//===================================================
#define FILL_LEDS(value) do { \
for (size_t i = 0; i < NUM_LEDS; ++i) { \
(leds) = (value); \
} \
} while(0)
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif
uint16_t XY(uint8_t x, uint8_t y) {
return (y % 2 == 0) ? (y * MATRIX_WIDTH + x) : (y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x));
}
BluetoothSerial SerialBT;
#define BUFFER_SIZE 1024
uint8_t buffer;
int write_idx = 0;
int read_idx = 0;
unsigned long total_bytes = 0;
unsigned long last_time = 0;
#define BT_DISCOVER_TIME 10000
static bool btScanAsync = true;
static bool btScanSync = true;
void btAdvertisedDeviceFound(BTAdvertisedDevice *pDevice) {
Serial.printf("Found a device asynchronously: %s\n", pDevice->toString().c_str());
}
void setup() {
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
randomSeed(analogRead(0)); // 初始化随机种子
Serial.begin(115200);
SerialBT.begin("ESP32-liuyan");//Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
// SerialBT.setMTU(1024);
if (btScanAsync) {
Serial.print("Starting asynchronous discovery... ");
if (SerialBT.discoverAsync(btAdvertisedDeviceFound)) {
Serial.println("Findings will be reported in \"btAdvertisedDeviceFound\"");
delay(10000);
Serial.print("Stopping discoverAsync... ");
SerialBT.discoverAsyncStop();
Serial.println("stopped");
} else {
Serial.println("Error on discoverAsync f.e. not working after a \"connect\"");
}
}
if (btScanSync) {
Serial.println("Starting synchronous discovery... ");
BTScanResults *pResults = SerialBT.discover(BT_DISCOVER_TIME);
if (pResults) {
pResults->dump(&Serial);
} else {
Serial.println("Error on BT Scan, no result!");
}
}
}
void fill_shape( struct CRGB * targetArray, int numToFill,
uint8_t initialhue,
uint8_t deltahue )
{
CHSV hsv;
hsv.hue = initialhue;
hsv.val = 255;
hsv.sat = 240;
for( int i = 0; i < numToFill; ++i) {
targetArray = hsv;
hsv.hue += deltahue;
}
}
void breath_light()
{
static uint8_t brightness = 0;
// 使用正弦波生成明度值(0-255)
brightness = beatsin8(10, 50, 255); // 参数:频率(Hz), 最小值, 最大值
fill_solid(leds, NUM_LEDS, CHSV(96, 255, brightness)); // 固定色相(如绿色)
FastLED.show();
}
void loop() {
#if 0
int index;
static uint8_t hue = 0;
// 非阻塞读取蓝牙数据
while (SerialBT.available()) {
int bytes_to_read = SerialBT.available();
bytes_to_read = min(bytes_to_read, BUFFER_SIZE - write_idx);
// 写入缓冲区
if (bytes_to_read > 0) {
flag = 1;
SerialBT.readBytes(&buffer, bytes_to_read);
if(buffer == 'r' || buffer == 'R'){
FILL_LEDS(red_c);
} else if(buffer == 'g' || buffer == 'G'){
FILL_LEDS(green_c);
} else if(buffer == 'b' || buffer == 'B'){
FILL_LEDS(blue_c);
} else if(buffer == 'w' || buffer == 'W'){
FILL_LEDS(white_c);
} else if(buffer == 'y' || buffer == 'Y'){
FILL_LEDS(yellow_c);
} else if(buffer == 's' || buffer == 'S'){
FILL_LEDS(black_c);
flag = 0;
} else if(buffer == 'q' || buffer == 'Q'){
FILL_LEDS(qing_c);
} else if(buffer == 'p' || buffer == 'P'){
FILL_LEDS(pin_c);
} else if(buffer == 'c' || buffer == 'C'){
FILL_LEDS(cheng_c);
} else if(buffer == 'z' || buffer == 'Z'){
FILL_LEDS(zi_c);
} else if(buffer == 'f' || buffer == 'F'){
FILL_LEDS(fen_c);
} else if(buffer == 'v' || buffer == 'V'){
FILL_LEDS(fen_c);
}
FastLED.show();
for(index = 0;index < bytes_to_read;index ++)
Serial.printf("%c",buffer);
write_idx = (write_idx + bytes_to_read) % BUFFER_SIZE;
total_bytes += bytes_to_read;
} else {
}
// 缓冲区切换检查(防止覆盖未读数据)
if (write_idx >= BUFFER_SIZE / 2 && read_idx < BUFFER_SIZE / 2) {
read_idx = BUFFER_SIZE / 2;// 切换到后半缓冲区
}
}
// 计算实时带宽
unsigned long current_time = millis();
if (current_time - last_time >= 1000) {
float rate = (total_bytes * 8) / 1000.0;// 转换为Kbps
//Serial.printf("当前接收速率: %.2f Kbps\n", rate);
total_bytes = 0;
last_time = current_time;
}
//if(!flag) {
fill_shape(leds, NUM_LEDS, hue++, 10);
FastLED.show();
//Serial.printf("%d\n",hue);
//delay(1000);
//FILL_LEDS(black_c);
//FastLED.show();
//delay(1000);
//delay(10);
//flag = !flag;
//}
#else
static uint8_t hue = 0;
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// 根据坐标生成色相和明度波浪
uint8_t ledHue = hue + x * 10 + y * 5;
uint8_t brightness = sin8(x * 20 + y * 20 + hue);
leds = CHSV(ledHue, 255, brightness);
}
}
FastLED.show();
hue += 2;
delay(20);
//breath_light();
#endif
} #if 1
void optim_vision()
{
float global_brightness = breathe(20.0);// 20秒呼吸周期
rotation_angle += ROTATION_SPEED * (millis() % 20) / 1000.0;
for(int i=0; i<NUM_LEDS; i++) {
float r = led_r;
float theta = led_theta + rotation_angle;
// 添加角度扰动
theta += randomGaussian();
CRGB color;
if(r < 0.15) {// 核心层
float layerBrightness = 0.8 + 0.2 * sin(millis()/500.0);// 动态亮度
color = colorBlend(coreColor1, coreColor2, layerBrightness);
}
else if(r < 0.4) {// 过渡层
color = colorBlend(mainColor, coreColor2, 0.3);
}
else {// 背景层
color = bgColor;
}
// 应用全局亮度
color.fadeToBlackBy(255 * (1 - global_brightness));
leds = color;
}
FastLED.show();
FastLED.delay(20);// 控制刷新率
}
#endif
#include "led_functions.h"
#define NOISE_STRENGTH 0.3 // 扰动强度
CRGB leds; // 实际定义LED数组
// 极坐标系统
float led_r; // 半径(归一化 0-1)
float led_theta;// 初始角度(弧度)
float rotation_angle = 0; // 当前旋转角度
// 高斯噪声生成
float randomGaussian() {
// 使用Box-Muller转换生成高斯分布
static float z0, z1;
static bool generate = false;
generate = !generate;
if (!generate) return z1 * NOISE_STRENGTH;
float u1, u2;
do {
u1 = random(1, 1000) / 1000.0;
u2 = random(1, 1000) / 1000.0;
} while (u1 <= 0.0001);
z0 = sqrt(-2.0 * log(u1)) * cos(TWO_PI * u2);
z1 = sqrt(-2.0 * log(u1)) * sin(TWO_PI * u2);
return z0 * NOISE_STRENGTH;
}
// 呼吸效果生成器
float breathe(float periodSeconds) {
static unsigned long lastUpdate;
float cycle = (millis() - lastUpdate) / (periodSeconds * 1000);
return (sin(cycle * TWO_PI) * 0.5 + 0.5);// 输出0-1
}
// 极坐标初始化
void initPolarCoords() {
// 假设LED排列在圆形区域(需要根据实际布局调整)
for(int i=0; i<NUM_LEDS; i++) {
// 转换为虚拟坐标(示例用圆形布局)
float x = (i % MATRIX_WIDTH) / MATRIX_WIDTH - 0.5;// 假设50x10的布局
float y = (i / MATRIX_WIDTH) / MATRIX_HEIGHT - 0.5;
led_r = sqrt(x*x + y*y) * 2; // 归一化半径
led_theta = atan2(y, x); // 初始角度
}
}
// 颜色混合函数
CRGB colorBlend(CRGB color1, CRGB color2, float weight) {
return CRGB(
color1.r * (1-weight) + color2.r * weight,
color1.g * (1-weight) + color2.g * weight,
color1.b * (1-weight) + color2.b * weight
);
}
uint16_t XY(uint8_t x, uint8_t y)
{
return (y % 2 == 0) ? (y * MATRIX_WIDTH + x) : (y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x));
}
void leds_setup()
{
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
initPolarCoords();
} 显示“祝你生日快乐”
#include <FastLED.h>
#define LED_PIN 15
#define COLOR_ORDER GRB
#define CHIPSET WS2815
#define NUM_LEDS 450
CRGB leds;
#define MATRIX_WIDTH30
#define MATRIX_HEIGHT 15
#define BITMAP_WIDTH 96
#define BITMAP_INDEX(x,y) ((14-y)*12 + (x/8) - 1)
#define BITMMAP_POSITION(x) (x%8)
// 颜色定义
const CRGB BACKGROUND_COLOR = CRGB(0x0, 0x0, 0x100); //
const CRGB TEXT_COLOR = CRGB(0xFF, 0x0, 0x00); //
// 点阵数据(已转换格式)
/*96 x 15*/
static const unsigned char textBitmap[] = {
0x33, 0xfc, 0x08, 0x80, 0x11, 0x00, 0x1f, 0xf0, 0x10, 0x40, 0x00, 0xf0,
0x13, 0xfc, 0x08, 0x80, 0x11, 0x00, 0x1f, 0xf0, 0x10, 0x40, 0x1f, 0xf0,
0xfa, 0x04, 0x19, 0xfe, 0x11, 0x00, 0x10, 0x10, 0x13, 0xf8, 0x1f, 0x00,
0xfa, 0x04, 0x11, 0xfe, 0x3f, 0xfc, 0x10, 0x10, 0x1b, 0xf8, 0x11, 0x00,
0x1a, 0x04, 0x33, 0x06, 0x3f, 0xfc, 0x10, 0x10, 0x5c, 0x48, 0x31, 0x00,
0x13, 0xfc, 0x36, 0x24, 0x61, 0x00, 0x10, 0x10, 0x54, 0x48, 0x21, 0x00,
0x3b, 0xfc, 0x74, 0x20, 0xc1, 0x00, 0x1f, 0xf0, 0x50, 0x48, 0x3f, 0xfc,
0x7c, 0x90, 0xd1, 0x28, 0x81, 0x00, 0x1f, 0xf0, 0xd7, 0xfe, 0x3f, 0xfc,
0xd4, 0x90, 0x91, 0x2c, 0x3f, 0xf8, 0x10, 0x10, 0x97, 0xfe, 0x09, 0x20,
0x94, 0x90, 0x13, 0x24, 0x3f, 0xf8, 0x10, 0x10, 0x10, 0xe0, 0x09, 0x30,
0x11, 0x92, 0x12, 0x26, 0x01, 0x00, 0x10, 0x10, 0x10, 0xa0, 0x19, 0x18,
0x11, 0x12, 0x16, 0x22, 0x01, 0x00, 0x10, 0x10, 0x11, 0xb0, 0x31, 0x0c,
0x13, 0x12, 0x14, 0x22, 0x01, 0x00, 0x10, 0x10, 0x11, 0x10, 0x61, 0x04,
0x16, 0x1e, 0x10, 0xa0, 0xff, 0xfe, 0x1f, 0xf0, 0x13, 0x18, 0x45, 0x04,
0x1c, 0x0e, 0x10, 0xe0, 0xff, 0xfe, 0x1f, 0xf0, 0x16, 0x0e, 0x07, 0x00
};
int scrollOffset = 0;
// 蛇形布局坐标转换
int getLedIndex(int x, int y) {
if (x < 0 || x >= MATRIX_WIDTH || y < 0 || y >= MATRIX_HEIGHT) return -1;
if (y % 2 == 0) return y * MATRIX_WIDTH + x;
else return y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x);
}
void setup() {
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(50);
}
void loop() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate > 100) {
lastUpdate = millis();
fill_solid(leds, NUM_LEDS, BACKGROUND_COLOR);
for (int x = 0; x < MATRIX_WIDTH; x++) {
int bitmapX = (scrollOffset + x) % BITMAP_WIDTH;
for (int y = 0; y < MATRIX_HEIGHT; y++) {
if ((textBitmap << BITMMAP_POSITION(bitmapX)) &0x80) {
int ledIndex = getLedIndex(x, y);
if (ledIndex != -1) leds = TEXT_COLOR;
}
}
}
FastLED.show();
scrollOffset = (scrollOffset + 1) % BITMAP_WIDTH;
}
}
页:
[1]