Optocoupler PC817: cách ly tín hiệu, nối đầu vào 12 V và đo đáp ứng bằng ESP32
Optocoupler PC817 là gì, cách ly để làm gì, tỉ số truyền dòng CTR, cách nối tín hiệu 12 V, 24 V vào ESP32, vì sao opto chậm và thí nghiệm đo thời gian bật tắt, ước lượng CTR bằng ESP32.
Muốn ESP32 đọc tín hiệu 12 V hoặc 24 V của cảm biến công nghiệp, nút nhấn ở xa, ngõ ra của PLC, cách an toàn là không nối thẳng mà đi qua một optocoupler (còn gọi là opto, bộ ghép quang). Bên trong opto, tín hiệu đi từ phía này sang phía kia bằng ánh sáng, hai phía không có dây điện nào nối với nhau. Bài viết giải thích cấu tạo của PC817 (loại opto rẻ và phổ biến nhất), thông số CTR quyết định cách tính mạch, cách nối tín hiệu 12 V vào ESP32, vì sao opto chậm, rồi làm thí nghiệm nối vòng PC817 với ESP32 để đo thời gian bật, tắt và thấy tận mắt chuyện gì xảy ra khi CTR không đủ.
1. Optocoupler là gì, vì sao cần cách ly

Optocoupler gồm hai linh kiện quen thuộc đặt chung một vỏ: một LED hồng ngoại ở phía vào và một phototransistor (transistor có base được chiếu sáng thay cho dòng base) ở phía ra. Cho dòng qua LED, LED phát sáng, ánh sáng làm transistor dẫn. Tắt dòng LED, transistor tắt. Giữa hai phía là khối nhựa trong suốt cách điện, chịu được vài nghìn vôn.

- Chân 1 (anode) và chân 2 (cathode) là LED phía vào. Như mọi LED, cần một điện trở nối tiếp để giới hạn dòng (xem lại bài 16 về LED).
- Chân 4 (collector) và chân 3 (emitter) là transistor phía ra. Không có chân base: ánh sáng đóng vai dòng base. Transistor này chỉ chịu dòng nhỏ (tối đa 50 mA), đủ để kéo một chân GPIO xuống hoặc bật một transistor khác, không đủ để chạy tải.
- Chấm tròn trên vỏ đánh dấu chân 1; đếm ngược chiều kim đồng hồ khi nhìn từ trên (cách đếm chân IC nói kỹ ở bài 23).
Cách ly có ba lợi ích chính:
- Bảo vệ vi điều khiển: nếu phía 12 V bị chạm chập, sét lan truyền, cuộn dây gây xung cao áp, ESP32 vẫn an toàn vì không có đường điện nào chạy sang.
- Không cần GND chung: hai mạch dùng hai nguồn riêng. Dòng lớn của động cơ chạy trên dây GND phía tải không làm nhiễu tín hiệu phía ESP32 (vấn đề "vòng đất" hay gặp khi dây dài).
- Đổi mức điện áp: phía vào 12 V, 24 V, phía ra 3,3 V hay 5 V tùy điện trở kéo lên, không cần mạch chuyển mức riêng.
2. Thông số quan trọng và tỉ số truyền dòng CTR

Thông số đặc trưng nhất của opto là CTR (current transfer ratio, tỉ số truyền dòng): tỉ số giữa dòng collector phía ra và dòng LED phía vào, tính bằng phần trăm. Nó giống hệ số β của transistor ở bài 18, nhưng nhỏ hơn nhiều và thay đổi rất rộng.
- PC817 có CTR từ 50 % đến 600 % ở IF = 5 mA, chia theo hạng: A (80–160 %), B (130–260 %), C (200–400 %), D (300–600 %). Hạng in bằng chữ cái sau số 817 trên vỏ; con không có chữ cái chỉ được bảo đảm tối thiểu 50 %.
- Với CTR 100 % và IF = 5 mA, transistor phía ra cho được tối đa khoảng 5 mA. Nếu mạch phía ra đòi nhiều hơn, transistor không bão hòa được và ngõ ra không xuống hẳn mức thấp.
- CTR giảm khi LED già đi (sau nhiều năm, nhất là khi chạy dòng lớn) và thay đổi theo nhiệt độ. Khi tính mạch, lấy CTR tối thiểu và chừa dư ít nhất 2 lần.
- Điện áp ngược của LED chỉ chịu 6 V. Phía vào có thể bị đấu nhầm cực 12 V thì phải thêm diode bảo vệ (mục 3).
Quy tắc thiết kế gọn: IF × CTR tối thiểu ≥ 2 × (U kéo lên / R kéo lên). Ví dụ phía ra kéo lên 3,3 V bằng 10 kΩ cần 0,33 mA; với IF = 5 mA và CTR tối thiểu 50 %, opto cho được 2,5 mA, dư khoảng 7 lần.
3. Đưa tín hiệu 12 V, 24 V vào ESP32 qua PC817

