Last active
January 28, 2021 13:19
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some additional links and useful material
Sparkfun video on timed interrupts
Article explaining timed interrupts
Atmel Atmega328P datasheet
Online timer interrupt calculator
Useful conversion tool