ARDUINO Решаем проблему с struct, забываем про loop - обман ради правильного исполнения кода.

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
Всем привет. Не особо упоротый программист, однако как хобби.

В общем понадобились в проекте такие типы данный С++ как "struct". В общем посмотрите на две реализации - одна работает, а другая нет (просто не компилируется).

- работающая версия
AsMain.png

- стандартная, но не работающая версия
Standart.png

В чем разница? С точки зрения логики там одно и тоже.

Сначала по второму варианту (стандартному).
Во втором, стандартном варианте, объявляем структуру "config" в области видимости выше чем setup и loop, и там же создаем два объекта типа "config" - идея простая - надо обеспечить видимость в loop, ведь если сделать это в setup, то в loop'е этих наших переменных просто не будет, и как с ними работать?! Компиляция приводит к какой-то билибирде - пишет, что мол "error: 'c2' does not name a type(id, pin)". Замечу, что изменить структуры можно двумя способами:
1. сразу инициализировать используя { a, b, c,....} (фигурные скобки c перечислением значений) - что не есть изменение(!), или 2. заполнять через точку. Так вот заполнение/изменение через "." (точку) не работает во втором случае. Однако, изменять/заполнять эти структуры в setup через точку получается, не ругается. НО тогда не получается работать с ними в loop.

В общем, решение такое. Функции setup и loop - это обязательные функции, которые выполняются сначала один раз setup, потом бесконечно loop. Ну тогда нам просто надо чтобы до loop выполнение просто не дошло. Первичные установки, настройки делаем в setup, а потом пишем цикл через while(1){...}. В этом цикле пишем весь наш основной рабочий код, точно также как это делается в loop. Цикл будет выполняться всегда, бесконечно, точно также как loop, и управление уже не отдаст никому, ведь 1 - это всегда true, т.е. loop управление никогда не получит. Так вот в setup уже можно манипулировать со структурами без проблем. К тому же именно такая логика , как оказалось, используется при программировании микроконтроллеров в чистом С/C++ (без фреймворка Arduino). Там нет функции типа setup, но обязательна функция int main(){/* код программы */ return 0; }, которая выполняется постоянно перезапускаясь, как loop (точнее loop как main). Объявление и первичная инициализация переменных там происходит перед main(), и проблем со struct там нет.

Что делает фреймворк Ардуино с исходным кодом (скетчем), мне не понятно, но явно какую-то пакость и ради чего?! Видимо ради того, чтобы было красиво: вот есть setup - пишите сюда настройки, вот есть loop - а сюда пишите основной код. А то что происходят непонятные вещи казалось бы с элементарными операциями (типа изменить значение одного из поля в struct) это как-то видимо пофигу.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
Как я вижу. Ты путаешь инициацию с исполняемым кодом. Т.е. начальная инициация переменной (хоть и структуры) происходит на этапе компиляции. а когда ты присваиваешь полям какие то значения, то это уже исполняемый код, он должен быть в разделе кода, а не в разделе данных.
Как сделать видимые переменные и в setup и в loop
C++:
int Variable1;
void setup()
{Variable1=10;
Serial.begin(9600);
}
void loop()
{Serial.println(Variable1);
delay(10000);
}
Вроде нигде не ошибся. тоже и со структурой. Объявляешь в самом верху и сразу инициируешь. если сразу не выходит -присваивай в setup, используй где хочешь ниже по коду.
 

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
Пробую. Итак, объявляю структуру с1 до setup. С ней спокойно работаю и в setup и в loop.
Однако, если создать структуру с2 в setup'е, то понятно что в loop уже с ней не поработать (ее там нету).

Задачу "динамического" создания новых объектов таким образом надо решать либо в setup (заблокировав запуск loop),
либо в самом loop.

Пример гипотетический.
Устройство загружается, считывая настройки с SD-карты и делает первичную настройку. Через некоторое время, уже после первичной настройки, пользователь отправляет в Serial команду на то, чтобы наше устройство подключило новый сенсор к указанному в команде пину, сделало его настройку и включило, так сказать, в очередь слежение за этим новым датчиком.
Так даже слово setup переводится как "настройка/установка" - короче первичная настройка устройства. И на мой личный взгляд настройка посредством подгрузки данных с SD-карты это тоже задача смысла setup, а это означает, что новые объекты управления создаются один раз, но никак не в режиме loop. Ну а вот когда все настроено, то пожалуйста зацикливайся и отрабатывай.

Т.е. решение задачи динамического создания новых объектов решается только, если работать либо в setup (запуская там бесконечный цикл для непосредственно работы устройства) либо в loop (циклически проверяя те или иные флаги, срабатывание которых запускает функции по созданию новых объектов управления). И в этом смысле то, что создатели фреймворка "нагородили" с setup и loop, просто нелогично с человеческой точки зрения (а не с точки зрения программиста - более-менее опытный программист все равно "хакнет" как ему надо), а как я понимаю именно к человечности они и приближали свой ардуино, чтобы снизить "порог вхождения". Только вот эта человечность оказывается не логичной.

Кстати, сегодня немного пошерстил по неру-инету - и там действительно, встречаются "глупые", мягко сказать, вопросы - "а собственно тогда что они хотели сказать своими setup и loop" ? И правда, в чистом С++ есть main(), из нее уже делаем/изменяем все и не паримся, хоть бесконечные циклы запускаем, хоть устанавливаем их. А тут получается у нас два абсолютно отдельных места, где мы можем кодить программу, да еще и названия, на мой взгляд, сбивают с толку. Но, например, объект, именно объект (а не класс) Serial работает прекрасно - ведь он создается еще до setup и до loop через подключение фреймворка <Arduino.h> - что даже не видно нам, хотя возможно нам он и не нужен в работе микроконтроллера - обычно не нужен!!! Но фреймворк нам его все равно сунет в наш код).
В общем, да, вы верно указали на разницу между объявлением и инициализацией объектов/структур. Только я немного о другом :) .

(с2 создали в setup, а в loop уже "приехали")
Standart.png
 

Александр Симонов

★★★★✩✩✩
2 Авг 2018
727
207
Пробую. Итак, объявляю структуру с1 до setup. С ней спокойно работаю и в setup и в loop.
Однако, если создать структуру с2 в setup'е, то понятно что в loop уже с ней не поработать (ее там нету).

Задачу "динамического" создания новых объектов таким образом надо решать либо в setup (заблокировав запуск loop),
либо в самом loop.

Пример гипотетический.
Устройство загружается, считывая настройки с SD-карты и делает первичную настройку. Через некоторое время, уже после первичной настройки, пользователь отправляет в Serial команду на то, чтобы наше устройство подключило новый сенсор к указанному в команде пину, сделало его настройку и включило, так сказать, в очередь слежение за этим новым датчиком.
Так даже слово setup переводится как "настройка/установка" - короче первичная настройка устройства. И на мой личный взгляд настройка посредством подгрузки данных с SD-карты это тоже задача смысла setup, а это означает, что новые объекты управления создаются один раз, но никак не в режиме loop. Ну а вот когда все настроено, то пожалуйста зацикливайся и отрабатывай.

Т.е. решение задачи динамического создания новых объектов решается только, если работать либо в setup (запуская там бесконечный цикл для непосредственно работы устройства) либо в loop (циклически проверяя те или иные флаги, срабатывание которых запускает функции по созданию новых объектов управления). И в этом смысле то, что создатели фреймворка "нагородили" с setup и loop, просто нелогично с человеческой точки зрения (а не с точки зрения программиста - более-менее опытный программист все равно "хакнет" как ему надо), а как я понимаю именно к человечности они и приближали свой ардуино, чтобы снизить "порог вхождения". Только вот эта человечность оказывается не логичной.

Кстати, сегодня немного пошерстил по неру-инету - и там действительно, встречаются "глупые", мягко сказать, вопросы - "а собственно тогда что они хотели сказать своими setup и loop" ? И правда, в чистом С++ есть main(), из нее уже делаем/изменяем все и не паримся, хоть бесконечные циклы запускаем, хоть устанавливаем их. А тут получается у нас два абсолютно отдельных места, где мы можем кодить программу, да еще и названия, на мой взгляд, сбивают с толку. Но, например, объект, именно объект (а не класс) Serial работает прекрасно - ведь он создается еще до setup и до loop через подключение фреймворка <Arduino.h> - что даже не видно нам, хотя возможно нам он и не нужен в работе микроконтроллера - обычно не нужен!!! Но фреймворк нам его все равно сунет в наш код).
В общем, да, вы верно указали на разницу между объявлением и инициализацией объектов/структур. Только я немного о другом :) .

(с2 создали в setup, а в loop уже "приехали")
Посмотреть вложение 5241
Много букв, всё не читал. Почему не объявить c2 так же глобальной переменной, как и c1. Зачем объявление c2 в setup-е?
 

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
"Много букв, всё не читал. Почему не объявить c2 так же глобальной переменной, как и c1. Зачем объявление c2 в setup-е? " - мдя... честно, как минимум это не красиво, не дослушав собеседника перебивать его задавая ему вопрос, ответ на который собеседник как раз ему и давал... не находите ли это именно так? ИМХО.

А если без лирики. То ответ на вопрос уже дан мною выше.
 

Александр Симонов

