花火撮影は、シャッターを押している間、シャッターが開くバルブモードにして撮影すると、いい感じに撮影できる。でも、どうしても手ブレが発生してしまう……。
この悩みは、どうやら “カメラ用リモコン”が解決してくれる!らしい。
リモコンにはいくつか種類があって、コストや使い勝手などで選ぶことを前回ご紹介しました。
- 有線タイプ
- 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イベント中は電池もちそうなので、実用できそう!
どうしても気になる人は、バッテリー外してピンヘッダーから電源供給して電流計ってみてくださいー