Software / Hardware Timer Emulated UART Serial, Perfect Timing!

For any AVR chips with a 8 bit timer, but an ATMEGA168 is used as an example, with a 16 MHz clock source.

 

It uses a FIFO buffer, it runs very fast and also runs in the background, it uses some software to completely simulate a UART with the help of hardware timer interrupts. Timing is super accurate since it uses interrupts. There is no software delay of any sort used. After the string is loaded into the buffer, your program runs in parallel with the interrupts.

 

Only Tx right now, working on Rx with pin change interrupt.

 

So far, it works flawlessly up to 4800 baud, I'm troubleshooting for 9600 baud.

 

You need to calculate OCR0A yourself, the formula is:

(MCU Frequency / Baud Rate) / 64

 

Here's the Code

 


// Standard AVR Headers
#include <avr/io.h>
#include <stdio.h>
#include <inttypes.h>
#include <avr/interrupt.h>

// Macros
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // clear bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // set bit

// General Compiler Instruction
#define F_CPU 16000000L // note this will only work with 16 MHz
// if you use a different clock speed, you need to calculate your own OCR0A
// number to get the correct baud rate.
#define __AVR_ATmega168__

// Pins and Ports
#define softSerTxPort PORTB
#define softSerTxDDR DDRB
#define softSerTxPin 5

// Baud Rate
// calculated by
// (MCU Frequency / Baud Rate) / 64
#define baudRateCalculation 52

// Global Variables
#define softSerOutSize 100 // the size of the buffer
// adjust this if you are running out of room
// be careful not to overflow it
volatile uint8_t softSerBusy; // busy indicator
volatile uint8_t softSerOut[softSerOutSize]; // buffer
volatile uint8_t softSerOutHead; // buffer head
volatile uint8_t softSerOutTail; // tail
volatile uint8_t softSerNextData; // next byte out
// this is the equivelent of UDR
volatile uint8_t softSerBitCounter; // which bit to send

// software timer serial functions begins here

void softSerInit()
{
// initialize port
sbi(softSerTxPort, softSerTxPin);
sbi(softSerTxDDR, softSerTxPin);

TCNT0 = 0; // reset timer
OCR0A = baudRateCalculation; // 56 for 4800 baud
OCR0B = OCR0A * 2; // compare match interrupt B is used for stop bit
TCCR0B = 0b00000000; // timer off for now
TIMSK0 = 0b00000111; // interrupts on
}

void softSerSend(uint8_t data)
{
uint8_t c = data;

// store in outbox buffer if buffer isn't empty
uint8_t i = (softSerOutHead + 1) % softSerOutSize;
if(i != softSerOutTail)
{
softSerOut[softSerOutHead] = c;
softSerOutHead = i;
}

// if first, then trigger transmission
if(softSerBusy == 0)
{
uint8_t d = softSerOut[softSerOutTail]; // take from buffer
softSerOutTail = (softSerOutTail + 1) % softSerOutSize;
softSerBusy = 1; // set busy flag
softSerNextData = d; // load data
TCNT0 = 0; // reset timer
softSerBitCounter = 0; // reset
TCCR0B = 0b00000011; // timer start, divide by 64
cbi(softSerTxPort, softSerTxPin); // start bit
}
}

ISR(SIG_OUTPUT_COMPARE0A)
{
// output bit to pin
if(bit_is_set(softSerNextData, softSerBitCounter))
{
sbi(softSerTxPort, softSerTxPin);
}
else
{
cbi(softSerTxPort, softSerTxPin);
}
softSerBitCounter++; // next bit
if(softSerBitCounter != 8)
{
// if not finished, send next bit by triggering interrupt A again
TCNT0 = 0;
}
// if finished, let the timer run into interrupt B
}

ISR(SIG_OUTPUT_COMPARE0B)
{
sbi(softSerTxPort, softSerTxPin); // stop bit
//TCNT0 = 255 - OCR0B; // trigger timer overflow faster
// enable this if you need it to be faster
}

ISR(SIG_OVERFLOW0)
{
// this is the same as a "data transfer finished" interrupt
if(((softSerOutSize + softSerOutHead - softSerOutTail) % softSerOutSize) == 0)
{
// if buffer empty
TCCR0B = 0b00000000; // timer off
softSerBusy = 0; // clear flag
sbi(softSerTxPort, softSerTxPin); // idle pin
}
else
{
// transmit next byte
uint8_t c = softSerOut[softSerOutTail]; // take from buffer
softSerOutTail = (softSerOutTail + 1) % softSerOutSize;
softSerBusy = 1; // set flag
softSerNextData = c; // load next byte
TCNT0 = 0; // reset timer
softSerBitCounter = 0; // reset
TCCR0B = 0b00000011; // timer start, divide by 64
cbi(softSerTxPort, softSerTxPin); // start bit
}
}

// software timer serial functions ends here

void mainInit()
{
sei(); // enable interrupt
softSerInit(); // start software serial
}

void mainLoop()
{
// demo
// sends endless lines of A, ten per line
uint8_t i;
for(i = 0; i < 10; i++)
{
softSerSend(65);
}
softSerSend(10);
softSerSend(13);
while(softSerBusy == 1);
}

int main()
{
mainInit();
while(1)
{
mainLoop();
}

return 0;
}