ผลต่างระหว่างรุ่นของ "มัลติทาสกิ้งด้วยไลบรารี Protothreads"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
 
(ไม่แสดง 23 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน)
แถว 1: แถว 1:
[http://dunkels.com/adam/pt/ Protothreads] พัฒนาขึ้นโดย [http://dunkels.com/adam/ Adam Dunkels] โดยมีวัตถุประสงค์เพื่อให้นักพัฒนาโค้ดสามารถเขียนโปรแกรมแบบมัลติทาสกิ้งในรูปแบบที่เข้าใจได้ง่ายบนอุปกรณ์ที่มีทรัพยากรจำกัดอย่างเช่นไมโครคอนโทรลเลอร์ ไลบรารีนี้นำเอาเทคนิคการบันทึกสถานะร่วมกับการใช้คำสั่ง goto ตามที่ได้กล่าวไปแล้วมารวมไว้เป็นชุดมาโครในภาษาซี ทำให้รูปแบบโค้ดของงานย่อยแต่ละงานมีลักษณะราวกับการทำงานแบบซิงเกิลทาสก์
+
: ''วิกินี้เป็นส่วนหนึ่งของรายวิชา [[01204223]]''
  
= การติดตั้งไลบรารี Protothreads เพื่อใช้งานร่วมกับ Arduino =
+
[http://dunkels.com/adam/pt/ Protothreads] พัฒนาขึ้นโดย [http://dunkels.com/adam/ Adam Dunkels] โดยมีวัตถุประสงค์เพื่อให้นักพัฒนาโค้ดสามารถเขียนโปรแกรมแบบมัลติทาสกิ้งในรูปแบบที่เข้าใจได้ง่ายบนอุปกรณ์ที่มีทรัพยากรจำกัดอย่างเช่นไมโครคอนโทรลเลอร์ ไลบรารีนี้นำเอาเทคนิคการบันทึกสถานะร่วมกับการใช้คำสั่ง <tt>goto</tt> มารวมไว้เป็นชุดมาโครในภาษาซี ทำให้รูปแบบโค้ดของงานย่อยแต่ละงานมีลักษณะคล้ายคลึงกับการเขียนโค้ดที่ทำงานแบบซิงเกิลทาสก์
ดาวน์โหลดไฟล์ [http://www.cpe.ku.ac.th/~cpj/204223/pt.tgz pt.tgz] แล้วแตกเอาไว้ในไดเรคตอรี libraries ภายใต้ไดเรคตอรีที่เก็บโปรเจ็ค Arduino ทั้งหมด
 
  
บนลินุกซ์ ไดเรคตอรีนี้คือ <code>~/sketchbook/libraries/</code>
+
== ตัวอย่างโปรแกรม ==
$ cd ~/sketchbook/libraries/
 
$ wget http://www.cpe.ku.ac.th/~cpj/204223/pt.tgz
 
$ tar zxf pt.tgz
 
 
 
บน MAC OS X ไดเรคตอรีปลายทางคือ <code>~/Documents/Arduino/libraries/</code>
 
$ cd ~/Documents/Arduino/libraries/
 
$ wget http://www.cpe.ku.ac.th/~cpj/204223/pt.tgz
 
$ tar zxf pt.tgz
 
 
 
= ตัวอย่างโปรแกรม =
 
 
โปรแกรมต่อไปนี้เขียนขึ้นโดยอาศัยไลบรารี Protothreads ซึ่งให้พฤติกรรมการทำงานที่เหมือนกับตัวอย่างตามวิกิ [[มัลติทาสกิ้งบนไมโครคอนโทรลเลอร์]] ทุกประการ
 
โปรแกรมต่อไปนี้เขียนขึ้นโดยอาศัยไลบรารี Protothreads ซึ่งให้พฤติกรรมการทำงานที่เหมือนกับตัวอย่างตามวิกิ [[มัลติทาสกิ้งบนไมโครคอนโทรลเลอร์]] ทุกประการ
  
แถว 21: แถว 10:
  
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
#include <Practicum.h>
+
#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 = millis(); \
+
     ts = timer_millis(); \
     PT_WAIT_WHILE(pt, millis()-ts < (ms));
+
     PT_WAIT_WHILE(pt, timer_millis()-ts < (ms));
  
 
struct pt pt_taskRed;
 
struct pt pt_taskRed;
แถว 38: แถว 33:
 
   PT_BEGIN(pt);
 
   PT_BEGIN(pt);
 
    
 
    
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN, 1);
 
     PT_DELAY(pt, 1000, ts);
 
     PT_DELAY(pt, 1000, ts);
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN, 0);
 
     PT_DELAY(pt, 500, ts);
 
     PT_DELAY(pt, 500, ts);
 
   }
 
   }
แถว 56: แถว 51:
 
   PT_BEGIN(pt);
 
   PT_BEGIN(pt);
 
    
 
    
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC0, HIGH);
+
     set_led(LED_RED, 1);
 
     PT_DELAY(pt, 700, ts);
 
     PT_DELAY(pt, 700, ts);
     digitalWrite(PIN_PC0, LOW);
+
     set_led(LED_RED, 0);
 
     PT_DELAY(pt, 300, ts);
 
     PT_DELAY(pt, 300, ts);
 
   }
 
   }
แถว 67: แถว 62:
 
}
 
}
  
///////////////////////////////////////////////////////
+
//////////////////////////////////////////////////
void setup()
+
int main()
 
{
 
{
   pinMode(PIN_PC0, OUTPUT);
+
   init_peripheral();
   pinMode(PIN_PC1, OUTPUT);
+
   timer_init();
   pinMode(PIN_PC2, OUTPUT);
+
   sei();
  pinMode(PIN_PC3, INPUT_PULLUP);
 
  pinMode(PIN_PC4, INPUT);
 
  pinMode(PIN_PD3, OUTPUT);
 
 
    
 
    
 
   PT_INIT(&pt_taskGreen);
 
   PT_INIT(&pt_taskGreen);
 
   PT_INIT(&pt_taskRed);
 
   PT_INIT(&pt_taskRed);
}
 
  
///////////////////////////////////////////////////////
+
  for (;;)
void loop()
+
  {
{
+
    taskGreen(&pt_taskGreen);
  taskGreen(&pt_taskGreen);
+
    taskRed(&pt_taskRed);
  taskRed(&pt_taskRed);
+
  }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
แถว 98: แถว 89:
 
void taskGreen()
 
void taskGreen()
 
{
 
{
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
     delay(1000);
+
     _delay_ms(1000);
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
     delay(500);
+
     _delay_ms(500);
 
   }
 
   }
 
}
 
}
แถว 115: แถว 106:
 
   PT_BEGIN(pt);
 
   PT_BEGIN(pt);
 
    
 
    
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
 
     PT_DELAY(pt, 1000, ts);
 
     PT_DELAY(pt, 1000, ts);
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
 
     PT_DELAY(pt, 500, ts);
 
     PT_DELAY(pt, 500, ts);
 
   }
 
   }
แถว 129: แถว 120:
 
|}
 
|}
  
= การทำงานของไลบรารี Protothreads =
+
== การทำงานของไลบรารี Protothreads ==
  
 
รายละเอียดคร่าว ๆ ของโปรแกรมและมาโครต่าง ๆ ในไลบรารี Protothreads ที่นำมาใช้มีดังนี้
 
รายละเอียดคร่าว ๆ ของโปรแกรมและมาโครต่าง ๆ ในไลบรารี Protothreads ที่นำมาใช้มีดังนี้
* <code>struct pt</code> ถูกนิยามไว้ในไฟล์เฮดเดอร์ <code>pt.h</code> ซึ่งภายในมีตัวแปรสมาชิกเพียงตัวเดียวที่เอาไว้เก็บสถานะปัจจุบันของงาน
+
* <tt>struct pt</tt> ถูกนิยามไว้ในไฟล์เฮดเดอร์ <tt>pt.h</tt> ซึ่งภายในมีตัวแปรสมาชิกเพียงตัวเดียวที่เอาไว้เก็บสถานะปัจจุบันของงาน
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
// นิยามโดยย่อของ struct pt
 
// นิยามโดยย่อของ struct pt
แถว 141: แถว 132:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* มาโคร <code>PT_THREAD(task(struct pt* pt))</code> ใช้สำหรับครอบการประกาศฟังก์ชันที่จะให้ทำหน้าที่เป็น protothread (เสมือนเป็นเครื่องจักรสถานะตัวหนึ่ง) ซึ่งมีผลเทียบเท่ากับการประกาศฟังก์ชันด้วยคำสั่ง
+
* มาโคร <tt>PT_THREAD(task(struct pt* pt))</tt> ใช้สำหรับครอบการประกาศฟังก์ชันที่จะให้ทำหน้าที่เป็น protothread (เสมือนเป็นเครื่องจักรสถานะตัวหนึ่ง) ซึ่งมีผลเทียบเท่ากับการประกาศฟังก์ชันด้วยคำสั่ง
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
char task(struct pt* pt)
 
char task(struct pt* pt)
แถว 147: แถว 138:
 
: ซึ่งไม่ต่างจากการประกาศฟังก์ชันทั่วไป แต่การประกาศฟังก์ชันผ่านมาโครนี้จะช่วยย้ำผู้เขียนโปรแกรมว่าฟังก์ชันนี้ทำหน้าที่เป็น protothread
 
: ซึ่งไม่ต่างจากการประกาศฟังก์ชันทั่วไป แต่การประกาศฟังก์ชันผ่านมาโครนี้จะช่วยย้ำผู้เขียนโปรแกรมว่าฟังก์ชันนี้ทำหน้าที่เป็น protothread
  
* มาโคร <code>PT_BEGIN(pt)</code> ถูกนิยามไว้เป็นโค้ดที่เรียกใช้คำสั่ง <code>goto</code> ตามสถานะที่เก็บไว้ในตัวแปร <code>pt</code> ซึ่งเทียบเท่ากับการใช้คำสั่ง
+
* มาโคร <tt>PT_BEGIN(pt)</tt> ถูกนิยามไว้เป็นโค้ดที่เรียกใช้คำสั่ง <tt>goto</tt> ตามสถานะที่เก็บไว้ในตัวแปร <tt>pt</tt> ซึ่งเทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
if (pt->state != NULL) goto *(pt->state);
 
if (pt->state != NULL) goto *(pt->state);
แถว 153: แถว 144:
 
: มาโครนี้จึงต้องถูกเรียกเป็นคำสั่งแรกเสมอในฟังก์ชันที่จะทำหน้าที่เป็นเครื่องจักรสถานะ
 
: มาโครนี้จึงต้องถูกเรียกเป็นคำสั่งแรกเสมอในฟังก์ชันที่จะทำหน้าที่เป็นเครื่องจักรสถานะ
  
* มาโคร <code>PT_END(pt)</code> ระบุจุดสิ้นสุดของ protothread โดยทำงานเทียบเท่ากับคำสั่ง return
+
* มาโคร <tt>PT_END(pt)</tt> ระบุจุดสิ้นสุดของ protothread โดยทำงานเทียบเท่ากับคำสั่ง return
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
return 3;
 
return 3;
แถว 159: แถว 150:
 
: ค่า 3 ถูกใช้ในไลบรารี Protothreads เป็นการภายในเพื่อระบุว่า protothread นี้จบการทำงานโดยสมบูรณ์
 
: ค่า 3 ถูกใช้ในไลบรารี Protothreads เป็นการภายในเพื่อระบุว่า protothread นี้จบการทำงานโดยสมบูรณ์
  
* มาโคร <code>PT_INIT(pt)</code> ทำหน้าที่กำหนดสถานะเริ่มต้นให้กับตัวแปร <code>pt</code> ซึ่งมีผลเทียบเท่ากับการใช้คำสั่ง
+
* มาโคร <tt>PT_INIT(pt)</tt> ทำหน้าที่กำหนดสถานะเริ่มต้นให้กับตัวแปร <tt>pt</tt> ซึ่งมีผลเทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
pt->state = NULL;
 
pt->state = NULL;
 
</syntaxhighlight>
 
