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




