Seeed K.K.の松岡です。
ブログ久しぶりに書きます。最後に書いたのは4/28なので、約1.5ヵ月ぶり。
Wio LTE Cat.1、発売開始から2年以上経っていますが、いまだに根強い人気で多くの方にご利用いただいておりますありがとうございます。
Seeed K.K.では、手軽に開発ができるようArduinoプラットフォームを提供しています。(MbedやC#はどうなったんだというのは忘れてください)
最近はお試しのフェーズから実用のフェーズに移ってきたようで、スレッド/タスクを立ててコード可読性を良くしたいとか、平行に実行したいという話を聞くことが多くなりました。
そこで今回は、Wio LTE Cat.1でFree RTOSを動かす方法を紹介します。
開発環境の構築
Arduino開発環境
従来の、Wio LTE Cat.1開発環境を最新にアップグレードしてください。
- IDE
- Arduino IDE 1.8.12
- パッケージ(ボード)
- SeeedJP STM32 Boards 1.3.0
- ライブラリ
- Wio LTE for Arduino 2.11.0
公式ドキュメントはこちらです。
FreeRTOSを追加
Arduino IDEに、FreeRTOSを追加します。
具体的には、ライブラリマネージャでSTM32duino FreeRTOSを追加してください。
サンプルコード
試しに、Wio LTE for Arduinoに同封のbasic/LedSetRGB(LEDをレインボー表示)とsoracom/soracom-harvest(稼働時間をSORACOM Harvestへ送信)の2つをFreeRTOSのタスクを使って平行に実行してみようと思います。
サンプルコード全文は最後につけてあります
まず、Wio LTEライブラリとFreeRTOSライブラリを使うので、includeします。
#include <WioLTEforArduino.h> #include <STM32FreeRTOS.h>
basic/LedSetRGB相当の処理をLedTask関数
にします。
処理を継続して実行したいので、while (true)
で永久ループを追記します。(←定番)
void LedTask(void* arg) { int hue = 0; while (true) { // <--- 永久ループ int r; int g; int b; if (hue < 60) { r = LED_VALUE; g = hue * LED_VALUE / 60; b = 0; } ... } }
soracom/soracom-harvest相当の処理をCellularTask関数
にします。
こちらも処理を継続して実行したいので永久ループを追記します。
void CellularTask(void* arg) { SerialUSB.println("### Power supply ON."); Wio.PowerSupplyCellular(true); delay(500); SerialUSB.println("### Turn on or reset."); if (!Wio.TurnOnOrReset()) { SerialUSB.println("### ERROR! ###"); vTaskDelete(nullptr); } SerialUSB.println("### Connecting to \"soracom.io\"."); if (!Wio.Activate("soracom.io", "sora", "sora")) { SerialUSB.println("### ERROR! ###"); vTaskDelete(nullptr); } while (true) { // <--- 永久ループ int connectId = -1; char data[100]; snprintf(data, sizeof(data), "{\"uptime\":%lu}", millis() / 1000); ... } }
タスクで待ち(wait)をするときは通常delay関数
を使いますが、この関数ではタスクを適切に停止(task state = blocked)しないので、、、vTaskDelay関数
に書き換えます。
例えば、delay(50)
はvTaskDelay(pdMS_TO_TICKS(50));
とします。
void LedTask(void* arg) { int hue = 0; while (true) { ... vTaskDelay(pdMS_TO_TICKS(50)); // <--- 待ち } }
LedTask関数
とCellularTask関数
をタスクとして起動、スケジューラを稼働するコードを追記します。
SerialUSB.println("### Start tasks."); xTaskCreate(LedTask , nullptr, 8192 / 4, nullptr, 2, nullptr); xTaskCreate(CellularTask, nullptr, 8192 / 4, nullptr, 1, nullptr); SerialUSB.println("### Setup completed."); vTaskStartScheduler(); for (;;) __asm__("wfi");
これでほぼオッケーですが、、、
実は、Wio LTE for Arduinoライブラリ内でdelay関数
を使っている箇所があるので、これをスケジューラ稼働前にvTaskDelay関数
に差し替えます。
Wio.SetDelayFunction([](int milliseconds) {
vTaskDelay(pdMS_TO_TICKS(milliseconds));
});
実行すると、LEDがカラフルに点灯し続けながら、SORACOM Harvestに送信します!!ちょっと感動。
注意事項
ライブラリ追加して、ちょこっとコード変更すれば平行実行できるんだ、RTOS楽勝じゃん!と思われたかもしれませんが、、、環境構築は楽ですが、実用かつ安全なコードを書くのは結構、相当、とても大変です。
勢いで書くのも大事ですが、書籍やネット情報などでコツコツと知恵をつけておくことをオススメします。落とし穴に落ちたときに這い上がれるように。
サンプルコード全文
#include <WioLTEforArduino.h> #include <STM32FreeRTOS.h> #define LED_VALUE (10) #define INTERVAL (60000) #define RECEIVE_TIMEOUT (10000) WioCellular Wio; //////////////////////////////////////////////////////////////////////////////////////// // setup // loop void setup() { delay(1000); SerialUSB.begin(115200); SerialUSB.println(""); SerialUSB.println("--- START ---------------------------------------------------"); SerialUSB.println("### I/O Initialize."); Wio.Init(); Wio.SetDelayFunction([](int milliseconds) { vTaskDelay(pdMS_TO_TICKS(milliseconds)); }); Wio.SetDoWorkInWaitForAvailableFunction([]() { }); SerialUSB.println("### Start tasks."); xTaskCreate(LedTask , nullptr, 8192 / 4, nullptr, 2, nullptr); xTaskCreate(CellularTask, nullptr, 8192 / 4, nullptr, 1, nullptr); SerialUSB.println("### Setup completed."); vTaskStartScheduler(); for (;;) __asm__("wfi"); } void loop() { } //////////////////////////////////////////////////////////////////////////////////////// // LedTask void LedTask(void* arg) { int hue = 0; while (true) { int r; int g; int b; if (hue < 60) { r = LED_VALUE; g = hue * LED_VALUE / 60; b = 0; } else if (hue < 120) { r = (120 - hue) * LED_VALUE / 60; g = LED_VALUE; b = 0; } else if (hue < 180) { r = 0; g = LED_VALUE; b = (hue - 120) * LED_VALUE / 60; } else if (hue < 240) { r = 0; g = (240 - hue) * LED_VALUE / 60; b = LED_VALUE; } else if (hue < 300) { r = (hue - 240) * LED_VALUE / 60; g = 0; b = LED_VALUE; } else { r = LED_VALUE; g = 0; b = (360 - hue) * LED_VALUE / 60; } Wio.LedSetRGB(r, g, b); hue += 10; if (hue >= 360) hue = 0; vTaskDelay(pdMS_TO_TICKS(50)); } } //////////////////////////////////////////////////////////////////////////////////////// // CellularTask void CellularTask(void* arg) { SerialUSB.println("### Power supply ON."); Wio.PowerSupplyCellular(true); delay(500); SerialUSB.println("### Turn on or reset."); if (!Wio.TurnOnOrReset()) { SerialUSB.println("### ERROR! ###"); vTaskDelete(nullptr); } SerialUSB.println("### Connecting to \"soracom.io\"."); if (!Wio.Activate("soracom.io", "sora", "sora")) { SerialUSB.println("### ERROR! ###"); vTaskDelete(nullptr); } while (true) { int connectId = -1; char data[100]; snprintf(data, sizeof(data), "{\"uptime\":%lu}", millis() / 1000); SerialUSB.println("### Open."); connectId = Wio.SocketOpen("harvest.soracom.io", 8514, WIOLTE_UDP); if (connectId < 0) { SerialUSB.println("### ERROR! ###"); goto err; } SerialUSB.println("### Send."); SerialUSB.print("Send:"); SerialUSB.print(data); SerialUSB.println(); if (!Wio.SocketSend(connectId, data)) { SerialUSB.println("### ERROR! ###"); goto err; } SerialUSB.println("### Receive."); int length; length = Wio.SocketReceive(connectId, data, sizeof (data), RECEIVE_TIMEOUT); if (length < 0) { SerialUSB.println("### ERROR! ###"); goto err; } if (length == 0) { SerialUSB.println("### RECEIVE TIMEOUT! ###"); goto err; } SerialUSB.print("Receive:"); SerialUSB.print(data); SerialUSB.println(""); err: if (connectId >= 0) { SerialUSB.println("### Close."); if (!Wio.SocketClose(connectId)) { SerialUSB.println("### ERROR! ###"); goto err; } } vTaskDelay(pdMS_TO_TICKS(INTERVAL)); } } ////////////////////////////////////////////////////////////////////////////////////////
変更履歴
日付 | 変更者 | 変更内容 |
---|---|---|
2020/6/11 | 松岡 | 作成 |