あみらぼ

電子工作がメインのDIYもの作り雑記

ESP32の自動書き込みが(無理やり)できるようになった!

前々回、WROVERへの自動書き込みを行いたくて、わざわざシリアル変換モジュールを購入したにもかかわらず、
CTSとDTRは別物!
という、知ってる人なら当たり前の事を知らなかったせいで、泣き寝入りしていましたが、なんとも無理やりな方法ではあるものの、DTR端子の無いシリアル変換モジュールでも、無事にWROVERへの自動書き込みができるようになりました。

どうやったかというと、以前の画像に載っていたarduino pro miniに、RTSの信号を監視させて、適切なタイミングでDTR信号を送るようにする、という、全くスマートではない方法です。
f:id:amilabo:20200302090148j:plain
以前に3個で1000円ちょいで買った5v 16Mhz のarduino pro miniが、ブレッドボード上で使ってなかったので、適当に動きそうなスケッチ書いてみたら、あっさり動きました。
こんな無駄な事にpro miniを使う人はいないと思いますが、もしかしたら、DTR端子の無いシリアル変換モジュールで自動書き込みができずに困っている人の助けになるかもなので、スケッチ載せておきますね。

int RTS_IN = 2; //使うピンは何番でもOK
int DTR_OUT = 4; //使うピンは何番でもOK

void setup() {
  delay(100);
  //事前に、CKDIV8 = 0 (クロック8分周有効)にヒューズビットを設定しておく
  //起動時は16Mhz / 8 = 2Mhzで動いてるので、ここから8Mhzで動くよう変更する
  //このpro miniは5V 16Mhz品だけど、16Mhzで動く瞬間は無いので3.3v動作OK!
  byte save_SREG = SREG;
  cli();
  CLKPR = 0x80; 
  CLKPR = 0x1;  // 8MHZ
  SREG = save_SREG;
  //ここまでで8Mhzへの変更終わり、以降はタイマーやシリアル通信も通常どおり動作可能
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH); //無事動き始めたよーって合図
  pinMode(RTS_IN, INPUT_PULLUP); //書き込み開始のリセットを監視
  pinMode(DTR_OUT, OUTPUT);
  digitalWrite(DTR_OUT, HIGH); //とりあえず平常時はDTRはHIGHの状態にする
  delay(100);
}

void loop() {
  while(digitalRead(RTS_IN) == HIGH); //書き込み開始のリセット待ち
  while(digitalRead(RTS_IN) == LOW); //リセット中はまだDTRはHIGHのまま
  digitalWrite(DTR_OUT, LOW); //リセット終えたらすぐDTRをLOWに変更
  delay(1500); //1.5秒くらい(適当)DTRをLOWにしたまま、これで書き込みモードで起動するはず
  digitalWrite(DTR_OUT, HIGH); //DTRをHIGHに戻す
  while(digitalRead(RTS_IN) == HIGH); //書き込み終了後のリセット待ち
  while(digitalRead(RTS_IN) == LOW); //リセット中もDTRはHIGHのまま
  //リセット後もDTRはHIGHのまま、これで通常モードで起動するはず
}

動作クロックの変更とかありますが、本質はloop()関数内だけです。ただこのままだと、書き込みに関係ないRTS信号を一瞬でも受けてしまうと、ESP32が書き込みモードに入って抜け出せなくなり、再起動が必要になるので、その辺はもう少し工夫した方がいいかもです。あと、基本スリープ状態でRTS_INのFALLINGを検出して割り込み、とかで節電するとよりスマートかもですね。ただ、一度シリアル変換モジュールを繋いでESP32の電源を入れたら、その後は何度も書き込みを繰り返してテストする、というような場合には十分実用かと思います。
atmega328pの8MhzでGPIOを2ピン使うだけで問題無く動いたので、8ピンで50円のATtiny13Aの9.6Mhzでもきっと同じ事ができると思いますし、近々ATtiny13Aを買ってきてそうする予定です。

ちなみに、WROVERからはどんどん脱線していきますが、以前にarduino pro miniを使った時にいろいろ調べて、ネットに載ってなさそうな他の人にも役立ちそうな情報を見つけたのでちょっと書いておきます。
arduino pro mini、及びatmega328pは、5v or 3.3v、16Mhz or 8Mhz、どんなものでも関係なく、データシートの安全領域さえ守っていれば、
基本的に自由な電圧、周波数で使えます。
実際、以前に作った機器では5v 16Mhzのpro miniを、乾電池2本で4Mhzで動作させ、全く問題なく動いています。電圧を下げていっても、1.8v程度でも問題なく動き、1.8vを切った所でBODが作動し、安全に電源を落としてくれている様子です。
なので、(基板上のLDOを使わない、書き込みはISPで行う場合)悩んだらとりあえず5v 16Mhz品のpro miniを買っておいて問題は無い気がします。16Mhz品を8Mhz やそれ以下で使うことはできますが、8Mhz品を発振器変えずにPCのCPUみたいに倍速して16Mhzで使うことは残念ながらできないので。まぁ、大は小を兼ねるといった感じでしょうか?

5v品も3.3v品も、LDOと発振器以外は全く同じ部品の回路なので、やることは、上記のコードのように最初にクロックプリスケーラで任意の周波数になるよう設定するだけです。
ただ、仕様書見た人はわかると思いますが、普通にやると、起動~ブートローダー~ユーザープログラムで周波数変更までの間のけっこうな時間、充電池2本だと2.4vで16Mhz動作させることになるので、それは非常にまずいです。
で、どうするかですが、CKDIV8のヒューズビットを0に設定しておきます。方法はいろいろあるかもですが、boards.txtに、CKDIV8と周波数(と必要に応じてBOD設定)の値だけ変えたオリジナルの定義を追加して、ブートローダーを書き込むのが簡単です。そうする事で、起動時~ブートローダー~ユーザープログラムで周波数変更までの時間、8分の1の周波数で動作するようになります(16Mhz品なら2Mhz、8Mhz品なら1Mhzといった感じです)。
最初、CKDIV8は何か特別な設定で、これを0に設定してしまうと、2Mhzや1Mhz からさらに分周して低い周波数でしか使う事ができないものかと思っていたのですが、そうじゃないんですね。CKDIV8は、単に起動時にクロックプリスケーラの設定を8分周にするかしないかを設定するだけで、その後にユーザーは通常通り自由な周波数に変更する事ができるということみたいです。となると、CKDIV8が本当に超絶便利機能に見えてきます。ブートローダーが8分周で動くので、シリアル経由でプログラムを書き込もうとすると大変かもですが、ISPで書き込めばいいだけで(Arduino uno or 互換機だけで簡単にISP書き込みはできます)、あとはユーザープログラムではdelay等のタイマ関連も期待通りの動作をするので、乾電池2本で余裕で安全に動かせるのは本当に便利です。4Mhz程度で動けばいい場合に、乾電池2本から3.3vに昇圧するなど、愚の骨頂ですね。1点だけ気になってる点としては、高周波の発振器が低電圧でも問題なく動くのか?という点ですが、こちらは調べてもちょっとわかりませんでした。ただ、もともとの設定が発振器はフルスイングではなく低電力で動かしているみたいなので、恐らく大丈夫…だと思っています。

と、いうわけで、今回のケースでも、使っているのは5v 16Mhzのpro miniですが、電源はWROVERと同系統の3.3vからとって、8Mhzで安全に動作させることができています。一応まとめとくと、
Arduino 〇〇は何でも1.8vまで安全に動く!
という事です。(もちろん、ちゃんとした使い方をした場合ですよw)


と、いうあたりで、WROVERとは全く関係ない話で終わってしまいましたが、続きは次回。ではでは。