STM32 Flash как EEPROM





Библиотека FlashPROM для использования Flash памяти как EEPROM.

Перед тем как приступать к изучению, настоятельно рекомендую прочесть эту статью.


Большинство микроконтроллеров stm32 не имеют EEPROM-памяти что конечно же вызывает трудности с долговременным хранением каких-либо данных. Для этих целей можно использовать флеш, благо её в избытке, однако проблема в том, что что у флеша довольно таки ограниченное количество циклов перезаписи, производитель гарантирует 10000. В связи с этим, нужно записывать сохраняемые данные каждый раз в новое место. Производитель предлагает свой механизм реализации этой задачи в документе EEPROM emulation AN2594, однако всегда хочется придумать что-то своё.

Представленная ниже библиотека не моя идея, в сети много подобного. Суть заключается в следующем: выбирается участок флеша не используемый программой, то есть несколько страниц в самом конце, и очищается — заполняется единицами (0хFF). Далее по ходу программы производится запись сохраняемых данных в самое начало этого участка, а перед каждой последующей записью производится поиск места где закончилась запись предыдущих данных, и новые данные записываются с этого места. Так продолжается пока не закончится вся память. Когда память заканчивается, весь участок очищается и всё повторяется заново. Таким образом количество циклов перезаписи увеличивается в десятки тысяч раз.

Далее пояснения делать лучше наглядно, поэтому скачайте пример. Если у вас BluePill, тогда ничего делать не нужно, если же плата другая, тогда надо создать проект с включённым USART'ом и активированным блоком CRC…



Подключить в проект файлы FlashPROM.c и FlashPROM.h. В main.c добавить инклюд…

/* USER CODE BEGIN Includes */
#include "FlashPROM.h"
/* USER CODE END Includes */


И объявить глобальную переменную…

/* USER CODE BEGIN PV */
uint32_t res_addr = 0;
/* USER CODE END PV */



Теперь идём в файл FlashPROM.h, там лежат все настройки…



#define STARTADDR ((uint32_t)0x0801F800) — тут надо указать адрес начала страницы, с которой будет начинаться область памяти используемая как EEPROM. В примере указана страница №126, то есть будут использоваться 126 и 127 страница. Для примера я использую всего две страницы, но в реальном проекте можно использовать все свободные странички. Адрес смотрите в мануале на свой камень.

#define ENDMEMORY ((uint32_t)0x0801FC00 + 1024) — адрес последней страницы плюс её размер, то есть это адрес последней ячейки флеша. Когда поиск свободного пространства доберётся до этого адреса, вся память будет очищена и запись пойдёт по новому кругу. Размер страницы см. в мануале.

#define PAGES 2 — количество страниц для очистки. Этот параметр нужен в функции erase_flash(). В примере используются страницы 126 и 127, поэтому 2. Если не понятно, то смотрите здесь.

#define BUFFSIZE 5 — сохраняемые данные складываются в массив, и он записывается во флеш. В данном случае размер массива состоит из пяти элементов, четыре элемента используются для полезных данных, а в последний нужно записать ноль. То есть размер массива нужно указывать исходя из количества полезных данных, плюс ячейка для нуля (см. ниже).

Буфер может быть либо из 16-ти битных, либо из 32-ух битных элементов. В принципе можно сделать массив из 8-ми битных элементов, но не все микроконтроллеры умеют записывать байт во флеш. Для записи 64-ёх битных значений эта библиотека не подходит. Точнее сделать то конечно можно, но придётся мудрить с CRC-суммой. Там 32-ух битный регистр.

#define DATAWIDTH 2 — размер элемента буфера. Если 16 бит, то пишем 2, если 32 бита, то пишем 4.

#define WIDTHWRITE FLASH_TYPEPROGRAM_HALFWORD — это аргумент прописываемый в функции HAL_FLASH_Program(...). Если элемент буфера 16-ти битный, то оставляем как есть, если 32-ух битный, то дефайним FLASH_TYPEPROGRAM_WORD.

#define DEBUG 1 — 1 -включает вывод инфы в уарт, 0 — отключает.

typedef uint16_t myBuf_t; — если элемент буфера 16-ти битный, тогда оставляем как есть, если 32-ух битный, то меняем uint16_t на uint32_t.


Теперь возвращаемся в main.c

Очищаем память один раз и комментируем эту функцию…

//erase_flash();


Далее запускаем функцию поиска места с которого будет происходить запись новых данных…

res_addr = flash_search_adress(STARTADDR, BUFFSIZE * DATAWIDTH);

При старте микроконтроллера поиск будет сделан с самого начала выделенной памяти, об этом говорит аргумент STARTADDR. Функция вернёт адрес с которого будет происходить сохранение новых данных.

Далее объявляем буфер из пяти 16-ти битных значений, четыре полезных, и один с нулём…

myBuf_t wdata[BUFFSIZE] = {0x1111, 0x2222, 0x3333, 0x4444, 0x0000};

Разумеется буфер может быть любых размеров, главное чтоб был завершающий ноль.


Теперь самое основное, как работает поиск: когда мы запускаем функцию…

res_addr = flash_search_adress(STARTADDR, BUFFSIZE * DATAWIDTH);

… она начиная с адреса указанного в первом аргументе ищет незаписанные ячейки (ячейки в которых записано FF) идущие друг за другом. Искомое количество прописано во втором аргументе — BUFFSIZE * DATAWIDTH. Поиск ведется по 8-ми битным ячейкам, поэтому указываем количество элементов в буфере умноженное на размер элемента в байтах.

Вне зависимости сделаны ли уже какие-то записи ранее, или память только что очищена, функция начинает перебирать ячейки и искать место с которого будут идти подряд 10 ячеек со значением 0хFF.

Визуально это выглядит так…


Ищем десять ячеек после последней записи.


Программно это происходит так…


Если 10 ячеек найдено, тогда возвращается адрес с которого нужно записывать очередные данные. Если память закончилась if(address == ENDMEMORY — 1) тогда очищаем всю память и возвращаем адреса начала памяти.

Последний элемент буфера должен быть равен нулю потому, что если его не будет, и в последнем элементе полезных данных будет записано значение 0хFF, тогда функция поиска воспримет это как незаписанную ячейку.

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


В бесконечном цикле создаём демонстрационный код, который раз в 10 секунд делает новую запись, и раз в 30 секунд читает последнюю запись…

/* USER CODE BEGIN WHILE */
  while (1)
  {
	if((HAL_GetTick() - timme) > 10000) // интервал  10сек
	{
		wdata[0] = wdata[0] + 1; // просто для разнообразия
		wdata[1] = wdata[1] + 1;
		wdata[2] = wdata[2] + 1;
		wdata[3] = wdata[3] + 1;
		
		write_to_flash(wdata); // запись данных во флеш
		
		timme = HAL_GetTick();
		count++;
	}
	
	if(count > 2)
	{
		count = 0;
		
		myBuf_t rdata[BUFFSIZE] = {0,}; // буфер для чтения (не обязательно его создавать, можно использовать буфер для записи)
		
		read_last_data_in_flash(rdata); // чтение данных из флеша
		
		char str[64] = {0,};
		snprintf(str, 64, "Read data: 0x%x 0x%x 0x%x 0x%x 0x%x\n", rdata[0], rdata[1], rdata[2], rdata[3], rdata[4]);
		HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), 100);
	}




В функцию write_to_flash(wdata) передаётся указатель на массив с данными для сохранения. Внутри функции происходит поиск последней записи, записываются новые данные, создаётся контрольная сумма этих данных, потом записанные данные читаются, создаётся их контрольная сумма, и происходит сравнение обоих сумм. Таким образом проверяется корректность записи.


С помощью функции read_last_data_in_flash(rdata) можно читать последнюю запись когда угодно.

Если нужно прочитать сохранённые данные при старте программы, тогда добавляем функцию чтения сразу после поиска адреса…

res_addr = flash_search_adress(STARTADDR, BUFFSIZE * DATAWIDTH);

myBuf_t rdata[BUFFSIZE] = {0,};

read_last_data_in_flash(rdata);



Это всё, всем спасибо


Библиотека


Телеграм-чат istarik

Телеграм-чат STM32


  • 0
  • 3326
Поддержать автора




Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.