Go Down

Topic: Bootloader and MCU reset register (Read 3375 times) previous topic - next topic

tim7

I'd like my application to be able to distinguish a power-on reset from an external reset.  It looks like the MCUSR register does what I want, except that the bootloader wipes this register when it runs:

Code: [Select]
  // Adaboot no-wait mod
  ch = MCUSR;
  MCUSR = 0;
  if (!(ch & _BV(EXTRF))) appStart();


Can I safely replace these lines with the following?

Code: [Select]

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


Coding Badly


I can't answer your question but I can, hopefully, give you a word of advice.

The risk with the modification is that the board may get stuck in a loop or the bootloader may not run when it should.  I suggest a mental walk through the code with various MCUSR bits set under a few common scenarios.

BenF


Can I safely replace these lines with the following?

Code: [Select]

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


I would say yes - this is a safe mod that will allow you to test for power on or reset in your sketch. It gets a bit more complicated if you want to add support for watchdog, but otherwise not.


westfw

Hmm.  The clearing of MCUSR comes from the ADAboot bootloader, and I'm not sure what it accomplishes.
Note that a normal "reset" that goes through the bootloader (button, auto-reset) will end up showing that the MCU was restarted by a watchdog timeout, rather than an external reset.

BenF


Note that a normal "reset" that goes through the bootloader (button, auto-reset) will end up showing that the MCU was restarted by a watchdog timeout, rather than an external reset.

That was cryptic …?

I've used the above mod for quite some time and my experience is that EXTRF is the only bit set (watchdog disabled) after a normal reset and so generally allows for cause of restart to be determined (power-on, reset or brown-out). This behavior is also supported in the documentation of MCUSR (AtMega328). The bits are sticky however and so the cause of a second restart (unless power is cycled) may not be correctly determined unless MCUSR is explicitly cleared.


tim7

Thanks for the great info guys.  P54 of the ATmega datasheet indicates that the MCUSR flags have to be explicitly cleared between resets.  As far as I can see it is safe to do this in the application code: the worst that can happen is that the application fails to clear EXTRF before a non-external reset, causing the "no-wait" check to execute the bootloader needlessly.

I haven't quite wrapped my head around the Reset vectors yet.  Reading the Optiboot v44 code, if the bootloader receives no readable character within 1s, then the watchdog induces a system reset.  But with BOOTRST programmed, why does this cause the application to run rather than restarting the bootloader?

westfw

#6
Jun 22, 2011, 01:37 am Last Edit: Jun 22, 2011, 01:39 am by westfw Reason: 1
Quote
The bits are sticky however and so the cause of a second restart (unless power is cycled) may not be correctly determined unless MCUSR is explicitly cleared.

Really?  That would be a problem, then.
The bootloader "decides" to run the user sketch by letting the WDT expire and reset the CPU, and the bootloader only runs if the reset reason was "External Reset."  (so the sketch starts "immediately" for poweron or after WDT Reset.)
So the "normal" sketch startups are:

  •  Power on, start sketch.

  •  Reset switch, start bootloader (enabled WDT), timeout causes WDT reset, start sketch.


If the bit is sticky as you describe, the second example would probably loop:
Reset switch, start bootloader, WDT Reset, (still sees EXT RESET), start bootloader, WDT...
I'm not quite sure I believe the stickiness works like that.  (It COULD be "sticky" in the sense that you could end up with multiple bits set in MCUSR.)
It should be safer to move the clear of MCUSR after the decision to run the bootloader.

(note that the older (2K) bootloader had a different startup mechanism.)

tim7

Aaaah, now I understand.  So to avoid looping I need to write
Code: [Select]
  // Adaboot no-wait mod
  ch = MCUSR;
  if (!(ch & _BV(EXTRF))) appStart();
  MCUSR = 0;


My application will then be able to read the reset status, except that External resets will appear as Watchdog resets.
Hmm.  What about storing MCUSR in another register, such as GPIOR0?
Code: [Select]
  // Adaboot no-wait mod
  GPIOR0 = MCUSR;
  MCUSR = 0;
  if (!(GPIOR0 & _BV(EXTRF))) appStart();


Time for some experimentation.  Thanks again,

westfw

An advantage of the way optiboot starts the sketch is that the sketch starts with the microcontroller with the state REALLY REALLY CLOSE to having everything in the "immediately after reset" state (which may or may not be what you expect, as Uno SMD discovered.)  Moving the clear of MCUSR would make that even more true, which I would normally count as "good."  Assuming no other side effects.

tim7

OK, so this doesn't work:
Code: [Select]
  // Adaboot no-wait mod
  ch = MCUSR;
  if (!(ch & _BV(EXTRF))) appStart();
  MCUSR = 0;

The watchdog is still running as the application starts, and resets it after a few ms - causing an endless loop.  Page 55 of the datasheet explains why:
Quote
WDE is overridden by WDRF in MCUSR. This means that WDE is always set when WDRF is
set. To clear WDE, WDRF must be cleared first.


This does work, at the cost of a couple of bytes of bootloader space:
Code: [Select]
  // Adaboot no-wait mod
  ch = MCUSR;
  MCUSR = 0; // required to prevent the bootloader from looping and the watchdog from biting
  GPIOR0 = ch; // <--- extra code
  if (!(ch & _BV(EXTRF))) appStart();

It goes slightly against the philosophy of having the bootloader interfere as little as possible with the state of the CPU, but all the IO registers are wiped anyway after a reset (p46 in the datasheet).

On powerup both the brown-out and power-on flags are set.  Presumably the brown-out condition is set as the power is removed and the on-board capacitors discharge, and remains when power is restored.  After an external reset, the watchdog-reset flag is set as expected.

I haven't looked at MCUSR so am not advocating one way or the other but FYI, a reset does not wipe RAM like a power on/power off does.  So you can squirrel away something in RAM and then check it later (so long as the bootloader does not clear RAM just for the fun of it).  I used this trick in my
"toastedboot" loader to implement different behavior on a "double-click" of the reset button.

Andrew
http://www.toastedcircuits.com Lightuino LED driver: 16 sources, 70 sinks, remote controlled.  Also high powered LED drivers.

westfw

Quote
a reset does not wipe RAM like a power on/power off does.

I'm pretty sure that even a power-on reset is not in any way guaranteed to leave RAM in any particular state...
(Nor register contents, as discovered with the Uno SMD bootloader looping problem.)
(the only things you should assume have a known state after reset are the IO registers documented as having an initial state in the data sheet.

Please excuse my quick summary:  To be extremely precise, I was using "wipe" not in the sense of "clear" but in the sense of "wipe out" or "destroy what is currently there".  Therefore if you placed a well known but random (flip a coin 32 times) 32 bit integer that is not 0 or 0xffffffff (just in case the CPU does wipe-as-in-clear) into one memory location and your data into another, then IF that well-known integer is there when the sketch begins, it is extremely likely to be a soft reset.  Note that this will only work if the bootloader does not use that memory location itself (or if you ARE the bootloader, or if you dispense with it entirely by doing ICSP programming, which has the added advantage of starting your sketch right away).

Cheers!
Andrew
http://www.toastedcircuits.com Lightuino LED driver: 16 sources, 70 sinks, remote controlled.  Also high powered LED drivers.

tim7

Thinking about the Optiboot reset logic a little more...  After an External reset the bootloader runs, executes a Watchdog reset, runs again, and starts the application.  Is there any objection to having the WatchDog call appStart() via an interrupt, avoiding the extra reset and the need to clear MCUSR?  The "Watchdog Interrupt Enable" flag is apparently cleared automatically, avoiding any risk of creating a loop.

The following produces a 568-byte binary.  Any ideas for a more efficient way of setting the interrupt vector?
Code: [Select]
158a159
> #include <avr/interrupt.h>
289d289
<   MCUSR = 0;
654c654
<   WDTCSR = _BV(WDCE) | _BV(WDE);
---
>   WDTCSR = _BV(WDCE) | _BV(WDIE);
655a656,659
> }
>
> ISR(WDT_vect) {
>   appStart();

Coding Badly

Quote
Is there any objection to having the WatchDog call appStart() via an interrupt


Yes.  The watchdog interrupt does not reset / clear the processor.

Go Up