Разбиение проекта Arduino на части, проблема с их сращиванием

Загорелся целью сделать прошивку для ардуины под адресную ленту, посмотрел примеры, +- разобрался. В С-подобные языки не умею, так что, разобрался как мог. В итоге, подошло время добавить смену режимов работы диодки. Тут и начались проблемы. Начал писать эффекты и тут понял, что если все ЭТО закинуть в один файл, будет нечитаемое месиво. Решил разделить на два файла проект, сделал это через Ctrl+Shift+N, дал имя файлу, закинул тестовые режимы и сохранил, из главного файла вызвал функцию с цветом из файла с эффектами, но получил ошибку, говорящую о необъявленной переменной (XXX was not declared in this scope). Почитал на разных форумах, че делать, но ответа не получил. Вопрос: Что делаю не так? Куски кода ниже.

MAIN FILE

#define LED_PIN 13       // пин ленты
#define NUMLEDS 36       // кол-во светодиодов
#define ORDER_GRB       // порядок цветов ORDER_GRB / ORDER_RGB / ORDER_BRG
#define COLOR_DEBTH 2   // цветовая глубина: 1, 2, 3 (в байтах)
// на меньшем цветовом разрешении скетч будет занимать в разы меньше места,
// но уменьшится и количество оттенков и уровней яркости!
// ВНИМАНИЕ! define настройки (ORDER_GRB и COLOR_DEBTH) делаются до подключения библиотеки!

#define pot_ligt A5            // пин, куда подключается потенциометр регулировки яркости
#define pot_mode A4            // пин, куда подключается потенциометр смены режимов
#define base_brt 200           // яркость ленты при включении (0-255)
int new_mode=0;

#include "microLED.h" // подключение библиотеки

LEDdata leds[NUMLEDS];  // буфер ленты типа LEDdata (размер зависит от COLOR_DEBTH)
microLED strip(leds, NUMLEDS, LED_PIN);  // объект лента
void setup() {
  
}
void mode_declare() {
  new_mode=round(analogRead(pot_mode)/20); // считывание информации с резистора 0-1023, деление на 20. Получаем 51 режим. FOR STACKOVERFLOW: Это ни на что не влияет, подготавливался к смене режимов
}

void change_mode() { 
  switch (new_mode) {
        //case 999: break;                           // пазуа
    case  2: red(); break;            // плавная смена цветов всей ленты
    case  9: flicker(); break;                 // случайный стробоскоп
    case 10: pulse_one_color_all(); break;     // пульсация одним цветом
    case 11: pulse_one_color_all_rev(); break; // пульсация со сменой цветов
   
    case 25: random_color_pop(); break;        // безумие случайных вспышек
    case 26: ems_lightsSTROBE(); break;        // полицейская мигалка

    case 29: matrix(); break;                  // зелёненькие бегают по кругу случайно
    case 30: new_rainbow_loop(); break;        // крутая плавная вращающаяся радуга
}
void loop() {
  
  // радуга!
  byte brightness=map(analogRead(pot_lig), 0, 1023, 0, 255); // переменная, хранящая яркость светодиодки 0-255, преобразует значения 0-1023 от потенциометра
  Serial.println(brightness);
  strip.setBrightness(brightness);    // яркость (0-255)
  
  static byte counter = 0;

  strip.show();
  
  delay(50);
}
SECONDARY FILE

/*void rainbow_fade() {                         //-m2-FADE ALL LEDS THROUGH HSV RAINBOW
  ihue++;
  if (ihue > 255) {
    ihue = 0;
  }
  for (int idex = 0 ; idex < LED_COUNT; idex++ ) {
    leds[idex] = CHSV(ihue, thissat, 255);
  }
  strip.show();
  if (safeDelay(thisdelay)) return;
}
*/
void red() {
  strip.clear();
  strip.fill(mCOLOR(RED));
  strip.show();
}
void blue() {
  strip.clear();
  strip.fill(mCOLOR(BLUE));
  strip.show();
}
void cyan() {
  strip.clear();
  strip.fill(mCOLOR(CYAN));
  strip.show();
}

Ответы (1 шт):

Автор решения: Roman Konoval

Чтобы объяснить в чем тут проблема нужно вкратце рассказать, как происходит сборка программы на С или C++. Она состоит из двух этапов:

  1. Компиляция
  2. Линковка

Компиляция

Каждый *.c, *.cpp (а для arduino еще и *.ino) файл компилируется отдельно компилятором. В результате из каждого файла .c(.cpp, *.ino) получаем объектный файл *.o.

Обратите внимание, что в исходных файлах могут использоваться функции или переменные, которые не определены в самом файле. Например, это могут быть функции из библиотек или из других файлов, которые вы сами создали. Однако, для того, чтобы компилятор знал, какого они типа и правильно ли идет к ним обращение, он должен знать объявление каждой какой функции или переменной. Объявление отличается от определения тем, что задает только тип функции или переменной, но не ее реализацию (для переменной не выделяет под нее места).

Пример, если у вас есть функция

void red() {
  strip.clear();
  strip.fill(mCOLOR(RED));
  strip.show();
}

Чтобы использовать ее в другом c/cpp/ino файле, нужно чтобы где-то перед ее использованием
в этом файле было ее объявление, а именно строка:

void red();

Но обычно для объявления функций и переменных используют заголовочные файлы, т.е. *.h или *.hpp.

Для файла скажем mylibrary.cpp, который содержит определение функции и переменной:

void myfun() {
  ...
}

SomeType myvar;

в файле mylibrary.h записывают объявление:

void myfun();
extern SomeType myvar;

А потом используют #include "mylibraryr.h" в другом файле, скажем myapp.ino, чтобы буквально включить этот заголовочный файл в myapp.ino. Таким образом при компиляции myapp.ino компилятор будет знать тип myfun и myvar и сможет успешно скомпилировать его. Если же, скажем, myfun используется в myapp.ino без объявления (прямого или через include), то будет ошибка myfun was not declared in this scope.

Линковка

При линковке уже линкер получает все объектные файлы плюс библиотеки и создает один исполняемый файл. При этом он "находит" все нужные функции и переменные, которые могут быть в разных объектных файлах, проверяет, что типы совпадают подставляет их адреса в результирующем выполняемом файле в местах, где они использовались в объектных файлах.

Итог

Проблемы в этом конкретном случае:

  1. функции из SECONDARY FILE используются без объявления в главном файле
  2. переменная strip используется в SECONDARY FILE без объявления

Самый простой способ исправить, это добавить объявления в главном файле (до использования функций)

void red();
void blue();
void cyan();

и объявить переменную strip в SECONDARY FILE:

#include "microLED.h" // это нужно чтоб можно было тут использовать тип microLED

extern microLED strip;
→ Ссылка