Инструменты пользователя

Инструменты сайта


prog:uart:flash_writer

Прошивка программы в Flash и запуск через UART

В статье Загрузка программы в ОЗУ и запуск через UART мы научились загружать программу в ОЗУ и запускать ее. Давайте напишем программу для ОЗУ, которая умеет записывать массив данных из ОЗУ во FLASH память. Назовем эту программу "прошиватель". В массиве данных будет лежать наша программа мигания диодом. В итоге после прошивки при запуске из Flash на плате будет мигать светодиод.

Программа "Прошиватель"

Создаем новый проект, назовем его "Flash_UartWriter". В библиотеках выбираем пункты - Startup_MDR1986BE9x, EEMPROM, PORT, RST_CLK. Добавляем в проект новый файл "main.c". Как это сделать описано здесь - Создаем новый проект.

Теперь делаем настройки проекта для запуска в ОЗУ. Подробно это описано тут - Запуск программы в ОЗУ из Keil. В данном случае, файл "setup.ini" можно не создавать и не подключать, мы не планируем запускать проект в отладчике.

Далее приведен листинг программы "прошивателя", состоящий из одного файла "main.c". Код собран из двух примеров:

  1. Мигание светодиодом - Hello World - светодиод. Отсюда мы возьмем настройку порта и цикл мигания светодиодом, для того чтобы как-то отображать статус того, что происходит в программе.
  2. И пример работы с EEPROM под названием "Sector_Operations" из библиотеки SPL. Этого примера мы уже слегка касались в статье Расположение функций в ОЗУ, программирование EEPROM.

Пример "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 файл для "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" и должно быть подано питание. Без установленного соединения, данный пункт меню не активен.

Давайте теперь нажмем Reset или передернем питание, чтобы убедиться, что светодиод не мигает. После работы нашего "прошивателя", Reset и подача питания должны будут приводить к миганию светодиодом.

Прошиваем через UART

Выставляем режим загрузки через UART, Mode = "110". Операции работы с UART загрузчиком мы рассмотрели в статье - Тестируем Bootloader в режиме UART. Далее загружаем обе программы "HelloWorld" и "прошиватель" в ОЗУ.

Как было указано в коде, "прошиватель" будет копировать память с адреса 0x2000_2000. Сюда и загрузим файл "HelloWorld.bin". Сам же "прошиватель" запишем в адреса с 0x2000_0000, потому что так было настроено в опциях проекта. Запуск "прошивателя" так же необходимо произвести с адреса 0x2000_0000. В свойствах bin файлов узнаем так же их размеры. В общем, делаем все тоже самое, что и в "Загрузка программы в ОЗУ и запуск через UART", с той лишь разницей, что дополнительно загружается программа "HelloWorld".

В итоге необходимая нам информация для загрузки:

  1. HelloWorld.bin
    • Адрес = 0x2000 2000
    • Размер = 1484 байт = 0x05CC
  2. Flash_UartWriter.bin
    • Адрес = 0x2000 0000
    • Размер = 2524 байт = 0x09DС

Подключаем 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
  

Проверяем, что "HelloWorld" еще не прошит

  1. В программе Terminal нажимаем Connect. Настройки обмена должны быть выставлены согласно спецификации или статье Тестируем Bootloader в режиме UART.
  2. Синхронизируем скорости - посылаем циклически 0. Запускаем макрос М1 - период посылки ставим минимальный, устанавливаем галочку. Дожидаемся приглашения '>' и останавливаем циклическую посылку 0 - снимаем галочку!.
  3. Запускаем Макрос М8 - прыгаем на программу из Flash, чтобы убедиться, что там еще не зашита программа мигания светодиодами. Наблюдаем, что светодиод не мигает. Если светодиод мигает, значит память не была очищена, возвращаемся к пункту Bin файл для "HelloWorld".

Важно не забыть снять галочку циклической посылки 0-ля после получения ответа от МК, иначе этот ноль продолжает посылаться и весь последующий обмен через UART будет нарушен!

Протокол обмена в окне терминала выглядит так:

