STM32でUSB DFUブートローダを実装

STM32を使い、自前でファームウェア更新機能を搭載する方法を解説します。
GitHubでソースコードを公開しています。

1.はじめに

STM32にはファームウェア更新機能が内蔵された機種が多く、STではこの機能を「ブートローダ」と呼んでいる。(→AN2606)。

内蔵ブートローダは様々なペリフェラルに対応しており、特にDFU(Device Firmware Upgrade)プロトコルを使えば、USB経由で手軽にファームウェアを更新できる(→「マイクロコントローラのDFUモードを使用して現場でファームウェアを更新」, Digikey)。

ただし、内蔵ブートローダでDFUを使う場合、以下の問題がある。

  • 問題1: USBデバイス名が「STM32 BOOTLOADER」または「STM Device in DFU Mode」に固定され、さらにUSBのVID/PIDが固定のため、ホスト側から機器の判別が困難。
  • 問題2: 内蔵ブートローダ実行中は、LEDの点滅といった任意の制御ができず、自由度が低い。
  • 問題3: Windows環境下では、ユーザ側でドライバのインストール or Zadigによるドライバの差し替えが必要で、手間を要する。

本稿では、以上の問題に対する解決策を示す。問題1・問題2に対しては、DFUブートローダを自作して内蔵ブートローダの代わりに使用する。問題3に対しては、Microsoft OS Descriptorに対応させることで、WinUSBドライバを自動インストールさせる。

なお、対象はSTM32H723ZGT6搭載のNUCLEOボード(NUCLEO-H723ZG)とし、STM32CubeIDEやCubeMXのコード生成機能を用いて簡単に開発する。

2.開発環境

3.構成

ブートローダとユーザプログラム

ファームウェアの更新中に電源断などのトラブルが発生すると、プログラムが破壊されて動作不能となるリスクがある(いわゆる文鎮化)。そのため、更新に失敗した場合でもリトライできる仕組みが必要となる。

STM32内蔵のブートローダは書換不能のSystem memory内にあり、プログラムが破壊されても内蔵ブートローダから起動してファームウェアの更新が可能である。

DFUブートローダを自作する場合も同様に、文鎮化するリスクを低くすべきと考える。

そこでMCUのファームウェアを、「DFUブートローダ」領域と「ユーザプログラム」領域に分割し、ファームウェアの更新時にはユーザプログラム領域を書き換える方式とする。また、DFUブートローダ領域はライトプロテクトする。さらに、DFUブートローダは電源投入直後に起動させ、(スイッチの状態を読む等して)ユーザプログラムを呼び出す形にする。

※この方式だと、DFUブートローダ自体を書き換える場合は、ST-LINK等のデバッガを使うか、DFUブートローダを書き換えるソフトをユーザプログラム領域に書き込む必要がある。

Flash領域

DFUブートローダ領域はセクタ単位にする必要がある。STM32H723ZGのFlash領域は0x0800 0000から始まり、128kBのセクタ8つ(Sector 0 – Sector 7)で構成される。Flashの消去はセクタ単位でのみ可能である。そのため、DFUブートローダとユーザプログラムが同じセクタに存在すると、ユーザプログラムの書き換え時にブートローダも消去されてしまう。

また、MCUリセット後にブートローダを起動させるためには、DFUブートローダ領域をFlashの最初(0x0800 0000-)に配置する必要がある(※)。

以上より、図のようにSector 0(0x0800 0000-0x0801 FFFF)をDFUブートローダ領域、Sector 1-7(0x0802 0000-)をユーザプログラム領域とする。

RM0468 Table 9. Boot modesによれば、オプションバイトのBOOT_ADDxを設定すれば、どこからでも起動可能。ただし初期状態では0x0800 0000から起動する。理由:NUCLEOの初期状態ではBOOT0ピン=LでありBOOT_ADD0が常に参照され、かつBOOT_ADD0の初期値が0x0800 0000である。

SRAM領域

SRAM領域は、DFUブートローダとユーザプログラムで共用とする。

4.ハードウェアの接続

NUCLEO-H723ZGを購入後、特に改造箇所が無ければそのまま使用できる。内蔵ブートローダが起動しないように、BOOT0ピンはGNDに接続されていることを確認しておく(→回路図参照)。(BOOT0ピンの役割についてはRM0468 2.6 Boot configuration参照)

開発時には図のように、左右のmicroBコネクタをPCに接続する。向かって左側のコネクタは電源供給&デバッガ用、右側のコネクタはMCUとの通信用である。

5.STEP1: シンプルなブートローダの作成

まず骨組みとして、ユーザプログラムを起動させるだけのブートローダを作成する。ブートローダはスイッチ(青いUSERスイッチ)の状態を読み、スイッチがOFFであればユーザプログラムを起動する。

CubeIDEによる設定とコード生成

STM32CubeIDEで、NUCLEO-H723ZG用のプロジェクトを2つ作成する。ブートローダ用をBTL_BOOT、ユーザプログラム用をBTL_APPとする。

ペリフェラルの設定は、NUCLEO-H723ZGボードのデフォルトにする。Board SelectorでNUCLEO-H723ZGを選ぶと、「Initialize all peripherals with their default Mode ?」のダイアログが出るので、Yesを選ぶとコードが生成される。

次に、生成されたBTL_xxx.iocを開いて編集する。BTL_BOOT.iocではUSB_OTG_HS-Internal FS Phy を”Device_Only”に、USB_DEVICEを”Download Firmware Update Class (DFU)”にする。ETH, USART3はDisableにする。BTL_APP.iocでは、ETH, USART3, USB_OTG_HS を”Disable”にする。詳細はGitHubにあるサンプルを参照。編集後、各々のプロジェクトでコード生成しておく。

さらに、BTL_BOOTのデバッグを開始すると、BTL_APPも同時にFlashへダウンロードするように設定する。手順は次の通り。

  1. あらかじめProject Explorer上でBTL_BOOTとBTL_APPをダブルクリックし、開いておく
  2. Project ExplorerからBTL_BOOTを右クリック->Degub As -> Debug Configurationsをクリック
  3. 「STM32 C/C++~」を右クリック->New Configurationをクリック
  4. 「BTL_BOOT Debug」という項目ができるので選択し、StartupタブのAddボタンを押す
  5. Add/Edit画面が出るので、図のように設定しOKボタンを押す
  6. Debug Configurations画面に戻るので、右下のApplyを押してCloseを押す
  7. プロジェクトBTL_APPに対しても、2.と3.の操作をし、BTL_APP Debugを作成する

これでBTL_BOOTプロジェクト内でF11キーを押すと、BTL_BOOTとBTL_APPの両方がビルドされ、Flashにダウンロード後デバッグ開始となる。

リンカスクリプトの編集

リンカスクリプトは、各プロジェクト内のSTM32H723ZGTX_FLASH.ldであり、次のように編集した。

  • メモリマップに従い、MEMORYリージョン内のFLASH(1024kB)を、FLASH_BOOT(128kB)とFLASH_APP(896kB)に分割した。RAMに関しては何も変更していない。
  • SECTIONSリージョン内のFLASHを、BTL_BOOTプロジェクトではFLASH_BOOTに、BTL_APPプロジェクトではFLASH_APPに、それぞれ置換した。
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200 ;      /* required amount of heap  */
_Min_Stack_Size = 0x400 ; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
  ITCMRAM     (xrw)   : ORIGIN = 0x00000000,   LENGTH = 64K
  DTCMRAM     (xrw)   : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH_BOOT  (rx)    : ORIGIN = 0x08000000,   LENGTH = 128K
  FLASH_APP   (rx)    : ORIGIN = 0x08020000,   LENGTH = 896K
  RAM_D1      (xrw)   : ORIGIN = 0x24000000,   LENGTH = 320K
  RAM_D2      (xrw)   : ORIGIN = 0x30000000,   LENGTH = 32K
  RAM_D3      (xrw)   : ORIGIN = 0x38000000,   LENGTH = 16K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH_BOOT

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH_BOOT

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH_BOOT

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH_BOOT
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH_BOOT

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH_BOOT

  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH_BOOT

  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH_BOOT

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM_D1 AT> FLASH_BOOT

  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM_D1

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM_D1

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}
/*BTL_APP/STM32H723ZGTX_FLASH.ldより抜粋 */
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200 ;      /* required amount of heap  */
_Min_Stack_Size = 0x400 ; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
  ITCMRAM     (xrw)   : ORIGIN = 0x00000000,   LENGTH = 64K
  DTCMRAM     (xrw)   : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH_BOOT  (rx)    : ORIGIN = 0x08000000,   LENGTH = 128K
  FLASH_APP   (rx)    : ORIGIN = 0x08020000,   LENGTH = 896K
  RAM_D1      (xrw)   : ORIGIN = 0x24000000,   LENGTH = 320K
  RAM_D2      (xrw)   : ORIGIN = 0x30000000,   LENGTH = 32K
  RAM_D3      (xrw)   : ORIGIN = 0x38000000,   LENGTH = 16K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH_APP

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH_APP

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH_APP

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH_APP
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH_APP

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH_APP

  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH_APP

  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH_APP

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM_D1 AT> FLASH_APP

  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM_D1

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM_D1

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

ブートローダ(BTL_BOOTプロジェクト)の実装

まず、mainループ内を実装する。スイッチ(青いUSERスイッチ)を押さずに電源投入すると、ユーザプログラムにジャンプさせる。スイッチを押しながら電源投入すれば、0.5s周期でLD1(緑LED)を点滅させる。

ちなみに、/* USER CODE BEGIN X*//* USER CODE END X*/で囲まれた箇所にコードを書くと、CubeMXでコードを再生成してもコードが消えない。

int main(void)
{
 //(~中略~)
  /* USER CODE BEGIN 2 */
  if(	GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) )
  {
	  //ユーザプログラムにジャンプ(後述)
	  jumpUserApp();
  }

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  //ファームウェア更新機能が有効であることを示す
	  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
	  HAL_Delay(250);
  }
  /* USER CODE END 3 */
}

次に、ユーザプログラムにジャンプする関数を実装する。Hands-on: Debug for BOOT+APPの”Into USER CODE Block 4″が参考になる(というかほぼ丸写し)

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

#define FLASH_APP_ADDR (0x08020000)

typedef void (*pFunction)(void);

