Giới thiệu

Chào các bạn, hôm này mình xin phép được chia sẽ những gì mình đã học được khi làm Cube LED 8x8x8. Mình cũng tìm hiểu nhiều tài liệu, đọc đi đọc lại và có rất nhiều điểm chưa hiểu. Lý do mình viết bài này là để chia sẽ kiến thức, với hy vọng là mình sẽ tìm được những câu trả lời cho những điểm mình chưa hiểu từ chính bạn đọc. Mong các bạn có thể cùng mình viết lên các hiệu ứng cho nó đẹp hơn. Do quá trình làm Cube LED có khá nhiều công việc nên mình phải chia nhỏ bài viết thành nhiều phần. Với phần đầu tiên, mình xin chia sẽ về ý tưởng thiết kế và phần cứng. Còn phần hai, mình sẽ đăng về cách lập trình các hiệu ứng ngay trong thời gian sớm nhất có thể. Nếu như sau này khi nhận được những đóng góp ý kiến từ các bạn để cải tiến lên thì mình sẽ update tiếp nhé!
Tài liệu mà mình cho là OK nhất là đây nhé: http://www.instructables.com/id/Led-...
I. Phầm cứng
1. Ý tưởng
Về bộ vi điều khiển thì mình sử dụng là Arduino Promini 16Mhz. Với Pro mini thì các bạn phải tìm hiểu thêm các bài viết về cách nạp phần mềm cho Promini. Mình chọn Promini vì mình muốn sau khi làm xong có thể để promini trên cube LED luôn. Trong lập trình Cube LED mình đã gặp vấn đề với bộ nhớ của vi điều khiển. Với Promini ta có bộ nhớ flash là 32Kb cái này không dùng hết nổi đâu. Nhưng Sram chỉ có 2Kb thôi, cái này nếu lập trình không khéo thì sẽ không đủ nè. Còn EEPROM là 1Kb, mình không muốn dùng cái EEPROM này vì khá tốn thời gian khi đọc dữ liệu từ nó.
Với khối Cube 8x8x8 chúng ta có tổng cộng là 512 LED. Vậy làm sao có thể điều khiển hết từng đèn? Mình sẽ dùng coi mỗi đèn LED là một điểm trong không gian Oxyz. Để gọi một điểm ra chúng ta cần 3 tham số x, y, z thể hiện toạ độ của một điểm. Với x, y, z chạy từ 0 đến 7.
Mình sẽ không hướng dẫn các hàn khối cube nữa. Vì mỗi người sẽ có một cách khác nhau.
Nhưng, để có thể đồng bộ với Code thì mình lưu ý là ở bài viết này, các tầng z là chân âm của LED, còn các cột là chân dương nhé! Như vậy mình sẽ có 8 tầng là 8 chân nối với cực âm của LED. là 64 chân nối với cực dương của LED. Mình đã dùng 9 dây bus 8 để nối các chân này với mạch điều khiển. Dây bus đầu tiên mình hàn cho 8 tầng của cube. 8 dây còn lại mỗi dây mình hàn cho 1 hàng x. Bus 8 có 8 dây nhỏ xếp thành hàng ngang, mỗi dây nhỏ tương ứng với 1 cột y. Với bus thứ 1 có x=0, y chạy từ 0 đến 7; bus thứ 2 có x=1. Y chạy từ 0 đến 7; …… Và mình có cái trình tự quét mảng này luôn (nó rất là quan trọng và sẽ được lặp lại rất nhiều lần trong code)
for (byte zo=0; zo<8; zo++) {
      for (byte xo=0; xo<8; xo++) {  
          for (byte yo=0; yo<8; yo++) {  
             mangba[xo][yo][zo]=0;  
         }
     }
}
Vậy chúng ta đã đơn giản 512 LED thành 8 tầng và 64 cột rồi. Nhưng như thế vẫn còn quá nhiều chân điều khiển so với Arduino Promini. Mình đã sử dụng 74hc595 để mở rộng chân điều khiển. Lại tiếp tục chiêu cũ, các bạn tự xem cách dùng của 74hc595 nhé! Ở đây mình dùng 8 con 74hc595 nối tiếp nhau, mỗi con điều khiển được 8 cột, 8 con là 64 cột rồi. Còn 8 tầng z mình sẽ dùng luôn 8 chân trên arduino điều khiển 8 con TIP41.
Lúc đầu mình có đề cập đến sram của arduino là 2Kb, khá nhỏ, nên mình có vẽ thêm chỗ cho một module đọc thẻ nhớ SD nữa. Nhưng hiện tại chưa biết dùng làm gì!

2. Phần cứng

Ở đây mình sẽ làm mạch in cho mạch điều khiển cả cube.
  • Arduino Promini (tự trang bị mạch nạp nhé phù hợp nhé)
  • 8 con 74hc595
  • 8 con TIP41
  • Điện trở 100 Ohm
  • Mấy cây rào để kết nối với dây bus nữa.
  • Dây nối.
Sơ đồ nguyên lý thì chỉ là nối tiếp các 74hc595 lại với nhau. Và 8 con TIP41 để nối 8 tầng LED với GND thôi. Khi mở TIP41 thì Chân âm của LED sẽ nối với GND, nếu chân dương ở mức cao thì LED sẽ sáng. Mình sẽ nói rõ hơn ở bài sau! Đại loại là thế này!
Promini nó sẽ nằm ngược chiều với 74hc595 nhé. Thể nhớ SD mình đã vẽ theo đúng thứ tự kết nối, mình sẽ có ghi chú cách nối dây trong phần code luôn. Còn nguồn cung cấp mình dùng nguồn 5v. Các bạn có thể mua cái adapter 5v-2A cho chắc nhé! Cái mạch nó thế này nè!

Hình 3D

File mạch in nè



II. Lập trình

1. Khai báo và các phần mềm hỗ trợ

1.1 Khai báo

Ở phần 1 mình đã đề cập đến việc dùng thêm thẻ nhớ SD để nhớ dữ liệu. Nên mình đã có khai báo thẻ nhớ SD luôn, cũng như đã vẽ các chân kết nối trên mạch in luôn rồi. Nếu như mọi người dùng đến thẻ nhớ SD thì có thể kết nối theo chuẩn SPI có ghi ở bên dưới (nhìn theo cái file mạch in nhé!).
Chân A1, A2, A3 lần lượt là các chân nối với 74hc595.
Mảng Tangz[8] là chân ra của các tầng z. Chúng ta sẽ quét các tầng z để mở các đèn.
  1. //byte DS=A1;
  2. //byte STCP=A2;
  3. //byte SHCP=A3;
  4. int Tangz[8] = {2, 3, 4, 5, 6, 7, 8, 9};
  5. /*
  6. SD card read/write
  7. This example shows how to read and write data to and from an SD card file
  8. The circuit:
  9. * SD card attached to SPI bus as follows:
  10. ** MOSI - pin 11
  11. ** MISO - pin 12
  12. ** CLK - pin 13
  13. ** CS - pin 10
  14. created Nov 2010
  15. by David A. Mellis
  16. modified 9 Apr 2012
  17. by Tom Igoe
  18. This example code is in the public domain.
  19. */
  20. #include <SPI.h>
  21. #include <SD.h>
  22. File myFile;

1.2 Setup

Khai báo thẻ nhớ SD nó sẽ có 2 dòng lệnh để xuất ra Serial cho ta biết là thẻ nhớ có hoạt động hay không. Để tiết kiệm tối đa dung lượng sram mình đã không cho nó in ra luôn, nhưng nếu mọi người muốn xem thẻ nhớ có hoạt động không thì có thể bỏ dấu //
Nhưng nếu các bạn vẫn thích in ra mà lại không muốn tốn RAM thì xem bài viết này của bạn ksp nhé.
  1. void setup() {
  2. ​Serial.begin(9600)
  3. ​pinMode(A1, OUTPUT)
  4. ​pinMode(A2, OUTPUT)
  5. ​pinMode(A3, OUTPUT)
  6. ​for (int x = 0; x < 8; x++)
  7. ​pinMode(Tangz[x], OUTPUT)
  8. ​while (!Serial)
  9. ​if (!SD.begin(10))
  10. ​//Serial.println("initialization failed!")
  11. ​return
  12. ​//Serial.println("initialization done.")
  13. }

2. Các phầm mềm hỗ trợ

dich74hc595 sẽ nhận vào một biến a (với a có giá trị là 0 hoặc 1; quy ước luôn là 1 là đèn sáng, 0 là đèn tắt) và sẽ trả ra (shiftout) một tín hiệu đến 74hc595. Khi A1 LOW hoặc HIGH, thể hiện trạng thái tắt hoặc mở của đèn. Còn A3 là một xung cho chân SHCP, nó phân biệt giữa 2 đèn đứng liền nhau. Mọi người có thể xem lại phần 74hc595 nhé!
  1. void dich74hc595(byte a){
  2. ​if (a==0)
  3. ​digitalWrite(A1, LOW)
  4. ​digitalWrite(A3, HIGH)
  5. ​digitalWrite(A3, LOW)
  6. } else if(a==1)
  7. ​digitalWrite(A1, HIGH)
  8. ​digitalWrite(A3, HIGH)
  9. ​digitalWrite(A3, LOW)
  10. }
mo74hc595 chỉ đơn giản là tạo ra một xung vào chân STCP thôi. Khi có xung ở đây 74hc595 sẽ xuất các tín hiệu trước đó đã nhận được từ dich74hc595 ra ngoài.
  1. void mo74hc595() {
  2. ​digitalWrite(A2, HIGH)
  3. ​digitalWrite(A2, LOW)
  4. }
reset sẽ đẩy 64 con 0 vào 74hc595. Làm như thế để tránh trường hợp khi chuyển từ tầng này sang tầng khác của Cube LED thì các đèn nó không bị sáng lung tung lên.
  1. void reset() {
  2. ​for (byte t=0; t<64; t++)
  3. ​dich74hc595(0)
  4. ​mo74hc595()
  5. }
hienthi là một hàm cho giúp mở hoặc tắt các đèn theo một dữ liệu cho trước. Do sram ít nên mình đã cho mangba trở thành biến cục bộ của các hiệu ứng, vì thế nên mình phải khai báo mangba là một mảng được nhập vào hàm hiển thị (tự động nó sẽ nhập vào thôi). Làm như thế mình sẽ dùng được hàm hienthi trên mọi hàm hiệu ứng. Còn biến Solan sẽ là số lần lặp lại của một hình ảnh nào đó, khoảng 60 lần thì được 1 giây.
Trong một lần hiển thị một hình ảnh, mình sẽ quét lần lượt 8 tầng z, ở mỗi tầng x mình sẽ quét x, quét y để tìm ra toạ độ của đèn nào sáng hay tắt. Sau đó mình sẽ cho mở tầng đó lên rồi tắt nó đi để chuẩn bị cho tầng tiếp theo.
  1. void hienthi(byte mangba[8][8][8], byte Solan){
  2.       for (byte t=0; t<Solan; t++) { // so 7 la thoi gian qua 1 tang
  3.         //to chinh la toc do roi cua mua
  4.         for (byte z=0; z<8; z++) {
  5.           for (byte x=0; x<8; x++) {
  6.             for (byte y=0; y<8; y++) {
  7.               dich74hc595(mangba[x][y][z]);
  8.             }
  9.           }
  10.           mo74hc595();
  11.           digitalWrite(Tangz[z], HIGH);   
  12.           reset();
  13.           digitalWrite(Tangz[z], LOW);
  14.         }
  15.       }
  16. }
 Như vậy là đã tạm đủ rồi, trong một số hiệu ứng chúng ta sẽ cần thêm vài chương trình con nữa để hỗ trợ, nhưng tới đó rồi tính sau nhé!

3. Hiệu ứng mưa rơi

3.1 Ý tưởng

Hiệu ứng này cần 2 thông số đầu vào đó là số lần mưa (Solanmua) và số lần hiển thị một ảnh (Solan).
Trong một lần mưa, đầu tiên ta phải tìm ra số hạt mưa rơi trong cùng một lúc (tìm ngẫu nhiên). Trong code mình đã cài cho random từ 1 đến 4. Mình cài là 1 vì không muốn có lúc lại ko có hạt nào rơi. Còn 4 vì không muốn rơi quá nhiều. Sau đó mình sẽ chọn ngẫu nhiên ra x, y để tìm toạ độ tại tầng z=7 (mưa rơi từ trên xuống) để tìm vị trí bắt đầu rơi của hạt mưa. Sau đó là hiển thị LED lên. Kế tiếp ta phải dời 7 tầng còn lại xuống 1 tầng. Sau đó trả các giá trị của tầng z=7 trở về 0 để chuẩn bị cho việc chọn ngẫu nhiên ở vòng lặp tiếp theo. 

3.2 Sơ đồ thuật toán

3.3 Code

  1. void mua(int solanmua, byte Solan)
  2. {
  3. ​byte mangba[8][8][8] = { 0 }
  4. ​for (byte s = 0; s < solanmua; s++
  5. ​byte n = random(1, 5); // chọn ngẫu nhiên số hạt mưa rơ
  6. ​for (byte h = 0; h < n; h++
  7. ​byte x = random(0, 8); // chọn ngẫu nhiên một toạ độ hạt mư
  8. ​byte y = random(0, 8)
  9. ​mangba[x][y][7] = 1; // Cho cái hạt mưa đó =1 để cái đèn đó sáng lê
  10. ​hienthi(mangba, Solan); // hiển thi
  11. ​for (byte z = 0; z < 7; z++
  12. { // dời các tầng z xuống một tần
  13. ​for (byte x = 0; x < 8; x++
  14. { // nhưng phải giữ nguyên toạ độ theo x, y để mưa rơi thẳn
  15. ​for (byte y = 0; y < 8; y++
  16. ​mangba[x][y][z] = mangba[x][y][z + 1]
  17. ​for (byte x = 0; x < 8; x++
  18. ​for (byte y = 0; y < 8; y++
  19. ​mangba[x][y][7] = 0; // trả tầng z=7 về
  20. }

4. Hiệu ứng plane

4.1 Ý tưởng

Hiệu ứng này sẽ tạo ra một mảng 2 chiều gồm 64 LED. Và mảng sáng này sẽ được dời đi theo trục tuỳ thích. Chắc hẳn mọi người sẽ rất muốn cube LED của mình có thể xuất ra chữ, chúng ta có thể thay đổi mảng sáng của hiệu ứng này thành một chữ cái, một ký tự hay hình ảnh này đó và cho nó dời đi để tạo thêm một hiệu ứng mới. Hoặc cho nó dời đi và trên đường đi sẽ bỏ lại vài đèn để tạo nên một sự ngẫu nhiên. Hiệu ứng này sẽ tạo ra cảm giác sâu rộng cho khối cube. Dời đi rồi thì phải trả trở về luôn nha.
Hiệu ứng plane cần 2 biến là trục (truc) và Solan (giống hiệu ứng trên). Trục là một ký tự (X, Y hoặc Z) để thể hiện trục chuyển động của mảng, với X nó sẽ chạy theo trục X.
Lần này tôi sẽ không sử dụng hàm resetmangba như trên nữa, vì tôi sẽ reset luôn trong đoạn code tính toán sáng hay tắt luôn. Đầu tiên ta sẽ dời theo trục một trục theo 8 lần. Với mỗi lần ta sẽ quét hết khối cube, với lần dời thứ nhất i=0 ta thay toạ độ của truc x bằng i (nếu dời theo trục x) nhé. Tương tự thế cho trục Y ,Z. Sau đó là hiển thị ra.
Sau khi dời đi, ta sẽ bắt đầu quá trình quay ngược lại. Cũng giống với chuyến đi, nhưng chuyến về chỉ có 7 lần thôi, vì cuối chuyến đi thì nó đã ở tầng (cột hoặc hàng) thứ 8 rồi. Nó hoàn toàn giống với chuyến đi, chỉ khác ở chỗ i lần này sẽ chạy ngược từ 6 đến 0 nên toạ độ cũng được ngược lại luôn, toạ độ một điểm sẽ trở thành mangba[7-x][7-y][i].

4.2 Sơ đồ thuật toán

4.3 Code

  1. void plane(char truc, byte Solan)
  2. {
  3. ​byte mangba[8][8][8] = { 0 }
  4. ​for (byte i = 0; i < 8; i++
  5. ​for (byte z = 0; z < 8; z++
  6. ​for (byte x = 0; x < 8; x++
  7. ​for (byte y = 0; y < 8; y++
  8. ​mangba[x][y][z] = 0
  9. ​if (truc == 'X'
  10. ​mangba[i][y][z] = 1
  11. ​if (truc == 'Y'
  12. ​mangba[x][i][z] = 1
  13. ​if (truc == 'Z'
  14. ​mangba[x][y][i] = 1
  15. ​hienthi(mangba, Solan)
  16. ​for (int i = 7; i >= 0; i--
  17. ​for (byte z = 0; z < 8; z++
  18. ​for (byte x = 0; x < 8; x++
  19. ​for (byte y = 0; y < 8; y++
  20. ​mangba[7 - x][7 - y][7 - z] = 0
  21. ​if (truc == 'X'
  22. ​mangba[i][7 - y][7 - z] = 1
  23. ​if (truc == 'Y'
  24. ​mangba[7 - x][i][7 - z] = 1
  25. ​if (truc == 'Z'
  26. ​mangba[7 - x][7 - y][i] = 1
  27. ​hienthi(mangba, Solan)
  28. }

5. Hiệu ứng Plane2

5.1 Ý tưởng

Hiệu ứng này là nâng cấp từ hiệu ứng Plane. Nó sẽ tạo ra một mảng sáng và dời mảng này đi theo trục mà ta chọn, trên đường đi ta sẽ chọn ngẫu nhiên 8 đèn nằm trên mỗi tầng để bỏ lại. Khi đi hết thì ta sẽ thu các đèn bị bỏ lại đó trở về để tạo thành 1 mảng ở mua bên kia của trục (cách làm giống hiệu ứng mưa rơi). Do hiệu ứng này giống với 2 hiệu ứng trên nên mình sẽ bỏ qua phần sơ đồ thuật toán!

5.2 Code

  1. void plane2(char truc, byte Solan)
  2. {
  3. ​byte mangba[8][8][8] = { 0 }
  4. ​byte manghai[8][8] = { 0 }; //Đây là mảng dùng để đánh dấu là đèn nào đã bị bỏ lạ
  5. ​for (byte x = 0; x < 8; x++
  6. { // Tạo ra một mảng sáng trước khi dời đ
  7. ​for (byte y = 0; y < 8; y++
  8. ​if (truc == 'X'
  9. ​mangba[0][x][y] = 1
  10. ​else if (truc == 'Y'
  11. ​mangba[x][0][y] = 1
  12. ​els
  13. ​mangba[x][y][0] = 1
  14. ​hienthi(mangba, Solan * 2)
  15. ​for (byte i = 1; i < 8; i++
  16. { // Do đã tạo 1 mảng sáng trước rồi nên chỉ cần dời mảng này đi trong 7 lầ
  17. ​byte u = 0
  18. ​while (u < 8
  19. { // tại mỗi lần rời đi, ta bỏ lại 8 điểm ngẫu nhiê
  20. ​byte x = random(8)
  21. ​byte y = random(8)
  22. ​if (manghai[x][y] == 0
  23. ​manghai[x][y] = 1
  24. ​u = u + 1
  25. ​hienthi(mangba, 1); // do sư ngẫu nhiên có thể bị trùng, nên ta thêm lệnh này đê
  26. ​// hình ảnh được liên tụ
  27. ​for (byte x = 0; x < 8; x++
  28. { // Bắt đầu việc dời trụ
  29. ​for (byte y = 0; y < 8; y++
  30. ​if (manghai[x][y] == 0
  31. ​if (truc == 'X'
  32. ​mangba[i][x][y] = 1
  33. ​mangba[i - 1][x][y] = 0
  34. ​else if (truc == 'Y'
  35. ​mangba[x][i][y] = 1
  36. ​mangba[x][i - 1][y] = 0
  37. ​else if (truc == 'Z'
  38. ​mangba[x][y][i] = 1
  39. ​mangba[x][y][i - 1] = 0
  40. ​hienthi(mangba, Solan)
  41. ​// ngược lạ
  42. ​for (byte z = 0; z < 8; z++
  43. { // Z là số lần dờ
  44. ​for (byte i = 1; i < 8 - z; i++
  45. { // i là số công việc phải làm trong 1 lần dờ
  46. ​for (byte x = 0; x < 8; x++
  47. ​for (byte y = 0; y < 8; y++
  48. ​if (truc == 'X'
  49. ​if (mangba[7 - i][x][y] == 1
  50. ​mangba[7 - i + 1][x][y] = 1
  51. ​mangba[7 - i][x][y] = 0
  52. ​else if (truc == 'Y'
  53. ​if (mangba[x][7 - i][y] == 1
  54. ​mangba[x][7 - i + 1][y] = 1
  55. ​mangba[x][7 - i][y] = 0
  56. ​els
  57. ​if (mangba[x][y][7 - i] == 1
  58. ​mangba[x][y][7 - i + 1] = 1
  59. ​mangba[x][y][7 - i] = 0
  60. ​hienthi(mangba, Solan + 4); // lấy Solan=4 rồi cộng thêm 4 nữa vì lúc chọn 8 vị trí ngẫ
  61. ​// nhiên mình đã cho hiển thi hết 8 lầ
  62. }

6. Kết quả

Còn chương trình chính chỉ việc gọi hiệu ứng ra mà thôi!
  1. void loop() {
  2. ​mua(100, 7)
  3. ​plane('X', 4)
  4. ​plane('Y', 4)
  5. ​plane('Z', 4)
  6. ​plane2('X', 4)
  7. ​plane2('Y', 4)
  8. ​plane2('Z', 4)
  9. }
Có cái link để coi cái hiệu ứng nó ra thế lào chứ nhỉ!