Как записать переменную во флеш память 80c51f120
Перейти к содержимому

Как записать переменную во флеш память 80c51f120

  • автор:

STM32. Обзор и работа с Flash-памятью микроконтроллера.

Сегодняшняя статья, как вы уже поняли из названия, будет посвящена микроконтроллерам STM32 и работе со встроенной Flash-памятью. Да-да, именно с той памятью, в которой хранится прошиваемая нами программа. Поскольку в STM32 нет EEPROM (энергонезависимой памяти) для хранения данных можно использовать Flash-память контроллера, и сегодня мы как раз и разберемся, как же это работает.

Сразу же скажу, что согласно документации Flash-память в STM32 позволяет осуществить минимум 10000 циклов перезаписи, что в принципе достаточно для того, чтобы использовать ее и в качестве энергонезависимой памяти для хранения неких данных.

Давайте для начала разберемся со структурой. Возьмем в качестве примера контроллер семейства STM32F10x, относящийся к High-Density устройствам (например, STM32F103VET6). Его память выглядит следующим образом:

STM32 Flash-память.

Как видите, все жестко структурировано. Information Block содержит 2 раздела:

  • System memory — тут хранится системный bootloader (забегая вперед скажу, что следующие статьи на нашем сайте будут целиком и полностью посвящены именно работе с bootloader’ом)
  • Option bytes — информация о защите основной области памяти.

И, собственно, второй блок — Main memory — именно тут хранится записанная нами в контроллер программа. Этот блок, в свою очередь, разделен на страницы по 2 Кб (в данном случае мы имеем 256 страниц и, соответственно, общий объем памяти составляет целых 512 Кб). Как вы уже поняли, Flash-памяти у STM32 более чем достаточно, почти всегда остается несколько свободных от основной прошивки страниц, которые как раз-таки можно использовать для хранения данных после выключения питания контроллера.

Но тут нельзя не упомянуть о некоторых ограничениях при работе с Flash. Перед записью определенной страницы она должна быть предварительна стерта («стертому» состоянию памяти соответствуют все биты, установленные в единицу). Соответственно, во время записи нужные биты могут быть «обнулены». Это приводит к ряду неудобств — например, у нас уже сохранено некоторое количество байт в определенной странице Flash-памяти. Для перезаписи одного байта нам нужно считать все ранее записанные, стереть страницу, а потом записать все байты обратно, включая измененный байт, который мы хотим сохранить.

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

С теорией все понятно, давайте рассмотрим некоторые практические моменты. Я буду, как и обычно, использовать SPL, а значит нам понадобятся файлы stm32f10x_flash.c и stm32f10x_flash.h в нашем проекте. И для того, чтобы работать с Flash-памятью нужно сначала ее разблокировать. Для этого в специальный регистр FLASH_KEYR необходимо записать два числа, одно за другим:

В SPL для этого реализована функция FLASH_Unlock() . После разблокировки уже можно стирать и записывать данные. Для очистки будем использовать функцию:

В качестве параметра мы должны передать в функцию адрес стираемой страницы. Итак, страница стерта, как записать данные? А для этого у нас есть:

С аргументами тут все понятно — передаем адрес ячейки памяти и собственно записываемые данные. Осталось понять, как же считать данные из Flash-памяти. А для этого просто:

Вот и все, ничего сложного, на этом сегодняшняя небольшая статья подходит к концу, в следующий раз мы будем обсуждать Bootloader, так что до скорого!

Как записать переменную во флеш память 80c51f120

Как записать переменную во флеш память 80c51f120

Почему FLASH?
  1. Не требуется использование внешней памяти, соответственно сокращается время на разработку платы и происходит удешевление продукта;
  2. Меньше программного кода, следовательно меньшее время затрачивается на разработку.
  1. Запись во Flash требует некоторого времени, что влияет на производительность МК в момент записи или очистки памяти;
  2. Чтобы внести изменения в уже существующие данные, нужно стереть всю страницу или записывать в «чистый» блок памяти;
  3. Количество циклов перезаписи гарантировано в районе 100 тысяч операций — вроде бы много, но перезаписывая данные в страницу раз в секунду, МК выработает ресурс flash чуть более, чем за сутки. Поэтому очень не рекомендую постоянно писать во flash, рекомендуется производить операции очистки / записи лишь для сохранения данных в «энергонезависимой» памяти. В остальных случаях работаем с оперативной памятью;
  4. Минимум Вы можете использовать 1 страницу памяти (даже для одного байта), а размер одной страницы составляет от одного до двух килобайт, в зависимости от модели микроконтроллера. Такова селяви устройства flash-памяти.
  • Для устройств серии XL-density: объем памяти составляет до 1Mb, разбитый на два банка памяти:
  • Для других устройств: объем памяти составляет до 512KB, состоящий из одного банка памяти размером до 512KB.
  • Сама по себе Flash-память состоит из трех блоков: основной блок памяти, информационный блок и блока регистров управления Flash. Структура блоков памяти и их характеристики приведены на рисунке №1.

  • Чтение из памяти с возможностью буферизации (2 слова по 64 бита);
  • Операции записи и очистки памяти;
  • Организация защиты flash от чтения и/или записи.
Операции с flash-памятью
Инициализация Flash.
  • Уровень «0», если 0 <SYSCLK ≤ 24 MHz;
  • Уровень «1», если 24 MHz < SYSCLK ≤ 48 MHz;
  • Уровень «2», если 48 MHz < SYSCLK ≤ 72 MHz;

Address offset: 0x00
Reset value: 0x0000 0030

Биты Название Описание
31:6 Зарезервировано
5 PRFTBS — Prefetch buffer status Состояние буферизации.
0: отключена; 1: включена. Бит доступен только для чтения.
4 PRFTBE — Prefetch buffer enable Включение буферизации.
0: отключена; 1: включена.
3 HLFCYA — Flash half cycle access enable Доступ к полному циклу Flash.
0: отключен; 1: включен.
2:0 LATENCY — Latency Управление задержкой.
Эти биты представляют отношение периода SYSCLK (системных часов) к времени доступа Flash.
000 Уровень «0», если 0 <SYSCLK≤ 24 МГц
001 Уровень «1», если 24 МГц <SYSCLK ≤ 48 МГц
010 Уровень «2», если 48 МГц <SYSCLK ≤ 72 МГц
Чтение данных из flash-памяти.
Запись данных во flash-память.
Использование структур данных.

Блокировка чтения / записи Flash
Программное исполнение.
Модифицированная функция установки/снятия защиты от записи Flash.
  • добавлен параметр «NewState», который отвечает за установку или снятия флага защиты;
  • в теле функции добавлена проверка на значение этого параметра и, в зависимости от него, устанавливаются или сбрасываются соответствующие биты.
  • сброс OperationByte производится не перед вызов функции, а внутри нее. Это связано с особенностью записи во Flash, поэтому сначала запоминаются предыдущие значения OperationByte, производится очистка OperationByte, а затем уже записываются новые значения с учетом предыдущих.
Использование утилиты STM32 ST-LINK Utility.

Флеш память разделена на страницы. Размер страницы зависит от общего объема флеш памяти — смотри в даташите.
Например, для тини13 есть такая табличка:

Небольшое дополнение: RWW и NRWW

Вообще, в контроллерах с поддержкой бутлоадера, весь флеш поделен на 2 части: RWW (Read-While-Write) и NRWW (No Read-While-Write). NRWW распологается в конце памяти и занимает место отведенное под бутлоадер. Т.е. размер этой области памяти равен максимальному размеру BLS. Остальное место (с начала флеша и до NRWW) занимает RWW область.

STM32 — память


Конечно же в вашем микроконтроллере нет никаких гигабайтов памяти однако камень 32-х битный, а значит можно адресовать (обратится по адресу) до 4ГБ. Это стандартизированная модель обеспечивающая переносимость кода между различными микроконтроллерами stm32, а так же дающая возможность подключать внешнюю память и совершенно спокойно обращаться к ней. То же самое касается и периферии, производитель микроконтроллеров на базе ядра Cortex, может напихать туда оооочень много всего и при этом не беспокоиться о нехватке адресов. Что же касается размера, то некоторых адресов просто нет, вместо них зарезервированные пустоты. Если обратится к зарезервированной области, то произойдёт аппаратный сбой процессора.


Reference manual — rm0008 стр. 51

Прежде чем продолжать, надо скачать программу STM32CubeProgrammer с помощью которой можно ковыряться в микроконтроллере. Прога хороша тем, что есть варианты для и


Нажимаем верхнюю левую кнопку (с карандашиком), и на вкладке Device memory видим программу залитую в камень. Можно указать адрес, с которого читать (по умолчанию стоит начальный адрес флеш-памяти) и сколько байт прочитать. Кнопочка Read считывает данные и показывает как на картинке. Если вместо Read выбрать Save As, то программа сдампится в указанный файл (надо только размер считываемых данных правильно указать, а то по умолчанию 1К).

Кнопка открывает интерфейс для загрузки прошивки в МК…


Выбираем нужный файл, очищаем флеш полностью (Full chip erase) или выборочные страницы, и жмём Start Programming.

— что-то связанное с внешним загрузчиком или внешним девайсом, не знаю, не вникал.

— очищает флеш.


Connectivity line devices — это, на сколько я понимаю, микроконтроллеры которые умеют работать с интернетом. Programming manual — PM0075 стр. 5.

Теперь нажмём кнопочку чтоб появился список Option bytes…


… и вернёмся к изучению памяти.


Бит RDP — если установить, то нельзя будет ни прочитать прошивку, ни загрузить новую. Чтобы установить, нужно поставить галочку, нажать кнопку Apply и обресетить МК. Снимается так же. При снятии защиты, существующая в МК прошивка будет удалена. Такой механизм гарантирует невозможность прочесть прошивку в залоченом камне.


AA — нет защиты.
BB — защита включена.
СС — микроконтроллер безвозвратно заблокирован от чтения/записи. Warning! Если установите этот уровень защиты, то больше никогда не сможете прошить МК. Операция необратима.


Тут комментировать особо нечего, думаю и так всё понятно. Однако очень не рекомендую снимать галочку с вачдога — камень будет постоянно ресетится. Настройки применяются так же, галочку сняли/поставили, и кнопка Apply. В более мощных МК есть доп. настройки.


Сюда можно сохранить какие-то свои данные, правда не много


Защита от записи конкретных страниц флеш-памяти. К этому вернёмся чуть позже.


RM0008 стр. 55. (Рис. 1)


Часть страниц по 16К, одна 64К, а часть по 128К. Здесь страницы называются секторами.


127-я страница на камне F103C8хх.


У low-density используется только WRP0 и защищает весь флеш, а у medium-density можно делать это выборочно. Зачем нужны остальные биты (WRP4 и т.д.) я так и не понял, может они зарезервированы.

То же самое происходит при переходе из одной функции в другую, например из в


Стек начинается от самого большого адреса и при увеличении размера ползёт вниз (записывая данные в меньшие адреса), а куча наоборот, при увеличении размера ползёт вверх, поэтому при выделении памяти на куче нужно следить чтоб эти области не налезли друг на друга.


Вся наша оперативка находится внутри узенькой полоски, а всё что выше не существует. То есть стек, куча, bss и data находится внутри этой полоски, а text в жёлтой области.


dec и hex это общий размер первых трёх значений.


Выполняется логическое «И», и в ячейку записывается нужное нам число 7.


Выполняется логическое «И» между старым значением 0х07 и новым 0x0d. В результате вместо желаемого числа 13, в ячейку записывается число 5.


Всё окей. Не забывайте нажимать Disconnect

Всем спасибо

Как записать переменную во флеш память 80c51f120

TINY_F, NEAR_F, SWITCH, DIFUNCT, CODE, FAR_F, HUGE_F, INITTAB, TINY_ID, NEAR_ID и CHECKSUM.

Функция Аргументы Возвращает
ReadFlashByte() MyAddressType flashAdr unsigned char
ReadFlashPage() MyAddressType flashStartAdr, unsigned char *dataPage unsigned char
WriteFlashByte() MyAddressType flashAddr, unsigned char data unsigned char
WriteFlashPage() MyAddressType flashStartAdr, unsigned char *dataPage unsigned char
RecoverFlash() void unsigned char

