RFID RC522 với ESP32: đọc mã thẻ không cần thư viện, lưu thẻ hợp lệ, mở khóa bằng relay

Nối RC522 với ESP32 qua SPI, hiểu REQA, chống va chạm, SELECT, HLTA, đọc UID 4 và 7 byte không cần thư viện, lưu thẻ bằng Preferences, đóng relay khi quẹt đúng thẻ.

Quẹt thẻ để mở cửa, chấm công, mở tủ đồ: phần lớn các hệ thống này dùng thẻ RFID tần số 13,56 MHz. Module RC522 đọc được loại thẻ này với giá rất rẻ, nên rất hay gặp trong đồ án và dự án tự làm. Bài này nối RC522 với ESP32 qua SPI, giải thích đầu đọc và thẻ trao đổi những byte nào, rồi viết chương trình đọc mã thẻ (UID) không cần thư viện. Chương trình đọc được cả thẻ UID 4 byte và 7 byte, lưu danh sách thẻ hợp lệ vào bộ nhớ flash và đóng relay 3 giây khi quẹt đúng thẻ.

1. RFID là gì, RC522 đọc được thẻ nào

RFID là nhận dạng bằng sóng vô tuyến. Đầu đọc phát từ trường 13,56 MHz qua cuộn anten. Thẻ không có pin, nó lấy năng lượng từ chính từ trường đó để chạy con chip bên trong rồi trả lời bằng cách thay đổi tải trên cuộn dây của thẻ. Khoảng cách đọc của module RC522 thường chỉ 2–5 cm.

RC522 dùng chip MFRC522, đọc các thẻ theo chuẩn ISO/IEC 14443A. Sau khi chọn thẻ, đầu đọc nhận một byte SAK cho biết loại thẻ:

SAKLoại thẻ thường gặpUIDGhi chú
0x08MIFARE Classic 1K (thẻ trắng, móc khóa đi kèm module)4 byteThẻ ra vào, gửi xe phổ biến
0x18MIFARE Classic 4K4 hoặc 7 byte
0x00MIFARE Ultralight, nhãn NFC NTAG213/215/2167 byteNhãn dán, thẻ NFC giá rẻ
0x20Thẻ ISO 14443-4: thẻ ngân hàng, điện thoại giả lập thẻThường đổi mỗi lầnKhông dùng làm thẻ ra vào bằng cách đọc UID

RC522 không đọc được thẻ 125 kHz (loại thẻ dày, chỉ có số in sẵn, hay dùng ở thang máy, chấm công đời cũ). Thẻ 125 kHz cần đầu đọc riêng.

2. Module RC522

Module RFID RC522 màu xanh có cuộn anten in trên mạch và hàng 8 chân SDA SCK MOSI MISO IRQ GND RST 3.3V, bên cạnh là module tím và thẻ móc khóa RFID
Module RC522 (trái) và bộ module kèm thẻ móc khóa (phải). Ảnh trái: JrawX, CC0, Wikimedia Commons; ảnh phải: Goga504, CC BY-SA 4.0, Wikimedia Commons; đã cắt và thu nhỏ.

Phần lớn mặt module là cuộn anten in trên mạch. Chip MFRC522 và thạch anh 27,12 MHz nằm ở cạnh trên, cạnh hàng 8 chân:

  • SDA: chân chọn chip (SS, CS) của SPI. Tên in là SDA nhưng không liên quan tới I2C.
  • SCK, MOSI, MISO: ba dây SPI.
  • IRQ: chân báo ngắt, bài này không dùng.
  • RST: chân khởi động lại chip, mức thấp là tắt chip.
  • 3.3V, GND: nguồn. Chỉ cấp 3,3 V; chip MFRC522 chịu tối đa khoảng 3,6 V.

3. Linh kiện cần chuẩn bị

4. Sơ đồ nối dây

Sơ đồ nối RC522 với ESP32: SDA vào D5, SCK vào D18, MOSI vào D23, MISO vào D19, RST vào D27, 3.3V vào 3V3; module relay IN vào D26, VCC vào VIN
RC522 dùng các chân SPI mặc định của ESP32, relay ở D26 (hình minh họa).
RC522ESP32RelayESP32
SDAD5IND26
SCKD18VCCVIN (5 V khi cắm USB)
MOSID23GNDGND
MISOD19
RSTD27
3.3V3V3
GNDGND

Dây SPI nên ngắn dưới 20 cm. Đặt module xa vật kim loại lớn, vì kim loại làm giảm khoảng cách đọc. Ý nghĩa bốn dây SPI có trong bài SPI là gì.

5. Đầu đọc và thẻ trao đổi những gì

Bốn bước đọc mã thẻ RFID: REQA 0x26 bảy bit, chống va chạm 93 20 nhận UID và BCC, SELECT 93 70 kèm CRC nhận SAK, HLTA 50 00 cho thẻ ngủ
Các byte ESP32 gửi qua RC522 và các byte thẻ trả lời (số liệu ví dụ).

RC522 chỉ là bộ phát và thu sóng. Việc nói chuyện với thẻ do chương trình trên ESP32 làm, theo bốn bước:

  1. REQA (một byte 0x26, chỉ gửi 7 bit): hỏi "có thẻ nào đang chờ không". Thẻ trong vùng đọc trả lời 2 byte ATQA.
  2. Chống va chạm (0x93 0x20): thẻ trả về 4 byte UID và 1 byte BCC. BCC là XOR của 4 byte UID, dùng để kiểm tra. Nếu có hai thẻ cùng lúc, các bit khác nhau va chạm và cần hỏi lại.
  3. SELECT (0x93 0x70, UID, BCC, 2 byte CRC): chọn đúng thẻ đó. Thẻ trả về SAK cho biết loại thẻ.
  4. HLTA (0x50 0x00 và CRC): cho thẻ ngủ. Thẻ không trả lời lệnh REQA nữa cho tới khi được đưa ra khỏi vùng đọc, nhờ vậy mỗi lần quẹt chỉ đọc một lần.

CRC ở đây là CRC_A của chuẩn ISO 14443, tính từ mọi byte đứng trước. Chương trình tự tính trong hàm crcA().

6. Thẻ UID 7 byte

Thẻ UID 7 byte: cấp 1 trả về byte 0x88 cờ nối tầng cùng 3 byte đầu UID, cấp 2 trả về 4 byte cuối UID, mỗi cấp có một byte BCC
Thẻ UID 7 byte phải đọc qua hai cấp; byte 0x88 báo còn phần sau (số liệu ví dụ).

Nhãn NFC và thẻ Ultralight có UID 7 byte. Ở cấp 1, thẻ trả về byte 0x88 (cờ nối tầng) cùng 3 byte đầu. Chương trình SELECT cấp 1 rồi hỏi tiếp cấp 2 bằng mã 0x95 để lấy 4 byte còn lại. Bỏ qua bước này thì mọi nhãn NFC cùng lô sẽ có "mã" bắt đầu bằng 88 và dễ trùng nhau.

7. Chương trình đọc thẻ, lưu thẻ, mở khóa

Chọn bo ESP32 Dev Module rồi nạp. Bài dùng gói esp32 phiên bản 3.3.10, chỉ cần SPI và Preferences có sẵn, không cần thư viện MFRC522.

// PYWORLD.VN - Đọc mã thẻ RFID bằng module RC522 (MFRC522) và ESP32, không cần thư viện; thẻ hợp lệ thì đóng relay 3 giây
// RC522: SDA(SS) D5, SCK D18, MOSI D23, MISO D19, RST D27, 3.3V vào 3V3 (không cấp 5 V); relay IN vào D26
// Giữ nút BOOT trong lúc quẹt thẻ để thêm hoặc xóa thẻ khỏi danh sách (lưu bằng Preferences, giữ khi mất điện)

#include <SPI.h>
#include <Preferences.h>

