Menu
CoddyTech

Khi millis() quay vòng

Thuộc phần Định thời không dùng delay trong hành trình Arduino của Coddy. Bài học 17 trên 18.

Một unsigned long dừng ở 4,294,967,295. Sau khoảng 49.7 ngày, millis() đạt đến giá trị đó rồi bắt đầu lại từ 0. Hiện tượng này gọi là tràn vòng.

Kiểu dữ liệu nhỏ hơn cũng cho thấy điều tương tự. Một unsigned int dừng ở 65535:

unsigned int a = 65530;
a = a + 10;   // 65540 vượt quá phạm vi: a giờ bằng 4

Phép trừ cũng quay vòng theo cách tương tự, nhờ đó phép tính này an toàn: một đồng hồ bắt đầu ở 65530 và hiện chỉ 4 đã đếm được 10 bước, và 4 trừ 65530, lưu trong một unsigned int, cho kết quả chính xác là 10. Tương tự, millis() - start luôn là khoảng thời gian tính từ start, ngay cả khi xảy ra tràn vòng.

Phép cộng không an toàn. Trong now >= start + interval, tổng có thể vượt quá giới hạn trên rồi quay về một số nhỏ, khiến bộ hẹn giờ lập tức báo đã xong. Còn khi now đã quay vòng nhưng tổng thì chưa, bộ hẹn giờ sẽ không bao giờ báo xong. Luôn dùng phép trừ:

if (now - start >= interval) {
  // hoàn tất
}
challenge icon

Thử thách

Trung bình

Không ai có thể chờ 49 ngày để kiểm tra, nên đoạn code này truyền vào isDone() các thời điểm gần lúc đồng hồ quay vòng rồi in ra kết quả. Hàm báo "done" quá sớm và báo "waiting" mãi mãi. Sửa dòng duy nhất trong isDone() để hàm so sánh thời gian đã trôi qua kể từ start với interval.

Nhấn Chạy, đọc bốn kết quả, sửa dòng đó, rồi nhấn Kiểm tra.

Tự làm thử

const unsigned long interval = 2000;

// Bộ đếm thời gian bắt đầu tại start đã kết thúc ở thời điểm now chưa?
bool isDone(unsigned long start, unsigned long now) {
  return now >= start + interval;   // Sửa dòng này
}

// In ra thời gian đã trôi qua và kết quả của isDone().
void test(unsigned long start, unsigned long now) {
  Serial.print(now - start);
  Serial.print(" ms: ");
  if (isDone(start, now)) {
    Serial.println("done");
  } else {
    Serial.println("waiting");
  }
}

void setup() {
  Serial.begin(9600);
  // millis() dừng ở 4294967295, rồi bắt đầu lại từ 0
  test(4294966000, 4294966500);   // ngay trước khi bộ đếm quay vòng
  test(4294966000, 4294967200);   // vẫn trước thời điểm đó
  test(4294966000, 900);          // đồng hồ đã vượt qua 0
  test(4294964000, 500);          // một bộ đếm thời gian bắt đầu sớm hơn
}

void loop() {
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Định thời không dùng delay

Tự luyện tập: Playground Arduino