5

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.

  1. 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.

  1. 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));
}
  1. 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:

  1. 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
  • I don't see any way of doing this if the UNO is running optiboot. The only options I see is to slightly change, compile, and flash a new bootloader on the UNO. Or connect one of the Arduino pins, or one of the ESP pins to the UNOs reset pin.
    – Gerben
    Jul 31, 2020 at 12:46

2 Answers 2

1

The source of Optiboot on Uno is here.

I would prefer a simple watchdog reset with

  wdt_enable(WDTO_15MS);
  while (true);

But I am not sure now if it runs sketch upload detection in bootloader.

For direct jump to Optiboot on Uno (one flash page) this should work

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

and then you can call bootloader_jump();

EDIT: while the Optiboot 4 on Uno from factory doesn't check for new upload after watchdog reset or direct jump, Optiboot 8 has checks to handle this right

6
  • It appear the new UNOs use the optiboot bootloader. Optiboot check if the reset button is pressed (or reset it pulled low via the DTR signal), and if not, will runs your sketch immediately (removing the 3 second delay). So I don't think these pieces of code will work.
    – Gerben
    Jul 31, 2020 at 12:43
  • @Gerben, Uno always used Optiboot. version 4 as bundled with the core package
    – Juraj
    Jul 31, 2020 at 12:45
  • Thank you for the reply. ((FLASHEND-512)>>1) gives 16127, which is 0x3EFF. The bootloader starts in the next flash word (0x3F00), so I don't think jumping to 0x3EFF will work. And, as I said, I already tried jumping to 0x3F00. :/
    – Nuno
    Aug 1, 2020 at 1:46
  • @Nuno, sorry. jumping to 0x3F00 with Optiboot 8 will work. github.com/Optiboot/optiboot/blob/…
    – Juraj
    Aug 2, 2020 at 8:49
  • @Juraj I see, if I change the bootloader to Optiboot 8 it may work. Thanks! I'll give it a try when I have some free time. I'm not sure if the bootloade can replace itself, so it'll take a little more effort as the arduino pins for SPI flashing are currently inaccessible.
    – Nuno
    Aug 3, 2020 at 2:13
0

Maybe you could jump to right after the line

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

Here is an excerpt of the disassembly:

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

#if LED_START_FLASHES > 0
  // Set up Timer 1 for timeout counter
  TCCR1B = _BV(CS12) | _BV(CS10); // div 1024
    7e0a:   85 e0           ldi r24, 0x05   ; 5
    7e0c:   80 93 81 00     sts 0x0081, r24

The conditional call to appStart() is made by the instructions sbrs and rcall. The instruction right after that is the ldi at byte address 0x7e0c (word address 0x3f05). I would try jumping right there. You won't miss much:

  • eor r1, r1 is not needed when jumping from C++, as the value of r1 is already enforced by the compiler as per the ABI.
  • in r24, 0x34 reads MCUSR into a register that is clobbered right away
  • out 0x34, r1 clears MCUSR; if this turns out to be useful, you could do it right before jumping to the bootloader.

You may want to check that the bootloader you have matches the disassembly I linked to.

1
  • Thanks! Indeed, my dumped flash matches at least the 4 lines of the excerpt you quoted. I tried jumping to 0x7e0au / 2, which is right after the check for the EXTRF bit. I tried this and I could see the bootloader blinking the LEDs but avrdude was not able to sync. :/ I've updated the original question with this (see 4. Jumping to after the bootloader checks the reset source).
    – Nuno
    Aug 1, 2020 at 2:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.