</syntaxhighlight>
: เมื่อพิจารณาคู่กับมาโคร <code>PT_BEGIN</code> แล้วจึงมีความหมายว่าให้เริ่มต้นทำงานตั้งแต่ต้นฟังก์ชัน
+
: เมื่อพิจารณาคู่กับมาโคร <tt>PT_BEGIN</tt> แล้วจึงมีความหมายว่าให้เริ่มต้นทำงานตั้งแต่ต้นฟังก์ชัน
  
* มาโคร <code>PT_WAIT_WHILE(pt, cond)</code> ถูกนิยามไว้เป็นการตรวจสอบเงื่อนไข <code>cond</code> ว่ายังเป็นจริงอยู่หรือไม่ หากเป็นจริงจะบันทึกสถานะบรรทัดปัจจุบันไว้ในตัวแปร <code>pt</code> ก่อนที่จะ return ออกจากฟังก์ชัน ซึ่งเทียบเท่ากับการใช้คำสั่ง
+
* มาโคร <tt>PT_WAIT_WHILE(pt, cond)</tt> ถูกนิยามไว้เป็นการตรวจสอบเงื่อนไข <tt>cond</tt> ว่ายังเป็นจริงอยู่หรือไม่ หากเป็นจริงจะบันทึกสถานะบรรทัดปัจจุบันไว้ในตัวแปร <tt>pt</tt> ก่อนที่จะ return ออกจากฟังก์ชัน ซึ่งเทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
L__LINE__: pt->state = &&L__LINE__;  // __LINE__ ถูกคอมไพเลอร์แทนที่ด้วยหมายเลขบรรทัดปัจจุบัน
 
L__LINE__: pt->state = &&L__LINE__;  // __LINE__ ถูกคอมไพเลอร์แทนที่ด้วยหมายเลขบรรทัดปัจจุบัน
แถว 171: แถว 162:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* มาโคร <code>PT_DELAY(pt, ms, ts)</code> ไม่ได้อยู่ในไลบรารี Protothreads แต่สร้างขึ้นเพื่อความสะดวกในการจำลองการทำงานของคำสั่ง delay ในแบบที่ไม่หยุดรอ นิยามไว้ให้เทียบเท่ากับการใช้คำสั่ง
+
* มาโคร <tt>PT_DELAY(pt, ms, ts)</tt> ไม่ได้อยู่ในไลบรารี Protothreads แต่สร้างขึ้นเพื่อความสะดวกในการจำลองการทำงานของคำสั่ง _delay_ms ในแบบที่ไม่หยุดรอ นิยามไว้ให้เทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
ts = millis();
+
ts = timer_millis();
PT_WAIT_WHILE(pt, millis()-ts < (ms));
+
PT_WAIT_WHILE(pt, timer_millis()-ts < (ms));
 
</syntaxhighlight>
 
</syntaxhighlight>
  
แถว 185: แถว 176:
 
   PT_BEGIN(pt);
 
   PT_BEGIN(pt);
 
    
 
    
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
 
     PT_DELAY(pt, 1000, ts);
 
     PT_DELAY(pt, 1000, ts);
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
 
     PT_DELAY(pt, 500, ts);
 
     PT_DELAY(pt, 500, ts);
 
   }
 
   }
แถว 205: แถว 196:
 
   if (pt->state != NULL) goto *(pt->state);
 
   if (pt->state != NULL) goto *(pt->state);
 
    
 
    
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
 
     // มาโคร PT_DELAY(pt, 1000, ts);
 
     // มาโคร PT_DELAY(pt, 1000, ts);
     ts = millis();
+
     ts = timer_millis();
 
     L43: pt->state = &&L43;  // สมมติว่า 43 คือเลขบรรทัดนี้
 
     L43: pt->state = &&L43;  // สมมติว่า 43 คือเลขบรรทัดนี้
     if (millis()-ts < 1000) return 0;
+
     if (timer_millis()-ts < 1000) return 0;
  
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
 
     // มาโคร PT_DELAY(pt, 500, ts);
 
     // มาโคร PT_DELAY(pt, 500, ts);
     ts = millis();
+
     ts = timer_millis();
 
     L45: pt->state = &&L45;  // สมมตว่า 45 คือเลขบรรทัดนี้
 
     L45: pt->state = &&L45;  // สมมตว่า 45 คือเลขบรรทัดนี้
     if (millis()-ts < 500) return 0;
+
     if (timer_millis()-ts < 500) return 0;
 
   }
 
   }
 
    
 
    
แถว 224: แถว 215:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
ซึ่งเหมือนกับโค้ดที่อิมพลิเมนต์เครื่องจักรสถานะแบบใช้ <code>goto</code>นั่นเอง
+
ซึ่งเหมือนกับโค้ดที่อิมพลิเมนต์เครื่องจักรสถานะแบบใช้ <tt>goto</tt> นั่นเอง
  
= ข้อควรระวังในการใช้ไลบรารี Protothreads =
+
== ข้อควรระวังในการใช้ไลบรารี Protothreads ==
 
เนื่องจากโค้ดที่เขียนในรูป protothread จะมีหน้าตาคล้ายกับโค้ดที่ทำงานเดี่ยวเป็นอย่างมาก จึงเป็นการง่ายที่จะเผลอเขียนโค้ดที่ไม่ได้คำนึงถึงการทำงานร่วมกับงานอื่น ด้านล่างเป็นข้อผิดพลาดที่เกิดขึ้นได้ง่ายในการใช้ Protothreads
 
เนื่องจากโค้ดที่เขียนในรูป protothread จะมีหน้าตาคล้ายกับโค้ดที่ทำงานเดี่ยวเป็นอย่างมาก จึงเป็นการง่ายที่จะเผลอเขียนโค้ดที่ไม่ได้คำนึงถึงการทำงานร่วมกับงานอื่น ด้านล่างเป็นข้อผิดพลาดที่เกิดขึ้นได้ง่ายในการใช้ Protothreads
  
* '''ตัวแปรแบบโลคัลถูกทำลาย''' จำไว้เสมอว่าฟังก์ชันมีการเรียกใช้ return ตลอดเวลา ดังนั้นค่าของตัวแปรที่ถูกประกาศแบบโลคัลจะสูญหายทันที โค้ดด้านล่างแสดงส่วนของโปรแกรมที่พยายามทำให้ LED สีเขียวกระพริบ 5 ครั้ง แล้วจึงให้ LED สีแดงกระพริบอีก 3 ครั้ง แต่โปรแกรมจะไม่ทำงานตามที่คาดหวัง
+
* '''ตัวแปรแบบโลคัลถูกทำลาย''' จำไว้เสมอว่าฟังก์ชันที่เป็น protothread มีการ return ตลอดเวลาแม้จะไม่ปรากฏคำสั่ง return ให้เห็น ดังนั้นค่าของตัวแปรที่ถูกประกาศแบบโลคัลจะสูญหายทันที โค้ดด้านล่างแสดงส่วนของโปรแกรมที่พยายามทำให้ LED สีเขียวกระพริบ 5 ครั้ง แล้วจึงให้ LED สีแดงกระพริบอีก 3 ครั้ง แต่โปรแกรมจะไม่ทำงานตามที่คาดหวัง
 
<syntaxhighlight lang="C" line>
 
<syntaxhighlight lang="C" line>
 
PT_THREAD(taskBlink(struct pt* pt))
 
PT_THREAD(taskBlink(struct pt* pt))
แถว 241: แถว 232:
 
   for (i = 0; i < 5; i++)
 
   for (i = 0; i < 5; i++)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
 
     PT_DELAY(pt,100,ts);
 
     PT_DELAY(pt,100,ts);
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
 
     PT_DELAY(pt,100,ts);
 
     PT_DELAY(pt,100,ts);
 
   }
 
   }
แถว 250: แถว 241:
 
   for (i = 0; i < 3; i++)
 
   for (i = 0; i < 3; i++)
 
   {
 
   {
     digitalWrite(PIN_PC0, HIGH);
+
     set_led(LED_RED,1);
 
     PT_DELAY(pt,100,ts);
 
     PT_DELAY(pt,100,ts);
     digitalWrite(PIN_PC0, LOW);
+
     set_led(LED_RED,1);
 
     PT_DELAY(pt,100,ts);
 
     PT_DELAY(pt,100,ts);
 
   }
 
   }
