ATtiny13 làm mạch đếm số lần sử dụng khóa thiết bị

17/05/2026
attiny13-lam-mach-dem-so-lan-su-dung-khoa-thiet-bi

ATtiny13 đủ dùng để làm mạch đếm số lần sử dụng (usage limiter) kiểu: dùng được N lần → hết lượt → khóa thiết bị.

Mình sẽ hướng dẫn theo hướng thực tế nhất để bạn có thể làm ngay.


1️⃣ Nguyên lý mạch đếm số lần sử dụng

Ví dụ ứng dụng:

  • Thiết bị chỉ cho bật 50 lần
  • Mỗi lần bật → giảm 1
  • Khi = 0 → khóa relay / khóa tín hiệu Enable

ATtiny13 sẽ làm nhiệm vụ:

  1. Nhận tín hiệu "thiết bị được bật"
  2. Giảm biến đếm
  3. Lưu vào EEPROM (để mất điện không mất số lần)
  4. Nếu = 0 → ngắt tín hiệu điều khiển

👉 Đây chính là “DRM phần cứng” 😄


2️⃣ Sơ đồ chân ATtiny13 dùng

ATtiny13 có 6 I/O là quá đủ.

Đề xuất dùng:

Chân Chức năng
PB0 Input phát hiện thiết bị ON
PB1 Output điều khiển Relay / Enable
PB2 LED báo hết lượt
PB3 Nút reset kỹ thuật (tuỳ chọn)
PB4 Không dùng / debug

3️⃣ Logic hoạt động

Flow:

Khi cấp nguồn:
    đọc số lần còn lại từ EEPROM

Nếu số lần = 0:
    khóa relay vĩnh viễn

Nếu còn lượt:
    chờ tín hiệu thiết bị bật (PB0 = HIGH)

    khi phát hiện bật lần đầu:
        giảm counter --
        lưu EEPROM
        cho phép relay hoạt động

⚠️ Quan trọng: phải chống đếm lặp khi thiết bị đang bật.


4️⃣ Cài Arduino core cho ATtiny13

Trong Arduino IDE cài core:

  • ATtinyCore by Spence Konde

Chọn:

  • Chip: ATtiny13
  • Clock: 9.6 MHz internal

5️⃣ Code hoàn chỉnh

Đây là code chuẩn mình đã tối ưu EEPROM + chống ghi quá nhiều.

Khai báo

#include <EEPROM.h>

#define PIN_TRIGGER 0   // PB0 - phát hiện thiết bị bật
#define PIN_RELAY   1   // PB1 - cho phép chạy
#define PIN_LED     2   // PB2 - báo hết lượt
#define PIN_RESET   3   // PB3 - reset kỹ thuật

#define MAX_USE 50      // số lần cho phép

uint8_t counter;
bool alreadyTriggered = false;

Setup

void setup() {
  pinMode(PIN_TRIGGER, INPUT);
  pinMode(PIN_RELAY, OUTPUT);
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_RESET, INPUT_PULLUP);

  counter = EEPROM.read(0);

  // Lần đầu chưa ghi EEPROM → set mặc định
  if(counter == 0xFF){
    counter = MAX_USE;
    EEPROM.write(0, counter);
  }

  if(counter == 0){
    lockDevice();
  }
}

Loop chính

void loop() {

  // nút reset kỹ thuật (ẩn)
  if(digitalRead(PIN_RESET) == LOW){
    counter = MAX_USE;
    EEPROM.write(0, counter);
  }

  // nếu đã hết lượt → khóa
  if(counter == 0){
    lockDevice();
    return;
  }

  bool trigger = digitalRead(PIN_TRIGGER);

  // phát hiện cạnh lên (thiết bị vừa bật)
  if(trigger && !alreadyTriggered){
      alreadyTriggered = true;
      useOnce();
  }

  if(!trigger){
      alreadyTriggered = false;
  }
}

Hàm trừ lượt sử dụng

void useOnce(){
  if(counter > 0){
    counter--;
    EEPROM.write(0, counter);
  }
}

Hàm khóa thiết bị

void lockDevice(){
  digitalWrite(PIN_RELAY, LOW);  // ngắt relay
  digitalWrite(PIN_LED, HIGH);   // báo hết lượt
  while(1);                      // khóa vĩnh viễn
}

6️⃣ Cách đấu mạch thực tế

Relay enable kiểu này:

Thiết bị ---- Relay ---- nguồn
                 ↑
              PB1 điều khiển

PB0 nhận tín hiệu:

  • lấy từ công tắc nguồn
  • hoặc từ tín hiệu MCU thiết bị

7️⃣ Nâng cấp chống hack (rất nên làm)

Nếu làm sản phẩm bán:

✔ Dùng wear leveling EEPROM

✔ Mã hóa counter

✔ Disable ISP sau khi nạp

Ví dụ mã hóa đơn giản:

EEPROM = counter XOR 0x5A

Người khác đọc EEPROM sẽ không hiểu.


8️⃣ Ý tưởng nâng cao

