こんにちは、ナナです。
スイッチを使うことで機器とユーザーがコミュニケーションを取ることができます。本記事ではスイッチを使った入力検知の方法を学びます。
本記事では次の疑問点を解消する内容となっています。
では、スイッチの仕組みを学んでいきましょう。
スイッチの役割
皆さんの生活の中でスイッチ(SW:switch)を触らない日はないでしょう。スイッチは多くの組み込み機器においてもユーザーからの入力を得るために搭載されています。
ビュートローバーに搭載されたスイッチとスイッチの種類
スイッチには様々なタイプの形状があります。
ビュートローバーに搭載されているスイッチはタクタイルスイッチやプッシュスイッチと呼ばれるものです。特徴として、通常はスイッチOFFの状態ですが、人が押している時だけONになるスイッチです。押したときに「カチッ」という音がします。
スイッチには他にも形状があります。トグルスイッチも代表的なスイッチの一つです。
スイッチ状態を知るための制御方法を知ろう
スイッチの回路と制御方法
タクタイルスイッチは次のような回路図で表現されます。押し込むことで通電するイメージがわかるでしょう。
ではビュートローバーの回路図を見てみます。一部簡略化しましたが次のような回路図となっていることがわかるでしょう。スイッチはポートの「P74」に接続されているのがわかります。
スイッチが押されていない時はGND(電流の回収場所)への道が遮断されているため、マイコン側に流れ込むような回路になっています。この状態はポートP74の電圧が高くなることを示します。
スイッチが押されるとGND側への道が通り、より流れやすいGND側に電流は流れ込みます。結果、ポートP74の電圧は低くなります。
このようにスイッチの押下状態によりポートへの入力電圧が変化します。この電圧状態をプログラムから読み取ることでスイッチが押されているかどうかを知ることができるということです。
スイッチの制御とI/Oポートレジスタの関係
スイッチもLEDと同様にI/Oポート機能を利用します。スイッチとLEDの大きな違いは入力系デバイスと出力系デバイスの分類が異なることです。
改めておさらいしますが、出力系と入力系デバイスはマイコンから見て制御方法が逆になります。
この違いはプログラムのI/Oポートレジスタの制御方法に大きな違いを生むため、明確に意識する必要があります。
ポートコントロールレジスタの設定
ポートコントロールレジスタはポートの入力/出力方向を設定するためのレジスタでした。スイッチは入力系デバイスですから「入力方向」に設定することになります。
ポートデータレジスタの読み取り
ポートコントロールレジスタが入力方向ですから、ポートデータレジスタは値を読み取ることに意味があります。スイッチの場合はユーザーが押す/押さないによってポートへの入力電圧が変化します。その電圧状態がポートデータレジスタに反映されるのです。
スイッチに対するポートコントロールレジスタとポートデータレジスタの扱いまとめ
スイッチに対する2つのレジスタの関係性をまとめましょう。スイッチはP74に接続されているため制御すべきレジスタはPCR7とPDR7になります。
PCRが入力の場合はPDRの値が外部からの入力電圧に従い自動的にレジスタに値が反映されます。その値を読み取ることで押下状態の判定を行います。今回の回路ではスイッチが押されたときにPDRのB4が0になることに注意が必要です。
PCR7においてビット4番以外に関しては一旦入力設定にしておきましょう。変更すべきタイミングが来たら変更します。
スイッチ制御を行うSWモジュールの作成
スイッチの制御方法がわかりましたのでプログラムの作成を行います。LEDモジュールの時と同様にSWモジュールのsw.cとsw.hをHEWプロジェクトに追加してください。
追加方法を忘れてしまった人はLED制御の章を見直しましょう。
SWモジュールの役割検討
SWモジュールもLEDモジュールと同様にスイッチに関わるレジスタ制御を隠蔽させます。
SW制御に対する考察と要望
- スイッチは1つしかない
- スイッチには押す・押さないの2つの状態がある
- スイッチを押しているか・押していないかを外部モジュールへ通知したい
ではこれらを満たすインターフェースを検討します。
SWモジュールのインターフェース仕様
それではSWモジュールのインターフェース仕様を決定します。皆さんはこの仕様を元にプログラムを作成していていただきます。
1.提供ヘッダファイル名
#include “sw.h”
2.定数定義
3.インターフェース定義
3.1 初期化
3.2 押下状態の取得
課題:SWモジュールを使ってスイッチ状態を取得してみよう
マイコン入門編での課題は、システムに対して順にプログラムを追加していく形とします。そのため、前回課題のLEDモジュールを踏襲したうえでSWモジュールを追加していきます。
課題1
課題内容
main.cに対して次の処理を追加せよ。
- MAINモジュールからSWモジュールを利用するためsw.hをインクルードせよ
- main_init関数にてスイッチに対するPCRを入力に設定せよ
- main_init関数にてLEDモジュールを初期化するSw_init関数を呼べ
main_init関数に関しては前章のLEDモジュール時に作成した下記をベースに処理を追加せよ。
//------------------------------------------------
// 概 要:システム初期化
//------------------------------------------------
void main_init(void)
{
//---------------------------------------------------
// ウォッチドッグタイマの停止(消さないこと)
//---------------------------------------------------
WDT.TCSRWD.BYTE = 0x92;
WDT.TCSRWD.BYTE = 0x92;
//---------------------------------------------------
// PCRの設定(PCRは書き込み専用レジスタ)
//---------------------------------------------------
IO.PCR6 = 0x11; // P60,P64を出力
//---------------------------------------------------
// モジュールの初期化(各モジュールの初期化を実施)
//---------------------------------------------------
Led_init();
}
main.c
#include "iodefine.h"
#include "led.h"
#include "sw.h" // SWモジュールを使用するためインクルード
//------------------------------------------------
// 内部プロトタイプ宣言
//------------------------------------------------
void main_init(void);
//------------------------------------------------
// 概 要:エントリーポイント
//------------------------------------------------
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
}
}
//------------------------------------------------
// 概 要:システム初期化
//------------------------------------------------
void main_init(void)
{
//---------------------------------------------------
// ウォッチドッグタイマの停止(消さないこと)
//---------------------------------------------------
WDT.TCSRWD.BYTE = 0x92;
WDT.TCSRWD.BYTE = 0x92;
//---------------------------------------------------
// PCRの設定(PCRは書き込み専用レジスタ)
//---------------------------------------------------
IO.PCR6 = 0x11; // P60,P64を出力
IO.PCR7 = 0x00; // P74を入力
//---------------------------------------------------
// モジュールの初期化(各モジュールの初期化を実施)
//---------------------------------------------------
Led_init();
Sw_init();
}
- main関数内のwhile文の処理は本章にて差し替えるためLEDの点灯処理は削除してよい。
課題2
課題内容
インターフェース仕様に従いSWモジュールを作成せよ。sw.cとsw.hは次のプログラムをベースにして修正を加えよ。
sw.h
#ifndef SW_H
#define SW_H
//------------------------------------------------
//------------------------------------------------
// マクロ定義(Macro definition)
//------------------------------------------------
//------------------------------------------------
// 型定義(Type definition)
//------------------------------------------------
//------------------------------------------------
// プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
//------------------------------------------------
#endif // SW_H
sw.hに追記すべきこと
- SWの押下状態の定数をマクロ定義にて実装せよ。
- インターフェースのプロトタイプ宣言を追加せよ。
sw.c
#include "iodefine.h"
#include "sw.h"
//------------------------------------------------
// 概 要:初期化
// 引 数:なし
// 戻り値:なし
//------------------------------------------------
void Sw_init(void)
{
}
//------------------------------------------------
// 概 要:スイッチ状態の取得
// 引 数:なし
// 戻り値:D_SW_STATE_OFF 押されていない
// D_SW_STATE_ON 押されている
//------------------------------------------------
int Sw_getPressed(void)
{
}
sw.cに追記すべきこと
- Sw_init関数では何も処理する必要はない。そのままでよい。
- Sw_getPressed関数はSWのPDRの値をチェックし、ユーザーがスイッチを押しているか、押していないかを判定する。判定結果に従って戻り値を決定する。
sw.h
#ifndef SW_H
#define SW_H
//------------------------------------------------
//------------------------------------------------
// マクロ定義(Macro definition)
//------------------------------------------------
#define D_SW_PRESSED_OFF (0) // スイッチOFF
#define D_SW_PRESSED_ON (1) // スイッチON
//------------------------------------------------
// 型定義(Type definition)
//------------------------------------------------
//------------------------------------------------
// プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
void Sw_init(void);
int Sw_getPressed(void);
//------------------------------------------------
#endif // SW_H
- スイッチの押下状態をマクロ定義するのと、インターフェースのプロトタイプ宣言を行う。
sw.c
#include "iodefine.h"
#include "sw.h"
//------------------------------------------------
// 概 要:初期化
// 引 数:なし
// 戻り値:なし
//------------------------------------------------
void Sw_init(void)
{
}
//------------------------------------------------
// 概 要:スイッチ状態の取得
// 引 数:なし
// 戻り値:D_SW_STATE_OFF 押されていない
// D_SW_STATE_ON 押されている
//------------------------------------------------
int Sw_getPressed(void)
{
// 電圧Lowの時にスイッチON
if (IO.PDR7.BIT.B4 == 0)
{
// スイッチが押されている
return D_SW_PRESSED_ON;
}
else
{
// スイッチが押されていない
return D_SW_PRESSED_OFF;
}
}
- スイッチはP74のためIO.PDR7.BIT.B4にユーザーが押しているか、押していないかの電圧状態が反映されている。回路構成上、電圧状態がLowの時にスイッチが押されていることになるため戻り値をONとして返却する。
課題3
課題内容
MAINモジュールのmain関数を変更し、次の動作になるようにせよ。
- スイッチを押していない時は、グリーンLEDを点灯、オレンジLEDを消灯
- スイッチを押している時は、オレンジLEDを点灯、グリーンLEDを消灯
※前章のLEDの点滅処理は消してよいものとする。
課題が完成したらビルドを行いビュートローバー上で動作させ、期待動作通りに動くことを確認せよ。
main.c
//------------------------------------------------
// 概 要:エントリーポイント
//------------------------------------------------
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
}
}
期待動作
スイッチを押していない時
- グリーンLEDが点灯
- オレンジLEDが消灯
スイッチを押している時
- グリーンLEDが消灯
- オレンジLEDが点灯
main.c
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
// スイッチが押されているか判定
if (Sw_getPressed() == D_SW_PRESSED_ON)
{
// オレンジLED点灯・グリーンLED消灯
Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_ON);
Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_OFF);
}
else
{
// オレンジLED消灯・グリーンLED点灯
Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_OFF);
Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_ON);
}
}
}
- スイッチのインターフェースであるSw_getPressed関数を呼び出し、押しているか、押されていないかをif文判定する。
- LEDの状態変更は前章で作成したLed_setLight関数を使用することに注意。ポート6をここで直接制御しないこと。
Q&A:スイッチ制御に関するよくある質問
これは将来性を考えて追加されているインターフェースです。モジュールとはシステム起動直後にまずはあるべき状態にしておくのです。
そのための処理が初期化用インターフェースなのです。
現時点でSWモジュールには初期化すべき項目はありませんが、未来においては初期化すべき項目が出てくるかもしれません。その時のために今は空っぽであっても、初期化するという枠組みを先に作っておくのです。
開発者が常に意識すべきなのは、今が楽なのではなく未来が楽になるかどうかなのです。