
別シリーズにて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毎のカウンタ値がカウントアップするのが観察できます。そんだけ。