ESP8266:Прошивки/Arduino/PROGMEM: различия между версиями

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску
Нет описания правки
Нет описания правки
 
(не показана 1 промежуточная версия этого же участника)
Строка 3: Строка 3:
{{Myagkij-редактор}}
{{Myagkij-редактор}}


{{Черновик}}
 


=Руководство по PROGMEM в аддоне ESP8266 для IDE Arduino<ref>[http://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html arduino-esp8266.readthedocs.io - Guide to PROGMEM on ESP8266 and Arduino IDE]</ref>=
=Руководство по PROGMEM в аддоне ESP8266 для IDE Arduino<ref>[http://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html arduino-esp8266.readthedocs.io - Guide to PROGMEM on ESP8266 and Arduino IDE]</ref>=
Строка 11: Строка 11:
На [[ESP8266]] для [[PROGMEM]] используется следующий макрос:
На [[ESP8266]] для [[PROGMEM]] используется следующий макрос:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
#define PROGMEM  ICACHE_RODATA_ATTR
#define PROGMEM  ICACHE_RODATA_ATTR
</syntaxhighlight>
</syntaxhighlight>
Строка 17: Строка 17:
'''ICACHE_RODATA_ATTR''' задается при помощи:
'''ICACHE_RODATA_ATTR''' задается при помощи:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
#define ICACHE_RODATA_ATTR  __attribute__((section(".irom.text")))
#define ICACHE_RODATA_ATTR  __attribute__((section(".irom.text")))
</syntaxhighlight>
</syntaxhighlight>
Строка 25: Строка 25:
Чтобы объявить глобальную переменную, сохраняемую во [[flash]]-памяти, нужно написать следующее:
Чтобы объявить глобальную переменную, сохраняемую во [[flash]]-памяти, нужно написать следующее:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
static const char xyz[] PROGMEM = "Это строка, сохраненная на flash";
static const char xyz[] PROGMEM = "Это строка, сохраненная на flash";
</syntaxhighlight>
</syntaxhighlight>
Строка 33: Строка 33:
Для этого можно воспользоваться макросом PSTR(). Он задан в файле [https://github.com/esp8266/Arduino/blob/master/cores/esp8266/pgmspace.h pgmspace.h].
Для этого можно воспользоваться макросом PSTR(). Он задан в файле [https://github.com/esp8266/Arduino/blob/master/cores/esp8266/pgmspace.h pgmspace.h].


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
#define PGM_P      const char *
#define PGM_P      const char *
#define PGM_VOID_P  const void *
#define PGM_VOID_P  const void *
Строка 41: Строка 41:
На практике:  
На практике:  


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
void myfunction(void) {
void myfunction(void) {
PGM_P xyz = PSTR("Сохраняем эту строку во flash-памяти");
PGM_P xyz = PSTR("Сохраняем эту строку во flash-памяти");
Строка 54: Строка 54:
Все эти функции заданы в заголовочном файле [https://github.com/esp8266/Arduino/blob/master/cores/esp8266/pgmspace.h pgmspace.h].
Все эти функции заданы в заголовочном файле [https://github.com/esp8266/Arduino/blob/master/cores/esp8266/pgmspace.h pgmspace.h].


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
int memcmp_P(const void* buf1, PGM_VOID_P buf2P, size_t size);
int memcmp_P(const void* buf1, PGM_VOID_P buf2P, size_t size);
void* memccpy_P(void* dest, PGM_VOID_P src, int c, size_t count);
void* memccpy_P(void* dest, PGM_VOID_P src, int c, size_t count);
Строка 82: Строка 82:
Но тут на помощь приходит '''__FlashStringHelper'''. Это класс-обертка, позволяющий использовать [[flash]]-строки как класс. Благодаря ему с [[flash]]-строками можно делать и проверку типа данных, и перегрузку функций. Думаю, многие знакомы с макросом F() и, возможно, с макросом FPSTR(). Оба этих макроса заданы в файле [https://github.com/esp8266/Arduino/blob/master/cores/esp8266/WString.h#L37 WString.h].
Но тут на помощь приходит '''__FlashStringHelper'''. Это класс-обертка, позволяющий использовать [[flash]]-строки как класс. Благодаря ему с [[flash]]-строками можно делать и проверку типа данных, и перегрузку функций. Думаю, многие знакомы с макросом F() и, возможно, с макросом FPSTR(). Оба этих макроса заданы в файле [https://github.com/esp8266/Arduino/blob/master/cores/esp8266/WString.h#L37 WString.h].


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define F(string_literal) (FPSTR(PSTR(string_literal)))
#define F(string_literal) (FPSTR(PSTR(string_literal)))
Строка 89: Строка 89:
Итак, макрос FPDTR()  берет [[PROGMEM-указатель]] на строку и преобразует его в класс __FlashStringHelper. Следовательно, если вы задали строку как '''xyz''', то можете воспользоваться FPSTR() для преобразования этой строки в класс __FlashStringHelper, чтобы ее можно было использовать в функциях, совместимых с этих классом.
Итак, макрос FPDTR()  берет [[PROGMEM-указатель]] на строку и преобразует его в класс __FlashStringHelper. Следовательно, если вы задали строку как '''xyz''', то можете воспользоваться FPSTR() для преобразования этой строки в класс __FlashStringHelper, чтобы ее можно было использовать в функциях, совместимых с этих классом.


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
static const char xyz[] PROGMEM = "Это строка, сохраненная на flash";
static const char xyz[] PROGMEM = "Это строка, сохраненная на flash";
Serial.println(FPSTR(xyz));
Serial.println(FPSTR(xyz));
Строка 98: Строка 98:
Например:
Например:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
Serial.println(F("Это строка, сохраненная на flash"));
Serial.println(F("Это строка, сохраненная на flash"));
</syntaxhighlight>
</syntaxhighlight>
Строка 104: Строка 104:
Хотя обе эти функции выполняют одну и ту же задачу, роли у них разные. Если FPSTR() позволяет задать глобальную [[flash]]-строку, а затем использовать ее в функции, принимающей класс __FlashStringHelper, то F() позволяет задать [[flash]]-строку лишь локально (т.е. ее нельзя будет использовать больше нигде). В результате FPSTR() позволяет обмениваться общими строками, а F() – нет. Класс String использует __FlashStringHelper, чтобы перегрузить свой конструктор (т.е. создать его разные версии).
Хотя обе эти функции выполняют одну и ту же задачу, роли у них разные. Если FPSTR() позволяет задать глобальную [[flash]]-строку, а затем использовать ее в функции, принимающей класс __FlashStringHelper, то F() позволяет задать [[flash]]-строку лишь локально (т.е. ее нельзя будет использовать больше нигде). В результате FPSTR() позволяет обмениваться общими строками, а F() – нет. Класс String использует __FlashStringHelper, чтобы перегрузить свой конструктор (т.е. создать его разные версии).


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
String(const char *cstr = ""); // конструктор из const char *
String(const char *cstr = ""); // конструктор из const char *
String(const String &str);    // копирующий конструктор
String(const String &str);    // копирующий конструктор
Строка 112: Строка 112:
В результате в коде можно написать следующее:
В результате в коде можно написать следующее:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
String mystring(F("Эта строка сохранена во flash-памяти"));
String mystring(F("Эта строка сохранена во flash-памяти"));
</syntaxhighlight>
</syntaxhighlight>
Строка 118: Строка 118:
Как я написал функцию, использующую __FlashStringHelper? Я преобразовал указатель обратно в '''PGM_P''' и воспользовался функциями '''_P''', показанными выше. Ниже это показано на примере класса String и функции конкатенации:
Как я написал функцию, использующую __FlashStringHelper? Я преобразовал указатель обратно в '''PGM_P''' и воспользовался функциями '''_P''', показанными выше. Ниже это показано на примере класса String и функции конкатенации:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
unsigned char String::concat(const __FlashStringHelper * str) {
unsigned char String::concat(const __FlashStringHelper * str) {
     if (!str) return 0;                // это значение возвращается,  
     if (!str) return 0;                // это значение возвращается,  
Строка 140: Строка 140:
== Как объявить глобальную [[flash]]-строку и использовать ее? ==
== Как объявить глобальную [[flash]]-строку и использовать ее? ==


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
static const char xyz[] PROGMEM = "Эта строка сохранена на flash. Len = %u";
static const char xyz[] PROGMEM = "Эта строка сохранена на flash. Len = %u";


Строка 156: Строка 156:
== Как использовать встраиваемые flash-строки? ==
== Как использовать встраиваемые flash-строки? ==


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
void setup() {
void setup() {
     Serial.begin(115200); Serial.println();
     Serial.begin(115200); Serial.println();
Строка 166: Строка 166:
== Как объявить и использовать данные в PROGMEM? ==
== Как объявить и использовать данные в PROGMEM? ==


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
const size_t len_xyz = 30;
const size_t len_xyz = 30;
const uint8_t xyz[] PROGMEM = {
const uint8_t xyz[] PROGMEM = {
Строка 187: Строка 187:
Об объявлении данных говорилось выше. Чтобы извлечь данные, воспользуйтесь '''pgm_read_byte'''.
Об объявлении данных говорилось выше. Чтобы извлечь данные, воспользуйтесь '''pgm_read_byte'''.


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
const size_t len_xyz = 30;
const size_t len_xyz = 30;
const uint8_t xyz[] PROGMEM = {
const uint8_t xyz[] PROGMEM = {
Строка 211: Строка 211:
=См.также=
=См.также=


{{ads}}
 


=Внешние ссылки=
=Внешние ссылки=
Строка 217: Строка 217:
<references />
<references />


{{Навигационная таблица/ESP8266}}
{{Навигационная таблица/Портал/ESP8266}}
{{Навигационная таблица/ESP8266 AT-команды}}
 
{{Навигационная таблица/Телепорт}}
 


[[Категория:ESP8266]]
[[Категория:ESP8266]]

Текущая версия от 12:51, 18 июня 2023

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.



Руководство по PROGMEM в аддоне ESP8266 для IDE Arduino[1]

PROGMEM – это функция AVR-процессоров Arduino, портированная на ESP8266 для сохранения совместимости с библиотеками Arduino, а также для экономии RAM-памяти. На ESP8266 объявление строки вроде const char * xyz = "это строка" поместит эту строку в RAM, а не во flash-память. Впрочем, есть способ поместить строку сначала во flash-память, а затем, если понадобится, в RAM. На 8-битных процессорах ARM этот процесс очень прост. На 32-битном ESP8266 для считывания с flash-памяти должны быть соблюдены определенные условия.

На ESP8266 для PROGMEM используется следующий макрос:

#define PROGMEM   ICACHE_RODATA_ATTR

ICACHE_RODATA_ATTR задается при помощи:

#define ICACHE_RODATA_ATTR  __attribute__((section(".irom.text")))

Это поместит переменную в секцию «.irom.text» во flash-памяти. Для сохранения строки во flash-память можно использовать оба этих метода.

Чтобы объявить глобальную переменную, сохраняемую во flash-памяти, нужно написать следующее:

static const char xyz[] PROGMEM = "Это строка, сохраненная на flash";

Как объявить flash-строку внутри кода

Для этого можно воспользоваться макросом PSTR(). Он задан в файле pgmspace.h.

#define PGM_P       const char *
#define PGM_VOID_P  const void *
#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))

На практике:

void myfunction(void) {
PGM_P xyz = PSTR("Сохраняем эту строку во flash-памяти");
const char * abc = PSTR("Эту строку тоже сохраняем во flash-памяти");
}

Вот два способа, с помощью которых строки можно сохранять во flash-памяти. Чтобы считывать и манипулировать этими flash-строками, они должны быть в виде 4-байтных слов. В аддоне ESP8266 для IDE Arduino есть несколько функций, которые позволяют извлекать из flash-памяти строки, сохраненные туда при помощи PROGMEM. Оба примера выше возвращают данные типа const char *. Впрочем, если использовать эти указатели без правильного 32-битного выравнивания, это повлечет ошибку сегментации, и ESP8266 крашнется. Данные, считываемые из flash-памяти, должны быть выровнены под 32 бита.

Функции для считывания из PROGMEM

Все эти функции заданы в заголовочном файле pgmspace.h.

int memcmp_P(const void* buf1, PGM_VOID_P buf2P, size_t size);
void* memccpy_P(void* dest, PGM_VOID_P src, int c, size_t count);
void* memmem_P(const void* buf, size_t bufSize, PGM_VOID_P findP, size_t findPSize);
void* memcpy_P(void* dest, PGM_VOID_P src, size_t count);
char* strncpy_P(char* dest, PGM_P src, size_t size);
char* strcpy_P(dest, src)
char* strncat_P(char* dest, PGM_P src, size_t size);
char* strcat_P(dest, src)
int strncmp_P(const char* str1, PGM_P str2P, size_t size);
int strcmp_P(str1, str2P)
int strncasecmp_P(const char* str1, PGM_P str2P, size_t size);
int strcasecmp_P(str1, str2P)
size_t strnlen_P(PGM_P s, size_t size);
size_t strlen_P(strP)
char* strstr_P(const char* haystack, PGM_P needle);
int printf_P(PGM_P formatP, ...);
int sprintf_P(char *str, PGM_P formatP, ...);
int snprintf_P(char *str, size_t strSize, PGM_P formatP, ...);
int vsnprintf_P(char *str, size_t strSize, PGM_P formatP, va_list ap);

Как видите, здесь много функций, но фактически это _P версии стандартных C-функций, адаптированные для считывания выровненных данных из 32-битной flash-памяти ESP8266. Все они используют тип данных PGM_P, который в сущности является const char *. Под «капотом» всех этих функций работает процесс, который считывает из flash-памяти 4 байта, но возвращает только тот байт, что был запрошен пользователем.

Но этот процесс исправно работает, только если ваша функция создана аналогично тем, что показаны выше. Все они адаптированы для работы с указателями PROGMEM, но в них нет проверки на тип данных, за исключением проверки на соответствие с const char*. Таким образом, вы вполне можете (пока разрешает компилятор) использовать в этих функциях строки с указателями типа const char *, но штука в том, что из-за этого программа может начать вести себя странно. Вы не сможете создавать перегруженные функции, способные использовать flash-строки, когда они заданы как PGM_P. Если попытаться сделать это, вы получите двусмысленную ошибку перегрузки, т.к. тип данных PGM_P, в сущности, идентичен типу данных const char*.

Но тут на помощь приходит __FlashStringHelper. Это класс-обертка, позволяющий использовать flash-строки как класс. Благодаря ему с flash-строками можно делать и проверку типа данных, и перегрузку функций. Думаю, многие знакомы с макросом F() и, возможно, с макросом FPSTR(). Оба этих макроса заданы в файле WString.h.

#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define F(string_literal) (FPSTR(PSTR(string_literal)))

Итак, макрос FPDTR() берет PROGMEM-указатель на строку и преобразует его в класс __FlashStringHelper. Следовательно, если вы задали строку как xyz, то можете воспользоваться FPSTR() для преобразования этой строки в класс __FlashStringHelper, чтобы ее можно было использовать в функциях, совместимых с этих классом.

static const char xyz[] PROGMEM = "Это строка, сохраненная на flash";
Serial.println(FPSTR(xyz));

Макрос F() объединяет в себе оба этих метода, создавая простой и быстрый способ для записи встраиваемых строк на flash-память, а также возвращения типа __FlashStringHelper.

Например:

Serial.println(F("Это строка, сохраненная на flash"));

Хотя обе эти функции выполняют одну и ту же задачу, роли у них разные. Если FPSTR() позволяет задать глобальную flash-строку, а затем использовать ее в функции, принимающей класс __FlashStringHelper, то F() позволяет задать flash-строку лишь локально (т.е. ее нельзя будет использовать больше нигде). В результате FPSTR() позволяет обмениваться общими строками, а F() – нет. Класс String использует __FlashStringHelper, чтобы перегрузить свой конструктор (т.е. создать его разные версии).

String(const char *cstr = ""); // конструктор из const char *
String(const String &str);     // копирующий конструктор
String(const __FlashStringHelper *str); // конструктор для flash-строк

В результате в коде можно написать следующее:

String mystring(F("Эта строка сохранена во flash-памяти"));

Как я написал функцию, использующую __FlashStringHelper? Я преобразовал указатель обратно в PGM_P и воспользовался функциями _P, показанными выше. Ниже это показано на примере класса String и функции конкатенации:

unsigned char String::concat(const __FlashStringHelper * str) {
    if (!str) return 0;                 // это значение возвращается, 
                                        // если указатель пуст
    int length = strlen_P((PGM_P)str);  // преобразуем обратно 
                                        // в PGM_P, который в сущности 
                                        // является const char *, 
                                        // и измеряем при помощи 
                                        // _P версии функции strlen()
    if (length == 0) return 1;
    unsigned int newlen = len + length;
    if (!reserve(newlen)) return 0;     // создаем буфер 
                                        // корректной длины
    strcpy_P(buffer + len, (PGM_P)str); // копируем строку в буфер
                                        // при помощи strcpy_P()
    len = newlen;
    return 1;
}

Как объявить глобальную flash-строку и использовать ее?

static const char xyz[] PROGMEM = "Эта строка сохранена на flash. Len = %u";

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println( FPSTR(xyz) );  //  просто печатаем строку, 
                                   //  но сначала конвертируем ее 
                                   //  в класс FlashStringHelper 
                                   //  при помощи FPSTR() 
    Serial.printf_P( xyz, strlen_P(xyz)); // используем printf_P()
                                          // со строкой PROGMEM
}

Как использовать встраиваемые flash-строки?

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println( F("Это встраиваемая строка")); //
    Serial.printf_P( PSTR("Это встраиваемая строка при помощи printf %s"), "привет");
}

Как объявить и использовать данные в PROGMEM?

const size_t len_xyz = 30;
const uint8_t xyz[] PROGMEM = {
  0x53, 0x61, 0x79, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20,
  0x74, 0x6f, 0x20, 0x4d, 0x79, 0x20, 0x4c, 0x69, 0x74, 0x74,
  0x6c, 0x65, 0x20, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x00};

 void setup() {
     Serial.begin(115200); Serial.println();
     uint8_t * buf = new uint8_t[len_xyz];
     if (buf) {
      memcpy_P(buf, xyz, len_xyz);
      Serial.write(buf, len_xyz); // выгружаем буфер
     }
 }

Как объявить данные в PROGMEM, а затем извлечь оттуда один байт?

Об объявлении данных говорилось выше. Чтобы извлечь данные, воспользуйтесь pgm_read_byte.

const size_t len_xyz = 30;
const uint8_t xyz[] PROGMEM = {
  0x53, 0x61, 0x79, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20,
  0x74, 0x6f, 0x20, 0x4d, 0x79, 0x20, 0x4c, 0x69, 0x74, 0x74,
  0x6c, 0x65, 0x20, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x00
};

void setup() {
  Serial.begin(115200); Serial.println();
  for (int i = 0; i < len_xyz; i++) {
    uint8_t byteval = pgm_read_byte(xyz + i);
    Serial.write(byteval); // выгружаем буфер
  }
}

Итого В использовании PROGMEM и PSTR() для сохранения строк на flash-память нет ничего сложного. Но PROGMEM и PSTR() генерируют указатели, поэтому вам понадобятся функции, которые специальным образом используют эти указатели, т.к. они являются, по сути, const char*. С другой стороны, FPSTR() и F() дают вам класс, из которого можно сделать неявные преобразования, что очень полезно при перегрузке функций. Стоит добавить, что если вам нужно сохранить int, float или указатель, то их можно сохранять и считывать напрямую, потому что их размер составляет 4 байта (т.е. они всегда остаются выровненными).

Надеюсь, эта статья была для вас полезна.

См.также

Внешние ссылки