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

от admin

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

Как разместить переменную по определенному адресу в Keil

Изредка возникает задача сохранить во flash памяти контрольную сумму, картинку, строчку текста, настройку. Иногда возникает задача сохранить не просто в ОЗУ, а в определенной области, чтобы для этой области например включить/выключить DCACHE. Или например иметь функцию, исполняемую из ОЗУ чтобы можно было присылать по UART и сразу исполнять новый код функции.

Рассмотрим задачу на примерах. В качестве испытуемого будет народный stm32f401ret6 со следующей адресацией flash памяти (страница 51 даташита):

Первый путь — ручной:
uint32_t keyFlash __attribute__((at(0x08004000))) = 0xAABBCCDD;
Здесь мы записали 32-битное число 0xAABBCCDD по адресу 0x08004000. Этот путь имеет следующий недостаток. Пусть мы точно разместили число в flash по адресу 0x08004000, рядом могут располагаться код программы, значения для констант. Если мы захотим перезаписать число по адресу, придётся стирать весь сектор, потому что во flash писать можно только посекторно. Я даже не знаю что будет, если выполняемые в данном секторе flash памяти инструкции попытаться стереть, но это очевидно плохая идея. Так что если предполагается возможность изменения данных во flash в процессе работы, под эти данные следует выделить отдельный сектор (сектора). И это нас приводит к второму пути.

Второй путь заключается в использовании скеттер (scatter) файла.Теорию можно прочесть здесь. Также желательно понимать что такое объектный файл. Совсем в двух словах, после работы препроцессор->компилятор получается множество
файлов *.o
где звездочка значит любое имя. Например, из main.c получается файл main.o

Рассмотрим пример scatter файла.

В дефолтный scatter файла, который генерирует сам Keil.

Были добавлены две правки. Первое, исходный файл выделял под программу всю флеш:

Но я для примера выделю только 0 сектор (он начинается с 0x08000000 ) размером 16 кБ (0x00004000 — это 16*1024 байт в шестнадцатеричной системе).

ER_IROM1 0x08000000 0x00004000

RW_IRAM1 0x20000000 0x00000900

MYREGION 0x0800C000 FIXED
>

А теперь собственно в файле main.c заведем нашу синтаксическую единицу для хранения в регионе:
const uint16_t ADC_Buf[7] __attribute__((section(«mysection»))) = ;

И тут нас ждёт ловушка. Язык высокого уровня мы любим за оптимизацию. Включаем -O3 и наша неиспользуемая в программе константа исчезает. Тут случай, когда про toolchain можно сказать «слишком умный». Чтобы объяснить, что в секции mysection у нас всё только нужное, зайдём на вкладку Linker в Misc controls и пропишем
—keep=»main.o(mysection)»

Свойства проекта Keil

Изредка бывает ситуация, когда мы хотим ограничить распространения региона только на синтаксические единицы из определенного файла, пусть main.c тогда вместо

MYREGION 0x0800C000 FIXED
пишите
MYREGION 0x0800C000 FIXED

А ещё можно например все переменные из файла пусть main.c заставить жить в вашей секции вот так

MYREGION 0x0800C000 FIXED

При тестировании не забывайте делать Full chip erase, чтобы точно очищать всю flash. А то окажется, что ваш текущий код работает неверно, а смотрите и радуетесь вы результату работы предыдущего кода.

Кому оказалась интересна тема scatter файлов, предлагаю упражнение. Наверняка вы писали программу моргания светодиодом. Я перелагаю вам написать одну программу, которая работает верно: зажигает и тушит светодиод, А вторая программа будет зажигать и зажигать светодиод:

После этого я предлагаю вам посмотреть содержимое памяти и глазами найти отличие. Далее blink() с помощью scatter файла поместите в ОЗУ, пусть оттуда выполняется. Затем перед вызовом функции blink() в main() попробуйте написать код, который починит blink(), чтобы она и зажигала и тушила. О своих (не) успехах пишите в лс или комментариях, может быть получится ещё одна заметка.

Дополнительные материалы:
Как не инициализировать переменные в кейл?
Про слово FIXED

AVR — Запись данных во флеш память

Встроенная EEPROM стремительно заканчивается, а данные куда-то записывать надо. Знакомая ситуация, не правда-ли?

Что мы обычно делаем в таких случаях? Ставим внешнюю EEPROM, флеш или SD карточку на 32 гига. Это оправдано, если устройство достаточно сложное. А если оно состоит из одной тиньки и двух с половиной светодиодов? Тогда подключение внешней памяти грозит кардинальными изменениями в алгоритме, а может и пинов банально не хватит.

Но ведь у нас есть своя флеш память, которая в подавляющем большинстве случаев заполнена чуть менее, чем на половину. Отлично! Её и используем для записи данных.

Механизм записи во флеш память на первый взгляд немного запутанный. Но если присмотреться — все просто. Он состоит из трех основных частей:
— Очистка страницы памяти.
— Подготовка (закидываем данные во временный буфер)
— Запись.
Причем первые два пункта можно невозбранно менять местами.

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

