0

I am trying to use atmega88 timer2 with a 32.768KHz crystal. I'm using a slightly modified version of Nick Gammon's code. I am using Minicore. No bootloader, 1MHz clock from internal oscillator, BOD disabled. Sleep is interrupted every 8 seconds. I use a counter to count 12 minutes. Since it overshoots about 8 seconds every 6 hours, I am subtracting 8 sec every 6 hours. I hope to eventually make a weather logger with this.

The code is mostly working in it's bare stripped down version, after I add a delay(100) after the digitalWrite. However, I also need Serial. When I enable Serial, it does not sleep anymore. Also, the serial output has a lot of excess weird characters. This happens at all baud rates, even 1200 (serial monitor set to same baud rate as in program). I think some interrupts are being called during digitalWrite and when the usart is powered on and it is messing with the sleep. I am a beginner regarding uC programming, and am really struggling with the datasheet and the code. Any help will be appreciated.

Here is the test code:

#include <avr/sleep.h>
#include <avr/power.h>

const byte tickPin = 4;// (do not use 3 and 11)

//byte intCounter = 0;
//byte leapCounter = 0;

int intCounter = 0;
int leapCounter = 0;

unsigned long prevTime = 0;
unsigned long currTime = 0;

// interrupt on Timer 2 compare "A" completion - does nothing
EMPTY_INTERRUPT (TIMER2_COMPA_vect);

void setup(){
  pinMode (tickPin, OUTPUT);


  Serial.begin(9600);
  prevTime = millis();
  Serial.print(" P: ");
  Serial.println(prevTime);


  // clock input to timer 2 from XTAL1/XTAL2
  ASSR = bit (AS2);

  // set up timer 2 to count up to 256 * 1024  (8 seconds)
  TCCR2A = bit (WGM21);                             // CTC
  TCCR2B = bit (CS20) | bit (CS21) | bit (CS22);    // Prescaler of 1024
  OCR2A =  255;                                     // (zero-relative)

  // enable timer interrupts
  TIMSK2 |= bit (OCIE2A);

  // disable ADC
  ADCSRA = 0;

  // turn off everything we can
  power_twi_disable();
//  power_timer0_disable();
//  power_timer1_disable();
  power_spi_disable();
  power_usart0_disable();
  power_adc_disable ();

  // full power-down doesn't respond to Timer 2
  set_sleep_mode (SLEEP_MODE_PWR_SAVE);

  // get ready ...
  sleep_enable();

}  // end of setup

void loop(){
  sleep_cpu ();
  ++intCounter;

  power_usart0_enable();
  Serial.print("I: ");
  Serial.print(intCounter);
  Serial.print(" L: ");
  Serial.print(leapCounter);
  currTime = millis();
  Serial.print(" C: ");
  Serial.print(currTime);
  Serial.print(" E: ");
  Serial.print(currTime - prevTime);
  Serial.println();
  prevTime = currTime;
  power_usart0_disable();
  delay(1000);

  if ((leapCounter > 30) && (intCounter == 1)){
    leapCounter = 0;
    intCounter = 0;
  }
  if (intCounter > 89){
    ++leapCounter;
    intCounter = 0;
    digitalWrite (tickPin, ! digitalRead (tickPin));
    delay(100);
  }
//  digitalWrite (tickPin, ! digitalRead (tickPin));
//  delay(100);
}  // end of loop

The chip is an Atmega88 (not P). It is drawing the same 6.5uA during sleep, with or without timer0 and timer1. So, I probably don't care if I have to leave them enabled.

I am planning to use a DHT11 and a ds18B20 and write the data to a w25Q64 flash, every 12 minutes. I'd prefer reading it all out via serial port every few weeks, without having to stop the uC.

update

Calling timer2 ISR, instead of handling wake up directly in main loop. Possibly more stable (not affected by Serial and digitalWrite)... but I am not sure.

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

