Không cần RTOS
Task giao tiếp bằng message, chạy hết việc mới nhường. Tốn ít RAM, không có race giữa các task.
Nền firmware bare-metal cho STM32L151, dựng trên kernel AK Active Object: các task giao tiếp qua message, chạy hết việc mới nhường (run-to-completion), không cần RTOS. Có sẵn bootloader, cập nhật firmware không cần ST-Link, và build bằng một lệnh. Kit trên trang này đang chạy thật: đó là chính mã C của firmware, biên dịch sang WebAssembly.
Bấm B1 B2 B3 (hoặc phím 1 2 3) để tự điều khiển · mở bản đầy đủ có shell →
Repo gồm hai phần: ak-mcu-base (bản viết lại, tách khỏi chip, có unit test; dự án mới
bắt đầu từ đây) và base cũ trong legacy/ (dành cho dự án đang chạy trên nó).
Cả hai cùng dựa trên kernel AK.
Task giao tiếp bằng message, chạy hết việc mới nhường. Tốn ít RAM, không có race giữa các task.
Kiểm ảnh trước khi chạy, cài bản mới từ flash ngoài, tự cứu khi app hỏng.
Qua Modbus RS485 (v1.3.0) hoặc UART console (ak-mcu-base), có tool PC đi kèm.
Bật/tắt module bằng cờ biên dịch, release tự gắn version, vượt dung lượng flash là báo lỗi ngay.
Mỗi task là một active object: có hàng đợi message riêng, chỉ xử lý một message mỗi lần. Ngắt (ISR) không làm việc nặng, chỉ post message rồi thoát. Scheduler luôn chọn task có ưu tiên cao nhất đang có message.
Có sự kiện (ngắt UART, hết giờ timer, task khác gửi) → task_post_*()
Lấy message từ pool tĩnh (pure / common / dynamic), không malloc trong ISR
8 mức ưu tiên, mỗi task một hàng đợi FIFO
Gọi handler của task, chạy xong mới lấy message tiếp; giải phóng message
/* task_list.h — thứ tự enum phải khớp bảng */ enum { TASK_TIMER_TICK_ID, TASK_SYSTEM_ID, TASK_SENSOR_ID, /* task mới */ AK_TASK_EOT_ID, }; /* task_list.c */ task_t app_task_table[] = { {TASK_TIMER_TICK_ID, TASK_PRI_LEVEL_7, task_timer_tick}, {TASK_SYSTEM_ID, TASK_PRI_LEVEL_2, task_system}, {TASK_SENSOR_ID, TASK_PRI_LEVEL_3, task_sensor}, {AK_TASK_EOT_ID, TASK_PRI_LEVEL_0, (pf_task)0}, };
/* task_sensor.c */ void task_sensor(ak_msg_t* msg) { switch (msg->sig) { case SENSOR_SIG_INIT: /* đọc cảm biến mỗi 500 ms */ timer_set(TASK_SENSOR_ID, SENSOR_SIG_READ, 500, TIMER_PERIODIC); break; case SENSOR_SIG_READ: { uint8_t v = sensor_read(); task_post_common_msg(TASK_SYSTEM_ID, SYSTEM_SIG_DATA, &v, 1); } break; } }
Kernel tra task theo chỉ số task_table[id], nên dòng thứ n
phải mang id = n. Lệch thứ tự thì FATAL("TK", 0x08) ngay lúc khởi động, thay vì để message đi nhầm task.
Làm sản phẩm ngay thì dùng base v1.3.0. ak-mcu-base là hướng đi tiếp theo: cùng ý tưởng nhưng tách khỏi chip để port sang chip khác, và có test chạy trên máy tính.
Base cũ v1.3.0 — legacy/ | ak-mcu-base — gốc repo | |
|---|---|---|
| Trạng thái | Ổn định, đã kiểm trên board | v1.1.0, đã chạy trên board; còn thiếu driver và Modbus |
| Chip | STM32L151CBT6 | Đa nền tảng qua lớp HAL · port STM32L151 + host (PC) |
| Build | PlatformIO | CMake (đã kiểm) · PlatformIO (mới kiểm cú pháp) |
| Cập nhật firmware | Modbus RS485 (env app_mbslave), UART bootloader | Giao thức fw_proto trên UART console, nhận qua bootloader hoặc app |
| Chỗ chứa ảnh mới | Flash SPI ngoài | Flash SPI ngoài (mặc định) hoặc flash trong |
| An toàn mất điện | Cắt ngang khi nhận: app cũ vẫn chạy (đã thử trên board) | Cắt ở bất kỳ bước ghi flash nào cũng tự cài tiếp (test giả lập) |
| Thư viện kèm | nanoMODBUS, ArduinoJson, QRCode, driver OLED/nút/buzzer | Tối giản: kernel, log, CRC, OTA |
| Test | Test host cho Modbus slave | Unit test kernel + bootloader, test OTA đầu-cuối trên trình giả lập |
Dựng lại từ ak-base-kit-stm32l151 (bản gốc build bằng Makefile) theo mô hình PlatformIO đã chạy ổn định trong sản phẩm. Bare-metal, SPL + CMSIS + startup + linker script nằm sẵn trong repo.
pio run -e app # firmware ứng dụng (Modbus master, mặc định) pio run -e app_mbslave # biến thể Modbus SLAVE (OTA qua RS485) pio run -e boot # bootloader pio run -e boot -t upload # 1. nạp boot (board trắng phải nạp cả hai) pio run -e app -t upload # 2. nạp app bằng ST-Link pio device monitor # console UART1 115200
Không cần cài Python. Chọn cổng COM, baud 9600, slave 1 → Kiểm tra board → chọn file
.bin của app_mbslave → Nạp. Xong hiện PASS/FAIL và tự đọc lại phiên bản.
legacy/tools/EPCB_Modbus_Flash_v1.0.0.exe
Kiểm ảnh trước khi gửi, tự xác nhận phiên bản sau khi board khởi động lại.
python -m epcb_applib.ota COMx \ legacy/release/app_mbslave/ak_base_kit_app_mbslave_v1.3.0.bin \ --slave 1 --baud 9600
app_mbslave. Ảnh của env app vẫn qua được bước kiểm
nhưng không có task Modbus slave: nạp xong board mất đường OTA, phải quay lại dùng ST-Link.v1.3.0, xoá .git, git init, ghi "Khởi tạo từ ak-base-kit-pio v1.3.0" vào README.platformio.ini: APP_TITLE, APP_VERSION, build_dir, prefix tên file release.TASK_MBMASTER_EN / TASK_MBSLAVE_EN / SERIAL2_EN (USART2 chỉ một chủ), OLED, link UART…io_cfg.h/.c, sys_cfg.c.app/task_xxx.cpp.APP_VERSION, build, lấy file trong release/.Kernel AK, bootloader và OTA viết một lần, chạy trên mọi chip có port. Code phía trên lớp HAL không include header của chip. Port host chạy unit test và giả lập cả bootloader lẫn app ngay trên máy tính, không cần board.
0x80000 như base cũ
Board không gắn chip SPI: build make stm32-internal → APP 58K + STAGING 58K, cả hai trong flash trong.
| Tình huống | Hành động |
|---|---|
App gửi lệnh LOADER | Ở lại bootloader, chờ ảnh qua UART |
App gửi lệnh UPDATE, STAGING hợp lệ, chưa quá 3 lần thử | Cài: chép STAGING → APP |
| APP hợp lệ | Chạy app (không đọc STAGING, boot nhanh) |
APP không có header (nạp .elf debug) và không có lần cài dở | Chạy kèm cảnh báo |
| APP hỏng, STAGING còn ảnh | Cứu: cài lại từ STAGING |
| Còn lại | Bootloader chờ nạp |
Trang header của APP bị xóa đầu tiên và chép cuối cùng. Mất điện ở bất kỳ bước nào thì APP luôn "không hợp lệ", lần boot sau tự chép lại từ STAGING.
CRC32 của header và ảnh, tên board (chống nạp nhầm board), địa chỉ nạp, bảng vector. App không ghi được vào chính nó hay vào bootloader.
Khối 128 B được ghi trong một lần thay vì 32 lần, bằng hàm chạy từ RAM. Header nằm sẵn trong .elf, nạp bằng SWD vẫn boot được.
cd ak-mcu-base make test # unit test + test OTA đầu-cuối trên trình giả lập make sim # chạy bootloader + app giả lập: gõ 'help' trong shell make stm32 # build/stm32l151/{boot.bin, app.img, *.elf} # nạp firmware qua UART console (board thật hoặc trình giả lập) python3 tools/ak_fw.py --port /dev/ttyUSB0 flash build/stm32l151/app.img python3 tools/ak_fw.py --sim build/host/ak_sim info
| Hạng mục | Kết quả |
|---|---|
| Unit test kernel (16 ca), ASan + UBSan | pass |
| Unit test bootloader/OTA, 2 kiểu staging, 6.504 điểm cắt điện | pass |
OTA đầu-cuối trên trình giả lập bằng chính tool ak_fw.py (13 bước) | pass |
Build STM32 cả hai chế độ, -Wall -Wextra -Werror | pass |
| Chạy trên board thật (UART, flash, SPI NOR, watchdog, nhảy app, OTA) | đã chạy — v1.1.0, 06/10/2026 |
README đầy đủ của ak-mcu-base → · Hướng dẫn port sang chip mới →
Chi tiết từng bản ở CHANGELOG.md.
Base đa nền tảng: kernel AK tách khỏi chip, bootloader + OTA an toàn khi mất điện, ghi half-page, staging trên flash SPI ngoài, port host + trình giả lập.
OTA qua Modbus RS485 dùng được thật trên env app_mbslave; đã kiểm trên board (≈ 78 s/lượt @ 9600, cắt ngang vẫn chạy app cũ).
Modbus chuyển sang nanoMODBUS (MIT), thêm chế độ slave, bỏ thư viện thương mại, thêm test host.
Sửa cờ biên dịch không tới file nguồn, version lấy từ APP_VERSION, kiểm thứ tự bảng task, giới hạn flash theo env.
Bootloader 0.0.2/0.0.3: tự kiểm bảng vector, tự vá BSF; sửa 11 lỗi (UART, RS485, ADC, GPIO, cờ build).
Bản đầu tiên đánh tag: port AK Embedded Base Kit sang PlatformIO, chống ghi đè bootloader khi nạp.
Toàn bộ tài liệu viết bằng tiếng Việt, nằm trong repo.