Re: writing ISR for UART
- From: Mark Borgerson <mborgerson@xxxxxxxxxxx>
- Date: Fri, 8 Feb 2008 09:30:37 -0800
In article <47ac830d$0$15002$8404b019@xxxxxxxxxxxxxxx>,
david@xxxxxxxxxxxxxxxxxxxxxxxxxxxxx says...
Mark Borgerson wrote:
In article <47ac2c51$0$14988$8404b019@xxxxxxxxxxxxxxx>,
david@xxxxxxxxxxxxxxxxxxxxxxxxxxxxx says...
Mark Borgerson wrote:I've seen and used queue code that compares the get and put pointers to
In article <MN2dnVFbIrGdszbanZ2dnUVZ_uidnZ2d@xxxxxxxxxxxx>, dta@xxxxxxxx<snip>
says...
"sohagiut" <sohagiut@xxxxxxxxx> wrote in message
The reason that your code (with the modifications you mentioned inFor example, in the code below, if the interrupt occurs betweenThis looks a lot like code I've used successfully for more than
approximately points /* 1 */ and /* 2 */ there may be serious logical
trouble. I'll leave it to you to figure out why.
struct
{
int putpoint;
int getpoint;
int nchars;
unsigned char body[QSIZE];
} queue;
_interrupt_ void rx_isr(void)
{
unsigned char c;
/* Get c from hardware */
if (queue.nchars < QSIZE)
{
queue.body[queue.putpoint] = c;
queue.nchars++;
queue.putpoint++;
if (queue.putpoint >= QSIZE)
queue.putpoint = 0;
}
}
unsigned char get_a_char_from_queue(void)
{
unsigned char return_value = 0;
/* 1 */
if (queue.nchars)
{
queue.nchars--;
return_value = queue.body[queue.getpoint];
queue.getpoint++;
if (queue.getpoint >= QSIZE)
queue.getpoint = 0;
}
/* 2 */
return(return_value);
}
a decade. Is there really a problem if q.nchars is accessed
atomically and the q.nchars-- operation occurs in a single
uninterruptible instruction? On the 68K machine where i
run the code,
q.nchars--; becomes subq.w #1, 4(A1)
so if the interrupt handler adds a character to the queue while
the get_a_char_from_queue() function is executing there
should be no problems. q.nchars is the only variable that
is modified by both the interrupt handler and by the
getchar routine.
I forsee a problem if queue.nchars was a 16-bit or
32-bit integer and the code was running on an 8-bit
processor. nchars could get messed up if an interrupt
occured between the mulitple instructions needed to
decrement the value.
another post, and a little care to avoid buffer overflows) is safe on
the m68k is that the m68k has an atomic decrement instruction. For many
embedded processors, that is not the case. First, it could be that the
nchars variable is wider than the processor (16-bit int on an 8-bit
processor, for example). This is generally due to the programmer not
understanding their target - it's rare that you would need more than 256
bytes of buffer on an 8-bit micro, so the programmer has picked the
wrong types when they used "int". Secondly, many modern processors are
RISC load-store architectures, so that the decrement is done in three
operations (load the old value, decrement it, store it again) and the
interrupt can break into any of these.
If you *don't* have access to such an atomic decrement, you have two
options - disable interrupts around the critical code to make the
operation atomic, or use a better buffer structure! (Hint - do you
really need nchars?)
detect whether a character is available. I avoided it for a couple of
reasons:
* the pointer comparisons are not atomic on the 68K although that may
not matter when checking for getptr != putptr
You simply calculate "nchars = putptr - getptr", then adjust if there
has been a wrap around the end of the buffer. It does not matter that
this is not atomic - if a new character has come in during this
operation, then the new putptr will be greater than the one used, and
your nchars will be one character too small. But that's just the same
as if the new character came in after your code had finished.
* there are times when it is handy to have a count of the characters
in the buffer. With that count you can implement a faster loop to
pull characters from the queue. It's easier to increment and
decrement a counter at one instruction each, than it is to get
the queue length by pointer arithmetic that accounts for queue
wraparound.
Thus you calculate "nchars = putptr - getptr" at the start of your code,
and any new incoming characters are ignored until you next run the code
- just as if they arrived after the code was finished.
That sounds simple--but there are a few details to remember:
1. putptr can be smaller than getptr if the buffer has just
wrapped around.
2. because of #1, you have to be careful about correcting
for buffer wrap if you intend to actually use the character
count in a loop and not just compare it with zero.
3. pointer arithmetic on 8 or 16-bit processors may involve
quite a large number of instructions.
With 8-bit processors, it may be best to limit the buffer
size to something less than 256 bytes. If you need maximum
efficiency, you could also use the technique of making
the buffer length equal to an even power of 2. Then you
can handle buffer wrap with a single AND instruction.
I don't think I've had to worry that much about performance
since my 6502 and 6800 assembly coding days.
I do use that technique in my serial handler for the AT91SAM7
processor. There, I use the DMA support in the Atmel chips,
so there is not an interrupt for each character or FIFO.
That leaves me with comparing the getptr with the DMA
receive pointer to detect incoming characters.
One of the first uses for my serial I/O code on a new system
is always as part of the monitor used to upload new code. If
you start getting a lot of checksum errors when uploading
new code, it's a good indication that you've missed something
in the serial I/O driver.
The simple example code here also lacks a few elements:
1: there is no error reporting for queue full errors
or queue empty errors. Without those error reports,
the program can't distinguish between a valid 0x00
character and the result of a get operation on an
empty queue.
2. There should be a ChAvailable() function that returns
the number of characters available. This prevents either
blocking on an empty queue or the queue empty 0x00
character problem.
3. Many UARTS have just one interrupt, which has to handle
receive, transmit and error interrupts, so the
ISR will be more complex than that shown. If the UART
has a FIFO, it gets to be even more fun!
All this is correct - the code above is (hopefully!) just an example.
Yep. There's usually a lot more going on under the hood of a good
serial driver than we've discussed in these examples. It gets to
be even more fun if there are FIFOs or DMA and you really want to
minimize time in the handlers in high-traffic situations.
I went through all that in a data multiplexer/concentrator application
a decade or two back. We didn't exactly have to count cycles,
but with 4 input channels and no FIFOs, we did have to optimize
the receive interrupt handlers as best we could. The input data
could be pretty 'bursty' and we had to make sure we could handle
four separate channels in less than one character time at 115KB.
IIRC the result involved a separate handler for each channel and
each handler used hard-coded register addresses rather than
computing addresses from a channel index.
Mark Borgerson
.
- Follow-Ups:
- Re: writing ISR for UART
- From: David Brown
- Re: writing ISR for UART
- References:
- writing ISR for UART
- From: sohagiut
- Re: writing ISR for UART
- From: David T. Ashley
- Re: writing ISR for UART
- From: Mark Borgerson
- Re: writing ISR for UART
- From: David Brown
- Re: writing ISR for UART
- From: Mark Borgerson
- Re: writing ISR for UART
- From: David Brown
- writing ISR for UART
- Prev by Date: Re: writing ISR for UART
- Next by Date: Re: writing ISR for UART
- Previous by thread: Re: writing ISR for UART
- Next by thread: Re: writing ISR for UART
- Index(es):
Relevant Pages
|