外部記憶装置

DEMO9S08QG8評価ボードのシリアル通信機能を使って、 外部記憶装置を作ってみました。 で、この外部記憶って、何の役に立つのだろう。

完成写真、ってこれだけ? こげこげ君

アジェンダ

ハードウェア

このアプリケーションは、 DEMO9S08QG8評価ボードのみ必要とします。

回路図

アプリケーションに必要な部分の回路は、 以下のようにシリアルインターフェース部分だけです。

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

実際の評価ボードは、 MAX3218というスイッチングレギュレータ内蔵の トランシーバチップを使っていますが、 ここでは簡単にMAX3232で置き換えて表現しています。

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

外部記憶装置(STEP1)部品表
品名型番個数調達先
評価ボードDEMO9S08QG81Freescale

仕様

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

シリアル通信で会話が出来る

基本的なコンセプトは、 「シリアル通信で会話が出来る」ことです。 会話といっても、もちろん普通の言葉を理解することはできません。 ある文法に従った「コマンド」をシリアル通信で入力し、解釈して、 それに対する反応をシリアル通信で返します。

外部記憶の概念

ここで想定している外部記憶は、 8ビットのデータが16個並んだものです。

16個のデータのうち、 どのデータを利用するかを決定するために 「アドレス」を指定することができます。

16個のデータを区別するために、 「アドレス」は、0から15までの数値をとります。 しかし、十進数を扱うのはプログラムには難しいので、 「アドレス」は、16進表記であらわします。 また、同様の理由で「データ」についても同様に16進表記で表現します。

<16進数文字> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
<16進数> ::= { <16進数文字> }..

コマンド

このアプリケーションでは、三種類のコマンドを扱います。

<コマンド> ::= <ダンプコマンド> | <読み出しコマンド> | <書き込みコマンド>

外部メモリからは、 コマンドの入力を促すためのプロンプトが出力されます。 このプロンプトは、コマンドの入力が開始されたことを示しています。

<プロンプト出力> ::= "*"

それぞれのコマンドは、以下のセクションで述べる文法が定められており、 文法から外れた入力がされた場合には、 すぐに入力中のコマンドはキャンセルされコマンドの入力開始状態に戻ります。

ダンプコマンド(D)

メモリの内容を一括で表示する機能を「ダンプ」と呼んでいます。 この外部メモリでは、"D"というコマンドを受け取ると メモリの内容を一括表示します。

キーボードから"D"を打っただけで表示をさせても良いのですが、 他のコマンドとの兼ね合いから、 "D"に続いて"Enter"キーを押したときにコマンドが終了するものとします。

<ダンプコマンド> ::= "D" <Enter>

内容の一括表示は、":"で区切られた16個の16進データの羅列で表現します。

<ダンプ出力> ::= { ":" <16進数> }.. <行末>

読み出しコマンド(R)

読み出しコマンドは、指定されたアドレスのデータを表示します。

<読み出しコマンド> ::= "R" <16進数> <Enter>

読み出しの結果は、 アドレスとデータを":"でつないで表現します。

<読み出し出力> ::= <16進数> ":" <16進数> <行末>

書き込みコマンド(W)

書き込みコマンドは、 指定されたアドレスに同じく指定されたデータを書き込みます。 書き込みコマンドの":"の前の部分がアドレス、後ろの部分がデータをあらわします。

<書き込みコマンド> ::= "W" <16進数> ":" <16進数> <Enter>

書き込みの結果は、 読み出し同様、アドレスとデータを":"でつないで表現します。

<書き込み出力> ::= <16進数> ":" <16進数> <行末>

プロジェクト作成

プログラムの開発には、CodeWarriorを使います。 CodeWarriorでは、 一つのプログラムの単位を「プロジェクト」と呼んでいます。 ここでは、新規にプロジェクトを作成します。 ここでは、V5.0を使って説明します。

CodeWarriorの起動

スタートメニューからCodeWarriorを起動するとCodeWarriorが起動します。

初めてCodeWarriorを起動したときには、 下のような"Startup"ダイアログが出てきます。

