ラーメンタイマ

麺類はお好きですか。 本格的な生めんもカップ麺も、麺類の調理に欠かせないのがタイマです。 この作品は、部品点数を極力減らしたラーメンタイマです。

これが、ラーメンタイマだ

アジェンダ

ラーメンタイマのハードウェア

このアプリケーションの回路図は、以下のようになっています。 押しボタン一個の操作で6個のLEDと圧電スピーカを駆動しています。

ラーメンタイマの回路図

このアプリケーションでは、以下の部品を使用しています。

ラーメンタイマの部品表
部品番号品名規格個数メーカ調達先
U1マイコンMC9RS08KA2CPC1Freescaledigi-key
-ICソケット8P DIP1-秋月電子
R1カーボン抵抗2.2kΩ 1/4W1KOA秋月電子
C1,C2積層セラミックコンデンサ0.1µF 50V2ムラタ製作所秋月電子
C3アルミ電解コンデンサ47µF 25V1Ruby-conマルツパーツ
LED0,LED1赤色発光ダイオードOSDR5113A2OptoSupply秋月電子
LED2,LED3黄色発光ダイオードOSNG5113A2OptoSupply秋月電子
LED4,LED5緑色発光ダイオードOSYL5113A2OptoSupply秋月電子
SW1押しボタンスイッチDP1-1201フジソク梅澤無線
BAT1ボタン電池ホルダCH25-20321SHOGYO秋月電子
-ボタン電池CR20321GP秋月電子
PS1圧電スピーカPS12401TDKdigi-key
HD1ボックス・ピン・ヘッダ2×31-ダイセン電子
-片面ユニバーサル基板72mm×48mm1-秋月電子
-ワッシャつきなべビス3mm×10mm4-廣杉計器
-スペーサM3 10mm4-廣杉計器

LEDの駆動方式

このアプリケーションでは、LEDに電流制限抵抗を入れていません。 これは、MC9RS08KA2の出力電流が多くないという事実を利用したものです。 下のグラフは、流れる電流に対する"H"出力ポートと"L"出力ポートの 電位差(VOH-VOL)とLEDの順方向電圧(VF)をモデル化したものです。 これら二つのグラフの交点でLEDが点灯します。

設計では、電源電圧3Vのときに、 "H"出力ポートと"L"出力ポートで0.5Vずつ電圧降下が起き、 それによって出力電流は約5mAに制限されます。 これは、電源電圧が3Vであったときに限った計算です。 したがって、この回路に5V電源を与えると マイコンとLEDに大電流が流れてしまい破壊する可能性がありますのでご注意ください。

ラーメンタイマの実体配線図

ラーメンタイマのソフトウェア

このアプリケーションのソフトウェアについて解説します。

動作

このアプリケーションは、押しボタン一個で操作します。

最初にタイマの時間を設定します。 押しボタンを押すと2分から7分の間でLEDの表示が移動していきます。 好みの時間の位置でLEDを点灯させてから、押しボタンを2秒間長押しすると、 タイマの時間測定が始まります。

設定した時間になったら音楽が鳴り始めます。 押しボタンを押すと音楽が止まります。

状態遷移図

このアプリケーションの状態遷移図は、このようになっています。

それぞれの状態は、赤、緑、青の三色に分けられおり、以下のような意味づけがされています。

プログラムリスト

このアプリケーションのプログラムは、以下のようになっています。

ヘッダ・タイトル

//*******************************************************************
//*
//* NoodleTimer.c : 
//*
//* Copyright 2007 (C) noritan.org
//* 
//*******************************************************************

#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */

#include 文は、 CodeWarrior が作成したままの物を使用しています。

デバッグ時設定の定義

ここから、デバッグ時のレジスタの初期値が定義されます。

#ifdef DEBUG
//==============================================================
//=  Debug Configuration.
//==============================================================
//==============================================================
//=  Initialization value for SPMSC1 register.
//=
//=  In DEBUG case, LVD cause RESET in RUN and STOP.
//==============================================================
const byte SPMSC1_INIT  = 0b01011100;
//                          |||||||+-- BGBE=0   (no bandgap)
//                          ||||||+--- Reserved
//                          |||||+---- LVDE=1   (LVD enable)
//                          ||||+----- LVDSE=1  (LVD enable in STOP)
//                          |||+------ LVDRE=1  (Reset by LVD enable)
//                          ||+------- LVDIE=0  (INT by LVD disable)
//                          |+-------- LVDACK=1 (Clear LVDF)
//                          +--------- LVDF=0   (N/A)
//==============================================================


//==============================================================
//  Initialization value for SOPT register.
//
//  In DEBUG case, STOP instruction and BKGD pin are enabled.
//==============================================================
const byte SOPT_INIT    = 0b00100010;
//                          |||||||+-- RSTPE=0  (no RESET*)
//                          ||||||+--- BKGDPE=1 (BKGD enable)
//                          |||+++---- Reserved
//                          ||+------- STOPE=1  (STOP available)
//                          |+-------- COPT=0   (COP rate)
//                          +--------- COPE=0   (no COP)
//==============================================================
デバッグ時設定
レジスタビット設定詳細
BGBE0バンドギャップ電圧は使用しません。
LVDE1LVD機能を使います。
LVDSE1STOP状態でもLVD機能を使います。
LVDRE1LVD機能でリセットを発生させます。
LVDIE0LVD機能では割り込みを発生させません。
LVDACK1もし、LVDFがセットされていたら、クリアします。
RSTPE0RESET*端子をリセット機能に使用しません。
BKGDPE1BKGD端子をBDM機能に使用します。
STOPE1STOP命令が実行できます。
COPT0COP機能を使用しません。
COPE0

ここでは、デバッグ時に使用するレジスタの初期値を決定しています。 この中には、リセット後に一回だけ書き込むことが出来るレジスタも含まれているので、 よく考えて設定する必要があります。

リリース時設定の定義

続いて、リリース時のレジスタの初期値が定義されます。

#else // !DEBUG

//==============================================================
//=  Release Configuration.
//==============================================================
//==============================================================
//=  Initialization value for SPMSC1 register.
//=
//=  In RELEASE case, LVD cause RESET in RUN.
//==============================================================
const byte SPMSC1_INIT  = 0b01010100;
//                          |||||||+-- BGBE=0   (no bandgap)
//                          ||||||+--- Reserved
//                          |||||+---- LVDE=1   (LVD enable)
//                          ||||+----- LVDSE=0  (LVD disable in STOP)
//                          |||+------ LVDRE=1  (Reset by LVD enable)
//                          ||+------- LVDIE=0  (INT by LVD disable)
//                          |+-------- LVDACK=1 (Clear LVDF)
//                          +--------- LVDF=0   (N/A)
//==============================================================


//==============================================================
//  Initialization value for SOPT register.
//
//  In RELEASE case, STOP instruction is enabled.
//==============================================================
const byte SOPT_INIT    = 0b00100000;
//                          |||||||+-- RSTPE=0  (no RESET*)
//                          ||||||+--- BKGDPE=0 (BKGD disable)
//                          |||+++---- Reserved
//                          ||+------- STOPE=1  (STOP available)
//                          |+-------- COPT=0   (COP rate)
//                          +--------- COPE=0   (no COP)
//==============================================================

#endif // !DEBUG
リリース時設定
レジスタビット設定詳細
BGBE0バンドギャップ電圧は使用しません。
LVDE1LVD機能を使います。
LVDSE0STOP状態ではLVD機能を使いません。
LVDRE1LVD機能でリセットを発生させます。
LVDIE0LVD機能では割り込みを発生させません。
LVDACK1もし、LVDFがセットされていたら、クリアします。
RSTPE0RESET*端子をリセット機能に使用しません。
BKGDPE0BKGD端子を汎用出力として使用します。
STOPE1STOP命令が実行できます。
COPT0COP機能を使用しません。
COPE0

リリース時には、消費電力を抑制する目的で、STOP状態の時にはLow-Voltage Detect (LVD) が 動作しないように設定します。

また、リリース時には、Background Debug Mode (BDM)を使用しません。

関数stopとwaitの定義

//==============================================================
//  STOP instruction.
//==============================================================
#pragma INLINE
void stop(void) {
  __asm("STOP");
}

//==============================================================
//  WAIT instruction.
//==============================================================
#pragma INLINE
void wait(void) {
  __asm("WAIT");
}

ここでは、 STOP 命令と WAIT 命令をプログラム内に埋め込む関数を定義しています。 これらの関数には、 "#pragma INLINE" 文が付加されているので、 実際には、他の関数の中で展開されたうえでコンパイルされます。

I/O端子の名前をつける

//==============================================================
//  Port Declaration
//
//    Beeper is assigned to PTA0
//    Start button is assigned to PTA2
//==============================================================
#define     BEEP      PTAD_PTAD0
#define     BEEP_MASK PTAD_PTAD0_MASK
#define     START     PTAD_PTAD2  // Start is assigned to PTA2.

このアプリケーションでは、PTA0に圧電スピーカを PTA2に押しボタンスイッチを接続しています。 以下のプログラムをできるだけ抽象化できるように、ここでマクロの宣言を行っています。

フラグレジスタを定義する

//==============================================================
//  Flag Declaration
//
//    blank : indicates LEDs are put off.
//    quiet : indicates BEEPER does not work.
//==============================================================
typedef struct {
    byte blank:1;    // BLANK flag.
    byte quiet:1;    // QUIET flag.
} Flag;

このアプリケーションでは、 "flag"という変数をビット操作可能なフラグの集合として使用しています。 ここでは、この変数の中のフラグの構成を宣言しています。

フラグレジスタの用途
フラグ名称用途
blank このフラグは、LED表示装置が消灯されているかどうかを示します。 LED表示プログラムは、このフラグの値を調べて、表示装置に送るデータを切り替えます。
quiet このフラグは、音楽を演奏するときに音を停止させます。 発音プログラムは、このフラグを使って、音と音の間に停止時間を作ります。

RTIモジュールの初期設定を定義する

//==============================================================
//  RTI initialization.
//  RTI period:  8msec
//
const byte SRTISC_INIT  = 0b01000001;
//                          |||||+++-- RTIS=001  (8msec period)
//                          ||||+----- Reserved
//                          |||+------ RTIE=0    (no interrupt)
//                          ||+------- RTICLKS=0 (internal 1kHz)
//                          |+-------- RTIACK=1  (clear RTIF)
//                          +--------- RTIF=0    (no effect)
const byte SRTISC_DISABLE  = 0b01100000;
//                             |||||+++-- RTIS=000  (disable)
//                             ||||+----- Reserved
//                             |||+------ RTIE=0    (no interrupt)
//                             ||+------- RTICLKS=1 (trimmed 1kHz)
//                             |+-------- RTIACK=1  (clear RTIF)
//                             +--------- RTIF=0    (no effect)
//==============================================================
RTIモジュールの設定
レジスタビット 設定 詳細
INITDISABLE
RTIS001000 使用時のみ8ミリ秒ごとにRTIイベントを発生させます。
RTIE00 RTIイベントが発生しても割り込みを発生させません。 割り込みが必要な場合には、別途RTIEフラグをセットします。
RTICLKS01 RTIのクロック源として1kHzの内部発振器を使います。 このクロック源は、低消費電力ですが精度の低いクロックなので、 時間測定には使用していません。 非使用時には、消費電力を減らすため、内部発振器を止めます。
RTIACK11 もし、RITFがセットされていたら、クリアします。

ここでは、 RTI モジュールの設定レジスタの初期値を定義しています。 このアプリケーションでは、 Real-Time Interrupt (RTI) モジュールを使用して、 8ミリ秒ごとに割り込みを発生させています。

また、RTIモジュールを使用しないときには、 消費電力を抑えるため、RTI機能と内部発振器を止めてしまいます。

MITIMモジュールの初期設定と定数を定義する

//==============================================================
//  MTIM initialization
//  MTIM clock: Freq=250kHz, Period=4usec
//
const byte MTIMCLK_INIT  = 0b00000100;
//                           ||||++++-- PS=0100   (x16 prescale)
//                           ||++------ CLKS=00   (BUSCLK)
//                           ++-------- Reserved
//
//
//  MTIMMOD as key-response:  334usec (1 / (2 * 1.497kHz))
const byte MTIMMOD_3000 =  83-1;  // 250kHz/3000Hz   =  83
//
//  MTIMMOD as accurate timer: 1msec (1 / 1000Hz)
const byte MTIMMOD_1000 = 250-1;  // 250kHz/ 250 = 1000Hz
//
//  MTIMMOD to play MUSIC: based on 250kHz
const byte P_H2         = 253-1;
const byte P_C3         = 239-1;
const byte P_C3s        = 225-1;
const byte P_D3         = 213-1;
const byte P_D3s        = 201-1;
const byte P_E3         = 190-1;
const byte P_F3         = 179-1;
const byte P_F3s        = 169-1;
const byte P_G3         = 159-1;
const byte P_G3s        = 150-1;
const byte P_A3         = 142-1;
const byte P_A3s        = 134-1;
const byte P_H3         = 127-1;
const byte P_C4         = 119-1;
const byte P_C4s        = 113-1;
const byte P_D4         = 106-1;
const byte P_D4s        = 100-1;
const byte P_E4         =  95-1;
const byte P_F4s        =  84-1;
const byte P_G4s        =  75-1;
const byte P_A4         =  71-1;
const byte P_H4         =  63-1;
const byte P_C5         =  60-1;
const byte P_C5s        =  56-1;
//
//==============================================================
MTIMモジュールの設定
レジスタビット設定詳細
PS01001/16プリスケーラを使用します。
CLKS00 MTIMのクロック源としてバスクロックを使用します。 このアプリケーションでは、バスクロックは4MHzです。

このアプリケーションでは、Modulo TIMer (MTIM) モジュールを使用して、 圧電スピーカからの発音、時間測定、およびチャタリング・キャンセルに 使用する周期的割り込みをさせています。 割り込み周期は、目的によって異なるので、その都度設定しなおしています。

プリフィックス"P_"の付いた定数は、音階を表現するときに使用します。 これらの定数を使って、楽譜を表現します。

定数パラメータを定義する

//==============================================================
//  Constant parameters
//
//  LED_COUNT : unit 1
//    Number of LEDs
//  DEBOUNCE_PERIOD : unit 334usec
//    Time period to recognize button released.
//    160*(334usec) = 53.4msec
//  BEEP_PERIOD : unit 334usec
//    Length of a BEEP in PRESSED & VALIDATED state.
//    240*(334usec) = 80msec
//  IDLE_PERIOD : unit 8 msec
//    Idling time to enter STOP state.
//    146*256*(8msec) = 299sec = 5min
//     29*256*(8msec) =  59sec = 1min (QUICK mode)
//  PRESSED_PERIOD : unit 334usec
//    Time to recognize START button pressed long.
//    19*256*(334usec) = 1.62sec
//  QSECOND_PERIOD : unit 1msec
//    Time period of 1/4 second.
//    250*(1msec) = 250msec
//  MINUTE_PERIOD : unit 250msec
//    240*(250msec) = 60sec =   1min
//     40*(250msec) = 10sec = 1/6min (QUICK mode)
//==============================================================
const byte LED_COUNT        =   6;      // Number of LEDs.
const byte DEBOUNCE_PERIOD  = 160;      // Period of debounce.
const byte BEEP_PERIOD      = 240;      // Period of beep.
#ifdef QUICK
const word IDLE_PERIOD      =  29*256;  // Idling in QUICK.
#else
const word IDLE_PERIOD      = 146*256;  // Period of idling.
#endif // QUICK
const word PRESSED_PERIOD   =  19*256;  // Pressed long time.
const word QSECOND_PERIOD   = 250;      // 1/4 second.
#ifdef QUICK
const byte MINUTE_PERIOD    =  40;      // 1/6 minute in QUICK.
#else
const byte MINUTE_PERIOD    = 240;      // 1 minute.
#endif // QUICK
各種定数の宣言
定数名設定詳細
LED_COUNT6 装備しているLEDの個数をあらわします。
DEBOUNCE_PERIOD160 チャッタリング防止のための猶予時間を指定します。 単位は、334マイクロ秒です。 この時間が短かすぎるとチャッタリングを除去することができません。 また長すぎるとボタンを押してからの反応時間が長くなります。
BEEP_PERIOD240 ボタンを操作した時に鳴らす確認音の長さを指定します。 単位は、334マイクロ秒です。
IDLE_PERIOD146*256(通常時) 操作をしなくなってから、STOP状態に入るまでの待ち時間を指定します。 単位は、8ミリ秒です。 通常の待ち時間は数分程度に設定されます。 ところが、待ち時間が長くなると、デバッグが難しくなります。 このため、短縮動作時には早くSTOP状態に入るように切り替えられるようになっています。
29*256(短縮動作時)
PRESSED_PERIOD19*256 押しボタンの長押しを判定するための時間を指定します。 単位は、334マイクロ秒です。 このアプリケーションでは、ボタンの長押しを検出して動作を切り替えます。 ボタンの長押し時間には個人差があるので、微調整が必要です。
QSECOND_PERIOD250 1/4秒の長さを指定します。 単位は、1ミリ秒です。 この定数は、時間を測定する際の基準となるため、時間の精度が必要です。 このアプリケーションでは、1ミリ秒ごとに割り込みを発生させ、 ソフトウェアカウンタを使って、時間の計測を行っています。 時間待ち状態では、1/4秒ごとにイベントを発生させます。
MINUTE_PERIOD240(通常時) 1分の長さを指定します。 単位は、1/4秒です。 この定数は、時間待ちの基準時間を示します。。 この定数もデバッグを容易にするため、 短縮動作時には早く時間を進められるように切り替えられるようになっています。
40(短縮動作時)

ここでは、プログラムで使用する各種定数を定義しています。 一部の定数は、環境変数"QUICK"の定義の有無によって設定が変更されます。

状態コードを定義する

このプログラムのステートマシンでは、 状態コードを数え上げ型(enumeration)で定義して使用しています。

//==============================================================
//  State code declaration
//
//    enum State indicates the state codes of the state
//    machine.
//==============================================================
typedef enum {
  ST_PROLOGUE   =   0,  // PROLOGUE
  ST_WAIT       =   1,  // WAIT
  ST_STOP       =   2,  // STOP
  ST_PRESSED    =   3,  // PRESSED
  ST_VALIDATED  =   4,  // VALIDATED
  ST_ACTIVE     =   5,  // ACTIVE
  ST_EPILOGUE   =   6   // EPILOGUE
} State;

いずれの状態も先の状態遷移図で紹介した状態です。

数え上げ型は、コンパイラの初期状態では16ビットの数値として表現されるので、 RS08でプログラムを記述するときには無駄なコードが生成されます。 これを8ビットの数値で表現させるためには、 コンパイラのオプションで"enum"のサイズを8ビットに指定する必要があります。

コンパイラオプションの指定 数え上げ型のサイズの指定

もし、オプションを変更したくないのであれば、"enum"を単なる定数宣言として使用し、 状態コード変数に8ビットの"byte"を使用する方法もあります。

楽譜の構造を定義する

ここでは、楽譜を表現するための構造体を定義しています。

//==============================================================
//  Music data structure
//
//    Music is a list of Phrase pointer.
//    Phrase is a list of Sono.
//==============================================================
typedef struct {
  byte        length;             // Length of a SONO
  byte        period;             // Period for MTIM
}  Sono;

typedef const Sono    * __paged SonoP;
typedef SonoP                   Phrase;
typedef const Phrase  * __paged PhraseP;
typedef PhraseP                 Music;

RS08は、原則として64バイト以上の長さの配列を扱うことが出来ません。 そのため、楽譜などの長い配列をそのまま表現する時には工夫が必要です。 このプログラムでは、楽譜を三層構造の多重配列(配列へのポインタの配列)として表現しています。

まず、音階(period)と長さ(count)からなる、Sonoという構造体が宣言されています。 この構造体で楽譜の音符ひとつ分が表現できます。 Phraseは、Sono構造体へのポインタとして宣言されています。 実際には、Sono構造体の配列を指して使用します。 この時に使用されるポインタには、__paged指示子が付いているため、 Sono構造体の配列は、64バイト境界をまたがない所に配置しなくてはなりません。

Musicは、Phrase構造体へのポインタとして宣言されています。 これもPhrase配列を指して使用されます。 この時に使用されるポインタにも、__paged指示子が付いているため、 Phrase構造体の配列は、64バイト境界をまたがない所に配置しなくてはなりません。

Musicは、ポインタの配列であるため、 ひとつのPhraseMusicの中に何度も使うことによって、 繰り返しの表現をすることもできます。

大域変数の宣言

RS08の大域変数は、TINYアドレッシング領域($0000-$000F)に配置されるものと それ以外のDIRECTアドレッシング領域($0020-$00BF)に配置されるものに大別されます。 一切の宣言をせずに、全ての変数をDIRECT領域に配置することも可能ですが、 このプログラムではコード効率と処理速度の向上を狙って、 厳密に二つの領域を使い分けています。

TINY領域($0000-$000F)には、14バイト($0000-$000D)のRAMが存在しますが、 コンパイラはこのうち5バイト($0000-$0004)も作業領域に使用します。 そのため、ユーザが自由に使えるRAM領域は9バイト($0005-$000D)だけです。

//**************************************************************
// Variable section
//
//   Some variables are assigned to TINY area.
//**************************************************************
#pragma DATA_SEG TINY TINY_RAM_VARS

