Wio LTE Cat.1 + FreeRTOS

Seeed K.K.の松岡です。
ブログ久しぶりに書きます。最後に書いたのは4/28なので、約1.5ヵ月ぶり。
Wio LTE Cat.1、発売開始から2年以上経っていますが、いまだに根強い人気で多くの方にご利用いただいておりますありがとうございます。

seeedjp.github.io

Seeed K.K.では、手軽に開発ができるようArduinoプラットフォームを提供しています。(MbedやC#はどうなったんだというのは忘れてください) 最近はお試しのフェーズから実用のフェーズに移ってきたようで、スレッド/タスクを立ててコード可読性を良くしたいとか、平行に実行したいという話を聞くことが多くなりました。

そこで今回は、Wio LTE Cat.1でFree RTOSを動かす方法を紹介します。

f:id:matsujirushix:20200611101615p:plain

開発環境の構築

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を追加してください。

f:id:matsujirushix:20200611092801p:plain

サンプルコード

試しに、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 松岡 作成