volatile uint32_t jumpAddress;
volatile pFunction jumpToApplication;

void jumpUserApp()
{
	//ジャンプ先であるReset_Handlerのアドレスは、基準となるアドレスから4バイト後(→<a href="https://www.stmcu.jp/design/document/programming_manual/51584/">PM0253</a> 2.4.4 Vector table)
	jumpAddress = *(uint32_t *) (FLASH_APP_ADDR + 4);
	jumpToApplication = (pFunction) jumpAddress;

	//main内でHAL_Init()→SystemClock_Config()の順に初期化関数を呼んでいるため、逆順にDeInitしておく。
	//各割り込みが有効化されていれば、ここで無効化も実施しておく。
	HAL_RCC_DeInit();
	HAL_DeInit();

	//スタックポインタにFLASH_APP_ADDRの値を設定
	__set_MSP(*(uint32_t*)FLASH_APP_ADDR);
	jumpToApplication();
}
/* USER CODE END 0 */

このコードは、MCUリセットの動作を模倣している。ユーザプログラムはMCUリセット後に起動することを想定しているので、ユーザプログラムへジャンプする直前に、MCUリセットの動作を模倣することが望ましい。

STM32ではMCUリセット後、ベクタテーブル先頭(0x0800 0000)の値をスタックポインタレジスタ(R13)にロードする(→PM0253 2.1.3 Core registersのStack pointer項)(※)。

ソフトウェア上でユーザプログラムにジャンプした際、リセット後の動作を模倣するためには、R13に対してユーザプログラム領域のベクタテーブル先頭位置の値(=0x08020000の値)をロードする必要がある。この操作が__set_MSP(*(uint32_t*)FLASH_APP_ADDR);である。

※VTORレジスタ(後述)の初期値に依存するようだ。また、STM32H7の場合、VTORレジスタの初期値はBOOT_ADDXに依存するようだ(Twitterのリプライで@Tekitounix氏に教えて頂いた)。

ユーザプログラム(BTL_APPプロジェクト)の実装

ユーザプログラムの起動状態を判断できるよう、2s周期でLD2(黄LED)を点滅させる。

int main(void)
{
 //(~中略~)
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  //ユーザプログラムが起動していることを表す
	  HAL_GPIO_TogglePin(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin);
	  HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

次に、ユーザプログラム内のベクタテーブルの参照先を指定する。

ベクタテーブルには、スタックポインタの初期値や各種割り込み命令のアドレスが保存されており(→PM0253 2.4.4 Vector table)、通常は0x0800 0000~から開始する。各種割り込みが発生するとベクタテーブルが参照され、ジャンプ先が決定される。

しかし図のように、ユーザプログラム内のベクタテーブルは0x0802 0000から開始するため、ベクタテーブルの開始位置が通常の0x0800 0000と異なることをMCUに教える必要がある。

これをVTORレジスタ(→PM0253 4.3.4 Vector table offset register)で設定する。出力したコードでは、system_stm32h7xx.c内にVTORの設定箇所がある。

//(~中略~)
/*!< Uncomment the following line if you need to relocate the vector table
     anywhere in FLASH BANK1 or AXI SRAM, else the vector table is kept at the automatic
     remap of boot address selected */
//下の行がコメントアウトされているので、コメントを外す。
#define USER_VECT_TAB_ADDRESS
//(~中略~)
//以下が設定箇所である。今回はベースアドレスを0x0800 0000(FLASH_BANK1_BASE)、オフセット値を0x0002 0000とし、
//SCB->VTORに0x0802 0000がセットされるようにした。
#define VECT_TAB_BASE_ADDRESS   FLASH_BANK1_BASE  /*!< Vector Table base address field.
                                                       This value must be a multiple of 0x300. */
#define VECT_TAB_OFFSET         0x00020000U       /*!< Vector Table base offset field.
                                                       This value must be a multiple of 0x300. */
//(~中略~)
/以下でVTORレジスタの値を設定している。
#if defined(USER_VECT_TAB_ADDRESS)
  SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal D1 AXI-RAM or in Internal FLASH */
#endif /* USER_VECT_TAB_ADDRESS */
//(~中略~)

CubeH7のバージョンによってsystem_stm32h7xx.cの構造が一部異なるので注意(上の例はCubeH7 Ver1.10.0)。

結局は、各種割り込みが生じる前に、SCB-VTORに対してユーザプログラムの先頭アドレスである0x0802 0000を設定すれば良い。

動作確認

CubeIDEでデバッグを開始し、動作確認を行う。

CubeIDEでBTL_BOOT, BTL_APP両方のプロジェクトを開き、BTL_BOOTプロジェクトをアクティブにした状態(BTL_BOOT内のmain.c等を開いて画面内をクリックした状態)でF11キーを押すと、BTL_BOOTとBTL_APPがダウンロードされ、デバッグが開始する。

RESETを押しMCUをリセットすれば黄色のLD2が2s周期で点滅し(:ユーザプログラム起動)、USERを押しながらRESETを押せば緑色のLD1が0.5s周期で点滅(:ファーム更新モード)するはずである。

6.STEP2: DFUブートローダの作成

STEP1」で作成した骨組みに対し、USB-DFU機能を追加する。以下、BTL_BOOTプロジェクトのみ変更する。

CubeIDEによるコード生成

BTL_BOOT.iocをCubeIDE or CubeMXで開き、USB_DEVICEを選択する。Class For HS IPから”Download Firmware Update Class (DFU)”を選択すると、以下の画面となる。

まず、Parameter Settingsを上のように設定する。USBD_DFU_XFER_SIZEを4096Bytes、USBD_DFU_APP_DEFAULT_ADDはユーザプログラムの開始位置0x08020000とする。USBD_DFU_MEDIA Interfaceは
@Internal Flash /0x08000000/01*0128Ka,01*0128Kg,01*0128Kg,01*0128Kg,01*0128Kg,01*0128Kg,01*0128Kg,01*0128Kgとする(※)。

※この表記については、UM0424 10.3.2 DFU mode descriptor setのDFU mode interface descriptor項に説明がある。簡単に説明すると、0x08000000からスタートし、128kBのread-onlyなセクタ(a)が1つ、128kBのRead/Erase/Write可能なセクタ(g)が1つ…といったように、セクタ0-7まで設定している。ちなみに、01*0128Kgを7個書く代わりに07*0128Kgにすると、後述するMEM_If_Erase_HS()がセクタ毎に呼ばれず、正しく消去できない(調査中)。


次に、Device Descriptorタブを開き、上のように設定する。MANUFACTURER_STRINGとPRODUCT_STRINGは、任意の文字列で良い。VID/PIDは0x1209/0x0003としておく(※)

※ここで使用するVID/PIDは、pid.codesで定められた実験用のIDである。サブライセンスなどを活用し、自分に割り当てられたVID/PIDを使用すること。

以上で設定は終了なので、コード生成をしておく。

DFU機能の実装

DFU機能のひな形は/USB_DEVICE/App/usbd_dfu_if.cに用意されており、ここにコードを追加する。

編集すべき関数は、MEM_If_Init_HS, MEM_If_DeInit_HS, MEM_If_Erase_HS, MEM_If_Write_HS, MEM_If_Read_HS, MEM_If_GetStatus_HS の6つである。STM32CubeL4サンプルに実装例がある。

MEM_If_Init_HS

ファームウェア更新モードに入った状態で、USBケーブルを接続すると呼ばれる関数。STM32のFlash領域は標準でロックされており、消去や書き込みをするためにはロックを解除する必要がある(→RM0468 4.5.1 FLASH configuration protection)。

uint16_t MEM_If_Init_HS(void)
{
  /* USER CODE BEGIN 6 */
	HAL_FLASH_Unlock();
	return (USBD_OK);
  /* USER CODE END 6 */
}

MEM_If_DeInit_HS

DFUプロトコルのDFU_DETACH(DFUモードを抜けること)に対応する関数。ファームウェア更新モードから抜ける前にFlashをロックする。

uint16_t MEM_If_DeInit_HS(void)
{
  /* USER CODE BEGIN 7 */
	HAL_FLASH_Lock();
	return (USBD_OK);
  /* USER CODE END 7 */
}

MEM_If_Erase_HS

アドレスAddに対応するセクタを消去する関数。

uint16_t MEM_If_Erase_HS(uint32_t Add)
{
  /* USER CODE BEGIN 8 */

	FLASH_EraseInitTypeDef erase;
	uint32_t error = 0;
	erase.TypeErase = FLASH_TYPEERASE_SECTORS;

	//アドレスから、消去するセクタ番号(0-7)を計算する関数。後述。
	erase.Sector = convertAddressToSector(Add);

	if(0xFF == erase.Sector){//Addが範囲外であれば0xFF(255)が返るのでエラー処理
		return (USBD_FAIL);
	}
	erase.Banks = FLASH_BANK_1;
	erase.NbSectors = 1;
	erase.VoltageRange = FLASH_VOLTAGE_RANGE_4;

	HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase, &error);

	if(HAL_OK != status || 0xFFFFFFFF != error){
		return (USBD_BUSY);
	}
	return (USBD_OK);
  /* USER CODE END 8 */
}

アドレスaddrに対し、該当するセクタ番号(0-7)を返す。addrが範囲外であれば、0xFF(255)を返す。

#define ADDR_SECTOR_START (0x08000000)
#define ADDR_SECTOR_SIZE  (0x00020000)
#define ADDR_SECTOR_END (0x080FFFFF)
uint8_t convertAddressToSector(uint32_t addr)
{
	uint8_t ret;

	if((addr > ADDR_SECTOR_END) || (addr < ADDR_SECTOR_START)){
		return 0xFF;//error magic number
	}

	ret = (addr - ADDR_SECTOR_START) / ADDR_SECTOR_SIZE;

	return ret;

}

MEM_If_Write_HS

アドレスdestに対し、srcの内容をLenバイトだけ書き込む関数。STM32H723のFlashは、256bit(Flash-Word)毎に書き込む仕様に注意(→RM0468 4.3.4 Flash memory architecture and usage)

