別シリーズにて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が切り替わったりすると意図した表示ができませぬ。そこで以下のように考えました。
-
- OLED表示に専念するTASKを1個作り、そのTASK内でのみOLED表示を制御する(初期化除く)
- 各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); } }
実機実行結果
上記のソースをビルドして実機に書き込んで動かした様子が以下に。
なお、例によってUSB Serialへのprintfも併用しているので、「シリアルモニタ」などの仮想端末をUSBシリアルに接続するまで無限ループで待ってます。接続後、TASKが走りはじめ、OLEDに表示されたTASK毎のカウンタ値がカウントアップするのが観察できます。そんだけ。