★★★★✩✩✩
2 Авг 2018
727
207
"Много букв, всё не читал. Почему не объявить c2 так же глобальной переменной, как и c1. Зачем объявление c2 в setup-е? " - мдя... честно, как минимум это не красиво, не дослушав собеседника перебивать его задавая ему вопрос, ответ на который собеседник как раз ему и давал... не находите ли это именно так? ИМХО.

А если без лирики. То ответ на вопрос уже дан мною выше.
А по мне, так простыни катать и лирику какую-то отвелченную разводить, это неуважение к чужому времени. Тебе нужны динамические массивы (vector). Структуры также можно создавать динамически.
 

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
"А по мне, так простыни катать и лирику какую-то отвлеченную разводить, это неуважение к чужому времени." - лично Вас именно, я не зазывал ни читать, ни отвечать, ни комментировать, и плату за входной билет тоже не брал. Это был ваш личный выбор, поэтому какие ко мне то претензии-то?!. Другое дело, что если бы я "напросился" к вам в собеседники, и начал бы "рассусоливать", то да это скорее всего неуважение. ИМХО.

"Тебе нужны динамические массивы (vector). Структуры также можно создавать динамически." - ну это вопрос уже второстепенный что именно создавать. Другое дело, что создавать динамически (и при этом иметь возможность менять их!) - надо либо в setup, либо в loop, и не прыгать из одной функции в другую. Именно об этом я и говорил в заголовочном своем сообщении.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
Объявление переменной заранее не есть динамическое объявление. Выделение памяти и указатели помогут спасти такое положение, когда датчики подключаются на ходу. Не пробовал на ходу подключать к пинам, знаю, что с i2c можно так делать.


Не хочется что бы выполнялся цикл loop ? да ради бога! полностью линейная программа в setup и не забыть в конце добавить выключение ардуины. ) по мне так разделение выполнения программы на 2 части очень правильно и логичное решение. Очень напоминает процедуру обработки сообщений в виндовс. но это все отвлеченная теория. Вопрос в необходимых требованиях. Да, и условия видимости переменных очень правильные условия, ноги у них растут из языка С. Позволяет экономично расходовать и так небольшую память.

Вернемся к практике. Madcape, можешь сказать какая у тебя задача с переменными ? Я не гуру программирования на С, но за 30 лет барахтанья в этом деле мозг уже повернут как надо, может предложу тебе алгоритм. Если же это просто теоретизация , то... ну упражняйся чо;)

Кстати, если хорошо поискать, то есть и решения для многозадачности ардуины, где можно запустить не 1, а сразу несколько циклов loop
 

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
Спасибо. В принципе вроде бы побеждаю потихоньку.Хотя вот сейчас бьюсь над тем, чтобы изменять размер массива указателей на объекты моего класса. Вот если взять простой интовый массив и и стандартный скрипт, то вроде бы ясно че к чему (функция заглатывает исходный интовый массив, создает новый интовый массив размером на 1 болььше, копирует значение из старого и еще добавляет в конец наше новое значение value, а потом зачищает прежнее место в ОЗУ, и меняет адрес указателя arr (кой указывал на прежний массив) на новый от newArr).

C++:
void push_back (int *&arr, int &size, const int value){
  
    int *newArr = new int[size + 1];
  
    for (int i = 0; i < size; i++){
      newArr[i] = arr[i];
    }
  
    newArr[size] = value;
  
    delete[] arr; 
    arr = newArr;
}
Так вот аналогично надо работать не с интовым массивом, а с массивом в котором указатели на мои объекты моего класса, ну и добавлять в этот массив тоже новый объект моего класса. Что-то не соображу как... пока ищу ответ на просторах рунета.
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
раньше делали так, упрощенно
класс Пример
{целое Поле1=1;
целое Поле2=2;
укзатель СледующийЭлемень=nil}
Изначально 1 элемент, создаешь второй и укзателю 1-ого элементу присваиваешь адрес. И так далее.
Пишутся несколько функций по вставке, удалению , подсчету , поиску элементов и можно работать.
 

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
Если честно не очень понял вашу идею... (вроде костыля... но почему и зачем?)
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,159
1,267
Москва
Это не моя идея, а стандартная практика работы со связанными списками. Очень простое решение по части использования именно классов. конечно тип байт хранить таким образом экономически не выгодно..
 

Madcape

✩✩✩✩✩✩✩
22 Окт 2019
15
2
"Связанные списки" (вычитал https://habr.com/ru/post/339656/ подробнее)

связанные списки.png

// Узел связанного списка
sometype object; // тот же объекта нашего какого-то класса
Node* next; // указатель на следующий объект

... вроде начал понимать идею. Спасибо. Буду курить )