Переход к загрузчику из кода приложения в atmega328p

Я пытаюсь запрограммировать Arduino Uno (atmega328p), используя только последовательные линии Tx/Rx (без DTR, поэтому без автоматического сброса). Это позволяет мне загружать код через WiFi/TCP с помощью ESP-01.

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

Я попробовал несколько подходов к переходу к загрузчику из кода приложения, но пока безуспешно.

  1. Использование сторожевого таймера
void reset_to_bootloader() {
    // см. 11.2.2 MCUCR — регистр управления MCU
    // Разрешить смену векторов прерываний
    MCUCR = (1<<IVCE);
    // Перемещаем прерывания в секцию Boot Flash
    MCUCR = (1<<IVSEL);

    // включить сторожевой таймер
    wdt_enable(WDTO_30MS);

    // блок, ожидающий истечения времени ожидания сторожевого таймера и перехода к загрузчику
    while(1);
}

К сожалению, в техническом описании [1] упоминается, что бит IVSEL недоступен в atmega328p, поэтому, несмотря на то, что он компилируется, он просто сбрасывает MCU, и загрузчик не запускается.

  1. Прямой переход к адресу загрузчика

В техническом описании есть таблица (Таблица 26-7. Конфигурация размера загрузки, ATmega328P), в которой указан начальный адрес загрузчика для 4 поддерживаемых размеров разделов загрузчика, которые настраиваются с помощью предохранителя BOOTSZ.

Я прочитал фьюзы и подтвердил, что длина раздела загрузчика составляет 512 байт (256 слов). Следовательно, согласно техническому описанию, секция флэш-памяти загрузчика имеет вид 0x3F00 - 0x3FFF. Итак, начало загрузчика должно быть по слову-адресу 0x3F00.

Я даже выгрузил всю 32768-байтную флэш-память из MCU и подтвердил, что загрузчик находится по байтовому адресу 0x7e00, который соответствует ожидаемому слову-адресу 0x3F00 ( 0x7e00 / 2 = 0x3F00).

Я попробовал несколько способов перехода на адрес 0x3F00. Вот несколько. Ни один из них не работает. Они просто сбрасывают MCU, и загрузчик, похоже, не запускается.

void jump_to_bootloader() {
    size_t bootloader_addr = 0x3F00;

    void (*bootloader_ptr)() = (void (*)())(bootloader_addr);

    SREG = 0;
    wdt_disable();
    bootloader_ptr();
}

void jump_to_bootloader2() {
    asm volatile ("ijmp" ::"z" (0x3F00));
}
  1. Переход и обновление значения MCUSR

Затем я понял, что загрузчик[4] проверяет источник сброса и работает только при внешнем сбросе:

ch = MCUSR;
if (! (ch &  _BV(EXTRF))) // если это не внешний сброс...
    pp_start();  // пропустить загрузчик

Поэтому я попытался притвориться перед прыжком, но это тоже не сработало:

    MCUSR = _BV(EXTRF);
    asm volatile ("ijmp" ::"z" (0x3F00));
}

Любая помощь приветствуется. Заранее спасибо!


изменить:

  1. Перейти после того, как загрузчик проверит источник сброса

Следуя предложению @Edgar Bonet, избегая if (!(ch & _BV(EXTRF))) appStart();, проверяйте загрузчик, переходя к следующей инструкции:

void jump_to_bootloader2() {
    // загрузчик предполагает, что это правда:
    cli();
    SP = RAMEND;
    SREG = 0;
    MCUSR = 0;
    asm volatile ("eor  __zero_reg__, __zero_reg__");
    asm volatile ("eor  r1, r1");
    // перейти после того, как загрузчик проверит источник сброса
    asm volatile ("ijmp" ::"z" (0x7e0au / 2));
    while(1);
}

Я видел, что он вошел в загрузчик (светодиоды мигнули пару раз), но avrdude не смог загрузить код.

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


Ссылки:

  • [1] Руководство по набору инструкций AVR: http:/ /ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf
  • [2] Техническое описание atmega328p: http://ww1.microchip .com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
  • [3] случайная ветка форума Я также прочитал: https://www.avrfreaks. net/forum/jump-bootloader-app-help-needed
  • [4] код загрузчика: https:/ /github.com/sparkfun/Arduino_Boards/blob/805def002e815509032fde37a674d2916af8aecc/sparkfun/avr/bootloaders/atmega/ATmegaBOOT_168.c#L299-L307

, 👍5

Обсуждение

