Последовательная печать из флэш-памяти (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__
}
@NickG, 👍1
Обсуждение2 ответа
подробно объясните, что конкретно делает каждое из ключевых слов 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
Во-первых, 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
- Есть ли способ подключить оперативную память компьютера к Arduino?
- Есть ли способ добавить внешнюю оперативную память (скажем, 100 МБ или 200 МБ) в этом контексте микширования аудиобуфера?
- Функция freeMemory() из библиотеки memoryfree не возвращает уменьшенное значение в arduino UNO
- Подключить SDRAM к STM32
- Библиотека MemoryFree, демонстрирующая нестандартное поведение..!
- Как заставить BLE и WiFi IoT работать с памятью без проблем ?
- Получить доступ к EEPROM ATtiny с помощью кода Arduino?
- Выделение строковой памяти Arduino
Хороший отчет, но я пропустил вопрос; Я думаю, вы не используете Uno, так как эта модель имеет только 2 КБ., @Michel Keijzers
Все эти процедуры используют 100 байт в стеке и ничего больше, кроме, возможно, временного пространства внутри вызываемых функций, которое также находится в стеке. Ничто из этого не имеет отношения к хранению данных и чтению данных из flash. Все, о чем вы спрашиваете, - это макросы. Вы можете найти, на что они расширяются, в исходном коде либо ядра, либо компилятора., @Majenko
Я предлагаю вам попробовать с сообщением гораздо дольше, чем "тест". Есть много деталей в процессе оптимизации компилятора, которые могут сделать небольшие изменения в потреблении памяти. Они могут сделать ваши измерения трудными для интерпретации с такой маленькой строкой., @Edgar Bonet
Попробуем сразу, и доложим с результатами. @MichelKeijzers Как ни странно, я использую UNO. Есть идеи?, @NickG
@EdgarBonet ваше предложение действительно, кажется, имеет влияние. Я изменил "test" на "testtesttesttesttesttesttesttesttesttesttesttesttest". Однако результаты кажутся еще более странными, поскольку все различные методы (включая простой
Serial.print
) сообщают о свободной оперативной памяти в размере 2299. Позвольте мне также отметить, что это FreeRAM, который я получаю, когда я только вызываюfreeMemory()
в своем коде., @NickG