ทำไมต้อง 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)

  1. เปิด catalog.bs4u-tech.com/products/rpi-pico-2 ดูสเปกจริง — เลือก Pico 2 / 2 W ตามโปรเจกต์
  2. รัน rustup target add thumbv8m.base-none-eabi ลงบนเครื่องคุณ
  3. คอมเมนต์ข้างล่าง: คุณจะเอา 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 คนละเป้าหมายคอมไพล์