Размер страницы 32 байта (16 слов), а всего таких страниц 32 штуки.
Так-же там написано, что в регистре адреса первые 4 бита (PC[3:0]) занимает адрес слова, а адрес страницы начинается с 5го бита. Значит, если нам надо записать адрес 3й страницы, то в регистр уйдет 3<<4.

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

При прошивке МК надо включить фьюз SELFPRGEN. Без этого ничего работать не будет.

1) Расчистка места.

Для очистки страницы памяти надо выполнить такую последовательность действий:
— Пихаем в регистр Z адрес. Из него МК выделит адрес страницы, а на первые 4 бита забьет.
— Поднимаем в регистре SPMCSR биты PGERS (Page Erase) и SELFPRGEN (Self Programming Enable).
— Быстро (в течении 4х тактов после записи в SPMCSR) выполняем команду SPM.

На время очистки страницы (а это около 4 мс) процессор подвисает.

2) Готовим данные к записи.

— Записываем в регистр Z адрес слова. Теперь все наоборот. МК будет ориентироваться по первым 4 битам адреса.
— В пару регистров R1:R0 пишем данные, которые хотим запихать в буфер.
— Поднимаем бит SELFPRGEN в регистре SPMCSR.
— Выполняем команду SPM.
… повторить, пока не будет заполнен весь буфер.

У этого буфера есть одна нехорошая особенность:
Записать два раза в одну ячейку нельзя. Т.е. если мы хотим переписать уже записанные в буфер данные, то его придется сначала очистить. Для очистки надо просто поднять бит CTPB в SPMCSR.
Алсо, буфер очищается сам после записи во флеш или перезагрузки.

Забавно так-же то, что данные в буфере будут потеряны, если записать что-то в EEPROM. Как-то хитро память устроена, не находите? ��
Последнее особенно актуально, если мы собираемся наполнять буфер постепенно, прерываясь на другие задачи (типа записи в EEPROM).

3) Пишем!

— Запихиваем адрес страницы в Z
— Устанавливаем биты SELFPRGEN и PGWRT.
— Выполняем SPM.

При записи страницы МК зависает на те-же 4 мс.

Пример устройства.

Для примера я хотел замутить термологгер на базе тини25. С записью температуры в флеш, работой со встроенным термометром и выдачей лога в UART. Но выяснилось, что UART у тини25 нету, а «родной» термометр уж больно кривой. Поэтому будем делать что по-проще. Например, записывать во флеш память напряжение, измеренное АЦП.

Алгоритм дешевого и сердитого логгера такой:
0) Инициализация периферии. Устанавливаем адрес записи на первую пустую страницу.
1) Ждем 1 сек — простым циклом, безо всяких таймеров и прерываний. Дешево и сердито.
2) Запускаем АЦП и ждем, пока он закончит преобразование.
3) Если мы начали писать новую страницу, то
3.1) Очишаем её.
4) Записываем во временнный буфер (по текущему адресу) значение АЦП.
5) Если мы уже заполнили весь буфер, то
5.1) Записываем его во флеш память.
6) Инкрементируем адрес записи.
7) Если дошли до конца памяти — затупляем в вечном цикле. Иначе идем на шаг 1.

Код на ассемблере:

Проверять код в протеусе бесполезно — не работает ��
Но в железе все прекрасно крутится.

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

В МК с поддержкой бутлоадера на команду SPM накладываются ограничения. Её можно выполнить только из BLS (Bootloader section) — области памяти в самом конце флеша, где живут бутлоадеры. Размер этой области устанавливается фьюзами BOOTSZ.

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

Разница между RWW и NRWW заключается вот в чем: Если бутлоадер (который находится в NRWW) пишет или стирает страницу в RWW области, то МК не останавливается на время выполения этой операции. Бутлоадер будет продолжать работать, пока страница памяти в RWW записывается или стирается. По этому поводу даже придумали прерывание SPM_RDY, которое возникает по завершении операции.

А если бутлоадер попытается записать данные в секцию NRWW (это не обязательно должна быть секция самого бутлоадера), то МК замрет на 4мс, пока производится запись.

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

Компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе. Поэтому я создал новую рубрику, посвященную работе уже с новыми инструментами, так что буду рад видеть вас там — STM32CubeMx. Также вот глобальная рубрика по STM32 — ссылка.

Сегодняшняя статья, как вы уже поняли из названия, будет посвящена микроконтроллерам 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, так что до скорого!

