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