Основы разработки загрузчика для Arduino. Часть 1

Загрузчик для ардуино

Загрузчик — это, по сути, начальный фрагмент кода, который запускается всякий раз, когда какой-либо микроконтроллер включается или перезагружается.

Это похоже на концепцию BIOS, которая запускается на нашем ПК, когда мы его включаем.

В случае BIOS он ожидает ввода данных пользователем для изменения параметров/настроек загрузки. Если он не получит таких входных данных, он запустится с предустановленной ОС.

То же самое происходит и с загрузчиком Arduino. Всякий раз, когда Arduino включается или сбрасывается, он ищет внешние входы (для загрузки новой программы). Если он не получает таких входных данных, он начинает выполнение программы, которая была загружена последней.

Загрузчик ардуино в памяти микроконтроллера
Рисунок 1. Разделы программной памяти микроконтроллера AVR

Arduino использует микроконтроллеры AVR для своих платформ, которые имеют разделы программной памяти, как показано на рисунке 1.

Раздел Boot Loader находится в нижней части флеш-памяти.

Программа загрузчика записывается  в разделе загрузчика, а прикладная программа в разделе приложения.

Как же запускается загрузчик?

Как мы знаем, всякий раз, когда микроконтроллер сбрасывается или включается, обычно он начинает выполнение программы с вектора сброса. То есть с адреса памяти программы 0x0000.

Мы можем изменить этот адрес вектора сброса (0x0000) на начальный адрес раздела загрузчика в случае, если мы используем загрузчик на микроконтроллере. Это означает, что всякий раз, когда микроконтроллер перезагружается/включается, он запускает выполнение программы из раздела загрузчика.

Загрузчики Arduino делают то же самое и выполняют программу загрузчика, когда микроконтроллер (используемый Arduino) сбрасывается/включается. То есть микроконтроллеры начинают выполнение программы со стартового адреса секции загрузчика.

Давайте обратимся к таблице данных микроконтроллеров AVR (которые используются для Arduino). Мы видим, что Fuse-бит сброса загрузки можно запрограммировать так, чтобы вектор сброса указывал на начальный адрес загрузочной флэш-памяти.

Фьюз сброса загрузки
Рисунок 2. Фьюз-бит сброса загрузки Atmega

Следовательно, мы можем установить вектор сброса на начало раздела загрузчика при включении/перезагрузке.

Зачем нам загрузчик?

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

Загрузчики Arduino используют простую последовательную связь UART для загрузки шестнадцатеричного файла программы и записи его в разделе приложения.

Загрузчик для ардуино
Рисунок 2. Как происходит загрузка программы в память ардуино

Как устроен загрузчик?

Теперь давайте кратко рассмотрим, как написан загрузчик Arduino и как он взаимодействует с Arduino IDE при загрузке программ.

Мы можем найти программу загрузчика arduino по следующему пути:

Arduinohardwarearduinobootloadersoptiboot

Как показано на рисунке ниже.

Где находится файл с загрузчиком ардуино
Рисунок 3. Где находится файл с загрузчиком ардуино

Заголовочный файл загрузчика boot.h включен в набор инструментов avr. Это модифицированная/оптимизированная версия загрузочного файла avr toolchain <avr/boot.h>. Вы можете найти этот файл по следующему пути:

Arduinohardwaretoolsavravrincludeavrboot.h

Заголовочный файл загрузчика AVR для доступа к регистру SPM использует инструкцию sts. Для этой инструкции характерно два машинных цикла.

А вот файл заголовка загрузчика, используемый в загрузчике arduino, использует инструкцию out. Ей требуется только один машинный цикл для доступа к регистру SPM.

Эта важная оптимизация уже упоминается в заголовочном файле avr toolchain для небольших устройств.

Заголовочный файл загрузчика содержит функцию, связанную с записью/чтением флэш-памяти (постранично). Кроме того, он содержит функцию записи/чтения фьюзов, блокировок и битов подписи.

Заголовочный файл stk500.h содержит команды программатора STK500. Они используются для надежного установления связи между программой arduino и avrdude при загрузке шестнадцатеричного файла прошивки.

Заголовочный файл определения пинов pin_defs.h содержит определение порта для светодиода (встроенного в Arduino). Этот светодиод используется для мигания во время прошивки Arduino.

Файл optiboot.c содержит основной программный код загрузчика. Он последовательно получает шестнадцатеричный код и записывает его в память программы. Файлы (boot.h, pin_defs.h, stk500.h) включены в файл optiboot.c.

Программа Optiboot начинается с регистра состояния MCUSR (MCU Status Register). Этот регистр предоставляет информацию об источнике сброса. Если источник сброса не является внешним (контакта сброса не подтянут физически к GND), то он напрямую запустит прикладную программу. Как показано на рисунке ниже, он вызовет функцию appStart(). А она уже перейдет к прямому адресу сброса 0x0000.

