Top.Mail.Ru

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

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

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

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

STM32 Flash-память.

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

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

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

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

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

Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также статья на смежную тему из нового курса: Эмуляция EEPROM на базе Flash-памяти микроконтроллеров STM32.

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

FLASH_KEYR = 0x45670123;
FLASH_KEYR = 0xCDEF89AB;

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

FLASH_ErasePage(uint32_t Page_Address)

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

FLASH_ProgramWord(uint32_t Address, uint32_t Data)

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

uint32_t FLASH_Read(uint32_t address)
{
	return (*(__IO uint32_t*)address);
}

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

Подписаться
Уведомить о
guest

65 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
dimas
dimas
9 лет назад

Ждем продолжения с нетерпением !

Паша
Паша
9 лет назад

Спасибо! думаю пригодиться! А еще было бы интересно узнать про LOCK биты для защиты программы от чтения, да и вообще в целом про Option bytes

dooctoor
dooctoor
9 лет назад

Спасибо за статью.
Почему я не могу написать несколько раз в флэш-памяти, только после сброса?

void Write_to_Flash(void)
{
flash_unlock();
flash_erase_page(ADRESS);
flash_write(ADRESS,333);

flash_erase_page(ADRESS);
flash_write(ADRESS,111);
flash_lock();
}

После чтения будет 333 но не 111
Спасибо.

Гость
Гость
Ответ на комментарий  Aveal
8 лет назад

почему нельзя было перезаписать ячейку в которой значение не равно FF?

Maxim
Maxim
Ответ на комментарий  Гость
6 месяцев назад

На самом деле можно но только в последовательности 0<<1; 0<<2; 0<<3 все дело в технологии NAND x & 0хFF. Будь она неладна. Я всю голову сломал как затащить туда данные повторно получается только двигать ноль и постепенно вот так 255>127>63>31>15>7>3>0 можно использовать 1 ячейку 7 раз без стирания. Я на Nand 25 серии наплясался с бубном до сиреневых кругов в глазах.

dooctoor
dooctoor
9 лет назад

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

dooctoor
dooctoor
9 лет назад

по электронной почте могу

Ioann_II
8 лет назад

Здравствуйте.
Пытаюсь писать во FLASH мк stm32f103 с библиотекой HAL.
Пишу двойное слово по адресу - записывается, но по следующему адресу появляются нули.
Даже если пишу что-то по следующему адресу, там всё равно нули. Не пойму в чём дело...подскажите.
Спасибо.
Вот код:
address = PAGE_32 + 0;
if(*(__IO uint32_t*) address == 0xffffffff)
HAL_FLASH_Program(TYPEPROGRAM_DOUBLEWORD, address, 0x77777777);
address = address + 4;
if(*(__IO uint32_t*) address == 0xffffffff) HAL_FLASH_Program(TYPEPROGRAM_DOUBLEWORD, address, 0x88888888);
address = address + 4;
if(*(__IO uint32_t*) address == 0xffffffff) HAL_FLASH_Program(TYPEPROGRAM_DOUBLEWORD, address, 0x99999999);
Результат:
0x08008000: 0x77777777 0x00000000 0x99999999 0x00000000
0x08008010: 0xffffffff 0xffffffff.......

Ioann_II
Ответ на комментарий  Ioann_II
8 лет назад

Кажется разобрался - можно удалять...
Оказывается, WORD это не 2 байта, а 4 и, соответственно,
DOUBLEWORD это не 4 байта а 8.....
А вот константы для байта в библиотеке не нашёл....

Ioann_II
8 лет назад

Вообще, пробовал создать USB Mass Storage, при помощи CubeMX, хотел попробовать при этом использовать внутреннюю память.
Всё равно не выходит - в функции STORAGE_Write_FS параметры blk_addr и blk_len почему-то нулевые....

При попытке отформатировать устройство Windows пишет, что не может завершить форматирование.

Ioann_II
8 лет назад

Пересмотрел, не нашёл ничего...м.б. Вы глянете? Спасибо. Вот код из usbd_storage_if.c:
#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_SIZ 0x400
#define ADDRESS 0x08008000
#define LENGTH 0x08008010
int8_t STORAGE_GetCapacity_FS (uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
*block_size = STORAGE_BLK_SIZ;
*block_num = 32;
return (USBD_OK);
}
int8_t STORAGE_Write_FS (uint8_t lun,
uint8_t *buf,
uint32_t blk_addr,
uint16_t blk_len)
{
HAL_GPIO_WritePin(led_green_GPIO_Port, led_green_Pin, GPIO_PIN_RESET);

if((*(__IO uint32_t*) (ADDRESS)) == 0xFFFFFFFF) HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ADDRESS, blk_addr);

if((*(__IO uint32_t*) (LENGTH)) == 0xFFFFFFFF) HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, LENGTH, blk_len);

HAL_GPIO_WritePin(led_green_GPIO_Port, led_green_Pin, GPIO_PIN_SET);
return (USBD_OK);
}

Ioann_II
8 лет назад

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

Ioann_II
8 лет назад

Здравствуйте.
Наверное, это не совсем интересно для всех - м.б. в личку?
1. Т.к. сразу не получилось реализовать задумку, начал пытаться смотреть значения параметров.
2. Проверяю чистоту ячейки памяти и записываю значение адреса, то же самое с длинной данных.

пс. только начинаю знакомство с stm32? ранее "сидел" на avr, так что прошу сильно не пинать.
ппс. ткните носом... может как чего смотреть типа методики

Спасибо.

Ioann_II
8 лет назад

Вот, кое-что проясняется - параметр blk_len принимает значение 0, если установлен размер блока 1024байт, 1 при 512 байтах и 2 при 256байтах... Надо полагать, это размер сектора в блоках...
Тогда размер блока должен быть не более размера сектора, т.е. 512байт. У меня stm32f103c8, там страницы по 1024байт.
Кроме этой статьи, ориентировался также на статью https://microtechnics.ru/stm32-i-usb-mass-storage-device/
Однако, воспользовался сгенерированным проектом с помощью куба. И все функции оказались не такими как в статье. Поэтому вот, разбираю...
Изначальная идея - попробовать сделать в части памяти маленькую флешку с целью записи конфигурационных данных.

Дмитрий
Дмитрий
Ответ на комментарий  Ioann_II
8 лет назад

loann, а чем дело то кончилось? Удалось реализовать?

Dimaster
Dimaster
8 лет назад

День добрый. Пытаюсь реализовать эмуляцию EEPROM с применением библиотеки от ST. Там, перед тем, как начать производить запись во Flash, используют функцию HAL_FLASH_Unlock(). Пишут, а затем производят запись переменных. Нужно ли после записи вызывать функцию HAL_FLASH_Lock() или можно так оставить и к чему негативному это может привести? Заранее спасибо!

Dimaster
Dimaster
7 лет назад

Тогда еще вопрос в догонку. Функции HAL_FLASH_Program_IT() и HAL_FLASHEx_Erase_IT() как использовать? Как их активизировать, где тот кусок кода, в который мы попадаем, когда вызываем соответствующее прерывание? И есть ли колбеки, так как они нужны, а в библиотеке их не видно

Dimaster
Dimaster
7 лет назад

У меня процессоры F103 и F407, и ни в одном такой строчки во вкладке NVIC нету. Что странно, вот пример:
http://henryomd.blogspot.ru/2016/06/stm32l476-based-usb-audio-device.html
В нем есть включение прерывания для Flash. Нашел данный проц в кубе. Там опять же нет такой настройки. Чего сделать, чтобы она там появилась? В чем подвох? Заранее спасибо!

Dimaster
Dimaster
7 лет назад

Все, извиняюсь за тупость. Галочку надо было убрать)

Dimaster
Dimaster
7 лет назад

Тогда другой вопрос. А где может пригодиться прерывание RCC и зачем оно надо?

Дмитрий
Дмитрий
7 лет назад

Добрый день, подскажите мне пожалуйста как правильно считать записанное ранее значение функцией HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);?
и нужно ли разблокировать память, стирать и после записи блокировать или это все делается автоматом функцией HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);?

Дмитрий
7 лет назад

Добрый день. В статье написано, что необходимо стереть страницу перед записью.
У меня запись во флеш, работает без предварительного стирания страницы. Для записи использую функцию HAL_FLASH_Program(), камень stm32f407.

Dimaster
Dimaster
7 лет назад

День добрый. У меня такой вопрос. Использую функцию HAL_FLASHEx_Erase_IT на процессоре f407. Всё работает, однако когда вызывается данная функция, временно перестают работать все прерывания, даже если их приоритет выше, чем у задачи, из под которой была вызвана функция стирания. В чем тут причина такого странного поведения и как с этим бороться? Мне некоторые процессы нельзя останавливать. Как сделать так, чтобы данные стирались в фоне и этот процесс можно было притормозить другой задачей? Заранее спасибо за ответ

Денис
Денис
7 лет назад

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

Сергей
Сергей
7 лет назад

Подскажите, пожалуйста, почему при получении данных из ячейки (*(__IO uint32_t*)address) они с отрицательным знаком, приходится прибавлять 65536, чтобы получить записанное значение?

Серегй
Серегй
Ответ на комментарий  Aveal
7 лет назад

Делаю так:

uint32_t flash_read(uint32_t address) { return ( *(__IO uint32_t*) address); }

void writeFlash(uint32_t data){
HAL_StatusTypeDef flash_ok = HAL_ERROR;
while(flash_ok != HAL_OK){ flash_ok = HAL_FLASH_Unlock(); }

flash_ok = HAL_ERROR;
while(flash_ok != HAL_OK){ flash_ok = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, 0x08002270, data); }

flash_ok = HAL_ERROR;
while(flash_ok != HAL_OK){ flash_ok = HAL_FLASH_Lock(); }
}

writeFlash(1);
flash_read(0x08002270);

Серегй
Серегй
Ответ на комментарий  Aveal
7 лет назад

Я выводил на LCD, но даже если сделать проверочное условие:
if (flash_read(0x08002270) == 1)
оно не выполняется.

А если сделать так:
uint32_t flash_read(uint32_t address) { return ( 65536 + *(__IO uint32_t*) address); }
то все работает, т.е. условие срабатывает и на дисплей все нормально выводится.

Извиняюсь, что мучаю вопросами, 4-й день пытаюсь познакомиться с микроконтроллерами, не подскажите, как в HAL перед записью стирать данные по определенному адресу? Читал, что необходимо удалять страницу, которой принадлежит адрес в который я записываю, но как сделать и как определить, какой странице принадлежит толком не нашел.

У меня МК STM32F103C8T6 адрес 0x08002270, как я понимаю, надо удалить 8-ю страницу?

Серегй
Серегй
Ответ на комментарий  Aveal
7 лет назад

Данные через ST-Link Utility Flash-память проверял, напротив 0x08002270 в первом столбце (0) FFFF0001, т.е. правильно отображается, а через flash_read при этом условие не выполняется почему-то.

http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf
Читал, но так и не нашел как удалить нужную страницу. Страниа вроде 8-я

Серегй
Серегй
Ответ на комментарий  Aveal
7 лет назад

Благодарю!
Сравнение с 0xFFFF0001 ( if (flash_read(0x08002270) == 0xFFFF0001 ) ) отлично работает. С прибавлением понял.
Кстати, при записи 0x00000001 в памяти получается все также 0xFFFF0001.

Серегй
Серегй
Ответ на комментарий  Aveal
7 лет назад

Точно, получается половина просто FFFF заполнялась. Поменял на целый WORD и стало все заполняться нулями "00000001".

Кстати, когда ранее прибавлял 65536 ( 65536 + *(__IO uint32_t*)address ) условие if (flash_read(0x08002270) == 1) выполнялось, хотя, как я понял, получалось по сути те же самые 00000001. А сейчас, когда в ячейке 00000001, условие выполняется только с "полным" сравнением if (flash_read(0x08002270) == 0x00000001. Если сравнить просто с 1, условие не выполняется.

Сергей
Сергей
Ответ на комментарий  Aveal
7 лет назад

Я под половиной (half) как раз 2 байта имел ввиду :). В общем благодаря Вам потихоньку что-то в голове проясняется.

А не подскажите еще, как можно наиболее "безболезнено" преобразовать число в строку? Например, сохраняю букву "a" в память (точнее числовой номер) HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, 'a' ) , а как потом преобразовать число (97 ASCII) в символ, чтобы можно было на дисплей вывести? Пробовал разные примеры, но либо компилятор ругается, что чего-то не хватает, либо вообще не работает (в частности функция itoa, которую принудительно добавил в файл).

Георгий
Георгий
6 лет назад

Доброго дня!
Подскажите пожалуйста. Не получается считать данные из флеш. Мне кажется что происходит зацикливание while.
flash_unlock();
uint32_t flash_read(uint32_t address);
{
return *((u32 *)address);
}
data=flash_read(*(__IO uint32_t*) address); // Читать данные
flash_lock();

Георгий
Георгий
6 лет назад

т.е. я хотел сказать, что по моему на этом месте виснет

uint32_t flash_read(uint32_t address);
{
return *((u32 *)address);
}

Георгий
Георгий
6 лет назад

Как мне прочитать и присвоить переменной data содержимое
указанной ячейки флеш-памяти? Объясните мне ЧАЙНИКУ.
Желательно на примере.

Георгий
Георгий
6 лет назад

Спасибо, уже разобрался сам. Нет конкретных примеров, а из того что есть много противоречий (CooCox не принимает).
Получилось так:
#define FLASH_KEY1 ((uint32_t)0x45670123)
#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)
void flash_unlock(void) {
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}

void flash_lock() {
FLASH->CR |= FLASH_CR_LOCK;
}

void flash_erase_page(uint32_t address) {
FLASH->CR|= FLASH_CR_PER; //Устанавливаем бит стирания одной страницы
FLASH->AR = address; // Задаем её адрес
FLASH->CR|= FLASH_CR_STRT; // Запускаем стирание
while(!(FLASH->SR & FLASH_SR_EOP)); //Ждем пока страница сотрется.
FLASH->CR&= ~FLASH_CR_PER; //Сбрасываем бит обратно
}

void flash_write(uint32_t address,uint32_t data)
{
FLASH->CR |= FLASH_CR_PG;
while(!(FLASH->SR & FLASH_SR_EOP));
*(__IO uint16_t*)address = (uint16_t)data;
while(!(FLASH->SR & FLASH_SR_EOP));
address+=2;
data>>=16;
*(__IO uint16_t*)address = (uint16_t)data;
while(!(FLASH->SR & FLASH_SR_EOP));
FLASH->CR &= ~(FLASH_CR_PG);
}
.
.
.
int main(void)
{
.
uint32_t dwr=0x12345678; // Переменная для записи (к примеру)
int32_t drd=0x0; // Переменная для чтения
.
.
while()
{
.
.
if(write) {
flash_unlock();
address=0x0800F000;
flash_erase_page(address);
flash_write(address,dwr);
flash_lock();
}
else
if(read) {
address=0x0800F000;
drd=(*(__IO uint32_t*) address); // Читать данные
}
else {
}
}
.... может кому пригодится.

Andriy
Andriy
5 лет назад

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

Andriy
Andriy
5 лет назад

Спасибо за ответ. Я новичек, туго доходит. Пока вижу, что единицы - не критерий. А если еще и пытаться обеспечить циркуляцию записи для продления ресурса - вообще муть. Читал вариант ограничивать размер кода в линкере в файле icf...но я пользуюсь Atollic TC, и что кооректировать там - не нашел, в моих тестовых проектах нет файлов с таким расширением (. Да и идея сама - тупая. Должен быть в конце кода какой-то железный признак конца программы. CRC или еще что-то. Если нет - его нужно придумать )))
Попутно, также, возник вопрос контроля целостности записи (и или факта первой записи).
Буду рад советем. В любом случае, спасибо Вам за статьи. Всегда их читаю с удовольствием

DairKa
DairKa
3 лет назад

Проблема заключалась в том, что не мог записать во FLASH втрой, третий итд раз.

В общем надо в HAL_FLASH_Program(); вставить такой код

CLEAR_BIT(FLASH->CR, FLASH_CR_PER);

Етот бит FLASH_CR_PER уставливается FLASH_PageErase(); и не сбрасывается и не дает записать по новой!!! Истратил полтора дня на поиск етого бага :-/

STM документация ничего не говорит об этом.

олег
олег
2 лет назад

Добрый день. Почему STM32 ST-Link Utilitu считывает прошивку с STM32F030 с начального адреса 0x0800 0000, а когда задаешь начальный адрес Flash memory 0x0804 0000 по даташиту, читать не хочет?

олег
олег
Ответ на комментарий  Aveal
2 лет назад

Сбило с толку, что адресация в даташите идет почему то снизу вверх. Прошивка нормально читалась, а я хотел, чтобы она читалась наоборот. Соответственно ни чего не получилось. На корпусе STM32F030 R8T6.

Image 12.jpg
65
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x