Россия, Московская область, Сергиев Посад, Пограничная улица
Телефон:
+7 (915) 422-22- Показать номер
Пн-вс: 09:00—21:00
whatsapp telegram vk email

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

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

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

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

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

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

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

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

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

ER_IROM1 0x08000000 0x00004000

RW_IRAM1 0x20000000 0x00000900

image

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

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

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

MYREGION 0x0800C000 FIXED

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

image

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

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

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

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

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

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

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

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

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

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

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

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

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

3) Пишем!

При записи страницы МК зависает на те-же 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-памятью микроконтроллера.

image

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

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

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

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

image

Как видите, все жестко структурировано. 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-памятью микроконтроллера.

    image

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

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

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

    image

    Как видите, все жестко структурировано. 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.

    image

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

    image

    Биты Название Описание
    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
    Чтение данных из flash-памяти.
    Запись данных во flash-память.
    Использование структур данных.

    image

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

    image

    Флеш память разделена на страницы. Размер страницы зависит от общего объема флеш памяти — смотри в даташите. Например, для тини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 есть такая табличка:

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

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

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

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

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

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

      3) Пишем!

      При записи страницы МК зависает на те-же 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) укажет на следующее:

      Когда адресуется страница 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.

    • Cosmic CXST7 — User Manual — документация по компилятору COSMIC
    • Reference Manual STM8S — RM0016, глава 4 «Flash program memory and data EEPROM»
    • Programming Manual PM0051 — руководство по записи в EEPROM и FLASH память для микроконтроллеров STM8S/STM8A
    • Application note — AN2659, — глава пятая — «Configuring the Cosmic compiler for RAM code execution».
    • Работа с EEPROM и Flash / STM8 / Сообщество EasyElectronics.ru хорошая вводная статья по теме на русском языке. Написано по существу, без лишней воды.
  • Создание базового проекта в среде разработки STVD+COSMIC
  • Добавление ассемблерного файла к проекту
  • Добавление ассемблерных обработчиков прерываний
  • Маппинг на физические адреса
  • Особенности EEPROM/FLASH подсистемы в микроконтроллерах STM8
  • Регистры подсистемы EEPROM/FLASH
  • Запись в EEPROM средствами COSMIC
  • Безопасное снятие защиты MASS, однобайтный режим записи в EEPROM/FLASH
  • Четырехбайтный режим записи в EEPROM/FLASH
  • Блоковый режим записи в EEPROM/FLASH. Копирование кода в ОЗУ и выполнение его оттуда средствами COSMIC
  • Блок — количество байтов которые можно стереть или записать одномоментно.
  • Страница — некоторое количество блоков. В страницах считается размер областей UBC и PCODE(только для STM8L high-density).
  • Слово — четыре байта.
  • MASS — система защиты ППЗУ от нежелательного изменения.
  • ROP — системы защиты памяти от нежелательного чтения. Устанавливается через Option Bytes.
  • RWW — позволяет записывать в EEPROM не останавливая при этом работу ядра микроконтроллера. Не доступна на low-density чипах. Запись во флеш-память останавливает работу ядра микроконтроллера на все время записи.
  • UBC — область пользовательского загрузчика. Размер задаётся в страницах через Option Bytes. Всегда начинается с адреса 0x8000. Таблица векторов соответственно переносится на размер области UBC. UBC закрыта для записи через IAP
  • PCODE — имеется только в STM8L medium и high density- чипах. Защищённая от записи и чтения область для размещения проприетарного кода. Доступ к этому участку памяти можно получить только через программное (TRAP) прерывание.
  • IAP — программный программатор, пользовательский загрузчик или любое другое изменение ППЗУ программными средствами.
  • ICP — аппаратный программатор использующий протокол SWIM.

Ссылка на основную публикацию
Похожее