Power Down Sleep with ATtiny441 / ATtiny841

By | January 14, 2015

I have spent some time putting together a firmware to enable Power Down sleep mode in ATtiny441. The examples on the web did not do the trick out of the box. Here is my code with some leftover bits for enabling / disabling peripheral devices when they are used. The MCU goes to Power Down sleep (~1uA) and gets woken up by the Watchdog every second to do its work.

#define F_CPU (4915200UL / 8)
#define BAUD 9600
#define SLEEP_DURATION_BITS (_BV(WDP1) | _BV(WDP2)) //1s
#include <avr/io.h>
#include <util/delay.h>
#include <util/setbaud.h>
#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros
#include <avr/sleep.h>      // Supplied AVR Sleep Macros
#include <avr/interrupt.h>

volatile unsigned char f_wdt = 1;

void uart_init(void) {
    PRR &= ~_BV(PRUSART0);
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;

    #if USE_2X
    UCSR0A |= _BV(U2X0);
    #else
    UCSR0A &= ~(_BV(U2X0));
    #endif

    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);   /* Enable RX and TX */
}

void uart0_transmit( unsigned char data )
{
    while ( !( UCSR0A & (1<<UDRE0)) );        // Wait for empty transmit buffer
    UCSR0A |= (1<<TXC0);                     // clear txc flag
    UDR0 = data;                            // Put data into buffer, sends the data
    loop_until_bit_is_set(UCSR0A, TXC0);    // Wait while the buffer is shifted out
}

void adc_setup (void)
{
    PRR &= ~_BV(PRADC);
    ADMUXB = 0x40; // set ADC reference to internal 2.2V

    ADCSRA |= _BV(ADPS1); // set ADC clock prescaler to 8 (614kHz/8). Should be between 50kHz and 200kHz
    ADCSRA |= _BV(ADEN); //Enable ADC
}

int adc_read (int channel)
{
    // Set the ADC input
    ADMUXA = channel;
    // Start the conversion
    ADCSRA |= (1 << ADSC);

    // Wait for it to finish
    while (ADCSRA & (1 << ADSC));

    int result = ADCL;
    result |= (ADCH << 8);
    return result;
}

void adc_off(void)
{
    //Must set ADCSRA register before PRR, otherwise power saving does not work
    ADCSRA &= ~_BV(ADEN);
    PRR |= _BV(PRADC);
}

void enter_sleep(void)
{
    sleep_enable();

    /* Now enter sleep mode. */
    sleep_mode();

    /* The program will continue from here after the WDT timeout*/
    sleep_disable(); /* First thing to do is disable sleep. */
}

int main(void)
{
    DDRA = 0x77;

    sei();

    /*** Setup the WDT ***/
    // unlock Watchdog configuration register
    CCP = 0xD8;
    /* enable the WD interrupt, set watchdog timeout prescaler value (interrupt period) */
    WDTCSR = _BV(WDIE) | SLEEP_DURATION_BITS;

    CCP = 0xD8; // unlock clock configuration change
    CLKPR = (_BV(CLKPS0) | _BV(CLKPS1)); // change clock prescaler to 8

    set_sleep_mode(SLEEP_MODE_PWR_DOWN);

    SPCR   &= ~_BV(SPE); // Disable SPI
    ACSR0A |= _BV(ACD0); // Disable analog comparators
    ACSR1A |= _BV(ACD1);

    PRR = 0xFF;

    uart_init();

    while(1)
    {
        if(f_wdt == 1) // Woken up up with periodic watchdog interrupt
        {
            /* Don't forget to clear the flag. */
            f_wdt = 0;
            
            //adc_setup();
            //must leave at least 1ms between first adc_setup and adc_read for the internal voltage reference to start.
            //int val = adc_read(ch);
            //adc_off();

            //uart0_transmit(val >> 8);
            //uart0_transmit(val & 0xFF);
        }
        /* Re-enter sleep mode. */
        enter_sleep();
    }
}

ISR(WDT_vect)
{
    sleep_disable();
    if(f_wdt == 0)
    {
        f_wdt=1;
    }
    sleep_enable();
}

I use Atmel Studio 6.2 where I may have made some relevant changes to AVR-gcc include files; I can’t find exactly what was changed at the moment. If this does not compile, please leave a comment and I’ll update the post.

2 thoughts on “Power Down Sleep with ATtiny441 / ATtiny841

  1. Tom

    Thanks! I’ve been looking for an example for the attiny841, I will try this.

    Reply

Leave a Reply

Your email address will not be published.