Bootloader resetting important registers

686 views
Skip to first unread message

Tim Chilton

unread,
May 9, 2015, 5:54:13 PM5/9/15
to devel...@arduino.cc
Hi all,

I'm trying to diagnose the cause of a random reset on a reasonably complex project that uses a Mega 2560 and I've found that the MCUSR register is always zero, which makes this diagnosis much harder as I'm not sure what I'm looking for (other than its probably related to noise) - is it a brownout, a  reset, the watchdog, etc. 

From the datasheet, I see the MCUSR register, so I've added code to show its value, on reset, but find its always zero.

I've been digging and determined it must be something in the bootloader before it gets to my code, so I've worked through boards.txt and find 
mega.menu.cpu.atmega2560.bootloader.file=stk500v2/stk500boot_v2_mega2560.hex

I've found the bootloader source for stk500boot.c and I found this section of code 

#ifdef _FIX_ISSUE_181_
//************************************************************************
//* Dec 29, 2011 <MLS> Issue #181, added watch dog timmer support
//* handle the watch dog timer
uint8_t mcuStatusReg;
mcuStatusReg = MCUSR;

__asm__ __volatile__ ("cli");
__asm__ __volatile__ ("wdr");
MCUSR = 0;
WDTCSR |= _BV(WDCE) | _BV(WDE);
WDTCSR = 0;
__asm__ __volatile__ ("sei");
// check if WDT generated the reset, if so, go straight to app
if (mcuStatusReg & _BV(WDRF))
{
app_start();
}
//************************************************************************

I note the typo above (timmer) and I've also found 
//* Issue 181: added watch dog timmer support
#define _FIX_ISSUE_181_

So, this code is complied in and is the source of my issue.

According to Google, issue 181 is detailed at https://code.google.com/p/arduino/issues/detail?id=181

Clearly the mcuStatusReg is local to main and not visible to anything else, so the sketch has no way of querying it.

I see that the code comes from the Mega 2560 data sheet, section 12.4.2, which shows the process for the Watchdog timer and the notes below require that the WDRF bit is reset.

What I don't understand is why the variable is not made visible to the sketch and why the init code clears both MCUSR and WDTCSR, since this makes both diagnostic of random power issues and low power sketches more difficult, since you have to modify the bootloader and re-flash, which implies a far higher level of understanding of the Arduino environment thus it will be a barrier to many - perhaps just because you need two Arduino's to do the boot loader flashing.

My first suggestion is that there should be an Arduino principle that the project should not make things harder for people to understand, things like this take time to find and could be better documented - although conversely, I do understand the bootloader's internal workings better now ;-)

My second suggestion is that as the current local variable in main has a scope that will exist through the whole sketch and therefore consume RAM, then, other than being a little ugly, why not move the declaration of mcuStatusReg to be global to the sketch, then anyone can query its value and there has been a zero increase in memory footprint.

I also suggest that the name mcuStatusReg should be renamed to shadowMCUSR,  _MCUSR, arduinoMCUSR, bootloaderMCUSR or something similar. This then allows that any registers that the boot loader needs to alter can be copied off and presented in a consistent manner that any sketch can access without the user needing to know anything more about the environment.

There is also a benefit to new users of Arduino that if they ask others ask for help, its easier to diagnose things by having them insert a couple of lines of debug Serial.println's on the MCUSR register.

My third suggestion is that I note that there are different boot loaders for different sets of devices and each works slightly differently - there must be a good case for the boot loaders to be unified across the platform. If not, then there has to be an easier way to tell which bootloader and version is in a particular uC - either by a couple of bytes that you can reference and decode, or a small function that can be called from the sketch which returns key info.

Can the above be incorporated into a future release ?

Regards

Tim

Matthijs Kooijman

unread,
May 9, 2015, 6:14:48 PM5/9/15
to devel...@arduino.cc
Hi Tim,

> MCUSR = 0;
> WDTCSR |= _BV(WDCE) | _BV(WDE);
> WDTCSR = 0;
Interesting. I think clearing WDTCSR is essential here, to prevent
reboot loops when the watchdog triggers. However, I can't actually think
of any reason to clear MCUSR, that could just be kept at its value, I
think?


> My second suggestion is that as the current local variable in main has a
> scope that will exist through the whole sketch and therefore consume RAM,
> then, other than being a little ugly, why not move the declaration of
> mcuStatusReg to be global to the sketch, then anyone can query its value
> and there has been a zero increase in memory footprint.

That won't work - the bootloader doesn't call the user sketch as a
normal function call, but instead it jumps to address 0x0, where the
avr-libc initialization code sets up a new stack and calls main, which
will overwrite the memory where mcuStatusReg used to live. I think that
technically, if you would read the right memory location at the top of
main, before any variables are written, you might get the right value,
but this is extremely fragile and non-portable across boards, so I don't
see that happening.


I think that optiboot is considered the new and fancy bootloader
nowadays, IIRC recent Unos ship with it. Did you see if that touches
MCUSR?

> My third suggestion is that I note that there are different boot loaders
> for different sets of devices and each works slightly differently - there
> must be a good case for the boot loaders to be unified across the platform.
> If not, then there has to be an easier way to tell which bootloader and
> version is in a particular uC - either by a couple of bytes that you can
> reference and decode, or a small function that can be called from the
> sketch which returns key info.
Yeah, it would be good if there was some reference about this. Even if
it's just a list of bootloader versions, pointers to the source version
and some info about when they were used for what devices in production.

Gr.

Matthijs
signature.asc

Tim Chilton

unread,
May 9, 2015, 6:49:09 PM5/9/15
to devel...@arduino.cc
Hi Matthijs,

I take your point. I had seen the pointer calculations and jumps .. but not thought through the impact on variables. Agree that yanking things from memory before they are overwritten is really ugly and not the way to go.

I don't see any reason for the MCUSR to be reset either, although the example code in the datasheet does clear the WDRF bit. This is probably just following the recommendation that states

To make use of the Reset Flags to identify a reset condition, the user should read and then Reset the MCUSR as early as possible in the program. If the register is cleared before another reset occurs, the source of the reset can be found by examining the Reset Flags.

Additionally, it could be argued that a single bit being cleared would be a *lot* harder to diagnose than the whole register being cleared - and I still want to see the original register.

I think that the tidiest way is not to clear the register at all, that way if the user doesn't care what caused the reset, it just contains junk from the various resets etc. and if they do care, then they can manage the register like any of the rest of them from within their own code.

Re optiboot, yes, it does have similar MCUSR clear in it but that is also conditionally compiled in for another reason. Have not really looked at that as much since its not the active bootloader (although I did have a play with sending !!! at reset ...) I didn't know about that either .. So much to learn and so little documented in one place and hard to find unless you know more about the  topic in the first place..

The conditional code further implies that the clear is not necessary. I'll have a play and see what I can find.

Regards

Tim

William Westfield

unread,
May 9, 2015, 6:57:19 PM5/9/15
to devel...@arduino.cc

>> as the current local variable in main has a scope that will exist through the whole sketch and therefore consume RAM,

this is “main” for the bootloader, which is entirely separate from main() in the sketch. Also, it doesn’t consume “sketch” RAM; basically the bootloader runs and uses RAM as appropriate, and then gives it all up to run the sketch.

