В данной статье покажем вам, как заменить содержимое Flash памяти в микроконтроллерах STM32 с использованием носителя в виде SD-карты. Для этого необходим специальный загрузчик, приведенный далее.
Загрузчик, поддерживающий передачу данных с SD-карты во флэш-память, находится в начальных областях памяти программы. Каждый раз после сброса, микроконтроллер запускает его (когда строка BOOT0 = 0, состояние линии BOOT1 не имеет значения), независимо от того, сохранена ли правильная версия пользовательской программы или нет.
Это решение дает большую гибкость, поскольку загрузчик сохраняется в памяти микроконтроллера только один раз (для этого вам необходим JTAG-программатор или вы также можете сделать это через USART1, в соответствии с рекомендациями производителя).
Рис. 1. Схема, иллюстрирующая работу загрузчика
В начале своей работы загрузчик запускается во флэш-памяти и сразу копирует себя в память SRAM, а затем запускается из нее (рис. 1), что позволяет свободно манипулировать содержимым флэш-памяти. Затем выполняется инициализация периферийных устройств, необходимых для работы загрузчика. Благодаря этому решению нет необходимости обновлять программное обеспечение во флэш-памяти после каждого сброса микроконтроллера, но мы делаем это в особых случаях:
- Когда флэш-память не запрограммирована, это сводится к чтению первых ячеек вектора пользовательского прерывания (указателя стека и вектора сброса) и проверке, были ли они удалены случайно. Этот простой код, конечно, не может определить, является ли приложение безошибочным, но этого достаточно, чтобы определить, находится ли приложение в памяти.
- Когда пользователь принудительно запускает загрузчик, что проверяется состоянием одной из линий микроконтроллера GPIO (определяется WakeUp_PORT и WakeUp_PIN).
- Когда загрузчик был вызван из пользовательского приложения. Этот метод позволяет вам разместить несколько строк кода в вашей программе, которые обеспечивают вызов загрузчика.
Внутренняя поддержка Flash
Производитель внедрил в микроконтроллеры STM32 специальный драйвер Flash Program / Erase Controller (FPEC), который можно использовать для изменения содержимого флэш-памяти. В микроконтроллерах STM32 флэш-память организована в виде страниц, размер которых составляет 1 КБ или 2 КБ, в зависимости от общего объема памяти.
Флэш-память находится в адресном пространстве процессора, начиная с адреса 0x08000000. Подробное описание того, как обрабатывать флэш-память в микроконтроллерах STM32, можно найти в документе PM0042 под названием «Программирование флэш-памяти STM32F10xxx», который доступен на веб-сайте STMicroelectronics.
После сброса микроконтроллера FPEC защищает содержимое флэш-памяти от сохранения, поэтому нет возможности случайного повреждения ее содержимого. Чтобы внести какие-либо изменения, вы должны сначала разблокировать память. Это делается путем ввода двух значений в реестр Flash_KEYR:
KEY1 = 0x45670123
а затем
KEY2 = 0xCDEF89AB
С этого момента могут выполняться операции стирания и записи памяти, пока не произойдет ошибка, которая блокирует доступ к памяти до следующей разблокировки. Алгоритм записи во флэш-память через FPEC показан на рис. 2.
Рис. 2. Алгоритм программирования флэш-памяти в микроконтроллерах STM32
Запись в память производится 2 байтами. Сначала установите бит PG в реестре Flash_CR, информируя контроллер о том, что мы будем программировать. После этого действия вы можете изменить содержимое памяти, что требует ввода 2 байтов по адресу, где они должны быть размещены в памяти. Затем дождитесь окончания этой операции и убедитесь, что запись была произведена правильно, для чего необходимо прочитать записанные 2 байта и убедиться в их правильности — это можно сделать путем сравнения. Если ошибок не нет, то вы можете записать следующие данные таким же образом. После сохранения всей страницы вы должны удалить бит PG.
Содержимое флэш-памяти в микроконтроллерах STM32 может быть удалено двумя способами: страница за страницей или все ее содержимое. В случае загрузчика память будет удаляться постранично. Постраничный алгоритм удаления показан на рис. 3.
Рис. 3. Алгоритм стирания флэш-памяти в микроконтроллерах STM32 (постраничный)
Мы начинаем стирать содержимое памяти, устанавливая бит PER, информирующий FPEC в регистре Flash_CR о том, что страница флэш-памяти будет удалена. Затем введите адрес удаляемой страницы в регистр Flash_AR и установите бит STRT в регистр Flash_CR. В этот момент начинается физическое удаление содержимого указанной страницы памяти. На следующем этапе мы ожидаем его завершения, после чего проверяем, была ли страница удалена правильно, читая и проверяя ее содержимое.
Поддержка SD-карты
Как упоминалось ранее, процессор обменивается данными с SD-картой через интерфейс SPI. На рис. 4 показан рекомендуемый способ подключения разъема SD-карты к микроконтроллеру.
Рис. 4. Электрическая схема подключения SD-карты к микроконтроллеру
Блок-схема приложения загрузчика показана на рисунке 5. Библиотека FAT, используемая в проекте, дает возможность читать файлы с SD-карты и поддерживает длинные имена.
Рис. 5. Блок-схема приложения, выполняющего роль загрузчика
Как показано в примере 1 функция FAT_ConnectEvent присоединена к библиотеке FAT и вызывается когда на SD-карте обнаруживается раздел FAT16/32. В нем открывается корневой каталог, затем проверяется, есть ли на карте каталог \stm32f10x и файл file_name.txt.
Затем содержимое этого файла считывается, обрабатывается загрузчиком как имя двоичного файла, содержащего новую версию программы, загруженную во флэш-память, которая должна находиться в том же каталоге. Далее открывается двоичный файл и считываются пакеты данных о размере страницы флэш-памяти микроконтроллера. После копирования содержимого двоичного файла во флэш-память он закрывается и загрузчик завершает работу. После выхода из него программа пользователя запускается автоматически.
Пример 1. Функция FAT_ConnectEvent
void FAT_ConnectEvent (FAT_Desc * FAT32D) { FILE file_desc; uint32_t result; uint32_t address_shift = 0; if (! sfopen (&file_desc, (const char *) FAT32D- & gt; FAT_name_string_descriptor, "r")) { return; } if (! sfopen (&file_desc, "stm32f10x / file_name.txt", "r")) { return; } fread (boot_read_buffer, 1, boot_page_size, &file_desc); fclose (&file_desc); if (! sfopen (&file_desc, (const char *) boot_read_buffer, "r")) { return; } FlashUnlock (); do { if (FlashErasePage (DEF_APP_ADDRESS + address_shift)) { break; } result = fread (boot_read_buffer, 1, boot_page_size, &file_desc); if (FlashWritePage (DEF_APP_ADDRESS + address_shift, boot_read_buffer, result)) { break; } address_shift + = boot_page_size; } while (result == boot_page_size); FlashLock (); fclose (&file_desc); }
Заключительные замечания
Загрузчик должен быть сохранен в памяти микроконтроллера так же, как и любая другая программа (с использованием интерфейса JTAG или через USART). После сохранения во флэш-памяти загрузчик сразу готов к использованию.
Чтобы использовать его для правильного запуска пользовательского приложения, вам необходимо выполнить в нем несколько операций. Во-первых, это перемещение приложения пользователя во флэш-памяти с адреса от 0x8000000 до 0x8002000, то есть на 8 КБ вверх (например, путем изменения адреса ссылки начала памяти в сценарии компоновщика). Во-вторых — удалить настройку вектора прерывания пользовательского приложения, как это делает загрузчик.
Дополнительная опция, упомянутая ранее, — это возможность вызова загрузчика во время, уже объявленное пользователем в его программе. В примере 2 показан вариант файла заголовка, позволяющий вызвать загрузчик для двух вышеупомянутых методов с использованием макросов BOOTLOADER_CALL_BY_JUMP или BOOTLOADER_CALL_BY_RESET.
В прикрепляемом файле вам нужно только подключить соответствующую библиотеку ST (CMSIS), определяющую показатели структуры регистров устройств (RCC, BKP, и т. д.)
Пример 2. Пример заголовочного файла для вызова загрузчика
#ifndef BOOTLOADER_H_ \ #define BOOTLOADER_H_ // адрес таблицы векторов прерываний загрузчика #define BOOTLOADER_VECTOR_ADDR 0x8000000 // определения, вставленные из файлов библиотеки ST / * --------- PWR регистрирует битовый адрес в области псевдонима ---------- * / #define PWR_OFFSET (PWR_BASE - PERIPH_BASE) / * --- CR Register --- * / / * Псевдоним слова адреса бита DBP * / #define CR_OFFSET (PWR_OFFSET + 0x00) #define DBP_BitNumber 0x08 #define CR_DBP_BB (PERIPH_BB_BASE + (CR_OFFSET * 32) + (DBP_BitNumber * 4)) // структура, описывающая начало стандартного вектора прерывания typedef struct { uint32_t SP; void (* RESET_ISR) (void); void (* NMIExc) (void); void (* HardFaultExc) (void); void (* MemManageExc) (void); void (* BusFaultExc) (void); void (* UsageFaultExc) (void); void (* RESRV1) (void); void (* RESRV2) (void); void (* RESRV3) (void); void (* RESRV4) (void); void (* SVC) (void); BOOTLOADER_CM3_ISR_TABLE}; // определение указателя на таблицу прерываний загрузчика #define BOOTLOADER_TAB ((BOOTLOADER_CM3_ISR_TABLE *) BOOTLOADER_VECTOR_ADDR) // макрос для вызова загрузчика путем перехода к нему #define BOOTLOADER_CALL_BY_JUMP () (BOOTLOADER_TAB- & SVC) () // макрос, служащие для вызова загрузчика через запись сигнатуры // к первому резервному регистру данных и сбросу процессора #define BOOTLOADER_CALL_BY_RESET () {\ RCC-> APB1ENR | = RCC_APB1Periph_BKP | RCC_APB1Periph_PWR; \ RCC-> APB1RSTR & amp; = ~ ((uint32_t) (RCC_APB1Periph_BKP | RCC_APB1Periph_PWR)); \\ * (vu32 *) CR_DBP_BB = (u32) 1; \ BKP-> DR1 = 0x159D; \ SCB-> AIRCR = (SCB-> AIRCR & 0xFFFF) | (0x5FA & lt; & lt; 16) | (1 & lt; & lt; 0); \ } #endif / * BOOTLOADER_H _ * /
Этот файл определяет базовый адрес вектора прерывания загрузчика, который совпадает с адресом начала флэш-памяти. Затем был определено начало стандартной для этих микроконтроллеров структуры вектора прерываний.
Необходимо обсудить, что находится внутри макроса BOOTLOADER_CALL_BY_RESET. Первые две строки отвечают за включение часов для контроллера резервного копирования и его выключение, третья строка отвечает за разблокировку записи в регистры резервного копирования (она поступает из библиотеки stm32f10x_pwr).
В четвертой строке необходимо ввести значение 0x159D в первый регистр данных резервного копирования, которое загрузчик будет искать в этом реестре после сброса. Пятая строка отвечает за программный сброс микроконтроллера. Это включает установку бита VECTRESET (самый младший бит) в регистре управления прерываниями и сбросом приложения. Это один из регистров контроллера NVIC, определенных в спецификации ядра Cortex-M3. После ввода в нулевой бит 1 с одновременным вводом в биты 16 … 31 сигнатуры 0x5FA, происходит сброс микроконтроллера.