7セグメントLED付き外部記憶装置

シリアル通信を使った外部記憶装置の拡張第一弾です。 外部記憶の内容に従って、7セグメントLEDを点灯させます。

完成写真 こげこげ君

アジェンダ

ハードウェア

このアプリケーションは、 DEMO9S08QG8評価ボードに 二桁表示太陽計と 同じように7セグメントLEDを2個追加して、 ダイナミック方式で点灯させます。

回路図

アプリケーションに必要な部分の回路は、 以下のようになっています。

外部記憶装置回路図(STEP2)

マイコンの右の部分が拡張した部分です。 よく見比べるとわかりますが、 二桁表示太陽計とは ポートの使い方が若干異なっています。 外部記憶装置と 同様にトランシーバチップをMAX3232で置き換えて表現しています。

必要な部品は、以下の通りです。

外部記憶装置(STEP2)部品表
品名型番個数調達先
評価ボードDEMO9S08QG81Freescale
ブレッドボードEIC-8012秋月電子
7セグメントLEDLN516RA2秋月電子
トランジスタ2SA10152千石電商
抵抗220Ω 1/6W8秋月電子
抵抗1kΩ 1/6W2秋月電子
両端オスピンソケット6604P-20G-1211秋月電子

部品表は、 二桁表示太陽計と 全く同じです。

ブレッドボード

出来上がったブレッドボードもほとんど同じですね。

仕様

このアプリケーションは、以下のような仕様で動作します。

外部記憶の内容がLEDに表示される

基本的なコンセプトは、 「シリアル通信で会話が出来、記憶した内容がLEDに表示される」ことです。 会話の内容は、STEP1と同じ文法を使います。

外部記憶とLED表示の対応

このアプリケーションでは、 アドレス"0"の内容を左のLEDに、アドレス"1"の内容を右のLEDに表示します。

各ビットとLEDのセグメントの関係は、 数値表示太陽計と 同じとします。

プロジェクト作成

最初にCodeWarriorで新規プロジェクトを以下の手順で作成します。 表示されるダイアログなどの詳細は、 外部記憶装置を 参照してください。 ここでは、V5.0を使って説明します。

CodeWarriorの起動
スタートメニューからCodeWarriorを起動します。
"Startup"ダイアログの呼び出し
"Startup"ダイアログが出てこない場合には、 メニューから"File → Startup Dialog..."を選びます。
"HC(S)08 New Project"ダイアログを開く
"Create New Project"(新しいプロジェクトを作る)をクリックします。
"Project Parameters"(プロジェクトのパラメータ)の設定
使用する言語"C"
Project Name(プロジェクト名)Extmemory2.mcp
Location(プロジェクトを作成するディレクトリ) C:\Projects\CW\Extmemory2

「次へ」ボタンをクリックします。

"Device and Connection"(デバイスと接続方法)の設定
デバイスHCS08 → HCS08QG Family → MC9S08QG8
デフォルトの接続方法P&E Multilink/Cyclone Pro

「次へ」ボタンをクリックします。

"Add Additional Files"(追加するファイル)の設定

追加するファイルは無いので、そのまま「次へ」ボタンをクリックします。

"Processor Expert"(プロセッサエキスパート)の設定

"Processor Expert"を選んで「次へ」ボタンをクリックします。

"C/C++ Options"の設定
startup codeANSI startup code
memory modelSmall
floating point formatNone

「次へ」ボタンをクリックします。

"PC-Lint"の設定
"No"をチェックして「完了」ボタンをクリックします。
パッケージを選ぶ
"Select CPUs"(CPUを選ぶ)ダイアログで "MC9S08QG8_16"だけを選択して「OK」ボタンをクリックします。
使用する設定を選ぶ
"Select Configurations"(設定を選べ)ダイアログで "Debug"だけを選択して「OK」をクリックします。

以上で、プロセッサエキスパートを使用する 新規プロジェクトができました。

新規プロジェクト完成

外部記憶装置を再現する

このアプリケーションは、 外部記憶装置を 改造することで実現します。

リソースの再現

まず、 外部記憶装置で 作成したリソースを再現します。

シリアル通信の設定

シリアル通信は、以下の手順で設定します。

