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.