STM32 — память


Конечно же в вашем микроконтроллере нет никаких гигабайтов памяти однако камень 32-х битный, а значит можно адресовать (обратится по адресу) до 4ГБ. Это стандартизированная модель обеспечивающая переносимость кода между различными микроконтроллерами stm32, а так же дающая возможность подключать внешнюю память и совершенно спокойно обращаться к ней. То же самое касается и периферии, производитель микроконтроллеров на базе ядра Cortex, может напихать туда оооочень много всего и при этом не беспокоиться о нехватке адресов. Что же касается размера, то некоторых адресов просто нет, вместо них зарезервированные пустоты. Если обратится к зарезервированной области, то произойдёт аппаратный сбой процессора.


Reference manual — rm0008 стр. 51

Прежде чем продолжать, надо скачать программу STM32CubeProgrammer с помощью которой можно ковыряться в микроконтроллере. Прога хороша тем, что есть варианты для и


Нажимаем верхнюю левую кнопку (с карандашиком), и на вкладке Device memory видим программу залитую в камень. Можно указать адрес, с которого читать (по умолчанию стоит начальный адрес флеш-памяти) и сколько байт прочитать. Кнопочка Read считывает данные и показывает как на картинке. Если вместо Read выбрать Save As, то программа сдампится в указанный файл (надо только размер считываемых данных правильно указать, а то по умолчанию 1К).

Кнопка открывает интерфейс для загрузки прошивки в МК…


Выбираем нужный файл, очищаем флеш полностью (Full chip erase) или выборочные страницы, и жмём Start Programming.

— что-то связанное с внешним загрузчиком или внешним девайсом, не знаю, не вникал.

— очищает флеш.


Connectivity line devices — это, на сколько я понимаю, микроконтроллеры которые умеют работать с интернетом. Programming manual — PM0075 стр. 5.

Теперь нажмём кнопочку чтоб появился список Option bytes…


… и вернёмся к изучению памяти.


Бит RDP — если установить, то нельзя будет ни прочитать прошивку, ни загрузить новую. Чтобы установить, нужно поставить галочку, нажать кнопку Apply и обресетить МК. Снимается так же. При снятии защиты, существующая в МК прошивка будет удалена. Такой механизм гарантирует невозможность прочесть прошивку в залоченом камне.


AA — нет защиты.
BB — защита включена.
СС — микроконтроллер безвозвратно заблокирован от чтения/записи. Warning! Если установите этот уровень защиты, то больше никогда не сможете прошить МК. Операция необратима.


Тут комментировать особо нечего, думаю и так всё понятно. Однако очень не рекомендую снимать галочку с вачдога — камень будет постоянно ресетится. Настройки применяются так же, галочку сняли/поставили, и кнопка Apply. В более мощных МК есть доп. настройки.


Сюда можно сохранить какие-то свои данные, правда не много


Защита от записи конкретных страниц флеш-памяти. К этому вернёмся чуть позже.


RM0008 стр. 55. (Рис. 1)


Часть страниц по 16К, одна 64К, а часть по 128К. Здесь страницы называются секторами.


127-я страница на камне F103C8хх.


У low-density используется только WRP0 и защищает весь флеш, а у medium-density можно делать это выборочно. Зачем нужны остальные биты (WRP4 и т.д.) я так и не понял, может они зарезервированы.

То же самое происходит при переходе из одной функции в другую, например из в


Стек начинается от самого большого адреса и при увеличении размера ползёт вниз (записывая данные в меньшие адреса), а куча наоборот, при увеличении размера ползёт вверх, поэтому при выделении памяти на куче нужно следить чтоб эти области не налезли друг на друга.


Вся наша оперативка находится внутри узенькой полоски, а всё что выше не существует. То есть стек, куча, bss и data находится внутри этой полоски, а text в жёлтой области.


dec и hex это общий размер первых трёх значений.


Выполняется логическое «И», и в ячейку записывается нужное нам число 7.


