Переход к загрузчику из кода приложения в atmega328p
Я пытаюсь запрограммировать Arduino Uno (atmega328p), используя только последовательные линии Tx/Rx (без DTR, поэтому без автоматического сброса). Это позволяет мне загружать код через WiFi/TCP с помощью ESP-01.
Для этого мне нужно, чтобы скетч/приложение Arduino перезагружались (и запускали загрузчик) каждый раз, когда он видит, что avrdude пытается загрузить код (например, когда в последовательном порту появляется 0 0
).
Я попробовал несколько подходов к переходу к загрузчику из кода приложения, но пока безуспешно.
- Использование сторожевого таймера
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, и загрузчик не запускается.
- Прямой переход к адресу загрузчика
В техническом описании есть таблица (Таблица 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));
}
- Переход и обновление значения
MCUSR
Затем я понял, что загрузчик[4] проверяет источник сброса и работает только при внешнем сбросе:
ch = MCUSR;
if (! (ch & _BV(EXTRF))) // если это не внешний сброс...
pp_start(); // пропустить загрузчик
Поэтому я попытался притвориться перед прыжком, но это тоже не сработало:
MCUSR = _BV(EXTRF);
asm volatile ("ijmp" ::"z" (0x3F00));
}
Любая помощь приветствуется. Заранее спасибо!
изменить:
- Перейти после того, как загрузчик проверит источник сброса
Следуя предложению @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
@Nuno, 👍5
Обсуждение2 ответа
Лучший ответ:
Источник 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
Может быть, вы могли бы перейти сразу после строки
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
- Не удается снова загрузиться после смены платы
- Что означает avrdude: Device signature = 0x000000? Неисправная проводка? Проблема с программным обеспечением? Неправильная инструкция?
- Atmega328p — переход на низкое энергопотребление 1,8 В с использованием генератора 4 МГц — прошивка загрузчика
- Автономный ATMega328 — нужно ли прожигать загрузчик?
- Загрузчик на ATmega328p (3.3V/8MHz), чувак не закончит свое дело
- Разница в загрузчике между Atmel328p (сквозное отверстие) и Atmel328p au (smd)
- Не могу записать загрузчик с помощью avrisp
- Как компилятор/ассемблер работает с загрузчиком?
Я не вижу никакого способа сделать это, если UNO работает под управлением Optiboot. Единственные варианты, которые я вижу, это немного изменить, скомпилировать и прошить новый загрузчик на UNO. Или подключите один из контактов Arduino или один из контактов ESP к выводу сброса UNO., @Gerben