The bootloader can’t read/set the bits it needs to in MCUSR without resetting the original reset cause, and of course the bootloader RUNS and “consumes” reset causes as well (for instance, optiboot starts the sketch by causing a WDT reset. This ensures that the chip is in its fully reset state, but it also means that the sketch will nearly always see “WDT” as the reset cause, if it sees anything at all. And it has to reset the bits because otherwise multiple bits will get set during the startup process.

Optiboot (in more recent versions) addressed this (some) by passing the original MCUSR contents to the sketch in a “well defined” register"
https://github.com/Optiboot/optiboot/issues/66
http://code.google.com/p/arduino/issues/detail?id=794
https://github.com/Optiboot/optiboot/issues/97

(and there’s a pull request for cleverly figuring out whether a WDT reset occurred from the application or from the bootloader, which hasn’t been incorporated yet.)

You’d need to check whether any of these will work for the 2560 bootloader.
Note that optiboot can NOT run on the ATmega2560.

BillW/WestfW

Matthijs Kooijman

unread,
May 9, 2015, 7:00:08 PM5/9/15
to devel...@arduino.cc
Hey Tim,

> I don't see any reason for the MCUSR to be reset either, although the
> example code in the datasheet does clear the WDRF bit. This is probably
> just following the recommendation that states
>
> *To make use of the Reset Flags to identify a reset condition, the user
> should read and then Reset the MCUSR as early as possible in the program.
> If the register is cleared before another reset occurs, the source of the
> reset can be found by examining the Reset Flags.*
I remembered some tricky thing with the watchdog and had a look at the
datasheet. The docs for the WDE (watchdog enable) bit say:

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 feature ensures multiple resets during conditions
causing failure, and a safe start-up after the failure.

In other words - if a watchdog reset occurs and WDRF is set, the
watchdog is automatically enabled again. So the bootloader must clear at
least WDRF, otherwise the watchdog might trigger before the bootloader
is finished and the sketch can start pinging the watchdog.

Arguably, if you clear one bit, you might as well clear them all. It
might be useful if the MCUSR value was exposed to the sketch, though.

IIRC, in te Pinoccio Scout board has we let the bootloader copy MCUSR
into GPIOR0, which is not otherwise used. Perhaps the Arduino bootloader
should do something similar?

Gr.

Matthijs
signature.asc

William Westfield

unread,
May 9, 2015, 7:11:04 PM5/9/15
to devel...@arduino.cc
> we let the bootloader copy MCUSR into GPIOR0, which is not otherwise used. Perhaps the Arduino bootloader should do something similar?

That’s an interesting idea…

BillW/WestfW

Britton Kerin

unread,
May 10, 2015, 12:34:19 AM5/10/15
to devel...@arduino.cc
On Sat, May 9, 2015 at 3:10 PM, William Westfield <wes...@mac.com> wrote:
>> we let the bootloader copy MCUSR into GPIOR0, which is not otherwise used. Perhaps the Arduino bootloader should do something similar?
>
> That’s an interesting idea…

There's a description of what avr libc presribes below. I went
through and validated and it works well for me.

http://www.nongnu.org/avr-libc/user-manual/group__avr__watchdog.html

Britton

Tim Chilton

unread,
May 10, 2015, 5:24:04 AM5/10/15
to devel...@arduino.cc
Thanks for all the feedback.

That's a really clean solution and it seems that GPIOR0 is present on the smaller Atmel chips too - such as the 328, so +1 from me for this to be part of the standard boot loader.

In the mean-time, I've started to look at tweaking my bootloader. I understand makefiles etc but I note that make is not included in the standard tools that come with Arduino. Whats the best way to do the recompile as I'm on Windows. Is it best to use WinAVR (seems old and un-maintained now ?) or AVR Studio ? Additionally will that give me any issues with conflicts to the Arduino bundled tools ?

Or is it best to bite the bullet and drop to a Linux VM and just do it on there ? Recommendations please.

Regards

Tim

Tim Chilton

unread,
May 10, 2015, 7:35:36 PM5/10/15
to devel...@arduino.cc
OK, an update.

I successfully modified the bootloader (and recompiled using WinAVR). The modified version keep the MCUSR into GPIOR0 as suggested and this has led me to a watchdog timeout that occurs in an Arduino library - Wire. I'm still not 100% sure why the WDT flag is not set in the MCUSR shadow register though ... I'm still looking at this, but I can see this with a while (1) test piece of code to get the same register values.

Having googled the Wire issue for a while, it seems that this is a known issue. twi.c has an endless loop in it if the bus is busy and it can't become the master, which is what happens in my condition. Here's the offending code.

  // wait for write operation to complete
  while(wait && (TWI_MTX == twi_state)){
    continue;
  }


I have two observations.

1. Surely the standard libraries should honor the watchdog time if its set and they go into time consuming activities or the sketch will just die. Alternatively the libraries should time out so that the users sketch can take care of this instead. After all, there is not much point in having a hung uC !

2. The Atmega 2560 data sheet details the loss of bus arbitration for TWI in section 24.5.3, which is what I'm experiencing . I need to read the TWI section in more detail to see how to cater for this condition..

In my specific case, I have two I2C masters, a Mega 2560 and a Raspberry PI (with level shifters) and some slaves that can be accessed by both (RTC, FLASH for logging, etc), I experience the issue intermittently when trying to read the EEPROM from the Pi. I can cause the failure by using the Pi with "i2detect -y 1" executed in a loop to make the bus busy. After a while, this results in the I2C bus hanging (shown by the i2cdetect showing the address as active). This occurs when the WDT has kicked in and the Arduino kills the bus, I believe this is during the reset. Should the bootloader ensure that shared busses like I2C are not hung during the boot loader ???. 

Regards

Tim

Matthew Ford

unread,
May 11, 2015, 3:44:39 AM5/11/15
to devel...@arduino.cc
This writeup might help, but your case is more complex with two masters.
http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html
matthew
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

C W Rose

unread,
May 11, 2015, 5:33:44 AM5/11/15
to devel...@arduino.cc
Bootloader resetting important registers.

The Arduino IDE lasted about five minutes before I reverted to my usual
editor and makefiles, but Geany seems a nice lightweight IDE if you want
one. Martin Oldfield wrote a useful set of Makefiles integrated with the
Arduino libraries, but someone else is now maintaing them
(https://github.com/sudar/Arduino-Makefile).

Getting the reset flags for the ATmega 328 is relatively straightforward,
but I've never used a 2560. Optiboot 5.0 has the code:

void appStart(uint8_t rstFlags) {
// save the reset flags in the designated register
// This can be saved in a main program by putting code in .init0 (which
// executes before normal c init code) to save R2 to a global variable.
__asm__ __volatile__ ("mov r2, %0\n" :: "r" (rstFlags));

watchdogConfig(WATCHDOG_OFF);
__asm__ __volatile__ (
#ifdef VIRTUAL_BOOT_PARTITION
// Jump to WDT vector
"ldi r30,4\n"
"clr r31\n"
#else
// Jump to RST vector
"clr r30\n"
"clr r31\n"
#endif
"ijmp\n"
);
}

Which means that if storage is allocated in the main program, the reset
flags can be read with the following function:

/**
** @note
** Storage for the contents of the MCUSR (which must be cleared during system
** initialization to ensure that continuous watchdog resetting doesn't occur;
** see http://www.nongnu.org/avr-libc/user-manual/group__avr__watchdog.html for
** details). This can be used to investigate the cause of a reset on reboot.
*/
uint8_t MCUSR_value __attribute__ ((section (".noinit")));

/** The Optiboot version number, if available */
uint16_t VERSION_value __attribute__ ((section (".noinit")));

/* Backup and clear the MCUSR register early in the AVR boot process */
void store_mcusr(void) \
__attribute__ ((naked)) \
__attribute__ ((section(".init3")));

/**
** Park the MCUSR register value and the Optiboot version
** number somewhere accessible
**
** @remark No parameters
** @return Nothing
** @note Optiboot 5 moves the MCUSR value to register R2, so
** store_mcusr() must be run before main(), which is done
** by placing it in one of avr-gcc's .init sections.
** Note that the Optiboot version number is available only
** in recent Optiboot code.
**
** @internal s t o r e _ m c u s r
*/
void store_mcusr(void)
{
/* Disable the WDT in case it's just forced a reset */
wdt_disable();
MCUSR = 0x0; /* Also zeroed in optiboot */

__asm__ __volatile__ (
"mov %0, r2\n"
: "=r" (MCUSR_value)
:);

__asm__ __volatile__ (
"ldi r31, 0x7f\n"
"ldi r30, 0xfe\n"
"lpm %A0, Z+\n"
"lpm %B0, Z\n"
: "=r" (VERSION_value)
:);

return;
}

and somewhere in main() we then have:

#ifdef DEBUG
Serial.print(F("Optiboot version: 0x"));
Serial.println(VERSION_value, HEX);

/* Report the MCUSR register value */
Serial.print(F("Reset type: "));
switch (MCUSR_value & 0xff) {
case 1:
Serial.println(F("Power On"));
break;
case 2:
Serial.println(F("External"));
break;
case 4:
Serial.println(F("Brownout"));
break;
case 8:
Serial.println(F("Watchdog"));
break;
default:
Serial.println(F("Unknown"));
Serial.print(F("MCUSR register : 0x"));
Serial.println(MCUSR_value, HEX);
break;
}
#endif

Will


Maxwell Dribinsky

unread,
Jul 23, 2015, 3:26:01 AM7/23/15
to devel...@arduino.cc
Tim,

Could you please explain how you compiled the bootloader? I'm working on a very similar project, but I'm having trouble using the makefile provided in the directory (I'm on Ubuntu). After cloning the arduino repo from github, I tried compiling and it seems like there were a few dependencies missing, such as LUFA and a few other things. Would you have any insight you could share with me?

Thanks

Max

Tim Chilton

unread,
Jul 23, 2015, 7:28:22 PM7/23/15
to Developers, max...@gmail.com
Hi Max,

I've referred back to my notes and what I recall doing here.
Not sure I can help too much, since I'm on Windows for the main development machine, but the overall process is the same., 

Anyhow, as to how, here's what I did
 
1. I installed WinAVR (http://sourceforge.net/projects/winavr/files/). This gave me the required compiler and tooset.
2. I copied the source for stk500v2 code (in C:\Program Files (x86)\Arduino\hardware\arduino\avr\bootloaders\stk500v2) to my boards area, 
3. Made the required mods for the boot reason - using GPIO0 and GPIO1 to store what I needed 
4. Then I just did a make and everything went swimmingly - new stk500_v2_mega_2560.hex file produced.
5. I copied the required .hex file to the folder for the correct arduino, You could copy to the above folder in 2, but obviously backup original just in case
6. Programmed up another arduino with the Arduino ISP sketch so that I had a programmer.
7. Cabled up per the instructions in the top of the Arduino ISP sketch
8. Changed board type back to Mega 2560.
9. Did a burn bootloader to my target board.
10. Done, time to test. Board back to being "blinky", so needed to reprogram my real sketch back onto it

My only guess is that as you are cross-compiling, perhaps some of your paths are set wrong or dependencies are missing. for example do you have gcc-avr, avr-libc and avrdude installed ? - see the following links I found from a quick google

http://angryelectron.com/stk500-arduino-bootloader/ <--- think this is the one you need.


When you get it going - how about doing a quick write up for the next person ?

Good luck

Tim
Reply all
Reply to author
Forward
0 new messages