非同期シリアルビーンの設定
"Bean Selector"から "CPU Internal Peripherals → Communication → AsynchroSerial"を ダブルクリックで呼び出し、 以下のプロパティを設定します。
非同期シリアルの設定
項目名設定する値
Baud rate(ボーレート) 9600

コードの生成

次は、Processor Expertにソースコードを生成させます。

コード生成する
Makeボタンを クリックするとコードが生成されます。

コードの再現

最後にメインプログラムを再現します。

テキストエディタを開く

"Project Panel"ウィンドウの "User Modules → Extmemory2.c:main"を ダブルクリックすると"Extmemory2.c"ファイルの テキストエディタが開きます。

関数部分の転記

ここでは、簡単に再現するために以下のリンクに 外部記憶装置の プログラムを用意しました。 このファイルの内容をvoid main(void)の宣言の 前にコピー&ペーストします。

STEP1で作成したプログラム

まず、上のリンクをクリックしてテキストファイルを開きます。 WEBブラウザで開く場合には、別のウィンドウで開くのがお勧めです。

開いたテキストのすべてを選択し、コピーします。 ブラウザにもよりますが、一般には"Ctrl+A"と"Ctrl+C"で可能です。

最後に"Extmemory2.c"ファイルのテキストエディタの中の void main(void)と書いてある行を探して、 そのすぐ上の行にカーソルを移動し、 "Ctrl+V"でペーストするとvoid main(void)以外の 部分は再現されます。

メイン関数の転記

プログラムの再現の最後として、 void main(void)関数の中身を転記します。 以下のプログラムを"Extmemory2.c"ファイルの /* Write your code here */と書いてある 下に書き込みます。

  for (;;) {
    interpret();
  }

以上でプログラムは、再現できました。

再構築

コード生成で行った時と同様に Makeボタンを クリックするとコードが生成されます。 ここで、コンパイルエラーなどが発生しなければ 外部記憶装置の 再現は完了です。

リソース再設定

ここからは、7セグメントLED出力を扱うための改造を行います。 まずは、"Processor Expert"のリソースの再設定からです。

セグメントドライバの設定

7セグメントLEDのカソードに接続される端子を 「セグメントドライバ」と表現します。 ここでは、セグメントドライバにかかわるビーンの設定を行います。

ビーンとセグメントの対応

数値表示太陽計の 時には、ポートBすべてがセグメントドライバに割り当てられていましたが、 このアプリケーションでは、PTB1, PTB0がシリアル通信に使用されているため、 使用することができません。 このため、以下のようにセグメントを二つのビーンに分けます。 また、ビーン同士を区別するために名前をつけます。

セグメントドライバの分割
ビーン端子名接続先セグメント
SegHiPTB7dp
PTB6g
PTB5f
PTB4e
PTB3d
PTB2c
SegLoPTA1b
PTA0a

ソフトウェアを簡単にするためにこのような分割方法をとっています。

上位セグメントドライバ(SegHi)の設定

上位セグメントドライバには、"BitsIO"ビーンを使います。 "Bean Selector"から "CPU Internal Peripherals → Port I/O → BitsIO"を ダブルクリックで呼び出し、 以下のプロパティを設定します。

上位セグメントドライバの設定
項目名設定する値
Port(ポート) PTB
Pins(ピン数) 6
Pin0 → Pin(0番目のピン) PTB2_KBIP6_SPSCK_ADP6
Pin1 → Pin(1番目のピン) PTB3_KBIP7_MOSI_ADP7
Pin2 → Pin(2番目のピン) PTB4_MISO
Pin3 → Pin(3番目のピン) PTB5_TPMCH1_SS
Pin4 → Pin(4番目のピン) PTB6_SDA_XTAL
Pin5 → Pin(5番目のピン) PTB7_ACL_EXTAL
Direction(信号の方向) Output(出力)

以上の設定を行った時点では、 ビーンは"Bits1"という名前になっているはずです。 名前は、左端の"Project Panel"で確認することができます。 これを"SegHi"という名前に変更します。

ビーン名変更前

"Bits1"ビーンの表示の所で右クリックすると、 コンテキストメニューが現れます。 メニューから"Rename Bean"(ビーンを改名する)を 選ぶと名前を変更することが できるようになります。 "SegHi"とタイプして"Enter"キーを押すと名前が変更されます。