uint16_t MEM_If_Write_HS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 9 */
	HAL_StatusTypeDef status;
	uint32_t dest_addr = (uint32_t)dest;

	//STM32H723では、256bit(32 * 8 bit)毎に書き込を行うため、
	//データを256bit(32バイト)毎にパッキングする必要がある。
	for(uint32_t i=0; i<Len; i+=32)
	{
		status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, dest_addr+i , (uint32_t)(&src[i]) );

		if(HAL_OK == status){
			for(uint32_t idx_check=0; idx_check<8; idx_check++){//正しく書き込まれたかチェック。
				if( *(uint32_t *)(src+i+idx_check) != *(uint32_t *)(dest_addr+i+idx_check) ){
					return USBD_EMEM;
				}
			}

		}else{
			return USBD_BUSY;
		}
	}
	return (USBD_OK);
  /* USER CODE END 9 */
}

MEM_If_Read_HS

アドレスsrcの内容を、長さLenだけdestに読み込む関数。

uint8_t *MEM_If_Read_HS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 10 */
	uint8_t *src_ptr = src;
	for(uint32_t i=0; i<Len; i++)
	{
		dest[i] = *src_ptr;
		src_ptr++;
	}

	return (uint8_t *)(dest);
  /* USER CODE END 10 */
}

MEM_If_GetStatus_HS

書き込みや消去に掛かる時間を、ホスト側にレポートする関数。セクタの消去時間FLASH_ERASE_TIMEと、上で設定したUSBD_DFU_XFER_SIZE(4096Byte)に対応する書き込み時間FLASH_PROGRAM_TIMEを設定する。単位はms。小さいとホスト側でタイムアウトとなり、大きいとホスト側でウェイトを掛けるため処理に時間がかかる。

//(中略)
/* Private define ------------------------------------------------------------*/
//時間を設定する。
#define FLASH_ERASE_TIME    (uint16_t)1000
#define FLASH_PROGRAM_TIME  (uint16_t)50
//(中略)
uint16_t MEM_If_GetStatus_HS(uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 11 */
	switch(Cmd)
	{
	case DFU_MEDIA_PROGRAM:
		buffer[1] = (uint8_t)FLASH_PROGRAM_TIME;
		buffer[2] = (uint8_t)(FLASH_PROGRAM_TIME << 8);
		buffer[3] = 0;
		break;

	case DFU_MEDIA_ERASE:
		buffer[1] = (uint8_t)FLASH_ERASE_TIME;
		buffer[2] = (uint8_t)(FLASH_ERASE_TIME << 8);
		buffer[3] = 0;
		break;

	default:
		break;
	}
	return  (USBD_OK);
  /* USER CODE END 11 */
}

以上で、ファームウェア更新が可能となる。

7.STEP3: Microsoft OS Descriptorへの対応

Windows環境下でDFUプロトコルを扱う場合、デバイスドライバに関する問題が生じる。

STM32内蔵ブートローダであればデバイスドライバが用意されており、自動インストール(Windows10以降)や、DfuSe付属のデバイスドライバを活用できる。

一方で、本稿で作成するDFUブートローダには、別途デバイスドライバが必要である。この用途ではWinUSBという汎用ドライバが良く使われる。デバイスマネージャやZadigというソフトウェアでWinUSBを適用できるが、面倒な作業を要求するため、ユーザフレンドリーとは言えない。

そこで機器をMicrosoft OS Descriptorに対応させると、Windows 8以降であればWinUSBドライバが自動でインストールされるようになる。これでユーザ側は、デバイスドライバのインストールやZadigの操作から解放される。

実装方針

Microsoft OS Descriptorの実装については、先人による詳しい解説がある→WinUSBドライバの自動インストール(東京に住むプログラマ 氏)。こちらの記事に従って実装する。

実装すべきDescriptorは以下の通りである。

  • OS string descriptor
  • OS feature descriptor(以下の2つがある)
    • Extended compat ID
    • Extended Properties
    • Genre(HIDデバイスのみ、今回は関係なし)

想定する挙動は以下の通り。

  • Windowsからデバイスに対し、OS string descriptor取得要求(GET_DESCRIPTOR)を送る
  • デバイスからWindowsに、OS string descriptorを送る
  • 送られてきたdescriptorをWindowsが検証し、有効であればExtended compat IDの取得要求(GET_MS_DESCRIPTOR)をデバイスに送る
  • デバイスからWindowsに、Extended compat IDを送る
  • Extended Propertiesの取得要求(GET_MS_DESCRIPTOR)をデバイスに送る
  • デバイスからWindowsに、Extended Properties IDを送る

OS string descriptor

まずはOS string descriptor取得要求(GET_DESCRIPTOR)に対する応答を実装する。

GET_DESCRIPTORは次のフィールドからなる。

フィールド サイズ
bmRequestType 1 0x80
bRequest 1 GET_DESCRIPTOR(0x06)
wValue 2 0x03EE
wIndex 2 0x0000
wLength 2 0x12

デバイスからの応答は、次の通り。

フィールド サイズ
bLength 1 0x12
bDescriptorType 1 0x03
qwSignature 14 ‘MSFT100’
bMS_VendorCode 1 0xBE(※)
bPad 1 0x12

※Windowsは、ここで送られたbMS_VendorCodeを、以降のGET_MS_DESCRIPTORで用いる。ここでは0xBEとしておいた(任意)。

これをコードに落とし込むと次の通り。/BTL_BOOT/Middlewares/ST/STM32_USB_Device_Library/Class/DFU/Src/usbd_dfu.cを編集する。

ただし、CubeMXでコードを再生成すると、このソースファイル(usbd_dfu.c)が上書きされるため、再生成の度に差し替える必要があることに注意。

//ここから追加
#define MS_VENDER_CODE (0xBE)
#define OS_STRING_SIZ (0x12)
uint8_t USBD_OS_STRING_DESC[OS_STRING_SIZ] =
{
		OS_STRING_SIZ,    //bLength
        0x03,    //bDescriptorType
        0x4D, 0x00, 0x53, 0x00, 0x46, 0x00, 0x54, 0x00, 0x31, 0x00, 0x30, 0x00, 0x30, 0x00,    //MSFT100
        MS_VENDER_CODE,
        0x00    //bPad
};
//ここまで追加
//(~中略~)
#if (USBD_SUPPORT_USER_STRING_DESC == 1U)
static uint8_t *USBD_DFU_GetUsrStringDesc(USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length)
{
  static uint8_t USBD_StrDesc[255];
  USBD_DFU_MediaTypeDef *DfuInterface = (USBD_DFU_MediaTypeDef *)pdev->pUserData[pdev->classId];

  /* Check if the requested string interface is supported */
  if (index <= (USBD_IDX_INTERFACE_STR + USBD_DFU_MAX_ITF_NUM))
  {
    USBD_GetString((uint8_t *)DfuInterface->pStrDesc, USBD_StrDesc, length);
    return USBD_StrDesc;
  }
//ここから追加
  else if(0xEE == index)
  {
      *length = 0x12;
      return USBD_OS_STRING_DESC;
  }
//ここまで追加
  else
  {
    /* Not supported Interface Descriptor index */
    length = 0U;
    return NULL;
  }
}
#endif /* USBD_SUPPORT_USER_STRING_DESC */

GET_DESCRIPTOR要求を受けると、USBD_DFU_GetUsrStringDesc()関数が呼ばれるので、この中でwValue(変数名はindexになる)をハンドリングし、OS string descriptorを返している。wValueの上位バイトはString Descriptorの場合0x03に固定なので、下位バイトのみ見れば良く、USBD_DFU_GetUsrStringDesc()も下位バイトのみ受け付けるようになっている。そのため、if(0xEE == index)~としている。

OS feature descriptor

次にWindows側は、OS feature descriptorの取得要求(GET_MS_DESCRIPTOR)を送る。
GET_MS_DESCRIPTORは次のフィールドからなる。

フィールド サイズ
bmRequestType 1 0xC0 or 0xC1
bRequest 1 0xBE(先ほどのbMS_VendorCode)
wValue 2 0x03EE
wIndex 2 0x0001(Genre),
0x0004(Extended Compat ID),
0x0005(Extended Properties)
wLength 2 Data fieldのバイト数

wLengthに関しては、Windowsは初回は0x10とし、ヘッダ部分のみ取得する。

先ほどと同じく、usbd_dfu.cを編集する。

static uint8_t USBD_DFU_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
  USBD_DFU_HandleTypeDef *hdfu = (USBD_DFU_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId];
  USBD_StatusTypeDef ret = USBD_OK;
  uint8_t *pbuf;
  uint16_t len;
  uint16_t status_info = 0U;

//ここから追加
  //MS OS String Descriptor
  if(0xC0 == req->bmRequest || 0xC1 == req->bmRequest){
	  if(MS_VENDER_CODE == req->bRequest){
		  USBD_GetMSDescriptor(pdev, req);
		  return ret;
	  }
	  return (uint8_t)USBD_FAIL;
  }
//ここまで追加
//(~中略~)
}

bmRequestがC0またはC1の場合、GET_MS_DESCRIPTOR要求はUSBD_DFU_Setup()に渡される(→/BTL_BOOT/Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ioreq.cからUSBD_StdEPReq()経由で呼ばれている)。bmRequestがC0またはC1の場合に、後述するUSBD_GetMSDescriptor()が呼ばれるようにしている。

さて、GET_MS_DESCRIPTOR要求に対する応答を返すUSBD_GetMSDescriptor()の中身を考える。

まず、Extended compat IDは次の通りとなる。

フィールド サイズ
dwLength 4 0x00000028,
bcdVersion 2 0x0100
wIndex 2 0x0004
bCount 1 0x01
RESERVED 7 0x00埋め
bFirstInterfaceNumber 1 0x00
RESERVED 1 0x01
compatibleID 8 ASCIIコードで”WINUSB”、残りは0x00埋め
subCompatibleID 8 0x00埋め
RESERVED 6 0x00埋め

次に、Extended Propertiesは次の通り。ここでDeviceInterfaceGUIDをOSに認識される

フィールド サイズ
dwLength 4 0x0000008E,
bcdVersion 2 0x0100
wIndex 2 0x0005
wCount 2 0x0001
dwSize 4 0x00埋め
dwPropertyDataType 4 0x00000084
wPropertyNameLength 2 0x00000001
bPropertyName 40 DeviceInterfaceGUID
dwPropertyDataLength 4 0x0000004E
bPropertyData 78 DeviceInterfaceGUIDの値

※これはbPropertyNameに、”DeviceInterfaceGUID”を指定した場合である。”DeviceInterfaceGUIDs”を指定する場合は、wPropertyNameLength以降が変わる。

