event calendar

M5StickC Plusで SONYデジカメ用リモコンを作る

tech

花火撮影は、シャッターを押している間、シャッターが開くバルブモードにして撮影すると、いい感じに撮影できる。でも、どうしても手ブレが発生してしまう……。

この悩みは、どうやら “カメラ用リモコン”が解決してくれる!らしい。
リモコンにはいくつか種類があって、コストや使い勝手などで選ぶことを前回ご紹介しました。

  • 有線タイプ
  • Bluetoothタイプ
  • 赤外線タイプ

それぞれのメリットデメリットをさらっと調べ、“赤外線タイプ” が一番自分に合ってそう!と考えポチろうとしたところでふと、思いました。

「これ、M5で作れるのでは ?」

そう、どこの家庭にも一つや二つ転がっているM5シリーズ。(転がってない)。
M5Stickには、赤外線LEDが内蔵されている。まさにうってつけ!

ということで「M5Stick C Plus」をつかって “シャッターリモコン”を自作してみることに。

工程

  • リモコンの赤外線通信の仕様を調べる
  • 送信データを調べる
  • コーディングする(オンオフ切り替えて赤外線LEDを光らせる)
  • 省電力化

リモコンの赤外線通信の仕様を調べる

ソニーの赤外線の仕様は下記の通り
・搬送波:40kHz Duty30%のPWM (38kHzという説も)
・1T :600us
・0:OFF 1T、ON 1T
・1:OFF 1T、ON 2T
・Leader:4T
・フレーム間隔:45ms

引用: 赤外線リモコンのフォーマット ChaNさん

必要なのは送信データ

アドレス:0x1E3A (0b1111000111010)
シャッター:0x2D (0b101101)

引用: IRリモコン3種製作(その1:SONY NEX用リモコン) vabenecosiさん

これだけ分かれば

コーディング

M5シリーズに使われているESP32にはPMW機能がついてるので、それをオンオフ切り替えて赤外線LEDを光らせるだけ!

#include <M5StickCPlus.h>

#define	LED				10	// 内蔵赤色LED(動作確認用)
#define	IR_OUTPUT		9	// 内蔵赤外線LED
#define	DEBUG_OUTPUT	26	// 波形確認用ピンヘッダ出力

void setup(){
	pinMode(DEBUG_OUTPUT, OUTPUT);
	pinMode(IR_OUTPUT, OUTPUT);
	ledcSetup(0, 40000, 8);		// PWM設定:CH0 40kHz, 8bit(256段階)
	ledcWrite(0, 180);			// PWM設定:CH0 Duty30%(180=256*0.7)
}

// Leader ON:2.4ms
void sendLeader(){
	// PWM出力
	ledcAttachPin(DEBUG_OUTPUT, 0);ledcAttachPin(IR_OUTPUT, 0);
	delayMicroseconds(2400);
	// PWM停止
	ledcDetachPin(DEBUG_OUTPUT);ledcDetachPin(IR_OUTPUT);
	digitalWrite(IR_OUTPUT, HIGH);digitalWrite(DEBUG_OUTPUT, HIGH);
}

// 0(OFF:600us ON:600us)  1(OFF:600us ON:1200us)
void sendBit(bool f){
	// OFF:600us
	delayMicroseconds(600);
	
	// PWM出力 ON:600us
	ledcAttachPin(DEBUG_OUTPUT, 0);ledcAttachPin(IR_OUTPUT, 0);
	delayMicroseconds(600);
	// もし1であれば追加のON:600us
	if (f)
		delayMicroseconds(600);
	
	// PWM停止
	ledcDetachPin(DEBUG_OUTPUT);ledcDetachPin(IR_OUTPUT);
	digitalWrite(IR_OUTPUT, HIGH);digitalWrite(DEBUG_OUTPUT, HIGH);
}

void sendCommand(uint8_t data, uint16_t addr){
	uint16_t c;
	
	c = data;
	// データを下位ビットから順番に送る
	for (int i = 0; i < 7; i++) {
		sendBit(c & 1);
		c >>= 1;
	}
	
	c = addr;
	// アドレスを下位ビットから順番に送る
	for (int i = 0; i < 13; i++) {
		sendBit(c & 1);
		c >>= 1;
	}
}

void loop(){
	pinMode(LED, OUTPUT);	// 動作確認用LED 点灯
	
	// 5回リピート
	for (int i=0;i<5;i++){
		sendLeader();
		sendCommand(0x2d, 0x1e3a);
		delay(11);	// 1フレーム45msになるよう間隔調整
	}
	
	digitalWrite(LED, LOW);	// 動作確認用LED 消灯
	
	delay(1000);
}

まずは、動作確認用で1秒おきに信号を送るコード。
うまく出来たら1秒ごとに、1フレーム45msが5回繰り返す、こんな波形がでます。👇👇

1フレームだけを拡大するとこんな感じ。

赤外線LEDの波形
上がってる時▶︎ 消えている ■
下がってる時▶︎ が光ってる 💡

間隔と逆なのは、M5Stickの回路のせい。

最初にリーダーが2.4ms、次にデータ・アドレスが続く構造。
シャッターを切るコマンドは 0x2Dで00101101 なのに 10110100 と逆になってるのは、
右から順番に送信するリトルエンディアンという(LSB)方式だから。アドレスも同じ。

波形的には良さそうなので、カメラに向けてみた。
でもなぜか2秒以上の間隔でシャッターが切れる。。。なんで?

あ、自動プレビューか!オフにしたら1秒間隔で撮影できた。

次はM5ボタンを押すとシャッターが切れるように変更、これは簡単。この3つ追加するだけ。

  • M5.begin()
  • M5.update()
  • M5.BtnA.wasPressed()
#include <M5StickCPlus.h>

#define	LED				10	// 内蔵赤色LED(動作確認用)
#define	IR_OUTPUT		9	// 内蔵赤外線LED
#define	DEBUG_OUTPUT	26	// 波形確認用ピンヘッダ出力

void setup(){
  	M5.begin();
	pinMode(DEBUG_OUTPUT, OUTPUT);
	pinMode(IR_OUTPUT, OUTPUT);
	ledcSetup(0, 40000, 8);		// PWM設定:CH0 40kHz, 8bit(256段階)
	ledcWrite(0, 180);			// PWM設定:CH0 Duty30%(実測値)
}

// Leader ON:2.4ms
void sendLeader(){
	// PWM出力
	ledcAttachPin(DEBUG_OUTPUT, 0);ledcAttachPin(IR_OUTPUT, 0);
	delayMicroseconds(2400);
	// PWM停止
	ledcDetachPin(DEBUG_OUTPUT);ledcDetachPin(IR_OUTPUT);
	digitalWrite(IR_OUTPUT, HIGH);digitalWrite(DEBUG_OUTPUT, HIGH);
}

// 0(OFF:600us ON:600us)  1(OFF:600us ON:1200us)
void sendBit(bool f){
	// OFF:600us
	delayMicroseconds(600);
	
	// PWM出力 ON:600us
	ledcAttachPin(DEBUG_OUTPUT, 0);ledcAttachPin(IR_OUTPUT, 0);
	delayMicroseconds(600);
	// もし1であれば追加のON:600us
	if (f)
		delayMicroseconds(600);
	
	// PWM停止
	ledcDetachPin(DEBUG_OUTPUT);ledcDetachPin(IR_OUTPUT);
	digitalWrite(IR_OUTPUT, HIGH);digitalWrite(DEBUG_OUTPUT, HIGH);
}

void sendCommand(uint8_t data, uint16_t addr){
	uint16_t c;
	
	c = data;
	// データを下位ビットから順番に送る
	for (int i = 0; i < 7; i++) {
		sendBit(c & 1);
		c >>= 1;
	}
	
	c = addr;
	// アドレスを下位ビットから順番に送る
	for (int i = 0; i < 13; i++) {
		sendBit(c & 1);
		c >>= 1;
	}
}

void loop(){
	M5.update();
	
	// M5ボタン押したら送信
	if( M5.BtnA.wasPressed() ){
		pinMode(LED, OUTPUT);	// 動作確認用LED 点灯
        // 5回リピート
        for (int i=0;i<5;i++){
            sendLeader();
            sendCommand(0x2d, 0x1e3a);
            delay(11);			// 1フレーム45msになるよう間隔調整
        }
      	digitalWrite(LED, LOW);	// 動作確認用LED 消灯
    }
	delay(1);
}

省電力化

初期状態で70mAぐらいの消費なので、内蔵バッテリー120mAhだと1時間ちょっとしか使えない……。

クロック周波数を下げてもいまいちだったので、
普段はディープスリープさせておき、M5ボタンを押したときだけ起きて、
赤外線送信後にまたディープスリープするように変更。

#include <M5StickCPlus.h>

#define	LED				10	// 内蔵赤色LED(動作確認用)
#define	IR_OUTPUT		9	// 内蔵赤外線LED
#define	DEBUG_OUTPUT	26	// 波形確認用ピンヘッダ出力

void setup(){
  	M5.begin();
	pinMode(DEBUG_OUTPUT, OUTPUT);
	pinMode(IR_OUTPUT, OUTPUT);
	ledcSetup(0, 40000, 8);		// PWM設定:CH0 40kHz, 8bit(256段階)
	ledcWrite(0, 180);			// PWM設定:CH0 Duty30%(実測値)
}

// Leader ON:2.4ms
void sendLeader(){
	// PWM出力
	ledcAttachPin(DEBUG_OUTPUT, 0);ledcAttachPin(IR_OUTPUT, 0);
	delayMicroseconds(2400);
	// PWM停止
	ledcDetachPin(DEBUG_OUTPUT);ledcDetachPin(IR_OUTPUT);
	digitalWrite(IR_OUTPUT, HIGH);digitalWrite(DEBUG_OUTPUT, HIGH);
}

// 0(OFF:600us ON:600us)  1(OFF:600us ON:1200us)
void sendBit(bool f){
	// OFF:600us
	delayMicroseconds(600);
	
	// PWM出力 ON:600us
	ledcAttachPin(DEBUG_OUTPUT, 0);ledcAttachPin(IR_OUTPUT, 0);
	delayMicroseconds(600);
	// もし1であれば追加のON:600us
	if (f)
		delayMicroseconds(600);
	
	// PWM停止
	ledcDetachPin(DEBUG_OUTPUT);ledcDetachPin(IR_OUTPUT);
	digitalWrite(IR_OUTPUT, HIGH);digitalWrite(DEBUG_OUTPUT, HIGH);
}

void sendCommand(uint8_t data, uint16_t addr){
	uint16_t c;
	
	c = data;
	// データを下位ビットから順番に送る
	for (int i = 0; i < 7; i++) {
		sendBit(c & 1);
		c >>= 1;
	}
	
	c = addr;
	// アドレスを下位ビットから順番に送る
	for (int i = 0; i < 13; i++) {
		sendBit(c & 1);
		c >>= 1;
	}
}

void loop(){
	//M5.update();

		// M5ボタン押したら送信
	if( M5.BtnA.wasPressed() ){
        // 5回リピート
        for (int i=0;i<5;i++){
            sendLeader();
            sendCommand(0x2d, 0x1e3a);
			// 5回目にはdelay入れないため
          	if(i!=4)
				delay(11);			// 1フレーム45msになるよう間隔調整
        }
		pinMode(LED, OUTPUT);	// 動作確認用LED 点灯
      	digitalWrite(LED, LOW);	// 動作確認用LED 消灯
    }
	
	// GPIO37(M5StickCのM5ボタン)がLOWになったら起動
	pinMode(GPIO_NUM_37, INPUT_PULLUP);
	esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);
	// ディープスリープ
	M5.Axp.ScreenBreath(0);    // 画面の輝度OFF
	esp_deep_sleep_start();
}

最終版の波形がこちら。緑がバッテリー端子、黄色が赤外線の波形

バッテリーの電圧降下的に、M5ボタンが押されて起動してから信号送信まで130msもかかってる。
起動処理かな?起動時間の37%も無駄にしてるのは電池がもったいないけど仕方が無い。。。

ディープスリープがまぁまぁ優秀なようで、画面消してるから消費電流表示させる手段がないけど、満充電してポチポチ適当に押して2日以上電池持ってる。
これでさすがに1イベント中は電池もちそうなので、実用できそう!
どうしても気になる人は、バッテリー外してピンヘッダーから電源供給して電流計ってみてくださいー

タイトルとURLをコピーしました