■>サンテックのホームページTOPへ       ■>サンおやじの工作室TOPへ
    工作室
       第2章 LEDランプを光らせる

<工作室に戻る
"CPU"と"プログラム"(初心者のために)
第1章 とりあえず動かす (H8Tiny入門その1)
■第2章 LEDの点灯    (H8Tiny入門その2)
第3章 キーの読み込み  (H8Tiny入門その3)
第4章 ブザーを鳴らす   (H8Tiny入門その4)

第2章 LEDランプを光らせる

まずはLEDランプを光らせてみましょう

コンソールボードには4つのLEDランプが付いており、CPUとの接続は下図のようになっています。
ということは、CPUのポートがハイレベルつまり5Vを出力すればLEDランプは光ります。
CPUのポートP8のLEDランプが接続している端子に1を書き込むことで5Vを出力します。
逆に0を書き込むことでLEDランプは消灯します。

※回路図のP80〜P83は、ポートP8のビット0〜3を意味します。

回路図1

※ CPUのポートから出力できる電流には制限があり、最大値を超えた場合にはCPUそのものを壊してしまう
  可能性があります。
  出力可能な電流値を超えないようにハードウェアを設計する必要があります。

CPUポートの仕組み

まずは、ポートというものについて説明します。
一般に、“ワンチップCPU”と呼ばれるCPUには“ポート“と呼ばれる入出力可能な端子が有ります。 
出力用のポートは、その先につながっている端子に信号を与え、入力用のポートはハードウェアからの信号を読み込みます。
それらの信号を絡み合わせることで、外部のハードウェアを制御します。
つまり、ワンチップCPUの場合、ポートの入出力ができれば何でも(ハードウェアが許す範囲で)できることになります。

それでは、コンソールボードを例にとって説明します。

ポートP8のLEDが接続されている端子に1を書き込むには、まずポートP8のアドレスを調べます。
H8/3672シリーズハードウェアマニュアル(以後ハードウェアマニュアル)より、アドレスはFFDB(16進数)です。
このアドレスの下位4ビット(ビット0〜3)に1を書けば良いことがわかります。
その前に、ポートP8が出力に設定されていなければポートデータレジスタに1がセットされても端子に5Vは出力されません。
ポートのアドレスを調べた要領で、ポート8の方向(入力/出力)を決めるP8ポートコントロールレジスタのアドレスを調べます。ハードウェアマニュアルよりFFEB(16進数)がアドレスと判ります。
このレジスタのビットを1にすることでそれに応じた端子が出力になります。

それでは、早速プログラムを書いてみましょう

H8TinyCPUのポートのレジスタにアクセスする方法は、ポインタを使って直接そのアドレスにアクセスする方法と、構造体/共用体を使う方法があります。
(構造体/共用体を使う場合でも実体を取るときにアドレスを定義しておく必要があります)

ここでは、ポインタを使う方法を説明していきます。

リスト1 ポインタを使ったポートデータレジスタへのアクセス
*(char *)0xffdb = 0x0f;

先頭の‘*’は、ポインタの指し示すアドレスの中身を意味します。
続く“(char *)”で、これに続く数値を“8ビット単位でアクセスされるアドレス”という型に変換しています。
このように型を変換することを“キャスト”と言います。
次の数値“0xffdb”は、ハードウェアマニュアルで調べたポートP8 ポートデータレジスタのアドレスです。
つまり、ここまでで、ポートP8 ポートデータレジスタのアドレスの中身を意味します。
‘=’から後ろは代入を意味しますので、ポートデータレジスタに“0x0f”を書き込むことができます。

P8の下位4ビット全てに1、上位4ビットには0を書き込むことができました。(リスト1)

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
ポイント!!
ここでは
*(char *)アドレス = 書きこむデータ;
と書けば「指定のアドレスにデータが書きこめる」と丸暗記しても良いのですが、
ポインタ・キャストはC言語を使う上で絶対避けてとおることができない重要なものです。
きちんと習得しておきましょう
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

