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.

Indraneel
  • 103
  • 3

1 Answers1

2

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.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81