UK High Altitude Society

User Tools

Site Tools


Interrupt driven RTTY on AVR – the proper way to use a microcontroller

Ever since I began developing software for embedded electronics for high altitude ballooning, I’ve been separating the execution of various instructions in the code using delays, where the running program simply waits for a few processing cycles before resuming normal operation. This is usually perfectly suitable for most applications, but when power consumption is a chief concern, it is often wise to wonder, how can I get the most out of my microcontroller without increasing the power consumption? And with that, we enter a whole new world of microcontroller operation – interrupts.

 NORB 3 tracker PCB

Interrupts work by doing exactly what they say on the tin, they interrupt the running program to do something, before allowing the main program to continue on its way. This means that instead of waiting for an instruction to be executed before moving on to the next step, the main program can be doing something useful, and the interrupt will intervene when necessary to execute the instruction in question.

There are many types of interrupt, and each type can be triggered in a certain way, details of which can be found in the interrupt vectors section of any AVR datasheet. An interrupt can be triggered by a pin being pulled low, or perhaps each time an external oscillator ticks, or in many other ways that are described in the datasheet. In this example, we will be focusing on using a software based timer interrupt using the popular ATMEGA328P, a chip used on many Arduino platforms. I will assume prior knowledge of the RTTY (radio teletype) radio mode.

Step 1: Create the interrupt service routine (ISR)

The interrupt service routine, or ISR for short, is simply the routine which runs when the interrupt is triggered. You may think of it as a self-defined function you might create in C or Arduino as void function_name() except here the name is specific to the interrupt vector being applied. Here the function will begin ISR(TIMER1_COMPA_vect) as we will be using the TIMER1 interrupt vector as described in the datasheet. Perhaps the most difficult part of this guide comes next as we prepare to write the code in the ISR itself.

Let us assume that we will be transmitting 50 baud RTTY. For 50 baud RTTY, there must be a delay of 20ms between the transmissions of each bit. This was quite easy to achieve with our delay driven RTTY, but for interrupt driven RTTY, a little more thought is required.

Below is a working example of a RTTY ISR as well as a function used at first setup to enable the interrupt. The initialize_interrupt() routine does this and also sets the interval for how often the interrupt should trigger. I strongly recommend that you trace the code through in your mind and try to understand what is going on prior to reading the following explanation of each part – this will help both your understanding of interrupt driven RTTY and your ability to understand code.

#include <avr/io.h>
#include <avr/interrupt.h>
#define RADIOPIN 9
#define BAUD_RATE 50 // change as required
volatile int tx_status = 0;
volatile char *ptr = NULL;
char currentbyte;
int currentbitcount;
volatile boolean sentence_needed;
char send_datastring[102] = "";
// RTTY Interrupt Routine
  switch (tx_status){
    case 0: // when the next byte needs to be gotten
      if (ptr){
	currentbyte = *ptr; // read first byte where pointer is pointing too
        if (currentbyte){
          tx_status = 1;
          sentence_needed = false;
          digitalWrite(LED_1, LOW);
          // warning! The lack of "break" in this branch means that we
          // fall through to "case 1" immediately, in order to start
          // sending the start bit.
        else {
          sentence_needed = true;
          digitalWrite(LED_1, HIGH);
      else {
        sentence_needed = true;
        digitalWrite(LED_1, HIGH);
    case 1: // first bit about to be sent
      rtty_txbit(0); // send start bit
      tx_status = 2;
      currentbitcount = 1; // set bit count to 0 ready for incrementing to 7 for last bit of a ASCII-7 byte
    case 2: // normal status, transmitting bits of byte (including first and last)
      rtty_txbit(currentbyte & 1); // send the currentb bit
      if (currentbitcount == 7){ // if we've just transmitted the final bit of the byte
        tx_status = 3;
      currentbyte = currentbyte >> 1; // shift all bits in byte 1 to right so next bit is LSB
    case 3: // if all bits have been transmitted and we need to send the first of two stop bits
      rtty_txbit(1); // send first stop bit
      tx_status = 4;
    case 4: // ready to send the last of two stop bits
      rtty_txbit(1); // send the final stop bit
      ptr++; // increment the pointer for reading next byte in buffer
      tx_status = 0;
// function to toggle radio pin high and low as per the bit
void rtty_txbit (int bit)
  digitalWrite(RADIOPIN, bit);
void initialise_interrupt() 
  // initialize Timer1
  cli();          // disable global interrupts
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B = 0;     // same for TCCR1B
  OCR1A = F_CPU / 1024 / (BAUD_RATE - 1);  // set compare match register to desired timer count
  TCCR1B |= (1 << WGM12);   // turn on CTC mode:
  // Set CS10 and CS12 bits for:
  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS12);
  // enable timer compare interrupt:
  TIMSK1 |= (1 << OCIE1A);
  sei();          // enable global interrupts
void setup()
void loop()
 // for you to have a play with ;-)

Firstly, you must understand that we are using 7 bit ASCII. ASCII is one of the common ways of encoding all the 128 characters we typically use. It gives each character a value which is comprised of so many bits. There’s 7 bit ASCII and 8 bit ASCII, though here we’ll be using 7 bit ASCII. So you understand now that the radio transmitter must transmit each bit at a time.

If the interrupt fires every 20ms, you will understand that we can’t possibly send more than one bit per routine as this would just register as a longer bit, making no sense to the way in which RTTY works. This means that we will have different conditions in which the routine will operate differently. We can mark each condition by having a status variable which will change depending on when the condition for transmission needs to be updated. Above, this variable is marked tx_status, and its different conditions are described below:

tx_status = 0:

The way in which this code works is that when a new sentence is required by the ISR, the sentence_needed Boolean is changed to true. This informs the running program that if this is the case, a new sentence is stored in a buffer which the ISR will be searching through. At this time also, a pointer is initialized in the line ptr = send_datastring (a clue for the creation of your own loop function). This pointer will point to the location of the first character in the buffer which will be used each time the ISR tries to find a new character. If the pointer happens to be valid and a character is present, the status is updated to 1 so that upon the next calling of the ISR, the sentence can begin to be transmitted.

tx_status = 1:

Under this condition, a start bit is sent as it is now known that new data is about to be sent. Of course, we can only send one bit at once, so after the start bit is sent, the status is updated to 2 ready for the next triggering of the ISR.

tx_status = 2:

This is the normal transmission condition where each bit is sent consecutively until there are no more left to send. The line rtty_txbit(currentbyte & 1); transmits the current bit of the byte. After doing this, it then checks to see if the bit it just sent was the last in the byte – in this case using 7 bit ASCII, it checks that the bit sent was the 7th bit. If so, the status is updated ready for sending the stop bits to complete transmission. If this is not the case however, the next bit needs to be sent.

RTTY works by sending the least significant bit first (LSB for short). If you imagine the byte 01100101, the LSB is the very last bit. If we had just sent this bit and wished to send the next bit, we need to in a way shift all the bits along 1 to the right so that the bit that was second to last is now the last and thus the LSB to be sent. This so called shift by 1 to the right is done with the line currentbyte = currentbyte » 1. The currentbitcount is then incremented ready for the next triggering of the ISR.

tx_status = 3:

When the status is updated from the previous status, the ISR knows that transmission of the current character is complete and in order to tell the decoder on the ground that this is the case, we must send two stop bits. The sending of two stop bits is not set in stone with RTTY, nor is the transmission of only one start bit. This is simply a standard example using a 7N2 configuration (7 data bits, no parity, 2 stop bits, and a single start bit). Since two stop bits must be sent, but only one can be sent at once, the first stop bit is send under this condition before the status is updated to 4.

tx_status = 4:

And as you might have guessed, under this condition we now send the final stop bit. As this signals the complete end of transmitting a character, the status must be updated to 0 ready for finding the next character, and the pointer must be incremented so that the next time the ISR triggers, the byte it reads will be the next, if any.

And there you have it, one fully functional example of interrupt driven 50 baud RTTY, fully explained. The code in this example of course is only a demonstration and is not a complete program in itself. However, it’s the little guides like this that provide opportunity for you to stitch each piece of knowledge together into a structure that, in time, you’ll be far more satisfied with in your HAB endeavors. Time I had a cup of tea and a biscuit, good luck!

By Matthew Beckett of

guides/interrupt_driven_rtty.txt · Last modified: 2015/02/03 13:43 by danielrichman

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki