1. Giới thiệu về FreeRTOS
Được cấu tạo từ 03 khái niệm chính: Free, Realtime và OS.
Trước hết, ta biết được OS (operating system), một hệ điều hành giống như Windows, macOS hay Linux. Mục đích chính của nó là khởi chạy một hệ thống với một tầng độc lập (OS). Khác với các Embedded System nhỏ lẻ, OS là một hệ thống lớn, nơi các công việc được quản lý theo các block khác nhau và có thể xử lý đồng thời nhiều tác vụ. Không có nhược điểm khi so sánh với Single Embedded System (về mặt kỹ thuật, đương nhiên là tôi không nói về các vấn đề như nó khó hơn hay đòi hỏi hệ thống tài nguyên lớn hơn).
Một vài đặc điểm/lợi ích khi làm việc với OS là: hệ thống luôn sống (alive) ngay cả khi một vài chương trình bị treo hoặc crash, parallel đồng thời được nhiều task, và dễ dàng phân cấp các task theo dạng block và layer, có được sự rõ ràng hơn (back-end/frontend).
Về Realtime, rõ ràng rằng OS luôn có cơ chế lập lịch để giúp system quản lý và chạy task một cách hợp lý dựa trên các thông tin cấu hình mà user mong muốn (#xem bài OS: CFS và EEVDF của mình để hiểu rõ hơn), Realtime có nghĩa là hệ thống phải đáp ứng được các deadline theo thời gian thực, hạn chế bị time skip hay trôi dẫn đến chương trình hoặc product hoạt động không chính xác.
Free là một open source mà cộng đồng đóng góp, bạn có thể dùng cho các ý tưởng và không lo về bản quyền. Bên cạnh FreeRTOS còn có các OS khác cho nhúng system như "Zephy os", "VxWorks", "QNX",....
2. Nguyên lý làm việc của hệ thống
Mô tả ngắn gọn về cách quản lý và sử dụng tài nguyên của OS
Mô tả ngắn gọn về cách quản lý và sử dụng tài nguyên của OS
Hệ thống có thể được nhìn nhận như một mô hình quản lý và công nhân, từng người có công việc của riêng họ; việc của các công nhân là cần hiểu công việc mình phải làm và báo cáo ngắn gọn theo format (các thông số) mà quản lý (hệ thống) đưa ra. Từ những thông tin đó, thuật toán (bộ não của người quản lý) sẽ tự điều chỉnh thực thi cho phù hợp với tình trạng của công ty (hệ thống).
Source code của FreeRTOS sẽ được cung cấp ở cuối bài viết. Để sử dụng được FreeRTOS, các bạn cần phải sử dụng thư viện này, đặc biệt trên các board Arduino hay STM32… Riêng đối với ESP32, một vài custom được thực hiện và đã có sẵn khi các bạn download resource từ nhà phát hành.
3. Các khái niệm phổ biến khi lập trình
Có vài thuật ngữ ta cần làm quen trong bài viết này bao gồm: Tasks, Delays, Queues, Semaphores, Mutex.
3.1. Tạo một task
Như đã giới thiệu, task hay block task gồm nhiều thành phần cấu thành và hoạt động độc lập, ví dụ như đọc dữ liệu cảm biến, gửi dữ liệu qua lại giữa cloud và system, hay đơn giản chỉ là điều khiển đèn led báo hiệu trạng thái của hệ thống. Hàm sử dụng thì rất đa dạng và được cập nhật thường xuyên, nên tốt hơn là các bạn nên tự đọc lấy trên trang chủ của hãng. Để tạo một task block, ta dựa vào các hàm sau.
Có 3 yếu tố chính cần quan tâm khi tạo 1 task là: mức độ ưu tiên, kích thước của chương trình (task's code size), function của task.
xTaskCreate() chỉ đơn giản là tạo một chương trình rồi để đó. xTaskCreatePinnedToCore() tạo một chương trình và gắn nó cụ thể với một core trên hệ thống/chip, nếu như có nhiều hơn 1 core. Thay về phải random trên các core như hàm trên.
Các công việc có cùng mức độ ưu tiên thì được chạy theo cơ chế round-robin fashion.
3.2 Delays
Cơ chế trì hoãn một tác vụ và cũng có thể tạm thời đưa tác vụ vào trạng thái sleeping cho đến khi có sự kiện gì đó gọi nó dậy.
vTaskDelay(ticks): trì hoãn task với thời gian cụ thể, chức năng hoạt động giống với Delay trong Arduino nhưng khác ở chỗ có Scheduler đưa task vào trạng thái sleep, có nghĩa là ngủ "x" ticks từ bây giờ. Theo cách sử dụng thông thường như cách mà mình hay sử dụng delay trong Arduino.
vTaskDelayUntil() tương tự như hàm trên, nhưng hàm này quy định khoảng thời gian chạy cụ thể cho task, ví dụ, tôi muốn chương trình này chỉ chạy "x" ticks trước khi ngủ và chờ đến lần tiếp theo.
3.3 Queue và Communication giữa các task
Ta đã biết về vùng không gian của từng task là một khu vực độc lập, có nghĩa là các giá trị chỉ tồn tại trong phạm vi của code đó. Do vậy, với những task như đọc cảm biến, có vẻ sẽ gặp vài vấn đề trong giao tiếp dữ liệu giữa các task. Điều này có thể gây overwrite data hoặc mismatch trong quá trình hoạt động.
Để khắc phục vấn đề đó, ta nên sử dụng Queue (hàng đợi) để sắp xếp các data và thống nhất rằng một dữ liệu sẽ được gắp với một queue cụ thể (cái này là tùy chúng ta lập trình, có thể có nhiều data khác nhau trong queue nào đó mà ta tạo - tùy mục đích).
xQueueCreate() tạo queue để lưu dữ liệu. xQueueSend(), xQueueReceive() tương ứng sẽ là gửi và nhận dữ liệu. Nó có hỗ trợ blocking, do  đó một vài task có thể tận dụng để cập nhật chương trình khi nào có dữ liệu mới được update.
3.4 Mutex và Semarphores
Tránh các race conditions giữa các task khi sử dụng và truy cập dữ liệu, giao tiếp với file, hay nói cách khác là sử dụng cùng tài nguyên.
Cái này thì phải dựa trên kinh nghiệm làm việc để biết lock hay unlock như thế nào sao cho hợp lý.
A. Chương trình code
Chương trình code làm quen với kiểu task() runner của FreeRTOS
Chương trình code làm quen với kiểu task() runner của FreeRTOS
B. Tài liệu bổ xung/ Tham Khảo
Trang web tra cứu API của hãng