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

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

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

Здесь приведен перевод апноута AVR106: C functions for reading and writing to Flash memory [1], посвященного подпрограммам чтения и записи памяти FLASH на языке C для микроконтроллеров AVR. Рассмотрены функции чтения и записи одного байта FLASH, чтение и записи одной страницы FLASH, опциональное восстановление после неожиданного пропадания питания. Рассмотренные функции могут использоваться с любым устройством (микроконтроллером AVR), которое имеет возможность записи памяти программ из кода приложения [2] (это почти вся линейка AVR микроконтроллеров Atmel). Вместе с апноутом AVR106 приведен также проект примера программы [3], использующей секцию памяти программ для сохранения параметров.

Почти все микроконтроллеры AVR® компании Atmel имеют так называемую возможность самопрограммирования (Self programming Program memory). Самопрограммирование является одной из особенностей технологии AVR. Чтобы лучше разобраться с этим, обратитесь к апноуту AVR109 [2]. Самопрограммирование позволяет AVR перепрограммировать собственную память FLASH во время работы программы. Это подходит для приложений, которые нуждаются в самостоятельном обновлении кода firmware (bootloader, загрузчики, бутлоадеры) или сохранении какой-либо информации в области памяти FLASH. Этот апноут предоставляет функции на языке C, которые позволяют получить доступ к области памяти FLASH.

[Ассемблерная инструкция SPM]

Память FLASH может быть запрограммирована с помощью инструкции SPM (Store Program Memory). На устройствах, поддерживающих самопрограммирование, память программ FLASH часто делится на 2 основные секции: секция основной программы (память приложения, Application Flash Section) и секция загрузки (Boot Flash Section). Прим. переводчика: секция основной программы начинается с адреса 0x0000, а секция загрузки занимает маленький блок памяти обычно 2..4 килобайта в конце FLASH.

На устройствах, которые имеют блок памяти загрузки (boot block), инструкция SPM имеет возможность записи во всю область памяти FLASH, но только в том случае, если она выполняется из области памяти загрузки. Выполнение SPM из секции приложения не дает никакого эффекта. На младших устройствах AVR, к которых нет блока загрузки, инструкция SPM может работать из любого места памяти FLASH.

Во время записи FLASH в секции загрузки работа CPU всегда останавливается. Однако большинство устройств могут выполнить код (чтение) из секции загрузки, когда происходит запись области памяти приложения (Application section). Очень важно, чтобы код, выполняемый при записи Application section, не пытался читать из Application section. Если это вдруг произойдет, то может быть нарушено общее выполнение программы.

Размер и область размещения этих двух областей памяти FLASH зависит от типа устройства и установки его фьюзов. Некоторые устройства имеют возможность выполнять инструкцию SPM из любого места области памяти FLASH.

[Процедура записи FLASH]

Технология памяти FLASH устроена так, что записывается эта память постранично. Запись осуществляется путем сохранения всей страницы во временном буфере страницы, перед тем, как страница будет записана во FLASH. Адрес, по которому будет записаны данные страницы, определяется по содержимому Z-регистра и RAMPZ-регистра. Страница должна быть очищена (erase) перед тем, как на страницу могут быть записаны данные из временного буфера. Функции, которые содержит этот апноут, используют следующую процедуру для записи страницы FLASH:

• Заполнение буфера страницы
• Очистка страницы
• Запись страницы

Как можно заметить, возможна потеря данных, если во время этой процедуры неожиданно произойдет сброс или пропадание питания сразу после очистки страницы. Потери данных можно избежать, если предварительно применить буферизирование записываемых данных в энергонезависимой памяти (nonvolatile memory, обычно EEPROM). Функции записи, содержащиеся в этом апноуте, предоставляют опциональное буферизирование при записи. Эти функции далее рассматриваются в секции «Описание firmware». Для устройств, которые имеют фичу read-while-write (чтение во время записи), бутлоадер может выполняться во время записи, и из функций не произойдет возврат, пока запись не завершится.

[Адресация FLASH]

Память FLASH в AVR поделена на 16-битные слова. Это означает, что каждый адрес FLASH означает ячейку из 2 байт данных. Для ATmega128 можно адресовать до 65k слов или 128k байт данных FLASH. В некоторых случаях память FLASH упоминается адресованной словами, и в других случаях — адресованной побайтно, что вносит некоторую путаницу. Все функции, содержащиеся в этом апноуте, используют байтовую адресацию. Соотношение между байтовым адресом и адресом слова следующее:

Байтовый адрес = Адрес слова * 2

Страница FLASH адресуется с использованием адреса байта для первого байта на странице. Соотношение между номером станицы (0, 1, 2, . ) и адресом байта следующее:

Байтовый адрес = номер страницы * размер страницы (в байтах)

Пример байтовой адресации: размер страницы FLASH у ATmega128 равен 256 байт. Байтовый адрес 0x200 (512) укажет на следующее:

• Байт FLASH 0x200 (512), что соответствует байту 0 на странице 2
• Страница 2 FLASH

Когда адресуется страница ATmega128, младший байт адреса всегда 0. Когда адресуется слово, то младший бит (LSB) адреса всегда 0.

[Реализация проекта примера, работающего с FLASH]

Проект firmware сделан для компилятора IAR. Функции могут быть портированы на другие компиляторы, но могут потребоваться некоторые усилия, поскольку используется специфическая мнемоника (intrinsic functions) компилятора IAR. Функции доступны через подключение файла Self_programming.h в главный модуль C, и добавление модуля Self_programming.c к проекту. Когда используется Self-programming (самопрограммирование), то важно, чтобы функции для записи размещались в пределах секции загрузки (Boot section) памяти FLASH. Размещение функций управляется путем использования определений сегмента памяти в файле конфигурации линкера (*.xcl). Все другие необходимые конфигурации, касающиеся firmware, сделаны в файле Self_programming.h.

PAGESIZE. Константа PAGESIZE должна быть определена равной размеру страницы FLASH (в байтах) используемого устройства.

__FLASH_RECOVER. Определение константы __FLASH_RECOVER разрешает опцию восстановления Flash, чтобы избежать потери данных при случайных пропаданиях питания. Когда восстановление разрешено, одна страница Flash выделена под буфер восстановления. Значение __FLASH_RECOVER будет определять адрес страницы FLASH, которая используется для этого. Адрес должен быть байтовым, указывающим на начало страницы FLASH, и функции записи не смогут записывать в эту страницу. Восстановление FLASH происходит вызовом функции RecoverFLASH() при старте программы (program startup).

ADR_LIMIT_LOW и ADR_LIMIT_HIGH. Диапазон памяти, в котором функциям разрешено записывать FLASH, задается константами ADR_LIMIT_LOW и ADR_LIMIT_HIGH. Функции могут записывать по адресам больше или равным ADR_LIMIT_LOW и меньше или равным ADR_LIMIT_HIGH.

[Размещение всего кода внутри секции загрузки]

Необходимо перезадать диапазон сегментов, определенных в файле настройки линкера по умолчанию (файл *.xcl), чтобы разместить весь код приложения в секции загрузки (Boot section) FLASH. Размещение и размер Boot section зависит от устройства и установок его фьюзов. Программирование фьюза BOOTRST перенесет вектор сброса в начало секции загрузки. Также можно перенести все вектора прерывания в секцию загрузки. Обратитесь к секции даташита на устройство, посвященной прерываниям, за инструкциями, как это сделать. Определения сегмента, которые должны быть изменены для размещения всего программного кода в секции загрузки:

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

В этом апноуте предоставляется в качестве примера файл lnkm128s.xcl, где задано размещение всего кода программы в 8 килобайт секцию загрузки Atmega128. Этот файл можно просто модифицировать для использования с другими устройствами, и в нем содержатся инструкции, как это осуществить.

[Размещение в секции загрузки только некоторых функций]

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

Определение сегмента загрузки (Boot segment) в файле *.xcl для ATmega128 с размером 8 килобайт:

1. Сделайте новое определение размера секции загрузки.

2. Задайте новый сегмент для всей секции загрузки, основанный на определении шага 1.

Размещение функций C в заданном сегменте:

Пример кода на языке C, приведенный выше, разместит функцию ExampleFunction() в определенный сегмент памяти BOOT_SEGMENT.

[Описание firmware]

Код firmware состоит из 5 функций на языке C, и один проект примера для IAR версии 2.28a / 3.10c для ATmega128. Проект примера сконфигурирован так, чтобы разместить код всего приложения в секцию загрузки (Boot section) памяти FLASH. Этот код можно использовать как стартовую точку Вашей программы, которая может записывать FLASH. В таблице показаны функции, которые осуществляют доступ к памяти FLASH.

Функция Аргументы Возвращает
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

Тип данных MyAddressType определен в Self_programming.h. Размер этого типа зависит от используемого устройства. Для устройств, у которых FLASH больше 64 килобайт, он будет задан как long int, и int для устройств, у которых объем FLASH меньше или равен 64 килобайта (адрес байта 16-битный). Типы данных в реальности используются как указатели __flash или __farflash (соответственно 16 и 24 бит). Причина определения нового типа в том, что использование целочисленных типов более удобно, чем типов указателя.

ReadFlashByte() возвращает 1 байт, который размещен по адресу FLASH, указанном во входном аргументе.

ReadFlashPage() читает одну страницу памяти FLASH от адреса ucFlashStartAdr, и сохраняет данные в массиве pucDataPage[]. Количество сохраняемых байт зависит от размера страницы FLASH. Функция вернет FALSE, если входной адрес не является адресом страницы FLASH, иначе вернет TRUE.

WriteFlashByte() запишет байт ucData по адресу FLASH ucFlashAddr. Функция вернет FALSE, если входной адрес не является допустимым адресом FLASH для записи, иначе вернет TRUE.

WriteFlashPage() запишет данные из массива pucDataPage[] в страницу FLASH по адресу ucFlashStartAdr. Количество записываемых байт зависит от размера страницы FLASH. Функция вернет FALSE, если входной адрес не является допустимым адресом страницы FLASH для записи, иначе вернет TRUE.

RecoverFlash() прочитает переменную состояния в EEPROM, и восстановит страницу FLASH, если это необходимо. Функция должна быть вызвана в начале программы, если разрешена опция восстановления FLASH (__FLASH_RECOVER). Функция вернет TRUE, если имело место восстановление, иначе вернет FALSE.

[Как работает восстановление FLASH]

Когда разрешена опция восстановления FLASH, запись страницы вовлекает предварительное сохранение данных в выделенную страницу восстановления FLASH, перед тем как произойдет действительная запись во FLASH. Адрес записываемой страницы сохраняется в EEPROM совместно с байтом статуса, который показывает, что страница восстановления FLASH содержит данные. Этот байт состояния будет очищен, когда произойдет успешное завершение операции записи указанной страницы. Переменные в EEPROM и буфер восстановления FLASH используются функцией восстановления RecoverFlash(), чтобы восстановить данные, если это необходимо. Запись одного байта в EEPROM занимает примерно такое же время, как и запись всей страницы во FLASH. Так что нужно иметь в виду, что разрешение опции восстановления приведет к значительному увеличению времени записи. EEPROM используется вместо FLASH, потому что выделение нескольких байт в FLASH исключит гибкое использование всей страницы, которая будет содержать эти байты.

STM32 — память

В статье рассказано про организацию памяти микроконтроллера stm32, использования flash’а для хранения пользовательских данных, и про всякие пользовательские биты/биты защиты.


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

Для примера часть адресов блока Peripheral микроконтроллера F103…


Reference manual — rm0008 стр. 51

Тут видны адреса некоторых таймеров, портов GPIO, и другой периферии. Все они находятся выше 0x40000000 и ниже 0x5fffffff.

Если попробовать прочитать зарезервированный адрес…

То получим Hard Fault…

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

Запускаем и жмём Connect, подключатся можно через ST-Link или UART…


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

Если нажать вкладку Open file, то можно открыть какой-нибудь .hex или .bin файл прямо в программе.

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


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

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

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

В нижней части лог подключения, а справа-снизу полезная информация о вашем МК. Здесь интересны два пункта…

Device — серия микроконтроллера и плотность (Medium-density).

Некоторые микроконтроллеры stm32, в рамках одной серии, например популярный F103, он же BluePill, выпускается не только в разных корпусах с разным количеством «ножек», но и разной плотности (объёмом флеш-памяти).


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

Device ID — код, который зашит в системный бутлоадер, и по нему ST-Link определяет что это за микроконтроллер.

Коды можно посмотреть в AN2606 стр. 307.

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


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

Всё что выше Peripheral особого интереса не представляет, SRAM — это оперативная память, а Code Area выглядит так…

Option bytes — здесь находятся различные биты для настройки МК. Защита от чтения/записи, включение/отключение вачдогов, и несколько пользовательских битов для хранения какой-либо инфы.


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

В более серьёзных камнях RDP имеет несколько уровней…


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


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


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


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

System memory — это область в которой располагается системный загрузчик (bootloader), он зашивается на заводе при изготовлении, его нельзя ни удалить, ни изменить.

Bootloader это такая небольшая программка, которая позволяет прошивать микроконтроллер через различные интерфейсы. Если подтянуть пин BOOT_0 к «земле» и нажать ресет, то загрузиться bootloader и будет ожидать поступления новой прошивки. Простенькие камни, типа F103, можно прошивать только через USART, а более «крутые» позволяют делать это и через другие интерфейсы. Посмотреть это можно в AN2606 стр. 25. Например вот…

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


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

Information block — это описанные выше System memory и Option Bytes.

Flash memory interface registers — это регистры отвечающие за работу Flash-памяти, они находятся в области Peripheral .

Main memory — это и есть наша Flash-память.

Flash -память организована постранично, а размер страницы колеблется от одного до нескольких килобайт, в зависимости от серии микроконтроллера. У F103 (Рис. 1) размер равен 1К, а вот например у F4 выглядит так…


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

OTP area — это биты защиты, которые можно записать только один раз (One-Time-Programmable). Больше ничего про это сказать не могу — у меня нет такой платы.

Тут есть один любопытный момент. Компания ST выпускает два почти одинаковых камня F103, один F103C8хх c объёмом флеш-памяти 64К (BluePill), и F103CBхх c объёмом 128К. Однако на самом деле у обоих этих камней объём флеш-памяти равен 128К (128 страниц по 1К).

Во-первых в мануале нет камней с 64-мя страницами (см. рис. 1), а во-вторых, не смотря на то, что ST-Link показывает объём 64К, у всех моих F103C8хх читаются и пишутся все 128 страниц. То есть программу размером больше 64К залить через TrueStudio не удаётся, а вот производить чтение/запись этих областей из своей программы можно. Да и STM32CubeProgrammer их тоже совершенно спокойно читает…


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

Выше я говорил что мы вернёмся к вопросу защиты конкретных страниц от чтения/записи…

Биты WRPх защищают сразу по несколько страниц. Количество защищаемых страниц зависит от плотности (density) МК.

Вот вырезка из Programming manual стр. 21…


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

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

CCM SRAM — у некоторых микроконтроллеров есть дополнительная оперативная память под названием Core Coupled Memory. Эта память подключена непосредственно к ядру, благодаря чему скорость доступа и исполнение кода будет быстрее чем в обычной SRAM. Соответственно в CCM выгодно размещать всякие статистические переменные/массивы, а при желании можно перенести туда стек/кучу/.data/.bss.

Cледует помнить, что доступ к CCM имеет только процессор, а значит DMA не сможет к ней обратиться. CCM бывают разные для разных микроконтроллеров, поэтому прежде чем начинать использование следует изучить документацию. Ещё инфа, и ещё инфа.

В качестве примера я перенёс пару массивов в ССМ (среда TrueStudio, камень F303)

Объявляем массивы глобально:

Чтоб компилятор не «оптимизировал» эти массивы, сделайте что-нибудь с ними, например выведите на печать.

Вуаля, они лежат где надо…

Прежде чем говорить о переносе стека/кучи/.data/.bss в ССМ, я очень кратко и обобщённо поясню что означают эти термины:

Стек — область памяти в ОЗУ, куда сохраняется адреса программы в момент возникновения прерывания или перехода в другую функцию.

Пошагово выглядит это так:

Наша программа работает-работает (указатель «бежит» последовательно по адресам).

Происходит прерывание — указатель перепрыгивает в обработчик этого прерывания.

Адрес, где находилась программа в момент возникновения прерывания, записывается в стек (чтоб запомнить куда возвращаться).

После того как обработка прерывания закончится, из стека вытаскивается адрес на который нужно вернуться.

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

Технически, стек можно представить в виде стопки книг где нельзя взять вторую сверху книгу не сняв предварительно первую. То есть стек работает по принципу LIFO буфера (Last In First Out) — «последним пришёл, первым вышел». Такая организация очень хорошо себя оправдывает. Представьте себе такую ситуацию:

Указатель перепрыгнул из функции main() в bla_bla() .

В стек записался адрес того места откуда выпрыгнул указатель.

Идёт обработка функции bla_bla() и в этот момент происходит прерывание.

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

В результате на стеке лежат два адреса — адрес, на котором была приостановлена функция main() , а поверх него адрес на котором приостановлена функция bla_bla() .

По окончании обработки прерывания система лезет в стек, «снимает со стопки» самый последний адрес, переходит по нему и заканчивает выполнение функции bla_bla() с того места где она была прервана. Больше этот адрес не нужен и он выкидывается в помойку, на стеке остаётся адрес приостановки функции main() .

Как только функция bla_bla() завершается, система опять лезет в стек и «снимает» адрес приостановки функции main() . Адрес опять же летит в помойку, а функция main() продолжает выполняться. Таким образом, снимая «книжку за книжкой» система не сможет вернуться куда-то не туда и нарушить целостность программы.

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

Куча — эта область ОЗУ, которая используется для динамического выделения памяти в процессе работы программы. То есть, когда вы делаете malloc/calloc/realloc, то память выделяется на куче.

bss — область ОЗУ, сюда помещаются не инициализированные глобальные переменные (uint8_t var;).

data — область ОЗУ, сюда помещаются инициализированные глобальные переменные (uint8_t var = 0;).

text — Flash, здесь лежит сама программа.

Выглядит всё это хозяйство следующим образом…


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

Чтоб лучше понять иллюстрацию выше, откройте файл STM32F103C8_FLASH.ld и найдите там такие строки…

Нас интересует переменная _estack , которая указывает на конец оперативной памяти. У микроконтроллера stm32F103c8 оперативная память располагаться в области от 0x20000000 до 0x20005000 , что равно 20480 байтам (20Кб). Визуально это выглядит так…


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

Посмотреть сколько места занимают некоторые данные можно в среде разработки. Вот картинка из TrueStudio…


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

Чтоб перенести какую-то из этих областей в ССМ, нужно подкорректировать файл компоновщика (или линковщика, называйте как вам удобнее) с расширением .ld …

Находим там интересующие нас блоки…

Меняем у каких-нибудь блоков, или у всех сразу, слово RAM на CCMRAM …

В результате получаем — до:

Запись данных во флеш

У большинства микроконтроллеров stm32 (кроме серии L0) нету EEPROM, поэтому сохранять пользовательские данные приходится во флеш-памяти. Количество циклов записи 10000.

Чтобы записать во флеш какие-то данные нужно вначале её очистить (во время очистки все биты заполняются единицами — 0xFF). Очистка происходит постранично/посекторно.

Очистка одной страницы на F103…

Объявляем структуру в которую заносятся параметры очистки:

TypeErase — что хотим очистить, какую-то конкретную страницу (или несколько), или всю флеш полностью.