const int CHAN_SS = 5;
const int CHAN_RST = 27;
const int CHAN_RELAY = 26;
const int CHAN_BOOT = 0;
const bool RELAY_KICH_MUC_THAP = true;    // module relay phổ biến đóng khi IN ở mức thấp
const uint32_t THOI_GIAN_MO_MS = 3000;
const int SO_THE_TOI_DA = 10;

// Thanh ghi và lệnh của MFRC522 (theo datasheet NXP)
enum : uint8_t {
  CommandReg = 0x01, ComIrqReg = 0x04, DivIrqReg = 0x05, ErrorReg = 0x06, FIFODataReg = 0x09, FIFOLevelReg = 0x0A,
  ControlReg = 0x0C, BitFramingReg = 0x0D, CollReg = 0x0E, ModeReg = 0x11, TxModeReg = 0x12, RxModeReg = 0x13,
  TxControlReg = 0x14, TxASKReg = 0x15, ModWidthReg = 0x24, TModeReg = 0x2A, TPrescalerReg = 0x2B,
  TReloadRegH = 0x2C, TReloadRegL = 0x2D, VersionReg = 0x37
};
enum : uint8_t { LENH_NGHI = 0x00, LENH_TRANSCEIVE = 0x0C, LENH_SOFT_RESET = 0x0F };

SPISettings caiDatSpi(4000000, MSBFIRST, SPI_MODE0);
Preferences boNho;
uint8_t danhSach[SO_THE_TOI_DA][7];
uint8_t doDaiThe[SO_THE_TOI_DA];
int soThe = 0;

void ghi(uint8_t tg, uint8_t giaTri) {
  SPI.beginTransaction(caiDatSpi);
  digitalWrite(CHAN_SS, LOW);
  SPI.transfer((tg << 1) & 0x7E);           // bit 7 = 0: ghi
  SPI.transfer(giaTri);
  digitalWrite(CHAN_SS, HIGH);
  SPI.endTransaction();
}

uint8_t doc(uint8_t tg) {
  SPI.beginTransaction(caiDatSpi);
  digitalWrite(CHAN_SS, LOW);
  SPI.transfer(((tg << 1) & 0x7E) | 0x80);  // bit 7 = 1: đọc
  uint8_t v = SPI.transfer(0);
  digitalWrite(CHAN_SS, HIGH);
  SPI.endTransaction();
  return v;
}

void datBit(uint8_t tg, uint8_t mask) { ghi(tg, doc(tg) | mask); }
void xoaBit(uint8_t tg, uint8_t mask) { ghi(tg, doc(tg) & ~mask); }

// CRC_A theo ISO/IEC 14443-3 (giá trị đầu 0x6363), byte thấp gửi trước.
uint16_t crcA(const uint8_t *du, size_t n) {
  uint16_t crc = 0x6363;
  for (size_t i = 0; i < n; i++) {
    uint8_t c = du[i] ^ (uint8_t)crc;
    c ^= (uint8_t)(c << 4);
    crc = (crc >> 8) ^ ((uint16_t)c << 8) ^ ((uint16_t)c << 3) ^ (c >> 4);
  }
  return crc;
}

void khoiDongRc522() {
  digitalWrite(CHAN_RST, LOW);
  delayMicroseconds(10);
  digitalWrite(CHAN_RST, HIGH);
  delay(50);
  ghi(CommandReg, LENH_SOFT_RESET);
  delay(50);
  ghi(TxModeReg, 0x00);
  ghi(RxModeReg, 0x00);
  ghi(ModWidthReg, 0x26);
  ghi(TModeReg, 0x80);                     // timer tự chạy khi gửi xong
  ghi(TPrescalerReg, 0xA9);                // 13,56 MHz / (2 × 169 + 1) ≈ 40 kHz, mỗi nhịp 25 us
  ghi(TReloadRegH, 0x03);
  ghi(TReloadRegL, 0xE8);                  // 1000 nhịp = 25 ms chờ thẻ trả lời
  ghi(TxASKReg, 0x40);                     // điều chế ASK 100 % theo ISO 14443A
  ghi(ModeReg, 0x3D);                      // giá trị đầu CRC 0x6363
  datBit(TxControlReg, 0x03);              // bật hai chân phát ra anten
}

// Gửi `n` byte (byte cuối chỉ gửi `bitCuoi` bit nếu khác 0) và nhận trả lời. Trả về số byte nhận, -1 nếu lỗi/hết giờ.
int traoDoi(const uint8_t *gui, uint8_t n, uint8_t *nhan, uint8_t toiDa, uint8_t bitCuoi = 0) {
  ghi(CommandReg, LENH_NGHI);
  ghi(ComIrqReg, 0x7F);                    // xóa các cờ ngắt
  ghi(FIFOLevelReg, 0x80);                 // xóa bộ đệm FIFO
  for (uint8_t i = 0; i < n; i++) ghi(FIFODataReg, gui[i]);
  ghi(BitFramingReg, bitCuoi);
  ghi(CommandReg, LENH_TRANSCEIVE);
  datBit(BitFramingReg, 0x80);             // StartSend
  uint32_t batDau = millis();
  while (true) {
    uint8_t irq = doc(ComIrqReg);
    if (irq & 0x30) break;                 // RxIRq hoặc IdleIRq: đã nhận xong
    if ((irq & 0x01) || millis() - batDau > 40) return -1;   // TimerIRq: thẻ không trả lời
  }
  xoaBit(BitFramingReg, 0x80);
  if (doc(ErrorReg) & 0x13) return -1;     // tràn bộ đệm, lỗi chẵn lẻ, lỗi giao thức
  uint8_t soByte = doc(FIFOLevelReg);
  if (soByte > toiDa) return -1;
  for (uint8_t i = 0; i < soByte; i++) nhan[i] = doc(FIFODataReg);
  return soByte;
}

// Chọn thẻ ở một cấp: chống va chạm lấy 4 byte + BCC, rồi SELECT để nhận SAK.
bool chonCap(uint8_t maCap, uint8_t uid4[4], uint8_t &sak) {
  uint8_t gui[9] = {maCap, 0x20};
  uint8_t nhan[5];
  if (traoDoi(gui, 2, nhan, 5) != 5) return false;
  if ((nhan[0] ^ nhan[1] ^ nhan[2] ^ nhan[3]) != nhan[4]) return false;   // BCC sai
  gui[1] = 0x70;
  memcpy(gui + 2, nhan, 5);
  uint16_t crc = crcA(gui, 7);
  gui[7] = crc & 0xFF;
  gui[8] = crc >> 8;
  uint8_t traLoi[3];
  if (traoDoi(gui, 9, traLoi, 3) != 3) return false;
  memcpy(uid4, nhan, 4);
  sak = traLoi[0];
  return true;
}

// Tìm thẻ mới trong vùng đọc. Trả về độ dài UID (4 hoặc 7 byte), 0 nếu không có thẻ.
uint8_t docUid(uint8_t uid[7], uint8_t &sak) {
  xoaBit(CollReg, 0x80);
  uint8_t reqa = 0x26, atqa[2];
  if (traoDoi(&reqa, 1, atqa, 2, 7) != 2) return 0;   // REQA chỉ có 7 bit
  uint8_t phan[4];
  if (!chonCap(0x93, phan, sak)) return 0;
  if (phan[0] != 0x88) {                   // không có cờ nối tầng: UID 4 byte
    memcpy(uid, phan, 4);
    return 4;
  }
  memcpy(uid, phan + 1, 3);                // 0x88 + 3 byte đầu, cấp 2 cho 4 byte còn lại
  if (!chonCap(0x95, phan, sak)) return 0;
  memcpy(uid + 3, phan, 4);
  return 7;
}

void choTheNghi() {                      // HLTA: thẻ ngủ tới khi ra khỏi vùng đọc, không bị đọc lặp
  uint8_t gui[4] = {0x50, 0x00};
  uint16_t crc = crcA(gui, 2);
  gui[2] = crc & 0xFF;
  gui[3] = crc >> 8;
  uint8_t bo[1];
  traoDoi(gui, 4, bo, 1);                  // thẻ không trả lời là đúng
}

