Power saving for Weather Station

Background: I'm working on a new weather station project to replace my old one. The new circuit eventually have an anemometer (3 rotating cups with magnet + reed switch type), a rain gauge (see-saw bucket with magnet & reed switch) and wind direction vane (magnet + 8 reed switch + 8 different resistors). The brains will be an atmega328P and a RFM95 LoRa transceiver. It will run for (hopefully) a long time on batteries, perhaps 3xAAA or 3xAA NiMh or 1x18650 Li-ion.

For the moment, I am just prototyping with the atmega and the anemometer on breadboard. I am trying to minimise the current draw while still monitoring the anemometer. I have it down to 2.5mA, but would like it to be considerably lower. Will post code this evening.

The atmega is running at 3.3V using 8MHz internal clock and I am saving power by disabling timer1, timer2, SPI, UART, TWI, and using SLEEP_MODE_IDLE. Maybe I could disable BOD and WDT also, not sure if that would make a significant saving at this point. I am leaving timer0 enabled so that millis() works and the chip wakes every 1ms, enables an internal pull-up, polls the anemometer pin, disables the pull-up and goes back to sleep.

I can get much reduced current draw by using SLEEP_MODE_POWER_DOWN and using the watchdog to wake up, but the minimum watchdog period is 15~16ms, which would not allow polling of the anemometer frequently enough for high winds. The anemometer's reed switch closes and opens twice per revolution and 1 revolution per second corresponds to 2.4Km/hr wind speed. To cope with 120Km/hr winds, corresponding to 50 revolutions per second and 200 reed switch openings & closings per second, I need to poll at least once every 5ms. I am also concerned that relying on the watchdog timer for long term (e.g. 15 mins) timing of the anemometer. Is it less accurate/stable than the main internal clock? I would guess so.

So, how can I further significantly reduce current consumption down from 2.5mA?

I have been considering reducing the clock speed to 1MHz, but suspect I may have to do this dynamically at run time so that I can revert to 8MHz when its time to use the LoRa transceiver to transmit sensor readings to TTN.

Before anyone suggests it, yes I have been reading Nick Gammon's excellent guide to power saving and trying to apply those techniques.

Could you have something else count the anemometer pulses? CMOS decade counter or some such so that you could wake the Arduino less frequently?

wildbill:
Could you have something else count the anemometer pulses? CMOS decade counter or some such so that you could wake the Arduino less frequently?

Yes, that's something to consider, and I have been thinking about it and will continue to think about. Two potential problems I have thought of are: 1. less accurate timing because of relying on the watchdog's timer; 2. more complex circuit - another chip to count, plus maybe an external timer chip/RTC. Neither of those are deal-breakers.

Maybe I can count pulses from the anemometer using an internal counter/timer... But I think SLEEP_MODE_POWER_DOWN disables them? Maybe a hardware interrupt pin with a ISR routine to increment a count. Again, would that work in POWER_DOWN? If I have to use IDLE for any of these ideas, I may as well poll the anemometer and rely on the main (not WDT) internal clock to be accurate enough for this project.

Here's the code that reduces current to 2.5mA (when the LED is off):

#include <avr/sleep.h>

#define ANEMOMETER 9

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Enable various power-savings
  //bitClear(ADCSRA, ADEN); // Disable ADC
  power_timer1_disable();
  power_timer2_disable();
  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();
  set_sleep_mode(SLEEP_MODE_IDLE);

}

void loop() {
  pinMode(ANEMOMETER, INPUT_PULLUP);
  digitalWrite(LED_BUILTIN, digitalRead(ANEMOMETER)); 
  pinMode(ANEMOMETER, INPUT);
  
  // Go to sleep until next 1ms timer interrupt
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

Current draw with power savings enabled one by one:

3.3V, 8MHz internal clock, no power saving measures: 5.14mA
SLEEP_MODE_IDLE (so sleeping for 1ms, waking when timer0 interrupt updates millis()): 2.90mA
Power down timer1: 2.87mA
Power down timer2: 2.76mA
Power down ADC: 2.70mA
Power down SPI: 2.59mA
Power down UART: 2.55mA
Power down I2C/TWI: 2.46mA
Disabling ADC: 2.39mA

The circuit is a little unstable. Putting my hand near it changes the consumption by a few tens of uA. Use of breadboard probably introduces quite a lot of stray capacitance and that is probably affected by nearby objects.

I am now trying out using clock_prescale_set():

#include <avr/sleep.h>

#define ANEMOMETER 9

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Enable various power-savings
  bitClear(ADCSRA, ADEN); // Disable ADC
  power_timer1_disable();
  power_timer2_disable();
  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();
  set_sleep_mode(SLEEP_MODE_IDLE);
  clock_prescale_set(clock_div_1);

}

void loop() {
  pinMode(ANEMOMETER, INPUT_PULLUP);
  digitalWrite(LED_BUILTIN, digitalRead(ANEMOMETER)); 
  pinMode(ANEMOMETER, INPUT);
  
  // Go to sleep until next 1ms timer interrupt
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

clock_div_1: 2.35mA
clock_div_2: 2.12mA
clock_div_4: 2.00mA
clock_div_8: 1.94mA
clock_div_16: 1.91mA
clock_div_32: 1.89mA
clock_div_64: 1.89mA
clock_div_128: 1.89mA
clock_div_256: 1.88mA

Problem is, I'm pretty certain these settings will stop millis() from working normally. Perhaps I can change timer0's pre-scaler to compensate.

Confirmed: using clock_prescale_set() affects millis(), stopping it from working normally. timer0 must be taking the output of the main clock pre-scaler, not taking the clock directly, which is what I read somewhere.

With this sketch, the led blinks on a 1s cycle with clock_div_1, but 8s cycle with clock_div_8, showing that millis() is not working normally/correctly:

#include <avr/sleep.h>

#define ANEMOMETER 9

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Enable various power-savings
  bitClear(ADCSRA, ADEN); // Disable ADC
  power_timer1_disable();
  power_timer2_disable();
  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();
  set_sleep_mode(SLEEP_MODE_IDLE);
  clock_prescale_set(clock_div_8);

}

void loop() {
//  pinMode(ANEMOMETER, INPUT_PULLUP);
//  digitalWrite(LED_BUILTIN, digitalRead(ANEMOMETER)); 
//  pinMode(ANEMOMETER, INPUT);

  static unsigned long lastChange;

  if (millis() - lastChange >= 500) {
    lastChange += 500;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 
  }
  
  // Go to sleep until next 1ms timer interrupt
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

I tried adding

TCCR0 |= (1 << CS00);

to remove any pre-scaling for timer0 to get millis() working correctly with clock_prescale_set(clock_div_8). But I get

'TCCR0' was not declared in this scope

(I tried adding "#include <avr/io.h>" but this did not help)

(PS. I'm using IDE 1.8.10)

UPDATE: Got it! I am using an atmega328p, not an atmega328, so there is no TCCR0! Instead there is TCCR0A and TCCR0B. Its the B register that has the pre-scaler controls for timer0.

This corrects timer0's pre-scaler, enabling millis() to work normally:

bitClear(TCCR0B, CS00);

Now my led is flashing at 1s intervals despite setting clock_div_8. The above works because normally, with 8MHz internal clock, both the CS00 and CS01 bits are set in TCCR0B, to achieve 1/64 pre-scale. Clearing CS00, and leaving CS01 set, changes it to 1/8 pre-scale, fixing millis() and the previous sketch blinks at 1s again.

So... here's the code blinking the led at 1s, current draw is just under 2mA:

#include <avr/sleep.h>

const byte anemometerPin = 9;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Enable various power-savings
  bitClear(ADCSRA, ADEN); // Disable ADC
  power_timer1_disable();
  power_timer2_disable();
  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();
  set_sleep_mode(SLEEP_MODE_IDLE);
  clock_prescale_set(clock_div_8);
  bitClear(TCCR0B, CS00); // Set timer0 pre-scaler to 1/8 (Note: CS01 is already set) to keep millis() running noramally.
}

void loop() {
//  pinMode(anemometerPin, INPUT_PULLUP);
//  digitalWrite(LED_BUILTIN, digitalRead(anemometerPin)); 
//  pinMode(anemometerPin, INPUT);

  static unsigned long lastChange;

  if (millis() - lastChange >= 500) {
    lastChange += 500;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 
  }
  
  // Go to sleep until next 1ms timer interrupt
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

Perhaps this is the lowest I can get, unless I go with the unstable watchdog timer and SLEEP_MODE_POWER_DOWN.

Any thoughts/suggestions welcome.

Not exactly what you're asking, but have you considered charging the battery using solar so that the draw isn't so important?

What would be really neat would be if you could find a way to charge using the anemometer :slight_smile:

wildbill:
Not exactly what you're asking, but have you considered charging the battery using solar so that the draw isn't so important?

Yes, definitely an option. But would prefer to lower the consumption to the point where battery changes are so infrequent that its not much of an issue.

wildbill:
What would be really neat would be if you could find a way to charge using the anemometer :slight_smile:

Its an interesting idea. Not straight-forward though, as the rotation rate of such a device would not only depend on the wind speed but on the battery charge level. When the battery is near empty, the loading put on the device by the charging circuits would be more, slowing the rotation speed. When the battery is full, the load would be lighter, and the rotation speed would be higher, even in the same wind conditions.

Progress update:

I have fitted a 32.768KHz crystal to the CLOCK pins of the atmega328p. No accompanying caps seem to be required, as far as I can tell from Google, and it seems to be working without them.

I have now disabled timer1, so millis() no longer works. But I have set up timer2 to receive the clock directly from the external crystal (no pre-scaling). Since timer2 is 8 bit, it overflows 128 times per second, causing an interrupt which I am using to keep track of time, in 1/128th second increments.

This means I can now use SLEEP_MODE_PWR_SAVE instead of SLEEP_MODE_IDLE, but still time periods accurately. Best of all, current consumption is down to < 50uA!

Here is the updated sketch. It blinks the led every 2 seconds.

#include <avr/sleep.h>

const byte anemometerPin = 9;

volatile unsigned long timeNow; // measured in 1/128ths of a second
volatile unsigned long timeBefore;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Enable various power-savings
  bitClear(ADCSRA, ADEN); // Disable ADC
  power_timer0_disable();
  power_timer1_disable();
  //power_timer2_disable();
  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();

  //TCCR2A = 0x00;  //overflow
  bitSet(TCCR2B, CS20);
  bitClear(TCCR2B, CS21);
  bitClear(TCCR2B, CS22); //set timer2 no pre-scaling
  bitSet(TIMSK2, TOIE2); //enable timer2 overflow interrupt
  bitSet(ASSR, AS2); //enable asynchronous mode for timer2, so it uses the 32768Hz external crystal
  //interrupts();
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
}

void loop() {

  if (timeNow - timeBefore >= 256) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    timeBefore = timeNow;
  }  

//  pinMode(anemometerPin, INPUT_PULLUP);
//  digitalWrite(LED_BUILTIN, !digitalRead(anemometerPin)); 
//  pinMode(anemometerPin, INPUT);

  // Go to sleep until next timer interrupt
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

ISR(TIMER2_OVF_vect) {
  timeNow++;
}

This interrupt, 128 times per second, isn't frequent enough. For high wind speeds, I need to poll the anemometer around 250 times per second. So next step will be to double the interrupt rate to 256 per second.

So I'm thinking I could simply set the timer to 128 each time it overflows?

I'd appreciate some expert review of the code above. Is it OK or is there more stuff I should be doing for reliable operation? The data sheet recommends all kinds of precautions around switching to the asynchronous mode. I don't think I'm doing any of those things, but it seems to be working...

ISR(TIMER2_OVF_vect) {
  TCNT2 = 128;
  timeNow++;
}

Nope, that doesn't work at all. No flashing. Hmmm...

Found a useful post by Coding Badly from 10 years ago! This code sets up timer2 so that it hits 128 and automatically resets to zero and generates an interrupt. The led is back to flashing every 2s, but now there are 256 interrupts per second.

#include <avr/sleep.h>

const byte anemometerPin = 9;

volatile unsigned long timeNow; // measured in 1/256ths of a second
volatile unsigned long timeBefore;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Enable various power-savings
  bitClear(ADCSRA, ADEN); // Disable ADC
  power_timer0_disable();
  power_timer1_disable();
  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();

  TCCR2B = 0; //Stop timer2
  bitSet(ASSR, AS2); //enable asynchronous mode for timer2, so it uses the 32768Hz external crystal
  bitSet(TCCR2A, WGM21);  //Set CTC waveform generation mode
  TIFR2 = 0; //Clear the interrupt flag
  bitSet(TIMSK2, OCIE2A); //Enable the compare interrupt
  OCR2A = 128; //Set the compare value
  bitSet(TCCR2B, CS20); //Start the timer (no pre-scale)
  //interrupts();
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
}

void loop() {

  if (timeNow - timeBefore >= 512) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    timeBefore = timeNow;
  }  

//  pinMode(anemometerPin, INPUT_PULLUP);
//  digitalWrite(LED_BUILTIN, !digitalRead(anemometerPin)); 
//  pinMode(anemometerPin, INPUT);

  // Go to sleep until next timer interrupt
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

ISR(TIMER2_COMPA_vect) {
  timeNow++;
}

+1 karma @Coding Badly!

I don't know if you've read it already, but Nick Gammon has a very good read on power savings for the arduino:

https://www.gammon.com.au/power

If you can use something else to poll the wind sensor, you can use a RTC DS3231 with the alarm register switching on the Arduino each hour, do the transmission and than switching itself off again for another hour.

Leroy2007:
I don't know if you've read it already, but Nick Gammon has a very good read on power savings for the arduino:

Gammon Forum : Electronics : Microprocessors : Power saving techniques for microprocessors

As I said in the original post:

PaulRB:
Before anyone suggests it, yes I have been reading Nick Gammon's excellent guide to power saving and trying to apply those techniques.

Leroy2007:
If you can use something else to poll the wind sensor, you can use a RTC DS3231 with the alarm register switching on the Arduino each hour, do the transmission and than switching itself off again for another hour.

Thanks for the suggestion, but that sounds much more complex than my current prototype. I would need the MCU, the RTC and the "something else". My current prototype is only the MCU, but I have accurate timing, polling the anemometer ~250 times per second, and <50uA current draw.

Maybe you could use interrupts for the anemometer?
I did not read the code closely but I believe you need to set OCR2A to 127 to get interrupt every 128 pulses (the timer will count from 0 to OCR2A and overflow to 0 at OCR2A+1 timer tick).
Read the datasheet to know how to use the timer 2 in asynchronous mode properly. (I.e. when you return to the sleep too soon it may cause trouble).
Disable BOD while in sleep too reduce current consumption further.

Smajdalf:
Maybe you could use interrupts for the anemometer?

The reason I have been thinking towards polling rather than interrupts is because I would need to have a pull-up resistor (internal or external) to pull the interrupt pin high so that the reed switch in the anemometer pulls it low when it closes. Unfortunately the reed switch in the anemometer is closed for half of each revolution, so current would be flowing half the time. I could use a very high value pull-up but I thought that might make the circuit vulnerable to noise and false triggering, given the cable to the anemometer is ~2m long. Polling means I can use a lower value pull-up, or an internal pullup, but switch it on only briefly when I poll the pin around every 4ms.

Smajdalf:
I did not read the code closely but I believe you need to set OCR2A to 127 to get interrupt every 128 pulses (the timer will count from 0 to OCR2A and overflow to 0 at OCR2A+1 timer tick).

Thanks, I wondered about that but have not done enough testing to know for sure. Now I know.

Smajdalf:
Read the datasheet to know how to use the timer 2 in asynchronous mode properly. (I.e. when you return to the sleep too soon it may cause trouble).
Disable BOD while in sleep too reduce current consumption further.

Thanks for that also, will try to understand what needs to be done.

Maybe you could use "my" "edge detector" for open drain output from this post:

Interesting, thanks. What values for R1, C1 & R2 did you use?