NTM Solutions

Chủ Nhật, 9 tháng 8, 2026

🥤 Bài 6 — Thiết kế phần mềm điều khiển bằng State Machine

Ở bài trước, chúng ta đã có:

  • ESP32

  • Động cơ

  • Driver

  • Encoder

  • Cảm biến rơi sản phẩm

Nếu tiếp tục viết chương trình theo kiểu:

if(...)
{
    ...
}

if(...)
{
    ...
}

if(...)
{
    ...
}

Thì chỉ sau vài ngày, chương trình sẽ trở nên rất khó đọc và khó bảo trì.

Các máy bán hàng thương mại không lập trình như vậy.

Họ sử dụng một mô hình gọi là State Machine (Máy trạng thái).


Mục tiêu

Sau bài này bạn sẽ:

  • Hiểu State Machine là gì.

  • Biết cách chia chương trình thành từng trạng thái.

  • Thiết kế phần mềm giống máy bán hàng thương mại.

  • Viết bộ khung chương trình đầu tiên.


State Machine là gì?

Hãy tưởng tượng một chiếc thang máy.

Nó không thể:

  • mở cửa

  • chạy

  • đóng cửa

cùng một lúc.

Nó luôn ở một trạng thái xác định.

Ví dụ:

Đang chờ

↓

Đóng cửa

↓

Di chuyển

↓

Mở cửa

↓

Chờ

Máy bán nước cũng vậy.


Máy bán nước có bao nhiêu trạng thái?

Phiên bản mini của chúng ta sẽ có khoảng 8 trạng thái.

IDLE

↓

SELECT

↓

PAYMENT

↓

DISPENSE

↓

CHECK

↓

SUCCESS

↓

IDLE

Nếu có lỗi:

CHECK

↓

ERROR

↓

IDLE

Ý nghĩa từng trạng thái

IDLE

Máy đang chờ.

Màn hình hiển thị:

Xin chào

Mời chọn sản phẩm

Động cơ:

Không chạy.


SELECT

Khách chọn:

A1

B2

C3

D4

ESP32 lưu lại:

selectedProduct = A1;

PAYMENT

Máy chờ thanh toán.

Có thể:

  • QR

  • NFC

  • Tiền mặt

Nếu thanh toán thành công:

DISPENSE


DISPENSE

Motor bắt đầu quay.

Motor

↓

Spiral

↓

Lon tiến lên

Encoder bắt đầu đếm.


CHECK

Motor đã dừng.

ESP32 chờ:

IR Sensor

↓

Có lon chưa?

Nếu:

Có.

SUCCESS

Nếu:

Không.

ERROR


SUCCESS

Hiển thị:

Cảm ơn!

Chúc ngon miệng.

Sau vài giây:

IDLE


ERROR

Ví dụ:

Sản phẩm bị kẹt.

Hoặc:

Hết hàng.

Máy sẽ:

  • Báo lỗi

  • Hoàn tiền (nếu có)

  • Gửi log


Sơ đồ hoạt động

           +------+
           |IDLE  |
           +------+
               |
               v
        +-------------+
        |SELECT       |
        +-------------+
               |
               v
        +-------------+
        |PAYMENT      |
        +-------------+
               |
               v
        +-------------+
        |DISPENSE     |
        +-------------+
               |
               v
        +-------------+
        |CHECK        |
        +-------------+
          |        |
     Có lon      Không
          |        |
          v        v
   +---------+  +-------+
   |SUCCESS  |  |ERROR  |
   +---------+  +-------+
          \      /
           \    /
            v  v
          +------+
          |IDLE  |
          +------+

Khai báo State

Trong Arduino:

enum State
{
    IDLE,
    SELECT,
    PAYMENT,
    DISPENSE,
    CHECK,
    SUCCESS,
    ERROR_STATE
};

State currentState = IDLE;

Vòng lặp chính

void loop()
{

    switch(currentState)
    {

        case IDLE:
            break;

        case SELECT:
            break;

        case PAYMENT:
            break;

        case DISPENSE:
            break;

        case CHECK:
            break;

        case SUCCESS:
            break;

        case ERROR_STATE:
            break;

    }

}

Đây chính là "bộ não" của chiếc máy.


Ví dụ

Khách nhấn:

A2

ESP32:

currentState = SELECT;

Thanh toán thành công.

currentState = PAYMENT;

currentState = DISPENSE;

currentState = CHECK;

currentState = SUCCESS;

currentState = IDLE;

Không dùng delay()

Thay vì:

delay(5000);

Ta dùng:

unsigned long timer;

Ví dụ:

if(millis()-timer>5000)
{
    currentState = IDLE;
}

Ưu điểm:

Máy vẫn:

  • đọc QR

  • đọc nút bấm

  • cập nhật màn hình

  • gửi WiFi

trong khi đang chờ.


Tách chương trình

Không nên viết:

main.cpp

3000 dòng

Hãy chia:

src

├── main.cpp

├── motor.cpp

├── motor.h

├── payment.cpp

├── payment.h

├── display.cpp

├── display.h

├── sensor.cpp

├── sensor.h

├── vending.cpp

└── vending.h

Sau này dự án lớn hơn vẫn rất dễ quản lý.


Nhật ký hoạt động (Log)

Mỗi giao dịch nên lưu:

20:31:20

A1

QR

SUCCESS

Hoặc:

20:35:02

B3

ERROR

NO PRODUCT

Nhật ký giúp:

  • tìm lỗi

  • thống kê doanh thu

  • kiểm tra bảo trì


Mở rộng trong tương lai

Khi đã có State Machine, việc thêm tính năng sẽ rất đơn giản:

IDLE

↓

UPDATE

↓

DOWNLOAD

↓

RESTART

Hoặc:

IDLE

↓

ADMIN MODE

↓

TEST MOTOR

↓

TEST SENSOR

Bạn không cần viết lại toàn bộ chương trình.


Kiến trúc hoàn chỉnh

           Người dùng
                │
                ▼
        Màn hình cảm ứng
                │
                ▼
           State Machine
                │
      ┌─────────┼─────────┐
      ▼         ▼         ▼
   Payment    Motor    Sensor
      │         │         │
      └─────────┼─────────┘
                ▼
              Display
                │
                ▼
             WiFi / Cloud

Đây là mô hình thường gặp trong các thiết bị IoT hiện đại, giúp mỗi thành phần có một nhiệm vụ rõ ràng và dễ bảo trì.


Bài tập

  1. Tạo enum State với 7 trạng thái như trong bài.

  2. Viết switch(currentState) trong loop().

  3. Mỗi trạng thái chỉ cần Serial.println() tên của nó để kiểm tra luồng hoạt động.

  4. Thử mô phỏng một giao dịch hoàn chỉnh:

    • IDLE → SELECT → PAYMENT → DISPENSE → CHECK → SUCCESS → IDLE.

  5. Thử mô phỏng trường hợp lỗi:

    • IDLE → SELECT → PAYMENT → DISPENSE → CHECK → ERROR → IDLE.


Tổng kết

Đến bài này, bạn đã chuyển từ việc điều khiển từng linh kiện riêng lẻ sang thiết kế kiến trúc phần mềm cho cả hệ thống.

Bạn đã biết:

  • State Machine là gì và vì sao nó được dùng trong máy bán hàng tự động.

  • Cách chia chương trình thành các trạng thái rõ ràng.

  • Tổ chức mã nguồn theo từng module.

  • Sử dụng millis() thay cho delay() để hệ thống luôn phản hồi tốt.

Bài tiếp theo

🥤 Bài 7 — Thiết kế giao diện người dùng (UI/UX) trên màn hình cảm ứng

Chúng ta sẽ xây dựng một giao diện hiện đại theo phong cách Windows/Material Design, với màn hình chọn sản phẩm, giỏ hàng, trạng thái thanh toán, hiệu ứng chuyển cảnh và các nguyên tắc UX giúp khách hàng thao tác nhanh, trực quan và hạn chế nhầm lẫn khi sử dụng máy bán nước tự động.

Không có nhận xét nào:

Đăng nhận xét

Facebook Youtube RSS