STM32F0 using FLASH HAL (запись чтение системной FLASH)

Принципы чтения и записи FLASH памяти в STM32F0 мало чем отличаются от подобных принципов для других семейств. Описываемый далее метод так же неплохо работает и в STM32F1 (проверено на STM32F103C6T6A). Библиотека HAL позволит унифицировать знания, то есть пользоваться функцией стирания/записи можно аналогично, независимо от модели микроконтроллера.

Первое, что стоит хорошо понять:

  1. память FLASH всегда начинается с адреса 0x08000000
  2. память FLASH всегда разбита на страницы и стирать данные мы можем только страницами (это 1 килобайт или 2 килобайта, читайте datasheet), а не по одному байту или слову.
  3. нельзя записать новые данные поверх старых данных. Перед записью новых данных необходимо сделать стирание нужной страницы.
  4. адрес страницы это не 1,2,3,4,5.. это абсолютный адрес в адресном пространстве, пример 0x08000000, 0x08000400, 0x08000800 и т.д. (внимание, адреса в примере актуальны для страниц размером 1 кБ).
  5. Исполняемая программа записывается в первую страницу, начиная с адреса 0x08000000 и занимает ровно столько страниц, сколько ей требуется, остальные страницы не записываются при прошивке (и не стираются тоже). Конфигурацию записывайте в неиспользуемые страницы. Например, в STM32F030F4P6 всего 16 страниц памяти по 1 кБ и если размер прошивки меньше 15 кБ, то смело пишите настройки в последний (16-ый) сектор.
  6. Не записывайте данные на флешку слишком часто, у нее ограниченное количество циклов стирания/записи (как правило, 10000 циклов). Флешкой нельзя пользоваться так же как EEPROM.

Чтение флешки

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

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

Функция позволяет читать по одному слову (4 байта) за раз.

Запись флешки

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

Допустим, вы определились с номером страницы для хранения настроек. Например, у меня, в STM32F030F4P6 это 16-ая страница, начинающаяся по адресу 0x08003C00. Именно этот адрес я буду указывать в функции стирания. Для удобства можно определить константу, в которой хранится этот адрес

#define SETTINGS_ADDRESS 0x08003C00

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

void WriteConfig() {
 HAL_FLASH_Unlock(); // Открыть доступ к FLASH (она закрыта от случайной записи)
 // В структуре settings хранятся настройки, преобразую ее в 16-битный массив для удобства доступа
 uint16_t* data = (uint16_t*) &settings;
 FLASH_EraseInitTypeDef ef; // Объявляю структуру, необходимую для функции стирания страницы

 HAL_StatusTypeDef stat;

 ef.TypeErase = FLASH_TYPEERASE_PAGES; // Стирать постранично
 ef.PageAddress = SETTINGS_ADDRESS; // Адрес страницы для стирания
 ef.NbPages = 1; //Число страниц = 1
 uint32_t temp; // Временная переменная для результата стирания (не использую)
 HAL_FLASHEx_Erase(&ef, &temp); // Вызов функции стирания
 // Будьте уверены, что размер структуры настроек кратен 2 байтам
 for (int i = 0; i < sizeof(settings); i += 2) { // Запись всех настроек
  stat = HAL_FLASH_Program (FLASH_TYPEPROGRAM_HALFWORD, SETTINGS_ADDRESS + i, *(data++));
  if (stat != HAL_OK) break; // Если что-то пошло не так - выскочить из цикла
 }
 HAL_FLASH_Lock(); // Закрыть флешку от случайной записи
}

 

А далее, пример использования функции для чтения конфигурации

// Пример чтения только 4 байт настроек. Для бОльшего объема данных используйте цикл
void ReadConfig() {
 // Структуру настроек превращаю в указатель на массив 8-ми битных значений
 uint8_t* setData = (uint8_t*)&settings;
 uint32_t tempData = FlashRead(SETTINGS_ADDRESS); // Прочесть слово из флешки
 if (tempData != 0xffffffff) { // Если флешка не пустая
 setData[0] = (uint8_t)((tempData & 0xff000000) >> 24); // Извлечь первый байт из слова
 setData[1] = (uint8_t)((tempData & 0x00ff0000) >> 16); // Извлечь второй байт из слова
 setData[2] = (uint8_t)((tempData & 0x0000ff00) >> 8); // Излечь третий байт из слова
 setData[3] = tempData & 0xff; // Извлечь четвертый байт из слова
 }
}

  1. S. При разработке ПО я столкнулся с одним неприятным моментом, флешка стиралась со статусом ffffffff - это значит, что все отлично; запись данных тоже имела статус HAL_OK - успешно. Однако область на FLASH по факту не записывалась совсем, оставалась стертой (заполнена 0xFF). Помогла случайность: я стер всю память микроконтроллера в ST-Link Utility и залил прошивку заново. С радостью заметил, что все заработало. Хотя застряли мы с этой проблемой почти на 2 рабочих дня.