В статье Загрузка программы в ОЗУ и запуск через UART мы научились загружать программу в ОЗУ и запускать ее. Давайте напишем программу для ОЗУ, которая умеет записывать массив данных из ОЗУ во FLASH память. Назовем эту программу "прошиватель". В массиве данных будет лежать наша программа мигания диодом. В итоге после прошивки при запуске из Flash на плате будет мигать светодиод.
Создаем новый проект, назовем его "Flash_UartWriter". В библиотеках выбираем пункты - Startup_MDR1986BE9x, EEMPROM, PORT, RST_CLK. Добавляем в проект новый файл "main.c". Как это сделать описано здесь - Создаем новый проект.
Теперь делаем настройки проекта для запуска в ОЗУ. Подробно это описано тут - Запуск программы в ОЗУ из Keil. В данном случае, файл "setup.ini" можно не создавать и не подключать, мы не планируем запускать проект в отладчике.
Далее приведен листинг программы "прошивателя", состоящий из одного файла "main.c". Код собран из двух примеров:
Пример "Sector_Operations" при стандартной установке Keil можно найти по пути:
C:\Keil_v5\ARM\PACK\Keil\MDR1986BExx\1.4\Examples\MDR1986VE9x\MDR32F9Q1_EVAL\EEPROM\Sector_Operations
Полный листинг файла "main.c" получился такой:
#include <MDR32F9Qx_port.h> #include <MDR32F9Qx_rst_clk.h> #include <MDR32F9Qx_eeprom.h> #define EEPROM_PAGE_SIZE (4*1024) // размер страницы flash памяти #define EEPROM_ADDR_START 0x08000000 // адрес куда копировать данные #define RAM_ADDR_START 0x20002000 // адрес откуда копировать данные // Период мигания светодиодом для индикации текущего статуса #define ST_DELAY_OK 2000000 // Успешное завершение #define ERR_DELAY_CLEAR 500000 // Ошибка - страница памяти не стерлась #define ERR_DELAY_VERIFY 100000 // Ошибка верификации памяти после записи uint32_t Led_Pin = PORT_Pin_1; // Вывод индикации на второй светодиод, в HelloWorld используется PORT_Pin_0 uint32_t readAddr(uint32_t address); // Функция возвращает 32 битное слово, расположенное по указанному адресу void Delay(int waitTicks); // Функция задержки из пример HelloWorld void LedInit(void); // Функция инициализации PortC из примера HelloWorld, код ранее лежал в main(). void LedSetState(uint16_t ledOn); // Функция зажигает или гасит светодиод Led_Pin. Включим светодиод на время работы с Flash и выключим по окончании. void LedShowStatus(uint32_t flashPeriod); // В Функции содержится бесконечный цикл мигания светодиодом с периодом flashPeriod из примера HelloWorld. int main(void) { uint32_t Data = 0; uint32_t i = 0; // Тактирование EEPROM RST_CLK_PCLKcmd(RST_CLK_PCLK_EEPROM, ENABLE); //--------- Включаем светодиод - индикатор работы с EEPROM ------- LedInit(); LedSetState(1); //------------ Стираем первую страницу в EEPROM ---------------- /* Erase main memory page MAIN_EEPAGE */ EEPROM_ErasePage (EEPROM_ADDR_START, EEPROM_Main_Bank_Select); /* Check main memory page MAIN_EEPAGE */ Data = 0xFFFFFFFF; for (i = 0; i < EEPROM_PAGE_SIZE; i += 4) { // При записи вся память страницы Flash заполняется единицами, т.е. значениями 0xFFFFFFFF. // Проверяем так ли это. if (EEPROM_ReadWord (EEPROM_ADDR_START + i, EEPROM_Main_Bank_Select) != Data) { // Уходим в цикл мигания в случае ошибки с периодом ERR_DELAY_CLEAR LedShowStatus(ERR_DELAY_CLEAR); } } //------------ Запись программы в первую страницу EEPROM ------- /* Fill main memory page MAIN_EEPAGE */ for (i = 0; i < EEPROM_PAGE_SIZE; i += 4) { // Считываем по словам код программы HelloWorld, записанной по UART с адреса 0x20002000 Data = readAddr(RAM_ADDR_START + i); // Записываем код в Flash память, начиная с адреса 0x08000000 EEPROM_ProgramWord (EEPROM_ADDR_START + i, EEPROM_Main_Bank_Select, Data); } /* Check main memory page MAIN_EEPAGE */ for (i = 0; i < EEPROM_PAGE_SIZE; i +=4 ) { // Считываем по словам код программы HelloWorld, записанной по UART с адреса 0x20002000 Data = readAddr(RAM_ADDR_START + i); // Считываем код программы из Flash памяти и сравниваем с кодом из ОЗУ. if (EEPROM_ReadWord (EEPROM_ADDR_START + i, EEPROM_Main_Bank_Select) != Data) { // Уходим в цикл мигания в случае ошибки с периодом ERR_DELAY_VERIFY LedShowStatus(ERR_DELAY_VERIFY); } } //------------ Гасим светодиод ------- // LedSetState(0); // Выключаем светодиод по окончании работы с Flash // Показываем длинными периодами мигания, что программа успешно закончила свою работу. LedShowStatus(ST_DELAY_OK); } uint32_t readAddr(uint32_t address) { return (*(__IO uint32_t*) address); } void Delay(int waitTicks) { int i; for (i = 0; i < waitTicks; i++) { __NOP(); } } void LedSetState(uint16_t ledOn) { if (ledOn) PORT_SetBits(MDR_PORTC, Led_Pin); else PORT_ResetBits(MDR_PORTC, Led_Pin); } void LedShowStatus(uint32_t flashPeriod) { // Запускаем бесконечный цикл обработки while (1) { // Считываем состояние вода PD0 // Если на выводе логический "0", то выставляем вывод в логическую "1" if (PORT_ReadInputDataBit (MDR_PORTC, Led_Pin) == 0) { PORT_SetBits(MDR_PORTC, Led_Pin); } // Задержка Delay(flashPeriod); // Считываем состояние вода PD0 // Если на выводе = "1", то выставляем "0" if (PORT_ReadInputDataBit (MDR_PORTC, Led_Pin) == 1) { PORT_ResetBits(MDR_PORTC, Led_Pin); }; // Задержка Delay(flashPeriod); } } void LedInit(void) { // Заводим структуру конфигурации вывода(-ов) порта GPIO PORT_InitTypeDef GPIOInitStruct; // Включаем тактирование порта C RST_CLK_PCLKcmd (RST_CLK_PCLK_PORTC, ENABLE); // Инициализируем структуру конфигурации вывода(-ов) порта значениями по умолчанию PORT_StructInit(&GPIOInitStruct); // Изменяем значения по умолчанию на необходимые нам настройки GPIOInitStruct.PORT_Pin = Led_Pin; GPIOInitStruct.PORT_OE = PORT_OE_OUT; GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW; GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL; // Применяем заполненную нами структуру для PORTC. PORT_Init(MDR_PORTC, &GPIOInitStruct); }
Думаю ничего сложного в программе нет, я написал побольше комментариев, чтобы вопросов не возникло. Данный код будет располагаться в ОЗУ, поскольку мы загрузим его через UART. По этой причине нет необходимости решать вопрос с расположением файла "MDR32F9Qx_eeprom.c" в ОЗУ, как мы это делали в Расположение функций в ОЗУ, программирование EEPROM.
Теперь нам необходимо получить bin файл для программы, которую мы будем прошивать в Flash. Как это сделать, написано тут - Загрузка программы в ОЗУ и запуск через UART, "Получение bin файла".
Открываем наш проект "HelloWorld". В настройках проекта идем в Options - User, находим пункт AfterBuild/Rebuild и ставим галочку в опции Run #1. Дописываем:
$K\ARM\ARMCC\bin\fromelf.exe --bin --output=@L.bin !L
Пересобираем проект ("F7") и находим в папке проекта файл "HelloWorld.bin".
Давайте так же сотрем Flash память и убедимся, что до запуска нашего "прошивателя" в памяти пусто. В меню выбираем Flash - Erase. Для того чтобы данная операция отработала, к демо-плате должен быть подключен программатор в Jtag_B, переключатели Mode должны быть в режиме "000" и должно быть подано питание. Без установленного соединения, данный пункт меню не активен.
Давайте теперь нажмем Reset или передернем питание, чтобы убедиться, что светодиод не мигает. После работы нашего "прошивателя", Reset и подача питания должны будут приводить к миганию светодиодом.
Выставляем режим загрузки через UART, Mode = "110". Операции работы с UART загрузчиком мы рассмотрели в статье - Тестируем Bootloader в режиме UART. Далее загружаем обе программы "HelloWorld" и "прошиватель" в ОЗУ.
Как было указано в коде, "прошиватель" будет копировать память с адреса 0x2000_2000. Сюда и загрузим файл "HelloWorld.bin". Сам же "прошиватель" запишем в адреса с 0x2000_0000, потому что так было настроено в опциях проекта. Запуск "прошивателя" так же необходимо произвести с адреса 0x2000_0000. В свойствах bin файлов узнаем так же их размеры. В общем, делаем все тоже самое, что и в "Загрузка программы в ОЗУ и запуск через UART", с той лишь разницей, что дополнительно загружается программа "HelloWorld".
В итоге необходимая нам информация для загрузки:
Подключаем UART адаптер, подаем питание на плату, открываем программу "Terminal v1.9b". Там у нас уже были написаны и сохранены макросы для работы с UART. Допишем к ним следующие:
Код M10: загрузка HelloWorld.bin L$00$20$00$20$CC$05$00$00 = L 0x00200020 0xCC050000 - младшими байтами вперед Код M11: загрузка Flash_UartWriter.bin L$00$00$00$20$DC$09$00$00 = L 0x00000020 0xDC090000 Код M12: запуск Flash_UartWriter R$00$00$00$20 = R 0x00000020
Важно не забыть снять галочку циклической посылки 0-ля после получения ответа от МК, иначе этот ноль продолжает посылаться и весь последующий обмен через UART будет нарушен!
Протокол обмена в окне терминала выглядит так:
>R - т.е. мы получили приглашение и выполнили команду Run.
в Hex окне видим пришедшие данные:
0D 0A 3E = '>' 52 = 'R'
Нажимаем Reset на плате для того, чтобы вернуться в UART загрузчик и начинаем все с начала:
В этот момент видим, что на плате зажегся светодиод. Т.е. запустился наш "прошиватель", и идет работа с Flash памятью. После короткого периода времени этот светодиод начинает медленно мигать, с периодом порядка 3 секунд. Это означает, что прошивка прошла успешно.
Если бы возникли проблемы, то светодиод мигал бы существенно быстрее. Возможно, индикация миганием не самый лучший вариант, раньше я просто включал и гасил светодиод по окончании процесса записи. Но при выключенном диоде было не понятно, выполняется какая-то программа, или внутри что-то зависло. По этой причине я остановился на варианте с миганием, он показывает, что программа внутри вертится. Для того чтобы различать, работает программа "Прошиватель" или "HelloWorld" мигание в них реализовано разными диодами.
Давайте проверим, как прошилась наша программа. Испробуем два варианта:
В обоих случаях убеждаемся, что программа прошита успешно. И после Reset и после сброса питания программа исполняется именно из Flash памяти, куда мы ее и записали.
Таким вот образом можно запросто прошить микроконтроллер через UART. В данном примере программа у нас была заведомо небольшая, поэтому мы прошивали только одну страницу Flash. Но при небольшой доработке программы "Прошиватель", можно организовать загрузку программ размера большего чем одна страница. Так же при прошивке программ больших, чем размер ОЗУ, можно побить исходный bin файл и прошить его частями.