STM32CubeMXでLチカコードを書く(V5.0.1)

自分用メモ

メジャーアップデートでUIが大幅に変わったので書き直し。
あと細かい変更も何箇所か。

## ターゲット

STBee F4mini(STM32F405RGT)を使用する。
オンボードのLEDを点滅させ、オンボードのSWで点滅のパルス幅を変更する。

USB CDC(VPC)でPCと接続し、COMポート経由で文字列のやり取りを行う。

いくつかの処理はC++で記述する。

## CubeMXでの操作

### デバイスの選択

"New Project"か`Ctrl-N`でMCU選択画面を出す。
数日ぶりの起動だとアップデートチェックが走るので時間がかかる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/69e17c11-42e5-c9f7-68d5-4f6277431eb5.png)

***

左上のPart Number Searchから使用するMCUを指定し、右下のリストから使用するMCUをダブルクリックで決定する。
この際、リストの左側の星マークを設定しておくと、次回以降は左上の星マークをクリックすることでお気に入りのMCUを表示できる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/363a5ee4-56e5-35b1-b164-7301a0a49e9e.png)

### プロジェクトの設定

Project ManagerタブのProjectから、Project NameとProject Locationを設定する。
Toolchain / IDEでMakefileを選択する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/81e951bc-8420-c1d0-0108-49a9445cd45e.png)

***

Code Generatorでチェックボックスを設定する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/48b64a46-04a3-20f8-c67a-4f9122f73ee4.png)

***

~~最初にMiddlewareのFREERTOSのEnableを設定しておく。~~
最初にMiddlewareのFREERTOSのInterfaceをCMSIS_V1に設定しておく。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/16952399-1f06-10fb-d322-73edb499df50.png)

FreeRTOSの有無で割り込み優先度が変化するので、あとからFreeRTOSを有効にすると不適切なままコードが生成される場合があるため。

FreeRTOSの設定は後で行う。

***

System CoreのRCCからHSEにCrystal/Seramic Resonatorを選択する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/8e1f5a3b-a8f7-d6e7-c5c3-829ada08c0c8.png)

***

ConnectivityのUSB_OTG_FSのModeをDevice_Onlyにする。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/2773b62f-2ffa-cc20-81c1-a9995054255c.png)

USB_OTG_HSと間違えないように注意。

***

MiddlewareのUSB_DEVICESのClass For FS IPでCommunication Device Class (Virtual Port Com)を選択する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/6df03c5a-c079-35fe-a221-38b86e54efd4.png)

***

PA0をクリックし、GPIO_Inputを選択する。
PA0を右クリックし、Enter User LabelでUSER_SWと入力する。

同様にPD2をGPIO_Output / USER_LEDと設定する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/006be3bd-c869-4d8c-347f-14d50a677337.png)

InputとOutputを間違えないように注意。

### クロックの設定

Clock Configurationタブを開く。
おそらくDo you want to run automatic clock issues solver ?というダイアログが出てくるので、Noで閉じる。

Input frequencyに12を設定する。
PLL Source MuxでHSEを選択する。
System Clock MuxでPLLCLKを選択する。
HCLK to AHB bus, core, memory and DMA (MHz)に168を書き込みEnterで決定する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/aca2582f-619c-4c3b-1976-c1ad8ba5f06d.png)

各逓倍・分周が自動的に設定される。
設定できない周波数を入力した場合は、その旨を表示して近い周波数に設定するかのダイアログが出てくる。

### FreeRTOSの設定

再びMiddlewareのFREERTOSを開く。

#### Config parametersタブ

Memory AllocationをDynamic / Staticに。
TOTAL_HEAP_SIZEを8192に。

USE_IDOL_HOOKをEnableに。

USE_TRACE_FACILITYとUSE_STATS_FORMATTING_FUNCTIONSをEnableに。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/0743139a-17e7-739d-9ae1-a4400b50f798.png)

#### Include parametersタブ

vTaskDelayUntilとuxTaskGetStackHighWaterMarkをEnableに。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/630d6661-1338-15e4-9330-087bbe0a3646.png)

#### Tasks and Queuesタブ

defaultTaskをダブルクリックして設定画面を表示し、AllocationをStaticに変更しOKで閉じる。

***

TasksのAddでNew Taskを表示する。

Task Nameに"LED_blink"、Entry Functionに"LED_blink_func"、Code Generation OptionにAs external、AllocationをStaticに設定し、OKで閉じる。

再びAddで、Task Nameに"Console"、PriorityにosPriorityNormal、Stack Sizeに1024、Entry Functionに"Console_func"、Code Generation OptionにAs external、AllocationにStaticを設定し、OKで閉じる。

***

QueuesのAddでNew Queueを表示する。

Queue Nameに"stdout_queue"、Queue Sizeに128、Item Sizeにuint8_t、AllocationにStaticを設定し、OKで閉じる。
同様に"stdin_queue"、64、uint8_t、Staticでキューを追加する。

***

こんな感じになる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/264452/ad3e5194-f627-fd6d-8267-04738a0a71b0.png)

***

FreeRTOS Heap Usageを見ると、使用済みのヒープサイズが確認できる。現在はTOTAL HEAP USEDが0 Bytesになっているはず。
例えばQueueに構造体を使用した場合、ソースコードを解析しなければ正確なサイズがわからないので、ここの数字は参考程度にしかならない。
また、スタックをStaticで確保すればコンパイル時にメモリ使用量のチェックが走るため、タスクやキューやその他を開始できないほどメモリを確保してしまった際にコンパイルエラーが発生して判断しやすい(さもなくば、「なぜかタスクが動かない」みたいなバグに陥る)。

### コードの生成

右上のGENERATE CODEをクリックしてコードを生成する。
なおCtrl-Shift-Gのショートカットは削除された模様。

警告ダイアログが出てくるけど、無視をしてYesで進める。

## ビルド

出力したフォルダをVisual Studio Codeで開く。

### Makefile

`C_SOURCES`の定義の下に書き加える。

```makefile
C_SOURCES += \
Src/syscalls.c \

CPP_SOURCES = \
Src/FreeRTOS_tasks/LED_blink.cpp \
Src/FreeRTOS_tasks/Console.cpp \
```

***

`PREFIX = arm-none-eabi-`の下に書き加える。

```makefile
GCC_PATH = /mnt/c/Devz/ARM/launchpad/bin
```

***

`CC`や`AS`の定義の部分を以下のように書き換える。

```makefile
CC = $(GCC_PATH)/$(PREFIX)gcc.exe
CPP = $(GCC_PATH)/$(PREFIX)g++.exe
AS = $(GCC_PATH)/$(PREFIX)gcc.exe -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy.exe
SZ = $(GCC_PATH)/$(PREFIX)size.exe
```

(拡張子を追加し、CPPを追加する)

***

`LDFLAGS`の定義から`-specs=nano.specs`を削除する。

***

```makefile
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
```

を

```makefile
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(C_SOURCES:.c=.o))
vpath %.c $(sort $(dir $(C_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(CPP_SOURCES:.cpp=.o))
vpath %.cpp $(sort $(dir $(CPP_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(ASM_SOURCES:.s=.o))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
```

のように書き換える。

(notdirの削除とC++周りを追加)

***

```makefile
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
```

を

```makefile
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	mkdir -p $(dir $@);
	$(CC) -std=c11 -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(<:.c=.lst) $< -o $@

$(BUILD_DIR)/%.o: %.cpp Makefile | $(BUILD_DIR) 
	mkdir -p $(dir $@);
	$(CPP) -std=c++14 -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(<:.cpp=.lst) $< -o $@
```

のように書き換える。

(mkdirの追加、C11の明示、notdirの削除、C++周りを追加)

***

```makefile
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@
```

を

```makefile
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CPP) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@
```

のように書き換える。

(C++でリンクする)

***

`-include $(wildcard $(BUILD_DIR)/*.d)`を以下のように書き換える。

```makefile
-include $(OBJECTS:%.o=%.d)
```

### 設定ファイルの作成

`.vscode/c_cpp_properties.json`を作成し、以下を貼り付ける。

```json
{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "/mnt/c/Devz/ARM/launchpad/arm-none-eabi/include",
                "/mnt/c/Devz/ARM/launchpad/lib/gcc/arm-none-eabi/8.3.1/include",
                "${workspaceFolder}/Inc",
                "${workspaceFolder}/Drivers/STM32F4xx_HAL_Driver/Inc",
                "${workspaceFolder}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy",
                "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/include",
                "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS",
                "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
                "${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F4xx/Include",
                "${workspaceFolder}/Drivers/CMSIS/Include",
                "${workspaceFolder}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc",
                "${workspaceFolder}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc"
            ],
            "defines": [
                "USE_HAL_DRIVER",
                "STM32F405xx"
            ],
            "compilerPath": "/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-g++.exe",
            "cStandard": "c11",
            "cppStandard": "c++14",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}
```

自分でファイルを書く場合は、`ctrl-shift-p`でコマンドパレットを開き、`c/cpp: editconfigurations`と入力し、設定ファイルを作成する。1回目は失敗する確率が高い。その場合は一旦ファイルを削除し、もう一度コマンドパレットから設定ファイルを作成する。
その後、makefileのC_INCLUDESとC_DEFSの内容を設定ファイルに書き込む。

makefileのDEFSを追加したり、インクルードパスを追加した場合は設定ファイルも適時変更すること。

`/mnt/c/Devz/ARM/launchpad/lib/gcc/arm-none-eabi/8.3.1/include`の`8.3.1`はコンパイラバージョンによって変わるので各自の環境に合わせて設定すること。

### ソースコード

#### Src/freertos.c

`/* USER CODE BEGIN Includes */`の下に以下のコードを追加する。

```c
#include <stm32f4xx.h>
#include <usbd_cdc_if.h>
```

***

`/* USER CODE END Variables */`の前に下のコードを追加する。

```c
#if 0
```

***

`/* USER CODE BEGIN FunctionPrototypes */`の後に下のコードを追加する。

```c
#else
osThreadId defaultTaskHandle __attribute__((section(".ccmram")));
uint32_t defaultTaskBuffer[128] __attribute__((section(".ccmram")));
osStaticThreadDef_t defaultTaskControlBlock __attribute__((section(".ccmram")));
osThreadId LED_blinkHandle __attribute__((section(".ccmram")));
uint32_t LED_blinkBuffer[128] __attribute__((section(".ccmram")));
osStaticThreadDef_t LED_blinkControlBlock __attribute__((section(".ccmram")));
osThreadId ConsoleHandle __attribute__((section(".ccmram")));
uint32_t ConsoleBuffer[1024] __attribute__((section(".ccmram")));
osStaticThreadDef_t ConsoleControlBlock __attribute__((section(".ccmram")));
osMessageQId stdout_queueHandle __attribute__((section(".ccmram")));
uint8_t stdout_queueBuffer[128 * sizeof(uint8_t)] __attribute__((section(".ccmram")));
osStaticMessageQDef_t stdout_queueControlBlock __attribute__((section(".ccmram")));
osMessageQId stdin_queueHandle __attribute__((section(".ccmram")));
uint8_t stdin_queueBuffer[64 * sizeof(uint8_t)] __attribute__((section(".ccmram")));
osStaticMessageQDef_t stdin_queueControlBlock __attribute__((section(".ccmram")));
#endif

osThreadId *const thread_list[] = {
    &defaultTaskHandle,
    &LED_blinkHandle,
    &ConsoleHandle,
    0};
```

***

`__weak void vApplicationIdleHook( void )`関数の中に以下のコードを追加する。

```c
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
```

***

```c
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
```

を

```c
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer __attribute__((section(".ccmram")));
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE] __attribute__((section(".ccmram")));
```

のように書き換える。

***

`void StartDefaultTask(void const *argument)`関数の中の、`for (;;)`の中を以下のように書き換える。

```c
static uint8_t buffer[32];
uint16_t count = 0;
BaseType_t dequeue_state = pdPASS;

while (dequeue_state == pdPASS && count < sizeof(buffer))
{
    dequeue_state = xQueueReceive(stdout_queueHandle, &buffer[count], 1);

    if (dequeue_state)
    {
        count++;
    }
}

if (count > 0)
{
    CDC_Transmit_FS(buffer, count);
}

osDelay(2);
```

#### Src/usbd_cdc_if.c

`/* USER CODE BEGIN INCLUDE */`のあとに下のコードを追加する。

```c
#include <cmsis_os.h>
```

***

`static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)`関数の中の、`case CDC_GET_LINE_CODING:`に下のコードを追加する。

```c
*((uint32_t *)&pbuf[0]) = 115200;
pbuf[4] = 0;
pbuf[5] = 0;
pbuf[6] = 8;
```

***

`static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)`の関数を下のように書き換える。