แถว 258: แถว 249:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
เหตุที่โปรแกรมไม่ทำงานตามที่คาดหวังเนื่องจากตัวแปร <code>i</code> ที่ประกาศไว้ในบรรทัดที่ 4 เป็นตัวแปรแบบโลคัลธรรมดา ภายในมาโคร <code>PT_DELAY</code> มีคำสั่ง return ซึ่งมีผลทำให้ตัวแปร <code>i</code> ถูกทำลาย และถูกสร้างใหม่เมื่อฟังก์ชันถูกเรียกให้ทำงานต่อ ดังนั้นค่าของ <code>i</code> ในลูป for แรกจึงเป็นศูนย์เสมอ วิธีที่ถูกต้องคือประกาศให้ตัวแปร <code>i</code> เป็นแบบ static
+
เหตุที่โปรแกรมไม่ทำงานตามที่คาดหวังเนื่องจากตัวแปร <tt>i</tt> ที่ประกาศไว้ในบรรทัดที่ 4 เป็นตัวแปรแบบโลคัลธรรมดา ภายในมาโคร <tt>PT_DELAY</tt> มีคำสั่ง return ซึ่งมีผลทำให้ตัวแปร <tt>i</tt> ถูกทำลาย และถูกสร้างใหม่เมื่อฟังก์ชันถูกเรียกให้ทำงานต่อ ดังนั้นค่าของ <tt>i</tt> ในลูป for แรกจึงเป็นศูนย์เสมอ วิธีที่ถูกต้องคือประกาศให้ตัวแปร <tt>i</tt> เป็นแบบ static
 
<syntaxhighlight lang="C" line start="4">
 
<syntaxhighlight lang="C" line start="4">
 
   static int i;
 
   static int i;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* '''ลูปอนันต์ไม่เปิดโอกาสให้ออกจากฟังก์ชัน''' การใช้ลูปแบบ <code>while (1)</code> ทำได้ใน protothread ก็จริง แต่ต้องให้ฟังก์ชันได้ return เพื่อให้โอกาสงานอื่น ๆ ทำงานด้วยเช่นกัน โค้ดด้านล่างเป็นตัวอย่างการตีความค่าแสง 2 ระดับและแสดงผลลัพธ์บน LED สีแดงตลอดเวลา
+
* '''ลูปอนันต์ไม่เปิดโอกาสให้ออกจากฟังก์ชัน''' การใช้ลูปแบบ <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))
แถว 269: แถว 260:
 
   PT_BEGIN(pt);
 
   PT_BEGIN(pt);
  
   while (1)
+
   for (;;)
 
   {
 
   {
     uint16_t light = analogRead(PIN_PC4);
+
     uint16_t light = read_adc(PC4);
     digitalWrite(PIN_PC0, light/512);
+
     set_led(LED_RED, light/512);
 
   }
 
   }
  
แถว 278: แถว 269:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
จะเห็นว่าเมื่อลูป while เริ่มทำงานแล้วจะไม่มีการเปิดโอกาสให้ออกจากฟังก์ชันได้อีกเลย นั่นหมายถึงงานอื่น ๆ ที่ต้องการให้ทำควบคู่กันไปจะหยุดชะงักลงทันที หากไม่ต้องการหน่วงเวลาใด ๆ อย่างน้อยที่สุดต้องมีการใช้คำสั่ง <code>PT_YIELD</code> เพื่อฟังก์ชันจะได้มีโอกาส return และให้งานอื่น ๆ ได้ทำงานบ้าง
+
จะเห็นว่าเมื่อลูป 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))
แถว 284: แถว 275:
 
   PT_BEGIN(pt);
 
   PT_BEGIN(pt);
  
   while (1)
+
   for (;;)
 
   {
 
   {
     uint16_t light = analogRead(PIN_PC4);
+
     uint16_t light = read_adc(PC4);
     digitalWrite(PIN_PC0, light/512);
+
     set_led(LED_RED, light/512);
 
     PT_YIELD(pt):
 
     PT_YIELD(pt):
 
   }
 
   }
แถว 294: แถว 285:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== มาโครที่ใช้งานบ่อย ==
 +
* [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#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#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 คู่มืออ้างอิงมาโครที่ 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 เตรียมให้