以上を実装すると、次の通りとなる。

#define LEN_EXTEND_COMPACT_ID (0x28)
static uint8_t EXTEND_COMPACT_ID_OS_FEAT_DESC[LEN_EXTEND_COMPACT_ID] =
{
        0x28, 0x00, 0x00, 0x00, //dwLength
        0x00, 0x01,    //bcdVersion
        0x04, 0x00, //wIndex
        0x01,    //bCount
        0, 0, 0, 0, 0, 0, 0,    //Reserved
        0,    //bFirstInterfaceNumber
        0x01,    //Reserved
		'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, //CompatibleID "WINUSB"
        0, 0, 0, 0, 0, 0, 0, 0,    //subCompatibleID
        0, 0, 0, 0, 0, 0, //Reserved
};

#define LEN_EXTEND_PROPERTIES (0x8E)
static uint8_t EXTEND_PROPERTIES_OS_FEAT_DESC[LEN_EXTEND_PROPERTIES] =
{
        0x8E, 0x00, 0x00, 0x00, //dwLength
        0x00, 0x01,    //bcdVersion
        0x05, 0x00, //wIndex
        0x01, 0x00,    //wCount
        //custom property section
        0x84, 0x00, 0x00, 0x00, //dwSize
        0x01, 0x00, 0x00, 0x00, //dwPropertyDataType
        0x28, 0x00,    //wPropertyNameLength
        'D',0, 'e',0, 'v',0, 'i',0, 'c',0, 'e',0, 'I',0, 'n',0, 't',0, 'e',0, 'r',0, 'f',0, 'a',0, 'c',0, 'e',0, 'G',0, 'U',0, 'I',0, 'D',0, 0, 0,    //bPropertyName
        0x4E, 0x00, 0x00, 0x00, //dwPropertyDataLength
        '{',0, 'F',0, 'A',0, '0',0, 'B',0, 'D',0, '5',0, '4',0, 'B',0, '-',0, '5',0, '0',0, 'F',0, '3',0, '-',0, '7',0, '8',0, '2',0, '8',0, '-',0, 'C',0, '4',0, 'B',0, '4',0,
        '-',0, '7',0, '4',0, 'E',0, '5',0, '0',0, 'E',0, '1',0, '6',0, '4',0, '9',0, 'D',0, '1',0, '}',0, 0, 0 //bPropertyData
};

/**
  * @brief  USBD_GetMSDescriptor
  *         Handle Get Descriptor requests
  * @param  pdev: device instance
  * @param  req: usb request
  * @retval status
  */
static void USBD_GetMSDescriptor(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
  uint16_t len = 0U;
  uint8_t *pbuf = NULL;
  uint8_t err = 0U;


  switch (req->wValue >> 8)
  {
    case 0x00:
        if(0x00 == (uint8_t)(req->wValue) || 0x01 == (uint8_t)(req->wValue) ){//device or interface
            switch(req->wIndex){
            case 0x0001://genre
                break;

            case 0x0004://Extended Compact ID
				pbuf = EXTEND_COMPACT_ID_OS_FEAT_DESC;
				len = LEN_EXTEND_COMPACT_ID;
                break;

            case 0x0005://Extended Properties
                pbuf = EXTEND_PROPERTIES_OS_FEAT_DESC;
                len = LEN_EXTEND_PROPERTIES;
                break;

            default:
                break;

            }
        }else{
            USBD_CtlError(pdev, req);
            err++;
        }
        break;

    default:
      USBD_CtlError(pdev, req);
      err++;
      break;
  }

  if (err != 0U)
  {
    return;
  }

  if (req->wLength != 0U)
  {
    if (len != 0U)
    {
      len = MIN(len, req->wLength);
      (void)USBD_CtlSendData(pdev, pbuf, len);
    }
    else
    {
      USBD_CtlError(pdev, req);
    }
  }
  else
  {
    (void)USBD_CtlSendStatus(pdev);
  }
}

ビルドして実行し、USBケーブルを繋いで確認する。デバイスマネージャを開き、以下の通り「Original DFU bootloader」デバイスが追加されていれば成功である。もし”!”マークが付くようであれば、USBDeviewで当該デバイスをアンインストールするか、VID/PIDペアを変更してみると良い。

8.STEP4: ライトプロテクト

STM32H7では、FlashのSectionごとにライトプロテクトを掛けることができる(→RM0468 4.5.2 Write protection)。

DFUブートローダ領域のSection 0をライトプロテクトすれば、バグ等によるブートローダ領域の書き換えを防ぐことができる。ただし、ライトプロテクトを外すまでDFUブートローダの書き換えが不可となるので、開発の終盤で実施すると良い。

ライトプロテクトを掛けるには、オプションバイトを書き換えれば良い。方法は次の通り。

  1. STM32CubeProgrammerをインストール&起動させ、NUCLEOをPCに接続する。
  2. 右上からST-LINKを選び、Connectボタンを押す。
  3. 左のアイコンからOB(オプションバイト)を選び、Write Protectionタブを開く。nWRP0がSection 0に対応しており、チェックを外す。Applyを押せばライトプロテクトが掛かる。ライトプロテクトを外す場合はチェックを入れてApplyを押せば良い。

9.DFUプロトコルを使用したファームウェア更新

DFUプロトコルを利用してファームを更新するツールはいくつか存在する。ここでは、スタンドアロンで動作するdfu-utilを用いる方法を示す。OSSかつマルチプラットフォーム対応であり、使いやすい。

公式ページよりバイナリをダウンロードしておく。

次に、書き込み用のバイナリを用意する。下図のように、BTL_APPプロジェクトのプロパティを開き、C/C++ Build -> Setting -> MCU Post build outputsオプションを選択し、Convert to binary fileにチェックを入れる。これでビルドすると、プロジェクトのDebugフォルダ(BTL_APP\Debug)にBTL_APP.binが生成される。

dfu-util.exeと同じフォルダに、先ほど生成したBTL_APP.binを入れ、コマンドプロンプトでフォルダを開き、以下のコマンドを打てばアップデートが始まる。以下はVID/PIDが0x1209/0x0003の場合である。コマンド詳細はマニュアルページ参照。

dfu-util -d 1209:0003 -s 0x08020000:917504 -D BTL_APP.bin

出力結果は次の通り。Warning: Invalid DFU suffix signatureと出ているが、現時点で回避する術はない模様

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2021 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Warning: Invalid DFU suffix signature
A valid DFU suffix will be required in a future dfu-util release
Opening DFU capable USB device...
Device ID 1209:0003
Device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Interface #0 ...
Determining device status...
DFU state(2) = dfuIDLE, status(0) = No error condition is present
DFU mode device DFU version 011a
Device returned transfer size 4096
DfuSe interface name: "Internal Flash   "
Downloading element to address = 0x08020000, size = 7724
Erase           [=========================] 100%         7724 bytes
Erase    done.
Download        [=========================] 100%         7724 bytes
Download done.
File downloaded successfully

10.既知の問題

STEP3で説明したように、Windows環境下では、Extended PropertiesによってGUIDを登録できるはずだが、実際には登録できない。

以下の手順で確認できる(→USB デバイス のレジストリ エントリ参照)。

  • スタートメニューからレジストリエディタ(regedit.exe)を開く
  • 上部のアドレスに \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB を打ち込む
  • /<シリアル番号>/ノードをクリックする。

この\Device Parametersノードに、DeviceInterfaceGUIDキーが登録されるはずだが、以下のようにキーが存在しない。

次に、libusb付属のxusbというソフトウェアで、応答を確認してみる。こちらの記事が参考になる。

libusbのWindowsバイナリをダウンロードして解凍すると、\VS2015-Win32\examples 又は \VS2015-x64\examples フォルダの中にxusb.exeがある。コマンドプロンプトでフォルダに移動し、以下のコマンドを打つ。VID/PIDが0x1209/0x0003の場合である。

xusb -w 1209:0003

出力は次の通り。

//(~中略~)
Reading string descriptors:
   String (0x01): "Hogehoge"
   String (0x02): "Original DFU bootloader"
   String (0x03): "385433663139"

Reading OS string descriptor:

Reading Extended Compat ID OS Feature Descriptor (wIndex = 0x0004):

  00000000  28 00 00 00 00 01 04 00 01 00 00 00 00 00 00 00  (...............
  00000010  00 01 57 49 4e 55 53 42 00 00 00 00 00 00 00 00  ..WINUSB........
  00000020  00 00 00 00 00 00 00 00                          ........

Reading Extended Properties OS Feature Descriptor (wIndex = 0x0005):

  00000000  8e 00 00 00 00 01 05 00 01 00 84 00 00 00 01 00  ................
  00000010  00 00 28 00 44 00 65 00 76 00 69 00 63 00 65 00  ..(.D.e.v.i.c.e.
  00000020  49 00 6e 00 74 00 65 00 72 00 66 00 61 00 63 00  I.n.t.e.r.f.a.c.
  00000030  65 00 47 00 55 00 49 00 44 00 00 00 4e 00 00 00  e.G.U.I.D...N...
  00000040  7b 00 46 00 41 00 30 00 42 00 44 00 35 00 34 00  {.F.A.0.B.D.5.4.
  00000050  42 00 2d 00 35 00 30 00 46 00 33 00 2d 00 37 00  B.-.5.0.F.3.-.7.
  00000060  38 00 32 00 38 00 2d 00 43 00 34 00 42 00 34 00  8.2.8.-.C.4.B.4.
  00000070  2d 00 37 00 34 00 45 00 35 00 30 00 45 00 31 00  -.7.4.E.5.0.E.1.
  00000080  36 00 34 00 39 00 44 00 31 00 7d 00 00 00        6.4.9.D.1.}...

Releasing interface 0...
Closing device...

出力を見る限りでは、Extended Propertiesは読めているため、問題無いように思えるのだが…。

さらにデバッガで確認したところ、Windows側からExtended compat IDの要求はあるが、Extended Propertiesの要求が無いようである。OS側からExtended Propertiesを要求する条件が存在するのかも。

本問題は引き続き調査を続ける。

11.今後の課題

  • GUIDが認識されない問題の解決
  • Microsoft OS Descriptor 2.0への対応

12.サンプルプロジェクト

本稿で作成したプロジェクト一式を、GitHubに上げておく

13.参考文献

コメント

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