ทำไมต้อง Rust บนบอร์ด RPi Pico2? (และทำไมไม่ใช้ C)
ภาษา C อาจจะทำให้โปรแกรมค้างได้ตลอดเวลา — Rust จะรู้ตั้งแต่ก่อนคอมไพล์ แถมฟรี
ทำไมต้อง Rust บนบอร์ด RPi Pico2 ? (และทำไมไม่ใช้ C)
TL;DR: C ยินดีเสี่ยงค้างได้ตลอดเวลา — Rust บอกให้คุณรู้ตั้งแต่ก่อนคอมไพล์ แถมฟรี วันนี้เรามาทำความรู้จักบอร์ด RP2350 และทำไมภาษาเดียวกันกับที่เขียนเว็บ ถึงลงมาอยู่บนฮาร์ดแวร์จิ๋วได้
บอร์ดราคามิตรภาพ ที่ไม่ธรรมดา
คุณอาจเคยเห็น Raspberry Pi Pico 2 วางอยู่ในชั้น ราคาหลักร้อย แต่มันไม่ใช่ "บอร์ดทดลองเล่น" อีกต่อไป
ข้างในคือชิป RP2350 — dual-core ความเร็ว 150 MHz สลับไปมาได้ระหว่าง ARM Cortex-M33 กับ Hazard3 RISC-V มี SRAM 520 KB และ Flash 4 MB บนบอร์ด มีขา GPIO 26 ขา พร้อมอินเทอร์เฟซครบชุด: 2× UART, 2× SPI, 2× I2C, 12× PWM, 4× ADC (12-bit) และ 2× PIO สำหรับสร้างโปรโตคอลเองได้
แล้วเวอร์ชันไร้สาย Pico 2 W เพิ่มชิป Infineon CYW43439 ให้ Wi-Fi 2.4 GHz (802.11n) และ Bluetooth 5.2 ในตัว
// สเปกที่เราจะเล่นด้วย — ไม่ใช่โค้ดรันได้ แต่เหมือน "ประตู"
// RP2350: dual-core 150 MHz, 520 KB SRAM, 4 MB flash, 26 GPIO
// เป้าหมายคอมไพล์ (Arm): thumbv8m.base-none-eabi
โจทย์ของเราคือ: จะเขียนโปรแกรมบอร์ดนี้ยังไง ให้ทั้ง เริ่มต้นง่าย และ ไม่พังกลางทาง คำตอบสั้นๆ คือ Rust
ทำไมไม่ใช้ C?
ผมเริ่มจาก C เหมือนทุกคน เพราะมันคือมาตรฐานของวงการ embedded มันเร็ว มันใกล้ฮาร์ดแวร์ แต่ผมเจ็บมาพอแล้ว
// สิ่งที่ C ยอมให้คุณทำ — และคุณจะเสียใจทีหลัง
int* buf = malloc(1024);
do_something(buf);
free(buf);
do_something_again(buf); // ← ใช้ฟรีแล้ว ไม่มีใครเตือน
โค้ดบรรทัดนี้ คอมไพล์ผ่าน 100% รันได้ปกติ แล้วพังตอน ตี 2 ทุ่มบนฮาร์ดแวร์ลูกค้า เพราะ buf ถูกฟรีไปแล้วแต่เรายังชี้มาใช้
นี่ไม่ใช่ข้อผิดพลาดของคุณคนเดียว — มันคือโครงสร้างของภาษา C ที่บอกว่า "จัดการเองนะ"
สิ่งที่ Rust ทำต่าง: มันแจ้งคุณก่อนรัน
Rust แก้ปัญหานี้ที่ระดับ compiler ไม่ใช่ตอนรันไทม์ ดูโค้ดเทียบกัน:
fn main() {
let buf = vec![0u8; 1024]; // เจอข้อมูลบนสแตก (stack) หรือฮีปผ่าน alloc
let buf2 = buf; // ← ownership ย้าย (move) ไปที่ buf2
println!("{}", buf.len()); // ← compile error! buf ย้ายไปแล้ว
}
ลองคอมไพล์โค้ดนี้ คุณจะได้ข้อความประมาณ:
error[E0382]: borrow of moved value: `buf`
--> src/main.rs:4:24
|
3 | let buf2 = buf;
| ---- value moved here
4 | println!("{}", buf.len());
| ^^^ value borrowed here after move
นั่นแหละ การแจ้งแบบนี้ โค้ดที่ผิด จะไม่มีทางลงไปบนบอร์ดเลย คุณแก้ตรงคอมไพล์ ไม่ต้องรอให้ลูกค้าโทรมาตอนเที่ยงคืน
แล้ว "ลงบอร์ด" ได้ยังไง? (เรื่องของ #![no_std])
นี่คือจุดที่มือใหม่สะดุด: บน Pico 2 ไม่มี operating system ไม่มี std ให้ใช้
#![no_std] // บอก compiler: ไม่เอา standard library
#![no_main] // ไม่เอา fn main แบบเดสก์ทอป
use rp235x_hal as hal; // Hardware Abstraction Layer ของ RP2350
#[cortex_m_rt::entry] // จุดเริ่มของโปรแกรมบน Cortex-M
fn entry() -> ! {
// เราเป็นคนคุมทุกอย่าง: clock, ขา GPIO, interrupt
loop {} // อย่าทิ้งฟังก์ชันนี้เด็ดขาด
}
เวลาลืมใส่ #![no_std] คุณจะได้ข้อความคลาสสิก:
error: can't find crate for `std`
|
= note: the rp2350 target does not support std
ข้อความเดียวนี้สอน no_std ได้ดีกว่าผมเขียนคำอธิบายร้อยบรรทัด เพราะมันบอกตรงๆ ว่า "เป้าหมายนี้ไม่มี std"
ทำไม Pico 2 ถึงเหมาะกับ Rust โดยเฉพาะ
สองเรื่องที่ทำให้ RP2350 คู่กับ Rust ลงตัว:
หนึ่ง — ความปลอดภัยระดับฮาร์ดแวร์: Cortex-M33 มี Arm TrustZone® กันไม่ให้โค้ดส่วนหนึ่งแกะส่วนอื่น Rust ที่ยึดเรื่อง "ใครเป็นเจ้าของข้อมูล" มาแต่เกิด เลยจับคู่กับ TrustZone ได้เนียนๆ
สอง — หน่วยความจำพอให้หายใจ: 520 KB SRAM ไม่ใช่เยอะในมาตรฐานคอมพิวเตอร์ แต่สำหรับ MCU มันกว้างพอให้ Rust จัดการ ownership/borrow ได้สบาย โดยไม่ต้องประหยัดจนง่อย
// เปรียบเทียบกำลัง: RP2350 vs Pico รุ่นแรก
// Pico 1 (RP2040): 264 KB SRAM, 2 MB flash, thumbv6m
// Pico 2 (RP2350): 520 KB SRAM, 4 MB flash, thumbv8m + TrustZone
// เอาแบบนี้ เขียน Rust สบายขึ้นเยอะ
ลองเจอของจริง: ตอนที่ผมเพิ่งเริ่ม
ผมนั่งตั้ง toolchain วันแรก แล้วเจอผิด target ทันที:
error: incorrect ELF magic — thumbv6m vs thumbv8m
สาเหตุ? ผมคอมไพล์ด้วย target ของ Pico รุ่นแรก (thumbv6m-none-eabi) แต่ Pico 2 ต้องการ thumbv8m.base-none-eabi เปลี่ยนคำสั่งเดียว ไฟกระพริบ
# สิ่งที่ต้องรันก่อนเขียนบรรทัดแรก
rustup target add thumbv8m.base-none-eabi
cargo new pico2-blink && cd pico2-blink
# แล้วใส่ rp235x-hal + embassy-time ใน Cargo.toml
บทเรียน: เช็คชิปก่อนเช็คโค้ด RP2350 ไม่ใช่ RP2040 ที่เร็วกว่า — มันคนละสถาปัตยกรรมเป้าหมาย
สรุป: ทำไม Rust บน Pico 2
- C เร็ว แต่บอกคุณไม่ได้ว่าจะเออเร่อ — บั๊กใช้-after-free รันผ่านได้
- Rust ตรวจสอบก่อนรัน — บั๊กส่วนใหญ่ตายตอนคอมไพล์ ไม่ถึงบอร์ด
- Pico 2 มีของให้เล่นครบ — dual-core 150 MHz, 520 KB SRAM, Wi-Fi บนตัว W, TrustZone บน M33
- ลงมือง่ายกว่าที่คิด — แค่ยอมรับ
#![no_std]แล้วตั้ง target ให้ถูก
ลองทำเอง (Takeaway)
- เปิด
catalog.bs4u-tech.com/products/rpi-pico-2ดูสเปกจริง — เลือก Pico 2 / 2 W ตามโปรเจกต์ - รัน
rustup target add thumbv8m.base-none-eabiลงบนเครื่องคุณ - คอมเมนต์ข้างล่าง: คุณจะเอา Pico 2 ทำโปรเจกต์อะไร? ผมเอาตอนหน้ามาสอน "ไฟกระพริบแรก" ให้ดูของจริง
ซื้อของแท้พร้อมคู่มือภาษาไทยและชุมชน Maker ที่ catalog.bs4u-tech.com — PWD Architect of Intelligent Learning & Edge Solutions
Further reading: The Rust Book (doc.rust-lang.org/book), Rust by Example, rp235x-hal บน GitHub, Raspberry Pi Pico 2 datasheet
คำถามที่พบบ่อย
Raspberry Pi Pico 2 ใช้ Rust ได้ไหม?
ได้ Pico 2 ใช้ชิป RP2350 รัน Rust ผ่าน rp235x-hal หรือ Embassy ได้ โดยคอมไพล์เป้าหมาย thumbv8m.base-none-eabi (Arm) หรือ riscv32imac-unknown-none-elf (RISC-V Hazard3)
ทำไมไม่ใช้ C แทน Rust บน embedded?
C คอมไพล์ผ่านแม้โค้ดจะผิดพร้อมบั๊กเช่น use-after-free Rust จับบั๊กส่วนใหญ่ตอนคอมไพล์ผ่านกฎ ownership/borrow จึงไม่หลุดลงบอร์ด
Pico 2 ต้องมี debugger ไหม?
แนะนำมี โปรแกรมผ่าน SWD ด้วย Picoprobe (Pico อีกตัว) หรือ CMSIS-DAP และดู log ผ่าน defmt + probe-rs โหมด H/WH มีพอร์ต debug 3-pin มาให้
RP2350 เป็น no_std จริงหรือ?
ใช่ บน Pico 2 ไม่มี OS ไม่มี std ต้องใส่ #![no_std] และ #![no_main] แล้วคุม entry/interrupt เอง หากลืมจะเจอ 'can't find crate for std'
Pico 2 กับ Pico รุ่นแรกต่างกันยังไง?
Pico 2 ใช้ RP2350: 520 KB SRAM / 4 MB flash / dual-core 150 MHz / TrustZone ส่วนรุ่นแรกใช้ RP2040: 264 KB SRAM / 2 MB flash / thumbv6m คนละเป้าหมายคอมไพล์