[i] Прошивка программы во Flash и запуск через UART
В статье Загрузка программы в ОЗУ и запуск через UART было рассказано о том, как загрузить программу в ОЗУ и запустить ее. Теперь рассмотрим создание программы для ОЗУ, которая умеет записывать массив данных из ОЗУ во Flash-память. Эту программу можно назвать "прошиватель". В массиве данных будет находиться программа мигания диодом. В итоге, после прошивки при запуске из Flash на плате будет мигать светодиод.
Программа "Прошиватель"
Создадим новый проект и назовем его "Flash_UartWriter". В библиотеках необходимо выбирать пункты - Startup, EEPROM, PORT, RST_CLK. Затем добавить в проект новый файл "main.c" (подробнее о создании нового проекта рассказано в статье Создаем новый проект)
Теперь установим настройки проекта для запуска в ОЗУ. Как это можно сделать, описано в Запуск программы из ОЗУ в среде Keil. В данном случае файл "setup.ini" можно не создавать и не подключать, так как не планируется запускать проект в отладчике.
Далее приведен листинг программы-"прошивателя", состоящий из одного файла "main.c". Код собран из двух примеров:
-
Мигание светодиодом - Hello World - светодиод. Отсюда взята настройка порта и цикл мигания светодиодом, для того чтобы отображать статус того, что происходит в программе.
-
И пример работы с EEPROM под названием "Sector_Operations" из библиотеки SPL (Расположение функций в ОЗУ, программирование EEPROM).
Пример "Sector_Operations" при стандартной установке Keil можно найти по пути:
*Стандартный путь до пака*\1.xx\Examples\MDR32F9Q2I\EEPROM\Sector_Operations
Файл "main.c" представлен в фрагменте кода 1:
#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>
#include <MDR32FxQI_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. По этой причине нет необходимости решать вопрос с расположением файла "MDR32FxQI_eeprom.c" в ОЗУ, как это было сделано в Расположение функций в ОЗУ, программирование EEPROM
Bin файл для "HelloWorld"
Теперь необходимо получить 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" и необходимо подать питание. Без установленного соединения данный пункт меню не активен.
Рисунок 1 - Keil, меню "Flash"
Теперь можно нажать Reset или выключить-включить питание, чтобы убедиться, что светодиод не мигает. После работы "прошивателя" Reset и подача питания должны будут приводить к миганию светодиодом.
программирование микроконтроллера по UART
Для программирования микроконтроллера по UART необходимо выставить режим загрузки через UART, Mode = "110". Операции работы с UART-загрузчиком были рассмотрены в статье Тестируем Bootloader в режиме UART. Далее загружаем обе программы - "HelloWorld" и "прошиватель" в ОЗУ.
Как было указано в коде, "прошиватель" будет копировать память с адреса 0x2000_2000. Сюда и необходимо загрузить файл "HelloWorld.bin". Сам же "прошиватель" запишем в адреса с 0x2000_0000, потому что так было настроено в опциях проекта. Запуск "прошивателя" также необходимо произвести с адреса 0x2000_0000. В свойствах bin-файлов узнаем их размеры. Подробно действия описаны в статье Загрузка программы в ОЗУ и запуск через UART. Дополнительно загружается программа "HelloWorld".
В итоге информация, необходимая для загрузки:
-
HelloWorld.bin
-
Адрес = 0x2000 2000
-
Размер = 1484 байт = 0x05CC
-
-
Flash_UartWriter.bin
-
Адрес = 0x2000 0000
-
Размер = 2524 байт = 0x09DС
-
Подключаем UART-адаптер, подаем питание на плату, открываем программу "Terminal v1.9b". Макросы, используемые в этой программе, можно найти в статье Тестируем Bootloader в режиме 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 L 0x00000020
Проверяем, что "HelloWorld" еще не прошит
-
В программе Terminal нужно нажать Connect. Настройки обмена должны быть выставлены согласно спецификации или статье Тестируем Bootloader в режиме UART.
-
Для синхронизации скорости необходимо отправлять "0" циклически. Запускаем макрос М1 - период посылки устанавливаем минимальный, ставим галочку. Дожидаемся приглашения '>' и останавливаем циклическую посылку 0 - снимаем галочку!.
-
Запустить Макрос М8 - переход на программу из Flash, чтобы убедиться, что там еще не зашита программа мигания светодиодами. Наблюдаем, что светодиод не мигает. Если светодиод мигает, значит память не была очищена, возвращаемся к пункту "Bin файл для "HelloWorld" текущей статьи.
Важно не забыть снять галочку циклической посылки нуля после получения ответа от МК, иначе этот ноль продолжает посылаться, и весь последующий обмен через UART будет нарушен!
Протокол обмена в окне терминала выглядит так:
>R - т.е. получили приглашение и выполнили команду Run.
в Hex окне видим пришедшие данные:
0D 0A 3E = '>' 52 = 'R'
Прошиваем Flash
Для того, чтобы прошить Flash-память, необходимо нажать Reset на плате для того, чтобы вернуться в UART-загрузчик, и начать все с начала:
-
Макрос "М1" - Синхронизация до получения приглашения '>'.
-
Макрос "М7" - Увеличиваем скорость обмена до 19200 бод, чтобы загрузка прошла быстрее. В данном случае приходит неправильный ответ от МК, игнорируем.
-
Выставляем в программе "Terminal" скорость обмена 19200 бод, и запрашиваем приглашение - макрос "М2". Получаем '>', скорость поменялась, значит все в порядке.
-
Загружаем "HelloWorld.bin".
-
Выполняем макрос "М10" - МК входит в режим загрузки массива данных и ожидает 1484 байт.
-
Нажимаем SendFile и выбираем "HelloWorld.bin". Файл загружается и приходит ответ - 'K'. Загрузка прошла успешно.
-
-
Загружаем аналогично "Flash_UartWriter.bin".
-
Выполняем макрос "М11".
-
В SendFile выбираем "Flash_UartWriter.bin". Ждем окончания загрузки, получаем ответ - 'K'.
-
-
Нажимаем макрос "М12" - запускаем "прошиватель". В ответ приходит символ 'R' как подтверждение команды запуска.
В этот момент видим, что на плате зажегся светодиод. То есть запустился "прошиватель", и идет работа с Flash-памятью. После короткого периода времени этот светодиод начинает медленно мигать, с периодом порядка 3 секунд. Это означает, что прошивка прошла успешно.
Если бы возникли проблемы, то светодиод мигал бы существенно быстрее. Для того чтобы различать, работает программа- "прошиватель" или "HelloWorld", мигание в них реализовано разными диодами.
Рисунок 2 - Работа в программе Terminal, последовательность действий
Проверяем зашитую программу
Давайте проверим, как прошилась наша программа. Попробуем два варианта:
1 - Проверка через UART-загрузчик
-
Нажимаем Reset на плате.
-
Меняем в Terminal скорость на начальную 9600 бод.
-
Запускаем макрос "М1" - синхронизация нулями. Получаем приглашение '>'.
-
Выполняем макрос "М8" - запускаем программу из Flash памяти.
-
Наблюдаем мигание светодиода.
2 - Проверка сбросом питания
-
Выключаем питание
-
Выставляем переключателями режим загрузки из Flash, Mode = "000".
-
Включаем питание.
-
Наблюдаем мигание светодиода.
В обоих случаях убеждаемся, что программа прошита успешно. И после Reset и после сброса питания программа исполняется именно из Flash-памяти, куда мы ее и записали.
Таким образом можно запросто прошить микроконтроллер через UART. В данном примере программа была заведомо небольшая, поэтому прошивали только одну страницу Flash-паммяти. Но при небольшой доработке программы-"прошивателя", можно организовать загрузку программ размера, большего, чем одна страница. Также при прошивке программ больших, чем размер ОЗУ, можно разбить исходный bin-файл и прошить его частями.
Сохранить статью в PDF
Программное обеспечение
