PROGMEM и массив указателей на функции

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Добрый день, камрады

Опять проклятый PROGMEM не дает мне покоя:

Есть такой скетч:

#define TYPE_0 0
#define TYPE_1 1
#define TYPE_2 2

struct menuStruct {
  const byte type;
  const byte param1;
  const byte param2;
  const byte param3;
  const byte param4;
};

const menuStruct menu[] PROGMEM = {
  {TYPE_0, 0, 0,  0, 0 },
  {TYPE_1, 0, 0,  0, 0 },
  {TYPE_2, 0, 0,  0, 0 },
  {TYPE_0, 0, 0,  1, 0 },
  {TYPE_1, 0, 0,  0, 0 },
  {TYPE_2, 0, 0,  0, 0 },
};

void testFunc1();
void testFunc2();
void testFunc3();
typedef void (*ptrFunc)();
const ptrFunc funcArray[] PROGMEM = {&testFunc1, &testFunc2, testFunc3};

void setup() {
  Serial.begin(57600);
  Serial.println("Started");
  delay(1000);
}

void loop() {


}

void testFunc1() {
  Serial.println("testFunc 1");
}

void testFunc2() {
  Serial.println("testFunc 2");
}

void testFunc3() {
  Serial.println("testFunc 3");
}

 

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Черт, ни удалить, ни отредактировать не могу после сохранения. Продолжение в этом письме:

Если в loop ставим такой код, то все работает нормально:


  funcArray[0]();
  delay(1000);
  funcArray[1]();
  delay(1000);
  funcArray[2]();
  delay(1000);

а если такой, то ардуина уходит в глухую перезагрузку:

  funcArray[pgm_read_byte(&menu[0].type)]();
  delay(1000);
  funcArray[pgm_read_byte(&menu[1].type)]();
  delay(1000);
  funcArray[pgm_read_byte(&menu[2].type)]();
  delay(1000);

При этом если считать pgm_read_byte(&menu[0].type) в переменную и вывести в сериал - видим корректные значения. Вопрос - что я делаю не так?

rkit
Offline
Зарегистрирован: 23.11.2016

Не надо в progmem хранить указатели на функции. В этом нет никакого смысла.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

rkit пишет:

Не надо в progmem хранить указатели на функции. В этом нет никакого смысла.

Понятно, что указатель это не текстовая строка и особой экономии памяти не получим. Но сам факт - почему не работает?

rkit
Offline
Зарегистрирован: 23.11.2016

Нет. Вообще никакой экономии не будет, и даже наоборот.

Не работает потому что ты совсем не понимаешь что делаешь. Указатель на функцию это не индекс в массиве.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

rkit пишет:

Не работает потому что ты совсем не понимаешь что делаешь. Указатель на функцию это не индекс в массиве.

Не отрицаю что я жестко туплю, вот и пытаюсь разобраться как это работает. Как правильно обратиться к функции через указатель, лежащий в массиве?

rkit
Offline
Зарегистрирован: 23.11.2016

array[1]()

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

в работающем варианте так и сделано. Но мне нужно индекс взять из поля type структуры menu, тоже лежащей в progmem (что и пытаюсь проделать в неработающем примере).

То есть у меня array[1]() работает.

Но в то же время 

array[pgm_read_byte(&menu[0].type)](); -  не работает

byte a = pgm_read_byte(&menu[0].type); (проверяю, значение а равно единице) 

array[a]();  - не работает.

 

rkit
Offline
Зарегистрирован: 23.11.2016

Потому что у тебя не массив указателей на функции, а массив указателей на флеш-память, в которой лежат указатели на функции.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

а как должна быть правильно реализована подобная конструкция?

rkit
Offline
Зарегистрирован: 23.11.2016

Без progmem. А еще лучше без указателей на функцию, если это просто меню.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

rkit пишет:

Без progmem. А еще лучше без указателей на функцию, если это просто меню.

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

P.S. Опять меня изучение ардуины в какие-то дебри завело?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Dinosaur пишет:
Про без progmem - это я услышал. а как обойтись без указателей на функцию? Из меню требуется запускать функции, сейчас использую указатели (как вы уже поняли по первому вопросу). как уйти от этого (и почему неправильно использовать для этого указатели)? Через флаги? Прибить гвоздями в обработчике нажатия кнопки ок (если номер пункта такой, то, то запускаем эту функцию, а если такой, то другую) - я думал что этот подход неверный?

И кто вам использовать лямда-функцию запретил? #298

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

qwone пишет:
И кто вам использовать лямда-функцию запретил? #298

Видимо отсутствие жопыта. Смотрю в ваш пост(который 298), вроде и все буквы знакомые, а смысл уловить не могу. Можете тезисно раскрыть идею?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016
Keys.Do_Right  = [] {goPage(page0);};

Keys.Do_Right это указатель на функцию

 [] {goPage(page0);} это сама лямда-функция. 

Ну а меню строится на принципах цифрового автомата. Любой переход на следующий пункт меню это переход в состояние где надо напечатать новую страницу и поменять обработчики кнопок. Ведь естественно действие кнопок в другом состоянии другое,чем в предыдущем состоянии.

ПС:#222 этот пост и немного ниже покажет как писать меню через цифровой автомат

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

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

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

qwone пишет:
Ведь естественно действие кнопок в другом состоянии другое,чем в предыдущем состоянии.

почему это естественно? кнопка + увеличивает значение переменной, - уменьшает..., select подтверждает выбор. То есть событие одно передается в функцию, а как она уже там будет его разруливать - определим в функции. Или я опять не правильно вас понял?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Dinosaur пишет:
почему это естественно? кнопка + увеличивает значение переменной, - уменьшает..., select подтверждает выбор. То есть событие одно передается в функцию, а как она уже там будет его разруливать - определим в функции. Или я опять не правильно вас понял?

Разумеется.Во-первых надо определится сколько кнопок у вас есть. Потом разработать переходы в меню.Если у вас 3 кнопки(+ - sel), то надо делать переключение режимов бег по пунктам меню/ изменение меню. И много много еще, что очень надо для нормальной работы меню,но при этом очень записывает код. Так что даже если вам к примеру дать рабочий код пусть структуру и прогмем вы все равно в нем запутаетесь.

ПС:#236 смотрите мне этого не жалко или https://habr.com/ru/post/257607/

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

вот по мотивам этой статьи и делал, но с поправкой на свой невысокий уровень :). Все работает, но вот указатели и прогмемы мне покоя не дают )))

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Ну а через лямду прячется в PROGMEM/ а точнее во флеш вся функция ,а не только указатель.  Это очень сильно упрощает код для тех кто понимает.

typedef void (*ptrFunc)();
ptrFunc Func;

