While I use this on the Attiny 1634, it should work on any Attiny that has a free output pin and an 8 bit timer.

Here is a link to my basic Attiny Circuit diagram: Attiny 1634 Base Circuit

This code is taken almost verbatim from AVRFREAKS and was originally written by Jeroen Lodder. I’ve massaged it just a bit for my purposes- most importantly to increase the bit accuracy. I was getting errors running at the internal 8mhz so I tried adding a crystal. Didn’t see much difference until I upped the IRQ rate and just ran the state machine every eighth cycle. Errors went away. So turns out you don’t need a crystal to get an accurate serial out, just tweak the code. Anyhow, here is the source if you are interested. I have it set to output on pin 17 of the Attiny 1634, you can change that to any I/O pin you wish in the header file.

(NOTE: since I’ve done this code I have discovered that inadvertently, I have been using the Attiny 1634 chips with a only a 10% tolerance on the internal clock! Duh, big design oops on my part. All the new widgets I build will be with the (more expensive) 2% chip which should tighten up the baud rate (and servo pulse performance) quite a bit!

SoftwareUART.h



// Slightly modified code from AVRFREAKS, written by Jeroen Lodder
// This code is very compact and uses timer 0

#ifndef byte
	#define byte uint8_t
#endif

#include 
#include 

/* Changeables */
#define TXport		0	// TX port, (use bit number)
#define PORTREG		PORTC
#define DIRREG		DDRC
#define TIMERVALUE  26		// this should be 206 or so, divide by 8 to hit the irq 8 times
				// before running state machine- gives better bit accuracy
#define IDLE		0
#define STARTBIT	1
#define TRANSMIT	2
#define STOPBIT 	3
#define FINISHED	4

// Main functions
void init_suart(void);		//Initialize the software uart and timers
void sTX_putchar(byte);		//Start sending a byte 

void sTX_putbyte_ascii(byte);	//Send a byte as ascii
void sTX_putint(int);		//Send a integer
void sTX_putint_ascii(int);	//Send a integer as ascii
void sTX_putstring(char *p);	//Send a string
void waitOnTX(void);
uint8_t getTXStatus(void);

/*	Calculation for compare register
	F_CPU		8000000			
	------------- =	--------- = TM  (Divide this value by 8 and place in TIMERVALUE)
	Baud * Presc	4800 * 8		
*/

SoftwareUART.c


//
// Slightly modified code from AVRFREAKS, written by Jeroen Lodder
//
// This code is very compact and uses timer 0
// I was getting some bit errors until I added the preclock variable
// This method divides the actual TIMERVALUE down by 8, increasing the IRQ frequency
// Every 8 ticks the state machine is run, this offers far better bit accuracy than
// just using the raw timer match value
//

#include "SoftwareUART.h"

// Variables
volatile byte sTX_state = 0;        //Volatile for interrupt
volatile byte sTX_bitcounter = 0;
volatile byte sTX_data = 5;

volatile uint16_t preclock;

// Functions
void init_suart(void)
{
    // PORT is set here
    DIRREG  |= (1<<TXport);   //As output
    PORTREG |= (1<<TXport);   //With pullup
    preclock = 0;

    // Timer 0
    TCCR0A = (1<<WGM01);      //CTC mode
    TCCR0B = (1<<CS01);       //Prescaler 8
    OCR0A  = TIMERVALUE;            //Set Output Compare A (See header file)
    TIMSK |= (1<<OCIE0A);     //Enable Output Compare A interrupt
}

ISR(TIMER0_COMPA_vect)
{
    preclock ++;                    // Every eighth interrupt, run the state machine
    if (preclock < 8)
       return;
    preclock = 0;
    
    switch(sTX_state)
    {
        case IDLE:                            // IDLE mode, wait for startbit
            TIMSK &= ~(1<<OCIE0A);      // Disable interrupt, waste of time when nothing has to be send
            break;
        
        case STARTBIT:                        // STARTBIT MODE (1 bit)
            PORTREG &= ~(1<<TXport);    // Pull down, low level
            sTX_state = TRANSMIT;             // Next state is transmit
            break;

        case TRANSMIT:                        // phase 1: Shift 1 bit
            if( (sTX_data & 0x01) == 0x01 ){  // 1 in lsb sTX_data                    
                PORTREG |= (1<<TXport); // Pull up, high level
            }else{                            // 0 in lsb sTX_data                    
                PORTREG  &= ~(1<<TXport);
            }
            sTX_data >>= 1;             // Shift away sent bit
                                              // phase 2: Incement Bit Counter and Compare
            sTX_bitcounter += 1;              // Increment
            if(sTX_bitcounter < 8){           // Not done yet
                sTX_state = TRANSMIT;         // Repeat transmit state next bit
            }else{                            // Done with data
                sTX_state = STOPBIT;          // Stopbit is the last bit
            }                                
            break;    //Wait 1 bit length
                
        case STOPBIT:
            PORTREG |= (1<<TXport);     // Pull up, High level
            sTX_bitcounter  = 0;              // Reset bitcounter
            sTX_state = IDLE;                 // Change mode to idle
            break;

        default:    //Any exceptions or whatever
            sTX_state     =    STOPBIT;
            break;
        }
}

void waitOnTX(void)
{
    while(sTX_state != IDLE);
}

uint8_t getTXStatus(void)
{
    return sTX_state;
}

void sTX_putchar(byte data)
{
    while(sTX_state != IDLE);                 // Wait for idle
    preclock = 0;
        sTX_data   = data;                    // Put data ready
        sTX_state  = STARTBIT;                // Set state to startbit
        TIMSK |= (1<<OCIE0A);           // Enable timer interrupt
}

void sTX_putbyte_ascii(byte data)
{
    unsigned int i;
    for(i=100;i>=1;i=i/10){            
        sTX_putchar ( ((data/i)%10)+48 );     // digit = (input / divisor) Mod 10
    }   
}

void sTX_putint(int data)
{
    sTX_putchar(data& 0xFF);     //Low
    sTX_putchar(data>>8);        //High 
}

void sTX_putint_ascii(int data)
{
    unsigned int i;
    for(i=10000;i>=1;i=i/10){            
        sTX_putchar ( ((data/i)%10)+48 );     // digit = (input / divisor) Mod 10
    }  
}

void sTX_putstring(char *p)
{
    byte i_putstring = 0; 
     while (p[i_putstring] != '\0')     
     {
        sTX_putchar(p[i_putstring]);
          i_putstring++;
     }
}