ksp gửi vào
- 24396 lượt xem
I. Giới thiệu
Ở bài viết trước, mình đã đề cập đến vấn đề là "Làm thế nào để xây dựng một quy trình công nghiệp trên Arduino". Ở bài viết đó, mình đã đề cập đến vấn đề quy trình tuần tự không điều kiện, và với sự hưởng ứng từ Cộng đồng qua vấn đề làm một máy công nghiệp dùng để sản xuất sản xuất thành phẩm, mình muốn đóng góp một cái gì đó để dự án này hoàn thiện, đó cũng làm một cách để rèn luyện khả năng xử lý vấn đề thông qua mô tả mà không trực tiếp "chạy" máy ! Vấn đề mình muốn giải quyết đó là xây dựng một quy trình công nghiệp có điều kiện.
II. Bài toán đặt ra
Bạn cần đọc kĩ và thực hành ở bài Tạo một quy trình công nghiệp với các bước bằng Arduino trước khi đọc tiếp, vì như vậy bạn mới thấy được những khó khăn trong bài viết đó. Từ đó, cảm nhận những thay đổi trong bài viết này.
Ngoài ra, bạn có thể download bộ thư viện tại đây và tạo một sketch mới sau đó paste vào hoặc donwload sketch mẫu tại đây. Mình chưa tạo một thư viện hoàn toàn vì muốn các bạn trải nghiệm trước từ đó đóng góp hơn nữa, mình vẫn thấy nó còn thiếu một điều gì đó .
Bài toán thực tế được đưa ra là, làm thế nào để điều khiển nhiều động cơ bước, trong đó, động cơ bước kia phải hoạt động rồi mới đến lượt động cơ bước này. Bài toán này không khó nếu chỉ có 2 hay 3 động cơ bước. Nhưng với 7, 8 động cơ bước thì việc biết chính xác lúc nào động cơ kia được chạy quả không phải là một điều đơn giản cho việc code theo hướng thủ tục. Ở bài viết trước, mình có nói đến quy trình công nghiệp, nhưng rõ ràng là nó không thực tế tí nào, vì nó không quan tâm đến công việc kia đã hoàn thành hay chưa, chỉ cần đến đúng lúc thì nó chạy :D. Có thể nó phù hợp với công việc quét LED hơn chăng ?
Bài toán đặt ra là: thư viện phải biết khi nào bước đó được hoạt động, nghĩa là thêm điều kiện để kiểm tra bước đó được phép hoạt động hay không? Nhưng chưa cần giải quyết rẻ nhánh (nếu không được trong bao lâu đó thì rẻ nhánh sang bước khác) .
III. Giải quyết vấn đề
Ta có thể dễ dàng thấy rằng vấn đề này rất giống với vấn đề trước chỉ khác ở chỗ xác định đươc "thời điểm kết thúc chinh xác". Như vậy, mình sẽ thiết kế một hướng đối tượng tên kIndustryCalendar kế thừa đối tượng kCalendar đã có để tiết kiệm thời gian ngồi gõ code .
Về cơ bản đối tượng kIndustryCalendar giống với kCalendar hết, vì vậy, các lưu ý sẽ được giữ nguyên không thay đổi gì, mình chỉ nói những thay đổi mới thôi nhé.
Khác với kCalendar, bạn phải tính toán thời điểm chính xác trong quy trình của bước đó, thì ở lớp kIndustryCalendar bạn chỉ cần xác định khoảng thời gian giữa bước thứ i - 1 và bước thứ i, hoặc là xác định điều kiện để bước thứ i được thực hiện .
IV. Lập trình
Để hiểu rõ hơn, bạn xem đoạn code mẫu mình đã viết như sau.
#include "Timer.h" #include "kIndustryCalendar.h" //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; } void setup() { //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 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); //Bắt đầu tính giờ thời gian của quy trình đầu tiên kIndustryCalendar::getInstance()->startFirstJob(); } //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(); kIndustryCalendar::getInstance()->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(); }
Giống như thư viện trước, job1 và job2 sẽ lần lượt được thực hiện, nhưng job3 chỉ được thực thi khi job1 đã chạy xong!
Điều khiển động cơ bước như thế nào nhỉ?
Mình lắp mạch như trên và thử đọn code này và cảm nhận đoạn code dưới đây nào. Thư viện điều khiển động cơ bước, các bạn download ở đây.
#include "Timer.h" #include "kIndustryCalendar.h" #include <AccelStepper.h> AccelStepper stepper1(1, 9, 8); AccelStepper stepper2(1, 7, 6); //job1 void job1() { unsigned long time = millis(); Serial.print("Job1: "); Serial.println(time); stepper1.moveTo(1000); } bool waitJob1Finish() { return stepper1.distanceToGo() == 0; } //job2 void job2() { unsigned long time = millis(); Serial.print("Job2: "); Serial.println(time); stepper2.moveTo(-1000); } bool waitJob2Finish() { return stepper2.distanceToGo() == 0; } //job3 void job3() { unsigned long time = millis(); Serial.print("Job3: "); Serial.println(time); stepper2.moveTo(1000); stepper1.moveTo(-1500); } bool waitJob3Finish() { return (stepper2.distanceToGo() == 0) && (stepper1.distanceToGo() == 0); } void jobEnd() { //chả làm gì cả, một hàm để đảm bảo được gọi trước khi kết thúc. Hoạt dùng để đếm số lần hoạt động của máy từ đó ra quyết định dừng máy lại :) } void setup() { //Khởi tạo serial ở mức baudrate 115200 Serial.begin(115200); //gán các giá trị tốc độ tối đa và gia tốc khác nhau cho động cơ bước! stepper1.setMaxSpeed(300); // tốc độ tối đa stepper1.setAcceleration(1000); // gia tốc stepper2.setMaxSpeed(200); stepper2.setAcceleration(800); //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 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, waitJob1Finish); kIndustryCalendar::getInstance()->addJob(job3, waitJob2Finish); kIndustryCalendar::getInstance()->addJob(jobEnd, waitJob3Finish); //Bắt đầu tính giờ thời gian của quy trình đầu tiên kIndustryCalendar::getInstance()->startFirstJob(); } //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(); kIndustryCalendar::getInstance()->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(); stepper1.run();stepper2.run(); //cho 2 động cơ bước chạy }
V. Kết luận
Thử và cảm nhận và đóng góp ý kiến cho mình dưới mục bình luận nhé. Và đừng quên chụp hình sản phẩm để mình cập nhập vào bài viết nha haha.