モダンOSのお砂場(81)UNO R4でFreeRTOS、第2のUARTで指令を送る(だけ)

Joseph Halfmoon

前回は規定温度を超えでPassiveブザーをPWMで鳴らす機能を追加。今回は第2UARTを使って外部から「指令」を受信する機能を付け加えたいと思います。UNO R4はUSBシリアルに向いているチャネルとは別にもう一本UARTを使えるのでこれとのやり取りをTask1にお任せします。今のところ指令を受信するだけだけれども。

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

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

Arduino UNO R4 MinimaのUART

UNO R4はR3と違って、USBシリアルに向いているUARTとは別にデジタル端子D0, D1に第2のUARTが接続されています。規定のオブジェクトは以下のとおり

    • Serial、ボード上のUSBポートのUSBシリアル
    • Serial1、D0にRX、D1にTXが向いている

Serialの方は、printfデバッグ的に使っているのでこれはこのまま維持し、Serial1の方を外部デバイスとの接続に利用したいと思います。今回は接続のテストなので接続先は定番のFT232RLを搭載したモジュールです。その先の仮想端末ソフト(TeratermPro)から手動で文字入力してテストしてます。この部分は後でホスト側のプログラムに置き換える予定。

TASKのお仕事のおさらい

このところチョイ変につぐチョイ変で、徐々に機能を付け加えていますが、大分構成部品が増えました。ここで現在4個ある各TASKのお仕事は以下のとおりです。

    • OLED_TASK

SSD1306とソフト「コンパチ」なSSD1315搭載のOLED、128×64ドット表示に表示を行うためのタスクです。TASK1とTASK2のそれぞれから表示すべき内容をQueueを使って受信し、表示を行います。

    • TASK1

前回までは定周期でカウントアップするだけのタスクでしたが、今回は第2のUARTからの指令を受けて「何かする」仕事を割り当てられました。といっても今のところ指令内容をOLED_TASKに送って表示してもらうだけですが。今後に期待?

    • TASK2

AD22100温度センサの温度をADコンバータを使って読み取ります。読み取り結果をOLED_TASKに送って表示するとともに、読み取った温度が既定の範囲(今のところ40℃)を超えるとTASK3にnotificationします。

    • TASK3

他TASKの誰かがnotificationするのを常に待っており、notificationされるとパッシブブザーをPWM駆動して警告音を鳴らして注意を喚起します。notificationが到来しなくなると規定の秒数で警報は止まります。今のところnotificationしてくるのはTASK2だけですが他のTASKからも可能。

ボード回路

今回実験している部分の回路図は以下です。FT232RLと書かれた部分が追加のところ。UNOR4_Serial1_Schematic

今回実験のソース

あちらこちらチョイ直ししているので、全文を以下に掲げます。

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

#define AWAITMAX1  (10)
#define AWAITMAX2  (2000)
#define BuzzorON (128)
#define BuzzorOFF (0)
#define BuzzorWait (3000)

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

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

int ad22100pin = A3;
int ad22100val = 0;
int buzzor = 6;
int rCount = 0;

int getTemperature() {
  ad22100val = analogRead(ad22100pin);
  return -(562650-2000*ad22100val)/9207;
}

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

  msgQueue1 = xQueueCreate(queueSize, sizeof(int));
  msgQueue2 = xQueueCreate(queueSize, sizeof(int));
  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;
  }
  auto const rc_task3 = xTaskCreate (
      task3_func, static_cast<const char*>("Task3"), 512 / 4, nullptr, 1, &task3
    );
  if (rc_task3 != pdPASS) {
    Serial.println("Failed to create 'task3' 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,"TEMP : ");
  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];
  int ts1, ts2;
  while (1) {
    if (xQueueReceive(msgQueue1, (void *)&ts1, 0) == pdTRUE) {
      sprintf(buf, "%c", ts1);
      u8x8.drawString(7,1,buf);
    }
    if (xQueueReceive(msgQueue2, (void *)&ts2, 0) == pdTRUE) {
      sprintf(buf, "%d", ts2);
      u8x8.drawString(7,2,buf);
    }
    sprintf(buf, "%d", rCount);
    u8x8.drawString(0,3,buf);
    u8x8.refreshDisplay();
    taskYIELD();
  }
}

void task1_func(void *pvParameters)
{
  int count = 0;
  while (1) {
    msg1 = 0x20;
    while(Serial1.available()){
      delay(2);
      msg1 = (int)Serial1.read();
      if (xQueueSend(msgQueue1, (void*)&msg1, 2) == pdTRUE) {
        rCount++;
      }
    }
    vTaskDelay(AWAITMAX1);
  }
}

void task2_func(void *pvParameters)
{
  int ecount = 0;
  while (1) {
    msg2 = getTemperature();
    if (msg2 > 40) {
      xTaskNotifyGive(task3);
    }
    if (xQueueSend(msgQueue2, (void*)&msg2, 2) != pdTRUE) {
      ecount++;
    }
    vTaskDelay(AWAITMAX2);
  }
}

void task3_func(void *pvParameters)
{
  while (1) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    analogWrite(buzzor, BuzzorON);
    vTaskDelay(BuzzorWait);
    analogWrite(buzzor, BuzzorOFF);
  }
}
実機動作確認

Serial1の先に接続したTeraterm Pro(8ビット、ノーパリティ、9600ボー、ローカルエコー設定)から一文字「a」を送ったところが以下に。Serial1

UNO R4上のTASK1で受信され、OLED_TASKに送られた様子が以下に。Serial1_to_OLED

小さいけどね、TASK1: a の aが送られてきた文字っす。次は指令を受けてなにか仕事しないと。

モダンOSのお砂場(80)UNO R4でFreeRTOS、規定温度越えたらブザーを鳴らす へ戻る

モダンOSのお砂場(82)UNO R4でFreeRTOS、ブラウザからUNO R4のLEDを操作 へ進む