■>サンテックのホームページTOPへ       ■>サンおやじの工作室TOPへ
    工作室
       第3章 キーの読み込み

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

第3章 キーの読み込み

ポートを使ってスイッチの状態を読み込んでみましょう

コンソールボードには、5列4行のマトリックスで20個のスイッチが付いています。
まず、マトリックスの1列のみを例にとって説明します。
CPUとの接続は下図のようになっています。
P1の方向レジスタを出力方向に設定し、ハイレベルを出すようにポートに1を書き込みます。
入力側のポートPBは抵抗を介してGNDに接続されているので、スイッチが押されない限りローレベル、つまり0が検出されます。
ところが、スイッチSW1〜5のどれかまたは全部が押されると、ポートP1からダイオードを経由して5Vが出力され、抵抗の両端に電位差が生じます。
抵抗とつながっているポートPBには5Vが検出され、ハイレベルとなります。
これでPBは、スイッチが押されるとハイレベル、押されないとローレベルとなり、スイッチのON/OFFの検出が出来るのです。
回路図では上からSW7、SW13、SW9、SW5、SW1となっていますが、今回はこの番号を付けています

回路図2

スイッチの読み込みをプログラムしてみましょう

1. スイッチの接続されているP1の端子をハイレベルにし、ポート方向も出力側にします。(リスト5−1の2〜3行目)
2. PB端子がAD端子と共用になっているため入力ポートとして使うときこの処理が必要です。
   (このCPU特有な処理です。)(リスト5−1の4行目)
3. ポートPBの0ビット目を読み取ります。
これで、変数swにスイッチの状態が格納されました。(リスト5−1の5行目)

リスト5−1
char sw; // スイッチ読み込み内容
IO.PDR1.BYTE = 0x67; // ポート1をハイレベルに設定
IO.PCR1 = 0x67; // ポート1の必要なビットを出力に設定
AD.CSR.BYTE = 0x03; // アナログ入力チャネルを端子3に設定
while(1){ // 無限ループ
 sw = IO.PDRB.BIT.B0; // PB0の状態読み込み

もしSW1だけを読み取りたい場合は、IO.PDR1.BYTE = 0x67;方向レジスタはこのままでポートにIO.PDR1.BYTE = 0x01;を書くようにすればよい。

スイッチの読み込みをプログラムしてみましょう

スイッチの状態をLEDランプに反映させてみましょう
スイッチが押されているときにLEDランプを点灯し、それ以外は消灯するプログラムを書いてみましょう。
リスト5−1に続けてスイッチの状態を判断するプログラムを書きます。(リスト5−2)
これでスイッチを押すとLED1が点灯するプログラムが出来ました。

リスト5−2
 if(sw == 1)
  IO.PDR8.BIT.B0 = 1; // LED1を点灯
 else
  IO.PDR8.BIT.B0 = 0; // LED1を消灯
 }


リスト5−1、リスト5−2にCPUポートのイニシャライズを含めたプログラムは“SW_TEST1.c”です。
ダウンロードアダプタでコンソールボードに書き込んでみましょう。(SW_TEST1.mot
スイッチを押すとLED1が点灯しますね。


写真10 この行のどのスイッチを押してもLED1が点灯しますね。(複数押しにも反応しますね)

スイッチを押すたびにLEDランプの点灯が右にシフトするプログラムを書いてみましょう。
先ほど作成したスイッチの読み取り部分(リスト5−1,2)と、2章で作ったLEDランプを1ビットずつシフトするプログラム(リスト4−2)を応用して使ってみましょう。
今回のワークスペース名は“CHATA_TEST1”とします。

リスト6(CHATA_TEST1.c
#include "iodefine.h"
void main(void)
{
 char data;
 // 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 = 0x00; // P8に初期値(全て消灯)を出力する
 IO.PCR8 = 0x0f; // P8のLEDが接続されたビットを出力に設定
 IO.PDR1.BYTE = 0x67; // ポート1をハイレベルに設定
 IO.PCR1 = 0x67; // ポート1の必要なビットを出力に設定
 AD.CSR.BYTE = 0x03; // アナログ入力チャネルを端子3に設定
 data = 0x01; // LED点灯のための初期値

 while(1){ // 無限ループ
  while(IO.PDRB.BIT.B0 == 0); // スイッチが押されるまで待つ
  IO.PDR8.BYTE = data; // 点灯データの出力
  data <<= 1; // 1ビット左シフトする
  if(data > 0x08) // LED4を越えた?
  data = 0x01; // LED点灯の初期値に戻す
  while(IO.PDRB.BIT.B0 == 1); // スイッチが放されるまで待つ
 }
}


これでスイッチが押されるたびにLEDランプの点灯が1つずつシフトするプログラムが出来ました。

ビルドし、CPUに書き込んで実行してみて下さい。
スイッチを押すたびにLEDが1つずつシフトしていきます。
今度はスイッチをゆっくり(そっと)押してみてください。
LEDが勝手にシフトしてしまいます。
この現象はリスト6のプログラムでは想定していなかった現象です。
このままにしておくと、装置が誤動作してしまうかも知れません。
実はこの現象はスイッチを普通に押しても発生することがあります。

この現象については次項で説明します。

スイッチの読み込みをプログラムしてみましょう

3−4 チャタリング処理
先程、作成した“CHATA_TEST1.c”は、うまく動いてくれませんね、これは、チャタリングが発生しているからです。
チャタリングとは、スイッチなどの接点の状態が移行する際、ON/OFFを繰り返しながら最終的に安定する事を言います。
コンソールボードに使われているスイッチは、数100uS〜数mSの期間を経て安定すると言われています。
人間にとってはほんの一瞬ですが、CPUにとってはとても長い時間です。

チャタリング概念図


安定期間(この期間でスイッチが読まれればよい)

チャタリング対策にはいくつかの方法がありますが。下記の方法を使ってプログラムを作成してみましょう。
この方法で安定期間内にスイッチの読み込みが可能になるはずです。
@ 複数回のスイッチ読み込みを行い読み込みの間隔はチャタリング期間より十分長く取る。
A 1回目と2回目のスイッチの状態が一致した時のみ正常にスイッチが読み込まれたと判断する。

まず、リスト6−1のスイッチ読み込み部を、方法@に従って10mSの間を空けて2回読み取るように変更します。
さらに、方法Aを盛り込み、リスト7−1にします。
変数sw1とsw2つまり1回目と2回目のスイッチの読み込み値を比べます。
一致していれば、LEDランプの点灯処理へ入ります。

リスト7−1
data = 0x01; // LED点灯のための初期値
while(1){ // 無限ループ
sw1 = IO.PDRB.BIT.B0; // スイッチが状態読み込み1回目
wait(10); // 10mS待つ
sw2 = IO.PDRB.BIT.B0; // スイッチが状態読み込み2回目
if((sw1 == 1) && (sw2 == 1)){ // 2回とも押されている
IO.PDR8.BYTE = data; // 点灯データの出力
data <<= 1; // 1ビット左シフトする
if(data > 0x08) // LED4を越えた?
data = 0x01; // LED点灯の初期値に戻す
while((sw1 == 1) || (sw2 == 1)){ // スイッチが放されるまで待つ
sw1 = IO.PDRB.BIT.B0; // スイッチが状態読み込み1回目
wait(10); // 10mS待つ
sw2 = IO.PDRB.BIT.B0; // スイッチが状態読み込み2回目
}
}
}


10mSの間隔でスイッチの読み取りを行い、n回目の値とn+1の値を変数sw1と
sw2にそれぞれ保管する。

リスト7−1の6行目、11行目のスイッチの状態判定は、以下のように書き換えることも出来ます。
if((sw1 & sw2) == 1){ // 2回とも押されている
while((sw1 | sw2) == 1){ // スイッチが放されるまで待つ

(sw1 & sw2)では変数sw1とsw2の論理積の結果が1であれば点灯データの出力へ進みます。
(sw1 | sw2)では変数sw1とsw2の論理和の結果が0になるまでスイッチを読み込み続けます。
※ 今はスイッチが押されたかどうかしか見ていませんので、1と比較しています。

そろそろmain()関数が長くなってきました。
決まり切っているI/Oポートの初期設定部分や、2カ所で同じことをやっているスイッチの読み込み部分を関数化してすっきりさせてみましょう。
I/Oポートの初期設定の関数は、“ioinit()”とし、“main()”のI/Oポート初期設定部分をそのまま抜き出します。
スイッチ読み込み部分の関数は、“SW_Read()”とし、スイッチが押されていれば1、押されていなければ0を返すという仕様にしておきます。

リスト7−1に必要な処理を加え、関数化などの変更を施したものがリスト7です。
CHATA_TEST2.c”(リスト7)がありますので、ビルドしてCHATA_TEST2.motを書き込んで動作させてみましょう。

リスト7
#include "iodefine.h"
// プロトタイプ宣言
void main(void);
void IOinit(void);
int SW_Read(void);
void wait(int);
void wait1mS(void);
void main(void)
{
 char data;
 IOinit(); // I/Oの初期化
 data = 0x01; // LED点灯のための初期値
 while(1){ // 無限ループ
  while(SW_Read() == 0); // スイッチが押されるまで待つ
  IO.PDR8.BYTE = data; // 点灯データの出力
  data <<= 1; // 1ビット左シフトする
  if(data > 0x08) // LED4を越えた?
   data = 0x01; // LED点灯の初期値に戻す
  while(SW_Read() == 1); // スイッチが放されるまで待つ
 }
}
// I/Oの初期化
void IOinit(void)
{
 IO.PDR1.BYTE = 0x00; // ポート1に0x00を設定
 IO.PDR2.BYTE = 0x00; // ポート2に0x00を設定
 IO.PDR5.BYTE = 0x00; // LCDデータセット
 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 = 0x00; // P8に初期値(全て消灯)を出力する
 IO.PCR8 = 0x0f; // P8のLEDが接続されたビットを出力に設定
 IO.PDR1.BYTE = 0x67; // ポート1をハイレベルに設定
 IO.PCR1 = 0x67; // ポート1の必要なビットを出力に設定
 AD.CSR.BYTE = 0x03; // アナログ入力チャネルを端子3に設定
}
// スイッチの状態読み込み
int SW_Read(void)
{
 int sw1,sw2;
 sw1 = IO.PDRB.BIT.B0; // スイッチが状態読み込み1回目
 wait(10); // 10mS待つ
 sw2 = IO.PDRB.BIT.B0; // スイッチが状態読み込み2回目
 return(sw1 & sw2); // 2回とも押されていれば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ミリ秒の間ループする
}


前と同じように普通にスイッチを押してみて下さい。
スイッチを押すたびにLEDが1つずつシフトしていきます。
今度はスイッチをゆっくり(そっと)押してみてください。
LEDが勝手にシフトするという現象が出なくなっているはずです。
これでチャタリング対策が効いていることが確認できます。

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