割り込みを使って、2個のESP8266のPWMの位相を同期させてみた

これは [ESP8266 Advent Calendar 2015](http://qiita.com/advent-calendar/2015/esp8266) の 14日目の記事です。

こんにちはkerokeroです。プログラム書いたり電子工作したり猫に遊ばれたりしてるおじさんです。Qiitaには初投稿です。ESP8266で外部割り込みを使ってみましたので、参考になればと思い投稿します。

# 背景
現在、ESP8266(ESP-WROOM-02)を使って、鉄道模型レイアウト(直流二線式)のワイヤレス化をたくらんでいます。
###### こんな感じのものを
![zoncontrol.png](https://qiita-image-store.s3.amazonaws.com/0/105365/62da89de-819e-bb37-b4fc-6d49df2bd854.png)

できそうかどうか検証中の様子
[![サーバー経由でブラウザとやりとりしている様子](http://img.youtube.com/vi/Jjh9JnXFLsk/0.jpg)](https://www.youtube.com/watch?v=Jjh9JnXFLsk)

でも、PWM制御では、異なる信号源が作るPWM電圧の間を通過する場合、両側の位相があっていないと実効電圧が一時的に上がってしまうため動きがギクシャクします。
![pwmmerge2.jpg](https://qiita-image-store.s3.amazonaws.com/0/105365/95f3cc52-af57-0472-2476-0e04a22904df.jpeg)

実際の動きはこんな感じです。セクション境界(黒いジョイントの部分)で加速してるのがわかります。

[![位相が揃っていないと](http://img.youtube.com/vi/3rtIDWl9Frg/0.jpg)](https://www.youtube.com/watch?v=3rtIDWl9Frg)

以下は「それなら位相を合わせてみよう」と試してみた報告です。

# どうやるか
自分のセクションに自分以外が出しているの電圧が検出されたことをきっかけに割り込みをかけ、その波形に同期させます。
通常、鉄道模型の動力車(Nゲージ)は複数軸の両側の車輪で集電していますので、列車が自区間と隣の区間とをオーバーラップするとき、隣の区間にかかる電圧を検出することができます。ただし、これは隣の区間と自分の区間のPWM波形のORですので、ここから自分のPWM信号をマイナスしてやります。(下図)
![phaseSyncDiagram2.jpg](https://qiita-image-store.s3.amazonaws.com/0/105365/f7065956-0518-7cef-1e42-a8aba5acddd7.jpeg)
(手描きかよ...)

ここまでの信号生成部分はロジック回路で作りました。
![phasediffDetect.jpg](https://qiita-image-store.s3.amazonaws.com/0/105365/77217b21-a1a5-0480-3f54-0ed65bb93f55.jpeg)
線路の電圧波形を検出する信号(上段の回路)から、自分のPWMを少し引き延した信号(中段)を差し引きします(中段の右端のインバータ＋下段の回路)

そうすると「隣の区間だけがONである状態」をあらわす信号が抽出できますので、これをESP8266の割り込みに使います。

ESP8266の割り込みは、RAISING(LOW→HIGH)、FALLING(HIGH→LOW)、CHANGE(両方)の3つのかけかたができますが、今回はCHANGEでかけました。その部分のコードを載せます(Arduinoで書いています、VisualStudioにVisualMicroプラグインを入れて使ってます)。

割り込みルーチンの中では「隣の区間だけがONである状態」になった理由が「「両方がONの状態」から「自分がOFFになった」ため」か「「両方がOFFの状態」から「隣がONになった」ため」かを判定し、それに応じてPWMのカウンタタイマーをリスタートさせています、関連するところだけ抜き出したものを下に載せます。タイマーのリセットはcore_esp8266_wiring_pwm.c内のpwm_start_timer()のコピペです。

```cpp
//setup()の中
	attachInterrupt(13, sync,  CHANGE);//割り込みを設定する

volatile long up = 0;//割り込み発生回数の調査用
volatile long dn = 0;//〃
volatile short adj = 0;//タイミング調整用(実行中に変化させながら様子を見たところ、120くらいがちょうどよいようです)
bool resetWithFALLING = false;
//割り込み処理(位相同期)
void sync() {
	if (rest > 0) {//ちょっと間引き
		rest--;
		return;
	}
	detachInterrupt(13);//再入を防ぐため割り込みをやめる
	Feeder *f = Feeders->get(0);
	short duty = f->getAnalogValue();//(上の行とあわせてGPIO14に設定している値のこと)
	int isRAISING = digitalRead(13) == 1;
	if (isRAISING) {
		up++;
		if (digitalRead(14) == HIGH) {//me delay
			resetWithFALLING = true;
		}
		else {
			timer1_disable();
			delayMicroseconds(1000 - duty - adj);
			timer1_write(1);
			timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
			rest = 5;
		}
	}
	else {//FALLING
		dn++;
		if (resetWithFALLING) {
			timer1_disable();
			timer1_write(1);
			timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
			resetWithFALLING = false;
		}
	}
	attachInterrupt(13, sync, CHANGE);//割り込みを許可する
}

volatile short rest = 0;
//loop()かonTimer()の中
	rest = 0;//割り込み間引き用カウンタをクリアする
```
上記のコードでなんとか動いてますが、割り込み検出をONにしたままだと10分に1回くらい落ちます。(なぜだかわかる人教えてください...))

# うまく動いた？
上記のときどき落ちる件を除いては(^^;;一応動きました。同期した様子と波形を載せますね。

[![位相同期してみた結果](http://img.youtube.com/vi/-2wJWZVDdHA/0.jpg)](http://www.youtube.com/watch?v=-2wJWZVDdHA)

[![位相同期してみた結果(波形)](http://img.youtube.com/vi/81jG-ZaHGCQ/0.jpg)]
(https://youtu.be/81jG-ZaHGCQ)
# システム全体について
今回はESP8266に関係ある部分だけ抜き出して書きましたが、システム全体についてははてなで少しずつ書きはじめたところですので、ご興味のある方はご覧ください→。[晴れ時々曇りところによって猫](http://kerokero.hatenablog.jp/)