CodeWarrior起動画面

出てこない場合には、 メニューから"File → Startup Dialog..."を選ぶと 出てきます。

"Startup"ダイアログの "Create New Project"(新しいプロジェクトを作る)をクリックして "HC(S)08 New Project"ダイアログを開きます。

使用言語とディレクトリの設定

まずは、"Project Parameters"(プロジェクトのパラメータ)を設定します。

新規プロジェクト画面1

使用する言語に"C"を選び、 プロジェクトを作成する場所を設定します。 この例では、"C:\Projects\CW\Extmemory1"というディレクトリに "Extmemory1.mcp"というプロジェクトファイルを作成しています。 設定を終えたら、「次へ」をクリックします。

デバイスと接続法の設定

画面が変わって、 "Device and Connection"(デバイスと接続方法)の設定に移ります。

新規プロジェクト画面2

デバイスには"HCS08 → HCS08QG Family → MC9S08QG8"を選び、 デフォルトの接続方法には"P&E Multilink/Cyclone Pro"を選び、 「次へ」をクリックします。

追加するファイルの設定

3枚目の画面は、 "Add Additional Files"(追加するファイル)の設定です。 プロジェクトで使用するソースコードなどがあれば、 ここで設定します。

新規プロジェクト画面3

今回は、追加するファイルはありませんので、 そのまま「次へ」をクリックします。

プロセッサエキスパートの設定

4枚目の画面は、 "Processor Expert"(プロセッサエキスパート)の設定です。

新規プロジェクト画面4

"Processor Expert"を選んでこの機能の使用を宣言します。 さらに、「次へ」をクリックします。

C/C++のオプションの設定

5枚目の画面は、"C/C++ Options"の設定です。

新規プロジェクト画面5

すべて、デフォルトのままの設定にします。

C/C++オプションの設定
startup codeANSI
memory modelSmall
floating point formatNone

設定を確認して 「次へ」をクリックします。

PC-Lintの設定

最後の画面は、"PC-Lint"の設定です。 PC-lint™というのは、 プログラムの書式を検査するためのプログラムです。 今回は使用しません。

新規プロジェクト画面6

"No"をチェックして 「完了」をクリックすると、 新規プロジェクトの作成が始まります。

パッケージを選ぶ

MC9S08QG8マイコンには、 いくつかの異なる種類のパッケージが存在します。 その中から、今から使おうとしているパッケージが どのパッケージなのかを指定するのが次のウィンドウです。

パッケージ選択画面

今回は、評価ボードに搭載されている 16PIN-DIPパッケージを使いますので、 "MC9S08QG8_16"だけを選択して「OK」をクリックします。

使用する設定を選ぶ

これが、新規プロジェクトのための 最後の設定です。 "Select Configurations"(設定を選べ)というのは、 実にあいまいですが、 ここでは、アプリケーション開発の結果を何に使うかを指定します。 もちろん、独立した製品に仕上げるのが最終目的には 違いないのですが、 プログラムの開発中には、デバッガを接続した状態で プログラムを実行する必要もあります。

"Debug"設定は、このようなデバッガを接続した状態を考慮した 設定を行います。 このため、BKGD端子には、デバッガが接続されアプリケーションでは 使用できなくなります。

一方、"Release"設定は、最終製品での使用を考慮した設定を行うため、 すべての端子がアプリケーションに開放されます。

今回は、デバッグ環境である評価ボードでアプリケーション開発を 行いますので、 "Debug"だけを選択して「OK」をクリックします。

新規プロジェクト最終設定画面

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

新規プロジェクト完成

リソース設定

ここから、 プロセッサエキスパートのプロパティを設定していきます。 ここでは、設定箇所だけ示します。 設定方法の詳細は、三値デジタル太陽計を参照ください。

シリアル通信の設定

RxD端子とTxD端子を使って、RS-232C規格でPCと接続します。

非同期シリアルの設定

