トホホな疑問(57) 複数 .ino ファイルに虚を突かれる。Arduino IDE

Joseph Halfmoon

Arduinoではinoという形式のファイルを主につかって記述することになっています。メンドイ作業を隠蔽し裏側でC++コンパイラでコンパイルできるようにしてくれるもの。複数の .ino ファイルを使うことも可能で見通しもよいです。.cppのソースを追加してもよいのですが、.ino はお楽。けれど落とし穴にはまりました。

※「トホホな疑問」投稿順Indexはこちら

※以下のArduino IDE(Windows11上)で動作確認しています。

バージョン: 2.0.3
日付: 2022-12-05T09:30:25.331Z
CLIバージョン: 0.29.0 [76251df9]

Copyright © 2023 Arduino SA
落とし穴にハマった背景

RISC-V搭載のESP32機、Seeeduino Xiao ESP32C3を使った、以下の別シリーズ記事でmicroSDカードにアクセスするサンプルプログラムを動かしました。

Iot何をいまさら(114) ESP32C3版XiaoにmicroSDカード接続

使用したのはArduino IDEからスケッチ例でロードできるソースで、勿論 ino 形式のものです。

テスト用のサンプルなのでさらっと1回走っておしまいです。それを外部のシリアル端末から人間が指示を出していろいろできるように「改造」しようと考えました。そこで以下のように3本のinoファイルに分割することにいたしました。

第1のinoファイル。プロジェクトフォルダ名と一致するもの。setup()とloop()関数を残し、SDカードテスト用のコードはほぼほぼ消去。かわりに人間からの指示を受けて「分岐」するためのメインループとする。

第2のinoファイル。仮想シリアルを通じて人間とのインタフェースをするための関数を置く。プロンプト文字列を出し、何か入力されるまでブロッキングで待つ。何か入力されたら選択結果をもって返る。

第3のinoファイル。サンプルソースに入っていた各種SDカードアクセス用の関数群を押し込めてある。

第3のファイルは既に動いている関数をコピペしたものなので「動かない」ってことはないだろ~と。実際、何度かビルドして「ヒューマン・インタフェース」(メニュー)をデバッグしている間は問題なかったです。

突如として問題勃発

第1と第2のファイルを触って、メニューシステムが動くようにデバッグしてました。念のため、第1のinoファイルが以下に。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include "FS.h"
#include "SD.h"
#include "SPI.h"
void setup(){
Serial.begin(9600);
if(!SD.begin()){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
showCardType(cardType);
}
void loop(){
bool menuEnable = true;
int choice = 0;
char menuStr[] = "MENU> D(rive F(ile Q(uit ";
while (menuEnable) {
choice = blockingMenu(menuStr);
switch (choice) {
case 'F':
Serial.println("File");
break;
case 'D':
Serial.println("Drive");
break;
case 'Q':
Serial.println("Quit");
menuEnable = false;
break;
default:
Serial.println("Unknown.");
break;
}
delay(2000);
}
Serial.end();
}
#include "FS.h" #include "SD.h" #include "SPI.h" void setup(){ Serial.begin(9600); if(!SD.begin()){ Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD.cardType(); if(cardType == CARD_NONE){ Serial.println("No SD card attached"); return; } showCardType(cardType); } void loop(){ bool menuEnable = true; int choice = 0; char menuStr[] = "MENU> D(rive F(ile Q(uit "; while (menuEnable) { choice = blockingMenu(menuStr); switch (choice) { case 'F': Serial.println("File"); break; case 'D': Serial.println("Drive"); break; case 'Q': Serial.println("Quit"); menuEnable = false; break; default: Serial.println("Unknown."); break; } delay(2000); } Serial.end(); }
#include "FS.h"
#include "SD.h"
#include "SPI.h"

void setup(){
    Serial.begin(9600);
    if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();
    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }
    showCardType(cardType);
}

void loop(){
  bool menuEnable = true;
  int choice = 0;
  char menuStr[] = "MENU> D(rive F(ile Q(uit ";
  while (menuEnable) {
    choice = blockingMenu(menuStr);
    switch (choice) {
      case 'F':
        Serial.println("File");
        break;
      case 'D':
        Serial.println("Drive");
        break;
      case 'Q':
        Serial.println("Quit");
        menuEnable = false;
        break;
      default:
        Serial.println("Unknown.");
        break;
    }
    delay(2000);
  }
  Serial.end();
}

第2のinoファイルが以下に。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
char blockingMenu(char *buf) {
int incomingByte = 0;
char selchar = 0;
Serial.print(buf);
while (selchar == 0) {
if (Serial.available() > 0) {
incomingByte = Serial.read();
if((incomingByte > 0) && (incomingByte < 128)) {
selchar = (char)incomingByte;
if (isLowerCase(selchar)) {
selchar -= 32;
}
}
}
}
if ((selchar < 0x20) || (selchar > 126)) {
Serial.print("0x");
Serial.println(selchar, HEX);
} else {
Serial.println(selchar);
}
return selchar;
}
char blockingMenu(char *buf) { int incomingByte = 0; char selchar = 0; Serial.print(buf); while (selchar == 0) { if (Serial.available() > 0) { incomingByte = Serial.read(); if((incomingByte > 0) && (incomingByte < 128)) { selchar = (char)incomingByte; if (isLowerCase(selchar)) { selchar -= 32; } } } } if ((selchar < 0x20) || (selchar > 126)) { Serial.print("0x"); Serial.println(selchar, HEX); } else { Serial.println(selchar); } return selchar; }
char blockingMenu(char *buf) {
  int incomingByte = 0;
  char selchar = 0;
  Serial.print(buf);
  while (selchar == 0) {
    if (Serial.available() > 0) {
      incomingByte = Serial.read();
      if((incomingByte > 0) && (incomingByte < 128)) {
        selchar = (char)incomingByte;
        if (isLowerCase(selchar)) {
          selchar -= 32;
        }
      }
    }
  }   
  if ((selchar < 0x20) || (selchar > 126)) {
    Serial.print("0x");
    Serial.println(selchar, HEX);
  } else {
    Serial.println(selchar);
  }
  return selchar;
}

しかし、上記のblockingMenuを再編集し、ビルドしなおしたところが、突如としてArduinoIDEから文句がつきました。第1のファイルの中で呼び出している

showCardType(cardType);

という関数が見当たらないというのです。上記は第3のファイルの中にあり、第3のファイルはまったく編集などしておりませぬ。呼び出されている関数が以下に。multi_INO_1

呼び出しているところが以下に。multi_INO_2

何が悪いの??

原因は第2のファイルの中かっこ抜け

Arduino素人の年寄にはなかなか原因がつかめませんでした。というのも上記の第1か第3、どちらかのファイルにエラーの原因がある筈だと思い込んでいたからです。まったくのC頭。しかし、Arduinoに慣れた人にはお分かりでしょう。Arduino IDEは実際にビルドする前に、複数の ino ファイルがあったらば「結合」して一本の長いファイルにしてから処理するのです。つまり、今回は 第1+第2+第3の順で3本の ino ファイルが結合されて処理された結果、第2のファイル内での中カッコ不足が、第3のファイルへ波及し、第3のファイルの先頭の関数が壊れているように見えたと。

なんだそんなことかい。

別のファイルに波及するなど全く想定外のトホホ。そこを直したら「メニュー」はちゃんとブロッキング動作するようになりました。こんな感じ。

menuWorking

頭の固いC頭の年寄の顛末トホホ。

トホホな疑問(56) Scilab、関数と同名の変数を定義してしまったときの挙動 へ戻る

トホホな疑問(58) LD__LIBRARY_PATH設定しているのにライブラリが見つからない? へ進む