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 
	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
	// Turn on LSI
	// 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;
	// 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
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
	NRFWriteCE(1); // turn radio back on	
	//Turn on ADC 
The main program loop looks like this:
	i = readADC();
	payload[1] = i & 0xff;
	payload[0] = (i >> 8);
	//GPIOF_ODR ^= 0x1;    // uncomment and attach an LED to check timing				
	low_power_mode();	// This will halt cpu and wait for an IRQ				
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.


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.


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