Introduction
This project improves on a previous effort to build a low power wireless
sensor node that can run for up to a year off a pair of AAA batteries. The
target current consumption is 85 micro amps or better (assuming a 750mAh battery).
Achieving this low level of current consumption required some careful programming
and there were some interesting results along the way. The biggest difficulty however
proved to be performing a reliable measurement of average current without access
to advanced lab equipment. Measurements of various kinds were made and I *believe*
the target current levels have been achieved.
The NRF24L01 code is the same as a previous project and is documented here.
Just as in that project and Intel Galileo Gen2 was used as a base station to receive the radio transmission.
The circuit

The STM32F030F4 is connected to the NRF24L01+ module as shown above. The ISP
and RESET buttons are used to put the device into ISP programming mode allowing firmware
to be downloaded using lpc21isp. A 100ohm resistor is placed in series in the power supply
return path allowing the current to be measured using oscilloscopes and similar devices.
A 33uF capacitor is also placed across the power supply to limit the changes to Vdd when
the NRF module pulls lots of current (spurious resets can result otherwise). A pair of AA
batteries was used to supply the circuit during testing.
The code
The over-riding goal of the code is to limit the current consumption of the MCU and the NRF
radio module. The main mechanism of achieving this is to make the MCU and radio sleep as much as possible.
The MCU supports different types of sleep - regular and deep. Regular sleep stops the CPU but leaves
peripheral clocks running - this does not reduce the current flow enough. Deep sleep stops the CPU,
peripheral clocks and also allows the internal voltage regulator to be switched to a low power mode.
Current consumption in this mode is VERY low - typically less than 10 microamps according to the datasheet.
The following function puts the MCU to sleep:
void low_power_mode()
{
// Turn off GPIOB,A and F
NRFWriteCE(0); // turn radio off
RCC_AHBENR &= ~(BIT17+BIT18+BIT22);
// Turn off ADC
haltADC();
RCC_CFGR |= 0xf0; // drop bus speed by a factor of 512
PWR_CR |= BIT0; // switch voltage regulator to low power mode
cpu_sleep(); // stop cpu
}
By default, the sleep mode is the shallow variety. Bit2 of the System control register must be set
to enable deep sleep as follows
SCR |= BIT2; // enable deep sleep
Coming out of a deep sleep requires an external or RTC interrupt. I didn't want to add any circuitry
to generate a wakeup signal as this would draw more power so the internal RTC was used to wake the MCU
about once every second. The RTC code is as follows (the order that you write to the RTC registers matters a lot).
void RTCISR(void)
{ // executed in response to RTC alarm
RTC_ISR &= ~BIT8;
EXTI_PR |= BIT17;
}
void initRTC()
{
// Turn on power control circuit clock
RCC_APB1ENR |= BIT28;
// Turn on LSI
RCC_CSR |= BIT0;
// Wait for LSI to be ready
while ( (RCC_CSR & BIT1) == 0);
// Unlock the RTC domain
PWR_CR |= BIT8; // set DBP in PWR_CR
RCC_BDCR |= BIT16; // put RTC power domain into reset
RCC_BDCR &= ~BIT16; // take it back out of reset
// Turn the RTC on, use LSI
RCC_BDCR |= BIT15 + BIT9;
RCC_BDCR &= ~BIT8;
// Unlock the RTC
RTC_WPR = 0xca;
RTC_WPR = 0x53;
// RTC Initialization procedure (see reference manual)
RTC_ISR |= BIT7; // set INIT bit
while ((RTC_ISR & BIT6)==0); // wait for init to start
ISER |= BIT2; // enable RTC IRQ in NVIC
// Alarm interrupt configuration
RTC_CR = 0;
RTC_ALRMAR = BIT31+BIT23+BIT15+BIT7; // ignore all fields -> alarm every second (approx)
RTC_ALRMASSR = 0x0f000000; // match all sub second bits
RTC_CR |= BIT12+BIT8; // Enable alarm and alarm interrupt
RTC_ISR &= ~BIT7; // clear INIT bit
// RTC alarm is an EXTI interrupt in fact so need to configure this too
EXTI_IMR |= BIT17; // RTC is triggered via EXTI17,18 or 19
EXTI_RTSR |= BIT17;
}
Following an RTC interrupt, the MCU must be woken up. This is done with the following function:
void resume_from_low_power()
{
RCC_CFGR &= ~0xf0; // speed up to 8MHz
// Turn on GPIOB,A and F
RCC_AHBENR |= BIT18+BIT17+BIT22;
NRFWriteCE(1); // turn radio back on
//Turn on ADC
resumeADC();
}
The main program loop looks like this:
while(1)
{
i = readADC();
haltADC();
payload[1] = i & 0xff;
payload[0] = (i >> 8);
NRFWriteData(10,payload);
//GPIOF_ODR ^= 0x1; // uncomment and attach an LED to check timing
low_power_mode(); // This will halt cpu and wait for an IRQ
resume_from_low_power();
}
Upon execution of the function low_power_mode() the MCU stops and drops down
to a low power state (the NRF radio may run on a little after this as it completes the transmission). When the
interrupt happens, the resume_from_low_power() function is executed and the main loop runs again.
Results
The main goal is keep the current down. Current flow was measured first without a capacitor and looked like
this:

For most of the time, the current flow was near zero. It jumps to 500uA when the MCU resumes from low power state and again
to 13.7mA during the radio transmission. Adding a power supply capacitor proved necessary as the volt drop across the 100 ohm
sensing resistor tended to reset the MCU. This changed the current flow waveform to this:

Measuring a pulsed current accurately like this isn't particularly easy. A multimeter (cheap) shows a current that varies between
about 55uA and 150uA. Measuring with the Bitscope micro introduced offsets that were not easy to deal with convincingly. An old
analogue scope produced what I felt to be credible results indicating a sleeping (quiescent) current of about 75uA. A further
measurement was made using a Tiva C Launchpad board. The Launchpad sampled the current waveform at 100kHz and accumulated a
count of the charge being transferred. This indicated an average current of about 90uA. Taking all these results together I
conculeded that the goal had *probably* been met. One interesting thing to note: If the base station goes offline, the NRF in the
wireless node retries the transmission 15 times before giving up. This more than tripled the average current demand which make
me wonder at the wisdom of requiring all transmissions to be ACK'd.
Downloads
The code is available for download here. It includes some modules that were used in testing as
well as the node.js code that runs on the Intel Galileo 2 base station.
Back to home Baremetal ARM page