- Điện trở phía vào R1 chọn để IF khoảng 5 mA: R1 = (12 − 1,2) / 0,005 ≈ 2,16 kΩ, chọn 2,2 kΩ. Công suất trên R1 = 10,8² / 2200 ≈ 53 mW, điện trở 1/4 W là đủ.
- Diode D1 (1N4148 hoặc 1N4007) mắc song song ngược chiều với LED: khi đấu nhầm cực, D1 dẫn và giữ điện áp ngược trên LED dưới 1 V, R1 giới hạn dòng.
- Phía ra: collector nối chân GPIO và một điện trở 10 kΩ kéo lên 3,3 V của ESP32; emitter nối GND của ESP32. Có thể dùng điện trở kéo lên bên trong (
INPUT_PULLUP, khoảng 45 kΩ) nhưng ngõ vào sẽ chậm hơn và dễ nhiễu khi dây dài, nên dùng điện trở ngoài. - Logic bị đảo: có 12 V thì LED sáng, transistor dẫn, GPIO đọc mức thấp. Chương trình hiểu
digitalRead() == LOWlà "có tín hiệu". - Không nối GND hai phía với nhau, nếu không thì mất tác dụng cách ly.

Với cảm biến tiệm cận NPN 12 V (ngõ ra kéo xuống), nối anode qua R1 lên dương 12 V và cathode vào dây tín hiệu của cảm biến: cảm biến tác động thì kéo cathode xuống 0 V, LED sáng. Với cảm biến PNP (ngõ ra đẩy lên 12 V), dùng đúng sơ đồ trên.
Chiều ngược lại cũng dùng được: ESP32 nối LED của opto qua điện trở 330 Ω (khoảng 6 mA), transistor phía ra nối vào ngõ vào của PLC hoặc base của một transistor khác ở mạch 24 V. Nhiều module relay loại có jumper JD-VCC dùng chính cách này: chân IN chỉ bật LED của một opto, cuộn relay chạy bằng nguồn riêng. Cách nối module relay an toàn có trong bài ESP32 điều khiển module relay 4 kênh.
Tín hiệu 220 V xoay chiều cũng phát hiện được bằng opto, nhưng phía vào khi đó là điện lưới nguy hiểm, cần điện trở chịu áp cao, khoảng cách cách điện trên mạch in và vỏ bọc. Người mới nên dùng module có sẵn, không tự lắp trên breadboard (xem bài 5 về an toàn điện).
4. Vì sao opto chậm
Datasheet ghi thời gian lên và xuống của PC817 chỉ khoảng 3–4 µs, nhưng đó là đo với tải 100 Ω ở dòng 2 mA. Trong mạch thật, thời gian tắt thường dài hơn nhiều, vài chục đến trăm micro giây, vì hai lý do:
- Bão hòa sâu: khi dòng LED lớn hơn nhiều so với cần thiết, transistor phía ra bị "nhồi" điện tích ở vùng base; lúc tắt LED, điện tích này phải thoát hết thì transistor mới ngắt (giống chuyện BJT bão hòa ở bài 19).
- Điện trở kéo lên lớn: transistor ngắt rồi, điện trở kéo lên phải nạp điện dung của chân collector, dây nối và chân GPIO; điện trở càng lớn, cạnh lên càng chậm (mạch RC ở bài 12).
Vì vậy PC817 hợp với tín hiệu chậm: nút nhấn, công tắc hành trình, cảm biến, đếm xung vài kHz trở xuống. UART tốc độ thấp (9600 baud) vẫn qua được nếu điện trở kéo lên nhỏ. Tín hiệu nhanh hơn (UART tốc độ cao, CAN, encoder nhanh) cần opto tốc độ cao như 6N137 (ngõ ra logic, vài Mbit/s) hoặc IC cách ly số.
5. Thí nghiệm: nối vòng PC817 với ESP32, đo thời gian và CTR
ESP32 vừa bật LED của opto, vừa đọc ngõ ra của chính opto đó, rồi đo bằng micros() thời gian từ lúc đổi mức LED tới lúc ngõ ra đổi theo. Chương trình thử bốn tổ hợp: dòng LED lớn hoặc nhỏ, điện trở kéo lên lớn hoặc nhỏ.
Chuẩn bị
- ESP32 DevKit V1, breadboard, dây cắm.
- Một optocoupler PC817 (hoặc EL817, LTV-817); điện trở 330 Ω, 2,2 kΩ, 4,7 kΩ, 1 kΩ.
Sơ đồ nối

- Chân LED không dùng được đặt ở chế độ
INPUT(hở mạch) để không kéo anode xuống. GPIO26 qua 330 Ω cho IF ≈ (3,3 − 1,2) / 330 ≈ 6,4 mA; GPIO27 qua 2,2 kΩ cho khoảng 0,95 mA. - GPIO25 xuất mức cao thì điện trở 1 kΩ song song với 4,7 kΩ, điện trở kéo lên còn khoảng 825 Ω; để GPIO25 ở
INPUTthì chỉ còn 4,7 kΩ. - GPIO32 đọc mức logic để đo thời gian. GPIO34 (chân chỉ nhập) đọc điện áp collector bằng ADC: không dùng chung một chân cho cả hai việc vì sau khi một chân đã dùng cho ADC, đọc mức logic trên chính chân đó có thể không còn đúng.
- Thí nghiệm dùng chung GND để ESP32 đo được cả hai phía; trong mạch dùng thật, hai phía phải có nguồn và GND riêng như mục 3.
Chương trình
/*
PYWORLD.VN - Optocoupler PC817 nối vòng với ESP32: đo thời gian bật, tắt, mức điện áp ngõ ra và ước lượng CTR
Phía LED: GPIO26 qua 330 Ω và GPIO27 qua 2,2 kΩ cùng vào chân 1 (anode); chân 2 (cathode) xuống GND
Phía transistor: chân 4 (collector) vào GPIO32 và GPIO34, kéo lên 3,3 V bằng 4,7 kΩ; GPIO25 qua 1 kΩ để kéo lên mạnh hơn; chân 3 (emitter) xuống GND
*/
#define CHAN_LED_MANH 26 // qua 330 Ω: IF ≈ (3,3 − 1,2) / 330 ≈ 6,4 mA
#define CHAN_LED_YEU 27 // qua 2,2 kΩ: IF ≈ (3,3 − 1,2) / 2200 ≈ 0,95 mA
#define CHAN_KEO_MANH 25 // qua 1 kΩ: khi xuất mức cao thì song song với 4,7 kΩ
#define CHAN_RA_SO 32 // đọc mức logic của collector để đo thời gian
#define CHAN_RA_DO 34 // đọc điện áp collector bằng ADC (chân chỉ nhập)
const float IF_MANH_MA = 6.4; // Dòng LED ước lượng ở hai chế độ
const float IF_YEU_MA = 0.95;
const float R_KEO_YEU = 4700.0; // Ôm: chỉ có điện trở kéo lên 4,7 kΩ
const float R_KEO_MANH = 825.0; // Ôm: 4,7 kΩ song song 1 kΩ
const float U_NGUON_MV = 3300.0;
const int SO_LAN_DO = 20;
const uint32_t HET_GIO_US = 5000; // Quá thời gian này coi như ngõ ra không đổi mức
int cheDo = 0; // 0..3: (LED mạnh, LED yếu) x (kéo lên yếu, kéo lên mạnh)
int chanLed = CHAN_LED_MANH;
void chonCheDo(int cd) {
// Chân LED không dùng để hở (INPUT) để nó không kéo anode xuống
pinMode(CHAN_LED_MANH, INPUT);
pinMode(CHAN_LED_YEU, INPUT);
chanLed = (cd < 2) ? CHAN_LED_MANH : CHAN_LED_YEU;
pinMode(chanLed, OUTPUT);
digitalWrite(chanLed, LOW);
if (cd % 2 == 1) { // Kéo lên mạnh: GPIO25 xuất 3,3 V qua 1 kΩ
pinMode(CHAN_KEO_MANH, OUTPUT);
digitalWrite(CHAN_KEO_MANH, HIGH);
} else { // Kéo lên yếu: GPIO25 để hở, chỉ còn 4,7 kΩ
pinMode(CHAN_KEO_MANH, INPUT);
}
}
// Đổi mức chân LED rồi đếm micro giây tới khi ngõ ra đạt mức chờ
uint32_t doThoiGian(int mucLed, int mucCho) {
uint32_t t0 = micros();
digitalWrite(chanLed, mucLed);
while (digitalRead(CHAN_RA_SO) != mucCho) {
if (micros() - t0 > HET_GIO_US) return HET_GIO_US;
}
return micros() - t0;
}
uint32_t docMv() {
uint32_t tong = 0;
for (int i = 0; i < 16; i++) tong += analogReadMilliVolts(CHAN_RA_DO);
return tong / 16;
}
void setup() {
Serial.begin(115200);
analogReadResolution(12);
pinMode(CHAN_RA_SO, INPUT); // Không bật điện trở kéo lên bên trong: đã có 4,7 kΩ bên ngoài
chonCheDo(0);
delay(300);
Serial.println("PC817 noi vong: LED sang thi ngo ra xuong muc thap (dao muc)");
Serial.println("R = dien tro keo len (ohm); U = dien ap ra khi LED sang / khi LED tat");
}
void loop() {
chonCheDo(cheDo);
delay(50);
uint32_t tongBat = 0, tongTat = 0, tongThap = 0, tongCao = 0;
int soBat = 0, soTat = 0;
for (int i = 0; i < SO_LAN_DO; i++) {
uint32_t t = doThoiGian(HIGH, LOW); // Bật LED, chờ collector xuống mức thấp
if (t < HET_GIO_US) { tongBat += t; soBat++; }
delay(2);
tongThap += docMv(); // Điện áp ngõ ra khi LED sáng
t = doThoiGian(LOW, HIGH); // Tắt LED, chờ điện trở kéo collector lên lại
if (t < HET_GIO_US) { tongTat += t; soTat++; }
delay(2);
tongCao += docMv(); // Điện áp ngõ ra khi LED tắt
}
float ifMa = (cheDo < 2) ? IF_MANH_MA : IF_YEU_MA;
float rKeo = (cheDo % 2 == 1) ? R_KEO_MANH : R_KEO_YEU;
uint32_t uThap = tongThap / SO_LAN_DO;
uint32_t uCao = tongCao / SO_LAN_DO;
float icMa = (U_NGUON_MV - uThap) / rKeo; // Dòng collector = điện áp trên điện trở kéo lên / R
Serial.printf("IF %4.2f mA, R %4u | ", ifMa, (unsigned)rKeo);
if (soBat > 0) Serial.printf("bat %4u us, ", (unsigned)(tongBat / soBat));
else Serial.print("bat --- us, ");
if (soTat > 0 && soBat > 0) Serial.printf("tat %4u us | ", (unsigned)(tongTat / soTat));
else Serial.print("tat --- us | ");
Serial.printf("U %4u/%4u mV | IC %4.2f mA", (unsigned)uThap, (unsigned)uCao, icMa);
if (uThap < 400) Serial.println(" bao hoa");
else Serial.printf(" CTR ~%3.0f%%\n", icMa / ifMa * 100.0); // Chưa bão hòa: IC do CTR giới hạn
cheDo = (cheDo + 1) % 4;
if (cheDo == 0) Serial.println();
delay(1500);
}
doThoiGian()đổi mức chân LED, rồi đọc liên tục GPIO32 tới khi ngõ ra đạt mức chờ; quá 5 ms coi như ngõ ra không đổi mức. Mỗi chế độ đo 20 lần rồi lấy trung bình.- Giữa hai lần đổi mức, chương trình đọc điện áp collector khi LED sáng (U thấp) và khi LED tắt (U cao).
- Dòng collector được tính bằng điện áp rơi trên điện trở kéo lên chia cho điện trở. Nếu U thấp dưới 0,4 V, transistor bão hòa: dòng do điện trở quyết định, chương trình in "bao hoa". Nếu không, dòng do CTR quyết định và chương trình ước lượng CTR = IC / IF.
Kết quả mong đợi

