8x8x8 RGB LED 立方体
组件和用品
| × | 512 | ||||
| × | 12 | ||||
| × | 1 | ||||
| × | 8 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 8 | ||||
| × | 8 | ||||
| × | 8 |
应用和在线服务
|
关于这个项目
视频
构建
此版本的灵感来自 Kevin Darrah RGB 立方体。
看看凯文的身材,他的耐心一定是无止境的——不幸的是我的耐心。
我决定用 12 个 DM13a LED 驱动器(在 eBay 上每个约 1 美元)替换 24 个移位寄存器、192 个晶体管和 640 个电阻器。
立方体本身是按照凯文在以下视频中描述的那样构建的:
制作立方体后,我使用墨粉方法制作了一块印刷电路板,以固定 DM13A 驱动器芯片和立方体本身。与其支付带有通孔电镀的商用板的成本,我决定使用绕线将每个 LED 连接手工连接到适当的 DM13A 引脚。包含的 Eagle 文件包含手动接线版本以及自动路由版本(未经测试)。
微处理器和阳极板还有一个 MSGEQ7 - 七频段图形均衡器和麦克风前置放大器,我计划在未来进行实验。现在,它们没有被使用。如果您想使用商用 UNO 或 Nano 板,您只需要 74HC138 3 至 8 线解码器和 8 个 P 沟道 MOSFET 和相关电阻。如果您愿意,您可以将它们连接到一些原型板上。
5V 20W 电源是在易趣上购买的。我用 40 毫米 x 9 毫米的松木制作了箱子。
我在 Kevin 的魔方软件中添加了一些动画,但基本保持不变。
结论
您应该能够以大约 30 美元的价格从 eBay 购买 600 x 5mm 共阳极扩散 RGB LED。即使在简化了电子设备之后,立方体的构建也非常耗时,但最终还是值得的。
代码
- Cube_8x8x8_V1.ino
Cube_8x8x8_V1.inoC/C++
/*The 8x8x8 RGB LED Cubeby John Bradnam 基于 Kevin DarrahLatestV12 04/17/2013 发布说明:V11-修复了 BAM 时序错误-将空白引脚设置移动到 ISR,因此引脚在写入 V12 按位操作以设置之前无效pin LOW 不正确应该是 PORTx &=~(1<// SPI 库用于将数据输出到移位寄存器#define LATCH_PIN 2 //可以使用任何你想锁存移位寄存器的引脚#define BLANK_PIN 4 // 同样,可以使用你想要的任何引脚,只要确保你通过a 1k to 5V#define DATA_PIN 11 // SPI 使用,必须是 pin 11#define CLOCK_PIN 13 // SPI 使用,必须是 13#define LAYER_A 5 //74138 A Input#define LAYER_B 6 //74138 A Input# define LAYER_C 7 //74138 A Input#define SWITCH_PGM 10 //PB2#define SWITCH_SEQ 9 //PB1#define CUBE_SIZE 8 //立方体中的列数、行数或层数#define CUBE_MAX (CUBE_SIZE - 1) //最大立方体dex#define LEDS_PER_LEVEL (CUBE_SIZE * CUBE_SIZE) //每级LED数量//***变量***变量***变量***变量***变量***变量***变量***变量//这些变量由多路复用和位角调制代码使用//这是每个LED的亮度存储方式,//每个LED只需要一个'位'来知道它应该是ON还是OFF,所以64字节给出you 512 bits=512 LEDs//由于我们调制LEDs,使用4位分辨率,每种颜色有4个数组,每字节64位red0[LEDS_PER_LEVEL], red1[LEDS_PER_LEVEL], red2[LEDS_PER_LEVEL], red3[LEDS_PER_LEVEL]; blue0[LEDS_PER_LEVEL], blue1[LEDS_PER_LEVEL], blue2[LEDS_PER_LEVEL], blue3[LEDS_PER_LEVEL];byte green0[LEDS_PER_LEVEL], green1[LEDS_PER_LEVEL], green2[LEDS_PER_LEVEL], green_3[LEVELS] 怎么吃分辨率more of yourprecious RAMint level=0;//跟踪我们将数据转移到哪个级别int anodeLevel=0;//这通过阳极级别递增int BAM_Bit , BAM_Counter=0; // 位角调制变量以跟踪事物int animation =0; //在主循环中跟踪动画//****setup****setup****setup****setup****setup****setup****setup**** setup****setup****setup****setup****setup****setupvoid setup(){ SPI.setBitOrder(MSBFIRST);//最高位优先 SPI.setDataMode(SPI_MODE0); // 模式 0 数据上升沿,保持时钟低电平 SPI.setClockDivider(SPI_CLOCK_DIV2);//以 16MHz/2 - 8MHz 的频率运行数据 //Serial.begin(115200);// 如果你需要它? noInterrupts();// 终止中断,直到每个人都设置好 // 我们使用定时器 1 来刷新立方体 TCCR1A =B00000000;// 注册 A 全 0,因为我们没有切换任何引脚 TCCR1B =B00001011;// 位 3 设置置于 CTC 模式,将在计数器匹配时调用中断 //位 0 和 1 设置为将时钟除以 64,因此 16MHz/64=250kHz TIMSK1 =B00000010;//位 1 设置为调用中断OCR1A 匹配 OCR1A=30; // 你可以玩这个,但我把它设置为 30,这意味着://我们的时钟运行在 250kHz,即 1/250kHz =4us //OCR1A 设置为 30,这意味着中断将被调用每个( 30+1)x4us=124us, // 这给出了大约 8kHz 的复用频率 //最后设置了 Outputs pinMode(LATCH_PIN, OUTPUT);//Latch pinMode(DATA_PIN, OUTPUT);//MOSI DATA pinMode(CLOCK_PIN, OUTPUT);//SPI时钟pinMode(LAYER_A, OUTPUT);//74138 A Input pinMode(LAYER_B, OUTPUT);//74138 B Input pinMode(LAYER_C, OUTPUT);//74138 C Input digitalWrite(LAYER_A, LOW);数字写入(LAYER_B,低);数字写入(LAYER_C,低); pinMode(SWITCH_PGM, INPUT);//PGM 1 / PGM 2 Switch pinMode(SWITCH_SEQ, INPUT);//SEQ/COLOR Switch //pinMode(BLANK_PIN, OUTPUT);//输出使能很重要,最后做这件事,所以 LED 做not flash on boot up SPI.begin();//启动SPI库interrupts();//让节目开始,这让多路复用开始}//***start loop***start loop*** start loop***start loop***start loop***start loop***start loop***start loop***start loopvoid loop(){ //每个动画位于一个子例程中 // 控制一个LED,你只需:// LED(你想要0-CUBE_MAX的级别,你想要0-CUBE_MAX的行,你想要0-CUBE_MAX的列,红色亮度0-15,绿色亮度0-15,蓝色亮度0-15); if (digitalRead(SWITCH_PGM) ==HIGH) test_leds();否则{清洁();动画 =动画 + 1; switch (animation) { case 1:rainVersionTwo(20);休息;案例2:文件夹(10);休息;情况 3:sinwaveTwo(15);休息;情况 4:随机颜色(10);休息;情况 5:wipe_out(10);休息;情况 6:bouncyvTwo(15);休息;情况 7:color_wheelTWO(10);休息;案例 8:harlem_shake();休息;案例9:涟漪(10);休息;情况 10:动画 =0;休息; } }}//****LED Routine****LED Routine****LED Routine****LED Routinevoid LED(int level, int row, int column, byte red, byte green, byte blue){ //这就是一切的开始 //这个例程是如何更新 LED,包括 LED 位置及其 RG 和 B 亮度级别的输入 // 首先,检查并确保没有超出限制,只需将东西夹在位置为 0 或 7,亮度级别为 0 或 15 =constrain(level, 0, CUBE_MAX);行 =约束(行,0,CUBE_MAX);列 =约束(列,0,CUBE_MAX);红色 =约束(红色,0, 15);绿色 =约束(绿色,0, 15);蓝色 =约束(蓝色,0, 15); //立方体中有(CUBE_SIZE * CUBE_SIZE * CUBE_SIZE)个LED,所以当我们写入第2层第5列第4行时,需要将其转换为从0到(CUBE_SIZE * CUBE_SIZE * CUBE_SIZE) - 1的数字//第一级LED在序列中排在第一位,然后是第二级,然后是第三级,依此类推//对于4 x 4 x 4立方体,(level * (4 * 4))是该级别起始位置的索引,所以级别 0 是 LED 0 - 15,级别 1 是 LED 16 - 31,依此类推 //如果你俯视立方体,只看底部 // 00 01 02 03 // 04 05 06 07 / / 08 09 10 11 // 12 13 14 15 //对于一个 8 x 8 x 8 的立方体, (level * (8 * 8)) 是该级别起始位置的索引,所以级别 0 是 LED 0 - 63,级别 1是 LED 64 - 127,依此类推 //如果你俯视立方体,只看底层 // 00 01 02 03 04 05 06 07 // 08 09 10 11 12 13 14 15 // 16 17 18 19 20 21 22 23 // 24 25 26 27 28 29 30 31 // 32 33 34 35 36 37 38 39 // 40 41 42 43 44 45 46 47 // 5 5 5 5 5 5 5 5 5 7 5 8 59 60 61 62 63 //然后,如果你增加级别,上面网格的右上角将从(CUBE_SIZE * CUBE_SIZE)开始 //这样做的原因是你不必记住一个数字对于每个 LED,允许您使用级别、行、列 //现在,在那里除以 8 怎么样? //...好吧,我们每个字节有 8 位,每个 LED 所需的所有 512 位在内存中有 64 个字节,所以 // 我们将刚刚找到的数字除以 8,然后取其整数,所以我们知道哪个字节,那个位位于 //confused?没关系,让我们举个例子,如果我们想将 LED 写入立方体中的最后一个 LED,我们会写一个 7, 7, 7 // 给 (7*64)+(7*8)=7 =511,这是对的,但是现在让我们将它除以 8,511/8 =63.875,然后取它的整数,我们得到 63,//这是数组中的最后一个字节,这是正确的,因为这是last LED // 获取 LED 编号 0 - 511 int wholebyte =(level * LEDS_PER_LEVEL) + (row * CUBE_SIZE) + column; // 获取数组中的索引。每个索引位置保存一个字节或 8 位; int whichbyte =int(wholebyte / 8); int whichbit =(wholebyte &7); //这一切都会在一秒钟内有意义 //这是4位颜色分辨率,因此每种颜色包含x4个64字节数组,解释如下: bitWrite(red0[whichbyte], whichbit, bitRead(red, 0)); bitWrite(red1[whichbyte], whichbit, bitRead(red, 1)); bitWrite(red2[whichbyte], whichbit, bitRead(red, 2)); bitWrite(red3[whichbyte], whichbit, bitRead(red, 3)); bitWrite(green0[whichbyte], whichbit, bitRead(green, 0)); bitWrite(green1[whichbyte], whichbit, bitRead(green, 1)); bitWrite(green2[whichbyte], whichbit, bitRead(green, 2)); bitWrite(green3[whichbyte], whichbit, bitRead(green, 3)); bitWrite(blue0[whichbyte], whichbit, bitRead(blue, 0)); bitWrite(blue1[whichbyte], whichbit, bitRead(blue, 1)); bitWrite(blue2[whichbyte], whichbit, bitRead(blue, 2)); bitWrite(blue3[whichbyte], whichbit, bitRead(blue, 3)); //你现在更困惑了吗?你不应该!现在开始变得有意义了。注意每一行是一个bitWrite,也就是,//bitWrite(你想写的字节,要写的字节的位,以及你想写的0或1) //这意味着'whichbyte'是 0-63 的字节,其中对应于 0-511 的 LED 的位//现在为什么我们这样做有意义?取 0-511 之间的值并将其转换为 0-63 之间的值,因为每个 LED 表示 //64 字节数组中的一个位。 //然后下一行是哪一位'wholebyte-(8*whichbyte)' //这只是简单地将LED的值0-511从BYTE中减去它的位在时间8中的位置 //想想看,字节63 将包含从 504 到 511 的 LED,所以如果你取 505-(8*63),你会得到一个 1,这意味着,//LED 编号 505 位于数组中字节 63 的第 1 位 //就是它?不,您仍然必须执行您尝试写入的亮度 0-15 的 bitRead,//如果您将 15 写入 RED,则该 LED 的所有 4 个阵列都将为该位设置为 1,这意味着它将亮起100% //这就是为什么四个数组读取为 RED、GREEN 和 BLUE 输入的值的 0-4 //希望这一切都有意义?}//***MultiPlex BAM***MultiPlex BAM** *MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAMISR(TIMER1_COMPA_vect){ //这个例程在后台以OCR1A设置的频率自动调用 //在这段代码中,我设置OCR1A为30,所以每124us调用一次,给立方体中每一级124us的ON时间 //有8级,所以我们有1/8的最大亮度,因为在下一级之前必须关闭该级开启 //复用的频率则为124us*8=992us,或1/992us=约1kHz PORTD |=1 < =CUBE_SIZE) { fx =CUBE_MAX; fxm =-1; } 休息;情况一:fy =fy + fym;如果 (fy <0) { fy =0; fym =1; } if (fy>=CUBE_SIZE) { fy =CUBE_MAX; fym =-1; } 休息;情况2:fz =fz + fzm;如果(fz <0){ fz =0; fzm =1; } if (fz>=CUBE_SIZE) { fz =CUBE_MAX; fzm =-1; } 休息; } switch (random(3)) { case 0:ftx =ftx + ftxm;如果(ftx <0){ ftx =0; ftxm =1; } if (ftx>=CUBE_SIZE) { ftx =CUBE_MAX; ftxm =-1; } 休息;情况1:fty =fty + ftym;如果(fty <0){ fty =0; ftym =1; } if (fty>=CUBE_SIZE) { fty =CUBE_MAX; ftym =-1; } 休息;情况2:ftz =ftz + ftzm;如果 (ftz <0) { ftz =0; ftzm =1; } if (ftz>=CUBE_SIZE) { ftz =CUBE_MAX; ftzm =-1; } 休息; } } //while clean();}//wipeout//****rainVersionTwo****rainVersionTwo****rainVersionTwo****rainVersionTwo****rainVersionTwovoidrainVersionTwo(int runtimeInSeconds){ int x[LEDS_PER_LEVEL ], y[LEDS_PER_LEVEL], z[LEDS_PER_LEVEL], ledcolor; int xx[LEDS_PER_LEVEL]、yy[LEDS_PER_LEVEL]、zz[LEDS_PER_LEVEL]、xold[LEDS_PER_LEVEL]、yold[LEDS_PER_LEVEL]、zold[LEDS_PER_LEVEL]; for(int addr =0; addr =200 &&ledcolor <300) { for(int addr =0; addr =300 &&ledcolor <400) { } if(ledcolor>=500 &&ledcolor <600) { } ledcolor++;如果(ledcolor>=300)ledcolor=0; for(int addr =0; addr
示意图
eagle_files_WfqPEUP7Mp.zip制造工艺