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

Сегодняшняя статья, как вы уже поняли из названия, будет посвящена микроконтроллерам STM32 и работе со встроенной Flash-памятью. Да-да, именно с той памятью, в которой хранится прошиваемая нами программа. Поскольку в STM32 нет EEPROM (энергонезависимой памяти) для хранения данных можно использовать Flash-память контроллера, и сегодня мы как раз и разберемся, как же это работает.
Сразу же скажу, что согласно документации Flash-память в STM32 позволяет осуществить минимум 10000 циклов перезаписи, что в принципе достаточно для того, чтобы использовать ее и в качестве энергонезависимой памяти для хранения неких данных.
Давайте для начала разберемся со структурой. Возьмем в качестве примера контроллер семейства STM32F10x, относящийся к High-Density устройствам (например, STM32F103VET6). Его память выглядит следующим образом:

Как видите, все жестко структурировано. 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?
- Не требуется использование внешней памяти, соответственно сокращается время на разработку платы и происходит удешевление продукта;
- Меньше программного кода, следовательно меньшее время затрачивается на разработку.
- Запись во Flash требует некоторого времени, что влияет на производительность МК в момент записи или очистки памяти;
- Чтобы внести изменения в уже существующие данные, нужно стереть всю страницу или записывать в «чистый» блок памяти;
- Количество циклов перезаписи гарантировано в районе 100 тысяч операций — вроде бы много, но перезаписывая данные в страницу раз в секунду, МК выработает ресурс flash чуть более, чем за сутки. Поэтому очень не рекомендую постоянно писать во flash, рекомендуется производить операции очистки / записи лишь для сохранения данных в «энергонезависимой» памяти. В остальных случаях работаем с оперативной памятью;
- Минимум Вы можете использовать 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 и дальше использовать его: