Lập trình

Cấu trúc chương trình Arduino - setup, loop và vòng đời một dòng code

6 phút đọc05/07/2026HocLapTrinhRobot
Cấu trúc chương trình Arduino - setup, loop và vòng đời một dòng code

Giải phẫu chương trình Arduino - setup chạy một lần, loop lặp mãi mãi, khai báo toàn cục, thứ tự thực thi và các mẫu tổ chức code robot sạch sẽ. 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.

Chương trình Arduino tối thiểu

Mọi chương trình Arduino/ESP32 (gọi là sketch) đều xoay quanh hai hàm bắt buộc:

cpp
void setup() {
  // chạy MỘT LẦN khi cấp nguồn hoặc nhấn reset
}

void loop() {
  // chạy LẶP LẠI mãi mãi, hết dòng cuối quay về dòng đầu
}

Thiếu một trong hai là lỗi biên dịch — chúng là hợp đồng giữa bạn và framework. Không có hàm main quen thuộc của C++ chuẩn? Thực ra có, Arduino giấu nó đi: main thật gọi setup() một lần rồi gọi loop() trong vòng while(true) — framework làm phần nhàm chán để người học tập trung vào logic.

Vòng đời chương trình từ lúc cắm điện

  1. Cấp nguồn / reset: bootloader chạy vài trăm ms (kiểm tra có ai muốn nạp code mới không)
  2. Khởi tạo biến toàn cục: các dòng khai báo ngoài hàm được thực thi
  3. setup() chạy một lần: nơi cấu hình chân, mở Serial, kết nối WiFi, hiệu chuẩn cảm biến
  4. loop() lặp vô hạn: trái tim của robot — mỗi vòng đọc cảm biến, quyết định, hành động
  5. Không có "kết thúc chương trình" — robot chạy đến khi mất điện. Muốn "dừng hẳn": while(true) delay(100);

Giải phẫu một sketch robot hoàn chỉnh

cpp
// ===== 1. VÙNG KHAI BÁO TOÀN CỤC =====
#include <Wire.h>                  // nạp thư viện

const int LED = 2;                 // hằng số - số chân không bao giờ đổi
const int SAFE_CM = 20;

int speed = 150;                   // biến toàn cục - mọi hàm đều thấy
long lastPing = 0;

// ===== 2. SETUP - CHUẨN BỊ =====
void setup() {
  Serial.begin(115200);            // mở kênh debug TRƯỚC TIÊN
  pinMode(LED, OUTPUT);            // khai báo vai trò từng chân
  Wire.begin();                    // khởi động bus I2C
  Serial.println("Robot san sang!");
}

// ===== 3. LOOP - CHU TRINH SENSE-THINK-ACT =====
void loop() {
  long distance = readDistance();  // SENSE: đọc cảm biến

  if (distance < SAFE_CM) {        // THINK: ra quyết định
    avoidObstacle();               // ACT: hành động
  } else {
    goForward();
  }
}

// ===== 4. HÀM TỰ ĐỊNH NGHĨA =====
long readDistance() {
  // ... đo và trả về khoảng cách
  return 100;
}

void goForward() { /* ... */ }
void avoidObstacle() { /* ... */ }

Bốn vùng này là khung xương của mọi sketch từ nhấp nháy LED đến robot thi đấu. Giữ đúng trật tự giúp người khác (và chính bạn sau 3 tháng) đọc hiểu ngay.

Những quy tắc ngầm cần biết sớm

Biến toàn cục vs biến cục bộ

Biến khai ngoài hàm sống mãi và mọi hàm thấy được; biến khai trong hàm sinh ra rồi chết theo mỗi lần gọi. Bẫy kinh điển của loop:

cpp
void loop() {
  int count = 0;      // SAI Ý ĐỒ: sinh lại = 0 ở MỖI vòng loop!
  count++;
  Serial.println(count);   // in mãi số 1
}

Muốn đếm qua các vòng: đưa count ra toàn cục, hoặc dùng static int count = 0; trong hàm (giữ giá trị giữa các lần gọi).

pinMode trước khi dùng chân

digitalWrite vào chân chưa pinMode(OUTPUT) không báo lỗi — chỉ âm thầm không chạy đúng. Kỷ luật: mọi chân dùng đến đều khai trong setup, nhóm gọn một chỗ.

delay() - tiện nhưng là thuốc độc liều cao

delay(1000) đóng băng toàn bộ chương trình: không đọc cảm biến, không nhận lệnh Bluetooth, không gì cả trong 1 giây. Robot tránh vật cản có delay(500) trong loop sẽ "mù" nửa giây mỗi vòng — đủ đâm tường ở tốc độ cao. Mẫu thay thế bằng đồng hồ millis():

cpp
unsigned long lastBlink = 0;

void loop() {
  // việc 1: nháy LED mỗi 500ms - KHÔNG chặn
  if (millis() - lastBlink >= 500) {
    digitalWrite(LED, !digitalRead(LED));
    lastBlink = millis();
  }
  // việc 2: đọc cảm biến chạy song song, không bị LED cản
  checkSensors();
}

Mẫu millis() này là kỹ năng phân thủy giữa người mới và người viết được robot đa nhiệm — đáng đầu tư hiểu sâu ngay từ tháng thứ hai. Các khái niệm biến và vòng lặp nền tảng có trong bài Biến, vòng lặp, điều kiện ở mục Kiến thức.

Kiểu dữ liệu hay gặp trên robot

KiểuPhạm viDùng cho
int±32 nghìn (Uno) / ±2 tỷ (ESP32)Đa dụng: tốc độ, số đếm nhỏ
long±2 tỷSố đếm encoder, thời gian tính toán
unsigned long0 đến 4.2 tỷBắt buộc cho millis()/micros()
floatSố thực ~7 chữ sốKhoảng cách, góc, hệ số PID
booltrue/falseCờ trạng thái
byte / uint8_t0-255Duty PWM, dữ liệu truyền

Bẫy đáng nhớ: lưu millis() vào int — tràn số sau ~32 giây trên Uno, robot "hóa điên đúng nửa phút sau khi bật" — lỗi huyền thoại của mọi thế hệ người học.

Tổ chức code khi chương trình phình to

Sketch 300 dòng một file bắt đầu khó thở. Ba nấc tiến hóa:

  1. Tách hàm theo động từ: readLine(), computePid(), driveMotors() — loop chỉ còn 5-10 dòng kể chuyện
  2. Tách file .h trong cùng sketch: motor.h chứa mọi hàm động cơ; Arduino IDE tự ghép các tab
  3. Máy trạng thái (state machine) cho robot nhiều chế độ: biến state + switch-case thay rừng if lồng nhau — mẫu chuẩn cho robot thi đấu có các pha đi-tìm-gắp-thả

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

Code trước setup() được không?

Khai báo (biến, hằng, include, định nghĩa hàm) — được và nên. Câu lệnh thực thi (digitalWrite, Serial.print) — không; lệnh phải nằm trong hàm nào đó.

Vì sao Serial.begin thường là dòng đầu tiên của setup?

Mọi thứ sau nó mới in log được — kể cả log lỗi khởi tạo của chính các bước setup tiếp theo. Mở kênh liên lạc trước khi làm việc là thói quen của người debug giỏi. Khai thác Serial sâu hơn ở bài Debug bằng Serial Monitor trong mục Kiến thức.

loop() chạy nhanh cỡ nào?

Loop rỗng trên ESP32: hàng trăm nghìn vòng mỗi giây. Thêm đọc cảm biến và tính toán: vài trăm đến vài nghìn vòng/giây — vẫn thừa cho robot. Thứ giết tốc độ loop là delay() và Serial.print dày đặc, không phải phép tính.

Hai việc chạy "cùng lúc" thật sự được không?

Trên một nhân, mẫu millis() là "luân phiên rất nhanh" — đủ cho mọi robot học tập. ESP32 có 2 nhân và FreeRTOS cho song song thật (xTaskCreate) — kỹ thuật nâng cao đáng học khi dự án có WiFi nặng chạy cạnh vòng điều khiển động cơ nhạy thời gian.

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