USI Serial UART Send on ATtiny

 

In my previous post, USI Serial UART Receive on ATtiny, we discussed how to implement a Serial UART receiver on an Atmel ATtiny using the USI module. This post describes how to implement a simple UART transmitter using the USI module.

UART Signal

The transmitter will be simpler than the receiver as we just need to generate the signal with the correct timing.

serialtiming

Our transmitter will send 8 data bits with one or two stop bits.

#define STOPBITS           1

Calculating the width of a bit

We define the clock speed, F_CPU, and the baud rate. Note F_CPU is already defined in Arduino and may already be defined in your development environment, it’s normally defined as a build symbol.

#define F_CPU              8000000
#define BAUDRATE           9600

We can use these to calculate the number of CPU clock cycles per bit width.

#define CYCLES_PER_BIT     ( F_CPU / BAUDRATE )

If this number is 255 or less then we set the clock source to be the CPU clock, otherwise we will use the prescaler to divide the clock input to Timer/Counter0 by 8.

We use the bottom three Clock Select bits of Timer/Counter0 Control Register B, TCCR0B, to configure the prescaler. A Clock Select value of 1 is for the CPU clock and a value of 2 is for CPU clock divided by 8.

#if (CYCLES_PER_BIT > 255)
#define DIVISOR             8
#define PRESCALE            2
#else
#define DIVISOR             1
#define PRESCALE            1
#endif
#define FULL_BIT_TICKS      ( CYCLES_PER_BIT / DIVISOR )

Choosing the Tx pin

We can configure the USI module to output data on PB0 (SDA), used in Two Wire I2C mode or PB1 (DO), used in Three Wire SPI mode. But it can only read data from PB0 (DI/SDA).

A serial UART has separate transmit, Tx, and receive, Rx, lines. So if we want to enable both transmit and receive on the same device then we will need to use DO/PB1 for output.

ATtiny85 pinout

Sending the first buffer

Finally we need to fill the USI Data Register with some data and start the clock countdown. Our packet is going to be longer than the 8 bit USI data register once we have added start and stop bits. So will need to split our packet across two buffer sends. The first buffer from our send function and will contain the start bit and 7 bits of data. The second buffer will be sent from the USI overflow interrupt and contain 1 bit of data and one or two stop bits.

usi_send_buffers

Because the USI overflow interrupt will be called twice, once after sending the first buffer and again after sending the second buffer, we need a way to tell it which buffer it has just sent, so it can decide what to do next. We will store this state in a volatile variable as it will be accessed from an interrupt.

enum USISERIAL_SEND_STATE { AVAILABLE, FIRST, SECOND };
static volatile enum USISERIAL_SEND_STATE usiserial_send_state = AVAILABLE;

The USI sends the most significant bit (bit 7) and shifts left on each clock cycle, but the UART standard requires that the least significant bit (bit 0) be sent first. So the data needs to be loaded into the USI register in reverse bit order.

We can use the following function to efficiently reverse the bits in a byte:

static uint8_t reverse_byte (uint8_t x) {
   x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
   x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
   x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
   return x;
}

We will use a volatile variable to store the reversed data as it will be accessed from an interrupt.

static volatile uint8_t usiserial_tx_data;

The send routine

The first thing our serial send routine does is set the usiserial_send_state variable to indicate that we are sending the FIRST buffer.

