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++;
}
}