ผลต่างระหว่างรุ่นของ "มัลติทาสกิ้งบนไมโครคอนโทรลเลอร์"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
 
(ไม่แสดง 27 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน)
แถว 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 วินาทีสลับกันไป''
  
จะเห็นว่างานทั้งหมดประกอบด้วยงานย่อยสองงาน ที่ผ่านมานั้นหากใช้เฟรมเวิร์กของ Arduino การทำให้เพียง LED สีเขียวกระพริบตามที่กำหนดทำได้โดยการเขียนโค้ดในฟังก์ชัน <code>loop</code> ลักษณะนี้
+
จะเห็นว่างานทั้งหมดประกอบด้วยงานย่อยสองงาน การทำให้เพียง LED สีเขียวกระพริบตามที่กำหนดทำได้โดยการเขียนโค้ดไว้ในลูปอนันต์ลักษณะนี้ (หรือเขียนโค้ดส่วนนี้ไว้ในฟังก์ชัน <tt>loop</tt> กรณีที่ใช้ Arduino)
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
// LED สีเขียวติด 1 วินาที ดับ 0.5 วินาที
 
// LED สีเขียวติด 1 วินาที ดับ 0.5 วินาที
void loop()
+
int main()
 
{
 
{
   digitalWrite(PIN_PC2, HIGH);
+
   :
  delay(1000);
+
  for (;;)
  digitalWrite(PIN_PC2, LOW);
+
  {
  delay(500);
+
    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 วินาที
void loop()
+
int main()
 
{
 
{
   digitalWrite(PIN_PC0, HIGH);
+
   :
  delay(700);
+
  for (;;)
  digitalWrite(PIN_PC0, LOW);
+
  {
  delay(300);
+
    set_led(LED_RED,1);
 +
    _delay_ms(700);
 +
    set_led(LED_RED,0);
 +
    _delay_ms(300);
 +
  }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
แถว 39: แถว 49:
 
void taskGreen()
 
void taskGreen()
 
{
 
{
   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);
 
}
 
}
  
 
void taskRed()
 
void taskRed()
 
{
 
{
   digitalWrite(PIN_PC0, HIGH);
+
   set_led(LED_RED,1);
   delay(700);
+
   _delay_ms(700);
   digitalWrite(PIN_PC0, LOW);
+
   set_led(LED_RED,0);
   delay(300);
+
   _delay_ms(300);
 
}
 
}
  
void loop()
+
int main()
 
{
 
{
   taskGreen();
+
   for (;;)
  taskRed();
+
  {
 +
    taskGreen();
 +
    taskRed();
 +
  }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
ทั้งนี้เนื่องจากว่าฟังก์ชัน <code>taskRed()</code> ไม่มีการถูกเรียกจนกว่าฟังก์ชัน <code>taskGreen()</code> จะทำงานเสร็จในหนึ่งรอบซึ่งใช้เวลาทั้งสิ้น 1.5 วินาที ในทำนองเดียวกัน ระหว่างที่ฟังก์ชัน <code>taskRed()</code> ทำงานอยู่นั้นฟังก์ชัน <code>taskGreen()</code> ก็ไม่มีโอกาสได้ทำงานเช่นกัน
+
ทั้งนี้เนื่องจากว่าฟังก์ชัน <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 สีเขียวติด และบันทึกเวลาปัจจุบันเป็นมิลลิวินาทีจากฟังก์ชัน <code>millis()</code> ไว้ในตัวแปร <code>ts</code> (ย่อมาจาก timestamp)
+
# เริ่มทำงานโดยเข้าสู่สถานะ ON ซึ่งมีการสั่งให้ LED สีเขียวติด และบันทึกเวลาปัจจุบันเป็นมิลลิวินาทีจากฟังก์ชัน <tt>millis()</tt> ไว้ในตัวแปร <tt>ts</tt> (ย่อมาจาก timestamp)
 
#: ''(หมายเหตุ: ฟังก์ชัน millis() เป็นฟังก์ชันที่เฟรมเวิร์ก Arduino มีให้ใช้สำหรับตรวจสอบว่าไมโครคอนโทรลเลอร์ได้ทำงานมาเป็นระยะเวลานานกี่มิลลิวินาที)''
 
#: ''(หมายเหตุ: ฟังก์ชัน millis() เป็นฟังก์ชันที่เฟรมเวิร์ก Arduino มีให้ใช้สำหรับตรวจสอบว่าไมโครคอนโทรลเลอร์ได้ทำงานมาเป็นระยะเวลานานกี่มิลลิวินาที)''
# ตรวจสอบเวลาที่อยู่ในสถานะนี้โดยคำนวณค่า <code>millis()-ts</code> เกินค่า 1000 มิลลิวินาที
+
# ตรวจสอบเวลาที่อยู่ในสถานะนี้โดยพิจารณาว่าค่า <tt>millis()-ts</tt> เกินค่า 1000 มิลลิวินาทีแล้วหรือไม่
 
#* หากยังไม่เกินให้อยู่ในสถานะเดิม
 
#* หากยังไม่เกินให้อยู่ในสถานะเดิม
#* หากเกินแล้ว ให้เข้าสู่สถานะ OFF โดยบันทึกค่า <code>ts</code> เป็นเวลาที่เข้าสู่สถานะใหม่ใน และดับ LED สีเขียว
+
#* หากเกินแล้ว ให้เข้าสู่สถานะ OFF โดยบันทึกค่า <tt>ts</tt> เป็นเวลาที่เข้าสู่สถานะใหม่ใน และดับ LED สีเขียว
 
# ดำเนินการในลักษณะเดียวกันเมื่ออยู่ในสถานะใหม่
 
# ดำเนินการในลักษณะเดียวกันเมื่ออยู่ในสถานะใหม่
  
== โค้ดเครื่องจักรสถานะ สำหรับ LED สีเขียว ==
+
=== โค้ดเครื่องจักรสถานะ สำหรับ LED สีเขียว ===
 
เรานำเครื่องจักรสถานะที่ออกแบบไว้ข้างต้นมาเขียนเป็นโค้ดภาษาซี/Arduino ได้ดังนี้
 
เรานำเครื่องจักรสถานะที่ออกแบบไว้ข้างต้นมาเขียนเป็นโค้ดภาษาซี/Arduino ได้ดังนี้
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
#include <Practicum.h>
+
#include <avr/io.h>
 +
#include <avr/interrupt.h>
  
enum State { ON, OFF };
+
#include "peri.h"
 
+
#include "timer.h"
State taskGreen_state;
+
 +
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 (millis() - ts >= 1000)
+
     if (timer_millis() - ts >= 1000)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, LOW);
+
       set_led(LED_GREEN,0);
 
       taskGreen_state = OFF;
 
       taskGreen_state = OFF;
 
     }
 
     }
 
   }
 
   }
 
+
   if (taskGreen_state == OFF)
+
   else if (taskGreen_state == OFF)
 
   {
 
   {
     if (millis() - ts >= 500)
+
     if (timer_millis() - ts >= 500)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, HIGH);
+
       set_led(LED_GREEN,1);
 
       taskGreen_state = ON;
 
       taskGreen_state = ON;
 
     }
 
     }
 
   }
 
   }
 
}
 
}
 
+
 
//////////////////////////////////////////////////
 
//////////////////////////////////////////////////
void setup()
+
int main()
 
{
 
{
   // ตั้งค่าอินพุท/เอาท์พุทของขาให้เหมาะสม
+
   init_peripheral();
  pinMode(PIN_PC0, OUTPUT);
+
   timer_init();
  pinMode(PIN_PC1, OUTPUT);
+
   sei();
  pinMode(PIN_PC2, OUTPUT);
+
  pinMode(PIN_PC3, INPUT_PULLUP);
 
   pinMode(PIN_PC4, INPUT);
 
   pinMode(PIN_PD3, OUTPUT);
 
 
 
 
   // กำหนดสถานะเริ่มต้น
 
   // กำหนดสถานะเริ่มต้น
 
   taskGreen_state = ON;
 
   taskGreen_state = ON;
   digitalWrite(PIN_PC2, HIGH);
+
   set_led(LED_GREEN,1);
}
+
 
+
  for (;;)
//////////////////////////////////////////////////
+
  {
void loop()
+
    taskGreen();
{
+
  }
  taskGreen();
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
โปรแกรมข้างต้นสามารถคอมไพล์และรันได้จริง แต่จะมีเพียงงานของการกระพริบ LED สีเขียวเท่านั้น อย่างไรก็ตามโปรแกรมนี้ถูกเขียนในรูปแบบแตกต่างจากโปรแกรมไฟกระพริบที่ผ่าน ๆ มา และมีจุดที่น่าสังเกตหลายจุดดังนี้
 
โปรแกรมข้างต้นสามารถคอมไพล์และรันได้จริง แต่จะมีเพียงงานของการกระพริบ LED สีเขียวเท่านั้น อย่างไรก็ตามโปรแกรมนี้ถูกเขียนในรูปแบบแตกต่างจากโปรแกรมไฟกระพริบที่ผ่าน ๆ มา และมีจุดที่น่าสังเกตหลายจุดดังนี้
* ฟังก์ชัน <code>taskGreen</code> นั้นทำงานตามที่ได้ออกแบบไว้ในเครื่องจักรสถานะทุกประการ ซึ่งจะเห็นว่าไม่มีการใช้งานคำสั่ง delay จึงทำให้ทำงานเสร็จแทบจะทันทีที่ถูกเรียกในแต่ละครั้ง อันเป็นพฤติกรรมที่เราต้องการในการทำงานแบบมัลติทาสกิ้ง
+
* ฟังก์ชัน <tt>taskGreen</tt> นั้นทำงานตามที่ได้ออกแบบไว้ในเครื่องจักรสถานะทุกประการ ซึ่งจะเห็นว่าไม่มีการใช้งานคำสั่ง delay จึงทำให้ทำงานเสร็จแทบจะทันทีที่ถูกเรียกในแต่ละครั้ง อันเป็นพฤติกรรมที่เราต้องการในการทำงานแบบมัลติทาสกิ้ง
* คำสั่ง enum เป็นการกำหนดชนิดข้อมูลแบบใหม่ชื่อ <code>State</code> เพื่อเอาไว้สร้างตัวแปรเก็บสถานะปัจจุบันของเครื่องจักร โดยมีค่าที่เป็นไปได้คือ ON และ OFF
+
* คำสั่ง enum เป็นการกำหนดค่าจำนวนเต็มที่ไม่ซ้ำกันให้กับค่าคงที่ในวงเล็บปีกกา ในที่นี้เราใช้กำหนดค่าคงที่ให้กับ ON และ OFF ซึ่งเอาไว้ใช้ในการกำหนดสถานะปัจจุบันให้กับเครื่องจักรสถานะ
* ตัวแปร <code>ts</code> ถูกประกาศให้เป็นตัวแปรแบบโลคัล แต่ต้องมีการคงค่าเดิมไว้แม้การทำงานจะออกจากฟังก์ชัน <code>taskGreen()</code> ไปแล้ว จึงต้องมีการระบุคีย์เวิร์ด static เอาไว้
+
* ตัวแปร <tt>ts</tt> ถูกประกาศให้เป็นตัวแปรแบบโลคัล แต่ต้องมีการคงค่าเดิมไว้แม้การทำงานจะออกจากฟังก์ชัน <tt>taskGreen()</tt> ไปแล้ว จึงต้องมีการระบุคีย์เวิร์ด static เอาไว้
  
== โค้ดที่สมบูรณ์: LED สองสีกระพริบอิสระ ==
+
=== โค้ดที่สมบูรณ์: LED สองสีกระพริบอิสระ ===
 
โค้ดข้างต้นนำมาเพิ่มเครื่องจักรสถานะสำหรับ LED สีแดงเข้าไปได้อย่างตรงไปตรงมา ทำให้ได้โค้ดที่สมบูรณ์ดังนี้
 
โค้ดข้างต้นนำมาเพิ่มเครื่องจักรสถานะสำหรับ LED สีแดงเข้าไปได้อย่างตรงไปตรงมา ทำให้ได้โค้ดที่สมบูรณ์ดังนี้
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
#include <Practicum.h>
+
#include <avr/io.h>
 +
#include <avr/interrupt.h>
  
enum State { ON, OFF };
+
#include "peri.h"
 
+
#include "timer.h"
State taskGreen_state;
+
State taskRed_state;
+
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 (millis() - ts >= 1000)
+
     if (timer_millis() - ts >= 1000)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, LOW);
+
       set_led(LED_GREEN,0);
 
       taskGreen_state = OFF;
 
       taskGreen_state = OFF;
 
     }
 
     }
 
   }
 
   }
 
+
   if (taskGreen_state == OFF)
+
   else if (taskGreen_state == OFF)
 
   {
 
   {
     if (millis() - ts >= 500)
+
     if (timer_millis() - ts >= 500)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, HIGH);
+
       set_led(LED_GREEN,1);
 
       taskGreen_state = ON;
 
       taskGreen_state = ON;
 
     }
 
     }
 
   }
 
   }
 
}
 
}
 
+
 
////////////////////////////////////
 
////////////////////////////////////
 
// เครื่องจักรสถานะสำหรับ LED สีแดง
 
// เครื่องจักรสถานะสำหรับ LED สีแดง
แถว 185: แถว 202:
 
   if (taskRed_state == ON)
 
   if (taskRed_state == ON)
 
   {
 
   {
     if (millis() - ts >= 700)
+
     if (timer_millis() - ts >= 700)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC0, LOW);
+
       set_led(LED_RED,0);
 
       taskRed_state = OFF;
 
       taskRed_state = OFF;
 
     }
 
     }
 
   }
 
   }
  
   if (taskRed_state == OFF)
+
   else if (taskRed_state == OFF)
 
   {
 
   {
     if (millis() - ts >= 300)
+
     if (timer_millis() - ts >= 300)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC0, HIGH);
+
       set_led(LED_RED,1);
 
       taskRed_state = ON;
 
       taskRed_state = ON;
 
     }
 
     }
แถว 204: แถว 221:
 
}
 
}
  
//////////////////////////////////////////////
+
//////////////////////////////////////////////////
void setup()
+
int main()
 
{
 
{
   // ตั้งค่าอินพุท/เอาท์พุทของขาให้เหมาะสม
+
   init_peripheral();
  pinMode(PIN_PC0, OUTPUT);
+
   timer_init();
  pinMode(PIN_PC1, OUTPUT);
+
   sei();
  pinMode(PIN_PC2, OUTPUT);
+
  pinMode(PIN_PC3, INPUT_PULLUP);
+
   // กำหนดสถานะเริ่มต้น
   pinMode(PIN_PC4, INPUT);
 
   pinMode(PIN_PD3, OUTPUT);
 
 
 
   // กำหนดสถานะเริ่มต้นให้กับเครื่องจักรสถานะทั้งคู่
 
 
   taskGreen_state = ON;
 
   taskGreen_state = ON;
   digitalWrite(PIN_PC2, HIGH);
+
   set_led(LED_GREEN,1);
 
   taskRed_state = ON;
 
   taskRed_state = ON;
   digitalWrite(PIN_PC0, HIGH);
+
   set_led(LED_RED,1);
}
+
 
+
  for (;;)
//////////////////////////////////////////////
+
  {
void loop()
+
    taskGreen();
{
+
    taskRed();
  taskGreen();
+
  }
  taskRed();
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
= การอิมพลิเมนต์เครื่องจักรสถานะด้วยคำสั่ง <code>goto</code> =
+
== การอิมพลิเมนต์เครื่องจักรสถานะด้วยคำสั่ง <tt>goto</tt> ==
 
โค้ดที่เขียนขึ้นจากเครื่องจักรสถานะนั้นจะดูตรงไปตรงมาก็ต่อเมื่อมีผังภาพเครื่องจักรสถานะดังเช่นรูปข้างต้นมาพิจารณาประกอบ แต่ถ้าหากดูจากตัวโค้ดเพียงอย่างเดียวนั้นแทบจะไม่สามารถถอดความได้ทันทีว่าผลลัพธ์จะเป็นอย่างไร เห็นได้จากตารางเปรียบเทียบโค้ดเดิมที่ทำงานแบบซิงเกิลทาสกิ้ง (ซ้ายมือ) และโค้ดที่เขียนตามเครื่องจักรสถานะเพื่อรองรับการทำงานแบบมัสติทาสกิ้ง (ขวามือ)
 
โค้ดที่เขียนขึ้นจากเครื่องจักรสถานะนั้นจะดูตรงไปตรงมาก็ต่อเมื่อมีผังภาพเครื่องจักรสถานะดังเช่นรูปข้างต้นมาพิจารณาประกอบ แต่ถ้าหากดูจากตัวโค้ดเพียงอย่างเดียวนั้นแทบจะไม่สามารถถอดความได้ทันทีว่าผลลัพธ์จะเป็นอย่างไร เห็นได้จากตารางเปรียบเทียบโค้ดเดิมที่ทำงานแบบซิงเกิลทาสกิ้ง (ซ้ายมือ) และโค้ดที่เขียนตามเครื่องจักรสถานะเพื่อรองรับการทำงานแบบมัสติทาสกิ้ง (ขวามือ)
  
แถว 241: แถว 253:
 
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,1);
     delay(500);
+
     _delay_ms(500);
 
   }
 
   }
 
}
 
}
แถว 258: แถว 270:
 
   if (taskGreen_state == ON)
 
   if (taskGreen_state == ON)
 
   {
 
   {
     if (millis() - ts >= 1000)
+
     if (timer_millis() - ts >= 1000)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, LOW);
+
       set_led(LED_GREEN,0);
 
       taskGreen_state = OFF;
 
       taskGreen_state = OFF;
 
     }
 
     }
 
   }
 
   }
  
   if (taskGreen_state == OFF)
+
   else if (taskGreen_state == OFF)
 
   {
 
   {
     if (millis() - ts >= 500)
+
     if (timer_millis() - ts >= 500)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, HIGH);
+
       set_led(LED_GREEN,1);
 
       taskGreen_state = ON;
 
       taskGreen_state = ON;
 
     }
 
     }
แถว 280: แถว 292:
 
|}
 
|}
  
ความต้องการของการเขียนโค้ดตามแบบด้านขวามือนั้นคือการทำให้ <code>taskGreen</code> ประมวลผลและออกจากฟังก์ชันให้เร็วที่สุดในการเรียกแต่ละครั้ง โดยจดจำสถานะปัจจุบันของตนเอาไว้เพื่อดำเนินการต่อในการถูกเรียกประมวลผลครั้งถัดไป โค้ดที่ได้จึงมีลักษณะเป็นคำสั่ง if หลาย ๆ คำสั่งกระจายอยู่ทั่วไปในฟังก์ชัน และบดบังลำดับการทำงานที่ต้องการจนแทบไม่เหลือเค้าเดิม ส่วนโค้ดทางด้านซ้ายนั้นแม้จะสื่อให้เห็นถึงพฤติกรรมการทำงานได้อย่างชัดเจน แต่การวนลูปแบบไม่รู้จบและหยุดรอของคำสั่ง delay นั้นเป็นสิ่งที่ยอมรับไม่ได้ในการทำงานแบบมัลติทาสกิ้ง
+
ความต้องการของการเขียนโค้ดตามแบบด้านขวามือนั้นคือการทำให้ <tt>taskGreen</tt> ประมวลผลและออกจากฟังก์ชันให้เร็วที่สุดในการเรียกแต่ละครั้ง โดยจดจำสถานะปัจจุบันของตนเอาไว้เพื่อดำเนินการต่อในการถูกเรียกประมวลผลครั้งถัดไป โค้ดที่ได้จึงมีลักษณะเป็นคำสั่ง if หลาย ๆ คำสั่งกระจายอยู่ทั่วไปในฟังก์ชัน และบดบังลำดับการทำงานที่ต้องการจนแทบไม่เหลือเค้าเดิม ส่วนโค้ดทางด้านซ้ายนั้นแม้จะสื่อให้เห็นถึงพฤติกรรมการทำงานได้อย่างชัดเจน แต่การวนลูปแบบไม่รู้จบและหยุดรอของคำสั่ง delay นั้นเป็นสิ่งที่ยอมรับไม่ได้ในการทำงานแบบมัลติทาสกิ้ง
  
ทางเลือกที่ใช้ข้อดีจากทั้งสองฝ่ายคือการหากลไกที่ทดแทนคำสั่ง delay โดยให้โปรแกรมทำงานต่อในบรรทัดถัดไปเมื่อเวลาผ่านไปครบถ้วนแล้ว และบังคับให้ออกจากฟังก์ชันทันทีหากยังไม่ถึงเวลาหน่วงที่กำหนดแต่ให้บันทึกตำแหน่งปัจจุบันเอาไว้ เพื่อที่ว่าการเรียกฟังก์ชันครั้งถัดไปจะได้กระโดดมาทำงาน ณ จุดที่ออกจากฟังก์ชันไปต่อได้ แนวคิดนี้ทำได้โดยอาศัยคำสั่ง <code>goto</code> ซึ่งมีให้อยู่แล้วในภาษาซี
+
ทางเลือกที่ใช้ข้อดีจากทั้งสองฝ่ายคือการหากลไกที่ทดแทนคำสั่ง delay โดยให้โปรแกรมทำงานต่อในบรรทัดถัดไปเมื่อเวลาผ่านไปครบถ้วนแล้ว และบังคับให้ออกจากฟังก์ชันทันทีหากยังไม่ถึงเวลาหน่วงที่กำหนดแต่ให้บันทึกตำแหน่งปัจจุบันเอาไว้ เพื่อที่ว่าการเรียกฟังก์ชันครั้งถัดไปจะได้กระโดดมาทำงาน ณ จุดที่ออกจากฟังก์ชันไปต่อได้ แนวคิดนี้ทำได้โดยอาศัยคำสั่ง <tt>goto</tt> ซึ่งมีให้อยู่แล้วในภาษาซี
  
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
#include <Practicum.h>
+
#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;
  
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
  
     // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ delay(1000) โดยไม่หยุดรอ
+
     // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ _delay_ms(1000) โดยไม่หยุดรอ
     ts = millis();
+
     ts = timer_millis();
 
ON: taskGreen_state = &&ON;  // จดจำบรรทัดปัจจุบัน
 
ON: taskGreen_state = &&ON;  // จดจำบรรทัดปัจจุบัน
     if (millis()-ts < 1000) return;
+
     if (timer_millis()-ts < 1000) return;
  
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
  
     // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ delay(500) โดยไม่หยุดรอ
+
     // สามบรรทัดนี้ทำงานเทียบเท่ากับการใช้ _delay_ms(500) โดยไม่หยุดรอ
     ts = millis();
+
     ts = timer_millis();
 
OFF:taskGreen_state = &&OFF; // จดจำบรรทัดปัจจุบัน
 
OFF:taskGreen_state = &&OFF; // จดจำบรรทัดปัจจุบัน
     if (millis()-ts < 500) return;
+
     if (timer_millis()-ts < 500) return;
 
   }
 
   }
 
}
 
}
  
 
//////////////////////////////////////////////////
 
//////////////////////////////////////////////////
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);
 
 
 
 
   taskGreen_state = NULL;
 
   taskGreen_state = NULL;
}
 
  
//////////////////////////////////////////////////
+
  for (;;)
void loop()
+
  {
{
+
    taskGreen();
  taskGreen();
+
  }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
แถว 339: แถว 351:
 
;หมายเหตุ
 
;หมายเหตุ
 
* โค้ดด้านบนคอมไพล์และรันได้จริง
 
* โค้ดด้านบนคอมไพล์และรันได้จริง
* การใช้คำสั่ง <code>goto</code> ในรูปแบบด้านบนเรียกว่าการทำ [http://en.wikipedia.org/wiki/Goto#Computed_GOTO_and_Assigned_GOTO computed goto] ซึ่งไม่จัดอยู่ในภาษาซีมาตรฐาน แต่รองรับโดยคอมไพเลอร์ส่วนใหญ่เช่น GCC คำสั่งในภาษาซีมาตรฐานที่มีพฤติกรรมใกล้เคียง computed goto มากที่สุดคือคำสั่ง switch..case
+
* การใช้คำสั่ง <tt>goto</tt> ในรูปแบบด้านบนเรียกว่าการทำ [http://en.wikipedia.org/wiki/Goto#Computed_GOTO_and_Assigned_GOTO computed goto] ซึ่งไม่จัดอยู่ในภาษาซีมาตรฐาน แต่รองรับโดยคอมไพเลอร์ส่วนใหญ่เช่น GCC คำสั่งในภาษาซีมาตรฐานที่มีพฤติกรรมใกล้เคียง computed goto มากที่สุดคือคำสั่ง switch..case
* แม้ฟังก์ชัน <code>taskGreen</code> จะมีลูปอนันต์อยู่ จะมีการออกจากฟังก์ชันเสมอด้วยคำสั่ง <code>return</code> จึงทำให้ทำงานร่วมกับงานอื่นแบบมัลติทาสกิ้งได้
+
* แม้ฟังก์ชัน <tt>taskGreen</tt> จะมีลูปอนันต์อยู่ จะมีการออกจากฟังก์ชันเสมอด้วยคำสั่ง <tt>return</tt> จึงทำให้ทำงานร่วมกับงานอื่นแบบมัลติทาสกิ้งได้
* เป็นที่ถกเถียงกันเรื่อง[http://en.wikipedia.org/wiki/Considered_harmful ความเลวร้ายของการใช้คำสั่ง goto] แต่การใช้คำสั่งนี้ในสถานการณ์ที่เหมาะสมจะทำให้โปรแกรมสั้นและทำความเข้าใจได้ง่ายขึ้น
+
* เป็นที่ถกเถียงกันเรื่อง[http://en.wikipedia.org/wiki/Considered_harmful ความเลวร้ายของการใช้คำสั่ง goto] แต่การใช้คำสั่ง goto ในสถานการณ์ที่เหมาะสมจะทำให้โปรแกรมกระชับและทำความเข้าใจได้ง่ายขึ้น
  
ฟังก์ชัน <code>taskGreen</code> ที่เขียนใหม่นั้นยังสามารถถูกมองในรูปของเครื่องจักรสถานะได้ เพียงแต่สถานะต่าง ๆ นั้นถูกฝังลงไปในจุดต่าง ๆ ของโค้ด ณ ตำแหน่งที่มีการกำกับด้วย <code>ON:</code> และ <code>OFF:</code> ลองเปรียบเทียบโค้ดที่ปรับแก้แล้วกับโค้ดดั้งเดิมและโค้ดที่เขียนขึ้นจากเครื่องจักรสถานะตรง ๆ
+
ฟังก์ชัน <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;
  
   while (1)
+
   for (;;)
 
   {
 
   {
     digitalWrite(PIN_PC2, HIGH);
+
     set_led(LED_GREEN,1);
  
     // เทียบเท่า delay(1000)
+
     // เทียบเท่า _delay_ms(1000)
     ts = millis();
+
     ts = timer_millis();
 
ON: taskGreen_state = &&ON;
 
ON: taskGreen_state = &&ON;
     if (millis()-ts < 1000) return;
+
     if (timer_millis()-ts < 1000) return;
  
     digitalWrite(PIN_PC2, LOW);
+
     set_led(LED_GREEN,0);
  
     // เทียบเท่า delay(500)
+
     // เทียบเท่า _delay_ms(500)
     ts = millis();
+
     ts = timer_millis();
 
OFF:taskGreen_state = &&OFF;
 
OFF:taskGreen_state = &&OFF;
     if (millis()-ts < 500) return;
+
     if (timer_millis()-ts < 500) return;
 
   }
 
   }
 
</syntaxhighlight>
 
</syntaxhighlight>
แถว 380: แถว 392:
 
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);
 
   }
 
   }
 
}
 
}
แถว 397: แถว 409:
 
   if (taskGreen_state == ON)
 
   if (taskGreen_state == ON)
 
   {
 
   {
     if (millis() - ts >= 1000)
+
     if (timer_millis() - ts >= 1000)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, LOW);
+
       set_led(LED_GREEN,0);
 
       taskGreen_state = OFF;
 
       taskGreen_state = OFF;
 
     }
 
     }
 
   }
 
   }
  
   if (taskGreen_state == OFF)
+
   else if (taskGreen_state == OFF)
 
   {
 
   {
     if (millis() - ts >= 500)
+
     if (timer_millis() - ts >= 500)
 
     {
 
     {
       ts = millis();
+
       ts = timer_millis();
       digitalWrite(PIN_PC2, HIGH);
+
       set_led(LED_GREEN,1);
 
       taskGreen_state = ON;
 
       taskGreen_state = ON;
 
     }
 
     }
แถว 419: แถว 431:
 
|}
 
|}
  
จะเห็นว่าโค้ดรูปแบบใหม่ทางด้านซ้ายมือแม้ว่าจะยาวกว่าเดิมก็ตาม แต่ก็มีลักษณะที่คล้ายคลึงกับโค้ดดั้งเดิมในช่องกลางมากขึ้น โดยเฉพาะอย่างยิ่งถ้ามีการรวบ 3 คำสั่งที่คอมเม้นต์ไว้ว่าทำงานเทียบเท่าคำสั่ง delay เอาไว้ให้เป็นคำสั่งเดียวได้จะได้โค้ดภายในลูป while ที่เหมือนกันเกือบทุกประการกับโค้ดเดิม จึงเหลือเพียงการทำให้โค้ดดูง่ายขึ้นโดยการรวมคำสั่งหลาย ๆ คำสั่งให้เสมือนเป็นคำสั่งเดียวโดยใช้[http://en.wikipedia.org/wiki/C_preprocessor#Macro_definition_and_expansion ระบบมาโครของภาษาซี] ทั้งหมดนี้มีโปรแกรมเมอร์หลายคนได้พัฒนาเอาไว้พร้อมใช้งานในรูปไลบรารีเรียบร้อยแล้ว หนึ่งในนั้นคือไลบรารี Protothreads
+
จะเห็นว่าโค้ดรูปแบบใหม่ทางด้านซ้ายมือแม้ว่าจะยาวกว่าเดิมก็ตาม แต่ก็มีลักษณะที่คล้ายคลึงกับโค้ดดั้งเดิมในช่องกลางมากขึ้น โดยเฉพาะอย่างยิ่งถ้ามีการรวบ 3 คำสั่งที่คอมเม้นต์ไว้ว่าทำงานเทียบเท่าคำสั่ง delay เอาไว้ให้เป็นคำสั่งเดียวได้จะได้โค้ดภายในลูป for ที่เหมือนกันเกือบทุกประการกับโค้ดเดิม จึงเหลือเพียงการทำให้โค้ดดูง่ายขึ้นโดยการรวมคำสั่งหลาย ๆ คำสั่งให้เสมือนเป็นคำสั่งเดียวโดยใช้[http://en.wikipedia.org/wiki/C_preprocessor#Macro_definition_and_expansion ระบบมาโครของภาษาซี] ทั้งหมดนี้มีโปรแกรมเมอร์หลายคนได้พัฒนาเอาไว้พร้อมใช้งานในรูปไลบรารีเรียบร้อยแล้ว หนึ่งในนั้นคือไลบรารี Protothreads ดูรายละเอียดการใช้งานไลบรารี Protothreads ได้จากวิกิ [[มัลติทาสกิ้งด้วยไลบรารี Protothreads]]
 
 
= ไลบรารี Protothreads =
 
[http://dunkels.com/adam/pt/ Protothreads] พัฒนาขึ้นโดย [http://dunkels.com/adam/ Adam Dunkels] โดยมีวัตถุประสงค์เพื่อให้นักพัฒนาโค้ดสามารถเขียนโปรแกรมแบบมัลติทาสกิ้งในรูปแบบที่เข้าใจได้ง่ายบนอุปกรณ์ที่มีทรัพยากรจำกัดอย่างเช่นไมโครคอนโทรลเลอร์ ไลบรารีนี้นำเอาเทคนิคการบันทึกสถานะร่วมกับการใช้คำสั่ง goto ตามที่ได้กล่าวไปแล้วมารวมไว้เป็นชุดมาโครในภาษาซี ทำให้รูปแบบโค้ดของงานย่อยแต่ละงานมีลักษณะราวกับการทำงานแบบซิงเกิลทาสก์
 
 
 
== การติดตั้งไลบรารี Protothreads เพื่อใช้งานร่วมกับ Arduino ==
 
ดาวน์โหลดไฟล์ [http://www.cpe.ku.ac.th/~cpj/204223/pt.tgz] แล้วแตกเอาไว้ในไดเรคตอรี ~/sketchbook/libraries/
 
 
 
$ cd ~/sketchbook/libraries/
 
$ wget http://www.cpe.ku.ac.th/~cpj/204223/pt.tgz
 
$ tar zxf pt.tgz
 
 
 
== ตัวอย่างโปรแกรม ==
 
โปรแกรมต่อไปนี้เขียนขึ้นโดยอาศัยไลบรารี Protothreads ซึ่งให้พฤติกรรมการทำงานที่เหมือนกับตัวอย่างในหัวข้อที่แล้วทุกประการ
 
 
 
<syntaxhighlight lang="C">
 
#include <Practicum.h>
 
#include <pt.h>
 
 
 
#define PT_DELAY(pt, ms, ts) \
 
  { \
 
    ts = millis(); \
 
    PT_WAIT_WHILE(pt, millis()-ts < (ms)); \
 
  }
 
 
 
struct pt pt_taskRed;
 
struct pt pt_taskGreen;
 
 
 
///////////////////////////////////////////////////////
 
PT_THREAD(taskGreen(struct pt* pt))
 
{
 
  static uint32_t ts;
 
 
 
  PT_BEGIN(pt);
 
 
 
  while (1)
 
  {
 
    digitalWrite(PIN_PC2, HIGH);
 
    PT_DELAY(pt, 1000, ts);
 
    digitalWrite(PIN_PC2, LOW);
 
    PT_DELAY(pt, 500, ts);
 
  }
 
 
 
  PT_END(pt);
 
}
 
 
 
///////////////////////////////////////////////////////
 
PT_THREAD(taskRed(struct pt* pt))
 
{
 
  static uint32_t ts;
 
 
 
  PT_BEGIN(pt);
 
 
 
  while (1)
 
  {
 
    digitalWrite(PIN_PC0, HIGH);
 
    PT_DELAY(pt, 700, ts);
 
    digitalWrite(PIN_PC0, LOW);
 
    PT_DELAY(pt, 300, ts);
 
  }
 
 
 
  PT_END(pt);
 
}
 
 
 
///////////////////////////////////////////////////////
 
void setup()
 
{
 
  pinMode(PIN_PC0, OUTPUT);
 
  pinMode(PIN_PC1, OUTPUT);
 
  pinMode(PIN_PC2, OUTPUT);
 
  pinMode(PIN_PC3, INPUT_PULLUP);
 
  pinMode(PIN_PC4, INPUT);
 
  pinMode(PIN_PD3, OUTPUT);
 
 
 
  PT_INIT(&pt_taskGreen);
 
  PT_INIT(&pt_taskRed);
 
}
 
 
 
///////////////////////////////////////////////////////
 
void loop()
 
{
 
  taskGreen(&pt_taskGreen);
 
  taskRed(&pt_taskRed);
 
}
 
</syntaxhighlight>
 
 
 
สิ่งที่น่าสนใจคืองานย่อยทั้งสองงาน (taskGreen และ taskRed) มีโค้ดที่เกือบจะเหมือนกับโค้ดดั้งเดิมที่เขียนสำหรับงานเดี่ยว
 
{| border="0" cellpadding="5" cellspacing="0" align="center"
 
! โค้ดดั้งเดิม (แบบใช้ delay)
 
! โค้ดที่อิมพลิเมนต์ตามเครื่องจักรสถานะ
 
|- valign="top"
 
|
 
<syntaxhighlight lang="C" enclose="div">
 
void taskGreen()
 
{
 
  while (1)
 
  {
 
    digitalWrite(PIN_PC2, HIGH);
 
    delay(1000);
 
    digitalWrite(PIN_PC2, LOW);
 
    delay(500);
 
  }
 
}
 
</syntaxhighlight>
 
|
 
<syntaxhighlight lang="C" enclose="div">
 
PT_THREAD(taskGreen(struct pt* pt))
 
{
 
  static uint32_t ts;
 
 
 
  PT_BEGIN(pt);
 
 
 
  while (1)
 
  {
 
    digitalWrite(PIN_PC2, HIGH);
 
    PT_DELAY(pt, 1000, ts);
 
    digitalWrite(PIN_PC2, LOW);
 
    PT_DELAY(pt, 500, ts);
 
  }
 
 
 
  PT_END(pt);
 
}
 
</syntaxhighlight>
 
|-
 
|}
 
 
 
 
 
== การทำงานของไลบรารี Protothreads ==
 
 
 
รายละเอียดคร่าว ๆ ของโปรแกรมและมาโครต่าง ๆ ในไลบรารี Protothreads ที่นำมาใช้มีดังนี้
 
* <code>struct pt</code> ถูกนิยามไว้ในไฟล์เฮดเดอร์ <code>pt.h</code> ซึ่งภายในมีตัวแปรสมาชิกเพียงตัวเดียวที่เอาไว้เก็บสถานะปัจจุบันของงาน
 
<syntaxhighlight lang="C">
 
// นิยามโดยย่อของ struct pt
 
struct pt
 
{
 
  void* state;
 
};
 
</syntaxhighlight>
 
 
 
* มาโคร <code>PT_THREAD(task(struct pt* pt))</code> ใช้สำหรับครอบการประกาศฟังก์ชันที่จะให้ทำหน้าที่เป็น protothread (เสมือนเป็นเครื่องจักรสถานะตัวหนึ่ง) ซึ่งมีผลเทียบเท่ากับการประกาศฟังก์ชันด้วยคำสั่ง
 
<syntaxhighlight lang="C">
 
char task(struct pt* pt)
 
</syntaxhighlight>
 
: ซึ่งไม่ต่างจากการประกาศฟังก์ชันทั่วไป แต่การประกาศฟังก์ชันผ่านมาโครนี้จะช่วยย้ำผู้เขียนโปรแกรมว่าฟังก์ชันนี้ทำหน้าที่เป็น protothread
 
 
 
* มาโคร <code>PT_BEGIN(pt)</code> ถูกนิยามไว้เป็นโค้ดที่เรียกใช้คำสั่ง <code>goto</code> ตามสถานะที่เก็บไว้ในตัวแปร <code>pt</code> ซึ่งเทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
if (pt->state != NULL) goto *(pt->state);
 
</syntaxhighlight>
 
: มาโครนี้จึงต้องถูกเรียกเป็นคำสั่งแรกเสมอในฟังก์ชันที่จะทำหน้าที่เป็นเครื่องจักรสถานะ
 
 
 
* มาโคร <code>PT_END(pt)</code> ระบุจุดสิ้นสุดของ protothread โดยทำงานเทียบเท่ากับคำสั่ง return
 
<syntaxhighlight lang="C">
 
return 3;
 
</syntaxhighlight>
 
: ค่า 3 ถูกใช้ในไลบรารี Protothreads เป็นการภายในเพื่อระบุว่า protothread นี้จบการทำงานโดยสมบูรณ์
 
 
 
* มาโคร <code>PT_INIT(pt)</code> ทำหน้าที่กำหนดสถานะเริ่มต้นให้กับตัวแปร <code>pt</code> ซึ่งมีผลเทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
pt->state = NULL;
 
</syntaxhighlight>
 
: เมื่อพิจารณาคู่กับมาโคร <code>PT_BEGIN</code> แล้วจึงมีความหมายว่าให้เริ่มต้นทำงานตั้งแต่ต้นฟังก์ชัน
 
 
 
* มาโคร <code>PT_WAIT_WHILE(pt, cond)</code> ถูกนิยามไว้เป็นการตรวจสอบเงื่อนไข <code>cond</code> ว่ายังเป็นจริงอยู่หรือไม่ หากเป็นจริงจะบันทึกสถานะบรรทัดปัจจุบันไว้ในตัวแปร <code>pt</code> ก่อนที่จะ return ออกจากฟังก์ชัน ซึ่งเทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
XX__LINE__: pt->state = &&XX__LINE__;  // __LINE__ ถูกคอมไพเลอร์แทนที่ด้วยหมายเลขบรรทัดปัจจุบัน
 
if (cond) return 0;
 
</syntaxhighlight>
 
 
 
* มาโคร <code>PT_DELAY(pt, ms, ts)</code> ไม่ได้อยู่ในไลบรารี Protothreads แต่สร้างขึ้นเพื่อความสะดวกในการจำลองการทำงานของคำสั่ง delay ในแบบที่ไม่หยุดรอ นิยามไว้ให้เทียบเท่ากับการใช้คำสั่ง
 
<syntaxhighlight lang="C">
 
ts = millis();
 
PT_WAIT_WHILE(pt, millis()-ts < (ms));
 
</syntaxhighlight>
 

รุ่นแก้ไขปัจจุบันเมื่อ 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 สีเขียว

ผังภาพเครื่องจักรสถานะของการทำไฟสีเขียวติด 1 วินาที ดับครึ่งวินาที

กลไกการทำงานของงาน (หรือเครื่องจักร) ข้างต้นเป็นดังนี้

  1. เริ่มทำงานโดยเข้าสู่สถานะ ON ซึ่งมีการสั่งให้ LED สีเขียวติด และบันทึกเวลาปัจจุบันเป็นมิลลิวินาทีจากฟังก์ชัน millis() ไว้ในตัวแปร ts (ย่อมาจาก timestamp)
    (หมายเหตุ: ฟังก์ชัน millis() เป็นฟังก์ชันที่เฟรมเวิร์ก Arduino มีให้ใช้สำหรับตรวจสอบว่าไมโครคอนโทรลเลอร์ได้ทำงานมาเป็นระยะเวลานานกี่มิลลิวินาที)
  2. ตรวจสอบเวลาที่อยู่ในสถานะนี้โดยพิจารณาว่าค่า millis()-ts เกินค่า 1000 มิลลิวินาทีแล้วหรือไม่
    • หากยังไม่เกินให้อยู่ในสถานะเดิม
    • หากเกินแล้ว ให้เข้าสู่สถานะ OFF โดยบันทึกค่า ts เป็นเวลาที่เข้าสู่สถานะใหม่ใน และดับ LED สีเขียว
  3. ดำเนินการในลักษณะเดียวกันเมื่ออยู่ในสถานะใหม่

โค้ดเครื่องจักรสถานะ สำหรับ 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