PageAddress — начальный адрес страницы, которую хотим очистить. Адреса можно посмотреть в мануале (правда там не все видны), либо посчитать самостоятельно, либо в примерах Куба — STM32CubeFx в файле main.h (для F103 — /STM32Cube_FW_F1_V1.8.0/Projects/STM32F103RB-Nucleo/Examples/FLASH/FLASH_EraseProgram/Inc/main.h). В конце есть ссылка на гитхаб с этим примером, там есть хедер (addr_pages.h) с задефайнеными адресами для BluePill.

NbPages — кол-во страниц для очистки. Если указать несколько, то они будут очищены начиная с адреса указанного выше.

Banks — у «жирных» камней память делиться на банки, поэтому нужно указать в каком именно банке находятся страницы.

HAL_FLASH_Unlock() — снимаем блокировку стирания/записи во флеш (для чтения этого делать не нужно). Это не относится к битам WRPx, если они установлены для текущей страницы, то стереть/записать страницу не получится.

Ну, а дальше стираем страницу/страницы, и возвращаем блокировку. Если что-то пойдёт не так, то программа выведет ошибку и зациклится.

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

Почему нужно очищать (заполнять значениями 0xFF) память?

Дело в том, что когда происходит запись байта в ячейку, то биты в этой ячейки не просто так берут и перезаписываются, а совершается операция логического «И» над тем что есть в ячейке и новым значением.

Наглядно это выглядит так. Ячейка у нас очищена (биты заполнены единицами) и мы записываем в неё число 7 (0х07)


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

А теперь допустим что мы хотим записать в эту же ячейку число 13 (0x0d) поверх старого значения…


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

Запись

У разных МК можно записывать разную длину «слова», например у F103 можно записать «слова» размером 16, 32 и 64 бита. Подсмотреть это можно в файле stm32f1xx_hal_flash.h …

Запишем два 16-ти (FLASH_TYPEPROGRAM_HALFWORD) битных числа в начало только что очищенной страницы…

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

Если записывать 32-х (FLASH_TYPEPROGRAM_WORD) битное число, то увеличим адрес на четыре, а если 64-х (FLASH_TYPEPROGRAM_DOUBLEWORD) битное, то на 8. Если в дальнейшем захотите добавить в эту страницу ещё что-то, то нужно запомнить адрес.

Идём в STM32CubeProgrammer, вписываем адрес 127-ой страницы и смотрим чего понаписали…


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

Прочитаем то, что записали…

Разблокировать не надо, а адрес опять же увеличиваем на два. Получаем ожидаемое…

Чтоб записать 8-ми битное значение (не смотря на то, что производитель не предоставил такой возможности), надо просто записывать по два байта в одно 16-ти битное «слово» со сдвигом. Всё происходит почти так же как и с 16-ти битным числом…

Увеличиваем адрес на 4 (чтоб добавить данные вслед за предыдущими), записывать будем массив, размер которого должен быть кратен двум (для этого сделана проверка), а в цикле запихиваем два символа в одно 16-ти битное «слово» и отправляем по адресу.

Читать можно по одному 8-ми битному символу.

Вот и всё, остаётся придумать как хранить адрес (со смещением) последней записи для добавления последующих, чтобы не мучить одну и туже ячейку, и не прикончить её в самом скором времени. Вот вариант как смещать очередную запись.

Всем спасибо

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


Artwork by Benny Kusnoto

Когда я писал драйвер FM-модуля RDA5807, то у меня возникла необходимость сохранять куда-то найденные станции, чтобы на них можно было переключиться минуя поиск. Но когда я полез в документацию STM8, чтобы поискать, как это можно было бы осуществить, то понял, что EEPROM и FLASH в STM8 — это отдельная подсистема микроконтроллера, и изучать ее надо всю.

Один из режимов записи в EEPROM/FLASH требует выполнения из ОЗУ. Вопрос копирования кода в ОЗУ и выполнения его оттуда я затрагивал в предыдущей статье, однако там вся реализация была на ассемблере. Сейчас же мне захотелось показать как это делается в Си.

В качестве компилятора я выбрал COSMIC, по которому уже как-то писал быстрый старт. Но тогда я писал об использованию COSMIC совместно с SPL библиотекой. На этот раз мне хочется раскрыть тему программирования в COSMIC, используя «чистый» Си в связке с ассемблером. Правда должен оговориться, что несколько отредактированные загловочные файлы из SPL в этой статье я все-таки использовать буду, т.к. нужны будут именновые константы масок периферийных регистров.

В итоге статья получилось составленной из двух взаимосвязанных тем: сначала рассматривается вопрос использования компилятора COSMIC, а затем, как с его помощью сохранять данные в EEPROM/FLASH памяти микроконтроллера.

В качестве Develop Board я буду использовать собственную плату с чипом STM8S105C4T6. Это Medium-Density чип с 16 КБ флеш-памяти, 2 КБ ОЗУ и 1 КБ ЭСППЗУ(EEPROM). Он более интересен чем STM8S103-й чип, т.к. в 105-ом имеется встроенный загрузчик(bootloader), механизм read-while-write (RWW), а размер блока составляет 128 байт вместо 64 байт на 103-м чипе. Вы в свою очередь можете использовать фирменную отладочную плату STM8S-DISCOVERY с чипом STM8S105C6T6. Там флеш-памяти будет побольше — 32 КБ. На худой конец, можно воспользоваться ещё одной китайской платой на 105-м чипе. Также как в STM8S-DISCOVERY в ней установлен кварц на 8 МГц. Сама плата выполнена в форм-факторе удобном для установки в беспаячную макету.

Cosmic у меня работает в связке с STVD, обе программы установлены на виртуалку, которая в свою очередь установлена в Linux. Гостевой ОС в виртуалке служит Windows XP SP3. О превратностях установки Cosmic я уже писал в вышеупомянутой статье два года назад. К сожалению, я тогда я не упомянул, что получить регистрационный ключ можно онлайн. Т.е. не надо ждать несколько дней чтобы ключ скинули на e-mail, как было в моем случае. Если не ошибаюсь, ключ действует один год, и по истечении регистрационного периода, его нужно получать заново. Кроме того, ключ «слетает» при копировании виртуальной машины. В этом случае его также следует получать по новой. В последнем случае я просто удалял Cosmic и затем ставил его заново, получая свежий ключ. Сейчас у меня следующая версия компилятора:

