Điều khiển ma trận LED 24x8 - Tìm hiểu kĩ thuật quét LED (Phần 1)

I. Giới thiệu

Hôm nay mình sẽ hướng dẫn các bạn làm ma trận LED kích thước 24x8 để các bạn có thể treo chơi Tết (năm sau heart).

Ờ quên, sắp tới valentine heart rồi nên các nếu cố gắng các bạn có thể làm kịp để tỏ tình đấy

II. Chuẩn bị tâm lí

WARNING: điều khiển ma trận LED là chuyện không hề dễ, đòi hỏi thuật toán khá phức tạp.

PHẦN 1 này mình chỉ viết mã để nó có thể chạy chữ được thôi, nhưng cũng khá phức tạp rồi

PHẦN 2 mình sẽ phát triển thêm một số hiệu ứng, dựa trên những thuật toán về đồ thị, có lẽ sẽ HƠI HƠI khó hiểu đối với những bạn không chuyên về lập trình :D

Các bài nên đọc trước

Để có thể nắm bắt tốt hơn, mình đề nghị các bạn đọc trước những bài hướng dẫn sau đây trước khi bắt đầu:

  • Make a 24X6 LED matrix (Mình làm dựa trên bài hướng dẫn này, nhưng của anh Tây là 24x6, của mình là 24x8, với lại thuật toán mà anh í sử dụng lạ quá nên mình không hiểu, vì vậy mình đã tự viết lại thuật toán dễ hiểu hơn cho các bạn)
  • Điều khiển 8 đèn LED sáng theo ý muốn của bạn, dễ hay khó? (Để biết cách sử dụng IC HC595 và tìm hiểu shiftout)
  • Hiển thị hình ảnh với LED MATRIX 8x8 (Để làm quen với kĩ thuật quét LED, mình sẽ nói kĩ hơn, với lại các hiển thị chữ trên ma trận LED trong bài này không được bắt mắt lắm, xin lỗi tác giả nhé, mình sẽ hiển thị chữ mượt hơn)

III. Chuẩn bị vật liệu

  • 24 x 8 = 192 đèn LED 5li (màu nào cũng được, không nên chọn màu vàng vì sáng yếu, nhớ mua dư nhé).
  • Một bảng PCB khổ lớn đủ để gắn 192 đèn LED
  • Một bảng PCB khổ nhỏ hơn để gắn các thứ linh tinh khác (không cần nếu bạn gắn chung với bảng LED)
  • 24 điện trở 480 Ohm
  • 8 điện trở 1 kOhm
  • 8 transistor 2N3904 (do mình thiếu hàng nên dùng dao mổ trâu (TIP122) để mổ con thằn lằn này, devil)
  • 4 IC HC595 (3 cho cột và 1 cho hàng)
  • 1 Board Arduino (Uno cho nó dễ nhé).

Lưu ý:

Sản phẩm của mình vẫn còn nằm trên BreadBoard, do trước Tết để dành tiền oánh bài cool nên không có đặt hàng về, vì vậy phần này tùy vào sự sáng tạo của bạn, sao cho đẹp là được

IV. Tìm hiểu kĩ thuật quét

Như mình  đã nói, các bạn tham khảo bài này - LED matrix 8x8 điều khiển để biết sơ sơ về kĩ thuật quét LED. Bây giờ mình sẽ nói kĩ hơn

Ma trận gồm 8 dòng và 24 cột như đánh thứ tự trong hình

Để sáng được đèn LED cần nối 2 đầu LED với nguồn điện

Ví dụ: Nếu  muốn đèn (B2, Z6) sáng, bạn cần nối chân B2 với cực dương và chân Z6 cũng được nối với cực dương luôn (ủa kì vậy? phải nối tới cực âm chứ). Thực ra IC HC595 không được sản xuất để làm cấp nguồn cho LED, vì vậy nó chịu dòng rất kém, nếu nối trực tiếp vào nó thì trong trường hợp cả 24 LEDs trong một hàng cùng sáng thì dòng điện qua nó sẽ quá tải, do vậy ta cần một transistor để hứng dòng đó, ta kéo Z6 lên cực dương để kích cho transistor thông dòng để dòng điện qua các LEDs về cực âm mới sáng được)

Oops, lạc đề rồi. Đó là giải thích tại sao cần có transistor đứng trước mỗi dòng của ma trận, nhưng thôi để cho dễ hiễu cũng như dễ giải thích, thì muốn đèn (B2, Z6) sáng, cần nối chân B2 với cực dương, chân Z6 với cực âm.

