tuenhi.n2012 gửi vào
- 31374 lượt xem
I. GIỚI THIỆU
Với khối RGB CUBE 8x8x8, chúng ta có 512 LED RED, 512 LED GREEN và 512 LED BLUE, tổng cộng là: 512x3 = 1,536 LED. Để điều khiển chúng riêng biệt với màu sắc mong muốn, chúng ta dùng 24 con 74HC595 và 8 con TIP42C và áp dụng phương pháp BAM – 4bit (Bit Angle Modulation). Về BAM, các bạn có thể tham khảo tại:
Bằng phương pháp 4 bit BAM, chúng ta có thể tạo ra 4,096 màu riêng biệt (16^3) cho từng RGB LED.
II. BOM CHO RGB CUBE 8x8x8
No. | Items | Specification | Q'ty | Remarks |
1 | Arduino Mega 2560 R3 | 1 | ||
2 | 74HC595 | 24 | ||
3 | ULN2803 | 24 | ||
4 | TIP42C | 8 | ||
5 | 2N2222A | 8 | ||
6 | Đế IC 16P | DIP16 | 24 | Dùng cho 74HC595 |
7 | Đế IC 18P | DIP18 | 24 | Dùng cho ULN2803 |
8 | RGB Led | 5mm | 512 | |
9 | Resistors R100 | 250 | ||
10 | Resistors R1K | 10 | ||
11 | Resistors R10K | 20 | ||
12 | Capacitor 0.1uF | 30 | ||
13 | Bus 8 | 30 | ||
14 | Bus 4 | 8 | ||
15 | Hàng rào đực đơn thẳng dài (40pin) | Chân dài | 7 | Dùng cho cube base |
16 | Hàng rào đực đơn thẳng ngắn (40pin) | Chân ngắn | 10 | |
17 | Jack nguồn DC đực 5V | 5.5x2.1mm | 1 | Cấp nguồn cho Arduino |
18 | Board đồng 2 lớp | Khổ A4 | 1 | Dùng cho cube base |
19 | Board đồng 1 lớp | Khổ A4 | 2 | |
20 | Mica trong khổ A3 | 1 | Làm hộpchứa đèn | |
21 | Mica đục khổ A3 | 1 | Làm hộp chứa board | |
22 | Công tắc gạt | ON/OFF | 1 | |
23 | Quạt nhỏ | 1 | Làm quạt hút cho hộp chứa board | |
24 | Dây kẽm/ dây đồng | Cuộn | 1 | |
25 | Pin 9V | 1 | Dùng để test led. | |
26 | Hộp đế pin 9V | 1 | ||
27 | Dung dịch rửa mạch in | 1 | ||
28 | Nhựa thông | 1 | ||
29 | Xăng thơm | 1 | ||
30 | Mũi khoan mạch các loại | 1 | ||
31 | Keo 502 | Hộp lớn | 1 | Dán hộp mica |
32 | Dao cắt mạch | 1 | Cắt mạch và mica |
III. PHẦN CỨNG
1. CUBE TEMPLATE
Các bạn dùng mũi khoan 5mm (RGB LED 5mm) để tạo ra 64 lỗ trên tấm gỗ với khoảng cách giữa các LED là 20mm.
2. HÀN KHỐI CUBE
Dựa vào TEMPLATE trên, các bạn sẽ tạo ra được một PLANE của LED CUBE. Mỗi PLANE có 64 RGB LED. Đây là công đoạn mất nhiều thời gian nhất và dễ sai sót nhất. Do vậy, trước và sau khi hàn LED các bạn phải kiểm tra lại, nếu không sẽ rất khó sửa sau khi làm thành khối CUBE.
3. SHIFT REGISTER BOARD & LAYER BOARD
Các bạn có thể tham khảo tại:
- http://arduino.vn/bai-viet/571-cube-led-8x8x8-arduino-promini-phan-1
- http://arduino.vn/bai-viet/573-cube-led-8x8x8-arduino-promini-phan-2
Lưu ý: Chúng ta cần 3 board mạch in SHIFT REGISTER BOARD, mỗi mạch in đều khiển một màu cho 64 RGB LED. Các mạch in này mắc nối tiếp với nhau để mỗi lần chốt ta có 24 byte data được shift out theo thứ tự:
4. CUBE BASE
Hộp chứa nguồn, các board, arduino và bên ngoài có thêm các công tắc, switch, hay nút nhấn. Các bạn có thể tham khảo trên internet cách làm một cube.
III. CHƯƠNG TRÌNH
Chương trình bên dưới tham khảo tại trang: http://www.kevindarrah.com/?cat=99 của Kevin Darrah.
#include <SPI.h> // Dùng thư viện SPI #define latch_pin 4 // Chân LATCH #define blank_pin 5 // Chân OE #define data_pin 51 // Chân DATA (MOSI, pin 51 on MEGA2560) #define clock_pin 52 // Chân CLOCK (SCK, pin 52 on MEGA2560) /* *Khai báo các layer* */ #define layer1 26 // bottom layer #define layer2 27 #define layer3 28 #define layer4 29 #define layer5 30 #define layer6 31 #define layer7 32 #define layer8 33 // top layer int layerArray[8] = { layer1, layer2, layer3, layer4, layer5, layer6, layer7, layer8 }; int lastAnode; //This is how the brightness for every LED is stored, //Each LED only needs a 'bit' to know if it should be ON or OFF, so 64 Bytes gives you 512 bits= 512 LEDs //Since we are modulating the LEDs, using 4 bit resolution, each color has 4 arrays containing 64 bits each byte red[4][64]; byte blue[4][64]; byte green[4][64]; //*********** Defining the Cube ************* #define BAM_RESOLUTION 4 // EG 4 bit colour = 15 variation of R, G & B (4,096 colours) const byte Size_X = 8; //Number of LEDs X axis const byte Size_Y = 8; //Number of Layers Y axis const byte Size_Z = 8; //Number of LEDs Z axis int level = 0; //keeps track of which level we are shifting data to int anodeLevel = 0; //this increments through the anode levels int BAM_Bit, BAM_Counter = 0; // Bit Angle Modulation variables to keep track of things //****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup void setup() { SPI.setBitOrder(MSBFIRST); //Most Significant Bit First SPI.setDataMode(SPI_MODE0); // Mode 0 Rising edge of data, keep clock low // SPI.setClockDivider(SPI_CLOCK_DIV2);//Run the data in at 16MHz/2 - 8MHz SPI.setClockDivider(SPI_CLOCK_DIV2); //Run the data in at 16MHz/2 - 8MHz //Serial.begin(115200);// if you need it? noInterrupts(); // kill interrupts until everybody is set up //We use Timer 1 to refresh the cube TCCR1A = B00000000; //Register A all 0's since we're not toggling any pins TCCR1B = B00001011; //bit 3 set to place in CTC mode, will call an interrupt on a counter match //bits 0 and 1 are set to divide the clock by 64, so 16MHz/64=250kHz TIMSK1 = B00000010; //bit 1 set to call the interrupt on an OCR1A match OCR1A = 30; // you can play with this, but I set it to 30, which means: //our clock runs at 250kHz, which is 1/250kHz = 4us //with OCR1A set to 30, this means the interrupt will be called every (30+1)x4us=124us, // which gives a multiplex frequency of about 8kHz //finally set up the Outputs // pinMode(latch_pin, OUTPUT);//Latch pinMode(2, OUTPUT); // turn off PWM and set PortD bit 4 as output pinMode(3, OUTPUT); // turn off PWM and set PortD bit 5 as output pinMode(data_pin, OUTPUT); //MOSI DATA pinMode(clock_pin, OUTPUT); //SPI Clock //pinMode(blank_pin, OUTPUT);//Output Enable important to do this last, so LEDs do not flash on boot up //*** Here layer pins are set as outputs pinMode(layer1, OUTPUT); pinMode(layer2, OUTPUT); pinMode(layer3, OUTPUT); pinMode(layer4, OUTPUT); pinMode(layer5, OUTPUT); pinMode(layer6, OUTPUT); pinMode(layer7, OUTPUT); pinMode(layer8, OUTPUT); SPI.begin(); //start up the SPI library interrupts(); //let the show begin, this lets the multiplexing start } //***end setup***end setup***end setup***end setup***end setup***end setup***end setup***end setup***end setup***end setup void loop() { //***start loop***start loop***start loop***start loop***start loop***start loop***start loop***start loop***start loop clearfast(); LED(4, 5, 6, 3, 6, 13); delay(5000); clearfast(); LED(0, 0, 0, 15, 0, 0); delay(5000); LED(7, 0, 0, 0, 15, 0); delay(5000); LED(7, 7, 0, 0, 0, 15); delay(5000); LED(0, 7, 0, 15, 15, 0); delay(5000); LED(0, 7, 7, 15, 0, 15); delay(5000); LED(7, 0, 7, 0, 15, 15); delay(5000); LED(0, 0, 7, 15, 15, 15); delay(5000); LED(7, 7, 7, 15, 9, 0); delay(5000); } void LED(int Z, int Y, int X, int R, int G, int B) { //****LED Routine****LED Routine****LED Routine****LED Routine // First, check and make sure nothing went beyond the limits, Limits are either 0 or Size of the Y,X,Z axis -1 for locations, and 0 or Bam_Resolution - 1 for brightness/colour X = constrain(X, 0, Size_X - 1); //Cube X axis Y = constrain(Y, 0, Size_Y - 1); //Cube Y axis Z = constrain(Z, 0, Size_Z - 1); //Cube Z axis R = constrain(R, 0, (1 << BAM_RESOLUTION) - 1); //Cube Red G = constrain(G, 0, (1 << BAM_RESOLUTION) - 1); //Cube Green B = constrain(B, 0, (1 << BAM_RESOLUTION) - 1); //Cube Blue //There are Y * X * Z LEDs (Size_Cube) in the cube, so when if we write to level (Y) 2, panel (X) 5, row 4 (Z), that needs to be translated into a number from 0 to Size_Cube then devided by the number of bits per byte // Since we use an integer we are left with the byte number, the remainder is dropped off. EG (2*64 + 5*8 + 4)/8 = 21.5, the int of this is WhichByte number 21.(Assuming a cube 8*8*8 LEDs) int WhichByte = int(((Z << 6) + (Y << 3) + X) >> 3); // This next variable is the same thing as before except we don't devide by 8, so we get the LED number 0 to Size_Cube. //Then we find which bit of the byte is required. EG (From Above) (2*64 + 5*8 + 4) = LED# 172 - (8 * WhichByte) = 4 int WhichBit = ((Z << 6) + (Y << 3) + X) - (WhichByte << 3); // So WhichByte 21 WhichBit 4 is the location we will write too for LED 172. //Now we write to our Cube array if Red, Green and Blue are on or off, using Bit angular modulation. for (byte I = 0; I < BAM_RESOLUTION; I++) { //*** RED *** bitWrite(red[I][WhichByte], WhichBit, bitRead(R, I)); //*** GREEN *** bitWrite(green[I][WhichByte], WhichBit, bitRead(G, I)); //*** BLUE *** bitWrite(blue[I][WhichByte], WhichBit, bitRead(B, I)); } } //****LED ROUTINE END**** ISR(TIMER1_COMPA_vect) { //***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM //This routine is called in the background automatically at frequency set by OCR1A //In this code, I set OCR1A to 30, so this is called every 124us, giving each level in the cube 124us of ON time //There are 8 levels, so we have a maximum brightness of 1/8, since the level must turn off before the next level is turned on //The frequency of the multiplexing is then 124us*8=992us, or 1/992us= about 1kHz PORTE |= 1 << blank_pin; //The first thing we do is turn all of the LEDs OFF, by writing a 1 to the blank pin //Note, in my bread-boarded version, I was able to move this way down in the cube, meaning that the OFF time was minimized //due to signal integrity and parasitic capcitance, my rise/fall times, required all of the LEDs to first turn off, before updating //otherwise you get a ghosting effect on the previous level //This is 4 bit 'Bit angle Modulation' or BAM, There are 8 levels, so when a '1' is written to the color brightness, //each level will have a chance to light up for 1 cycle, the BAM bit keeps track of which bit we are modulating out of the 4 bits //Bam counter is the cycle count, meaning as we light up each level, we increment the BAM_Counter if (BAM_Counter == 8) BAM_Bit++; else if (BAM_Counter == 24) BAM_Bit++; else if (BAM_Counter == 56) BAM_Bit++; BAM_Counter++; //Here is where we increment the BAM counter switch (BAM_Bit) { //The BAM bit will be a value from 0-3, and only shift out the arrays corresponding to that bit, 0-3 //Here's how this works, each case is the bit in the Bit angle modulation from 0-4, //Next, it depends on which level we're on, so the byte in the array to be written depends on which level, but since each level contains 64 LED, //we only shift out 8 bytes for each color case 0: //Red mySPI(red[0][level]); mySPI(red[0][level + 1]); mySPI(red[0][level + 2]); mySPI(red[0][level + 3]); mySPI(red[0][level + 4]); mySPI(red[0][level + 5]); mySPI(red[0][level + 6]); mySPI(red[0][level + 7]); //Green mySPI(green[0][level]); mySPI(green[0][level + 1]); mySPI(green[0][level + 2]); mySPI(green[0][level + 3]); mySPI(green[0][level + 4]); mySPI(green[0][level + 5]); mySPI(green[0][level + 6]); mySPI(green[0][level + 7]); //Blue mySPI(blue[0][level]); mySPI(blue[0][level + 1]); mySPI(blue[0][level + 2]); mySPI(blue[0][level + 3]); mySPI(blue[0][level + 4]); mySPI(blue[0][level + 5]); mySPI(blue[0][level + 6]); mySPI(blue[0][level + 7]); break; case 1: //Red mySPI(red[1][level]); mySPI(red[1][level + 1]); mySPI(red[1][level + 2]); mySPI(red[1][level + 3]); mySPI(red[1][level + 4]); mySPI(red[1][level + 5]); mySPI(red[1][level + 6]); mySPI(red[1][level + 7]); //Green mySPI(green[1][level]); mySPI(green[1][level + 1]); mySPI(green[1][level + 2]); mySPI(green[1][level + 3]); mySPI(green[1][level + 4]); mySPI(green[1][level + 5]); mySPI(green[1][level + 6]); mySPI(green[1][level + 7]); //Blue mySPI(blue[1][level]); mySPI(blue[1][level + 1]); mySPI(blue[1][level + 2]); mySPI(blue[1][level + 3]); mySPI(blue[1][level + 4]); mySPI(blue[1][level + 5]); mySPI(blue[1][level + 6]); mySPI(blue[1][level + 7]); break; case 2: //Red mySPI(red[2][level]); mySPI(red[2][level + 1]); mySPI(red[2][level + 2]); mySPI(red[2][level + 3]); mySPI(red[2][level + 4]); mySPI(red[2][level + 5]); mySPI(red[2][level + 6]); mySPI(red[2][level + 7]); //Green mySPI(green[2][level]); mySPI(green[2][level + 1]); mySPI(green[2][level + 2]); mySPI(green[2][level + 3]); mySPI(green[2][level + 4]); mySPI(green[2][level + 5]); mySPI(green[2][level + 6]); mySPI(green[2][level + 7]); //Blue mySPI(blue[2][level]); mySPI(blue[2][level + 1]); mySPI(blue[2][level + 2]); mySPI(blue[2][level + 3]); mySPI(blue[2][level + 4]); mySPI(blue[2][level + 5]); mySPI(blue[2][level + 6]); mySPI(blue[2][level + 7]); break; case 3: //Red mySPI(red[3][level]); mySPI(red[3][level + 1]); mySPI(red[3][level + 2]); mySPI(red[3][level + 3]); mySPI(red[3][level + 4]); mySPI(red[3][level + 5]); mySPI(red[3][level + 6]); mySPI(red[3][level + 7]); //Green mySPI(green[3][level]); mySPI(green[3][level + 1]); mySPI(green[3][level + 2]); mySPI(green[3][level + 3]); mySPI(green[3][level + 4]); mySPI(green[3][level + 5]); mySPI(green[3][level + 6]); mySPI(green[3][level + 7]); //Blue mySPI(blue[3][level]); mySPI(blue[3][level + 1]); mySPI(blue[3][level + 2]); mySPI(blue[3][level + 3]); mySPI(blue[3][level + 4]); mySPI(blue[3][level + 5]); mySPI(blue[3][level + 6]); mySPI(blue[3][level + 7]); //Here is where the BAM_Counter is reset back to 0, it's only 4 bit, but since each cycle takes 8 counts, //, it goes 0 8 16 32, and when BAM_counter hits 64 we reset the BAM if (BAM_Counter == 120) { BAM_Counter = 0; BAM_Bit = 0; } break; } //switch_case // ** This routine selects layer without shift register. lastAnode = (anodeLevel - 1); if (anodeLevel == 0) { lastAnode = 7; } // if we are at the bottom, the last layer was the top digitalWrite(layerArray[lastAnode], LOW); // turn off the previous layer digitalWrite(layerArray[anodeLevel], HIGH); // turn on the current layer PORTE |= 1 << latch_pin; //Latch pin HIGH PORTE &= ~(1 << latch_pin); //Latch pin LOW delayMicroseconds(3); //???; PORTE &= ~(1 << blank_pin); //Blank pin LOW to turn on the LEDs with the new data //delayMicroseconds(3); //???; // Blank is the same as the OE or ENABLE pin anodeLevel++; //inrement the anode level level = level + 8; //increment the level variable by 8, which is used to shift out data, since the next level woudl be the next 8 bytes in the arrays if (anodeLevel == 8) //go back to 0 if max is reached anodeLevel = 0; if (level == 64) //if you hit 64 on level, this means you just sent out all 63 bytes, so go back level = 0; pinMode(blank_pin, OUTPUT); //moved down here so outputs are all off until the first call of this function } void clearfast() { for (unsigned char j = 0; j < 64; j++) { red[0][j] = 0; red[1][j] = 0; red[2][j] = 0; red[3][j] = 0; green[0][j] = 0; green[1][j] = 0; green[2][j] = 0; green[3][j] = 0; blue[0][j] = 0; blue[1][j] = 0; blue[2][j] = 0; blue[3][j] = 0; } } inline static uint8_t mySPI(uint8_t mydata) { SPDR = mydata; asm volatile("nop"); asm volatile("nop"); }
IV. GIẢI THÍCH CHƯƠNG TRÌNH
1. INPUT & OUTPUT ARDUINO MEGA 2560
Sơ đồ đấu nối như hình vẽ và RGB LED được dùng là loại Common Anode, điện trở hạn dòng được dùng là R100.
2. SƠ LƯỢC VỀ CHƯƠNG TRÌNH
Vậy để điều khiển một đèn, ta cần có 2 thông số: tọa độ và màu, ex: LED (z, y, x, red, green, blue).
2.1. XÁC ĐỊNH TỌA ĐỘ (z, y, x)
WhichByte = int (((z << 6) + (y << 3) + x) >> 3) --> Xác định được byte cần set --> 74HC595 cần set.
WhichBit = ((z << 6) + (y << 3) + x) - (WhichByte << 3) --> Xác định được bit cần set --> Chân cần set của 74HC595 tương ứng ở trên.
Lấy ví dụ ta cần set led lại tọa độ (z=4, y=5, x=6). Như tính toán trong chương trình, vị trí cần set là byte thứ 37, bit thứ 6 của tọa độ không gian LED CUBE.
WhichByte = int ((4*2^6 + 5*2^3 + 6)/2^3) = 37
WhichBit = (4*2^6 + 5*2^3 + 6) - (37 * 2^3) = 6
Hình trên biểu thị 512 RGB LED với mối liên hệ theo tọa độ không gian, byte và bit. Phần tô xanh, chữ đỏ là vị trí cần set cho RGB LED:
Layer (Z) = 4, Y = 5, X =6 --> LED thứ 302 --> Byte thứ 37, bit thứ 6.
2.2. XÁC ĐỊNH MÀU (red, green, blue)
Lấy ví dụ ta cần set led lại tọa độ (z=4, y=5, x=6) màu (red=3, green=6, blue=13), khi đó chương sẽ biên dịch như sau:
Như phân tích ở trên, ta thấy tại vị trí byte 37, bit thứ 6 (tại mỗi vị trí có 3 led bên trong đèn RGB), các đèn sẽ được phối màu như hình sau:
CÁC LƯU Ý
- Trục tọa độ X trên hình vẽ biểu thị thời gian LED sáng/ tắt với Time_Base = 125us. Khoảng thời gian của Bam_Bit[0] = 8 x Time_Base = 1,000us (1kHz), của Bam_Bit[1]=16 x Time_Base = 2,000us, của Bam_Bit[2]=32 x Time_Base = 4,000us, của Bam_Bit[3]=64 x Time_Base = 8,000us.
- Lưu ý các con số BAM_Counter = 8, 24, 56 và 120 và BAM_Bit++. Cứ mỗi lần TIMER1 interupt (sau mỗi 125us), thì BAM_Counter sẽ tăng giá trị lên 1, đến giá trị 120 thì reset về 0. Mối liên hệ giữa BAM_Counter và BAM_Bit như sau:
- BAM_Bit = 0, khi BAM_Counter nằm trong dãy: 0 ~ 7.
- BAM_Bit = 1, khi BAM_Counter nằm trong dãy: 8 ~ 23.
- BAM_Bit = 2, khi BAM_Counter nằm trong dãy: 24 ~ 55.
- BAM_Bit = 3, khi BAM_Counter nằm trong dãy: 56 ~ 119.
- Ở đây, mình không dùng hàm có sẵn trong thư viện SPI: SPI.transfer(mydata) mà tạo ra hàm mySPI(mydata). Lý do là để cải thiện tốc độ hiển thị LED. Sau này, mình dùng một số hiệu ứng phức tạp, nếu dùng hàm trong thư viện sẽ bị flicker (nhấp nháy) và rất chậm, nhìn không mượt mà.
2.3. HÌNH ẢNH THỰC TẾ LED (4, 5, 6, 3, 6, 13)
2.4. MỘT SỐ VIDEO VÀ HÌNH ẢNH CỦA DỰ ÁN
Các hiệu ứng trong Video bên dưới đa số mình dùng ColorWheel. Nếu có thời gian, mình rất vui khi cùng chia sẻ với các bạn ở các phần tiếp theo.
THANKS..