モダンOSのお砂場(78)UNO R4でFreeRTOS、Task発Queue経由OLED行

Joseph Halfmoon

別シリーズにてUNO R4にOLEDディスプレイを接続。これでprintfしなくても「出力」可能となりました。とは言え一つしかない虎の子のOLEDを複数Taskで奪い合うのは醜い。そこでOLED表示TASKを作製、他のTASKどもはそちらにQueue経由でお願いする形にいたしました。今回のはそのプロトタイプです。

※「モダンOSのお砂場」投稿順Indexはこちら

※Arduino IDE上で「スケッチ」形式のソースからFreeRTOSを使って実験してみてます。ターゲット機はArduino UNO R4 Minima。泣く子も黙る?ArmコアのルネサスRA4M1マイコン搭載機です。

今回使用のOLED

今回使用のOLEDモジュールはSeeed Studio社のGroveコネクタ付のものです。以下にSeeed Studio社の製品ページへのリンクを貼り付けました。国内でも『SWITCH SCIENCE』様など「電子工作業界」各社で取り扱いのあるものです。

Grove – 0.96インチOLED ディスプレイ(SSD1315)- OLED Display

制御チップはSSD1315ですが、UNO R4ではSSD1306のつもりで使えます。ただし、UNO R3では使用上の注意あり、です。その辺は以下の別シリーズ記事にて。

部品屋根性(107) SSD1315制御のOLEDのArduino Uno R4/R3接続

FreeRTOSからの使用

複数のタスクが並行に走っている場合、一連のOLED操作の途中でTASKが切り替わったりすると意図した表示ができませぬ。そこで以下のように考えました。

    1. OLED表示に専念するTASKを1個作り、そのTASK内でのみOLED表示を制御する(初期化除く)
    2. 各TASK毎にQueueを持たせ、各TASKは表示したい内容をQueueに入れる。OLED表示TASKは複数のQueueを巡回して、データが到着していたらTask毎に決まった位置に表示する。
今回実験に使用したソース

今回は、データを書き出したいタスクは2個だけです。それぞれ勝手な周期で寝たり起きたりしながら、キューにカウンタ値を書き込みます。カウンタ値はキューに書き込み成功した回数そのものです。

#include <Arduino_FreeRTOS.h>
#include <U8x8lib.h>

#define AWAITMAX1  (1000)
#define AWAITMAX2  (2000)

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); 	      

TaskHandle_t oled_task, task1, task2;
const uint8_t queueSize = 1;
QueueHandle_t msgQueue1, msgQueue2;
unsigned long msg1, msg2;

void setup()
{
  Serial.begin(115200);
  while (!Serial) { }
  u8x8.begin();
  u8x8.setPowerSave(0);

  msgQueue1 = xQueueCreate(queueSize, sizeof(unsigned long));
  msgQueue2 = xQueueCreate(queueSize, sizeof(unsigned long));
  auto const rc_oled = xTaskCreate (
      oled_thread_func, static_cast<const char*>("OLED Thread"), 512 / 4, nullptr, 1, &oled_task
    );
  if (rc_oled != pdPASS) {
    Serial.println("Failed to create 'OLED' thread.");
    return;
  }
  auto const rc_task1 = xTaskCreate (
      task1_func, static_cast<const char*>("Task1"), 512 / 4, nullptr, 1, &task1
    );
  if (rc_task1 != pdPASS) {
    Serial.println("Failed to create 'task1' thread");
    return;
  }
  auto const rc_task2 = xTaskCreate (
      task2_func, static_cast<const char*>("Task2"), 512 / 4, nullptr, 1, &task2
    );
  if (rc_task2 != pdPASS) {
    Serial.println("Failed to create 'task2' thread");
    return;
  }
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.setInverseFont(1);
  u8x8.drawString(0,0,"TASK->OLED");
  u8x8.setInverseFont(0);
  u8x8.drawString(0,1,"TASK1: ");
  u8x8.drawString(0,2,"TASK2: ");
  Serial.println("Starting scheduler ...");
  vTaskStartScheduler();
  for( ;; ); /* Never! */
}

/* NEVER CALLED! */
void loop()
{
  Serial.println(millis());
  vTaskDelay(configTICK_RATE_HZ);
}

void oled_thread_func(void *pvParameters)
{
  char buf[16];
  unsigned long ts1, ts2;
  while (1) {
    if (xQueueReceive(msgQueue1, (void *)&ts1, 0) == pdTRUE) {
      sprintf(buf, "%d\0", ts1);
      u8x8.drawString(7,1,buf);
    }
    if (xQueueReceive(msgQueue2, (void *)&ts2, 0) == pdTRUE) {
      sprintf(buf, "%d\0", ts2);
      u8x8.drawString(7,2,buf);
    }
    u8x8.refreshDisplay();
    taskYIELD();
  }
}

void task1_func(void *pvParameters)
{
  unsigned long count = 0;
  while (1) {
    msg1 = count;
    if (xQueueSend(msgQueue1, (void*)&msg1, 2) == pdTRUE) {
      count++;
    }
    vTaskDelay(AWAITMAX1);
  }
}

void task2_func(void *pvParameters)
{
  unsigned long count = 0;
  while (1) {
    msg2 = count;
    if (xQueueSend(msgQueue2, (void*)&msg2, 2) == pdTRUE) {
      count++;
    }
    vTaskDelay(AWAITMAX2);
  }
}
実機実行結果

上記のソースをビルドして実機に書き込んで動かした様子が以下に。FreeRTOS_OLED_DUT

なお、例によってUSB Serialへのprintfも併用しているので、「シリアルモニタ」などの仮想端末をUSBシリアルに接続するまで無限ループで待ってます。接続後、TASKが走りはじめ、OLEDに表示されたTASK毎のカウンタ値がカウントアップするのが観察できます。そんだけ。

モダンOSのお砂場(77) UNO R4でFreeRTOS、再帰的ミューテックス へ戻る

モダンOSのお砂場(79)UNO R4でFreeRTOS、AD22100で温度監視? へ進む