2013年11月2日土曜日

PIC18F用のHIDブートローダの調査

PIC18F14K50は割り切って使うにはなかなか便利なデバイスなのですが、MPLAB-X(Linux版)とPICkit3の組み合わせで毎回書き換えて使うには、書き込む度に
  1. PIC18F14K50側のUSBケーブルを抜いて、
  2. PICkit3を接続し、
  3. ダウンロードをして、
  4. PICkit3側のUSBケーブルを外してから
  5. PICkit3を外して、
  6. PIC18F14K50側のUSBケーブルを挿す
  7. (次回書き込みに備えて)PICkit3側のUSBケーブルを挿す
というなかなか面倒くさい手順が必要です。理由は、
  1. ICSPの端子とUSBのD+/D-端子を共有している
  2. MPLAB-XのPICkit3に対する制御がイマイチ
    (MPLAB-Xからターゲットへの電源が供給されっぱなしなので、一旦PICkit3を外して電源供給を止める必要がある)
というためです。
そこで調べていたところ、HIDプロトコルによるブートローダがMicrochipから提供されていることがわかりました。これを使うと、
  • ブートローダを書き込んであればPICkit3なしでターゲットプログラムを書き換えることができる
  • PowerON時のポートの状態に応じてブートローダを起動させたり、ターゲットプログラムを起動させたりするのを切り替えることができる
  • ターゲットプログラムのダウンロード用のLinux用のホストプログラムはブートローダに同梱されている(Qtベースっぽい)
  • ブートローダは0番地付近の約4KBを占有する模様。割り込みベクタは4KB上のアドレス(1000H~)に移動させる細工がされている。
  • アプリケーションプログラムも当然1000Hあたりからに置かれるように細工しなければならない
ということがわかりました。
どこから手をつけるか考えていたところ、CDCクラスのBasic Demoのlkrファイルが「rm18f4550 - HID Bootload.lkr」となっていていかにも対応していそうなファイル名です。中身も、
LIBPATH .

FILES c018i.o
FILES clib.lib
FILES p18F4550.lib

CODEPAGE   NAME=bootloader START=0x0              END=0xFFF          PROTECTED
CODEPAGE   NAME=vectors    START=0x1000           END=0x1029         PROTECTED
CODEPAGE   NAME=page       START=0x102A            END=0x7FFF
CODEPAGE   NAME=idlocs     START=0x200000          END=0x200007       PROTECTED
CODEPAGE   NAME=config     START=0x300000          END=0x30000D       PROTECTED
CODEPAGE   NAME=devid      START=0x3FFFFE          END=0x3FFFFF       PROTECTED
CODEPAGE   NAME=eedata     START=0xF00000          END=0xF000FF       PROTECTED
102A番地から7FFF番地までに配置されそうな感じです。
そこで、手始めにPIC18F4550(AE-18F4550ボード)でブートローダを動かしてみることにしました。

途中までは順風満帆かと思われたのですが・・・・

ブートローダのプロジェクトは microchip_solutions_v2013-06-15/USB/Device - Bootloaders/HID/Firmware - PIC18 Non-J/MPLAB.X に収められています。これを開いて、ProjectのPropertyを開いてPIC18F4550の設定をActiveにすることと、PICkit3から電源供給するように設定変更します。
ブートローダ用のSWとLEDは io_cfg.h を見ると68行目付近から記述があり、LEDはPORT D0、SWはPORT B4に接続する前提になっていることがわかります。そこで、ブレッドボード上で適当にLEDとSW(プルアップ抵抗をつけておきます)を配線します。
ビルド自体はこれで難なく通り、書き込みもできました。(コンパイラは例によってMPLABC18です)
18F4550のPORT B4をGNDに落とした状態でUSBポートをホストPCと接続するとLEDがチカチカ点滅します。そして、「microchip_solutions_v2013-06-15/USB/Device - Bootloaders/HID/Firmware - PIC18 Non-J/HIDBootloader(Linux)」を起動すると「Device Attatched」という表示が出て、無事に認識されました。
次にアプリケーションですが、先に調査した通り、CDC Basic Demoで試してみます。
microchip_solutions_v2013-06-15/USB/Device - CDC - Basic Demo/Firmware/MPLAB.X に格納されたプロジェクトを開いて、ProjectのPropertyを開いてPIC18F4550の設定をActiveにしてビルドします。
ビルドして出来上がったHEXファイルはmicrochip_solutions_v2013-06-15/USB/Device - CDC - Basic Demo/Firmware/MPLAB.X/dist/PICDEM_FSUSB/productionの下に格納されています。これをHIDBootloader(Linux)で File → Import Firmaware Image で読み込ませた後、Program → Erase/Program/Verify Device で書き込みます。書き込み後、PORT B4をHレベルになるようにした後、Program → Reset Device でリセットを掛けます。・・・が、再びBootloaderに認識されてしまいました。
うーん、HID Bootloader上のアプリケーション・・・動きません。

