Longan Nanoを使ってみる 11 ～ボタンの入力



# 前の記事

[Longan Nanoを使ってみる 10 ～パドルを動かす～](https://qiita.com/BUBUBB/items/eb8149c68f6e5456e7ca)

# 全体の目次
[Longan Nanoを使ってみる 1 ～ビルド環境の構築～](https://qiita.com/BUBUBB/items/7ce85ada67a3f6d1944d)
[Longan Nanoを使ってみる 2 ～デバッガの環境設定～](https://qiita.com/BUBUBB/items/864b2ef9404453810d5f)
　[Sipeed RISC-V Debugger](https://qiita.com/BUBUBB/items/61316a4df1d9f81e2864)
[Longan Nanoを使ってみる 3 ～デバッガの使用方法～](https://qiita.com/BUBUBB/items/8efa5989e7f16f67672e)
[Longan Nanoを使ってみる 4 ～printfを使ったデバッグ～](https://qiita.com/BUBUBB/items/b607a3eedc4b66e7a74f)
[Longan Nanoを使ってみる 5 ～ゲームのプロジェクトを作成～](https://qiita.com/BUBUBB/items/98d9c25120c0955d5200)
[Longan Nanoを使ってみる 6 ～文字出力～](https://qiita.com/BUBUBB/items/8dee2eabe78f38364674)
　[Longan Nanoを使ってみる ～FONTX2ファイルを作る～](https://qiita.com/BUBUBB/items/8760ac886423bf06be68)
[Longan Nanoを使ってみる 7 ～外枠とブロックを書く～](https://qiita.com/BUBUBB/items/96abb1158b413c9efd27)
　[Longan Nanoを使ってみる ～謎の画像表示関数～](https://qiita.com/BUBUBB/items/40b99468839e1daeebfb)
[Longan Nanoを使ってみる 8 ～ボールを動かす～](https://qiita.com/BUBUBB/items/92f4816af3c41ea13b9e)
[Longan Nanoを使ってみる 9 ～A/Dコンバータから入力～](https://qiita.com/BUBUBB/items/311c179f7d3a0e30495d)
[Longan Nanoを使ってみる 10 ～パドルを動かす～](https://qiita.com/BUBUBB/items/eb8149c68f6e5456e7ca)
[Longan Nanoを使ってみる 11 ～ボタンの入力](https://qiita.com/BUBUBB/items/2445940a6b0d2110ee04)
[Longan Nanoを使ってみる 12 ～ボールのロス～](https://qiita.com/BUBUBB/items/f8c6ff860de127c93cb9)
[Longan Nanoを使ってみる 13 ～ステージの遷移とゲームオーバー～](https://qiita.com/BUBUBB/items/f1ae81b835f55abe319e)
[Longan Nanoを使ってみる 14 ～PWMとサウンド～](https://qiita.com/BUBUBB/items/3b592078d30cfed0ec53)
[Longan Nanoを使ってみる 15 ～音楽を鳴らす～](https://qiita.com/BUBUBB/items/adb35e02209ad78dfa9b)
[Longan Nanoを使ってみる 16 ～とりあえずのまとめ～](https://qiita.com/BUBUBB/items/ba782ccc80f50b7ab8c3)

# はじめに
[Longan Nanoを使ってみる 9 ～A/Dコンバータから入力～](https://qiita.com/BUBUBB/items/311c179f7d3a0e30495d)で作った入力装置は、ボリュームとボタンを用意してある。パドルの動きにボリュームを使ったので、次はボタンの入力処理を行う。

作った回路では、スイッチはGPA6に接続した。　下の絵では、 A6、の部分にボタンが接続されている。また、将来、コントローラを増やすときのため、A7もボタンに使用する。

<img width=50% alt="sch2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2096509/9c4f0dda-3c7b-5270-f042-ec7a1b4029ef.png">

このポートを読み取り、ボタンの状態を取得する。


# 注意
このページは、quiita.com で公開されています。URLがqiita.com以外のサイト、例えばjpdebug.comなどのページでご覧になっている場合、悪質な無許可転載サイトで記事を見ています。正しい記事は、https://qiita.com/BUBUBB/items/7ce85ada67a3f6d1944d からリンクしています。

無許可転載サイトでの権利表記（CC BY SA 2.5、CC BY SA 3.0とCC BY SA 4.0など）は、不当な表示です。
正確な内容は、qiitaのページで参照してください。

# チャタリング

こうしたマイコンで機械的なボタンを入力するとき、チャタリングを考慮する必要がある。チャタリングとは、ボタンを押した瞬間に、ボタンの電極がバウンドしたり、電気接点の接触などが原因で、オンとオフが短い間で繰り返される現象。

下の図は、人間がスイッチを押したときの様子。理想的には、スイッチ状態がOFF/ONにすぐに切り替わるが、実際にはそうならない。スイッチがオンになった後、機械的に跳ね返りが起こったり、スイッチ端子が完全に接触する直前に、電流が漏れて流れる、など不安定な要素がある。その結果、実際のOn/OFFは、スイッチが入る（あるいは切れる）瞬間にON/OFFが繰り返されることになる
![chatter1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2096509/7153794b-56ae-aea7-09c7-045219bf4b66.png)

マイコンは十分早いため、この瞬間的なオン/オフをすべて読み取ってしまうことで、誤動作となってしまう。

チャタリングを防ぐには、アナログ回路(CR回路)による方法や、論理回路（RSラッチ）による方法があるが、今回は部品点数を減らすため、ソフトウェアによる方法を使う。ソフトウェアによる方法は低速で低信頼だが安価（追加部品なし）で簡単な方法になる。


# ソフトウェアによるチャタリング防止

ソフトウェアでチャタリング防止を行う定番の処理は、「nマイクロ秒間隔でボタンのチェックを行い、ｍ回連続してオンの場合にのみ、オンと扱う」という処理になる。チャタリングは、ボタンが完全に押されてしまった後は起こらない（ボタンが劣化すると起こるが、それはまた別の話）ので、一定の時間以上、連続して押されていることを判断すればよい。

下の図は、チェックの様子を示したもの。この例では、4回連続してオンの時にのみ、オンと扱う、と考えた。　最初の１でスイッチがオンになったことを知るが、ソフトウェア上ではこれをオンとは扱わない。その後、2で２回目もオンだが、3回目にオフが来たので、カウンタはリセットする。その後、再度スイッチがオンになり、カウントを１から再開、その後、スイッチは安定してオンのままをキープできるので、4回目で実際にオンとなる。

![chatter2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2096509/a1cd6a5d-fb5a-e53d-1530-5054794c2fa2.png)

（この例では、４回連続としたが、時間を十分長くとれば2回連続でも使い物になる。）

ボタン関連の処理を、button.cとbutton.hに実装する。

このbutton.cでは、　IsButtonPushedとIsButtonReleasedの2つの関数を実装している。これらの関数は、引数にコントローラー番号として０か１を受け取り、PortTbl配列で定義されたポート、それぞれ Ａ６、Ａ７からデータを受け取る。

チェックする間隔は、この関数を呼び出す間隔であり、50回連続してボタンが押されて（あるいは離されて）いたらtrueを返すようになっている。

FlagStatus  fr = gpio_input_bit_get (GPIOA,PortTBL[PlayerNo]);　

の部分が、実際にGPIOのA6から、値を読み出す部分で、これはAPI　[GD32V Firmware Library User Guide](http://dl.sipeed.com/LONGAN/Nano/SDK/GD32VF103_Firmware_Library_User_Guide_V1.0.pdf) の中で説明されている。

ここから読みだした、ポートの生の状態は、チャタリングを考えるとそのままでは使えない。そこで、Counterという配列の変数で、連続してオンになっている数を数え、５０を超えたら初めて trueを返すようにしている。

重要なのは（当たり前だが）この関数は、1回読んだだけでtrueを返すことはない。一回ボタンが押されたことを検知するためには、この関数は最低でも51回は呼ばれる必要がある。ボタンを検知する必要がない場合でも、常にこの処理は定期的に呼び出す必要があるということ。


#### button.h
```
#ifndef __button_h__
#define __button_h__
bool IsButtonReleased(int PlayerNo);
bool IsButtonPushed(int PlayerNo);

#endif
```

#### button.c

```
#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"



// ボタンが押され続けた
static const uint32_t PortTBL[] = {GPIO_PIN_6,GPIO_PIN_7};
bool IsButtonPushed(int PlayerNo)
{
	static u8 Counter[2];

	FlagStatus  fr = gpio_input_bit_get (GPIOA,PortTBL[PlayerNo]);
	if (!fr) {
		if (Counter[PlayerNo] == 0xFF) Counter[PlayerNo]=0xFE;
		Counter[PlayerNo]++;
	} else {
		Counter[PlayerNo]=0;
	}
	if (Counter[PlayerNo] >= 50) {
		return TRUE;
	} 
	return FALSE;
}
// ボタンが離され続けた
bool IsButtonReleased(int PlayerNo)
{
	static u8 Counter[2];
	FlagStatus  fr = gpio_input_bit_get (GPIOA,PortTBL[PlayerNo]);
	if (fr) {
		if (Counter[PlayerNo] == 0xFF) Counter[PlayerNo]=0xFE;
		Counter[PlayerNo]++;
	} else {
		Counter[PlayerNo]=0;
	}
	if (Counter[PlayerNo] >= 50) {
		return TRUE;
	} 
	return FALSE;
}

```

### ボタン押下を検出する

実際の、ユーザーインタフェースのボタンの多くは、押した時ではなく話したときに反応する。そのため、このプログラムも同じように処理をする。ボタンは、基本的に離された状態にある。そのため、単に IsButtonReleasedを呼び出すだけだと、いきなり最初からボタンが「押された」（実際には離された）という状態になってしまう。

そのため、「ボタンが押されたあとに離された」という実装が必要になる。

ここで実装する CheckP1Buttonは、定期的に呼び出され、ボタンが押された後に離されたら戻り値で trueを返すと同時に、 グローバル変数 p1ButtonPushedをtrueにする。この、グローバル変数がtrueになっているのは、一瞬だけで、次回CheckP1Buttonが呼び出されるとクリアされる。


#### button.h

```
  :
extern bool CheckP1Button();
extern bool p1ButtonPushed;
extern bool p2ButtonPushed;
  :
```



#### button.c

```

bool p1ButtonPushed = FALSE;
bool p2ButtonPushed = FALSE;


// 一定時間間隔で呼び出され、ボタンが押されたかを監視する。
// 一般的なボタンは、押したときに反応するのではなく、離した時に反応する。
// そのため、この処理では、ボタンの状態を示す p1ButtonPushed グローバル変数、関数の戻り値とも、
// ボタンが押された時ではなく、離されたときにtrueとなる


bool CheckP1Button()
{
	p1ButtonPushed = RESET;			// ボタンの変化は一回だけトリガーされれば良い
	static bool  isButton1 = FALSE;
	if (isButton1 == FALSE) {
		if (IsButtonPushed(0)) { //ボタンがOFF→ONに変化
			isButton1 = TRUE; 								// ボタンが押された
		} 
	}  else {
		if (IsButtonReleased(0)) {//ボタンがON→OFFに変化
			isButton1 = FALSE;
			p1ButtonPushed = TRUE;
            return TRUE;
		}
	}
    return FALSE;
}
```

# 動作を確認する

ゲームのメインプログラムから、この関数を呼び出して、実際に期待する動作をするか、確認する。チェックするポイントは、次の通り
- 一回押しただけなのに、複数回押された、という状態にならない
- ボタンは押した時ではなく、離したときに反応する

メインプログラムに、printfを入れて動作を確認する。ソースコードレベルのデバッガでもよいが、ボタンを押しながらマウスでデバッグというのも不便。
LEDを点灯させるのも、こうした「繰り返される一瞬のイベント」をテストするには不便。やはり原始的なprintfデバッグ（[Longan Nanoを使ってみる 4 ～printfを使ったデバッグ～](https://qiita.com/BUBUBB/items/b607a3eedc4b66e7a74f)）が便利。

メインループのウエイトを抜けた直後（タイマー割込みにより、ループから抜けた直後）に、ボタンのチェックを行い、printfを追加する。

#### game.c

```
    :
    while (gameState != STATE_GAMEOVER) {   //ゲームオーバーになるまで繰り返す
		timer_enable(TIMER5);               // タイマーを有効にする
        // タイマーのウェイト処理。wakeFlagが割り込みルーチン内で１になるまで無限ループする
		
        while(WakeFlag == 0) {
            delay_1ms(10);
            break;
        }

        if (CheckP1Button()) {
            printf("pushed!\r\n\r\n");
        } 
      :

```

モニターターミナルには、ボタンを押す（離す）たびに、pushed!と表示される。

<img width=60% alt="console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2096509/142cd4e8-086e-2aa3-346f-c9f13c8ba970.png">

動作確認が終わったら、テスト用のコードは削除しておく。

# ここまででできたこと

接続したボタンの状態を読み込み、チャタリングを除去してプログラムから利用できるようになった。

# 次の記事に進む

[Longan Nanoを使ってみる 12 ～ボールのロス～](https://qiita.com/BUBUBB/items/f8c6ff860de127c93cb9)

# 今回の追加を反映したソースコード

#### button.h

```
#ifndef __button_h__
#define __button_h__

bool IsButtonReleased(int PlayerNo);
bool IsButtonPushed(int PlayerNo);

extern bool CheckP1Button();
extern bool p1ButtonPushed;
extern bool p2ButtonPushed;

#endif
```

#### button.c

```
#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"



// ボタンが押され続けた
static const uint32_t PortTBL[] = {GPIO_PIN_6,GPIO_PIN_7};
bool IsButtonPushed(int PlayerNo)
{
	static u8 Counter[2];

	FlagStatus  fr = gpio_input_bit_get (GPIOA,PortTBL[PlayerNo]);
	if (!fr) {
		if (Counter[PlayerNo] == 0xFF) Counter[PlayerNo]=0xFE;
		Counter[PlayerNo]++;
	} else {
		Counter[PlayerNo]=0;
	}
	if (Counter[PlayerNo] >= 50) {
		return TRUE;
	} 
	return FALSE;
}
// ボタンが離され続けた
bool IsButtonReleased(int PlayerNo)
{
	static u8 Counter[2];
	FlagStatus  fr = gpio_input_bit_get (GPIOA,PortTBL[PlayerNo]);
	if (fr) {
		if (Counter[PlayerNo] == 0xFF) Counter[PlayerNo]=0xFE;
		Counter[PlayerNo]++;
	} else {
		Counter[PlayerNo]=0;
	}
	if (Counter[PlayerNo] >= 50) {
		return TRUE;
	} 
	return FALSE;
}


bool p1ButtonPushed = FALSE;
bool p2ButtonPushed = FALSE;


// 一定時間間隔で呼び出され、ボタンが押されたかを監視する。
// 一般的なボタンは、押したときに反応するのではなく、離した時に反応する。
// そのため、この処理では、ボタンの状態を示す p1ButtonPushed グローバル変数、関数の戻り値とも、
// ボタンが押された時ではなく、離されたときにtrueとなる


bool CheckP1Button()
{
	p1ButtonPushed = RESET;			// ボタンの変化は一回だけトリガーされれば良い
	static bool  isButton1 = FALSE;
	if (isButton1 == FALSE) {
		if (IsButtonPushed(0)) { //ボタンがOFF→ONに変化
			isButton1 = TRUE; 								// ボタンが押された
		} 
	}  else {
		if (IsButtonReleased(0)) {//ボタンがON→OFFに変化
			isButton1 = FALSE;
			p1ButtonPushed = TRUE;
            return TRUE;
		}
	}
    return FALSE;
}
```