int timThe(const uint8_t *uid, uint8_t n) {
  for (int i = 0; i < soThe; i++)
    if (doDaiThe[i] == n && memcmp(danhSach[i], uid, n) == 0) return i;
  return -1;
}

void luuDanhSach() {
  boNho.putInt("so", soThe);
  boNho.putBytes("uid", danhSach, sizeof(danhSach));
  boNho.putBytes("dai", doDaiThe, sizeof(doDaiThe));
}

void relay(bool dong) {
  digitalWrite(CHAN_RELAY, (dong == RELAY_KICH_MUC_THAP) ? LOW : HIGH);
}

void setup() {
  Serial.begin(115200);
  pinMode(CHAN_SS, OUTPUT);
  digitalWrite(CHAN_SS, HIGH);
  pinMode(CHAN_RST, OUTPUT);
  pinMode(CHAN_BOOT, INPUT_PULLUP);
  pinMode(CHAN_RELAY, OUTPUT);
  relay(false);
  SPI.begin(18, 19, 23, CHAN_SS);          // SCK, MISO, MOSI, SS
  khoiDongRc522();
  uint8_t ban = doc(VersionReg);
  if (ban == 0x00 || ban == 0xFF) {
    Serial.println("Khong doc duoc RC522: kiem tra day SPI, nguon 3.3V");
  } else {
    Serial.printf("RC522 phien ban 0x%02X\n", ban);
  }
  boNho.begin("rfid", false);
  soThe = boNho.getInt("so", 0);
  boNho.getBytes("uid", danhSach, sizeof(danhSach));
  boNho.getBytes("dai", doDaiThe, sizeof(doDaiThe));
  Serial.printf("Da luu %d the. Giu BOOT khi quet de them/xoa the\n", soThe);
}

void loop() {
  static uint32_t lucMo = 0;
  if (lucMo && millis() - lucMo > THOI_GIAN_MO_MS) {
    relay(false);
    lucMo = 0;
  }

  uint8_t uid[7], sak;
  uint8_t n = docUid(uid, sak);
  if (!n) {
    delay(50);
    return;
  }
  choTheNghi();
  char chuoi[22];
  for (uint8_t i = 0; i < n; i++) sprintf(chuoi + i * 3, "%02X%s", uid[i], i + 1 < n ? ":" : "");
  int viTri = timThe(uid, n);

  if (digitalRead(CHAN_BOOT) == LOW) {     // chế độ quản lý thẻ
    if (viTri >= 0) {
      for (int i = viTri; i < soThe - 1; i++) {
        memcpy(danhSach[i], danhSach[i + 1], 7);
        doDaiThe[i] = doDaiThe[i + 1];
      }
      soThe--;
      Serial.printf("Da XOA the %s, con %d the\n", chuoi, soThe);
    } else if (soThe < SO_THE_TOI_DA) {
      memcpy(danhSach[soThe], uid, n);
      doDaiThe[soThe++] = n;
      Serial.printf("Da THEM the %s, co %d the\n", chuoi, soThe);
    } else {
      Serial.println("Danh sach day, xoa bot the truoc");
    }
    luuDanhSach();
    return;
  }

  if (viTri >= 0) {
    Serial.printf("The %s (SAK 0x%02X): HOP LE, mo khoa %lu ms\n", chuoi, sak, (unsigned long)THOI_GIAN_MO_MS);
    relay(true);
    lucMo = millis();
  } else {
    Serial.printf("The %s (SAK 0x%02X): KHONG HOP LE\n", chuoi, sak);
  }
}

8. Giải thích các phần chính

  • Byte địa chỉ SPI: MFRC522 nhận địa chỉ thanh ghi dịch trái 1 bit, bit 7 bằng 1 là đọc, bằng 0 là ghi. Hàm ghi() và doc() làm đúng việc này.
  • khoiDongRc522(): reset chip, đặt bộ hẹn giờ trong chip chạy 40 kHz và đếm 1000 nhịp, tức 25 ms chờ thẻ trả lời. Sau đó bật điều chế ASK 100 % của chuẩn 14443A và bật anten.
  • traoDoi(): đổ byte vào bộ đệm FIFO 64 byte của chip, ra lệnh Transceive, chờ cờ nhận xong hoặc hết giờ rồi đọc bộ đệm. Thanh ghi BitFramingReg quy định byte cuối gửi mấy bit; REQA chỉ gửi 7 bit.
  • chonCap(): chống va chạm rồi SELECT ở một cấp, kiểm tra BCC trước khi chọn.
  • docUid(): gọi cấp 1, thấy 0x88 thì gọi tiếp cấp 2. Kết quả là UID 4 hoặc 7 byte.
  • Danh sách thẻ lưu bằng Preferences vào bộ nhớ flash, mất điện vẫn còn. Giữ nút BOOT trên mạch trong lúc quẹt: thẻ chưa có thì được thêm, thẻ đã có thì bị xóa. Cách dùng Preferences có trong bài lưu dữ liệu vào flash của ESP32.
  • Relay đóng 3 giây rồi tự nhả, đo bằng millis() nên vẫn đọc được thẻ khác trong lúc đó. Module relay phổ biến đóng khi chân IN ở mức thấp; module của bạn đóng ở mức cao thì đổi RELAY_KICH_MUC_THAP thành false.

9. Kết quả trên Serial Monitor

Serial Monitor in phiên bản chip RC522, mã thẻ, SAK, thông báo thẻ không hợp lệ, đã thêm thẻ và mở khóa 3000 ms
Quẹt thẻ lạ, giữ BOOT để thêm thẻ, quẹt lại thì mở khóa (hình minh họa, số liệu ví dụ).

Dòng đầu in phiên bản chip đọc từ thanh ghi VersionReg: 0x91, 0x92 là chip chính hãng; một số module dùng chip tương thích cho 0x12, 0x88 hoặc 0xB2 và vẫn chạy bình thường. Đọc ra 0x00 hoặc 0xFF là ESP32 không nói chuyện được với chip.

10. Mã thẻ không phải mật khẩu

UID chỉ là số nhận dạng, ai cũng đọc được, và có loại thẻ "đổi được UID" bán rất rẻ để chép mã của thẻ khác. Vì vậy khóa chỉ so UID hợp với tủ đồ, cổng phòng thí nghiệm, đồ án. Cửa cần an toàn thật thì dùng thẻ có mã hóa như MIFARE DESFire, hoặc kết hợp thêm mã PIN như bài dự án khóa cửa RFID và bàn phím trong cùng loạt.

Relay đóng cắt khóa điện 12 V hay thiết bị 220 V thì đọc kỹ bài ESP32 điều khiển module relay an toàn trước khi đấu: phần điện lưới phải có vỏ cách điện, cầu chì, và người có chuyên môn đấu nối.

11. Xử lý lỗi thường gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
"Khong doc duoc RC522" (phiên bản 0x00 hoặc 0xFF)Sai dây SPI, quên nối RST, hàn chân module chưa ăn thiếcKiểm tra từng dây theo bảng; hàn lại hàng chân module
Đọc được phiên bản nhưng không thấy thẻThẻ 125 kHz, đặt thẻ xa quá 3 cm, module nằm trên mặt kim loạiDùng thẻ 13,56 MHz đi kèm; áp thẻ sát giữa cuộn anten
Chip nóng, chạy lúc được lúc khôngCấp 5 V vào chân 3.3VChỉ cấp 3,3 V; chip có thể đã hỏng nếu cấp 5 V lâu
Mã thẻ NFC bắt đầu bằng 88Chương trình chỉ đọc cấp 1Đọc thêm cấp 2 như hàm docUid()
Quẹt một lần in nhiều dòngKhông gửi HLTA, thẻ cứ trả lời REQAGọi choTheNghi() sau khi đọc
ESP32 tự khởi động lại khi relay đóngCuộn relay làm sụt nguồnCấp nguồn 5 V riêng cho relay, nối chung GND; xem bài relay làm ESP32 bị reset

