Chuyển từ RP2040 sang RTOS
(blog.brixit.nl)- Khi bộ điều khiển thiết bị video dựa trên Raspberry Pi Pico đòi hỏi nhiều tác vụ đồng thời, chỉ riêng pico-sdk và việc tách hai nhân khiến cấu trúc phần mềm ngày càng khó kham nổi
- Bộ điều khiển phải xử lý cùng lúc RS-485 VISCA, Ethernet, 9 nút RGB, joystick, màn hình, đồng thời còn cần DHCP, mDNS và xử lý giao thức ATEM dựa trên UDP
- FreeRTOS có thể hiện thực một phần nhờ scheduler và giao tiếp giữa các task, nhưng việc printf bị treo và thiếu trừu tượng hóa phần cứng khiến debug và tái sử dụng mã trở nên khó khăn
- Apache NuttX cung cấp shell, filesystem, trừu tượng hóa thiết bị như
/dev/i2c0và cấu hình Kconfig, nhưng vấn đề I2C về sau được đính chính là do lỗi cấu hình, nên phần lớn đánh giá khi đó có thể không còn giá trị - Zephyr bị cản bởi repository và SDK cỡ 5GB, định nghĩa bo mạch và độ phức tạp khi build; cuối cùng hướng đi nghiêng về tiếp tục thử FreeRTOS vì có thể gắn vào môi trường hiện có một cách đơn giản nhất
Vì sao cần RTOS
- Nhiều dự án vi điều khiển nhỏ đang được xây dựng quanh bo Raspberry Pi Pico
- Pico dễ tích hợp với IDE nhờ SDK ổn, phần cứng rẻ và hỗ trợ debug bằng gdb/openocd
- Dự án hiện tại là một bộ điều khiển phần cứng để điều khiển nhiều thiết bị video
- Thiết bị được điều khiển gồm 2 camera PTZ có mô-tơ, 1 camera cố định và một thiết bị chuyển mạch video được kết nối
- Bộ điều khiển camera PTZ hiện tại là một panel không tên trông giống Marshall VS-PTC-200
- Vài năm trước có giá €650, nhưng cảm giác bấm nút và chất lượng joystick analog không tốt
- Nhiều nút không hoạt động với các camera đang dùng hiện nay, có vẻ được tối ưu cho camera an ninh
- Nó kết nối với camera qua bus RS-485
- Việc điều khiển video switcher ATEM hiện chỉ được thực hiện bằng panel phần mềm trên máy tính
- Panel phần cứng của Blackmagic Design rất đắt
Cấu hình phần cứng của bộ điều khiển
- Thiết kế tối thiểu được xác định gồm 9 nút, joystick và màn hình cho giao diện người dùng
- Sau một năm lặp lại nhiều phiên bản thiết kế phần cứng, PCB đã được hoàn thiện
- 9 nút RGB
- Joystick giá $10 cũng có trong panel clone Marshall
- TP8485E để giao tiếp RS-485 với camera PTZ
- Module Wiznet W5500 để giao tiếp với video switcher qua Ethernet
- Sau các lần chỉnh sửa bo mạch, tất cả linh kiện phần cứng đã hoạt động, nhưng phần khó hơn lại là phần mềm
Phần mềm trở nên quá sức nếu chỉ dùng pico-sdk
- Bắt đầu theo cách như các dự án RP2040 trước đây: lấy pico-sdk trong một dự án cmake
- Để có được mức hoạt động tối thiểu, nhân thứ hai của Pico được dành cho việc xử lý module Wiznet, còn nhân thứ nhất xử lý I/O của giao diện người dùng
- Có thể làm được LED nhấp nháy và cả triển khai DHCP client chạy trên nhân thứ hai
- Nhưng triển khai phần còn lại của hệ thống phức tạp hơn nhiều
- Các tác vụ cần xử lý đồng thời tăng lên nhanh chóng
- Vẽ giao diện người dùng lên màn hình tương đối mượt
- Gửi lệnh VISCA qua giao diện RS-485
- Phản hồi đầu vào từ nút bấm
- Duy trì network stack, gồm nhiều kết nối
- Phía mạng cần có tác vụ nền riêng
- Hỗ trợ DHCP đúng chuẩn phải theo dõi thời gian hết hạn và thỉnh thoảng liên lạc với DHCP server để duy trì trạng thái lease
- mDNS cần để tự động phát hiện IP của video switcher ATEM, và nếu có thể thì cũng nên quảng bá sự tồn tại của control panel
- Giao thức ATEM khá đơn giản, nhưng đôi khi nhận dữ liệu vượt quá kích thước buffer của module Wiznet, và nếu việc gửi UDP datagram dừng lại thì timeout ngắt kết nối rất ngắn
- Trong các điều kiện này, chia công việc bằng RTOS có vẻ phù hợp hơn là tự chồng thêm các vòng lặp
FreeRTOS: đơn giản nhưng thiếu trừu tượng hóa
- Về mặt kỹ thuật FreeRTOS đã có trong pico-sdk, nhưng các tutorial lại dùng cách tải một bản sao mới nên đã làm theo như vậy
- Trong các RTOS được xem xét, nó có vẻ đơn giản nhất, chủ yếu cung cấp scheduler và giao tiếp giữa các task
- Cấu trúc là tạo task bằng
xTaskCreatevà khởi động scheduler bằngvTaskStartScheduler - Có thể dùng queue để truyền trạng thái nút sang task LED như một dạng IPC
- Cấu trúc là tạo task bằng
- Sau vài ngày sử dụng, codebase được chia thành nhiều task dù chức năng thực tế vẫn còn ít
buttonsTask: poll bộ mở rộng GPIO qua I2C để kiểm tra đầu vào nút và đưa message vào button queueledTask: đặt màu RGB của nút cụ thể theo message trongledQueuemainTask: chạy vòng lặp chính cập nhật trạng thái dự án theo đầu vào nútnetworkTask: giao tiếp với module WiznetdhcpTask: donetworkTasktạo khi cáp mạng được kết nốimdnsTask: dodhcpTasktạo sau khi lấy được địa chỉ IPatemTask: được tạo khi mDNS nhận phản hồi từ thiết bị ATEMviscaTask: hiện chưa làm gì, nhưng sẽ phải gửi dữ liệu qua cổng RS-485
- Số lượng task đã trở nên nhiều dù phần cứng vẫn chưa làm gì ngoài xuất hiện trên mạng
- Vấn đề khó chịu nhất là printf lần nào cũng gây treo
- Debugger gdb vẫn hoạt động, nhưng không phù hợp với cách in lưu lượng DHCP ra để kiểm tra
- FreeRTOS không cung cấp trừu tượng hóa phần cứng, nên khó tái sử dụng dễ dàng mã đã viết để giao tiếp với nhiều chip
- Sau đó đã thử tạo một dự án FreeRTOS sạch và chuyển các chức năng sang, nhưng vì bất tiện khi phải debug mù mà không có output serial, các lựa chọn khác đã được xem xét
Apache NuttX: cấu trúc kiểu Unix và lỗi cấu hình
- Apache NuttX trông giống một hệ điều hành thông thường hơn, đối xử với vi điều khiển như một hệ thống Unix
- Tutorial hướng dẫn lấy pico-sdk và thiết lập biến môi trường
- SDK đã có sẵn trong
/usr/sharevà biến môi trường cũng đã có, nhưng NuttX cố ghi đè fileversion.hcủa pico-sdk, khiến build thất bại do vấn đề quyền truy cập
- SDK đã có sẵn trong
- Sau khi build firmware NuttX tối thiểu và kết nối vào cổng serial, một shell thật sự xuất hiện
- Các lệnh như
uptime,uname,uname -ahoạt động - Phiên bản hiển thị là
NuttX 12.5.1, target làarm raspberrypi-pico
- Các lệnh như
- Vì theo cách Unix, có vẻ có thể viết ứng dụng và tự động chạy khi boot
- Có filesystem và phần cứng được phơi bày qua các trừu tượng hóa như
/dev/i2c0,/dev/adc0
- Có filesystem và phần cứng được phơi bày qua các trừu tượng hóa như
- Điểm được thích là cấu hình dựa trên menuconfig/Kconfig
- Đây là cách quen thuộc khi phát triển Linux
- Có hệ thống driver phần cứng thực sự, và driver cho chip mở rộng GPIO dùng cho các nút cũng đã tồn tại
- Thiết lập pin mux của RP2040 cũng có thể chỉ định trong menuconfig, nên không cần duy trì riêng các hằng số số chân hoặc viết nhiều mã khởi tạo bus I2C
- Cũng có thể đưa tiện ích kiểm thử I2C vào firmware
- Ban đầu có vẻ I2C cơ bản không hoạt động
- Trong bản cập nhật sau đó, điều này được đính chính là thực ra nó hoạt động tốt, còn bus I2C bị hỏng do lỗi cấu hình
- Có nêu rõ rằng phần lớn đánh giá còn lại trong mục NuttX này có khả năng không còn giá trị
- Khi đó vẫn chưa tìm ra cách cho NuttX biết các nút GPIO nằm sau bộ mở rộng GPIO, cũng như cách nối bộ mở rộng GPIO với bus I2C
- Sau khi
configure.shthất bại, repository rơi vào trạng thái không nhất quán, nên phải clone lại repository NuttX nhiều lần- Có lúc
distcleancũng không hoạt động đúng vì cùng lý do
- Có lúc
- Cách tiếp cận giống Unix ban đầu có vẻ hay, nhưng không muốn phải xử lý các đường dẫn filesystem giả trên vi điều khiển
- Hệ thống production không cần shell, chỉ cần code chạy là đủ
Zephyr: SDK lớn và rào cản định nghĩa bo mạch
- Lựa chọn tiếp theo là Zephyr, có cung cấp tiện ích Python để thiết lập dự án
- Bước đầu tiên kéo về một Git repository khoảng 5GB
- Trong đó có các thư viện HAL cho nhiều chip
- Nó cũng ảnh hưởng đến cấu hình cmake toàn cục của người dùng
- Tutorial yêu cầu cài Zephyr SDK
- Zephyr SDK gồm toolchain, compiler, assembler, linker và công cụ build cho từng kiến trúc được hỗ trợ
- Cũng gồm các công cụ host như QEMU và OpenOCD
- Vì đã có nhiều toolchain ARM, nên không muốn build hoặc tải sẵn compiler cho mọi kiến trúc
- Để build mà không dùng Zephyr SDK, đã thiết lập các tùy chọn cross compile
ZEPHYR_TOOLCHAIN_VARIANT=cross-compileCROSS_COMPILE=/usr/bin/arm-none-eabi-west build -p always -b sparkfun_pro_micro_rp2040 samples/basic/blinky
- Raspberry Pi Pico thực ra không được hỗ trợ, chỉ có các bo khác dùng cùng SoC được hỗ trợ
- Vì cùng SoC nên xem như về thực chất là bo tương tự và tiếp tục
- Ngay lập tức gặp vấn đề demo
blinkykhông build được- Demo yêu cầu định nghĩa
led0là đối tượng để nhấp nháy - Sparkfun Pro Micro RP2040 không có LED GPIO đơn giản mà có LED định địa chỉ WS2812B
- Demo yêu cầu định nghĩa
- Đã làm theo manual bo tùy chỉnh bằng cách copy định nghĩa bo khác, nhưng dù đã sửa lỗi build và cảnh báo, vẫn không build thành công cho bo mục tiêu
Cuối cùng quay lại FreeRTOS
- Trong ba RTOS, chỉ FreeRTOS là đã build được một phần ứng dụng thực tế
- Vấn đề printf có khả năng phải được giải quyết theo hướng dẫn trực tuyến: thay bằng một implementation printf khác và gọi hàm khác ở những nơi cần thiết
- Kế hoạch sắp tới là tiếp tục thử FreeRTOS
- Vì đây là lựa chọn duy nhất có thể tích hợp tương đối đơn giản vào môi trường hiện có, thay vì phải điều chỉnh môi trường phát triển cho phù hợp với RTOS như các lựa chọn khác
1 bình luận
Các ý kiến trên Hacker News
Có vẻ tác giả bài viết xem RTOS như môi trường Arduino, hoặc như thứ có thể cứ mò sơ sơ là kỳ vọng chạy được. Phần lớn không phải vậy
Ngày nay khá nhiều Arduino bên trong đã có mbed hoặc FreeRTOS, và cũng có cách để lộ chúng ra dùng, nên với phong cách của tác giả có lẽ hướng đó phù hợp hơn
Zephyr dễ dùng và hỗ trợ CLion cũng tốt, nhưng không thể kỳ vọng mọi thứ hoạt động mà không cần cài toolchain. Pi Pico cũng được hỗ trợ rõ ràng, và khi tự dùng tôi không gặp vấn đề gì
Tóm lại, FreeRTOS được hỗ trợ gần như ở mọi nơi, nhưng driver thường theo từng SoC/thiết bị nên khá phiền, API cũng không thân thiện lắm nhưng có thể quen dần. Nếu muốn dùng Bluetooth thì phải tự tìm stack
Zephyr hỗ trợ trừu tượng hóa phần cứng thực sự và hỗ trợ hầu hết các SoC, nhưng có thể phải làm một chút việc với board. Bluetooth stack được cung cấp, và có thể cần bổ sung chút hỗ trợ HCI
NuttX không được hỗ trợ tuyệt vời, nhưng nếu làm cho nó chạy được thì là một lựa chọn khá hay. Hỗ trợ từ ngành hiện vẫn chưa mạnh. Cũng có mbed, nhưng ở đây tôi bỏ qua
Trong mảng RTOS thực tế, thường người ta chọn thứ mà nhà cung cấp SoC hỗ trợ. Kiểu như Nordic thì Zephyr, NXP thì FreeRTOS, như vậy mới nhận được hỗ trợ tốt
Tôi chưa từng thấy trường hợp nào trong dự án firmware cho thiết bị OEM mà các developer không khổ sở với Zephyr. Tôi cũng chưa gặp developer nào làm firmware thực tế cho sản phẩm xuất xưởng mà nghĩ rằng trừu tượng hóa phần cứng của Zephyr là hữu ích
Không có nghĩa là không có người như vậy, nhưng khoảng 5 năm qua tôi chưa gặp
Theo tôi, Zephyr có tài liệu marketing rất đẹp. Nhưng phía sau vẻ hào nhoáng đó là sự cồng kềnh quá mức, biên dịch rất chậm, và môi trường khó bắt đầu
Việc cài toolchain cho toàn hệ thống theo kiểu UNIX truyền thống rất khổ sở, và thật lòng mà nói cũng không phải cách tiếp cận thông minh lắm
Nếu chỉ một mình dùng và chạy tốt thì ổn, nhưng khi nhiều developer xử lý nhiều dự án với nhiều target khác nhau, sẽ tốn rất nhiều thời gian để lần ra vấn đề build và cấu hình
Việc cứ dùng Python cho công cụ cũng chẳng giúp gì. Tôi không hiểu vì sao lại cố dùng một ngôn ngữ mang theo vấn đề phiên bản riêng và hành xử khác nhau trên từng máy developer
Tôi làm embedded development như cả sở thích lẫn công việc khoảng 10 năm rồi, và không hiểu nổi thái độ dành cả tuần để đồng bộ môi trường của mọi người trong dự án mà vẫn không xem đó là vấn đề
Đây là vấn đề thật, gây bực mình, lãng phí thời gian, và không cần thiết
Công cụ nên là binary được link tĩnh. Viết bằng Rust, Go, C, C++ gì cũng được, nhưng tôi mong ưu tiên các công cụ vững chắc có thể tin là hoạt động giống nhau bất kể máy tính đã cài gì, thay vì phát triển chắp vá
Python không làm được điều đó, và việc nổi giận rồi phòng thủ thay vì nhìn nhận vấn đề này nghiêm túc hơn cũng chẳng giúp gì
Dù vậy, những thứ như PlatformIO là hướng đi đúng. Tôi biết nó là dự án Python và đôi khi cũng gây vấn đề, nhưng ít hơn các công cụ khác, và ý tưởng là đúng
Toolchain, SDK, thư viện cần được quản lý; cấu hình dự án phải đơn giản; và build phải tái lập được ở mọi nơi, mọi lúc
Tôi mong ngành embedded nhận ra nhiều hơn giá trị của nỗ lực cấu trúc hóa chung. Tôi quen nhiều người làm ở các hãng MCU lớn, nhưng nhìn chung họ bận giải quyết vấn đề trước mắt của mình nên có vẻ khá hạn hẹp trước nhu cầu của developer, điều đó luôn làm tôi thất vọng
Tôi có một dự án bàn phím chạy trên RP2040 và firmware viết bằng Rust. Khi chỉ có repository và chưa có Rust toolchain, quy trình flash như sau
(cài rustup)
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh$ rustup target add thumbv6m-none-eabi$ cargo install elf2uf2-rs* chuyển bàn phím sang chế độ bootloader *$ cargo run --releaseỞ đây rustup và Cargo xử lý phần lớn việc quản lý toolchain và build, và cả hai thật sự rất tuyệt. Dự án Python không thể lại gần mức đó
Chỉ cần chạy một lệnh là dù ở local hay CI, mọi thứ đều được thiết lập và chạy theo đúng cùng một cách với cùng phiên bản công cụ build và thư viện
Vì vậy mỗi lần quay lại xử lý CMake và Make rồi vật lộn cài thư viện, tôi lại thấy khổ sở. Nó quá khác với kiểu
compile 'library-name-here'Thay vào đó, họ chọn vì hệ sinh thái thư viện. Trong phân tích dữ liệu, trực quan hóa, tính toán khoa học, không có lựa chọn nào ở ngôn ngữ khác có thể sánh được
Đây là những lĩnh vực không ai muốn triển khai lại từ đầu. Những phần như độ ổn định số rất dễ đưa bug vào, có thể 99% cho kết quả đúng nhưng 1% còn lại thì nhìn có vẻ hợp lý mà lại hoàn toàn sai
Cá nhân tôi đã bắt đầu chuyển các dự án RP2040 sang Rust và Embassy
Rust cần một chút thời gian để làm quen, nhưng tôi khá thích. Nó không phải RTOS, nhưng đáp ứng được nhiều nhu cầu vốn khiến người ta cần đến RTOS
Rust và Cargo loại bỏ nỗi khổ khi build và flash RP2040, hoặc STM32. Đây là môi trường embedded dễ chịu nhất mà tôi từng dùng cho đến nay
Nhiều ứng dụng không dùng MPU. Cứ xét thêm tính an toàn bộ nhớ của Rust và độ phức tạp tổng thể của firmware giảm đi khi không dùng RTOS là được
Bất đồng bộ của Embassy executor cũng hoạt động rất tốt, giúp giảm rất nhiều đau đớn trong quá trình thiết kế RTOS
Thêm cả giao diện PIO cho RP2040 nữa thì code trở nên rất đơn giản và đẹp, tạo ra một cấu hình khó đạt được với các bộ xử lý khác
Pi Pico được Zephyr hỗ trợ 100%. Có phải họ chưa xem tài liệu https://github.com/zephyrproject-rtos/zephyr/tree/main/board... không? https://docs.zephyrproject.org/latest/boards/raspberrypi/rpi...
Ngoài ra, trong nhiều trường hợp, cách dùng được chủ đích không phải là dùng một bản cài Zephyr “main” duy nhất, mà là đưa các module bên ngoài cần thiết vào
west.ymlcủa dự ánViệc nhiều dự án chia sẻ cùng một bản cài Zephyr là một câu chuyện khác, nhưng cài mọi toolchain và HAL có thể có không phải là cách duy nhất
ThreadX, hiện là mã nguồn mở, đã không được nhắc đến
https://github.com/eclipse-threadx/threadx/
https://github.com/RIOT-OS/RIOT
Phần so sánh các lựa chọn RTOS khá hay
Cá nhân tôi thấy MicroPython là con đường dễ hơn. Đa nhiệm hợp tác dựa trên
async/awaithợp với tôiTrong dự án gần đây, tôi xử lý cả 6 động cơ bước, nhiều LED và quét nút bấm, và với người dùng thì nó trông như thời gian thực
Thật đáng ngạc nhiên là đến giờ nhiều người vẫn chưa thực sự hiểu ngày đó chúng ta có ít tài nguyên đến mức nào, vậy mà vẫn dùng ngôn ngữ bậc cao
Khi dùng MicroPython trên ESP32, tôi thích việc nó hỗ trợ trình xử lý ngắt theo kiểu rất Python. Tôi nghĩ RP2040 cũng được hỗ trợ tương tự
Trên kính hiển vi của tôi, việc điều khiển động cơ bước gần như thời gian thực do một board FluidNC đảm nhiệm, còn tôi điều khiển bằng một giao thức serial nhẹ
Tuy vậy, tôi đang xem https://pypi.org/project/micropython-stepper/, và nó trông như dùng timer phần cứng cùng code Python
Tôi muốn thử dùng Hubris trong một dự án thực sự (https://hubris.oxide.computer/reference/)
Cách tiếp cận kiến trúc của nó khá phù hợp với hướng tôi nhắm tới trong lĩnh vực embedded. Chỉ là tôi đang tự làm khổ mình hơn bằng C
Nó cũng không khác quá xa cách làm với Erlang/Elixir trong môi trường hosted
Trong những tình huống bộ nhớ hạn chế hơn, không gánh nổi nhiều stack, Embassy có vẻ là lựa chọn tốt
Dự án embedded mới luôn luôn, thực sự luôn luôn, nên bắt đầu trong máy ảo. Đừng trộn lẫn các công cụ trên cùng một hệ thống
Trong các dự án thương mại của tôi, đây chính là nguyên nhân số 1 gây vấn đề chất lượng
Nếu bắt đầu một dự án với chipset mới, vendor mới, hãy tạo một VM mới, chỉ cài công cụ của vendor vào VM đó rồi build ở đó
Phát triển thử nghiệm thì làm trên máy local không phải VM của bạn cũng được. Nhưng release thì nhất định phải làm trong VM. Và làm ơn hãy đồng bộ VM với workstation phát triển
Hiện tôi đang chịu khổ khủng khiếp vì phải xử lý một bản build firmware bắt buộc phải sửa trong khi lập trình viên ban đầu đang đi nghỉ. Không ai truy cập được workstation của người đó, VM đã chuẩn bị thì tụt hậu 6 tháng, còn khách hàng thì hỏi tại sao họ phải trả chi phí cho cả đội vì một việc mà trên đời chỉ một lập trình viên đặc biệt duy nhất làm được
Tôi nghĩ release nên do hệ thống CI build. Khi gắn tag release trong git, sau khi test pass thì binary được tạo ra
Nếu chọn FreeRTOS thì khó mà sai nghiêm trọng. Ở thời điểm này, trên thực tế nó là tiêu chuẩn ngành
printf()hoạt động không?Trông có vẻ khá đau đầu
Tôi cũng từng có trải nghiệm tương tự bài viết gốc
Vì vậy tôi đã tự làm một timer green thread đơn giản
Nó không hỗ trợ quản lý tiến trình thực sự như một kernel thật và cũng không đưa ra bất kỳ bảo đảm nào, nhưng đã giúp tôi đi xa hơn so với lập lịch bare-metal và tránh được mớ hỗn độn mang tên RTOS
Có thể xem nó như callback timer kiểu JavaScript trong C, nhận một struct ngữ cảnh tùy chọn
Tôi đã có thể xử lý việc truy vấn nhiều cảm biến, xử lý tín hiệu đầu vào, đưa ra quyết định điều khiển và gửi lệnh ra ngoài, mỗi việc ở các chu kỳ khác nhau
Trước khi tự làm khổ mình với một kiến trúc chậm chạp và trừu tượng như vậy, tôi rất khuyến nghị hãy thử cách này trước