void setup() {
  Serial.begin(57600);
  Serial.println("Started");
  Func = [] {Serial.println("testFunc");};
}
void loop() {
  delay(1000);
  Func();
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Dinosaur пишет:

Если в loop ставим такой код, то все работает нормально:


  funcArray[0]();
  delay(1000);
  funcArray[1]();
  delay(1000);
  funcArray[2]();
  delay(1000);

а если такой, то ардуина уходит в глухую перезагрузку:

  funcArray[pgm_read_byte(&menu[0].type)]();
  delay(1000);
  funcArray[pgm_read_byte(&menu[1].type)]();
  delay(1000);
  funcArray[pgm_read_byte(&menu[2].type)]();
  delay(1000);

При этом если считать pgm_read_byte(&menu[0].type) в переменную и вывести в сериал - видим корректные значения. Вопрос - что я делаю не так?

В первом случае - конcтанта потому она просто заменяется нужным адресом на этапе компиляции. Именно потому и кажется, что всё работает. Во втором случае Вы не читаете из PROGMEM! 

Чтобы разобраться попробуйте переписать первый случай но не с конcтантой, а с переменной (нетривиальной, чтобы на этапе компиляции не вычислялась) - тоже не будет работать. Чтобы работать с переменной, нужно адрес функции явно из прогмем читать, а не просто писать funcArray[i].

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

qwone пишет:

Ну а через лямду прячется в PROGMEM/ а точнее во флеш вся функция 

А что без лямбд функция живёт не во флеше? А где?

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

ЕвгенийП пишет:
В первом случае - конcтанта потому она просто заменяется нужным адресом на этапе компиляции. Именно потому и кажется, что всё работает. Во втором случае Вы не читаете из PROGMEM!

Евгений, спасибо за объяснение причины. Вопрос, почему тогда:

  for (byte i = 0; i < 3; i++) {
    byte idx = pgm_read_byte(&menu[i].type);
    Serial.print("idx - ");
    Serial.println(idx);
    delay(1000);
  }

возвращает правильные значения? Тоже компилятор вычисляет? Или все-таки чтение значения из menu[i].type проходит верно?

Но стоит добавить в цикл 

    funcArray[idx]();

получаем в порт полную фигню. Уже не перезагрузку платы, как ранее, но тоже понятно что куда то не туда обращаюсь. То есть получается я как то не так пытаюсь обратиться к указателю функции (или как то неправильно я функции и(или) указатели сложил в прогмем?

typedef void (*ptrFunc)();
const ptrFunc funcArray[] PROGMEM = {&testFunc1, &testFunc2, testFunc3};

ЕвгенийП пишет:
Чтобы разобраться попробуйте переписать первый случай но не с конcтантой, а с переменной

Так progmem же используется для констант?

ЕвгенийП пишет:
Чтобы работать с переменной, нужно адрес функции явно из прогмем читать, а не просто писать funcArray[i].

Перекурил несколько сайтов, везде такой пример чтения данных из progmem  через указатель описан. Или я не улавливаю Вашу мысль? Или я где то глобально свернул с верного пути в своих рассуждениях?

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Dinosaur пишет:
ЕвгенийП пишет:
Чтобы работать с переменной, нужно адрес функции явно из прогмем читать, а не просто писать funcArray[i].

Перекурил несколько сайтов, везде такой пример чтения данных из progmem  через указатель описан. Или я не улавливаю Вашу мысль? Или я где то глобально свернул с верного пути в своих рассуждениях?

Dinosaur. Вот Вы можете отличить атом от молекулы. В некоторых случаях это одно и тоже, а с другой стороны это разные вещи. По все же подумайте как отличить атом от молекулы.Это очень фундаментально.

 Это я к тому, что есть ОЗУ и есть Флеш-память. Для Ардуины это совершенно разные памяти. Но когда рисовался язык Си это было одно и тоже.То есть находилось в одном адресном пространстве, и ОЗУ и ПЗУ. По факту Си Ардуины работает с ОЗУ и только с ней. А с данными с PROGMEM только через  PROGMEM и pgm_read_XXX  размещение и извлечение. Не буду говорить про организацию ссылочных классов, которые могут реально подменять работу с флеш или EEROM как с ОЗУ c точки зрения Си.

typedef void (*ptrFunc)();
void testFunc1();
void testFunc2();
void testFunc3();
typedef void (*ptrFunc)();
const ptrFunc funcArray[] PROGMEM = {&testFunc1, &testFunc2, &testFunc3};


void setup() {
  Serial.begin(57600);
  Serial.println("Started");

}
void loop() {
  delay(1000);
  ptrFunc Func =  //<- создать указатель на функцию
    (ptrFunc)   //<- операция приведения к указателю на функцию
    pgm_read_word(&funcArray[1]); //<- извлечь адресс функции из PROGMEM
  Func(); //<- запустить функцию
}
void testFunc1() {
  Serial.println("testFunc 1");
}

void testFunc2() {
  Serial.println("testFunc 2");
}

void testFunc3() {
  Serial.println("testFunc 3");
}

Вот такие "различия атомов и молекул" у Ардуино.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Dinosaur пишет:

Так progmem же используется для констант?

Я миел в виду индекс массива.

А по остальному, знаете, Вы путаетесь что у Вас где. Посмотрите вот этот пост (ну и тему вверх=вниз). Там я пытался пояснить разницу между хранением в прогмем указателя, сами данных и "и того, и другого".

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

rkit пишет:

Не надо в progmem хранить указатели на функции. В этом нет никакого смысла.

С этим утверждением можно поспорить. Например при реализации табличного перехода, вполне себе указатели могут быть в массивах, а массив в прогмем.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

brokly пишет:

при реализации табличного перехода, вполне себе указатели могут быть в массивах, а массив в прогмем.

Читать их потом накладно оттудова. :)