บทความ › IoT และ Embedded

เขียน ESP32 ด้วย Rust: esp-hal 1.0 อะไรเสถียรแล้ว และไฟกะพริบตัวแรก

esp-hal 1.0 คือ Rust SDK สำหรับ ESP32 ที่ Espressif หนุนเอง GPIO UART SPI I2C เสถียรแล้ว Wi-Fi ยังไม่ ตั้งเครื่องมือ สร้างโปรเจกต์ด้วย esp-generate และเขียนไฟกะพริบ

กลาง อ่าน 10 นาที 23 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • esp-hal 1.0 ออก 30 ตุลาคม 2025 เป็น Rust SDK แบบ no_std ที่ Espressif สนับสนุนเอง ไม่ใช่โปรเจกต์ชุมชนล้วนอีกต่อไป
  • ส่วนที่เสถียร: init, GPIO, UART, SPI, I2C (ทั้ง blocking และ async), โมดูล time และ macro #[main] ที่เหลืออยู่หลัง feature unstable
  • Wi-Fi และ Bluetooth อยู่ใน esp-radio ซึ่ง ณ ตุลาคม 2026 ยังเป็น 1.0.0-beta งานที่ต้องต่อเน็ตยังต้องยอมรับ API ที่อาจเปลี่ยน
  • สร้างโปรเจกต์ด้วย esp-generate แล้ว cargo run --release จะ build, แฟลช และเปิด serial monitor ให้ในคำสั่งเดียว

เขียน firmware ESP32 ด้วย Rust ทำได้มาหลายปี แต่เดิมเป็นโปรเจกต์ที่ API เปลี่ยนแทบทุกเวอร์ชัน esp-hal 1.0 เปลี่ยนเรื่องนี้ เป็นครั้งแรกที่ผู้ผลิตชิปหนุน Rust SDK เอง และสัญญาว่า API ส่วนที่ประกาศว่าเสถียรจะไม่พังจนถึง 2.0 บทความนี้บอกว่าตอนนี้อะไรใช้ได้จริง อะไรยังรอ และพาไปถึงไฟกะพริบดวงแรก

ข้อมูลและโค้ดตรวจกับเอกสาร esp-hal 1.0.0 (docs.espressif.com), template ของ esp-generate 1.4.0 และ crates.io ณ ตุลาคม 2026 (esp-hal ล่าสุด 1.2.2, espflash 4.6.0) ตัวอย่างใช้ ESP32-C6

esp-hal คืออะไร และ 1.0 เปลี่ยนอะไร

esp-hal เป็น hardware abstraction layer แบบ no_std ทำงานบนชิปตรง ๆ ไม่มี FreeRTOS หรือ ESP-IDF อยู่ข้างใต้ ไทม์ไลน์

วันที่เหตุการณ์
24 ก.พ. 20251.0.0-beta.0 Espressif ตั้งทีม Rust อย่างเป็นทางการ เรียกว่า Rust SDK ตัวแรกที่ผู้ผลิตหนุนเอง
30 ต.ค. 2025esp-hal 1.0.0
ก.ย. 2026esp-hal 1.2.2 และ esp-radio 1.0.0-beta.2

ประกาศ beta บอกด้วยว่า crate ฝั่ง std (esp-idf-sys, esp-idf-hal, esp-idf-svc) กลายเป็นของชุมชนดูแล ใครอยากได้การสนับสนุนจาก Espressif ให้ย้ายมา esp-hal

เสถียรแล้ว (semver 1.x)init และ configGPIO UART SPI I2Ctime และ #[main]ยังไม่เสถียรdriver อื่นอยู่หลัง unstableesp-radio (Wi-Fi/BLE)ยังเป็น 1.0.0-beta
งานที่จบในสายใช้ได้วันนี้ งานที่ต้องต่อเน็ตยังรอ esp-radio

อะไรเสถียร อะไรยังไม่

ส่วนสถานะ ณ ตุลาคม 2026
esp_hal::init และ configเสถียร
GPIO, UART, SPI, I2C (blocking และ async)เสถียร
โมดูล time (Instant, Duration, Rate)เสถียร
macro #[main], esp-config, system API บางตัว เช่น SoC resetเสถียร
driver อื่นทั้งหมดต้องเปิด feature unstable
Wi-Fi, Bluetooth, ESP-NOW, 802.15.4 (esp-radio)1.0.0-beta ยังไม่ออก 1.0

“unstable” ในที่นี้หมายถึง ความเสถียรของ API ไม่ใช่ driver ห่วย ทีมบอกว่า driver ที่ unstable ใช้กับงานทั่วไปได้ แค่เวอร์ชันย่อยถัดไปอาจเปลี่ยนชื่อหรือรูปร่าง API driver แบบ async ใช้ได้กับ executor หลายตัว เช่น Embassy

เกณฑ์เลือกง่าย ๆ งานที่จบในสาย (เซนเซอร์ I2C, จอ SPI, โมดูล UART) ใช้ esp-hal ได้วันนี้ งานที่ต้องต่อเน็ตเป็นหัวใจ ยอมรับว่า esp-radio ยังเป็น beta

ชิปที่รองรับ

feature ของ esp-hal 1.0 มีให้เลือก esp32, esp32c2, esp32c3, esp32c6, esp32h2, esp32s2, esp32s3 ต้องเปิดตัวเดียวพอดี ชิปแบ่งเป็นสองสถาปัตยกรรม ซึ่งกำหนดวิธีติดตั้ง toolchain

ชิปสถาปัตยกรรมRust target
ESP32-C2, C3RISC-Vriscv32imc-unknown-none-elf
ESP32-C6, H2RISC-Vriscv32imac-unknown-none-elf
ESP32, S2, S3Xtensaxtensa-esp32-none-elf, xtensa-esp32s2-none-elf, xtensa-esp32s3-none-elf

ติดตั้งเครื่องมือ

รันบนเครื่องที่ใช้พัฒนา (Linux, macOS หรือ Windows ที่มี Rust ผ่าน rustup)

# ชิป RISC-V (ตัวอย่างนี้ใช้ ESP32-C6)
rustup toolchain install stable --component rust-src
rustup target add riscv32imac-unknown-none-elf

# ชิป Xtensa (ESP32, S2, S3) ต้องใช้ toolchain ของ Espressif แทน
cargo install espup --locked
espup install

# เครื่องมือแฟลช และตัวสร้างโปรเจกต์
cargo install espflash --locked
cargo install esp-generate --locked

บน Linux/macOS หลัง espup install ต้องตั้ง environment variable ตามที่ README ของ espup บอก ไม่อย่างนั้น build Xtensa ไม่ผ่าน

สร้างโปรเจกต์และไฟกะพริบ

esp-generate -o esp32c6 blinky
cd blinky

ชื่อชิปเป็นหนึ่งในค่าของ -o esp-generate จะเปิดเมนู TUI ให้เลือกตัวเลือกอื่นต่อ (เช่น unstable-hal, Embassy, log) ใส่ --headless ถ้าไม่ต้องการเมนู ดูตัวเลือกทั้งหมดด้วย esp-generate list-options สำหรับไฟกะพริบไม่ต้องเลือกอะไรเพิ่ม มันสร้าง Cargo.toml, rust-toolchain.toml และ .cargo/config.toml ที่ตั้ง runner เป็น espflash flash --monitor ไว้ให้

ต่อ LED กับตัวต้านทาน 330 Ω จาก GPIO5 ลง GND แล้วแก้ src/bin/main.rs

#![no_std]
#![no_main]

use esp_hal::{
    clock::CpuClock,
    gpio::{Level, Output, OutputConfig},
    main,
    time::{Duration, Instant},
};

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

// app descriptor ที่ bootloader ของ ESP-IDF ต้องการ (esp-generate ใส่ให้)
esp_bootloader_esp_idf::esp_app_desc!();

#[main]
fn main() -> ! {
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    let mut led = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default());

    loop {
        led.toggle();
        let start = Instant::now();
        while start.elapsed() < Duration::from_millis(500) {}
    }
}
# เสียบบอร์ดผ่าน USB แล้ว
cargo run --release

cargo run จะ build, แฟลชผ่าน espflash และเปิด serial monitor ให้ในคำสั่งเดียว

สังเกตสามจุด peripherals คือ singleton ได้มาครั้งเดียวจาก init ถ้าเผลอใช้ GPIO5 ซ้ำในสองที่ compiler ไม่ยอม (ownership ของ Rust กันบั๊ก “สองโค้ดแย่งขาเดียวกัน” ตั้งแต่ตอน compile) ข้อสอง ทุกอย่างในโค้ดนี้อยู่ในส่วนที่เสถียร ไม่ต้องเปิด unstable ข้อสาม ลูปนี้เป็น busy-wait CPU ทำงานเต็มตลอด ใช้ได้กับตัวอย่าง งานจริงที่ใช้แบตใช้ async กับ Embassy หรือ sleep แทน

ตัวอย่างคำนวณ: UART ช้ากว่าที่คิด

เพิ่ม UART ส่งข้อความทุกรอบ (ส่วนที่เสถียรเช่นกัน)

use esp_hal::uart::{Config, Uart};

let mut uart = Uart::new(peripherals.UART1, Config::default().with_baudrate(115_200))
    .unwrap()
    .with_rx(peripherals.GPIO4)
    .with_tx(peripherals.GPIO6);

uart.write(b"tick: led toggled, sensor ok, temp=23.5C, rssi=n/a\r\n").unwrap();

คำนวณเวลา ที่ 115,200 baud แบบ 8N1 หนึ่งไบต์ใช้ 10 บิต (start 1 + data 8 + stop 1)

  1. 115,200 ÷ 10 = 11,520 ไบต์ต่อวินาที
  2. ข้อความ 64 ไบต์ → 64 ÷ 11,520 = 5.56 ms
  3. ลูป 500 ms ส่ง 64 ไบต์ ใช้ช่องสาย 5.56 ÷ 500 ≈ 1.1% สบาย
  4. ถ้าลดลูปเหลือ 5 ms และส่ง 64 ไบต์ทุกรอบ ต้องใช้ 5.56 ms ต่อรอบ มากกว่าตัวรอบเอง write แบบ blocking จะดึงจังหวะลูปช้าลงจนเวลาไม่ตรง

บทเรียนคือ log ในลูปถี่ ๆ คือภาระจริง ลดข้อความ ลดความถี่ หรือเพิ่ม baud ไม่ใช่เดาว่า “แค่ print”

ข้อผิดพลาดที่เจอบ่อย

  • ใช้ LED บนบอร์ดแล้วไม่กะพริบ บอร์ด DevKit หลายรุ่นใช้ LED RGB แบบ addressable (WS2812) ซึ่งเปิดปิดด้วย GPIO ธรรมดาไม่ได้ ต้องส่งสัญญาณตามจังหวะเฉพาะ ตรวจ schematic ของบอร์ดก่อน หรือต่อ LED ภายนอกแบบในตัวอย่าง
  • ใช้ขา strapping หรือขาที่ต่อ flash บางขามีหน้าที่ตอนบูต ดู datasheet ของชิปก่อนเลือกขา
  • เปิด unstable ใน library crate เอกสารบอกให้ library ใช้ requires-unstable แทน ให้แอปปลายทางเป็นคนเปิด unstable
  • ลืม espup environment บน Xtensa build ไม่ผ่านด้วย error ว่าหา target หรือ linker ไม่เจอ
  • อัปเกรดแบบ cargo update ทั้งชุดโดยไม่อ่าน changelog ส่วนเสถียรปลอดภัยตาม semver แต่ esp-radio และส่วน unstable เปลี่ยนได้ pin เวอร์ชันไว้ใน Cargo.toml
  • คิดว่าย้ายมา Rust แล้วได้ OTA แบบเดิมฟรี การอัปเดตผ่าน Wi-Fi แบบสองช่องและ rollback ในบทความ OTA ESP32เขียนบน ESP-IDF และต้องใช้ Wi-Fi ซึ่งฝั่ง Rust ยังเป็น beta ถ้าเครื่องในสนามต้องอัปเดตผ่านเน็ต ตรวจ crate ที่จะใช้ก่อนตัดสินใจย้าย

แหล่งอ้างอิง

คำถามที่พบบ่อย

ควรเลือก esp-hal หรือ ESP-IDF (C) สำหรับงานจริง

ถ้างานต้องพึ่ง Wi-Fi, BLE, OTA หรือ component ของ ESP-IDF เต็มรูปแบบวันนี้ ESP-IDF ยังพร้อมกว่า ถ้างานเป็นเซนเซอร์หรือ protocol บนสาย (UART/SPI/I2C) esp-hal 1.x ใช้ได้แล้ว และได้ความปลอดภัยของ Rust กับ API ที่สัญญาว่าจะไม่เปลี่ยนจนถึง 2.0

esp-idf-hal (Rust แบบ std) ยังใช้ได้ไหม

ใช้ได้ แต่ตั้งแต่ประกาศ beta ปี 2025 Espressif ระบุว่า esp-idf-sys, esp-idf-hal, esp-idf-svc เป็นโปรเจกต์ที่ชุมชนดูแล ทางที่ Espressif สนับสนุนเองคือ esp-hal แบบ no_std

ใช้ feature unstable ในงานจริงได้ไหม

ได้ คำว่า unstable หมายถึง API อาจเปลี่ยนในเวอร์ชันย่อย ไม่ได้แปลว่า driver ใช้ไม่ได้ ทีมบอกว่าใช้ได้กับงานทั่วไป แค่ต้อง pin เวอร์ชันและอ่าน changelog ก่อนอัปเกรด เอกสารห้าม library crate เปิด unstable เอง

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#IoTกลาง

อัปเดต ESP32 ผ่าน Wi-Fi ไม่ให้เครื่องพัง: OTA สองช่อง rollback และ signed image

OTA บน ESP-IDF ที่ปลอดภัย: ota_0/ota_1 + otadata เปิด app rollback ให้เฟิร์มแวร์ใหม่ต้องผ่าน self-test ดาวน์โหลดผ่าน HTTPS ตรวจลายเซ็น และคำนวณพาร์ทิชันบน flash 4 MB

อ่าน 14 นาที
#IoTกลาง

ต่อเครื่องพิมพ์ 3D ด้วย MQTT: snapshot + delta ต่อใหม่เอง และเฝ้าว่ายังทำงาน

เรียนรู้ MQTT จากงานจริง ดึงสถานะเครื่องพิมพ์ 3D หลายยี่ห้อมาแสดงบนไลฟ์ ขอ snapshot แล้วรวม delta ต่อใหม่เมื่อหลุด แยก process และเฝ้า token ที่หมดอายุเงียบ ๆ

อ่าน 7 นาที
#IoTเริ่มต้น

กล้องและอุปกรณ์ IoT เปลี่ยน IP เองทุกครั้งที่รีบูต แก้ยังไง

ทำไมกล้อง เครื่องพิมพ์ และบอร์ด ESP32 ได้ IP ใหม่หลังปิดเปิด และ 4 วิธีแก้ ตั้งแต่จอง IP ในเราเตอร์ จนถึงให้ระบบสแกนหาเองอัตโนมัติ

อ่าน 3 นาที