CPUの起動時には、ポートの状態が不定になっているものがあります。
そういった場合には、意図しないデータの出力を防ぐためにポートを一旦入力方向にし、出力させたいデータを書き込んだ後でポートを出力方向に設定します。
H8Tinyでは、ポートは初期状態が入力になっていますので心配ありませんが、そういった場合を想定してプログラムを変更してみます。
※ 一部のLSIではこの方法が使えない場合があります。

1. 意図しないデータの出力を防ぐために、P8を一旦入力方向にします。(リスト2の1行目)
2. それから、リスト1のように出力したいデータを書き込みます。(リスト2の2行目)
3. その後、ポートの方向を出力方向にします。(リスト2の3行目)

これでLEDランプが全て点灯します。
この部分を続けて書くと、リスト2のようになります。

リスト2
*(char *)0xffeb = 0x00; /* P8を入力に設定 */
*(char *)0xffdb = 0x0f; /* P8に出力データをセット */
*(char *)0xffeb = 0x0f; /* P8のLEDが接続された4ビットを出力に設定 */

LEDランプの点灯部分(リスト2)に加え、CPUの初期設定やポートのイニシャライズを含めたプログラムは以下のようになります。(LED_TEST1.c

リスト3
void main(void)
{
/* I/Oの初期化 */
*(char *)0xffd4 = 0x00; /* ポート1に0x00を設定 */
*(char *)0xffd5 = 0x00; /* ポート2に0x00を設定 */
*(char *)0xffd8 = 0x00; /* LCDデータセット(ポート5に0x00を設定) */
*(char *)0xffda = 0xff; /* LCD信号全てHigh */
*(char *)0xffe0 = 0x00; /* ポート1を汎用ポートに設定 */
*(char *)0xffd0 = 0x00; /* ポート1 プルアップしない */
*(char *)0xffe4 = 0x00; /* ポート1を入力に設定 */
*(char *)0xffe5 = 0x00; /* ポート2を入力に設定 */
*(char *)0xffe1 = 0x00; /* ポート5を汎用ポートに設定 */
*(char *)0xffd1 = 0x00; /* ポート5 プルアップしない */
*(char *)0xffe8 = 0xff; /* LCDデータ出力端子に設定 */
*(char *)0xffea = 0x70; /* LCD信号出力端子に設定 */
/* メイン(LED点灯) */
*(char *)0xffeb = 0x00; /* ポート8を入力に設定 */
*(char *)0xffdb = 0x0f; /* P8のLEDが接続されたビットを1にする */
*(char *)0xffeb = 0x0f; /* P8のLEDが接続されたビットを出力に設定 */
while(1); /* プログラム実行終了(無限ループ) */
}


それでは、実際にプログラムを書き込んでみましょう。LED_TEST1.motです。
書き込みが終わったら、電源を入れ直します。
4つのLEDランプが全部点灯しましたね。(写真10)
写真10

開発ツールは、ルネサスエレクトロ二クスのホームページで、Tiny/SLP専用無償版コンパイラをダウンロードできますので、これを使うことにします。

このツールでは、3672がありませんのでCPU Typeで3664Fを選択して下さい。

次はLEDランプを1つずつ、ずらしながら点灯させてみましょう

前回、4つのLEDランプを全部点灯させましたが、今度はLEDランプを1つずつずらしながら点灯させていくプログラムに変更してみましょう。
また、ここでは構造体/共用体を使ったポートアクセスの方法についても説明します。

H8TinyCPUの内蔵I/Oポートレジスタは、0xFF80番地以降に連続して割り付けられています。
このような場合、構造体/共用体を使ってI/Oポートレジスタにアクセスすることができます。
HEW2では、I/Oポートレジスタの構造体を定義したファイル“iodefine.h”が自動生成されています。
構造体の内容までは説明しませんが、“iodefine.h”の最後に20行ほどアドレスを指定して実体を取っている部分があります。
これでI/Oポートレジスタにアクセスすることができるようになっています。

例 シンボル値の設定 : 絶対値やアドレス値をシンボルで置き換えることが出来る。
IO.PDR8.BYTE = 0x00; /* ポートデータレジスタ8に0を書き込む */

また、ビットフィールドも定義されていますので、必要なビットのみ入出力することもできます。
IO.PDR8.BIT.B0 = 1; /* ポートデータレジスタ8のビット0に1を書き込む */

ここからは、この構造体を使ってプログラムを作っていきます。

LEDランプをずらしながら点灯させるには、ビットシフトをしてそのデータをポートに出力します。
ビットシフトとは、データのビットの並びはそのままに、左または右にnビットずらすことを言います。
左1ビットシフトの場合は1(初期値)→2→4→8…となり、右1ビットシフトでは逆に8(初期値)→4→2→1→0(これ以下はありません)となります。
通常、左シフトには「左シフト演算子」“<<”、右シフトには「右シフト演算子」“>>”を使いますが、上の数値を見てお気づきの通り、左シフトは掛け算、右シフトは割り算で代用できます。

それでは、これらを踏まえてプログラムを書いてみましょう。

HEW2を起動し、前回と同じ要領でプロジェクトを作成します。
今回のワークスペース名は“LED_TEST2”とします。
但し、今回は“C Source File”の下にある“LED_TEST2.c”を削除せず、このファイルを編集して使います。

リスト中の“//”以降、改行まではコメントです。
C言語では通常“/*”から“*/”までをコメントとして扱いますが、コンパイラによっては“//”が許されている物もあります。H8Tiny用のコンパイラでは“//”が使えますので、今後積極的に使っていきます。

最初に“iodefine.h”を使えるようにします。
ソースファイルの先頭に、 #include “iodefine.h” と書いておきます。

リスト4−1
#include "iodefine.h"

次にmain関数の中身を考えます。
LEDのポートに1→2→4→8→1…と書き込むのですから、何か変数を使わなければならないことが予想できます。
使いそうな変数は、
@ LEDを点灯させるためのデータ
A データを書き込む部分を4回ループさせるためのカウンタ

これらを関数の先頭で定義します。(ローカル変数)

リスト4−2 ローカル変数の定義
void main(void)
{
 char data; // LEDを点灯させるためのデータ
 int i; // ループカウンタ


次にリスト3−1のI/Oの初期化を打ち込みます
ポートのアクセスには“iodefine.h”の構造体を使います。

リスト4−3 I/Oの初期化部分
 // I/Oの初期化
 IO.PDR1.BYTE = 0x00; // ポート1に0x00を設定
 IO.PDR2.BYTE = 0x00; // ポート2に0x00を設定
 IO.PDR5.BYTE = 0x00; // LCDデータセット(ポート5に0x00を設定)
 IO.PDR7.BYTE = 0xff; // LCD信号全てHigh
 IO.PMR1.BYTE = 0x00; // ポート1を汎用ポートに設定
 IO.PUCR1.BYTE = 0x00; // ポート1 プルアップしない
 IO.PCR1 = 0x00; // ポート1を入力に設定
 IO.PCR2 = 0x00; // ポート2を入力に設定
 IO.PMR5.BYTE = 0x00; // ポート5を汎用ポートに設定
 IO.PUCR5.BYTE = 0x00; // ポート5 プルアップしない
 IO.PCR5 = 0xff; // LCDデータ出力端子に設定
 IO.PCR7 = 0x70; // LCD信号出力端子に設定
 IO.PCR8 = 0x00; // ポート8を入力に設定
 IO.PDR8.BYTE = 0x01; // P8に初期値を出力する
 IO.PCR8 = 0x0f; // P8のLEDが接続されたビットを出力に設定


続けてLEDを点灯させる部分を書いていきます。

リスト4−4 LEDを点灯させる部分
 // メイン(LEDをずらしながら点灯)
 while(1){ // 無限ループ
  data = 0x01; // LED点灯のための初期値
  for(i = 0 ; i < 4 ; i++){ // 4回ループする
   IO.PDR8.BYTE = data; // 点灯データの出力
   data = data << 1; // 1ビット左シフトする
  }
 }


最後に、main関数を閉じます。
 }

data = data << 1; の部分は、data <<= 1; と書くこともできます。

“<<=”は左シフト代入演算子と言い、左シフトした内容を代入するための演算子です。

C言語にはこのほかにも単純代入演算子(=)と、他の演算子を組み合わせた“○○代入演算子”という演算子が用意されています。ここでは書き込むデータを1ビットずつシフトさせていきましたが、少し考え方を変えるだけで何通りかの書き方が考えられます。どの書き方を採用するかはプログラムの読み易さ、実行速度、ビルド後のメモリ消費量等を考慮して決めていくことになります。以下に他の書き方を4種類記載しておきますので、何かの時に参考にして下さい。

@ データは固定値にしてシフトするビット数を変えるというプログラムの組み方。
この場合、dataという変数は不要です。

リスト4−4a
// メイン(LEDをずらしながら点灯)
while(1){ // 無限ループ
 for(i = 0 ; i < 4 ; i++){ // 4回ループする
  IO.PDR8.BYTE = 1 << i; // 点灯データの出力
 }
}


A リスト4−4aを元に、forループを無くしてしまう組み方。

リスト4−4b
// メイン(LEDをずらしながら点灯)
i = 0;
while(1){ // 無限ループ
 IO.PDR8.BYTE = 1 << (i & 0x03); // 点灯データの出力
 i++; // 左シフト数加算
}


B 配列変数(テーブル)を使ったプログラムの組み方。
この場合、dataという変数は配列で定義します。
この方法の利点は、点灯させる順番の変更が容易なことです。
(データの並び順を変えれば自由に設定できる)

リスト4−4c
char data[] = { 1, 2, 4, 8 }; // ローカル変数のため、関数の先頭で定義する
// メイン(LEDをずらしながら点灯)
while(1){ // 無限ループ
for(i = 0 ; i < 4 ; i++){ // 4回ループする
IO.PDR8.BYTE = data[i]; // 点灯データの出力
}
}


C switch〜case文を使ったプログラムの組み方。
一番単純な方法です。

main()関数先頭の“int i;”を置き換えて下さい。

リスト4−4d
// メイン(LEDをずらしながら点灯)
unsigned char i = 0;
while(1){ // 無限ループ
 switch(i & 0x03){
  case 0:
  IO.PDR8.BYTE = 0x01; // 点灯データの出力
  break;
  case 1:
  IO.PDR8.BYTE = 0x02; // 点灯データの出力
  break;
  case 2:
  IO.PDR8.BYTE = 0x04; // 点灯データの出力
  break;
  case 3:
  IO.PDR8.BYTE = 0x08; // 点灯データの出力
  break;
 }
 i++;
}


リスト4−4のままでは早すぎて、全てのLEDランプが光っているようにしか見えません。
動いていることが判るようにするには、1回シフトするごとに待ち時間を入れる必要があります。

ウェイト関数を使ってLEDランプの点灯消灯タイミングを考慮してプログラムを完成させると、リスト4になります。(リスト4−1 + 4−2 + 4−3 + 4−4(タイミング考慮) + ウェイト関数)

リスト4
#include “iodefine.h”
// プロトタイプ宣言
void main(void);
void wait(int);
void wait1mS(void);
void main(void)
{
 char data; // LEDを点灯させるためのデータ
 int i; // ループカウンタ
 // I/Oの初期化
 IO.PDR1.BYTE = 0x00; // ポート1に0x00を設定
 IO.PDR2.BYTE = 0x00; // ポート2に0x00を設定
 IO.PDR5.BYTE = 0x00; // LCDデータセット(ポート5に0x00を設定)
 IO.PDR7.BYTE = 0xff; // LCD信号全てHigh
 IO.PMR1.BYTE = 0x00; // ポート1を汎用ポートに設定
 IO.PUCR1.BYTE = 0x00; // ポート1 プルアップしない
 IO.PCR1 = 0x00; // ポート1を入力に設定
 IO.PCR2 = 0x00; // ポート2を入力に設定
 IO.PMR5.BYTE = 0x00; // ポート5を汎用ポートに設定
 IO.PUCR5.BYTE = 0x00; // ポート5 プルアップしない
 IO.PCR5 = 0xff; // LCDデータ出力端子に設定
 IO.PCR7 = 0x70; // LCD信号出力端子に設定
 IO.PCR8 = 0x00; // ポート8を入力に設定
 IO.PDR8.BYTE = 0x01; // P8に初期値を出力する
 IO.PCR8 = 0x0f; // P8のLEDが接続されたビットを出力に設定
 // メイン(LEDをずらしながら点灯)
 while(1){ // 無限ループ
  data = 0x01; // LED点灯のための初期値
  for(i = 0 ; i < 4 ; i++){ // 4回ループする
   IO.PDR8.BYTE = data; // 点灯データの出力
   data = data << 1; // 1ビット左シフトする
   wait(1000); // 1秒待つ
  }
 }
}

// nミリ秒待つウェイト関数
void wait(int time)
 {
 int i; // ループカウンタ
 for(i = 0 ; i < time ; i++) // time回数分ループ
 wait1mS(); // 1ミリ秒のウェイト関数を呼び出す
 }

// 1ミリ秒待つウェイト関数
void wait1mS(void)
 {
 int i; // ループカウンタ
 for(i = 0 ; i < 2662 ; i++); // 1ミリ秒の間ループする
 }

wait1mS関数で、C言語で全て書く場合の欠点が出てしまいました。
1ミリ秒の間ループするために使われている数値“2662”とは一体何でしょうか?
実は、この数値には理論も何もなく、単に実験して決定した数値です。
C言語では、実際にCPUが実行できるコード(マシン語)への変換は、完全にコンパイラ任せです。
コンパイラが変わったり、コンパイラの最適化オプションを変更したりすると、再度実験して値を変更しなければなりません。
そういったことが起きないようにするために、通常はインラインアセンブラという書き方や、CPUがもともと持っているタイマ割り込み(後で説明します)という機能を使います。
今はそこまでする必要がありませんのでこのままの値を使って下さい。

それでは、打ち込んだプログラムをビルドしてみましょう。

“Debug”ディレクトリの中に“LED_TEST2.mot”が出来ましたね。
早速コンソールボードに書き込んで動作確認をしましょう。
LEDランプが1つずつ移動しながら点滅します。

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
ポイント!!
Debug(デバッグ)とはプログラム作成中にチェックなどを行うモードのことです。
HEW2に限らずプログラムの開発統合環境ではデバッグモードが初期状態になっています。
これに対して、チェックが完了し製品に書き込むプログラムを作成する時はRelease(リリース)
モードを使用します。
皆さんが作ったプログラムはデバッグモードでコンパイルされています。
このテキストで扱う内容ではデバッグもリリースもほとんど差が無いため、HEW2の設定を変更
せず、デバッグモードのまま使用しています。興味の有る方はモードをリリースに変更してみて
ください。Releaseディレクトリが作成され、その中にLED_TEST2.motができています。
これを実行すると、デバッグモードよりほんの少し動作速度が速くなっています。
人間の間隔でわからないかもしれませんけどね。
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

この文章は、“H8Tinyコンソールボードを動かしてみよう(C言語編)“からの抜粋です。
"CPU"と"プログラム"(初心者のために)
第1章 とりあえず動かす (H8Tiny入門その1)
■第2章 LEDの点灯    (H8Tiny入門その2)
第3章 キーの読み込み  (H8Tiny入門その3)
第4章 ブザーを鳴らす   (H8Tiny入門その4)