Arduinoのブートローダとパソコン(IDE)の通信内容を解析してみた

Arduinoのブートローダとパソコン(IDE)の通信内容を解析してみた Arduino
スポンサーリンク

Arduino Unoはブートローダー(Optiboot)が工場で書き込まれているので、Arduino IDEから「書き込み」を押すだけでプログラムをFlashメモリに書き込むことができます。

ここでは、Arduino Unoのブートローダ(Optiboot)が、Arduino IDEとどのような信号をやり取りしているのか実際に通信データを解析してみたいと思います。

メカつば
メカつば

通信データを見ると、より理解が深まるよね

ブートローダ

Aruduino UnoにはOptibootというブートローダが工場で書き込まれた状態になっています。

Optibootは起動後にパソコンからUARTでデータが送られてこないか監視をします。
データ(プログラム)が送られてきた場合はFlashメモリの指定位置に書き込み、書き込んだプログラムを起動します。
データが送られなければ、元々書き込まれていたプログラムを起動します。

Optibootの詳細は以下の記事で紹介していますので、興味のある方は読んで頂けると嬉しいです。

Arduinoのブートローダと起動処理の仕組み
Arduinoのブートローダや起動処理について仕組みを理解することで、組み込み機器がどのように起動するのかを知ることができます。 ここではArduino Unoがどのような原理で起動しているのかをまとめていきたいと思います。 ブートローダは...

準備物

OptibootとArduino IDEはUARTと呼ばれるシリアル通信でデータをやり取りします。
UARTの通信データを見るために安価なロジックアナライザと、解析ソフトを使用します。

●ロジックアナライザ

このロジックアナライザを使用すれば、UART、IIC、SPIなどのシリアル通信のデータを見ることができます。

●解析ソフト pluseview

上記のロジックアナライザのデータを見るために、pluseviewというフリーソフトを使用します。
以下のサイト様でインストール方法から使用方法まで詳細に説明されているので、参考にしてください。

データベースエラー

Arduino Unoとロジックアナライザの接続

Arduino UnoはUSBで取得したデータを、USB-シリアル変換を行うチップでUARTの信号に変換して、マイコン(ATmega328P)の2番ピン・3番ピンへ入力します。

このマイコンの2番ピン・3番ピンはArduino UnoのD0ピン・D1ピンにもつながっているので、ロジックアナライザのCH0・CH1をD0ピン・D1ピンへ接続することで、UARTの信号を横取りすることができます。

また、ロジックアナライザのGNDをArduino UnoのGNDピンへ接続します。

Arduinoとロジックアナライザ、パソコンを接続します。

UART

UARTはシリアル通信の1つです。
調歩同期式なのでお互いでルールを決めて、そのルールに従って信号をやり取りします。

OptibootとArduino IDEのUARTは以下の設定で通信を行うので、pluseviewに設定をします。

UART 通信設定

ボーレート:115200
データビット:8
パリティ:none
ストップビット:1
ビットオーダー:LSBファースト

通信データ

早速、OptibootとArduino IDEのUART通信のデータを見てみることにします。

今回は、Arduino IDEがスケッチ例として準備している、LEDを光らせるプログラム「Blink」をArduino Unoに転送する通信データを確認します。

Arduinoのスケッチ例に用意されている「Blink」

通信の確立

まず、Arduino IDEから「0x30」「0x20」のデータが送られています。
これに対して、Optibootは「0x14」「0x10」を返しています。

/* Forever loop */
  for (;;) {
    /* get character from UART */
    ch = getch();

Optibootはループの初めにUARTから1Byteのコマンドを読み取り、そのコマンドによって処理を分けています。
今回は「0x30」が送られていますがどこのif文にもコマンドが定義されていないので、以下のelse処理に落ちます。

    else {
      // This covers the response to commands like STK_ENTER_PROGMODE
      verifySpace();
    }
    putch(STK_OK);
void verifySpace() {
  if (getch() != CRC_EOP) {
    watchdogConfig(WATCHDOG_16MS);    // shorten WD timeout
    while (1)			      // and busy-loop so that WD causes
      ;				      //  a reset and app start.
  }
  putch(STK_INSYNC);
}

verifySpace()の関数ではさらに送られてきた信号を1Byte読み取り、「CRC_EOP(0x20)」であれば「STK_INSYNC(0x14)」を送り、else文を抜けて続けて「STK_OK(0x10)」を送ります。

接続が確立したことを確認しあっているように見えます。
どうやら正しくデータが取れていそうです。

バージョンの確認

次に、Arduino IDEから「0x41」コマンドが2回立て続けに送られています。
1つ目は「0x41」「0x81」「0x20」2つ目は「0x41」「0x82」「0x20」のデータが送られています。

「0x41」コマンドは、Optiboot上で「STK_GET_PARAMETER」を意味しており、その次に送られてくるデータが「0x82」であればマイナーバージョンを、「0x81」であればメジャーバージョンを返す仕様のようです。

    if(ch == STK_GET_PARAMETER) {
      unsigned char which = getch();
      verifySpace();
      if (which == 0x82) {
	/*
	 * Send optiboot version as "minor SW version"
	 */
	putch(OPTIBOOT_MINVER);
      } else if (which == 0x81) {
	  putch(OPTIBOOT_MAJVER);
      } else {
	/*
	 * GET PARAMETER returns a generic 0x03 reply for
         * other parameters - enough to keep Avrdude happy
	 */
	putch(0x03);
      }

確かに、今使っているOptibootのバージョンは4.4と定義されているので、正しいデータが返されているようです。

#define OPTIBOOT_MAJVER 4
#define OPTIBOOT_MINVER 4

デバイスの識別

上記の後に何個かコマンドが流れましたが、Optibootでは無視されるコマンドだったのでここでは割愛します。
次に意味のあるコマンドとしてArduino IDEから「0x75」コマンドが送られています。

「0x75」コマンドは、Optiboot上で「STK_READ_SIGN」を意味しており、device signatureを取得するためのコマンドのようです。

    else if(ch == STK_READ_SIGN) {
      // READ SIGN - return what Avrdude wants to hear
      verifySpace();
      putch(SIGNATURE_0);
      putch(SIGNATURE_1);
      putch(SIGNATURE_2);
    }

全てのAVRマイコンにはデバイスを識別するための3Byteのコード(device signature)があり、これを取得してマイコンがATmega328Pであることを確認しているようです。

データシートによると、ATmega328Pのdevice signatureは「0x1E」「0x95」「0x0F」です。
正しくデータが取得できています。

ロードアドレスの指定

次に、Arduino IDEから「0x55」コマンドが送られています。

「0x55」コマンドは、Optiboot上で「STK_LOAD_ADDRESS」を意味しており、ユーザーのプログラムを書き込むメモリの位置を指定します。

    else if(ch == STK_LOAD_ADDRESS) {
      // LOAD ADDRESS
      uint16_t newAddress;
      newAddress = getch();
      newAddress = (newAddress & 0xff) | (getch() << 8);
#ifdef RAMPZ
      // Transfer top bit to RAMPZ
      RAMPZ = (newAddress & 0x8000) ? 1 : 0;
#endif
      newAddress += newAddress; // Convert from word address to byte address
      address = newAddress;
      verifySpace();
    }

Arduino IDEはアドレス0x0000番地にプログラムを書き込むよう指示しています。Optibootはこのアドレスを変数addressに保持して、そこへプログラムを書き込みます。

マイコンは通常、電源が入るとプログラムカウンタレジスタが0x0000番地から処理を始めると思っていましたが、ATmega328Pは違うようです。

データシートによると、アプリケーションとブートプログラムの書き込み位置は以下の関係にあるようです。

ブートFlashセクションの開始アドレスはレジスタで設定ができるようです。

「BOOTSZ1」「BOOTSZ2」レジスタヒューズビットと呼ばれる設定項目のなかにあり、ヒューズビットはブートローダを書き込むときに一緒に書き込まれているようです。
「boards.txt」内で設定内容を見ることができます。

今回は「BOOTSZ1:1」「BOOTSZ2:1」なので、ブートローダーは「0x3F00」番地に書き込むことになるようです。
ATmega328Pは全ての命令が2Byteで構成されるので、正しくは「0x7E00」番地に書き込まれています。

ややこしい

試しに、Optibootのビルド後のバイナリファイル「optiboot_atmega328.hex」を覗いてみましょう。
私の環境ではArduino IDEをインストールした時に、以下に保存されています。

C:\Users\ユーザー名\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.5\bootloaders\optiboot

以下のコードはhexファイルの初めの3行を抽出したものです。

:107E0000112484B714BE81FFF0D085E080938100F7
:107E100082E08093C00088E18093C10086E0809377
:107E2000C20080E18093C4008EE0C9D0259A86E02C

hexファイルは黄色で下線を引いたところが、データを書き込むアドレス位置を指定しています。
「0x7E00」からデータを書き始めていることがわかります。

プログラムの書き込み

いよいよ最後のプログラム書き込みです。
Arduino IDEから「0x64」コマンドが送られています。

「0x64」コマンドは、Optiboot上で「STK_PROG_PAGE」を意味しており、これからプログラムデータが書き込まれることを意味しています。

以下のコードはコマンド「STK_PROG_PAGE」を受けた時の処理の初め数行を抜き出しています。

    else if(ch == STK_PROG_PAGE) {
      // PROGRAM PAGE - we support flash programming only, not EEPROM
      uint8_t *bufPtr;
      uint16_t addrPtr;

      getch();			/* getlen() */
      length = getch();
      getch();

3Byteのデータの長さなどを読みだした後、プログラムの転送が始まります。

以下がスケッチ例「Blink」のhexファイルの初め3行を抜き出したものです。

:100000000C945C000C946E000C946E000C946E00CA
:100010000C946E000C946E000C946E000C946E00A8
:100020000C946E000C946E000C946E000C946E0098

青色の下線を引いたところが、実際のプログラムのデータです。
このhexファイルは「0x0000」アドレスから順番に青色の下線データを書き込むよう指示したファイルです。
正しくプログラムが転送されているのがわかります。

まとめ

Arduino Unoに書き込まれているブートローダ「Optiboot」と、Arduino IDEの通信内容を確認しました。

マイコンのアドレスの概念や、hexファイル、データシートの解読など、組み込み技術者として必要な知識が多く出てきて、改めて勉強になりました。

また、通信データを簡単に見れるのはとても楽しかったです!

通信データを見るだけで賢くなった気がする

Arduino
スポンサーリンク
tsubablog

メーカーで組み込みプログラマーとして勤務しているサラリーマンです。
プログラミングの楽しさについて発信しています。

業務で扱っている言語はC、C++です。

tsubablogをフォローする
つばブログ

コメント

タイトルとURLをコピーしました