まず、非同期シリアルビーンを設定します。 "Bean Selector"から "CPU Internal Peripherals(CPU内部ペリフェラル) → Communication(通信) → AsynchroSerial(非同期シリアル)"を ダブルクリックで呼び出したら、 以下の項目を設定します。

非同期シリアルの設定
項目名設定する値
Baud rate(ボーレート) 9600

MC9S08QG8の場合、非同期シリアルがつながるのは"PTB0"と"PTB1"だけですので、 使用するポートは自動的に設定されます。 ここで唯一設定している「ボーレート」とは、 PCとデータを送受信するときの通信速度のことです。 ここでは、一般的な"9600baud"(毎秒9600ビット)を使用します。 最終的に属性は以下のようになります。

非同期シリアルの設定終了

コードの生成

以上で必要なビーンの宣言は終わりました。 次は、Processor Expertにソースコードを生成させます。

CodeWarriorのウィンドウの左側にあるプロジェクトペインの Makeボタンを クリックして、ソースコードを生成させます。

Makeボタン

コード生成では、"Extmemory1.c"というファイルも同時に生成されています。 左端の"Project Panel"ウィンドウに"Extmemory1.c"というファイルが見えます。

Extmemory1.cファイル

このファイルには、 マイコンがリセットされた後で実行される "main()"関数が記述されています。 ここをダブルクリックすると、 "Extmemory1.c"ファイルを編集するテキストエディタが開きます。

テキストエディタを開く

このテキストエディタで残りの必要なコードを記述します。

便利な関数

このアプリケーションは、 ハードウェアが簡単である代わりに、 多くのソフトウェアを記述していく必要があります。 このセクションでは、 いくつかの便利な関数について説明していきます。

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

getc(void)関数は、 シリアル通信で文字が一つ受信されるのを待ち、 受信した文字を返します。 このアプリケーションで仮定しているシリアル通信のフォーマットは、 8ビットパリティなしフォーマットです。 このため、この関数で返される値も8ビットの値になります。

/**************************************************************
*  Get a character from AS1.
**************************************************************/
char getc(void)
{ 
  AS1_TComData c; 

  while (AS1_RecvChar(&c) != ERR_OK) ;
  return (char)c;
}

"Processor Expert"が生成してくれたコードには、 RecvChar(AS1_TComData*)という 一文字受信関数があります。 この関数は、文字が受信出来なかった場合には、 エラーコードを返すことで、すぐに呼び出し元に制御を返してくれます。 getc(void)には、 文字を受信できるまで待つ動作をさせたいので、 RecvChar(AS1_TComData*)が 正常受信コードを返すまでwhile文で待たせる ようにしました。

"AS1_TComData"型とは

シリアル通信では、一文字のビット幅をユーザが定義できるように なっています。 "AS1_TComData"型は、このユーザが定義したビット幅のデータを 格納することができる型として定義されます。

例えば、MC9S08QG8の場合、 8ビットのデータを送受信する場合には、 "AS1_TComData"型は"byte"型として定義されます。 また、9ビットのデータを送受信する場合には、 "AS1_TComData"型は"word"型として定義されます。 このように、送受信しているデータのビット幅を ユーザが意識せずにプログラムを書くことが出来るようにという 配慮なのでしょう。

実際にプログラム中でデータを使う場合には、"char"型を使うので、 役に立つ仕様なのかどうか微妙なところです。

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

putc(char)関数は、 シリアル通信で送信可能になるまで待ち、 文字を一つ送信します。 この関数で送信するデータも8ビットの値です。

/**************************************************************
*  Put a character to AS1.
**************************************************************/
void putc(const char c) {
  while (AS1_SendChar((AS1_TComData)c) != ERR_OK) ;
}

"Processor Expert"が生成してくれたコードには、 SendChar(AS1_TComData)という 一文字送信関数があります。 この関数は、文字が送信出来なかった場合には、 エラーコードを返すことで、すぐに呼び出し元に制御を返してくれます。 putc(char)には、 文字を確実に送信できるまで待つ動作をさせたいので、 SendChar(AS1_TComData)が 正常送信コードを返すまでwhile文で待たせる ようにしました。