//const byte tickPin = 4;// (do not use 3 and 11)

volatile bool t2flag = LOW;
volatile byte t2counter = 0;

byte leapCounter = 0;

//preventing digitalWrite from causing interrupt (did not help much)
EMPTY_INTERRUPT(PCINT2_vect);

void setup(){
  pinMode (4, OUTPUT);
  pinMode (1, INPUT_PULLUP); //set TX pin default HIGH

  // clock input to timer 2 from XTAL1/XTAL2
  ASSR = bit (AS2);
  // set up timer 2 to count up to 256 * 1024  (8 seconds)
  TCCR2A = bit (WGM21);                             // CTC
  TCCR2B = bit (CS20) | bit (CS21) | bit (CS22);    // Prescaler of 1024
  OCR2A =  255;                                     // (zero-relative)
  // enable timer interrupts
  TIMSK2 |= bit (OCIE2A);
  // disable ADC
  ADCSRA = 0;

  // turn off everything we can
  power_twi_disable();
//  power_timer0_disable();
//  power_timer1_disable();
  power_spi_disable();
  power_usart0_disable();
  power_adc_disable();
  wdt_disable();// reduces 6.5uA to 2.2uA @ 3.3V

  // full power-down doesn't respond to Timer 2
  set_sleep_mode (SLEEP_MODE_PWR_SAVE);
  sleep_enable();
}

//ISR to handle timer2
ISR (TIMER2_COMPA_vect){
  ++t2counter;
  if (t2counter > 89){t2flag = HIGH;}
}

void loop(){
  if (t2flag == LOW){

    power_usart0_enable();
    Serial.begin(9600);
    Serial.print("I: ");
    Serial.print(t2counter);
    Serial.println();
    Serial.flush();
    delay(10);
    Serial.end();
    power_usart0_disable();

    //blip the tickPin every 8 sec
    PORTD |= (1<<PD4);
    delay(10);
    PORTD &= ~(1<<PD4);

    delay(100);//helps a lot
    sleep_cpu();
    return;
  }

  ++leapCounter;  
  if (leapCounter == 30){
    t2flag = LOW;
    return; //delay another 8 sec every 6 hours
  }
  if (leapCounter > 30){leapCounter = 0;}

  t2flag = LOW;
  t2counter = 0;


  power_usart0_enable();
  Serial.begin(9600);
  Serial.print("L: ");
  Serial.print(leapCounter);
  Serial.println();
  Serial.flush();
  delay(10);
  Serial.end();
  power_usart0_disable();

  //long blip every 12 minutes
  PORTD |= (1<<PD4);
  delay(100);
  PORTD &= ~(1<<PD4);
}

The problem is not always present. But when it appears, it happens the same way. After pressing reset button, first led blink is always after 8 seconds. After that, one of three things seem to happen. 1. Second flash is after 8 seconds and everything works as expected. 2. Second flash is after 3 seconds and all flashes after that are also after every 3 seconds. 3. Second flash happens immediately after first flash (and any delay() in the code) and this keeps on happening. In all cases, the serial output also shows the same pattern.

Sometimes it seems that wires connected to the serial or miso, mosi, sck may be causing this, but that is not always the case. The problem may occur even if these wires are not connected. Also, the problem may or may not occur with the same code (e.g. pressing reset after 30 sec may not show any problem, but pressing reset within 5 or 10 seconds leads to problem cases 2 and 3 above. So there is some kind of race condition, or buffer or interrupt noise or something.

I have to program with Arduino as ISP, as the bootloader is unable to sync with the ftdi chip for some reason while uploading sketch. But data transfer from the sketch with serial.print is working fine. So pulling out the miso mosi sck pins everytime is a hassle. Everything is on breadboard. I transferred full circuit to new breadboard (from old loose breadboard) but there is no change. So maybe not a breadboard issue.

  • this is not an Arduino question, but it is very interesting – jsotola May 29 '18 at 20:52
  • Thanks. I didn't know where to ask, and the old arduino had a somewhat similar atmega8, I think (although not a watch crystal). – Indraneel May 29 '18 at 20:57
  • 1
  • @jsotola Thanks for the link. However, that is not what I see. Enabling usart is waking up the uC even from power save (or at least seems like it). Besides, there is also the problem of garbled output even with correctly set baud rates. – Indraneel May 30 '18 at 3:55
3

I see two issues in this code.

The first is that you are trying to send data through an UART that has been powered off. Serial.print() doesn't wait for the data to get out, it merely stores it into a memory buffer. You should wait until everything gets out before switching the UART off. This can be done by calling either Serial.flush() or Serial.end(). I would use the latter, as it feels safer to stop the UART before switching if off. But then you have to Serial.begin() again on wakeup.

To avoid the garbled characters in the serial stream, you could try to either pinMode(1, INPUT_PULLUP); or digitalWrite(1, HIGH);. This way you ensure that the TX line stays HIGH (which is its normal idle state) when the UART goes off.

The second issue is your use of the Arduino timekeeping functions millis() and delay(). These functions rely on an interrupt triggered by Timer 0. If you power off that timer, millis() and delay() won't work. If you don't, SLEEP_MODE_PWR_SAVE does it for you while sleeping. Then those function will work while awake, but millis() will not count the time you spent sleeping.


Edit: There are two interrupts related to the UART that are enabled by Serial.begin():

  • USART_UDRE, “Data Register Empty”, is used for transmission. It fires when the hardware transmit buffer is ready to receive new data. The corresponding ISR's job is to copy one byte from the transmit memory buffer (the one Serial.print() writes into) into the hardware transmit buffer.

  • USART_RX, “Receive Complete”, is used for reception. It fires when a byte is received. The corresponding ISR copies this byte into receive memory buffer (the one Serial.read() reads from).

The first one fires when transmitting. Once you wait for the end of the transmission (say with Serial.flush()), you shouldn't see this interrupt firing anymore.

The second one fires only when receiving. If you are not receiving anything, this interrupt shouldn't fire. If the RX pin is floating, the interrupt could be triggered by external noise. This can be prevented by enabling the internal pullup on the RX pin. Alternatively, you can disable the receiver part of the UART:

UCSR0B &= ~_BV(RXEN0);  // disable the UART receiver

Note that if you stop the UART with Serial.end(), both the transmitter and the receiver are disabled, and you don't get UART-related interrupts anymore.

  • Thanks. I will check if that fixes the serial issues. I did not know that millis would be halted during sleep, inspite of keeping timer0 enabled. Maybe I'll be able to check that if I can get serial to work reliably. However, I'm only using millis() as a rough estimate as it's running from the internal oscillator, so not a dealbreaker if it is messed up. – Indraneel May 30 '18 at 14:39
  • I checked, and your instructions fixed the garbled messages of the serial line. Also, millis() does indeed only count the wake time. However, it seems serial uses interrupts, so it is still causing some problems. I put the timer2 interrupt in an ISR, and it is slightly better. It causes a problem only if the serial wires are connected during reset. – Indraneel May 30 '18 at 23:29
  • @Indraneel: Re serial interrupts: see the edited answer. I don't know what you mean by “I put the timer2 interrupt in an ISR”. – Edgar Bonet May 31 '18 at 8:24
  • Thanks for the update. I'll see if that helps. I think, writing to the LED pin may also be causing an interrupt. It is helping to put a delay after the write. I have edited my question above with the updated code. – Indraneel May 31 '18 at 15:02
  • @Edgar_Bonet Problem is mostly during startup, like if I press the reset pin for too long (1-2 seconds, instead of small tap). But problem shows up only after 1st sleep cycle is complete (after 1st 8 seconds). No difference on adding 0.1uF capacitor to reset pin. – Indraneel May 31 '18 at 16:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.