Bài tập

Xử lý nhiều tiến trình cùng một lúc trên Arduino - Xử lý bất đồng bộ - Có thể hay không?

Huy Erick

Giới thiệu Bạn muốn viết một chương trình Arduino mà không sử dụng hàm delay? Bài viết này sẽ giới thiệu với bạn một thư viện đáng chú ý để giải quyết vấn đề này...

Giới thiệu

Bạn muốn viết một chương trình Arduino mà không sử dụng hàm delay? Bài viết này sẽ giới thiệu với bạn một thư viện đáng chú ý để giải quyết vấn đề này - xử lý nhiều tiến trình cùng một lúc trên Arduino.

Khởi nguồn

Từ khi là một newbie, tôi đã tự hỏi liệu có cách nào để viết chương trình Arduino mà không sử dụng hàm delay? Từ những chia sẻ của cộng đồng Arduino, tôi đã hiểu được vấn đề và tìm thấy một bài viết chia sẻ về điều đó. Vậy từ đâu tôi lại có suy nghĩ đó? Đó là vì khi sử dụng hàm delay, chương trình sẽ bị đứng cứng cho đến khi hàm delay chạy xong. Điều này gây khó khăn khi muốn thực hiện nhiều chức năng cùng một lúc trên một sản phẩm Arduino. Bạn có thể dễ dàng nhận thấy điều này qua đoạn chương trình điều khiển đèn LED sau.

int setup() {
  //Cài đặt các chân kết nối
}

int loop() {
  digitalWrite(led1, HIGH);
  digitalWrite(led2, HIGH);
  delay(500);
  digitalWrite(led2, LOW);
  delay(500);
  digitalWrite(led1, LOW);
  digitalWrite(led2, HIGH);
  delay(500);
  digitalWrite(led2, LOW);
  delay(500);
}

Câu hỏi đặt ra là liệu có cách nào rút gọn hơn? Vâng, tôi đã nghĩ ra ý tưởng sử dụng biến state để lưu trạng thái của LED và tinh chỉnh cho LED đó. Tuy nhiên, nếu chu kỳ không "đẹp" thì sẽ gặp vấn đề. Vậy nên tôi sử dụng hàm millis để kiểm tra thời điểm thực hiện các đoạn chương trình.

byte led1 = 5;
byte led2 = 6;
unsigned long time1 = 0;
unsigned long time2 = 0;

void setup() {
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
}

void loop() {
  if ((unsigned long)(millis() - time1) > 696) {
    if (digitalRead(led1) == LOW) {
      digitalWrite(led1, HIGH);
    } else {
      digitalWrite(led1, LOW );
    }
    time1 = millis();
  }
  if ((unsigned long)(millis() - time2) > 133) {
    if (digitalRead(led2) == LOW) {
      digitalWrite(led2, HIGH);
    } else {
      digitalWrite(led2, LOW );
    }
    time2 = millis();
  }
}

Như bạn có thể thấy, đoạn code này có những đoạn lệnh giống nhau, nhưng cách kiểm tra đúng thời gian để chạy lại cần được viết lại nhiều lần. Có cách nào rút gọn hơn không? Đúng vậy, thư viện của anh Đại Huỳnh đã giải quyết vấn đề này.

Cải tiến

Bài viết của anh Đại Huỳnh đã giải quyết hầu hết những vấn đề tôi đề cập, nhưng vẫn còn khá khó để sử dụng. Vì vậy, tôi đã cải tiến thư viện và viết nó dưới dạng thư viện để bạn có thể dễ dàng sử dụng trong các dự án của mình.

Trước khi tiếp tục, tôi khuyên bạn nên mở thêm một tab để đọc các bài viết về hàm millis() và lập trình các pin của anh Đại Huỳnh để có cái nhìn rõ hơn về các hàm (nếu bạn chưa hiểu).

Một lưu ý nhỏ trước khi tiếp tục là thư viện của tôi là sự cải tiến từ thư viện WorkScheduler của anh Đại Huỳnh, và tôi đã lược bỏ một số khái niệm phức tạp như lập trình hướng đối tượng và singleton để giúp bạn hiểu được vấn đề giao tiếp giữa các mạch Arduino một cách đơn giản.

Quên mất, thư viện của tôi là một phương pháp viết chương trình bất đồng bộ (asynchronous), không phải là một hệ điều hành thời gian thực (RTOS). Nó có nhược điểm và tôi sẽ nói về nó sau.

Phần cứng

Trước khi tiếp tục với các ví dụ, bạn cần chuẩn bị như sau:

  1. 01 mạch Arduino (dùng UNO cho dễ)
  2. 01 breadboard
  3. Chùm dây cắm breadboard

Các ví dụ

Qua các ví dụ dưới đây, tôi sẽ trình bày điểm mạnh và yếu của thư viện này (vì cách tiếp cận là xử lý không đồng bộ chứ không phải là xây dựng hệ điều hành thời gian thực).

1. Viết chương trình không đồng bộ một cách dễ dàng

Như bạn đã thấy trong đoạn code trên, để chương trình hoạt động, bạn cần khởi tạo đối tượng Timer (singleton).

void setup() {
  //Khởi tạo Timer (singleton) - bắt buộc phải có trong hàm setup (trước khi khởi tạo các job)
  Timer::getInstance()->initialize();
  //new WorkScheduler
  //...
}

void loop() {
  //Cập nhập thời điểm
  Timer::getInstance()->update();
  //...
}

Sau đó, bạn có thể khai báo các công việc (job) như trong ví dụ hướng dẫn. Hoặc bạn cũng có thể lên lịch cho một chân pin như trong bài viết của anh Đại Huỳnh.

Ưu điểm của thư viện này là cách tổ chức hàm loop đơn giản và dễ hiểu. Không còn phức tạp như trước đây!

2. Chạy nhiều job hơn và chu kỳ ngắn hơn

Bạn có thể thử chạy đoạn chương trình sau. Đèn LED 13 sẽ nhấp nháy rất nhanh với chu kỳ 100ms. Tuy nhiên, bạn sẽ thấy rằng nó tự động tắt đi ngẫu nhiên. Điều này là do chân A0 không được nối với bất kỳ điều gì, khiến giá trị tại chân này nhảy lên xuống. Để giữ cho đèn nhấp nháy liên tục, bạn có thể kết nối chân A0 với 5V hoặc 3.3V.

Lưu ý: Thư viện này không thể thay thế được interrupt và nếu có quá nhiều job với chu kỳ quá nhỏ, chương trình có thể hoạt động không đúng.

Nấu code vui vẻ cuối tuần nhé!

Đến đây, tôi tin rằng bạn đã hiểu được cách viết chương trình bất đồng bộ và lợi ích của việc sử dụng thư viện này. Chúc bạn thành công và hãy trải nghiệm viết code vui vẻ vào cuối tuần!

1