Работа с 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 и дальше использовать его:

    Как записать переменную во флеш память 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 и дальше использовать его:

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

      Встроенная EEPROM стремительно заканчивается, а данные куда-то записывать надо. Знакомая ситуация, не правда-ли?

      Что мы обычно делаем в таких случаях? Ставим внешнюю EEPROM, флеш или SD карточку на 32 гига. Это оправдано, если устройство достаточно сложное. А если оно состоит из одной тиньки и двух с половиной светодиодов? Тогда подключение внешней памяти грозит кардинальными изменениями в алгоритме, а может и пинов банально не хватит.

      Но ведь у нас есть своя флеш память, которая в подавляющем большинстве случаев заполнена чуть менее, чем на половину. Отлично! Её и используем для записи данных.

      Механизм записи во флеш память на первый взгляд немного запутанный. Но если присмотреться — все просто. Он состоит из трех основных частей:
      — Очистка страницы памяти.
      — Подготовка (закидываем данные во временный буфер)
      — Запись.
      Причем первые два пункта можно невозбранно менять местами.

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

      Размер страницы 32 байта (16 слов), а всего таких страниц 32 штуки.
      Так-же там написано, что в регистре адреса первые 4 бита (PC[3:0]) занимает адрес слова, а адрес страницы начинается с 5го бита. Значит, если нам надо записать адрес 3й страницы, то в регистр уйдет 3<<4.

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

      При прошивке МК надо включить фьюз SELFPRGEN. Без этого ничего работать не будет.

      1) Расчистка места.

      Для очистки страницы памяти надо выполнить такую последовательность действий:
      — Пихаем в регистр Z адрес. Из него МК выделит адрес страницы, а на первые 4 бита забьет.
      — Поднимаем в регистре SPMCSR биты PGERS (Page Erase) и SELFPRGEN (Self Programming Enable).
      — Быстро (в течении 4х тактов после записи в SPMCSR) выполняем команду SPM.

      На время очистки страницы (а это около 4 мс) процессор подвисает.

      2) Готовим данные к записи.

      — Записываем в регистр Z адрес слова. Теперь все наоборот. МК будет ориентироваться по первым 4 битам адреса.
      — В пару регистров R1:R0 пишем данные, которые хотим запихать в буфер.
      — Поднимаем бит SELFPRGEN в регистре SPMCSR.
      — Выполняем команду SPM.
      … повторить, пока не будет заполнен весь буфер.

      У этого буфера есть одна нехорошая особенность:
      Записать два раза в одну ячейку нельзя. Т.е. если мы хотим переписать уже записанные в буфер данные, то его придется сначала очистить. Для очистки надо просто поднять бит CTPB в SPMCSR.
      Алсо, буфер очищается сам после записи во флеш или перезагрузки.

      Забавно так-же то, что данные в буфере будут потеряны, если записать что-то в EEPROM. Как-то хитро память устроена, не находите? ��
      Последнее особенно актуально, если мы собираемся наполнять буфер постепенно, прерываясь на другие задачи (типа записи в EEPROM).

      3) Пишем!

      — Запихиваем адрес страницы в Z
      — Устанавливаем биты SELFPRGEN и PGWRT.
      — Выполняем SPM.

      При записи страницы МК зависает на те-же 4 мс.

      Пример устройства.

      Для примера я хотел замутить термологгер на базе тини25. С записью температуры в флеш, работой со встроенным термометром и выдачей лога в UART. Но выяснилось, что UART у тини25 нету, а «родной» термометр уж больно кривой. Поэтому будем делать что по-проще. Например, записывать во флеш память напряжение, измеренное АЦП.

      Алгоритм дешевого и сердитого логгера такой:
      0) Инициализация периферии. Устанавливаем адрес записи на первую пустую страницу.
      1) Ждем 1 сек — простым циклом, безо всяких таймеров и прерываний. Дешево и сердито.
      2) Запускаем АЦП и ждем, пока он закончит преобразование.
      3) Если мы начали писать новую страницу, то
      3.1) Очишаем её.
      4) Записываем во временнный буфер (по текущему адресу) значение АЦП.
      5) Если мы уже заполнили весь буфер, то
      5.1) Записываем его во флеш память.
      6) Инкрементируем адрес записи.
      7) Если дошли до конца памяти — затупляем в вечном цикле. Иначе идем на шаг 1.

      Код на ассемблере:

      Проверять код в протеусе бесполезно — не работает ��
      Но в железе все прекрасно крутится.

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

      В МК с поддержкой бутлоадера на команду SPM накладываются ограничения. Её можно выполнить только из BLS (Bootloader section) — области памяти в самом конце флеша, где живут бутлоадеры. Размер этой области устанавливается фьюзами BOOTSZ.

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

      Разница между RWW и NRWW заключается вот в чем: Если бутлоадер (который находится в NRWW) пишет или стирает страницу в RWW области, то МК не останавливается на время выполения этой операции. Бутлоадер будет продолжать работать, пока страница памяти в RWW записывается или стирается. По этому поводу даже придумали прерывание SPM_RDY, которое возникает по завершении операции.

      А если бутлоадер попытается записать данные в секцию NRWW (это не обязательно должна быть секция самого бутлоадера), то МК замрет на 4мс, пока производится запись.

      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

      Как записать переменную во флеш память 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

      Как записать переменную во флеш память 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. Мне лично система показалась чересчур «костыльной», но возможно вы будете другого мнения.

      Читать:
      Sla аккумулятор что это

Похожие публикации