Skip to content

Instantly share code, notes, and snippets.

@Br4ggs
Last active January 28, 2021 13:19
Show Gist options
  • Save Br4ggs/9bfe629727eed8fb2dc9d168163395d9 to your computer and use it in GitHub Desktop.
Save Br4ggs/9bfe629727eed8fb2dc9d168163395d9 to your computer and use it in GitHub Desktop.
Small Arduino Uno demo demonstrating working UART via the usage of external and timed interrupts
//Simple UART demo
//this is a simple non-duplex UART demo demonstrating
//receiving individual bytes and sending them back via the use of
//external and timed interrupts
//With regular typing speeds this demo seems to keep up well
//however when sending bytes to the arduino at a high speed it tends
//to incorrectly interpret about 5-10% of the data
//pins used for UART
const uint16_t UART_Tx = 2;
const uint16_t UART_Rx = 3;
const uint16_t UART_transmission_size = 10; // this includes: the start bit (1 bit) + the data bits (8 bits) + the stop bit (1 bit)
const uint16_t UART_receive_size = 8; // this includes just the data bits (8 bits)
volatile uint16_t UART_current_bit_Tx = 0;
volatile uint16_t UART_data_Tx = 0;
volatile uint16_t UART_current_bit_Rx = 0;
volatile uint16_t UART_data_Rx = 0;
//the UART baudrate for this demo is 9600, which can be seen as the bits per second.
//so for sending a transmission with this baudrate, we'll have an interval of:
//1 / 9600 = 0,000104 S => 104 µs
//
//clockcycles on the arduino have a frequency of 16 mHz, to find the time it
//takes for one clockcycle we do:
//1 / 16.000.000 Hz = 0.0000000625 S => 62.5 ns
//
//finally to get the amount of clock cycles we need to wait for each bit
//we calculate:
//0,000104 / 0.0000000625 = 1.664
//
//and divide it by a prescaler of 64 to get:
//1.664 / 64 = 26
const uint16_t tl_load = 0;
const uint16_t tl_comp = 26;
const uint16_t tl_init = 35;
volatile uint8_t receivedData = 0;
volatile bool transmitReceived = false;
void setup()
{
//setup pins 2 and 3 as outputs
DDRD &= ~(1 << UART_Rx);
PORTD &= ~(1 << UART_Rx);
DDRD |= (1 << UART_Tx);
PORTD |= (1<< UART_Tx);
//setup falling edge external interrupt on Rx line
//to detect incomming messages
EICRA |= (1 << ISC11);
EICRA &= ~(1 << ISC10);
EIFR |= (1 << INTF1);
EIMSK |= (1 << INT1);
//timer 1
//reset timer1 control register A
TCCR1A = 0;
TCCR1B = 0;
//set CTC mode so TCNT1 clears after compare
TCCR1B &= ~(1 << WGM13);
TCCR1B |= (1 << WGM12);
//set prescaler to 64
TCCR1B &= ~(1 << CS12);
TCCR1B |= (1 << CS11);
TCCR1B |= (1 << CS10);
//set timer register to zero
TCNT1 = tl_load;
//set up compare value
OCR1A = tl_comp;
//mask timer compare interrupt
TIMSK1 &= ~(1 << OCIE1A);
//timer 2
//reset timer2 control register A
TCCR2A = 0;
TCCR2B = 0;
//set CTC mode so TCNT2 clears after compare
TCCR2B &= ~(1 << WGM22);
TCCR2A |= (1 << WGM21);
TCCR2A &= ~(1 << WGM20);
//set prescaler to 64
TCCR2B |= (1 << CS22);
TCCR2B &= ~(1 << CS21);
TCCR2B &= ~(1 << CS20);
//mask timer compare interrupt
TIMSK2 &= ~(1 << OCIE2A);
//set timer register to zero
TCNT2 = tl_load;
//set up compare value
OCR2A = tl_init;
//finally, enable global interrupts
sei();
}
void loop()
{
//echo back data if a byte was received
if(transmitReceived)
{
transmitReceived = false;
UART_Transmit(receivedData);
}
}
void UART_Transmit(byte data)
{
//mask the Rx falling edge interrupt to prevent
//a voltage dip triggering the interrupt
EIMSK &= ~(1 << INT1);
UART_data_Tx = 0;
UART_data_Tx |= data;
UART_data_Tx <<= 1;
//additionally if using parity bits you could add a parity bit
//to the UART_DATA_Tx
//add stop bit
UART_data_Tx |= (1 << 9);
UART_current_bit_Tx = 0;
//reset and turn on transmission timer
TIFR1 |= (1 << OCF1A);
TCNT1 = tl_load;
TIMSK1 |= (1 << OCIE1A);
}
//unused method for calculating a parity bit
uint16_t UART_CalculateParityBit(byte data)
{
int highBits = 0;
for(int i = 0; i < 8; i++)
{
highBits += ((data >> i) & 0b00000001);
}
return !(highBits % 2)? 0 : 1;
}
//External falling edge interrupt routine for detecting incoming messages
ISR(INT1_vect)
{
EIMSK &= ~(1 << INT1);
UART_data_Rx = 0;
UART_current_bit_Rx = 0;
//reset and turn on reciever timer
TIFR2 |= (1 << OCF2A);
//we initially add a bit more delay as to
//read the bits in the middle of their pulse
TCNT2 = tl_load;
TIMSK2 |= (1 << OCIE2A);
}
//Transmission timer
ISR(TIMER1_COMPA_vect)
{
if(UART_current_bit_Tx < UART_transmission_size)
{
uint8_t transmissionBit = (UART_data_Tx & (1 << UART_current_bit_Tx))? 1 : 0;
PORTD = (PORTD & ~(1 << UART_Tx)) | (transmissionBit << UART_Tx);
UART_current_bit_Tx++;
}
else
{
//mask and reset timer again
TIMSK1 &= ~(1 << OCIE1A);
TCNT1 = tl_load;
UART_current_bit_Tx = 0;
UART_data_Tx = 0;
//reset interrupt flag and unmask external interrupt again
EIFR |= (1 << INTF1);
EIMSK |= (1 << INT1);
}
}
//Receiver timer
ISR(TIMER2_COMPA_vect)
{
//if this is the first bit we set the we can set the
//timer's compare value to the correct interval
if(UART_current_bit_Rx == 0)
{
OCR2A = tl_comp;
}
uint8_t receivedBit = (PIND & (1 << UART_Rx))? 1 : 0;
UART_data_Rx |= receivedBit << UART_current_bit_Rx;
UART_current_bit_Rx++;
if(UART_current_bit_Rx >= UART_receive_size)
{
//mask and reset timer again
TIMSK2 &= ~(1 << OCIE2A);
TCNT2 = tl_load;
OCR2A = tl_init;
//additionally if using a parity bit you could validate
//wheter or not the message was correct
//remove the stop bit from the received data
receivedData = UART_data_Rx;
transmitReceived = true;
UART_current_bit_Rx = 0;
UART_data_Rx = 0;
//reset interrupt flag and unmask external interrupt again
EIFR |= (1 << INTF1);
EIMSK |= (1 << INT1);
}
}
@Br4ggs
Copy link
Author

Br4ggs commented Jan 26, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment