I'm trying to program an Arduino Uno (atmega328p) using only the serial Tx/Rx lines (no DTR, so no automatic reset). This is so I can upload code via WiFi/TCP using an ESP-01.
To achieve this, I need to Arduino sketch/application to reset itself (and run the bootloader) whenever it sees avrdude trying to upload code (e.g. when 0 0
appears in the serial port).
I've tried a few approaches to jumping to the bootloader from application code but had no success so far.
- Using the watchdog timer
void reset_to_bootloader() {
// see 11.2.2 MCUCR – MCU Control Register
// Enable change of interrupt vectors
MCUCR = (1<<IVCE);
// Move interrupts to Boot Flash section
MCUCR = (1<<IVSEL);
// enable watchdog timer
wdt_enable(WDTO_30MS);
// block waiting for the watchdog to timeout and jump to the bootloader
while(1);
}
Unfortunately, the datasheet[1] mentions the IVSEL
bit is not available in atmega328p, so even though this compiles, it simple resets the MCU and the bootloader does not run.
- Jumping to the bootloader address directly
There is a table in the datasheet (Table 26-7. Boot Size Configuration, ATmega328P) that specifies the bootloader start address for the 4 supported bootloader section sizes, which are configured by the BOOTSZ fuse.
I have read the fuses and confirmed the bootloader section is 512-bytes (256 words) long.
Therefore, according to the datasheet, the Boot Loader Flash Section is 0x3F00 - 0x3FFF
. So, the start of the bootloader should be at word-address 0x3F00.
I have even dumped the whole 32768-byte flash off the MCU and confirmed the bootloader is at byte-address 0x7e00
, which matches the expected word-address 0x3F00
(0x7e00 / 2 = 0x3F00
).
I have tried a couple methods of jumping to address 0x3F00. Here's a few. None of them seem to work. They just reset the MCU and the bootloader doesn't seem to run.
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));
}
- Jumping and updating the value of
MCUSR
I then realised the bootloader[4] checks the reset source and only runs on an external reset:
ch = MCUSR;
if (! (ch & _BV(EXTRF))) // if its a not an external reset...
pp_start(); // skip bootloader
So I tried faking it before jumping, but it didn't work either:
MCUSR = _BV(EXTRF);
asm volatile ("ijmp" ::"z" (0x3F00));
}
Any help is appreciated. Thanks in advance!
edit:
- Jumping to after the bootloader checks the reset source
Following @Edgar Bonet's suggestion, avoiding the if (!(ch & _BV(EXTRF))) appStart();
check in the bootloader by jumping to the instruction that follows it:
void jump_to_bootloader2() {
// the bootloader assumes these are true:
cli();
SP = RAMEND;
SREG = 0;
MCUSR = 0;
asm volatile ("eor __zero_reg__, __zero_reg__");
asm volatile ("eor r1, r1");
// jump to after the bootloader checks the reset source
asm volatile ("ijmp" ::"z" (0x7e0au / 2));
while(1);
}
I could see it entered the bootloader (LEDs flashed a couple times), but avrdude was not able to upload code.
The bootloader sets up a watchdog timer (WDT), so I'm suspecting avrdude waits for to long after sending the initial 0 0 0
and since the bootloader does not see anything on the serial port the WDT resets it.
References:
- [1] AVR Instruction Set Manual: http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf
- [2] atmega328p datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
- [3] random forum thread I have also read: https://www.avrfreaks.net/forum/jump-bootloader-app-help-needed
- [4] bootloader code: https://github.com/sparkfun/Arduino_Boards/blob/805def002e815509032fde37a674d2916af8aecc/sparkfun/avr/bootloaders/atmega/ATmegaBOOT_168.c#L299-L307