ここからが苦難の道のり・・・(涙)

ブートローダのファームウェア側main.cを見ると、421行目近辺に
DoFlashSignatureCheck:    
    //Check if the application region flash signature is valid
    #ifdef ENABLE_FLASH_SIGNATURE_VERIFICATION
        if(*(rom unsigned int*)APP_SIGNATURE_ADDRESS == APP_SIGNATURE_VALUE)
        {
            //The flash signature was valid, implying the previous 
            //erase/program/verify operation was a success.

            //Go ahead and jump out of bootloader mode into the application run mode
      _asm goto REMAPPED_APPLICATION_RESET_VECTOR _endasm 
        }    
        //else the application image is missing or corrupt.  In this case, we
        //need to stay in the bootloader mode, so the user has the ability to 
        //try (again) to re-program a valid application image into the device.

     //We should stay in bootloader mode
        _asm goto BootMain _endasm
    #else

        //Ideally we shouldn't get here.  It is not recommended for the user to 
        //disable both the I/O pin check and flash signature checking 
        //simultaneously.  Doing so would make the application non-recoverable
        //in the event of a failed bootload attempt (ex: due to power loss).
        _asm goto REMAPPED_APPLICATION_RESET_VECTOR _endasm 

    #endif
というコードがあって、APP_SIGNATURE_ADDRESS番地にAPP_SIGNATURE_VALUEが書かれていないと、BootMain(ブートローダのMain)に飛んでいくことがわかります。具体的には、0x1006番地が0x600Dでなければなりません。
そこで、MPLAB IPEでROMの内容を吸い出して確認してみたところ、見事に0xFFFFになっていました・・・。ここをMPLAB IPEで0x600Dに変更して書き戻してみたところ、ブートローダプログラムでは「Detached」になり、さらにGtkTermで /dev/ttyACM0 にアクセスしてCDC Basic Demo(入力した文字が1文字ずれてエコーバックされる)の機能を確認することができました。
つまり、SIGNATURE VALUEが書かれていないことが原因であることがわかりました。

では、SIGNATURE VALUEは誰が書くのか?

ソースコードの「BootPIC18NonJ.c」の359行目からのProcessIO()を見ると、532行目付近に
   case SIGN_FLASH:
       SignFlash();
       BootState = IDLE;
       break;
というコードがあって、これはホスト(PC)からのコマンド解析部分のようです。ホストから「SIGN_FLASH」というコマンド(ちなみに値は0x09)が来ると、591行目付近のSignFlash()という関数が呼び出されて、該当領域を読み込んだ後、バッファ内のSIGNATUREの部分を書き換えて、書き込む処理を行うように見えます。
つまり、そもそもこの「SIGN_FLASH」というコマンドが来ていないのではないか、ということになります。

SIGN_FLASHが送られてくるコードになっているのか?

こんどはホスト側のソースコードが格納されている「microchip_solutions_v2013-06-15/USB/Device - Bootloaders/HID/software_cross_platform/Bootloader」の下を見てみました。
「SIGN_FLASH」を送出するっぽいコードは、Comm.cppの640行目付近にある、「Comm::SignFlash(void)」という関数のようです。送られていれば、それなりのデバッグメッセージを出すコードのようなのですが、残念ながらビルドできる環境がありません。(方法もわかりません)
この部分を呼び出すのはMainWindow.cppの555行目付近のようです。
    if(failureDetected == false)
    {
        //Successfully verified all regions without error.
        //If this is a v1.01 or later device, we now need to issue the SIGN_FLASH
        //command, and then re-verify the first erase page worth of flash memory
        //(but with the exclusion of the signature WORD address from the verify,
        //since the bootloader firmware will have changed it to the new/magic
        //value (probably 0x600D, or "good" in leet speak).
        if(deviceFirmwareIsAtLeast101 == true)
        {
            comm->SignFlash();
            //Now re-verify the first erase page of flash memory.
            if(device->family == Device::PIC18)
            {
書き込み後にVerifyをする関数の最後の方で、Verifyに問題がなく、なおかつdeviceFirmwareIsAtLeast101がセットされていれば、呼び出されるようです。で、この値はブートローダファームウェアのバージョンが1.01以上であればセットされるようです。
実際にセットしている箇所は、MainWindow.cppの1150行目付近、GetQuery()という関数・・・GetQueryコマンドに対する応答パケットを解析する部分の中で、
    for(int i = 0; i < MAX_DATA_REGIONS; i++)
    {
        if(bootInfo.memoryRegions[i].type == END_OF_TYPES_LIST)
        {
            //Before we quit, check the special versionFlag byte,
            //to see if the bootloader firmware is at least version 1.01.
            //If it is, then it will support the extended query command.
            //If the device is based on v1.00 bootloader firmware, it will have
            //loaded the versionFlag location with 0x00, which was a pad byte.
            if(bootInfo.versionFlag == BOOTLOADER_V1_01_OR_NEWER_FLAG)
            {
                deviceFirmwareIsAtLeast101 = true;
                qDebug("Device bootloader firmware is v1.01 or newer and supports Extended Query.");
                //Now fetch the extended query information packet from the USB firmware.
                comm->ReadExtendedQueryInfo(&extendedBootInfo);
                qDebug("Device bootloader firmware version is: " + extendedBootInfo.PIC18.bootloaderVersion);
            }
            else
            {
                deviceFirmwareIsAtLeast101 = false;
            }
            break;
        }
bootInfo構造体のversionFlagがBOOTLOADER_V1_01_OR_NEWER_FLAG(これはDeviceData.hの中で0xA5として定義されています)となっているかで判別しているようです。そしてこの構造体は、Comm.hというヘッダファイルで、
    #pragma pack(1)
    struct MemoryRegion
    {
        unsigned char type;
        unsigned long int address;
        unsigned long int size;
    };

    struct BootInfo
    {
        unsigned char command;
        unsigned char bytesPerPacket;
        unsigned char deviceFamily;
        MemoryRegion memoryRegions[MAX_DATA_REGIONS];
        unsigned char versionFlag;
        unsigned char pad[7];
    };
として宣言されています。BootInfoは受信したパケットそのもののようなので、WiresharkでUSBのパケットキャプチャをしてみました。
Screenshot-USB bus number 1   [Wireshark 1.6.7 ]
0040Hからが受信したデータの部分で、構造体の構造にそって読んでいくと、赤丸の部分がversionFlagで0xa5になっていることがわかります。つまり、ブートローダのファームウェア側からは正しく情報が上がっているようです。

SIGN_FLASHは送出されているのか?

書き込みの処理の部分全体をUSBパケットキャプチャしてみたところ、最後のパケットは、
Screenshot-USB bus number 1   [Wireshark 1.6.7 ]-1
となっていて、最後のコマンドは0x07で「GET_DATA」でした。遡っていってもずーっと0x07で、その前が0x06(PROGRAM_COMPLETE)が2つ、その前が0x05(PROGRAM_DEVICE)がずっと続きます。先頭から書いていくと、
  • 0x04(ERASE_DEVICE)
  • 0x02(QUERY_DEVICE)
    これに対する応答もきちんとVer1.01以上である0xa5が含まれていました。
  • 0x05(PROGRAM_DEVICE)がたくさん
  • 0x06(PROGRAM_COMPLETE)が2つ
  • 0x07(GET_DEVICE)はたくさん
という感じで、0x09(SIGN_FLASH)は含まれていません。・・・・そりゃあ、ダウンロードしたプログラムが動くわけがありません。

で、ここで気づいたことが・・・・

よくみると、ブートローダアプリケーションに表示される文字列は、ブートローダアプリのソースコードではコメントアウトされているものになっています。つまり、ソースコードはついているけど、それをコンパイルしたアプリケーションがついているわけではないようです。・・・・おいおい。
ちなみに、タイムスタンプをみると、ソースコードのタイムスタンプは2013年6月18日2時37分18秒で、添付されているアプリケーションのタイムスタンプは2013年6月18日2時36分36秒です。・・・・この微妙な逆転、一体何なんでしょうね・・・・。

最終的な対策

Qt5をコンパイルする環境はないので、 処置するとしたらブートローダのファームウェア側で処置するしかありません。
簡単に対策するなら、SIGNATUREのチェックを外す、ということになります。
main.c の317行め付近に、
//Never comment this out.  If you do, you won't be able to recover if the user
//unplugs the USB cable (or power is lost) during an erase/program sequence, 
//unless you rely on the I/O pin check entry method.  The only reason ever to
//comment this out, is if you are trying to use this bootloader firmware with
//an old bootloader PC application that does not have knowledge of the v1.01
//and newer command set (with QUERY_EXTENDED_INFO and SIGN_FLASH commands).
//However, a better solution in such a case, is to upgrade to use a newer PC
//application to do the bootloading.
#define ENABLE_FLASH_SIGNATURE_VERIFICATION
というコードがあって、コメントには、「ここはコメントするな!」と書いてありますが、そんなことを言っても仕方ありませんので、ここをコメントアウトしてSIGNATUREチェックをやめさせます。コメントの内容によると、書き換え中に電源を切ったりUSBケーブルを抜いたりすると、復活できなくなる・・・・ということみたいです。
ここをコメントしてビルドしたブートローダを書き込んで、PORT B4をGNDに落としてブートローダを起動、ブートローダアプリケーションでCDC Basic Demoを書き込んでみました。その後、PORT B4をHにしてブートローダアプリの「Reset Device」をクリックしたところ、無事にCDC Basic Demoが動作しました。
疲れてしまったので、PIC18F14K50でのトライはまた今度ということで。

おまけ

HIDブートローダを使う場合の各PICでのリンカスクリプトは「microchip_solutions_v2013-06-15/USB/Device - Bootloaders/HID/Firmware - PIC18 Non-J/Linker files for applications」にあるようです。

0 件のコメント: