适用于 WS2812 RGB LED 阵列动画的 Excel
组件和用品
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
必要的工具和机器
| ||||
|
关于这个项目
检查是否有在线支持后,我订购了一个 WS2812 8x8 RGB LED 矩阵,希望它可以作为 XY 坐标结构进行寻址,而不是 64 个 LED 的菊花链(带)包裹成 8 行,每行 8 行!
“WS2812 Led 动画”搜索结果很少,我可以立即轻松理解。当我发现由 Kevin Darrah 制作的 YouTube 视频时,他正在处理一个 16 x 16 矩阵(256 个 LED 菊花链),好像它是一个 XY 坐标系。
他的 Excel 实用程序编译了用于复制/粘贴到他的草图中的文本代码,其中包含所有必要的功能;无需导入库。
当他在他的效用范围内选择单元格并添加 RGB 值时,它们会毫无区别地应用于那些选定的单元格;不是我想要的,所以我重新设计并重建了它。这也意味着我不得不修改他的草图以兼容 8x8。
随着我对 WS2812 的要求越来越高,我不得不不断修改我的 Excel 应用程序 LED_Utility.xlsm。
Excel 工作簿现在提供:
- 要单独处理的 LED RGB 属性。
- 通过编程动态改变这些属性。
- 这些属性的 XY 重定位以模拟运动。
- 要处理的 RGB 值以及更改 XY 位置。
- 8x8 和 16x16 LED 寻址。
- 用于快速参考的一小部分 RGB 值集合。
具体来说,我的目标是:
- 开发一个红色的空心方块并打开和关闭它。
- 用不同颜色的图层覆盖另一个正方形并在它们之间交替。
- 种植红色方块并使用它。
- 考虑使用函数来保存显示元素;我将建立一个逐渐消失的螺旋。
- 创建一个颜色淡入淡出的设计。
- 在设计中加入动感。
- 寻址 16x16 数组。
我确实认识到编程知识会产生问题。我提供了一些指导,但建议进行研究或询问可能会亲自为您提供建议的人。
到 开始
组装硬件
如果您的矩阵没有排针,则将排针排焊接到矩阵背面较低位置(DIN、+5V、GND)。将 DIN 引脚连接到 Arduino 右侧的引脚 8,将另外两个连接到电路板另一侧的匹配位置。
对于此板和实用程序,XY (0, 0) 是左上角。
Excel 实用程序
打开 LED_Utility.xlsm 文件。您将看到三个工作表,“八”、“十六”和“ColMap”。
最后是一小部分 RGB 代码示例,供参考。
我已将 LED_Utility.xlsm 设置为只读以防止覆盖,同时也鼓励使用“另存为”来保留文件以供可能的重用。还内置了其他一些“错误陷阱”。
选择“八”工作表。
请注意,右侧网格填充了零。这很重要,因为草图不接受“空”值。
如上所示,有四个按钮(在“十六”上,它们可能会在屏幕右侧,一个在另一个下方,但功能如下所述)。
生成文本代码:
“显示”在左侧网格中显示值的 RGB。它只处理 0-255 之间的值。它将提醒高于 255 的值或任何变量。
“Var Map”接受数字和变量但不提供显示。
“Wrap Map”是两层的。它接受通常为“显示”显示输入的 RGB 值。然后它为草图编码添加一个模函数,以允许矩阵溢出。其次,可以添加变量,但必须稍后在草图中编程。我不是在这里解决这个问题。
每个都在网格下方(在两个工作表中)生成与设计为接受它的草图兼容的“红色”文本。
“清除”显然使两个网格都无效。
这些都将在后面描述。
当您使用 Excel 实用程序,我建议您在使用后使用 File/SaveAs... 生成您的代码,这样您就不必在某些时候重新创建您的设计 另外一点,特别是如果设计相当复杂。另外,您的 原始 LED_Utility.xlsm 已设置为只读以防止其被破坏 被覆盖。
草图
提供代码 下方 与 注意事项 使用只读属性保存。
打开 Arduino IDE 并加载“LED_Arduino.ino”草图。
你会看到:
- 第 3 行定义了正在使用的 Arduino 引脚。此设置为 Pin 8。
- 第 4 行定义了板上的 LED 单元。这些是 64。
- 然而,第 5 行将该数字乘以 3 以考虑 RBG 计数,即 192。
- 第 8、10 和 25 行定义了正在使用的函数。
在第 3 行和第 19 行之间,原始文本已更改,但您可以编辑以寻址 16x16 矩阵。也就是说,我在软件中提供了 LED_Ardino16.ino 并且 LED_Utility.xlsm 将容纳它。
我经常会建议你打开“LED_Arduino.ino”,然后用一些名字“另存为”。如果您没有将适当的属性设置为“只读”,这将确保“LED_Arduino.ino”不变。 [我发现我新创建的文件仍然设置了只读属性;发布以确保未来的编辑和保存]。
同时,新创建的草图可能需要重新定义端口和电路板中的一个或两个;会出现错误消息,但可能不会立即清除。
项目 1
将 LED_Arduino.ino 加载到 Arduino IDE 中并保存为 Red_Square(ino 会自动添加)。
最初我提议建立一个 3x3 红色方块 并将该代码放在草图的第 40 行。单元格 N8、O8、P8、N11、P11、N14、O14 和 P14 将保持 255 的值。
当 LED_Utility 红色文字时,
mapLEDXY(2,2,255,0,0);mapLEDXY(2,3,255,0,0);mapLEDXY(2,4,255,0,0);mapLEDXY(3,2,255,0,0); mapLEDXY(3,4,255,0,0);mapLEDXY(4,2,255,0,0);mapLEDXY(4,3,255,0,0);mapLEDXY(4,4,255,0,0);
复制到第40行,下面的文字自然会下移。
当您将草图上传到 Arduino 时,您的矩阵将打开和关闭该方块。
在继续之前,您需要考虑:
- 清除两个网格。
- 更改该方块内的一些颜色。
- 在原件周围添加一个不同颜色的正方形。
项目2
建议您打开草图Red_Square.ino 并保存为Colour_Sq('.ino' 会自动添加)以防止覆盖Red_Square.ino。
将 Colour_Sq.ino 加载到 IDE 中后,转到实用程序中的 Red_Square 并修改中间方块。 MakeO8、O10、O14 和 O16 ‘255’。 N11 和 P11 为“0”,但 N12 和 P12 为“255”。按“显示”。
添加新代码后,我预计 IDE 中的第 47 行,您需要使用以下代码块:
RGB_update(-1, 0, 0, 0);delay(1000);clearLEDs();RGB_update(-1, 0, 0, 0);延迟(1000);
请注意,您可以更改延迟值以适合自己; 1000 等于 1 秒。
项目3
重新加载 Red_Square.ino 并将其另存为 Grow。
在实用程序中重新创建红色方块。在该方块周围添加您选择的值以创建边框,它们可以是 0 到 255 之间的任何值,但低于 32 可能会非常暗淡。这是我在“LED_Utility.xlsm”中的创作:
现在删除那个内部红色方块的值,然后按“显示”。将生成的代码复制到 IDE 中的第 47 行,然后复制/粘贴第 42 至 46 行。上传到 Arduino 以交替使用一个内部红色方块,然后一个被各种颜色包围的方块。
如果您愿意,请尝试扩展到另一个边界。
项目4
您可能已经意识到,当我们将代码添加到“循环”中时,它会变得冗长,并且可能会导致以后难以编辑。
在这里,我正在构建一个螺旋。
图片来自工作簿“spir5.xlsm”。这是 4 个早期工作簿的高潮。
“spir1.xlsm”是中心有明亮的(255)块,“spir2.xlsm”使用内部4x4方块的值,“spir3.xlsm”是27个方块,等等。在每个阶段我复制了代码到我的草图但不是“循环”。
相反,我在“loop”下面创建了 5 个 void 函数并在“loop”中引用它们:
Void loop(){Sp1();Sp2();Sp3();Sp4();Sp5();clearLEDs();delay(1000);}
我的第一个函数 void Sp1() 是:
void Sp1(){mapLEDXY(3,3,255,0,0);mapLEDXY(3,4,255,0,0);mapLEDXY(4,3,255,0,0);mapLEDXY(4,4,255, 0,0);RGB_update(-1, 0, 0, 0);delay(100);clearLEDs();RGB_update(-1, 0, 0, 0);delay(10);}
除了“mapLEDXY…”的两行外,每个连续的函数都是相同的。
从这个例子来看,建议打开“spir4.xlsm”来编辑外部提示的颜色来编辑专用函数而不是在“循环”内编辑似乎微不足道。
与荒谬的接壤,假设您想一次一个地连续显示单词“MISSISSIPPI”的字母。有 11 个字符,这意味着要处理 77 行代码来处理“void loop()”。如果您决定更改“S”,则必须进行 4 次编辑。这个词只使用了4个字符。因此,为它们中的每一个创建一个函数并从“循环”中适当地调用它们是有意义的。
项目5
该项目考虑了 LED_Utility 的另一个功能“Var Map”。这里将介绍变量,因此需要一些基本的编程知识。
将使用“For 循环”语法以及“If”条件。
“for”将用于增加或减少 RGB 值,例如:
for (int r=0; r<=256; r++) {} orfor (int r=255; r>=0; r--) {}
“如果”将根据需要修改程序。
让我们从简单开始,我的意思是简单。
在 LED_utility 和“Show”中间创建一个 2x2 的红色方块,代码如下:
mapLEDXY(3,3,255,0,0);mapLEDXY(3,4,255,0,0);mapLEDXY(4,3,255,0,0);mapLEDXY(4,4,255,0,0);
现在将所有 255 个值更改为“r”。按“显示”...。哎哟!它不喜欢那样。没关系,是我加的保护。按“Var Map”并检查生成的代码:
mapLEDXY(3,3,r,0,0);mapLEDXY(3,4,r,0,0);mapLEDXY(4,3,r,0,0);mapLEDXY(4,4) ,r,0,0);
255 已被替换为“r”。
在 IDE 中打开存档的 LED_Arduino.ino 并将其保存为“更改”。
在第 40 行输入:
for (int r=0; r<256;r++){}/// 后跟:for (int r=255;r>=0;r--){}
请注意,每个“for”语句后都有一个空行。
在第 41 行粘贴您的 Excel 代码:
mapLEDXY(3,3,r,0,0);mapLEDXY(3,4,r,0,0);mapLEDXY(4,3,r,0,0);mapLEDXY(4,4) ,r,0,0);接着是:RGB_update(-1, 0, 0, 0);delay(50);
现在将相同的 4 行代码块复制/粘贴到第二个“for”块中。
在“for”块之后,在“}”下面添加“delay(200);”。我发现有必要让我的眼睛知道第二个街区已经关闭!
上传后,红色块增加到全亮度然后减小,关闭然后重复。
项目6
现在让我们利用“If”条件。
在 LED_utility 中重新创建之前的红色方块,但使用 255 的 G 和 B 值用“天蓝色”块围绕它。使用“显示”。
保留红色 255,但将所有 G 255 更改为“g”,将 B 255 更改为“b”,
然后按“Var Map”。
将“LED_Arduino.ino”重新加载到IDE并另存为“FadeBorder”。
现在我们将遇到一些问题。我们有两个变量,'g' 和 'b',每个变量都必须被赋予一个值。它们也必须在程序中声明。我打算在“for”语句中声明“g”,但我需要在草图中更早地声明“b”。在第 5 行的草图中是声明 BYTE RGB[192]。在下面输入‘int b=0;’。
作为解释的一点,不能使用两个“for”循环,稍后将需要。这里每个‘b’值必须与“for”循环生成的‘g’值相同。
我的“循环”结构如下,但没有我的地图代码:
void loop() {delay(50);for (int g=0; g<256;g++){b=g;[在此添加地图代码]RGB_update(-1, 0, 0, 0 );延迟(50);}for (int g=255;g>=0;g--){b=g; [此处添加地图代码]RGB_update(-1, 0, 0, 0);delay(50);}}
请注意,B 值通过语句“b=g;”链接到 G 值。 R值保持不变,为255,而边框的亮度逐渐变淡。
项目7
现在是使用“if”语句的时候了。
使用 255 变量创建并“显示”以下内容,其中变量出现在右侧网格中。请注意,与红色单元格相邻的单元格中出现了一个很小的“L”。这将允许程序在适当的时候控制这些单元格。
这一次 LED 将逐渐变为绿色和红色,直到达到 64 值,然后这些中间边界单元将随着电路板的亮度继续增加而变为黄色。然后这个过程将反过来。
我将再次提供我的“循环”的基本结构。我将此草图称为“FadeColourChange.ino”。
void loop() { delay(50);for (int r=0; r<256;r++){g=r;if(g>64){ g=65; l=r;}[此处添加地图代码]RGB_update(-1, 0, 0, 0);延迟(50);}对于(int r=255;r>=0;r--){if(r<65){g=r; l=0;}[此处添加MapCode]RGB_update(-1, 0, 0, 0);delay(50);}}
项目8
对于最后一个项目,我不会改变颜色而是引入运动;我有一个箭头,它可以在棋盘上移动,但会从左侧重新出现。
这里我只想用“Show”生成代码。
由于我希望箭头从其当前位置移动到每个下一个位置,因此我需要更改“x”值。
这是在 G29:G33 生成的代码:
mapLEDXY(0,3,0,255,255);mapLEDXY(1,1,255,128,128);mapLEDXY(1,3,0,255,255);mapLEDXY(1,5,255,128,128);mapLEDXY(2,2,832155) ,0,255,255);mapLEDXY(2,4,255,128,128);mapLEDXY(3,3,255,128,128);
由于“1”是我对 X 坐标的最小值,因此我将其称为“x”并将所有其他值减 1。
mapLEDXY(x+0,3,0,255,255);mapLEDXY(x+1,255,128,128);mapLEDXY(x+1,3,0,255,255);mapLEDXY(x+1,5,255,128,128);mapLEDXY(x+1,3,0,255,128);mapLEDXY(x 2,255,128,128);mapLEDXY(x+2,3,0,255,255);mapLEDXY(x+2,4,255,128,128);mapLEDXY(x+3,3,255,128,128);
我的 ‘for (int x...loop’ 做得很好,除了箭头溢出到下一行循环几次!
解决方案! 如果一个值超过“8”,我需要它的模值。这迫使我创建“Wrap Map”按钮。一个 LED 的代码现在显示为:
mapLEDXY((x +1) % 8, (y +1) %8, 255, 128, 128);
为了解决 XY 布局,我觉得最好嵌套两个“for”循环,即使一个不会被使用(是的,我找到了一个尴尬的替代方案)。
for(int x=0;x<8;x++){for(int y=0;y<8;y++){[mapcode]}}
如果我在第二个循环中设置“y<1”,则忽略“y”。相反,将“y”更改为 8,将“x”更改为 0 会产生不同的效果。
最后,“Wrap Map”将接受 RGB 变量。见上面我引用的地方。具有基本编程的人应该能够处理这个问题。我添加了一个 INO 草图,ArrowVar.ino,作为一个小演示,箭头前面的亮度会发生变化。
16x16 矩阵使用。
上面提到的所有内容都适用于 16x16 WS2812 矩阵。但是必须使用“LED_Arduino16.ino”草图。
提供的草图是为 8x8 矩阵设计的,除了“Spiral16.ino”。它提供比“Spiral.ino”更大的显示。
这个螺旋形 16x16 矩阵有 2 张打印纸作为光漫射器。显示暂停了大约 10 秒以捕获合理的图像。
要开发您的代码,请打开 LED_Utility.xlsm 并选择页面底部的“十六”选项卡。
如果和我一样,你的显示器太大而你需要滚动,那么使用缩小选项。即便如此,也需要进行一些滚动才能复制您的代码。
扩展?
是否可以处理其他大小的矩阵?
我想知道为什么我的“LED_Arduino.ino”和“LED_Arduino16.ino”的开场白如此不同。我在第一行注释掉了某些行;两个草图都按我的意愿工作。
即使我刚刚获得了一块电路板,我也不打算为“8x32”编写工作表。我会引导你到“草图 “ 多于。有一些参数需要处理。也要特别注意第 15 行和第 18 行。
注意:我也在考虑比 UNO 类型板更小的选项。
代码
- LED_Arduino
LED_ArduinoC/C++
该草图构成了项目的基础。保存时应将其设置为只读,进一步使用时应设为“另存为”以进行存档以防止覆盖并允许轻松重新编辑。//此处的变量和定义 - WS2812 驱动程序代码需要#define WS2812_pin 8 // 现在只有数字引脚 8 可以工作#define numberOfLEDs 64// RGB LED 的总数 [256]byte RGB[192];//将 LED 的数量乘以 3 [768]// FUNCTIONS HEREvoid RGB_update(int LED, byte RED, byte GREEN, byte BLUE);//驱动LED的函数void mapLEDXY(int x, int y, byte RED, byte GREEN, byte BLUE) { int RGBlocation =0; //if (y % 2 ==0) { //偶数列[取消注释] RGBlocation =x + y * 8; //[16] // } else { //奇数列[取消注释] //RGBlocation =7 - x + y * 8; //[15] 和 [16] // } [取消注释] RGB[RGBlocation * 3] =BLUE; RGB[RGBlocation * 3 + 1] =红色; RGB[RGBlocation * 3 + 2] =GREEN;}void clearLEDs() { memset(RGB, 0, sizeof(RGB));}void setup() { pinMode(WS2812_pin, OUTPUT); clearLEDs(); RGB_update(-1, 0, 0, 0);}//setup0void loop() {//在下面RGB_update的正上方粘贴mapLEDXY线。RGB_update(-1, 0, 0, 0);延迟(1000); clearLEDs(); RGB_update(-1, 0, 0, 0); delay(1000);}//loop//WS2812 Driver Functionvoid RGB_update(int LED, byte RED, byte GREEN, byte BLUE) { // LED 是从 0 开始的 LED 编号 // RED, GREEN, BLUE 是亮度 0 ..255 该 LED 字节的设定点 ExistingPort, WS2812pinHIGH;//此处的局部变量以加快 pinWrites if (LED>=0) { //将 REG GREEN BLUE 值映射到 RGB[] 数组 RGB[LED * 3] =绿色; RGB[LED * 3 + 1] =红色; RGB[LED * 3 + 2] =蓝色; } noInterrupts();//在我们发送比特流的同时终止中断... ExistingPort =PORTB; // 保存整个 PORT B 的状态 - 让我们写入整个端口而不弄乱该端口上的其他引脚 WS2812pinHIGH =PORTB | 1; //这给了我们一个字节,我们可以用 WS2812 引脚设置整个 PORTB 高 int bitStream =numberOfLEDs * 3;//LED 串中的总字节数 //这个 for 循环遍历所有位(8 位)时间)设置 WS2812 引脚 ON/OFF 时间(int i =bitStream - 1; i>=0; i--){ PORTB =WS2812pinHIGH;//第 7 位,将引脚设置为高电平 - 不管0/1 //这里是棘手的部分,检查字节中的位是否为高/低,然后将该状态正确设置为引脚 // (RGB[i] &B10000000) 将去除 RGB[i 中的其他位],所以这里我们将剩下 B10000000 或 B00000000 // 然后很容易检查该位是高还是低,方法是使用位掩码 ""&&B10000000)"" 对它进行 AND 运算,结果为 1 或 0 //如果它是 1,我们将它与现有端口进行或运算,从而保持引脚为高电平,如果为 0,则引脚写入低电平 PORTB =((RGB[i] &B10000000) &&B10000000) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");//这些是NOPS - 这些让我们延迟时钟周期更精确的计时 PORTB =ExistingPort;//好吧,这里我们知道无论 0/1 位状态如何 __asm__("nop\n\t""nop\n\t""nop\n\ t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");//不管0/1位状态如何,引脚的最小低电平时间//然后做再次为下一位,依此类推...看到最后一位虽然略有变化 PORTB =WS2812pinHIGH;//bit 6 PORTB =((RGB[i] &B01000000) &&B01000000) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t "); PORTB =WS2812pinHIGH;//bit 5 PORTB =((RGB[i] &B00100000) &&B00100000) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t "); PORTB =WS2812pinHIGH;//bit 4 PORTB =((RGB[i] &B00010000) &&B00010000) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t "); PORTB =WS2812pinHIGH;//bit 3 PORTB =((RGB[i] &B00001000) &&B00001000) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t "); PORTB =WS2812pinHIGH;//bit 2 PORTB =((RGB[i] &B00000100) &&B00000100) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t "); PORTB =WS2812pinHIGH;//bit 1 PORTB =((RGB[i] &B00000010) &&B00000010) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t "); PORTB =WS2812pinHIGH;//bit 0 __asm__("nop\n\t");//在最后一个位上,检查要快得多,所以不得不在这里添加一个NOP PORTB =((RGB[i] &B00000001) &&B00000001) |现有端口; __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); PORTB =ExistingPort;//注意在将引脚写为低电平后没有 NOP,这是因为 FOR 循环使用我们可以使用的时钟周期来代替 NOPS }//for loop interrupts();//启用中断 //全部完成!}//void RGB_update
Led_Utility.xlsm
此应用程序生成用于复制/粘贴到草图中的各种文本。https://github.com/CobraCat/LED_Utility草图.zip
这些是开发过程中使用的一些或更多草图。它们可作为“支持”使用。https://github.com/CobraCat/LED_Utility制造工艺