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.
Thanks! I’ve been looking for an example for the attiny841, I will try this.
Awesome, just tried it and it works great!