PROGMEM tricks
- Войдите на сайт для отправки комментариев
Думаю, что ни для кого не является секретом, что любой отвязный ардуинщик рано или поздно пытается засунуть в самый дешёвый микроконтроллер если не веб-сервер, то хотя бы "умный дом". Ну, а какой умный дом обходится без вложенных в него автором красивых фраз, названий праздников и матросских ругательств? Но полёт мысли и приложенное к нему творчество быстро пресекается безжалостным фактом - оперативная память микроконтроллера не бесконечна. Кто-то этот намёк понимает и останавливается, кто-то приделывает к Ардуине внешнее хранилище, но многие начинают свой тернистый путь с простого ключевого слова PROGMEM, активирующее механизмы, которые на поверку оказываются не таким уж и простым для понимания.
В этом посте я не ставлю своей целью разложить всё по полочкам, но хочу поделиться некоторыми приёмами, с помощью которых можно сэкономить пару-тройку байт оперативной памяти. Так, как первый пост не редактируется, продолжу в следущих (люблю ошибки исправлять).
Для начала приведу небольшое исследование "типовых приёмов", скопированное отсюда:
№3 (изначальный)
P.S. В фрагментах кода закомментированы работающие методы получения правильного указателя на строку для каждого случая, так как сначала я оставил во всех трех один и те же, чтобы исследовать изменения только в объемах хранимых данных.
Всё фокусы с PROGMEM выглядят достаточно просто, пока на сцену не выходит Enum, размер элемента которого выбирается компилятором самостоятельно. Хотя, справедливости ради, необходимо отметить, что задать данный размер всё же можно вручную.
Как загнать в PROGMEM enum к тому же находящийся внутри struct, а также как получить доступ к полям таковой структуры, продемонстрированно в коде, находящемся ниже. Строки, отвечающие за доступ к PROGMEM-данным, выделены цветом.
Пояснение: трюком, позволяющим получить значение enum, хранимое в PROGMEM, является копирование данных функцией memcpy_P() области памяти размером, эквивалентным размерности одиночного элемента enum.
А как насчёт хранения в PROGMEM данных, запакованных обобщённым zip'ом, с распаковкой на лету по мере чтения. Имеет право на жизнь?
Как минимум, вдвое получается выигрыш. Проигрыш по времени вытаскивания из PROGMEM, конечно значительный, но некритичный для львиной доли приложений.
Могу подогнать пример (только не прямо сейчас, он у меня есть, но «в домашних тапочках» – нужно привести его к демонстрабельному виду). Или сами пример сделайте. Упаковку можно делать внешней программой (и плевать на её скорость и пр.), в код вставлять готовый массив байтов, а распаковывать на лету.
Я только "за". Это ж можно будет не только умный дом засунуть в ардуину, но и пару чясиков с календарём.
но и пару чясиков с календарём.
Ну, да. И в цыкле их перебирать, включая рыле в нужное время.
А еще говоря о PROGMEM народ стыдливо умалчивает о const __FlashStringHelper* . А ведь умение с ней работать очень упрощает работу с хранением данных во флеш.
А еще говоря о PROGMEM народ стыдливо умалчивает о const __FlashStringHelper* . А ведь умение с ней работать очень упрощает работу с хранением данных во флеш.
причина проста - в примере использования ПРОГМЕМ на сайте ардуино какой-то умник вставил печать строки через буфер - вот все и копируют оттуда, хотя оператор print() умеет печатать строчки из флеша без промежуточного копирования
у меня const __FlashStringHelper* почти в каждой программе
b707, вот как только научаться писать 1 !!! функции , а потом 2 !!! писать функции с параметром const __FlashStringHelper*то те костыли которые показывает sadman41 станут не очень нужны.
Квон, у меня возражений нет: пости пример с хелпером, но без лямбда-функций.
b707, вот как только научаться писать 1 !!! функции , а потом 2 !!! писать функции с параметром const __FlashStringHelper*то те костыли которые показывает sadman41 станут не очень нужны.
так вы вглядитесь в код - Садман как раз использует функции с этим параметром.
но без лямбда-функций.
Это как же? Не, так по правилам!
Нельзя ограничивать свободу художника!
Итак программировать const __FlashStringHelper* очень просто. Но надо знать язык Си, а не ограничиваться кухаркиным блинк програмированием.
А вот так можно написать свою функцию
Теперь вопрос с проверкой у флешхеперов. У них ее нет. Да и флешхеперы используют для написания посланий на других языках - это и человеческие сообщения в различных меню , а так же создания и общения на серверах. И все это находится за пределами языка Си. Вы же не требуете от среды программирования что бы она проверяла правильность вашего телефоного номера для послания вам СМС по какому-то событию.
Итак программировать const __FlashStringHelper* очень просто. Но надо знать язык Си
Позвольте узнать, какое отношение к языку Си имеет код, приведённый в Вашем посте?
Спасибо. Я уже решил - вообще не на Си - ни на каком :)
После небольшого сеанса черной магии в исполнении qwone обязательно, должно последовать разоблачение, коего по словам классика, традиционно требует общественность. ЕвгенияП прошу следить за моими руками и, в случае чего, подрихтовать их.
Итак, чудесный класс __FlashStringHelper описан в WString.h как:
И является, в сущности, синтаксическим сахаром, который позволяет сообщить вызываемой функции, что ей на вход передаётся указатель не на строку в RAM (const char*), а на строку, расположенную в Program Space. В функции, соответственно, следует использовать соответствующие приёмчики: вызывать *_P(), pgm_read_*() и пр. и др. (полный список).
Макрос F() описан рядом с волшебным классом __FlashStringHelper и делает только одно: приводит строку, расположенную в макросе PSTR() к типу __FlashStringHelper*:
PSTR() же просто создаёт из переданного ему аргумента строку, обладающую свойством PROGMEM-ресурса:
Такая вот матрёшка.
Недостатком применения макросов F()/PSTR() являетсято, что создаваемые ими строки никак не оптимизируются и десять одинаковых строк дадут девятикратный оверхед по Program Space. Выходом из сложившейся ситуации может стать смешанное использование строк, сохраняемых как через макрос F(), так и напрямую определяемых, как PROGMEM-ресурс.
Продемонстрирую объяснение кодом воображаемого веб-сервера, в котором:
1) Раскомментированный USE_F_MACRO_FOR_ALL даёт полную власть в руки F()
2) Закомментированный USE_F_MACRO_FOR_ALL заменяет некоторые строки, определяемые в F() на прямо заданные PROGMEM-строки.
Типа запрашиваем состояния датчиков, включаем лампочки моторчики, выводим чего либо на дисплейкики и т.п.
Любимый интерфейс (для меня) командная строка, ни каких проблем curl-ом выполнять запросы на эти устройства.
Если захотели всяких бантиков с плюшечками (вся статика), положили на роутер и не паримся.
Повелся таки я на эти понты с веб мордой. На esp8266 можно для хранения статики использовать spiffs, но грузить приходится отдельно от скетча, да и загрузка не очень быстрая.
Во время сборки, нелету, преобразую хтмл-контент в исходник, и копирую в директорию билда.
Засунул кроме страниц стилей классов ещё бутстрап с джиквери, без прогмема скетч уже просто не собирается, не лезет в dram0_0_seg.
sadman41.Я конечно понимаю Вера она у каждого индивидульная. Но если у вас много одинаковых строк , что надо выносить их в начало. Но можно эти строки упаковать в отдельные функции и вызывать уже их в нужном месте программы. Или вы после каждого использования написания своей функции каететесь в грехе.
Пух в нас тычет пальцем, наганяя страх
Толька слишком рано каяца в грехах...
Не согрешишь - не покаешься.
Так, как я из поста qwone не совсем понял кого, куда и за что необходимо, постфиксно покаявшись, выносить, но узнал слова "отдельные" и "функции", то приведу пример упаковки PROGMEM-строк с оператором семейства printf() в отдельную функцию и вызова оной в нужном месте. Надеюсь, что это, учитывая упоминание святого millis()-а, искупит мои грехи.
Испытательным объектом послужит всё тот же воображаемый веб-сервер, который имеет не только метод print(), но и, так же, метод write(). К слову - на месте Serial может оказаться любой наследник класса Stream... даже EthernetClient. Как видно из кода - предполагается, что у МК можно временно позаимствовать несколько сотен байт, сэкономленных за счёт использования строк как PROGMEM-ресурса.
Примечание: передаваемые в функции *printf*() Си-строки обозначаются спецификатором %s, PROGMEM-строки - спецификатором %S.
P.S. За флейм насчёт printf() знаю.
Спасибо за тему.
Совсем забыл про, наверняка волнующий всех, вопрос: а что там с быстродействием?
Проведём для прояснения ситуации небольшой колхозный тест (без применения ассемблера и иных видов тяжелых наркотиков) и выясним относительную потерю в скорости.
На выходе имеем проигрыш PROGMEM в ~29,5%:
МК ATMega2560
RAM: 100000 cycles => 4427556 uS, 1 cycle => 44.28 uS
ROM: 100000 cycles => 5735328 uS, 1 cycle => 57.35 uS
МК ATMega328
думаю, что Пух имел в виду, что для константных строк первый sprintF
можно переписать вот так:
думаю, что Пух имел в виду, что для константных строк первый sprintF
Это-то ради бога - универсального способа нет и не будет. Пример был написан для иллюстрации того, что при бездумном применении F()/PSTR() можно ещё и бессмысленный перерасход по Programm Space получить.
Совсем забыл про, наверняка волнующий всех, вопрос: а что там с быстродействием?
Проведём для прояснения ситуации небольшой колхозный тест (без применения ассемблера и иных видов тяжелых наркотиков) и выясним относительную потерю в скорости.
На выходе имеем проигрыш PROGMEM в ~29,5%:
МК ATMega2560
RAM: 100000 cycles => 4427556 uS, 1 cycle => 44.28 uS
ROM: 100000 cycles => 5735328 uS, 1 cycle => 57.35 uS
МК ATMega328
Насколько понимаю, за экономию ОЗУ платим временем добывания данных из флеш командой lpm, она должна быть медленней: 3 такта вместо 2-х.
Я имел в виду разницу в скорости между ATMega2560 и ATMega328. Она небольшая, но есть. И это мне не особо понятно - ведь у нас не мультизадачность, всё такт в такт должно совпадать, теоретически.
А вот это уже не ко мне .. ничего сказать не могу. Возможно компиляция под платы разная и скорость "гуляет". Можно в "асм" глянуть в чем дело.. размер кода - одинаков? Должен отличаться только на размер таблицы прерываний и массивов Ардуино по преобразованию номера пина. Но, если Вы их не используете (а тут это так), то их и не должно быть в конечном коде. То есть, остается только размеры таблиц прерываний. Посмотрите.
Мега системой команд несколько отличается хотя бы из-за наличия "длинных" указателей
Ни разу не видел чтобы они применялись компилятором для мелких программ. Но, то что компилятор может создавать разный код для них (Нано, Мега) - уже натыкался.
В некоторых задачах, таких как, например, применение меню в интерфейсе пользователя, совместно используются как изменяемые переменные (например - значение метрики), так и неизменяемые (например - название метрики, единица измерения). Тратить на статические данные такой ценный ресурс, как RAM, жалко, а динамические в PROGMEM не положишь. Как же сделать так, чтобы пользоваться было удобно и ненакладно?..
Попробуем усидеть на двух стульях сразу и хранить изменяемую (динамическую) часть структуры памяти в RAM, а неизменную, (статическую) в PROGMEM.
1) Закомментированный макрос STATIC_DATA_USED_PROGMEM - статические данные хранятся в RAM
2) Раскомментированный макрос STATIC_DATA_USED_PROGMEM - статические данные хранятся в PROGMEM
Хороший тактический прием, но если решать стратигически задачу меню, то все приведеное выше окажется не так здоровски. Прежде всего лучше рассматривать меню , как некий автомат с состоянием - отображение конкретного екрана и написав отдельную функцию перехода в это состояния ,ну к к примеру go(/*на нужный экран*/ ) мы очень сокращаем себе головную боль , которую sadman41 решает тактически.
Пух, а лямбды где? Низачот.
Пух, а лямбды где? Низачот.
qwone, ну ты же сумеешь добавить в структуру стратегический указатель на функцию. Или надо иллюстративный материал обвесить автоматами так, чтобы, разбираясь в нём, захотелось срочно опрокинуть стакан для выхода из состояния когнитивного диссонанса?
Скорее структуру нафиг. Нечего инвалидов кормить.
Структура в меню это тоже костыль к которому все привыкли, хрен оторвешь.
Ну что ж, пока адепты святого Миллиса не начали лупить классами и сгонять в гулаги сторонников честного Си вперемешку с ардуинщиками - будем хромать, опираясь на структуры, и побираться у вокзалов.
Так и прогресс не стоит на месте. Может еще какой-то прием вылезет и уже эта схема отправится к инвалидам. Структура тем плоха , что не позволяет организовывать нестуктурированные окна в меню. Что препятсвует дополнению новых пунктов при модернизации программы.
qwone, остановись уже. Тут тема про PROGMEM, а не про алгоритмы, которые роятся у каждого из нас в голове. Нет никакой универсальной схемы построения меню (и всего другого) в рамках ограниченных ресурсов МК. Всегда выбирается решение под задачу. И эту задачу МК продолжает выполнять годами, не требуя ежедневного добавления пунктов меню.
Пух, а лямбды где? Низачот.
Патамушта сейчас в моде лямбды, а для перегрузок не сезон :(
Да, кстати, динамической перегрузки в этом языке нету :(
Так и прогресс не стоит на месте. Может еще какой-то прием вылезет
Срочно освойте prvalue и начинайте пихать перемещение во все "классы от Пуха" :)
А вот здесь вы не правы. Как раз написание меню и есть тот момент как использовать PROGMEM. Для чего может быть использован PROGMEM ? 1 - строки сообщений .2- создание различных таблиц,шрифтов и матриц изображения 3 - первичная настройка объектов 4- первичная настройка данных в EЕPROM. И все это так или иначе используется в меню. Так вот и с организации меню и использовании там PROGMEM и надо говорить. А иначе говорить не о чем. Все это давно написано в интернете и разжовано не один раз.
Дак это и понятно - кто автоматы и лямбды не пользует, тот правым быть не может по определению. И, да, в интернетах разжовано всё, кроме лямбд и автоматного программирования через классы. Посему я предлагаю немедля составить петицию модераторам, чтобы они выпилили с этого форума всё, что не относится к данным двум вещам. Чтобы, стало быть, Интернет не разорвало от переизбытка информации.
Срочно освойте prvalue и начинайте пихать перемещение во все "классы от Пуха" :)
Кстати, раз вы начали бесплатные советы давать, не могу не спросить - есть ли какие-то простые приёмы для доступа к массивам структур? Для веб-программистов вон хеш-массивы понаделали, а тут вот приходится извращаться:
Никакого волшебства нет, чтобы не плодить такие конструкции? Я, конечно, догадываюсь, что можно поменьше писать руками, размножив ЭТО через шаблоны...
Специализация шаблона для перегрузки опрератора<< .
Специализация шаблона от сюда https://www.youtube.com/watch?v=_kJJoWDNvxM
https://ru.stackoverflow.com/questions/495141/Перегрузка-операторов-и-в-шаблонном-классе-как-дружественных-функций
Пух, просто, само понятие "динамическое переопределение метода" имеет право на существование. В более продвинутых ООП языках есть динамическое создание/изменение классов причём бывает можно это сделать даже двумя способами - полиморфно и неполиморфно. Например, так в JavaScript - там правда перегрузки как таковой нет, но методы можно прицеплять и отцеплять на лету, причём как так, чтобы всем потомкам (а иногда и предкам) тоже цеплялось, так и без этого. А в С++ с динамически измененим классов напряг :(((
есть ли какие-то простые приёмы для доступа к массивам структур?
Простите, не понял вопроса. Приведите пример как "хорошо". Можно на JS или что Вы там под "языками для web" понимаете.
есть ли какие-то простые приёмы для доступа к массивам структур?
Простите, не понял вопроса. Приведите пример как "хорошо". Можно на JS или что Вы там под "языками для web" понимаете.
Например, что-то похожее:
Т.е., в сущности, хотелось бы получить удобный маппинг (в обе стороны) между полями этого массива и десятка таких же однотипных, но без написания под каждый тип структуры своей функции поиска сравнения с бегом по элементам.
подпишусь
Но скорее всего данный камень это уже не потянет.
Пух, просто, само понятие "динамическое переопределение метода" имеет право на существование. В более продвинутых ООП языках есть динамическое создание/изменение классов
Женя! Я же не за красивое название Питон люблю.... вернее -- не только за него. ;))