Выполняется логическое «И» между старым значением 0х07 и новым 0x0d. В результате вместо желаемого числа 13, в ячейку записывается число 5.


Всё окей. Не забывайте нажимать Disconnect

Всем спасибо

Как записать переменную во флеш память 80c51f120


Artwork by Benny Kusnoto

Работа с PROGMEM памятью

Часто бывает нужно сохранить в памяти микроконтроллера большой объём данных, которые не будут меняться в процессе работы, например:

  • Калибровочный массив
  • Текст названий пунктов меню
  • Просто какой-то текст
  • Посчитанная тригонометрия (синус, косинус)
  • Изображения для дисплея (bitmap)
  • И многое другое

Хранить такие данные в оперативной памяти (в виде обычной переменной) – не самая лучшая идея, ведь они не будут меняться, а место займут! Оперативной памяти всегда гораздо меньше, чем программной (Flash) памяти: в той же ATmega328 (Arduino UNO/Nano/Pro mini) – 32 кб Flash и 2 кб SRAM, в 16 раз меньше! Так что гораздо эффективнее хранить такие данные во Flash, он же программная память, он же program memory, он же PROGMEM. Но как?

Мы привыкли к тому, что переменные мы можем менять во время выполнения программы, на то они и переменные, на то и память называется динамической. А вот с Flash памятью всё не так просто – писать в неё может только программатор, при помощи которого загружается код программы, либо загрузчик (bootloader), который практически выполняет функцию программатора. Есть кстати модифицированный загрузчик, который позволяет иметь доступ к Flash памяти прямо из программы, но в этих уроках мы рассматриваем стандартные средства, в данном случае – утилиту PROGMEM. Для работы с PROGMEM используется встроенная библиотека avr/pgmspace.h, подключать её не нужно, она подключится сама (в версиях Arduino IDE выше 1.0).

Запись

Ключевое слово (модификатор переменной) PROGMEM позволяет записать данные во Flash память. Синтаксис такой:

Всё! Данные, в показанном случае массивы тип_данных будут помещены во Flash память. PROGMEM может работать со всеми целочисленными типами (8, 16, 32, 64 бита), float и char.

Важный момент! Модификатор PROGMEM можно применять только к глобальным (определённым вне функций) или статическим (глобальным или локальным, но со словом static ) переменным! Читай урок про типы данных. Полный список возможностей pgmspace можно посмотреть в документации.

Чтение

Если с записью всё очень просто (добавляется ОДНО ключевое слово), то с чтением всё гораздо интереснее: оно осуществляется при помощи специальных функций. Основная функция чтения из progmem – pgm_read_тип(адрес) . Мы можем использовать вот эти 4:

  • pgm_read_byte(data); – для 1-го байта (char, byte, int8_t, uint8_t)
  • pgm_read_word(data); – для 2-х байт (int, word, unsigned int, int16_t, int16_t)
  • pgm_read_dword(data); – для 4-х байт (long, unsigned long, int32_t, int32_t)
  • pgm_read_float(data); – для чисел с плавающей точкой

Где data – адрес (или указатель) сохранённого блока данных! Вспомните урок про указатели, чтобы понимать, о чём речь. Полный список возможностей pgmspace можно посмотреть в документации.

Одиночные числа

Рассмотрим запись и чтение одиночных чисел:

Что здесь важно помнить: читая отрицательные числа (например типы int и long ) нужно обязательно приводить тип, потому что PROGMEM хранит числа в беззнаковом представлении. Обратите внимание на чтение signed_data из примера выше, без приведения к int число выводится некорректно!

Одномерные массивы

С массивами чисел всё весьма ожидаемо:

Читай урок по массивам в блоке базовых уроков программирования.

Двумерные массивы

При создании двумерного массива нужно обязательно указывать размер хотя бы одной из размерностей:

Массив массивов

Можно хранить несколько массивов в одном, объявив так называемую таблицу ссылок, то есть ещё один массив, который содержит указатели на массивы данных. Этот вариант отличается от двумерного массива тем, что количество “столбцов” в каждой строке может быть любое, необязательно одинаковое:

Другая ситуация – есть у нас функция, которая принимает 1-мерный массив байтов (например), который хранится в PROGMEM. Например эта функция выводит на дисплей изображение, которое как раз и закодировано в массиве, и функция ожидает, что массив будет PROGMEMный и сама знает, как с ним работать (например так сделано в библиотеках GyverOLED и GyverGFX). Условный пример вывода:

Если изображений нужно вывести много и подряд, или иметь возможность вызывать “по номеру” – такой подход будет очень громоздким. Можно заменить его на список массивов и выводить по индексу. Но для этого придётся спуститься на уровень ниже – получить адрес массива из списка, и уже его передать в функцию:

Как это работает – мы читаем адрес указанного массива (т.к. массив – указатель сам на себя) в памяти и преобразуем его к (const uint8_t*) т.к. наша функция ожидает именно его. Что тут важно – мы читаем адрес массива в памяти, в AVR Arduino (Nano, UNO…) адресация 16 битная, поэтому адрес из PROGMEM списка мы читаем как pgm_read_word . Если работать на 32-битных МК (ESP8266, ESP32…), то нужно будет использовать pgm_read_dword !

Строки в PROGMEM

Строка (как массив символов) хранится в оперативной памяти программы. Речь идёт о строках вида:

PROGMEM позволяет хранить символьные строки в программной памяти. Это очень удобно, ведь большинство текста не меняется в процессе работы программы: названия пунктов меню, имена параметров запросов к сайту, статичные части веб-страниц и так далее. Далее по тексту для краткости будем называть такие строки PGM-строки, или строки в программной памяти.

