Kế hoạch · học gì
Lộ trình 7 giai đoạn
Mỗi giai đoạn có tiêu chí hoàn thành kiểm chứng được. Không chuyển giai đoạn khi tiêu chí chưa đạt, kể cả khi "về cơ bản là chạy".
GĐ 0 — Dựng toolchain
1 phiên.
$ rustup target add riscv32imac-unknown-none-elf # C6 là RISC-V có atomics → Rust stable
$ cargo install espflash
$ cargo install esp-generate --locked
$ esp-generate --chip esp32c6 -o embassy -o unstable-hal -o alloc helloVì sao dùng esp-generate thay vì tự viết Cargo.toml: một project no_std trên ESP cần
khớp đúng bộ Cargo.toml + .cargo/config.toml (runner, rustflags) + rust-toolchain.toml
build.rs+ linker script. Bộ này đổi giữa các version esp-hal, nên chép từ blog gần như chắc chắn sai Nguồn source · ví dụ esp-rs/esp-generate Sinh project no_std đúng bộ Cargo.toml + .cargo/config.toml + build.rs; đọc mục Quickstart và list-options. Đọc thêm tài liệu chính chủ The Rust on ESP Book Tài liệu chính chủ cho Rust trên chip Espressif — có cả phần OTA, debugging và troubleshooting. Mở trong tab mới · Esc để đóng . Chạyesp-generate list-optionsđể xem hết option.
Chọn cơ chế log ngay từ đầu và giữ nguyên cả dự án: esp-println cộng log (đơn giản,
đọc bằng espflash monitor), hoặc defmt cộng probe-rs (nhẹ hơn, cần công cụ
decode) Nguồn sách defmt book Cơ chế hoãn format và mục timestamp — feature này tắt mặc định, phải tự bật. Mở trong tab mới · Esc để đóng . Thêm esp-backtrace để panic in ra stack trace thay vì
reset im lặng Nguồn tài liệu chính chủ esp-backtrace Panic handler in ra stack unwind; feature flag quyết định backtrace có hay không. Mở trong tab mới · Esc để đóng .
Trên Arch và các bản dẫn xuất, group cổng serial là uucp, không phải dialout Nguồn tài liệu chính chủ Arch Wiki — Users and groups Bảng group hệ thống: uucp là group cổng serial trên Arch, không phải dialout. Mở trong tab mới · Esc để đóng . Sai chỗ này thì espflash báo permission denied trên /dev/ttyACM0, và người mới gần như luôn đi tìm lỗi ở toolchain thay vì ở quyền truy cập.
Sau khi usermod, phải đăng xuất rồi đăng nhập lại hoặc chạy newgrp thì group mới có hiệu lực — nếu không sẽ tưởng lệnh không ăn.
Cắm board vào máy nào
Máy dev, không phải máy trạm lab. Đây là điểm hay bị hiểu nhầm, nên nói rõ lý do:
- Vòng lặp sửa code →
cargo run→ xem log chạy hàng chục lần mỗi phiên. Đó là vòng lặp chặt nhất của cả dự án; đặt board ở máy khác là thêm một hop SSH vào đúng chỗ không nên có. - Board nói với Mosquitto qua WiFi, nên để breadboard cạnh máy trạm không mua được lợi ích gì.
- Logic analyzer cũng cắm cùng máy — PulseView và serial monitor cần mở cạnh nhau trên một màn hình.
Ngoại lệ, từ GĐ 5: khi chạy bài test 72h, đừng để board cắm vào máy dev — máy đó sẽ sleep hoặc reboot và giết bài test. Cấp nguồn từ củ sạc tường là đủ, vì dữ liệu đi qua MQTT. Chỉ chuyển board sang máy always-on nếu muốn bắt log UART liên tục suốt 72h vào file để biết nó chết vì lý do gì — đó là lúc duy nhất máy always-on có giá trị thật.
Tiêu chí hoàn thành: cargo run nạp được binary lên board và thấy dòng log của chính
mình trên serial monitor; cố tình panic!() và thấy backtrace.
Bẫy: cáp không có dây data; board chưa vào bootloader (giữ BOOT, nhấn nhả EN, thả BOOT).
GĐ 1 — GPIO, thời gian, và async
2–3 phiên. Theo đúng thứ tự này, mỗi bước là một phát hiện:
- Blink bằng delay chặn (blocking).
- Đọc nút nhấn bằng polling. Đếm số lần bấm. Quan sát: một lần bấm cho ra 3–7 lần đếm. Đó là nảy phím, vật lý chứ không phải bug.
- Chuyển sang GPIO interrupt.
- Thêm debounce, dùng con số đo được từ chính cái nút của mình.
- Chuyển toàn bộ sang embassy async, tách thành 2 task độc lập Đọc thêm sách Embassy Book Mô hình async trên bare metal; đọc chương executor và task trước Khối A. Mở trong tab mới · Esc để đóng .
Tiêu chí hoàn thành: một task nhấp nháy LED chu kỳ 500ms, một task đếm nút nhấn; đo bằng logic analyzer thấy chu kỳ blink không bị lệch khi bấm nút liên tục. Nếu blink bị giật, tức là vẫn đang chặn ở đâu đó.
Khái niệm mới: no_std nghĩa là gì; peripheral là singleton và ownership của Rust ép
anh không thể chiếm đoạt hai lần; tại sao không được cấp phát bộ nhớ trong ISR;
embassy_sync::Mutex khác std::sync::Mutex ở chỗ nào và vì sao nó phải khác Đọc thêm sách The Embedded Rust Book Viết cho STM32 nhưng khái niệm nền — peripheral singleton, ISR, no_std — dùng chung. Mở trong tab mới · Esc để đóng .
Nút nhấn tactile 4 chân có hai cặp chân nối sẵn bên trong. Chọn nhầm cặp thì "nút" là một đoạn dây nối cứng, luôn đóng — code chạy đúng mà hành vi vẫn sai, và không có gì báo lỗi. Chế độ đo thông mạch có còi tìm ra trong 5 giây.
LED cắm ngược. Chân dài là anode. Đo bằng thang diode thấy ngay.
Dây dupont đứt ngầm. Vỏ còn nguyên, lõi đứt bên trong. Không đo thông mạch thì không có cách nào phát hiện ngoài việc thay từng sợi.
Cả ba đều có chung một triệu chứng — "code đúng mà mạch không chạy" — và cả ba đều nằm ở lớp vật lý. GĐ 1 là lần đầu đấu dây, tức là lúc xác suất mắc chúng cao nhất.
GĐ 2 — I²C và datasheet
3–4 phiên. Giai đoạn quan trọng nhất của cả lộ trình.
Đây là chỗ quyết định anh có thực sự học được gì hay chỉ đang copy ví dụ. Kỹ năng ở đây chuyển được sang mọi cảm biến khác.
- Viết đoạn scan bus I²C, quét địa chỉ 0x08–0x77, in ra thiết bị nào ACK.
- Đọc thanh ghi
0xD0của BME280, xác nhận nhận về0x60. Nếu ra0x58thì đang cầm BMP280. - Đọc khối calibration từ NVM của cảm biến — dữ liệu hiệu chuẩn riêng của từng con chip, ghi tại nhà máy.
- Cài đặt công thức bù nhiệt độ/áp suất/độ ẩm theo đúng datasheet Nguồn datasheet BME280 Datasheet (Bosch Sensortec) Riêng phần công thức bù — chú ý có hai phiên bản, số nguyên 32-bit và số thực, trộn lẫn là sai. Mở trong tab mới · Esc để đóng . Viết thành hàm thuần trong crate riêng và unit test trên host bằng bộ số mẫu lấy từ driver tham chiếu của hãng Nguồn source · ví dụ boschsensortec/BME280_SensorAPI Driver tham chiếu của hãng — lấy bộ số mẫu ở đây để viết test chạy trên host. Mở trong tab mới · Esc để đóng .
- Đóng gói thành driver implement trait
embedded-hal1.0, generic overI2c, test vớiembedded-hal-mockNguồn tài liệu chính chủ embedded-hal 1.0 Trait I2c/SpiDevice — driver generic over trait mới chạy được trên chip khác. Đọc thêm source · ví dụ embedded-hal-mock Giả lập transaction I²C để cargo test driver trên máy tính, không cần board. Mở trong tab mới · Esc để đóng — để driver chạy được trên bất kỳ chip nào khác.
Tiêu chí hoàn thành: nhiệt độ đọc ra khớp trong khoảng ±1°C với DS18B20 đặt cạnh;
cargo test của crate driver pass trên host; và dùng logic analyzer decode được transaction
I²C, chỉ ra được start condition, byte địa chỉ, ACK Đọc thêm tài liệu chính chủ sigrok wiki — PulseView Phần mềm bắt và giải mã waveform; con trỏ đo khoảng thời gian nằm ở đây. Mở trong tab mới · Esc để đóng .
Datasheet BME280 cho công thức bù ở hai dạng: số nguyên 32-bit và số thực. Trộn lẫn hai phiên bản cho ra kết quả sai một cách rất thuyết phục — số trông hợp lý, đơn vị đúng, chỉ có giá trị là sai. Đây là lý do bước này phải có test trên host với bộ số mẫu của hãng, chứ không được "nhìn thấy 28°C thì chắc là đúng".
Bẫy còn lại:
- Thiếu điện trở pull-up 4.7kΩ lên 3.3V trên SDA/SCL. Nhiều module GY-BME280 có sẵn, nhiều module thì không — đo bằng đồng hồ, đừng đoán.
- Cấp 5V vào module 3.3V.
- Địa chỉ I²C của BME280 là
0x76hoặc0x77tuỳ chân SDO.
GĐ 3 — Năng lượng và deep sleep
2 phiên.
- Cho chip ngủ sâu 30 giây, thức dậy, đọc cảm biến, ngủ tiếp.
- Giữ một biến đếm qua các lần ngủ bằng RTC fast memory — C6 có, khác vài chip khác
trong họ Nguồn tài liệu chính chủ esp-hal trên docs.rs API reference; mục esp_hal::gpio cho Output/Input/Level/Pull/OutputConfig. Đọc thêm source · ví dụ esp-rs/esp-hal — examples/ và qa-test/ Tài liệu thật của dự án. README nói thẳng: xem examples ở đúng tag của bản esp-hal đang dùng, đừng đọc nhánh main. Mở trong tab mới · Esc để đóng . Cần feature
unstablecủa esp-hal. - Đo dòng tiêu thụ: nối đồng hồ vạn năng nối tiếp với đường nguồn.
Tiêu chí hoàn thành: counter tăng đúng qua 10 chu kỳ ngủ/thức; dòng lúc ngủ giảm rõ rệt so với lúc chạy WiFi.
Deep sleep làm USB-Serial-JTAG tích hợp rớt khỏi máy tính mỗi chu kỳ — log qua cổng USB native sẽ đứt quãng. Đây chính là lý do phải có đường log đi qua chip bridge rời: chip bridge ăn nguồn từ rail board, độc lập với trạng thái ngủ của C6.
Dựng đường log trước, viết code sleep sau. Làm ngược thứ tự là mất cả phiên vào việc "sao không thấy log gì".
Lưu ý về con số đo được:
- Đồng hồ vạn năng phổ thông không đo chính xác được dòng cỡ µA. Sẽ đo được mA và thấy được xu hướng — thế là đủ cho giai đoạn này.
- Dòng sleep trên board devkit cao hơn datasheet nhiều lần vì LED nguồn và LDO trên board vẫn ăn điện. Đừng kết luận firmware sai khi thấy số xấu. Muốn số đẹp thì phải sang board tối giản ở GĐ 6.
GĐ 4 — ESP-NOW: hai con chip nói chuyện
2–3 phiên.
Vì sao đi đường này trước khi đụng WiFi/TCP: ESP-NOW gửi gói trực tiếp trên lớp WiFi MAC — không association, không DHCP, không TCP/IP stack Nguồn tài liệu chính chủ ESP-NOW (Espressif) Gói đi thẳng trên lớp WiFi MAC — không association, không DHCP, không TCP/IP. Mở trong tab mới · Esc để đóng . Anh có liên lạc không dây thật với khoảng 1/5 độ phức tạp.
- Khởi tạo
esp-radioở chế độ ESP-NOW, khai báo peer bằng địa chỉ MAC Nguồn tài liệu chính chủ esp-radio (tên mới của esp-wifi) Còn ở 1.0-beta, API vẫn đổi — ghim cứng version bằng dấu =. Đọc thêm source · ví dụ esp-rs/esp-hal — examples/ và qa-test/ Tài liệu thật của dự án. README nói thẳng: xem examples ở đúng tag của bản esp-hal đang dùng, đừng đọc nhánh main. Mở trong tab mới · Esc để đóng . - Định nghĩa struct dữ liệu cảm biến, serialize sang byte Nguồn tài liệu chính chủ postcard Serialize no_std cho struct cảm biến gửi qua ESP-NOW; hàm serialize test được trên host. Mở trong tab mới · Esc để đóng , gửi đi. Hàm serialize và deserialize test trên host.
- Bên nhận: deserialize, in ra.
Tiêu chí hoàn thành: node A gửi 100 gói có đánh số thứ tự, node B nhận được ít nhất 95 gói và anh báo cáo được tỉ lệ mất gói — chứ không chỉ "thấy có dữ liệu về".
Khái niệm mới: esp-radio cần heap, nghĩa là phải cấu hình esp-alloc thủ công; kích
thước heap là thứ anh tự chọn và tự chịu trách nhiệm.
GĐ 5 — Gateway: WiFi + MQTT + Home Assistant
4–5 phiên. Giai đoạn gai nhất. Chia làm hai bước, không gộp.
5a — Một node, WiFi thẳng lên MQTT
embassy-netcộng DHCP để lấy IP Nguồn source · ví dụ esp-rs/esp-hal — examples/ và qa-test/ Tài liệu thật của dự án. README nói thẳng: xem examples ở đúng tag của bản esp-hal đang dùng, đừng đọc nhánh main. Mở trong tab mới · Esc để đóng .rust-mqttpublish lên Mosquitto Nguồn tài liệu chính chủ rust-mqtt Client MQTT no_std dùng ở GĐ 5. Mở trong tab mới · Esc để đóng . Bật auth user/pass từ đầu.- Publish payload theo định dạng MQTT Discovery của Home Assistant để entity tự xuất hiện Nguồn tài liệu chính chủ Home Assistant — MQTT Discovery Định dạng payload để entity tự xuất hiện, không phải sửa dòng YAML nào. Mở trong tab mới · Esc để đóng . Hàm build payload JSON test trên host.
- Publish kèm một counter uptime để phát hiện reset ngầm.
- WiFi SSID/password và MQTT credential không commit vào git: đọc từ env var lúc build, file cấu hình local bị gitignore.
5b — Gateway: thêm ESP-NOW vào node đang chạy WiFi
Gateway vừa làm STA vừa nhận ESP-NOW.
ESP-NOW và STA dùng chung radio, nên phải ở cùng một kênh WiFi. Kênh của gateway do router quyết định; các node cảm biến phải set đúng kênh đó.
Mesh WiFi có thể tự đổi kênh, và khi đó node mất liên lạc "ngẫu nhiên". Giải pháp: ghim kênh cố định trên mesh, hoặc cho node quét kênh khi mất gói. Nếu node mất liên lạc không theo quy luật nào, nghi kênh trước khi nghi code.
Tiêu chí hoàn thành: entity xuất hiện trong Home Assistant mà không cấu hình tay dòng YAML nào; thiết bị chạy 72 giờ liên tục với uptime counter tăng đơn điệu.
Bẫy — đây là nơi phần lớn thời gian sẽ bị đốt:
- Heap và stack size của task phải chỉnh tay; thiếu thì lỗi hiện ra dưới dạng stack overflow hoặc watchdog reset, không phải compile error.
- Task watchdog đá khi block quá lâu mà không yield.
- Brownout reset lúc WiFi phát — cắm tụ 470µF, dùng nguồn ngoài thay vì cổng USB máy tính.
- Logic reconnect: WiFi sẽ rớt. Firmware không có đường phục hồi sẽ treo im lặng.
GĐ 6 — Sản phẩm hoá
Dài hạn, không giới hạn phiên.
- OTA update — cần đổi partition table sang 2 slot app Nguồn source · ví dụ esp-rs/esp-hal — examples/ và qa-test/ Tài liệu thật của dự án. README nói thẳng: xem examples ở đúng tag của bản esp-hal đang dùng, đừng đọc nhánh main. Đọc thêm tài liệu chính chủ The Rust on ESP Book Tài liệu chính chủ cho Rust trên chip Espressif — có cả phần OTA, debugging và troubleshooting. Mở trong tab mới · Esc để đóng . Việc này phải làm ngay từ lần flash đầu của thiết bị sẽ bắt lên tường, không phải sau.
- Hàn mạch lên perfboard, bỏ breadboard.
- Chạy LiFePO4 cấp thẳng 3V3, tối ưu duty cycle deep sleep, cắt LED nguồn.
- Watchdog và đường phục hồi tự động.
- Đóng hộp.
Tiêu chí hoàn thành: cập nhật firmware không cần chạm vào thiết bị; thiết bị chạy pin trên 1 tháng.
GĐ 7 — Thread
Tuỳ chọn. Không nằm trong lộ trình chính. Ghi ở đây để biết cánh cửa này tồn tại, và chỉ mở sau khi GĐ 6 xong.
- Crate
openthread— binding Rust bare-metal cho OpenThread, có thư viện build sẵn choriscv32imacnên chỉ cầncargo, không phải cài CMake hay Clang. Hỗ trợ Thread 1.4, MTD/FTD/SED, tích hợpembassy-netNguồn source · ví dụ esp-rs/openthread Binding Rust bare-metal cho OpenThread, có thư viện build sẵn cho riscv32imac — cánh cửa GĐ 7. Mở trong tab mới · Esc để đóng . - Lưu ý khi link: OpenThread gọi vài hàm
str*của C stdlib — phải poly-fill. - Cần một Thread Border Router để vào được mạng; Home Assistant có add-on OTBR.
Stack Zigbee của Espressif chỉ có bản C trên ESP-IDF; crate zigbee trên crates.io bỏ hoang từ 2017. Muốn Zigbee bằng Rust hôm nay là tự viết stack — không phải việc của lộ trình này.
Tài nguyên
Bắt buộc đọc
- Thư mục
examples/củaesp-rs/esp-halNguồn source · ví dụ esp-rs/esp-hal — examples/ và qa-test/ Tài liệu thật của dự án. README nói thẳng: xem examples ở đúng tag của bản esp-hal đang dùng, đừng đọc nhánh main. Mở trong tab mới · Esc để đóng — tài liệu thật, không phải blog. - The Rust on ESP Book Nguồn tài liệu chính chủ The Rust on ESP Book Tài liệu chính chủ cho Rust trên chip Espressif — có cả phần OTA, debugging và troubleshooting. Mở trong tab mới · Esc để đóng — có cả phần OTA, debugging, troubleshooting.
- ESP32-C6 Datasheet và Technical Reference Manual Nguồn datasheet ESP32-C6 Datasheet (Espressif) Mục Electrical Characteristics cho V_OH/drive strength, và mục strapping pin quyết định chân nào được dùng. tài liệu chip ESP32-C6 Technical Reference Manual Nơi tra khi HAL không đủ — thanh ghi, sơ đồ khối peripheral. Mở trong tab mới · Esc để đóng . TRM là nơi tra khi HAL không đủ.
- Datasheet BME280, riêng phần công thức bù Nguồn datasheet BME280 Datasheet (Bosch Sensortec) Riêng phần công thức bù — chú ý có hai phiên bản, số nguyên 32-bit và số thực, trộn lẫn là sai. Mở trong tab mới · Esc để đóng .
- Embassy Book — mô hình async trên bare metal Nguồn sách Embassy Book Mô hình async trên bare metal; đọc chương executor và task trước Khối A. Mở trong tab mới · Esc để đóng .
Nên đọc: The Embedded Rust Book Đọc thêm sách The Embedded Rust Book Viết cho STM32 nhưng khái niệm nền — peripheral singleton, ISR, no_std — dùng chung. Mở trong tab mới · Esc để đóng (viết cho STM32 nhưng
khái niệm nền dùng chung), awesome-esp-rust Đọc thêm source · ví dụ esp-rs/awesome-esp-rust Danh mục crate và ví dụ trong hệ sinh thái — chỗ tra khi cần driver có sẵn. Mở trong tab mới · Esc để đóng , tài liệu MQTT
Discovery của Home Assistant Đọc thêm tài liệu chính chủ Home Assistant — MQTT Discovery Định dạng payload để entity tự xuất hiện, không phải sửa dòng YAML nào. Mở trong tab mới · Esc để đóng , và trang wiki của sigrok về
con logic analyzer đang cầm Đọc thêm tài liệu chính chủ sigrok wiki — fx2lafw Driver cho họ logic analyzer clone dùng chip Cypress CY7C68013A; danh sách thiết bị hỗ trợ 100%. Mở trong tab mới · Esc để đóng .
Chỗ hỏi khi bí: kênh chat cộng đồng esp-rs trên Matrix — có người của Espressif tham
gia.
Đọc trước hoặc trong phiên
- source · ví dụesp-rs/esp-hal — examples/ và qa-test/
Tài liệu thật của dự án. README nói thẳng: xem examples ở đúng tag của bản esp-hal đang dùng, đừng đọc nhánh main.
- tài liệu chính chủThe Rust on ESP Book
Tài liệu chính chủ cho Rust trên chip Espressif — có cả phần OTA, debugging và troubleshooting.
- source · ví dụesp-rs/esp-generate
Sinh project no_std đúng bộ Cargo.toml + .cargo/config.toml + build.rs; đọc mục Quickstart và list-options.
- sáchEmbassy Book
Mô hình async trên bare metal; đọc chương executor và task trước Khối A.
- datasheetBME280 Datasheet (Bosch Sensortec)
Riêng phần công thức bù — chú ý có hai phiên bản, số nguyên 32-bit và số thực, trộn lẫn là sai.
- tài liệu chính chủsigrok wiki — PulseView
Phần mềm bắt và giải mã waveform; con trỏ đo khoảng thời gian nằm ở đây.
- datasheetESP32-C6 Datasheet (Espressif)
Mục Electrical Characteristics cho V_OH/drive strength, và mục strapping pin quyết định chân nào được dùng.
Tra khi cần, không đọc tuyến tính
- sáchdefmt book
Cơ chế hoãn format và mục timestamp — feature này tắt mặc định, phải tự bật.
- tài liệu chính chủesp-backtrace
Panic handler in ra stack unwind; feature flag quyết định backtrace có hay không.
- tài liệu chính chủArch Wiki — Users and groups
Bảng group hệ thống: uucp là group cổng serial trên Arch, không phải dialout.
- tài liệu chính chủesp-hal trên docs.rs
API reference; mục esp_hal::gpio cho Output/Input/Level/Pull/OutputConfig.
- source · ví dụboschsensortec/BME280_SensorAPI
Driver tham chiếu của hãng — lấy bộ số mẫu ở đây để viết test chạy trên host.
- tài liệu chính chủembedded-hal 1.0
Trait I2c/SpiDevice — driver generic over trait mới chạy được trên chip khác.
- source · ví dụembedded-hal-mock
Giả lập transaction I²C để cargo test driver trên máy tính, không cần board.
- tài liệu chính chủsigrok wiki — fx2lafw
Driver cho họ logic analyzer clone dùng chip Cypress CY7C68013A; danh sách thiết bị hỗ trợ 100%.
- tài liệu chipESP32-C6 Technical Reference Manual
Nơi tra khi HAL không đủ — thanh ghi, sơ đồ khối peripheral.
Để dành cho giai đoạn sau
- sáchThe Embedded Rust Book
Viết cho STM32 nhưng khái niệm nền — peripheral singleton, ISR, no_std — dùng chung.
- tài liệu chính chủESP-NOW (Espressif)
Gói đi thẳng trên lớp WiFi MAC — không association, không DHCP, không TCP/IP.
- tài liệu chính chủesp-radio (tên mới của esp-wifi)
Còn ở 1.0-beta, API vẫn đổi — ghim cứng version bằng dấu =.
- tài liệu chính chủpostcard
Serialize no_std cho struct cảm biến gửi qua ESP-NOW; hàm serialize test được trên host.
- tài liệu chính chủrust-mqtt
Client MQTT no_std dùng ở GĐ 5.
- tài liệu chính chủHome Assistant — MQTT Discovery
Định dạng payload để entity tự xuất hiện, không phải sửa dòng YAML nào.
- source · ví dụesp-rs/openthread
Binding Rust bare-metal cho OpenThread, có thư viện build sẵn cho riscv32imac — cánh cửa GĐ 7.
- source · ví dụesp-rs/awesome-esp-rust
Danh mục crate và ví dụ trong hệ sinh thái — chỗ tra khi cần driver có sẵn.