>R   - т.е. мы получили приглашение и выполнили команду Run.

в Hex окне видим пришедшие данные:

0D
0A
3E  = '>'
52  = 'R'

Прошиваем Flash

Нажимаем Reset на плате для того, чтобы вернуться в UART загрузчик и начинаем все с начала:

  1. Макрос "М1" - Синхронизируемся, до получения приглашения '>'.
  2. Макрос "М7" - Увеличиваем скорость обмена до 19200 бод, чтобы загрузка прошла быстрее. В данном случае приходит неправильный ответ от МК, игнорируем.
  3. Выставляем в программе "Terminal" скорость обмена 19200 бод, и запрашиваем приглашение - макрос "М2". Получаем '>', скорость поменялась, значит все в порядке.
  4. Загружаем "HelloWorld.bin".
    • Выполняем макрос "М10" - МК входит в режим загрузки массива данных и ожидает 1484 байт.
    • Нажимаем SendFile и выбираем "HelloWorld.bin". Файл загружается и нам приходит ответ - 'K'. Загрузка прошла успешно.
  5. Загружаем аналогично "Flash_UartWriter.bin".
    • Выполняем макрос "М11".
    • В SendFile выбираем "Flash_UartWriter.bin". Ждем окончания загрузки, получаем ответ - 'K'.
  6. Нажимаем макрос "М12" - запускаем "прошиватель". В ответ приходит символ 'R', как подтверждение команды запуска.

В этот момент видим, что на плате зажегся светодиод. Т.е. запустился наш "прошиватель", и идет работа с Flash памятью. После короткого периода времени этот светодиод начинает медленно мигать, с периодом порядка 3 секунд. Это означает, что прошивка прошла успешно.

Если бы возникли проблемы, то светодиод мигал бы существенно быстрее. Возможно, индикация миганием не самый лучший вариант, раньше я просто включал и гасил светодиод по окончании процесса записи. Но при выключенном диоде было не понятно, выполняется какая-то программа, или внутри что-то зависло. По этой причине я остановился на варианте с миганием, он показывает, что программа внутри вертится. Для того чтобы различать, работает программа "Прошиватель" или "HelloWorld" мигание в них реализовано разными диодами.

Проверяем зашитую программу

Давайте проверим, как прошилась наша программа. Испробуем два варианта:

1 - Проверка через UART загрузчик

  • Нажимаем Reset на плате.
  • Меняем в Terminal скорость на начальную 9600 бод.
  • Запускаем макрос "М1" - синхронизация нулями. Получаем приглашение '>'.
  • Выполняем макрос "М8" - запускаем программу из Flash памяти.
  • Наблюдаем мигание светодиода.

2 - Проверка сбросом питания

  • Выключаем питание
  • Выставляем переключателями режим загрузки из Flash, Mode = "000".
  • Включаем питание.
  • Наблюдаем мигание светодиода.

В обоих случаях убеждаемся, что программа прошита успешно. И после Reset и после сброса питания программа исполняется именно из Flash памяти, куда мы ее и записали.

Таким вот образом можно запросто прошить микроконтроллер через UART. В данном примере программа у нас была заведомо небольшая, поэтому мы прошивали только одну страницу Flash. Но при небольшой доработке программы "Прошиватель", можно организовать загрузку программ размера большего чем одна страница. Так же при прошивке программ больших, чем размер ОЗУ, можно побить исходный bin файл и прошить его частями.

На самом деле загрузчик Bootloader, который работает в МК и обеспечивает связь по UART является программой, написанной на Си. Поэтому младшие адреса заняты под глобальные переменные и кучу, а старшие адреса ОЗУ заняты под стек. Загружая программу с адреса 0х2000_0000, мы рискуем затереть данные программы загрузчика. Поэтому при загрузке в память следует отступить от края ОЗУ. На форуме приводится рекомендация при загрузке программы по UART использовать диапазон адресов 0x2000_0100 - 0x2000_7E00.
prog/uart/flash_writer.txt · Последнее изменение: 2022/04/03 23:09 (внешнее изменение)