Điều khiển nâng cao

Máy trạng thái (state machine) - kiến trúc code cho robot nhiều chế độ

6 phút đọc05/07/2026HocLapTrinhRobot
Máy trạng thái (state machine) - kiến trúc code cho robot nhiều chế độ

Thay rừng if-else bằng máy trạng thái - thiết kế sơ đồ trạng thái, code switch-case chuẩn, xử lý chuyển tiếp và ví dụ robot thi đấu tìm-gắp-thả hoàn chỉnh. Trong bài viết này, chúng ta sẽ cùng tìm hiểu chi tiết và ứng dụng thực tế trong robot.

Khi if-else bắt đầu phản chủ

Robot của bạn lớn dần: vừa dò line, vừa tránh vật cản, vừa nghe Bluetooth, gặp giao lộ thì rẽ theo bản đồ, hết pin thì về trạm. Code viết kiểu chồng if lồng nhau nhanh chóng thành mê cung: sửa hành vi A làm hỏng hành vi B, thêm chế độ mới phải đọc lại toàn bộ, và không ai — kể cả tác giả — trả lời nổi câu "lúc này robot đang làm gì?".

Máy trạng thái hữu hạn (finite state machine - FSM) là lời giải kinh điển: tại mỗi thời điểm, robot ở đúng MỘT trạng thái; mỗi trạng thái có hành vi riêng và các điều kiện chuyển sang trạng thái khác được liệt kê tường minh. Đây là bước nhảy tư duy từ "viết code chạy được" sang "thiết kế hệ thống" — và là kiến trúc của firmware chuyên nghiệp từ máy giặt đến xe tự lái.

Ba khái niệm, một sơ đồ

  • Trạng thái (state): robot đang-là-gì — TÌM_VẠCH, BÁM_VẠCH, NÉ_VẬT, VỀ_ĐÍCH
  • Sự kiện/điều kiện chuyển (transition): điều gì khiến đổi trạng thái — "thấy vật cản dưới 15 cm", "mất vạch quá 500 ms"
  • Hành động (action): làm gì khi ở trong trạng thái, và có thể thêm hành động một-lần khi VÀO/RA trạng thái

Trước khi gõ code, vẽ sơ đồ trạng thái ra giấy — các ô tròn nối bằng mũi tên ghi điều kiện. Ví dụ robot dò line có né vật:

text
            mất vạch >500ms
  [BÁM_VẠCH] ----------------> [TÌM_VẠCH]
      |  ^                          |
 vật <15cm|  hết vật + thấy vạch    | thấy vạch
      v  |                          v
  [NÉ_VẬT] <----(không)         (về BÁM_VẠCH)

Sơ đồ này là tài liệu quý nhất của dự án: giáo viên nhìn là hiểu logic, đồng đội sửa được code của nhau, và lỗi "robot đứng đơ" truy ra ngay là kẹt ở trạng thái nào thiếu đường ra.

Mẫu code chuẩn - enum + switch + millis

cpp
enum RobotState { FOLLOW_LINE, AVOID_OBSTACLE, FIND_LINE, FINISHED };
RobotState state = FOLLOW_LINE;
unsigned long stateEntered = 0;    // thời điểm vào trạng thái hiện tại

void changeState(RobotState next) {
  state = next;
  stateEntered = millis();
  Serial.printf("-> trang thai %d\n", next);   // log chuyển tiếp - vàng khi debug
}

unsigned long inStateFor() { return millis() - stateEntered; }

void loop() {
  long distance = readDistanceCm();
  int linePos = readLinePosition();
  bool lineVisible = isLineVisible();

  switch (state) {
    case FOLLOW_LINE:
      runLinePid(linePos);                         // hành vi của trạng thái
      if (distance < 15) changeState(AVOID_OBSTACLE);
      else if (!lineVisible && inStateFor() > 300) changeState(FIND_LINE);
      break;

    case AVOID_OBSTACLE:
      runAvoidManeuver();                          // vòng cung né vật
      if (avoidManeuverDone()) changeState(FIND_LINE);
      break;

    case FIND_LINE:
      drive(120, 60);                              // quét vòng cung tìm vạch
      if (lineVisible) changeState(FOLLOW_LINE);
      else if (inStateFor() > 4000) changeState(FINISHED);  // bỏ cuộc an toàn
      break;

    case FINISHED:
      drive(0, 0);
      break;
  }
}

Bốn quy tắc làm nên FSM sạch:

  1. Mọi đổi trạng thái qua MỘT hàm changeState — có chỗ duy nhất để log, reset biến, đo thời gian trong trạng thái
  2. `inStateFor()` thay cho delay: "quét tìm vạch tối đa 4 giây" viết bằng so sánh thời gian, robot vẫn đọc cảm biến mỗi vòng — nguyên tắc millis từ bài Cấu trúc chương trình Arduino, nâng cấp có tổ chức
  3. Mỗi trạng thái phải có đường ra (kể cả đường "bỏ cuộc") — trạng thái không lối thoát là robot treo; rà sơ đồ giấy trước khi code là bắt được hết
  4. Hành vi gói thành hàm riêng (runLinePid, runAvoidManeuver) — switch chỉ còn là bảng điều phối đọc như văn xuôi

Ví dụ thực chiến - robot thi đấu tìm-gắp-thả

Đề bài phổ biến các giải: xuất phát, tìm vật, gắp, mang về vùng đích, thả, lặp lại. FSM hóa:

text
XUẤT_PHÁT → TÌM_VẬT → TIẾP_CẬN → GẮP → VỀ_ĐÍCH → THẢ → TÌM_VẬT (lặp)
                |________________________________________|
                     hết giờ 90s → VỀ_ĐÍCH (mang gì về nấy)

Cái hay của FSM lộ ra ở yêu cầu "hết giờ thì về": không FSM, luật này phải cấy vào mọi ngóc ngách code; có FSM, nó là MỘT điều kiện kiểm tra trước switch — if (millis() - matchStart > 90000 && state != VE_DICH && state != THA) changeState(VE_DICH);. Luật thi đấu đổi sát giờ? Sửa một dòng.

Kỹ thuật cộng thêm cho trận đấu: biến đếm objectsCollected, trạng thái GẮP có kiểm tra "gắp hụt" (quá 2 giây không thấy vật trong kẹp → quay lại TÌM_VẬT thay vì mang kẹp rỗng về đích) — chính các nhánh xử lý thất bại này phân biệt đội thắng với đội "demo được ở nhà".

FSM lồng nhau và các mở rộng

  • FSM phân tầng: trạng thái BÁM_VẠCH bên trong có FSM con (THẲNG / CUA_TRÁI / CUA_PHẢI theo độ cong) — tầng trên lo nhiệm vụ, tầng dưới lo kỹ thuật lái; giữ tối đa 2 tầng cho robot học đường, sâu hơn là rối ngược
  • Bảng chuyển tiếp: FSM lớn (10+ trạng thái) chuyển từ switch sang mảng struct {trạng thái, điều kiện, đích} — duyệt vòng lặp; đọc như bảng luật, thêm bớt không đụng code lõi
  • Đa FSM song song: FSM nhiệm vụ + FSM đèn báo hiệu + FSM kết nối Bluetooth chạy độc lập trong cùng loop — mỗi cái một biến state riêng, không can thiệp nhau

Câu hỏi thường gặp

Bao nhiêu trạng thái là quá nhiều?

Robot học đường: 3-7 trạng thái là vùng đẹp; trên 10 mà vẫn một tầng thì nên gom nhóm thành FSM phân tầng. Dấu hiệu thiết kế sai không phải số lượng mà là trạng thái mơ hồ: nếu không nói được một câu "robot đang X" cho mỗi trạng thái, hãy vẽ lại sơ đồ.

FSM có làm robot chậm đi không?

Ngược lại: switch-case nhảy thẳng đến nhánh đúng, nhanh hơn chuỗi if dài kiểm tra tuần tự. Chi phí thật của FSM là công thiết kế ban đầu — trả một lần, lãi suốt dự án.

Blockly có làm được máy trạng thái không?

Được: biến trangThai + khối "nếu-thì" theo giá trị biến là FSM đúng nghĩa. Phòng lab HocLapTrinhRobot dạy mẫu này ở các bài robot nhiều chế độ — học sinh nắm tư duy trạng thái từ kéo thả, sang C++ chỉ đổi vỏ enum + switch.

Khi nào KHÔNG cần FSM?

Robot một hành vi tuyến tính (dò line thuần, tránh vật thuần) — vòng sense-think-act trần là đủ, FSM lúc này là lễ nghi thừa. Ngưỡng chuyển đổi thực dụng: khi bạn viết đến biến cờ bool thứ ba (dangNe, dangTimVach, daGap...) để nhớ "đang làm gì" — đó chính là FSM tự phát đang đòi được đặt tên.

Tài nguyên tải về

Tải bản PDF của bài viết để in giáo án hoặc gửi trước cho học viên.

Tải PDF
Chia sẻ bài viết