Главная

Главная

Сообщество

Сообщество

Форум

Форум

STM32

STM32

Электроника

Электроника

Raspberry Pi

Raspberry Pi

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
11 лет назад

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

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

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

dooctoor
dooctoor
11 лет назад

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

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 MT
10 лет назад

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

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

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

dooctoor
dooctoor
11 лет назад

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

dooctoor
dooctoor
11 лет назад

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

Ioann_II
10 лет назад

Здравствуйте.
Пытаюсь писать во 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
10 лет назад

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

Ioann_II
10 лет назад

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

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

Ioann_II
10 лет назад

Пересмотрел, не нашёл ничего...м.б. Вы глянете? Спасибо. Вот код из 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
10 лет назад

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

Ioann_II
10 лет назад

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

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

Спасибо.

Ioann_II
10 лет назад

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

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

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

Dimaster
Dimaster
9 лет назад

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

Dimaster
Dimaster
9 лет назад

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

Dimaster
Dimaster
9 лет назад

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

Dimaster
Dimaster
9 лет назад

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

Dimaster
Dimaster
9 лет назад

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

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

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

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

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

Dimaster
Dimaster
9 лет назад

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

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

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

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

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

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

Делаю так:

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 MT
8 лет назад

Я выводил на 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 MT
8 лет назад

Данные через 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 MT
8 лет назад

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Andriy
Andriy
7 лет назад

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

DairKa
DairKa
4 лет назад

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

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

CLEAR_BIT(FLASH->CR, FLASH_CR_PER);

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

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

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

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

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

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

Image 12.jpg
Обзор конфиденциальности

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