aki_iic’s blog

己の欲せざる処人に施す事無かれ、狂人の真似するは即ち狂人なり

リスキリング:)

 過日YouTubeでツールラボというチャネルを知りました。

 徘徊してみるとarduinoでFreeRTOSを動かすトピックがありまして・・・コレだ!と飛びついた次第↓

tool-lab.com

 こちらはYouTubeと連携したhpでYouTubeはこちら↓

www.youtube.com

 このチャンネルはとても解説が丁寧で教材も簡潔明瞭でナレーションも解説図も良く練られていて大変解りやすいのでありました(拍手)。

 話を戻すとarduinoでFreeRTOSを導入出来るのはarduino(AVR8)と他にもsamd(CortexM0+系)他がある様です(ESP32系もありますよね)。そういう訳でarduinoでFreeRTOSが動かせるのであればと・・・久々に中華arduino nanoを買ってしまいました。最近のはMiniBではなくてUSB-Cの様で併せてUSB-Cケーブルも調達。中華らしく(最近は多い様ですが)CH340ですがFTDIデバイスに見えるのでarduino ideからは普通に認識される様になった様です(昔は駄目の場合もあった様な・・・)。

arduino nano usb-c

 基板逆で読みにくいのですがLEDは上からL(13),PWR,TX,RXです(一般的な構成)。

目的のFreeRTOSはライブラリマネージャでFreeRTOSで出てくるのでFreeRTOSをインストール

FreeRTOS for arduino(Mega328)

 上図のとおりsamd21,samd51版もある様です(xiao(samd21),wio terminal(samd51等)その際はseeed製品ではそれ用のツールライブラリ導入要。

環境整備も出来たのでFreeRTOSのサンプルをコンパイルしてみます。

#include <Arduino_FreeRTOS.h>

// define two tasks for Blink & AnalogRead
void TaskBlink( void *pvParameters );
void TaskAnalogRead( void *pvParameters );

// the setup function runs once when you press reset or power the board
void setup() {
  
  // initialize serial communication at 9600 bits per second:
  Serial.begin(115200);
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  }

  // Now set up two tasks to run independently.
  xTaskCreate(
    TaskBlink
    ,  "Blink"   // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );

  xTaskCreate(
    TaskAnalogRead
    ,  "AnalogRead"
    ,  128  // Stack size
    ,  NULL
    ,  1  // Priority
    ,  NULL );

  // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
}

void loop()
{
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskBlink(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, LEONARDO, MEGA, and ZERO 
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN takes care 
  of use the correct LED pin whatever is the board used.
  
  The MICRO does not have a LED_BUILTIN available. For the MICRO board please substitute
  the LED_BUILTIN definition with either LED_BUILTIN_RX or LED_BUILTIN_TX.
  e.g. pinMode(LED_BUILTIN_RX, OUTPUT); etc.
  
  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products
  
  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald
  
  modified 2 Sep 2016
  by Arturo Guadalupi
*/

  // initialize digital LED_BUILTIN on pin 13 as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  for (;;) // A Task shall never return or exit.
  {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for one second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for one second
  }
}

void TaskAnalogRead(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  
/*
  AnalogReadSerial
  Reads an analog input on pin 0, prints the result to the serial monitor.
  Graphical representation is available using serial plotter (Tools > Serial Plotter menu)
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.

  This example code is in the public domain.
*/

  for (;;)
  {
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);
    // print out the value you read:
    Serial.println(sensorValue);
    vTaskDelay(1000 / portTICK_PERIOD_MS);  // one tick delay (15ms) in between reads for stability
  }
}

FreeRTOS sample(blink analogread)

 上図のとおりタスクは2つ(TaskBlink,TaskAnalogRead)でxTaskCreate()で各タスクを定義(所謂TCB)して、各タスクはvoid TaskBlink()みたいな形で記述する様です。早速arduino ideで動かしてみると、

FreeRTOS arduino sample 実行画面

 画面右中がserialprintされたa0ポートのADC(何も繋いてないので適当な値が出ている)arduino上で13のledが500ms周期で点滅しております。

 arduinoでFreeRTOSが動くとは上記YouTubeチャネルで初めて知りました(恥ずかし)のですがリスキリング的な意味でFreeRTOSを触ってみようかと思ったりします。

 今回引用させて頂いたツールラボチャンネルは大変解りやすいので個人的にはお勧めです:)

 

20251212 16:02追記:FreeRTOS(samd21)を導入し、seeed xiao(samd21g18)でサンプルを動かしてみました。

FreeRTOS(samd21)

 まづ、FreeRTOS(samd21)を導入しておきます。

 それからサンプルを開き、led(13)の点滅を追加して・・・

