Arduino UnoでNikon用赤外線リモコンを作成する - 準備編(ML-L3エミュレート)

#はじめに
今週末(2018/07/28早朝)に皆既月食が発生します。これを写真に収めるべく準備を起こなっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/35f27268-fafc-dc56-d30e-c422b44b31d9.png)
こんな風に連続写真で収めたい。

##画角の設定
欠け始めから欠け終りまで2時間を収めるには、APS-Cで28mmのレンズを使用すれば良いらしい。ステラナリウムでシミュレーションしました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/c0632c40-bbd9-ae43-f5ea-ad635bc08142.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/92ff0d42-84b0-17b3-eddf-4fd0fc5a2f6b.png)

##インターバル撮影
さて本題に入ります。連続写真にするには合成時に写真を選べば良いため、撮影間隔は何分でも良い。いろいろ備えるとしたら短かい方が良いが、一定間隔である方が望ましい。

##問題点
私のカメラ(Nikon D7000)にはインターバル撮影機能は無く、Nikon MC-36AはD7000には使用できません。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/d7de4f25-adcc-381c-a922-2f1e36e148e8.png)

中華製の互換品なら存在していますが、いまから注文して月食に間に合いません。自作するにもコネクタの入手が難しい。

##解決策
そこで赤外線リモコンI/Fを使用することにします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/66236d1b-73d6-c664-5a8a-7277a20503be.png)
赤外線リモコン ML-L3

赤外線リモコンでもミラーアップは可能ですし、バルブ撮影も可能なことが判りました。(バルブは1回押下で開放、もう1回押下でシャッター閉じとなります。)
赤外線リモコンでも天体撮影を行なうに必要な機能が実現可能です。リモコンの使用可能距離を考えると、むしろ有線より便利かも。

#赤外線リモコンML-L3をエミュレート
それでは本題、赤外線リモコンML-L3をArduino Unoエミュレートすることにします。
[SB-Project](https://www.sbprojects.net/projects/nikon/)
[Nikon D40用天体撮影インターバルタイマー作成](http://wpjzrn.ddo.jp/astro/D40_Interval_timer/D40_Interval_timer.html)
すこしぐぐると上記のリファレンスがみつかりました。がどちらもATtinyでの制作例だったため、私はArduion Unoで実現することにします。

##プロトコル
赤外線I/Fのプロトコルは下記のタイミングで赤外線を**2回**送信します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/62c82c5d-d8d7-d30e-6b5c-4bce0e11914b.png)

キャリア周波数は38kHzです。黒色の場所はONしていれば良いというわけではなく、26.3us毎(38kHz)に1/3のデューティー比でON/OFFを繰り返す必要があります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/97129c1d-07b2-15a9-a5ba-1da113e18d87.png)

38kHzリモコン信号モジュールは市販されているのですが、手持ちはなく、このキャリア信号もArduion Unoで実現することにします。

##必要なもの
1. Arduion Uno
2. 赤外線LED
3. 330オーム程度の抵抗
4. タクトスイッチ

##接続
簡単なので回路図は省略します。今回は2つのピンのみ使用します。
PIN 0 <--> 330Ω抵抗 <--> 赤外線LED <--> GND
PIN 3 <--> タクトスイッチ <--> GND

##プログラム
下記サイトを参考にしています。
[
2015.1.8 ニコン・ソニー用赤外リモコンの製作(IR-Remote-Timer)
](https://blogs.yahoo.co.jp/otaru1960/39180460.html)


```C++:NikonRemoteTest.cpp
///////////////////////////////////////////////////////////
// REMOTE FOR NIKON
// 参考 : https://blogs.yahoo.co.jp/otaru1960/39180460.html
//        http://wpjzrn.ddo.jp/astro/D40_Interval_timer/D40_Interval_timer.html
//        https://www.sbprojects.net/projects/nikon/
//
// 2018/07/24 First
//
////////////////////////////////////////////////////////////

void setup() {
  Serial.begin (115200);
  Serial.println ();

  pinMode(0, INPUT_PULLUP);    // BTN
  pinMode(3, OUTPUT)  ;       // Remote LED

}

void loop() {
  // スイッチ押下を検出(注意:テストなのでいい加減な処理です)
  if ( LOW == digitalRead( 0 ) )
  {
    Send_IR();
    delay(1000);
  }
}

void Send_IR()
{
  // 信号は2セットで1回となります。
  for ( int i = 0 ; i < 2; i++)
  {
    // 引数は38kHzのサイクル数となります。
    nikon_IRLED_1(76);    // 2ms
    nikon_IRLED_0(1064);  // 28ms
    nikon_IRLED_1(15);    // 0.4ms
    nikon_IRLED_0(60);    // 1.58ms
    nikon_IRLED_1(15);    // 0.4ms
    nikon_IRLED_0(136);   // 3.58ms
    nikon_IRLED_1(15);    // 0.4ms
    nikon_IRLED_0(2101);  // 63.2ms
  }
}

// 信号ONの処理
void nikon_IRLED_1(unsigned int    no)
{
  int i;
  for (i = 0; i < no; i++)
  {
    PORTD |= _BV(3);  // 0.3-0.4uS    digitalWrite(3, HIGH)は遅いのでポートを直接操作    // 1
    delayMicroseconds(8);  // 13usで良いはずなのだが、14にしないと動作しない(13x2=28uF = 38kHz)
    PORTD &= !_BV(3);  // digitalWrite(3, LOW);    // 0
    delayMicroseconds(23);  // 8-20から動作した.8-24はNG. 8-22or23はOK
  }
}
// 信号OFFの処理
void nikon_IRLED_0(unsigned int    no)
{
  int i;
  for (i = 0; i < no; i++)
  {
    PORTD &= !_BV(3); // digitalWrite(3, LOW);    // 0
    delayMicroseconds(8);
    PORTD &= !_BV(3);  // digitalWrite(3, LOW);    // 0
    delayMicroseconds(23);
  }
}

```

##解説
###void loop()
メインループにてボタン押下の検出をしています。このままだと押下しっぱなしで信号を連続送出するため問題のあるコードです。信号の**変化**を検出しボタン開放を検出するように次回修正します。

###void Send_IR()
信号の間隔を設定しておきます。

###void nikon_IRLED_1(unsigned int    no)
信号ON, OFF時の処理となります。引数は繰り返しのサイクル数となります。38kHzであるため、関数の1ループは26.3usec、ON,OFFのデューティ1:3となるようにします。
digitalWrite()はArduino Unoのアセンブラ展開で44サイクルにもなり、4usecを消費することになります。26.3usecで処理を終らせる必要がある中でLED ON OFFに1回4usecを使うとなると、少し考える必要があります。

```C++:digitalWrite.cpp
void digitalWrite(uint8_t pin, uint8_t val)
{
    uint8_t timer = digitalPinToTimer(pin);
    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);
    volatile uint8_t *out;
 
    if (port == NOT_A_PIN) return;
 
    // If the pin that support PWM output, we need to turn it off
    // before doing a digital write.
    if (timer != NOT_ON_TIMER) turnOffPWM(timer);
 
    out = portOutputRegister(port);
 
    uint8_t oldSREG = SREG;
    cli();
 
    if (val == LOW) {
        *out &= ~bit;
    } else {
        *out |= bit;
    }
 
    SREG = oldSREG;
}
```

そこでピン制御レジスタに直接データを書き込みます。PORTDのBit3がPIN3となります。これをON, OFFすることでdigitalWriteと同じ動作をしてくれます。

```C++:直接制御
    PORTD |= _BV(3);  // Bit 3 ON = digitalWrite(3, HIGH);
    PORTD &= !_BV(3);  // Bit 3 OFF = digitalWrite(3, LOW);

```

こちらは3サイクルで終了するためdigitalWriteと比較して処理を時間を1/10以下にすることが可能です。

delayMicroseconds()でデューティと時間を設定します。数字だけを見ると26.3usecより長いのですが、クロックの誤差でかわっているのでしょうか。動作しない場合は数値を変更してみてください。
私の環境では8-20で動作。8-24はNG。 8-22or23はOKでした。

###void nikon_IRLED_0(unsigned int    no)
信号を送出しないコードです。void nikon_IRLED_1(unsigned int    no)と同じ構造とすることで処理時間を同じにしています。ディレイを変更した場合はこちらも忘れずに。
一つにまとめてしまうと、条件分岐に処理時間が必要となりますので可読性が悪くなりますので、あえて2つ用意しています。

#デバッグ
ブレッドボードでさくっと配線してデバッグしました。LCDやボリュームがついていますが、次回の実用編用途ですので無視ください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/b94388a2-0ccc-25a8-135b-93b3127aa6d2.png)

カメラ側はレリーズダイヤルをリモコンにセットします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/4378707c-6e7f-12a6-1a4c-e8aefc27896a.png)

設定でリモコン時はミラーアップの設定とします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/ac203914-ed76-94fd-2660-1531c513e7d0.png)

デバッグボードのボタンを押下し、1回目でミラーアップ、2回目でレリーズすればデバッグ完成です。動作しない場合はdelayMicroseconds()の数値を変更してみてください。

なお、バルブの場合は、3回目を押下することでミラーダウンします。

#おわりに
強力な赤外LED(とはいってもテレビリモコン程度のLEDで十分ですが)を使用すれば、3m離れていてもOKというのは便利です。各種センサーと組み合わせも可能となります。

いろいろと応用の効く実験だと思います。Nikon1やCoolpixにも使用できる筈ですので、お持ちのかたは是非。


次回はインターバルタイマーを組み込み、天体撮影の為の実用リモコンを作成します。

#追記(2018/07/29)
インターバル撮影機能が標準で存在してました。かつレリーズモードをMupにすればミラーアップもしました。初めからこれを使えばよかったんだな。
まあ、待機時間を過ぎると空レリーズさせて機能を目覚めさせる必要があるとわかったし、バルブで長時間撮影にも応用できるし、無駄ではなかったと信じたい。

2018/07/25 Ikeda