millis() - Tạo 1 đồng hồ theo thời gian thực và Lịch làm việc cho các Pin

Lời chia sẻ đầu tiên thì tôi không phải học ngành Điện - Điện tử và chuyên ngành của tôi là Công nghệ phần mềm và lập trình Game ( rock devil). Tôi đam mê tìm hiểu về điện - điện tử và việc tìm đến Arduino đối với tôi thật tuyệt. Arduino có những hàm được viết sẵn làm cho công việc lập trình và làm quen thật nhanh nhưng về hiệu quả thì tôi không chắc lắm. Nếu ai đó đọc bài này mà đã học qua lập trình thì việc phải Sleep trên main thread là 1 điều không nên và ảnh hưởng rất nhiều đến hiệu suất của chương trình và hàm delay(int) trong Arduino cũng tương tự như thế. Vì tôi muốn tác vụ của tôi phải chạy thời gian thực, code ngắn gọn, hướng đối tượng nên tôi viết 1 lớp Timer cho riêng mình để hoạt động trong hàm loop() của tôi không phải nghỉ 1 khoảng rồi mới làm tiếp. Đồng thời tôi cũng viết thêm 1 lớp WorkScheduler để thuận tiện cho công việc lập lịch làm việc cho các pin mà tôi muốn làm việc.

  • Timer:
    • Sử dụng singleton để tất cả mọi hàm đều sử dụng 1 lần trong cả chương trình.
    • Yêu cầu initialize ( khởi tạo ) ban đầu ở hàm setup() của Arduino.
    • Yêu cầu gọi hàm update() đầu tiên trong hàm loop().
    • Yêu cầu gọi hàm resetTick() cuối cùng trong hàm loop().
    • Tôi xin giải thích về hàm Timer như sau:
      • Đầu tiên chúng ta sẽ khởi tao Timer bằng cách cho lastTick ( lần tick trước ) bằng thời gian trôi qua hiện tại trong Arduino và đó là lý do tại sao phải gọi hàm initialize() trong setup().
      • Ở hàm loop() mỗi lần bắt đầu phải gọi hàm update() để đánh dấu currentTick (lần tick hiện tại ) bằng thời gian trôi qua hiện tại trong Arduino để chúng ta có thể tính được delta giữa 2 lần loop() cũa Arduino. Thời gian delta phụ thuộc rất nhiều bằng rất nhiều vào logic trong hàm loop của bạn. Logic càng phức tạp thì thời gian delta giữa 2 lần loop() càng lớn. 
      • Ở cuối hàm loop() ta sẽ gọi resetTick() để cập nhật lastTick = currentTick để cho phép lần loop() sau tính delta()
class Timer {
  private:
    unsigned long _lastTick;
    unsigned long _currentTick;
    
    Timer() { }

    ~Timer() { }
  public:
  static Timer* getInstance() {
    static Timer* instance = new Timer();
    return instance; 
  }
  
  /*
   * Gọi trong hàm setup 1 lần duy nhất
   */
  void initialize() {
    _lastTick = millis(); 
  }
  
  /*
   * Gọi đầu tiên nhất trong hàm loop()
   */
  void update() {
    _currentTick = millis();
  }
  
  /*
   * Trả về thời gian giữa 2 lần loop()
   * giá trị delta là millisecond
   */
  unsigned long delta() {
    return _currentTick - _lastTick;
  }
  
  /*
   * Yêu cầu gọi cuối cùng trong hàm loop()
   */
  void resetTick() {
    _lastTick = _currentTick;
  }
};
  • WorkScheduler:
    • Ý tưởng của tôi khi viết lớp này là tôi muốn viết 1 lớp để lên lịch làm việc cho 1 pin trong Arduino sau 1 khoảng thời gian nhất định.
    • Ở lớp này tôi dùng còn trỏ hàm với dạng như sau void (*func)(int) có thể hiểu là đây là 1 hàm không trả về giá trị void và nhận 1 tham số là int là số pin trên Arduino mà ta muốn nó sẽ làm việc. Bạn có thể thay đổi tùy ý dựa trên nhu cầu làm việc của mình.
    • Tham số đầu vào cho hàm dựng của WorkScheduler của tôi yêu cầu 3 tham số là: số pin sẽ phải làm việc, thời gian sẽ làm việc, con trỏ hàm định nghĩa việc sẽ làm.
    • Hàm update() được gọi để cập nhật thời gian trôi qua, nếu thời gian trôi qua lờn hơn thời gian làm việc thì Pin sẽ phải làm việc rồi chúng ta sẽ reset lại thời gian trôi qua. Tại sao tôi không set là 0 mà lại dùng -= cho thời gian sẽ làm việc? Đó là do thời gian delta giữa các loop() không đều nhau dẫn đến thời gian trôi qua nhiều khi sẽ lố qua thời gian làm việc nên chúng ta sẽ có 1 cơ số thời gian dư thừa cần phải bù đắp lại cho lần làm việc sau, vì vậy tôi sẽ giữ lại những cơ số thừa đó cho những lần cập nhật sau để lịch làm việc chính xác hơn
class WorkScheduler {
  private:
    unsigned long _ellapsedTime;
    unsigned long _workTime;
    int _workPin;
    void (*func)(int);
  public:
    WorkScheduler(int workPin, unsigned long time,void (*func)(int)) {
      _workPin = workPin;
      _workTime = time;
      _ellapsedTime = 0;
      this->func = func;
    }
    
    ~WorkScheduler() {
      _workPin = 0;
      _workTime = 0; 
      _ellapsedTime = 0;
      func = NULL;
    }
    
    void update() {
      _ellapsedTime += Timer::getInstance()->delta();
      if (_ellapsedTime >= _workTime) {
        _ellapsedTime -= _workTime;
       if (func != NULL) {
         func(_workPin);
       } 
      }
    }
};

 

Sau đây là chương trình mẫu của tôi là cho 1 pin làm việc bằng cách chớp tắt 1 LED và bạn hoàn toàn có thể thêm bao nhiêu cũng được. Tôi đã thử cho 3 LED hoạt động trong thời gian khác nhau và nó hoàn toàn chính xác.

 

const int pinRed = 8;

WorkScheduler *redPinWorkScheduler;

void OnRedPin_Work(int pin) {
  static boolean wasLightedUp = false;
  digitalWrite(pin, !wasLightedUp ? HIGH: LOW);
  wasLightedUp = !wasLightedUp;
}

void setup() {
  pinMode(pinRed, OUTPUT);
  
  Timer::getInstance()->initialize();
  
  redPinWorkScheduler = new WorkScheduler(pinRed, 1000UL, OnRedPin_Work);  
}

void loop() {
  Timer::getInstance()->update();
  
  redPinWorkScheduler->update();
 
  Timer::getInstance()->resetTick();
}

Kết luận

Hi vọng bài viết này sẽ làm cho thời gian lập trình của các bạn trên Arduino thật hiệu quả.

lên
20 thành viên đã đánh giá bài viết này hữu ích.
Các dự án được truyền cảm hứng

Select any filter and click on Apply to see results

Các bài viết cùng tác giả

Cảm biến nhiệt LM35DZ, Cảm biến ánh sáng TEPT5700 cùng LCD1602

Hiển thị nhiệt độ của cảm biến nhiệt LM35DZ lên LCD 1602 và điều khiển backlight, contrast của LCD bằng cảm biến ánh sáng TEPT 5700

lên
11 thành viên đã đánh giá bài viết này hữu ích.