Сегодняшняя статья, как вы уже поняли из названия, будет посвящена микроконтроллерам STM32 и работе со встроенной Flash-памятью. Да-да, именно с той памятью, в которой хранится прошиваемая нами программа. Поскольку в STM32 нет EEPROM (энергонезависимой памяти) для хранения данных можно использовать Flash-память контроллера, и сегодня мы как раз и разберемся, как же это работает.
Сразу же скажу, что согласно документации Flash-память в STM32 позволяет осуществить минимум 10000 циклов перезаписи, что в принципе достаточно для того, чтобы использовать ее и в качестве энергонезависимой памяти для хранения неких данных.
Давайте для начала разберемся со структурой. Возьмем в качестве примера контроллер семейства STM32F10x, относящийся к High-Density устройствам (например, STM32F103VET6). Его память выглядит следующим образом:
Как видите, все жестко структурировано. 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, так что до скорого!
Ждем продолжения с нетерпением !
Спасибо! думаю пригодиться! А еще было бы интересно узнать про LOCK биты для защиты программы от чтения, да и вообще в целом про Option bytes
Спасибо за статью.
Почему я не могу написать несколько раз в флэш-памяти, только после сброса?
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
Спасибо.
Если хочешь, скинь проект, я посмотрю
почему нельзя было перезаписать ячейку в которой значение не равно FF?
На самом деле можно но только в последовательности 0<<1; 0<<2; 0<<3 все дело в технологии NAND x & 0хFF. Будь она неладна. Я всю голову сломал как затащить туда данные повторно получается только двигать ноль и постепенно вот так 255>127>63>31>15>7>3>0 можно использовать 1 ячейку 7 раз без стирания. Я на Nand 25 серии наплясался с бубном до сиреневых кругов в глазах.
в моей программы должно регулярно сохранять данные во Flash - 18 коэффициент. После первого запись в память не могу стереть прошлые значения чтобы записать новые значения - только после сброса
Надо полный код смотреть
по электронной почте могу
Aveal.MicroTechnics@gmail.com
Здравствуйте.
Пытаюсь писать во 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.......
Кажется разобрался - можно удалять...
Оказывается, WORD это не 2 байта, а 4 и, соответственно,
DOUBLEWORD это не 4 байта а 8.....
А вот константы для байта в библиотеке не нашёл....
Не успел ответить )
Вообще, пробовал создать USB Mass Storage, при помощи CubeMX, хотел попробовать при этом использовать внутреннюю память.
Всё равно не выходит - в функции STORAGE_Write_FS параметры blk_addr и blk_len почему-то нулевые....
При попытке отформатировать устройство Windows пишет, что не может завершить форматирование.
Возможно что-то не совсем так с передачей в драйвер USB данных об объеме памяти/количестве блоков итд.
Пересмотрел, не нашёл ничего...м.б. Вы глянете? Спасибо. Вот код из 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);
}
Я честно говоря не понял идею, почему в функцию одну и ту же равнозначными аргументами могут приниматься как адрес, так и длина. При это сами данные, которые нужно записать нигде не фигурируют (buf).
это для отладки - проверки с какими значениями вызвана функция, эти значения записываются в память, если не были записаны ранее, чтобы исключить повторную запись.
Здравствуйте.
Наверное, это не совсем интересно для всех - м.б. в личку?
1. Т.к. сразу не получилось реализовать задумку, начал пытаться смотреть значения параметров.
2. Проверяю чистоту ячейки памяти и записываю значение адреса, то же самое с длинной данных.
пс. только начинаю знакомство с stm32? ранее "сидел" на avr, так что прошу сильно не пинать.
ппс. ткните носом... может как чего смотреть типа методики
Спасибо.
Привет!
Да, можешь писать на почту - Aveal.MicroTechnics@gmail.com. Вообще лучше смотреть значения переменных под отладчиком, flash-память все-таки не очень для этого подходит...
Вот, кое-что проясняется - параметр blk_len принимает значение 0, если установлен размер блока 1024байт, 1 при 512 байтах и 2 при 256байтах... Надо полагать, это размер сектора в блоках...
Тогда размер блока должен быть не более размера сектора, т.е. 512байт. У меня stm32f103c8, там страницы по 1024байт.
Кроме этой статьи, ориентировался также на статью https://microtechnics.ru/stm32-i-usb-mass-storage-device/
Однако, воспользовался сгенерированным проектом с помощью куба. И все функции оказались не такими как в статье. Поэтому вот, разбираю...
Изначальная идея - попробовать сделать в части памяти маленькую флешку с целью записи конфигурационных данных.
blk_len - это сколько блоков данных хочет записать драйвер USB в память.
loann, а чем дело то кончилось? Удалось реализовать?
День добрый. Пытаюсь реализовать эмуляцию EEPROM с применением библиотеки от ST. Там, перед тем, как начать производить запись во Flash, используют функцию HAL_FLASH_Unlock(). Пишут, а затем производят запись переменных. Нужно ли после записи вызывать функцию HAL_FLASH_Lock() или можно так оставить и к чему негативному это может привести? Заранее спасибо!
Я бы все-таки не пренебрегал блокировкой памяти после работы с ней.
Тогда еще вопрос в догонку. Функции HAL_FLASH_Program_IT() и HAL_FLASHEx_Erase_IT() как использовать? Как их активизировать, где тот кусок кода, в который мы попадаем, когда вызываем соответствующее прерывание? И есть ли колбеки, так как они нужны, а в библиотеке их не видно
В Cube во вкладке Configuration можно зайти в NVIC - там включается прерывание Flash - Flash global interrupt. А в файле stm32fxxx_hal_flash.c есть callback - HAL_FLASH_EndOfOperationCallback().
У меня процессоры F103 и F407, и ни в одном такой строчки во вкладке NVIC нету. Что странно, вот пример:
http://henryomd.blogspot.ru/2016/06/stm32l476-based-usb-audio-device.html
В нем есть включение прерывания для Flash. Нашел данный проц в кубе. Там опять же нет такой настройки. Чего сделать, чтобы она там появилась? В чем подвох? Заранее спасибо!
Все, извиняюсь за тупость. Галочку надо было убрать)
Тогда другой вопрос. А где может пригодиться прерывание RCC и зачем оно надо?
Можно отловить события готовности HSI, HSE, LSI, LSE, PLL
Добрый день, подскажите мне пожалуйста как правильно считать записанное ранее значение функцией HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);?
и нужно ли разблокировать память, стирать и после записи блокировать или это все делается автоматом функцией HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);?
По поводу блокировки - надо просто по коду функции посмотреть, есть ли там Lock/Unlock. Насколько я помню - нет.
А считать можно по адресу, также как в примере в статье.
Добрый день. В статье написано, что необходимо стереть страницу перед записью.
У меня запись во флеш, работает без предварительного стирания страницы. Для записи использую функцию HAL_FLASH_Program(), камень stm32f407.
Может в HAL добавили внутрь функции стирание, а может память изначально была чистая.
День добрый. У меня такой вопрос. Использую функцию HAL_FLASHEx_Erase_IT на процессоре f407. Всё работает, однако когда вызывается данная функция, временно перестают работать все прерывания, даже если их приоритет выше, чем у задачи, из под которой была вызвана функция стирания. В чем тут причина такого странного поведения и как с этим бороться? Мне некоторые процессы нельзя останавливать. Как сделать так, чтобы данные стирались в фоне и этот процесс можно было притормозить другой задачей? Заранее спасибо за ответ
Подскажите, пожалуйста, а какая функция в HAL отвечает за чтение из памяти? Т.е. как из самой программы прочитать память по определенному адресу?
Добрый день.
Там вроде нет функции для чтения - читаем просто по адресу, как в статье.
Подскажите, пожалуйста, почему при получении данных из ячейки (*(__IO uint32_t*)address) они с отрицательным знаком, приходится прибавлять 65536, чтобы получить записанное значение?
Тип данных наверно неверный, напишите полный код, как получаете значения.
Делаю так:
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);
А результат flash_read() куда сохраняется?
Я выводил на LCD, но даже если сделать проверочное условие:
if (flash_read(0x08002270) == 1)
оно не выполняется.
А если сделать так:
uint32_t flash_read(uint32_t address) { return ( 65536 + *(__IO uint32_t*) address); }
то все работает, т.е. условие срабатывает и на дисплей все нормально выводится.
Извиняюсь, что мучаю вопросами, 4-й день пытаюсь познакомиться с микроконтроллерами, не подскажите, как в HAL перед записью стирать данные по определенному адресу? Читал, что необходимо удалять страницу, которой принадлежит адрес в который я записываю, но как сделать и как определить, какой странице принадлежит толком не нашел.
У меня МК STM32F103C8T6 адрес 0x08002270, как я понимаю, надо удалить 8-ю страницу?
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
Тут вот с 53 страницы есть все адреса, страницы итд.
А чтение нужно так проверить:
uint32_t readData = flash_read(0x08002270).
И потом с этой переменной дальше работать. Но вообще я думаю, что и Вашем варианте компилятор должен правильно все обработать и дать верный результат. Поэтому, возможно, проблема с записью. Нужно после записи считать через ST-Link Utility Flash-память и там этот адрес проверить.
Данные через 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-я
В файле stm32f1xx_hal_flash_ex.c есть функция HAL_FLASHEx_Erase(). А if не работает потому что функция чтения возращает 0xFFFF0001 (не 1), тип данных то uint32_t. А при прибавлении 65536 получаем 0xFFFF0001 + 0x10000 = 0x100000001. Старшая единица пропадает (потому что uint32_t - 4 байта) и условие "случайно" срабатывает. Нужно сравнивать с 0xFFFF0001 или записывать не "1", а 0x00000001.
Благодарю!
Сравнение с 0xFFFF0001 ( if (flash_read(0x08002270) == 0xFFFF0001 ) ) отлично работает. С прибавлением понял.
Кстати, при записи 0x00000001 в памяти получается все также 0xFFFF0001.
Скорее всего при записи надо FLASH_TYPEPROGRAM_HALFWORD поменять на FLASH_TYPEPROGRAM_WORD - так получается он 2 байта пишет всего.
Точно, получается половина просто FFFF заполнялась. Поменял на целый WORD и стало все заполняться нулями "00000001".
Кстати, когда ранее прибавлял 65536 ( 65536 + *(__IO uint32_t*)address ) условие if (flash_read(0x08002270) == 1) выполнялось, хотя, как я понял, получалось по сути те же самые 00000001. А сейчас, когда в ячейке 00000001, условие выполняется только с "полным" сравнением if (flash_read(0x08002270) == 0x00000001. Если сравнить просто с 1, условие не выполняется.
Не совсем так по поводу половины ) Просто после очистки в ячейке записано 0xFFFFFFFF и мы потом переписывали 2 байта и получалось, 0xFFFF0001 (2 других байта не менялись).
Наверно компилятор воспринимает единицу не как uint32_t. Можно попробовать так:
if (flash_read(0x08002270) == (uint32_t)1)
Должно работать.
Я под половиной (half) как раз 2 байта имел ввиду :). В общем благодаря Вам потихоньку что-то в голове проясняется.
А не подскажите еще, как можно наиболее "безболезнено" преобразовать число в строку? Например, сохраняю букву "a" в память (точнее числовой номер) HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, 'a' ) , а как потом преобразовать число (97 ASCII) в символ, чтобы можно было на дисплей вывести? Пробовал разные примеры, но либо компилятор ругается, что чего-то не хватает, либо вообще не работает (в частности функция itoa, которую принудительно добавил в файл).
А функция вывода на дисплей какие параметры принимает на входе?
Доброго дня!
Подскажите пожалуйста. Не получается считать данные из флеш. Мне кажется что происходит зацикливание while.
flash_unlock();
uint32_t flash_read(uint32_t address);
{
return *((u32 *)address);
}
data=flash_read(*(__IO uint32_t*) address); // Читать данные
flash_lock();
т.е. я хотел сказать, что по моему на этом месте виснет
uint32_t flash_read(uint32_t address);
{
return *((u32 *)address);
}
Как мне прочитать и присвоить переменной data содержимое
указанной ячейки флеш-памяти? Объясните мне ЧАЙНИКУ.
Желательно на примере.
Спасибо, уже разобрался сам. Нет конкретных примеров, а из того что есть много противоречий (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 {
}
}
.... может кому пригодится.
Добрый день!
Прошу прощения, что вовремя не ответил, отсутствовал в праздничные дни... Рад, что получилось запустить!
Подскажите, пожалуйста, как из программы определить, где заканчивается запись код, т.е. найти свободное место для записи? Возможно, есть пример кода?
Добрый день!
Можно начиная с конца flash-памяти читать данные - там, где информации нет, там будут 0xFFFFFFFF.
Спасибо за ответ. Я новичек, туго доходит. Пока вижу, что единицы - не критерий. А если еще и пытаться обеспечить циркуляцию записи для продления ресурса - вообще муть. Читал вариант ограничивать размер кода в линкере в файле icf...но я пользуюсь Atollic TC, и что кооректировать там - не нашел, в моих тестовых проектах нет файлов с таким расширением (. Да и идея сама - тупая. Должен быть в конце кода какой-то железный признак конца программы. CRC или еще что-то. Если нет - его нужно придумать )))
Попутно, также, возник вопрос контроля целостности записи (и или факта первой записи).
Буду рад советем. В любом случае, спасибо Вам за статьи. Всегда их читаю с удовольствием
Проблема заключалась в том, что не мог записать во FLASH втрой, третий итд раз.
В общем надо в HAL_FLASH_Program(); вставить такой код
CLEAR_BIT(FLASH->CR, FLASH_CR_PER);
Етот бит FLASH_CR_PER уставливается FLASH_PageErase(); и не сбрасывается и не дает записать по новой!!! Истратил полтора дня на поиск етого бага :-/
STM документация ничего не говорит об этом.
Добрый день. Почему STM32 ST-Link Utilitu считывает прошивку с STM32F030 с начального адреса 0x0800 0000, а когда задаешь начальный адрес Flash memory 0x0804 0000 по даташиту, читать не хочет?
Добрый день, а какой именно контроллер? STM32F030RC?
Сбило с толку, что адресация в даташите идет почему то снизу вверх. Прошивка нормально читалась, а я хотел, чтобы она читалась наоборот. Соответственно ни чего не получилось. На корпусе STM32F030 R8T6.
Тут еще такой нюанс, что на схеме 256 КБ памяти, а это только для RC. У STM32F030R8 - 64 КБ.