2019年8月8日木曜日

[Elegoo:14] 赤外線ワイヤレスリモコンと
赤外線受信モジュール


概要 (Overview)
  • 赤外線リモコンのコードフォーマットについて学ぶ
  • 赤外線ワイヤレスリモコンからのコードを赤外線受信モジュールで受信し、
    シリアルモニタに表示する



参照 (Reference)
赤外線とは
  • 可視光線の赤色より波長が長く電波より波長の短い電磁波の一種
  • ヒトの目では見られない光
  • 分光学などの分野で IR (infrared) と略称される
波長による電磁波の分類(nm:ナノメートル)
マイクロ波・短波・ラジオ波・携帯通信網 等
1,000nm
|赤外線
760nm
可視光線 (光の3原色(青/緑/赤)の波長範囲)

紫外線(10-380nm)
380nm

X線(0.01-10nm)
短ガンマ線(0.01nm以下)
0nm

赤外線リモコン
  • 赤外線LEDの点灯・消灯を切り替えて命令を送っている
  • また太陽や照明など他の光と区別するために、 大抵の赤外線リモコンは発光する周波数を38KHzと設定している
  • 国内製のほとんどの家電の赤外線リモコンは下記3種類の代表的な
    赤外線リモコンコードフォーマットを採用している
    1. NEC方式(映像音響機器)
      => NEC,TOSHIBA,HITACHI,SANYO,JVCなど
    2. 家電製品協会(AEHA)方式(生活家電機器)
      => PANASONIC,SHARP,YAMAHAなど
    3. 独自方式
      => SONYなど
  • 赤外線リモートライブラリ を利用すると国内・海外製の家電フォーマットを使用できる
    =>NEC, SONY, SANYO, MITSUBISHI, RC5, RC6, DISH, SHARP, PANASONIC, JVC, LG, AIWA_RC_T501
  • 国内製ではフォーマットやベンダーコードが重複しないよう配慮され、信号内容の違いにより混信や誤動作を防いでいる。海外製ではこのフォーマットに配慮しないで製造されたものもあり、輸入された家電品が日本製の別の機器に付属するリモコンの信号で誤動作するものがある



使用する電子部品 (Component Required)
電子種別電子部品個数
マイコン Elegoo UNO R3 マイコンボード1
USBケーブル1
能動 赤外線ワイヤレスリモコン1
赤外線受信モジュール1
機構 赤外線ジャンプワイヤ(赤)[オス-メス]1
赤外線ジャンプワイヤ(黒)[オス-メス]1
赤外線ジャンプワイヤ(黄)[オス-メス]1



電子部品について (Component Introduction)
赤外線受信モジュール

  • IR受信機は赤外線を受信するように調整されたフォトセルを備えており、 赤外線リモコンコードをデコードする。殆どリモコンの検出に使用される
  • 38KHzで変調された赤外線を探す復調器があり太陽光や照明等他の光と区別する
  • 周波数のピークは38HkzでLED色のピークは940nm(900-1000)
  • IR LEDのデータシートで波長を確認すること
《ピン》
GND(G)GND
Vcc(R)電源入力(5V or 3V)
DATA(Y)DATA



電子回路 (Electronic Circuit)
回路図 (Schematic)


実体配線図 (Wiring Diagram)





実践 (To Practice)
配線写真 (Example Picture)


プログラム (Code)

赤外線リモコンコードフォーマット[国内製]
IR_Receiver.ino:日本国内の殆どの家電をカバー [nT]:パルスON/OFFの長さ(変調単位時間T)
方式時間(T) リーダー部/ リピート送信 DATA[BIT]0の時1の時 STOP(データ終了)
NEC562u[sec] ON(16T)+OFF(8T)/ ON(16T)+OFF(4T) ON(1T)+OFF(1T)/ ON(1T)+OFF(3T) ON(1T)
AEHA425u[sec] ON( 8T)+OFF(4T)/ ON( 8T)+OFF(8T) ON(1T)+OFF(1T)/ ON(1T)+OFF(3T) ON(1T)
SONY600u[sec] ON( 4T)[+OFF(1T)]/ - ON(1T)+OFF(1T)/ ON(2T)+OFF(1T) -

DATA[]:
NEC :32ビット固定長(データ送信時)、なし(リピート送信時)
AEHA :可変長(データ送信時)、なし(リピート送信時)
SONY :可変長, ヘッダー部を含め同じコードを3回送る(間隔:45ms)

スケッチ
/*************************************************************/
/* 【 Elegoo:14 】赤外線ワイヤレスリモコンと赤外線受信モジュール */
/*    赤外線ワイヤレスリモコンからのコードを                     */
/*    赤外線受信モジュールで受信し、                            */
/*    シリアルモニタに表示する                                 */
/*                                                           */
/*   赤外線リモコンコードフォーマット使用 [国内製]               */
/*************************************************************/

int pin = 11;           //受信モジュール出力の信号入力ピン
int num = 0;
unsigned long st;       //計測開始時間μs
unsigned long dur;      //時間μs
int num_max = 400;      //配列要素数
long time[400];         //num_maxと同じに
boolean mode = HIGH;    //赤外線モジュール出力がHIGHの待機から始まる
long timeout = 120000;  //無受信時のタイムアウトμs

/*********/
/* setup */
/*********/
void setup() {
  Serial.begin(9600);
  pinMode(pin, INPUT);
}

/********/
/* loop */
/********/
void loop() {

  // --------------------------------------------------
  //計測開始の時間を記憶
  st = micros();
  //LOW,HIGHが変化するまでループで待機
  while(digitalRead(pin) == mode){    
    //無受信時のタイムアウトでループを抜ける
    if(micros() - st > timeout){
      break;
    }
  }

  // --------------------------------------------------
  // 時間を計算
  dur = micros() - st;
  if(mode == LOW){
    time[num] = dur;
  }
  else{
    //LEDオフの時間(受信センサー出力はHIGH)はマイナス符号をつける
    time[num] = dur * -1;
  }

  // --------------------------------------------------
  // タイムアウトした時
  if(dur > timeout){
    //配列に格納されていれば、シリアル出力
    if(num > 0){
      ser_pri();
    }
    //タイムアウトかつデータ無しなので変数リセット
    num = -1;
    mode = LOW;
  }

  // --------------------------------------------------
  // データが多すぎた時の処理
  num += 1;
  mode =! mode;
  if(num == num_max){
    Serial.println("Too Many Data");
    delay(2000);
    ser_pri();
    num = 0;
    mode = HIGH;
  }
}

/*********************/
//受信データのシリアル出力
/*********************/
void ser_pri(){
  int data = 0;
  Serial.println("");

//  //配列のデータをそのまま出力
//  for(int i = 0; i < num; i++){
//    Serial.print(i);
//    Serial.print("\t");
//    Serial.println(time[i]);
//  }

  // --------------------------------------------------
  // メーカーフォーマットを判別
  // リーダー部のON-OFF時間+-1Tの範囲で判別
  // [ 1:NECフォーマット2:家電製品協会(AEHA)フォーマット3:ソニーフォーマット0:不明 ]
  // NEC:T=562 leader 16T+8T repeat 16T+4T
  // 家電製品協会:T=425 leader 8T+4T repeat 8T+8T
  // ソニー:T=600 leader 4T
  // --------------------------------------------------
  Serial.println("");
  byte data_f = 0;

  // --------------------------------------------------
  // NECフォーマット
  if(time[1] >= 8430 && time[1] < 9554 && time[2]*-1 >= 3934 && time[2]*-1 < 5058){
    Serial.println("NEC Format");
    data_f = 1;
  }
  // --------------------------------------------------
  // 家電製品協会フォーマット
  if(time[1] >= 2975 && time[1] < 3825 && time[2]*-1 >= 1275 && time[2]*-1 < 2125){
    Serial.println("AEHA Format");
    data_f = 2;
  }
  // --------------------------------------------------
  // SONYフォーマット
  if(time[1] >= 1800 && time[1] < 3000 && time[2]*-1 < 600){
    Serial.println("SONY Format");
    data_f = 3;
  }
  // --------------------------------------------------
  // フォーマット不明
  if(data_f == 0){
    Serial.println("UNKNOWN Format");
  }
 
 // --------------------------------------------------
  //NECフォーマットの時
  // --------------------------------------------------
  if(data_f == 1){
    byte dig = 0;
    for(int j = 4; j < num; j=j+2){
      if(time[j]* -1  < 1000){
        Serial.print("0");
      }
      if(time[j]* -1 >=1000 && time[j]* -1 <1800 ){
        Serial.print("1");
        bitSet(data,dig);
      }

      //リピートコード
      if(time[j]* -1 >=1800 && time[j]* -1 <2500){
        //4つ前のデータもリピートコードかどうか
        if((time[j-4]* -1 >=1800) && (time[j-4]* -1 <2500)){
          Serial.print(" .");
        }
        else{
          Serial.print("REPEAT CODE");
        }
        dig = 0;
      }
      dig += 1;

      //8ビットごとに区切り
      if(dig == 8){
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        dig = 0;
        data = 0;
      }
    }
  }

  // --------------------------------------------------
  // 家電製品協会フォーマットの時
  // --------------------------------------------------
  if(data_f == 2){
    byte dig = 0;
    for(int j = 4; j < num; j=j+2){
      if(time[j]* -1  < 1000){
        Serial.print("0");
      }
      if(time[j]* -1 >=1000 && time[j]* -1 <1800 ){
        Serial.print("1");
        bitSet(data,dig);
      }
      
      //リピートコード
      if(time[j]* -1 >=2975 && time[j]* -1 <3825){
        //4つ前のデータもリピートコードかどうか
        if((time[j-4]* -1 >=2975) && (time[j-4]* -1 <3825)){
          Serial.print(" .");
        }
        else{
          Serial.print("REPEAT CODE");
        }
        dig = 0;
      }

      //同信号繰り返し
      if(time[j]* -1 >=2000){
        Serial.println("REPEAT");
        j += 2;
        dig = -1;
      }
      dig += 1;

      //8ビットごとに区切り
      if(dig == 8){
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        dig = 0;
        data = 0;
      }
    }
  }

  // --------------------------------------------------
  // ソニーフォーマットの時
  // --------------------------------------------------
  if(data_f == 3){
    byte dig = 0;
    boolean sony_func = 0;
    for(int j = 3; j < num; j=j+2){
      if(time[j]  < 1000){
        Serial.print("0");
      }
      if(time[j] >=1000 && time[j] < 1800 ){
        Serial.print("1");
        bitSet(data,dig);
      }

      //同信号繰り返し
      if(time[j] >= 2000){
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        Serial.println("REPEAT");
        dig = -1;
        data = 0;
        sony_func = 0;
      }
      dig += 1;

      //最初の7ビットが機能コード、以下5~13ビットは機器識別コード
      if(dig == 7 && sony_func == 0){
        sony_func = 1;
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        dig = 0;
        data = 0;
      }
    }
    Serial.print("\t");
    Serial.print(data,HEX);
    data = 0;
  }

  // --------------------------------------------------
  // フォーマット不明の時
  // --------------------------------------------------
  if(data_f == 0){
  }

  Serial.println("");
}

赤外線リモートライブラリ使用 [国内・海外製]

