Search This Blog

Loading...

Friday, August 27, 2010

Building Blocks: The UART

I know these posts aren’t in any kind of logical order, but I figure I should post about what I feel inspired to post about and, eventually, it’ll all be there. So, today I’m going to talk about the UART code I use on the atMega328p to communicate with the WiFly GSX. It is a bit more complex than the very basic tutorials out there, but I suspect it’s not up to professional grade either. I use an interrupt driven transmit/receive with 256byte buffer for each.

The inspiration for this bit of code came from a couple sources. There is a great and simple UART tutorial on Sparkfun that got me started when all other tutorials failed me. Additionally, reading the AVR Freaks forums, I educated myself on more advanced UART techniques including a suggestion by Dean, I believe, to use ring buffers to store data and handle transmitting/receiving the data in interrupt routines.

In my library, I have the following data structures:

  volatile char in[256];
  volatile char out[256];
  volatile uint8_t inptr;
  volatile uint8_t outptr;
  uint8_t  curoutptr;
  uint8_t  curinptr;


and the following routines:

  uart_init();
  uart_tx(char c);
  uart_send_string(const char* s);
  uart_check();
  ISR (USART_RX_vect);
  ISR (USART_UDRE_vect);


In the next few paragraphs, I’ll show you how these are used, but first, let’s take a quick look at the data structures.



  volatile char in[256];
  volatile char out[256];

These are what I call my ring buffers. They are called that because they wrap around to the beginning when they exceed 256 characters. Why? Because I am using an 8-bit integer for the index of the buffer. The AVR is an 8-bit micro, so the integers are only 8 bits long, allowing a maximum value of 255. If you add 1 to an 8-bit integer variable that contains the value of 255, the result is a value of 0. Very cool! It makes for a very convenient way of handling buffers for all kinds of things. As with everything there are trade offs. Two 256 byte buffers may be overkill for most UART applications. Some AVR micros don’t have or can’t spare the 512 bytes of memory needed for this. I used this library on a Tiny and had to drop the buffer size down to 16 bytes each. It wasn’t too difficult, but it’s just some extra coding.

  volatile uint8_t inptr;
  volatile uint8_t outptr;

These are the pointers into the index of each of the buffers that lets me know where to put new data. It also lets me figure out what data has not yet been sent.

  uint8_t curoutptr;
  uint8_t curinptr;

These are also pointers. These point to the last character that was sent/received. Using this pointer and the previous pointers, I can check to see if I need to send any data to the UART, or process any received data.

Now, on to the routines.

uart_init()

Let’s start with the uart_init() routine. As you probably suspect, it is the routine you call at the beginning of your main program to initialize the UART so that you can use it. Once this init routine is complete, you will be able to the other routines to send data out the UART. Let’s take a look at the code:

  void uart_init (void)
  {


    DDRD &= ~(1«DDD0);
    DDRD |= (1«DDD1);
    PORTD |= (1«PORTD0);


    UCSR0B |= (1 « RXEN0) | (1 « TXEN0);
    UCSR0C |= (1 « UCSZ00) | (1 « UCSZ01);
    UCSR0C &= ~(1«UPM01);
    UCSR0C &= ~(1«UPM00);


    UBRR0H = (unsigned char)(BAUD_PRESCALE » 8);
    UBRR0L = (unsigned char)BAUD_PRESCALE;
    UCSR0B |= (1 « RXCIE0) | (1«UDRIE0);


    inptr = 0;
    outptr = 0;
    curoutptr = 0;
    curinptr = 0;


    uart_tx(‘o’);
    uart_tx(‘k’);
    uart_tx(‘\r’);
    uart_tx(‘\n’);


  }

The code is split into five sections. The first section is where the pins on the micro are initialized to “known” values. I suppose that these pins have default values and that I am possibly wasting my time setting some of these values, but I feel that it’s best to KNOW that they are set correctly, rather than rely on supposed manufacturer default. The first three lines accomplish the following: set pin D0 (RX) to input, set pin D1 (TX) to output, enable pullup resistor on D0. The last statement is very important. I’ve not remembered to do this and it makes UART transmissions virtually impossible.

The second section sets up the parameters for the UART hardware itself. The first line simply turns on the UART TX/RX circuitry in the micro. The second line instructs the UART to use 8-bit characters. The third and fourth lines set the UART hardware to use no parity. This is a prime example of when defaults are not what they say they are. For some reason, the micro prefers to boot with odd parity. That is not what the datasheet from Atmel says. I wasted a week of effort just on this one issue. Word to the wise, if a bit needs to be set to zero, then set it to zero. Don’t assume that it will be zero by default.

The third section sets the BAUD rate in the UART hardware. The BAUD rate is either calculated by the following formula:

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

or you can simply set it manually to a specific value. There are suggested values in the Atmel datasheets. There is a lot to consider when setting the baudrate on an Atmel micro and I won’t really go into it, but I’ve typically just gone with 9600 baud at a micro clock rate of 8MHz. It’s worth spending the time to read and understand this if you want to be successful at using the UART for your projects. I suggest going over to AVR Freaks and doing some reading on the subject in their forums.

The fourth section just initializes the ring buffer pointers to zero. Standard stuff.

The last section sends an “ok” to the UART letting me know that the UART was successfully initialized. Clearly this is optional, but I prefer to have a little bit of interactivity here and there so that I know my micro is behaving.

uart_tx(char c)

This routine is very simple. It inserts characters into the transmit ring buffer and increments the buffer pointer.


  void uart_tx(char c) {
    out[outptr]=c;
    outptr++;
    UCSR0B |= (1«UDRIE0);
  }

What it also does is enable the UART’s data register empty interrupt. This setting causes the this interrupt to fire so that you know when to put more data out of your buffer into the UART. As you will see later, the interrupt either sends data or turns off the interrupt (so that it doesn’t just continue to interrupt with no data to send…that’s bad), so this routine must turn on the interrupt again when there is data to send. I think this is a confusing way to do it, but it is what it is.

uart_send_string(const char* s)

This routine is a simple wrapper around the uart_tx() routine. As you can see in the uart_init() routine, it’s not exactly “code efficient” to send a string to the UART one character at a time. In this routine, we are taking the string and simply iterating through the it and dumping the characters into the uart_tx() routine.

  void uart_send_string(const char* s) {
    while (*s)
    uart_tx(*s++);
  }

The observant will notice that there is no error checking here. The ring buffers are only 256 characters long. If you pass a 260 character string to this routine, the buffer will overlap and, at best, you will only get the last four characters sent to the UART. This is because the routine packs the buffer so quickly that the UART can’t keep up. This is an area of improvement for my code, but I haven’t done anything yet. I think that a simple check of the buffer pointers will allow you to insert a small delay if you detect an potential overlap condition, allowing the UART to catch up.

uart_check()

This routine checks the receive buffer for characters and processes them. I will publish a skeleton of my actual code because there is quite a few lines of code here. This is where I implement the interactive part of the scoreboard (i.e. the H, h, I, i, G, g, b, s, o, d commands and others)

  void uart_check()
  {
    while (curinptr != inptr) {
      switch (in[curinptr]) {
        case ‘.’:
        default:
      }
    }
  }

This routine is called from the tight loop in the main routine. It checks the input buffer for new data and then parses the new data character by character using a simple switch structure. It’s tight, fast, and restrictive enough to encourage me to KISS (keep it simple stupid).


ISR (USART_RX_vect)

This is an interrupt vector for when data has been received on the micro’s receive buffer. It is important to service this routine and get out as quickly as possible. There are only two lines and they simply add the data to the receive ring buffer and then increment the buffer pointer.

  ISR (USART_RX_vect)
  {
    in[inptr] = UDR0; // Fetch the recieved byte value into the variable “ByteReceived”
    inptr++;
  }


ISR (USART_UDRE_vect)

This interrupt handles sending the data from the transmit ring buffer to the micro’s UART. Again, it is always a good idea to take care of business and get out of your interrupt routines, so the code is intentionally short and to the point.

  ISR (USART_UDRE_vect)
  {
    if (curoutptr != outptr) {
      UDR0 = out[curoutptr];
      curoutptr++;
    } else {
      UCSR0B &= ~(1«UDRIE0);
    }
  }

The first half of the IF statement checks to see if the current output pointer is not the same as the ring buffer pointer. If they are not, that means that there is data in the buffer that has not been sent. Put a byte on the output register and increment the current output pointer. If the current output pointer and the transmit ring buffer pointer are the same, that means there’s nothing to do, so you simply disable the interrupt. Remember, back in the uart_tx() routine, you enable the interrupt again when there is data to send.

Okay, I’m on a roll with the long blog posts. Hopefully it was coherent enough to help you out. As always, please feel free to comment and I will try to respond.

2 comments:

  1. I actually enjoyed reading through this posting.Many thanks.
    SMPS Battery Charger

    ReplyDelete
  2. Ken - did you ever finish this? Want to make (sell) 4 more for my lacrosse club? Need to include a timer. email me if so to discuss:
    j c i r c u i t @ i o a r e . c o m

    ReplyDelete