Flip-flop, bộ đếm và thanh ghi dịch 74HC595: mở rộng chân ra cho ESP32
Mạch chốt SR, flip-flop D lấy mẫu theo xung nhịp, bộ đếm nhị phân, thanh ghi dịch và IC 74HC595, dạng sóng khi dịch một byte, thí nghiệm ESP32 tự dịch bit ra 8 LED, đếm nhị phân và đèn chạy.
Cổng logic ở bài 27 chỉ phản ứng với đầu vào hiện tại: đầu vào đổi thì đầu ra đổi theo, không nhớ gì. Muốn mạch nhớ, đếm được, hay nhận dữ liệu từng bit một, cần thêm một loại mạch mới: flip-flop. Bài viết đi từ mạch chốt SR hai cổng NOR (một bit bộ nhớ), tới flip-flop D lấy mẫu theo xung nhịp, bộ đếm nhị phân, thanh ghi dịch, rồi tới IC 74HC595 rất hay dùng để mở rộng chân ra: chỉ ba chân ESP32 điều khiển được 8, 16, 24 đèn hay relay. Phần thí nghiệm tự viết hàm dịch từng bit ra 74HC595 để thấy rõ flip-flop làm việc thế nào.
1. Mạch chốt SR: một bit bộ nhớ


Nối hai cổng NOR sao cho ngõ ra của cổng này quay về đầu vào của cổng kia, ta được một mạch tự giữ trạng thái:
- Đưa S (set, đặt) lên 1 trong chốc lát: Q lên 1. Khi S về 0, Q vẫn ở 1 vì chính Q đang giữ cổng kia ở trạng thái phù hợp.
- Đưa R (reset, xóa) lên 1: Q về 0 và cũng ở yên đó.
- S = R = 0: mạch giữ nguyên trạng thái cũ, tức là nhớ. S = R = 1 cùng lúc là trạng thái không dùng.
Đây là cách bộ nhớ RAM tĩnh (SRAM) trong ESP32 lưu từng bit: mỗi bit là một vòng hai cổng đảo nối chéo, chỉ khác là làm bằng sáu transistor.
2. Flip-flop D: lấy mẫu theo xung nhịp

Mạch chốt SR phản ứng ngay lập tức. Trong mạch số, ta muốn mọi thứ đổi trạng thái đồng loạt theo nhịp của một xung nhịp (clock). Flip-flop D làm đúng việc đó:
- Tại cạnh lên của xung nhịp CLK (lúc CLK chuyển từ 0 lên 1), Q chép giá trị đang có ở D.
- Giữa hai cạnh lên, Q giữ nguyên, D đổi thế nào cũng không ảnh hưởng.
- Ký hiệu hình tam giác ở chân CLK nghĩa là "tác động theo cạnh". Bên trong flip-flop D là hai mạch chốt nối tiếp.
Điều kiện để flip-flop lấy mẫu đúng: D phải ổn định từ một chút trước tới một chút sau cạnh lên (thời gian thiết lập và giữ, cỡ vài chục nano giây với 74HC). Vì vậy khi lập trình, luôn đặt dữ liệu xong rồi mới tạo cạnh lên của xung nhịp.
3. Bộ đếm nhị phân

- Nối Q̄ (ngõ ra đảo) của flip-flop D về chính đầu D: mỗi cạnh lên, Q chép giá trị ngược với chính nó, tức là đảo trạng thái. Ngõ ra Q có tần số bằng một nửa xung nhịp: đây là mạch chia đôi tần số.
- Nối nhiều tầng như vậy, tầng sau lấy xung nhịp từ tầng trước: ba ngõ ra Q0, Q1, Q2 đọc thành số nhị phân tăng dần 0, 1, 2 … 7 rồi quay về 0. Đó là bộ đếm.
- Đồng hồ điện tử dùng chính nguyên lý này: thạch anh 32 768 Hz qua 15 tầng chia đôi còn đúng 1 Hz, mỗi giây một xung. Bộ định thời (timer) trong ESP32 cũng là các bộ đếm như vậy, chỉ dài hơn (64 bit).
4. Thanh ghi dịch và IC 74HC595

Nối 8 flip-flop D thành hàng, ngõ ra của ô này vào đầu D của ô kế tiếp, chung một xung nhịp: mỗi cạnh lên, mọi bit dịch sang phải một ô, ô đầu tiên nhận bit mới từ chân dữ liệu. Sau 8 xung nhịp, 8 bit đưa vào nối tiếp trên một dây đã nằm song song trên 8 ngõ ra. Đó là thanh ghi dịch (shift register) kiểu nối tiếp vào, song song ra.
74HC595 có thêm một hàng 8 mạch chốt phía sau thanh ghi dịch:
- DS (chân 14): dữ liệu vào. SH_CP (chân 11): xung dịch. ST_CP (chân 12): xung chốt, chép 8 bit từ thanh ghi dịch sang hàng chốt.
- Nhờ hàng chốt, trong lúc đang dịch, ngõ ra Q0–Q7 vẫn giữ giá trị cũ; chỉ khi có xung ST_CP, 8 ngõ ra mới đổi cùng lúc, đèn không bị nhấp nháy theo từng bit.
- OE (chân 13, mức thấp là bật) bật tắt cả 8 ngõ ra; MR (chân 10, mức thấp là xóa) xóa thanh ghi dịch. Dùng bình thường thì OE nối GND, MR nối VCC.
- Q7' (chân 9) là bit bị đẩy ra khỏi ô cuối: nối vào DS của IC 74HC595 thứ hai, dùng chung SH_CP và ST_CP, được 16 ngõ ra mà vẫn chỉ tốn ba chân vi điều khiển. Ghép bao nhiêu IC cũng được, chỉ là dịch lâu hơn.

Một chiều ngược lại cũng có: 74HC165 là thanh ghi song song vào, nối tiếp ra, dùng để đọc 8 nút nhấn qua ba chân. Còn khi tải lớn hơn vài mA mỗi kênh (relay, dải LED), ngõ ra 74HC595 đi qua transistor (bài 19) hoặc IC đệm ULN2803.
5. Thí nghiệm: ESP32 điều khiển 8 LED qua 74HC595
Chuẩn bị
- ESP32 DevKit V1, breadboard, dây cắm.
- Một IC 74HC595 vỏ DIP-16, 8 LED, 8 điện trở 330 Ω, tụ gốm 100 nF.
Sơ đồ nối

- IC nuôi 3,3 V để mức logic khớp với ESP32. Với LED đỏ, mỗi kênh khoảng (3,3 − 1,8) / 330 ≈ 4,5 mA, cả 8 kênh khoảng 36 mA, dưới giới hạn tổng 70 mA của IC.
- Q0 ở chân 15 (phía bên kia vỏ), Q1–Q7 ở chân 1–7: dễ cắm nhầm thứ tự LED, nên kiểm tra bằng chế độ đèn chạy.
- Tụ 100 nF sát chân 16 và chân 8.
Chương trình
/*
PYWORLD.VN - Thanh ghi dịch 74HC595 với ESP32: tự dịch từng bit ra 8 LED, đếm nhị phân, đèn chạy và hiển thị số gõ từ Serial
74HC595 nuôi 3,3 V: DS (chân 14) nối GPIO23, SH_CP (chân 11) nối GPIO18, ST_CP (chân 12) nối GPIO5; OE (13) xuống GND, MR (10) lên 3,3 V
Q0 (chân 15) và Q1–Q7 (chân 1–7) mỗi chân qua 330 Ω tới anode một LED, cathode xuống GND
*/
#define CHAN_DS 23 // dữ liệu nối tiếp
#define CHAN_SH 18 // xung nhịp dịch (shift clock)
#define CHAN_ST 5 // xung chốt (storage/latch clock)
const uint32_t CHU_KY_MS = 250;
int cheDo = 0; // 0: đếm nhị phân, 1: đèn chạy, 2: giữ giá trị gõ vào
uint8_t giaTri = 0;
uint32_t lanTruoc = 0;
// Dịch 8 bit ra 74HC595, bit cao trước: sau 8 xung, bit 7 nằm ở Q7, bit 0 nằm ở Q0
void ghi595(uint8_t b) {
digitalWrite(CHAN_ST, LOW);
for (int i = 7; i >= 0; i--) {
digitalWrite(CHAN_DS, (b >> i) & 1); // Đặt bit lên chân DS trước
digitalWrite(CHAN_SH, HIGH); // Cạnh lên: mọi flip-flop nhận bit của ô bên cạnh
delayMicroseconds(1);
digitalWrite(CHAN_SH, LOW);
}
digitalWrite(CHAN_ST, HIGH); // Cạnh lên: chép 8 bit sang thanh ghi chốt, LED đổi cùng lúc
}
void inGiaTri(uint8_t b) {
char nhiPhan[9];
for (int i = 0; i < 8; i++) nhiPhan[i] = ((b >> (7 - i)) & 1) ? '1' : '0';
nhiPhan[8] = '\0';
const char *ten[] = {"dem", "chay", "nhap"};
Serial.printf("[%s] Q7..Q0 = %s = %3u = 0x%02X\n", ten[cheDo], nhiPhan, (unsigned)b, (unsigned)b);
}
void docLenh() {
if (!Serial.available()) return;
String s = Serial.readStringUntil('\n');
s.trim();
if (s.length() == 0) return;
if (s == "d") {
cheDo = 0;
giaTri = 0;
} else if (s == "c") {
cheDo = 1;
giaTri = 1;
} else {
long v = s.toInt();
if (v < 0 || v > 255 || (v == 0 && s != "0")) {
Serial.println("Go so 0-255, 'd' de dem, 'c' de chay");
return;
}
cheDo = 2;
giaTri = (uint8_t)v;
}
ghi595(giaTri);
inGiaTri(giaTri);
lanTruoc = millis();
}
void setup() {
Serial.begin(115200);
pinMode(CHAN_DS, OUTPUT);
pinMode(CHAN_SH, OUTPUT);
pinMode(CHAN_ST, OUTPUT);
digitalWrite(CHAN_SH, LOW);
ghi595(0);
delay(300);
Serial.println("74HC595: go so 0-255 de hien thi, 'd' de dem nhi phan, 'c' de den chay");
}
void loop() {
docLenh();
if (cheDo == 2 || millis() - lanTruoc < CHU_KY_MS) return;
lanTruoc = millis();
if (cheDo == 0) {
giaTri++; // Bộ đếm 8 bit: 255 + 1 quay về 0
} else {
giaTri = (giaTri << 1) | (giaTri >> 7); // Xoay vòng trái một bit
}
ghi595(giaTri);
inGiaTri(giaTri);
}
ghi595()là phần chính: kéo ST_CP xuống, lần lượt đặt từng bit (bit cao trước) lên DS rồi tạo một xung SH_CP, cuối cùng kéo ST_CP lên để chốt. Hàm này làm đúng những gì hình 6 vẽ. Gói esp32 có sẵn hàmshiftOut()làm việc tương tự; tự viết một lần giúp hiểu rõ thanh ghi dịch.- Chế độ đếm:
giaTri++mỗi 250 ms, biến 8 bit tự quay về 0 sau 255; 8 LED hiện số đếm nhị phân, LED Q0 nháy nhanh nhất, mỗi LED sau chậm gấp đôi, giống hệt dạng sóng bộ đếm ở hình 4. - Chế độ đèn chạy: xoay vòng các bit sang trái. Gõ một số 0–255 vào Serial Monitor (chọn kiểu kết thúc dòng "Newline") để hiện số đó; gõ
dquay về đếm,csang đèn chạy.
Kết quả mong đợi

LED sáng đúng theo dãy bit in ra: bit 1 là LED sáng. Gõ 178 thì LED Q7, Q5, Q4, Q1 sáng. Thử bỏ dòng digitalWrite(CHAN_ST, HIGH) ở cuối ghi595() rồi nạp lại: LED không đổi nữa, vì dữ liệu đã vào thanh ghi dịch nhưng chưa được chốt ra ngõ ra.
6. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Không LED nào sáng | OE để hở hoặc nối 3,3 V; MR nối GND; thiếu nguồn chân 16 | OE xuống GND, MR lên 3,3 V; đo nguồn giữa chân 16 và 8 |
| LED sáng lộn xộn, không theo số | Nhầm SH_CP với ST_CP, hoặc DS cắm nhầm chân | Đối chiếu chân 11, 12, 14 với sơ đồ |
| Thứ tự LED bị ngược hoặc lệch một vị trí | Quên Q0 ở chân 15; LED cắm theo thứ tự chân 1–8 | Dời LED Q0 sang chân 15; chân 8 là GND |
| LED nháy nhẹ khi đang dịch | Nối ST_CP chung với SH_CP | Tách riêng ST_CP, chỉ chốt sau khi đủ 8 bit |
| Gõ số không đổi gì | Serial Monitor không gửi ký tự xuống dòng | Chọn "Newline" ở góc Serial Monitor |
| IC nóng, LED mờ khi sáng nhiều | Dòng tổng quá lớn (điện trở nhỏ, LED trắng nuôi 5 V) | Giữ điện trở 330 Ω trở lên; tải lớn dùng ULN2803 |
7. Xem thêm
- Các bài liên quan trong loạt điện tử căn bản: LED và điện trở hạn dòng (bài 16), transistor làm công tắc (bài 19), cổng logic (bài 27).
- Bài tiếp theo của loạt: tìm lỗi mạch điện tử từng bước.
Xem thêm