ผลต่างระหว่างรุ่นของ "มัลติทาสกิ้งบนไมโครคอนโทรลเลอร์"
Chaiporn (คุย | มีส่วนร่วม) |
Chaiporn (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 31 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 1: | แถว 1: | ||
+ | : ''วิกินี้เป็นส่วนหนึ่งของรายวิชา [[01204223]]'' | ||
+ | |||
โปรแกรมควบคุมที่ใช้ในคอมพิวเตอร์แบบฝังตัวนั้นมักต้องการให้มีการทำงานหลายส่วนขนานกันไป เรียกว่าเป็นการทำงานแบบ ''มัลติทาสกิ้ง'' ([http://en.wikipedia.org/wiki/Computer_multitasking multitasking]) อาทิเช่นการตรวจสอบสถานะของแสงเพื่อเปิดปิดไฟในขณะที่ต้องตรวจสอบสถานะการกดปุ่มสวิตช์ไปด้วยในเวลาเดียวกัน หรือการทำไฟกระพริบเป็นจังหวะเพื่อแสดงให้เห็นว่าอุปกรณ์กำลังทำงานในขณะที่ต้องคอยวนตรวจสอบข้อมูลที่มาจากพอร์ท USB เป็นต้น ในสถานการณ์เหล่านี้แม้ว่าแต่ละงานย่อยจะมีการทำงานที่ตรงไปตรงมา แต่การทำงานย่อยหลาย ๆ งานให้เสมือนว่าพร้อมกันบนไมโครคอนโทรลเลอร์ที่มีหน่วยประมวลผลเดียวโดยไม่มีระบบปฏิบัติการคอยช่วยเหลือเป็นเรื่องที่ค่อนข้างซับซ้อน | โปรแกรมควบคุมที่ใช้ในคอมพิวเตอร์แบบฝังตัวนั้นมักต้องการให้มีการทำงานหลายส่วนขนานกันไป เรียกว่าเป็นการทำงานแบบ ''มัลติทาสกิ้ง'' ([http://en.wikipedia.org/wiki/Computer_multitasking multitasking]) อาทิเช่นการตรวจสอบสถานะของแสงเพื่อเปิดปิดไฟในขณะที่ต้องตรวจสอบสถานะการกดปุ่มสวิตช์ไปด้วยในเวลาเดียวกัน หรือการทำไฟกระพริบเป็นจังหวะเพื่อแสดงให้เห็นว่าอุปกรณ์กำลังทำงานในขณะที่ต้องคอยวนตรวจสอบข้อมูลที่มาจากพอร์ท USB เป็นต้น ในสถานการณ์เหล่านี้แม้ว่าแต่ละงานย่อยจะมีการทำงานที่ตรงไปตรงมา แต่การทำงานย่อยหลาย ๆ งานให้เสมือนว่าพร้อมกันบนไมโครคอนโทรลเลอร์ที่มีหน่วยประมวลผลเดียวโดยไม่มีระบบปฏิบัติการคอยช่วยเหลือเป็นเรื่องที่ค่อนข้างซับซ้อน | ||
− | = ตัวอย่างแอพลิเคชัน = | + | == ตัวอย่างแอพลิเคชัน == |
พิจารณาตัวอย่างโปรแกรมควบคุม LED สองดวงให้กระพริบเป็นอิสระต่อกันดังนี้ | พิจารณาตัวอย่างโปรแกรมควบคุม LED สองดวงให้กระพริบเป็นอิสระต่อกันดังนี้ | ||
แถว 8: | แถว 10: | ||
: ''เขียนเฟิร์มแวร์ที่ทำให้ LED สีเขียวบนบอร์ดพ่วงติด 1 วินาทีและดับ 0.5 วินาทีสลับกันไป ในขณะเดียวกันทำให้ LED สีแดงติด 0.7 วินาทีและดับ 0.3 วินาทีสลับกันไป'' | : ''เขียนเฟิร์มแวร์ที่ทำให้ LED สีเขียวบนบอร์ดพ่วงติด 1 วินาทีและดับ 0.5 วินาทีสลับกันไป ในขณะเดียวกันทำให้ LED สีแดงติด 0.7 วินาทีและดับ 0.3 วินาทีสลับกันไป'' | ||
− | จะเห็นว่างานทั้งหมดประกอบด้วยงานย่อยสองงาน | + | จะเห็นว่างานทั้งหมดประกอบด้วยงานย่อยสองงาน การทำให้เพียง LED สีเขียวกระพริบตามที่กำหนดทำได้โดยการเขียนโค้ดไว้ในลูปอนันต์ลักษณะนี้ (หรือเขียนโค้ดส่วนนี้ไว้ในฟังก์ชัน <tt>loop</tt> กรณีที่ใช้ Arduino) |
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
// LED สีเขียวติด 1 วินาที ดับ 0.5 วินาที | // LED สีเขียวติด 1 วินาที ดับ 0.5 วินาที | ||
− | + | int main() | |
{ | { | ||
− | + | : | |
− | + | for (;;) | |
− | + | { | |
− | + | set_led(LED_GREEN,1); | |
+ | _delay_ms(1000); | ||
+ | set_led(LED_GREEN,0); | ||
+ | _delay_ms(500); | ||
+ | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
แถว 23: | แถว 29: | ||
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
// LED สีแดงติด 0.7 วินาที ดับ 0.3 วินาที | // LED สีแดงติด 0.7 วินาที ดับ 0.3 วินาที | ||
− | + | int main() | |
{ | { | ||
− | + | : | |
− | + | for (;;) | |
− | + | { | |
− | + | set_led(LED_RED,1); | |
+ | _delay_ms(700); | ||
+ | set_led(LED_RED,0); | ||
+ | _delay_ms(300); | ||
+ | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
แถว 39: | แถว 49: | ||
void taskGreen() | void taskGreen() | ||
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
− | + | _delay_ms(1000); | |
− | + | set_led(LED_GREEN,0); | |
− | + | _delay_ms(500); | |
} | } | ||
void taskRed() | void taskRed() | ||
{ | { | ||
− | + | set_led(LED_RED,1); | |
− | + | _delay_ms(700); | |
− | + | set_led(LED_RED,0); | |
− | + | _delay_ms(300); | |
} | } | ||
− | + | int main() | |
{ | { | ||
− | taskGreen(); | + | for (;;) |
− | + | { | |
+ | taskGreen(); | ||
+ | taskRed(); | ||
+ | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | ทั้งนี้เนื่องจากว่าฟังก์ชัน < | + | ทั้งนี้เนื่องจากว่าฟังก์ชัน <tt>taskRed()</tt> ไม่มีการถูกเรียกจนกว่าฟังก์ชัน <tt>taskGreen()</tt> จะทำงานเสร็จในหนึ่งรอบซึ่งใช้เวลาทั้งสิ้น 1.5 วินาที ในทำนองเดียวกัน ระหว่างที่ฟังก์ชัน <tt>taskRed()</tt> ทำงานอยู่นั้นฟังก์ชัน |
+ | <tt>taskGreen()</tt> ก็ไม่มีโอกาสได้ทำงานเช่นกัน | ||
− | = เครื่องจักรสถานะ = | + | == เครื่องจักรสถานะ == |
เวลาที่สูญเสียไปกับฟังก์ชันทั้งคู่ตามตัวอย่างข้างต้นนั้นเกือบทั้งหมดเกิดจากการใช้คำสั่ง delay ซึ่งเป็นการหยุดรอโดยไม่ทำอะไรทั้งสิ้น จึงส่งผลกระทบให้งานอื่นที่ต้องการการประมวลผลหยุดชะงักลงด้วย เราจึงต้องออกแบบทั้งสองฟังก์ชันใหม่เพื่อให้ทำงานเสร็จสิ้นในการเรียกแต่ละครั้งให้เร็วที่สุดเพื่อให้งานอื่นมีโอกาสได้ประมวลผล แนวคิดที่ใช้กันอย่างแพร่หลายคือมองงานแต่ละงานในรูป ''เครื่องจักรสถานะจำกัด'' ([http://en.wikipedia.org/wiki/Finite-state_machine Finite State Machine]) หรือเรียกสั้น ๆ ว่าเครื่องจักรสถานะ ผังภาพด้านล่างแสดงเครื่องจักรสถานะของงานควบคุม LED สีเขียว | เวลาที่สูญเสียไปกับฟังก์ชันทั้งคู่ตามตัวอย่างข้างต้นนั้นเกือบทั้งหมดเกิดจากการใช้คำสั่ง delay ซึ่งเป็นการหยุดรอโดยไม่ทำอะไรทั้งสิ้น จึงส่งผลกระทบให้งานอื่นที่ต้องการการประมวลผลหยุดชะงักลงด้วย เราจึงต้องออกแบบทั้งสองฟังก์ชันใหม่เพื่อให้ทำงานเสร็จสิ้นในการเรียกแต่ละครั้งให้เร็วที่สุดเพื่อให้งานอื่นมีโอกาสได้ประมวลผล แนวคิดที่ใช้กันอย่างแพร่หลายคือมองงานแต่ละงานในรูป ''เครื่องจักรสถานะจำกัด'' ([http://en.wikipedia.org/wiki/Finite-state_machine Finite State Machine]) หรือเรียกสั้น ๆ ว่าเครื่องจักรสถานะ ผังภาพด้านล่างแสดงเครื่องจักรสถานะของงานควบคุม LED สีเขียว | ||
แถว 69: | แถว 83: | ||
กลไกการทำงานของงาน (หรือเครื่องจักร) ข้างต้นเป็นดังนี้ | กลไกการทำงานของงาน (หรือเครื่องจักร) ข้างต้นเป็นดังนี้ | ||
− | # เริ่มทำงานโดยเข้าสู่สถานะ ON ซึ่งมีการสั่งให้ LED สีเขียวติด และบันทึกเวลาปัจจุบันเป็นมิลลิวินาทีจากฟังก์ชัน < | + | # เริ่มทำงานโดยเข้าสู่สถานะ ON ซึ่งมีการสั่งให้ LED สีเขียวติด และบันทึกเวลาปัจจุบันเป็นมิลลิวินาทีจากฟังก์ชัน <tt>millis()</tt> ไว้ในตัวแปร <tt>ts</tt> (ย่อมาจาก timestamp) |
#: ''(หมายเหตุ: ฟังก์ชัน millis() เป็นฟังก์ชันที่เฟรมเวิร์ก Arduino มีให้ใช้สำหรับตรวจสอบว่าไมโครคอนโทรลเลอร์ได้ทำงานมาเป็นระยะเวลานานกี่มิลลิวินาที)'' | #: ''(หมายเหตุ: ฟังก์ชัน millis() เป็นฟังก์ชันที่เฟรมเวิร์ก Arduino มีให้ใช้สำหรับตรวจสอบว่าไมโครคอนโทรลเลอร์ได้ทำงานมาเป็นระยะเวลานานกี่มิลลิวินาที)'' | ||
− | # | + | # ตรวจสอบเวลาที่อยู่ในสถานะนี้โดยพิจารณาว่าค่า <tt>millis()-ts</tt> เกินค่า 1000 มิลลิวินาทีแล้วหรือไม่ |
#* หากยังไม่เกินให้อยู่ในสถานะเดิม | #* หากยังไม่เกินให้อยู่ในสถานะเดิม | ||
− | #* หากเกินแล้ว ให้เข้าสู่สถานะ OFF โดยบันทึกค่า < | + | #* หากเกินแล้ว ให้เข้าสู่สถานะ OFF โดยบันทึกค่า <tt>ts</tt> เป็นเวลาที่เข้าสู่สถานะใหม่ใน และดับ LED สีเขียว |
# ดำเนินการในลักษณะเดียวกันเมื่ออยู่ในสถานะใหม่ | # ดำเนินการในลักษณะเดียวกันเมื่ออยู่ในสถานะใหม่ | ||
− | == โค้ดเครื่องจักรสถานะ สำหรับ LED สีเขียว == | + | === โค้ดเครื่องจักรสถานะ สำหรับ LED สีเขียว === |
เรานำเครื่องจักรสถานะที่ออกแบบไว้ข้างต้นมาเขียนเป็นโค้ดภาษาซี/Arduino ได้ดังนี้ | เรานำเครื่องจักรสถานะที่ออกแบบไว้ข้างต้นมาเขียนเป็นโค้ดภาษาซี/Arduino ได้ดังนี้ | ||
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
− | #include < | + | #include <avr/io.h> |
+ | #include <avr/interrupt.h> | ||
− | enum | + | #include "peri.h" |
− | + | #include "timer.h" | |
− | + | ||
+ | enum { ON, OFF }; | ||
+ | |||
+ | uint8_t taskGreen_state; | ||
////////////////////////////////////////////////// | ////////////////////////////////////////////////// | ||
แถว 89: | แถว 107: | ||
{ | { | ||
static uint32_t ts = 0; | static uint32_t ts = 0; | ||
− | + | ||
if (taskGreen_state == ON) | if (taskGreen_state == ON) | ||
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 1000) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,0); | |
taskGreen_state = OFF; | taskGreen_state = OFF; | ||
} | } | ||
} | } | ||
− | + | ||
− | if (taskGreen_state == OFF) | + | else if (taskGreen_state == OFF) |
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 500) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,1); | |
taskGreen_state = ON; | taskGreen_state = ON; | ||
} | } | ||
} | } | ||
} | } | ||
− | + | ||
////////////////////////////////////////////////// | ////////////////////////////////////////////////// | ||
− | + | int main() | |
{ | { | ||
− | + | init_peripheral(); | |
− | + | timer_init(); | |
− | + | sei(); | |
− | + | ||
− | |||
− | |||
− | |||
− | |||
// กำหนดสถานะเริ่มต้น | // กำหนดสถานะเริ่มต้น | ||
taskGreen_state = ON; | taskGreen_state = ON; | ||
− | + | set_led(LED_GREEN,1); | |
− | + | ||
− | + | for (;;) | |
− | + | { | |
− | + | taskGreen(); | |
− | { | + | } |
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
โปรแกรมข้างต้นสามารถคอมไพล์และรันได้จริง แต่จะมีเพียงงานของการกระพริบ LED สีเขียวเท่านั้น อย่างไรก็ตามโปรแกรมนี้ถูกเขียนในรูปแบบแตกต่างจากโปรแกรมไฟกระพริบที่ผ่าน ๆ มา และมีจุดที่น่าสังเกตหลายจุดดังนี้ | โปรแกรมข้างต้นสามารถคอมไพล์และรันได้จริง แต่จะมีเพียงงานของการกระพริบ LED สีเขียวเท่านั้น อย่างไรก็ตามโปรแกรมนี้ถูกเขียนในรูปแบบแตกต่างจากโปรแกรมไฟกระพริบที่ผ่าน ๆ มา และมีจุดที่น่าสังเกตหลายจุดดังนี้ | ||
− | * ฟังก์ชัน < | + | * ฟังก์ชัน <tt>taskGreen</tt> นั้นทำงานตามที่ได้ออกแบบไว้ในเครื่องจักรสถานะทุกประการ ซึ่งจะเห็นว่าไม่มีการใช้งานคำสั่ง delay จึงทำให้ทำงานเสร็จแทบจะทันทีที่ถูกเรียกในแต่ละครั้ง อันเป็นพฤติกรรมที่เราต้องการในการทำงานแบบมัลติทาสกิ้ง |
− | * คำสั่ง enum | + | * คำสั่ง enum เป็นการกำหนดค่าจำนวนเต็มที่ไม่ซ้ำกันให้กับค่าคงที่ในวงเล็บปีกกา ในที่นี้เราใช้กำหนดค่าคงที่ให้กับ ON และ OFF ซึ่งเอาไว้ใช้ในการกำหนดสถานะปัจจุบันให้กับเครื่องจักรสถานะ |
− | * ตัวแปร < | + | * ตัวแปร <tt>ts</tt> ถูกประกาศให้เป็นตัวแปรแบบโลคัล แต่ต้องมีการคงค่าเดิมไว้แม้การทำงานจะออกจากฟังก์ชัน <tt>taskGreen()</tt> ไปแล้ว จึงต้องมีการระบุคีย์เวิร์ด static เอาไว้ |
− | == โค้ดที่สมบูรณ์: LED สองสีกระพริบอิสระ == | + | === โค้ดที่สมบูรณ์: LED สองสีกระพริบอิสระ === |
โค้ดข้างต้นนำมาเพิ่มเครื่องจักรสถานะสำหรับ LED สีแดงเข้าไปได้อย่างตรงไปตรงมา ทำให้ได้โค้ดที่สมบูรณ์ดังนี้ | โค้ดข้างต้นนำมาเพิ่มเครื่องจักรสถานะสำหรับ LED สีแดงเข้าไปได้อย่างตรงไปตรงมา ทำให้ได้โค้ดที่สมบูรณ์ดังนี้ | ||
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
− | #include < | + | #include <avr/io.h> |
+ | #include <avr/interrupt.h> | ||
− | enum | + | #include "peri.h" |
− | + | #include "timer.h" | |
− | + | ||
− | + | enum { ON, OFF }; | |
+ | |||
+ | uint8_t taskGreen_state; | ||
+ | uint8_t taskRed_state; | ||
//////////////////////////////////// | //////////////////////////////////// | ||
แถว 154: | แถว 171: | ||
{ | { | ||
static uint32_t ts = 0; | static uint32_t ts = 0; | ||
− | + | ||
if (taskGreen_state == ON) | if (taskGreen_state == ON) | ||
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 1000) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,0); | |
taskGreen_state = OFF; | taskGreen_state = OFF; | ||
} | } | ||
} | } | ||
− | + | ||
− | if (taskGreen_state == OFF) | + | else if (taskGreen_state == OFF) |
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 500) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,1); | |
taskGreen_state = ON; | taskGreen_state = ON; | ||
} | } | ||
} | } | ||
} | } | ||
− | + | ||
//////////////////////////////////// | //////////////////////////////////// | ||
// เครื่องจักรสถานะสำหรับ LED สีแดง | // เครื่องจักรสถานะสำหรับ LED สีแดง | ||
แถว 185: | แถว 202: | ||
if (taskRed_state == ON) | if (taskRed_state == ON) | ||
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 700) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_RED,0); | |
taskRed_state = OFF; | taskRed_state = OFF; | ||
} | } | ||
} | } | ||
− | if (taskRed_state == OFF) | + | else if (taskRed_state == OFF) |
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 300) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_RED,1); | |
taskRed_state = ON; | taskRed_state = ON; | ||
} | } | ||
แถว 204: | แถว 221: | ||
} | } | ||
− | ////////////////////////////////////////////// | + | ////////////////////////////////////////////////// |
− | + | int main() | |
{ | { | ||
− | + | init_peripheral(); | |
− | + | timer_init(); | |
− | + | sei(); | |
− | + | ||
− | + | // กำหนดสถานะเริ่มต้น | |
− | |||
− | |||
− | |||
− | // | ||
taskGreen_state = ON; | taskGreen_state = ON; | ||
− | + | set_led(LED_GREEN,1); | |
taskRed_state = ON; | taskRed_state = ON; | ||
− | + | set_led(LED_RED,1); | |
− | + | ||
− | + | for (;;) | |
− | + | { | |
− | + | taskGreen(); | |
− | { | + | taskRed(); |
− | + | } | |
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | = การอิมพลิเมนต์เครื่องจักรสถานะด้วยคำสั่ง < | + | == การอิมพลิเมนต์เครื่องจักรสถานะด้วยคำสั่ง <tt>goto</tt> == |
โค้ดที่เขียนขึ้นจากเครื่องจักรสถานะนั้นจะดูตรงไปตรงมาก็ต่อเมื่อมีผังภาพเครื่องจักรสถานะดังเช่นรูปข้างต้นมาพิจารณาประกอบ แต่ถ้าหากดูจากตัวโค้ดเพียงอย่างเดียวนั้นแทบจะไม่สามารถถอดความได้ทันทีว่าผลลัพธ์จะเป็นอย่างไร เห็นได้จากตารางเปรียบเทียบโค้ดเดิมที่ทำงานแบบซิงเกิลทาสกิ้ง (ซ้ายมือ) และโค้ดที่เขียนตามเครื่องจักรสถานะเพื่อรองรับการทำงานแบบมัสติทาสกิ้ง (ขวามือ) | โค้ดที่เขียนขึ้นจากเครื่องจักรสถานะนั้นจะดูตรงไปตรงมาก็ต่อเมื่อมีผังภาพเครื่องจักรสถานะดังเช่นรูปข้างต้นมาพิจารณาประกอบ แต่ถ้าหากดูจากตัวโค้ดเพียงอย่างเดียวนั้นแทบจะไม่สามารถถอดความได้ทันทีว่าผลลัพธ์จะเป็นอย่างไร เห็นได้จากตารางเปรียบเทียบโค้ดเดิมที่ทำงานแบบซิงเกิลทาสกิ้ง (ซ้ายมือ) และโค้ดที่เขียนตามเครื่องจักรสถานะเพื่อรองรับการทำงานแบบมัสติทาสกิ้ง (ขวามือ) | ||
แถว 241: | แถว 253: | ||
void taskGreen() | void taskGreen() | ||
{ | { | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
− | + | _delay_ms(1000); | |
− | + | set_led(LED_GREEN,1); | |
− | + | _delay_ms(500); | |
} | } | ||
} | } | ||
แถว 258: | แถว 270: | ||
if (taskGreen_state == ON) | if (taskGreen_state == ON) | ||
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 1000) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,0); | |
taskGreen_state = OFF; | taskGreen_state = OFF; | ||
} | } | ||
} | } | ||
− | if (taskGreen_state == OFF) | + | else if (taskGreen_state == OFF) |
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 500) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,1); | |
taskGreen_state = ON; | taskGreen_state = ON; | ||
} | } | ||
แถว 280: | แถว 292: | ||
|} | |} | ||
− | ความต้องการของการเขียนโค้ดตามแบบด้านขวามือนั้นคือการทำให้ < | + | ความต้องการของการเขียนโค้ดตามแบบด้านขวามือนั้นคือการทำให้ <tt>taskGreen</tt> ประมวลผลและออกจากฟังก์ชันให้เร็วที่สุดในการเรียกแต่ละครั้ง โดยจดจำสถานะปัจจุบันของตนเอาไว้เพื่อดำเนินการต่อในการถูกเรียกประมวลผลครั้งถัดไป โค้ดที่ได้จึงมีลักษณะเป็นคำสั่ง if หลาย ๆ คำสั่งกระจายอยู่ทั่วไปในฟังก์ชัน และบดบังลำดับการทำงานที่ต้องการจนแทบไม่เหลือเค้าเดิม ส่วนโค้ดทางด้านซ้ายนั้นแม้จะสื่อให้เห็นถึงพฤติกรรมการทำงานได้อย่างชัดเจน แต่การวนลูปแบบไม่รู้จบและหยุดรอของคำสั่ง delay นั้นเป็นสิ่งที่ยอมรับไม่ได้ในการทำงานแบบมัลติทาสกิ้ง |
− | ทางเลือกที่ใช้ข้อดีจากทั้งสองฝ่ายคือการหากลไกที่ทดแทนคำสั่ง delay โดยให้โปรแกรมทำงานต่อในบรรทัดถัดไปเมื่อเวลาผ่านไปครบถ้วนแล้ว และบังคับให้ออกจากฟังก์ชันทันทีหากยังไม่ถึงเวลาหน่วงที่กำหนดแต่ให้บันทึกตำแหน่งปัจจุบันเอาไว้ เพื่อที่ว่าการเรียกฟังก์ชันครั้งถัดไปจะได้กระโดดมาทำงาน ณ จุดที่ออกจากฟังก์ชันไปต่อได้ แนวคิดนี้ทำได้โดยอาศัยคำสั่ง < | + | ทางเลือกที่ใช้ข้อดีจากทั้งสองฝ่ายคือการหากลไกที่ทดแทนคำสั่ง delay โดยให้โปรแกรมทำงานต่อในบรรทัดถัดไปเมื่อเวลาผ่านไปครบถ้วนแล้ว และบังคับให้ออกจากฟังก์ชันทันทีหากยังไม่ถึงเวลาหน่วงที่กำหนดแต่ให้บันทึกตำแหน่งปัจจุบันเอาไว้ เพื่อที่ว่าการเรียกฟังก์ชันครั้งถัดไปจะได้กระโดดมาทำงาน ณ จุดที่ออกจากฟังก์ชันไปต่อได้ แนวคิดนี้ทำได้โดยอาศัยคำสั่ง <tt>goto</tt> ซึ่งมีให้อยู่แล้วในภาษาซี |
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
− | #include < | + | #include <stddef.h> // for NULL |
+ | #include <avr/io.h> | ||
+ | #include <avr/interrupt.h> | ||
+ | |||
+ | #include "peri.h" | ||
+ | #include "timer.h" | ||
void* taskGreen_state; | void* taskGreen_state; | ||
แถว 299: | แถว 316: | ||
if (taskGreen_state != NULL) goto *taskGreen_state; | if (taskGreen_state != NULL) goto *taskGreen_state; | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
− | // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ | + | // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ _delay_ms(1000) โดยไม่หยุดรอ |
− | ts = | + | ts = timer_millis(); |
ON: taskGreen_state = &&ON; // จดจำบรรทัดปัจจุบัน | ON: taskGreen_state = &&ON; // จดจำบรรทัดปัจจุบัน | ||
− | if ( | + | if (timer_millis()-ts < 1000) return; |
− | + | set_led(LED_GREEN,0); | |
− | // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ | + | // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ _delay_ms(500) โดยไม่หยุดรอ |
− | ts = | + | ts = timer_millis(); |
OFF:taskGreen_state = &&OFF; // จดจำบรรทัดปัจจุบัน | OFF:taskGreen_state = &&OFF; // จดจำบรรทัดปัจจุบัน | ||
− | if ( | + | if (timer_millis()-ts < 500) return; |
} | } | ||
} | } | ||
////////////////////////////////////////////////// | ////////////////////////////////////////////////// | ||
− | + | int main() | |
{ | { | ||
− | + | init_peripheral(); | |
− | + | timer_init(); | |
− | + | sei(); | |
− | |||
− | |||
− | |||
− | |||
taskGreen_state = NULL; | taskGreen_state = NULL; | ||
− | |||
− | + | for (;;) | |
− | + | { | |
− | { | + | taskGreen(); |
− | + | } | |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
แถว 339: | แถว 351: | ||
;หมายเหตุ | ;หมายเหตุ | ||
* โค้ดด้านบนคอมไพล์และรันได้จริง | * โค้ดด้านบนคอมไพล์และรันได้จริง | ||
− | * การใช้คำสั่ง < | + | * การใช้คำสั่ง <tt>goto</tt> ในรูปแบบด้านบนเรียกว่าการทำ [http://en.wikipedia.org/wiki/Goto#Computed_GOTO_and_Assigned_GOTO computed goto] ซึ่งไม่จัดอยู่ในภาษาซีมาตรฐาน แต่รองรับโดยคอมไพเลอร์ส่วนใหญ่เช่น GCC คำสั่งในภาษาซีมาตรฐานที่มีพฤติกรรมใกล้เคียง computed goto มากที่สุดคือคำสั่ง switch..case |
− | * แม้ฟังก์ชัน < | + | * แม้ฟังก์ชัน <tt>taskGreen</tt> จะมีลูปอนันต์อยู่ จะมีการออกจากฟังก์ชันเสมอด้วยคำสั่ง <tt>return</tt> จึงทำให้ทำงานร่วมกับงานอื่นแบบมัลติทาสกิ้งได้ |
− | * เป็นที่ถกเถียงกันเรื่อง[http://en.wikipedia.org/wiki/Considered_harmful ความเลวร้ายของการใช้คำสั่ง goto] | + | * เป็นที่ถกเถียงกันเรื่อง[http://en.wikipedia.org/wiki/Considered_harmful ความเลวร้ายของการใช้คำสั่ง goto] แต่การใช้คำสั่ง goto ในสถานการณ์ที่เหมาะสมจะทำให้โปรแกรมกระชับและทำความเข้าใจได้ง่ายขึ้น |
− | ฟังก์ชัน < | + | ฟังก์ชัน <tt>taskGreen</tt> ที่เขียนใหม่นั้นยังสามารถถูกมองในรูปของเครื่องจักรสถานะได้ เพียงแต่สถานะต่าง ๆ นั้นถูกฝังลงไปในจุดต่าง ๆ ของโค้ด ณ ตำแหน่งที่มีการกำกับด้วย <tt>ON:</tt> และ <tt>OFF:</tt> ลองเปรียบเทียบโค้ดที่ปรับแก้แล้วกับโค้ดดั้งเดิมและโค้ดที่เขียนขึ้นจากเครื่องจักรสถานะตรง ๆ |
{| border="0" cellpadding="5" cellspacing="0" align="center" | {| border="0" cellpadding="5" cellspacing="0" align="center" | ||
แถว 359: | แถว 371: | ||
goto *taskGreen_state; | goto *taskGreen_state; | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
− | // เทียบเท่า | + | // เทียบเท่า _delay_ms(1000) |
− | ts = | + | ts = timer_millis(); |
ON: taskGreen_state = &&ON; | ON: taskGreen_state = &&ON; | ||
− | if ( | + | if (timer_millis()-ts < 1000) return; |
− | + | set_led(LED_GREEN,0); | |
− | // เทียบเท่า | + | // เทียบเท่า _delay_ms(500) |
− | ts = | + | ts = timer_millis(); |
OFF:taskGreen_state = &&OFF; | OFF:taskGreen_state = &&OFF; | ||
− | if ( | + | if (timer_millis()-ts < 500) return; |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
แถว 380: | แถว 392: | ||
void taskGreen() | void taskGreen() | ||
{ | { | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
− | + | _delay_ms(1000); | |
− | + | set_led(LED_GREEN,0); | |
− | + | _delay_ms(500); | |
} | } | ||
} | } | ||
แถว 397: | แถว 409: | ||
if (taskGreen_state == ON) | if (taskGreen_state == ON) | ||
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 1000) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,0); | |
taskGreen_state = OFF; | taskGreen_state = OFF; | ||
} | } | ||
} | } | ||
− | if (taskGreen_state == OFF) | + | else if (taskGreen_state == OFF) |
{ | { | ||
− | if ( | + | if (timer_millis() - ts >= 500) |
{ | { | ||
− | ts = | + | ts = timer_millis(); |
− | + | set_led(LED_GREEN,1); | |
taskGreen_state = ON; | taskGreen_state = ON; | ||
} | } | ||
แถว 419: | แถว 431: | ||
|} | |} | ||
− | จะเห็นว่าโค้ดรูปแบบใหม่ทางด้านซ้ายมือแม้ว่าจะยาวกว่าเดิมก็ตาม แต่ก็มีลักษณะที่คล้ายคลึงกับโค้ดดั้งเดิมในช่องกลางมากขึ้น โดยเฉพาะอย่างยิ่งถ้ามีการรวบ 3 คำสั่งที่คอมเม้นต์ไว้ว่าทำงานเทียบเท่าคำสั่ง delay เอาไว้ให้เป็นคำสั่งเดียวได้จะได้โค้ดภายในลูป | + | จะเห็นว่าโค้ดรูปแบบใหม่ทางด้านซ้ายมือแม้ว่าจะยาวกว่าเดิมก็ตาม แต่ก็มีลักษณะที่คล้ายคลึงกับโค้ดดั้งเดิมในช่องกลางมากขึ้น โดยเฉพาะอย่างยิ่งถ้ามีการรวบ 3 คำสั่งที่คอมเม้นต์ไว้ว่าทำงานเทียบเท่าคำสั่ง delay เอาไว้ให้เป็นคำสั่งเดียวได้จะได้โค้ดภายในลูป for ที่เหมือนกันเกือบทุกประการกับโค้ดเดิม จึงเหลือเพียงการทำให้โค้ดดูง่ายขึ้นโดยการรวมคำสั่งหลาย ๆ คำสั่งให้เสมือนเป็นคำสั่งเดียวโดยใช้[http://en.wikipedia.org/wiki/C_preprocessor#Macro_definition_and_expansion ระบบมาโครของภาษาซี] ทั้งหมดนี้มีโปรแกรมเมอร์หลายคนได้พัฒนาเอาไว้พร้อมใช้งานในรูปไลบรารีเรียบร้อยแล้ว หนึ่งในนั้นคือไลบรารี Protothreads ดูรายละเอียดการใช้งานไลบรารี Protothreads ได้จากวิกิ [[มัลติทาสกิ้งด้วยไลบรารี Protothreads]] |
− | |||
− | |||
− | [ | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− |
รุ่นแก้ไขปัจจุบันเมื่อ 02:28, 10 พฤศจิกายน 2559
- วิกินี้เป็นส่วนหนึ่งของรายวิชา 01204223
โปรแกรมควบคุมที่ใช้ในคอมพิวเตอร์แบบฝังตัวนั้นมักต้องการให้มีการทำงานหลายส่วนขนานกันไป เรียกว่าเป็นการทำงานแบบ มัลติทาสกิ้ง (multitasking) อาทิเช่นการตรวจสอบสถานะของแสงเพื่อเปิดปิดไฟในขณะที่ต้องตรวจสอบสถานะการกดปุ่มสวิตช์ไปด้วยในเวลาเดียวกัน หรือการทำไฟกระพริบเป็นจังหวะเพื่อแสดงให้เห็นว่าอุปกรณ์กำลังทำงานในขณะที่ต้องคอยวนตรวจสอบข้อมูลที่มาจากพอร์ท USB เป็นต้น ในสถานการณ์เหล่านี้แม้ว่าแต่ละงานย่อยจะมีการทำงานที่ตรงไปตรงมา แต่การทำงานย่อยหลาย ๆ งานให้เสมือนว่าพร้อมกันบนไมโครคอนโทรลเลอร์ที่มีหน่วยประมวลผลเดียวโดยไม่มีระบบปฏิบัติการคอยช่วยเหลือเป็นเรื่องที่ค่อนข้างซับซ้อน
เนื้อหา
ตัวอย่างแอพลิเคชัน
พิจารณาตัวอย่างโปรแกรมควบคุม LED สองดวงให้กระพริบเป็นอิสระต่อกันดังนี้
- ตัวอย่าง
- เขียนเฟิร์มแวร์ที่ทำให้ LED สีเขียวบนบอร์ดพ่วงติด 1 วินาทีและดับ 0.5 วินาทีสลับกันไป ในขณะเดียวกันทำให้ LED สีแดงติด 0.7 วินาทีและดับ 0.3 วินาทีสลับกันไป
จะเห็นว่างานทั้งหมดประกอบด้วยงานย่อยสองงาน การทำให้เพียง LED สีเขียวกระพริบตามที่กำหนดทำได้โดยการเขียนโค้ดไว้ในลูปอนันต์ลักษณะนี้ (หรือเขียนโค้ดส่วนนี้ไว้ในฟังก์ชัน loop กรณีที่ใช้ Arduino)
// LED สีเขียวติด 1 วินาที ดับ 0.5 วินาที
int main()
{
:
for (;;)
{
set_led(LED_GREEN,1);
_delay_ms(1000);
set_led(LED_GREEN,0);
_delay_ms(500);
}
}
ในขณะที่การทำให้ LED สีแดงกระพริบจะใช้โค้ดดังนี้
// LED สีแดงติด 0.7 วินาที ดับ 0.3 วินาที
int main()
{
:
for (;;)
{
set_led(LED_RED,1);
_delay_ms(700);
set_led(LED_RED,0);
_delay_ms(300);
}
}
อย่างไรก็ตาม การให้ LED ทั้งสองดวงกระพริบตามจังหวะของตัวเองขนานกันไปนั้นไม่อาจทำได้โดยการรวมงานทั้งคู่เข้าด้วยกันอย่างตรงไปตรงมาเช่นนี้ได้
// *** โค้ดด้านล่างไม่ได้ทำงานตามที่พึงประสงค์ ***
void taskGreen()
{
set_led(LED_GREEN,1);
_delay_ms(1000);
set_led(LED_GREEN,0);
_delay_ms(500);
}
void taskRed()
{
set_led(LED_RED,1);
_delay_ms(700);
set_led(LED_RED,0);
_delay_ms(300);
}
int main()
{
for (;;)
{
taskGreen();
taskRed();
}
}
ทั้งนี้เนื่องจากว่าฟังก์ชัน taskRed() ไม่มีการถูกเรียกจนกว่าฟังก์ชัน taskGreen() จะทำงานเสร็จในหนึ่งรอบซึ่งใช้เวลาทั้งสิ้น 1.5 วินาที ในทำนองเดียวกัน ระหว่างที่ฟังก์ชัน taskRed() ทำงานอยู่นั้นฟังก์ชัน taskGreen() ก็ไม่มีโอกาสได้ทำงานเช่นกัน
เครื่องจักรสถานะ
เวลาที่สูญเสียไปกับฟังก์ชันทั้งคู่ตามตัวอย่างข้างต้นนั้นเกือบทั้งหมดเกิดจากการใช้คำสั่ง delay ซึ่งเป็นการหยุดรอโดยไม่ทำอะไรทั้งสิ้น จึงส่งผลกระทบให้งานอื่นที่ต้องการการประมวลผลหยุดชะงักลงด้วย เราจึงต้องออกแบบทั้งสองฟังก์ชันใหม่เพื่อให้ทำงานเสร็จสิ้นในการเรียกแต่ละครั้งให้เร็วที่สุดเพื่อให้งานอื่นมีโอกาสได้ประมวลผล แนวคิดที่ใช้กันอย่างแพร่หลายคือมองงานแต่ละงานในรูป เครื่องจักรสถานะจำกัด (Finite State Machine) หรือเรียกสั้น ๆ ว่าเครื่องจักรสถานะ ผังภาพด้านล่างแสดงเครื่องจักรสถานะของงานควบคุม LED สีเขียว
กลไกการทำงานของงาน (หรือเครื่องจักร) ข้างต้นเป็นดังนี้
- เริ่มทำงานโดยเข้าสู่สถานะ ON ซึ่งมีการสั่งให้ LED สีเขียวติด และบันทึกเวลาปัจจุบันเป็นมิลลิวินาทีจากฟังก์ชัน millis() ไว้ในตัวแปร ts (ย่อมาจาก timestamp)
- (หมายเหตุ: ฟังก์ชัน millis() เป็นฟังก์ชันที่เฟรมเวิร์ก Arduino มีให้ใช้สำหรับตรวจสอบว่าไมโครคอนโทรลเลอร์ได้ทำงานมาเป็นระยะเวลานานกี่มิลลิวินาที)
- ตรวจสอบเวลาที่อยู่ในสถานะนี้โดยพิจารณาว่าค่า millis()-ts เกินค่า 1000 มิลลิวินาทีแล้วหรือไม่
- หากยังไม่เกินให้อยู่ในสถานะเดิม
- หากเกินแล้ว ให้เข้าสู่สถานะ OFF โดยบันทึกค่า ts เป็นเวลาที่เข้าสู่สถานะใหม่ใน และดับ LED สีเขียว
- ดำเนินการในลักษณะเดียวกันเมื่ออยู่ในสถานะใหม่
โค้ดเครื่องจักรสถานะ สำหรับ LED สีเขียว
เรานำเครื่องจักรสถานะที่ออกแบบไว้ข้างต้นมาเขียนเป็นโค้ดภาษาซี/Arduino ได้ดังนี้
#include <avr/io.h>
#include <avr/interrupt.h>
#include "peri.h"
#include "timer.h"
enum { ON, OFF };
uint8_t taskGreen_state;
//////////////////////////////////////////////////
void taskGreen()
{
static uint32_t ts = 0;
if (taskGreen_state == ON)
{
if (timer_millis() - ts >= 1000)
{
ts = timer_millis();
set_led(LED_GREEN,0);
taskGreen_state = OFF;
}
}
else if (taskGreen_state == OFF)
{
if (timer_millis() - ts >= 500)
{
ts = timer_millis();
set_led(LED_GREEN,1);
taskGreen_state = ON;
}
}
}
//////////////////////////////////////////////////
int main()
{
init_peripheral();
timer_init();
sei();
// กำหนดสถานะเริ่มต้น
taskGreen_state = ON;
set_led(LED_GREEN,1);
for (;;)
{
taskGreen();
}
}
โปรแกรมข้างต้นสามารถคอมไพล์และรันได้จริง แต่จะมีเพียงงานของการกระพริบ LED สีเขียวเท่านั้น อย่างไรก็ตามโปรแกรมนี้ถูกเขียนในรูปแบบแตกต่างจากโปรแกรมไฟกระพริบที่ผ่าน ๆ มา และมีจุดที่น่าสังเกตหลายจุดดังนี้
- ฟังก์ชัน taskGreen นั้นทำงานตามที่ได้ออกแบบไว้ในเครื่องจักรสถานะทุกประการ ซึ่งจะเห็นว่าไม่มีการใช้งานคำสั่ง delay จึงทำให้ทำงานเสร็จแทบจะทันทีที่ถูกเรียกในแต่ละครั้ง อันเป็นพฤติกรรมที่เราต้องการในการทำงานแบบมัลติทาสกิ้ง
- คำสั่ง enum เป็นการกำหนดค่าจำนวนเต็มที่ไม่ซ้ำกันให้กับค่าคงที่ในวงเล็บปีกกา ในที่นี้เราใช้กำหนดค่าคงที่ให้กับ ON และ OFF ซึ่งเอาไว้ใช้ในการกำหนดสถานะปัจจุบันให้กับเครื่องจักรสถานะ
- ตัวแปร ts ถูกประกาศให้เป็นตัวแปรแบบโลคัล แต่ต้องมีการคงค่าเดิมไว้แม้การทำงานจะออกจากฟังก์ชัน taskGreen() ไปแล้ว จึงต้องมีการระบุคีย์เวิร์ด static เอาไว้
โค้ดที่สมบูรณ์: LED สองสีกระพริบอิสระ
โค้ดข้างต้นนำมาเพิ่มเครื่องจักรสถานะสำหรับ LED สีแดงเข้าไปได้อย่างตรงไปตรงมา ทำให้ได้โค้ดที่สมบูรณ์ดังนี้
#include <avr/io.h>
#include <avr/interrupt.h>
#include "peri.h"
#include "timer.h"
enum { ON, OFF };
uint8_t taskGreen_state;
uint8_t taskRed_state;
////////////////////////////////////
// เครื่องจักรสถานะสำหรับ LED สีเขียว
////////////////////////////////////
void taskGreen()
{
static uint32_t ts = 0;
if (taskGreen_state == ON)
{
if (timer_millis() - ts >= 1000)
{
ts = timer_millis();
set_led(LED_GREEN,0);
taskGreen_state = OFF;
}
}
else if (taskGreen_state == OFF)
{
if (timer_millis() - ts >= 500)
{
ts = timer_millis();
set_led(LED_GREEN,1);
taskGreen_state = ON;
}
}
}
////////////////////////////////////
// เครื่องจักรสถานะสำหรับ LED สีแดง
////////////////////////////////////
void taskRed()
{
static uint32_t ts = 0;
if (taskRed_state == ON)
{
if (timer_millis() - ts >= 700)
{
ts = timer_millis();
set_led(LED_RED,0);
taskRed_state = OFF;
}
}
else if (taskRed_state == OFF)
{
if (timer_millis() - ts >= 300)
{
ts = timer_millis();
set_led(LED_RED,1);
taskRed_state = ON;
}
}
}
//////////////////////////////////////////////////
int main()
{
init_peripheral();
timer_init();
sei();
// กำหนดสถานะเริ่มต้น
taskGreen_state = ON;
set_led(LED_GREEN,1);
taskRed_state = ON;
set_led(LED_RED,1);
for (;;)
{
taskGreen();
taskRed();
}
}
การอิมพลิเมนต์เครื่องจักรสถานะด้วยคำสั่ง goto
โค้ดที่เขียนขึ้นจากเครื่องจักรสถานะนั้นจะดูตรงไปตรงมาก็ต่อเมื่อมีผังภาพเครื่องจักรสถานะดังเช่นรูปข้างต้นมาพิจารณาประกอบ แต่ถ้าหากดูจากตัวโค้ดเพียงอย่างเดียวนั้นแทบจะไม่สามารถถอดความได้ทันทีว่าผลลัพธ์จะเป็นอย่างไร เห็นได้จากตารางเปรียบเทียบโค้ดเดิมที่ทำงานแบบซิงเกิลทาสกิ้ง (ซ้ายมือ) และโค้ดที่เขียนตามเครื่องจักรสถานะเพื่อรองรับการทำงานแบบมัสติทาสกิ้ง (ขวามือ)
โค้ดดั้งเดิม (แบบใช้ delay) | โค้ดที่อิมพลิเมนต์ตามเครื่องจักรสถานะ |
---|---|
void taskGreen()
{
for (;;)
{
set_led(LED_GREEN,1);
_delay_ms(1000);
set_led(LED_GREEN,1);
_delay_ms(500);
}
}
|
void taskGreen()
{
static uint32_t ts = 0;
if (taskGreen_state == ON)
{
if (timer_millis() - ts >= 1000)
{
ts = timer_millis();
set_led(LED_GREEN,0);
taskGreen_state = OFF;
}
}
else if (taskGreen_state == OFF)
{
if (timer_millis() - ts >= 500)
{
ts = timer_millis();
set_led(LED_GREEN,1);
taskGreen_state = ON;
}
}
}
|
ความต้องการของการเขียนโค้ดตามแบบด้านขวามือนั้นคือการทำให้ taskGreen ประมวลผลและออกจากฟังก์ชันให้เร็วที่สุดในการเรียกแต่ละครั้ง โดยจดจำสถานะปัจจุบันของตนเอาไว้เพื่อดำเนินการต่อในการถูกเรียกประมวลผลครั้งถัดไป โค้ดที่ได้จึงมีลักษณะเป็นคำสั่ง if หลาย ๆ คำสั่งกระจายอยู่ทั่วไปในฟังก์ชัน และบดบังลำดับการทำงานที่ต้องการจนแทบไม่เหลือเค้าเดิม ส่วนโค้ดทางด้านซ้ายนั้นแม้จะสื่อให้เห็นถึงพฤติกรรมการทำงานได้อย่างชัดเจน แต่การวนลูปแบบไม่รู้จบและหยุดรอของคำสั่ง delay นั้นเป็นสิ่งที่ยอมรับไม่ได้ในการทำงานแบบมัลติทาสกิ้ง
ทางเลือกที่ใช้ข้อดีจากทั้งสองฝ่ายคือการหากลไกที่ทดแทนคำสั่ง delay โดยให้โปรแกรมทำงานต่อในบรรทัดถัดไปเมื่อเวลาผ่านไปครบถ้วนแล้ว และบังคับให้ออกจากฟังก์ชันทันทีหากยังไม่ถึงเวลาหน่วงที่กำหนดแต่ให้บันทึกตำแหน่งปัจจุบันเอาไว้ เพื่อที่ว่าการเรียกฟังก์ชันครั้งถัดไปจะได้กระโดดมาทำงาน ณ จุดที่ออกจากฟังก์ชันไปต่อได้ แนวคิดนี้ทำได้โดยอาศัยคำสั่ง goto ซึ่งมีให้อยู่แล้วในภาษาซี
#include <stddef.h> // for NULL
#include <avr/io.h>
#include <avr/interrupt.h>
#include "peri.h"
#include "timer.h"
void* taskGreen_state;
//////////////////////////////////////////////////
// เครื่องจักรสถานะสำหรับ LED สีเขียว
//////////////////////////////////////////////////
void taskGreen()
{
static uint32_t ts;
// กระโดดไปยังตำแหน่งที่บันทึกเอาไว้ก่อนออกจากฟังก์ชันครั้งล่าสุด (ถ้ามี)
if (taskGreen_state != NULL) goto *taskGreen_state;
for (;;)
{
set_led(LED_GREEN,1);
// สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ _delay_ms(1000) โดยไม่หยุดรอ
ts = timer_millis();
ON: taskGreen_state = &&ON; // จดจำบรรทัดปัจจุบัน
if (timer_millis()-ts < 1000) return;
set_led(LED_GREEN,0);
// สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ _delay_ms(500) โดยไม่หยุดรอ
ts = timer_millis();
OFF:taskGreen_state = &&OFF; // จดจำบรรทัดปัจจุบัน
if (timer_millis()-ts < 500) return;
}
}
//////////////////////////////////////////////////
int main()
{
init_peripheral();
timer_init();
sei();
taskGreen_state = NULL;
for (;;)
{
taskGreen();
}
}
- หมายเหตุ
- โค้ดด้านบนคอมไพล์และรันได้จริง
- การใช้คำสั่ง goto ในรูปแบบด้านบนเรียกว่าการทำ computed goto ซึ่งไม่จัดอยู่ในภาษาซีมาตรฐาน แต่รองรับโดยคอมไพเลอร์ส่วนใหญ่เช่น GCC คำสั่งในภาษาซีมาตรฐานที่มีพฤติกรรมใกล้เคียง computed goto มากที่สุดคือคำสั่ง switch..case
- แม้ฟังก์ชัน taskGreen จะมีลูปอนันต์อยู่ จะมีการออกจากฟังก์ชันเสมอด้วยคำสั่ง return จึงทำให้ทำงานร่วมกับงานอื่นแบบมัลติทาสกิ้งได้
- เป็นที่ถกเถียงกันเรื่องความเลวร้ายของการใช้คำสั่ง goto แต่การใช้คำสั่ง goto ในสถานการณ์ที่เหมาะสมจะทำให้โปรแกรมกระชับและทำความเข้าใจได้ง่ายขึ้น
ฟังก์ชัน taskGreen ที่เขียนใหม่นั้นยังสามารถถูกมองในรูปของเครื่องจักรสถานะได้ เพียงแต่สถานะต่าง ๆ นั้นถูกฝังลงไปในจุดต่าง ๆ ของโค้ด ณ ตำแหน่งที่มีการกำกับด้วย ON: และ OFF: ลองเปรียบเทียบโค้ดที่ปรับแก้แล้วกับโค้ดดั้งเดิมและโค้ดที่เขียนขึ้นจากเครื่องจักรสถานะตรง ๆ
โค้ดเครื่องจักรสถานะแบบใช้ goto | โค้ดดั้งเดิม (แบบใช้ delay) | โค้ดเครื่องจักรสถานะแบบใช้ if |
---|---|---|
void taskGreen()
{
static uint32_t ts;
if (taskGreen_state != NULL)
goto *taskGreen_state;
for (;;)
{
set_led(LED_GREEN,1);
// เทียบเท่า _delay_ms(1000)
ts = timer_millis();
ON: taskGreen_state = &&ON;
if (timer_millis()-ts < 1000) return;
set_led(LED_GREEN,0);
// เทียบเท่า _delay_ms(500)
ts = timer_millis();
OFF:taskGreen_state = &&OFF;
if (timer_millis()-ts < 500) return;
}
|
void taskGreen()
{
for (;;)
{
set_led(LED_GREEN,1);
_delay_ms(1000);
set_led(LED_GREEN,0);
_delay_ms(500);
}
}
|
void taskGreen()
{
static uint32_t ts = 0;
if (taskGreen_state == ON)
{
if (timer_millis() - ts >= 1000)
{
ts = timer_millis();
set_led(LED_GREEN,0);
taskGreen_state = OFF;
}
}
else if (taskGreen_state == OFF)
{
if (timer_millis() - ts >= 500)
{
ts = timer_millis();
set_led(LED_GREEN,1);
taskGreen_state = ON;
}
}
}
|
จะเห็นว่าโค้ดรูปแบบใหม่ทางด้านซ้ายมือแม้ว่าจะยาวกว่าเดิมก็ตาม แต่ก็มีลักษณะที่คล้ายคลึงกับโค้ดดั้งเดิมในช่องกลางมากขึ้น โดยเฉพาะอย่างยิ่งถ้ามีการรวบ 3 คำสั่งที่คอมเม้นต์ไว้ว่าทำงานเทียบเท่าคำสั่ง delay เอาไว้ให้เป็นคำสั่งเดียวได้จะได้โค้ดภายในลูป for ที่เหมือนกันเกือบทุกประการกับโค้ดเดิม จึงเหลือเพียงการทำให้โค้ดดูง่ายขึ้นโดยการรวมคำสั่งหลาย ๆ คำสั่งให้เสมือนเป็นคำสั่งเดียวโดยใช้ระบบมาโครของภาษาซี ทั้งหมดนี้มีโปรแกรมเมอร์หลายคนได้พัฒนาเอาไว้พร้อมใช้งานในรูปไลบรารีเรียบร้อยแล้ว หนึ่งในนั้นคือไลบรารี Protothreads ดูรายละเอียดการใช้งานไลบรารี Protothreads ได้จากวิกิ มัลติทาสกิ้งด้วยไลบรารี Protothreads