Так же как и в предыдущей статье, для контроля кода прошивки я буду использовать дизассемблер из комплекта утилит stm8-binutils.

    Список используемой документации:
  1. Cosmic CXST7 — User Manual — документация по компилятору COSMIC
  2. Reference Manual STM8S — RM0016, глава 4 «Flash program memory and data EEPROM»
  3. Programming Manual PM0051 — руководство по записи в EEPROM и FLASH память для микроконтроллеров STM8S/STM8A
  4. Application note — AN2659, — глава пятая — «Configuring the Cosmic compiler for RAM code execution».
  5. Работа с EEPROM и Flash / STM8 / Сообщество EasyElectronics.ru хорошая вводная статья по теме на русском языке. Написано по существу, без лишней воды.
    I. Основы работы со связкой COSMIC + STVD
  1. Создание базового проекта в среде разработки STVD+COSMIC
  2. Добавление ассемблерного файла к проекту
  3. Добавление ассемблерных обработчиков прерываний
  4. Маппинг на физические адреса
    II. Основы работы с EEPROM/FLASH подсистемой в микроконтроллерах STM8
  1. Особенности EEPROM/FLASH подсистемы в микроконтроллерах STM8
  2. Регистры подсистемы EEPROM/FLASH
  3. Запись в EEPROM средствами COSMIC
  4. Безопасное снятие защиты MASS, однобайтный режим записи в EEPROM/FLASH
  5. Четырехбайтный режим записи в EEPROM/FLASH
  6. Блоковый режим записи в EEPROM/FLASH. Копирование кода в ОЗУ и выполнение его оттуда средствами COSMIC

Скачать исходники, workspace с проектами и скомпилированными прошивками к статье можно будет по ссылке в конце статьи.

1 Создание базового проекта в среде разработки STVD+COSMIC

В STVD откроем новый Си проект с использованием тулчейна COSMIC. Выглядеть он будет так:

Если проект скомпилировать и после заглянуть в «Debug» каталог проекта, то можно будет увидеть следующие, сгенерированные при компиляции файлы:

Здесь s19 и elf — бинарные файлы с итоговой прошивкой. Файлы с расширением «o» — это объектные файлы. lkf — это скрипт компоновщика.

Заглянем например в 01_blink.lkf:

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

Технически, мы можем использовать COSMIC без STVD, программировать в командной строке, писать Makefile’ы и пр. Например составим такой main.c

Скомпилируем его в объектный файл командой:

На выходе мы получим объектный файл main.o. Теперь составим скрипт компоновщика main.lkf следующего вида:

В этот раз на выходе получаем main.sm8. Следующим шагом будет получение elf-файла:

Смотрим дизассемблером что у нас получилось:

Таким образом работает компилятор COSMIC в backend режиме STVD. Сегодня я не собираюсь что-то делать в таком стиле, но на мой взгляд иметь представление об этапах компиляции полезно.

Возвращаясь с списку сгенерированых файлов проекта в STVD, хочу обратить ещё внимание на файлы с расширением ls. В них помещается ассемблерный вариант программы:

У вас есть выбор: пользоваться дизассемблером или поглядывать в эти файлы для контроля компиляции.

Ок. Начинаем писать базовый проект на COSMIC. В начале файла main.c добавляем следующие заголовочные файлы:

Первый заголовочный файл добавляет целочисленные типы uint8_t, uint16_t и прочее. Второй заголовочный файл добавляет адреса регистров периферийных устройств. Путь к заголовочным файлам задается в свойствах проекта:

Ниже представлены заголовочные файлы которые идут вместе с компилятором COSMIC:

Теперь добавляем именованные константы для светодиодов, и функцию задержки.

Главная функция main() не будет отличаться оригинальностью:

Компилируем, прошиваем. Если все прошло успешно, то в дизассемблерном виде программа будет выглядеть так:

Можно видеть, что параметр функции передаётся через X регистр, а не через стек как в SDCC.

Я хочу обратить внимание на то, что для вызова функции delay() используется инструкция вызова подпрограммы по относительному адресу — CALLR. Если мы зайдём в настройки проекта и откроем вкладку настройки компилятора, то в выпадающем списке сможем настроить используемую модель памяти.

По умолчанию используется Short Stack (+mods0), когда по мере возможности используются относительные вызовы CALLR, а в стек помещается один байт в качестве адреса возврата. Если поменять модель использования памяти на Long Stack (+modsl0), то по умолчанию, для вызова подпрограмм будет использоваться инструкция CALL, и в стек будет помещаться двухбайтный адрес возврата. Если же переключиться на модель Long Stack (+modsl), то забегая вперёд, метка вызываемой функции должна будет иметь префикс f_ вместо простого символа подчёркивания. Во-вторых, вместо инструкции CALL будет использоваться расширенный вызов CALLF с 3-х байтным адресом.

В модели использования памяти Short Stack (+mods0), внешние функции из других модулей которые мы в дальнейшем будем добавлять, будут вызываться через «обычную» инструкцию вызова CALL.

2 Добавление ассемблерного файла в проект

Теперь заменим функцию задержки на ассемблерный вариант. Для этого добавим в проект файл с именем, допустим: «asm.s», в который будем складывать ассемблерные функции и обработчики прерываний на ассемблере.

Синтаксис ассемблера в COSMIC самый что ни на есть классический, т.е. после метки должно идти двоеточие, шестнадцатеричное число начинается с префикса «0x»(не совсем канон, да), а комментарий должен начинаться с точки с запятой. В макросы я вдаваться не хочу, т.к. предполагаю, что ассемблер будет использоваться только для самого необходимого.

Содержимое asm.s пусть пока будет таким:

Если функция вызывается из Си-программы, то метка должна начинаться с символа подчёркивания. Директива xref делает метку видимой компоновщику, а директива switch указывает рабочий сегмент.

В main.c заменим функцию delay объявлением внешней функции:

В главном цикле, в параметре вызова функции delay(), заменим абстрактное число 0xffff на 1000, т.е. на одну секунду.

Если в функции указать более одного параметра, то первый будет по прежнему помещён в Х регистр, а второй будет передан через стек.

Хочу обратить внимание, что если дизассмблировать прошивку с флагом «S», то мы увидим следующую картинку:

Здесь нам дизассеблировали только тот код который мы сами написали. Таблица векторов и библиотеки COSMIC отображались массивом данных, т.к. там нет отладочной информации. Можно использовать флаг D вместо S для полного дизассемблирования, но тогда на выходе получим «портянку» без указания периферийных регистров и сегментов.

3 Добавление ассемблерных обработчиков прерываний

Теперь займёмся прерываниями. Предполагаем, что обработчики прерываний мы будем писать на ассемблере. Тогда первое за что следует взяться, это файл по умолчанию входящий в шаблон проекта: «stm8_interrupt_vector.c»

Ассемблер COSMIC также как ST-Assembler не знает о существовании инструкции INT, поэтому таблица векторов задана в виде массива констант: опкода инструкции INT, плюс адреса обработчика прерывания. Для удобства, я добавил комментарии к таблице:

В обработчик пустого прерывания NonHandledInterrupt я вставил строку:

Не могу сказать сколько раз меня это выручало. Если во время отладки перестаёт мигать светодиод главного цикла, значит я снова забыл вставить обработчик прерывания в таблицу векторов.

В качестве примера добавим функцию задержки на таймере TIM4. Тема не нова и была не раз уже разобрана: Задержка по таймеру TIM4, с обработчиком прерывания на ассемблере, STM8+SDCC+SPL: функции delay_ms() и delay_us() на таймере TIM4. Так что дело за техникой.

Для наглядности, я добавлю заголовочный файл stm8s_tim4.h с именованными константами из SPL. Там закомментированы функции, и добавлены маски битовых флагов из stm8s.h Скачать файл можно отсюда , а разместить его нужно будет в каталоге проекта.

Также в каталог проекта нужно добавить заголовочный файл tim4.h с объявлением функции delay_ms(uint16_t ms):

После этого, добавим в проект файл: «tim4.c» следующего содержания:

Далее, в ассемблерный файл «asm.s» добавим обработчик прерывания таймера TIM4:

Во-первых, обращаю внимание, что ассемблерные обработчики прерываний объявляются через директиву xdef, тогда как ассемблерные функции через директиву xref.

Во-вторых, здесь используется мнемоника для регистра TIM4_SR, что означает, что в проект нужно добавить ещё один файл, на этот раз ассемблерный, с адресами периферийных регистров. С помощью потокового редактора sed, я конвертировал файл: «STM8S105C_S.h» из комплекта STVD в нужный формат. Скачать его можно здесь. Файл следует добавить в каталог проекта, после чего в начале «asm.s» вставить объявление: #include «stm8s105c_s.inc».

Теперь в начало файла: «stm8_interrupt_vector.c» добавим объявление внешнего обработчика:

После чего, у 23-го прерывания поменяем обработчик «NonHandledInterrupt» на наш «(interrupt_handler_t)tim4_handler».

Осталось в main.c добавить объявление заголовочного файла tim4.h, перед главным циклом разрешить прерывания командой: _asm(«rim»), и после, в главном цикле можно менять вызов функции delay(1000) на delay_ms(1000).

У этого простого примера есть интересный нюанс. Если из функции main() убрать инструкцию разрешения прерывания — «rim», то прерывание все-равно будет работать. Судя по отладчику, разрешение прерываний происходит после выполнения инструкции iret. При этом, каким-то образом этот обработчик вызывается. Могу предположить, что инструкция wfi разрешает прерывания.

4 Маппинг на физические адреса


Fun Art by Derek Ring — «He is Risen»

В COSMIC есть способ привязать переменные к реальным физическим адресам. Делается это так:

Здесь массив foo будет занимать адреса с 0х120 по 0х129 включительно.

Таким образом можно маппить не только адреса в ОЗУ но и адреса периферийных регистров. В документации настаивают, что бы при маппинге на регистры ввода-вывода, переменные объявлялись с модификатором volatile:

Переходя ближе к теме скажу, что с помощью модификатора @eeprom можно назначать переменные в области EEPROM-памяти. Примеры:

Маппить можно не только адреса но и отдельные биты. В COSMIC есть встоенный булевый тип: _Bool. С его помощью можно сделать например так:

Приведу реальный пример. Для этого в каталог проекта нужно будет добавить ещё один заголовочный файл из SPL stm8s_clk.h. Далее в начале функции main() заменим строку:

на код переключения на внешний кварц:

Так же нужно будет добавить обработчик второго(CLK) прерывания:

Я предполагаю что мы используем модель сборки по умолчанию т.е. «Debug» в которой оптимизация отключена. Теперь посмотрим как компилятор раскладывает цикл while (CLK_SWCR & CLK_SWCR_SWBSY):

Вышло на три инструкции. Если бы STM8 не имел битовых инструкций, то на этом бы и закончилось. Тип _Bool предполагает, что для обработки таких переменных используются битовые инструкции. В начале функции main() объявим булеву переменную:

После чего поменяем цикл:

В этом случае компилятор уже использует битовую инструкцию:

Если использовать модель сборки Release, то там оба варианта цикла будут реализованы через одну инструкцию btjt, здесь мне придраться не к чему.

Еще нужно учесть такой момент. Если уж мы используем в проекте заголовочные файлы SPL, то в свойствах проекта нужно будет задать версию своего чипа:

5 Особенности EEPROM/FLASH подсистемы в микроконтроллерах STM8

В STM8, запись в EEPROM практически не отличается от записи во флеш-память, меняются местами некоторые регистры и флаги, но алгоритм остаётся тем же. Поэтому, если подходить к вопросу алгоритмически, то флеш-память можно рассматривать как дополнительную EEPROM, или наоборот — EEPROM-память рассматривать как довесок к флеш-памяти. Но не следует забывать, что ресурс флеш-памяти в 30 раз ниже чем у EEPROM.

Одномоментно записать можно: байт, слово или блок. Давайте разбираться, что есть что.


Карта памяти для Medium-Density чипов STM8S

  1. Блок — количество байтов которые можно стереть или записать одномоментно.
  2. Страница — некоторое количество блоков. В страницах считается размер областей UBC и PCODE(только для STM8L high-density).
  3. Слово — четыре байта.
  4. MASS — система защиты ППЗУ от нежелательного изменения.
  5. ROP — системы защиты памяти от нежелательного чтения. Устанавливается через Option Bytes.
  6. RWW — позволяет записывать в EEPROM не останавливая при этом работу ядра микроконтроллера. Не доступна на low-density чипах. Запись во флеш-память останавливает работу ядра микроконтроллера на все время записи.
  7. UBC — область пользовательского загрузчика. Размер задаётся в страницах через Option Bytes. Всегда начинается с адреса 0x8000. Таблица векторов соответственно переносится на размер области UBC. UBC закрыта для записи через IAP
  8. PCODE — имеется только в STM8L medium и high density- чипах. Защищённая от записи и чтения область для размещения проприетарного кода. Доступ к этому участку памяти можно получить только через программное (TRAP) прерывание.
  9. IAP — программный программатор, пользовательский загрузчик или любое другое изменение ППЗУ программными средствами.
  10. ICP — аппаратный программатор использующий протокол SWIM.

Область EEPROM в STM8S чипах начинается с адреса 0х4000, в STM8L с 0х1000. Ниже приводится карта памяти для STM8S Medium Density чипов:

Карта памяти для STM8L high-density чипов:

Кстати, в STM32 чипах нет EEPROM памяти вообще. Во флеш там можно писать только страницами. Страница там если не ошибаюсь, равна одному килобайту. И если вам нужно будет записать меньшее количество байт, содержимое страницы придётся куда-то сохранить перед стиранием. Так что, когда вам будут говорить, что STM32F0xx тоже самое что STM8 только лучше, не верьте.

Ок. Я пока предлагаю не трогать такие штуки как UBC и PCODE. Если мы в Option Bytes выставим какое-либо значение UBC отличное от нуля, то лишимся возможности отладки. Значение же PCODE байта вообще выставляется только однажды, сбросить его потом не получится.

Во всех чипах с размером флеш-памяти 16 Кбайт и выше имеется встроенный загрузчик. Он начинается с адреса 0x6000. Блок-схема с алгоритмом его работы представлена ниже:

В отличие от 103-го чипа, в начале трассировки прошивки отладчиком мы попадаем не в таблицу векторов, а именно в этот загрузчик. Его можно увидеть в окне дизассемблера:

Здесь в начале проверяется, есть ли по адресу 0х8000 опкод инструкций INT или JRF. Затем идёт проверка, включён ли bootloader в Option Bytes и нет ли установки ROP. После этого идёт или переход или на адрес 0x8000 или на код загрузчика. В этом фрагменте ещё можно увидеть код переключения на внешний кварц и снятие защиты на запись (MASS) с флеш-памяти и EEPROM. Код загрузчика в STM8L151C8 немного отличается, но суть та же:

6 Регистры подсистемы EEPROM/FLASH

Регистров здесь немного, предлагаю их бегло рассмотреть:

Первый управляющий регистр, сегодня нам не придётся им пользоваться, так что можно принять просто к сведению. Флаги HALT и AHALT позволяют отключать питание с флеш-памяти в режимах энергосбережения halt и active-halt. IE — разрешает прерывание, FIX переключает режим стандартного программирования на быстрое при режиме записи блоками. По умолчанию там стоит ноль, т.е. стандартный режим программирования. Им мы и будем пользоваться.

В L-серии на третьем битe висит EEPM, а на втором WAITM. Они отвечают за перевод флеш-памяти в режим пониженного энергопотребления IDDQ. EEPM делает возможным перевод флеш-памяти в режим пониженного энергопотребления при выполнении программы из ОЗУ, а WAITM переводит флеш-память в режим пониженного энергопотребления IDDQ при ждущем и спящем режимах.

Второй управляющий регистр уже поинтереснее, он переключает режимы записи. OPT — защищает область Option Bytes которая расположена в последнем блоке EEPROM (у S-серии). WPRG — включает 4-х байтный режим записи. ERASE — стирает содержимое блока в «быстром» режиме блочной записи. FPRG — включает «быстрый» режим блочной записи, PRG Включает режим стандартной блочной записи.

Регистру FLASH_CR2 соответствует комплементарный регистр FLASH_NCR2. Т.е. изменяя один регистр, следует изменить и другой, до того как что-то писать в ППЗУ.

Ещё одна комплементарная пара регистров, которых мы не будем сегодня касаться: FLASH_FPR и FLASH_NFPR. Определяет размер секции защищённой от записи — UBC.

FLASH_PUKR — регистр снятия защиты MASS с флеш-памяти. Для с снятия защиты потребуется последовательно записать в этот регистр числа 0х56 и 0xAE. При записи неверной комбинации, содержимое флеш-памяти будет блокироваться до следующего Reset.

FLASH_DUKR — регистр снятия защиты MASS с EEPROM. Для с снятия защиты потребуется последовательно записать в этот регистр числа 0хAE и 0x56. При записи неверной комбинации, содержимое EEPROM будет заблокировано до следующего Reset.

Статусный регистр. Флаг HVOFF выставляется и сбрасывается аппаратно во время записи в EEPROM. Служит для определения окончания записи в EEPROM для чипов с RWW. В чипах без RWW следить за этим флагом не надо, там выполнение программы «подвешивается» на время записи. DUL — флаг разблокировки EEPROM. PUL Флаг разблокировки флеш-памяти. EOP — конец операции записи. Флаг устанавливается аппаратно, и сбрасывается программно путём чтения регистра или при новых операциях записи/стирании. Служит для определения момента завершения записи во флеш-память. Флаг WG_PG_DIS устанавливается при попытке писать в защищённую область. Используется для определения ошибок в работе.

7 Запись в EEPROM средствами СOSMIC

COSMIC нам существенно упрощает работу когда мы хотим что-то записать в EEPROM или FLASH память.

Для работы с EEPROM/FLASH нам понадобится ещё один заголовочный файл c константами из SPL: stm8s_flash.h Скачать его можно здесь или сделать самому (требуется небольшая доработка). Файл нужно будет поместить в каталог проекта.

Далее добавим в проект файл с исходным кодом следующего содержания:

Здесь функция write_to_eeprom() записывает некие байты в массив data, определённый в области EEPROM. Используется одно-байтный режим работы.

Остаётся добавить объявление функции void write_to_eeprom( void ); в main.c и ПЕРЕД(!) главным циклом поставить вызов этой функции. Замечу, что в процессе отладки бесполезно отслеживать содержимое eeprom. Когда после прошивки мы откроем ST Visual Programmer и считаем содержимое EEPROM то увидим результат работы:

Программа работает должным образом. Но если мы посмотрим на дизассемблерный листинг:

То можно увидеть, что для операции присваивания используется вызов непонятной функции (выделено красным). Вызываемая функция выглядит так:

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

Теперь попробуем изменить тип данных массива data c uint8_t на uint16_t. Тогда строка кода data[i]=(uint16_t)i; обернётся вызовом такой функции:

Здесь я красным обвёл финальную часть функции, где устанавливается 4-х байтный режим записи в регистре FLASH_CR2, после чего идёт сама запись в EEPROM. Результат можно увидеть опять же в STVP:

Первое, что здесь бросается в глаза, то что двухбайтные числа пишутся в обычном, а не перевёрнутом порядке, когда пишется сначала младший байт, а потом старший. Это нужно учитывать, когда будете читать с ППЗУ.

В целом, нужно отдать должное COSMIC, — это замечательный инструмент для работы с EEPROM. Им удобно пользоваться если вам нужно скинуть туда какие-то небольшой объем данных, например: показания сенсора, станции FM-модуля, но если мы хотим писать во флеш-память и EEPROM блоками, то нам нужно научиться обходиться без его помощи.