ビーン名変更後

これで、上位セグメントドライバの設定は終了です。

下位セグメントドライバ(SegLo)の設定

下位セグメントドライバにも"BitsIO"ビーンを使います。 "Bean Selector"から "CPU Internal Peripherals → Port I/O → BitsIO"を ダブルクリックで呼び出し、 以下のプロパティを設定します。

下位セグメントドライバの設定
項目名設定する値
Port(ポート) PTA
Pins(ピン数) 2
Pin0 → Pin(0番目のピン) PTA0_KBIP0_TPMCH0_ADP0_ACMPPLUS
Pin1 → Pin(1番目のピン) PTA1_KBIP1_ADP1_ACMPMINUS
Direction(信号の方向) Output(出力)

以上の設定を行った後、"SegHi"ビーンで行ったように、 ビーンの名前を"SegLo"に変更します。 これで、下位セグメントドライバの設定は終了です。

桁ドライバの設定

7セグメントLEDのアノードに接続されるトランジスタを 制御する端子を「桁ドライバ」と表現します。 ここでは、桁ドライバにかかわるビーンの設定を行います。

十の位ドライバ(Dig1)の設定

十の位ドライバには、"BitIO"ビーンを使います。 "Bean Selector"から "CPU Internal Peripherals → Port I/O → BitIO"を 選んでダブルクリックで呼び出し、 以下のプロパティを設定します。

十の位ドライバの設定
項目名設定する値
Pin for I/O(端子名) PTA3_KBIP3_SCL_ADP3
Direction(信号の方向) Output(出力)
Initialization → Init. value(初期値) 1

初期値は、リセット直後にLEDが点灯しないように"1"に設定します。 以上の設定を行った後、ビーンの名前を"Dig1"に変更します。 これで、十の位ドライバの設定は終了です。

一の位ドライバ(Dig2)の設定

一の位ドライバにも"BitIO"ビーンを使います。 "Bean Selector"から "CPU Internal Peripherals → Port I/O → BitIO"を 選んでダブルクリックで呼び出し、 以下のプロパティを設定します。

一の位ドライバの設定
項目名設定する値
Pin for I/O(端子名) PTA2_KBIP2_SDA_ADP2
Direction(信号の方向) Output(出力)
Initialization → Init. value(初期値) 1

この初期値もリセット直後にLEDが点灯しないように"1"に設定します。 以上の設定を行った後、ビーンの名前を"Dig2"に変更します。 これで、一の位ドライバの設定は終了です。

RTIタイマの設定

二桁表示太陽計では、 ダイナミック点灯の切り替えのための待ち時間を for文によるループで作っていました。 今回は、"RTI"と呼ばれる機能を使って待ち時間を作ります。

RTIとは、何か?

RTIは、"Real-Time Interrupt"(実時間割り込み)の略です。 このモジュールは、 一定の間隔で時間が経過したことを知らせるモジュールです。 マイコンの中には、 一定の時間間隔で動作するモジュールが多く入っているのですが、 このモジュールは、その中でも比較的長い間隔を扱うことが出来ます。 その反面、細かい時間の設定はできません。

ここでは、RTIを割り込みを使用せずに使います。 RTIは、その名のとおり割り込みを起こすモジュールですが、 割り込みを発生させずに使用することも可能です。

でも、ご安心ください。 この記事の終わりの方では、 RTIの割り込みを使う方法まで記述していますので、 「今すぐに割り込みが使いたい」という方の要求を満たすことが できると思います。

RTIの初期設定ビーンを使う

実は、割り込みを使わずRTIを直接扱うビーンは存在しません。 RTIに関連したビーンはすべて割り込みを前提として作られています。

そこで、ここでは、RTIモジュールの初期設定だけを Processor Expertにやらせます。 初期設定を行うビーンは、"Init_RTI"です。 "Bean Selector"から "CPU Internal Peripherals → Peripheral Initialization Beans → Init_RTI"を 選んでダブルクリックで呼び出し、 以下のプロパティを設定します。

RTI初期設定ビーンの設定
項目名設定する値
Clock settings → Prescaler(クロック分周比) 256

このビーンは、時間を与えて設定を決めさせることができません。 その代わり、"Prescaler"に"256"を設定すると、 その下の"Period"(周期)欄に設定時間が表示されます。

RTIの周期設定

これで、8ミリ秒ごとにLEDの表示を切り替える仕掛けが準備できました。

再びコード生成する

ビーンの再設定が出来たので、 コードを生成させます。 Makeボタンを クリックするとコードが生成されます。 コンパイルエラーなどが無ければ、いよいよプログラミングです。

この時に以下のようなダイアログが出る場合があります。

ソースコード再ロードの確認

このファイル(Extmemory2.c)は、ソースエディタ以外の場所で 変更されました。 ソースエディタにファイルを再ロードしますか?

このメッセージは、 ビーンの設定を変更したためにソースコードが書き換えられ、 結果としてソースエディタに表示されているファイルの内容と 合わなくなったということを示しています。

このメッセージを出さなくするためには、 コード生成をさせる前にソースエディタを閉じれば良いのですが、 ソースコードを開いたままコード生成をしてしまうことも良くあります。

このダイアログに対して「No」をクリックすると ビーンの状態とソースコードの応対に矛盾が起きますので、 必ず「Yes」をクリックします。

また、プログラム開発の途中でビーンを再設定するような場合には、 ソースコードをProcessor Expertが書き換える事がありますので、 必ず、Processor Expertの流儀に従った記述をするように 心がけてください。

筆者もProcessor Expertの使い始めの頃には、 この機能のおかげで、 せっかく書いたソースコードを何度も飛ばしてしまいました。

表示プログラムの追加

ここから、表示プログラムを追加していきます。 でも、どこに追加すれば良いのでしょうか。

外部記憶装置のシーケンス

実は、外部記憶装置のプログラムは、 CPUをいつでも100%使用しているので、 LEDに表示をしている暇など無いのです。 このセクションでは、外部記憶装置のCPUの使い方について考えて見ます。

コマンド受信のシーケンス

下の図は、コマンドを受信している時のシーケンスです。 左右の「受信」と「送信」がシリアルインターフェースの ハードウェアの処理時間を示し、 中央の三つがそれぞれの関数の処理時間を示しています。

シリアル通信からコマンドが到着する時間間隔に比べて、 コマンド処理の時間が圧倒的に短いので、 全体を見た時には、getc(void)関数で入力を 待っている時間がほとんどを占めます。 この事から、getc(void)関数の中で 文字入力を待っている箇所に表示プログラムを入れれば良さそうです。

ダンプコマンド実行のシーケンス

実は、getc(void)関数に表示プログラムを 入れただけでは、ダンプコマンドの実行時に問題が発生しました。 下の図は、ダンプコマンドを実行している時のシーケンスです。

ダンプコマンドは、コマンドを受信した後、 外部記憶の内容をシリアル通信で送り出します。 この時には、新たなコマンド入力は受け付けていません。 このため、この期間はgetc(void)関数の実行が発生せず、 ダンプコマンドを実行すると片方しか表示されない症状があらわれました。

このシーケンスでは、putc(char)関数で 送信バッファが空になるのを待っている時間がほとんどを占めます。 この事から、putc(char)関数の中で 送信待ちをしている箇所に表示プログラムを入れれば良さそうです。

シーケンスを表現する図

ここでシーケンスを表現するために使用した図の形式を その名も「シーケンス図」といいます。 元は、UMLというソフトウェアを表現する規格で定められたものですが、 ここではハードウェアとソフトウェアの相互作用を表すために 使用しています。

本来は、それぞれの縦軸は一つのオブジェクトに相当させるなどの 決まりがあるのですが、 この図では、一つの関数をオブジェクトと見立てています。

シーケンス図を検討した結果、二つの関数の待ち時間に表示プログラムを 挿入すれば良さそうだという事がわかりました。 次は、挿入すべき表示関数を作成しましょう。

表示関数

二桁表示太陽計では、 表示プログラムのループの中に測定プログラムと時間待ちプログラムが 含まれていました。 今回は、反対にシリアル通信プログラムの中に表示プログラムが 取り込まれてしまいます。 更に、表示プログラムは、二つの関数 getc(void)putc(char)の中で 呼び出されるため、独立した関数として定義することにします。

これらの関数の宣言は、 配列memory[16]の宣言の直後に入れます。

表示関数の宣言

状態変数 : digit

表示プログラムは、一定時間間隔を知らせてくれる RTIの発生によって、以下のような状態遷移を起こします。

この二つの状態を表現するために変数digitを 宣言します。

/**************************************************************
*  digit    : a digit number to be shown.
**************************************************************/
byte digit = 0;

変数digitは、 十の位の表示を行っているときには"1"になり、 一の位の表示を行っているときには"2"になります。

表示更新関数 : showLED(void)

この関数は、 変数digitによって決定された桁の LEDを表示します。

/**************************************************************
*  Show a digit of LEDs.
**************************************************************/
void showLED(void) {
  switch (digit) {
    case 1:                       // (A)  Process digit-1
       :
      break;                      // (B)  End of digit-1 processing.
    case 2:                       // (C)  Process digit-2
       :
      break;                      // (D)  End of digit-2 process.
    default:                      // (E)  Otherwise should not occur.
      digit = 1;                  // (F)  Process digit-1 next.
  }
}

(A)と(B)で十の位の表示を行い、 (C)と(D)で一の位の表示を行っています。 また、(E)と(F)は、その他の場合の処理を行っています。 下に省略されたそれぞれの詳細が、記述されています。

      Dig2_SetVal();              // (A1) Turn digit-2 off.
                                  // (A2) Set segements for digit-1.
      SegHi_PutVal(~(memory[0] >> 2));
      SegLo_PutVal(~(memory[0] & 0x03));
      Dig1_ClrVal();              // (A3) Turn digit-1 on.
      digit = 2;                  // (A4) Process digit-2 next.

十の位の表示部分では、まず、(A1)で一の位の表示を消しています。 "Dig2"ビーンの"SetVal()"メソッドを使うと、一の位のコモンアノードに 電流が流れなくなります。

次に(A2)でセグメントの値を決定しています。 セグメントの値に使用されるのは"0"番地のメモリ内容です。 ただし、このアプリケーションでは、セグメントが二つのビーンに 分かれてしまったため、二回に分けて出力値を設定しなくてはなりません。

セグメントデータの設定について

セグメントデータは、8ビットの値で以下のように割り当てられ、 いずれのビットも"1"の時にLEDが点灯するようにプログラムされます。

これらのデータは、リソース再設定で 述べたように上位6ビットと下位2ビットの二つのビーンに分けて 利用されます。 また、ポート出力が"0"のときに点灯するようになっているため、 データの反転が必要です。

まず、上位6ビットですが、以下のように右に2ビットシフトした上で、 反転してビーンに渡します。

わざわざシフトしているのは、 SegHiビーンがLSB側に寄せたデータを期待しているからです。

これに対して、SegLoの場合には少し簡単です。 以下のように下2ビットをマスクした上で、 反転してビーンに渡します。

本当は、ユーザのプログラムでマスクしなくても、 データを受け取るビーンがマスクしてくれるのですが、 データが不必要であることを明示するためにマスクをしています。

(A3)では、十の位のコモンアノードに電流を流します。 これには、"Dig1"ビーンの"ClrVal()"メソッドを使います。

最後の(A4)で状態遷移を起こします。 十の桁の表示ができたら、次は、一の位を表示するために 状態変数digitを"2"に更新します。

      Dig1_SetVal();              // (C1) Turn digit-1 off.
                                  // (C2) Set segments for digit-2.
      SegHi_PutVal(~(memory[1] >> 2));
      SegLo_PutVal(~(memory[1] & 0x03));
      Dig2_ClrVal();              // (C3) Turn digit-2 on.
      digit = 1;                  // (C4) Process digit-1 next.

一の位の表示部分も、十の位と同様です。 (C1)で一の位の表示を消すために、十の位のコモンアノードに 流れる電流を止めます。

次に(C2)でセグメントの値を決定しています。 セグメントの値に使用されるのは"1"番地のメモリ内容です。

(C3)では、一の位のコモンアノードに電流を流します。

最後の(C4)で状態遷移を起こします。 一の桁の表示ができたら、次は、十の位を表示するために 状態変数digitを"1"に更新します。

RTI検出関数 : showIfRTI(void)

この関数は、RTIが発生した場合にshowLED(void)関数を 呼び出す関数です。 RTIの発生を検出する機能まで取り込んでしまったため、 この関数を適宜埋め込むだけで、 一定時間間隔ごとに表示の更新が出来ます。

/**************************************************************
*  Show a digit if RTI occurs.
**************************************************************/
void showIfRTI(void) {
  if (SRTISC_RTIF) {              // (A) Check if RTI occurs.
    SRTISC_RTIACK = 1;            // (B) Clear RTIF.
    showLED();                    // (C) Show a digit of LED.
  }
}

この関数では、Processor Expertのメソッドを使うのではなく、 RTIのレジスタを直接操作しています。 (A)では、SRTISCレジスタのRTIFフラグによってRTIの発生を 確認しています。 もし、RTIが発生していなければ、 この関数は即座に呼び出し元に制御を移します。

RTIが発生していた場合には、 RTIFフラグをクリアしてRTI発生による処理が受け付けられた事を示すために、 (B)でSRTISCレジスタのRTIACKビットに"1"を書き込みます。

(C)では、上で定義したshowLED(void)関数を 呼び出して表示する桁を変更します。

送受信関数の改造

待ち時間に実行すべき関数が出来ましたので、 送受信関数に挿入します。

一文字受信関数の改造 : getc(void)

一文字受信関数の改造は、以下の通りです。

BEFORE
char getc(void)
{ 
  AS1_TComData c; 
  
  while (AS1_RecvChar(&c) != ERR_OK) ;
  return (char)c;
}
AFTER
char getc(void)
{ 
  AS1_TComData c; 
  
  while (AS1_RecvChar(&c) != ERR_OK) showIfRTI();
  return (char)c;
}

待ち時間は、while文で費やされますので、 この文の中で処理関数を呼び出します。

一文字送信関数の改造 : putc(char)

一文字送信関数の改造箇所は、以下の通りです。

BEFORE
void putc(const char c) {
  while (AS1_SendChar((AS1_TComData)c) != ERR_OK) ;
}
AFTER
void putc(const char c) {
  while (AS1_SendChar((AS1_TComData)c) != ERR_OK) showIfRTI();
}

この場合も、待ち時間を費やしている while文の中で処理関数を呼び出します。

アプリケーションの確認

プログラムが完成したら、テストしてみます。

マイコンにプログラムを書き込む

アプリケーションの実行のために マイコンにプログラムを書き込みます。

ジャンパを設定する

評価ボードをPCにつなぐ前に 評価ボード上のジャンパを設定しなくてはなりません。 この設定は、評価ボード上のRS-232Cトランシーバを利用可能な 状態にするためと 評価基板上でアナログ信号線に接続されている PTA1とPTA0を開放するために必要です。

COM_ENジャンパの設定

まず、基板表面にある"COM_EN"と表示されたジャンパをはずします。 私は、はずしたジャンパが行方不明にならないように ピンヘッダの真ん中のピンにジャンパを残しています。

USER_ENジャンパの設定

次に、基板表面にある"USER_EN"と表示されたジャンパのうち、 LED1, LED2, RV1, RZ1の四つをはずします。 これも、はずしたジャンパが行方不明にならないように ピンヘッダの片側のピンにジャンパを残しています。

DEMO9S08QG8評価ボードをPCに接続する

DEMO9S08QG8評価ボードをUSBケーブルで接続します。

デバッガ兼書き込みプログラムを呼び出す

ドロップダウンリストが"P&E Multilink/Cyclone Pro"になっているのを 確認して、 Debugアイコンを クリックするとコンパイル、リンクの後、デバッガが立ち上がります。

デバッガの呼び出し

USBインターフェースの設定

接続方法の指定

USBインターフェースの設定を確認したら、 "Connect"をクリックします。

マイコンに書き込む

消去確認

「Yes」をクリックして、マイコンに書き込みます。

ターミナルソフトを立ち上げる

ターミナルソフトは、 外部記憶装置で作成した設定ファイルが そのまま使えます。 設定ファイルは、 WindowsXPでは、 「スタート → すべてのプログラム → アクセサリ → 通信 → ハイパーターミナル → 9600-8N1」 に残っています。 これを選択するとハイパーターミナルが起動します。

シリアルケーブルをつなぐ

次にシリアルケーブルをつなぎます。

シリアルケーブルをつなぐ

これで、すべての準備が整いました。

動作テスト

ここから、LEDの表示の様子をテストしていきます。

ツールボタンの Start/Continueアイコンを クリックして、マイコンのプログラムを起動します。 すると、プロンプト*が表示されます。

*

LEDは、消灯したままです。

消灯状態

次に、アドレス"0"に"76"を書き込むため、 W0:76<Enter>とタイプします。

*W0:76
0:76
*

すると、左のLEDに"H"が表示されます。

H点灯

続けて、アドレス"1"に"39"を書き込むため、 W1:39<Enter>とタイプします。

*W1:39
1:39
*

すると、右のLEDに"C"が表示されます。

HC点灯

更に、 アドレス"0"に"5C"を アドレス"1"に"7F"を書き込みます。

*W0:5C
0:5C
*W1:7F
1:7F
*

すると、LEDには、"08"が表示されます。

08点灯

最後に D<Enter>とタイプして ダンプコマンドを実行し、 LEDの表示がちらつかないことを確認しましょう。

*D
:5C:7F:00:00:00:00:00:00:00:00:00:00:00:00:00:00
*

ちらつきを実感する

この記事では、 はじめから二つの関数の双方にLED表示関数を挿入したため、 LEDがちらつく様子を観察できていません。 余力があれば、 putc(char)関数から 表示関数showIfRTI(void)の呼び出しを 削除して、 ダンプコマンドを実行したときに表示がどのようになるかを 実感してみてください。

美しくないぞ

以上で、LED付きの記憶装置の出来上がりです。 でも、何だか美しくありません。

  1. シーケンスを解析しないと表示が出来ない。

    表示プログラムを挿入する箇所を突き止めるために シーケンス図を描かなくてはなりませんでした。 しかも、考えられるすべてのシーケンスを網羅しないと 必要なすべての箇所に挿入できたかがわかりません。 バグが発生する典型的なパターンです。

  2. 表示と通信は、関係ないはずなのに。

    例えば、このプログラムからシリアル通信の部分だけを 他のアプリケーションで再利用したいと思ったとします。 ところが、、シリアル通信プログラムの深いところに 表示プログラムが食い込んでしまっているので、 このままでは、必要の無い表示プログラムまで持っていくことに なりかねません。

    このため、表示プログラムを削除した上で再利用しなくては ならないのですが、どこからどこまでが通信のプログラムかが あいまいだと、表示プログラムが残る可能性を棄て切れません。 これもバグが発生するパターンになります。 再利用する際は、手を加えないのが一番です。

この問題を解決する一つの方法が、「割り込み」です。 次のセクションからは、このアプリケーションを「割り込み」を 使って書き直します。

割り込みの概念

まず、割り込みとは何かという事からはじめます。 下の図は、上で紹介したダンプコマンドのシーケンスです。

この中のputc(char)関数に表示関数を 入れることによって、表示の変更が行えるようになったのが、 以下の図です。

この図を見るとわかるように、 表示が更新されるのは、putc(char)が実行されている 時に限ります。 例えば、interpret(void)関数が実行されている時には 表示の更新は、行いません。

割り込みとは、 どの関数が実行されている時でも他の関数、例えば表示更新ルーチンを 呼び出すことができる仕掛けです。 以下のように、メインルーチンの実行中、いつでも好きなときに 割り込み処理を行うことができます。

割り込みを発生させるきっかけを作ったのは、ハードウェアであるRTIです。 RTI割り込みを使うと、どんな関数を実行している時でも 8msecごとに決められた処理を行う事ができます。

割り込みが発生したときに実行される関数には、 割り込み処理(Interrupt Service Routine : ISR)イベントハンドラの二種類があります。 ISRは、Processor Expertがコード生成した関数で、 割り込み判別フラグをクリアし、 イベントハンドラに制御を渡します。 一方、 イベントハンドラは、Processor Expertで作成されたテンプレートを アプリケーションの作成者が書き直して準備します。

リソース再再設定

RTI割り込みを使うために、 まず、Processor Expertに戻ってビーンの再再設定を行います。

RTI初期設定ビーンを削除する

これまでは、フラグを参照することにより、 RTIモジュールを使用していましたが、 これからは「割り込み」を使用します。 そのため、RTI初期設定ビーンは、使用しません。

"Project Panel"の"Beans → RTI1:Init_RTI"を選んで、 右クリックするとコンテキストメニューが現れます。

RTI初期設定ビーンの削除

メニューから "Remove Bean from Project"(プロジェクトからビーンを削除する)を クリックすると削除の確認を求めるダイアログが表示されます。

ビーン削除の確認

本当に選択しているビーンを削除したいのか?

ここで、「Yes」ボタンをクリックすると RTI初期設定ビーンは削除されます。

周期割り込みタイマの設定

RTI初期設定ビーンの代わりに周期割り込みタイマビーンを設定します。 "Bean Selector"から "CPU Internal Peripherals → Timer → TimerInt"を 選んでダブルクリックで呼び出し、 以下のプロパティを設定します。

周期割り込みタイマの設定
項目名設定する値
Timer(使用するタイマ) RTIfree(RTIをフリーランニングで使用)
Interrupt period(割り込みの周期) 8ms

再びコード生成する

ビーンの再再設定が出来たので、 コードを生成させます。 Makeボタンを クリックするとコードが生成されます。 コンパイルエラーなどが無ければ、いよいよ最後のプログラミングです。

割り込みを使ってプログラムする

いよいよ、最後のコーディングです。

割り込み処理ルーチンを書く

コード生成の結果、"Project Panel"に "User Modules → Events.c:event"という 新しいファイルができました。

イベントファイル

このファイルの中に イベントハンドラ関数の テンプレートが記述されています。

イベントハンドラ

ここに、表示更新関数の呼び出しを書けば出来上がりです。

void TI1_OnInterrupt(void)
{
  /* Write your code here ... */
  extern void showLED(void);
  showLED();
}

extern宣言とは何だろう

showLED(void)関数は、"Extmemory2.c"ファイルで 定義された関数です。 この関数を"Events.c"ファイルで使うときには、 showLED(void)関数がどのような引数をとり、 どのような型の値を返すのかという情報を与えなくてはなりません。 このための宣言がextern宣言です。

extern宣言は、もちろん関数定義の外で行っても かまいませんが、 ここでは、「使うところに近い場所」ということで TI1_OnInterrupt(void)関数の中で 宣言しています。

一文字送受信関数を元にもどす

一文字送受信関数にshowIfRTI(void)関数の 呼び出しを追加しましたが、 割り込みによって処理をするので必要なくなりました。 よって、以下のように削除します。

BEFORE
char getc(void)
{ 
  AS1_TComData c; 
  
  while (AS1_RecvChar(&c) != ERR_OK) showIfRTI();
  return (char)c;
}
AFTER
char getc(void)
{ 
  AS1_TComData c; 
  
  while (AS1_RecvChar(&c) != ERR_OK) ;
  return (char)c;
}
BEFORE
void putc(const char c) {
  while (AS1_SendChar((AS1_TComData)c) != ERR_OK) showIfRTI();
}
AFTER
void putc(const char c) {
  while (AS1_SendChar((AS1_TComData)c) != ERR_OK) ;
}

さらにshowIfRTI(void)関数も 使われなくなりましたので、削除してもかまいません。 ここでは、存在していても害にはならないので、 そのまま残すことにします。

アプリケーションの再確認

以上で完成です。 アプリケーションの確認手順は、 アプリケーションの確認と 全く同じです。

課題

他のマイコンとつなぐ

このアプリケーションは、決められたプロトコルにしたがって、 シリアル通信を行うので、 ターミナルソフトの代わりに他のマイコンと通信を 行うことが出来ます。 測定係マイコンからデータを受信した表示係マイコンが表示するような アプリケーションに使えます。

他の入出力にも使いたい

今回は、二桁のLEDに出力を出しましたが、 他の表示デバイスなどに出力することも考えられます。 また、入力デバイスをつなぐことも考えられます。 さて、どんなデバイスとつなぎましょうか。

2006-11-12 「セグメントデータの設定について」を加筆。

2006-03-16 発行。

Updated: $Date: 2006/11/12 03:56:26 $

Copyright (C) 2006 noritan.org ■