```c
static int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)
{
    /* USER CODE BEGIN 6 */
    USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
    USBD_CDC_ReceivePacket(&hUsbDeviceFS);

    extern osMessageQId stdin_queueHandle;
    uint32_t i = 0;
    for (i = 0; i < *Len; i++)
    {
        xQueueSendFromISR(stdin_queueHandle, &Buf[i], 0);
    }

    return (USBD_OK);
    /* USER CODE END 6 */
}
```

#### Src/FreeRTOS_tasks/LED_blink.cpp

`Src/FreeRTOS_tasks/LED_blink.cpp`を作成し、下のコードを貼り付ける。

```c
#include <cmsis_os.h>
#include <main.h>
#include <stm32f4xx_hal.h>

extern "C" void LED_blink_func(void const *argument)
{
    constexpr uint32_t interval = 1000;
    uint32_t prevWakeTime = osKernelSysTick();

    while (1)
    {
        HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_SET);
        osDelay(HAL_GPIO_ReadPin(USER_SW_GPIO_Port, USER_SW_Pin) == GPIO_PIN_SET
                    ? 500
                    : 100);

        HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_RESET);

        osDelayUntil(&prevWakeTime, interval);
    }
}
```

#### Inc/FreeRTOS_tasks/Console.h

`Inc/FreeRTOS_tasks/Console.h`を作成し、下のコードを貼り付ける。

```c++
#ifndef FreeRTOS_tasks__Console__H
#define FreeRTOS_tasks__Console__H

#include <stdint.h>

namespace FreeRTOS_tasks
{
class Console
{
  protected: // portable
    static constexpr uint16_t line_buff_len = 100;
    static constexpr uint8_t format_buff_len = 20;

  public:
    void task_loop(void);

    static Console &get_instance(void)
    {
        return (instance);
    }

  protected:
  private:
    const char *const format;
    char line_buff[line_buff_len];

    static Console instance;

    void parse_command(void);

    void read_line(void);

    bool equal(const char *expected);
    bool equal_n(const char *expected);

    static const char *format_alloc_and_generate(void);

    Console(void);
    ~Console(void);
    Console(const Console &);
    Console &operator=(const Console &);
};
} // namespace FreeRTOS_tasks

#endif
```

#### Src/FreeRTOS_tasks/Console.cpp

`Src/FreeRTOS_tasks/Console.cpp`を作成し、下のコードを貼り付ける。

```c++
#include <FreeRTOS_tasks/Console.h>
#include <cmsis_os.h>
#include <stdio.h>
#include <string.h>
#include <stm32f4xx.h>

extern osThreadId *thread_list[];

namespace FreeRTOS_tasks
{
Console Console::instance;

void Console::task_loop(void)
{
    osDelay(900);

    printf("hello world\n");

    while (1)
    {
        read_line();
        parse_command();
    }
}

void Console::parse_command(void)
{
    if (equal("tasklist"))
    {
        constexpr uint8_t buff_length_per_task = 45;
        constexpr uint8_t number_of_task = 10;

        char buff[number_of_task * buff_length_per_task] = {};
        osThreadList((uint8_t *)buff);
        printf(
            "Name            State   Priorty Stack   Num\n"
            "*******************************************\n"
            "%s"
            "*******************************************\n",
            buff);
    }

    if (equal("water"))
    {
        printf("*** high water mark *****\n");

        for (uint16_t i = 0; thread_list[i]; i++)
        {
            const uint32_t water_mark = uxTaskGetStackHighWaterMark(*thread_list[i]);
            const char *const task_name = pcTaskGetName(*thread_list[i]);

            printf("%2hu %-*s %4lu\n", i + 1, configMAX_TASK_NAME_LEN, task_name, water_mark);
        }

        printf("*************************\n");
    }
}

bool Console::equal(const char *const expected)
{
    return (0 == strcmp(expected, line_buff));
}

bool Console::equal_n(const char *const expected)
{
    return (0 == strncmp(expected, line_buff, strlen(expected)));
}

void Console::read_line(void)
{
    scanf(format, line_buff);
    getchar();

    char *const p = strchr(line_buff, '\r');

    if (p)
    {
        *p = '\0';
    }
}

const char *Console::format_alloc_and_generate(void)
{
    static char format[format_buff_len] = {};

    sprintf(format, "%%%hhu[^\n]%%*[^\n]", (sizeof(line_buff) / sizeof(line_buff[0])) - 1);

    return (format);
}

Console::Console(void)
    : format(format_alloc_and_generate()),
      line_buff()
{
}

Console::~Console(void)
{
    delete[] format;
}
} // namespace FreeRTOS_tasks

extern "C" void Console_func(void const *argument)
{
    FreeRTOS_tasks::Console::get_instance().task_loop();
}
```

#### Src/syscalls.c

`Src/syscalls.c`を作成し、下のコードを貼り付ける。

```c
#include <errno.h>
#include <sys/stat.h>
#include <cmsis_os.h>
#include <stm32f4xx_hal.h>

int _close_r(struct _reent *r, int file)
{
    return 0;
}

void _exit(int n)
{
label:
    goto label;
}

int _fstat_r(struct _reent *r,
             int file,
             struct stat *st)
{
    st->st_mode = S_IFCHR;
    return (0);
}

int _getpid(int file)
{
    return 1;
}

int _isatty_r(struct _reent *r, int fd)
{
    return (1);
}

int _kill(int pid, int sig)
{
    errno = EINVAL;
    return -1;
}

_off_t _lseek_r(struct _reent *r,
                int file,
                _off_t ptr,
                int dir)
{
    return ((_off_t)0);
}

_ssize_t _read_r(struct _reent *r,
                 int file,
                 void *ptr,
                 size_t len)
{
    int i;
    uint8_t *p = (uint8_t *)ptr;
    extern osMessageQId stdin_queueHandle;

    for (i = 0; i < len; i++)
    { // break exit

        uint8_t data;
        xQueueReceive(stdin_queueHandle, &data, portMAX_DELAY);

        p[i] = data;

        if (data == '\n')
        {
            i++;
            break;
        }
    }

    return (i);
}

void *_sbrk_r(struct _reent *_s_r, ptrdiff_t nbytes)
{
    static char *heap_ptr = 0;

    if (!heap_ptr)
    {
        extern char end[];
        heap_ptr = end;
    }

    extern char _estack[];
    extern char _Min_Stack_Size[];
    if (heap_ptr + nbytes > (char *)(_estack - _Min_Stack_Size - 4))
    {
        errno = ENOMEM;
        return ((caddr_t)-1);
    }

    char *base = heap_ptr;
    heap_ptr += nbytes;

    return (base);
}

_ssize_t _write_r(struct _reent *r,
                  int file,
                  const void *ptr,
                  size_t len)
{
    int i;
    uint8_t *p = (uint8_t *)ptr;
    extern osMessageQId stdout_queueHandle;

    if ((__get_IPSR() & 0x000001FF) == 0)
    { // interrupt program status register is base level

        for (i = 0; i < len; ++i)
        {
            xQueueSend(stdout_queueHandle, &p[i], portMAX_DELAY);
        }
    }
    else
    { // call from interrupt service routine

        for (i = 0; i < len; ++i)
        {
            xQueueSendFromISR(stdout_queueHandle, &p[i], 0);
        }
    }

    return (i);
}
```

### ビルド

`Ctrl-Shift-@`でターミナルを開く。

`make`でビルドする。

ビルドに成功すると、

```terminal
/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-size.exe build/hoge_5_0_1.elf
   text    data     bss     dec     hex filename
  70412    9228   16512   96152   17798 build/hoge_5_0_1.elf
/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-objcopy.exe -O ihex build/hoge_5_0_1.elf build/hoge_5_0_1.hex
/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-objcopy.exe -O binary -S build/hoge_5_0_1.elf build/hoge_5_0_1.bin
```

のような表示が出る。

## 書き込み

dfuwを使う場合、ターミナルで`dfuw.exe build/*.hex`のようにすれば書き込みができる(dfuw.exeにパスが通っている場合)。

## 動作確認

書き込みしてリセットすると、LEDが点滅する。スイッチを押すと点灯時間が変化する。
また、USBでPCに接続しているとCOMポートとして認識される。

ターミナルでSTBee F4miniに接続すると、リセット時に"hello world"と表示される。リセットしてから1秒未満で表示されるので、すばやく接続しなければならない。Tera Termなら自動的に再接続されるが、仮想COMポートを使う場合はその機能があるターミナルソフトを選ぶと楽。


## 感想

なれてる分、ver4のほうが使いやすい。
まぁ、それは「慣れ」の問題だから、慣れればver5でも問題ないだろう。