STM32CubeMX:チュートリアル 1 RTOSでLチカしてみる

***
追記
　CubeMXのバージョンアップに合わせて書き直しました
　[STM32CubeMX:RTOSでLチカしてみる(v4.26.1)](https://qiita.com/amutou/items/3eb772dfdde83f922574)
***

### 対象
* STM32マイコンを使いたい人
* 各種エラーにめげず自分で解決できる人

### 開発環境
* Win10 pro (April 2018 Update / 1803)
* Windows Subsystem for Linux
* STM32CubeMX v4.25.1
* Visual Studio Code
* arm-none-eabi-gcc 2017q4 major
* dfuw(0.11)
* STBee F4mini (STM32F405RGTx)

### 目標
* FreeRTOSを使ってLチカを行う
* 最低限のC++環境を整える

#### 開発環境の構築

　Win10/WSLが必要です(それ以外の環境の場合、make等のUNIXライクコマンドを使えるようにしておいてください)。

***

　GCCは以下のリンクからダウンロードできます
　[GNU Arm Embedded Toolchain | Downloads – Arm Developer](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads)

　Windows ZIPをダウンロードします。
　展開するとarm-none-eabi, bin, lib, shareの4フォルダになります。
これを`C:\Devz\ARM\launchpad`フォルダにコピーします。
bashで`/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-gcc.exe --version`が実行できれば大丈夫です。

***

　DFUWは以下のリンクからダウンロードします。
　[STBee - DFUWでプログラムを書き込む](http://strawberry-linux.com/stbee/dfuw)

　ダウンロードしたら、適当な場所に展開しておきます。

***

　CubeMXやVS Codeは各自インストールしてください。CubeMXはちょっとクセが強いかもです。諦めずに頑張りましょう。

***

　STBee F4miniボードは基本的に買ったままで使えますが、できればUSBコネクタの横にあるBOOT0と書いてある3ピンのスルーホールの真ん中とPA0をジャンパしておくと便利です。こうすることにより、USR SWを押しながらリセットすることでUSB DFUを起動できます。

## プロジェクトの作成

### MCUの選択

![2018-06-18_16-32.png](https://qiita-image-store.s3.amazonaws.com/0/264452/9739a42f-663f-1454-44fd-d1d983be666a.png)

　STM32CubeMXを開き、New Project...(`Ctrl-N`)でNew Project画面を開きます。アップデートのチェックにそれなりに時間がかかるので、気長に待ちましょう。でもあんまり長過ぎる時は途中で止まってるかもしれません。


　左側のPart Number SearchでMCUの型番を入力します。MCU Listで目的のMCUをダブルクリックすると決定します。
　リストの左の星アイコンをクリックすると黄色くなり、次からはMCU Filtersの左上の黄色い星のアイコンでそのMCUを表示することができます。

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

　Project->Settings...からProject Settings画面を開きます。

![2018-06-18_16-40.png](https://qiita-image-store.s3.amazonaws.com/0/264452/ef279970-bc88-1ab0-14e2-4b9b21d81d51.png)

　Project Nameにプロジェクト名を設定します。
　Project Locationに適当なフォルダを選択します。

　Project Locationの中にProject Nameのフォルダ名が作成され、その中に各種ファイルが生成されます。

　Toolchain / IDEをMakefileに変更します。

　Code Generatorタブを開きます。

![2018-06-18_16-41.png](https://qiita-image-store.s3.amazonaws.com/0/264452/bf1fb884-4dbc-74bb-0d51-34b6f490f0cf.png)

　上図のようにチェックボックスを設定します。

　OkでProject Settingsを閉じます。

### ハードウェアの設定

![2018-06-18_16-46.png](https://qiita-image-store.s3.amazonaws.com/0/264452/ba35923e-ad1e-82ac-20e5-508c2076002c.png)

　左側のツリーからRCCを展開し、High Speed Clock (HSE)をCrystal/Ceramic Resonatorに設定します。

　MCUのアイコン、左下のPA0をクリックし、GPIO_Inputを選択します。PA0を右クリックしEnter User Labelから`USER_SW`と名前を設定します。
　同様に右上のPD2をGPIO_Output, `USER_LED`と設定します。

### クロックの設定

　Clock Configurationタブを開きます。

![2018-06-18_16-49.png](https://qiita-image-store.s3.amazonaws.com/0/264452/5d86ad13-5122-1834-fc3f-60430fef2a14.png)

　左側のInput frequencyを12に、PLL Source MuxをHSEに、System Clock MuxをPLLCLKにし、最後にHCLK to AHB bus, core, memory and DMA (MHz)に168と入力しEnterで決定します。
　これにより各逓倍・分周比が自動的に設定されます。

### ソフトウェアの設定

　Configurationタブを開きます。

　左側のツリーから、FREERTOSのEnableをチェックします。
　右側のMiddlewaresのFREERTOSボタンをクリックします。

![2018-06-18_16-52.png](https://qiita-image-store.s3.amazonaws.com/0/264452/eb4ddb82-cb76-7aec-84d7-ad7e5ce904fc.png)

　Config parametersタブから、USE_IDLE_HOOKをEnableに設定します。

　OkでFREERTOS Configurationを閉じます。

### コードの生成

　Project->Generate Code(Ctrl-Shift-G)でコード生成を開始します。
　この際、FreeRTOSを使用している時にWARNINGが出ます。今の所、僕の使う範囲ではこれに関連する不具合は発生していません。ということで、気にせずにYesで続行しています。
　未確認ですが、おそらくFreeRTOSのコンテキストスイッチを1kHz以外にした場合や、割り込み処理の中でHALのTimeout系関数を呼んだ場合などで不具合が発生すると思われます。
　そのような状態で使用する場合はSYSのTimebase Sourceを変更してください。

　コード生成が終了すれば成功した旨のダイアログが表示されます。

## コードの修正

　プロジェクトフォルダをVisual Studio Codeで開いてください。

### Makefileの修正

　`C_SOURCES`で誤って絶対パスが指定されているファイルが有れば、適切に修正しておきます。具体的には、`/Src/system_stm32f4xx.c \`を`Src/system_stm32f4xx.c \`に変更します。
　Cubeでコードを生成した場合、毎回system_stm32f4xx.cを絶対パスライクでC_SOURCESに追加しています。2回目以降の場合は、すでにC_SOURCESに追加されているので、新しく追加された絶対パスライクな方を削除します。

　`C_SOURCES`の下辺りに以下のコードを追加します。

```
CPP_SOURCES = \
Src/CCM.cpp \
```

　99行目前後、`BINPATH =`を`BINPATH = /mnt/c/Devz/ARM/launchpad/bin`に変更します。
　また、`CC`, `AS`, `CP`, `AR`, `SZ`にそれぞれ`.exe`拡張子を追加します。
　CCの下の行に`CPP = $(BINPATH)/$(PREFIX)g++.exe`を追加します。

　162行目前後、`CFLAGS +=`の`-MT"$(@:%.o=%.d)"`を削除します。

　174行目前後、`LDFLAGS`の`-specs=nano.specs`を削除します。

　184行目前後、`OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))`を`OBJECTS = $(addprefix $(BUILD_DIR)/,$(C_SOURCES:.c=.o))`に変更します。また、そのすこし下の`OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))`を`OBJECTS += $(addprefix $(BUILD_DIR)/,$(ASM_SOURCES:.s=.o))`に変更します。

　適当な所(`# list of ASM program objects`の直上など)に以下のコードを追加します。

```
OBJECTS += $(addprefix $(BUILD_DIR)/,$(CPP_SOURCES:.cpp=.o))
vpath %.cpp $(sort $(dir $(CPP_SOURCES)))
```

　適当な所(`$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) `の上など)に以下のコードを追加します。

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

　195行目前後の`$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)`の直下に`mkdir -p $(dir $@);`を追加します。

　197行目前後の`$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@`を`$(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 $@
```

　207行目前後、`$(CC) $(OBJECTS) $(LDFLAGS) -o $@`を`$(CPP) $(OBJECTS) $(LDFLAGS) -o $@`に変更します。

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

　とりあえず適当なソースファイル(`Src/freertos.c`等)を開いておきます。

　Ctrl-Shift-Pを押し、コマンドパレットを開き、`>c/cpp:editconfigurations`と入力して`c_cpp_properties.json`を作成します。

　`"includePath"`と`"defines"`をMakefileにあるように変更します。
　`"compilerPath"`を`"/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-gcc.exe"`に変更します。
　`"cStandard"`が`"c11"`なのを確認し、`"cppStandard"`を`"c++14"`に変更します。
　以下のようになるはずです。

```JSON
"includePath": [
    "${workspaceFolder}/Inc",
    "${workspaceFolder}/Drivers/STM32F4xx_HAL_Driver/Inc",
    "${workspaceFolder}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy",
    "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
    "${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F4xx/Include",
    "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/include",
    "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS",
    "${workspaceFolder}/Drivers/CMSIS/Include"
],
"defines": [
    "USE_HAL_DRIVER",
    "STM32F405xx"
],
"compilerPath": "/mnt/c/Devz/ARM/launchpad/bin/arm-none-eabi-gcc.exe",
"cStandard": "c11",
"cppStandard": "c++14",
```

### リンカスクリプトの変更

　`STM32F405RGTx_FLASH.ld`を開きます。

　142行目前後の`*(.ccmram*)`の直下に`*CCM.o`を追加します。

### FreeRTOSの設定の変更

　`Inc/FreeRTOSConfig.h`を開きます

　169行目前後、`/* USER CODE END Defines */`の直上に`#define configAPPLICATION_ALLOCATED_HEAP 1`を追加します。

### Src/CCM.cppの追加

　Srcに`CCM.cpp`ファイルを作成し、以下のコードを入力します。

```c++
#include <FreeRTOS.h>

#if (configAPPLICATION_ALLOCATED_HEAP == 1)
uint8_t ucHeap[configTOTAL_HEAP_SIZE] = {1};
#endif
```

### FreeRTOSのタスクの変更

　`Src/freertos.c`を開きます。

　56行目前後、`USER CODE BEGIN Includes`の下に`#include <stm32f4xx.h>`を追加します。

　80行目前後、`vApplicationIdleHook`関数の中に`HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);`を追加します。

　131行目前後、`StartDefaultTask`関数の中にある`for (;;)`無限ループの中を以下のように書き換えます。

```c++
HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin);
osDelay(HAL_GPIO_ReadPin(USER_SW_GPIO_Port, USER_SW_Pin) == GPIO_PIN_SET
            ? 100
            : 500);
```

## ビルド

　プロジェクトフォルダをbashで開き、`make`を実行するとビルドが始まります。正常に終われば`プロジェクトフォルダ/build`に`.bin`, `.dfu`, `.elf`, `.hex`のようなファイルが作成されます。

## 書き込み

　DFUWを解凍して出てきたdfuw-0.11の中にある`dfuw.exe`に`*.hex`をドラッグアンドドロップすると書き込みが始まり、書き込みが終わればコンソールが消えます。
　MCUがDFUモードで接続されていない等、エラーが有るとすぐに画面が消えていまいます。
　ということで、dfuwはコマンドプロンプト(`cmd.exe`)で実行しておくと便利です。

## 実行

　リセットスイッチを押してMCUをリセットします。
　うまく動けばUSER LEDが1Hzで点滅します。また、USER SWを押すと、USER LEDが5Hzで点滅します。

## まとめ

　ということで、STM32CubeMXでプロジェクトを作成してから書き込む所までを駆け足で解説してみました。
　Arduinoやmbedと比べるとLチカするまでがとても面倒ですが、STBee F4miniならArduinoよりも安く、STM32F0のやすいチップ単体ならPIC16よりも安いです。またSTBee F4miniはmbedよりも高速で動作します。STM32は比較的入手性が良く、またバリエーションも豊富なため、自分で好きなMCUを使えるようになれば幅広い範囲で使うことができるはずです。

　今回の方法ではprintfやmalloc/free、new/delete等は使うことができません。そのあたりは注意してください。