ผลต่างระหว่างรุ่นของ "มัลติทาสกิ้งด้วยไลบรารี Protothreads"
Chaiporn (คุย | มีส่วนร่วม) ล |
Chaiporn (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 10 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 2: | แถว 2: | ||
[http://dunkels.com/adam/pt/ Protothreads] พัฒนาขึ้นโดย [http://dunkels.com/adam/ Adam Dunkels] โดยมีวัตถุประสงค์เพื่อให้นักพัฒนาโค้ดสามารถเขียนโปรแกรมแบบมัลติทาสกิ้งในรูปแบบที่เข้าใจได้ง่ายบนอุปกรณ์ที่มีทรัพยากรจำกัดอย่างเช่นไมโครคอนโทรลเลอร์ ไลบรารีนี้นำเอาเทคนิคการบันทึกสถานะร่วมกับการใช้คำสั่ง <tt>goto</tt> มารวมไว้เป็นชุดมาโครในภาษาซี ทำให้รูปแบบโค้ดของงานย่อยแต่ละงานมีลักษณะคล้ายคลึงกับการเขียนโค้ดที่ทำงานแบบซิงเกิลทาสก์ | [http://dunkels.com/adam/pt/ Protothreads] พัฒนาขึ้นโดย [http://dunkels.com/adam/ Adam Dunkels] โดยมีวัตถุประสงค์เพื่อให้นักพัฒนาโค้ดสามารถเขียนโปรแกรมแบบมัลติทาสกิ้งในรูปแบบที่เข้าใจได้ง่ายบนอุปกรณ์ที่มีทรัพยากรจำกัดอย่างเช่นไมโครคอนโทรลเลอร์ ไลบรารีนี้นำเอาเทคนิคการบันทึกสถานะร่วมกับการใช้คำสั่ง <tt>goto</tt> มารวมไว้เป็นชุดมาโครในภาษาซี ทำให้รูปแบบโค้ดของงานย่อยแต่ละงานมีลักษณะคล้ายคลึงกับการเขียนโค้ดที่ทำงานแบบซิงเกิลทาสก์ | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
== ตัวอย่างโปรแกรม == | == ตัวอย่างโปรแกรม == | ||
แถว 23: | แถว 10: | ||
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
− | #include < | + | #include <stddef.h> // for NULL |
− | #include <pt.h> | + | #include <avr/io.h> |
+ | #include <avr/interrupt.h> | ||
+ | #include <pt/pt.h> | ||
+ | #include "peri.h" | ||
+ | #include "timer.h" | ||
+ | |||
+ | // นิยามมาโคร PT_DELAY ไว้เพื่อจำลองการหน่วงเวลาแบบมัลติทาสกิ้ง | ||
#define PT_DELAY(pt, ms, ts) \ | #define PT_DELAY(pt, ms, ts) \ | ||
− | ts = | + | ts = timer_millis(); \ |
− | PT_WAIT_WHILE(pt, | + | PT_WAIT_WHILE(pt, timer_millis()-ts < (ms)); |
struct pt pt_taskRed; | struct pt pt_taskRed; | ||
แถว 40: | แถว 33: | ||
PT_BEGIN(pt); | PT_BEGIN(pt); | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN, 1); | |
PT_DELAY(pt, 1000, ts); | PT_DELAY(pt, 1000, ts); | ||
− | + | set_led(LED_GREEN, 0); | |
PT_DELAY(pt, 500, ts); | PT_DELAY(pt, 500, ts); | ||
} | } | ||
แถว 58: | แถว 51: | ||
PT_BEGIN(pt); | PT_BEGIN(pt); | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_RED, 1); | |
PT_DELAY(pt, 700, ts); | PT_DELAY(pt, 700, ts); | ||
− | + | set_led(LED_RED, 0); | |
PT_DELAY(pt, 300, ts); | PT_DELAY(pt, 300, ts); | ||
} | } | ||
แถว 69: | แถว 62: | ||
} | } | ||
− | + | ////////////////////////////////////////////////// | |
− | + | int main() | |
{ | { | ||
− | + | init_peripheral(); | |
− | + | timer_init(); | |
− | + | sei(); | |
− | |||
− | |||
− | |||
PT_INIT(&pt_taskGreen); | PT_INIT(&pt_taskGreen); | ||
PT_INIT(&pt_taskRed); | PT_INIT(&pt_taskRed); | ||
− | |||
− | + | for (;;) | |
− | + | { | |
− | { | + | taskGreen(&pt_taskGreen); |
− | + | taskRed(&pt_taskRed); | |
− | + | } | |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
แถว 100: | แถว 89: | ||
void taskGreen() | void taskGreen() | ||
{ | { | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
− | + | _delay_ms(1000); | |
− | + | set_led(LED_GREEN,0); | |
− | + | _delay_ms(500); | |
} | } | ||
} | } | ||
แถว 117: | แถว 106: | ||
PT_BEGIN(pt); | PT_BEGIN(pt); | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
PT_DELAY(pt, 1000, ts); | PT_DELAY(pt, 1000, ts); | ||
− | + | set_led(LED_GREEN,0); | |
PT_DELAY(pt, 500, ts); | PT_DELAY(pt, 500, ts); | ||
} | } | ||
แถว 173: | แถว 162: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * มาโคร <tt>PT_DELAY(pt, ms, ts)</tt> ไม่ได้อยู่ในไลบรารี Protothreads แต่สร้างขึ้นเพื่อความสะดวกในการจำลองการทำงานของคำสั่ง | + | * มาโคร <tt>PT_DELAY(pt, ms, ts)</tt> ไม่ได้อยู่ในไลบรารี Protothreads แต่สร้างขึ้นเพื่อความสะดวกในการจำลองการทำงานของคำสั่ง _delay_ms ในแบบที่ไม่หยุดรอ นิยามไว้ให้เทียบเท่ากับการใช้คำสั่ง |
<syntaxhighlight lang="C"> | <syntaxhighlight lang="C"> | ||
− | ts = | + | ts = timer_millis(); |
− | PT_WAIT_WHILE(pt, | + | PT_WAIT_WHILE(pt, timer_millis()-ts < (ms)); |
</syntaxhighlight> | </syntaxhighlight> | ||
แถว 187: | แถว 176: | ||
PT_BEGIN(pt); | PT_BEGIN(pt); | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
PT_DELAY(pt, 1000, ts); | PT_DELAY(pt, 1000, ts); | ||
− | + | set_led(LED_GREEN,0); | |
PT_DELAY(pt, 500, ts); | PT_DELAY(pt, 500, ts); | ||
} | } | ||
แถว 207: | แถว 196: | ||
if (pt->state != NULL) goto *(pt->state); | if (pt->state != NULL) goto *(pt->state); | ||
− | + | for (;;) | |
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
// มาโคร PT_DELAY(pt, 1000, ts); | // มาโคร PT_DELAY(pt, 1000, ts); | ||
− | ts = | + | ts = timer_millis(); |
L43: pt->state = &&L43; // สมมติว่า 43 คือเลขบรรทัดนี้ | L43: pt->state = &&L43; // สมมติว่า 43 คือเลขบรรทัดนี้ | ||
− | if ( | + | if (timer_millis()-ts < 1000) return 0; |
− | + | set_led(LED_GREEN,0); | |
// มาโคร PT_DELAY(pt, 500, ts); | // มาโคร PT_DELAY(pt, 500, ts); | ||
− | ts = | + | ts = timer_millis(); |
L45: pt->state = &&L45; // สมมตว่า 45 คือเลขบรรทัดนี้ | L45: pt->state = &&L45; // สมมตว่า 45 คือเลขบรรทัดนี้ | ||
− | if ( | + | if (timer_millis()-ts < 500) return 0; |
} | } | ||
แถว 243: | แถว 232: | ||
for (i = 0; i < 5; i++) | for (i = 0; i < 5; i++) | ||
{ | { | ||
− | + | set_led(LED_GREEN,1); | |
PT_DELAY(pt,100,ts); | PT_DELAY(pt,100,ts); | ||
− | + | set_led(LED_GREEN,0); | |
PT_DELAY(pt,100,ts); | PT_DELAY(pt,100,ts); | ||
} | } | ||
แถว 252: | แถว 241: | ||
for (i = 0; i < 3; i++) | for (i = 0; i < 3; i++) | ||
{ | { | ||
− | + | set_led(LED_RED,1); | |
PT_DELAY(pt,100,ts); | PT_DELAY(pt,100,ts); | ||
− | + | set_led(LED_RED,1); | |
PT_DELAY(pt,100,ts); | PT_DELAY(pt,100,ts); | ||
} | } | ||
แถว 265: | แถว 254: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * '''ลูปอนันต์ไม่เปิดโอกาสให้ออกจากฟังก์ชัน''' การใช้ลูปแบบ <tt> | + | * '''ลูปอนันต์ไม่เปิดโอกาสให้ออกจากฟังก์ชัน''' การใช้ลูปแบบ <tt>for (;;)</tt> ทำได้ใน protothread ก็จริง แต่ต้องให้ฟังก์ชันได้ return เพื่อให้โอกาสงานอื่น ๆ ทำงานด้วยเช่นกัน โค้ดด้านล่างเป็นตัวอย่างการตีความค่าแสง 2 ระดับและแสดงผลลัพธ์บน LED สีแดงตลอดเวลา |
<syntaxhighlight lang="C" line> | <syntaxhighlight lang="C" line> | ||
PT_THREAD(taskLight(struct pt* pt)) | PT_THREAD(taskLight(struct pt* pt)) | ||
แถว 271: | แถว 260: | ||
PT_BEGIN(pt); | PT_BEGIN(pt); | ||
− | + | for (;;) | |
{ | { | ||
− | uint16_t light = | + | uint16_t light = read_adc(PC4); |
− | + | set_led(LED_RED, light/512); | |
} | } | ||
แถว 280: | แถว 269: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | จะเห็นว่าเมื่อลูป | + | จะเห็นว่าเมื่อลูป for เริ่มทำงานแล้วจะไม่มีการเปิดโอกาสให้ออกจากฟังก์ชันได้อีกเลย นั่นหมายถึงงานอื่น ๆ ที่ต้องการให้ทำควบคู่กันไปจะหยุดชะงักลงทันที หากไม่ต้องการหน่วงเวลาใด ๆ อย่างน้อยที่สุดต้องมีการใช้คำสั่ง <tt>PT_YIELD</tt> เพื่อฟังก์ชันจะได้มีโอกาส return และให้งานอื่น ๆ ได้ทำงานบ้าง |
<syntaxhighlight lang="C" line highlight="9"> | <syntaxhighlight lang="C" line highlight="9"> | ||
PT_THREAD(taskLight(struct pt* pt)) | PT_THREAD(taskLight(struct pt* pt)) | ||
แถว 286: | แถว 275: | ||
PT_BEGIN(pt); | PT_BEGIN(pt); | ||
− | + | for (;;) | |
{ | { | ||
− | uint16_t light = | + | uint16_t light = read_adc(PC4); |
− | + | set_led(LED_RED, light/512); | |
PT_YIELD(pt): | PT_YIELD(pt): | ||
} | } | ||
แถว 298: | แถว 287: | ||
== มาโครที่ใช้งานบ่อย == | == มาโครที่ใช้งานบ่อย == | ||
− | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#gae6bae7dc0225468c8a5ac269df549892 <tt>PT_INIT(pt)</tt>] | + | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#gae6bae7dc0225468c8a5ac269df549892 <tt>PT_INIT(pt)</tt>] กำหนดค่าสถานะเริ่มต้นให้กับโพรโทเทรด |
− | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga2ffbb9e554e08a343ae2f9de4bedfdfc <tt>PT_BEGIN(pt)</tt>] | + | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga2ffbb9e554e08a343ae2f9de4bedfdfc <tt>PT_BEGIN(pt)</tt>] ประกาศจุดเริ่มต้นภายในฟังก์ชันที่ใช้งานเป็นโพรโทเทรด |
− | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga7b04a0035bef29d905496c23bae066d2 <tt>PT_END(pt)</tt>] | + | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga7b04a0035bef29d905496c23bae066d2 <tt>PT_END(pt)</tt>] ประกาศจุดสิ้นสุดภายในฟังก์ชันที่ใช้งานเป็นโพรโทเทรด |
* [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#gaad14bbbf092b90aa0a5a4f9169504a8d <tt>PT_WAIT_WHILE(pt,cond)</tt>] รอ (แบบมัสติทาสกิ้ง) ตราบเท่าที่เงื่อนไข <tt>cond</tt> ยังเป็นจริงอยู่ | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#gaad14bbbf092b90aa0a5a4f9169504a8d <tt>PT_WAIT_WHILE(pt,cond)</tt>] รอ (แบบมัสติทาสกิ้ง) ตราบเท่าที่เงื่อนไข <tt>cond</tt> ยังเป็นจริงอยู่ | ||
* [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga99e43010ec61327164466aa2d902de45 <tt>PT_WAIT_UNTIL(pt,cond)</tt>] รอ (แบบมัสติทาสกิ้ง) จนกระทั่งเงื่อนไข <tt>cond</tt> เป็นจริง | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga99e43010ec61327164466aa2d902de45 <tt>PT_WAIT_UNTIL(pt,cond)</tt>] รอ (แบบมัสติทาสกิ้ง) จนกระทั่งเงื่อนไข <tt>cond</tt> เป็นจริง | ||
− | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga155cba6121323726d02c00284428fed6 <tt>PT_YIELD(pt)</tt>] | + | * [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html#ga155cba6121323726d02c00284428fed6 <tt>PT_YIELD(pt)</tt>] เปิดโอกาสให้โพรโทเทรดอื่นได้ทำงาน |
ดูรายละเอียดเพิ่มเติมและคำอธิบายมาโครอื่น ๆ ได้จาก [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html คู่มืออ้างอิงมาโครที่ Protothreads เตรียมให้] | ดูรายละเอียดเพิ่มเติมและคำอธิบายมาโครอื่น ๆ ได้จาก [http://www.cpe.ku.ac.th/~cpj/204223/pt/a00008.html คู่มืออ้างอิงมาโครที่ Protothreads เตรียมให้] |
รุ่นแก้ไขปัจจุบันเมื่อ 11:57, 6 เมษายน 2562
- วิกินี้เป็นส่วนหนึ่งของรายวิชา 01204223
Protothreads พัฒนาขึ้นโดย Adam Dunkels โดยมีวัตถุประสงค์เพื่อให้นักพัฒนาโค้ดสามารถเขียนโปรแกรมแบบมัลติทาสกิ้งในรูปแบบที่เข้าใจได้ง่ายบนอุปกรณ์ที่มีทรัพยากรจำกัดอย่างเช่นไมโครคอนโทรลเลอร์ ไลบรารีนี้นำเอาเทคนิคการบันทึกสถานะร่วมกับการใช้คำสั่ง goto มารวมไว้เป็นชุดมาโครในภาษาซี ทำให้รูปแบบโค้ดของงานย่อยแต่ละงานมีลักษณะคล้ายคลึงกับการเขียนโค้ดที่ทำงานแบบซิงเกิลทาสก์
เนื้อหา
ตัวอย่างโปรแกรม
โปรแกรมต่อไปนี้เขียนขึ้นโดยอาศัยไลบรารี Protothreads ซึ่งให้พฤติกรรมการทำงานที่เหมือนกับตัวอย่างตามวิกิ มัลติทาสกิ้งบนไมโครคอนโทรลเลอร์ ทุกประการ
- ตัวอย่าง
- เขียนเฟิร์มแวร์ที่ทำให้ LED สีเขียวบนบอร์ดพ่วงติด 1 วินาทีและดับ 0.5 วินาทีสลับกันไป ในขณะเดียวกันทำให้ LED สีแดงติด 0.7 วินาทีและดับ 0.3 วินาทีสลับกันไป
#include <stddef.h> // for NULL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <pt/pt.h>
#include "peri.h"
#include "timer.h"
// นิยามมาโคร PT_DELAY ไว้เพื่อจำลองการหน่วงเวลาแบบมัลติทาสกิ้ง
#define PT_DELAY(pt, ms, ts) \
ts = timer_millis(); \
PT_WAIT_WHILE(pt, timer_millis()-ts < (ms));
struct pt pt_taskRed;
struct pt pt_taskGreen;
///////////////////////////////////////////////////////
PT_THREAD(taskGreen(struct pt* pt))
{
static uint32_t ts;
PT_BEGIN(pt);
for (;;)
{
set_led(LED_GREEN, 1);
PT_DELAY(pt, 1000, ts);
set_led(LED_GREEN, 0);
PT_DELAY(pt, 500, ts);
}
PT_END(pt);
}
///////////////////////////////////////////////////////
PT_THREAD(taskRed(struct pt* pt))
{
static uint32_t ts;
PT_BEGIN(pt);
for (;;)
{
set_led(LED_RED, 1);
PT_DELAY(pt, 700, ts);
set_led(LED_RED, 0);
PT_DELAY(pt, 300, ts);
}
PT_END(pt);
}
//////////////////////////////////////////////////
int main()
{
init_peripheral();
timer_init();
sei();
PT_INIT(&pt_taskGreen);
PT_INIT(&pt_taskRed);
for (;;)
{
taskGreen(&pt_taskGreen);
taskRed(&pt_taskRed);
}
}
สิ่งที่น่าสนใจคืองานย่อยทั้งสองงาน (taskGreen และ taskRed) มีโค้ดที่เกือบจะเหมือนกับโค้ดดั้งเดิมที่เขียนสำหรับงานเดี่ยว
โค้ดดั้งเดิม (แบบใช้ delay) | โค้ดที่อิมพลิเมนต์ด้วยไลบรารี Protothreads |
---|---|
void taskGreen()
{
for (;;)
{
set_led(LED_GREEN,1);
_delay_ms(1000);
set_led(LED_GREEN,0);
_delay_ms(500);
}
}
|
PT_THREAD(taskGreen(struct pt* pt))
{
static uint32_t ts;
PT_BEGIN(pt);
for (;;)
{
set_led(LED_GREEN,1);
PT_DELAY(pt, 1000, ts);
set_led(LED_GREEN,0);
PT_DELAY(pt, 500, ts);
}
PT_END(pt);
}
|
การทำงานของไลบรารี Protothreads
รายละเอียดคร่าว ๆ ของโปรแกรมและมาโครต่าง ๆ ในไลบรารี Protothreads ที่นำมาใช้มีดังนี้
- struct pt ถูกนิยามไว้ในไฟล์เฮดเดอร์ pt.h ซึ่งภายในมีตัวแปรสมาชิกเพียงตัวเดียวที่เอาไว้เก็บสถานะปัจจุบันของงาน
// นิยามโดยย่อของ struct pt
struct pt
{
void* state;
};
- มาโคร PT_THREAD(task(struct pt* pt)) ใช้สำหรับครอบการประกาศฟังก์ชันที่จะให้ทำหน้าที่เป็น protothread (เสมือนเป็นเครื่องจักรสถานะตัวหนึ่ง) ซึ่งมีผลเทียบเท่ากับการประกาศฟังก์ชันด้วยคำสั่ง
char task(struct pt* pt)
- ซึ่งไม่ต่างจากการประกาศฟังก์ชันทั่วไป แต่การประกาศฟังก์ชันผ่านมาโครนี้จะช่วยย้ำผู้เขียนโปรแกรมว่าฟังก์ชันนี้ทำหน้าที่เป็น protothread
- มาโคร PT_BEGIN(pt) ถูกนิยามไว้เป็นโค้ดที่เรียกใช้คำสั่ง goto ตามสถานะที่เก็บไว้ในตัวแปร pt ซึ่งเทียบเท่ากับการใช้คำสั่ง
if (pt->state != NULL) goto *(pt->state);
- มาโครนี้จึงต้องถูกเรียกเป็นคำสั่งแรกเสมอในฟังก์ชันที่จะทำหน้าที่เป็นเครื่องจักรสถานะ
- มาโคร PT_END(pt) ระบุจุดสิ้นสุดของ protothread โดยทำงานเทียบเท่ากับคำสั่ง return
return 3;
- ค่า 3 ถูกใช้ในไลบรารี Protothreads เป็นการภายในเพื่อระบุว่า protothread นี้จบการทำงานโดยสมบูรณ์
- มาโคร PT_INIT(pt) ทำหน้าที่กำหนดสถานะเริ่มต้นให้กับตัวแปร pt ซึ่งมีผลเทียบเท่ากับการใช้คำสั่ง
pt->state = NULL;
- เมื่อพิจารณาคู่กับมาโคร PT_BEGIN แล้วจึงมีความหมายว่าให้เริ่มต้นทำงานตั้งแต่ต้นฟังก์ชัน
- มาโคร PT_WAIT_WHILE(pt, cond) ถูกนิยามไว้เป็นการตรวจสอบเงื่อนไข cond ว่ายังเป็นจริงอยู่หรือไม่ หากเป็นจริงจะบันทึกสถานะบรรทัดปัจจุบันไว้ในตัวแปร pt ก่อนที่จะ return ออกจากฟังก์ชัน ซึ่งเทียบเท่ากับการใช้คำสั่ง
L__LINE__: pt->state = &&L__LINE__; // __LINE__ ถูกคอมไพเลอร์แทนที่ด้วยหมายเลขบรรทัดปัจจุบัน
if (cond) return 0;
- มาโคร PT_DELAY(pt, ms, ts) ไม่ได้อยู่ในไลบรารี Protothreads แต่สร้างขึ้นเพื่อความสะดวกในการจำลองการทำงานของคำสั่ง _delay_ms ในแบบที่ไม่หยุดรอ นิยามไว้ให้เทียบเท่ากับการใช้คำสั่ง
ts = timer_millis();
PT_WAIT_WHILE(pt, timer_millis()-ts < (ms));
ดังนั้นส่วนของโปรแกรมที่นิยาม protothread ชื่อ taskGreen
PT_THREAD(taskGreen(struct pt* pt))
{
static uint32_t ts;
PT_BEGIN(pt);
for (;;)
{
set_led(LED_GREEN,1);
PT_DELAY(pt, 1000, ts);
set_led(LED_GREEN,0);
PT_DELAY(pt, 500, ts);
}
PT_END(pt);
}
เมื่อแทนที่มาโครต่าง ๆ เรียบร้อยแล้วจะมีผลเทียบเท่ากับโค้ดด้านล่าง
char taskGreen(struct pt* pt)
{
static uint32_t ts;
// มาโคร PT_BEGIN(pt)
if (pt->state != NULL) goto *(pt->state);
for (;;)
{
set_led(LED_GREEN,1);
// มาโคร PT_DELAY(pt, 1000, ts);
ts = timer_millis();
L43: pt->state = &&L43; // สมมติว่า 43 คือเลขบรรทัดนี้
if (timer_millis()-ts < 1000) return 0;
set_led(LED_GREEN,0);
// มาโคร PT_DELAY(pt, 500, ts);
ts = timer_millis();
L45: pt->state = &&L45; // สมมตว่า 45 คือเลขบรรทัดนี้
if (timer_millis()-ts < 500) return 0;
}
// มาโคร PT_END(pt);
return 3;
}
ซึ่งเหมือนกับโค้ดที่อิมพลิเมนต์เครื่องจักรสถานะแบบใช้ goto นั่นเอง
ข้อควรระวังในการใช้ไลบรารี Protothreads
เนื่องจากโค้ดที่เขียนในรูป protothread จะมีหน้าตาคล้ายกับโค้ดที่ทำงานเดี่ยวเป็นอย่างมาก จึงเป็นการง่ายที่จะเผลอเขียนโค้ดที่ไม่ได้คำนึงถึงการทำงานร่วมกับงานอื่น ด้านล่างเป็นข้อผิดพลาดที่เกิดขึ้นได้ง่ายในการใช้ Protothreads
- ตัวแปรแบบโลคัลถูกทำลาย จำไว้เสมอว่าฟังก์ชันที่เป็น protothread มีการ return ตลอดเวลาแม้จะไม่ปรากฏคำสั่ง return ให้เห็น ดังนั้นค่าของตัวแปรที่ถูกประกาศแบบโลคัลจะสูญหายทันที โค้ดด้านล่างแสดงส่วนของโปรแกรมที่พยายามทำให้ LED สีเขียวกระพริบ 5 ครั้ง แล้วจึงให้ LED สีแดงกระพริบอีก 3 ครั้ง แต่โปรแกรมจะไม่ทำงานตามที่คาดหวัง
1 PT_THREAD(taskBlink(struct pt* pt))
2 {
3 static uint32_t ts;
4 int i; // <- ถูกทำลายและสร้างใหม่ตลอดเวลา
5
6 PT_BEGIN(pt);
7
8 // สีเขียวกระพริบ 5 ครั้ง
9 for (i = 0; i < 5; i++)
10 {
11 set_led(LED_GREEN,1);
12 PT_DELAY(pt,100,ts);
13 set_led(LED_GREEN,0);
14 PT_DELAY(pt,100,ts);
15 }
16
17 // สีแดงกระพริบ 3 ครั้ง
18 for (i = 0; i < 3; i++)
19 {
20 set_led(LED_RED,1);
21 PT_DELAY(pt,100,ts);
22 set_led(LED_RED,1);
23 PT_DELAY(pt,100,ts);
24 }
25 PT_END(pt);
26 }
เหตุที่โปรแกรมไม่ทำงานตามที่คาดหวังเนื่องจากตัวแปร i ที่ประกาศไว้ในบรรทัดที่ 4 เป็นตัวแปรแบบโลคัลธรรมดา ภายในมาโคร PT_DELAY มีคำสั่ง return ซึ่งมีผลทำให้ตัวแปร i ถูกทำลาย และถูกสร้างใหม่เมื่อฟังก์ชันถูกเรียกให้ทำงานต่อ ดังนั้นค่าของ i ในลูป for แรกจึงเป็นศูนย์เสมอ วิธีที่ถูกต้องคือประกาศให้ตัวแปร i เป็นแบบ static
4 static int i;
- ลูปอนันต์ไม่เปิดโอกาสให้ออกจากฟังก์ชัน การใช้ลูปแบบ for (;;) ทำได้ใน protothread ก็จริง แต่ต้องให้ฟังก์ชันได้ return เพื่อให้โอกาสงานอื่น ๆ ทำงานด้วยเช่นกัน โค้ดด้านล่างเป็นตัวอย่างการตีความค่าแสง 2 ระดับและแสดงผลลัพธ์บน LED สีแดงตลอดเวลา
1 PT_THREAD(taskLight(struct pt* pt))
2 {
3 PT_BEGIN(pt);
4
5 for (;;)
6 {
7 uint16_t light = read_adc(PC4);
8 set_led(LED_RED, light/512);
9 }
10
11 PT_END(pt);
12 }
จะเห็นว่าเมื่อลูป for เริ่มทำงานแล้วจะไม่มีการเปิดโอกาสให้ออกจากฟังก์ชันได้อีกเลย นั่นหมายถึงงานอื่น ๆ ที่ต้องการให้ทำควบคู่กันไปจะหยุดชะงักลงทันที หากไม่ต้องการหน่วงเวลาใด ๆ อย่างน้อยที่สุดต้องมีการใช้คำสั่ง PT_YIELD เพื่อฟังก์ชันจะได้มีโอกาส return และให้งานอื่น ๆ ได้ทำงานบ้าง
1 PT_THREAD(taskLight(struct pt* pt))
2 {
3 PT_BEGIN(pt);
4
5 for (;;)
6 {
7 uint16_t light = read_adc(PC4);
8 set_led(LED_RED, light/512);
9 PT_YIELD(pt):
10 }
11
12 PT_END(pt);
13 }
มาโครที่ใช้งานบ่อย
- PT_INIT(pt) กำหนดค่าสถานะเริ่มต้นให้กับโพรโทเทรด
- PT_BEGIN(pt) ประกาศจุดเริ่มต้นภายในฟังก์ชันที่ใช้งานเป็นโพรโทเทรด
- PT_END(pt) ประกาศจุดสิ้นสุดภายในฟังก์ชันที่ใช้งานเป็นโพรโทเทรด
- PT_WAIT_WHILE(pt,cond) รอ (แบบมัสติทาสกิ้ง) ตราบเท่าที่เงื่อนไข cond ยังเป็นจริงอยู่
- PT_WAIT_UNTIL(pt,cond) รอ (แบบมัสติทาสกิ้ง) จนกระทั่งเงื่อนไข cond เป็นจริง
- PT_YIELD(pt) เปิดโอกาสให้โพรโทเทรดอื่นได้ทำงาน
ดูรายละเอียดเพิ่มเติมและคำอธิบายมาโครอื่น ๆ ได้จาก คู่มืออ้างอิงมาโครที่ Protothreads เตรียมให้