void usiserial_send_byte(uint8_t data)
{
  usiserial_send_state = FIRST;

Then we save the reversed data in our volatile usiserial_tx_data variable.

  usiserial_tx_data = reverse_byte(data);

Configuring the timer

We want to configure Timer/Counter0 to Clear Timer on Compare Match (CTC) mode. We do this by setting the three bits of the Waveform Generation Mode, WGM0, flag to 2 (binary 010). Just for fun bit 0 and bit 1 are in the TCCR0A register and bit 2 is in the TCCR0B register. The other bits of TCCR0A are set to zero to indicate normal port operation. The least significant three bits of TCCR0B are used for the Clock Select mode and the rest of the bits (including WGM0 bit 2) are set to zero.

  TCCR0A = 2<<WGM00;           // CTC mode
  TCCR0B = CLOCKSELECT;        // Set prescaler to clk or clk /8

Then we reset the prescaler to indicate that we changed its configuration and start Timer/Counter0 at zero.

  GTCCR |= 1 << PSR0;           // Reset prescaler
  TCNT0 = 0;                    // Count up from 0

We store the number of bits to count in Output Compare Register A, OCR0A.

  OCR0A = FULL_BIT_TICKS;       // Trigger every full bit width

 Configuring the USI module

To use PB1 (DO) for USI output we select Three Wire SPI mode by setting Wire Mode, USIWM[1:0], to 01 in the USI Control Register USICR and setting the PB1 (DO) data direction to output. We also use the USI Control Register to enable the USI Counter overflow interrupt, USIOIE=1 and select Timer0 Compare Match as the input source, USICS[1:0]=01 and USICLK=0.

  USICR = (0<<USIWM1)|(1<<USIWM0)|       // Select three wire mode so USI output on PB1
    (1<<USIOIE)|                         // Enable USI Counter OVF interrupt.
    (0<<USICS1)|(1<<USICS0)|(0<<USICLK); // Timer0 Compare Match as USI clock source
  DDRB |= (1<<PB1);                      // Configure USI DO, PB1 as output

Now we can load up the USI data register with the start bit (low) and the first 7 bits of reversed data.

  USIDR = 0x00 |                     // Start bit (low)
          usiserial_tx_data >> 1;    // followed by first 7 bits of serial data

Finally we ensure that the USI Overflow Interrupt Flag is cleared and set the counter to increment eight times before overflowing. The 4 bit USI counter can hold a value from 0 to 15 and overflows as it attempts to increment to 16, so we set the starting value of the counter to 16 – 8 = 8.

  USISR = 1<<USIOIF |               // Clear USI overflow interrupt flag
          (16 - 8);                 // and set USI counter to count 8 bits
}

The USI interrupt vector

After the first buffer has been sent by the USI module, the USI overflow interrupt vector will be called with our state variable, usiserial_send_state, set to FIRST. Now we set usiserial_send_state to SECOND to indicate we are sending the second buffer.

ISR (USI_OVF_vect)
{
  if (usiserial_send_state == FIRST)
  {
    usiserial_send_state = SECOND;

We load up the USI Data Register with our last bit of data and the stop bits (high). There should only be one or two stop bits, but we need to set the rest of the bits to something, so we set them high. Interestingly, while one and two are the UART supported number of stop bits, values higher than two would work as the serial UART standard supports periods of line idle (high) between packets.

    USIDR = usiserial_tx_data << 7         // Send last 1 bit of data
            | 0x7F;                        // and stop bits (high)

The number of bits to send is the number of stop bits + 1, so we set the counter to 16 minus this value. The USI Overflow Interrupt Flag is cleared to indicate the interrupt has been processed. If we did not do this the microcontroller would immediately re-invoke the interrupt vector rather than when the counter overflowed.

    USISR = 1<<USIOIF |                 // Clear USI overflow interrupt flag
            (16 - (1 + (STOPBITS)));    // Set USI counter for 1 data bit and stop bits
  }

After the second buffer

After the second buffer has been sent by the USI module, the USI overflow interrupt vector will be called with our state variable, usiserial_send_state, set to SECOND.

Our packet has been sent, so we just need to turn off the USI module returning the Tx pin to an idle high state. Just to be safe we make sure PB1 is still configured as a high output.

  else // usiserial_send_state == SECOND
  {
    PORTB |= 1 << PB1;                  // Ensure output is high
    DDRB |= (1<<PB1);                   // Configure USI_DO as output.
    USICR = 0;                          // Disable USI.

Finally we clear the USI Overflow Interrupt Flag and set our state variable to AVAILABLE, to indicate that our routine is available to send another packet.

    USISR |= 1<<USIOIF;                  // clear interrupt flag
    usiserial_send_state = AVAILABLE;
  }
}

Sending multiple bytes

To keep this tutorial simple our routine only sends one byte at a time. To send multiple bytes we need to wait for our state variable to become AVAILABLE before calling our send routine with the next byte.

   char message[] = "USI Serial\r\n";
   uint8_t len = sizeof(message)-1;
   for (uint8_t i = 0; i<len; i++)
   {
       while (!(usiserial_send_state==AVAILABLE))
       {
           // Wait for last send to complete
       }
       usiserial_send_byte(message[i]);
   }

For safety and convenience we could also wait for usiserial_send_state to become AVAILABLE at the very beginning of our send routine, usiserial_send_byte.

Enhancing for multiple bytes

When sending multiple bytes we stop the USI module after sending the second buffer and reinitialize the timer when we start sending the next packet. This introduces a small gap of idle time between packets, this isn’t a problem as the UART standard supports idle (line high) time between packets.

However if we knew that we were going to send another packet we could just load the USI Data Register with the first buffer of the next packet and set the state variable back to FIRST.

Arduino Timer

When running this code on Arduino we need to borrow the timer used for millis. To avoid disrupting the Arduino timer we should save the state of the timer registers in volatile variables.

#ifdef ARDUINO
volatile static uint8_t oldTCCR0A;
volatile static uint8_t oldTCCR0B;
volatile static uint8_t oldTCNT0;
#endif

We can save these registers in our send routine after setting the usiserial_send_state to FIRST.

#ifdef ARDUINO  
   oldTCCR0B = TCCR0B;
   oldTCCR0A = TCCR0A;
   oldTCNT0 = TCNT0;
#endif

Then we can restore the registers it in the USI Overflow Interrupt vector before setting usiserial_send_state to AVAILABLE.

#ifdef ARDUINO
       TCCR0A = oldTCCR0A;
       TCCR0B = oldTCCR0B;
       TCNT0 = oldTCNT0;
#endif

Arduino will lose the time it takes us to send each byte, approximately 1ms at 9600 baud, which will not be an issue for most programs. However, if you need to maintain Arduino time with more accuracy, see my post on Borrowing an Arduino Timer.

Get the example code

I have uploaded an Arduino sketch to GitHub at:

https://github.com/MarkOsborne/becomingmaker/tree/master/USISerialSend

This example sends the message “USI Serial” every second. Connect your favorite serial device’s Rx pin to PB1/DO.

USI Serial UART Receive on ATtiny

Atmel USI Block Diagram

Many ATtiny microprocessors don’t include a hardware UART, but do include a Universal Serial Interface, USI. The USI module can be used to implement SPI, TWI (also known as I2C) and UART serial interfaces. This post describes how to implement a simple UART receiver using the USI module.

Arduino Software Serial Library

The Arduino core for ATtiny includes a Software Serial library which implements a serial UART interface. This does not take advantage of the USI module and requires 2K of flash. This is probably your best option if you are using Arduino and have plenty of flash available. Using the USI allows you to use faster baud rates and less microcontroller resources, the approach below uses less than 1K of flash.

Atmel app note AVR307

Atmel describe how to use the USI to implement a serial UART for an ATtiny26 in app note AVR307. This provides great information and is worth a read. They also provide the source code at www.atmel.com/images/AVR307.zip.

We will be targeting the ATtiny25, ATtiny45 and ATtiny85 in the code below.

Borrowing Timer/Counter0

The USI module uses Timer/Counter0 which is also used by the hilowtech Arduino core to keep track of time. So if you are using Arduino you will need to borrow Timer/Counter0 and give it back. I’ve written a separate post describing how to do this: Borrowing an Arduino timer.

Calibrating the internal oscillator

You can use an external oscillator or the internal oscillator. In my experience the internal oscillator is factory calibrated accurately enough that you probably won’t need to do any user tuning.

But if you need to tune your internal oscillator, then take a look the following post: Tuning ATtiny internal oscillator.

UART input signal

A serial UART packet consists of a start bit, 5 to 9 bits of data, an optional parity bit and one or two stop bits. But we will just consider the typical configuration of 8 data bits and no parity bit.

When the input line is idle and no data is being transmitted it is held high. You will want to include a pull up resistor in you circuit or enable pull up on the pin so that a floating input doesn’t trigger a false start.

The start of a byte is indicated by pulling the input line low for one bit width. This is immediately followed by each data bit with low representing zero and high representing one with the least significant bit transmitted first. This is then followed by one or two stop bits which are high.

uart trace
UART Oscilloscope trace

Note how the bits appear in the reverse order in the oscilloscope trace above.

Timing a bit width

We are going to use Timer/Counter0 to time a bit width. This is an 8 bit timer, so the its maximum value is 255. We will need to use the Prescaler to adjust the clock input to Timer/Counter0 so that the number of ticks is 255 or less.

We need to choose the right Prescaler value for the regular ATtiny clock speeds of 1MHz, 8MHz and 16Mz and a range of common UART baud rates of such as 9600, 14400, 28800, 57600, 115200 and 230400. For example 9600 baud is 1666 CPU cycles at 16MHz, which is 208.25 when divided by 8, 208 whole CPU cycles with a 0.12% drift.

When setting the baud rate and CPU clock speed you will want the timer to be as accurate as possible, keeping drift below 5%. So higher baud rates won’t be available at lower CPU speeds. For example 230400 baud with a 1MHz clock is 4.34 CPU cycles (1000000/230400) which is just 4 whole CPU cycles with a 34% drift. And that’s before we take code execution time into account.

We define the clock speed, F_CPU, and the baud rate. Note F_CPU is already defined in Arduino and may already be defined in your development environment, it’s normally defined as a build symbol.

#define F_CPU               8000000
#define BAUDRATE            9600

We can use these to calculate the number of CPU clock cycles per bit width.

#define CYCLES_PER_BIT      ( F_CPU / BAUDRATE )

If this number is 255 or less then we set the clock source to be the CPU clock, otherwise we will use the prescaler to divide the clock input to Timer/Counter0 by 8.

We use the bottom three Clock Select bits of Timer/Counter0 Control Register B, TCCR0B, to configure the prescaler. A Clock Select value of 1 is for the CPU clock and a value of 2 is for CPU clock divided by 8.

#if (CYCLES_PER_BIT > 255)
#define DIVISOR             8
#define PRESCALE            2
#else
#define DIVISOR             1
#define PRESCALE            1
#endif
#define FULL_BIT_TICKS      ( CYCLES_PER_BIT / DIVISOR )

Using the USI

The USI module is optimized for use in either Two Wire I2C or Three Wire SPI mode, it doesn’t have a dedicated mode for UART. However the USI module is flexible enough that we can use an internal clock to trigger the USI to left shift the data bits into the USI data register.

We can use a pin change interrupt to detect the beginning of the start bit and then configure the USI to sample the value of the input pin in the center of each of the eight bits. We can then read the back the eight bit values that the USI shifted into the USI register and reverse them to get the original byte value.

Getting setup

ATtiny85 pinoutThe first thing we need to do is ensure that the USI data input pin, DI, is enabled for input. This pin 0 of port B, PB0 in the diagram above.

DDRB &= ~(1 << DDB0);          // Set pin PB0 to input
PORTB |= 1 << PB0;             // Enable internal pull-up on pin PB0

This is equivalent to the following Arduino function call:

pinMode(0, INPUT_PULLUP);

We will also ensure that the USI is disabled at this point.

USICR = 0;                     // Disable USI

Finally we want to enable pin change interrupts on pin PB0 so we can trigger at the beginning of the start bit.

GIMSK |= 1<<PCIE;               // Enable pin change interrupts
PCMSK |= 1<<PCINT0;             // Enable pin change on pin PB0

UART start bit

The pin change interrupt, PCINT0_vect, will fire when any pin on port B with pin change enabled in PCMSK changes from low to high or high to low. We are only interested on a high to low, falling edge, on pin PB0. We will disable pin change on the data input pin while we are reading the byte, so we just need to check whether PB0 is low.

ISR (PCINT0_vect)
{
  uint8_t pinbVal = PINB;
  if (!(pinbVal & 1<<PINB0))   // Trigger if DI is Low
  {
    onSerialPinChange();
  }
}

Move to the middle

To be resilient to any clock drift we will want the USI to take a sample in the middle of each bit. So we need to delay for half a bit width before starting the USI module.

#define HALF_BIT_TICKS      ( FULL_BIT_TICKS / 2 )

We should also account for the number of CPU cycles the interrupt vector and our code introduce between the beginning of the start bit and us starting the USI. I used the Atmel Studio simulator to get numbers for my code.

#define START_DELAY         ( 65 + 42 )
#define TIMER_START_DELAY   ( START_DELAY  / DIVISOR )

First we need to the disable pin change interrupts as we don’t want to trigger our start bit interrupt vector while we are reading data bits.

void onSerialPinChange() {
  GIMSK &= ~(1<<PCIE);            // Disable pin change interrupts

We configure Timer/Counter0 to Clear Timer on Compare Match (CTC) mode. We do this by setting the three bits of the Waveform Generation Mode, WGM0, flag to 2 (binary 010). Just for fun bit 0 and bit 1 are in the TCCR0A and bit 2 is in TCCR0B, but as bit 2 is being set zero you won’t see it explicitly set in the code. The other bits of TCCR0A should be set to zero to indicate normal port operation. The least significant three bits of TCCR0B are used for the Clock Select mode and the rest of the bits (including WGM0 bit 2) should be set to zero.

  TCCR0A = 2<<WGM00;              // CTC mode
  TCCR0B = CLOCKSELECT;           // Set prescaler to cpu clk or clk/8

Then we can reset the prescaler to indicate that we changed its configuration and start Timer/Counter0 at zero.

  GTCCR |= 1 << PSR0;             // Reset prescaler
  TCNT0 = 0;                      // Count up from 0

We store the number of bits to count in Output Compare Register A, OCR0A. There is no code here to check that HALF_BIT_TICKS is actually greater than TIMER_START_DELAY, so be careful with choosing values for F_CPU, BAUDRATE and TIMER_START_DELAY.

  OCR0A = HALF_BIT_TICKS - TIMER_START_DELAY;

Finally we enable the output compare interrupt.

  TIFR = 1 << OCF0A;              // Clear output compare interrupt flag
  TIMSK |= 1<<OCIE0A;             // Enable output compare interrupt
}

Start the USI

When we reach the middle of the start bit, the Timer/Counter0 Compare Match A interrupt vector will fire.

ISR (TIMER0_COMPA_vect) {

First we disable the Compare Match A interrupt.

  TIMSK &= ~(1<<OCIE0A);          // Disable COMPA interrupt

We are going to configure the USI to sample every time Timer/Counter0 has counted up from zero to the value in Output Compare Register A, so we initialize the counter to zero and OCR0A to the full bit width.

  TCNT0 = 0;                      // Count up from 0
  OCR0A = FULL_BIT_TICKS;         // Shift every bit width

We configure the USI using the USI Control Register, USICR.

We want be notified when a byte has been read, so we set Counter Overflow Interrupt Enable, USIOIE, so that the USI_OVF interrupt will be called when the 8 bits have been shifted into the USI register.

We are not using either of the wire modes directly so we set the Wire Mode, USIWM, to zero indicating the automatic wire mode functionality is disabled.

To indicate we are using Timer/Counter0 Compare Match as the clock source we set Clock Source Select, USICS to 1.

  USICR = 1<<USIOIE | 0<<USIWM0 | 1<<USICS0;

Finally we start the USI using the USI Status Register, USISR.

The bottom four bits hold the USI Counter Value (0 to 15) and we want the overflow interrupt to be called after 8 bits have been read. The USI will still shift data into the USI Data Register on the Timer/Counter0 Compare Match trigger that results in an overflow, so we preset USICNT to 8.

We write 1 to the USI Overflow Interrupt Flag, USIOIF,  to clear the flag and ensure the interrupt doesn’t trigger immediately.

  USISR = 1<<USIOIF | 8;
}

Reversing a byte

The bits are going to be in the USI Data Register in reverse order, so we are going to need some code to reverse the bits. Needing to reverse a byte is a common problem so people have spent time producing the most efficient code to do this, so rather than write our own, we just borrow a common approach:

uint8_t ReverseByte (uint8_t x) {
    x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
    x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
    x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
    return x;
}

Reading the result

When USI has sampled 8 bits the USI counter will overflow and the USI Overflow interrupt USI_OVF will be called. The first thing we do is grab the received byte from the USI Data Register, USIDR and disable the USI.

ISR (USI_OVF_vect) {
  uint8_t usiByte = USIDR;
  USICR  =  0;                    // Disable USI

Then you will need to do something with the result, as we are still in an interrupt you will probably want to store the result in a volatile variable and then read and act on it in your main loop. Below I just call serialReceived function with the received data.

  serialReceived(ReverseByte(temp));

Now we are done we clear the Pin Change Interrupt Flag, by writing 1 to PCIF in the General Interrupt Flag register, GIFR, so the interrupt doesn’t trigger immediately. And we re-enable pin change interrupts so we are ready to receive the next byte by setting the Pin Change Interrupt Enable bit in the General Interrupt Mask Register, GIMSK.

  GIFR = 1<<PCIF;                 // Clear pin change interrupt flag
  GIMSK |= 1<<PCIE;               // Enable pin change interrupts again
}

We are actually in the middle of the last bit and if this is zero then a pin change interrupt will be called at the beginning of the stop bit which is high. However we only call onSerialChanged() if the state of the pin is low, so it will be ignored.

A one byte buffer

A really simple implementation of serialReceived() is to store a single byte, which is read by the main loop. If we receive a new byte before the main loop has read the last one then we just overwrite the value. A simple implementation like this is might be fine if we were only using the serial input to set the brightness of an LED say.

volatile bool serialDataReady = false;
volatile uint8_t serialInput;

void serialReceived(uint8_t data)
{
    serialDataReady = true;
    serialInput = data;
}

bool readSerialData(uint8_t* pData)
{
    if (serialDataReady)
    {
        *pData =serialInput;
        serialDataReady = false;
        return true;
    }
    return false;
}

The main loop calls readSerialData() to whenever it is ready to receive data. If the return value is true then new data is available in the location passed in the pData argument. For example:

unsigned char serialInput;
if (readSerialData(&serialInput))
{
  ledUpdate(serialInput);
}

Get the example code

I have uploaded an Arduino sketch to GitHub at:

https://github.com/MarkOsborne/becomingmaker/tree/master/USISerial

This example sets the PWM duty load of PB4 to the value of the byte read from the UART input SDA. Connect your favorite serial devices Tx pin to SDA and an LED (plus current limiting resistor) to PB4 and you can set the brightness of the LED by sending bytes of serial.

USI Serial Send

If you want to transmit serial data from an ATtiny using the USI then take a look at my blog post USI Serial UART Send on ATtiny.

Sources of inspiration:

Thanks to @Atmel for publishing app note AVR307 and the source code at www.atmel.com/images/AVR307.zip.

Thanks to @technoblogy for the post Simple ATtiny USI UART which inspired me to write my own based on AVR307.

And of course the ATTiny24/45/85 Datasheet is an essential resource.

Tuning ATtiny internal oscillator

square wavesThe internal oscillator of an ATtiny can be inaccurate and might require tuning.

This post discusses how to do that if you have access to an oscilloscope or a frequency counter.

An accurate clock speed is important if you are doing timing critical operations such as serial UART communication.

The internal oscillator

ATtiny microprocessors can use an internal RC oscillator or an external crystal oscillator. External crystal oscillators are more accurate, but require two pins. For these low pin count devices it can be beneficial to use the internal oscillator.

Atmel claim the internal oscillator is factory calibrated to +/-10% at 25 degrees centigrade and three volts. In my experience they are actually calibrated to +/- 1%.

Higher temperatures will increase the clock rate and higher voltages will decrease the clock rate. My ATtiny85 microcontrollers run about 1.1% slower at 5V.

For serial UART you’ll probably need to be within 5% (less than half a bit width drift over one start and 8 data bits), so even a factory calibrated ATtiny running at 9600 baud will work fine even at 5V. However there are times when you might want the internal oscillator more finely tuned.

The oscillator calibration register, OSCCAL

Tuning of the internal oscillator is controlled by the oscillator calibration register, OSCCAL. When the microcontroller starts the factory calibration value is automatically loaded into the OSCCAL register. You cannot modify the factory calibration value, but your program can change the OSCCAL register at runtime.

So tuning the oscillator is as simple as adjusting OSCCAL at the beginning of your program. For example I added the following to my code for an ATtiny85 running at 5V.

OSCCAL += 3;

Using a delta adjustment of the factory calibrated value can be better than baking in the actual OSCCAL value if you don’t want to tune each chip individually.

For example I know that Atmel did a good job calibrating the oscillator at 3V, but I will be running at 5V so the oscillator will run slightly slower at that voltage. If adding 3 to OSCCAL works for one chip, it will probably work reasonably well for every chip.

Using EEPROM

For most hobbyists it is sufficient to add an OSCCAL adjustment to our code. Even if you have to change that line of code from chip to chip, you are only working with a handful of chips.

However this would be impractical in a production environment where we could be working with hundreds or thousands of chips.

For a production run, we would want to write the value into EEPROM during calibration and copy it from EEPROM to the OSCCAL register at the beginning of the program.

In Arduino we can use the EEPROM library:

#include <EEPROM.h>
OSCCAL = EEPROM.read(0);

In avr-libc we can use the avr/eeprom.h routines:

#include <avr/eeprom.h>
OSCCAL = eeprom_read_byte((uint8_t*)0x00);

Measuring oscillator accuracy

XMEGA XprotolabThere are approaches that involve comparing the clock speed to an external clock source, but if you have access to an oscilloscope or a frequency counter then its simpler to just measure the clock speed directly.

I have a tiny 1″ $49 Gabotronic XMEGA Xprotolab oscilloscope that does the job nicely.

So then we just need to generate a specific frequency on one of the pins, measure it, adjust OSCCAL up or down accordingly and try again.

Using the CKOUT fuse

If you are comfortable setting fuses, then the simplest approach is to set the CKOUT fuse (bit 6 of the LOW fuse register) which will send the clock signal to PB4. Then you can use your oscilloscope or frequency counter to see how close you are to the selected frequency.

I use Atmel Studio and an Atmel-ICE Programmer/Debugger to set this fuse.

Atmel Stdion CKOUT fuse

WARNING: You can “brick” your ATtiny by setting the fuses incorrectly, for example by selecting an external crystal when you don’t have one connected. In the worst case you could make it impossible to reprogram your ATtiny without a special high voltage programmer.

A safer approach

If you don’t want to mess with fuses, then an alternative is to load a small program that oscillates a pin at a measureable frequency. I like to use 10Khz as it’s a nice round number and fast enough for calibration.

We can do this with a PWM output or with a Timer/Counter compare match. The PWM approach is slightly less code, but I am going to use the Timer/Counter approach as I think the code is slightly easier to understand.

The following program uses Timer/Counter0 Comparator A to generate a 10KHz 50% load on pin PB4 for an ATtiny25/45/85. It generates 10KHz whether the ATtiny is running directly off the internal oscillator at 8Mhz or is running at 1MHz because the CKDIV8 fuse is set.

Using Arduino:

// Timer/Counter0 Compare Match A interrupt handler 
ISR (TIMER0_COMPA_vect) {
   PORTB ^= 1 << PINB4;        // Invert pin PB4
}
 
void setup() {
    OSCCAL += 3;                // User calibration
    pinMode(4,OUTPUT);          // Set PB4 to output
    TCNT0 = 0;                  // Count up from 0
    TCCR0A = 2 << WGM00;        // CTC mode
    if (CLKPR == 3)             // If clock set to 1MHz
        TCCR0B = (1<<CS00);     // Set prescaler to /1 (1uS at 1Mhz)
    else                        // Otherwise clock set to 8MHz
        TCCR0B = (2<<CS00);     // Set prescaler to /8 (1uS at 8Mhz)
    GTCCR |= 1 << PSR0;         // Reset prescaler
    OCR0A = 49;                 // 49 + 1 = 50 microseconds (10KHz)
    TIFR = 1 << OCF0A;          // Clear output compare interrupt flag
    TIMSK |= 1 << OCIE0A;       // Enable output compare interrupt
}
 
void loop() {}

Using avr-libc:

 #include <avr/interrupt.h>
 
 // Timer/Counter0 Comparator A interrupt vector
 ISR (TIMER0_COMPA_vect) {
     PORTB ^= 1<< PINB4;         // Invert pin PB4
 }
 
 int main(void)
 {
     OSCCAL += 3;                // User calibration
     DDRB = 1 << PINB4;          // Set PB4 to output
     TCNT0 = 0;                  // Count up from 0
     TCCR0A = 2<<WGM00;          // CTC mode
     if (CLKPR == 3)             // If clock set to 1MHz
         TCCR0B = (1<<CS00);     // Set prescaler to /1 (1uS at 1Mhz)
     else                        // Otherwise clock set to 8MHz
         TCCR0B = (2<<CS00);     // Set prescaler to /8 (1uS at 8Mhz)
     GTCCR |= 1 << PSR0;         // Reset prescaler
     OCR0A = 49;                 // 49 + 1 = 50 microseconds (10KHz)
     TIFR = 1 << OCF0A;          // Clear output compare interrupt flag
     TIMSK |= 1<<OCIE0A;         // Enable output compare interrupt
     sei();                      // Enable global interrupts
     
     while (1) {}
}

 

Borrowing an Arduino Timer

TimerIf you want to use a microcontroller timer for your own purposes and you are using Arduino you might find that you need to borrow a timer from the Arduino core and then give it back.

The ATtiny85 (and lower memory ATtiny45, ATtiny25 variants) only have two timers and one is used by Arduino to keep track of time. This post explains how you can borrow that timer from Arduino and give it back.

Microcontroller timer counters

Microcontrollers contain a fixed number of timer-counter modules which can be used for various tasks such as driving a PWM output on a pin, driving a Universal Serial Interface (USI), or calling an interrupt to keep track of time passing. The ATTiny85 has two such timers Timer/Counter0 and Timer/Counter1.

The ATtiny85 Timer/Count0 has two Timer Counter Control Registers called TCCR0A and TCCR0B. These registers control the clock source, the prescaler, the output compare units and the PWM waveform generator.

The counter value for Timer/Counter0 is stored in the Timer Counter Register TCNT0.

Timer counter interrupts

The ATtiny Arduino core is using Timer/Counter0 and the Timer/Counter0 Overflow interrupt, TIMER0_OVF, to keep track of time for millis() and micros(). You can verify this by looking at the wiring.c source code in your Arduino core.

Timer/Counter0 can trigger two other interrupts, Compare Match A and Compare Match B (TIMER0_COMPA and TIMER0_COMPB) which are still available to use for your purposes. These trigger when Timer/Counter0 hits the value specified in Output Compare Registers OCR0A or OCR0B.

You can avoid triggering the TIMER0_OVF interrupt, used by Arduino, from being inadvertently called by disabling the overflow interrupt vector.

TIMSK &= ~(1<<TOIE0);

And enabling it when you are finished.

TIMSK |= 1<<TOIE0;

Or by using a Compare Match and never letting Timer/Counter0 overflow.

When we borrow Timer/Count0 for a little while, say to do accurate timing for a serial or IR input, we will probably need to change the timer counter control registers TCCR0A and TCCR0B.

So we can save these values along with the current counter value, TCNT0, before we borrow Timer/Counter0:

// Save Timer/Counter0 values
oldTCCR0B = TCCR0B;
oldTCCR0A = TCCR0A;
oldTCNT0 = TCNT0;

And restore them when we are finished:

//Restore old Timer/Counter0 values
TCCR0A = oldTCCR0A;
TCCR0B = oldTCCR0B;
TCNT0 = oldTCNT0;

Lost Time

This simple approach is perfectly acceptable, but there will be a small amount of time lost. For example to receive a byte of serial at 9600 baud takes approximately one millisecond, so every time we borrowed Timer/Counter0 to receive a byte, Arduino millis() would lose a millisecond.

If you were using millis() for something like button debouncing, where you want to ensure a button stays down for 50 milliseconds before registering a click, then losing a millisecond here and there isn’t going to make much difference to your application.

However if you really want Arduino millis() and micros() to remain accurate then we will need to fix up Arduino’s internal record of time before returning control to Arduino.

Arduino millis and micros

Looking at wiring.c there are two variables used to record the passage of time that we can gain access to using extern.

extern volatile unsigned long timer0_millis;
extern volatile unsigned long timer0_overflow_count;

The first variable, timer0_millis is the number that millis() returns, it is incremented by MILLIS_INC every time Timer/Counter0 overflows.

MILLIS_INC is defined in wiring.c as follows, you will need to redefine these in your code.

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

The second variable, timer0_overflow_count, is used in combination with the Timer/Counter0 counter value, TCNT0, to calculate the value returned by micros(), which we can summarize as:

((timer0_overflow_count<<8) + TCNT0) * (64 / clockCyclesPerMicrosecond())

Here timer0_overflow_count*256 + TCNT0 gives us the recorded number of Timer/Counter0 increments.

The Arduino core sets the prescaler to 64 which causes Timer/Counter0 to increment once every 64 clock cycles, so this is multiplied by 64 to get the number of clock cycles and divided by clockCyclesPerMicrosecond() to get the number of microseconds.

Recovering lost time

To adjust the Arduino time we need to adjust Arduino’s record of the number of Timer/Counter increments represented by timer0_overflow_count*256 + TCNT0. We need to increment this by the number of microseconds borrowed, delta, multiplied by clockCyclesPerMicrosecond(), to get the number of clock cycles, divided by 64, to get the number of timer increments.

unsigned int newCounter = oldTCNT0 +
    delta / (64 / clockCyclesPerMicrosecond() );
timer0_overflow_count += (newCounter>>8);
TCNT0 = (byte)newCounter;

And for each time that Timer/Counter0 would have overflowed, we need to increment timer0_millis by MILLIS_INC.

unsigned long m = timer0_millis;
m += MILLIS_INC * (newCounter>>8);
timer0_millis = m;

This approach will allow you to fix up Ardunio time to the nearest Timer/Counter0 tick which is 8 microseconds for an ATTiny85 running at 8Mhz.