12. Xem thêm


Tin tức liên quan

Module 4G A7680C với ESP32: gửi SMS, bật tắt relay bằng tin nhắn, vì sao SIM800L không còn dùng được
Module 4G A7680C với ESP32: gửi SMS, bật tắt relay bằng tin nhắn, vì sao SIM800L không còn dùng được

Sóng 2G ở Việt Nam đã tắt nên SIM800L không còn dùng được; chọn module 4G thay thế, nối A7680C với ESP32, dùng lệnh AT gửi SMS, nhận lệnh từ số chủ máy để bật tắt relay.

Phased array siêu âm 40 kHz với ESP32 (phần 1): nguyên lý lái búp sóng và tạo 8 kênh lệch pha
Phased array siêu âm 40 kHz với ESP32 (phần 1): nguyên lý lái búp sóng và tạo 8 kênh lệch pha

Nguyên lý phased array và cách dùng ESP32 tạo 8 kênh xung 40 kHz lệch pha để lái búp sóng siêu âm: công thức độ trễ, giới hạn do khoảng cách đầu phát, cấu hình LEDC với hpoint, sơ đồ nối và code.

Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny
Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny

Hướng dẫn cài driver, cài Thonny và nạp firmware MicroPython cho mạch ESP32 Lite, dùng REPL, nháy LED, lưu main.py, đọc nút nhấn, quét I2C và xử lý lỗi thường gặp.

Đưa ESP32 và relay 4 kênh vào Home Assistant bằng MQTT discovery, không viết YAML
Đưa ESP32 và relay 4 kênh vào Home Assistant bằng MQTT discovery, không viết YAML

ESP32 tự gửi tin cấu hình MQTT discovery, Home Assistant tạo 4 công tắc relay và cảm biến Wi-Fi; Mosquitto, tài khoản, di chúc, retain và bảng xử lý lỗi.

ESP32 điều khiển động cơ bước 28BYJ-48 với ULN2003: nửa bước, quay đúng góc, kim giây
ESP32 điều khiển động cơ bước 28BYJ-48 với ULN2003: nửa bước, quay đúng góc, kim giây

Hướng dẫn điều khiển động cơ bước 28BYJ-48 bằng ESP32 qua mạch ULN2003: chế độ nửa bước, 4076 bước một vòng, quay đúng góc theo lệnh, kim giây đồng hồ và thí nghiệm STEM.

Đồng hồ và dự báo thời tiết trên OLED: ESP32 lấy dữ liệu Open-Meteo qua HTTP API
Đồng hồ và dự báo thời tiết trên OLED: ESP32 lấy dữ liệu Open-Meteo qua HTTP API

ESP32 lấy giờ NTP và thời tiết Open-Meteo qua HTTPS mỗi 15 phút, đọc JSON bằng ArduinoJson, hiện nhiệt độ, độ ẩm, gió và dự báo 3 ngày trên OLED 0.96 inch.

Vẽ đồ thị dữ liệu ESP32 trên ThingSpeak: theo dõi pin 18650, ngủ sâu và ước tính thời gian chạy pin
Vẽ đồ thị dữ liệu ESP32 trên ThingSpeak: theo dõi pin 18650, ngủ sâu và ước tính thời gian chạy pin

ESP32 chạy pin 18650 thức dậy mỗi 10 phút, đo điện áp pin, gửi lên ThingSpeak rồi ngủ sâu; cách tạo kênh, ước tính thời gian chạy pin và giảm dòng lúc ngủ.

Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266
Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266

Hướng dẫn nối OLED SSD1306 với ESP8266, quét địa chỉ I2C, cài thư viện và hiển thị thời gian bằng mã Arduino mẫu.


Bình luận
Đã thêm vào giỏ hàng