Микроконтроллер и Bootloader. Описание и принцип работы.

Приветствую всех на нашем сайте и сегодня мы после небольшого перерыва вернемся к теме микроконтроллеров. А если быть совсем точным, то мы начинаем обсуждать одну очень интересную и важную тему, а именно использование bootloader’а (загрузчика) при программировании контроллеров. Сегодня мы разберем теоретическую часть — зачем bootloader нужен, как он работает и что это вообще такое. Следующая статья будет посвящена целиком и полностью практике. Забегая вперед скажу, что мы напишем свой bootloader для любимых микроконтроллеров STM32 😉

Использование bootloader'а

Итак, простыми словами, bootloader — это специальная программа, которая располагается в памяти микроконтроллера и может самостоятельно перепрограммировать его. Давайте для лучшего понимания процесса посмотрим как вообще выполняется программа, прошитая в микроконтроллер, и где она располагается.

Как вы помните из статьи, посвященной Flash-памяти микроконтроллеров STM32, основная пользовательская программа начинается с первой страницы памяти, а точнее с адреса 0х08000000. То есть при подаче питания контроллер сразу же убегает по этому адресу )

При использовании загрузчика все выглядит несколько иначе. Основная программа записывается уже по другим адресам и располагается начиная, например, с адреса 0х0800A000. А область памяти (0х080000000х0800А000) целиком и полностью отдается bootloader’у. В итоге в flash-памяти контроллера у нас как бы находятся две полноценные программы. При включении устройства управление получает bootloader (поскольку он находится в области, начинающейся со «стартового» адреса 0х08000000), а при дальнейшей работе bootloader, выполнив все свои задачи передает управление нашей основной программе, которая располагается по адресу 0х0800А000 (этот адрес мы взяли для примера). Вот небольшая схемка для демонстрации работы загрузчика:

Использование Bootloader

Вроде бы понятно как устроено, но возникает вопрос — зачем все это надо?

Давайте разбираться…

Первостепенной задачей bootloader’а является программирование микроконтроллера. Он не просто выполняет какие-то действия, а затем передает управление основной программе (переходит на адрес, который соответствует началу основной программы), он, в первую очередь, самостоятельно записывает эту основную программу в flash-память по нужным адресам.

Давайте разберем небольшой пример для лучшего понимания. Пусть мы создали bootloader (bootloader — точно такой же обычный проект, как и любая другая программа для микроконтроллера), который реализует взаимодействие с внешней картой памяти, ищет на карте файл программы и, если находит, записывает программу в нужную область памяти.

Небольшое отступление от основной темы… Поясню, что я тут имею ввиду под «файлом программы».

Когда мы создаем проект (Keil, IAR — без разницы), то на выходе (после сборки проекта) мы получаем скомпилированный файл для прошивки в микроконтроллер. Чаще всего мы использовали .hex файл программы. Так вот именно этот файл нам и нужен в данном случае. Но именно hex-файл не совсем подходит для наших целей, поскольку помимо кода нашей программы он несет в себе дополнительную служебную информацию. Чтобы ее не обрабатывать и не вытаскивать из hex-файла нужный нам код, который bootloader должен записать во flash, мы в настройках компилятора во вкладке Output попросим его генерировать нам вместо hex-файла bin-файл. Бинарник, в отличие от hex, содержит в себе только последовательный код программы и ничего больше. То есть bootloader’у остается только читать байты из bin-файла и записывать их во flash-память. То есть в нашем примере задачей загрузчика является чтение байт из файла на карте памяти и запись их по адресам, начиная с 0х0800A000. Вот псевдокод для наглядности:

void main()
{
    // Инициализируем интерфейс SDIO для общения с картой памяти
    SDIO_Init();
 
    while(1)
    {
        // Ищем файл прошивки
        if (f_open(файл.bin) == FR_OK)
        {
            ProgramFlash();
            JumpToMainProgram();
        } 
    }
}

Конечно, это сильно упрощенная версия загрузчика 😉 Тут мы в вечном цикле пытаемся открыть файл с программой, как только это нам удается (пользователь записал на карту долгожданный файл) bootloader программирует flash-память и перескакивает на адрес записанной им же программы. После этого контроллер начинает выполнять пользовательскую программу. Еще раз повторюсь, это всего лишь псевдокод для примера, нормальный, работающий bootloader мы обязательно напишем в следующей статье )

Все это, конечно, очень интересно, но по-прежнему, непонятно, зачем нужны все эти сложности….

С этим на самом деле все просто — вот, например, первая ситуация — есть огромное количество устройств, на заводе работники прошили в каждый контроллер (например, при помощи ST-Link) на каждой плате bootloader, который при подключении к плате флэшки (в заранее предусмотренный разъем) ищет на ней файл прошивки и выполняет программирование. Основную программу, конечно же, тоже можно прошить вместе с bootloader’ом через ST-Link. Но тонкость тут в том, что проект bootloader’а остается всегда неизменным и перепрошивать его не надо, а вот версия основной программы может обновляться кучу раз в процессе тестирования устройств. И тут уже гораздо проще один раз подключить ST-Link и прошить загрузчик, а впоследствии просто скидывать новую версию основной программы на флэшку и втыкать ее в готовое устройство, где перепрошивкой займется bootloader, чем по сто раз бегать от одной платы к другой, втыкать ST-Link, при этом перенося с собой ноутбук с ST-Link Utility… Как видите, польза загрузчика очевидна 😉

Вторая ситуация еще лучше это демонстрирует. Устройство уже выпущено и куча экземпляров распродана пользователям. Как бы хороша не была финальная версия программы никто не застрахован от неожиданного появления ошибок в процессе эксплуатации. И тут уже человек, купивший устройство, точно не сможет разобрать его и подпаяться, чтобы выполнить перепрошивку через программатор. То есть программу обновить просто нереально. Совсем другое дело, если изготовители предусмотрели встроенный bootloader. Пользователь может без проблем скинуть на карту памяти или на флэшку скачанный бинарник и подключить карту/флэшку к устройству. Изготовителю остается только выкладывать новые версии прошивок на своем сайте =) В этом на самом деле кроется куча возможностей — изготовитель устройств может спокойно выпустить девайс на рынок с базовой прошивкой, которая реализует все нужные функции, но не содержит всяких приятных графических вещей или возможности подключения к ПК (зависит от того, что это за устройство). Производителю не нужно ломать голову над тем, чтобы выпустить с первого раза идеальную прошивку, в которой будет реализовано абсолютно все, что только можно, ведь он знает что встроенный bootloader без проблем поможет пользователю в будущем обновить программу.

В общем, о пользе и применениях загрузчика можно говорить очень и очень долго…;) Помимо упомянутых возможностей обновления прошивки при помощи флэшки или карты памяти, bootloader может использовать какой-нибудь из интерфейсов передачи данных, например SPI, I2C или USART.

В общем-то, вроде бы мы разобрались с теоретической частью, посвященной использованию bootloader’а, но давайте еще один момент обсудим в этой статье — а именно аппаратный загрузчик микроконтроллеров STM32.

У STM32 уже есть bootloader, который инженеры ST поместили в специально отведенную область памяти микроконтроллера (System Memory). Удалить его оттуда нельзя, да и незачем =) Для того, чтобы ввести контроллер в режим загрузчика необходимо подать определенные сигналы на ножки BOOT0 и BOOT1. После этого микроконтроллер готов принимать по USART новую прошивку. Для этого необходимо подключить плату к ПК, скачать специальную софтинку от ST — Flash Loader Demonstrator и загрузить в нее свой файл прошивки.

Я, честно говоря, аппаратным загрузчиком предпочитаю не пользоваться по нескольким причинам. Во-первых, прошивка никак не шифруется, что для коммерческих устройств зачастую недопустимо. Если я использую свой собственный bootloader, то я могу сделать с bin-файлом все, что угодно, например, поменять определенные байты местами. Если такой файл попадет в руки конкурентов, то это ничего не даст, поскольку только мой bootloader знает как расшифровать бинарник. При использовании аппаратного botloader’а такое невозможно — Flash Loader Demonstrator принимает bin-файл в исходном виде, то есть все байты в нем не зашифрованы. Во-вторых, пользователю будет необходимо подключать свою плату к ПК и скачивать дополнительный софт для перепрошивки устройства, и это не очень хорошо. Чем проще устройство в использовании, тем лучше 😉 Но есть и плюсы аппаратного bootloader’а — он не занимает flash-память. При использовании своего загрузчика нужно иметь ввиду, что и загрузчик и основная программа должны поместиться в flash-памяти. С аппаратным bootloader’ом такой проблемы нет — он расположен в специальной области памяти, использовать которую программист не может.

Давайте на этом на сегодня и закончим, не пропустите статью, посвященную практической реализации загрузчика!

 

Понравилась статья? Поделись с друзьями!

Микроконтроллер и Bootloader. Описание и принцип работы.: 19 комментариев
  1. За статью спасибо! А увидеть рабочий bootloader с SD карточки ( а еще лучше с USB флешки ))) ) ну это будет вообще великолепно.

  2. Спасибо! Познавательно!
    Необходимо прошить STM32 с помощью Flash Loader Demonstrator
    1. Заглушка загрузчика с адреса 0x08000000
    2. Прошивка с адреса 0x08002000
    Как правильно настроить Flash Loader Demonstrator?
    Прошивка будет происходить в два этапа?
    По возможности разъясните подробнее. Что, где выставлять.
    Спасибо!

    • Добрый день!
      Я Flash Loader не использую, только свои загрузчики. По идее на сайте ST должен быть какой-нибудь мануал.

  3. Здравствуйте!
    Подскажите, правильно ли я понимаю суть загрузчика?
    1. Загрузчик мы можем написать как отдельную программу, и прошивать его первоначально с помощью программатора, или встроенного аппаратного загрузчика, а потом уже с помощью нашего загрузчика зашивать основную программу.
    2. Сделать программу с загрузчиком и основной программой единым файлом и так же записать её с помощью программатора или аппаратного загрузчика.
    3. Последующие обновления программы надо делать отдельным проектом в котором нет кода загрузчика и его уже скармливать нашему загрузчику
    Правильно ли я понимаю?

  4. А подскажите как в программе которая будет содержать загрузчик и основной код программы быть с переходом на адрес основной программы?
    файл линкера мы не меняем, что нужно задавать функциям абсолютные адреса? или как что то туплю

    • В проекте бутлодера файл линкера не меняем, а в проекте с основной программой — меняем и указываем там новый адрес.

  5. Спасибо, вам за терпение и ваш труд!!!
    Я наверно не правильно сформулировал вопрос,
    когда проект содержит и загрузчик и основную программу мы в при включении питания попадаем на адрес 0x08000000 где у нас расположен загрузчик, проверяем условие на обновление и если условие не выполнено, то переходим на основную программу и в функции перехода указываем адрес 0x0800A000. Когда проект загрузчика не содержит код основной программы вопросов не возникает( зашили отдельно загрузчик в плату, а потом уже с помощью загрузчика зашиваем основную программу), а как быть когда и загрузчик и основная программа это один проект? какой адрес перехода должен быть?

    • Тогда вообще загрузчик теряет свой смысл ) Если это тот же проект, что и основная программа. Ему же нужно бужет потом перешивать только основную программу, а как он это сделает, ведь при компиляции мы получим бинарник и с загрузчиком и с основной программой. Смысл именно в том, что это по сути независимые программы.

      Загрузчик можно поместить в проект основной программы так:
      1. Компилируем проект загрузчика и получаем бинарный файл.
      2. Переводим этот файл в обычный массив.
      3. В проекте основной программы этот массив располагаем по адресам, начиная с 0x08000000, как обычный массив.

      Теперь мы собираем проект основной программы, прошиваем и получаем загрузчик вместе с основной программой в контроллере. Небольшой пример — зачем это может быть нужно.

      Пусть у нас бутлодер работает через карту-памяти, то есть ищет на ней файл прошивки и загружает по адресу основной программы. При этом карта памяти из устройства не вынимается. А для того, чтобы пользователь мог закинуть новую версию прошивки он подключает устройство по USB и получает доступ к карте памяти. Но для того, чтобы сам загрузчик занимал меньше места в памяти контроллера можно убрать из него драйвер USB, получается, что доступ к карте памяти будет у пользователя только из основной программы.

      И тут возникает проблема — если первоначально прошить только загрузчик, то далее невозможно будет зашить основную программу, ведь карта не вынимается, а поддержки USB в проекте загрузчика нет. И решением тут может быть использование массива как выше я описал. Тогда первоначально прошив контроллер мы помещаем туда не только загрузчик, но и основную программу, а, соответственно, мы можем подключить устройство по USB и при необходимости записать уже другой файл прошивки, который загрузчик потом прошьет на место основной программы.

      Вот как-то так 🙂

  6. Я хотел сделать следующее:
    1. При старте проверяем ключ из памяти МК есть ли обновление и была ли записана основная программа? если есть обновление то переписываем ключ на обновление и начинаем обновлять основную программу, после обновления ставим ключ что есть основная программа.
    Если нет ключа обновления, но есть ключ наличия основной программы просто перейдем на основную программу.
    Если нет ключа обновления и нет ключа наличия основной программы то в загрузчике инициализируем USB и будем ждать подключения к ПК для последующего обновления.
    Зачем все это надо? так мы сможем обновлять прибор несколькими способами, пришло нам обновление через интернет, по проводу, через радиоканал мы его запишем в отдельно выделенный блок памяти или внешнею память , обновим ключ наличия обновления и перезапустимся. Если нет основной программы можем обновиться через USB если предположим что обновление было с ошибкой и работа основной программы нарушена (допустим накосячил в обновлении).
    Зачем загрузчик и основная программа в едином проекте? Что-бы при производстве не прошивать контроллер два раза.
    Хотя я может чересчур усложняю 🙂

    • Не-не, не чересчур ) Как раз мы об одних вещах говорим )

      Вот именно, чтобы на производстве прошивать один раз можно делать через массив как я описал. То есть с точки зрения программиста — два отдельных проекта, а с точки зрения производства — один файл прошивки. Этот файл получается из проекта с основной программой, в которой мы изменяем файл линкера, а по адресам с 0x08000000 размещаем массив с кодом загрузчика, который мы получили из проекта загрузчика.

  7. еще хотел таким образом сделать возможность своего образа отката на предыдущею версию прибора самим прибором(хранить два обновления и при возникновении ошибок возвращаться на предыдущий вариант программы)

  8. Опечатка в статье:
    «В общем, о пользе и применениях загрузчика можно говорить очень и очень догло…;)»

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *