目次
前
次
Flashing toy with RTOS
知人から次のようなLEDを使った玩具を見せて貰いました。
これを出番の減ったPIC16F84Aで実現できるのでは思い
次の状態遷移図を描いて考えてみました。
状態値で担当する処理が異なる場合、RTOS(Real Time Operating System)
を利用すれば簡単になると思い、自作のRTOSであるUSOを適用することに。
USOは、使えるSRAMの量が少ないワンチップマイコンのために
開発したRTOSなので、各タスクの仕事を次のように割当て。
- TASK0 各LEDの動作管理
- TASK1 LED#0の点滅
- TASK2 LED#1の点滅
- TASK3 LED#2の点滅
- TASK4 LEDをすべて消灯
- TASK5 チャタリング除去
TASK1からTASK3は、LEDの点滅なので、個々にカウンタを
用意して、カウンタの値で論理値の1か0を出力します。
#define LLED0 PORTB.F0
#define LLED1 PORTB.F1
#define LLED2 PORTB.F2
volatile UBYTE xtim0 ;
volatile UBYTE xtim1 ;
volatile UBYTE xtim2 ;
#define OFF 0
#define ON OFF+1
/* LED0 flashing */
void tsk1_proc(void)
{
/* impress */
LLED0 = xtim0 & ON ;
/* increment */
xtim0++ ;
/* 250ms */
wai_tsk( 25 ) ;
}
/* LED1 flashing */
void tsk2_proc(void)
{
/* impress */
LLED1 = xtim1 & ON ;
/* increment */
xtim1++ ;
/* 250ms */
wai_tsk( 25 ) ;
}
/* LED2 flashing */
void tsk3_proc(void)
{
/* impress */
LLED2 = xtim2 & ON ;
/* increment */
xtim2++ ;
/* 250ms */
wai_tsk( 25 ) ;
}
USOでは、時間待ちにシステムコールwai_tskを利用。
wai_tskのパラメータは、10msの整数倍の整数として
PICの場合は、0から255とします。
各タスクでは、カウンタのLSB(Least Significant Bit)を使い
1と0で点滅を扱うため、250msx2ごとに点灯と消灯が変化。
LEDには、トランジスタを接続して電流増幅します。
トランジスタのインバータが接続されているので、PICの
ピンから1を出力すると点灯になります。
TASK4は全LEDを消灯するので、ポートBに0を出力して終了
させればよいと判断。
void tsk4_proc(void)
{
/* turn off all LED */
PORTB = 0x00 ;
/* exit */
slp_tsk();
}
USOでは、他のタスクに起動して貰い、必要な仕事を
終えて、次に起動するまで休息に入る場合、システム
コールslp_tskを使います。
チャタリングは、スイッチを使っていると発生する
ON、OFFが高速で切り替わる現象。30msくらいで収束
するので、時間間隔をおいて何度かスイッチの状態
を読み込んで、変化を捉えます。
USOでは、周期的な処理で時間間隔を作りたいときに
システムコールwai_tskを使えばよく、チャタリング
除去は簡単。
次のようにTASK5を定義します。
#define MASK0F 0x0F
#define MASK0E 0x0e
volatile UBYTE xsft ;
volatile UBYTE ysft ;
volatile UBYTE zsft ;
void tsk5_proc(void)
{
UBYTE tmp;
/* get switch state */
tmp = PORTA ;
tmp ^= MASK0F ;
/* shift */
xsft <<= 1;
ysft <<= 1;
zsft <<= 1;
/* mask */
xsft &= MASK0E ;
ysft &= MASK0E ;
zsft &= MASK0E ;
/* update LSB */
if ( tmp & 1 ) { xsft |= ON ; }
if ( tmp & 2 ) { ysft |= ON ; }
if ( tmp & 4 ) { zsft |= ON ; }
/* generate event flag */
if ( xsft == 1 ) { XFLG = ON ; }
if ( ysft == 1 ) { YFLG = ON ; }
if ( zsft == 1 ) { ZFLG = ON ; }
/* delay 50ms */
wai_tsk( 5 );
}
シフトレジスタを使い、4回読み込んで0→1のエッジ
変化でイベントフラグを設定しています。
スイッチの状態読込み周期を50msとしました。
スイッチは、次の回路のように負論理動作なので
状態を反転して正論理で処理しています。
TASK0は、各LEDの動作管理をしますが、スイッチの
状態変化を捉えて、状態遷移図に合わせて状態値を
遷移させるだけにとどめます。
TASK1からTASK4は、電源を入れたときには休息状態で
TASK0とTASK5は、常に与えられた仕事をしていれば
よいと考え、システム起動時の状態を決めます。
- TASK0 (READY) 各LEDの動作管理
- TASK1 (SUSPEND)LED#0の点滅
- TASK2 (SUSPEND)LED#1の点滅
- TASK3 (SUSPEND)LED#2の点滅
- TASK4 (SUSPEND)LEDをすべて消灯
- TASK5 (READY) チャタリング除去
READYは、順番が回ってきたらCPUを使って仕事をし
SUSPENDは、他のタスクがREADYにしてくれるまで
休息状態としています。
USOではWAITという状態も用意しています。
WAITは、周期的に仕事をさせたいときに使います。
タスクは、次のように状態を遷移していきます。
TASK1からTASK4は、TASK0がSUSPENDからREADYに状態を
変化させます。TASK0は、状態遷移をスイッチのエッジ
変化をトリガーに使い、判断します。
スイッチのエッジ変化は、イベント通知フラグで捉えて
状態値を変化させればよいと考えます。
TASK0の処理は、単純で以下。
void tsk0_proc(void)
{
/* judge */
if ( XFLG == ON ) {
/* clear event flag */
XFLG = OFF ;
/* update state */
if ( state == 1 ) { state = 4 ; }
if ( state == 0 ) { state = 1 ; }
/* change state */
SFLAG = ON ;
}
if ( YFLG == ON ) {
/* clear event flag */
YFLG = OFF ;
/* update state */
if ( state == 2 ) { state = 4 ; }
if ( state == 0 ) { state = 2 ; }
/* change state */
SFLAG = ON ;
}
if ( ZFLG == ON ) {
/* clear event flag */
ZFLG = OFF ;
/* update state */
if ( state == 3 ) { state = 4 ; }
if ( state == 0 ) { state = 3 ; }
/* change state */
SFLAG = ON ;
}
/* update task state */
if ( SFLAG == ON ) {
/* clear flag */
SFLAG = OFF ;
/* stage 1 */
if ( state == 1 ) { rsm_tsk( TSK_ID1 ); }
/* stage 2 */
if ( state == 2 ) { rsm_tsk( TSK_ID2 ); }
/* stage 3 */
if ( state == 3 ) { rsm_tsk( TSK_ID3 ); }
/* return first state */
if ( state == 4 ) {
state = 0 ;
sus_tsk( TSK_ID1 );
sus_tsk( TSK_ID2 );
sus_tsk( TSK_ID3 );
rsm_tsk( TSK_ID4 );
}
}
}
前半で状態値を遷移させ、後半でタスクの状態を変えています。
TASK4は、TASK1からTASK3をSUSPENDにしたとき
LEDを消灯するために用意。
TASK0は、他のタスクの状態を変えるだけで、ハードウエアに
関係する処理には関与していないことが重要です。
PICのUSOでは、タスク数を最大8として、状態遷移は
round robin方式を採用しました。状態遷移は、単純。
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 ;
case TSK_ID4 : tsk4_proc(); break ;
case TSK_ID5 : tsk5_proc(); break ;
case TSK_ID6 : tsk6_proc(); break ;
case TSK_ID7 : tsk7_proc(); break ;
default : break ;
}
}
run_tsk++;
if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }
10msごとにカウンタ値を減らすのは、タイマー割込みで
イベント通知フラグをセットして知らせています。
if ( EFLAG == ON ) {
EFLAG = OFF ;
timer_handler();
}
void timer_handler(void)
{
UBYTE loop;
for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
if ( waitq & *(bpat+loop) ) {
*(wcount+loop) = *(wcount+loop) - 1 ;
if ( *(wcount+loop) == 0 ) { rsm_tsk(loop); }
}
}
}
タスクの状態は、次の3変数で管理。
volatile UBYTE ready ;
volatile UBYTE suspend;
volatile UBYTE waitq ;
タスクは3状態のどれかに属するので、タスクIDが3変数の
該当するビットのセットで判断できるようになっています。
電源を入れたときに、タスクの初期状態を決めておいた方が
楽として、システムコールsta_tskで各タスクの状態を指定
します。そのとき、変数ready、suspend、waitqのタスクID
で示すビットをセット。
システムコールは、次のように使います。
sta_tsk(TSK_ID0,TTS_READY);
sta_tsk(TSK_ID1,TTS_SUSPEND);
sta_tsk(TSK_ID2,TTS_SUSPEND);
sta_tsk(TSK_ID3,TTS_SUSPEND);
sta_tsk(TSK_ID4,TTS_SUSPEND);
sta_tsk(TSK_ID5,TTS_READY);
sta_tsk(TSK_ID6,TTS_SUSPEND);
sta_tsk(TSK_ID7,TTS_SUSPEND);
USOを使い、スイッチでLEDを点滅させる玩具の
ファームウエアは以下。
typedef unsigned char UBYTE ;
typedef unsigned int UWORD ;
typedef union {
struct {
unsigned B7:1;
unsigned B6:1;
unsigned B5:1;
unsigned B4:1;
unsigned B3:1;
unsigned B2:1;
unsigned B1:1;
unsigned B0:1;
} BIT ;
unsigned char DR ;
} FLAGSP ;
volatile FLAGSP xflags ;
#define SFLAG xflags.BIT.B0
#define EFLAG xflags.BIT.B1
#define XFLG xflags.BIT.B2
#define YFLG xflags.BIT.B3
#define ZFLG xflags.BIT.B4
#define OFF 0
#define ON OFF+1
#define MASKFF 0xff
#define MASK0F 0x0F
#define MASK0E 0x0e
#define LLED0 PORTB.F0
#define LLED1 PORTB.F1
#define LLED2 PORTB.F2
#define CNTBEGIN 6
#define CNTMAX 50
#define TSK_ID_MAX 8
#define TSK_ID0 0
#define TSK_ID1 1
#define TSK_ID2 2
#define TSK_ID3 3
#define TSK_ID4 4
#define TSK_ID5 5
#define TSK_ID6 6
#define TSK_ID7 7
#define TTS_SUSPEND 0
#define TTS_WAIT TTS_SUSPEND+1
#define TTS_READY TTS_SUSPEND+2
volatile UBYTE ready ;
volatile UBYTE suspend;
volatile UBYTE waitq ;
volatile UBYTE run_tsk;
volatile UBYTE bpat[8] ;
volatile UBYTE wcount[8] ;
volatile UBYTE xtim0 ;
volatile UBYTE xtim1 ;
volatile UBYTE xtim2 ;
volatile UBYTE timcnt ;
volatile UBYTE state ;
volatile UBYTE xsft ;
volatile UBYTE ysft ;
volatile UBYTE zsft ;
/*------------------------*/
/* task function protoype */
/*------------------------*/
void tsk0_proc(void);
void tsk1_proc(void);
void tsk2_proc(void);
void tsk3_proc(void);
void tsk4_proc(void);
void tsk5_proc(void);
void tsk6_proc(void);
void tsk7_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);
/* function prototype */
void init_usr(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 == CNTMAX ) {
/* clear */
timcnt = 0 ;
/* set flag */
EFLAG = ON ;
}
}
}
void main(void)
{
/* initialize */
init_usr() ;
/* initialize task */
init_os();
/* endless loop */
run_tsk = TSK_ID0 ;
while ( ON ) {
/* 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 ;
case TSK_ID4 : tsk4_proc(); break ;
case TSK_ID5 : tsk5_proc(); break ;
case TSK_ID6 : tsk6_proc(); break ;
case TSK_ID7 : tsk7_proc(); break ;
default : break ;
}
}
run_tsk++;
if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }
/* 10ms counter */
if ( EFLAG == ON ) {
EFLAG = OFF ;
timer_handler();
}
}
}
/* define function body */
void init_usr(void)
{
/* I/O values */
PORTB = 0x00 ;
/* I/O directions */
TRISA = 0xff ;
TRISB = 0x00 ;
/* initialize Timer 0 */
{
/*
10MHz/4 = 2.5MHz -> 2.5MHz/2 = 1,250kHz prescaler = 1:2
1,250kHz / 250 = 5kHz
*/
OPTION_REG = 0x80 ;
/* 256 - 6 = 250 */
TMR0 = CNTBEGIN ;
/* enable timer0 overflow interrupt */
INTCON.TMR0IE = ON ;
}
/* enable general interrupt */
INTCON.GIE = ON ;
/* clear flag */
xflags.DR = 0 ;
/* initialize variables */
timcnt = 0 ;
xtim0 = 0 ;
xtim1 = 0 ;
xtim2 = 0 ;
state = 0 ;
xsft = 0 ;
ysft = 0 ;
zsft = 0 ;
}
/*----------------*/
/* task functions */
/*----------------*/
/* system control */
void tsk0_proc(void)
{
/* judge */
if ( XFLG == ON ) {
/* clear event flag */
XFLG = OFF ;
/* update state */
if ( state == 1 ) { state = 4 ; }
if ( state == 0 ) { state = 1 ; }
/* change state */
SFLAG = ON ;
}
if ( YFLG == ON ) {
/* clear event flag */
YFLG = OFF ;
/* update state */
if ( state == 2 ) { state = 4 ; }
if ( state == 0 ) { state = 2 ; }
/* change state */
SFLAG = ON ;
}
if ( ZFLG == ON ) {
/* clear event flag */
ZFLG = OFF ;
/* update state */
if ( state == 3 ) { state = 4 ; }
if ( state == 0 ) { state = 3 ; }
/* change state */
SFLAG = ON ;
}
/* update task state */
if ( SFLAG == ON ) {
/* clear flag */
SFLAG = OFF ;
/* stage 1 */
if ( state == 1 ) { rsm_tsk( TSK_ID1 ); }
/* stage 2 */
if ( state == 2 ) { rsm_tsk( TSK_ID2 ); }
/* stage 3 */
if ( state == 3 ) { rsm_tsk( TSK_ID3 ); }
/* return first state */
if ( state == 4 ) {
state = 0 ;
sus_tsk( TSK_ID1 );
sus_tsk( TSK_ID2 );
sus_tsk( TSK_ID3 );
rsm_tsk( TSK_ID4 );
}
}
}
/* LED0 flashing */
void tsk1_proc(void)
{
/* impress */
LLED0 = xtim0 & ON ;
/* increment */
xtim0++ ;
/* 250ms */
wai_tsk( 25 ) ;
}
/* LED1 flashing */
void tsk2_proc(void)
{
/* impress */
LLED1 = xtim1 & ON ;
/* increment */
xtim1++ ;
/* 250ms */
wai_tsk( 25 ) ;
}
/* LED2 flashing */
void tsk3_proc(void)
{
/* impress */
LLED2 = xtim2 & ON ;
/* increment */
xtim2++ ;
/* 250ms */
wai_tsk( 25 ) ;
}
void tsk4_proc(void)
{
/* turn off all LED */
PORTB = 0x00 ;
/* exit */
slp_tsk();
}
/* debouncing */
void tsk5_proc(void)
{
UBYTE tmp;
/* get switch state */
tmp = PORTA ;
tmp ^= MASK0F ;
/* shift */
xsft <<= 1;
ysft <<= 1;
zsft <<= 1;
/* mask */
xsft &= MASK0E ;
ysft &= MASK0E ;
zsft &= MASK0E ;
/* update LSB */
if ( tmp & 1 ) { xsft |= ON ; }
if ( tmp & 2 ) { ysft |= ON ; }
if ( tmp & 4 ) { zsft |= ON ; }
/* generate event flag */
if ( xsft == 1 ) { XFLG = ON ; }
if ( ysft == 1 ) { YFLG = ON ; }
if ( zsft == 1 ) { ZFLG = ON ; }
/* delay 50ms */
wai_tsk( 5 );
}
void tsk6_proc(void)
{
}
void tsk7_proc(void)
{
}
/*------------------*/
/* 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 ;
*(bpat+4) = 0x10 ; *(bpat+5) = 0x20 ;
*(bpat+6) = 0x40 ; *(bpat+7) = 0x80 ;
/* TASK create */
cre_tsk(TSK_ID0); cre_tsk(TSK_ID1);
cre_tsk(TSK_ID2); cre_tsk(TSK_ID3);
cre_tsk(TSK_ID4); cre_tsk(TSK_ID5);
cre_tsk(TSK_ID6); cre_tsk(TSK_ID7);
/* 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_SUSPEND);
sta_tsk(TSK_ID4,TTS_SUSPEND);
sta_tsk(TSK_ID5,TTS_READY);
sta_tsk(TSK_ID6,TTS_SUSPEND);
sta_tsk(TSK_ID7,TTS_SUSPEND);
}
void cre_tsk(UBYTE tid)
{
wcount[tid] = 0;
}
void sta_tsk(UBYTE tid,UBYTE sta)
{
UBYTE tmp ;
/* get bit pattern */
tmp = *(bpat+tid);
/* set bit */
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;
for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
if ( waitq & *(bpat+loop) ) {
*(wcount+loop) = *(wcount+loop) - 1 ;
if ( *(wcount+loop) == 0 ) { rsm_tsk(loop); }
}
}
}
このファームエアは、次のハードウエアでテストしました。
スイッチとLEDは、別の基板上に半田付けしてあります。
PIC16F84Aのテスト基板は、クロックを水晶、セラミック
振動子と交換できるようになっています。
PIC16F84Aで動かすと、ファームウエアのROMとRAMの利用率は以下。
ROM 583 bytes / 1024 bytes (57%)
RAM 38 bytes / 64 bytes (73%)
PIC12F1501のような8ピンのPICでも、充分対応できるサイズ。
PIC12F1501では、3ピンの入力が面倒なので、74LS148のような
プライオリティエンコーダを使う等の工夫が必要。
ICの数を増やすくらいなら、PIC16F1823のような14ピンの安価な
(秋月電子で¥100)で実現した方が、費用面でお得。
3個のLEDを同一周期で点滅させるだけであれば、RTOSを利用
することもないです。点滅周期が、個々のLEDで異なるように
という拡張仕様がきたとき、RTOSであれば点滅を担当している
タスクの中にあるパラメータを変えるだけで済みます。
目次
前
次