ksp gửi vào
- 21435 lượt xem
I. Giới thiệu
Ở trong loạt bài này và một bài viết khác, mình đã đề cập đến vấn đề quy trình Công nghiệp (phần 1, phần 2) và vấn đề xử lý bất đồng bộ trên Arduino. Hôm nay, mình muốn phát triển loạt bài này với mục đích, bạn có thể xây dựng một máy công nghiệp với các quy trình tuần tự nhưng có thể can thiệp để dừng ngay được. Ngoài ra, mình còn cập nhập thêm khả năng quy ước trước số lượt chạy của quy trình và một số API khác giúp cho các bạn có thể kết hợp lại 2 thư viện này! Để đọc hiểu, và tiếp cận nhanh bài này, các bạn cần đọc 3 bài viết mà mình có liên kết trong đoạn giới thiệu này.
II. Những khả năng đã có trước đó
1. Xử lý bất đồng bộ
Ở các bài viết trước, cụ thể là bài xử lý bất đồng độ, mình đã đề cập và xây dựng một thư viện có khả năng thực hiện các tác vụ theo chu kỳ, điều đó làm giảm bớt gánh nặng chi phí thực hiện chương trình và lên lịch các công việc theo các chu kỳ nhất định. Tuy nhiên, nếu nó hoạt động một mình thì quả thật là không hay. Vì sao lại như vậy? Vì các công việc cứ đến chu kỳ thì lặp lại, hết chu kỳ lại lặp lại tiếp, một vòng quanh khép kín. Thực sự với mình, nó rất bức bối! Tuy nhiên, với các bạn làm LED, như led cube, led ma trận, led trái tim, led chữ,... nói chung là các ứng dụng giải trí mà nó chỉ yêu cầu nhiều chu kỳ chạy khác nhau. Và toàn bộ những chu kỳ đó là sự lặp đi và lặp lại và không có bất cứ ứng dụng nào có thể xen vô được!
Các chu kỳ chạy độc lập và chẳng thể nào liên quan với nhau
Tuy nhiên, với cách lập trình sử dụng biến tĩnh static mà mình đã viết trong bài viết đó, chúng ta có thể kết giao lưu kết hợp các chu kỳ với nhau bằng cách thay đổi trạng thái của các biến static hoặc các biến toàn cục! Các bạn nên xem trong bài viết đó để hiêu rõ về vấn đề này.
Chu kỳ 1, 3 chỉ được hoạt động khi chù kỳ 2 sét 2 biến được chạy ở các chu kỳ sang true.
2. Tiến trình công nghiệp
Nếu để ý trong các workscheduler ở bài chu trình, thì nó là những hàm đơn thuần! Nghĩa là mỗi tiến trình (chu kỳ) chỉ là một hàm đơn giản. Nếu đặt vấn đề: những hàm đó phức tạp hơn và có thể yêu cầu hoạt động trong một khoàng thời gian trước khi kết thúc, ví dụ như: điều khiển động cơ bước di chuyển từ tọa độ này sang tọa độ kia ở khoảng cách xa...
Như vậy, thư viện tiến trình công nghiệp chính là điều quan trọng nhất giải quyết những chu kỳ yêu cầu xử lý nhiều đó. Nghĩa là, ta sẽ tìm cách phân giải những chu kỳ đó thành các bước, rồi kiểm tra bước này đã xong chưa, rồi mới thực hiện bước kia.
Ở đây, mình sẽ ví dụ ở việc cắt chu kỳ 1 trong hình trên. Giả sử nó xử lý khá nhiều và có hàm delay.
void ChuKy1 () { for (int i = 0; i < 16; i++) shiftOut(...); // shiftout ra 16 led tắt delay(2000); for (int i = 15; i >= 0; i--) shiftOut(...); // shiftout ra 16 led bật delay(2000); }
Nếu như mình ứng dụng code này vào chu kỳ 1 sẽ làm toàn bộ hệ thống hoạt động sai vì điều tối kỵ nhất trong cả thư viện đó là sử dụng hàm delay. Tuyệt đối không được sử dụng hàm delay dưới mọi hình thức.
Nếu suy nghĩ theo kiểu tiến trình, ta có thể chia nó thành 3 tiến trình như sau.
void ChuKy1 () { //bắt đầu tiến trình 1 for (int i = 0; i < 16; i++) shiftOut(...); // shiftout ra 16 led tắt //kết thúc tiến trình 1 delay(2000); //bắt đầu tiến trình 2 for (int i = 15; i >= 0; i--) shiftOut(...); // shiftout ra 16 led bật //kết thúc tiến trình 2 delay(2000); //bắt đầu tiến trình 3 //kết thúc tiến trình 3 }
- Ở tiến trình 1 (bước 1), mình sẽ cho nó shiftout ra 16 đèn led để bật hết. (hiệu ứng gì cũng đươc hết nha)
- Ở tiến trình 2 (bước 2), mình sẽ cho nó shiftout ra 16 đèn led để tắt hết. (hiệu ứng gì cũng được hết nha)
- Vì sao lại chỉ có tiến trình 3, mà nó chả hề có một dòng code nào cả? Đó là vì, bạn có ý định dùng hàm delay, nghĩa là nó phải dừng chương trình. Nếu muốn dừng chương trình trong 2 giây thì bạn phải có một tiến trình thứ 3 để thư viện hiểu được là phải chờ 2000ms rồi mới kết thúc (thực ra thực hiện tiến trình 3, nhưng tiến trình 3 lại không có code).
Khi đã quy về từng bước, ta sẽ có một bảng vẽ các bước như sau:
Mỗi tiến trình sẽ được thiết kế và cài đặt riêng. Như vậy, mình hoàn toàn có thể làm nên những tiến trình rất phức tạp. Kết hợp việc xử lý có điều kiện đã nói ở phần 2, bạn có thể làm những tiến trình công nghiệp rất chuyên nghiệp!
3. Kết hợp cả 2 thư viện
Ngay từ khi thư viện tiến trình công nghiệp ra đời, bạn hoàn toàn có thể kết hợp cả 2 thư viện lại. Chỉ việc suy luận một tí là kết hợp đươc ngay. Vì cả 2 đều được xây dựng trên thư viện Timer mà . Tuy nhiên, Ở các phần trước, thư viện tiến trình công nghiệp không thể bị can thiệp từ bên ngoài, mà cứ chạy mãi, chãy mãi . Như vậy chả vui tí nào, phải không nào? Vì vậy, đó là lý do mình viết bài này. Với mục đích kết hợp cả 2 thư viện lại một cách chuyên nghiệp hơn bằng cách cung cấp các API start, pause, stop, startAgain cho các bạn! Ngoài ra, mình còn làm thêm một chức năng cho thư viện để giới hạn số lần chạy của toàn cộ tiến trình công nghiệp. Nào, chiến đấu thôi.
III. Những API mới và ví dụ
Thư viện WorkScheduler để xử lý các tiến trình bất đồng bộ không thay đổi gì, các bạn nhé. Chỉ có thư viện kIndustryCalendar. Các bạn có thể download tổng hợp các thư viện đã được tổng hợp tại đây.
1. start
Nó sẽ cho tiến trình công nghiệp chạy (chỉ nên dùng cho lần đầu tiên). Hay còn gọi là khởi động. Các tiến trình sẽ được gọi hết nếu trong thời gian bị pause, stop nó không được chạy!
2. startAgain (Resume)
Nó sẽ cho tiến trình công nghiệp chạy lại (nên dùng). Vì nó sẽ lưu lại thời điểm cuối mà chương trình đã thực hiện xong. Ví dụ: tiến trình 1, 2 đã chạy xong. Đang đợi tiến trình 3 thì mình pause, sau này startAgain (resume) lại thì nó sẽ tính lại thời điểm mà nó bị dừng rồi chạy tiếp.
3. stop
4. pause
Tạm dừng các tiến trình
5. setProcessCount
Giới hạn số lần tiến trình công nghiệp sẽ được lặp. Thích hợp làm bộ đếm sản phầm.
6. Ví dụ của sự kết hợp
Ở ví dụ này, mình sẽ sử dụng 3 nút bấm làm nút resume, pause và nút stop ở 2 chu kỳ khác nhau nhưng được gọi liên tục ở mức chu kỳ 0UL để đảm bảo mọi thứ được diễn ra trong thời gian thực.
a. Linh kiện điện tử
b. Lắp mạch
c. Lập trình
Các bạn download sketch tại đây nhé.
#include "Timer.h" #include "kIndustryCalendar.h" #include "WorkScheduler.h" #include "FallingButton.h" ////////////////////////////////////////////////////////////////// Phần bổ sung cho ví dụ const byte startButtonPin = 6; const byte pauseButtonPin = 7; const byte stopButtonPin = 8; FallingButton startButton = FallingButton(startButtonPin), pauseButton = FallingButton(pauseButtonPin), stopButton = FallingButton(stopButtonPin); ////////////////////////////////////////////////////////////////// Phần chính ////////////// các job của tiến trình công nghiệp //job1 void job1() { unsigned long time = millis(); Serial.print("Job1: "); Serial.println(time); } //job2 void job2() { unsigned long time = millis(); Serial.print("Job2: "); Serial.println(time); } void job3() { Serial.println("Ban da nhan phim tren Serial"); } bool waitForSerial() { bool res = Serial.available(); //xóa hết buffer của Serial while (Serial.available()) Serial.read(); return res; } ///////////////////////các workscheduler WorkScheduler *processWorkScheduler; //xử lý tiến trình công nghiệp WorkScheduler *buttonWorkScheduler; //kiểm tra tiến trình button /////////////////////// các work void updateProcessWork() { kIndustryCalendar::getInstance()->update(); } void buttonWork() { if (startButton.check() && !kIndustryCalendar::getInstance()->isRunning()) { //cho quy trình chạy lại nếu nhán nút khởi động kIndustryCalendar::getInstance()->startAgain(); Serial.println("Start"); } if (kIndustryCalendar::getInstance()->isRunning()) { if (pauseButton.check()) { // nhấn nút tạm dừng kIndustryCalendar::getInstance()->pause(); Serial.println("pause"); } else if (stopButton.check()) { // nhấn nút thoát kIndustryCalendar::getInstance()->stop(); Serial.println("stop"); } } } void setup() { ///// Phần chính trong thư viện //Khởi tạo serial ở mức baudrate 115200 Serial.begin(115200); //Khởi gạo class timer (design pattern 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(); ////////////////// Khởi tạo kIndustryCalendar trước ////////////////// //Khởi tạo lịch, các công việc phải được sắp xếp theo chiều thời gian tăng dần để tránh lỗi kIndustryCalendar::getInstance()->initialize(); //Chạy lệnh job1 ở thời điểm 0 trong chu kỳ kIndustryCalendar::getInstance()->addJob(job1, (unsigned long)0); //Chạy lệnh job2 ở thời điểm 1000ms trong chu kỳ kIndustryCalendar::getInstance()->addJob(job2, (unsigned long)1000); //Job3 sẽ chỉ được thực hiện khi bạn gửi một ký tự gì đó qua Serial (bật serial monitor lên và nhấn bất kì rồi ấn Enter :D) kIndustryCalendar::getInstance()->addJob(job3, waitForSerial); //***Gán số lần hoạt động của chu trình kIndustryCalendar::getInstance()->setProcessCount(3); ////////////////// Khởi tạo WorkScheduler ////////////////// //khởi tạo workscheduler cập nhập tiến trình công nghiệp processWorkScheduler = new WorkScheduler(0UL, updateProcessWork); //khởi tạo workscheduler cập nhập giá trị button để quản trị tiến trình công nghiệp buttonWorkScheduler = new WorkScheduler(0UL, buttonWork); } //trong hàm loop chỉ nên có những hàm này, bạn muốn viết một chức năng khác? Xin hãy tạo một job và đưa vào thời khóa biểu scheduler như hàm dưới void loop() { //đầu hàm loop phải có để cập nhập thời điểm diễn ra việc kiểm tra lại các tiến trình Timer::getInstance()->update(); //cập nhập tiến trình công nghiệp processWorkScheduler->update(); //cập nhập tiến trình kiểm tra nút nhấn buttonWorkScheduler->update(); //cuối hàm loop phải có để cập nhập lại THỜI ĐIỂM (thời điểm chứ ko phải thời gian nha, tuy tiếng Anh chúng đều là time) để cho lần xử lý sau Timer::getInstance()->resetTick(); }
d. Giải thích
- Trong code trên, mình có sử dụng 3 nút nhấn được pinMode là INPUT_PULLUP. Mình đã viết thư viện FallingButton.h để xác định sự kiện Falling (xem thêm tại).
- Có 2 WorkScheduler là:
-
processWorkScheduler: chu kỳ kiểm tra tiến trình công nghiệp. (ưu tiên 1)
-
buttonWorkScheduler: chu kỷ kiểm tra việc nhấn button. (ưu tiên 1)
-
-
Trong processWorkScheduler có một tiến trình công nghiệp với 3 job (tiến trình).
-
Job1: in ra thời gian chạy job này
-
delay 1000ms
-
-
Job2: in ra thời gian chạy job này
-
đợi có tín hiệu Serial
-
-
Job3: ghi nhận là đã nhận tín hiệu serial
-
-
Các bạn nhớ xem trong comment trong code nhé.
-
Bình luận thêm nếu có phần chưa rõ, mình sẽ giải thích thêm hi.
e. Video demo
Vì ở nhà không còn nút nhấn và nhận thấy nút nhấn có thể thay thế bởi một sợi sây điện . Nên mình chỉ dùng một mạch Arudino và một sợi dây diện để demo cho các bạn, hehe.
IV. Kết luận
Như vậy là về logic, Arduino có thể điều khiển các máy công nghiệp ngon lành. Tuy nhiên, rất cần một bạn thiết kế shield relay gắn lên các chân IO và analog của arduino. Từ đó xây dựng một phần cứng mã nguồn mở công nghiệp thực sự hehe. Mình rất vui được hợp tác với các bạn.