Arduinoのソフト構造・スケジューリングについて
この記事の目的
Arduinoで複数の機能を効率よく開発するため、ファイル構造・スケジューリング設定がしやすいプロジェクトの構築方法を記載する
特に下記に重点を当てて説明する
ファイル構造・・・1ファイルを機能単位で構成し、複数のCファイル/Hファイルを扱えるようにする
スケジューリング・・・各機能を順序性を保ちながら一定周期で呼び出しやすいソフト構造を取り入れる
Arduinoのソフトの実装方法と課題
Arduino向けのソフトを開発する場合に最も基本的な方法は公式サイトで配布されているArduino IDEを用いて.inoファイルに処理を記載する方法である。
公式サイトへのリンク
簡易的なスクリプトの開発
小規模なプロジェクトであれば上記ツールで記載しても問題はないが、複数人数で開発する処理内容が多いプロジェクト(外部通信/センシング/アクチュエータ制御を同時に実施するなど)の場合
下記のような課題がある。
1ファイル内の処理内容が長くなり複数人同時開発がしにくい
動作タイミングが処理の長さに依存し、一定周期で呼び出す処理などが記載しにくい。 (たとえばPCとの通信が発生するときだけ、センサ処理が遅れるなど)
そこで、下記に示すように複数ファイルに独立した処理を記載して、スケジューラで周期的に呼び出すことが可能なソフトウェアアーキテクチャの構築を試みる。
機能別ファイル構造の構築
公式IDEを使う場合には気づきにくいが、ArduinoではC言語の書式に従い複数のcpp/hファイルに記載されたファイルのコンパイル・リンクが可能である。
下記サイトに従いVisual Studio Code でC言語の記述に従って開発可能な環境を構築するとintellisenseによるコード補間やジャンプも利用できるため効率が良い。
ポイントは下記となる。
"プロジェクト名.ino"ファイルはarduinoプロジェクトのコンパイルに必須である。(処理は記述せずダミーファイルとして残す)
関数setup(初期化関数)、loop(周期関数)は必須関数となる。また、ヘッダArduino.hをインクルードする。(下記のように標準ヘッダにまとめると管理しやすい)
その他の機能は任意のファイル名.cpp上に開発し、ヘッダのインクルードによって機能を呼び出す。
関数loopに後述のスケジューリング処理を入れると周期呼び出しがしやすい環境が構築できる。
#ifndef __STDAFX_H__ #define __STDAFX_H__ #include "Arduino.h" void setup(); void loop(); #endif /* __STDAFX_H__ */
処理の周期呼び出しのスケジューリング
複数の処理を一定周期(数ミリ秒など)で順序に従い呼び出すためには、一定周期の経過をカウントして呼び出すスケジューラを使うと効率化できる。
下記サイトに公開されてるintervalクラスを用いると上記機能に容易に対応できる。
ただしそのまま使うとタイマの単位がミリ秒とやや長い間隔なので、millis()をmicros()にしてlong型でカウンタを保持するとより短い周期にも対応できる。
template <uint16_t time> class interval { unsigned long next_run = 0; template <class T> void _run(T func) { unsigned long now = micros(); if (next_run < now) { func(); next_run = now + time; } } interval() {} public: template <class T> static void run(T func) { static interval<time> instance; instance._run(func); } };
上記クラスを使って生成した周期的な動作タイミングに加え、スケジューラ内でも分散カウンタ (switch case文で実装)をすると通信/センシング/アクチュエータなどの各機能が周期条件以内で呼び出されることを確認しながら処理を実装することが可能になる。(下記は5msec毎に種類の違う処理を順版に呼び出す例)
#include "stdafx.h" #include "communication.h" #include "sensor.h" #include "actuator.h" void setup() { com_init(); sensor_init(); actuator_init(); } void loop() { interval<5000>::run([]{ switch (cyc_count) { case 0: com_main(); break; case 1: sensor_main(); break; case 2: actuator_main(); break; } if (cyc_count>=2) { cyc_count = 0; } else { cyc_count++; } }