16進文字数値化関数 : hex2byte(char)

仕様でも述べたように、 コマンドでは16進数を使用してインターフェースを行っています。 この関数では、文字を16進数とみなして数値化して返してきます。

/**************************************************************
*  Convert a HEXADECIMAL character to a byte.
*  Returns 255 if an invalid character provided.
**************************************************************/
byte hex2byte(const char c) {
  if (c >= '0' && c <='9') {
    return (c - '0') + 0;
  }
  if (c >= 'A' && c <= 'F') {
    return (c - 'A') + 10;
  }
  return 255;
}

16進文字として使用可能なのは、'0'から'9'までの数字と 'A'から'F'までの文字のあわせて16種類です。 これを二つの場合に分けて処理を行います。

この関数の前半部分では、'0'から'9'までの数字が与えられた場合の 処理を行っています。 文字'0'とのコードの差を計算すると数値化することができます。

また、後半部分では、'A'から'F'までの文字が与えられた場合の 処理を行っています。

もし、16進文字以外の文字が与えられたら、 数値化に失敗したことを示すために"255"を返します。

16進文字検査関数 : isHex(char)

この関数では、与えられた文字が16進文字として適合するかどうかを 検査します。

/**************************************************************
*  Check if a character is a HEXADECIMAL character.
*  Return TRUE if the character is a HEXADECIMAL character.
**************************************************************/
bool isHex(const char c) {
  return (hex2byte(c) != 255);
}

この関数では、hex2byte(char)関数を 呼び出しています。 上で述べたように、hex2byte(char)関数は、 16進文字以外の文字が与えられると"255"を戻します。 この関数では、戻ってきた値が"255"かそれ以外かを検査して 16進文字としての適合性を判断します。

16進文字化関数 : byte2hex(byte)

この関数は、 hex2byte(char)と逆の動作をします。 つまり、与えられた数値を16進文字に変換します。

/**************************************************************
*  Convert a byte data to a HEXADECIMAL character.
*  Returns '*' if an invalid byte data which is larger than
*  15 provided.
**************************************************************/
char byte2hex(const byte d) {
  if (d <= 9) {
    return '0' + (d - 0);
  }
  if (d <= 15) {
    return 'A' + (d - 10);
  }
  return '*';
}

この関数でも二つの場合に分けて処理をしています。

まず、数値が'0'から'9'までの場合には、文字'0'のコードに数値を足して 文字コードを計算しています。

また、数値が'10'から'15'までの場合には、 数値が'10'のときに文字'A'が出てくるように 文字'A'のコードに数値(d - 10)を足して 文字コードを計算しています。

もし、数値が16進の範囲を超える値であった場合には、 '*'を返すことで数値の以上を示します。

インタプリタ

上で述べた「便利な関数」群を使って、 コマンドを解釈するための簡単なインタプリタを作成します。

外部記憶の実体化 : memory[16]

まず、外部記憶領域を宣言します。 仕様の通りに、外部記憶は、8ビットのデータ16個分とします。

/**************************************************************
*  memory[] : a memory area used by commands.
**************************************************************/
byte memory[16];

大域変数としてmemory[16]を宣言します。

受信データキャッシュ : ch

インタプリタでは、 読み込んだ文字によってすべき処理を決定していきます。 このため、読み込んだ文字を一時的に覚えておく場所があると 便利です。

/**************************************************************
*  ch       : a last received character from SCI.
**************************************************************/
char ch;

このインタプリタでは、シリアル通信から受信した文字を いったnこの変数に格納してから処理を行います。

キャッシュは一文字で足りるか

このインタプリタでは、 一文字でコマンドが完結してしまう仕様になっているので、 一文字分のキャッシュがあれば十分に処理を行うことができます。

ところが、もっと複雑な処理系になると、 一文字分のキャッシュでは足りないものも出てきます。 このような場合には、複数文字のキャッシュを用意したり、 字句解析器(Lexical Analyzer)という別のソフトウェアで 解析させてその結果を使ったりします。

また、BASICインタプリタなどでは、 中間言語という機械に扱いやすい記号列にコンパイルしてから 処理を行う場合もあります。 この場合には、コンパイルという作業に字句解析も含まれています。

エコーバック付き一文字受信関数 : getput(void)

ヒューマンインターフェースでは、 入力した文字を人間がすぐに確認できたほうが便利です。 この機能のために「一文字受信し、すぐに同じ文字を送り返す。」 という複合型関数getput(void)を 用意しました。

/**************************************************************
*  Get a character from AS1 and returns an echo.
**************************************************************/
char getput(void) {
  ch = getc();
  putc(ch);
  return ch;
}

ご覧のように、getc(void)で受信した文字を putc(char)で送り返しています。

この関数では、同時に受信した文字をキャッシュに入れる動作も 行っています。

wordデータ受信関数 : getWord(void)

このwordデータ受信関数は、 getput(void)hex2byte(byte)を 使用して、 シリアル通信で受信した16進数の文字列から 16ビットのword型のデータを得ます。 この関数は、仕様のセクションで述べた<16進数>に相当する 解析を行います。

/**************************************************************
*  Returns a word data constructed from characters
*  received by AS1.  Any characters can be a terminator
*  except HEXADECIMAL characters.
**************************************************************/
word getWord(void) {
  word d = 0;

  while (isHex(getput())) {      // (A) Repeat for HEX characters.
    d = (d << 4)| hex2byte(ch);  // (B) Convert to a word.
  }
  return d;                      // (C) Returns a converted word.
}

「文字列から得る」といっても、ここでは文字列を作成せずに 一文字ずつ処理を行います。

(A)では、シリアル通信で一文字受信し受信した文字が16進数文字であるか どうかを検査しています。 もし、16進数文字以外の文字を受信した場合は、 while文を抜けて、(C)に向かいます。

(B)では、受信した16進数文字を使って16進数の値を計算します。 ここで使用されているのは、4ビットのシフト関数です。 16進数文字は、一文字で4ビット分を表現しています。 このため、新しい文字が受け入れられるたびに、 word型の値を4ビット分シフトしています。

16進数文字以外の文字が見つかったら、 (C)で変換された値を返します。 この時、文字キャッシュ"ch"には、 「16進文字ではない文字とは何であったか」という 情報が残っていますので、 この関数を呼び出したプログラムで判断することができます。

16進文字が一つも無い場合はどうなるか

16進文字が期待されているのに、 一文字も16進数文字が受信されなかった場合にはどうなるのでしょうか。 この関数では、処理を簡単にするために16進文字が"0"個でも 正常に動作してしまい、値"0"を返してきます。 つまり、コマンドとして"R<Enter>"を与えると "R0<Enter>"と同じと解釈してしまいます。

まあ、「仕様」の不備と言えなくもないのですが、 どうやったら、エラーとして検出できるかは、今後の課題とします。

wordデータ送信関数 : putWord(word, byte)

getWord(void)関数とは逆にこの関数では、 16ビットのword型の値を 指定された桁数で16進数表示します。

/**************************************************************
*  Put a word as a HEXADECIMAL value in a specified number
*  of characters.
**************************************************************/
void putWord(const word data, const byte w) {
  byte i;                         // Loop counter
  byte pos;                       // Position of a digit.
  byte digit;                     // Value of a digit.
  
  for (i = w; i-- > 0; ) {        // (A) loop for i = (w-1)..0
    pos = i * 4;                  // (B) Get the digit's position.
    digit = (data >> pos) & 0xF;  // (C) Get the digit's value.
    putc(byte2hex(digit));        // (D) Put a HEX character.
  }
}

表示する桁数は、"w"変数で指定されます。 (A)では、"(w - 1)"を初期値として"0"までの値をとる ループ変数"i"が定義されています。 ループ内の(B)から(D)までは、 "i"の示す桁に対して処理を行っています。

(B)では、"i"の示す桁のビット位置を"pos"変数に求めています。 16進数文字は、一文字が4ビットに相当しますので、 "i"を4倍することで目的のビット位置を知ることができます。

(C)では、(B)で得られたビット位置から16進数一文字分のデータを 取り出して"digit"変数に入れます。 具体的には、wordのデータをビット位置分だけ シフトして、さらに、4ビット分だけを取り出すために 定数"0xF"とAND演算を行っています。

(D)では、(C)で得られた値を16進数文字一文字に変換した上で、 シリアル通信で送信しています。

インタプリタ関数 : interpret(void)

いよいよ、最後の関数です。 インタプリタ関数では、コマンド一行分の解釈と実行を行います。 もし、解釈の途中でエラーを発見したら即座に呼び出しもとの ルーチンに戻ります。

/**************************************************************
*  interpret a line.
*  Returns to invoking routine if any error occurs.
**************************************************************/
void interpret(void) {
  word addr;                      // Address pointer.
  word data;                      // Work area for a data.
  
  putc('\r');                     // (A) Put CR/LF
  putc('\n');
  putc('*');                      // (B) Put a prompt.
  switch (getput()) {             // (C) Get a command character.
    case 'R':                     // (D) "Read" command.
      :
      return;                     // (E) Command completed.
    case 'W':                     // (F) "Write" command.
      :
      return;                     // (G) Command completed.
    case 'D':                     // (H) "Dump command.
      :
      return;                     // (J) Command completed.
  }
}

これが、インタプリタ関数の骨組み部分です。 インタプリタが呼び出されると、まず、 (A)と(B)で改行とプロンプト"*"を送信します。

次に(C)でコマンドの最初の一文字を取り込んで、 その文字にしたがってコマンド処理に分岐します。 分岐する先は、(D)(F)そして(H)の三箇所です。 これ以外の文字が見つかった場合には、 インタプリタを抜けます。

いずれのコマンドも処理が正常に終わった場合には、 (E)(G)および(J)のreturn文で インタプリタを抜けます。

以下では、それぞれのコマンドの処理の詳細について述べます。

読み出しコマンド処理部

コマンドが"R"で始まる場合には、 読み出しコマンドであると認識して処理を行います。

      addr = getWord();           // (D1) Get address to be read.
      if (ch != '\r') return;     // (D2) Error for an invalid terminator.
      putc('\n');                 // (D3) Put LF following CR.
      addr &= 0x0F;               // (D4) Limit the address.
      data = memory[addr];        // (D5) Get a data from memory.
      putWord(addr, 1);           // (D6) Put address.
      putc(':');                  // (D7) Put a separator.
      putWord(data, 2);           // (D8) Put data.

(D1)で、コマンド文字"R"に続くことが期待されるアドレスを取り込みます。 正しい読み出しコマンドのアドレス部分は、<Enter>で終わります。 これを確認するのが、(D2)です。 <Enter>キーは、'\r'(コード13)で表現されます。 もし、<Enter>が押されたのでなければ、 後の処理をキャンセルするため、returnで インタプリタを抜けます。

(D3)では、"Line Feed"(LF / 改行)コードを出力します。 一般のターミナルソフトでは、"Carriage Return"(CR / 復帰)コードに 続いて LF コードを受信することによって改行動作を行います。 CR コードは、getWord(void)関数の中で エコーバックによって出力されているので、 残りの LF コードだけをここで出力しています。 LF コードは、'\n'(コード10)で表現されます。

(D4)では、(D1)で受信したアドレスを加工しています。 (D1)で受信したアドレスは、word型の値をとるため、 0から65535までの値になる可能性があります。 ところが、外部記憶には、0から15までのアドレスしか存在しません。 存在しないアドレスに対してアクセスを行わせないように アドレスの値の下位4ビットだけを取り出します。 この取り出しの操作に使用されるのが'&'(AND / 論理積)演算子です。 (D4)の操作を経るとアドレスは、0から15までのアドレスをとります。

(D5)では、外部記憶の指定されたアドレスからデータを取り出します。

(D5)で取り出した値をシリアル通信で送信するのが、 (D6)(D7)そして(D8)です。 (D6)は16進数一桁のアドレスを送信し、 (D7)はセパレータ文字':'を送信し、 (D8)は16進数二桁のデータを送信します。

書き込みコマンド処理部

コマンドが"W"で始まる場合には、 書き込みコマンドであると認識して処理を行います。

      addr = getWord();           // (F1) Get address to be written.
      if (ch != ':') return;      // (F2) Error for an invalid separator.
      data = getWord();           // (F3) Get data to be written.
      if (ch != '\r') return;     // (F4) Error for an invalid terminator.
      putc('\n');                 // (F5) Put LF following CR.
      addr &= 0x0F;               // (F6) Limit bit width of address.
      data &= 0xFF;               // (F7) Limit bit width of data.
      memory[addr] = (byte)data;  // (F8) Write a data into memory.
      putWord(addr, 1);           // (F9) Put address for confirmation.
      putc(':');                  // (FA) Put a separator.
      putWord(data, 2);           // (FB) Put data for confirmation.

(F1)で、コマンド文字"W"に続くことが期待されるアドレスを取り込みます。 正しい書き込みコマンドのアドレス部分は、 セパレータ文字':'で終わります。 これを確認するのが、(F2)です。 もし、':'が押されたのでなければ、 後の処理をキャンセルするため、returnで インタプリタを抜けます。

(F3)で、セパレータ文字':'に続くことが期待されるデータを取り込みます。 正しい書き込みコマンドのデータ部分は、<Enter>で終わります。 これを確認するのが、(F4)です。 もし、<Enter>が押されたのでなければ、 後の処理をキャンセルするため、returnで インタプリタを抜けます。

(F5)では、読み出し処理と同様の理由で、 "Line Feed"(LF / 改行)コードを出力します。

(F6)と(F7)では、(F1)と(F3)で受信したアドレスとデータを加工しています。 (F1)で受信したアドレスも(F3)で受信したデータも、 word型の値をとるため、 0から65535までの値になる可能性があります。 ところが、外部記憶には、0から15までのアドレスしか存在しませんし、 データは8ビットの値です。 存在しないアドレスや数値を使わせないように アドレスの値の下位4ビットとデータの下位8ビットだけを取り出します。 この取り出しの操作に使用されるのが'&'(AND / 論理積)演算子です。 (F6)と(F7)の操作を経るとアドレスは、 アドレスは0から15までの、データは0から255までの値をとります。

(F8)では、外部記憶の指定されたアドレスにデータを格納します。

(F8)で取り出した値をシリアル通信で送信するのが、 (F9)(FA)そして(FB)です。 (F9)は16進数一桁のアドレスを送信し、 (FA)はセパレータ文字':'を送信し、 (FB)は16進数二桁のデータを送信します。

ダンプコマンド処理部

コマンドが"D"で始まる場合には、 ダンプコマンドであると認識して処理を行います。

      getput();                   // (H1) Get a terminator cahracter.
      if (ch != '\r') return;     // (H2) Error for an invalid terminator.     
      putc('\n');                 // (H3) Put LF following CR.
      for (addr = 0; addr < sizeof(memory); addr++) {
                                  // (H4) Scan all address.
        putc(':');                // (H5) Put a separator.
        putWord(memory[addr], 2); // (H6) Put a data at the address.
      }

ダンプコマンドは、"D"一文字のコマンドです。 (H1)で"D"に続く文字を受信し、 (H2)で <Enter> かどうかを確認します。 もし、<Enter>が押されたのでなければ、 後の処理をキャンセルするため、returnで インタプリタを抜けます。

(H3)では、読み出し処理と同様の理由で、 "Line Feed"(LF / 改行)コードを出力します。

(H4)は、外部記憶のすべての領域を走査するためのループ宣言です。 変数"addr"は、0から変数"memory"の最後の要素の位置まで 変化していきます。

(H5)と(H6)は、 実際のダンプ表示を行う部分です。 (H5)はセパレータ文字':'を送信し、 (H6)は外部記憶の変数"addr"で示されるアドレスにある 16進数二桁のデータを送信します。

メインルーチン部

忘れてはいけないのが、メインルーチンの変更です。

メインルーチン部

必要なのは、for文で始まる3行だけです。

  /* Write your code here */
  /* For example: for(;;) { } */
  for (;;) {
    interpret();
  }

このアプリケーションでは、メインルーチンに無限ループを配置し、 その中で延々とinterpret(void)関数を 呼び出します。

以上でプログラムの準備は出来ました。

アプリケーションの確認

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

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

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

ジャンパを設定する

評価ボードをPCにつなぐ前に 評価ボード上のジャンパを設定しなくてはなりません。 この設定は、評価ボード上のRS-232Cトランシーバを利用可能な 状態にするために必要です。

COM_ENジャンパの設定

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

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

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

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

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

デバッガの呼び出し

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

接続方法の指定

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

マイコンに書き込む

消去確認

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

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

ここで、ツールボタンの Start/Continueアイコンを クリックするとマイコンは自律走行を始めます。 ですが、このアプリケーションは、シリアルインターフェースを相手に 動作するのでこのままでは何も起こりません。 そこで、ターミナルソフトアプリケーションを起動して、 会話をします。

ハイパーターミナルを起動する

Windowsには、オペレーティングシステム標準の「ハイパーターミナル」という ターミナルソフトが付属しています。 今回は、これを使います。

まず、「ハイパーターミナル」を起動します。 WindowsXPでは、 「スタート → すべてのプログラム → アクセサリ → 通信 → ハイパーターミナル」という結構深いところにあります。

ハイパーターミナルの設定

ハイパーターミナルを起動すると、 「接続の設定」というダイアログが現れます。

接続の設定 (1)

このダイアログでは、名前とアイコンを設定します。 名前は、これから設定するハイパーターミナルの設定内容を保存する ファイルの名前として、 また、アイコンは保存されたファイルのアイコンとして使用されます。 適当な名前とアイコンを設定したら、「OK」をクリックします。 ここでは、"9600-8N1"という名前をつけました。

次のダイアログも「接続の設定」という名前がついています。

接続の設定 (2)

ここでは、"COM1"ポートを使用しますので、 接続方法コンボボックスに"COM1"を設定して、「OK」をクリックします。

これで、ターミナルソフトの設定は終わりです。

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

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

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

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

動作テスト

ここから、一連のコマンドをテストしていきます。

プロンプトの確認

ツールボタンの Start/Continueアイコンを クリックして、マイコンのプログラムを起動します。

プロンプトの表示

すると、プロンプトが表示されます。 これで、マイコンがコマンドを待っている状態であることが確認できます。

書き込みテスト

次に書き込みコマンドをタイプしてみます。

書き込みコマンドの確認

表示を見る限り、正しく書き込まれたように見えます。 ただし、ここで返ってくる値は、 コマンドで与えられた値であって、外部記憶の内容ではありません。 そのため、確認には、次の読み出しの確認が必要です。

読み出しテスト

次に読み出しコマンドをタイプしてみます。

読み出しコマンドの確認

書き込みコマンドで書き込まれた値が確認でき、 書き込みを行っていないところは初期値として"00"が入っているのが 確認できます。

ダンプテスト

次にダンプコマンドをタイプしてみます。

ダンプコマンドの確認

確かに外部記憶の16バイトすべての値が表示されました。

未定義コマンドテスト

次に未定義なコマンド文字をタイプしてみます。

未定義コマンドの確認

すると、未定義コマンド文字をタイプしたとたんに プロンプトが返ってきます。 未定義コマンドでコマンド処理が中断されたのがわかります。

課題

実用的な応用例を考える

シリアルインターフェースを介して16バイトのデータを送受信する事が できる事はわかりましたが、 これだけでは実用的なアプリケーションではありません。 ここから、どういった、応用が考えられるでしょうか。

2006-03-08 発行。

Copyright (C) 2006 noritan.org ■