書式引数説明
IRrecv objIRrecv(int PinNO) objIRrecv:IRrecvオブジェクト
PinNO:ピンNO
IRrecvオブジェクトを作成してピンを割り当てる
objIRrecv.enableIRIn() - 受信を開始
objIRRecv.blink13(true/false) true/false:許可/不可 受信時にLEDの点灯を許可する
decode_results results results :受信結果 受信結果の格納先を作成
受信結果
decode_typeメーカー名
value受信データ
bits受信ビット数
rawbuf[]赤外線ON/OFFの切替時間(μ秒)の配列
rawlen赤外線のON/OFF回数
int objIRrecv.decode(&results) &results :受信結果 受信したコードがある場合は1を返しそれ以外の場合は0を返す
int objIRrecv.resume() .decode()の返り値をリセット

スケッチ
/*************************************************************/
/* 【 Elegoo:14 】赤外線ワイヤレスリモコンと赤外線受信モジュール */
/*    赤外線ワイヤレスリモコンからのコードを                     */
/*    赤外線受信モジュールで受信し、                            */
/*    シリアルモニタに表示する                                 */
/*                                                           */
/*   赤外線リモートライブラリ使用 [国内・海外製]                */
/*************************************************************/

/*--------------------------------------------*/
/* 赤外線リモートライブラリ                     */
/*--------------------------------------------*/
#include <irremote .h> //Ver2.0.1 June, 2015 Panasonic受信不安定

/*--------------------------------------------*/
// 受信用
#define PIN_RECEIVER 11           // 赤外線受信用ピン 
IRrecv objIRRecv(PIN_RECEIVER);   // 赤外線受信用オブジェクトを作成
decode_results objResults;        // 赤外線受信結果を格納する


/**************************************/
/* 関数名:getDecodeTypeValue          */
/*  内容:受信したType(メーカー)を取得する */
/*  引数:無し                         */
/* 戻り値:無し                         */
/**************************************/
void  getDecodeTypeValue(){

  Serial.print(objResults.decode_type); Serial.print(" : ");

  // --------------------------------------------------
  switch (objResults.decode_type) {
    default:
    case UNKNOWN:      Serial.print("UNKNOWN");       break ;
    case NEC:          Serial.print("NEC");           dispIRcodeNEC(); break ;
    case SONY:         Serial.print("SONY");          break ;
    case RC5:          Serial.print("RC5");           break ;
    case RC6:          Serial.print("RC6");           break ;
    case DISH:         Serial.print("DISH");          break ;
    case SHARP:        Serial.print("SHARP");         break ;
    case JVC:          Serial.print("JVC");           break ;
    case SANYO:        Serial.print("SANYO");         break ;
    case MITSUBISHI:   Serial.print("MITSUBISHI");    break ;
    case SAMSUNG:      Serial.print("SAMSUNG");       break ;
    case LG:           Serial.print("LG");            break ;
    case WHYNTER:      Serial.print("WHYNTER");       break ;
    case AIWA_RC_T501: Serial.print("AIWA_RC_T501");  break ;
    case PANASONIC:    Serial.print("PANASONIC : Address[");
                       Serial.print(objResults.address, HEX);
                       Serial.print("]");             break ;
    case DENON:        Serial.print("Denon");         break ;
//    case AEHA:          Serial.print("AEHA");           break ;
//    case AEHA:         Serial.print("AEHA : Address[");
//                       Serial.print(objResults.address, HEX);
//                       Serial.print("]");             break ;
  }

}

/*************************************************/
/* 関数名:dispIRcodeNEC                          */
/*  内容:受信したType[NEC]のValue(IRcode)を表示する */
/*  引数:無し                                    */
/* 戻り値:無し                                    */
/*************************************************/
void dispIRcodeNEC() {
  Serial.print(" : "); Serial.print(objResults.value, HEX);
  Serial.print(" ==> ");

  if( objResults.decode_type == NEC ){
    // 受信したValue(IRcode)
    switch(objResults.value){
      case 0xFFA25D:    Serial.println("POWER");        break;
      case 0xFFE21D:    Serial.println("FUNC/STOP");    break;
      case 0xFF629D:    Serial.println("VOL+");         break;
      case 0xFF22DD:    Serial.println("FAST BACK");    break;
      case 0xFF02FD:    Serial.println("PAUSE");        break;
      case 0xFFC23D:    Serial.println("FAST FORWARD"); break;
      case 0xFFE01F:    Serial.println("DOWN");         break;
      case 0xFFA857:    Serial.println("VOL-");         break;
      case 0xFF906F:    Serial.println("UP");           break;
      case 0xFF9867:    Serial.println("EQ");           break;
      case 0xFFB04F:    Serial.println("ST/REPT");      break;
      case 0xFF6897:    Serial.println("0");            break;
      case 0xFF30CF:    Serial.println("1");            break;
      case 0xFF18E7:    Serial.println("2");            break;
      case 0xFF7A85:    Serial.println("3");            break;
      case 0xFF10EF:    Serial.println("4");            break;
      case 0xFF38C7:    Serial.println("5");            break;
      case 0xFF5AA5:    Serial.println("6");            break;
      case 0xFF42BD:    Serial.println("7");            break;
      case 0xFF4AB5:    Serial.println("8");            break;
      case 0xFF52AD:    Serial.println("9");            break;
      case 0xFFFFFFFF:  Serial.println(" REPEAT");      break;  
      default:          Serial.println(" other button");
    }
  }

  Serial.println(" ");
  delay(500);
}

/********/
/* loop */
/********/
void loop() {

  // 赤外線を受信
  if (objIRRecv.decode(&objResults)) {
    // 受信したType(メーカー)とValue(ボタン)を表示する
    getDecodeTypeValue();
    // 受信機をリセット
    objIRRecv.resume(); 
  }  
}

/*********/
/* setup */
/*********/
void setup() {
  // --------------------------------------------------
  // シリアルモニタ開始
  Serial.begin(9600);

  // --------------------------------------------------
  // 受信を開始
  objIRRecv.enableIRIn();
  // 受信時にLEDの点灯を許可する
  objIRRecv.blink13(true);
}

結果(動画)・モニタリング
リモコンの上段①POWER②VOL+③FUNC/STOPボタン押下


赤外線リモコンコードフォーマット[国内製]

赤外線リモートライブラリ使用 [国内・海外製]
  • 他のメーカーのリモコンも試したが、メーカーとフォーマットが一致するとは限らない様子
  • 電子工作でリモコンによる赤外線通信を行う場合は以下の手順が良さそう
    ①上記スケッチで使用するリモコンのメーカーとIR Codeを調査
    ②その結果(メーカーとIR Code)を利用して制御する

2019年6月11日火曜日

[Elegoo:13] アナログ ジョイスティックモジュール




概要 (Overview)
  • アナログ ジョイスティックについて学ぶ
  • XY軸の位置データと押し込んだ状態をシリアルモニタに表示する



使用する電子部品 (Component Required)
電子種別電子部品個数
マイコン Elegoo UNO R3 マイコンボード1
USBケーブル1
能動 アナログ ジョイスティック1
機構 ジャンプワイヤ(黄)[オス-メス]1
ジャンプワイヤ(緑)[オス-メス]1
ジャンプワイヤ(青)[オス-メス]1
ジャンプワイヤ(赤)[オス-メス]1
ジャンプワイヤ(黒)[オス-メス]1




電子部品について (Component Introduction)

アナログ ジョイスティック


  • アナログ ジョイスティックはX/Y二軸のポテンショメータと、SWのプッシュボタンの組み合わせ
  • X/Y軸:ジョイスティックが中立位置にあるときアナログ値[512]を、
    ジョイスティックを倒したX/Y方向にアナログ値[0-1024]を返す
  • SW:ジョイスティックを押し込むとデジタル値[LOW]を返す
  • SWピンから安定した読み取るため、Arduinoデジタルピンに内蔵されたプルアップ抵抗を介してVCCに接続する

《ピン》
GNDGND
Vcc電源入力(5V or 3V)
VRxアナログIN [0-1024]
VRyアナログIN [0-1024]
SWデジタルIN (プルアップ抵抗付)プッシュ時[LOW]



電子回路 (Electronic Circuit)

回路図 (Schematic)


実体配線図 (Wiring Diagram)





実践 (To Practice)

配線写真 (Example Picture)


プログラム (Code)


スケッチ
/******************************************************
 * 【 Elegoo:13 】ジョイスティックモジュール
 *    X/Yからデータを読み取るためにアナログPINを使用し、
 *    Switchを読むためにデジタルピンを使用する
 *****************************************************/

// ピン
#define PIN_SW 2  // スイッチ[D2]
#define PIN_X  0  // X軸    [A0]
#define PIN_Y  1  // Y軸    [A1]

/*********/
/* setup */
/*********/
void setup() {
  // --------------------------------------------------
  //Digital PIN (Switch)の設定
  pinMode(PIN_SW, INPUT);
  digitalWrite(PIN_SW, HIGH);   //初期化

  // --------------------------------------------------
  // シリアルモニタ開始
  Serial.begin(9600);
}

/********/
/* loop */
/********/
void loop() {

  // --------------------------------------------------
  //Switch
  Serial.print("Switch[");
  Serial.print(digitalRead(PIN_SW));
  Serial.print("] ");
  // --------------------------------------------------
  //X
  Serial.print("X-axis[");
  Serial.print(analogRead(PIN_X));
  Serial.print("] ");
  // --------------------------------------------------
  //Y
  Serial.print("Y-axis[");
  Serial.print(analogRead(PIN_Y));
  Serial.println("]");

  delay(500);
}

結果(動画)・モニタリング


ジョイスティックの状態をシリアルモニタに表示する

2019年5月30日木曜日

[Elegoo:12] DHT11 温度・湿度センサ




概要 (Overview)
  • 温度・湿度センサについて学ぶ
  • DTH11から温度・湿度・データを取得してシリアルモニタに表示する



使用する電子部品 (Component Required)
電子種別電子部品個数
マイコン Elegoo UNO R3 マイコンボード1
USBケーブル1
能動 DHT11 デジタル温度・湿度センサ1
機構 ジャンプワイヤ(緑)[オス-メス]1
ジャンプワイヤ(黒)[オス-メス]1
ジャンプワイヤ(赤)[オス-メス]1



電子部品について (Component Introduction)

DHT11 温度・湿度センサ


  • 「DHT11 デジタル温度・湿度センサ」は温度と湿度の較正されたデジタル信号出力を含む複合センサ
  • <較正>湿度・温度データに8Bitのチェックサムを付加し、データの送り手側が決められた計算方法によって算出した値と、受け手側が同一の計算方法によって算出した値が一致するかどうかを調べ、データの信頼性の向上を図ることができる
《データシート》
精度(25℃)温度±2℃、湿度±5%RH
サンプリング期間2秒以上

《ピン》
Vcc電源入力(5V or 3V)
DATAデジタルIO (プルアップ抵抗付)
GNDGND



電子回路 (Electronic Circuit)

回路図 (Schematic)



実体配線図 (Wiring Diagram)





実践 (To Practice)

配線写真 (Example Picture)


プログラム (Code)

SimpleDHT11ライブラリ
書式引数説明
int SimpleDHT11::read(int pin,
 byte* ptemperature,
 byte* phumidity,
 byte pdata[40])
int pin:ピンNO
byte* ptemperature:温度
byte* phumidity:湿度
byte pdata[40]:取得データ
温度・湿度・データを取得する

スケッチ
/*************************************************
 * 【 Elegoo:12 】DHT11 温度・湿度センサー
 *    温度・湿度をシリアルモニタに表示する
 *************************************************/
/*--------------------------------------------*/
/* SimpleDHT11ライブラリ                       */
/*--------------------------------------------*/
#include <simpledht.h>

// SimpleDHT11オブジェクトを作成
SimpleDHT11 objDHT11;

// ピン
#define PIN_DHT11  2

/********/
/* loop */
/********/
void loop() {
  Serial.println("=================================");
  Serial.println("DHT11 サンプリング中...");
  
  byte byteTemperature = 0;   //温度
  byte    byteHumidity = 0;   //湿度
  byte    byteData[40] = {0}; //取得データ

  // --------------------------------------------------
  // DTH11から温度・湿度・データを取得する
  // --------------------------------------------------
  if (objDHT11.read(PIN_DHT11, &byteTemperature, &byteHumidity, byteData)) {
    // 取得 失敗
    Serial.print("Read DHT11 failed");
    return;
  }

  // --------------------------------------------------
  // 取得した温度・湿度・データをシリアルモニタに表示する
  // --------------------------------------------------
  Serial.print("Read DHT11 OK : ");
  // --------------------------------------------------
  // 温度
  Serial.print((int)byteTemperature); Serial.print(" *C, ");
  // --------------------------------------------------
  // 湿度
  Serial.print((int)byteHumidity); Serial.println(" %");
  // --------------------------------------------------
  // データ
  // [湿度(整数)8Bit][湿度(小数)8Bit][温度(整数)8Bit][温度(小数)8Bit][チェックサム8bit]
  // チェックサム=温度・湿度のByteDataを足した下位8Bit
  Serial.print("RAW Bits: ");
  for (int i = 0; i < 40; i++) {
    Serial.print((int)byteData[i]);
    if (i > 0 && ((i + 1) % 4) == 0) {
      Serial.print(' ');
    }
  }
  Serial.println("");

  
  // サンプリング:2秒以上
  delay(2000);
}

/*********/
/* setup */
/*********/
void setup() {
  // シリアルモニタ開始
  Serial.begin(9600);
}

結果(動画)・モニタリング


温度・湿度・データをシリアルモニタに表示する

2019年5月19日日曜日

[Elegoo:11] キーパッドでミュージックボックス



概要 (Overview)
  • キーパッド[メンブレン(膜)スイッチモジュール]について学ぶ
  • ユーザーに押されたキー別にメロディを鳴らすミュージックボックスを作る



使用する電子部品 (Component Required)
電子種別電子部品個数
マイコン Elegoo UNO R3 マイコンボード1
USBケーブル1
能動 パッシブブザー1
機構 4×4マトリックス
メンブレンキーパッド(#27899)
1
ジャンプワイヤ(黒)[オス-メス]1
ジャンプワイヤ(赤)[オス-メス]1
ジャンプワイヤ(白)[オス-オス]2
ジャンプワイヤ(青)[オス-オス]2
ジャンプワイヤ(緑)[オス-オス]2
ジャンプワイヤ(黄)[オス-オス]2



電子部品について (Component Introduction)

マトリクスキーパッド


使用するキーパッドには16個のキー(0-9, A-D, *, #)がある。
リニアキーパッドではキー総数分とグランドピン1本の出力ピンが必要だが
マトリクス不等号化式キーパッドは、行列各4ピンの計8ピンと配線が少なくて済む。
まずは4列(ピン1〜4)を一度にローまたはハイにしてから、4行(ピン5〜8)の状態をポーリングして 列の状態に応じて押されたキーを認識する

《データシート》
最大定格24VDC/30mA
インターフェース4×4マトリックスへの8ピンアクセス
使用温度0〜50℃

《端子とキー配列》
白/黒4 ↓3 ↓2 ↓1 ↓
8 →123A
7 →456B
6 →789C
5 →*0#D




電子回路 (Electronic Circuit)
回路図 (Schematic)


実体配線図 (Wiring Diagram)




実践 (To Practice)
配線写真 (Example Picture)


プログラム (Code)

KeyPadライブラリ
書式引数説明
Keypad::Keypad(char userKeymap,
 byte rowPins, byte colPins,
 byte rows, byte cols)
userKeymap:キー配列
rowPins:行ピンNo配列
colPins:列ピンNo配列
rows:行数
cols:列数
KeyPadオブジェクトを作成し、
ピンを割り当てる
Keypad::makeKeymap(char hexKeymap) hexKeymap:キー配列 キーマップ作成
char Keypad::getKey()
押されたキーを取得

音階の周波数(Hz)
pitches.h

/*************************************************
 * pitches.h : 音階(NOTE_B0 - DS8)の周波数(Hz)を定義
 * https://www.arduino.cc/en/Tutorial/ToneMelody
 * Public Constants 
 *************************************************/

#define NOTE_B0    31
#define NOTE_C1    33
#define NOTE_CS1  35
       …

#define NOTE_CS8  4435
#define NOTE_D8    4699
#define NOTE_DS8  4978

メロディ リスト
melodyList.h

/*************************************************
 * 【melodyList.h】 メロディ リスト
 *    [0] 音階     [ NOTE_xx, 0:休符 ]
 *    [1] 音の長さ[ 4:四分音符, 8:八分音符 ]
 *-------------------------------------------------
 *      ①ドレミの歌 [ドレミファソラシド]
 *      ②きらきらぼし
 *      ③ゆかいな牧場
 *      ④にんげんっていいな
 *      ⑤おもちゃのチャチャチャ
 ************************************************/

// 音階の周波数(Hz)
#include "pitches.h"

#define NOTE      0       // 音階
#define DURATIONS 1       // 音の長さ

//-------------------------------------------------
// ドレミの歌
//-------------------------------------------------
// ド[C]
int aryMelodyC[][7] = {
  { NOTE_C4, NOTE_D4, NOTE_E4, NOTE_C4, NOTE_E4, NOTE_C4, NOTE_E4 },
  { 2, 4, 2, 4, 2, 2, 2 }
};
// レ[D]
int aryMelodyD[][7] = {
  { NOTE_D4, NOTE_E4, NOTE_F4, NOTE_F4, NOTE_E4, NOTE_D4, NOTE_F4 },
  { 2, 4, 2, 4, 2, 2, 2 }
};
// ミ[E]
int aryMelodyE[][7] = {
  { NOTE_E4, NOTE_F4, NOTE_G4, NOTE_E4, NOTE_G4, NOTE_E4, NOTE_G4 },
  { 2, 4, 2, 4, 2, 2, 2 }
};
// ファ[F]
int aryMelodyF[][7] = {
  { NOTE_F4, NOTE_G4, NOTE_A4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_A4 },
  { 2, 4, 8, 8, 8, 8, 2 }
};
// ソ[G]
int aryMelodyG[][7] = {
  { NOTE_G4, NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4 },
  { 2, 4, 8, 8, 8, 8, 2 }
};
// ラ[A]
int aryMelodyA[][7] = {
  { NOTE_A4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4 },
  { 2, 4, 8, 8, 8, 8, 2 }
};
// シ[B]
int aryMelodyB[][7] = {
  { NOTE_B4, NOTE_E4, NOTE_FS4, NOTE_GS4, NOTE_A4, NOTE_B4, NOTE_C5 },
  { 2, 4, 8, 8, 8, 8, 2 }
};
// ド[C5]
int aryMelodyC5[][7] = {
  { NOTE_B4, NOTE_B4, NOTE_A4, NOTE_F4, NOTE_B4, NOTE_G4, NOTE_C5 },
  { 8, 8, 4, 4, 4, 4, 2 }
};
// C4 -> C5
int aryMelodyC4_C5[][8] = {
  { NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5 },
  { 4, 4, 4, 4, 4, 4, 4, 4 }
};
// C5 -> C4
int aryMelodyC5_C4[][8] = {
  { NOTE_C5, NOTE_B4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_E4, NOTE_D4, NOTE_C4 },
  { 4, 4, 4, 4, 4, 4, 4, 4 }
};

//-------------------------------------------------
// きらきらぼし
//-------------------------------------------------
int aryMelodyStar[][14] = {
  { NOTE_C4, NOTE_C4, NOTE_G4, NOTE_G4, NOTE_A4, NOTE_A4, NOTE_G4, 
    NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_D4, NOTE_C4},
  { 4, 4, 4, 4, 4, 4, 2,
    4, 4, 4, 4, 4, 4, 2 }
};

//-------------------------------------------------
// ゆかいな牧場
//-------------------------------------------------
int aryMelodyCow[][13] = {
  { NOTE_G4, NOTE_G4, NOTE_G4, NOTE_D4, NOTE_E4, NOTE_E4, NOTE_D4,
    0,       NOTE_B4, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_G4 },
  { 8, 8, 8, 8, 8, 8, 2,
    8, 8, 8, 8, 8, 2 }
};

//-------------------------------------------------
// にんげんっていいな
//-------------------------------------------------
int aryMelodyHuman[][14] = {
  { NOTE_G4, NOTE_F4, NOTE_E4, NOTE_F4, NOTE_D4, NOTE_C4, 0,
    NOTE_E4, NOTE_E4, 0,       NOTE_G4, NOTE_F4, NOTE_D4, NOTE_E4 },
  { 4, 4,  4, 4, 4, 4, 8,
    2, 4, 16, 8, 4, 4, 4 }
};

//-------------------------------------------------
// おもちゃのチャチャチャ
//-------------------------------------------------
int aryMelodyCha[][24] = {
  { NOTE_C4, NOTE_C4, NOTE_C4, NOTE_E4, NOTE_A4, NOTE_A4, NOTE_A4,
    NOTE_D4, NOTE_D4, NOTE_D4, NOTE_F4, NOTE_G4, NOTE_G4, NOTE_G4,
    NOTE_G4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_GS4,
    NOTE_G4, NOTE_B4, NOTE_C5 },
  { 8, 8, 8, 8, 8, 8, 2,
    8, 8, 8, 8, 8, 8, 2,
    8, 8, 8, 8, 8, 8, 8,
    8, 8, 8 }
};

スケッチ
/**************************************************
 * 【 Elegoo:11 】膜(メンブレイン)スイッチモジュール
 *    キーパッドでミュージックボックス
 *************************************************/

/*--------------------------------------------*/
/* Keypadライブラリ                            */
/*--------------------------------------------*/
#include <keypad .h>
// 行列数
const byte bytRows = 4;                  // 行数
const byte bytCols = 4;                  // 列数
// 行列ピンNo
byte aryRowPins[bytRows] = {9, 8, 7, 6}; // 行ピンNo
byte aryColPins[bytCols] = {5, 4, 3, 2}; // 列ピンNo
// キー配列
char hexKeymap[bytRows][bytCols] = {     
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};
// Keypadオブジェクトを作成
Keypad objKeypad = Keypad( makeKeymap(hexKeymap),   // キーマップ作成
                           aryRowPins, aryColPins,  // 行列ピンNo
                           bytRows, bytCols         // 行列数
                          );

/*--------------------------------------------*/
/* ブザー                                     */
/*--------------------------------------------*/
// ピンの定義
#define BUZZER    12      // D12

/*--------------------------------------------*/
/* メロディ                                   */
/*--------------------------------------------*/
#include "pitches.h"      // 音階の周波数(Hz)
#include "melodyList.h"   // メロディリスト

/*****************************************/
/* 関数名:メロディ 出力                   */
/*  引数:rAryNote      [音階   ]        */
/*      :rAryDurations [音の長さ]        */
/*      :rBytNoteNum   [音階総数]        */
/*****************************************/
void outputMelody( int *rAryNote, int *rAryDurations, byte rBytNoteNum){

  // --------------------------------------------------
  //  音階数の周波数を音の長さだけ出力する 
  // --------------------------------------------------
  for( int i = 0; i < rBytNoteNum; i++){
    tone( BUZZER, rAryNote[i],( 1000 / rAryDurations[i] ) );
    delay( 500 );   //演奏速度
  }

  // --------------------------------------------------
  // 周波数の出力を停止する
  // --------------------------------------------------
  noTone( BUZZER );
  delay( 500 );
}


/********/
/* loop */
/********/
void loop() {
  byte bytNoteNum;    // 音階総数

   // 押されたキーを取得する
  char chrKey = objKeypad.getKey();

  // --------------------------------------------------
  // 押されたキー別にメロディを出力する
  //    メロディ[0] 音階
  //    メロディ[1] 音の長さ
  // --------------------------------------------------
  if (chrKey){
    switch(chrKey){
      // --------------------------------------------------
      // 1:ド
      case '1':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyC[NOTE] ) / sizeof( aryMelodyC[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyC[NOTE], aryMelodyC[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 2:レ
      case '2':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyD[NOTE] ) / sizeof( aryMelodyD[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyD[NOTE], aryMelodyD[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 3:ミ
      case '3':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyE[NOTE] ) / sizeof( aryMelodyE[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyE[NOTE], aryMelodyE[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 4:ファ
      case '4':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyF[NOTE] ) / sizeof( aryMelodyF[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyF[NOTE], aryMelodyF[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 5:ソ
      case '5':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyG[NOTE] ) / sizeof( aryMelodyG[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyG[NOTE], aryMelodyG[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 6:ラ
      case '6':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyA[NOTE] ) / sizeof( aryMelodyA[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyA[NOTE], aryMelodyA[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 7:シ
      case '7':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyB[NOTE] ) / sizeof( aryMelodyB[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyB[NOTE], aryMelodyB[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 8:ド
      case '8':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyC5[NOTE] ) / sizeof( aryMelodyC5[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyC5[NOTE], aryMelodyC5[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 9:ド[C4 -> C5]
      case '9':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyC4_C5[NOTE] ) / sizeof( aryMelodyC4_C5[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyC4_C5[NOTE], aryMelodyC4_C5[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // 0:ド[C5 -> C4]
      case '0':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyC5_C4[NOTE] ) / sizeof( aryMelodyC5_C4[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyC5_C4[NOTE], aryMelodyC5_C4[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // A:きらきらぼし
      case 'A':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyStar[NOTE] ) / sizeof( aryMelodyStar[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyStar[NOTE], aryMelodyStar[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // B:ゆかいな牧場
      case 'B':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyCow[NOTE] ) / sizeof( aryMelodyCow[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyCow[NOTE], aryMelodyCow[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // C:にんげんっていいな
      case 'C':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyHuman[NOTE] ) / sizeof( aryMelodyHuman[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyHuman[NOTE], aryMelodyHuman[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // D:おもちゃのチャチャチャ
      case 'D':
        // 音階総数
        bytNoteNum = sizeof( aryMelodyCha[NOTE] ) / sizeof( aryMelodyCha[NOTE][0] );
        // メロディ 出力
        outputMelody( aryMelodyCha[NOTE], aryMelodyCha[DURATIONS], bytNoteNum ); 
      break;
      // --------------------------------------------------
      // その他:シリアルモニタに表示
      default:
        Serial.println(chrKey);
    }
  }
}


/*********/
/* setup */
/*********/
void setup(){
  // pinの設定
  pinMode( BUZZER, OUTPUT );  // BUZZER : 出力用

  // シリアルモニタ開始
  Serial.begin( 9600 );
}

結果(動画)・モニタリング

動画


ドレミの歌
ボタン1→2→3→4:ド→レ→ミ→ファ


 ボタンA:きらきらぼし
ボタンB:ゆかいな牧場
ボタンD:おもちゃのチャチャチャ

2019年3月7日木曜日

[Elegoo:10] 超音波センサモジュールで
楽器(テルミン)を作る


概要 (Overview)
  • 超音波センサについて学ぶ
  • 超音波センサモジュールで楽器(テルミン)を作る



使用する電子部品 (Component Required)
電子種別電子部品個数
マイコン Elegoo UNO R3 マイコンボード1
USBケーブル1
能動 超音波センサモジュール1
パッシブブザー1
機構 ジャンプワイヤ(赤)[オス-メス]1
ジャンプワイヤ(黒)[オス-メス]2
ジャンプワイヤ(青)[オス-メス]1
ジャンプワイヤ(緑)[オス-メス]1
ジャンプワイヤ(黄)[オス-メス]1



電子部品について (Component Introduction)

超音波センサモジュール HC-SR04


超音波距離センサモジュールは、モジュールに2つ取り付けられているうちの超音波スピーカーから40kHzの周波数を出力し、 物体に反射された音をもう片方の超音波マイクで受信する
超音波は「音」なので超音波を出力してから受信するまでの時間に音速を掛けることで距離を求める(測距)

《データシート》
動作電圧5V
測定可能距離2~400cm(精度3mm)
測定角度15度以下

《端子》
Vcc電源入力 (5V)
Trig超音波スピーカー:超音波信号を送信
Echo超音波マイク:超音波信号を受信
(デジタルINPUT-距離を測定するPWNピン)
GNDGND

《測距の流れ》
(1) 超音波スピーカーのTrigピンを10μs以上Highにする
(2) モジュールが自動的にTrigピンからパルス(40kHzの信号を8回)を送信する
(3) モジュールが測定物から反射したパルス信号を超音波マイクに受信する
(4) パルス信号の送信~受信までに掛かった時間分だけEchoピンがHighになる
(5) EchoピンがHighになった時間をμs単位で測定し、音速を掛けて距離を計算する

《測距の計算》
(HR-SR04ライブラリのDistance関数)
片道の距離[cm] = 時間[μs→msec→sec] × 音速[m/sec→cm/sec] ÷ 2[片道]
空気中の音速は公式「331.5 + 0.6 t (m/sec) tは摂氏温度」:一般的に340(m/sec)

片道の距離[cm] = (パルス送受信時間[μs] ÷ 1,000,000[μs→msec→sec])
                              × (340[m/s] × 100[m/s→cm/s])
                              ÷ 2[片道]
  => 片道の距離(cm)=パルス送受信時間[μs] × 0.017
 マイコンは小数計算しないほうが良いので 0.017=1/59 とすることもある
  => 片道の距離(cm)=パルス送受信時間[μs] ÷ 59[cm/μs]




電子回路 (Electronic Circuit)
回路図 (Schematic)





実体配線図 (Wiring Diagram)





実践 (To Practice)
配線写真 (Example Picture)

拡大           全体

プログラム (Code)

HR-SR04ライブラリ
書式引数説明
SR04::SR04(int echoPin, int triggerPin) echoPin:超音波を受信
triggerPin:超音波を送信
SR04にピンに割り当てる
long SR04::Distance() 対象物までの距離を測る(cm)
long SR04::DistanceAvg(int wait, int count) wait :待機遅延  [ms]
count:計測回数 []
指定計測回数分計数を行い、
平均を計算する(cm)

HR-SR04ライブラリを使用しない場合は《測距の流れ》に従って
①直接Trigピンから10μs間超音波を出力し
②PulseIn関数でechoピンのHIGH→LOWのパルスの長さ(パルス送受信時間)を受信して
③測距の計算を行う
《測距の計算》片道の距離(cm)=パルス送受信時間[μs] ÷ 59[cm/μs]

音階(ドレミ)の周波数(Hz)
pitches.h


/*************************************************/
/* 音階(NOTE_B0 - DS8)の周波数(Hz)を定義           */
/* https://www.arduino.cc/en/Tutorial/ToneMelody */
/*************************************************/

#define NOTE_B0    31
#define NOTE_C1    33
#define NOTE_CS1   35
    …
#define NOTE_CS8   4435
#define NOTE_D8    4699
#define NOTE_DS8   4978

スケッチ
/**********************************************/
/* 【 Elegoo:10 】超音波センサモジュール       */
/*    超音波センサモジュールで                  */
/*    楽器のテルミンを作る                     */
/* ※※ 音階の周波数と間隔距離(cm)を任意で設定   */
/**********************************************/

/*--------------------------------------------*/
/* SR04ライブラリの作成                        */
/*--------------------------------------------*/
#include <SR04.h>
#define PIN_TRIG    12  // D12 : 超音波スピーカー
#define PIN_ECHO    11  // D11 : 超音波マイク

// SR04オブジェクトの作成
SR04 objSr04 = SR04( PIN_ECHO, PIN_TRIG );

/*-------------------------------------------*/
/* ブザーと音階                               */
/*-------------------------------------------*/
#define PIN_BUZZER  13  // D13 : ブザー

// 音階(ドレミ)の周波数(Hz):NOTE_B0 - DS8     ※※
#include "pitches.h"
int aryNote[] = {
    NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4,
    NOTE_G4, NOTE_A4, NOTE_B4, 
    NOTE_C5
};
#define NOTE_STEP    3  // 音階の間隔距離(cm)  ※※
int intNoteCM;          // 音階全長(cm)


/*********/
/* setup */
/*********/
void setup() {

  // --------------------------------------------------
  // ブザーpinの設定
  // --------------------------------------------------
  pinMode( PIN_BUZZER,  OUTPUT );

  // --------------------------------------------------
  // 音階の数と間隔距離から音階全長を設定
  // --------------------------------------------------
  intNoteCM = ( sizeof(aryNote) / sizeof(aryNote[0]) ) * NOTE_STEP;

  // --------------------------------------------------
  // シリアルモニタ開始
  // --------------------------------------------------
  Serial.begin( 9600 );

  delay( 1000 );
}

/********/
/* loop */
/********/
void loop() {

  // 対象物までの距離を測る(cm)
  // 不安定解消のため、計数後(ms, 回数)平均を算出
  long lngDistance = objSr04.DistanceAvg( 10, 10 );   

  // --------------------------------------------------
  // 測定可能距離の範囲内:2~400cm(精度3mm)
  // --------------------------------------------------
  if( 2 <= lngDistance && lngDistance <= 402 ){

    // 対象物までの距離 - 測定可能開始距離(2cm)
    lngDistance -= 2;

    // --------------------------------------------------
    // 対象物までの距離別にブザーから音(周波数)を出力
    // --------------------------------------------------
    // 音階全長(cm)以内
    if( lngDistance <  intNoteCM ) {

      // 出力する音の決定
      int intCnt = lngDistance / NOTE_STEP; 

      // シリアルモニタに表示
      Serial.print( lngDistance ); Serial.print("cm : ");
      Serial.println( intCnt );

      // 音の周波数を出力
      tone( PIN_BUZZER, aryNote[intCnt] );

    // 音階全長(cm)以外
    } else {
      // 周波数の出力を停止
      noTone( PIN_BUZZER );
    }

  // --------------------------------------------------
  // 測定可能距離の範囲外
  // --------------------------------------------------
  }else{
      // 周波数の出力を停止
      noTone( PIN_BUZZER );
  }

  delay( 100 ); 
}

結果(動画)・モニタリング

動画

モニタリング