Vấn đề hiển thị nhiều LED

Giả sử nếu bạn muốn đèn (B2, Z6) và đèn (C3, Z8) sáng thì làm sao, chúng ta cần nối chân B2 với C3 với cực dương, chân Z6 và Z8 với cực âm. Nếu vậy thì ngoài 2 đèn cần sáng đó ra, sẽ có thêm vị khách không mời là thằng (C3, Z6) nó cũng sáng theo. Vậy làm thế lào mà sáng theo ý muốn được chớ, nó sẽ hiển thị búa lua xua hết?

Đó là lí do tại sao kĩ thuật quét ra đời.

Kỹ thuật quét led

Kỹ thuật quét thay vì cấp nguồn như trên, nó sẽ cấp nguồn theo một thứ tự khác. Nó sẽ lần lượt nối các hàng Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8 với cực âm theo thứ tự từng cái một, như vậy chỉ những đèn trên hàng được nối nguồn âm mới có thể sáng được (nếu như cột của nó cũng được nối với nguồn dương)

Để hiểu rõ hơn, hãy nhìn vào bảng sau

Khi hàng số 1 được nối cực âm, thì bộ 24 giá trị trên hàng 1 trong bảng trên sẽ được cập nhật là trạng thái cho 24 cột của ma trận (đen là được nối cực dương, trắng thì không), tiếp theo là tới hàng 2 và bộ 24 cột của hàng 2, cứ tiếp tục tới hàng 8 rồi trở lại hàng 1. Quá trình chân 1 được nối cực âm, rồi tới chân 2, chân 3, tới chân 8 rồi trở về chân 1 được thực hiện rất nhanh, cập nhật trạng thái cho 24 cột cũng rất nhanh nên do hiện tượng lưu ảnh của mắt nên ta không thể thấy kịp từng hàng sáng lên mà chỉ thấy cả ma trận sáng theo thứ tự đúng y như trong bảng thôi.

Bạn tưởng mình nói xạo ư, nói có sách mách có chứng, mình đã thêm một hàm delay giữa các lần chuyển đổi hai chân để nối cực âm. Các bạn xem video sau rồi so sánh với video phía trên sẽ rõ.

Thế là xong kĩ thuật quét, dễ như ăn bánh nhể

V. Mắc mạch

Nối chân IC HC595

(Cái này do mình vẽ nên hơn xấu, nếu muốn xem cái đẹp hơn các bạn hãy vào trang instructables đã nói ở trên ^^).

Nối LED

Đây là hình ảnh thực tế của em nó

VI. Lập trình nào

Thiệt tình mà nói thì code của mình viết vừa đơn giản vừa phức tạp. Đơn giản với dân chuyên lập trình, đọc sẽ tự hiểu và phức tạp với dân điện tử ít tiếp xúc lập trình nhiều, mình cũng không biết phải chú thích gì nên bỏ không vậy, bạn nào có thắc mắc trong code cứ việc comment, mình rất sẵn lòng giúp.

//tốc độ chạy chữ, càng lớn chạy càng lâu
const byte SPEED = 4;

//kéo lên HIGH để cháy đèn LED, xài chung cho ROW và COL
const byte LATCH = 2;

const byte CLOCK_ROW = 3;
const byte DATA_ROW = 4;

const byte CLOCK_COL = 5;
const byte DATA_COL = 6;

//Dùng cho ROW, ứng với {00000001, 00000010, 00000100, 00001000, 00010000, 00100000, 01000000, 10000000};
const byte ROWS[8] = {1, 2, 4, 8, 16, 32, 64, 128};

//Ma trận gồm 8 dòng và 24 cột
//Mỗi byte trong 8 byte của một phần tử trong mảng sau ứng với 1 cột của ma trận. Vậy tại sao có 8 byte trong khi chỉ cần 5 cột là đủ biểu diễn một kí tự?
//Đó là vì dư ra cho tiện cho làm thêm các hình khác, trái tim chẳng hạn

//Mình chỉ làm kí tự hoa, số và vài kí tự đặt biệt
//Chữ thường làm hơi xấu nên sẽ tính sau

//Mình cũng sẽ đăng một phần mềm nhỏ nhỏ do mình viết dùng để tạo các kí tự
byte up[26][8] = {
  {B00111111, B01010000, B10010000, B01010000, B00111111, B00000000, B00000000, B00000000},//A
  {B11111111, B10010001, B10010001, B10010001, B01101110, B00000000, B00000000, B00000000},//B
  {B01111110, B10000001, B10000001, B10000001, B10000001, B00000000, B00000000, B00000000},//C
  {B11111111, B10000001, B10000001, B10000001, B01111110, B00000000, B00000000, B00000000},//D
  {B11111111, B10010001, B10010001, B10010001, B10010001, B00000000, B00000000, B00000000},//E
  {B11111111, B10010000, B10010000, B10010000, B10010000, B00000000, B00000000, B00000000},//F
  {B01111110, B10000001, B10000001, B10001001, B01001110, B00001000, B00000000, B00000000},//G
  {B11111111, B00010000, B00010000, B00010000, B11111111, B00000000, B00000000, B00000000},//H
  {B10000001, B10000001, B11111111, B10000001, B10000001, B00000000, B00000000, B00000000},//I
  {B10000011, B10000001, B11111111, B10000000, B10000000, B00000000, B00000000, B00000000},//J
  {B11111111, B00011000, B00100100, B01000010, B10000001, B00000000, B00000000, B00000000},//K
  {B11111111, B00000001, B00000001, B00000001, B00000001, B00000000, B00000000, B00000000},//L
  {B11111111, B01000000, B00100000, B01000000, B11111111, B00000000, B00000000, B00000000},//M
  {B11111111, B01000000, B00100000, B00010000, B11111111, B00000000, B00000000, B00000000},//N
  {B01111110, B10000001, B10000001, B10000001, B01111110, B00000000, B00000000, B00000000},//O
  {B11111111, B10010000, B10010000, B10010000, B01100000, B00000000, B00000000, B00000000},//P
  {B01111110, B10000001, B10000001, B10000101, B01111110, B00000001, B00000000, B00000000},//Q
  {B11111111, B10011000, B10010100, B10010010, B01100001, B00000000, B00000000, B00000000},//R
  {B01100001, B10010001, B10010001, B10010001, B01001110, B00000000, B00000000, B00000000},//S
  {B10000000, B10000000, B11111111, B10000000, B10000000, B00000000, B00000000, B00000000},//T
  {B11111110, B00000001, B00000001, B00000001, B11111110, B00000000, B00000000, B00000000},//U
  {B11111100, B00000010, B00000001, B00000010, B11111100, B00000000, B00000000, B00000000},//V
  {B11111111, B00000010, B00000100, B00000010, B11111111, B00000000, B00000000, B00000000},//W
  {B11000011, B00100100, B00011000, B00100100, B11000011, B00000000, B00000000, B00000000},//X
  {B11100000, B00010000, B00001111, B00010000, B11100000, B00000000, B00000000, B00000000},//Y
  {B10000111, B10001001, B10010001, B10100001, B11000001, B00000000, B00000000, B00000000}//Z
};

byte num[10][8] = {
  {B01111110, B10000001, B10000001, B10000001, B01111110, B00000000, B00000000, B00000000},//0
  {B00100001, B01000001, B11111111, B00000001, B00000001, B00000000, B00000000, B00000000},//1
  {B01000011, B10000101, B10001001, B10010001, B01100001, B00000000, B00000000, B00000000},//2
  {B01000001, B10010001, B10010001, B10010001, B01101110, B00000000, B00000000, B00000000},//3
  {B11110000, B00010000, B00010000, B11111111, B00000000, B00000000, B00000000, B00000000},//4
  {B11110001, B10010001, B10010001, B10010001, B10001110, B00000000, B00000000, B00000000},//5
  {B01111110, B10010001, B10010001, B10010001, B10001110, B00000000, B00000000, B00000000},//6
  {B10000000, B10000000, B10011111, B10100000, B11000000, B00000000, B00000000, B00000000},//7
  {B01101110, B10010001, B10010001, B10010001, B01101110, B00000000, B00000000, B00000000},//8
  {B01100000, B10010001, B10010001, B10010001, B01111110, B00000000, B00000000, B00000000}//9
};

byte specials[5][8] = {
  {B00011000, B00100100, B01000010, B00100001, B01000010, B00100100, B00011000, B00000000},//HEART
  {B00000001, B00000110, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000},//,
  {B00000001, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000},//.
  {B11111101, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000}//!
};

//Lưu trữ giá trị để biểu diễn ma trận, 24 byte là 24 cột
//1 byte = 8 bit ứng mỗi điểm trên cột
byte leds[24];

void copyArr(byte target[8], byte source[8]) {
  for (byte i = 0; i < 8; i++) {
    target[i] = source[i];
  }
}

//Tìm xem một kí tự ứng với đâu trong các mảng UP, NUM, SPECIALS)
void getArrFromChar(char ch, byte arr[8]) {
  byte ind = (byte) ch; //Lấy mã ASCII của kí tự

  if ((ind >= 48) && (ind <= 57)) {
    copyArr(arr, num[ind - 48]);
    return;
  }

  if ((ind >= 65) && (ind <= 90)) {
    copyArr(arr, up[ind - 65]);
    return;
  }

  switch (ch) {
    case '$': {
        copyArr(arr, specials[0]);
        break;
      }
    case ',': {
        copyArr(arr, specials[1]);
        break;
      }
    case '.': {
        copyArr(arr, specials[2]);
        break;
      }
    case '!': {
        copyArr(arr, specials[3]);
        break;
      }
  };
}

//Thêm một kí tự, sau đó gọi hàm addCol để hiển thị từng phần của kí tự
void addChar(char chr) {
  byte arr[8];
  getArrFromChar(chr, arr);
  for (byte i = 0; i < 8; i++) {
    if (arr[i] != 0) {
      addCol(arr[i]);
    }
  }
  addCol(0);
}

//Thêm vào một cột vào mảng leds để hiển thị
void addCol(byte col) {
  moveLeft();
  leds[23] = col;
  show(leds, SPEED);
}

void moveLeft() {
  for (byte i = 0; i < 23; i++) {
    leds[i] = leds[i + 1];
  }
}


void parseString(String s) {
  s += "      ";
  for (byte i = 0; i < s.length(); i++) {
    if (s.charAt(i) == ' ') {
      addCol(0);
      addCol(0);
    } else {
      addChar(s.charAt(i));
    }
  }
}

//Hiển thị mảng leds ra ma trận
void show(byte leds[24], byte hold) {
  for (byte k = 0; k < hold; k++) {
    for (byte i = 0; i < 8; i++) {
      byte d[3] = {0, 0, 0};

      for (byte j = 0; j < 24; j++) {
        d[  j / 8] = d[j / 8] | ((leds[j] >> i & 1) * ROWS[7 - j % 8]);
      }

      digitalWrite(LATCH, LOW);
      shiftOut(DATA_ROW, CLOCK_ROW, LSBFIRST, ROWS[i]);
      shiftOut(DATA_COL, CLOCK_COL, MSBFIRST, d[0]);
      shiftOut(DATA_COL, CLOCK_COL, MSBFIRST, d[1]);
      shiftOut(DATA_COL, CLOCK_COL, MSBFIRST, d[2]);
      digitalWrite(LATCH, HIGH);
//      delay(10); bỏ comment nếu bạn muốn tìm hiểu kĩ thuật quét LED
    }
  }
}

void initPin() {
  pinMode(LATCH, OUTPUT);
  pinMode(CLOCK_COL, OUTPUT);
  pinMode(DATA_COL, OUTPUT);
  pinMode(CLOCK_ROW, OUTPUT);
  pinMode(DATA_ROW, OUTPUT);
}

void setup() {
  Serial.begin(9600);
  initPin();
}

void loop() {
  parseString("HAPPY NEW YEAR 2016");
}

VII. Kết luận

Ok baby, vậy là mình đã cùng các bạn tìm hiểu về kĩ thuật quét LED, cách mắc mạch và chưa nói gì nhiều về thuật toán. Cái đó để dàng phần 2 nhé. Phần 2 mình sẽ nói kĩ hơn lại về phần lập trình này và sẽ bổ sung một số hiệu ứng đẹp vi diệu khác nữa, mong các bạn ủng hộ.

Hi vọng là các bạn tìm thấy điều lí thú trong bài viết này. Chúc các bạn làm kịp Valentine và tỏ tình thành công. :D

Chúc các bạn năm mới vui nhé và cộng đồng Arduino ngày càng phát triển.

lên
26 thành viên đã đánh giá bài viết này hữu ích.
Các dự án được truyền cảm hứng

Bộ điều khiển PID - ứng dụng phần 2 - xe dò line dùng thuật toán PID

Tiép nối bài viết về xe dò line cảm ơn Đỗ Hữu Toàn đã viết hộ mình phần 4. hôm nay mình sẽ làm cho chiếc xe dò line đi mượt và có hồn hơn 

lên
34 thành viên đã đánh giá bài viết này hữu ích.