Voting resources, early voting, and poll worker information - VOTE. ... Adafruit is open and shipping.
0

AVR uart tx buffer
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

AVR uart tx buffer

by FazJaxton on Fri Apr 16, 2010 10:44 pm

I'm trying to write an interrupt-driven UART TX buffer program for the ATMega328p, using Arduino as a development platform. However, I'm having some trouble with the interrupt handling. My code looks like this:

Code: Select all | TOGGLE FULL SIZE
#define USART_BUFFER_SIZE 128
#define USART_8BIT (3 << UCSZ00)

static uint8_t usart_buffer[USART_BUFFER_SIZE];
static uint8_t *writep;
static uint8_t *readp;

#define usart_int_enable() (UCSR0B |= (1<<UDRIE0))
#define usart_int_disable() (UCSR0B &= ~(1<<UDRIE0))

ISR(USART_UDRE_vect) {
   if (readp == writep) {
      usart_int_disable();
   } else {
      UDR0 = (*readp++);
      if (readp == usart_buffer + USART_BUFFER_SIZE)
         readp = usart_buffer;
   }
}

void init_usart ()
{
   UBRR0H = 0;
   UBRR0L = 16;  /* 115200 at 16MHz */
   UCSR0A |= (1 << U2X0);

   UCSR0C = USART_8BIT;

   readp = usart_buffer;
   writep = usart_buffer;

   UCSR0B = (1 << TXEN0) | (1 << RXEN0);
}

void uart_send (uint8_t byte)
{
   /*
   if (writep == usart_buffer + USART_BUFFER_SIZE - 1)
      while (readp == usart_buffer);
   else
      while (readp == writep + 1);
      */

   *writep++ = byte;
   usart_int_enable();
}

void print (const char *s)
{
   while (*s)
      uart_send(*s++);
}

int main (void)
{
   init_usart ();
   sei();

   print ("Hello world!\n\r");

   while (1);
}


The interrupt is not enabled until the first byte is written into the buffer and the write pointer has been incremented. I then expect the interrupt routine to continue printing characters until the buffer is empty, at which point it disables the interrupt. When more characters are added, the interrupts start again.

However, when I run this, I see nothing in my PC terminal program. I discovered that it worked if I "primed" the buffer by hard-coding a few characters into it at startup and increasing the write pointer to match. Two characters seems to be the magic number. I then see the two primed characters followed by "Hello world!". I then removed the priming and changed my interrupt handler to this:

Code: Select all | TOGGLE FULL SIZE
ISR(USART_UDRE_vect) {
   if (readp == writep) {
      UDR0 = '0';  /*  <<< HERE IS THE DIFFERENCE */
   } else {
      UDR0 = (*readp++);
      if (readp == usart_buffer + USART_BUFFER_SIZE)
         readp = usart_buffer;
   }
}


Now when there's nothing to print, it sends a '0' instead of disabling the interrupt. My output now looks like this:

00Hello world!
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000


This is the same result, just displayed a little differently. There are two complete characters printed between the time that the interrupt is enabled and the time that the interrupt routine is able to determine that the buffer is not empty. I have looked through the assembly listing, and everything appears okay to me. (I'm glad to post that if requested, but I'll leave it out for now to avoid making a bigger mess). I have tried making the read and write pointers volatile, but it didn't help. I don't see how it can take dozens of clock cycles before the interrupt routine is able to see that the read and write pointers are not equal. Are there deep secrets of the AVR that I don't know about? :)
FazJaxton
 
Posts: 59
Joined: Mon Sep 28, 2009 2:18 pm
Location: Kansas, USA

Re: AVR uart tx buffer

by westfw on Sat Apr 17, 2010 2:56 am

There are two complete characters printed between the time that the interrupt is enabled and the time that the interrupt routine is able to determine that the buffer is not empty.

This sounds typical of uarts. The first character written to an idle UART transmitter goes straight into the TX shift register, so the data register stays empty. If the interrupt gets triggered on the data register BECOMING empty, then it won't trigger. Each uart is slightly different, and I'm not intimately familiar with Atmel's implementation, but there was this earlier discussion that included an interrupt-driven transmitter:
http://www.arduino.cc/cgi-bin/yabb2/YaB ... 1235799875
It works by writing characters directly to the UART registers until it can't anymore (and then starts writing to the SW buffer.)

westfw
 
Posts: 1717
Joined: Fri Apr 27, 2007 1:01 pm
Location: SF Bay area

Re: AVR uart tx buffer

by FazJaxton on Sat Apr 17, 2010 3:31 pm

Thanks for the response, and I think that avoiding the software buffer altogether might be a good idea where it's possible.

However, the buffer that I'm referring to isn't the UART hardware buffer, but the software buffer itself. It seems that even though the value is stored to RAM from the registers, when it's read back from RAM in the interrupt, it doesn't reflect its new state for the first two reads. Here is equivalent source code for what seems to be going on to me:

Code: Select all | TOGGLE FULL SIZE
static int val = 0;

void my_func (void)
{
   if (val == 0) {
      print ("zero")
   } else {
      print ("nonzero")
   }
}

int main (void)
{
   val = 1;
   my_func();

   return 0;
}


For what seems to be to be happening, the output of this program is "zero", even though I've already set "val" to be nonzero. In my real code, I'm setting the write pointer to be ahead of the read pointer, but for two calls, the interrupt acts as though they are the same (printing '0' instead of the characters that I've written to the buffer). I cannot figure out why the interrupt cannot tell that the pointer has changed when it has.
FazJaxton
 
Posts: 59
Joined: Mon Sep 28, 2009 2:18 pm
Location: Kansas, USA

Re: AVR uart tx buffer

by Entropy on Mon Apr 19, 2010 10:03 am

As westfw mentioned, UARTs typically fire off an interrupt when they "become" ready.

Another way of saying it is that they fire an interrupt when they complete an operation.

Most interrupt-driven UART routines use the interrupt to "flush" a buffer - When the UART finishes, it fires the interrupt that puts the next byte in its register.

HOWEVER - the key here is that it'll never finish the first byte if it isn't started somehow to begin with! Enabling the interrupt alone won't fire it.

(After reading your code, it looks like your problem is actually slightly different... Not sure what is going on, it could be one of those cases where you need to declare variables as volatile but I'm not sure...)
Entropy
 
Posts: 472
Joined: Tue Jan 08, 2008 12:43 am
Location: Owego, NY USA

Re: AVR uart tx buffer

by FazJaxton on Mon Apr 26, 2010 1:33 pm

It turns out there were two issues here. First, when I claimed that I had made the read and write pointers volatile, I was wrong. I had tried but failed. I had written:
Code: Select all | TOGGLE FULL SIZE
static volatile uint8_t *writep;

When I should have written:
Code: Select all | TOGGLE FULL SIZE
static uint8_t * volatile writep;

The first makes the data that the pointer points to volatile, and the second makes the pointer itself volatile.

Correcting the volatile usage fixed my problem, but I didn't understand why. I had looked at the assembly generated from my code and seen that it seemed to be correct even when the values weren't volatile. However, what I missed was that there were two copies of my "uart_send" function: one standalone and one inlined. The compiler decided to inline the call to "uart_send" into "print" to avoid the function call overhead. However, since uart_send is not static, it also has to produce a copy to be linked against by other objects. I was looking at the standalone version and not the inlined version. In the standalone, everything was fine, but the volatile made a difference in the inlined version (the write pointer was not stored back to RAM from registers until after the interrupt bit was set).

Another project, something new learned!
FazJaxton
 
Posts: 59
Joined: Mon Sep 28, 2009 2:18 pm
Location: Kansas, USA

Re: AVR uart tx buffer

by FazJaxton on Mon Apr 26, 2010 1:41 pm

An additional note: In looking at other implementations of UART buffers, I discovered that this pointer-based implementation is quite inefficient for AVR. Better implementations I saw use indices into the buffer. For code semantics, I prefer pointers over indices, and on architectures where the native word size is the same as your pointer size, this is fine. On AVR, though, using pointers forces the 8-bit machine to do 16-bit math for every operation, requiring more steps, and causing potential problems with atomicity (a write can be half complete when an interrupt occurs). If you instead use indices, the math can be 8 bits for indices into arrays of 256 bytes or less.

I have fixed my implementation and the result is much smaller code. :D
FazJaxton
 
Posts: 59
Joined: Mon Sep 28, 2009 2:18 pm
Location: Kansas, USA

Please be positive and constructive with your questions and comments.