[Arduino资料] 零知开源——使用 GPIO 模拟时序驱动 WS2812B LED 灯带

[复制链接]
 楼主| lingzhiLab 发表于 2025-2-20 10:47 | 显示全部楼层 |阅读模式
本帖最后由 lingzhiLab 于 2025-2-20 10:51 编辑

#申请原创#

利用零知增强版的GPIO 模拟时序      
      
      在本教程中,我们将探讨如何使用 零知增强版的 GPIO 接口来模拟 WS2812B LED 灯带的信号传输时序,从而实现对单色或多彩 LED 灯带的控制。这种技术允许我们避开专用驱动库,直接与硬件进行交互,理解并掌握 WS2812B 的通信机制。


一、工具原料
电脑、Windows系统
零知增强版开发板
Micro-usb线
WS2812RGB灯

       WS2812B 是一款内含控制器芯片的全彩 LED 灯珠,每个灯珠可以独立显示红、绿、蓝三色。它通过单一数据线接收命令,实现高精度颜色控制。


二、硬件连接
                             零知增强版     
                        
                            WS2812B   
                        
                        5V
                        
                        VCC
                        
                        GND
                        
                        GND
                        
                        51
                        
                        Din
                        
1、硬件连接示意图
a8c9671deece4cd0b644ee0a76decd8a.png


2、实际效果
5e843b45eddd424bb8f17811854d456f.png



三、传输时序和颜色控制

1、信号传输时序
WS2812B 的数据传输遵循特定的时间序列:

  • 高电平持续时间决定比特值:T1H 和 T0H 分别代表比特 1 和比特 0 的高电平持续时间。
  • 低电平持续时间:T1L 和 T0L。
注:T1H 为 800ns,T1L 为 450ns 表示 1 比特。
  T0H 为 400ns,T0L 为 850ns 表示 0 比特。


2、颜色控制控制全局亮度和遵循WS2812B发送的时序:

  • 通过brightness参数调节RGB灯的全局亮度
  • WS2812B协议发送时序为G -> R -> B

四、代码驱动

1、相关定义和初始化
  1. // WS2812B相关定义
  2. #define WS2812B_PIN 51      // WS2812B数据引脚
  3. #define NUM_LEDS 8          // 灯珠数量
  4. #define MAX_BRIGHTNESS 0.5  // 全局亮度调节(范围:0.0 - 1.0)

  5. // WS2812B控制协议时间(根据各自的时序进行修改该定义)
  6. #define T1H 800
  7. #define T1L 450
  8. #define T0H 400
  9. #define T0L 850

  10. // 初始化WS2812B引脚
  11. void setupWS2812B() {
  12.     pinMode(WS2812B_PIN, OUTPUT);
  13.     digitalWrite(WS2812B_PIN, LOW);
  14. }

  15. // 更精确的纳秒延时函数(这里只是示例,实际可能需要更复杂的实现)
  16. // 假设使用了支持纳秒级延时的定时器库
  17. // 这里暂时使用简单的微秒级延时近似
  18. void delayNanoseconds(unsigned long ns) {
  19.     delayMicroseconds(ns / 1000);
  20. }


2、控制颜色和发送相关数据
  1. // 发送一个比特
  2. void WS2812B_SendBit(bool bitVal) {
  3.     if (bitVal) {
  4.         // 发送逻辑1
  5.         digitalWrite(WS2812B_PIN, HIGH);
  6.         delayNanoseconds(T1H);
  7.         digitalWrite(WS2812B_PIN, LOW);
  8.         delayNanoseconds(T1L);
  9.     } else {
  10.         // 发送逻辑0
  11.         digitalWrite(WS2812B_PIN, HIGH);
  12.         delayNanoseconds(T0H);
  13.         digitalWrite(WS2812B_PIN, LOW);
  14.         delayNanoseconds(T0L);
  15.     }
  16. }

  17. // 发送一个字节
  18. void WS2812B_SendByte(uint8_t byte) {
  19.     for (int i = 7; i >= 0; i--) {
  20.         WS2812B_SendBit(byte & (1 << i));
  21.     }
  22. }

  23. // 发送RGB颜色数据(带亮度调节)
  24. void WS2812B_SendColor(uint8_t red, uint8_t green, uint8_t blue, float brightness) {
  25.     // 应用全局亮度调节
  26.     red = (uint8_t)(red * brightness * MAX_BRIGHTNESS);
  27.     green = (uint8_t)(green * brightness * MAX_BRIGHTNESS);
  28.     blue = (uint8_t)(blue * brightness * MAX_BRIGHTNESS);

  29.     // WS2812B协议发送顺序:G -> R -> B
  30.     WS2812B_SendByte(green);
  31.     WS2812B_SendByte(red);
  32.     WS2812B_SendByte(blue);
  33. }


3、实现流水灯、呼吸灯等功能
  1. // 效果:彩虹追逐
  2. void rainbowChaseEffect(uint8_t wait) {
  3.     for (int offset = 0; offset < 255; offset++) {
  4.         for (int i = 0; i < NUM_LEDS; i++) {
  5.             int hue = (i * 255 / NUM_LEDS + offset) % 255;
  6.             uint8_t r = 0, g = 0, b = 0;
  7.             if (hue < 85) {
  8.                 r = 255 - hue * 3;
  9.                 g = hue * 3;
  10.                 b = 0;
  11.             } else if (hue < 170) {
  12.                 hue -= 85;
  13.                 r = 0;
  14.                 g = 255 - hue * 3;
  15.                 b = hue * 3;
  16.             } else {
  17.                 hue -= 170;
  18.                 r = hue * 3;
  19.                 g = 0;
  20.                 b = 255 - hue * 3;
  21.             }
  22.             WS2812B_SendColor(r, g, b, MAX_BRIGHTNESS);
  23.         }
  24.         delay(wait);
  25.     }
  26. }

  27. // 呼吸灯效果
  28. void breathAndFlow(uint8_t red, uint8_t green, uint8_t blue, uint8_t steps, uint16_t period, uint8_t wait, uint8_t iterations) {
  29.     int ledStep[NUM_LEDS]; // 为每个 LED 创建一个步骤计数器
  30.     for (int i = 0; i < NUM_LEDS; i++) {
  31.         ledStep[i] = 0; // 初始化每个LED的步进
  32.     }

  33.     uint8_t cycleCounter = 0; // 添加循环计数器

  34.     while (cycleCounter < iterations) { // 有限循环,迭代指定次数
  35.         for (int i = 0; i < NUM_LEDS; i++) {
  36.             // 计算当前 LED 的亮度比例
  37.             float brightness = (sin(ledStep[i] * (M_PI / (steps))) + 1) / 2;
  38.             WS2812B_SendColor(red, green, blue, brightness); // 使用计算出的亮度

  39.             // 更新 LED 的步骤计数器,模拟呼吸效果
  40.             ledStep[i] = (ledStep[i] + 1) % (steps * 2); // 确保计数器在达到两倍步骤后重置

  41.             // 计算每个步骤的时间间隔
  42.             delayMicroseconds(period / steps);
  43.         }
  44.       
  45.         // 在一轮呼吸之后关闭所有灯
  46.         clearAllLeds();

  47.         // 增加循环计数器
  48.         cycleCounter++;

  49.         // 根据需要添加延迟,虽然这不是必须的
  50.         delay(wait);
  51.     }
  52. }

  53. // 增加一个状态变量来记录是否有颜色覆盖
  54. bool isCovered = false;

  55. // 流水灯
  56. void ShampEffect(uint8_t red, uint8_t green, uint8_t blue, uint8_t trailDecay, uint8_t wait) {
  57.     // 特殊处理第一个灯
  58.     WS2812B_SendColor(red, green, blue, MAX_BRIGHTNESS);
  59.     // 从第二个灯开始的索引为1
  60.     for (int i = 0; i < NUM_LEDS; i++) {
  61.         for (int j = 0; j <= NUM_LEDS; j++) {
  62.             if (i - j == 0) {
  63.                 if (!isCovered) {
  64.                     // 如果没有被覆盖,设置为绿色
  65.                     WS2812B_SendColor(0, 0, 0, MAX_BRIGHTNESS);
  66.                 } else {
  67.                     WS2812B_SendColor(red, green, blue, MAX_BRIGHTNESS);
  68.                 }
  69.             } else {
  70.                 WS2812B_SendColor(0, 0, 0, MAX_BRIGHTNESS * trailDecay / 255.0);
  71.             }
  72.         }
  73.         if (i == NUM_LEDS - 1) {
  74.             // 当到达最后一个灯时,标记为已覆盖
  75.             isCovered = true;
  76.         }
  77.         delay(wait);
  78.     }
  79. }


4、控制灯的状态
  1. // 设置特定位置灯珠颜色
  2. void setLedColor(uint8_t pos, uint8_t red, uint8_t green, uint8_t blue, float brightness) {
  3.     if (pos < NUM_LEDS) {
  4.         // 只发送前面灯珠的关闭信号,直到要设置颜色的灯珠位置
  5.         for (int i = 0; i < pos; i++) {
  6.             WS2812B_SendColor(0, 0, 0, 0);
  7.         }
  8.         // 设置目标灯珠颜色
  9.         WS2812B_SendColor(red, green, blue, brightness);
  10.         // 发送后面灯珠的关闭信号,从目标灯珠的下一个位置开始
  11.         for (int i = pos + 1; i < NUM_LEDS; i++) {
  12.             WS2812B_SendColor(0, 0, 0, 0);
  13.         }
  14.     }
  15. }

  16. // 设置所有灯珠颜色
  17. void setAllLeds(uint8_t red, uint8_t green, uint8_t blue, float brightness) {
  18.     clearAllLeds();// 先清除所有灯珠,确保没有杂色
  19.     for (int i = 0; i < NUM_LEDS; i++) {
  20.         WS2812B_SendColor(red, green, blue, brightness);
  21.     }
  22. }

  23. // 清除所有灯珠
  24. void clearAllLeds() {
  25.     for (int i = 0; i < NUM_LEDS * 3; i++) {
  26.         WS2812B_SendByte(0);
  27.     }
  28. }


5、主循环
  1. // 初始化
  2. void setup() {
  3.     setupWS2812B();
  4.     clearAllLeds();  // 确保灯带初始状态关闭
  5. }

  6. // 主循环
  7. void loop() {
  8.     uint8_t Count = 0;
  9.     //clearAllLeds();
  10.     // 设置第六个灯珠为蓝色
  11.     //setLedColor(5, 0, 0, 255, MAX_BRIGHTNESS);
  12.     //delay(500);
  13.     while(Count < 10)
  14.     {
  15.       ShampEffect(0, 0, random(255), 256, 200);
  16.       Count ++;
  17.     }
  18.    
  19.     //rainbowChaseEffect(1000);
  20.     breathAndFlow(0,255,0,5,50,100,100);
  21. }


五、成果展示将上诉代码验证后上传到零知板,可以看到以下流水灯、呼吸灯等测试结果。
https://live.csdn.net/v/437153?spm=1001.2014.3001.5501

使用 GPIO 模拟时序驱动 WS2812B LED 灯带

1277267b6966836429.png
王栋春 发表于 2025-2-20 13:48 | 显示全部楼层
做常识性围观一下,毕竟很多代码都不认识。

评论

代码中也可能存在问题,互相学习探讨!  发表于 2025-2-20 15:11
wang1979 发表于 2025-2-21 09:32 | 显示全部楼层
非常不错的学习实例
xionghaoyun 发表于 2025-2-22 08:21 | 显示全部楼层
用定时器写好些

评论

是的,幻彩灯要求的时序非常精确  发表于 2025-2-22 09:40
通宵敲代码 发表于 2025-2-28 17:05 | 显示全部楼层
数年前用2812玩过两米长的灯带,小二百颗灯珠
gejigeji521 发表于 2025-3-1 16:07 | 显示全部楼层
如果用SPI的话,那使用DMA发送是不是更给力。

评论

对的,传输大数据量时使用DMA更合适  发表于 2025-3-5 16:17
暖心小太阳 发表于 2025-3-30 12:05 | 显示全部楼层
在实现流水灯和呼吸灯功能时,是不是可以通过调整代码中的参数来改变灯带的显示效果

评论

按理来说可以改变颜色的  发表于 2025-4-1 09:26
gaochy1126 发表于 2025-4-30 09:45 来自手机 | 显示全部楼层
这个不是可以通过spi和dma实现驱动的吗            
gaochy1126 发表于 2025-4-30 09:46 来自手机 | 显示全部楼层
延时驱动ws2812挺麻烦的               
gaochy1126 发表于 2025-4-30 09:46 来自手机 | 显示全部楼层
arduino的库挺多的,可以直接使用的            
gaochy1126 发表于 2025-4-30 09:47 来自手机 | 显示全部楼层
推荐fast led这个库函数,可以试试看   
 楼主| lingzhiLab 发表于 2025-5-16 13:54 | 显示全部楼层
gaochy1126 发表于 2025-4-30 09:46
延时驱动ws2812挺麻烦的

对的,不仅要考虑时序精确还要数据完整
hp860629 发表于 2025-6-27 08:42 | 显示全部楼层
lingzhiLab 发表于 2025-5-16 13:54
对的,不仅要考虑时序精确还要数据完整

时许要求太高了,兼容性就差了
hp860629 发表于 2025-6-27 08:43 | 显示全部楼层
lingzhiLab 发表于 2025-5-16 13:54
对的,不仅要考虑时序精确还要数据完整

有时候换个供应商的灯带就不行
hp860629 发表于 2025-6-27 08:44 | 显示全部楼层
用51单片机可以达到一样的效果吗?
hp860629 发表于 2025-6-27 08:45 | 显示全部楼层
gejigeji521 发表于 2025-3-1 16:07
如果用SPI的话,那使用DMA发送是不是更给力。

这种灯珠是集成了芯片在里面吧?
hp860629 发表于 2025-7-4 09:05 | 显示全部楼层
速度怎么样?
 楼主| lingzhiLab 发表于 2025-7-7 11:24 | 显示全部楼层
hp860629 发表于 2025-6-27 08:44
用51单片机可以达到一样的效果吗?

51应该也是可以的,单片机时钟频率和存储空间是有要求滴
 楼主| lingzhiLab 发表于 2025-7-7 11:26 | 显示全部楼层

STM32传输灯带数据还挺快的
hp860629 发表于 2025-7-7 14:35 | 显示全部楼层
lingzhiLab 发表于 2025-7-7 11:26
STM32传输灯带数据还挺快的

用STC8H单片机速度还行吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

22

主题

33

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部

22

主题

33

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部