SAMD21 Brownout configurations on Arduino MKRs

I've just struggled over a interesting blog topic:

Does anybody knows how the SAMDs are configured coming with the Arduino MKRs?
How can i configure the CPU myself? Is there a lib or a codesnippet? Or a general hint?
Thx

Hi @intstarep

The BOD33 (3.3V) settings are loaded into the System Control BOD33 register from the SAMD21's fuses (aka Non-Volatile Memory (NVM) User ROW Mapping) when the microcontroller is powered on.

The NVM BOD33 fuses can be set semi-permanently either using a suitable programmer or programatically with code.

The SAMD21 also allows the system control BOD33 register to control the BOD33's enable, trigger level, sample rate, hysteresis and action (none, reset or interrupt) to be modified.

With the exception of the hysteresis, the BOD12 settings are fixed and cannot be changed.

The MKR's BOD33 is enabled by default.

Thx for your answer!
Same question in simpler words that i might understand the answer.. :
Does the CPU reset in the default (Arduino) config with low power?
OR
Is the threat described in the article banned for MKRs?
OR
Can i tick this box with MKRs?
Many thanks

Hi @intstarep

No, since the BOD33 on the MKR by default is set to continous mode. This typically draws around 25uA, whereas sampling mode drops this to between 132nA typical and 1uA max.

Sampling mode means driving the BOD33 and BOD12 modules with a 1kHz clock derived from the on-chip Ultra Low 32.768kHz Power Oscillator (OSCULP32K) and using a prescaler to set the desired sampling interval.

Nothing's banned, it's just that any changes are performed at you own risk.

If you're just modifying the system control's BOD33 register, like in Thea's article, you should be OK, because a board reset will cause the microcontroller to reload this register with those (fuse) settings in the NVM User Row Mapping. In other words, upon reset the SAMD21's BOD33 settings will simply return to Arduino's default.

Things can become tricker if you start changing the NVM User Row Mapping, since these changes affect the microcontroller from start-up, which may include bootloader operation. In this instance, if you own a suitable programmer and have a board with and accessable SWD debug port then it should be possible to always recover.

The NVM User Row Mapping configuration bits can either be toggled using a programmer together with an application such as Microchip/Atmel Studio, or programmatically by running a simply sketch that accesses the NVM Controller registers.

If however you board lacks a debug port and you don't have a programmer, then personally I'd steer clear of setting NVM User Row Mapping bits. While you might not actually be bricking the board per se, it does make it difficult to recover from any fault.

Please could you explain what you mean by tick and box in this context?

1 Like

Hi @intstarep

Just to correct my previous comment. The default Arduino config will shutdown the BOD33 operation during low power standby. Only if the BOD33 register's RUNSTDBY (Run On Standby) bit is set, will the BOD33 continue to operate in continuous mode at 25uA typical.

I meant does the problem exist with the MKRs that Thea's article describes?

Do you recommend a change to Arduinos default config for MKRs going into productive devices?
If so, do you have a code snippet?

Many thanks for your detailed answer that goes over my knowledge of the MCU, i just read Thea's article and asked myself if the MKRs are affected.

Hi @intstarep

Unless you're experiencing start-up issues, or are in need of the BOD33 during low power standby, I'd just leave the settings as they are.

Thea was using a different bootloader from the standard Arduino one.

You can check the settings of the BOD33 register for your own MKR board using this sketch:

// System Control BOD33 Register Output
void setup() {
  Serial.begin(115200);                                // Open the native USB port
  while(!Serial);                                      // Wait for the console to open
  
  Serial.print(F("ENABLE: "));                         
  Serial.println(SYSCTRL->BOD33.bit.ENABLE, HEX);      // BOD33 enable bit
  Serial.print(F("HYST: "));
  Serial.println(SYSCTRL->BOD33.bit.HYST, HEX);        // Hysteresis enable bit
  Serial.print(F("ACTION: "));
  Serial.println(SYSCTRL->BOD33.bit.ACTION, HEX);      // Action enable bit - 0: None, 1: Reset, 2: Interrupts
  Serial.print(F("RUNSTDBY: "));
  Serial.println(SYSCTRL->BOD33.bit.RUNSTDBY, HEX);    // Run on standby enable bit
  Serial.print(F("MODE: "));
  Serial.println(SYSCTRL->BOD33.bit.MODE, HEX);        // Mode bit - 0: Continuous Mode, 1: Sampling Mode
  Serial.print(F("CEN: "));
  Serial.println(SYSCTRL->BOD33.bit.CEN, HEX);         // Sampling mode clock enable bit
  Serial.print(F("PSEL: "));
  Serial.println(SYSCTRL->BOD33.bit.PSEL, HEX);        // Sampling clock prescaler division
  Serial.print(F("LEVEL: "));
  Serial.println(SYSCTRL->BOD33.bit.LEVEL, HEX);       // Voltage Level 34mV steps
}

void loop() {}
1 Like

Hi MartinL

Here's the output from a MKR WIFI:

16:33:47.205 -> ENABLE: 0
16:33:47.205 -> HYST: 0
16:33:47.205 -> ACTION: 0
16:33:47.205 -> RUNSTDBY: 0
16:33:47.205 -> MODE: 0
16:33:47.205 -> CEN: 0
16:33:47.205 -> PSEL: 0
16:33:47.205 -> LEVEL: 0

Question: Shouldn't
Serial.println(SYSCTRL->BOD33.bit.ACTION, HEX);
be 1 (Reset)?

Thx! You're a real expert!

Hi @intstarep

That's interesting. My SAMD21 based Arduino Zero boards both have BOD33 activated:

ENABLE: 1
HYST: 0
ACTION: 1
RUNSTDBY: 0
MODE: 0
CEN: 0
PSEL: 0
LEVEL: 7

These are the microcontroller's default BOD33 settings.

According to the SAMD21 datasheet, level 7 puts the voltage threshold typically at around 1.65V.

I'm not sure why your MKR board has these values set to zero?

I read out the values of a MKR1400 and a MKR1500 -> Same result, all values 0.

Here's the output from the ZeroRegs lib (For an expert like you)

--------------------------- SCS
CPUID: REV=0x1 PARTNO=0xC60 ARCH=0xC VAR=0x0 IMPL=0x41
SysTick: ENABLE TICKINT clksource=CPU RELOAD=47999 TENMS=79999 SKEW
irq pri0: PM SYSCTRL WDT RTC EIC NVMCTRL DMAC USB EVSYS SERCOM0 SERCOM1 SERCOM3 SERCOM4 SERCOM5 TCC0 TCC1 TCC2 TC3 TC4 TC5 TC6 TC7 ADC AC DAC PTC I2S
irq pri1:
irq pri2:
irq pri3: SERCOM2
--------------------------- SYSCTRL
OSCULP32K: CALIB=0x10
OSC8M: ENABLE presc=1 CALIB=0x80A frange=8-11MHz
XOSC32K: ENABLE XTALEN EN32K STARTUP=0x6
DFLL: ENABLE mode=closed-loop QLDIS WAITLOCK MUL=1465
VREG:
VREF: CALIB=0x70
--------------------------- GCLK
GEN00: GENEN DFLL48M IDC
GEN01: GENEN XOSC32K
GEN02: GENEN OSCULP32K
GEN03: GENEN OSC8M
GCLK_MAIN: GEN00 (always)
GCLK_DFLL48M_REF: CLKEN GEN01
GCLK_USB: CLKEN GEN00
GCLK_SERCOM2_CORE: CLKEN GEN00
GCLK_ADC: CLKEN GEN00
GCLK_DAC: CLKEN GEN00
--------------------------- EVSYS
CTRL:
--------------------------- PAC
PAC1: DSU
PAC2:
--------------------------- PM
SLEEP: idle=CPU
CPUSEL: /1
APBASEL: /1
APBBSEL: /1
APBCSEL: /1
AHBMASK: CLK_HPBA_AHB CLK_HPBB_AHB CLK_HPBC_AHB CLK_DSU_AHB CLK_NVMCTRL_AHB CLK_DMAC_AHB CLK_USB_AHB
APBAMASK: CLK_PAC0_APB CLK_PM_APB CLK_SYSCTRL_APB CLK_GCLK_APB CLK_WDT_APB CLK_RTC_APB CLK_EIC_APB
APBBMASK: CLK_PAC1_APB CLK_DSU_APB CLK_NVMCTRL_APB CLK_PORT_APB CLK_DMAC_APB CLK_USB_APB
APBCMASK: CLK_SERCOM0_APB CLK_SERCOM1_APB CLK_SERCOM2_APB CLK_SERCOM3_APB CLK_SERCOM4_APB CLK_SERCOM5_APB CLK_TCC0_APB CLK_TCC1_APB CLK_TCC2_APB CLK_TC3_APB CLK_TC4_APB CLK_TC5_APB CLK_ADC_APB CLK_DAC_APB
--------------------------- NVMCTRL
CTRLB: RWS=1 MANW sleepprm=WAKEONACCESS readmode=NO_MISS_PENALTY
PARAM: NVMP=4096 psz=64bytes
LOCK: 1111111111111111
user row: bootprot=0 eeprom_size=16k region_locks=1111111111111111
software calibration: ADC_LINEARITY=0x10 ADC_BIAS=0x7 OSC32K_CAL=0x76 USB_TRANSN=0xA USB_TRANSP=0x1A USB_TRIM=0x7 DFLL48M_COARSE_CAL=0x36
serial # 0x24D8F8A0 0x50504335 0x382E314A 0xFF061507
--------------------------- PORT A
PA08: pmux=SERCOM2:0(i2c:sda) input INEN
PA09: pmux=SERCOM2:1(i2c:scl) input INEN
PA10: input INEN
PA11: input INEN
PA16: input INEN
PA17: input INEN
PA19: input INEN
PA20: input INEN
PA21: input INEN
PA22: input INEN
PA23: input INEN
PA24: pmux=USB:DN
PA25: pmux=USB:DP
PA27: output INEN
PA30: pmux=SWCLK
--------------------------- PORT B
PB08: output INEN
PB09: pmux=ADC:3,Y15 input INEN
PB10: input INEN
PB11: input INEN
PB22: input INEN
PB23: input INEN
--------------------------- ARDUINO PINS
D0: input INEN
D1: input INEN
D2: input INEN
D3: input INEN
D4: input INEN
D5: input INEN
D6: input INEN
D7: input INEN
D8: input INEN
D9: input INEN
D10: input INEN
D11: pmux=SERCOM2:0(i2c:sda) input INEN
D12: pmux=SERCOM2:1(i2c:scl) input INEN
D13: input INEN
D14: input INEN
PA24: pmux=USB:DN
PA25: pmux=USB:DP
PA27: output INEN
D31: output INEN
A7: pmux=ADC:3,Y15 input INEN
--------------------------- SERCOM0 I2C master
CTRLA: ENABLE sdahold=DIS speed=SM<100kHz,FM<400kHz inactout=DIS
CTRLB: ackact=NACK
BAUD: BAUD=0xEF BAUDLOW=0x0 HSBAUD=0x0 HSBAUDLOW=0x0
ADDR: ADDR=0xD6 LEN=0x0
--------------------------- USB
CTRLA: ENABLE RUNSTDBY mode=DEVICE
QOSCTRL: cqos=MEDIUM dqos=MEDIUM
CTRLB: spdconf=FS LPMHDSK=0x0
DADD: 0xA ADDEN
PADCAL: TRANSP=0x1D TRANSN=0x5 TRIM=0x3
ENDPOINT0:
BANK0: eptype=CTRL-out ADDR=0x200009A8 size=64byte EXTREG=0x0
BANK1: eptype=CTRL-in ADDR=0x200007E8 size=64byte
ENDPOINT1:
BANK0: eptype=--disabled--
BANK1: eptype=INT-in ADDR=0x20000828 size=64byte
ENDPOINT2:
BANK0: eptype=BULK-out ADDR=0x20000E88 size=64byte EXTREG=0x0
BANK1: eptype=--disabled--
ENDPOINT3:
BANK0: eptype=--disabled--
BANK1: eptype=BULK-in ADDR=0x200008A8 size=64byte
ENDPOINT4:
BANK0: eptype=--disabled--
BANK1: eptype=--disabled--
ENDPOINT5:
BANK0: eptype=--disabled--
BANK1: eptype=--disabled--
ENDPOINT6:
BANK0: eptype=--disabled--
BANK1: eptype=--disabled--
ENDPOINT7:
BANK0: eptype=--disabled--
BANK1: eptype=--disabled--

Hi @intstarep

Unfortunately, the ZeroRegs lib doesn't print out the SYSCTRL (System Control) module's BOD33 register or the two fuse registers: UserWord0 and UserWord1.

To output the fuse registers and their BOD33 bitfields (ENABLE, ACTION, etc...), just run the following sketch:

// Display the SAMD21 BOD33 fuse settings
void setup() {
  Serial.begin(115200);                                    // Start Serial communication on the native USB port
  while(!Serial);                                          // Wait for the console to open
  
  uint32_t userWord0 = *((uint32_t*)NVMCTRL_USER);         // Read fuses for user word 0
  uint32_t userWord1 = *((uint32_t*)(NVMCTRL_USER + 4));   // Read fuses for user word 1
  
  Serial.println(F("General Fuse Settings:"));
  Serial.print(F("User Word 0: "));
  Serial.println(userWord0, HEX);                          // Display the User Word 0 register
  Serial.print(F("User Word 1: "));
  Serial.println(userWord1, HEX);                          // Display the User Word 1 register
  
  Serial.println(F("BOD33 Fuse settings:"));
  Serial.print(F("ENABLE: "));
  Serial.println((userWord0 & FUSES_BOD33_EN_Msk) >> FUSES_BOD33_EN_Pos, HEX);               // Display the BOD33 Enable
  Serial.print(F("ACTION: "));
  Serial.println((userWord0 & FUSES_BOD33_ACTION_Msk) >> FUSES_BOD33_ACTION_Pos, HEX);       // Display the BOD33 Action
  Serial.print(F("LEVEL: "));
  Serial.println((userWord0 & FUSES_BOD33USERLEVEL_Msk) >> FUSES_BOD33USERLEVEL_Pos, HEX);   // Display the BOD33 Level
  Serial.print(F("HYST: "));
  Serial.println((userWord1 & FUSES_BOD33_HYST_Msk) >> FUSES_BOD33_HYST_Pos, HEX);           // Display the BOD33 Hysteresis
  Serial.println();
}

void loop() {}

Hi @intstarep

I do NOT recommend running the sketch below on your MKR board, since there might be a reason why Arduino disabled the BOD33.

However, here's how to programmatically change the fuses back to the SAMD21's factory default setting:

// Reset the SAMD21's fuse settings back to factory default
void setup() {
  Serial.begin(115200);                                       // Start serial communication on the native USB port
  while(!Serial);                                             // Wait for the console to open
  Serial.println(F("Fuse settings before:"));
  Serial.println((*(uint32_t*)NVMCTRL_USER), HEX);            // Display the current user word 0 fuse settings
  Serial.println((*(uint32_t*)(NVMCTRL_USER + 4)), HEX);      // Display the current user word 1 fuse settings
  NVMCTRL->CTRLB.bit.CACHEDIS = 1;                            // Disable the cache
  NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2;               // Set the address
  NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_EAR |                // Erase the auxiliary user page row
                       NVMCTRL_CTRLA_CMDEX_KEY;
  while(!NVMCTRL->INTFLAG.bit.READY)                          // Wait for the NVM command to complete
  NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK;                 // Clear the error flags
  NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2;               // Set the address
  NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_PBC |                // Clear the page buffer
                       NVMCTRL_CTRLA_CMDEX_KEY;
  while(!NVMCTRL->INTFLAG.bit.READY)                          // Wait for the NVM command to complete
  NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK;                 // Clear the error flags
  *((uint32_t*)NVMCTRL_USER) = 0xD8E0C7FF;                    // Set user word 0 to default fuse settings
  *((uint32_t*)(NVMCTRL_USER + 4)) = 0xFFFFFC5D;              // Set user word 1 to default fuse settings
  NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_WAP  |               // Write to the user page
                       NVMCTRL_CTRLA_CMDEX_KEY;
  while(!NVMCTRL->INTFLAG.bit.READY)                          // Wait for the NVM command to complete
  NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK;                 // Clear the error flags
  NVMCTRL->CTRLB.bit.CACHEDIS = 0;                            // Enable the cache
  Serial.println(F("Fuse settings after:"));
  Serial.println((*(uint32_t*)NVMCTRL_USER), HEX);            // Display the current user word 0 fuse settings
  Serial.println((*(uint32_t*)(NVMCTRL_USER + 4)), HEX);      // Display the current user word 1 fuse settings
}

void loop() {}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.