47905

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

Дата последнего изменения: 03.07.2025 12:44:26

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

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

Создадим новый проект и назовем его "Flash_UartWriter". В библиотеках необходимо выбирать пункты - Startup, EEPROM, PORT, RST_CLK. Затем добавить в проект новый файл "main.c" (подробнее о создании нового проекта рассказано в статье Создаем новый проект)

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

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

  1. Мигание светодиодом - Hello World - светодиод. Отсюда взята настройка порта и цикл мигания светодиодом, для того чтобы отображать статус того, что происходит в программе.
  2. И пример работы с 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".

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

  1. HelloWorld.bin
    • Адрес = 0x2000 2000
    • Размер = 1484 байт = 0x05CC
  2. 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" еще не прошит

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

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

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

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

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

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

Прошиваем Flash

Для того, чтобы прошить 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", мигание в них реализовано разными диодами.

Рисунок 2 - Работа в программе Terminal, последовательность действий


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

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

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.

Сохранить статью в PDF

Программное обеспечение

Теги

Была ли статья полезной?