Проблема с массивом массивов

seri0shka

✩✩✩✩✩✩✩
4 Авг 2021
8
0
Простой пример из статьи "РАБОТА С PROGMEM ПАМЯТЬЮ", раздел "Массив массивов", немного доработан. Выдаёт в порт некорректные данные.
C++:
int x;
// массивы
const uint8_t data0[] PROGMEM = {10, 20, 30, 40, 50};
const uint8_t data1[] PROGMEM = {60, 70, 80, 90, 100};
const uint8_t data2[] PROGMEM = {110, 120, 130, 140, 150};
const uint8_t data3[] PROGMEM = {160, 170, 180, 190, 200};
// таблица ссылок
const uint8_t* const data_array[] PROGMEM = {data0, data1, data2, data3};
void setup() {
  Serial.begin(9600);
  // выведет 170, второй элемент четвёртого массива
  Serial.println(pgm_read_byte(&data_array[3][1]));
}
void loop() {
  if (x < 4) {
    Serial.print(x);
    Serial.print('-');
    Serial.println(pgm_read_byte(&data_array[x][1]));
    x++;
  }
}
Выдаёт:
170
0-148
1-224
2-148
3-67
Должно быть:
170
0-20
1-70
2-120
3-170
Если вместо х указывать число, всё работает нормально.
 

Сотнег

★★★★★★★
15 Янв 2020
4,446
1,520
@seri0shka,
если у вас все данные статические (все массивы в progmem записаны),
почему просто двумерный массив не использовать?

А с чтением проблема из-за того, что вы читаете 1 байт (pgm_read_byte),
а адрес этого байта у вас хранится в другом месте, который вы не читаете.
Наверное...
 
Изменено:
  • Лойс +1
Реакции: poty

seri0shka

✩✩✩✩✩✩✩
4 Авг 2021
8
0
двумерный массив мне не подходит, потому что все массивы будут разного размера
 

seri0shka

✩✩✩✩✩✩✩
4 Авг 2021
8
0
С этого начинал. Всё так само, только результаты длиннее.
Можете сами попробовать. Кроме Ардуино ничего не требуется.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
А где у Вас инициализируется x?
Я считаю, что использование массива ссылок на одномерный массив как двумерного массива, если массив ссылок располагается в PROGMEM, неправомерно. PROGMEM не поддерживает тип "адрес", поэтому перед использованием его нужно прочитать в нормальную ссылку и по ней уже действовать. В случае константных чисел компилятор может подставить правильный адрес непосредственно на этапе компиляции в рамках оптимизации и это срабатывает, но это неправильно, так как с разными опциями это будет работать по-разному.
Используйте примеры со строками, где обращение идёт через указатели в обычной области переменных, либо переносите массив ссылок в область переменных.
 
Изменено:

poty

★★★★★★✩
19 Фев 2020
3,261
948
Вы можете использовать ссылки. Для ссылки нужно будет всего 2 байта.
 

bort707

★★★★★★✩
21 Сен 2020
3,066
915
Только жаль, для меня это лишних 80 байт динамической памяти ( по 2 байта на каждый указатель)
Можно и с указателями в ПРОГМЕМ. Только тогда, как указал @poty, необходимо сначала прочитать адрес из массива указателей функцией pgm_read_word_near(), потом к этому адресу применить pgm_read_byte() :
C++:
int x;
// массивы
const uint8_t data0[] PROGMEM = {10, 20, 30, 40, 50};
const uint8_t data1[] PROGMEM = {60, 70, 80, 90, 100};
const uint8_t data2[] PROGMEM = {110, 120, 130, 140, 150};
const uint8_t data3[] PROGMEM = {160, 170, 180, 190, 200};
// таблица адресов массивов как чисел uint16_t
const uint16_t data_array[] PROGMEM = {data0, data1, data2, data3};
void setup() {
  Serial.begin(9600);
  // выведет 170, второй элемент четвёртого массива
  Serial.println(pgm_read_byte(pgm_read_word_near(data_array + 3)+1));
}
void loop() {
  if (x < 4) {
    Serial.print(x);
    Serial.print('-');
    Serial.println(pgm_read_byte(pgm_read_word_near(data_array +x)+1));
    x++;
  }
}
результат
170
0-20
1-70
2-120
3-170

Обратите внимание на два момента
  • вместо оператора индекса при доступе к массивам используем сложение
  • массив для адресов data_array[] описан не как массив ссылок, а как массив обычных целых чисел uint16_t
 
  • Лойс +1
Реакции: poty

seri0shka

✩✩✩✩✩✩✩
4 Авг 2021
8
0
Спасибо! Это работает в ардуино 1.8.1. Моя ардуино 1.6.5 ругается:
sketch_aug07a:10: error: invalid conversion from 'const uint8_t* {aka const unsigned char*}' to 'uint8_t {aka unsigned char}' [-fpermissive]
Я уже вчера вечером сделал по другому- применил 2 массива, оба байтовые числовые.
Массив 1, самый большой - все данные (то, что в примерах разбито на 4 массива, только у меня данных намного больше).
Массив 2- размеры каждого блока данных из первого массива, так как все блоки разные по размеру.
дальше просто каждый раз вычисляется адрес начала блока для выборки. Можно было не вычислять, а задать сразу, но размеры могут меняться в процессе отладки, ну и уже будут не byte. a int.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
@seri0shka, нужно просто сделать преобразование. Приведите конкретную строку, на которую ругается.
 

seri0shka

✩✩✩✩✩✩✩
4 Авг 2021
8
0
sketch_aug07c:10: error: invalid conversion from 'const uint8_t* {aka const unsigned char*}' to 'uint16_t {aka unsigned int}' [-fpermissive]
И выделяет строку 10:
const uint16_t data_array[] PROGMEM = {data0, data1, data2, data3};
 

Sergo_ST

★★★★★★✩
15 Мар 2020
993
831
Массив ссылок можно замаскировать и никаких ошибок не будет.
C++:
const uint16_t data_array[] PROGMEM = {(uint16_t)data0, (uint16_t)data1, (uint16_t)data2, (uint16_t)data3};
 

seri0shka

✩✩✩✩✩✩✩
4 Авг 2021
8
0
Ура! Спасибо! Это именно то, что нужно! было...вчера... :rolleyes:
На будущее пригодится. В данном случае отказался полностью, экономия памяти не очень большая в моём случае, а логическая часть усложняется сильно, в результате вычисления съедают сэкономленное.
 

bort707

★★★★★★✩
21 Сен 2020
3,066
915
Какие вычисления?:) - это вы про запись (data_array + x) вместо data_array[x] ?
А Вы в курсе что элемент массива так и извлекается - прибавлением индекса к базовому адреса массива? :)- так что вычислений тут ровно столько же