Последовательная печать из флэш-памяти (F() macro, PROGMEM, sprintf_P, SPTR)

Я тестировал на Arduino UNO различные методы последовательной печати из флэш-памяти вместо оперативной памяти. В то же время я использовал функцию freeMemory() из Adafruit.

Я создал программу, которая печатает слово "тест", с различными методами:

  • Простая печать:
Serial.print("test");
Serial.print(freeMemory());

Этот вызов сообщает о свободной оперативной памяти = 2267

  • Использование макроса F ():
Serial.print(F("test"));
Serial.print(freeMemory());

Этот вызов сообщает о свободной оперативной памяти = 2299

  • Использование sprintf_P()
char buf[100];
sprintf_P(buf,PSTR("test"));
Serial.print(buf);
Serial.print(freeMemory());

Этот вызов сообщает о свободной оперативной памяти = 2267

  • Использование strcpy_P()
 char buf[100];
 strcpy_P(buf, (PGM_P)F("test"));
 Serial.print(buf);
Serial.print(freeMemory());

Этот вызов сообщает о свободной оперативной памяти = 2267

  • Использование ПРОГМЕМА strcpy_P и const char[]
char buf[100];
const char buff[] PROGMEM = "test";
strcpy_P(buf, buff);
Serial.print(buf);
Serial.print(freeMemory());

Этот вызов сообщает о свободной оперативной памяти = 2267

Основной момент этого вопроса заключается в том, чтобы собрать все возможные методы печати флэш-памяти. Также было бы очень полезно, если бы вы могли подробно объяснить, что именно делает каждый из ключевых слов F (), (PGM_P)F, PSTR, const PROGMEM.

ВАЖНАЯ ПРАВКА1: В зависимости от того, где и как я использую вызов Serial.print(freeMemory()); результаты меняются. Например, в последнем примере (PROGMEM, strcpy_P), когда я включил другой Serial.print(freeMemory()); перед вызовом функций печати, то оба freeMemory() вызывает report 2299!

Другая интересная проблема заключается в том, что freeMemory() сообщает 2299 как FreeRAM, в то время как SRAM моего arduino UNO составляет всего 2kbytes.

EDIT2: Следуя предложению в комментариях, я изменил "тест" на "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest". Теперь все различные методы сообщают о свободной оперативной памяти в размере 2299 байт, включая простой Serial.print().

freeMemory():

#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__     
int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

, 👍1

Обсуждение

Хороший отчет, но я пропустил вопрос; Я думаю, вы не используете Uno, так как эта модель имеет только 2 КБ., @Michel Keijzers

Все эти процедуры используют 100 байт в стеке и ничего больше, кроме, возможно, временного пространства внутри вызываемых функций, которое также находится в стеке. Ничто из этого не имеет отношения к хранению данных и чтению данных из flash. Все, о чем вы спрашиваете, - это макросы. Вы можете найти, на что они расширяются, в исходном коде либо ядра, либо компилятора., @Majenko

Я предлагаю вам попробовать с сообщением гораздо дольше, чем "тест". Есть много деталей в процессе оптимизации компилятора, которые могут сделать небольшие изменения в потреблении памяти. Они могут сделать ваши измерения трудными для интерпретации с такой маленькой строкой., @Edgar Bonet

Попробуем сразу, и доложим с результатами. @MichelKeijzers Как ни странно, я использую UNO. Есть идеи?, @NickG

@EdgarBonet ваше предложение действительно, кажется, имеет влияние. Я изменил "test" на "testtesttesttesttesttesttesttesttesttesttesttesttest". Однако результаты кажутся еще более странными, поскольку все различные методы (включая простой Serial.print) сообщают о свободной оперативной памяти в размере 2299. Позвольте мне также отметить, что это FreeRAM, который я получаю, когда я только вызываю freeMemory() в своем коде., @NickG


2 ответа


3

подробно объясните, что конкретно делает каждое из ключевых слов F (), (PGM_P)F, PSTR, const PROGMEM.

  • F() обертывает строковый литерал в PSTR (...), а затем приводит его к __FlashStringHelper. Это заставляет его оставаться во flash, а также придает ему тип, который ядро Arduino может идентифицировать как строку во flash для перегрузки.
  • PGM_P-это "Указатель на память программы". Это тип, который требуется для всех _P-вариантов функций. В основном это символ const*, предоставляемый avr/pgmspace.h
  • постоянная ПРОГРАММА на самом деле состоит из двух вещей. слово const означает, что вы не меняете значение, и PROGMEM это другое название __М_PROGMEM__ , который сам является псевдонимом __атрибут__((__progmem__)) что это флаг компилятора, чтобы сказать ему, чтобы сохранить переменную во Flash, а не копировать его в память.

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

,

Не могли бы вы объяснить: sprintf_P(buf,PSTR("тест")); ? Разве это неправильно? Должно ли это быть так: sprintf_P(buf,PGM_P("тест"));? Также, как насчет этой команды: strcpy_P(buf, (PGM_P)F("тест"));, которая сочетает в себе как PGM_P, так и F()?, @NickG

` (PGM_P)F("тест") ' - это то, что я обычно использую., @Majenko

"(PGM_P)F("тест") " эквивалентно `PSTR("тест")". Что происходит, так это то, что"F ("тест") "приводит строковый литерал в класс "__FlashStringHelper", а затем преобразует его в "PGM_P", который в основном представляет собой макрос как " const char*". Это то же самое, что"PSTR ("тест")", который преобразует строковый литерал s[] в переменную const char* s., @hcheung


