สำหรับน้องๆ เรื่องนี้อาจจะยังไหม่และไม่ใช่เรื่องพื้นฐานแน่ๆ ถ้ายังมือใหม่แนะนำขอให้ข้ามหัวข้อนี้ไปก่อนครับ หากสนใจก็ยินดีครับ
ข้อดีของการใช้ Co-operative multitasking ด้วย state machine คือใช้กับ cpu/mcu ขนาดเล็ก ที่มี resource น้อย ข้อจำกัดมากได้ดี ทำงานซับซ้อนเกือบเทียบเท่าการใช้ RTOS เลย
ในการเขียนโปรแกรมแบบ Co-operative multitasking ในเรื่องหนึ่งที่สำคัญคือการใช้ state machine เพื่อใช้การทำการ code
แต่เนื่องด้วย code ที่เป็นพื้นฐานด้านการเขียนในแนว state machine ค่อนข้างจะน้อย หนังสือก็น้อย อาจเป็นเพราะเข้าใจยากและไม่ใช่เรื่องพื้นฐานนัก จึงอยากจะขอแนะนำ code ที่เป็น state machine แบบเบื้องต้น ทำให้ดูง่าย(เท่าที่จะทำได้) และหลายๆท่านในนี้ อาจจะชำนาญแล้วด้วยซ้ำ แต่เห็นว่าน่าจะยังมีอีกส่วนหนึ่งที่ยังไม่มีโอกาศได้รู้จึงนำมาเสนอกัน และผมได้ทำการประยุกต์ปรับปรุงให้ดูง่ายพอที่จะนำเสนอได้ ส่วนถ้าเป็นรายละเอียดทั้งหมดของเรื่องนี้ ขอแนะนำให้ดูจากหนังสือที่ชื่อ Embededded Multitasking โดย Keith E. Curtis จะเหมาะสมกว่าครับ (หา Download ได้จาก web บางแห่ง หรือหาซื้อมาอ่านก็จะเป็นการดีครับ)
เนื่องจากกระทู้ก่อนหน้านี้ ทำให้ผมพยายามหาตัวอย่างการเขียนในแนวนี้ ซึ่งเคยทำไว้นานมากแล้ว ซึ่งเกิดโดยอาศัยข้อมูลจากนิตยสาร Embedded system programming เพื่อมาเป็นแนวทาง แต่คงหายากมากแล้ว และเ็ป็นบทย่อยๆ ต้องประติดประต่อเอามาก แต่หากสนใจให้อ่านทีุ่่ี่ัั็หนังสือ Embededded Multitasking จะอ่านได้เป็นเรื่องเป็นราวกว่าครับ
ผมเองได้ทำตัวอย่างไว้กับ MCS-51 แต่อาจนำไปปรับแก้้ให้เป็น CPU หรือ MPU อื่นๆได้เช่น PIC , AVR เพราะใช้แค่ความสามารถของภาษา C ในการทำงาน หากถนัด C อยู่แล้วก็จะง่ายขึ้น แต่ท่านสามารถนำหลักของ state machine ไปประยุกต์กับภาษาือื่นๆก็น่าจะได้ ตามที่ตนถนัดเพียงขอให้เข้าใจในหลักการครับ (แต่พวกหนังสือก็อาจจะอ้างอิงกับ C มากกว่านะครับ)
ตัวอย่าง Code สาธิตนี้ เพื่อให้ง่ายต่อความเข้่าใจ ผมจึงทำเป็นพวก ไฟกระพริบ กับ Switch กด และใช้ Uart ในการทำงานนะครับ โดย
ผมให้มีการทำงานดังนี้
1. LED1 กระพริบแบบ Standby mode ( แบบไฟของโทรศัพท์มือถือ )
2. LED2 กระพริบแบบ Cycle เป็นจังหวะ
3. LED3 กระพริบแบบ Standby mode หรือ Cycle ตาม Switch กด (กดครั้งที่ 1 เป็น Standby ครั้งที่ 2 เป็น Cycle และสลับไปมา)
4. Uart ให้รับค่าจาก RS-232 ของ PC (ที่ 9600 BPS) แล้วส่งค่านั้นกลับไปยัง PC อีกครั้ง เหมือน Echo แต่ให้สามารถรับข้อมูลได้ต่อเนื่องได้
Hardware ใช้ MPU กลุ่ม MCS-51 เช่น 89C51 89C2051 อะไรทำนองนี้ จึงขออธิบายเป็นการต่อตามแบบนี้ครับ
(ผมไม่ได้วาดเป็นวงจรไว้่นะครับ คิดว่าน่าจะพอเข้าใจ)
ให้ P1.0 เป็นการควบคุม LED1 , ให้ P1.1 เป็นการควบคุม LED2 , ให้ P1.2 เป็นการควบคุม LED3 โดยการต่อทั้ง 3 ดวงเป็นแบบ กระแส Sink นะครับ และให้ P1.7 ต่อกับเป็น Switch ส่วนช่อง Uart RXD และ TXD ต่อไปสื่อสารกับ PC ที่ช่อง Comport
เอาละครับ มาดู code กันเลยดีกว่า สำหรับใน Code นี้ผมแก้แล้วให้ใช้ Keil เป็นตัว compile นะครับ (ใครที่ติดเป็น Demo version ก็ใช้ได้เพราะได้ hex file ที่ไม่ใหญ่เลย) หรือท่านอื่นๆอาจจะใช้ compiler ตัวอื่นๆได้ ก็คงแก้ไม่มากนัก
ใน code ผมมีการใช้ Interrupt timer (ช่อง 0) เพียงอย่างเดียวเพื่อทำเรื่องของ state timer แต่ก็มีวิธีที่ไม่ใช้ Interrupt timer ได้ ก็ลองปรับแก้ิูได้ แต่ที่ทำไว้เพื่อความสะดวก และผมคิดว่า้จะมีแง่มุมบางอย่างด้วยครับ ส่วน Uart ไม่ได้ใช้ interrupt เลย อีกอย่าง Code นี้ไม่ได้ทำการ optimize ใดๆ เป็นตัวอย่างเพื่อสร้างความเข้าใจเท่านั้น หากนำไปใช้จริง คงต้องจัดการต่อเองครับ
ผมจะแสดงเน้นเฉพาะ file สำคัญนะครับ
อันนี้เป็น main.c
/*************************************************************************
* File name : main.c
* Created by : AppleIIe
* Date created : 2000/6/26
* Description : main , io , section
* Notes : -
*************************************************************************/
/* Include section
* Add all #includes here
*************************************************************************/
#include <REG52.H> /* special function register declarations */
/* for the intended 8051 derivative */
#include "state.h"
#include "main.h"
#include "co_state.h"
/*************************************************************************
* Defines section
* Add all #defines here
*************************************************************************/
#define XTAL_BASE 110592000
#define F_CPU ( XTAL_BASE / 12 )
// timer overflow with 1ms.
#define T1MS -( F_CPU / 1000 )
// calculate baud to TH1
#define UART_BAUD 9600
#define _BAUD ( 256 - ( ( F_CPU / 32 ) / UART_BAUD ) )
/*************************************************************************
* Function Prototype Section
* Add prototypes for all functions called by this
* module, with the exception of runtime routines.
*************************************************************************/
/*************************************************************************
initialize device
*************************************************************************/
static void init_port(void)
{
P1 = 0xff;
P3 = 0xff;
}
//------------------------------------------------------------------------
static void init_uart(void)
{
SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr */
TMOD = 0x21; /* TMOD: timer 1, mode 2, 8-bit reload */
TH1 = _BAUD; /* TH1: reload value for 9600 baud */
TR1 = 1; /* TR1: timer 1 run */
TI = 0; // CLEAR TI
ES = 0; // uart interrupt
}
//------------------------------------------------------------------------
static void init_timer(void)
{
// load T1MS
TMOD = 0x21; /* TMOD: timer 1, mode 2, 8-bit reload */
TL0 = T1MS & 0xff;
TH0 = T1MS >> 8;
TR0 = 1;
ET0 = 1;
}
/*************************************************************************
Timer interrupt
*************************************************************************/
static timer_pack _state_time;
void isr_timer0(void) interrupt 1
{
// load T1MS
TR0 = 0;
TL0 = T1MS & 0xff;
TH0 = T1MS >> 8;
TR0 = 1;
_state_time++;
}
timer_pack get_state_timer(void)
{
timer_pack temp;
DI; // critical
temp = _state_time;
EI; // end of critical
return temp;
}
/*************************************************************************
* Function name : void main( void )
* Description : main loop process
* Notes : -
*************************************************************************/
void main(void)
{
timer_pack _time;
init_port(); // port
init_uart(); // uart
init_timer(); // timer with interrupt
EI; // enable interrupt
// begin of time
_time = get_state_timer();
for (;;) {
// co-operative task without timer
co_state_task_uart();
// ... other co-state at here
// check point of state timer
{
if (_time == get_state_timer())
continue;
_time++;
}
// engine of co-operative task with timer
co_state_timer_task_led1();
co_state_timer_task_led2();
co_state_timer_task_led3();
co_state_timer_task_button_switch();
// ... other co-state timer at here
}
}
file นี้เป็นการทำงานของแต่ละ state machine ครับ co_state.c
/*************************************************************************
* File name : co_state.c
* Created by : AppleIIe
* Date created : 2000/6/26
* Description : operate with co-state
* Notes : -
*************************************************************************/
/* Include section
* Add all #includes here
*************************************************************************/
#include <REG52.H> /* special function register declarations */
/* for the intended 8051 derivative */
#include "state.h"
#include "main.h"
#include "co_state.h"
/*************************************************************************
* Defines section
* Add all #defines here
*************************************************************************/
sbit LED1 = P1 ^ 0;
sbit LED2 = P1 ^ 1;
sbit LED3 = P1 ^ 2;
sbit BTN_SW = P1 ^ 7;
#define ON_LED1 { LED1 = 0; }
#define OFF_LED1 { LED1 = 1; }
#define ON_LED2 { LED2 = 0; }
#define OFF_LED2 { LED2 = 1; }
#define ON_LED3 { LED3 = 0; }
#define OFF_LED3 { LED3 = 1; }
/*************************************************************************
* Function Prototype Section
* Add prototypes for all functions called by this
* module, with the exception of runtime routines.
*************************************************************************/
/*************************************************************************
Co-state task LED1
*************************************************************************/
void co_state_timer_task_led1(void)
{
enum { LED_STATE_BEGIN, LED_STATE_1, LED_STATE_2 };
state_declare;
state_timer_declare;
state_timer_process();
state_switch {
case LED_STATE_BEGIN:
// initial co-state at here
state_jump(LED_STATE_1); // jump to next state
return;
case LED_STATE_1:
ON_LED1;
state_delay(T1SEC * 1 / 10, LED_STATE_2); // delay 100 mS.
return;
case LED_STATE_2:
OFF_LED1;
state_delay(T1SEC * 1, LED_STATE_1); // delay 1 Sec.
return;
}
}
/*************************************************************************
Co-state task LED2
*************************************************************************/
void co_state_timer_task_led2(void)
{
enum { LED_STATE_BEGIN, LED_STATE_1, LED_STATE_2 };
state_declare;
state_timer_declare;
state_timer_process();
state_switch {
case LED_STATE_BEGIN:
// initial co-state at here
state_jump(LED_STATE_1); // jump to next state
return;
case LED_STATE_1:
ON_LED2;
state_delay(T1SEC * 3 / 10, LED_STATE_2); // delay 300 mS.
return;
case LED_STATE_2:
OFF_LED2;
state_delay(T1SEC * 3 / 10, LED_STATE_1); // delay 300 mS.
return;
}
}
/*************************************************************************
Co-state task LED3
*************************************************************************/
static bit _led_busy;
void co_state_timer_task_led3(void)
{
enum { LED_STATE_BEGIN, LED_STATE_MON,
LED_STATE_STB1, LED_STATE_STB2,
LED_STATE_BUSY1, LED_STATE_BUSY2
};
state_declare;
state_timer_declare;
state_timer_process();
state_switch {
case LED_STATE_BEGIN:
// initial co-state at here
state_jump(LED_STATE_MON); // jump to next state
return;
case LED_STATE_MON:
if (!_led_busy) {
state_jump(LED_STATE_STB1);
return;
}
state_jump(LED_STATE_BUSY1);
return;
case LED_STATE_STB1:
ON_LED3;
state_delay(T1SEC * 1 / 10, LED_STATE_STB2); // delay 100 mS.
return;
case LED_STATE_STB2:
OFF_LED3;
state_delay(T1SEC * 1, LED_STATE_MON); // delay 1 Sec.
return;
case LED_STATE_BUSY1:
ON_LED3;
state_delay(T1SEC * 3 / 10, LED_STATE_BUSY2); // delay 300 mS.
return;
case LED_STATE_BUSY2:
OFF_LED3;
state_delay(T1SEC * 3 / 10, LED_STATE_MON); // delay 300 mS.
return;
}
}
/*************************************************************************
co-state task button switch
This state will toggle _lcd_busy
*************************************************************************/
static bit get_btn_switch(void);
void co_state_timer_task_button_switch(void)
{
enum { SW_STATE_BEGIN, SW_STATE_1 };
state_declare;
state_timer_declare;
state_timer_process();
state_switch {
case SW_STATE_BEGIN:
state_jump(SW_STATE_1); // jump to next state
return;
case SW_STATE_1:
if (get_btn_switch())
_led_busy = !_led_busy;
state_delay(10, SW_STATE_1); // debounce 10 ms.
return;
}
}
//------------------------------------------------------------------------
// get switch press
static bit get_btn_switch(void)
{
static bit sw_flg;
if (BTN_SW) {
sw_flg = false;
return false;
}
if (sw_flg)
return false;
sw_flg = true;
return true;
}
/*************************************************************************
Co-state task uart
*************************************************************************/
static void co_state_task_uart_rx(void);
static void co_state_task_uart_tx(void);
static unsigned char _rx_buff;
static bit _rx_flag;
void co_state_task_uart(void)
{
co_state_task_uart_rx();
co_state_task_uart_tx();
}
//------------------------------------------------------------------------
// co-state uart rx
static void co_state_task_uart_rx(void)
{
enum { RX_STATE_BEGIN, RX_STATE_1 };
state_declare;
state_switch {
case RX_STATE_BEGIN:
// initial co-state at here
state_jump(RX_STATE_1); // jump to next state
return;
case RX_STATE_1:
if (RI) {
RI = 0;
_rx_buff = SBUF;
_rx_flag = true;
}
return;
}
}
//------------------------------------------------------------------------
// co-state uart tx
static void co_state_task_uart_tx(void)
{
enum { TX_STATE_BEGIN, TX_STATE_1, TX_STATE_2 };
state_declare;
state_switch {
case TX_STATE_BEGIN:
// initial co-state at here
TI = 0;
state_jump(TX_STATE_1); // jump to next state
return;
case TX_STATE_1: // wait rx buffer
if (_rx_flag) {
SBUF = _rx_buff;
_rx_flag = false;
state_jump(TX_STATE_2); // jump to next state
return;
}
return;
case TX_STATE_2: // wait ti flag
if (TI) {
TI = 0;
state_jump(TX_STATE_1); // jump to next state
return;
}
return;
}
}
สุดท้าย Header file ที่ชื่อ state.h
#ifndef STATE_PROCESS_H
#define STATE_PROCESS_H
/*************************************************************************
* File name : STATE.H
* Created by : AppleIIe
* Date created : 2000/6/26
* Description : Header of State machine file
* Notes : define macro for state machine control.
*************************************************************************/
// #include "state_cfg.h"
#define USE_SIMPLE_STATE
#ifdef USE_SIMPLE_STATE
/*************************************************************************
Macro for simple state machine control.
*************************************************************************/
typedef unsigned timer_pack;
typedef unsigned char state_pack;
// declare state variable
#define state_declare static state_pack _state
// jump to other state
#define state_jump( target ) { _state = target; return; }
/* use this macro in state timer only */
// declare state timer variable
#define state_timer_declare static timer_pack _state_timer
// process timer of state
#define state_timer_process() { if ( _state_timer ) { _state_timer--; return; } }
// delay with state timer , alway tmr > 0
#define state_delay(tmr,target) { _state_timer = (tmr)-1; _state = target; return; }
// way of state
#define state_switch switch ( _state )
/*************************************************************************
End of simple state macro
*************************************************************************/
#endif
#endif // STATE_PROCESS_H
ลองนำไปศึกษาดูก่อนครับ น่าจะไม่ยากนัก
หากท่านใดทดสอบแล้วผลเป็นอย่างไร ก็แนะนำกันได้นะครับ ติชมกันแบบตรงไปตรงมาได้ แต่ขอให้อย่างเป็นประโยชน์และสร้างสรรค์
เพื่อจะได้นำไปเป็นข้อมูลไปทำการปรับปรุงต่อให้สำหรับท่านอื่นๆให้ได้ประโยชน์มากขึ้น และขออภัยหากอธิบายรายละเอียดได้ไม่ดี
Download file ทั้งหมดได้ครับ จากข้างล่างนี้ครับ