Có thể thêm:

  • đếm giờ sử dụng thay vì số lần
  • mở khóa bằng key bí mật
  • lưu lịch sử lỗi
  • tự huỷ khi bị nạp lại firmware 😈

1️⃣ Wear leveling EEPROM (cực quan trọng)

Vì sao cần?

EEPROM ATtiny13 chỉ ~100.000 lần ghi / 1 ô.

Nếu mỗi lần bật thiết bị bạn ghi 1 lần → chết EEPROM rất nhanh.

👉 Giải pháp: xoay vòng nhiều ô EEPROM

ATtiny13 có 64 byte EEPROM
Ta dùng 32 ô → tuổi thọ tăng 32 lần


Ý tưởng

Không ghi đè địa chỉ 0 nữa.
Thay vào đó tạo "vòng tròn" lưu counter.

 
[slot0] [slot1] [slot2] ... [slot31]

Mỗi lần dùng:

  • tìm slot mới nhất
  • ghi sang slot tiếp theo

Cấu trúc dữ liệu lưu

Mỗi slot gồm 2 byte:

Byte Nội dung
0 counter đã mã hóa
1 checksum

👉 Tổng dùng 64 byte = full EEPROM.


Hàm checksum siêu nhẹ

 
uint8_t checksum(uint8_t v){
  return v ^ 0xA5;
}

Mã hóa counter (encryption nhẹ)

Không cần crypto nặng. Chỉ cần làm người thường đọc không hiểu.

 
uint8_t encode(uint8_t v){
  return v ^ 0x5A;
}

uint8_t decode(uint8_t v){
  return v ^ 0x5A;
}

Tìm slot mới nhất khi khởi động

 
#define EEPROM_SLOTS 32

uint8_t currentSlot = 0;

void loadCounter(){
  uint8_t lastValidSlot = 255;

  for(uint8_t i=0;i<EEPROM_SLOTS;i++){
    uint8_t data = EEPROM.read(i*2);
    uint8_t crc  = EEPROM.read(i*2+1);

    if(crc == checksum(data)){
        lastValidSlot = i;
    }
  }

  if(lastValidSlot == 255){
      counter = MAX_USE;
      saveCounter();
      return;
  }

  currentSlot = lastValidSlot;
  counter = decode( EEPROM.read(currentSlot*2) );
}

Hàm ghi wear leveling

 
void saveCounter(){
  currentSlot++;
  if(currentSlot >= EEPROM_SLOTS) currentSlot = 0;

  uint8_t data = encode(counter);

  EEPROM.write(currentSlot*2, data);
  EEPROM.write(currentSlot*2+1, checksum(data));
}

👉 Bây giờ EEPROM sống hàng chục năm.


2️⃣ Mã hóa counter (anti copy EEPROM)

Nếu không mã hóa:

  • hacker đọc EEPROM
  • copy sang chip mới
    → reset số lần

Sau khi mã hóa:

  • dữ liệu không có nghĩa
  • không biết byte nào là slot mới

Bạn có thể nâng level:

Thêm KEY riêng cho từng lô sản phẩm

 
#define SECRET_KEY 0x3C

uint8_t encode(uint8_t v){
  return v ^ SECRET_KEY ^ 0x5A;
}

👉 Mỗi batch sản phẩm 1 KEY khác nhau.


3️⃣ Disable ISP sau khi nạp (quan trọng nhất)

Đây là bước chống đọc firmware.

Sau khi test OK → set fuse lock bits.


Lock bits ATtiny13

Có 2 mức khóa:

Level Ý nghĩa
LB1 Không đọc flash
LB2 Không đọc + không ghi

👉 Dùng Mode 3 (full lock)


Burn fuse bằng Arduino as ISP

Sau khi nạp code xong chạy lệnh:

 
avrdude -p t13 -c usbasp -U lock:w:0xFC:m

Hoặc trong Arduino IDE:

 
Tools → Burn Bootloader

(ATtinyCore sẽ set lock bits)


Sau khi lock:

Người khác sẽ:

  • ❌ không đọc được code
  • ❌ không ghi lại chip
  • ❌ không reset counter bằng ISP

Muốn hack → phải thay chip mới 😄


4️⃣ Nâng cấp chống thay chip (pro level)

Nếu muốn hardcore hơn:

Thêm kiểm tra signature chip

 
if(SIGRD != 0x1E9007) selfDestruct();

Hoặc:

  • kiểm tra voltage glitch
  • kiểm tra debugWire

Nhưng thường 3 bước trên là đủ bán sản phẩm.


5️⃣ Tóm tắt mức bảo mật sau khi áp dụng

Bảo vệ Kết quả
Wear leveling EEPROM bền 20–30 năm
Mã hóa Không copy counter
Lock fuse Không đọc firmware
Checksum Không sửa EEPROM

👉 Với ATtiny13 là mức bảo mật rất tốt.

Bình luận
Nội dung này chưa có bình luận, hãy gửi bình luận đầu tiên của bạn.
VIẾT BÌNH LUẬN CỦA BẠN