byte        led_index;            // index for LED.
byte        debounce_count;       // timer for debounce.
word        time_count;           // Event timer counter.
byte        qsecond_count;        // Quarter second counter.
SonoP       sono_point;           // Pointer to SONOs.
PhraseP     phrase_point;         // Phrase to be played.


#pragma DATA_SEG DIRECT DIRECT_RAM_VARS

byte        minute_count;         // Minute counter.
byte        minute_target;        // Minute mode to be set.
Flag        flag;                 // user defined flag.
State       state;                // State code.
byte        sono_count;           // Sono counter.
Music       music;                // Music to be played.

#pragma DATA_SEG FAR DEFAULT
大域変数表
変数名詳細
led_indexbyte 点灯させるLEDの位置を記憶します。
debounce_countbyte 押しボタンスイッチのグリッチを取り除くためのタイマカウンタです。
time_countword 時間を計測するための汎用ソフトウェア・タイマ・カウンタです。
qsecond_countbyte 待ち時間を計測するための1/4秒ごとに動作するソフトウェア・カウンタです。
sono_pointSonoP 音楽演奏中に音符を指し示すポインタ変数です。
phrase_pointPhraseP 音楽演奏中にPhrase構造体を指し示すポインタ変数です。
minute_countbyte 待ち時間を計測するための1分ごとに動作するソフトウェア・カウンタです。
minute_targetbyte 待ち時間として設定された分単位の時間を記憶します。
flagFlag 汎用ソフトウェア・フラグ群です。
stateState ステートマシンで使用される状態コードです。
sono_countbyte 音楽演奏中に使用される発音の残り時間を計測するソフトウェア・カウンタです。
musicMusic 演奏中の楽譜を記憶します。

楽譜

楽譜は、定数配列で宣言します。 今回準備したのは、3曲です。 一曲目は、不朽の名作、チャルメラ。 これは、単一のPhraseで出来ています。

#pragma CONST_SEG PAGED PAGED_ROM
//==============================================================
//  Charumera
//==============================================================
const Sono phrase_noodle1[] = {
      2*12,  P_C4,
      2*12,  P_D4,
      6*12,  P_E4,
      2*12,  P_D4,
      4*12,  P_C4,
      2*12,  P_C4,
      2*12,  P_D4,
      2*12,  P_E4,
      2*12,  P_D4,
      2*12,  P_C4,
     16*12,  P_D4,
      8*12,  0,
       0,  0
};

const Phrase music_noodle[] = {
    phrase_noodle1,
    (Phrase)0
};

二曲目は、ムソルグスキーの「展覧会の絵」から「卵の殻をつけたひよこの踊り」から。 冒頭のPhraseを三番目のPhraseとして使いまわしています。

//==============================================================
//  Ballett
//==============================================================
const Sono phrase_ballett1[] = {
    4*6, P_C5,
    4*6, 0,
    4*6, P_H4,
    4*6, 0,
    4*6, P_C5,
    4*6, 0,
    4*6, P_G4s,
    4*6, P_C5,
    4*6, P_C5,
    4*6, 0,
    4*6, P_H4,
    4*6, 0,
    4*6, P_C5,
    4*6, 0,
    4*6, P_G4s,
    4*6, 0,
    0,0
};

const Sono phrase_ballett2[] = {  
    4*6, P_H2,
    4*6, P_C3,
    4*6, P_D3,
    4*6, P_D3s,
    4*6, P_E3,
    4*6, P_F3,
    4*6, P_G3,
    4*6, P_G3s,
    4*6, P_A3s,
    4*6, P_H3,
    4*6, P_C4,
    4*6, P_C4s,
    4*6, P_D4,
    4*6, P_D4s,
    4*6, P_E4,
    4*6, P_C4,
      0, 0
};

const Sono phrase_ballett3[] = {  
    4*6, P_C3s,
    4*6, P_D3s,
    4*6, P_E3,
    4*6, P_F3,
    4*6, P_F3s,
    4*6, P_G3s,
    4*6, P_A3,
    4*6, P_H3,
    4*6, P_D3s,
    4*6, P_F3,
    4*6, P_F3s,
    4*6, P_G3,
    4*6, P_G3s,
    4*6, P_A3s,
    4*6, P_H3,
    4*6, P_C4,
      0, 0
};

const Sono phrase_ballett4[] = {  
    4*6, P_C3,
    4*6, P_E3,
    4*6, P_C3s,
    4*6, P_F3,
    4*6, P_D3s,
    4*6, P_F3s,
    4*6, P_E3,
    4*6, P_G3,
    4*6, P_F3,
    4*6, P_A3,
    4*6, P_F3s,
    4*6, P_A3s,
    4*6, P_G3s,
    4*6, P_H3,
    4*6, P_A3s,
    4*6, P_C4,
      0, 0
};

const Sono phrase_ballett5[] = {  
   24*6, P_C5s,
    4*6, 0,
    4*6, P_C4,
    4*6, 0,
      0, 0
};

const Phrase music_ballett[] = {
    phrase_ballett1,
    phrase_ballett2,
    phrase_ballett1,
    phrase_ballett3,
    phrase_ballett4,
    phrase_ballett5,
    (Phrase)0
};

三曲目は、ショパンの「別れの曲」から。 この曲も一部、Phraseの使い回しをしています。

//==============================================================
//  L'Adieu
//==============================================================
const Sono phrase_adieu1[] = {
    8*12, P_H2,
    8*12, P_E3,
    4*12, P_D3s,
    4*12, P_E3,
    0,0
};

const Sono phrase_adieu2[] = {
   20*12, P_F3s, // 2;10
    4*12, P_G3s,
    4*12, P_G3s,
    4*12, P_F3s,
   20*12, P_G3s, // 3;11
    4*12, P_A3,
    4*12, P_A3,
    4*12, P_G3s,
   12*12, P_C4s,
    4*12, P_H3,
    
    4*12, P_A3,  // 4;12
    4*12, P_G3s,
    4*12, P_D3s,
    4*12, P_E3,
   20*12, P_F3s, // 5;13
    4*12, P_G3s,
    4*12, P_G3s,
    4*12, P_F3s,
   16*12, P_E3,
   0,0
};

const Sono phrase_adieu3[] = {
    4*12, P_G3s,  // 6
    4*12, P_A3,
    4*12, P_F3s,
    4*12, P_G3s,
    4*12, P_A3,
    4*12, P_H3,
    4*12, P_G3s,
    4*12, P_A3,
    8*12, P_C4s, // 7
   16*12, P_F3s,
    4*12, P_G3s,    
   20*12, P_F3s, // 8
    4*12, P_G3s,
    4*12, P_F3s,
   16*12, P_H3,
    8*12, P_G3s, // 9
    4*12, P_D3s,
    4*12, P_E3,
    0,0
};

const Sono phrase_adieu4[] = {
    4*12, P_H3, // 14
    4*12, P_C4s,
    4*12, P_C4s,
    4*12, P_H3,
    4*12, P_A3,
    4*12, P_H3,
    4*12, P_G3s,
    4*12, P_A3,
    4*12, P_D4s, // 15
    4*12, P_E4,
    4*12, P_E4,
    4*12, P_D4s,
    4*12, P_C4s,
    4*12, P_D4s,
    4*12, P_H3,
    4*12, P_C4s,
    4*12, P_E4,  // 16
    4*12, P_F4s,
    4*12, P_D4s,
    4*12, P_E4,
    4*12, P_F4s,
    4*12, P_G4s,
    4*12, P_E4,
    4*12, P_F4s,
   0,0
};

const Sono phrase_adieu5[] = {
   20*12, P_G4s,  // 17
    4*12, P_F4s,
    4*12, P_E4,
    4*12, P_C4s,
   16*12, P_D4s,  // 18
    4*12, P_E4,
    4*12, P_D4s,
    4*12, P_C4s,
    4*12, P_G4s,
   16*12, P_H3,   // 19
    4*12, P_C4s,
    4*12, P_H3,
    4*12, P_A3,
    4*12, P_E3,
   16*12, P_G3s,  // 20
    8*12, 0,
   0,0
};

const Phrase music_adieu[] = {
   phrase_adieu1,
   phrase_adieu2,
   phrase_adieu3,
   phrase_adieu2,
   phrase_adieu4,
   phrase_adieu5,
   (Phrase)0
};

#pragma CONST_SEG DEFAULT

PORT-Aの出力パターン定義

このアプリケーションでは、特定のLEDを点灯させるために、 PTADDレジスタとPTADレジスタの双方ともに適切に 設定する必要があります。 ここでは、これらのレジスタに設定すべき値を宣言しています。

#pragma CONST_SEG PAGED PAGED_ROM
//==============================================================
//  PORTA drive pattern.
//  Tables must be in a same page.
//==============================================================
const struct {
  byte ptad;
  byte ptadd;
} __paged ptad_pattern[6] = {
//  PTAD        PTADD
    0b00010000, 0b00110001, //  0 4-5
    0b00100000, 0b00110001, //  1 5-4
    0b00000010, 0b00010011, //  2 1-4
    0b00010000, 0b00010011, //  3 4-1
    0b00100000, 0b00100011, //  4 5-1
    0b00000010, 0b00100011  //  5 1-5
};
const byte    PTAD_BLANK  = 0b00000000;
const byte    PTADD_BLANK = 0b00110011;
//                            |||||||+-- BEEPER as OUTPUT
//                            ||||||+--- SEG0 as OUTPUT
//                            |||||+---- START as INPUT
//                            ||||+----- BKGD
//                            ||++------ SEG1,SEG2 as OUTPUT
//                            ++-------- Reserved

#pragma CONST_SEG DEFAULT

さらに"PTAD_BLANK"と"PTADD_BLANK"は、STOP状態などのように LEDを消灯する際のレジスタの設定について定義しています。

リアルタイム割り込みを制御する関数群

Real Time Interrupt (RTI) を扱う際の便利な関数群を定義しています。

//==============================================================
//=  Functions controlling RTI module.
//=
//=  enable_rti : 
//=    Enable the RTI interrupt.
//=    SRTISC is initialized to start RTI counter.
//=
//=  software_rti : 
//=    Enable the RTI but interrupt.
//=    SRTISC is initialized to start RTI counter.
//=
//=  accept_rti : 
//=    Accept an RTI event.
//=    RTIACK is set to clear the RTIF
//=
//=  disable_rti
//=    Disable the RTI interrupt.
//=    Counter is stopped to reduce power.
//=
//==============================================================
void enable_rti(void) {
  SRTISC      = SRTISC_INIT;
  SRTISC_RTIE = 1;
}

void software_rti(void) {
  SRTISC      = SRTISC_INIT;
}

byte detect_rti(void) {
  return SRTISC & SRTISC_RTIF_MASK;
}

void accept_rti(void) {
  SRTISC_RTIACK = 1;
}

void disable_rti(void) {
  SRTISC = SRTISC_DISABLE;
}
リアルタイム割り込みに関するお便利関数
関数名効能
enable_rtiRTI割り込みを受け付け可能にします。
software_rtiRTI機能を有効にしますが、割り込みは発生させません。
detect_rtiRTIイベントが発生した場合TRUEを返します。
accept_rtiRTIイベントフラグをクリアします。
disable_rtiRTI機能を停止します。

これらの関数を使ってリアルタイム割り込みに関する処理を抽象化しています。 RS08では、サブルーチンの呼び出しが深くなると不要な処理が増えるので、 一般にこのような粒度の低い関数は避けたほうが無難であると思われます。

ところが、このように粒度の極端に低い関数は、「インライン展開」と呼ばれる処理によって、 コンパイルの段階で呼び出し側のプログラムに埋め込む事が可能です。 そのため、これらの関数はサブルーチン呼び出しとしてではなく、 マクロがそうであるように単なる呼び出し側プログラムの一部として実装されることになります。

もし、コンパイラに「インライン展開」を行わせるのではなく、 「マクロ」として記述したい場合には以下のような記述も出来ます。

#define enable_rti()   (SRTISC=SRTISC_INIT,SRTISC_RTIE=1)
#define software_rti() (SRTISC=SRTISC_INIT)
#define detect_rti()   (SRTISC_RTIF)
#define accept_rti()   (SRTISC_RTIACK=1)
#define disable_rti()  (SRTISC=SRTISC_DISABLE)

これでも、同じプログラムが生成されますが、 私は「関数」として定義したほうが理解しやすいと考えています。

キーボード割り込みを制御する関数群

KeyBoard Interrupt (KBI) でも RTI と同じように関数群が定義されます。

//==============================================================
//=  Functions controling KBI module.
//=
//=  enable_kbi : 
//=    Enable the KBI interrupt.
//=    KBACK is set to clear the KBF
//=
//=  accept_kbi : 
//=    Accept an KBI event.
//=    KBACK is set to clear the KBF
//=
//=  disable_kbi
//=    Disable the KBI interrupt.
//=
//==============================================================
void enable_kbi(void) {
  KBISC_KBACK = 1;
  KBISC_KBIE = 1;
}

byte detect_kbi(void) {
  return KBISC & KBISC_KBF_MASK;
}

void accept_kbi(void) {
  KBISC_KBACK = 1;
}

void disable_kbi(void) {
  KBISC_KBIE = 0;
}
キーボード割り込みに関するお便利関数
関数名効能
enable_kbiキーボード割り込みを受け付け可能にします。
detect_kbiキーボード割り込みが発生した場合TRUEを返します。
accept_kbiキーボード割り込みフラグをクリアします。
disable_kbiキーボード割り込みを受け付け不可にします。

この関数の場合にも、以下のようなマクロ定義を使用することができます。

#define enable_kbi()  (KBISC_KBACK=1, KBISC_KBIE=1)
#define detect_kbi()  (KBISC_KBF)
#define accept_kbi()  (KBISC_KBACK=1)
#define disable_kbi() (KBISC_KBIE=0)

モジュロ・タイマ割り込みを制御する関数群

Modulo TIMer (MTIM) でも同様に関数群が定義されます。

//==============================================================
//=  Functions controling MTIM module.
//=
//=  enable_mtim : 
//=    Enable the TOF interrupt.
//=    TRST is set to clear the TOF and MTIMCNT.
//=    TSTP is controlled for safety enabling.
//=
//=  accept_mtim : 
//=    Accept an TOF event.
//=    TOF is reset to clear the TOF.
//=
//=  disable_mtim
//=    Disable the TOF interrupt.
//=    TRST is set to reset the timer counter.
//=    TSTP is set to stop the timer counter.
//=
//==============================================================
void enable_mtim(void) {
  MTIMSC = MTIMSC_TOIE_MASK + MTIMSC_TRST_MASK + MTIMSC_TSTP_MASK;
  MTIMSC_TSTP = 0;
}

byte detect_mtim(void) {
  return MTIMSC & MTIMSC_TOF_MASK;
}

void accept_mtim(void) {
  MTIMSC_TOF = 0;
}

void disable_mtim(void) {
  MTIMSC = MTIMSC_TRST_MASK + MTIMSC_TSTP_MASK;
}
モジュロ・タイマ割り込みに関するお便利関数
関数名効能
enable_mtimモジュロ・タイマ割り込みを受け付け可能にします。
detect_mtimモジュロ・タイマ割り込みが発生した場合TRUEを返します。
accept_mtimモジュロ・タイマ割り込みフラグをクリアします。
disable_mtimモジュロ・タイマ割り込みを受け付け不可にします。

この関数の場合にも、以下のようなマクロ定義を使用することができます。

#define enable_mtim() (\
  MTIMSC=MTIMSC_TOIE_MASK+MTIMSC_TRST_MASK + MTIMSC_TSTP_MASK,\
  MTIMSC_TSTP = 0,\
  MTIMMOD = MTIMMOD_3000\
)
#define detect_mtim()  (MTIMSC_TOF)
#define accept_mtim()  (MTIMSC_TOF=0)
#define disable_mtim() (MTIMSC=MTIMSC_TRST_MASK+MTIMSC_TSTP_MASK);

LEDの表示を更新する

ここから、プログラムの本体が始まります。 最初の関数は、put_ledです。 この関数は、 大域変数led_indexとフラグblankの状態によって、 LEDを点灯・消灯します。

//**************************************************************
// code section
//**************************************************************
//-----------------------------------------------------
//  Put on an LED at led_index.
//-----------------------------------------------------
#pragma NO_INLINE
void put_led(void) {
  PTADD = BEEP_MASK;
  if (flag.blank) {
    PTAD  = PTAD_BLANK;
    PTADD = PTADD_BLANK;
  } else {
    PTAD  = ptad_pattern[led_index].ptad;
    PTADD = ptad_pattern[led_index].ptadd;
  }
  
}

この関数には、"#pragma NO_INLINE"という行が追加されており、 インライン展開しないように指定しています。 この関数をインライン展開するとコードが大きくなるため、このような行を加えています。 この関数は、局所変数や作業領域を使用しない単純な構造になっているため、 サブルーチン呼び出しとされてもそれほどコストがかかりません。

圧電スピーカ出力を反転させる

この関数は、必要に応じて圧電スピーカ出力を反転させます。

//-----------------------------------------------------
//  Make a BEEP if required.
//-----------------------------------------------------
void sono_tick(void) {
  if (sono_count > 0) {
    BEEP = ~BEEP;
    sono_count--;
  }
}

圧電スピーカからの発音時間は、変数sono_countで管理されています。 この変数は、関数が呼び出されるたびに減少していき、"0"になったときに音が停止します。

アプリケーションの初期化

この関数では、アプリケーションで必要なモジュールなどの初期化を行っています。

//==============================================================
//=  Genral initialization.
//=
//=  Modules KBI, RTI and MTIM are initialized and ready to be
//=  used.  
//==============================================================
void general_init(void) {
  // Initalize configuration.
  SOPT   = SOPT_INIT;
  SPMSC1 = SPMSC1_INIT;
  // Reload TRIM value.
  ICSTRM_TRIM = ((const NV_ICSTRMSTR * __paged)CONVERT_TO_PAGED(0x00003FFA))->Bits.TRIM;
  ICSSC_FTRIM = ((const NV_FTRIMSTR * __paged)CONVERT_TO_PAGED(0x00003FFB))->Bits.FTRIM;
  // Specify initial position of LED.
  led_index = 0;
  // Enable KBI function on PTA2
  KBIPE_KBIPE2 = 1;
  PTAPE_PTAPE2 = 1;
  KBISC_KBIMOD = 1;
  // Initialize MTIM
  MTIMCLK = MTIMCLK_INIT;
}

この中では、内部発振器のトリム・レジスタの値も設定していますが、 CodeWarriorが提供するヘッダファイルに不備があるため、 このような回りくどい書き方をする必要があります。 ヘッダファイルが修正されたら、以下のようなシンプルな書き方をすることができます。

  ICSTRM_TRIM = NVICSTRM_TRIM;
  ICSSC_FTRIM = NVFTRIM_FTRIM;

ステートマシンの宣言

この後、記述されているステートマシンでは、 それぞれの状態の初期化ルーチンを呼び出して状態遷移を表現しています。 ここでは、それら初期化ルーチンのプロトタイプ宣言を行っています。

//==============================================================
//  State machine declaration.
//    XXX_init() functions are used to make a transition.
//==============================================================
void prologue_init(void);
void wait_init(void);
void stop_init(void);
void pressed_init(void);
void validated_init(void);
void active_init(void);
void epilogue_init(void);

PROLOGUE状態

初期化ルーチンでは、 モジュロ・タイマ割り込みだけを許可して、 処理ルーチンに備えます。 モジュロ・割り込みの周期は、3000Hzに相当する周期です。

//==============================================================
//=  Initialization for PROLOGUE state
//=    KBI  -- disabled not to capture KBI.
//=    RTI  -- disabled.
//=    MTIM -- enabled to detect button released.
//==============================================================
void prologue_init(void) {
  disable_kbi();
  disable_rti();
  enable_mtim();
  MTIMMOD = MTIMMOD_3000;
  sono_count = BEEP_PERIOD;
  flag.blank = 0;
  debounce_count = 0;
  state = ST_PROLOGUE;
}

処理ルーチンでは、 モジュロ・タイマ割り込みにより定期的に押しボタンの状態を確認すると 同時にボタンを押したことを示す3000Hzの短い音を出します。 押しボタンが放された事を検出したときには、WAIT状態に遷移します。 押しボタンの監視には、デバウンス検出カウンタを使用し、 チャッタリングを防止しています。

//==============================================================
//=  PROLOGUE state
//=    Confirm START button released periodically.
//=    Go to WAIT state if START released.
//==============================================================
void prologue_state(void) {
  // Update LED position.
  put_led();
  wait();
  if (detect_mtim()) {
    accept_mtim();
    sono_tick();
    if (!START) {
      // START still pressed.
      debounce_count = 0;
    } else {
      // Count released period
      if (++debounce_count >= DEBOUNCE_PERIOD) {
        wait_init();
      }
    }
  }
}

WAIT状態

初期化ルーチンでは、 キーボード割り込みとリアルタイム割り込みを許可して、 処理ルーチンに備えます。

//==============================================================
//=  Initialization for WAIT state
//=    KBI  -- enabled to wait START button.
//=    RTI  -- enabled to wait TIMEOUT event.
//=    MTIM -- disabled to reduce power.
//==============================================================
void wait_init(void) {
  enable_kbi();
  enable_rti();
  disable_mtim();
  time_count = 0;
  state = ST_WAIT;
}

処理ルーチンでは、キーボード割り込みとリアルタイム割り込みを待ちます。 キーボード割り込みが発生したら、 PRESSED状態に遷移して押しボタンを監視します。 リアルタイム割り込みが発生したら、 時間待ちカウンタを進めます。 この時、ほったらかし時間を経過していたらSTOP状態に遷移します。

//==============================================================
//=  WAIT state
//=    Wait for a START trigger and go to PRESSED state.
//=    Go to PRESSED state if START pressed.
//=    Go to STOP state if wait period expired.
//==============================================================
void wait_state(void) {
  // Update LED position.
  put_led();
  wait();
  if (detect_kbi()) {
    // Detect START button pressed.
    pressed_init();
  } else if (detect_rti()) {
    // Detect periodical event
    accept_rti();
    if (++time_count >= IDLE_PERIOD) {
      stop_init();
    }
  }
}

STOP状態

初期化ルーチンでは、 キーボード割り込みを許可して、 処理ルーチンに備えます。

//==============================================================
//=  Initialization for STOP state
//=    KBI  -- enabled to wake up by START button.
//=    RTI  -- disabled to reduce power.
//=    MTIM -- disabled to reduce power.
//==============================================================
void stop_init(void) {
  enable_kbi();
  disable_rti();
  disable_mtim();
  flag.blank = 1;
  state = ST_STOP;
}

処理ルーチンでは、LEDを消灯して省電力モードに遷移し、 キーボード割り込みを待ちます。 キーボード割り込みが発生したら、 PROLOGUE状態に遷移します。

//==============================================================
//=  STOP state
//=    Go to PROLOGUE state if START button pressed.
//==============================================================
void stop_state(void) {
  // Update LED position.
  put_led();
  stop();
  if (detect_kbi()) {
    prologue_init();
  }
}

PRESSED状態

初期化ルーチンでは、 モジュロ・タイマ割り込みを許可して、 処理ルーチンに備えます。 モジュロ・割り込みの周期は、3000Hzに相当する周期です。

//==============================================================
//=  Initialization for PRESSED state.
//=    KBI  -- disabled.
//=    RTI  -- disabled.
//=    MTIM -- enabled to detect RELEASED timing.
//==============================================================
void pressed_init(void) {
  disable_kbi();
  disable_rti();
  enable_mtim();
  MTIMMOD = MTIMMOD_3000;
  debounce_count = 0;
  time_count = 0;
  sono_count = BEEP_PERIOD;
  state = ST_PRESSED;
}

処理ルーチンでは、 モジュロ・タイマ割り込みにより定期的に押しボタンの状態を確認すると 同時にボタンを押したことを示す3000Hzの短い音を出します。 押しボタンが放された事を検出した時には、 押しボタンが短く押されたと判断して、 LEDの位置を進めてWAIT状態に遷移します。 押しボタンが長押しされた事を検出した時には、 VALIDATED状態に遷移します。

//==============================================================
//=  PRESSED state
//=    Wait for a START released.
//=    Move LED and go to WAIT state if START released.
//==============================================================
void pressed_state(void) {
  // Update LED position.
  put_led();
  wait();
  if (detect_mtim()) {
    accept_mtim();
    sono_tick();
    if (++time_count >= PRESSED_PERIOD) {
      // Validated as START
      validated_init();
    }
    if (!START) {
      // START button still pressed
      debounce_count = 0;
    } else if (++debounce_count >= DEBOUNCE_PERIOD) {
      // RELEASE recognized as MODE change
      if (++led_index >= LED_COUNT) {
        led_index = 0;
      }
      wait_init();
    }
  }
}

VALIDATED状態

初期化ルーチンでは、 モジュロ・タイマ割り込みだけを許可して、 処理ルーチンに備えます。 モジュロ・割り込みの周期は、3000Hzに相当する周期です。

//==============================================================
//=  Initialization for VALIDATED state.
//=    KBI  -- disabled.
//=    RTI  -- disabled.
//=    MTIM -- enabled to detect RELEASED timing.
//==============================================================
void validated_init(void) {
  disable_kbi();
  disable_rti();
  enable_mtim();
  MTIMMOD = MTIMMOD_3000;
  debounce_count = 0;
  sono_count = BEEP_PERIOD;
  state = ST_VALIDATED;
}

処理ルーチンでは、 モジュロ・タイマ割り込みにより定期的に押しボタンの状態を確認すると 同時にボタンを押したことを示す3000Hzの短い音を出します。 押しボタンが放された事を検出したら、 LEDの位置を記録してからACTIVE状態に遷移します。

//==============================================================
//=  VALIDATED state
//=    Wait for a START released.
//=    Go to ACTIVE state if START button released.
//==============================================================
void validated_state(void) {
  // Update LED position.
  put_led();
  wait();
  if (detect_mtim()) {
    accept_mtim();
    sono_tick();
    if (!START) {
      // START still pressed
      debounce_count = 0;
    } else if (++debounce_count >= DEBOUNCE_PERIOD) {
      // RELEASE recognized
      minute_target = led_index + 2;
      active_init();
    }
  }
}

ACTIVE状態

初期化ルーチンでは、 キーボード割り込みと モジュロ・タイマ割り込みを許可して、 処理ルーチンに備えます。 モジュロ・割り込みの周期は、1000Hzに相当する周期です。

//==============================================================
//=  Initialization for ACTIVE state.
//=    KBI  -- enabled to abort by START button.
//=    RTI  -- disabled.
//=    MTIM -- enabled to detect TIMEOUT event.
//==============================================================
void active_init(void) {
  enable_kbi();
  disable_rti();
  enable_mtim();
  MTIMMOD = MTIMMOD_1000;
  minute_count  = minute_target;
  time_count = 0;
  qsecond_count = 0;
  state = ST_ACTIVE;
}

処理ルーチンでは、 キーボード割り込みとモジュロ・タイマ割り込みを待ちます。 キーボード割り込みが発生したら、 LEDの点灯位置を修復してPROLOGUE状態に遷移します。 モジュロ・タイマ割り込みが発生したら、 時間待ちカウンタを進めます。 そして、設定時間が過ぎたら、EPILOGUE状態に遷移します。 ここでは、残り時間に従って、LEDの点灯状態を変更しています。

//==============================================================
//=  ACTIVE state
//=  Wait for a TIMEOUT and START pressed.
//=    Go to PROLOGUE state if START button pressed.
//=    Go to EPILOGUE state if TIMEOUT.
//==============================================================
void active_state(void) {
  // Update LED position.
  put_led();
  wait();
  if (detect_kbi()) {
    // Process aborted by START button.
    // Revert LED to last position.
    led_index = minute_target - 2;
    prologue_init();
  } else if (detect_mtim()) {
    accept_mtim();
    // Occcurs every 1msec.
    if (++time_count >= QSECOND_PERIOD) {
      // Occurs every 1/4 second.
      time_count = 0;
      if (++qsecond_count >= MINUTE_PERIOD) {
        // Occurs every 1 minute.
        qsecond_count = 0;
        if (--minute_count == 0) {
          // Time-up
          // Revert LED to last position.
          led_index = minute_target - 2;
          epilogue_init();
          return;
        }
      }
      // Update LED position.
      if (minute_count <= 1) {
        // LED running last 1 minute.
        led_index = MINUTE_PERIOD - qsecond_count;
        while (led_index >= 6) {
          led_index -= 6;
        }
        flag.blank = 0;
      } else {
        // Blink in 2Hz at left minute LED.
        led_index = minute_count - 2;
        flag.blank = (qsecond_count & 2)?(1):(0);
      }
    }
  }
}

EPILOGUE状態

初期化ルーチンでは、 モジュロ・タイマ割り込みだけを許可して、 処理ルーチンに備えます。 ここでは、処理ルーチンで使用するリアルタイム割り込みタイマも設定しますが、 割り込みは禁止します。 その後、待ち時間に従って、演奏する楽譜を選んで変数にセットします。

//==============================================================
//=  Initialization for EPILOGUE state.
//=    KBI  -- disabled.
//=    RTI  -- enable flag only to handle SONO count.
//=    MTIM -- enabled to detect TIMEOUT event.
//==============================================================
void epilogue_init(void) {
  disable_kbi();
  software_rti();
  enable_mtim();
  // Select a music
  switch (minute_target) {
    case 2: 
      music = music_ballett; break;
    case 3: 
      music = music_noodle; break;
    case 4: 
      music = music_adieu; break;
    case 5: 
      music = music_noodle; break;
    case 6: 
      music = music_noodle; break;
    case 7: 
      music = music_noodle; break;
    default: 
      music = music_noodle; break;
  }
  phrase_point = music;
  sono_point   = *phrase_point++;
  sono_count   = 1;
  flag.quiet   = 1;
  state = ST_EPILOGUE;
}

処理ルーチンでは、 モジュロ・タイマ割り込みにより音の半周期ごとに処理を行います。 また、音を出すためにポートを反転しています。 押しボタンが押されたら、音楽の演奏を止めるためにPROLOGUE状態に遷移します。 もし、リアルタイムイベントが発生していたら、 音の残り時間を確認し、楽譜から新たな音符を選んできます。

//==============================================================
//=  EPILOGUE state
//=  Play MUSIC and wait for START button pressed.
//=    Go to PROLOGUE state if START button pressed.
//=    Play music with RTI and MTIM.
//=    All behavior is implemented in MTIM event handler
//=    to make a clear MUSIC sound.
//==============================================================
void epilogue_state(void) {
  // Update LED position.
  put_led();
  wait();
  if (detect_mtim()) {
    accept_mtim();
    // Reverse BEEP if required.
    if (!flag.quiet) {
      BEEP = ~BEEP;
    }
    // Abort if START pressed.
    if (!START) {
        prologue_init();
    }
    // Update SONO every 8msec
    if (detect_rti()) {
      accept_rti();
      if (--sono_count == 0) {
        // SONO time exhausted.
        if (sono_point->length == 0) {
          // a phrase exhausted.
          if (*phrase_point == 0) {
            // restart when reached to end of MUSIC
            phrase_point = music;
          }
          // Get a phrase.
          sono_point = *phrase_point++;
        }
        if (sono_point->period) {
          // Update MTIM timer setting.
          MTIMMOD    = sono_point->period;
          flag.quiet = 0;
          sono_count = sono_point->length;
        } else {
          MTIMMOD    = MTIMMOD_1000;
          flag.quiet = 1;
          sono_count = sono_point->length;
        }
        sono_point++;
      }
      // Stop last 3*8msec
      if (sono_count < 3) {
        flag.quiet = 1;
      }
    }
  }
}

MAIN関数

メイン関数では、システム全体の初期化を行った後、 状態遷移を行うための無限ループに入ります。

//==============================================================
//=  MAIN loop.
//==============================================================
void main(void) {
  // General Initialization
  general_init();
  
  // Specify the first state
  prologue_init();

  // State machine engine.
  for(;;) {
    switch (state) {
      case ST_PROLOGUE:   prologue_state();   break;
      case ST_WAIT:       wait_state();       break;
      case ST_STOP:       stop_state();       break;
      case ST_PRESSED:    pressed_state();    break;
      case ST_VALIDATED:  validated_state();  break;
      case ST_ACTIVE:     active_state();     break;
      case ST_EPILOGUE:   epilogue_state();   break;
      default:            prologue_init();
    }
  } /* loop forever */
  /* please make sure that you never leave main */
}

PRMファイル

このプログラムをリンクするためには、 通称PRMファイルと呼ばれるファイルを用意しなくてはなりません。

CodeWarriorが提供するひながたファイルでは、 ROM領域が0x3800から0x3FF7まで確保されていたのですが、 このうち0x3800から0x3AFFまでを楽譜などの定数配列を置く PAGED_ROM領域として宣言しました。 従って、ROM領域は0x3B00から0x3FF7までになっています。

/* This is a linker parameter file for the MC9RS08KA2 */

NAMES END /* CodeWarrior will pass all the needed files to the linker by command line. But here you may add your own files too. */

SEGMENTS /* Here all RAM/ROM areas of the device are listed. Used in PLACEMENT below. */
    RESERVED_RAM             =  NO_INIT      0x0000 TO 0x0004;
    TINY_RAM                 =  READ_WRITE   0x0005 TO 0x000D;
    DIRECT_RAM               =  READ_WRITE   0x0020 TO 0x004F;
    ROM_PAGE0                =  READ_ONLY    0x3800 TO 0x383F;
    ROM_PAGE1                =  READ_ONLY    0x3840 TO 0x387F;
    ROM_PAGE2                =  READ_ONLY    0x3880 TO 0x38BF;
    ROM_PAGE3                =  READ_ONLY    0x38C0 TO 0x38FF;
    ROM_PAGE4                =  READ_ONLY    0x3900 TO 0x393F;
    ROM_PAGE5                =  READ_ONLY    0x3940 TO 0x397F;
    ROM_PAGE6                =  READ_ONLY    0x3980 TO 0x39BF;
    ROM_PAGE7                =  READ_ONLY    0x39C0 TO 0x39FF;
    ROM_PAGE8                =  READ_ONLY    0x3A00 TO 0x3A3F;
    ROM_PAGE9                =  READ_ONLY    0x3A40 TO 0x3A7F;
    ROM_PAGE10               =  READ_ONLY    0x3A80 TO 0x3ABF;
    ROM_PAGE11               =  READ_ONLY    0x3AC0 TO 0x3AFF;
    ROM                      =  READ_ONLY    0x3B00 TO 0x3FF7;
//  RESET_JMP_AREA           =  READ_ONLY    0x3FFD TO 0x3FFF; // area defined by RESET 0 below.
END

PLACEMENT /* Here all predefined and user segments are placed into the SEGMENTS defined above. */
    RESERVED                 INTO RESERVED_RAM;
    TINY_RAM_VARS            INTO TINY_RAM;
    DIRECT_RAM_VARS,
    DEFAULT_RAM              INTO DIRECT_RAM, TINY_RAM;
    DEFAULT_ROM              INTO ROM;
    PAGED_ROM                INTO
            ROM_PAGE0,  ROM_PAGE1,  ROM_PAGE2,  ROM_PAGE3,
            ROM_PAGE4,  ROM_PAGE5,  ROM_PAGE6,  ROM_PAGE7,
            ROM_PAGE8,  ROM_PAGE9,  ROM_PAGE10, ROM_PAGE11;
END


STACKSIZE 0x00 /* no stack for RS08 */

VECTOR 0 _Startup /* Reset vector: this is the default entry point for an application. */
2007-10-24 正式公開

Updated: $Date: 2007/10/24 12:32:25 $

Copyright (C) 2007 noritan.org ■