FreeRTOS(samd21) sample

上図中、88行目が動作確認用に追加したled点滅。TaskBにディスパッチする度にled(13)を反転するいつものコード。

//**************************************************************************
// FreeRtos on Samd21
// By Scott Briscoe
//
// Project is a simple example of how to get FreeRtos running on a SamD21 processor
// Project can be used as a template to build your projects off of as well
//
//**************************************************************************
 
#include <FreeRTOS_SAMD21.h>
 
//**************************************************************************
// Type Defines and Constants
//**************************************************************************
 
#define  ERROR_LED_PIN  13 //Led Pin: Typical Arduino Board
//#define  ERROR_LED_PIN  2 //Led Pin: samd21 xplained board
 
#define ERROR_LED_LIGHTUP_STATE  HIGH // the state that makes the led light up on your board, either low or high
 
// Select the serial port the project should use and communicate over
// Some boards use SerialUSB, some use Serial
//#define SERIAL          SerialUSB //Sparkfun Samd21 Boards
#define SERIAL          Serial //Adafruit, other Samd21 Boards
 
//**************************************************************************
// global variables
//**************************************************************************
TaskHandle_t Handle_aTask;
TaskHandle_t Handle_bTask;
TaskHandle_t Handle_monitorTask;
 
//**************************************************************************
// Can use these function for RTOS delays
// Takes into account processor speed
// Use these instead of delay(...) in rtos tasks
//**************************************************************************
void myDelayUs(int us)
{
  vTaskDelay( us / portTICK_PERIOD_US );  
}
 
void myDelayMs(int ms)
{
  vTaskDelay( (ms * 1000) / portTICK_PERIOD_US );  
}
 
void myDelayMsUntil(TickType_t *previousWakeTime, int ms)
{
  vTaskDelayUntil( previousWakeTime, (ms * 1000) / portTICK_PERIOD_US );  
}
 
//*****************************************************************
// Create a thread that prints out A to the screen every two seconds
// this task will delete its self after printing out afew messages
//*****************************************************************
static void threadA( void *pvParameters ) 
{
  
  SERIAL.println("Thread A: Started");
  for(int x=0; x<100; ++x)
  {
    SERIAL.print("A");
    SERIAL.flush();
    myDelayMs(300);
 
  }
  
  // delete ourselves.
  // Have to call this or the system crashes when you reach the end bracket and then get scheduled.
  SERIAL.println("Thread A: Deleting");
  vTaskDelete( NULL );
}
 
//*****************************************************************
// Create a thread that prints out B to the screen every second
// this task will run forever
//*****************************************************************
static void threadB( void *pvParameters ) 
{
  SERIAL.println("Thread B: Started");
 
  while(1)
  {
    SERIAL.println("B");
    SERIAL.flush();
    myDelayMs(1000);
    digitalWrite(13,!digitalRead(13));
  }
 
}
 
//*****************************************************************
// Task will periodically print out useful information about the tasks running
// Is a useful tool to help figure out stack sizes being used
// Run time stats are generated from all task timing collected since startup
// No easy way yet to clear the run time stats yet
//*****************************************************************
static char ptrTaskList[400]; //temporary string buffer for task stats
 
void taskMonitor(void *pvParameters)
{
    int x;
    int measurement;
    
    SERIAL.println("Task Monitor: Started");
 
    // run this task afew times before exiting forever
    while(1)
    {
    myDelayMs(10000); // print every 10 seconds
 
    SERIAL.flush();
SERIAL.println("");  
    SERIAL.println("****************************************************");
    SERIAL.print("Free Heap: ");
    SERIAL.print(xPortGetFreeHeapSize());
    SERIAL.println(" bytes");
 
    SERIAL.print("Min Heap: ");
    SERIAL.print(xPortGetMinimumEverFreeHeapSize());
    SERIAL.println(" bytes");
    SERIAL.flush();
 
    SERIAL.println("****************************************************");
    SERIAL.println("Task            ABS             %Util");
    SERIAL.println("****************************************************");
 
    vTaskGetRunTimeStats(ptrTaskList); //save stats to char array
    SERIAL.println(ptrTaskList); //prints out already formatted stats
    SERIAL.flush();
 
SERIAL.println("****************************************************");
SERIAL.println("Task            State   Prio    Stack   Num     Core" );
SERIAL.println("****************************************************");
 
vTaskList(ptrTaskList); //save stats to char array
SERIAL.println(ptrTaskList); //prints out already formatted stats
SERIAL.flush();
 
SERIAL.println("****************************************************");
SERIAL.println("[Stacks Free Bytes Remaining] ");
 
measurement = uxTaskGetStackHighWaterMark( Handle_aTask );
SERIAL.print("Thread A: ");
SERIAL.println(measurement);
 
measurement = uxTaskGetStackHighWaterMark( Handle_bTask );
SERIAL.print("Thread B: ");
SERIAL.println(measurement);
 
measurement = uxTaskGetStackHighWaterMark( Handle_monitorTask );
SERIAL.print("Monitor Stack: ");
SERIAL.println(measurement);
 
SERIAL.println("****************************************************");
SERIAL.flush();
 
    }
 
    // delete ourselves.
    // Have to call this or the system crashes when you reach the end bracket and then get scheduled.
    SERIAL.println("Task Monitor: Deleting");
    vTaskDelete( NULL );
 
}
 
 
//*****************************************************************
 
void setup() 
{
  pinMode(13, OUTPUT);
  SERIAL.begin(115200);
 
  delay(1000); // prevents usb driver crash on startup, do not omit this
  while (!SERIAL) ;  // Wait for serial terminal to open port before starting program
 
  SERIAL.println("");
  SERIAL.println("******************************");
  SERIAL.println("        Program start         ");
  SERIAL.println("******************************");
  SERIAL.flush();
 
  // Set the led the rtos will blink when we have a fatal rtos error
  // RTOS also Needs to know if high/low is the state that turns on the led.
  // Error Blink Codes:
  //    3 blinks - Fatal Rtos Error, something bad happened. Think really hard about what you just changed.
  //    2 blinks - Malloc Failed, Happens when you couldn't create a rtos object. 
  //               Probably ran out of heap.
  //    1 blink  - Stack overflow, Task needs more bytes defined for its stack! 
  //               Use the taskMonitor thread to help gauge how much more you need
  vSetErrorLed(ERROR_LED_PIN, ERROR_LED_LIGHTUP_STATE);
 
  // sets the serial port to print errors to when the rtos crashes
  // if this is not set, serial information is not printed by default
  vSetErrorSerial(&SERIAL);
 
  // Create the threads that will be managed by the rtos
  // Sets the stack size and priority of each task
  // Also initializes a handler pointer to each task, which are important to communicate with and retrieve info from tasks
  xTaskCreate(threadA,     "Task A",       256, NULL, tskIDLE_PRIORITY + 3, &Handle_aTask);
  xTaskCreate(threadB,     "Task B",       256, NULL, tskIDLE_PRIORITY + 2, &Handle_bTask);
  xTaskCreate(taskMonitor, "Task Monitor", 256, NULL, tskIDLE_PRIORITY + 1, &Handle_monitorTask);
 
  // Start the RTOS, this function will never return and will schedule the tasks.
  vTaskStartScheduler();
 
  // error scheduler failed to start
  // should never get here
  while(1)
  {
  SERIAL.println("Scheduler Failed! \n");
  SERIAL.flush();
  delay(1000);
  }
 
}
 
//*****************************************************************
// This is now the rtos idle loop
// No rtos blocking functions allowed!
//*****************************************************************
void loop() 
{
    // Optional commands, can comment/uncomment below
    SERIAL.print("."); //print out dots in terminal, we only do this when the RTOS is in the idle state
    SERIAL.flush();
    delay(100); //delay is interrupt friendly, unlike vNopDelayMS
}
 
 
//*****************************************************************

 

 seeed XIAO(samd21g18)は昔買っていたのですが・・・行方不明につき秋月で購入@950円

seeed XIAO(samd21g18)

 左の丸い物体は最近のXIAOの容器の様です。下というか裏というかにピンヘッダが入っています。前の袋よりコスト掛かっていそうですが梱包事故防止の為か否かは不明。

FreeRTOS(samd21) sample

 シリアルモニタの出力。TaskAは100回ループすると自殺(taskをdeleteする)コードなので画面真ん中付近に「Thread A: Deleting」のメッセージが入っています。

 FreeRTOSはRTOSですがプリエンプションもサポートしているので昔の(ITRONの様な)タスクが全責任を持ってコンテキスト切り替えを行わなくてもシステムタイマでプリエンプションが発生するので固まる可能性が少ないので精神的に楽というかガチガチのリアルタイム性能を求めない用途にはその方が有難いというか・・・

 arduinoもFreeRTOSが動くとそれなりの実用性が出てくるというか楽にプログラムが組めるのでとても助かります。arduinoはハードウェアをデバイスレベルで抽象化してくれてるので楽で良いです(楽する事しか考えていない:)。

 

20251212 21:39追記:YouTube動画ですが(アフィリエイトの可能性:)seeed XIAOファミリを紹介したコンテンツです。この手のモノがジジイばかりなのは・・・余暇活用?

www.youtube.com