STM32のFreeRTOSを使ってみる

PlatformIOでの開発


PlatformIOは様々なボードをサポートしていますが、STM32もサポートしています。
サポートしているボードはこ ちらで 確認する事ができますが、ほとんどすべての開発ボードをサポートしています。
また、様々なFramework(開発環境)をサポートしていますが、Arduinoもサポートしています。
サポートしているFrameworkはこ ちらで確認する事ができます。
PlatformIOは指定する項目が多いので、上級者向けの開発環境ですが慣れるとArduino-IDEよりも遥かに効率よく、
アプリケーションを開発する事ができます。

Arduino-IDEに比べて以下の点が優れています。
- ターゲットボードの変更がすぐにできる
- ビルドが超高速
- ST-LINK/V2とV2.1の両方のアダプターをサポートしている
- 必要なToolchainやライブラリを自動的にインストールしてくれる
- 必要なライブラリをプロジェクト単位にインストールすることができる
- 使い慣れたエディターを使うことが出来る

Arduinoのライブラリには同じ名前で内容の違うものがよくあります。
i2cのLCD表示で使われるLiquidCrystal_I2Cなどは10以上のレポジトリを確認する事ができます。
Arduino-IDEでは、同じ名前のライブラリを同時に使うことができませんが、
PlatformIOはプロジェクト単位でライブラリをインストールできるので、
hoge/LiquidCrystal_I2Cと、fuga/LiquidCrystal_I2Cを同時に試すことがで きます。

NUCLEO-F446REを使って、PlatformIOのFreeRTOSを確認してみました。

最初にPlatformIOをインストールします。
開発マシンには、debianをベースにした軽量LinuxのSparkyLinuxを使いました。
SparkyLinuxはマイナーなLinuxですが、旧型のノートPCでもディスクトップが軽快に表示されるので気に入っています。
$ cat /etc/os-release
PRETTY_NAME="SparkyLinux 5.15 (Nibiru)"
NAME="SparkyLinux"
VERSION_ID="5.15"
VERSION="5.15 (Nibiru)"
ID=sparky
ID_LIKE=debian
HOME_URL="https://sparkylinux.org/"
SUPPORT_URL="https://forum.sparkylinux.org/"
BUG_REPORT_URL="https://sourceforge.net/p/sparkylinux/tickets"

以下の手順でPlatformIOのインストールを行います。
$ python3 --version
Python 3.7.3

$ sudo apt install python3-pip python3-setuptools

$ python3 -m pip -V
pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7)

aptでインストールされるpipは古いので最新にアップデート
$ python3 -m pip install -U pip

$ python -m pip -V
pip 20.3.1 from /home/orangepi/.local/lib/python3.7/site-packages/pip (python 3.7)

wheelもアップデート
$ python3 -m pip install -U wheel

$ python3 -m pip install -U platformio

次にPlatfromIOの実行ファイルへのパスを追加します。
PlatfromIOの実行ファイルは$HOME/.local/binにあります。
そこで、ログインスクリプト(.profileや.bashrc)の末尾に以下の1行を追加します。
$ vi $HOME/.profile

PATH=$HOME/.local/bin:$PATH

$ source $HOME/.profile

$ pio --version
PlatformIO Core, version 5.2.4



FreeRTOSのインストールと、NUCLEOボードの追加を行います。
PlatformIOのFreeRTOSはこ ちらに公開されています。
$ pio lib --global install 2093

$ mkdir stm32-freeRTOS

$ cd stm32-freeRTOS

$ pio boards | grep nucleo

$ pio init -b nucleo_f446re

main.cppは以下の様に単純にタスクをスケジュールするものです。
#include <Arduino.h>
#include <STM32FreeRTOS.h>

// Task Body
void task(void *pvParameters)
{
    UBaseType_t prio;
    TickType_t nowTick;

    vTaskDelay(1000 / portTICK_PERIOD_MS);
    prio = uxTaskPriorityGet( NULL );
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
    for(int i=0;i<10;i++) {
        nowTick = xTaskGetTickCount();
        printf("[%s:%d] now\n",pcTaskGetName(0),nowTick);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    vTaskDelete( NULL );
}


//------------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  Serial.println("setup() start");
  Serial.print("configTICK_RATE_HZ:");
  Serial.println(configTICK_RATE_HZ);
  Serial.print("portTICK_PERIOD_MS:");
  Serial.println(portTICK_PERIOD_MS);
  Serial.print("freeRTOS version:");
  Serial.println(tskKERNEL_VERSION_NUMBER);

  portBASE_TYPE xTask = xTaskCreate(task, "Task", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  configASSERT( xTask );

  // start scheduler
  Serial.println("Start Scheduler.....");
  vTaskStartScheduler();

  Serial.println("Insufficient RAM");
  while(1);

}

//------------------------------------------------------------------------------
// WARNING loop() called from vApplicationIdleHook(), so don't use this function.
// loop must never block
void loop() {
  // Not used.
}

以下のコマンドでファームのビルドを行い、シリアルモニターを起動します。
Arduini-IDEでは延々と待たされるコンパイルも一瞬で終わります。
$ pio run -t upload -e nucleo_f446re && pio device monitor -b 115200

ファーム書き込み時に以下のエラーとなる事が有ります。
could not open port '/dev/ttyACM0': [Errno 13] could not open port /dev/ttyACM0: [Errno 13] Permission denied: '/dev/ttyACM0'

このエラーとなるときはこのポート(/dev/ttyACM0)に対するアクセス権が無いので、以下のコマンドでユーザにサブグループを追加しま す。
再ログインで有効になります。
$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0  5月  5 10:08 /dev/ttyACM0

$ sudo usermod -a -G dialout ログインユーザ名

ファームの書き込みはOpenOCDを使いますが、ST-LINKデバイスに対するアクセス権がない場合は、以下の様に書き込みエラーとなりま す。
xPack OpenOCD x86_64 Open On-Chip Debugger 0.11.0+dev (2021-10-16-21:15)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 1

srst_only separate srst_nogate srst_open_drain connect_deassert_srst

Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
Error: open failed
in procedure 'program'
** OpenOCD init failed **
shutdown command invoked

*** [upload] Error 1

このエラーとなるときは、以下のコマンドでgithubから新しいルールを取り出して追加します。
$ git clone https://github.com/stlink-org/stlink

$ cd stlink

$ sudo cp config/udev/rules.d/* /etc/udev/rules.d/

$ sudo udevadm control --reload-rules

$ sudo udevadm trigger

STM32のFreeRTOSは V10.0.1 とかなり新しいものが使われてい ます。




FreeRTOSライブラリは、以下の様に依存ライブラリとして定義することができます。
依存ライブラリとして定義した場合、実行時に自動的にインストールしてくれます。
これがPlatformIOの優れているところです。
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nucleo_f446re]
platform = ststm32
board = nucleo_f446re
framework = arduino
; Real Time Operating System implemented for STM32
lib_deps = stm32duino/STM32duino FreeRTOS@^10.2.1



この様な中華ST-LINKアダプターを安価($2程度)に入手することができます。


コントローラとしてSTM32F103C8T6が使われています。
Nucleoのデバッガー部分と同じ機能を持っています。
そもそもこの製品自体がクローンですが、STM32F103でなく、CS32F103やGD32F103を使っているクローンのクローンが結構出 回っています。
STM32F103にはST-LINK/V2.0が書き込まれています。


このアダプターを使えば、ST-LINK機能を持っていないBluePillやBlackPillなど、NUCLEO以外の開発ボードにも、
直接ファームを書き込むことができ ます。
Arduino-IDEは、ST-LINK V2.1のアダプターしかサポートしていませんが、PlatformIOが使用するOpenOCDはV2.0とV2.1の両方をサポートしています。
アダプターに書き込まれているST-LINKのバージョンは以下のコマンドで確認することができます。
$ lsusb
Bus 002 Device 024: ID 0483:374b STMicroelectronics ST-LINK/V2.1 --> Nucleo
Bus 001 Device 005: ID 0483:3748 STMicroelectronics ST-LINK/V2 --> 中華ST-Link

このアダプターは以下のデバイスとしてホストでは認識されます。
$ ls -l /dev/stlinkv2*
lrwxrwxrwx 1 root root 15  1月 18 15:00 /dev/stlinkv2_3 -> bus/usb/003/004

アダプターを抜き差しすると番号が変わります。
$ ls -l /dev/stlinkv2*
lrwxrwxrwx 1 root root 15  1月 18 15:00 /dev/stlinkv2_3 -> bus/usb/003/004
$ ls -l /dev/stlinkv2*
lrwxrwxrwx 1 root root 15  1月 18 15:02 /dev/stlinkv2_3 -> bus/usb/003/005
$ ls -l /dev/stlinkv2*
lrwxrwxrwx 1 root root 15  1月 18 15:02 /dev/stlinkv2_3 -> bus/usb/003/006

このデバイス(/dev/stlinkv2*)が見つからないときは、以下のコマンドでgithubから新しいルールを取り出して追加します。
$ ls /dev/st*
/dev/stderr  /dev/stdin  /dev/stdout

$ git clone https://github.com/stlink-org/stlink

$ cd stlink

$ sudo cp config/udev/rules.d/* /etc/udev/rules.d/

$ sudo udevadm control --reload-rules

$ sudo udevadm trigger

$ ls /dev/stlink*
/dev/stlinkv2_4



ST-LINKアダプターとSTM32開発ボードを以下の様に接続します。
ST-LINK STM32
3.3V 3.3V
GND GND
SWDIO SWIO(=PA13)
SWCLK SWCLK(=PA14)



platformio.iniにSTM32F103_128kの定義を追加してビルドします。
$ cat platformio.ini
;PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nucleo_f446re]
platform = ststm32
board = nucleo_f446re
framework = arduino

[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino

$ pio run -e bluepill_f103c8 -t upload
========================= [SUCCESS] Took 3.39 seconds =========================

Environment      Status    Duration
---------------  --------  ------------
bluepill_f103c8  SUCCESS   00:00:03.389
========================= 1 succeeded in 00:00:03.389 =========================

ST-LINKアダプターにはシリアル出力の機能は有りません。
シリアル出力をモニターするためには、ST-LINKアダプターとは別に、USB-TTL変換アダプターを使う必要が有ります。
ほとんどの開発ボードはPA9=TX PA10=RXです。
シリアル出力モニターのポート(/dev/ttyUSB0)は、書き込みに使うUSBデバイスとは違うデバイスになります。
ホストマシンとの接続は、書き込みに使うST-LINKのUSBポート(stlinkv2)と、シリアル出力用のUSBポート(ttyUSB0) の2つで接続します。
$ pio run -t upload -e bluepill_f103c8 && pio device monitor -b 115200 -p /dev/ttyUSB0

+------------+            +------------+             +------------+
| STM32 F103 |            |  ST-LINK   |             |    HOST    |
|            |            |            |             |            |
|      PA13  +------------+ SWIO       |/dev/stlinkv2|            |
|      PA14  +------------+ SWCLK      +=============+            |
|      3.3V  +------------+ 3.3V       |             |            |
|      GND   +------------+ GND        |             |            |
|            |            |            |             |            |
|            |            +------------+             |            |
|            |                                       |            |
|            |            +------------+             |            |
|            |            |   USB-TTL  |             |            |
|            |            |            |             |            |
|      PA9   +------------+ RX         |/dev/ttyUSB0 |            |
|      PA10  +------------+ TX         +=============+            |
|      GND   +------------+ GND        |             |            |
|            |            |            |             |            |
+------------+            +------------+             +------------+

もちろん、USB-TTL変換アダプターをWindowsマシンに接続して、TeraTermでシリアル出力をモニターしても構いません。
その場合は、モニターコマンドは不要になります。
$ pio run -t upload -e bluepill_f103c8

+------------+            +------------+             +------------+
| STM32 F103 |            |  ST-LINK   |             |    HOST    |
|            |            |            |             |            |
|      PA13  +------------+ SWIO       |/dev/stlinkv2|            |
|      PA14  +------------+ SWCLK      +=============+            |
|      3.3V  +------------+ 3.3V       |             |            |
|      GND   +------------+ GND        |             |            |
|            |            |            |             |            |
|            |            +------------+             +------------+
|            |                                      
|            |            +------------+             +------------+
|            |            |   USB-TTL  |             |  Windows   |
|            |            |            |             |            |
|      PA9   +------------+ RX         |/dev/ttyUSB0 |            |
|      PA10  +------------+ TX         +=============+            |
|      GND   +------------+ GND        |             |            |
|            |            |            |             |            |
+------------+            +------------+             +------------+


bluepill_f103c8の定義を使ってビルドした場合、以下の4つのシリアルオブジェクトを使うことができま す。
使う事ができるシリアルオブジェクトの数は、ビルド時に使用するboardの指定で変わります。

TX RX
Serial PA9 PA10
Serial1 PA9 PA10
Serial2 PA2 PA3
Serial3 PB10 PB11



デフォルトのアップロードプロトコルがST-LINKになっていないVariantが有ります。
こ ちらにgenericSTM32F405RGのドキュメントが有りますが、デフォルトのアップロードプロトコルがserialになっ てい ます。
STM32は全てST-LINKがデフォルトプロトコルになっていると思うとハマります。
書き込み時のメッセージを注意深く見ると、upload_protocolがserialになっていることが分かります。
Configuring upload protocol...
AVAILABLE: blackmagic, dfu, jlink, serial, stlink
CURRENT: upload_protocol = serial
Looking for upload port...

Warning! Please install `99-platformio-udev.rules`.
More details: https://docs.platformio.org/page/faq.html#platformio-udev-rules

Error: Please specify `upload_port` for environment or use global `--upload-port` option.
For some development platforms it can b

ST-LINKがデフォルトプロトコルになっていない場合は、以下の様にプロトコルを指定する必要が有ります。
[env:genericSTM32F405RG]
platform = ststm32
board = genericSTM32F405RG
framework = arduino
upload_protocol = stlink



PlatformIOでオンボードのUSBポートを使う方法がこ ちらに公開されていました。
BluePillやBlackPillのUSBポートがSerialオブジェクトで使えるようになります。
platformio.iniに bluepill_f103c8_usbcon の定義を追加してビルドします。
$ cat platformio.ini
;PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nucleo_f446re]
platform = ststm32
board = nucleo_f446re
framework = arduino

[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino

[env:bluepill_f103c8_usbcon]
platform = ststm32
board = bluepill_f103c8
framework = arduino
build_flags =
 -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
 -D USBCON

$ pio run -e bluepill_f103c8_usbcon -t upload
========================= [SUCCESS] Took 3.39 seconds =========================

Environment      Status    Duration
---------------  --------  ------------
bluepill_f103c8  SUCCESS   00:00:03.389
========================= 1 succeeded in 00:00:03.389 =========================

bluepill_f103c8_usbconの定義を使ってビルドした場合、USBポートを含む以下の4つのシリアルオブジェクトを使うことが できます。

TX RX
Serial USB USB
Serial1 PA9 PA10
Serial2 PA2 PA3
Serial3 PB10 PB11

bluepill_f103c8_usbconの定義を使ってビルドした場合、以下のコマンドでUSBを使ったシリアルモニターが有効になりま す。
$pio run -e bluepill_f103c8_usbcon -t upload && pio device monitor -b 115200 -p /dev/ttyACM0

ホストマシンとの接続は、書き込みに使うST-LINKのUSBポート(stlinkv2)と、シリアル出力用のUSBポート(ttyACM0) の2つで接続します。
オンボードのUSBポートを使うときは、USBからの5V給電となるので、ST-LINKアダプターからの3.3Vの給電は止める必要が有り ます。

+------------+            +------------+             +------------+
| STM32 F103 |            |  ST-LINK   |             |    HOST    |
|            |            |            |             |            |
|       PA13 +------------+ SWIO       |/dev/stlinkv2|            |
|       PA14 +------------+ SWCLK      +=============+            |
|       GND  +------------+ GND        |             |            |
|            |            |            |             |            |
|            |            +------------+             |            |
|            |                                       |            |
|            |                          /dev/ttyACM0 |            |
|       USB  +=======================================+            |
|            |                                       |            |
|            |                                       |            |
+------------+                                       +------------+

その後の調査でBluePillやBlackPillはUSBポートから給電することができますが、USBポートから給電できるかどうかは、
ボードに依存することが分かりました。
片っ端からF103ボードを試しましたが、Maple互換ボードは給電できますが、MapleMiniは給電できませんでした。

また、CDCによるUSB通信ができるかどうかもボードに依存することが分かりました。
BluePillやBlackPillはUSBで通信できますが、MapleクローンやMapleMiniクローンはできません。
CDCによるUSB通信は完全にハード(おそらくUSB周りの回路構成)に依存します。



ファームの書き込みにST-LINKは必要ありませんが、ST-LINKツールをインストールしておくと、st-infoやst-flashコマ ンドが使えるようになります。
$ cd $HOME
$ sudo apt install cmake libusb-1.0-0-dev
$ git clone https://github.com/stlink-org/stlink
$ cd stlink
$ make
$ sudo make install
$ sudo /sbin/ldconfig
$ st-flash --version
v1.7.0-184-g468b1d2

st-infoでflashサイズとchipidを確認する事ができます。
chipidの一覧はこ ちらに定義されています。
BluePillやBlackPillにはflashサイズが64Kの物と128Kの物が混在していますが、st-infoで見分けることができ ます。
こちらはflashサイズが64KのBluePillです。
$ st-info --probe
Found 1 stlink programmers
  version:    V2J33S25
  serial:     066EFF534951775087182628
  flash:      65536 (pagesize: 1024)
  sram:       20480
  chipid:     0x410

こちらはflashサイズが128KのBluePillです。
$ st-info --probe
Found 1 stlink programmers
  version:    V2J33S25
  serial:     066EFF534951775087182628
  flash:      131072 (pagesize: 1024)
  sram:       20480
  chipid:     0x410

こちらは256KのF103VCT6です。
Found 1 stlink programmers
  version:    V2J33S25
  serial:     066EFF534951775087182628
  flash:      262144 (pagesize: 2048)
  sram:       65536
  chipid:     0x414

pioでビルドしたファームウエアは以下のディレクトリに作られます。
pioのuploadはOpenOCDを使ってファームを書き込みますが、
その代わりにst-flashコマンドでもファームを書き込むことができます。
バイナリー形式のファーム(firmware.bin)を配布する場合は、st-flashコマンドで書き込むことになります。
$ pio run -e bluepill_f103c8

$ ls -l .pio/build/bluepill_f103c8/
合計 56
drwxrwxr-x 4 nop nop   4096  1月 12 15:44 FrameworkArduino
drwxrwxr-x 2 nop nop   4096  1月 12 15:44 FrameworkArduinoVariant
-rwxrwxr-x 1 nop nop  12096  1月 12 15:44 firmware.bin
-rwxrwxr-x 1 nop nop 146556  1月 12 15:44 firmware.elf
drwxrwxr-x 2 nop nop   4096  1月 12 15:44 src

$ st-flash write .pio/build/bluepill_f103c8/firmware.bin 0x8000000



「STM32のFreeRTOSを使って見る」というタイトルで、このシリーズを始めましたが、
ほとんどArduino core for STM32(正式名称はArduino core support for STM32 based boards)の紹介に終始しました。

FreeRTOSは、pre-empteveなタスクスケジュール方式を採用しているので、
同じ優先度で実行可能な全てのタスクに「公平な」割合のプロセッサ時間を与えます。
IOを行っているタスクはIOの途中で、実行権を強制的に剥奪されてしまいます。
CPUの実行権が戻っても、Arduino core for STM32が提供するIOは、
スレッドセーフでは無いので、正しい状態で継続してIOを行うことができません。

タスクの優先度を変えると、優先度の高いタスクが、優先度の低いタスクの実行権を強制的に奪ってしまいます。
優先度の低いタスクに実行権が戻っても、、同じ理由で、継続してIOを行うことができません。

つまり、Arduino core for STM32 + FreeRTSには、IOを行うタスクを複数、並行して動かすことが出来ないという致命的な問題が有ります。
Arduino core for STM32 + FreeRTOSは、あまり使い道が無いというのが現時点の結論です。

なお、Arduino core for STM32を使ってSTM32の開発をする場合は、Arduino-IDEよりも、PlatformIOの方が断然いいです。
それだけは間違いないです。