Я не вижу никакого способа сделать это, если UNO работает под управлением Optiboot. Единственные варианты, которые я вижу, это немного изменить, скомпилировать и прошить новый загрузчик на UNO. Или подключите один из контактов Arduino или один из контактов ESP к выводу сброса UNO., @Gerben


2 ответа


Лучший ответ:

1

Источник Optiboot для Uno находится здесь. р>

Я бы предпочел простой сброс сторожевого таймера

  wdt_enable(WDTO_15MS);
  while (true);

Но теперь я не уверен, запускает ли он обнаружение загрузки скетча в загрузчик.

Для прямого перехода к Optiboot на Uno (одна флэш-страница) это должно работать

typedef void (*bootloader_jump_t)();
const bootloader_jump_t bootloader_jump = (bootloader_jump_t)((FLASHEND-511)>>1);

а затем вы можете вызвать bootloader_jump();

ИЗМЕНИТЬ: в то время как Optiboot 4 на Uno с завода не проверяет новую загрузку после сброса сторожевого таймера или прямого перехода, Optiboot 8 имеет проверки для обработки этого права

,

Похоже, что новые UNO используют загрузчик optiboot. Optiboot проверяет, нажата ли кнопка сброса (или сбрасывает ее на низкий уровень с помощью сигнала DTR), и если нет, немедленно запускает ваш скетч (удаляя 3-секундную задержку). Поэтому я не думаю, что эти фрагменты кода будут работать., @Gerben

@Gerben, Уно всегда использовал Optiboot. версия 4 в комплекте с основным пакетом, @Juraj

Спасибо за ваш ответ. ((FLASHEND-512)>>1) дает 16127, то есть 0x3EFF. Загрузчик начинается со следующего слова флэш-памяти (0x3F00), поэтому я не думаю, что переход на 0x3EFF будет работать. И, как я уже сказал, я уже пытался перейти на 0x3F00. :/, @Nuno

@Нуно, извини. переход на 0x3F00 с Optiboot 8 будет работать. https://github.com/Optiboot/optiboot/blob/6f98751c6d184ec4a9c7f1fe0f0dcf12f9db2cc9/optiboot/загрузчики/optiboot/optiboot.c#L628, @Juraj

@Juraj Я вижу, если я поменяю загрузчик на Optiboot 8, это может сработать. Спасибо! Я попробую, когда у меня будет свободное время. Я не уверен, что загрузчик может заменить сам себя, поэтому потребуется немного больше усилий, поскольку контакты Arduino для прошивки SPI в настоящее время недоступны., @Nuno

@Juraj Я изменил загрузчик на Optiboot 8 с тайм-аутом 4 секунды (2 секунды не сработали), а затем использовал WDT для сброса до загрузчика, и это работает;) Большое спасибо!, @Nuno


0

Может быть, вы могли бы перейти сразу после строки

if (!(ch & _BV(EXTRF))) appStart();

Вот выдержка из разборки :

  if (!(ch & _BV(EXTRF))) appStart();
    7e06:   81 ff           sbrs    r24, 1
    7e08:   f0 d0           rcall   .+480       ; 0x7fea <appStart>

#if LED_START_FLASHES > 0
  // Настраиваем Таймер 1 для счетчика времени ожидания
  TCCR1B = _BV(CS12) | _BV(CS10); // раздел 1024
    7e0a:   85 e0           ldi r24, 0x05   ; 5
    7e0c:   80 93 81 00     sts 0x0081, r24

Условный вызов appStart() выполняется инструкциями sbrs и rcall. Инструкция сразу после этого - это ldi в байте адрес 0x7e0c (адрес слова 0x3f05). Я бы попробовал прыгнуть прямо там. Вы ничего не пропустите:

  • eor r1, r1 не требуется при переходе с C++, так как значение r1 уже применяется компилятором в соответствии с ABI.
  • в r24, 0x34 считывает MCUSR в регистр, который затирается правильно далеко
  • out 0x34, r1 очищает MCUSR; если это окажется полезным, вы мог бы сделать это прямо перед переходом к загрузчику.

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

,

Спасибо! Действительно, мой сброшенный флеш совпадает как минимум с 4 строками процитированного вами отрывка. Я попытался перейти к 0x7e0au/2, сразу после проверки бита EXTRF. Я попробовал это и увидел, что загрузчик мигает светодиодами, но avrdude не смог синхронизироваться. :/ Я обновил исходный вопрос этим (см. ** 4. Переход после того, как загрузчик проверит источник сброса **)., @Nuno