Category Archives: AVR

Cruising with the ATtiny85

The ATtiny85 is a lovely chip for relatively simple applications. More than enough flash, 512 byte SRAM, 5 usable pins, cheap (half the cost of ATmega328 – 10 cost me £10) and most importantly a small form factor (8 pin chip).

Datasheet: http://electronics.arunsworld.com/?attachment_id=116

Google provides an Arduino core for the ATtiny85. Located here. I have it installed and use it when I need some functions like millis rather than write it myself. However, I have also setup my own core that is absolutely barebones and effectively lets me use the Arduino IDE to write & upload sketches using nothing more than AVR Libc.

All the core contains is a main.cpp that calls setup and loop. It also includes one header file: WProgram.h and I have an Arduino.h that is simply including WProgram.

Below is a BareMinimum sketch that doesn’t do anything but can be used as a template to start a sketch. It includes power saving features that can be tweaked based on what’s needed in the sketch. The sketch compiles to only 150 bytes.

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

void setup() {
  // Turn off unnecessary peripherals

  MCUSR = 0;
  wdt_disable(); // watch dog timer
  ACSR |= _BV(ACD); // Disable analog comparator
  ADCSRA &= ~_BV(ADEN); // Ensure ADC is indeed disabled
  PRR |= _BV(PRTIM0) | _BV(PRTIM1) | _BV(PRUSI) | _BV(PRADC); // shut down timers, USI, ADC
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);

  // do something - enable interrupts, whatever
}

void loop() {
  cli();
  sleep_enable();
  sei();
  sleep_cpu();
  sleep_disable();
}

Useful registers on the ATtiny85:

Register Bits Purpose
 PRR PRTIM1
PRTIM0
PRUSI
PRADC
Writing a logic 1 shuts down power to Timer1, Timer0, USI & ADC.
Note: Google core uses Timer1 to implement delay, millis, micros etc.
 MCUCR ICS01
ICS00
Controls the logic to drive the external interrupt 0 (INT0)
 GIMSK INT0
PCIE
INT0: Enables interrupt on INT0 (by writing 1)
PCIE: Enables pin change interrupt (PCINT[5:0]).
Before setting PCIE though remember to set PCMSK.
 PCMSK PCINT5
PCINT4

PCINT0
Selects whether pin change interrupt enabled on that pin.
 PORTB PORTB5
PORTB4

PORTB0
Set PORT to 0 or 1 to control an output pin.
Set 1 on an input pin to enable pull-up.
 DDRB DDB5
DDB4

DDB0
Set 1 to make a pin an output pin. 0 for input.
 PINB PINB5
PINB4

PINB0
Use to read value of pin.
Write 1 to toggle the value, especially if it’s an output pin.
 TCCR0A WGM01
WGM00
WGM00, WGM01 and WGM02 control the timer mode. eg. Normal, CTC, PWM
Normal implies it counts up to counter overflow.
CTC: clear timer on compare match.
 TCCR0B WGM02
CS02
CS01
CS00
CS: clock select – sets the pre-scaler
No prescaling, 8, 64, 256 & 1024.
 OCR0A Value: Output compare register A.
 TCNT0 Value: Timer/Counter register.
 TIMSK OCIE0A
OCIE0B
TOIE0
OCIE: Output compare match interrupt enable. (write 1).
TOIE0: overflow interrupt enable (write 1)
 ADCSRB ACME Write ACME bit to logic 0 to apply AIN1 as negative input to analog comparator.
Write logic 1 with ADC off to use ADC multiplexer to comparator
 ACSR ACD
ACBG
ACIE
ACIS[1:0]
ACD: analog comparator disable (write 1 to disable)
ACBG: use band gap voltage as positive input. If cleared use AIN0.
ACIE: Interrupt enable (write 1).
ACIS: Logic to determine whether interrupt based on toggle, falling or rising edge.
 ADMUX REFS[2:0]
ADLAR
REFS: Voltage ref selection. Vcc, AREF (PB0), 1.1V, 2.56V
ADLAR: ADC Left Adjust Result.
 ADCSRA ADEN
ADSC
ADATE
ADIE
ADPS[2:0]
ADEN: ADC Enable (write 1 to enable or 0 to disable).
ADSC: start conversion. In free running, this starts the conversion.
ADATE: auto-trigger enable. Trigger set by ADTS in ADDSRB.
ADIE: Interrupt enable.
ADPS: pre-scaler
 ADCSRB ADTS[2:0] Choice of: Free Running Mode
Analog Comparator
Ext. interrupt 0, Timer/Counter CTC or overflow,
Pin Change request.
 ADCL, ADCH Value: depending on ADLAR either left or right justified 10 bits.
 USIDR  Value of USI data. Copy also available at USIBR.
 USIBR  Loaded here when transfer completed.
 USICR USISIE
USIOIE
USIWM[1:0]
USICS[1:0]
USICLK
USITC
USISIE: enables interrupt on start condition (write 1).
USIOIE: counter overflow interrupt enable. 4 bit counter
USIWM: wire mode selection: 3-wire mode, 2-wire mode.
USITC: toggle clock port pin.
USICLK: clock strobe.

List of interrupts available:

 Interrupt  description
 INT0_vect  External Interrupt 0
 PCINT0_vect  Pin change Interrupt Request 0
 TIMER1_COMPA_vect  Timer/Counter1 Compare Match 1A
 TIMER1_OVF_vect  Timer/Counter1 Overflow
 TIMER0_OVF_vect  Timer/Counter0 Overflow
 ANA_COMP_vect  Analog comparator
 ADC_vect  ADC Conversion ready
 TIMER1_COMPB_vect  Timer/Counter1 Compare Match B
 TIMER0_COMPA_vect  Timer/Counter0 Compare Match A
 TIMER0_COMPB_vect  Timer/Counter0 Compare Match B
 USI_START_vect  USI START
 USI_OVF_vect  USI Overflow

Defining a board for the ATtiny85 so it shows up on the boards list:

Setting up a board helps set the fuses (via Burn Bootloader) and also in calling the appropriate core and then uploading to chip.

Here are my settings in boards.txt under hardware/tiny/

attiny85at8.name=ATtiny85 @ 8 MHz
attiny85at8.upload.using=arduino:arduinoisp
attiny85at8.upload.maximum_size=8192

attiny85at8.bootloader.low_fuses=0xE2
attiny85at8.bootloader.high_fuses=0xDE
attiny85at8.bootloader.extended_fuses=0xFF
attiny85at8.bootloader.path=empty
attiny85at8.bootloader.file=empty

attiny85at8.build.mcu=attiny85
attiny85at8.build.f_cpu=8000000L
attiny85at8.build.core=tiny

################################################

attiny85at8ab.name=ATtiny85 @ 8 MHz w/ ABTiny Core
attiny85at8ab.upload.using=arduino:arduinoisp
attiny85at8ab.upload.maximum_size=8192

attiny85at8ab.bootloader.low_fuses=0xE2
attiny85at8ab.bootloader.high_fuses=0xDE
attiny85at8ab.bootloader.extended_fuses=0xFF
attiny85at8ab.bootloader.path=empty
attiny85at8ab.bootloader.file=empty

attiny85at8ab.build.mcu=attiny85
attiny85at8ab.build.f_cpu=8000000L
attiny85at8ab.build.core=abtiny

Installation of cores and boards

This can be done in the folder that has the sketches (eg. ~/Documents/Arduino/) under a folder called hardware.

Folder structure:

  • hardware/tiny/ <– root folder for the files for ATTiny85
  • boards.txt (under root folder above)
  • cores/ <– under root folder it contains the cores
  • cores/tiny <– contains the google core
  • cores/abtiny <– contains my core (main.cpp, WProgram.h etc.)

Slowing down the clock

Sometimes the clock – be it external or internal – is too fast, especially for simple applications (think blinking light) in power saving mode.

I have an example where I use Timer2 as a counter to raise interrupts to do stuff. Even at 8MHz the counter overflows too many times causing the chip to wake up.

Wouldn’t it be nice to slow the clock down so that the chip only wakes up when it needs to?

Well, we can’t slow the clock down but divide it down using a pre-scaler.

The pre-scaler can be set via the CLKPR register. It’s slightly complicated as it’s a 2 step process:

  1. First set CLKPCE while clearing out all other bits
  2. Then set CLKPS[0..3] bits with CLKPCE bit = 0

Example (for a 1/8 pre-scaler):

CLKPR = 0b10000000;
CLKPR = 0b00000011; // or 0x03

Alternatively:

#include <avr/power.h>

clock_prescale_set(x)
Value (CLKPR) Clock Division Factor
  0x01     2
  0x02     4
  0x03     8
  0x04    16
  0x05    32
  0x06    64
  0x07   128
  0x08   256

Pico Power

An active Atmega328 consumes over 10mA of current at around 5V. To reduce power consumption various techniques can be used. Using all the techniques below I was able to reduce power consumption to less than 0.001 mA (my multimeter reads 0.000mA).

Turn off ADC
Set ADEN bit to 1 in register ADCSRA:

ADCSRA &= ~(_BV(ADEN));

Turn off Analog Comparator
Set ACD bit to 1 in ACSR

ACSR &= ~(_BV(ACD));

Turn off the Watchdog Timer
Straightforward but first remember to unset the flags which are set on Reset.

#include <avr/wdt.h>

MCUSR = 0;
wdt_disable();

Turn off power to peripherals
Set bits in the PRR (Power Reduction Register)

PRR = 0b11001111;
Bit Label Purpose
 7  PRTWI  Shut down TWI by writing 1.
 6  PRTIM2  Shut down Timer/Counter2 in synch mode (AS2 = 0).
 5  PRTIM0  Shut down Timer/Counter0 by writing 1.
 4  Reserved (set to 0).
 3  PRTIM1  Shut down Timer/Counter1.
 2  PRSPI  Shut down SPI by writing 1.
 1  PRUSART0  Shut down USART by writing 1.
 0  PRADC  Shut down ADC. ADC must be disabled before shut down.

Sleep whenever possible
Setup the chip in a particular mode. Can be done in setup.

set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Sleep Mode Meaning
 SLEEP_MODE_IDLE  Turn off CPU clock
 SLEEP_MODE_ADC  ADC Noise Reduction. Turn of I/O clock in addition.
 SLEEP_MODE_PWR_DOWN  Power Down. All clocks off. Cannot wake up from timer
 SLEEP_MODE_PWR_SAVE  Allows Timer2 to be running. Power Down + Timer.
#include <avr/sleep.h>
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Setup Power mode

cli();  // Disable interrupts
// Do whatever if needed to confirm we need to go to sleep
sleep_enable(); // Enable sleep (needed to prevent us going to sleep by accident)
sleep_bod_disable();  // Optionally disable the BOD
sei();  // Enable interrupts just before going to sleep
sleep_cpu();  // Go to sleep
sleep_disable(); // When woken up - disable sleep to be on the safe side

Disable BOD when sleeping
This should have been really simple but it’s unnecessarily complex (it seems to me) requiring first setting and then unsetting a bit. The below function may be used (as in example above).

#define sleep_bod_disable() \
{ \
uint8_t tempreg; \
__asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
"ori %[tempreg], %[bods_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" "\n\t" \
"andi %[tempreg], %[not_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" \
: [tempreg] "=&d" (tempreg) \
: [mcucr] "I" _SFR_IO_ADDR(MCUCR), \
[bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \
[not_bodse] "i" (~_BV(BODSE))); \
}