- Dòng LED lớn, kéo lên 4,7 kΩ: bật rất nhanh, vài micro giây; tắt chậm hơn hẳn vì transistor bão hòa sâu và điện trở kéo lên lớn.
- Kéo lên 825 Ω: thời gian tắt giảm rõ. Đây là cách làm opto nhanh hơn: điện trở kéo lên nhỏ nhất mà CTR còn cho phép.
- Dòng LED nhỏ, kéo lên 4,7 kΩ: bật chậm hơn vì ánh sáng yếu; vẫn bão hòa vì mạch chỉ cần khoảng 0,7 mA.
- Dòng LED nhỏ, kéo lên 825 Ω: mạch cần 4 mA nhưng opto chỉ cho được IF × CTR ≈ 1,2 mA, ngõ ra chỉ xuống khoảng 2,3 V, ESP32 vẫn đọc mức cao. Đây đúng là lỗi khi chọn điện trở kéo lên quá nhỏ hoặc dòng LED quá ít. Con số CTR in ra giúp biết hạng CTR thật của con opto đang có.
Điện áp mức cao đọc được thường chỉ khoảng 3,1–3,2 V dù thực tế gần 3,3 V: ADC của ESP32 kém chính xác ở gần đỉnh thang đo. Thời gian thật phụ thuộc hạng CTR, nhiệt độ và dây nối trên breadboard; điều đáng xem là xu hướng giữa bốn dòng.
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ý |
|---|---|---|
| GPIO luôn đọc mức cao | LED phía vào không sáng: nhầm chân 1–2, thiếu dòng, đấu ngược cực | Đo điện áp trên LED (khoảng 1,1–1,2 V khi dẫn); kiểm tra chấm đánh dấu chân 1 |
| GPIO luôn đọc mức thấp | Nhầm chân 3–4, thiếu điện trở kéo lên, opto hỏng | Kiểm tra điện trở kéo lên lên 3,3 V; thử thay opto |
| Ngõ ra chỉ xuống 1–2 V | CTR không đủ cho điện trở kéo lên đang dùng | Tăng dòng LED (giảm R phía vào) hoặc tăng điện trở kéo lên |
| Đọc được khi dây ngắn, sai khi dây dài | Nhiễu cảm ứng trên dây phía vào, kéo lên bên trong quá yếu | Dùng điện trở kéo lên ngoài 4,7–10 kΩ; thêm tụ 100 nF ở ngõ ra nếu tín hiệu chậm |
| Đếm xung bị sót | Tín hiệu quá nhanh so với PC817 | Giảm điện trở kéo lên, tăng dòng LED, hoặc dùng opto tốc độ cao 6N137 |
| Mất cách ly, ESP32 vẫn hỏng khi phía 12 V có sự cố | GND hai phía bị nối chung | Tách hẳn GND, mỗi phía một nguồn |
7. Xem thêm
- Hướng dẫn ESP32 điều khiển module relay 4 kênh an toàn: chân IN của module relay và jumper JD-VCC.
- Các bài liên quan trong loạt điện tử căn bản: LED (bài 16), transistor làm công tắc (bài 19).
- Bài tiếp theo của loạt: IC là gì, kiểu vỏ, đếm chân và đọc datasheet.
Xem thêm