ชื่อโปรเจกต์: มัลติทาสก์กิ้ง (Multitasking) บน Arduino
ที่มาที่ไป (Background)
เหมือนพวกเราเหล่านักเล่น Arduino ทั่วไปนั่นแหละ พี่เริ่มมาจาก Arduino Uno ก่อนเลย แล้วเผลอแป๊บเดียว Shield ก็งอกมาเพียบ พอจะขับ Shield พวกนี้ทีไร ต้องลง Library กระหน่ำจน Memory แทบไม่เหลือให้หายใจ
พี่เลยขยับไปเล่น Arduino Mega ก็สนุกดีนะ จนกระทั่งเริ่มทำโปรเจกต์ที่มันซับซ้อนขึ้นอีกนิด อย่างพวกการทำหน้าจอ Display ไปพร้อมๆ กับเล่นเสียง (Sound)
ล่าสุด พี่เลยอัปเกรดอาวุธใหม่เป็น Arduino Due ซึ่งสถาปัตยกรรมต่างกับ Board พวก AVR ตัวเก่าแบบคนละเรื่องเลย (มันเป็น ARM Cortex-M) มี Core แบบ 32-bit พร้อม Memory ที่เยอะแบบเหลือๆ แถม Clock Speed ก็พุ่งกระฉูด ถือว่าเป็นอาวุธที่โคตรโหดเลยน้อง แต่ถ้าจะดึงพลังมันออกมาให้สุด การเขียนแค่ฟังก์ชัน loop() แบบเดิมๆ มันไม่พอหรอก นี่แหละคือเหตุผลที่โปรเจกต์ Arduino RT-Thread library นี้เกิดมาเพื่อแก้ปัญหานี้โดยเฉพาะ
รู้จักกับ RT-Thread
RT-Thread คือ RTOS (Real-Time Operating System) แบบฟรีและ Open Source ที่น่าสนใจคือมันมีฟีเจอร์เด็ดๆ อย่าง Shell ขนาดจิ๋ว (ชื่อว่า FinSH), การโหลด Module แบบ Dynamic และมี Device Driver ให้เลือกใช้เพียบ แต่ที่สำคัญที่สุดคือ Community ของเขานี่แหละที่เหนียวแน่นสุดๆ
มาเริ่มกันเลย (Let's Start)
Library นี้พี่เอาขึ้นไว้บน Github แล้ว น้องสามารถกดติดตั้งผ่าน "Library Manager" ในโปรแกรมได้เลย จัดไปวัยรุ่น!
ตอนนี้มีตัวอย่างให้ลอง 2 ตัว (อยู่ในส่วน CODE) เดี๋ยวตัวอย่างอื่นๆ จะตามมาทีหลังนะ
- ตัวอย่างแรก "Blink": จะโชว์วิธีสร้างและเริ่มทำงาน Thread ที่ชื่อว่า "Blink" โดยหน้าที่ของมันคือสั่งให้ LED บน Board กะพริบทุกๆ 1 วินาที แยกการทำงานออกมาเลย
- ตัวอย่างที่สอง "FinSH": อันนี้จะสอนวิธีเพิ่ม Variable (ตัวแปร) และคำสั่ง (Command) ที่เรากำหนดเองลงไปใน Shell จิ๋ว โดยในโค้ดจะมีตัวแปร
led_id,led_stateและคำสั่งled()เตรียมไว้ให้ดูเป็นแนวทาง
FinSH (ตัวแสบประจำงาน)
FinSH คือ Shell จิ๋วที่รับ Command สไตล์ภาษา C (เช่น hello()) รองรับการกด Tab เพื่อช่วยพิมพ์ (Auto-completion) และมี History เก็บไว้ด้วย (ลองกดปุ่มลูกศรขึ้น-ลงดูสิ แล้วจะรู้ว่ามันหล่อเท่แค่ไหน)
มันมีคำสั่งพื้นฐาน (Predefined commands) มาให้ตามนี้เลย:
hello(): พิมพ์ "Hello RT-Thread!"version(): ดูข้อมูล Versionlist(): ดูรายการคำสั่งที่มีให้ใช้list_mem(): เช็กสถานะ Memorylist_thread(): ดู Thread ที่รันอยู่list_sem(): ดูข้อมูล Semaphorelist_mutex(): ดูข้อมูล Mutexlist_event(): ดูข้อมูล Eventlist_mb(): ดูข้อมูล Mailboxlist_mq(): ดูข้อมูล Message Queuelist_memp(): ดูข้อมูล Memory Poollist_timer(): ดูข้อมูล Timer
น้องสามารถเพิ่มคำสั่งของตัวเองได้ง่ายๆ แค่ 2 ขั้นตอน:
- อย่างแรก เขียนฟังก์ชันขึ้นมาก่อน จะให้ Return เป็น
voidหรืออะไรก็ได้ แต่พี่แนะนำให้ Return เป็นพวกchar,byte,short,int(ทั้งแบบมี Sign หรือไม่มี) เพราะ FinSH มันจะเข้าใจได้ดีกว่า ส่วนเรื่อง Parameters (พารามิเตอร์) จัดเต็มได้เลยไม่มีกั๊ก (แต่ความยาว Command รวมห้ามเกิน 80 ตัวอักษรนะ เดี๋ยวเครื่องงง) - อย่างที่สอง เอา Macro
ADD_SHELL_CMD(...)ไปหยอดไว้ในไฟล์shell_cmd.h
นี่คือตัวอย่างการเพิ่มคำสั่ง led() เข้าไป:
- เขียนฟังก์ชัน
led():
unsigned int led(unsigned int id, byte state) {
// ใช้ "rt_kprintf()" เพื่อพ่นข้อความออกมาดู
rt_kprintf("led%d=%d\n", id, state);
if (id != 0) {
return 1;
}
if (state) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
}
return 0;
}
- เพิ่มบรรทัดนี้ลงใน
shell_cmd.h:
ADD_SHELL_CMD(led, Turn on/off builtin LED, led, unsigned int, unsigned int id, rt_int32_t val)
ในตัวอย่างนี้ ชื่อ Command กับชื่อฟังก์ชันเหมือนกันเป๊ะ แต่จริงๆ น้องจะตั้งให้ต่างกันก็ได้นะ (Command จะกลายเป็น Alias ของฟังก์ชัน) เวลาสั่งใน FinSH ก็ใช้ชื่อ Command นั่นแหละ
ส่วนถ้าจะเพิ่มตัวแปร (Variable) ของตัวเอง ก็ทำเหมือนๆ กันเลย:
- ประกาศตัวแปรขึ้นมา (ใช้ได้ทั้ง
char,byte,int,void*ฯลฯ) - เพิ่ม Macro
ADD_SHELL_VAR(ชื่อเล่นตัวแปร, คำอธิบาย, ชื่อตัวแปรจริงๆ, ประเภทตัวแปร)ลงในไฟล์shell_var.h
พอน้อง Upload ตัวอย่าง "FinSH" ลง Board เสร็จแล้ว ให้เปิด "Serial Monitor" ขึ้นมา แล้วน้องจะเจอกับ Shell เท่ๆ แบบนี้:
\\ | /
- RT - Thread Operating System
/ | \\ 4.0.1 build Mar 8 2019
2006 - 2019 Copyright by rt-thread team
finsh >
ลองเล่นคำสั่งพื้นฐานดูหน่อยสิ:
finsh >hello()
Hello RT-Thread!
0, 0x00000000
finsh >list()
--Function List:
hello -- say hello world
version -- show RT-Thread version information
list -- list available commands
...
led -- Turn on/off builtin LED
--Variable List:
id -- LED ID
state -- LED state
0, 0x00000000
น้องจะสังเกตเห็นว่าหลังจบคอมมานด์ มันจะมีค่า "0, 0x00000000" พ่นออกมาด้วย ไม่ต้องตกใจ มันคือค่า Return ของฟังก์ชันนั้นๆ ในรูปแบบฐานสิบกับฐานสิบหกนั่นเอง ถ้าลองสั่ง led(1, 0) แล้วมัน Return ค่าอื่นออกมา แสดงว่าติด Error Code แล้วล่ะ!
เอาล่ะ ลองไปซนกับคำสั่งและตัวแปรที่น้องสร้างเองได้เลย ขอให้สนุกนะวัยรุ่น!
ขั้นตอนต่อไป (Next Steps)
พี่ยังไม่ได้ทำตัวอย่างพวกการใช้ฟีเจอร์โหดๆ ของ Kernel อย่าง Semaphore, Mutex, Mailbox หรือ Memory Pool มาให้ดูนะ แม้ว่าจริงๆ ใน Library มันจะพร้อมใช้งานแล้วก็เถอะ เดี๋ยววันหลังพี่จะมาอัปเดตบทความกับ Library ให้อีกที ระหว่างนี้ถ้าคันไม้คันมือ ไปแอบดูตัวอย่างจากโปรเจกต์ต้นฉบับเขาก่อนได้
พี่กะว่าจะเอาพวก Component เสริมที่เคยถอดออกตอน Porting กลับมาใส่ใหม่ แล้วจะทำตัวอย่างโปรเจกต์ยากๆ เพื่อโชว์ความโหดของ RT-Thread ให้ดู
ขอบใจมากที่อ่านจนจบนะน้อง!
- การใช้ SD library ให้เทพขึ้นด้วย RT-Thread
- RT-Thread Primer (เร็วๆ นี้)
อัปเดต (Update)
- 15 มี.ค. 2019: Library ปล่อยลง "Library Manager" เรียบร้อยแล้ว
- 17 เม.ย. 2019: รองรับ FAT file system และ Driver ของ SD card แล้วใน v0.4.4
- 6 พ.ค. 2019: ตั้งแต่ v0.5.1 เป็นต้นไป ให้เปลี่ยนไปใช้ ADD_FINSH_CMD แทน ADD_SHELL_CMD นะครับ (จดไว้ด้วย เดี๋ยวหาว่าพี่ไม่เตือน!)
ข้อมูลทางเทคนิคเพิ่มเติม (Expanded Technical Details)
การจำลองการประมวลผลขนาน (Parallel Processing Simulation)
คู่มือฉบับ Advance นี้จะสอนวิธีทำ "Pseudo-Multitasking" บน Arduino ที่มี Core เดียว โดยใช้โค้ดแบบ Non-blocking และ Custom Kernels
- การใช้งาน RT-Thread IoT OS: ใช้ Kernel ของ RT-Thread Nano เพื่อสร้าง Thread แยกเป็นอิสระสำหรับสั่ง LED กะพริบ, อ่านค่า Sensor และจัดการเหตุการณ์ทาง Network ไปพร้อมๆ กัน
- State Machines ขับเคลื่อนด้วย Millis: โชว์วิธีเลิกใช้
delay()ที่ทำให้เครื่องค้าง (Blocking) แล้วเปลี่ยนมาคำนวณส่วนต่างของเวลาแทน ทำให้ Arduino ของน้องดูเหมือนทำงานเป็นสิบๆ อย่างได้ในเวลาเดียวกัน!
ประสิทธิภาพ (Efficiency)
- ตารางลำดับความสำคัญของงาน (Task Prioritization Matrix): ตัว Kernel ของ RTOS จะคอยคุมให้งานที่ซีเรียสเรื่องเวลา (อย่างพวกการคุม Motor) ได้รอบ CPU ไปใช้ก่อนงานที่มีความสำคัญต่ำกว่า (อย่างพวกการอัปเดตตัวเลขนาฬิกาบนจอ) หล่อเท่เลยงานนี้!