8 Безопасное снятие защиты MASS, одно-байтный режим записи в EEPROM/FLASH


Undefined behavior (UB) is the result of executing computer code whose behavior is not prescribed by the language specification to which the code adheres, for the current state of the program. (c) wiki

Но прежде всего мне хотелось бы коснуться, вопроса о защите от дребезга питания или от неопределённого поведения при разблокировке MASS. На мой взгляд, последовательная запись ключей в регистр разблокировки не очень хорошая идея. Будет лучше, если объявить две глобальные переменные которые будут хранить в оперативной памяти значения ключей. Глобальные переменные инициализируются в начале работы программы, и перед их использованием должно пройти какое-то время. Это даёт некоторую защиту от неопределённого поведения. Идею можно развить и сделать как в защите серийных номеров от дизассамблирования, когда номера хранятся не в чистом виде, а являются результатом вычисления какой-нибудь функции. Допустим, можно объявить глобальный массив, со значениями которого будут производиться некие арифметические операции, в результате выполнения которых будут выдаваться числа: 0хАЕ и 0х56. В идеале можно написать генератор псевдослучайного числа, который при инициализации тем или иным значением будет выдавать нужные числа.

Писать генератор псевдослучайного числа мне показалось чересчур и поэтому в онлайн-редакторе я на скорую руку состряпал пару функций:

Здесь первая функция использует 140 итераций, вторая — 90. Ассемблерный листинг функций можно посмотреть под спойлером.

Если весь этот код выполняется успешно, то ППЗУ разблокируется. Если нет, то происходит блокировка до следующего Reset.

Самописная функция write_to_eeprom() у меня получилась такой:

где data — это следующий массив:

В отличии от варианта COSMIC’а, здесь ожидается установка флага HVOFF вместо EOP (так делается в SPL) в качестве признака окончания записи. Кроме того, здесь анализируется флаг FLASH_IAPSR_WR_PG_DIS на случай, если что-то пойдёт не так. В low-density чипах без RWW ждать окончания записи не нужно. Там на время записи приостанавливается работа CPU. Учтите, что прерывания тоже перестают работать, так что там можно писать байты без всяких задержек. Я сам не побывал работать с EEPROM на STM8S103, но здесь: «STM8S EEPROM надо ли ждать EOP флага после записи 1 байта? — Форум разработчиков электроники ELECTRONIX.ru» утверждается, что все именно так и обстоит.

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

Функция записи во флеш-память будет не сильно отличаться:

9 Четырехбайтный режим записи в EEPROM/FLASH

Четырех-байтный режим записи устанавливается флагами WPRG в регистре FLASH_CR2 и NWPRG в FLASH_NCR2. От однобайтного режима отличается увеличением скорости записи в четыре раза, т.к. за одну операцию записи пишется разом четыре байта.

Алгоритм практически аналогичен предыдущим примерам:

где data — это следующий массив:

Обращаю внимание, что здесь запись двухбайтных данных организована в классическом «перевёрнутом» порядке. Т.е. сначала пишется младший байт, затем старший.

10 Блоковый режим записи в EEPROM/FLASH. Копирование кода в ОЗУ и выполнение его оттуда средствами COSMIC


Artwork by Su Jian

Последний самый интересный режим записи — блоковый режим. Позволяет за раз записать в ППЗУ целый блок — 128 байт. Имеется два варианта этого режима: стандартный и быстрый. Я буду рассматривать стандартный режим как самый удобный, требующий меньше телодвижений и следовательно, с ним меньше шансов наделать ошибок. Быстрый режим очень походит на режим записи флеш-памяти в STM32.

Самое сложное в этом режиме то, запись должна выполняться из ОЗУ. Данные должны быть выровнены по границам блока. В STM32 есть регистр в котором можно выбирать записываемую страницу, в STM8 местоположение блока придётся рассчитывать самим.

Я ничего не нашёл в документации относительно особенностей чипов 208/207/007, но анализ кода SPL для этих чипов говорит о том, что там за одну операцию можно записать целую страницу, это четыре блока по 128Б т.е. 512 байт. Сам лично пока не проверял.

Копирование кода в ОЗУ подробно рассмотрено в руководстве: Application note — AN2659, — глава пятая — «Configuring the Cosmic compiler for RAM code execution».

Весь процесс примерно делится на три этапа.

Этап первый. Перед началом функций write_to_eeprom() и write_to_flash() нужно поставить объявление новой секции: #pragma section(FLASH_CODE)

После функций write_to_eeprom() и write_to_flash() нужно будет объявить завершение новой секции: #pragma section()

Этап второй. В настройках проекта перейти на вкладку компоновщика, выбрать там категорию Input, и щёлкнув правой кнопкой мыши по сегменту «RAM» добавить туда новую секцию «.FLASH_CODE» (точку в префиксе не забудьте):

Щёлкнув правой кнопкой по ряду «Option» добавленной секции, измените пустое поле на «-ic». Это даст линковщику команду считать данную секцию перемещаемым кодом. При ее линковке не будет использоваться абсолютная адресация (проверьте!).

Этап третий. Теперь нам понадобится механизм копирования секции в OЗУ. В COSMIC для этого существует готовая функция: int _fctcpy(char name). она поставляется в ассемблерном виде, в файле fctcpy.s. Его нужно будет скопировать в папку проекта и далее добавить к проекту.

Осталось дело за малым. Нужно добавить объявление функции в начало файла main.c:

В качестве параметра функции используется первая буква в названии секции. В начало функции main() вставим вызов этой функции: _fctcpy(‘F’); и дело в шляпе. Это всё.

Пример функций write_to_eeprom() и write_to_flash() реализующих запись двух блоков в EEPROM и флеш-память приведён ниже:

Где массив data в этот раз начинается с начала блока:

В основе лежит алгоритм однобайтной записи, просто добавлен ещё один цикл для записи блока.

Проверяем. Ставим точку останова на вызове функции write_to_eeprom() и смотрим куда ведёт вызов функции:

Вызов идёт в ОЗУ, как и должно быть. Прыгаем, и в окне дизассемлера видим код функций write_to_eeprom() и write_to_flash() перенесённых сюда с помощью _fctcpy().

Результат работы как обычно можно посмотреть в STVP. На этом всё. Остался не рассмотренным быстрый режим блочной записи. Если когда-нибудь соберусь писать статью по написанию своего загрузчика, то начну именно с этого режима.

В заключении могу ещё рекомендовать почитать Executing code from RAM on STM8 | lujji, где автор исследовал возможности перемещения исполняемого кода в ОЗУ для компилятора SDCC. Мне лично система показалась чересчур «костыльной», но возможно вы будете другого мнения.

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

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