目次
前
次
応用例A 夜間点滅器
USOを、夜間点滅器システムに適用してみます。
システム構成は、下図とします。
仕様は、以下とします。
光センサーの出力を入力して、暗いと判断できる
場合、LEDを点滅します。
明るいと判断できる場合、LEDを消灯します。
点滅間隔は、1秒とします。
システム構成図から、必要となるタスクを考えます。
- 光センサーの出力で、明暗を判定し、他のタスクを起動
- LEDを1秒ごとに、点灯、消灯
- LEDを消灯
1から3までのタスクとなったので、タスク0からタスク2
として、動作を定義します。
タスク動作定義
タスク2を定義します。
「LEDを消灯する。」が与えられている仕様なので
LEDを接続しているポートに、消灯の信号を送ります。
LED消灯は、ワンショット処理なので、システムコール
slp_tskを利用して、処理が終わったならばSUSPENDにします。
void tsk2_proc(void)
{
/* LED消灯 */
/* SUSPENDに状態遷移 */
}
LEDは、LED_PORTに接続されているとして
消灯信号を出力します。
void tsk2_proc(void)
{
LED_PORT = LED_OFF ;
/* SUSPENDに状態遷移 */
}
処理終了で、SUSPEND状態にします。
void tsk2_proc(void)
{
LED_PORT = LED_OFF ;
slp_tsk();
}
まとめます。
void tsk2_proc(void)
{
LED_PORT = LED_OFF ;
slp_tsk();
}
タスク1を定義します。
「LEDを1秒ごとに、点灯、消灯。」が考えた
仕様なので、周期タスクを定義すれば充分です。
1秒の間隔は、システムコールwai_tskを利用して生成します。
タスクが実行されるごとに、LED_OFF、LED_ONの信号を
出力します。ON、OFF信号は、0か1なので、カウンタ
をインクリメントして、LSB(Least Significant Bit)の
値を出力します。
void tsk1_proc(void)
{
/* LED点滅 */
/* カウンタインクリメント */
/* 時間待ち指定 */
}
タスクの実行回数を計数するカウンタ変数をcountとし
インクリメント処理をコードにします。
void tsk1_proc(void)
{
/* LED点滅 */
count++;
/* 時間待ち指定 */
}
LEDを点滅する処理をコードにします。
void tsk1_proc(void)
{
LED_PORT = count & 1 ;
count++;
/* 時間待ち指定 */
}
1秒間隔の周期起動処理をコードにします。
void tsk1_proc(void)
{
LED_PORT = count & 1 ;
count++;
wai_tsk( 100 ) ;
}
タスク0を定義します。
「光センサーの出力で、明暗を判定し、他のタスクを起動。」が
考えた仕様なので、光センサーの出力を判断しなければなりません。
明暗の論理値が、出力されるとして判断します。
すでに、他のタスクを起動している場合に、再起動するのは
無駄なので、現在と一つ前の状態を比較して、異なっている
ならば動作するように定義します。
また、明暗の判断は、30秒に1度程度で充分なので
周期処理させます。
以上の内容を踏まえ、動作を擬似コーディングします。
void tsk0_proc(void)
{
/* センサーの状態入力 */
/* 2つの状態を比較し、異なればタスク起動 */
/* 現在の状態を保存 */
/* 時間待ち指定 */
}
現在と一つ前の状態を、cur_state、pre_stateに保存して
いるとします。
void tsk0_proc(void)
{
/* センサーの状態入力 */
if ( cur_state != pre_state ) {
if ( cur_state == NIGHT ) {
rsm_tsk(TSK_ID1);
}
if ( cur_state == DAY ) {
sus_tsk(TSK_ID1);
rsm_tsk(TSK_ID2);
}
}
/* 現在の状態を保存 */
/* 時間待ち指定 */
}
現在の状態を、保存します。
void tsk0_proc(void)
{
/* センサーの状態入力 */
if ( cur_state != pre_state ) {
if ( cur_state == NIGHT ) {
rsm_tsk(TSK_ID1);
}
if ( cur_state == DAY ) {
sus_tsk(TSK_ID1);
rsm_tsk(TSK_ID2);
}
}
pre_state = cur_state ;
/* 時間待ち指定 */
}
センサー出力を取得します。
void tsk0_proc(void)
{
cur_state = SENSOR_OUT ;
if ( cur_state != pre_state ) {
if ( cur_state == NIGHT ) {
rsm_tsk(TSK_ID1);
}
if ( cur_state == DAY ) {
sus_tsk(TSK_ID1);
rsm_tsk(TSK_ID2);
}
}
pre_state = cur_state ;
/* 時間待ち指定 */
}
30秒の時間間隔を設定します。
void tsk0_proc(void)
{
cur_state = SENSOR_OUT ;
if ( cur_state != pre_state ) {
if ( cur_state == NIGHT ) {
rsm_tsk(TSK_ID1);
}
if ( cur_state == DAY ) {
sus_tsk(TSK_ID1);
rsm_tsk(TSK_ID2);
}
}
pre_state = cur_state ;
wai_tsk( 3000 ) ;
}
変数、定数、フラグ定義
タスクで利用しているフラグを、タスク関数から拾い出します。
タスク0で利用している変数、定数、フラグを拾います。
変数 cur_state pre_state
定数 DAY NIGHT
フラグ なし
タスク1で利用している変数、定数、フラグを拾います。
変数 count
定数 なし
フラグ なし
タスク2で利用している変数、定数、フラグを拾います。
変数 なし
定数 LED_OFF
フラグ なし
変数を定義します。
typedef unsigned char UBYTE ;
UBYTE cur_state;
UBYTE pre_state;
UBYTE count;
定数を定義します。
#define LED_OFF 1
#define NIGHT 1
#define DAY 0
コンフィグレーション
各タスクの内容を定義したので、コンフィグレータを利用
して、スケルトンのソースコードを生成します。
スケルトンソースコードの中にある、次の関数にタスク処理
を定義します。
- tsk0_proc
- tsk1_proc
- tsk2_proc
main関数で、各タスクの初期状態を指定します。
cre_tsk(TSK_ID0,tsk0_proc);
cre_tsk(TSK_ID1,tsk1_proc);
cre_tsk(TSK_ID2,tsk2_proc);
sta_tsk(TSK_ID0,TTS_READY);
sta_tsk(TSK_ID1,TTS_SUSPEND);
sta_tsk(TSK_ID2,TTS_SUSPEND);
コンフィグレーションした場合、デフォルトで
すべてのタスクをREADYとします。
クロスコンパイラのために、定義ファイルをインクルードして
必要な初期化をします。
main関数の中で、無限ループに入る前に、必要な初期化を
記述すると、次のようになります。(コンパイラにより、
割込み禁止、割込み許可の関数は異なります。)
/* disable interrupt */
di();
/* initialize system */
user_initialize();
/* initialize monitor */
init_os();
cre_tsk(TSK_ID0,tsk0_proc);
cre_tsk(TSK_ID1,tsk1_proc);
cre_tsk(TSK_ID2,tsk2_proc);
sta_tsk(TSK_ID0,TTS_READY);
sta_tsk(TSK_ID1,TTS_SUSPEND);
sta_tsk(TSK_ID2,TTS_SUSPEND);
/* enable interrupt */
ei();
関数user_initializeの中に、ポートの入出力関連の初期化
内蔵モジュール初期化、変数の定数設定を含めます。
タイマー割込み定義
USOのためにタイマーを1個利用します。
マイクロコンピュータとして、ATTiny2313を利用する
と仮定し、システムクロックを12MHzを分周して100Hz
(=10ms)を生成します。
タイマーのプリスケーラを利用して、64分周します。
12MHz/64=187500
100Hzを生成するためには、1875分周すれば充分です。
187500/100=1875
タイマー1のコンペアマッチ割込みを利用するため、2つ
のレジスタに数値を設定します。
OCR1A = 1874 ;
OCR1B = 2000 ;
タイマー1の初期化は、モード指定、カウンタとレジスタ設定
タイマースタートの3処理にまとめます。
/* select clock source */
TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10);
/* clear timer/counter */
TCNT1 = 0 ;
/* set compare values */
OCR1A = 1874 ;
OCR1B = 2000 ;
/* Enable Compare match interruption */
TIMSK = (1 << OCIE1A) ;
このコードは、関数user_initializeに含めます。
割込みが発生した場合は、WAITであるタスクを探して
カウンタを減算します。
カウンタが0になれば、WAITからREADYに変更します。
SIGNAL(SIG_TIMER1_COMPA)
{
UWORD xtmp;
UBYTE loop;
/* clear counter */
TCNT1 = 0 ;
/* call timer handling */
xtmp = waitq ;
for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
if ( xtmp & 1 ) {
tcb[loop].wcount-- ;
if ( tcb[loop].wcount == 0 ) { rsm_tsk(loop); }
}
xtmp >>= 1 ;
}
}
全ソースコード
実際にクロスコンパイラにかけ、テストしたソース
コードは、以下です。
LEDの点灯、消灯は、コンパイラの仕様にあわせて
変更してあります。
#include <avr/io.h>
#include <avr/interrupt.h>
#define TSK_ID_MAX 3
#define TSK_ID0 0
#define TSK_ID1 1
#define TSK_ID2 2
typedef unsigned char UBYTE ;
typedef unsigned short UWORD ;
typedef signed char SBYTE ;
typedef signed short SWORD ;
typedef struct {
void (*tsk)(void);
UWORD wcount ;
} TCBP ;
#define TTS_SUSPEND 0
#define TTS_WAIT TTS_SUSPEND+1
#define TTS_READY TTS_SUSPEND+2
#define NO 0
#define YES 1
#define OFF 0
#define ON 1
#define DAY 0
#define NIGHT 1
#define SENSOR_OUT (PIND & 1)
volatile UBYTE ready ;
volatile UBYTE suspend;
volatile UBYTE waitq ;
volatile UBYTE run_tsk;
TCBP tcb[TSK_ID_MAX];
volatile UBYTE pre_state ;
volatile UBYTE cur_state ;
volatile UBYTE count ;
/*------------------------*/
/* task function protoype */
/*------------------------*/
void tsk0_proc(void);
void tsk1_proc(void);
void tsk2_proc(void);
/*-----------------------*/
/* system call prototype */
/*-----------------------*/
void init_os(void);
void cre_tsk(UBYTE tid,void (*tsk)(void));
void sta_tsk(UBYTE tid,UBYTE sta);
void rsm_tsk(UBYTE tid);
void sus_tsk(UBYTE tid);
void slp_tsk(void);
void wai_tsk(UWORD x);
UBYTE is_tsk_ready(UBYTE tid);
void timer_handler(void);
/*--------------------------------*/
/* Insert user functions protoype */
/*--------------------------------*/
void user_initialize(void);
/*------*/
/* main */
/*------*/
int main(void)
{
TCBP pcur_tsk ;
cli() ;
/* initialize monitor */
init_os();
cre_tsk(TSK_ID0,tsk0_proc);
cre_tsk(TSK_ID1,tsk1_proc);
cre_tsk(TSK_ID2,tsk2_proc);
sta_tsk(TSK_ID0,TTS_READY);
sta_tsk(TSK_ID1,TTS_SUSPEND);
sta_tsk(TSK_ID2,TTS_SUSPEND);
user_initialize();
sei() ;
/* loop */
run_tsk = TSK_ID0 ;
while ( ON ) {
pcur_tsk = tcb[run_tsk] ;
if ( is_tsk_ready( run_tsk ) == YES ) { (*(pcur_tsk.tsk))(); }
run_tsk++;
if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }
}
/* dummy */
return 0 ;
}
/*----------------*/
/* task functions */
/*----------------*/
/* judge */
void tsk0_proc(void)
{
cur_state = SENSOR_OUT ;
if ( cur_state != pre_state ) {
if ( cur_state == NIGHT ) {
rsm_tsk(TSK_ID1);
}
if ( cur_state == DAY ) {
sus_tsk(TSK_ID1);
rsm_tsk(TSK_ID2);
}
}
pre_state = cur_state ;
wai_tsk( 3000 ) ;
}
#define LED_BIT 0x40
/* impress LED state */
void tsk1_proc(void)
{
if ( count & 1 ) {
PORTD |= LED_BIT ;
} else {
PORTD &= ~LED_BIT ;
}
count++;
wai_tsk( 100 ) ;
}
/* turn off LED */
void tsk2_proc(void)
{
PORTD &= ~LED_BIT ;
slp_tsk();
}
/*------------------*/
/* system call body */
/*------------------*/
void init_os(void)
{
ready = 0 ;
suspend = 0 ;
waitq = 0 ;
}
void cre_tsk(UBYTE tid,void (*tsk)(void))
{
tcb[tid].tsk = tsk;
tcb[tid].wcount = 0;
}
void sta_tsk(UBYTE tid,UBYTE sta)
{
UBYTE tmp ;
tmp = (1 << tid);
if ( sta == TTS_READY ) { ready |= tmp; }
if ( sta == TTS_SUSPEND ) { suspend |= tmp; }
if ( sta == TTS_WAIT ) { waitq |= tmp; }
}
void rsm_tsk(UBYTE tid)
{
UBYTE tmp ;
tmp = (1 << tid);
ready |= tmp;
suspend &= ~tmp;
waitq &= ~tmp;
}
void sus_tsk(UBYTE tid)
{
UBYTE tmp ;
tmp = (1 << tid);
ready &= ~tmp;
suspend |= tmp;
waitq &= ~tmp;
}
void slp_tsk(void)
{
sus_tsk(run_tsk);
}
void wai_tsk(UWORD x)
{
UBYTE tmp ;
tmp = (1 << run_tsk);
ready &= ~tmp;
suspend &= ~tmp;
waitq |= tmp;
tcb[run_tsk].wcount = x ;
}
UBYTE is_tsk_ready(UBYTE tid)
{
return( (ready >> tid) & ON ) ;
}
/*-----------------------*/
/* Insert user functions */
/*-----------------------*/
void user_initialize(void)
{
/* set I/O initial values */
PORTB = 0b00000010 ; /* oooooooo */
PORTD = 0b11111111 ; /* oooiiiii */
/* set I/O directions */
DDRB = 0b11111111 ; /* oooooooo */
DDRD = 0b11110000 ; /* oooiiiii */
/* set flags */
pre_state = DAY ;
cur_state = DAY ;
/* initialize timer1 */
{
/* select clock source */
TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS11);
/* clear timer/counter */
TCNT1 = 0 ;
/* set compare values */
OCR1A = 1874 ;
OCR1B = 2000 ;
/* Enable Compare match interruption */
TIMSK = (1 << OCIE1A) ;
}
}
/* timer1 interrupt */
ISR(TIMER1_COMPA_vect)
{
UBYTE xtmp;
UBYTE loop;
/* clear counter */
TCNT1 = 0 ;
/* call timer handling */
xtmp = waitq ;
for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
if ( xtmp & 1 ) {
tcb[loop].wcount-- ;
if ( tcb[loop].wcount == 0 ) { rsm_tsk(loop); }
}
xtmp >>= 1 ;
}
}
PICではスタックがないチップもあるので、USOを導入し難いですが
ディスパッチャとスケジューラをswitch文で実現するという条件を
加えれば可能です。(以下は、mikroCを利用した場合)
#define LLED0 PORTA.F0
#define MFOUT PORTA.F2
#define CDS PORTA.F5
#define OFF 0
#define ON OFF+1
#define MASKFF 0xff
#define MASK0F 0x0F
#define MASK0E 0x0e
#define CNTBEGIN 6
#define CNTMAX 10
#define TSK_ID_MAX 4
#define TSK_ID0 0
#define TSK_ID1 1
#define TSK_ID2 2
#define TSK_ID3 3
#define TTS_SUSPEND 0
#define TTS_WAIT TTS_SUSPEND+1
#define TTS_READY TTS_SUSPEND+2
typedef unsigned char UBYTE ;
volatile UBYTE wflag ;
volatile UBYTE ready ;
volatile UBYTE suspend;
volatile UBYTE waitq ;
volatile UBYTE run_tsk;
volatile UBYTE bpat[4] ;
volatile UBYTE wcount[4] ;
volatile UBYTE xcnt ;
volatile UBYTE timcnt ;
volatile UBYTE state ;
#define DAY 0
#define NIGHT 1
/*------------------------*/
/* task function protoype */
/*------------------------*/
void tsk0_proc(void);
void tsk1_proc(void);
void tsk2_proc(void);
void tsk3_proc(void);
/*-----------------------*/
/* system call prototype */
/*-----------------------*/
void init_os(void);
void cre_tsk(UBYTE tid);
void sta_tsk(UBYTE tid,UBYTE sta);
void rsm_tsk(UBYTE tid);
void sus_tsk(UBYTE tid);
void slp_tsk(void);
void wai_tsk(UBYTE x);
UBYTE is_tsk_ready(UBYTE tid);
void timer_handler(void);
/* interrupt handler */
void interrupt(void)
{
/* generate 100Hz */
if ( INTCON.T0IF == ON ) {
/* clear flag */
INTCON.T0IF = OFF ;
/* initialize */
TMR0 = CNTBEGIN ;
/* increment */
timcnt++ ;
/* judge */
if ( (timcnt & 15) == CNTMAX ) {
/* set flag */
wflag = ON ;
}
}
}
/* function prototype */
void init_usr(void);
void main(void)
{
/* initialize */
init_usr() ;
/* initialize task */
init_os();
/* endless loop */
run_tsk = TSK_ID0 ;
while ( ON ) {
/* 10ms counter */
if ( wflag == ON ) {
wflag = OFF ;
timer_handler();
}
/* round robbin */
if ( is_tsk_ready( run_tsk ) ) {
switch ( run_tsk ) {
case TSK_ID0 : tsk0_proc(); break ;
case TSK_ID1 : tsk1_proc(); break ;
case TSK_ID2 : tsk2_proc(); break ;
case TSK_ID3 : tsk3_proc(); break ;
default : break ;
}
}
run_tsk++;
run_tsk &= 3 ;
}
}
/* define function body */
void init_usr(void)
{
/* select 4MHz */
OSCCON = (0x0d << 3) | 0x03 ;
/* disable A/D converter */
ADCON0.ADON = OFF ;
ADCON2 = 0 ;
/* disable D/A converter */
DACCON0.DACEN = OFF ;
/* disable compare module */
CM1CON0.C1ON = OFF ;
CM1CON0.C1OE = OFF ;
/* I/O directions */
TRISA = 0x38 ; /* bit0,1,2 as output , others as input */
/* pull-up */
WPUA = 0x30 ;
/* initialize Timer 0 */
{
/*
4MHz/4 = 1MHz -> 1MHz/4 = 250kHz prescaler = 1:4
*/
OPTION_REG = 0x01 ;
/* 256 - 250 = 6 */
TMR0 = CNTBEGIN ;
/* enable timer0 overflow interrupt */
INTCON.TMR0IE = ON ;
}
/* enable general interrupt */
INTCON.GIE = ON ;
/* initialize variables */
state = NIGHT ;
timcnt = 0 ;
xcnt = 0 ;
/* clear flags */
wflag = OFF ;
}
/*----------------*/
/* task functions */
/*----------------*/
/* system control */
void tsk0_proc(void)
{
/* judge */
if ( CDS == OFF ) {
state = DAY ;
sus_tsk( TSK_ID1 );
rsm_tsk( TSK_ID2 );
} else {
if ( state == DAY ) {
state = NIGHT ;
rsm_tsk( TSK_ID1 );
}
}
/* 2000ms */
wai_tsk( 200 ) ;
}
/* LED flashing */
void tsk1_proc(void)
{
/* impress */
LLED0 = OFF ;
if ( xcnt & ON ) { LLED0 = ON ; }
/* increment */
xcnt++ ;
/* 1000ms */
wai_tsk( 100 ) ;
}
/* turn off LED */
void tsk2_proc(void)
{
/* impress */
LLED0 = OFF ;
/* exit */
slp_tsk();
}
/* get sensor state */
void tsk3_proc(void)
{
/* monitor */
MFOUT = OFF ;
if ( timcnt & ON ) { MFOUT = ON ; }
/* 1000ms */
wai_tsk( 100 ) ;
}
/*------------------*/
/* system call body */
/*------------------*/
void init_os(void)
{
/* clear RTOS values */
ready = 0 ;
suspend = 0 ;
waitq = 0 ;
/* bit pattern */
*(bpat+0) = 0x01 ;
*(bpat+1) = 0x02 ;
*(bpat+2) = 0x04 ;
*(bpat+3) = 0x08 ;
/* TASK create */
cre_tsk(TSK_ID0);
cre_tsk(TSK_ID1);
cre_tsk(TSK_ID2);
cre_tsk(TSK_ID3);
/* initialize task state */
sta_tsk(TSK_ID0,TTS_READY);
sta_tsk(TSK_ID1,TTS_SUSPEND);
sta_tsk(TSK_ID2,TTS_SUSPEND);
sta_tsk(TSK_ID3,TTS_READY);
}
void cre_tsk(UBYTE tid)
{
*(wcount+tid) = 0 ;
}
void sta_tsk(UBYTE tid,UBYTE sta)
{
UBYTE tmp ;
/* get bit pattern */
tmp = *(bpat+tid);
/* store */
if ( sta == TTS_READY ) { ready |= tmp; }
if ( sta == TTS_SUSPEND ) { suspend |= tmp; }
if ( sta == TTS_WAIT ) { waitq |= tmp; }
}
void rsm_tsk(UBYTE tid)
{
UBYTE tmp ;
UBYTE tmpx ;
/* get bit pattern */
tmp = *(bpat+tid);
tmpx = tmp ^ MASKFF ;
/* bit handling */
ready |= tmp ;
suspend &= tmpx;
waitq &= tmpx;
}
void sus_tsk(UBYTE tid)
{
UBYTE tmp ;
UBYTE tmpx ;
/* get bit pattern */
tmp = *(bpat+tid);
tmpx = tmp ^ MASKFF ;
/* bit handling */
ready &= tmpx;
suspend |= tmp ;
waitq &= tmpx;
}
void slp_tsk(void)
{
sus_tsk(run_tsk);
}
void wai_tsk(UBYTE x)
{
UBYTE tmp ;
UBYTE tmpx ;
/* get bit pattern */
tmp = *(bpat+run_tsk);
tmpx = tmp ^ MASKFF ;
/* bit handling */
ready &= tmpx;
suspend &= tmpx;
waitq |= tmp ;
/* update counter */
*(wcount+run_tsk) = x ;
}
UBYTE is_tsk_ready(UBYTE tid)
{
return( ready & *(bpat+tid) ) ;
}
void timer_handler(void)
{
UBYTE loop;
UBYTE wcnt ;
for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
if ( waitq & *(bpat+loop) ) {
wcnt = *(wcount+loop);
wcnt--;
*(wcount+loop) = wcnt ;
if ( wcnt == 0 ) { rsm_tsk(loop); }
}
}
}
PICでは、アセンブリ言語レベルでシフト命令がなく
ローテート命令で代用します。
利用タスク数を最大8に限定しておき、シフト命令を
使わないで、該当タスクのREADY、SUSPEND、WAITを
論理演算で判断します。
目次
前
次