Для манипуляций с PGM-строкой (вывод, сложение с другими строками, передача в функции) понадобятся дополнительные преобразования. Почему? Программа не знает, что строка хранится не в оперативной памяти: для неё это обычная const char* строка. Но данные ведь находятся в другой области памяти! Если начать читать их как обычную строку – можно прочитать “мусор”, либо программа и вовсе зависнет из за ошибок чтения по указанному адресу.

  • Для удобства программиста существует “тип данных” PGM_P , который является макросом на const char* , то есть это просто указатель на строку. Так сделано для того, чтобы визуально разделить в программе обычные строки от PGM-строк:
    • const char* – строка в оперативной памяти
    • PGM_P – строка в программной памяти

    Рассмотрим сначала запись, а потом чтение, при помощи разных инструментов.

    Глобальная строка

    Строка объявляется глобально, то есть вне функций в программе. Это удобно и выгодно в том случае, когда строка будет использоваться в программе несколько раз. Объявив её один раз, мы будем избегать дубликатов. Синтаксис следующий, строка объявляется как массив в примерах выше:

    Обращаться к этой строке в программе можно по её имени, message .

    Массив строк

    Иногда бывает удобно хранить несколько строк под одним именем, например для пунктов меню. В таком случае можно использовать массив строк, про него мы говорили в уроке про строки. Механизм абсолютно такой же, как у массива массивов, который мы рассмотрели чуть выше: создаём сами строки, а затем – массив с указателями на них:

    Обращаться к нужной строке в программе можно по индексу в массиве names , например names[1] .

    Локальная строка

    Иногда бывает удобно объявлять и использовать PGM-строки локально внутри функции, например если строка используется только в этой функции и больше нигде. Для этого нужно обернуть текст в макрос PSTR() , который поместит текст в PROGMEM и вернёт на него указатель типа const char* (используем PGM_P , чтобы не перепутать с обычной строкой):

    Чтение строк из PROGMEM

    Переписать в буфер

    Можно скопировать строку из PROGMEM в оперативную память для решения следующих задач:

    • Изменение строки
    • Работа как с обычной си-строкой в оперативной памяти
    • Отправка в функцию, которая принимает тип char* (строка в оперативной памяти)

    Для этого нужно:

    • Определить размер PGM-строки при помощи функции strlen_P() (вернёт длину строки без учёта завершающего символа)
    • Создать буфер – массив char такого же размера +1 символ (для завершающего нулевого символа строки)
    • Скопировать в буфер PGM-строку при помощи функции strcpy_P() (копирует строку вместе с завершающим символом)

    Преобразовать к __FlashStringHelper*

    Способ подходит для следующих задач:

    • Отправка PGM-строки в функцию, которая принимает тип __FlashStringHelper*
    • Прибавить PGM-строку к String -строке ( String поддерживает работу с __FlashStringHelper )
    • “Напечатать” PGM-строку при помощи print() / println() в монитор порта/на дисплей/веб/любой объект стандартного класса Print

    В Arduino-фреймворке есть очень удобный инструмент, позволяющий работать с PGM-строками, называется он __FlashStringHelper . Не углубляясь в подробности, будем считать что это просто ещё один тип строковых данных наряду с char* и String . Некоторые функции в библиотеках принимают этот тип данных (можно посмотреть в документации или заголовочном файле библиотеки), что позволяет передавать в них PGM-строки без лишних действий, нужно просто преобразовать переменную к (const __FlashStringHelper*) . Например:

    В “ядре” esp8266/esp32 для такого преобразования есть удобный макрос FPSTR(строка) , непонятно почему его не сделали для AVR Arduino. Можно объявить макрос самостоятельно и поместить его в начале программы:

    И предыдущий код будет выглядеть более компактно:

    Передать в _P функцию

    Способ подходит для следующих задач:

    • Передача PGM-строки в функцию, которая принимает тип PGM_P (в основном это функции с постфиксом _P )
    • Удобное помещение текста в PROGMEM и сразу передача в функцию, которая принимает тип PGM_P

    Некоторые функции в библиотеках поддерживают работу напрямую с PGM строками: они принимают тип данных const char* или PGM_P , а в имени обязательно имеют постфикс _P . Например write_P(PGM_P buf) из библиотеки к esp8266 (можно посмотреть в документации или заголовочном файле библиотеки). Это означает, что в такую функцию можно передать PGM-строку без дополнительных преобразований:

    F() макро

    Способ подходит для следующих задач:

    • Удобное помещение текста в PROGMEM и сразу передача в функцию, которая принимает тип __FlashStringHelper*
    • В том числе для сборки String -строк

    Вы могли подумать, а зачем создавать PSTR() строку и PGM_P переменную ради одного вывода? Действительно, может просто можно сделать print((const __FlashStringHelper*)PSTR(«Hello, World!»)) ? Да, можно! Причём всё уже придумано за нас и называется “F() macro”, этот удобный макрос позволяет ещё проще хранить строки в программной памяти для отправки в функции, которые поддерживают __FlashStringHelper (можно посмотреть в документации или заголовочном файле библиотеки):

    F() макро + __FlashStringHelper

    Также F-макро позволяет создавать и хранить строки типа __FlashStringHelper* . F-строки не оптимизируются компилятором, то есть например здесь

    Строки займут место в памяти программы как две строки, то есть компилятор не объединит две одинаковые строки, как это происходит при обычной работе со строками. Поэтому F-строку можно создать отдельно и передать в нужные функции или сложить со стрингой, но сделать это можно только локально:

    Проблема с циклами (AVR)

    Осталось нам обсудить ещё один момент: рассмотрим “массив строк” в прогмем, который мы называли str_list в примерах выше. Если выводить из него строки вручную – всё будет прекрасно работать, например через FPSTR() :

    Но стоит нам вывести все строки в цикле – программа сломается:

    Это происходит по крайней мере на AVR (Arduino Nano, UNO и так далее), на ESP8266/ESP32 чтение в цикле работает корректно.

    На AVR компилятор не смог оптимизировать процесс чтения, потому что он не знает значения переменной-счётчика в конкретной строке кода. Это можно обойти, преобразовав данные через функции из самого начала урока. Преобразовывать будем к размеру, который занимает указатель в памяти – это pgm_read_word для AVR (16 бит). В функцию нужно передать “адрес” переменной, а массив по сути является указателем сам на себя, то есть просто pgm_read_word(str_list) . Это адрес первого (нулевого) элемента. Соответственно для получения доступа к следующему элементу нужно увеличить адрес на 1 и получится pgm_read_word(str_list + i) . Да, отличается от доступа к массиву!

    Для удобства можно сохранить результат в указатель типа PGM_P и дальше использовать его:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *