Arduino CLI

 

スケッチについて

Arduinoのプロジェクトは「スケッチ(sketch)」と呼ばれます。

多くのArduinoプロジェクトは.inoファイルが1つだけで完結しているので、そのファイルをスケッチと呼ぶものだと思いがちですが、スケッチは他の環境で呼ぶところの「プロジェクト」で、現代では複数のファイル及びディレクトリから構成されています。

スケッチフォルダーの構成

スケッチを置くディレクトリを「スケッチフォルダー(sketch folder)」と呼びます。また、スケッチフォルダーの最上位のディレクトリを「スケッチルートフォルダ(sketch root folder)」と呼びます。

スケッチルートフォルダには少なくとも1つの.inoファイルが存在する必要があります。また、その1つの名前はスケッチ名.inoである必要があります。このファイルを「プライマリスケッチファイル(primary sketch file)」と呼びます。

実は無理にそうする必要はありません。arduino-cli compileのオプションとして、ファイル名を指定すれば、それでコンパイルすることは可能です。ただし、それをやるメリットは何もありませんしコマンドラインが長くなるだけなので、そういったものだと思って使うのが良さそうです。

追加のコードファイル

スケッチは複数のファイルで構成可能です。

arduino-cliが対応している拡張子は以下のものです。

拡張子 意味
.ino Arudino言語
.cpp C++
.c C
.S アセンブラ
.h .hpp, hh ヘッダファイル
.tpp .ipp ヘッダファイル

これらの拡張子があるファイルに関しては、arduino-cliが適当に処理してくれます。

srcディレクトリ

arduino-cliはsrcというディレクトリは再帰的にコンパイルするようになっています。

ですからたとえば、

foo/
    foo.ino
    src/
        bright/
            bright.c
            bright.h

というようなディレクトリ構成になっていれば、勝手にsrc/を辿ってbright/をコンパイルをして… ということをしてくれます。

ですから、モジュール構成をしたい場合は、src/というディレクトリを作って、その中に適当に分割して置いてやれば、自動的に処理してコンパイルしてくれます。

dataディレクトリ

arduino-cliはdataというディレクトリに関しては何もしません。

ですから、「何もして欲しくないファイル」はここに置くと良いでしょう。

実のところ、arduino-cliがコンパイルの対象にするのはスケッチルートフォルダとsrc以下だけなので、これら以外であればarduino-cliは何もしません。

ではなぜこのような仕様が存在しているかと言えば、Arduino IDEの歴史的な事情のようです。詳しいことは公式ドキュメントに書かれているので、興味のある人はそっちを読んで下さい。

ただし、歴史的にそうであったと言うことは多くの人達(先達)がこういった流儀をとっているはずなので、「そういったものなのだ」ということは認識しておいて良いでしょう。

複数ファイルに分割する例

複数ファイルに分割した場合、inoとそれ以外とでは扱いが異なります。

inoの場合、特に考えることなく、必要箇所をそのまま分割します。たとえば、

#define	MIX(col, val)	((uint16_t)((uint8_t)(col) * (uint8_t)(val)) >> 8)

int
bright(
	int	_br,
	int	*f,
	int	upper,
	int	lower)
{
	int	r = (int)( random() & 0x3);
	int	low = 10; //lower + (int)( random() & 0x3F );
	int	high = 200; //upper + (int)( random() & 0x3F );

	_br = _br + (( *f > 0 ) ? r : -r);
	if	( _br < low )	{
		*f = 1;
		_br = low;
	} else
	if	( _br > high )	{
		*f = -1;
		_br = high;
	}
	return	(_br);
}

static  int	r, br[NR_LED];
static  int	f[NR_LED];
static  int i;
static  int fact;
static  int msec;
static  int num = 0;

このような部分があったとして、brightだけ分割して別ファイルにすることを考えます。

この場合、拡張子をinoに分割する場合は、brightだけのファイルにして構いません。

int
bright(
	int	_br,
	int	*f,
	int	upper,
	int	lower)
{
	int	r = (int)( random() & 0x3);
	int	low = 10; //lower + (int)( random() & 0x3F );
	int	high = 200; //upper + (int)( random() & 0x3F );

	_br = _br + (( *f > 0 ) ? r : -r);
	if	( _br < low )	{
		*f = 1;
		_br = low;
	} else
	if	( _br > high )	{
		*f = -1;
		_br = high;
	}
	return	(_br);
}

元のファイルからは、この部分を除きます。本当に単純に「抜いて別ファイル」にするだけです。

これでコンパイルすれば、問題なく処理が終わります。

他方、「これはCだよな」と思って、分割した部分の拡張子をcにすると、

$ arduino-cli compile --fqbn esp32:esp32:esp32
/home/ogochan/foo/foo.ino: In function 'void loop()':
/home/ogochan/foo/foo.ino:194:11: error: 'bright' was not declared in this scope
   br[j] = bright(br[j], &f[j], 0xC0, 0);
           ^~~~~~
/home/ogochan/foo/foo.ino:194:11: note: suggested alternative: 'sig_t'
   br[j] = bright(br[j], &f[j], 0xC0, 0);
           ^~~~~~
           sig_t


Used library Version Path                                                                        
WiFi         2.0.0   /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11/libraries/WiFi

Used platform Version Path                                                         
esp32:esp32   2.0.11  /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11
Error during build: exit status 1

このようにいろいろエラーとなります。

この場合は、以下のような対応が必要になります。

  • ヘッダファイルを用意してincludeする

    inoの方にもcの方にも必要です

  • cには必要なヘッダファイルを自分でincludeする

    inoの場合はいろいろ「よしな」にしてくれますが、cは自分で対応する必要があります

  • includeする時はextern "C"でくくる必要がある

    inoの本質はC++です。C++からCを呼び出す時にはこうしてやる必要があります(呼び出すコードがC++であるなら当然これは不要です)。

自分の書いたコードだけであれば、inoとして分割する方が簡単ですが、他から持って来たコードだと、inoとして扱うことが出来ないこともあります。そのような場合はCやC++だったりしますから、このような使い方は把握しておく必要があります。

このような手当をしてやることにより、

$ arduino-cli compile -b esp32:esp32:esp32
Sketch uses 276153 bytes (21%) of program storage space. Maximum is 1310720 bytes.
Global variables use 23096 bytes (7%) of dynamic memory, leaving 304584 bytes for local variables. Maximum is 327680 bytes.

Used library Version Path                                                                        
WiFi         2.0.0   /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11/libraries/WiFi

Used platform Version Path                                                         
esp32:esp32   2.0.11  /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11

というように、無事コンパイルが通るようになりました。

スケッチメタ情報

sketch.yamlというファイル名でスケッチルートフォルダにあります。このファイルは自分で作らないと存在しないので、ないこともあります)。

これにはスケッチの「プロファイル」が定義されています。これは要するにスケッチをビルド(コンパイル)する時の情報が記録されているファイルです。

スケッチメタ情報の内容

このファイルには以下の情報が含まれています。

  • ボードFQBN

    ターゲットのコア プラットフォームの名前とバージョン (必要に応じてサードパーティ プラットフォームのインデックス URL も含む)

  • ターゲット コア プラットフォームの依存関係である、考えられるコア プラットフォームの名前とバージョン (必要に応じてサードパーティ プラットフォームのインデックス URL を含む)

  • スケッチで使用されているライブラリ (バージョンを含む)

ファイルの形式としては、

profiles:
  <PROFILE_NAME>:
    notes: <USER_NOTES>
    fqbn: <FQBN>
    platforms:
      - platform: <PLATFORM> (<PLATFORM_VERSION>)
        platform_index_url: <3RD_PARTY_PLATFORM_URL>
      - platform: <PLATFORM_DEPENDENCY> (<PLATFORM_DEPENDENCY_VERSION>)
        platform_index_url: <3RD_PARTY_PLATFORM_DEPENDENCY_URL>
    libraries:
      - <LIB_NAME> (<LIB_VERSION>)
      - <LIB_NAME> (<LIB_VERSION>)
      - <LIB_NAME> (<LIB_VERSION>)

  ...more profiles here...

という感じです。それぞれの意味は、

  • <PROFILE_NAME>はプロファイル識別子であり、ユーザー定義フィールドであり、使用できる文字は英数字、アンダースコア_、ドット.、およびダッシュ-です。

  • は、ターゲットコアプラットフォームの識別子です。たとえば、arduino:avrやadafruit:samdのような文字列です。

  • <PLATFORM_VERSION>必要なターゲットコアプラットフォームのバージョンです。

  • <3RD_PARTY_PLATFORM_URL>ターゲットコアプラットフォームをダウンロードするためのインデックスのURL(Arduino IDEでは「追加のボードマネージャーURL」とも呼ばれます)。公式のarduino:*プラットフォームの場合はこのフィールドは省略できます。

  • <PLATFORM_DEPENDENCY>, <PLATFORM_DEPENDENCY_VERSION>および<3RD_PARTY_PLATFORM_DEPENDENCY_URL>はそれぞれ, <PLATFORM_VERSION>および<3RD_PARTY_PLATFORM_URL>と同じ情報が含まれていますが、メインコアプラットフォームのコアプラットフォームについての依存関係の情報が含まれています。これらのフィールドはオプションです。

  • libraries:プロジェクトをビルドするために必要なライブラリが定義されるセクションです。このセクションはオプションです。

  • <LIB_VERSION>は、ライブラリに必要なバージョンです (例: 1.0.0)。

  • <USER_NOTES>開発者がコメントを追加するために使用できるフリーのテキスト文字列です。このフィールドはオプションです。

となっています。

簡単な例ではこんな感じです。

profiles:
  esp32:
    fqbn: esp32:esp32:esp32
    platforms:
      - platform: esp32:esp32 (2.0.11)
  XIAO:
    fqbn: esp32:esp32:XIAO_ESP32C3
    platforms:
      - platform: esp32:esp32 (2.0.11)
default_profle: esp32
default_fqbn: esp32:esp32:esp32
default_port: /dev/ttyACM0

このプロファイル名は、コンパイル時に使ってコマンドラインを簡単にすることに使えます。

スケッチメタ情報の雛形を得る

スケッチメタ情報は全部自分で書いても良いのですが、「今のコンパイル環境とコマンドラインに対応したもの」であれば、arduino-cliに出力させることができます。

$ arduino-cli compile --fqbn esp32:esp32:esp32 --dump-profile --no-color --only-compilation-database

のようにすると、ライブラリ情報と共にプロファイルが出力されます。

$ arduino-cli compile --fqbn esp32:esp32:esp32 --dump-profile --no-color --only-compilation-database
Used library Version Path                                                                        
WiFi         2.0.0   /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11/libraries/WiFi

Used platform Version Path                                                         
esp32:esp32   2.0.11  /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11

profiles:
  esp32:
    fqbn: esp32:esp32:esp32
    platforms:
      - platform: esp32:esp32 (2.0.11)

このprofiles:の部分を切り取ってsketch.yamlの元にすれば良いです。

複数対応している場合だと、たとえば

$ arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32C3 --dump-profile --no-color --only-compilation-database
Used library Version Path                                                                        
WiFi         2.0.0   /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11/libraries/WiFi

Used platform Version Path                                                         
esp32:esp32   2.0.11  /home/ogochan/.arduino15/packages/esp32/hardware/esp32/2.0.11

profiles:
  XIAO_ESP32C3:
    fqbn: esp32:esp32:XIAO_ESP32C3
    platforms:
      - platform: esp32:esp32 (2.0.11)

のように出力されたものをマージしてやれば良いでしょう。

デフォルトの設定

sketch.yamlの中に記述することにより、コマンドラインのデフォルト値が設定可能です。

以下のものがデフォルトとして設定できます。

--fbqn
default_fqbn
--port
default_port
--protocol
default_protocol
--profile
default_profile

たとえば、

default_fqbn: arduino:avr:uno
default_port: /dev/ttyACM0
default_protocol: serial
default_profile: myprofile

これらを設定しておくことにより、コンパイルやアップロードの時に、これらのオプションを指定する必要がなくなります。