ラベル Arduino の投稿を表示しています。 すべての投稿を表示
ラベル Arduino の投稿を表示しています。 すべての投稿を表示

2020年8月21日金曜日

【家の自動化プロジェクト】#3 一つ目のBLEデバイス

どうも、caketetuです。
相変わらず家の自動化プロジェクトを進めています。今回は一つ目のデバイスが完成したので
ご紹介します。



外観


前回でESP32を使ったベースに小型液晶とサブプロセッサとしてのArduino、環境センサ
を乗っけたデバイスを作りました。今回はさらに機能を足して最終的に以下機能を持った
デバイスになりました。(リンクは個別記事です。参考にどうぞ)

・照明操作(アナログサーボ) アナログサーボを動かす
・エアコン操作(赤外線LED) 赤外線でエアコンを操作する
・環境測定(環境センサ) 自動化プロジェクト#2
・画面表示(小型液晶) 自動化プロジェクト#2

全部乗せ感がすごいですね(笑)


回路設計



回路はこんな感じです。赤外線LEDとサーボはAruduinoを介して接続しているので
ちょっとごちゃついています。ESP32のBLEを使うと割込みが不安定になることが
あったのでこのような構成にしました。

照度センサが標準でついているのは寝るときに画面を消すためです。<<意外と重要>>





ハード設計


画像中央のコアユニット部です。タミヤのユニバーサルプレートに合わせて
設計することで他の工作にも応用が利くように工夫しました。


画像左側のユニットにくっついているのはエアコン操作用の赤外線照射
モジュールです。LEDを三個直列につなげて照射範囲を広くしています。
結構遠くでも反応してくれます(5mぐらい?)
2自由度あるので調整も割と簡単です。


画像上部に乗っているのは照明操作用のサーボモータです。一軸で二つの照明を操作
出来ます。既製品のスイッチに左右のネジでクランプして固定します。
既存のモノの面積がとても小さいので、ちゃんと固定するのに苦労しました。



ソフト

上記の個別記事で紹介したサンプルプログラムをこちらで作ったBLEデバイスのプロトタイプにぶち込んで作りました。参考までにすべて載せておきます。

BLE_Unit_Firmware1.0.c      ESP32のメインプログラム
code_list.h                        通信コードヘッダ
Arduino_sub_prossor1.0.c    Aruduino 側プログラム
IR_Ctrl.h                          エアコン操作ヘッダ 

通信プロトコルはESP32側の136行目から始まる関数とcode_list.hに定義されており、
BLEデータの先頭バイトで識別します。
現在、以下のように定義されています。

//*****命令コード表****
CMD_SERVO_MOVE   0x78   //照明用サーボ動作指令  
        命令コード +  角度(2byte) + 動作時間(2byte)
CMD_LIGHT_LIV      0x80   //リビング照明
        命令コードのみ
CMD_LIGHT_ENT       0x81   //エントランス照明
        命令コードのみ
CMD_IR_SEND           0x79   //エアコン、データ書き込み
        命令コード  + ボタン + 温度+ 風力+ 動作モード + ON/OFF (全1byte)
CMD_IR_POWER_OFF 0x82   //エアコンOFF
        命令コードのみ
CMD_IR_REPEAT        0x83   //エアコン指令をリピート
        命令コードのみ
REQ_DATA                 0x50   //全データリクエスト
   命令コードのみ(読み取りで実行)

現在、通信エラーはめったに発生していませんが、チェックサムなどを用いた
エラーチェックをしたほうがいいかもしれませんね。

また、パラメータ設定指令などを作ってパラメータを保存できるようにするのも課題です。



今回は以上です。若干ゴツゴツした見た目が意外とお気に入りです。
次回はRaspberry piにWEBサーバを作りそこから操作できるようにしたいと思います。











2020年7月18日土曜日

【Arduino】赤外線でエアコンを操作する

どうもcaketetuです。現在、自宅のスマートホーム化を進めています。
その中でエアコンの自動化は必要不可欠と言えます。IoT対応のエアコンを
買えば楽ですが、価格が高く、賃貸なので簡単にエアコン交換ができません。
よって今回は後付けでもエアコン自動化できるように赤外線LEDとセンサーを使って
Arduinoから操作してみます。

早速アマゾンで赤外線リモコンセットを購入します。
私が購入したのはこれです。
もう売り切れでしたが、同じようなやつはたくさんありました。