7

Во-первых, freeMemory() функция, которую вы использовали от Adafruit возникла из на GitHub, я не знаю о __рука__ реализацией, но и для AVR, она является неполной для обработки угловой случай, когда программа не используйте malloc()в функции, если ваша программа никогда не используйте функции malloc(), формула, используемая в freeMemory() будет производить неправильный результат. Простая исправленная версия AVR, которую я использовал, находится здесь:

extern unsigned int __heap_start;
extern char *__brkval;

int freeMemory() {
  char top_of_stack;

  if (__brkval == 0) {
    Serial.println(((int)&top_of_stack - (int)&__heap_start));
  } else {
    Serial.println((int)&top_of_stack - (int)__brkval);
  }
}

подробно объясните, что конкретно делает каждое из ключевых слов F (), (PGM_P)F, PSTR, const PROGMEM.

PSTR("строковый литерал")

PSTR появился в Arduino из части avr-библиотек(библиотеки C), определенной в avr/pgmspace.h, на самом деле это макрос, а не функция, как многие думают, и определяется как:

#define     PSTR(s)   ((const PROGMEM char *)(s))

Это выглядит немного пугает, но это на самом деле довольно проста, она не только расскажет ресивер avr-gcc, которые строковый литерал С должны храниться в памяти программ (например, флэш-памяти в случае с Ардуино), но программа позволяет преобразовать его в константный тип char *с PROGMEM переменной, так что его можно обойти в некоторых PROGMEM-зависимые функции в качестве параметра.

 //это функция, зависящая от программы, в С
 printMsg_P(const char* str) {
     char buf[strlen_P(str)+1];
     strcpy_P(buf, str);
     puts(buf);
 }

 // это обрабатывает обычную функцию массива строк
 printMsg(const char* str) {
     puts(str);
 }

F("строковый литерал")

F() также является макросом, но он не является частью avr/progmem.h, на самом деле он является частью класса String, определенного в WString.h вместе с определением класса с именем __FlashStringHelper:

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

Чтобы понять макрос F() и разницу между F() и PSTR (), нужно немного пояснить.

Как вы можете видеть из определения F(), он преобразует строковый литерал в переменную const char* с помощью PSTR(), а затем повторно преобразует его в класс __FlashStrngHelper. Если вы далее посмотрите на __FlashStringHelper, на самом деле это пустой класс без какой-либо конструкции или метода, он буквально пуст, так почему это полезно? и почему нельзя просто передать строковый литерал, просто используя PSTR().

Это связано с перегрузкой функций C++. Перегрузка функций-это особенность C++, в которой несколько методов могут иметь одно и то же имя функции, но каждый с разными параметрами. На протяжении всего сериала.печать() метода, существует метод последовательной.печать(константный тип char* СПО) , которые будут принимать в строковом литерале и распечатать его на последовательный монитор, но это не PROGMEM-знаете, если вы проходите через PROGMEM строковый литерал серийный.печать(ПСТР("строковый литерал"));тот же метод будет принимать то, что вы передаете, потому что он отвечает типа проверки (помните ПСТР фактически константный тип char*), но оно не будет распечатать правильную строку для ПСТР("строковый литерал") , потому что он просто ожидал обычную строку.

В C, как мы видели, то решает проблему, используя две функции с разными именами для обработки обычной строки и PROGMEM строку, а серийный - это класс C++, он должен еще одна функция перегруженный метод, чтобы быть PROGMEM-осознает, и ПСТР() явно не решение проблемы, как мы только что упомянули.

Что за чертовщина.h и Print.cpp задача состоит в том, чтобы создать метод перегрузки, учитывающий ПРОГМУ, приняв класс в качестве параметра, чтобы вы могли передать завернутый строковый литерал F (), который имеет тип данных класса __FlashStringHelper в Serial.print() и правильно распечатать из памяти программы. Посмотрите исходный код самостоятельно.

Serial.print(const __FlashStringHelper *ifsh)

Таким образом, подводя итог, можно сказать, что F() и PSTR() похожи, оба говорят компилятору сохранить строковый литерал в памяти программы и разрешить передачу строкового литерала в некоторую функцию в качестве параметра. Но он был разработан для другой цели, отличной от другой парадигмы программирования (C по сравнению с C++). Функция F() удобна для Serail.print(), в то время как функция PSTR ()-нет. Если вы получаете PST() в функции и все же хотите отправить ее в Serial.print(), вам придется явно передать ее в __FlashStringHelper следующим образом:

Serial.print((__FlashStringHelper*)PSTR("test"));

PGM_P

PGM_P-это макрос, определяемый как:

#define     PGM_P   const char *

Хотите ли вы использовать PGM_P или const char*, зависит от личных предпочтений, например, это копия из https://www.arduino.cc/reference/en/language/variables/utilities/progmem/:

const char *const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

может быть записано следующим образом:

PGM_P const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

Это пространное объяснение, но я надеюсь, что это поможет лучше понять предмет и, следовательно, сможет эффективно использовать программу и все связанные с ней макросы.

,

Re “_PSTR [...] определяется как [...]_”: Это определение вводится комментарием “_The #define ниже является просто фиктивным, который служит только для целей документации._” [Фактическое definition](http://svn.savannah.gnu.org/viewvc/avr-libc/tags/avr-libc-2_0_0-release/include/avr/pgmspace.h?revision=2516&view=markup#l408) несколько более запутан., @Edgar Bonet