Как различить массив в RAM и в PROGMEM?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Собственно, нужно написать функцию (семейство одноименных функций), которые будут адекватно работать как с массивами в оперативной памяти, так и во флеш-памяти.

Для символьных массивов можно использовать типы данных char и __FlashStringHelper соответственно. А как быть с массивами других типов?

void myFunc(char* ch); // работает с массивами в оперативной памяти
void myFunc(__FlashStringHelper* ch); // работает с массивами в flash

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Создать свой фейковый пустой тип данных по подобию __FlashStringHelper и аргумент приводить к нему?

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

Можно воспользоваться тем, что массив в PROGMEM всегда const и одной хитростью процедуры поиска нужной функции при перегрузке, а именно: если для обычных переменных модификаторы const и volatile не создают разницы (функции считаются одинаковыми), то для указателей и ссылок - ещё как! А массивы, как раз и есть указатели.

Вот пример Здесь функция doInt перегружена а вся разница как раз в const.

#include <avr/pgmspace.h>

void doInt(const int * n) {
	Serial.print("doProgMemInt *:");
	for (int i = 0; i < 3; i++) {
		Serial.print(' ');
		Serial.print(pgm_read_word_near(n+i));
	}
	Serial.println();
}

void doInt(int * n) {
	Serial.print("doInt *:");
	for (int i = 0; i < 3; i++) {
		Serial.print(' ');
		Serial.print(n[i]);
	}
	Serial.println();
}

const int PROGMEM pn[3] = {3, 2, 1};
int n[3] = {1, 2, 3};

void setup(void) {
	Serial.begin(9600);

	doInt(n);
	doInt(pn);
}

void loop(void) {}

Одно ограничение - массивы не в PROGMEM не должны быть const, чтобы не принять их за PROGMEM'ые

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

К великому сожалению авторы GCC, не скажу про другие компиляторы, не дали программисту способа использовать в шаблонах или перегрузках известную компилятору информацию об АТРИБУТАХ переменной. :((

Остается "расово-верный" путь, истиного ООП. :))

Нужно создать класс для массивов, который содержит в себе информацию о местоположении (РАМ/РОМ) своих данных. При некой ловкости, как недавно показал ДетСимен, можно даже инициализировать такой массив "как настоящий" ;)) Хотя я бы, все таки выбрал путь конструктора.

Тогда функции обработки будут всегда одинаковые - для этого класса. Их можно вообще методами сделать. И преобразовалки типа из обычного массива, причем можно как с копированием, так и с переносом только указателя.

Путь, предложенный Женей оставляет огромный простор для "стрельбы себе в ногу". Хотя хозяин - барин! ;))

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

Сделать тот же flashstringhelper для других типов.

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

wdrakula пишет:

для "стрельбы себе в ногу".

Енто мы могём! Собсна, это значимая часть профессионального юмора.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

sadman41 пишет:

Создать свой фейковый пустой тип данных по подобию __FlashStringHelper и аргумент приводить к нему?

Да, это первое, о чем я подумал. Но подумать - мало, нужно еще знать, как (я никогда не скрывал, что для меня С/C++ не являются родными языками).

Единственное, что я нашел в папке Arduino:

class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal))) 

только, сдается мне, здесь определяется только макрос F(), а определения самого класса мне найти не удалось. 

Т.е. образца, чтобы сделать "по подобию" у меня нет, а собственных знаний чтобы сделать без образца - не хватает.

Не получилось даже описать экземпляр класса:

__FlashStringHelper pill_f[] ={ 49, 50, 51, 52 };

__FlashStringHelper pill_f ={ 49, 50, 51, 52 }; 

ни первая, ни вторая строка не компилируется (ошибка: incomplete type).

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

Да, сделайте как в моём примере, делов -то.

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ЕвгенийП пишет:

если для обычных переменных модификаторы const и volatile не создают разницы (функции считаются одинаковыми), то для указателей и ссылок - ещё как! А массивы, как раз и есть указатели.

Да, с формулировано четко, но мне удалось подобрать примеры, где преобразование происходит по каким-то другим правилам. В частности неконстантный int8_t почему-то распознается как PROGMEM.

PGM_VOID_P pill_P[] = { 49, 50, 51, 52 };             // _PGM_  -  _PGM_  -
byte pill_b[] = { 49, 50, 51, 52 };                   // Byte   +  Byte   +
char pill_c[] ={ 49, 50, 51, 52 };                    // Char   +  Char   +
uint8_t pill_u[] ={ 49, 50, 51, 52 };
int8_t pill_s[] ={ 49, 50, 51, 52 };
unsigned char pill_w[] ={ 49, 50, 51, 52 };
const byte pill_cb[] = { 49, 50, 51, 52 };            // C_Byte +  _PGM_  -
const char pill_cc[] ={ 49, 50, 51, 52 };             // C_Char +  _PGM_  -
const uint8_t pill_cu[] ={ 49, 50, 51, 52 };
const int8_t pill_cs[] ={ 49, 50, 51, 52 };
const unsigned char pill_cw[] ={ 49, 50, 51, 52 };
const byte PROGMEM pill_pcb[] = { 49, 50, 51, 52 };   // C_Byte -  _PGM_  +
const char PROGMEM pill_pcc[] ={ 49, 50, 51, 52 };    // _PGM_  +  _PGM_  +
const uint8_t PROGMEM pill_pcu[] ={ 49, 50, 51, 52 };
const int8_t PROGMEM pill_pcs[] ={ 49, 50, 51, 52 };
const unsigned char PROGMEM pill_pcw[] ={ 49, 50, 51, 52 };
//__FlashStringHelper pill_f[] ={ 49, 50, 51, 52 };     // _

void funA(char * c) {
  Serial.print("Char   ");
  Serial.println((byte)c[0]);
}

void funA(const char * c) { //*
  Serial.print("C_Char ");
  Serial.println((byte)c[0]);
}

void funA(byte * c) {
  Serial.print("Byte   ");
  Serial.println(c[0]);
}

void funA(const byte * c) { //*
  Serial.print("C_Byte ");
  Serial.println(c[0]);
}

void funA(PGM_VOID_P c) {
  Serial.print("_PGM_  ");
  Serial.println(pgm_read_byte(c));
}
 
void funA(__FlashStringHelper * c) { //*
  Serial.print("_FSH_  ");
  Serial.println(pgm_read_byte(c));
}
 

void setup() {
  Serial.begin(115200);
  Serial.println("Hello!");
  funA(pill_P);
  Serial.println();
  funA(pill_b);
  funA(pill_c);
  funA(pill_u);
  funA(pill_s);
  funA(pill_w);
  Serial.println();
  funA(pill_cb);
  funA(pill_cc);
  funA(pill_cu);
  funA(pill_cs);
  funA(pill_cw);
  Serial.println();
  funA(pill_pcb);
  funA(pill_pcc);
  funA(pill_pcu);
  funA(pill_pcs);
  funA(pill_pcw);
  Serial.println();
  funA("1 Hello");
  funA(F("1 Hello"));
//  funA(pill_f);
}

void loop() {
}

результат работы:

Hello!
_PGM_  126

Byte   49
Char   49
Byte   49
_PGM_  252
Byte   49

C_Byte 49
C_Char 49
C_Byte 49
_PGM_  128
C_Byte 49

C_Byte 1
C_Char 0
C_Byte 0
_PGM_  49
C_Byte 0

C_Char 49
_PGM_  49

Примечание: во всех результатах "правильным" числом является 49. Если в выводе что-то другое, имеет место ошибка распознавания.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

wdrakula пишет:

Остается "расово-верный" путь, истиного ООП. :))

Нужно создать класс для массивов...

Да, иногда я для себя так и делаю. Но осложнение в том, что я делаю библиотечку, которую собираюсь выложить в общий доступ. А значит, при использовании другими людьми там неизбежно будет "зоопарк". Вот хотелось бы сделать внутреннюю часть библиотечки так, что бы этот "зоопарк" с пользовательской стороны не создавал проблем.

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

andriano пишет:

Вот хотелось бы сделать внутреннюю часть библиотечки так, что бы этот "зоопарк" с пользовательской стороны не создавал проблем.

Не получится. Делай буквальные вызовы.

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

andriano пишет:
В частности неконстантный int8_t почему-то распознается как PROGMEM.

Вы что-то напутали. Я взял Ваш пример, выбросил из него всё, кроме uint8_t и всё отлично работает

Код:

#include <avr/pgmspace.h>

uint8_t pill_u[] ={ 49, 50, 51, 52 };
const uint8_t PROGMEM pill_pcu[] ={ 39, 50, 51, 52 };

void funA(uint8_t * c) {
  Serial.print("uint8_t   ");
  Serial.println((byte)c[0]);
}

void funA(const uint8_t * c) {
  Serial.print("const uint8_t ");
  Serial.println((byte)c[0]);
}
void setup() {
  Serial.begin(9600);
  Serial.println("Hello!");
  funA(pill_u);
  funA(pill_pcu);
}

void loop() {
}

Результат:

Hello!
uint8_t   49
const uint8_t 39

Что там с полным примером я не стал разбираться, сами разберётесь.

sadman41
Offline
Зарегистрирован: 19.10.2016

andriano пишет:

Единственное, что я нашел в папке Arduino:

class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal))) 

только, сдается мне, здесь определяется только макрос F(), а определения самого класса мне найти не удалось. 

А оно так и работает - пустой фейковый класс чисто для приведения типа к нему, чтобы была вызвана соответствующая overload-функция, в которой уже применяются соответствующие для PROGMEM методы доступа.

Типа такого:

printString(const char* _src) {
  ... функции для доступа к RAM
}

printString(const __FlashStringHelper* _src) {
  const char* ptrPGM=(const char*) _src;
  ... функции для доступа к PROGMEM
}

const char progmemArray[] PROGMEM = "Stay in PROGMEM";
const char ramArray[]             = "Stay in RAM";
...
printString((const __FlashStringHelper*) progmemArray);
printString(ramArray);
...

 

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ЕвгенийП пишет:

Я взял Ваш пример, выбросил из него всё, кроме uint8_t и всё отлично работает

Вот именно!

Когда что-то добавляешь или выбрасываешь из функций, реакция остальных изменяется существенным образом.

Крайний случай - единственный массив и единственная функция - работают всегда!

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

Что-то тут не то. Так не бывает :-(

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Как это не бывает?

Наоборот - не может быть иначе: если мы добавляем функцию, то она "отбирает" часть вызовов у других функций. 

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

Ну, разве что :-)

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

"Гоп-функция" - отжимает вызовы! ;)))))

Попкорн? ;)))