・赤外線信号を調べる

「Arduino 赤外線」とかで調べると赤外線コードやライブラリがたくさん出て
きますが、残念なことに我が家のエアコンは対応していませんでした。
なのでエアコンのリモコンから出ている信号を調べなければなりません。

Arduinoに赤外線センサーを取り付けて信号を読み出します。
以下のプログラムを使用します。

#define READ_PIN 2
#define LOW_STATE 0
#define HIGH_STATE 1

void setup(){
  Serial.begin(115200UL);
  pinMode(READ_PIN,INPUT);

  Serial.println("Ready to receive");
}

void waitLow() {
  while (digitalRead(READ_PIN)==LOW) {
    ;
  }
}

int waitHigh() {
  unsigned long start = micros();
  while (digitalRead(READ_PIN)==HIGH) {
    if (micros() - start > 5000000) {
      return 1;
    }
  }
  return 0;
}

unsigned long now = micros();
unsigned long lastStateChangedMicros = micros();
int state = HIGH_STATE;

void loop() {
    if (state == LOW_STATE) {
      waitLow();
    } else {
      int ret = waitHigh();
      if (ret == 1) {
        Serial.print("\n");
        return;
      }
    }

    now = micros();
    Serial.print((now - lastStateChangedMicros) / 10, DEC);
    Serial.print(",");
    lastStateChangedMicros = now;
    if (state == HIGH_STATE) {
      state = LOW_STATE;
    } else {
      state = HIGH_STATE;
    }
}

また、Arduinoとセンサの配線は次の通りです。

Arduino センサー
 IO2-----OUT
 GND----GND
 5V-----VCC

実行しリモコンを照射しながらシリアルモニタで見てみると数字の羅列が出力されます。
これは赤外線のONとOFFが切り替わる間の時間を表しています。これを解析していきます。

おそらくAEHAフォーマットで40ぐらいが1Tで120ぐらいが3Tでしょうか。
ボタンを何回も押していき、変化した値を見て信号を解析するのですが
いちいちやっていては大変なのでデコードプログラムをVSのC#で作りました。
GUIのプログラムを全部乗せるのは大変なのでコア部分だけ載せます。

		private void TimerSerial_Tick(object sender, EventArgs e)
        {
            //二秒ごとにバッファからすべて吐き出す
            String buffer;  //シリアルバッファ―
            buffer = Serial1.ReadExisting();    //バッファからすべて読み出す
            RTB_Raw_Data.Text = buffer;         //バッファをすべて書き出す
            if (buffer.Length > 500)
            {
                string[] arr = buffer.Split(',');   //','で区切って取り出す
                int ms, digit=0;        //時間、桁数
                int code_size=0;        //HEXコードサイズ
                UInt16 ir_sig = 0x00;   //HEXデータ
                //有功データから一つとびに値を見る
                for (int i = 4; i < arr.Length; i+=2)
                {
                    int.TryParse(arr[i], out ms);   //データを整数に直す
                    if (ms > 80) ir_sig |= 0x80;    //データが80以上(=1)なら先頭ビットを1に

                    digit++;        //桁数プラス
                    if ( digit>=8 ) //桁数が8なら表示
                    {
                        digit = 0;  //桁数リセット
                        String sighex = string.Format("{0,3:X2}", ir_sig);  //HEXにフォーマット指定
                        RTB_IR_Signal.Text += sighex + " "; //表示
                        ir_sig = 0x00;  //ir_sigリセット
                        code_size++;    //コードサイズ更新
                    }

                    ir_sig /= 2;    //ir_sigの桁をひとつ下げる
                }
                RTB_Raw_Data.Text += "  DataSize=" + arr.Length.ToString() + "\n";  //データサイズ表示
                RTB_IR_Signal.Text += "  DataSize=" + code_size.ToString() + "\n";  //データサイズ表示

            }
        }

簡単に説明すると
  1. シリアルバッファからすべて読み出す
  2. ","ごとに分割してそれぞれをint型に直す。
  3. 最初のいらない部分を除いて一つ飛ばしに値を読んでいき80以上なら一番左のbitを1に、それ以外なら0にする。その後右にビットシフト(/2)する。
  4. 8回ごとにデータを16進数でTEXTに追加する。
VC#を選んだのは実装がラクだったからです。アルゴリズムが分かれば他言語でも簡単に実装できると思います。

検証の様子です。
結構取りこぼしてますが信号数33個が正常なデータと見ていいでしょう。



以下が検証から得られたデータです。~はビット反転を表します。


・信号を送信する

調べられた値を使って信号を送信してみます。解析時と逆の手順で16進数の
コードをON、OFF信号に変換して送信します。

メインループ
#include "IR_Ctrl.h"

//赤外線送信データ
byte ir_hexdata[33];

void setup() {
  // put your setup code here, to run once:
  ir_Init();                  //赤外線モジュール初期化
  for(int i=0;i<33;i++){
    ir_hexdata[i]=i;
  }
  delay(500);
}

void loop() {
  // put your main code here, to run repeatedly:
  ir_send(ir_hexdata);    //赤外線照射
  delay(2000);
}
赤外線リモコン送信ヘッダ
//*********************************************************
//    IR Control header
//    
//*********************************************************
#define IR_SEND_PIN  6  //define ir-signal send pin
#define IR_BUTTON 11
#define IR_MODE 25
#define IR_TEMP 13
#define IR_POWER 27

#define IR_POWER_ON  0xD1
#define IR_POWER_OFF 0cC1

#define IR_BTN_OFF 0x13
#define IR_BTN_UP  0x44
#define IR_BTN_DOWN 0x43
#define IR_BTN_TISTOP 0x31
#define IR_BTN_TIWKUP 0x22
#define IR_BTN_WIND_LEVEL 0x42
#define IR_BTN_WIND_DIRECTION 0x81

#define IR_WIND_AUTO    0x50
#define IR_WIND_STRONG  0x40
#define IR_WIND_NOMAL   0x30
#define IR_WIND_WEAK    0x20

#define IR_MODE_COOLING  0x03
#define IR_MODE_DEHUMID  0x05
#define IR_MODE_HEATING  0x06


//----------------------------------------------------------
//    sumple send data
//----------------------------------------------------------
byte ir_heating[33] = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x50, 0xAF, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x56, 0xA9, 0xD1, 0x2E, 0x00, 0xFF, 0x00, 0xFF};

//01  10  00  40  BF  FF  00  CC  33  92  6D  13  EC  50  AF  00  FF  00  FF  00  FF  00  FF  00  FF  56  A9  D1  2E  00  FF  00  FF 


byte ir_cooling[33] = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x68, 0x97, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x53, 0xAC, 0xD1, 0x2E, 0x00, 0xFF, 0x00, 0xFF};

//01  10  00  40  BF  FF  00  CC  33  92  6D  13  EC  68  97  00  FF  00  FF  00  FF  00  FF  00  FF  53  AC  D1  2E  00  FF  00  FF 

byte ir_dehumid[33] = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x68, 0x97, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x25, 0xDA, 0xD1, 0x2E, 0x00, 0xFF, 0x00, 0xFF};

// 01  10  00  40  BF  FF  00  CC  33  92  6D  13  EC  68  97  00  FF  00  FF  00  FF  00  FF  00  FF  25  DA  D1  2E  00  FF  00  FF 

byte ir_off[33]      = {0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 
                      0x33, 0x92, 0x6D, 0x13, 0xEC, 0x6C, 0x93, 0x00,
                      0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,  
                      0xFF, 0x53, 0xAC, 0xC1, 0x3E, 0x00, 0xFF, 0x00, 0xFF};


//----------------------------------------------------------
//    Initialize
//----------------------------------------------------------
void ir_Init( void ){
      //check led setup
      pinMode(IR_SEND_PIN,OUTPUT);    //led pin set output
      digitalWrite(IR_SEND_PIN,LOW); //set initial value(high-level off)  
}

//----------------------------------------------------------
//    IR send signals
//----------------------------------------------------------
void sendSignal( unsigned short *data ,int data_len) {
  //int dataSize = sizeof(data) / sizeof(data[0]);
  int dataSize = data_len;
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data[cnt];
    unsigned long us = micros();
    do {
      digitalWrite(IR_SEND_PIN, 1 - (cnt&1));
      delayMicroseconds(10);
      digitalWrite(IR_SEND_PIN, 0);
      delayMicroseconds(8);
    } while (long(us + len - micros()) > 0); // 送信時間に達するまでループ
  }
}

//----------------------------------------------------------
//    IR send bytes
//----------------------------------------------------------
void ir_send(byte *data){
    int khz = 38; // 38kHz carrier frequency for the NEC protocol
    unsigned short send_buf[16] = {390, 390, 390, 390, 390, 390, 390, 390,
                    390, 390, 390, 390, 390, 390, 390, 390};    //send buffer
    unsigned short for_data[2] = {3340,1700};   //head data
    unsigned short low_data[2] = {1240,390};    //tail data
    byte hex;
    
    sendSignal(for_data, sizeof(for_data) / sizeof(for_data[0]));
    
    for(int i=0;i<33;i++){
        hex = data[i];
        for(int j=1;j<16; j+=2){
          send_buf[j] = 410;
          if( hex&0x01 == 0x01 ){ send_buf[j] = 1240;}
          hex/=2;
        }
        sendSignal(send_buf, sizeof(send_buf) / sizeof(send_buf[0]));
    }
    
    sendSignal(low_data, sizeof(low_data) / sizeof(low_data[0]));
}
実行環境によって多少の調整が必要です。
delayMicroseconds()の値を調整するとうまくいくかもしれません。

Arduinoを二つ使って受信、送信をテストします。
テストでは、01,02,03・・・の信号を送信しています。



・エアコンを操作する

実際にエアコンを操作してみます。LEDをピン直結では発光が弱いので
LEDを3個使いトランジスタで信号を増幅して照射します。

照射モジュール


動作の様子
無事信号が認識されました。ちょっと光軸がずれていても認識してくれます。




無事Arduinoからエアコンを操作することができました。情報も結構出ていたので
簡単なのかなと思いましたが、意外と解析にてこずって時間がかかりました。
赤外線の知識、信号の作り方、信号の検証となかなかハードです。ライブラリ
もあるので対応していたらそちらを使ったほうがラクにできると思います。

スマートホーム化に際しては、これをWEB経由で操作できるようにしてみます。


【Arduino】アナログサーボをいい感じに動かす

どうもcaketetuです。今回はArduinoでアナログサーボを動かすテストです。
ただ動かすだけでは味気ないので、使いやすくスムーズに動かせるようにしてみます。

・とりあえず動かしてみる

まずはよくあるライブラリを使った簡単な実装です。

#include <Servo.h>

#define SERVO_PIN 5

Servo myservo;
 
void setup() {
    Serial.begin(115200);
    myservo.attach(SERVO_PIN);    //サーボ初期化
    myservo.write(90);            //初期角度90に設定
    delay(500);                   //移動まで待つ
}
 
void loop() {
    myservo.write(0);
    Serial.println("Servo move 0");
    delay(1000);
    myservo.write(90);
    Serial.println("Servo move 90");
    delay(1000);
    myservo.write(180);
    Serial.println("Servo move 180");
    delay(1000);
}

動作の様子です



・スムーズに動かしてみる

上のような簡単な実装では角度を与えたときにサーボの能力限界の速さで
動こうとするため、ゆっくり動かすのは不可能です。よって随時Delayを入れて
少しづつ指示を出すようにします。具体的には、指定した角度に何msで
移動せよという関数を作ります。

#include <Servo.h>

#define SERVO_PIN 5

int servo_move(int pos, int msec );

Servo servo;
int pos_now;    //角度保存用
 
void setup() {
    Serial.begin(115200);
    servo.attach(SERVO_PIN);    //サーボ初期化
    servo.write(90);            //初期角度90に設定
    delay(500);                 //移動まで待つ
    pos_now=90;                 //現在角度保存
    servo.detach();             //サーボピン解除
}
 
void loop() {
    //myservo.write(0);
    servo_move(0,1000);
    Serial.println(pos_now,DEC);
    Serial.println("Servo move 0");
    //myservo.write(90);
    servo_move(90,1000);
    Serial.println(pos_now,DEC);
    Serial.println("Servo move 90");
    //myservo.write(180);
    servo_move(180,1000);
    Serial.println(pos_now,DEC);
    Serial.println("Servo move 180");
}

//----------------------------------------------------------
//    サーボ動作関数    deg;-90~90 msec: 移動時間[ms]
//----------------------------------------------------------
int servo_move(int pos, int msec ){
  if(pos>180 || pos<0) return -1;    //posが範囲外ならリターン
  int d_pos = pos - pos_now;          //角度偏差計算
  int s_delay = abs(int(msec/d_pos)); //角度偏差からディレイ時間計算
  if(s_delay<0) return -1;            //ディレイ時間が0以下ならリターン(速すぎるため)
  
  servo.attach(SERVO_PIN);    //サーボ初期化
  
  if (d_pos > 0) { //d_posが0以上の時、プラス向きに動作
    for (int i = pos_now; i<=pos; i++) {
        servo.write(i);   //サーボ書き込み
        delay(s_delay);   //s_delayまで待つ
        pos_now = i;      //pos_now保存
    }
  }else if(d_pos < 0) { //d_posが0以下の時、マイナス向きに動作
    for(int i = pos_now; i>=pos; i--) {
        servo.write(i);   //サーボ書き込み
        delay(s_delay);   //s_delayまで待つ
        pos_now = i;      //pos_now保存
    }
  }else{ //d_posが0の時
  }
  
  servo.detach();   //サーボピン解除
}

動作の様子です
サーボの動きがゆっくり、スムーズなものになりました。


・割込みで実行してみる

ここまではメイン関数に書いていましたが、メイン関数はLCDの描写や通信
なんかで忙しいのでバックグラウンドで動くようにしたい。
ついでにサーボを動かした後はサーボをOFFし余計な電力消費や騒音を抑えます。

#include <MsTimer2.h>
#include <Servo.h>

#define SERVO_PIN 5

int servo_set(int pos, int msec);

Servo servo;                //サーボクラス
volatile int pos_now=90;    //角度保存用
volatile int pos_target=90; //ターゲットポジション
volatile int servo_flg=0;   //サーボ移動フラグ

//サーボタイマー
void timer_servo() {
   if(pos_target>pos_now){
      servo_flg = 1;
      pos_now++;
      servo.write(pos_now);
   }else if(pos_target<pos_now){
      servo_flg = 1;
      pos_now--;
      servo.write(pos_now);
   }else{
      servo_flg = 0;
      servo.detach();   //サーボピン解除
      MsTimer2::stop();
   }
}

//サーボ角度セット
int servo_set(int pos, int msec) {
    if(pos>180 || pos<0) return -1;    //posが範囲外ならリターン
    if(pos == pos_now)   return -1;    //pos=pos_nowならリターン
    int d_pos = pos-pos_now;          //角度偏差計算
    int s_delay = abs(int(msec/d_pos)); //角度偏差からディレイ時間計算
    pos_target = pos;
    servo.attach(SERVO_PIN);    //サーボ初期化
    servo_flg = 1;              //フラグON
    MsTimer2::set(s_delay, timer_servo);  //タイマセット
    MsTimer2::start();                    //タイマスタート
    return 1;           //正常リターン
}
 
void setup() {
    Serial.begin(115200);       //シリアルセット
    servo.attach(SERVO_PIN);    //サーボ初期化
    servo.write(90);            //初期角度90に設定
    delay(500);                 //移動まで待つ
    pos_now=90;                 //現在角度保存
    pos_target=90;              //現在角度保存
    servo.detach();             //サーボピン解除
}
 
void loop() {
    servo_set(0,1000);
    while(servo_flg);
    //delay(5000);
    servo_set(90,1000);
    while(servo_flg);
    //delay(5000);
    servo_set(180,1000);
    while(servo_flg);
    //delay(5000);
}

動きは変わらないので動画は割愛します。サーボの動きは同じですが、
サーボ動作中もメイン関数で色々できるのでより効率的なプログラミングができます。


今回は以上です。実はまだ割込みを細かくするだとかサーボの指示をDuty比で
指示できる関数を使ったりだとかまだ改善の余地はあります。もっと精密な動作
が必要になった時は実装してみようかなと思います。

2020年7月12日日曜日

【家の自動化プロジェクト】#2 モジュールをテストしてみた

どうも。caketetuです。
前回はESP32を使ってBLE子機ユニットと拡張モジュールを作りました。
これだけではただマイコンを囲った箱ですので接続できても何もできません。
よって今回は、拡張モジュールにいくつか機能を付け足し、テストしてみよう
と思います。

前回より、以下のモジュールを追加しました。
 ・環境センサモジュール
 ・ミニI/Fパネル
 ・サブプロセッサモジュール



これが全面のミニI/Fパネルです。ここで紹介したSSD1306 OLED
ディスプレイと3つのボタン、赤青のLEDがついています。これで
単体でも最低限の操作をできるようになると思います。




モジュール右側に追加した環境センサモジュールです。使用してるセンサは
BME280とCCS811で気温、湿度、気圧、CO2濃度、総揮発性有機化合物(TVOC)
を測定できます。どちらもI2C接続なので配線は電源合わせ4線で済みます。




モジュール上側に追加したサブプロセッサモジュールです。Arduinoを
マイコン単体で動作させ、シリアル通信で接続します。ESP32はBLEを使ったとき
動作が不安定になる感じがあったので、ハードウェア制御は別のマイコンを
使うことにしました。マイコン単体をArduino化させるのはここで紹介しています。



本ユニット全体の回路としてはこんな感じになってます。本体には
デバック用として圧電ブザーを付けました。子機デバイス一つ目としては
ほとんど完成だと思います。



ここからソフトを書き込んでモジュールごとにチェックしていきます。
まずミニI/Fパネルから動かしていきます。ボタン操作でページを変える
サンプルを作ってみました。
I/Fパネルサンプルプログラム
//*********************************************************
//    Easy_IF_pannel_test
//    ESP32_Core_Unit向け簡易IFパネルテストプログラム
//    1.0   2020 7/3  作成
//*********************************************************

//==========================================================
//    Include Header
//==========================================================
#include <Wire.h>             //I2Cヘッダ(OLED)
#include <Adafruit_GFX.h>     //Adafruit グラフィックヘッダ(OLED)
#include <Adafruit_SSD1306.h> //Adafruit LCDヘッダ(OLED)

//==========================================================
//    Macro Definition
//==========================================================
//ディスプレイ関連
#define SCREEN_WIDTH 128    //OLEDピクセルX
#define SCREEN_HEIGHT 64    //OLEDピクセルY
#define OLED_RESET     -1   //OLEDリセット(本モジュールは無し)
//ピン定義
#define BUZZER_PIN 23
#define LED1 12
#define LED2 14
#define SW1 13
#define SW2 27
#define SW3 26

#define PAGE_MAX 2    //表示ページ最大
#define KEY_OFF 150   //ボタンウェイト時間

//==========================================================
//    Global Variable
//==========================================================
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);   //ディスプレイクラス
int page = 0;     //ページ


//==========================================================
//    Function Prototype
//==========================================================
void show_disp0(void);
void show_disp1(void);
void show_disp2(void);


//----------------------------------------------------------
//    Set Up Function
//----------------------------------------------------------
void setup() {
  Serial.begin(115200);   //シリアル初期化
  ledcSetup(1,12000, 8);  //ブザーPWM初期化

  //ピン設定
  pinMode(LED1,OUTPUT);
  pinMode(LED2,OUTPUT);
  digitalWrite(LED1,HIGH);
  digitalWrite(LED2,HIGH);
  pinMode(SW1,INPUT);
  pinMode(SW2,INPUT);
  pinMode(SW3,INPUT);

  //OLED設定
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // 失敗の時は無限ループ
  }
  display.display();      //ディスプレイ表示
  delay(1000);            //ちょっと待つ
  display.clearDisplay(); //ディスプレイクリア
  
}

//----------------------------------------------------------
//    Main Loop Function
//----------------------------------------------------------
void loop() {
  if(digitalRead(SW1)==LOW){        //ボタン1動作
    ledcAttachPin(BUZZER_PIN,1);    //ブザーピンセット
    digitalWrite(LED1,LOW);         //LED1 ON
    digitalWrite(LED2,LOW);         //LED2 ON
    ledcWriteNote(1, NOTE_C, 4);    //ブザーを鳴らす
    delay(KEY_OFF);                 //指定時間待つ(チャタリング防止)
    ledcDetachPin(BUZZER_PIN);      //ブザーピンを解除
    page++;   //次のページへ
  }else if(digitalRead(SW2)==LOW){  //ボタン2動作
    ledcAttachPin(BUZZER_PIN,1);    //ブザーピンセット
    digitalWrite(LED1,LOW);         //LED1 ON
    ledcWriteNote(1, NOTE_G, 4);    //ブザーを鳴らす
    delay(KEY_OFF);                 //指定時間待つ(チャタリング防止)
    ledcDetachPin(BUZZER_PIN);      //ブザーピンを解除
    page=0;   //最初のページに戻る
  }else if(digitalRead(SW3)==LOW){  //ボタン3動作
    ledcAttachPin(BUZZER_PIN,1);    //ブザーピンセット
    digitalWrite(LED2,LOW);         //LED2 ON
    ledcWriteNote(1, NOTE_A, 4);    //ブザーを鳴らす
    delay(KEY_OFF);                 //指定時間待つ(チャタリング防止)
    ledcDetachPin(BUZZER_PIN);      //ブザーピンを解除
    page--;   //前のページへ
  }
  
  digitalWrite(LED1,HIGH);    //LED1 OFF
  digitalWrite(LED2,HIGH);    //LED2 OFF

  if(page>PAGE_MAX) page=0;         //ページ上限
  else if(page<0) page = PAGE_MAX;  //ページ下限

  //pageによって表示を切り替え
  switch(page){
    case 0: show_disp0(); break;
    case 1: show_disp1(); break;
    case 2: show_disp2(); break;
    default: break;
  }
}

//----------------------------------------------------------
//    Show Display0 Function
//----------------------------------------------------------
void show_disp0(void){
  display.clearDisplay();   //ディスプレイクリア
  display.setTextSize(2);   //テキストサイズ設定
  display.setTextColor(SSD1306_WHITE);    //テキストカラー設定
  display.setCursor(10, 0);     //表示場所設定
  display.println(F("Page 0")); //テキスト出力
  display.display();            //画面表示
}

//----------------------------------------------------------
//    Show Display1 Function
//----------------------------------------------------------
void show_disp1(void){
  display.clearDisplay();   //ディスプレイクリア
  display.setTextSize(2);   //テキストサイズ設定
  display.setTextColor(SSD1306_WHITE);    //テキストカラー設定
  display.setCursor(10, 0);     //表示場所設定
  display.println(F("Page 1")); //テキスト出力
  display.display();            //画面表示
}

//----------------------------------------------------------
//    Show Display2Function
//----------------------------------------------------------
void show_disp2(void){
  display.clearDisplay();   //ディスプレイクリア
  display.setTextSize(2);   //テキストサイズ設定
  display.setTextColor(SSD1306_WHITE);    //テキストカラー設定
  display.setCursor(10, 0);     //表示場所設定
  display.println(F("Page 2")); //テキスト出力
  display.display();            //画面表示
}



こんな感じで動きます。画面も小さいので本当に最低限といった感じです。



次に環境センサモジュールを動かしていきます。
使用したセンサは結構有名でライブラリが豊富にそろっていますので
そちらを使っていくのが楽でしょう。私はSparkFunBME280とSparkFunCCS811
を使うことにしました。とりあえずセンサ値を垂れ流すサンプルを載せます。
//*********************************************************
//    Environmental_Sensor_Test
//    ESP32_Core_Unit向け環境センサユニットテストプログラム
//    1.0   2020 7/3  作成 BME280に対応
//    1.1   2020 7/3  作成 CCS811に対応
//*********************************************************
//==========================================================
//    Include Header
//==========================================================
#include <Wire.h>
#include "SparkFunBME280.h"
#include "SparkFunCCS811.h"

//==========================================================
//    Macro Definition
//==========================================================
#define SDA 18    //I2C SDA PIN
#define SCL 19    //I2C SCL PIN

#define BME280_ADDR 0x76    //センサアドレス BME280
#define CCS811_ADDR 0x5A    //センサアドレス CCS811

//==========================================================
//    Global Variable
//==========================================================
BME280 sen_bme280;    //センサクラス BME280
CCS811 sen_ccs811(CCS811_ADDR);    //センサクラス CCS811
//センサデータ
double temp_act = 0.0, press_act = 0.0,hum_act=0.0;
unsigned short co2=0, tvoc=0;

//==========================================================
//    Function Prototype
//==========================================================

