Пишем драйвер виртуального EEPROM для STM32F030

от автора


Кто хотел сохранять какие-либо данные в FLASH микроконтроллера во время работы устройства сталкивались с особенностями работы с этим видом памяти. Из-за необходимости стирания страницы большого объёма для перезаписи ячейки FLASH памяти, возникает угроза потери данных из-за отключения питания во время процесса обновления (один из вариантов). В этой статье я расскажу как можно упростить работу с FLASH, да и ещё с гарантией сохранения данных при прерывании процедуры обновления на любом этапе. Реализуем это на STM32F030.

▍ Введение

Можно ли работать с FLASH памятью, так же как и с EEPROM? Почему бы и нет? Мы можем сделать прослойку в виде драйвера, который будет предоставлять интерфейс EEPROM, а внутри жонглировать FLASH памятью. Такая эмуляция EEPROM на FLASH памяти называется виртуальным EEPROM (VEEPROM). Такая шутка позволяет не только удобно работать с FLASH, но и даёт определённые гарантии на целостность данных.

Требования:

  • Обеспечение сохранности данных при прерывании процесса чтения\записи на любом этапе
  • Простой интерфейс, который даст возможность записывать по 1, 2, 4 и N байт

▍ Давайте немного подумаем

Страницы FLASH памяти имеют относительно большой размер (в STM32F030 страница имеет размер 1024 байта) и в случае необходимости записи 1 ячейки нам нужно стереть эти 1024 байта.

Стоит сделать небольшое отступление. Требование к стиранию страницы перед записью актуально для FLASH памяти МК (для STM32F030 точно). На данный момент имеются внешние микросхемы FLASH памяти, которые умеют выполнять byte write operation.

«Ну и что тут такого? Давайте сохраним данные в буфер RAM, сотрём страницу и запишем данные, делов-то». Для начала взглянем на datasheet STM32F030 и увидим там прекрасную таблицу:

Flash memory characteristics

Нас интересует Page erase time — 30 ms, Карл! Этого времени достаточно, чтобы выдернуть питание, пока мы стираем страницу. В результате мы потеряем данные, т.к. мы их храним во временном буфере в RAM. К тому же, выделить буфер в RAM размером 1024 байта далеко не всегда можно.

Сохранность данных можно обеспечить работая с двумя страницами FLASH памяти, не копируя данные в RAM во время процедуры обновления. При записи мы копируем данные из одной страницы в другую, попутно меняя нужные ячейки. В итоге в одной странице хранятся актуальные данные, а в другой устаревшие. Такая процедура продолжается по кругу из одной страницы в другую при каждой операции записи. Всё же это не 100% решает проблему сохранности данных, т.к. при старте МК мы не сможем определить, какая из страниц содержит актуальные данные. Давайте думать дальше.

В нашем примере есть 2 страницы по 1024 байта, в этом случае размер VEEPROM будет равен 1014 байт: 10 байт мы забираем для хранения нужной драйверу информации: 2 байта под контрольную сумму и 8 байт для состояния страницы. Хранить эту информацию мы будем в конце страницы:

Формат страниц

Состояний страницы может быть 5: ERASED, WRITE, VALID, COPY, INVALID:

  • INVALID — страница содержит устаревшие данные
  • COPY — страница содержит актуальные данные, но уже началась процедура копирования данных в другую страницу
  • VALID — страница содержит актуальные данные
  • WRITE — в страницу пишутся данные
  • ERASED — страница пустая

Почему так много байт под состояние? FLASH память в STM32F030 позволяет записывать данные только по 16 бит и после записи мы не сможем повторно как-то изменить значение этой ячейки без стирания целой страницы. Нужна возможность менять статус страницы без стирания, иначе мы вернёмся в начало. Так вот каждая ячейка по 16 бит кодирует одно из состояний. Думаю будет лучше показать их значения:

#define PAGE_STATE_INVALID                  ((uint64_t)(0x0000000000000000)) #define PAGE_STATE_COPY                     ((uint64_t)(0x000000000000FFFF)) #define PAGE_STATE_VALID                    ((uint64_t)(0x00000000FFFFFFFF)) #define PAGE_STATE_WRITE                    ((uint64_t)(0x0000FFFFFFFFFFFF)) #define PAGE_STATE_ERASED                   ((uint64_t)(0xFFFFFFFFFFFFFFFF)) 

Понимаете да о чём я? Нерационально, согласен. Я пытался это оптимизировать, но в голову ничего не пришло. Очень удачно, что статусы влезают в uint64_t, и мы можем работать с ними в коде без танцев с бубном.

Если взглянуть на граф, то переход между состояниями сопровождается сбросом значения ячейки в 0x0000. Переходы по верхним стрелкам выполняются только 1 раз, когда VEEPROM ещё не был инициализирован или произошёл серьёзный сбой.

Граф состояний

В итоге мы получаем следующий алгоритм работы:

  • Инициализация
    1. Ищем страницу в состоянии VALID или COPY путём проверки состояния страницы. Состояние VALID имеет приоритет;
    2. Если нашли, то сохраняем её адрес для последующей работы и завершаем на этом инициализацию;
    3. Если не нашли, то берём любую страницу и стираем её. Этим мы автоматически переводим её в ERASED. В эту страницу и будут записываться новые данные. Запоминаем и завершаем инициализацию.
  • Запись данных
    1. Стираем вторую страницу, которая в INVALID, ERASED или WRITTING (при первом запуске тут мы получаем 2 пустые страницы, то что нужно);
      Текущее состояние страниц: [VALID] [ERASED] или [ERASED] [ERASED];
    2. Сбрасываем ячейку состояние первой страницы в 0х0000, переводя её в COPY. Состояние COPY нужно, чтобы избежать ситуации, при которой мы получим 2 страницы в состоянии VALID, при этом одна будет содержать более свежие данные. Нам нужна однозначность.
      Текущее состояние страниц: [COPY] [ERASED];
    3. Сбрасываем ячейку второй страницы в 0x0000, переводя её в WRITE. Это состояние нужно, на случай пропадания питания во время записи данных во вторую страницу. Без этого состояния страница останется в состоянии ERASED в процессе записи. Пробегаться каждый раз по всей странице, чтобы убедиться в её пустоте не очень хорошее решение.
      Текущее состояние страниц: [COPY] [WRITE];
    4. Копируем данные во вторую страницу и не забываем во время этого изменять требуемые ячейки на новые значения.
      Текущее состояние страниц: [COPY] [WRITE];
    5. Сбрасываем ячейку второй страницы в 0x0000, переводя её в VALID. Вот тут у нас может получиться 2 страницы в VALID состоянии, если бы не было состояния COPY
      Текущее состояние страниц: [COPY] [VALID];
    6. Сбрасываем ячейку второй страницы в 0x0000, переводя её в INVALID.
      Текущее состояние страниц: [INVALID] [VALID];

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

Давайте я лучше покажу это на уже реализованном примере. Тут я специально уменьшил размер страницы VEEPROM до 256 байт, чтобы удобно было смотреть.

1. Инициализация. Обе страницы пустые

2. Началась процедура записи данных. Стираем вторую страницу от прошлых итераций и переводим первую в состояние COPY

3. Переводим вторую страницу в состояние WRITE

4. Пишем данные

5. Незабываем про checksum (считаем и пишем)

6. Переводим вторую страницу в состояние VALID (почему бы и нет, она же содержит актуальные данные)

7. Переводим первую страницу в состояние INVALID (данные там устарели)

8. Делаем вторую страницу активной. Профит!

▍ Разделяй и властвуй

Итак, алгоритм работы понятен, но какие же нам страницы FLASH памяти выбрать для размещения VEEPROM в них? В 95% случаев берут либо первые, либо последние (я предпочитаю последние). Хорошо, теперь нам нужны адреса страниц — окунёмся в документацию.

Большое «спасибо» STM за запихивание в один reference manual несколько МК, в итоге мне приходится тратить время на чтение великолепных сносок под таблицами. Ну сделайте вы эти таблицы отдельными, чтобы глазами глянуть и всё было сразу видно

Нас интересует сноска под таблицей. В нашем случае память заканчивается на адресе 0x08003FFF (16 страниц FLASH по 1024 байта). Берём две последние страницы для VEEPROM с адресами 0x08003800-0x08003BFF и 0x08003C00-0x08003FFF.

У нас есть адреса страниц и нужно их зарезервировать, т.е. нужно дать знать линкеру, что в эти страницы код программы записывать нельзя. Я использую IAR для разработки и там есть .icf файл, в котором определены области памяти для каждого МК. Добавляем туда новый регион и не забываем уменьшить размер ROM региона на 2048 байт:

/*-Memory Regions-*/ define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__   = 0x080037FF;  define symbol __ICFEDIT_region_VEEPROM_start__ = 0x08003800; define symbol __ICFEDIT_region_VEEPROM_end__   = 0x08003FFF;  define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__   = 0x20000FFF;   define region ROM_region     = mem:[from __ICFEDIT_region_ROM_start__      to __ICFEDIT_region_ROM_end__    ]; define region VEEPROM_region = mem:[from __ICFEDIT_region_VEEPROM_start__  to __ICFEDIT_region_VEEPROM_end__]; define region RAM_region     = mem:[from __ICFEDIT_region_RAM_start__      to __ICFEDIT_region_RAM_end__    ]; 

В целом можно просто уменьшить ROM, но лучше создать отдельный именованный регион, чтобы потом быстро вспомнить распределение FLASH памяти.

▍ Глянем реализацию

Начнём с лица — интерфейс:

//  *************************************************************************** /// @brief  VEEPROM driver initializetion /// @return true - init success, false - fail //  *************************************************************************** extern bool veeprom_init();  //  *************************************************************************** /// @brief  Mass erase VEEPROM /// @return true - init success, false - fail //  *************************************************************************** extern bool veeprom_mass_erase();  //  *************************************************************************** /// @brief  Read data from VEEPROM /// @param  [in] veeprom_addr: virtual address [0x0000...size-1] /// @param  [out] buffer: pointer to buffer for data /// @param  [in] bytes_count: bytes count for read /// @return true - init success, false - fail //  *************************************************************************** extern bool veeprom_read(uint32_t veeprom_addr, uint8_t* buffer, uint32_t bytes_count); extern uint8_t veeprom_read_8(uint32_t veeprom_addr); extern uint16_t veeprom_read_16(uint32_t veeprom_addr); extern uint32_t veeprom_read_32(uint32_t veeprom_addr);  //  *************************************************************************** /// @brief  Write data to VEEPROM /// @param  [in] veeprom_addr: virtual address [0x0000...size-1] /// @param  [out] data: pointer to data for write /// @param  [in] bytes_count: bytes count for write /// @return true - init success, false - fail //  *************************************************************************** extern bool veeprom_write(uint32_t veeprom_addr, uint8_t* data, uint32_t bytes_count); extern bool veeprom_write_8(uint32_t veeprom_addr, uint8_t  value); extern bool veeprom_write_16(uint32_t veeprom_addr, uint16_t value); extern bool veeprom_write_32(uint32_t veeprom_addr, uint32_t value); 

Прекрасно, не правда ли? Всё просто и без лишних параметров, адреса тут относительные [0x0000… SIZE — 1]. И ведь не подумаешь, что за таким интерфейсом скрываются ужасы работы с FLASH памятью.

Глянем реализацию. Весь замес происходит в функциях veeprom_write и veeprom_init, остальные функции либо обёртки, либо функции для сокращения кода.

Код. Много кода

//  *************************************************************************** /// @file    veeprom.c /// @author  NeoProg //  *************************************************************************** #include "veeprom.h" #include "project_base.h" #define FLASH_PAGE_SIZE                     (1024) #define VEEPROM_SERVICE_HEADER_SIZE         (10) #define VEEPROM_PAGE_1_ADDR                 (0x08003800) #define VEEPROM_PAGE_2_ADDR                 (0x08003C00) #define VEEPROM_PAGE_SIZE                   (FLASH_PAGE_SIZE - VEEPROM_SERVICE_HEADER_SIZE)  #define PAGE_CHECKSUM_OFFSET                (VEEPROM_PAGE_SIZE)  #define PAGE_STATE_OFFSET                   (VEEPROM_PAGE_SIZE + 2) #define PAGE_STATE_INVALID                  ((uint64_t)(0x0000000000000000)) #define PAGE_STATE_COPY                     ((uint64_t)(0x000000000000FFFF)) #define PAGE_STATE_VALID                    ((uint64_t)(0x00000000FFFFFFFF)) #define PAGE_STATE_WRITE                    ((uint64_t)(0x0000FFFFFFFFFFFF)) #define PAGE_STATE_ERASED                   ((uint64_t)(0xFFFFFFFFFFFFFFFF))   static uint32_t active_page_addr = 0; static uint32_t inactive_page_addr = 0;   static bool flash_lock(); static bool flash_unlock(); static bool flash_wait_and_check(); static bool flash_page_erase(uint32_t flash_addr);  static uint64_t flash_page_get_state(uint32_t flash_addr); static bool     flash_page_set_state(uint32_t flash_addr, uint64_t state);  static uint16_t flash_page_calc_checksum(uint32_t flash_addr); static uint16_t flash_page_read_checksum(uint32_t flash_addr); static bool     flash_page_write_checksum(uint32_t flash_addr, uint16_t checksum);  static uint8_t  flash_read_8(uint32_t flash_addr); static uint16_t flash_read_16(uint32_t flash_addr); static uint32_t flash_read_32(uint32_t flash_addr); static bool     flash_write_16(uint32_t flash_addr, uint16_t value);    //  *************************************************************************** /// @brief  VEEPROM driver initializetion /// @return true - init success, false - fail //  *************************************************************************** bool veeprom_init() {     // Search active page     uint64_t page1_state = flash_page_get_state(VEEPROM_PAGE_1_ADDR);     uint64_t page2_state = flash_page_get_state(VEEPROM_PAGE_2_ADDR);     if (page1_state == PAGE_STATE_VALID) {         active_page_addr = VEEPROM_PAGE_1_ADDR;         inactive_page_addr = VEEPROM_PAGE_2_ADDR;     }      else if (page2_state == PAGE_STATE_VALID) {         active_page_addr = VEEPROM_PAGE_2_ADDR;         inactive_page_addr = VEEPROM_PAGE_1_ADDR;     }      else if (page1_state == PAGE_STATE_COPY) {         active_page_addr = VEEPROM_PAGE_1_ADDR;         inactive_page_addr = VEEPROM_PAGE_2_ADDR;     }     else if (page2_state == PAGE_STATE_COPY) {         active_page_addr = VEEPROM_PAGE_2_ADDR;         inactive_page_addr = VEEPROM_PAGE_1_ADDR;     }     else {         if (!flash_page_erase(VEEPROM_PAGE_1_ADDR)) {             return false;         }         active_page_addr = VEEPROM_PAGE_1_ADDR;         inactive_page_addr = VEEPROM_PAGE_2_ADDR;         return true;     }          // Check checksum     return flash_page_read_checksum(active_page_addr) != flash_page_calc_checksum(active_page_addr); }  //  *************************************************************************** /// @brief  Mass erase VEEPROM /// @return true - init success, false - fail //  *************************************************************************** bool veeprom_mass_erase() {     return flash_page_erase(VEEPROM_PAGE_1_ADDR) && flash_page_erase(VEEPROM_PAGE_2_ADDR); }  //  *************************************************************************** /// @brief  Read data from VEEPROM /// @param  [in] veeprom_addr: virtual address [0x0000...size-1] /// @param  [out] buffer: pointer to buffer for data /// @param  [in] bytes_count: bytes count for read /// @return true - init success, false - fail //  *************************************************************************** bool veeprom_read(uint32_t veeprom_addr, uint8_t* buffer, uint32_t bytes_count) {     if (veeprom_addr + bytes_count >= VEEPROM_PAGE_SIZE || !active_page_addr) {         return false;     }     while (bytes_count) {         *buffer = flash_read_8(active_page_addr + veeprom_addr);         ++buffer;         --bytes_count;     }     return true; } uint8_t veeprom_read_8(uint32_t veeprom_addr) {     uint8_t data = 0;     veeprom_read(veeprom_addr, &data, sizeof(data));     return data; } uint16_t veeprom_read_16(uint32_t veeprom_addr) {     uint16_t data = 0;     veeprom_read(veeprom_addr, (uint8_t*)&data, sizeof(data));     return data; } uint32_t veeprom_read_32(uint32_t veeprom_addr) {     uint32_t data = 0;     veeprom_read(veeprom_addr, (uint8_t*)&data, sizeof(data));     return data; }  //  *************************************************************************** /// @brief  Write data to VEEPROM /// @param  [in] veeprom_addr: virtual address [0x0000...size-1] /// @param  [out] data: pointer to data for write /// @param  [in] bytes_count: bytes count for write /// @return true - init success, false - fail //  *************************************************************************** bool veeprom_write(uint32_t veeprom_addr, uint8_t* data, uint32_t bytes_count) {     // Erase inactive page (set ERASED state)     if (!flash_page_erase(inactive_page_addr)) {         return false;     }          flash_unlock();          // Set COPY state for active page     if (!flash_page_set_state(active_page_addr, PAGE_STATE_COPY)) {         flash_lock();         return false;     }          // Set WRITE state for inactive page     if (!flash_page_set_state(inactive_page_addr, PAGE_STATE_WRITE)) {         flash_lock();         return false;     }          // Copy data from active page into inactive with change data     for (uint32_t offset = 0; offset < VEEPROM_PAGE_SIZE; /* NONE */) {         uint8_t byte[2] = {0};         for (uint32_t i = 0; i < 2; ++i) {             if (offset >= veeprom_addr && offset < veeprom_addr + bytes_count) {                 byte[i] = *data;                 ++data;             } else {                 byte[i] = flash_read_8(active_page_addr + offset);             }             ++offset;         }         uint16_t word = ((byte[0] << 8) & 0xFF00) | byte[1];         if (word != flash_read_16(inactive_page_addr + offset - 2)) {             // Write data             if (!flash_write_16(inactive_page_addr + offset - 2, word)) {                 flash_lock();                 return false;             }         }     }          // Calc checksum for inactive page     uint16_t checksum = flash_page_calc_checksum(inactive_page_addr);     if (!flash_page_write_checksum(inactive_page_addr, checksum)) {         flash_lock();         return false;     }          // Set VALID state for inactive page     if (!flash_page_set_state(inactive_page_addr, PAGE_STATE_VALID)) {         flash_lock();         return false;     }          // Set INVALID state for active page     if (!flash_page_set_state(active_page_addr, PAGE_STATE_INVALID)) {         flash_lock();         return false;     }          // Swap pages     uint32_t tmp = inactive_page_addr;     inactive_page_addr = active_page_addr;     active_page_addr = tmp;          flash_lock();     return true; } bool veeprom_write_8(uint32_t veeprom_addr, uint8_t value) {     return veeprom_write(veeprom_addr, &value, 1); } bool veeprom_write_16(uint32_t veeprom_addr, uint16_t value) {     return veeprom_write(veeprom_addr, (uint8_t*)&value, 2); } bool veeprom_write_32(uint32_t veeprom_addr, uint32_t value) {     return veeprom_write(veeprom_addr, (uint8_t*)&value, 4); }      //  *************************************************************************** /// @brief  Lock/unlock FLASH /// @return true - init success, false - fail //  *************************************************************************** static bool flash_lock() {     FLASH->CR |= FLASH_CR_LOCK;     return (FLASH->CR & FLASH_CR_LOCK) == FLASH_CR_LOCK; } static bool flash_unlock() {     if (FLASH->CR & FLASH_CR_LOCK) {         FLASH->KEYR = 0x45670123;         FLASH->KEYR = 0xCDEF89AB;     }     return (FLASH->CR & FLASH_CR_LOCK) != FLASH_CR_LOCK; }  //  *************************************************************************** /// @brief  Wait FLASH operation complete /// @return true - operation comleted, false - operation comleted with error //  *************************************************************************** static bool flash_wait_and_check() {     while (FLASH->SR & FLASH_SR_BSY);     if (FLASH->SR & (FLASH_SR_PGERR | FLASH_SR_WRPRTERR)) {         FLASH->SR |= FLASH_SR_PGERR | FLASH_SR_WRPRTERR | FLASH_SR_EOP;         return false;     }     FLASH->SR |= FLASH_SR_PGERR | FLASH_SR_WRPRTERR | FLASH_SR_EOP;     return true; }  //  *************************************************************************** /// @brief  Erase FLASH page /// @param  [in] flash_addr: page address for erase /// @return true - success, false - fail //  *************************************************************************** static bool flash_page_erase(uint32_t flash_addr) {     flash_unlock();          FLASH->CR |= FLASH_CR_PER;     FLASH->AR = flash_addr;     FLASH->CR |= FLASH_CR_STRT;     bool result = flash_wait_and_check();     FLASH->CR &= ~FLASH_CR_PER;          flash_lock();     return result; }  //  *************************************************************************** /// @brief  Get/set FLASH page state /// @param  [in] flash_addr: page address /// @param  [in] state: new page state (for flash_page_set_state) /// @return true - success, false - fail //  *************************************************************************** static uint64_t flash_page_get_state(uint32_t flash_addr) {     uint64_t state = 0;     for (uint8_t i = 0; i < 4; ++i) {         state = (state << 16) | flash_read_16(flash_addr + PAGE_STATE_OFFSET + i * 2);     }     return state; } static bool flash_page_set_state(uint32_t flash_addr, uint64_t state) {     uint64_t mask = 0xFFFF000000000000;     for (uint8_t i = 0; i < 4; ++i) {         if (state & mask) {             if (flash_read_16(flash_addr + PAGE_STATE_OFFSET + i * 2) != 0xFFFF) {                 return false;             }             continue;         }         if (!flash_write_16(flash_addr + PAGE_STATE_OFFSET + i * 2, 0x0000)) {             return false;         }         mask >>= 16;     }     return true; }  //  *************************************************************************** /// @brief  Calc/read/write checksum /// @param  [in] flash_addr: page address /// @param  [in] checksum: new page checksum (for flash_page_write_checksum) /// @return true - success, false - fail //  *************************************************************************** static uint16_t flash_page_calc_checksum(uint32_t flash_addr) {     uint32_t bytes_count = VEEPROM_PAGE_SIZE;     uint16_t checksum = 0;     while (bytes_count) {         checksum += flash_read_8(flash_addr);         ++flash_addr;         --bytes_count;     }     return checksum; } static uint16_t flash_page_read_checksum(uint32_t flash_addr) {     return flash_read_16(flash_addr + PAGE_CHECKSUM_OFFSET); } static bool flash_page_write_checksum(uint32_t flash_addr, uint16_t checksum) {     return flash_write_16(flash_addr + PAGE_CHECKSUM_OFFSET, checksum); }  //  *************************************************************************** /// @brief  Read data from FLASH in BE format /// @param  [in] flash_addr: page address /// @param  [in] state: new page state (for flash_page_set_state) /// @return cell value //  *************************************************************************** static uint8_t flash_read_8(uint32_t flash_addr) {     return *((uint8_t*)flash_addr); } static uint16_t flash_read_16(uint32_t flash_addr) {     return __REV16(*((uint16_t*)flash_addr)); } static uint32_t flash_read_32(uint32_t flash_addr) {     return __REV(*((uint32_t*)flash_addr)); }  //  *************************************************************************** /// @brief  Write word to FLASH in LE format /// @param  [in] flash_addr: page address /// @param  [in] value: new cell value /// @return true - success, false - fail //  *************************************************************************** static bool flash_write_16(uint32_t flash_addr, uint16_t value) {     FLASH->CR |= FLASH_CR_PG;     *((uint16_t*)flash_addr) = __REV16(value);     bool result = flash_wait_and_check();     FLASH->CR &= ~FLASH_CR_PG;          if (flash_read_16(flash_addr) != value) {         return false;     }     return result; } 

Есть крайне важное замечание, которое стоит сказать. В даташите на МК сказано следующее:

Если своими словами, то МК не может обращаться к FLASH памяти, пока мы с ней что-то делаем (стираем, пишем), в том числе и не может читать инструкции для выполнения. Т.е. по сути МК залипает. Для решения этой проблемы нужно использовать __ramfunc — функции, инструкции которых хранятся в RAM, а не в FLASH и в этом случае программа будет продолжать работать. При использовании VEEPROM рекомендуется скопировать всю таблицу прерываний и все критичные обработчики в RAM. И не забываем про функции, которые вызываются из этих обработчиков. Не должно быть никакого обращения к FLASH, это важно. В нашем случае я не стал так глубоко закапываться и __ramfunc опустил.

▍ Итоги

Какие из этого можно сделать выводы? VEEPROM требователен к ресурсам (2 физических страниц на 1 виртуальную) и на все эти танцы вокруг страниц создают кучу процессорных инструкций.

Производительность у него тоже не очень. Поэтому лучше всего писать данные большими пачками, иначе на каждый байт будут меняться страницы, а это 30мс + время на копирование данных из страницы в страницу. Неплохо так, да? Хуже некуда.

Тем не менее, когда целостность данных важна он их сохранит. Надеюсь, кому-то это поможет и натолкнёт на какую-нибудь мысль. Спасибо за внимание.


ссылка на оригинал статьи https://habr.com/ru/articles/578690/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *