I'm trying to disable the brownout detection (BOD) of a ATmega328p at runtime, before putting it into power-down sleep like this:
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli(); // disable interrupts
sleep_enable();
sleep_bod_disable();
sei(); // enable interrupts
sleep_cpu();
// ...zZz...zZz...zZz...
sleep_disable();
But this doesn't work. That means I measure no difference in power consumption during power-down sleep: 17 μA before and after (MCU runs at 3.3V)
For comparison, disabling BOD via the efuse (i.e. I set it to 0xff with avrdude) does work as expected, i.e. after that the power-consumption drops to 0.13 μA during power-down sleep. (which pretty much matches what is specified in the datasheet)
Is there some pitfall I'm missing here? Is the above power-down/BOD sequence sub-optimal?
Update 2022年05月30日: To simplify things I've replaced the sleep_bod_disable()
macro/inline assembler call with just:
MCUCR = _BV(BODS) | _BV(BODSE);
MCUCR = _BV(BODS);
This yields the following machine code/assembly:
488: c0 e6 ldi r28, 0x60 ; 96
48a: d0 e4 ldi r29, 0x40 ; 64
4e8: 83 b7 in r24, 0x33 ; 51
4ea: 81 7f andi r24, 0xF1 ; 241
4ec: 84 60 ori r24, 0x04 ; 4
4ee: 83 bf out 0x33, r24 ; 51
4f0: f8 94 cli
4f2: 83 b7 in r24, 0x33 ; 51
4f4: 81 60 ori r24, 0x01 ; 1
4f6: 83 bf out 0x33, r24 ; 51
4f8: c5 bf out 0x35, r28 ; 53
4fa: d5 bf out 0x35, r29 ; 53
4fc: 78 94 sei
4fe: 88 95 sleep
500: 83 b7 in r24, 0x33 ; 51
502: 8e 7f andi r24, 0xFE ; 254
504: 83 bf out 0x33, r24 ; 51
However, this doesn't make a difference, i.e. power consumption is still at 17 μA in power down sleep.
With the original sleep_bod_disable()
call the dis-assembly reads:
4e4: 83 b7 in r24, 0x33 ; 51
4e6: 81 7f andi r24, 0xF1 ; 241
4e8: 84 60 ori r24, 0x04 ; 4
4ea: 83 bf out 0x33, r24 ; 51
4ec: f8 94 cli
4ee: 83 b7 in r24, 0x33 ; 51
4f0: 81 60 ori r24, 0x01 ; 1
4f2: 83 bf out 0x33, r24 ; 51
4f4: 85 b7 in r24, 0x35 ; 53
4f6: 80 66 ori r24, 0x60 ; 96
4f8: 85 bf out 0x35, r24 ; 53
4fa: 8f 7d andi r24, 0xDF ; 223
4fc: 85 bf out 0x35, r24 ; 53
4fe: 78 94 sei
500: 88 95 sleep
502: 83 b7 in r24, 0x33 ; 51
504: 8e 7f andi r24, 0xFE ; 254
506: 83 bf out 0x33, r24 ; 51
See also: compiler explorer
AFAICS, both versions satisfy the constraints specified in the ATmega328p manual:
Writing to the BODS bit is controlled by a timed sequence and an enable bit, BODSE in MCUCR. To disable BOD in relevant sleep modes, both BODS and BODSE must first be set to one. Then, to set the BODS bit, BODS must be set to one and BODSE must be set to zero within four clock cycles.
The BODS bit is active three clock cycles after it is set. A sleep instruction must be executed while BODS is active in order to turn off the BOD for the actual sleep mode. The BODS bit is automatically cleared after three clock cycles.
(Section 10.11.2 MCUCR - MCU Control Register, page 54, emphasis mine)
NB: The sei/sleep/out are all specified as taking one clock cycle and sei is specified to enable interrupts only after the following instruction executed.
-
\$\begingroup\$ I agree to your timing assumptions, the code looks perfect. Does it work if you remove the CLI/SEI instructions just for a test? \$\endgroup\$Jens– Jens2022年05月30日 01:12:00 +00:00Commented May 30, 2022 at 1:12
-
1\$\begingroup\$ @maxschlepzig: Is there a chance, that it wakes up immediately for unknown reason? What is your default clock source while running? \$\endgroup\$Jens– Jens2022年06月01日 00:17:41 +00:00Commented Jun 1, 2022 at 0:17
-
1\$\begingroup\$ @maxschlepzig: I tried your first assembly code on an atMega168PA (sorry, no 328P in house) with watchdog wake up each second and BOD fuses set to 1.8V. I measure 3.85µA consumption. This is expected for the WD, so this code works with this MCU. Without software BOD disable I measure 59.6µA (pretty much). \$\endgroup\$Jens– Jens2022年06月01日 22:44:08 +00:00Commented Jun 1, 2022 at 22:44
-
1\$\begingroup\$ @maxschlepzig: Update, I found a bare 328P and tested it with your code fragment. I read 24µA with BOD and 4.4µA with software BOD disable. What may be the difference? \$\endgroup\$Jens– Jens2022年06月02日 00:36:11 +00:00Commented Jun 2, 2022 at 0:36
-
1\$\begingroup\$ @Jens, thank you for your independent tests. I now managed to properly software disable BOD. See my comment to bigjosh's answer for the unexpected cause ... \$\endgroup\$maxschlepzig– maxschlepzig2022年06月02日 23:01:28 +00:00Commented Jun 2, 2022 at 23:01
1 Answer 1
Can you remove the cli right before the sleep and then take a current reading while the chip is halted? This eliminates any possibility that there is an ISR inadvertently running during the measurement.
Also would be nice to add an interlock at the top of the program so you know the chip is not silently resetting. Something like: pick an unused io pin and then add at the top of the program a 'while (iopin high)'. When you start your test, use a jumper to make the pin low then connect.thr pin to high. This way if the chip resets you will see mA of current indicating that the chip did reset and is stuck in the interlock.
-
2\$\begingroup\$ Ok, I did that, including the interlock. Also, I started a fresh platformio project and stripped everything down for a minimal reproducer. Turns out I couldn't reproduce it anymore, i.e. the software BOD disable suddenly worked as expected (0.2 µA). Root cause: for my previous tests I symlinked some source files and I was affected by an SCons bug where the build system fails to detect changed files through symlinks. Yes, platformio uses SCons under the hood, and while I was using it on the command line, it's pretty non-verbose by default. \$\endgroup\$maxschlepzig– maxschlepzig2022年06月02日 22:57:25 +00:00Commented Jun 2, 2022 at 22:57