//----------------------------------------------------------
//    Set Up Function
//----------------------------------------------------------
void setup() {
  Serial.begin(115200);   //シリアル初期化

  //I2C設定&センサ初期化
  Wire.begin( SDA, SCL, 40000);           //Pin SDA, Pin SCL, Frequency
  sen_bme280.setI2CAddress(BME280_ADDR);  //BME280 アドレスセット
  //BME280 セットアップ
  if(sen_bme280.beginI2C() == false){ 
    Serial.println("Sensor BME280 connect failed");   //エラーなら停止
    while(1);
  }
  //CCS811 セットアップ
  if (sen_ccs811.begin() == false){
      Serial.print("Sensor CCS811 connect failed");   //エラーなら停止
      while (1);
  }
  Serial.println("start ");
}


//----------------------------------------------------------
//    Main Loop Function
//----------------------------------------------------------
void loop() {
  //センサ値取得 BME280
  temp_act = sen_bme280.readTempC();
  press_act = sen_bme280.readFloatPressure();
  hum_act = sen_bme280.readFloatHumidity();

  //センサ値取得 CCS811
  if (sen_ccs811.dataAvailable()){
    sen_ccs811.readAlgorithmResults();
    co2 = sen_ccs811.getCO2();
    tvoc = sen_ccs811.getTVOC();
  }

  //シリアル出力
  Serial.print(" Humidity: ");
  Serial.print(hum_act, 0);
  Serial.print(" Pressure: ");
  Serial.print(press_act, 0);
  Serial.print(" Temp: ");
  Serial.print(temp_act, 2);

  Serial.print(" CO2: ");
  Serial.print(co2);
  Serial.print(" tVOC: ");
  Serial.print(tvoc);
  
  Serial.println();

  delay(500);   //ちょっと待つ
}

動きはこんな感じです。
値は取れているので通信はOKのようです。しかし、値は何となく違う感じ
がしています。実用にはセンサ値を分析してフィルタや補正を考えてやる
必要がありそうです。


今回、拡張モジュールを追加してIoT機器っぽくなりました。しかしこれだけではちょっと
物足りません。部屋のものをいろいろ操作できるぐらいでないとホームオートメーション
とは言えません。次回はESP32から部屋の機器を何かを操作できるようにしてみたい
と思います。





2020年7月11日土曜日

【家の自動化プロジェクト】#1 BLE子機の製作1

どうもcaketetuです。

去年末からHA(ホームオートメーション)に興味があり、色々作って遊んでおりました。
Google Assistantの力を借りて音声認識で色々動かせるところまでいったのですが
ちょっとお粗末な部分もあったのでリニューアルも兼ねて作り直してみようと思います。


構成としてはRaspBerryPiがBLE親機になり複数のBLE子機を一括管理します。
そこにNode.jsでwebサーバーを作り、スマホやパソコンでアクセスし命令を飛ばす
仕組みです。今回は実際の指令を実行するBLE子機ユニットをESP32で作っていこう
と思います。



設計はこんな感じ。ESP32モジュールを入れるためのただの箱です。
穴がたくさん開いている板はタミヤのユニバーサルプレートで、
これを共通規格として用いることで上下左右に拡張できる所が
本計画のミソになっています。




内部の回路です。これもIOを使いやすいように引っ張ってきてるだけの基板
です。GND、3.3V、Vinはよく使うのでピンを多くしています。



各パーツになります。ユニバーサルプレートを使用しているので
パーツをそろえる時間がぐっと節約できます。こういった板状のものを
印刷すると時間かかりますからね。




組み立てた様子がこちら。しっかりと組みあがりました。
タミヤのユニバーサルプレートは設計しやすい寸法になっており、
穴でカットすれば簡単に精度よく加工できるので重宝しています。



次に拡張パーツを作っていきます。本計画は拡張性の高い基本設計に
拡張パーツをどんどん付け足せるようにすることで機能を簡単に
増やすことができます。PLCにモジュールを足す感じにヒントを得ました。



こんな感じで拡張していきます。最初の基本設計から全面のIFパネルと
上、横に回路を取り付けられる拡張モジュールを増やしました。




わーっと出力し…



はい、全部乗せ完成です。
拡張モジュールの使い方としては今のところ以下を考えています。

 ・温湿度センサ、CO2センサなどをまとめた、”環境センサモジュール”
 ・マイコンを乗っけてピンとリソースを拡張する "サブプロセッサモジュール"
 ・モータ制御に対応した、”モータコントローラモジュール”

次回は拡張モジュールに回路を追加していくつかのサンプルを動かしてみようと思います。