Код обработки сброса
Рисунок 4. Код обработки сброса

Внимание! Регистр MCUSR очищается после использования. Поэтому мы не можем использовать его снова для получения источника сброса в нашей программе. Это не проблема, поскольку мы можем изменить загрузчик в соответствии с нашими требованиями, если захотим.

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

Сторожевой таймер настроен на тайм-аут в 1-секунду. В случае ошибки при загрузке программы, произойдет сброс. А сторожевой таймер сбросит микроконтроллер после завершения загрузки.

Рисунок 5. Настройка Watchdog таймера на 1 секунду

Затем загрузчик инициализирует последовательную связь (в нашем случае UART) для связи с Arduino IDE, работающей на ПК/ноутбуке.

Инициализация UART
Рисунок 6. Инициализация UART

После описанной выше инициализации загрузчик переходит в бесконечный цикл, где ждет данные для загрузки в микроконтроллер пользовательской программы с использованием протокола, используемого STK500.

Рисунок 7. Бесконечный цикл

Память программ микроконтроллера записывается/обновляется постранично. Размер страницы зависит от контроллера. Например, Atmega328/328P имеет размер страницы 64 слова (т.е. 128 байт), тогда как Atmega88A/88PA имеет размер страницы 32 слова (т.е. 64 байта).

Процесс записи памяти программ выполняется постранично следующим образом:

  • В упомянутом выше бесконечном цикле шестнадцатеричные байты, поступающие последовательно от загрузчика Arduino, работающего на ПК/ноутбуке, сначала копируются во временную память данных (например, в ОЗУ).
  • После копирования шестнадцатеричных байтов размера страницы во временную память данных выполняется стирание первой страницы памяти программ.
  • После стирания страницы сначала она заполняется (просто заполняется, а не записывается) шестнадцатеричными байтами, хранящимися во временной памяти данных.
  • Затем, используя инструкцию записи страницы SPM, успешно выполняется запись/обновление страницы.
  • Описанный выше процесс чтения данных из последовательного порта и последующей записи их в память программ постранично выполняется до тех пор, пока полные шестнадцатеричные байты не будут записаны/обновлены в памяти программ.
  • После завершения операции записи шестнадцатеричного файла выполняется противоположный процесс, т. е. чтение из памяти программы и его последовательная отправка на ПК/ноутбук постраничным способом, чтобы проверить, был ли шестнадцатеричный файл записан/обновлен в памяти программы или нет.

Ниже приведен пример функции записи, которая уже указана в заголовочном файле загрузки.

#include &lt;inttypes.h&gt;
#include &lt;avr/interrupt.h&gt;
#include &lt;avr/pgmspace.h&gt;

void boot_program_page (uint32_t page, uint8_t *buf)
{
    uint16_t i;
    uint8_t sreg;
    // Disable interrupts.
    sreg = SREG;
    cli();
    eeprom_busy_wait ();
    boot_page_erase (page);  //erase page
    boot_spm_busy_wait ();      // Wait until the memory is erased.

    for (i=0; i < SPM_PAGESIZE; i+=2)
    {
        // Set up word from temp buffer.
        uint16_t w = *buf++;
        w += (*buf++) << 8;
        boot_page_fill (page + i, w);  //fill (page + i ) address with word
    }
    boot_page_write (page);     // Store/write buffer in flash page.
    boot_spm_busy_wait();       // Wait until the memory is written.
    // Reenable RWW-section again. We need this if we want to jump back
    // to the application after bootloading.
    boot_rww_enable ();
    // Re-enable interrupts (if they were ever enabled).
    SREG = sreg;
}

Все вышеизложенное, является основной общей идеей о том, как шестнадцатеричный файл записывается в память программы. Функции, используемые в вышеуказанной программе, т. е. boot_page_fill(), boot_page_write(), boot_spm_busy_wait() описаны в заголовочном файле boot.h, который написан со встроенными инструкциями по сборке.

Как программа, находящаяся в нижней части памяти, умудряется записывать в память программу? т.е. как программа в разделе загрузки пишет в раздел приложения?

Это возможно, поскольку микроконтроллеры AVR предоставляют механизм самопрограммирования (SPM) для загрузки и выгрузки кода самим микроконтроллером. Механизм самопрограммирования может использовать любой доступный интерфейс данных и связанный с ним протокол для чтения кода и записи (программирования) этого кода в память программы.

В следующей статье мы ответим на вопросы:

  • Как ардуино программируется с его IDE?
  • Как сначала загрузить загрузчик?
  • Как изменить загрузчик Arduino?
  • А также настроим проект в  Atmel Studio для  разработки своего загрузчика.
3 2 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии