[TUT] [SOFT] Using the USART - Interrupt driven serial comms

324 posts / 0 new
Last post
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For an updated version of this tutorial in PDF format, please see this page of my website.

Interrupt driven USARTs

The following is a short extension of my previous tutorial, Using the USART - Serial communications. This tutorial will teach the basics for creating interrupt-driven USART communications. It assumes that the reader has both read and fully understood my previous tutorial on basic serial communications.

Interrupts?

AVRs - and almost all microcontrollers - contain a feature known as interrupts. Interrupts, as their name implies, allows for external events (such as inputs from the user or AVR peripheral) to momentarily pause the main microcontroller program and execute an "Interrupt Service Routine" (shorthand "ISR") before resuming the main program where it left off. Interrupts are extremely useful for dealing with irregular input (such as pin changes or the arrival of a serial byte), as well as for processing "background tasks" like togling a LED each time a timer overflows.

In this tutorial, we are going to make use of the AVR's USART peripheral interrupts.

A recap, our echo program

From the last serial tutorial, we have created a simple program from scratch which will echo bytes received on the AVR's USART interface. The full program listing is as follows:

#include 

#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

int main (void)
{
   char ReceivedByte;

   UCSRB = (1 << RXEN) | (1 << TXEN);   // Turn on the transmission and reception circuitry
   UCSRC = (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1); // Use 8-bit character sizes

   UBRRH = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
   UBRRL = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register

   for (;;) // Loop forever
   {
      while ((UCSRA & (1 << RXC)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
      ReceivedByte = UDR; // Fetch the received byte value into the variable "ByteReceived"

      while ((UCSRA & (1 << UDRE)) == 0) {}; // Do nothing until UDR is ready for more data to be written to it
      UDR = ReceivedByte; // Echo back the received byte back to the computer
   }   
}

Readers should be able to fully understand this code - if you cannot please re-read the previous tutorial on basic serial communication.

Now, we want to extend this code so that the serial data is echoed back when received in an interrupt, rather than our main program loop. To do this, first we need to include the AVRLIBC standard library header, "avr/interrupt.h". This file contains library functions and macros which relate to the interrupt functionality of the AVR. We'll add this to the top of our code, below the "avr/io.h" header include:

#include 
#include 

Once included, we now have a way to make our ISR to deal with the serial reception. To make an ISR, we use the syntax:

ISR({Vector Name})
{
	// Code to be executed when ISR fires
}

And place it in our program as if it was a normal function. To add one to deal with the reception of a byte via the USART, we need to look for the appropriate name in our AVR's datasheet. In the datasheet for our example AVR, the MEGA16, we see that the name of the interrupt for when a byte is received is "USART_RXC". The standard AVRLIBC library file "avr/io.h" - included in our program as well as any other AVR-GCC program involving the AVR's IO functionality - defines the vector names for us.

AVRLIBC's symbolic names for each of the interrupt vectors is identical to the datasheet, with the addition of a "_vect" suffix to denote that it is a vector name. So, as our datasheet listed "USART_RXC" as the vector name, the syntax for our program is:

ISR(USART_RXC_vect)
{
	// Code to be executed when the USART receives a byte here
}

Which we'll place at the end of our program, after our main function. The new program looks like this:

#include 
#include 

#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

int main (void)
{
   char ReceivedByte;

   UCSRB = (1 << RXEN) | (1 << TXEN);   // Turn on the transmission and reception circuitry
   UCSRC = (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1); // Use 8-bit character sizes

   UBRRH = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
   UBRRL = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register

   for (;;) // Loop forever
   {
      while ((UCSRA & (1 << RXC)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
      ReceivedByte = UDR; // Fetch the received byte value into the variable "ByteReceived"

      while ((UCSRA & (1 << UDRE)) == 0) {}; // Do nothing until UDR is ready for more data to be written to it
      UDR = ReceivedByte; // Echo back the received byte back to the computer
   }   
}

ISR(USART_RXC_vect)
{
	// Code to be executed when the USART receives a byte here
}

Populating the ISR

At the moment our new USART reception ISR doesn't actually do anything - we've just defined it. We want it to echo back the byte that is sent, so we'll move our main loop code:

while ((UCSRA & (1 << RXC)) == 0) {}; // Do nothing until data have been received and is ready to be read from UDR
ReceivedByte = UDR; // Fetch the received byte value into the variable "ByteReceived"

while ((UCSRA & (1 << UDRE)) == 0) {}; // Do nothing until UDR is ready for more data to be written to it
UDR = ReceivedByte; // Echo back the received byte back to the computer

Over to it. However, we can now remove the two while loops - since the ISR only fires when a byte is received, and only one byte is sent after each reception we can guarantee that both checks are now redundant. When the ISR fires we know that there is both a byte received in the USART input buffer, as well as nothing in the output buffer. Using this knowledge, we can simplify our ISR code to the following:

ISR(USART_RXC_vect)
{
   char ReceivedByte;
   ReceivedByte = UDR; // Fetch the received byte value into the variable "ByteReceived"
   UDR = ReceivedByte; // Echo back the received byte back to the computer
}

Note that I've also moved the variable declaration of "ReceivedByte" over to the ISR, as that is now where it is actually used.

It's worth mentioning at this point a small section on the datasheet about the RXC interrupt:

Quote:
When interrupt-driven data reception is used, the receive complete routine must read the received data from UDR in order to clear the RXC Flag, otherwise a new interrupt will occur once the interrupt routine terminates.

That's important to remember - if you are using the RXC interrupt, you must read a byte from the UDR register to clear the interrupt flag. We do that in our above code, but keep it in mind for your future projects!

Enabling the USART receive interrupt

Let's take a look at the latest incarnation of our test code:

#include 
#include 

#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

int main (void)
{
   UCSRB = (1 << RXEN) | (1 << TXEN);   // Turn on the transmission and reception circuitry
   UCSRC = (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1); // Use 8-bit character sizes

   UBRRH = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
   UBRRL = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register

   for (;;) // Loop forever
   {
         // Do nothing - echoing is handled by the ISR instead of in the main loop
   }   
}

ISR(USART_RXC_vect)
{
   char ReceivedByte;
   ReceivedByte = UDR; // Fetch the received byte value into the variable "ByteReceived"
   UDR = ReceivedByte; // Echo back the received byte back to the computer
}

If you compile and run this, you'll notice that nothing happens - no characters are echoed back to the PC. This is because although we've defined and populated the ISR, we haven't enabled it. To do so, we need to do two things:

    1) Turn on global interrupts 2) Enable the USART Byte Received interrupt

Item one is simple, so we'll do that first. The AVR microcontrollers contain a global flag which can be set or cleared to enable or disable the handling of interrupts. Note that setting this flag doesn't enable all interrupts, it only allows for the possibility of running them. If the Global Interrupt Enable flag is disabled, all interrupts will be ignored, even if they are enabled (more on that later).

To turn on the Global Interrupt Enable flag, we can use the macro "sei()" which the "avr/interrupt.h" library helpfully defines for us. This is so named as it generates a "SEI" assembly instruction in the final code listing, which the AVR interprets as an order to set the Global Interrupt Enable flag. The compliment of "sei()" is "cli()" (to turn off the handling of interrupts) however we will not be using that macro in this tutorial.

We'll add our "sei();" instruction to our main routine, after configuring the USART registers:

   // ...
   UBRRH = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
   UBRRL = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register

   sei(); // Enable the Global Interrupt Enable flag so that interrupts can be processed

   for (;;) // Loop forever
   // ...

Now for item 2 on our list, which needs to be performed before the interrupt will be enabled. We need to specifically enable the USART Receive Complete interrupt, which we can do by setting the appropriate flag in the USART control register.

In the MEGA16, this bit is called RXCIE (Recieve Complete Interrupt Enable) and is part of UCSRB. Setting this bit enables the handling of the USART_RXC event vector:

UCSRB |= (1 << RXCIE);

We'll add this to our main routine, before our new "sei();" command.

Putting it all together

Now we have a working interrupt driven serial example:

#include 
#include 

#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

int main (void)
{
   UCSRB = (1 << RXEN) | (1 << TXEN);   // Turn on the transmission and reception circuitry
   UCSRC = (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1); // Use 8-bit character sizes

   UBRRH = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
   UBRRL = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register

   UCSRB |= (1 << RCXIE); // Enable the USART Recieve Complete interrupt (USART_RXC)
   sei(); // Enable the Global Interrupt Enable flag so that interrupts can be processed

   for (;;) // Loop forever
   {
         // Do nothing - echoing is handled by the ISR instead of in the main loop
   }   
}

ISR(USART_RXC_vect)
{
   char ReceivedByte;
   ReceivedByte = UDR; // Fetch the received byte value into the variable "ByteReceived"
   UDR = ReceivedByte; // Echo back the received byte back to the computer
}

Which, like out original program, will echo characters received via the USART. However, because our program is now interrupt driven, we can add in code into the main loop which will be executed when data is not received - such as flashing a LED.

Interrupts allow for infrequent "background" tasks to be executed when they occur, without posing a run-time penalty of having to poll the hardware until the even occurs. This frees up our main loop to take care of the critical code, with the interrupt code pausing the main code to execute when the event of interest ocurs.

Interrupts should be made to be as short as possible in execution time. This is because while one ISR is executing, others are blocked and thus if another ISR condition occurs while one ISR is executing, that ISR event will be missed or delayed.

Because of this, communication ISRs are generally made short by receiving in characters via an ISR, and placing them into a buffer which the main code may read at its leisure. This ensures that received data will not be missed, while giving the main program time to complete what it is currently doing before having to process the input. Similarly, long transmissions may be made by placing the data to be sent into a buffer, and have an interrupt send the data as the hardware is ready while the main program performs other tasks.

Please note that the names of the registers and bits for the UART(s) in the AVR you are using may be different from those shown above. In place of UDR, UBRRH, UCSRA etc. you may find they are called UDR0 (or UDR1), UBRR0H, UCSR0A and so on.

I hope this tutorial is informative - if not please post your suggestions! I'm open to feedback, and please feel free to post your own examples, code, etc. below.

For an updated version of this tutorial in PDF format, please see this page of my website.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Dean, thanks for the great tutorial. One thing - I think the line:

   UCSRB |= (1 << RCXIE); // Enable the USART Recieve Complete interrupt (USART_RXC) 

actually needs to use RXCIE rather than RCXIE. I have this running and it's working great in hyperterminal.

A quick question: Do you know a way to do a 'full speed' loopback test in windows? Typing isn't really stressing it at all. I'm using Artur Lipowski's USART library in a motor controller project and I'm losing the occasional character (with a 0% error clock frequency). I'd like to blast a large text file through the loopback, capture it when it gets back to the pc and then do a file compare to test for differences. ie if the avr is doing nothing but passing the character back and there are still errors I can narrow it down to some problems on my board.

Thanks again for the tutorial, it's very timely. I've been fighting with the usart for a couple of days now. much appreciated.

Update: I went away and checked and hyperterminal has a "paste to host" option that allows you to paste in a large block of text. I turned off local echo and posted in a large source file. Although this method doesn't allow an actual file compare, inspection showed that the AVR returned the text file completely intact. I guess this means there is some problem with my motor control code fighting with the code to handle the USART, although I'm not sure how.

Anyway, thanks once again for your great tutorial. I'll look at implementing a simple circular buffer based off your code and swapping my existing handler out.

Cheers
Andrew

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Connect your TxD pin to your RxD pin.

Smiley

FREE TUTORIAL: 'Quick Start Guide for Using the WinAVR C Compiler with ATMEL's AVR Butterfly' AVAILABLE AT: http://www.smileymicros.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the thought, Smiley. But I don't think that's what I need - I was sorta using the avr for the loopback, just sending stuff to the chip and trying to measure if I got exactly the same stuff back.

Actually I think I understand what's happening now.

The UART code works fine in a project that does nothing but send/recieve characters, but if I include it in the motor controller (with 5 servo motors) it drops characters.

BUT (and here's the n00b thing to watch) if the motors are disconnected there's no problem and no dropped characters. So it appears to be the motors drawing current that are causing the problems. (Typical - I'm a software guy so I think in terms of software problems. I'll just go and beat myself with a stick now. :-/ )

I've seen many schematics that put a capacitor between Supply and ground probably for this very reason. Please pardon my ignorance. Perhaps if someone could confirm I'm guessing right, and verify what to do about it it'd be great, and thanks again.

Andrew

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"reverse" diodes on the motor lines (4 in total per motor), a 0.1uF ceramic cap as close as possible to the AVR power pins. this should minimize the motors effect on the circuit. power isolation would be the best (with optical isolation on the control signal) but i doubt you need that much of "protection".
You could also add a capacitor triangle on the motor leads (if using DC motor): cap between leads, lead1 to case, lead2 to case. also ceramic.

very OT although ;) maybe could be moved to another thread in OT or something...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you very much for your help (although I profess to not completely understanding what you said i can research it and work it out now you have given me a direction.) :)

Apologies if this is off topic. I considered before posting here, but thought - if the beginner is trying to use the USART and it's dropping characters then it'd be worth highlighting this as possible cause to consider. Once again, apologies and let me know if I should delete my posts here, and thanks again.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
actually needs to use RXCIE rather than RCXIE. I have this running and it's working great in hyperterminal.

Whoops! I wrote the above in response to your post, since you were having trouble (and I thought I could help others at the same time). Should have checked the datasheet more thoroughly. I'll fix that ASAP.

Sorry about the lack of polish on this one - I only had an hour in which to write it. So long as I get my main point across I suppose it's a passable tutorial ;).

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Great tutorial. It will definitely come in handy. Thanks! I have a question though I am not entirely sure I should post it here, but I'll do so anyway. (If you want me to delete it and repost somewhere else, just ask!)

Your code works wonders if I only recieve one byte (one character) of data. But what happens if I want to recieve, let's say, 5 consecutive bytes? This probably just requires some smart C-code, but it I believe it is somewhat connected to how the AVR works too. Remember that I am a novice, but I still hope my question makes sense. Let me explain.

Let's say I want to send 5 bytes of data. Now as I see it, if I have multiple interrupts enabled in my program, they will stack them and do them one after another in the order they happened? Is that correct? So let's say that I send a byte but I am currently in another interrupt, that is when a character is sent I will not directly go to ISR(USART_RXC_vect) since another interrupt is currently beeing processed. Let's say this interrupt also takes some time. What will happen then? Will the sender just write 5 characters really fast and thus not let the AVR get a chance to read the bytes one by one. ie will only the last byte sent be read when the AVR finally goes into the ISR(USART_RXC_vect) interrupt function? Will only the last byte be in the UDR?

What I am basically asking is will the AVR somehow recieve bytes into a certain buffer and not send them out to the UDR register until it has been read when receiving data? Or do I have somehow have to use the RTS/CTS flags in the RS232 protocol? (even though I have no clue how that would work anyway with this particual problem)

Thanks for any answers I might get.

Edit:
I also found a very nonsignificant error heh.

char ReceivedByte; 
   ReceivedByte = UDR; // Fetch the recieved byte value into the variable "ByteReceived" 

should probably be:

char ReceivedByte; 
   ReceivedByte = UDR; // Fetch the recieved byte value into the variable "ReceivedByte" 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No they will not be stacked like you said. when you are in a interrupt routine all other interrupts are disabled. so you should get out if it as fast as possible.

I'll see if I can find my code on receiving more than one byte...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The UDR register is double buffered, I think, so you have have two pending characters in it at a time (reading one then frees up that place for the next character).

If one interrupt is executing, other interrupts that are pending will have have to wait until the current interrupt is complete. It's possible to make interrupts that are themselves interruptible, but that's an advanced topic and certain to cause problems if you don't know what you are doing.

To receive blocks of characters, you need to stuff them into a buffer inside the interrupt (to keep it short), then read those characters out later in your main routine to process them. This is commonly done in the form of a "Ring Buffer", a special type of circular buffer. You might want to Google that to find some example code (or I could PM some to you).

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the informative answer Dean. I just have one followupquestion. bloody-orc said all other interrupts are disabled. I read that as they won't happen at all if I'm currently in another interrupt. When I read your post I read it as they will indeed stack and happen one after another, in the order they happened? Which one of thoose two statements are correct?

Also, I'd love some example code! Thanks in advance.

Pages