Arduino Nano Every を買った.
Arduino Nano / Nano Every を載せて格安中華 LCD と接続できる基板を作ったので,載せてみた.
I2C に温度センサをつないで,ついでに時刻を表示させてみた.
で,4時間ほど経つと…
表示時刻が 24 秒も進んでいる.
Nano Every の回路図を見てみると,水晶発振子が無い. マイコン内蔵の発振器を使っていて,クロック周波数が不正確なのだろう. Nano のほうには水晶発振子は乗っているので,Nano Every の非互換の1つと言えるだろう.
ここで Arduino Nano Every に載ってる ATmega4809 のデータシートを読むと
が載っている.
で,ArduinoIDE 側の SDK のソースコードを読んでみると
という状況.
というわけで,sketch の setup() 内で強引に補正をかけてみた.
// // millis() speed adjust. for Arduino Nano Every // extern uint16_t millis_inc, fract_inc; if ( ( millis_inc == 1 ) && ( fract_inc == 24 ) ) { uint32_t tick_us = millis_inc*1000L + fract_inc; int8_t sigrow_val = SIGROW.OSC16ERR5V; tick_us = ( tick_us*1024 + ( 1024 + sigrow_val )/2 ) / ( 1024 + sigrow_val ); // adjust manually // tick_us += 1; // faster // tick_us -= 1; // slower millis_inc = tick_us / 1000; fract_inc = tick_us % 1000; }
未補正時の +6 sec/hour の時計の狂いが +3 sec/hour 程度に改善された. さらに手動補正で tick_us -= 1 してみると,1時間経過しても「やや遅れてるな」という程度まで追い込めた.
以前,試しに FusionPCB で作った基板を引っ張り出して組み立てました.
ESP32 DevkitC に JTAG アダプタを接続するゲタです.
19 ピンの足の長い連結ピンソケットが売られてないので,40 ピンの連結ピンソケットの不要な足を刈り込んで使いました.
ESP-IDF 環境での JTAG デバッグは前にネタにしたので,今回は ArduinoIDE 環境でのプログラムのデバッグに挑戦してみます.
Linux PC 上での開発を前提に書きますが,道具立ては Windows でも一緒なので,Windows 上での手順としても参考になると思います.
Olimex の ARM-USB-TINY-H を使いました. ピンヘッダの配列は ARM JTAG20 というやつです. OpenOCD が対応している ARM JTAG 20 配列のアダプタなら,たぶん他のものでも使えると思います(未確認).
ArduinoIDE 環境にも ESP32 のツールチェーンは入ってますが,ESP32 対応の OpenOCD が無いので,ESP-IDF 環境もインストールします. ツールチェーンが2組インストールされることになってしまうけど,気にしないことにします.
このページを参考にインストールしましょう.
ピンソケットに ESP32 基板を刺し,ピンヘッダに JTAG アダプタを接続します.
ArduinoIDE 上でプログラムを書きます.
void setup() { // put your setup code here, to run once: Serial.begin ( 115200 ); } void loop() { // put your main code here, to run repeatedly: for ( int i = 10; i >= 0; --i ) { Serial.println ( i ); delay ( 1000 ); } }
カウントダウンを表示するだけのプログラムです.
で,コンパイルする前に
ファイル - 環境設定 - 設定 で「より詳細な情報を表示する」
にチェックを入れておきます. ArduinoIDE の下の画面にコンパイル時のコマンドラインが表示されるようになるので,ELF ファイルがどこに生成されるか確認できます.
コンパイル・書き込みを実行し,シリアルモニタを開いて,ターゲット上でカウントダウンが動いてることを確認します.
OpenOCD を起動します.
$ cd ~/espressif/tools/openocd-esp32/v0.10.0-esp32-20200420/openocd-esp32 $ bin/openocd -f share/openocd/scripts/interface/ftdi/olimex-arm-usb-tiny-h.cfg \ -f share/openocd/scripts/board/esp-wroom-32.cfg \ -c 'adapter_khz 1000' -c 'gdb_port 3333' Open On-Chip Debugger v0.10.0-esp32-20200420 (2020-04-20-16:15) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html WARNING: boards/esp-wroom-32.cfg is deprecated, and may be removed in a future release. Info : Configured 2 cores adapter speed: 1000 kHz Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : clock speed 1000 kHz Info : JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1) Info : JTAG tap: esp32.cpu1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1) Info : esp32: Debug controller 0 was reset. Info : esp32: Core 0 was reset. Info : esp32: Debug controller 1 was reset. Info : esp32: Core 1 was reset. Info : cpu0: Detected debug stubs @ 3ffc0c64 Info : Listening on port 3333 for gdb connections
gdb は,ArduinoIDE のツールチェーンにも ESP-IDF のツールチェーンにも含まれています. ESP-IDF ツールチェーンのほうがパスが通ってるので,こちらを使いました.
で,今回は gdb のフロントエンドに gdbgui というものを使いました. web ブラウザ上にに表示してくれます.
$ gdbgui -g xtensa-esp32-elf-gdb /tmp/arduino_build_795862/sketch_jun19a.ino.elf
もちろん,普通に gdb を起動しても問題ありません.
起動したら以下のコマンドを打ち込みます.
(gdb) target remote localhost:3333 (gdb) mon reset halt (gdb) hb loop (gdb) c
あとは,変数の内容を確認したり,ステップ実行したりと,セルフ開発時の gdb と同じような感覚で使えます. ただし,ブレークポイントを仕掛けるときは,b ではなく hb コマンドを使います. ターゲット CPU 内部のデバッグ回路を使用したハードウェアブレークポイントコマンドです. ESP32 ではフラッシュ ROM 上のプログラムを RAM 上にロードせずに実行しているので,trap 命令を埋め込めないためです.
という話を目にしたので,ちょろりと書いてみた.
以下のコードを sketch の void setup() の前にコピペするだけです.
size_t devprintf ( Print *dev, const char *fmt, ... ) { va_list ap; char buf[80]; va_start ( ap, fmt ); vsnprintf ( buf, sizeof(buf), fmt, ap ); va_end ( ap ); return dev->print ( buf ); }
長さも長さなので,著作権うんぬんは特に主張しません. 改変・再配布は自由にどうぞ.
シリアル出力と LCD に秒数を表示し続けるプログラムです.
#include <LiquidCrystal.h> size_t devprintf ( Print *dev, const char *fmt, ... ) { va_list ap; char buf[80]; va_start ( ap, fmt ); vsnprintf ( buf, sizeof(buf), fmt, ap ); va_end ( ap ); return dev->print ( buf ); } LiquidCrystal lcd ( 2, 3, 4, 5, 6, 7, 8 ); void setup() { Serial.begin(115200); lcd.begin ( 16, 2 ); } void loop() { unsigned s = millis() / 1000; devprintf ( &Serial, "%05u sec.\r\n", s ); lcd.setCursor ( 0, 0 ); devprintf ( &lcd, "%05u sec.", s ); delay ( 1000 ); }
使っているのは loop() 内に2箇所あって
devprintf ( &Serial, "%05u sec.\r\n", s );
と
devprintf ( &lcd, "%05u sec.", s );
です.
シリアル出力だけでなくて,標準ライブラリを使った LCD 出力にも使えます. ちなみに LCD は,こんなものを使いました.
実行すると,シリアルからはこんな↓出力が
LCD にはこんな↓出力が表示されます.