47905

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

Дата последнего изменения: 17.10.2022 16:06:04

В статье Загрузка программы в ОЗУ и запуск через 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" текущей статьи.

Важно не забыть снять галочку циклической посылки 0-ля после получения ответа от МК, иначе этот ноль продолжает посылаться, и весь последующий обмен через 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

Теги

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