Forums

LPC17xx question - ISR driven Serial possible?

Started by Ron February 26, 2010
Is it possible to have a fully ISR driven serial driver on the LPC17xx series of chips? One that would follow this form:

serial_putChar(char ch){
sendBuffer[in++] = ch;
UART1->IER |= UART_IER_THREINT_EN;
}

serialISR(){
if(UART1->IIR & UART_IIR_INTID_THRE){
UART1->THR = sendBuffer[out++];
if (out == in){
UART1->IER &= ~UART_IER_THREINT_EN;
}
}
}

Or something similar to that.

The biggest problem I am having is that it doesn't appear that it is possible to (or I can't figure out how to) enable the THRE interrupt in the IER and cause an immediate THRE interrupt in the IIR.

Ultimately I am using FreeRTOS, so should it be needed, I can create a serial driver task that would fill the THR register for me if needed. But I wanted to see if this would work first.

An Engineer's Guide to the LPC2100 Series

Your serial_putChar routine needs to force a byte into the
FIFO (bypassing your buffer) if the FIFO is empty, and enable
the THRE interrupt. Unlike other serial implementations I've
seen, this one only generates a THRE interrupt when the FIFO
transitions to empty, rather than continuing to generate
interrupts while empty.

You don't need to disable the THRE interrupt, as it won't
generate further interrupts once the FIFO is empty. Which is
also why initiating the send process cannot be done from inside
the ISR.

Mike

Quoting Ron :

> Is it possible to have a fully ISR driven serial driver on the
> LPC17xx series of chips? One that would follow this form:
>
> serial_putChar(char ch){
> sendBuffer[in++] = ch;
> UART1->IER |= UART_IER_THREINT_EN;
> }
>
> serialISR(){
> if(UART1->IIR & UART_IIR_INTID_THRE){
> UART1->THR = sendBuffer[out++];
> if (out == in){
> UART1->IER &= ~UART_IER_THREINT_EN;
> }
> }
> }
>
> Or something similar to that.
>
> The biggest problem I am having is that it doesn't appear that it is
> possible to (or I can't figure out how to) enable the THRE interrupt
> in the IER and cause an immediate THRE interrupt in the IIR.
>
> Ultimately I am using FreeRTOS, so should it be needed, I can create
> a serial driver task that would fill the THR register for me if
> needed. But I wanted to see if this would work first.
>

--- In l..., "Ron" wrote:
>
> Is it possible to have a fully ISR driven serial driver on the LPC17xx series of chips? One that would follow this form:
>
> serial_putChar(char ch){
> sendBuffer[in++] = ch;
> UART1->IER |= UART_IER_THREINT_EN;
> }
>
> serialISR(){
> if(UART1->IIR & UART_IIR_INTID_THRE){
> UART1->THR = sendBuffer[out++];
> if (out == in){
> UART1->IER &= ~UART_IER_THREINT_EN;
> }
> }
> }
>
> Or something similar to that.
>
> The biggest problem I am having is that it doesn't appear that it is possible to (or I can't figure out how to) enable the THRE interrupt in the IER and cause an immediate THRE interrupt in the IIR.
>
> Ultimately I am using FreeRTOS, so should it be needed, I can create a serial driver task that would fill the THR register for me if needed. But I wanted to see if this would work first.
>

You will need to implement a real simple state machine for the UART. In your putchar function check the current state. If the state is IDLE (buffer is empty) put the first byte in the UART transmit holding register and set the state to NOT IDLE. If the state evaluates NOT IDLE when entering the putchar function just place the byte in the transmit buffer. In the transmit interrupt routine set the state back to IDLE when the last byte has been sent.

Regards,

Frank

--- In l..., "carnac100" wrote:
>
> --- In l..., "Ron" wrote:
> >
> > Is it possible to have a fully ISR driven serial driver on the LPC17xx series of chips? One that would follow this form:
> >
> > serial_putChar(char ch){
> > sendBuffer[in++] = ch;
> > UART1->IER |= UART_IER_THREINT_EN;
> > }
> >
> > serialISR(){
> > if(UART1->IIR & UART_IIR_INTID_THRE){
> > UART1->THR = sendBuffer[out++];
> > if (out == in){
> > UART1->IER &= ~UART_IER_THREINT_EN;
> > }
> > }
> > }
> >
> > Or something similar to that.
> >
> > The biggest problem I am having is that it doesn't appear that it is possible to (or I can't figure out how to) enable the THRE interrupt in the IER and cause an immediate THRE interrupt in the IIR.
> >
> > Ultimately I am using FreeRTOS, so should it be needed, I can create a serial driver task that would fill the THR register for me if needed. But I wanted to see if this would work first.
> > You will need to implement a real simple state machine for the UART. In your putchar function check the current state. If the state is IDLE (buffer is empty) put the first byte in the UART transmit holding register and set the state to NOT IDLE. If the state evaluates NOT IDLE when entering the putchar function just place the byte in the transmit buffer. In the transmit interrupt routine set the state back to IDLE when the last byte has been sent.
>
> Regards,
>
> Frank
>

..and beware the race condition where the interrupt routine sets the state to IDLE just after your putchar has tested it and found it NOT-IDLE. This can result in single chars. being occasionally delayed until the next putchar call, characters getting swapped or even tx fail forever if you do not precisely follow the logic posted by Frank, (ie. if the state is tested as NOT IDLE, do not set the state variable to NOT IDLE. This might seem obvious, but many experienced engineers 'automatically' set state variables to an explicit value when an action occurs, irrespective of any previous value. This is good for clarity and debugging in most 'application' state machines, but not when dealing with interrupt interactions:).

If the race problem could affect your comms protocols, you could disable transmit interrupts if the test evaluates as NOT IDLE and perform the test again. Re-enable them after acting on the result.

Rgds,
Martin
Rgds,
Martin

I wanted to add my $.02 to the issue of state machines. States should
only be set inside the state machine, never from external functions
such as an interrupt handler, or another task. "Jamming" a state
machine may work 99.999% of the time. You'll spend a lot of time on
the .001% failures.

Dave

On Sun, Feb 28, 2010 at 11:29 PM, mjames_doveridge
wrote:
> --- In l..., "carnac100" wrote:
>>
>> --- In l..., "Ron" wrote:
>> >
>> > Is it possible to have a fully ISR driven serial driver on the LPC17xx series of chips? One that would follow this form:
>> >
>> > serial_putChar(char ch){
>> > sendBuffer[in++] = ch;
>> > UART1->IER |= UART_IER_THREINT_EN;
>> > }
>> >
>> > serialISR(){
>> > if(UART1->IIR & UART_IIR_INTID_THRE){
>> > UART1->THR = sendBuffer[out++];
>> > if (out == in){
>> > UART1->IER &= ~UART_IER_THREINT_EN;
>> > }
>> > }
>> > }
>> >
>> > Or something similar to that.
>> >
>> > The biggest problem I am having is that it doesn't appear that it is possible to (or I can't figure out how to) enable the THRE interrupt in the IER and cause an immediate THRE interrupt in the IIR.
>> >
>> > Ultimately I am using FreeRTOS, so should it be needed, I can create a serial driver task that would fill the THR register for me if needed. But I wanted to see if this would work first.
>> >
>>
>> You will need to implement a real simple state machine for the UART. In your putchar function check the current state. If the state is IDLE (buffer is empty) put the first byte in the UART transmit holding register and set the state to NOT IDLE. If the state evaluates NOT IDLE when entering the putchar function just place the byte in the transmit buffer. In the transmit interrupt routine set the state back to IDLE when the last byte has been sent.
>>
>> Regards,
>>
>> Frank
>> ..and beware the race condition where the interrupt routine sets the state to IDLE just after your putchar has tested it and found it NOT-IDLE. This can result in single chars. being occasionally delayed until the next putchar call, characters getting swapped or even tx fail forever if you do not precisely follow the logic posted by Frank, (ie. if the state is tested as NOT IDLE, do not set the state variable to NOT IDLE. This might seem obvious, but many experienced engineers 'automatically' set state variables to an explicit value when an action occurs, irrespective of any previous value. This is good for clarity and debugging in most 'application' state machines, but not when dealing with interrupt interactions:).
>
> If the race problem could affect your comms protocols, you could disable transmit interrupts if the test evaluates as NOT IDLE and perform the test again. Re-enable them after acting on the result.
>
> Rgds,
> Martin
> Rgds,
> Martin
>
--- In l..., David Smead wrote:
>
> I wanted to add my $.02 to the issue of state machines. States should
> only be set inside the state machine, never from external functions
> such as an interrupt handler, or another task. "Jamming" a state
> machine may work 99.999% of the time. You'll spend a lot of time on
> the .001% failures.
>
> Dave
>

You are correct 99.999% of the time:)

Unfortunately, these cases where interrupt interaction is the primary function, 'nice, safe' rules sometimes have to be broken. I can agree that great care has to be taken! This case is a very simple state machine, hardly worthy of the name, and serialization of the events, (by disabling the interrupt - often the only means available), is only necessary when testing the state for NOT IDLE.

There are doubtless more 'visibly secure' means of attaining the same ends. If an OS is being used, a thread on top could handle semaphore signalling from the interrupt to control buffering, but this puts another layer of processing on top of the UART - probably not justified unless whole buffer pointers, rather than single chars, are being passed to the tx.interrupt handler. On these simple controllers, semaphore handling often requires a brief interrupt-disable anyway if the controller thread is to be set running.

Rgds,
Martin

". If an OS is being used, a thread on top could handle semaphore signalling
from the interrupt to control buffering, but this puts another layer of
processing on top of the UART - probably not justified unless whole buffer
pointers, rather than single chars, are being passed to the tx.interrupt
handler."

I do not agree with that.. Semaphores and events are system level entities,
and they can be processed by SVC Pending SV. Here I mean, no interrupt
disabling. Interrupt just marks pending sv flag, so when the cpu returns
from interrupt(s), it also process pending sv code, so updates events, and
semaphores. This is very cheap.

When I use UART as terminal -one character comes, one character goes-, I use
a middle layer FIFO, that carries out the UART incoming and outgoing data
between interrupt and application code. That restricts all the state machine
on app layer.. If you use C++, it is just a declaration of variable such
that:

UartPipe myPipe;

that's it.. But when I use framed transportation, here I mean, data is
carried out in frames, I again use FIFO but this time fifo caries frame
struct instead of char itself.. Here the state machine works at interrupt
level, because framed transportation is structured data spread over time..
Result is in FIFO, app code may have separate state machine..

From: l... [mailto:l...] On Behalf Of
mjames_doveridge
Sent: 01 March, 2010 16:11
To: l...
Subject: [lpc2000] Re: LPC17xx question - ISR driven Serial possible?

--- In l... , David
Smead wrote:
>
> I wanted to add my $.02 to the issue of state machines. States should
> only be set inside the state machine, never from external functions
> such as an interrupt handler, or another task. "Jamming" a state
> machine may work 99.999% of the time. You'll spend a lot of time on
> the .001% failures.
>
> Dave
>

You are correct 99.999% of the time:)

Unfortunately, these cases where interrupt interaction is the primary
function, 'nice, safe' rules sometimes have to be broken. I can agree that
great care has to be taken! This case is a very simple state machine, hardly
worthy of the name, and serialization of the events, (by disabling the
interrupt - often the only means available), is only necessary when testing
the state for NOT IDLE.

There are doubtless more 'visibly secure' means of attaining the same ends.
If an OS is being used, a thread on top could handle semaphore signalling
from the interrupt to control buffering, but this puts another layer of
processing on top of the UART - probably not justified unless whole buffer
pointers, rather than single chars, are being passed to the tx.interrupt
handler. On these simple controllers, semaphore handling often requires a
brief interrupt-disable anyway if the controller thread is to be set
running.

Rgds,
Martin
Let me know what you think about this, because I have a solution I have
tried, but it isn't quite working either. Take a look:

So I created this task to processes characters from the ISR:

void xUARTsendTask(void *pvParameter)
{
uint8_t sendChar;
vSemaphoreCreateBinary(xSendSemaphore);
xSemaphoreTake(xSendSemaphore, 0);
for( ;; ){
xQueueReceive(xCharsForTx, &sendChar, portMAX_DELAY); // block
waiting for main to send a character
UART1->THR = sendChar; // send character when it is
received
xSemaphoreTake(xSendSemaphore, portMAX_DELAY); // block
waiting for ISR signal Semaphore
}
}
The new putchar Call is this:
signed portBASE_TYPE serial_putChar(signed portCHAR cOutChar,
portTickType xBlockTime )
{
signed portBASE_TYPE xReturn = pdFAIL;
if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) == pdPASS ){
// place character into buffer - this will trigger the send task
xReturn = pdPASS;
}
return xReturn;
}
The ISR would be very simple
void serialISR(void)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
portCHAR cChar;
if(UART1->IIR & UART_IIR_INTID_THRE){ // when there is a Transmit
Hold Register Empty Interrtupt
{
xSemaphoreGiveFromISR(xSendSemaphore, &xHigherPriorityTaskWoken); //
signal semaphore that THR is ready for char
}
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}

I think the concept is right, but I am still having problems with it -
the Transmitter sends to characters and then bombs out.

--- In l..., "Deniz Can Cigsar" wrote:
>
> ". If an OS is being used, a thread on top could handle semaphore
signalling
> from the interrupt to control buffering, but this puts another layer
of
> processing on top of the UART - probably not justified unless whole
buffer
> pointers, rather than single chars, are being passed to the
tx.interrupt
> handler."
>
> I do not agree with that.. Semaphores and events are system level
entities,
> and they can be processed by SVC Pending SV. Here I mean, no interrupt
> disabling. Interrupt just marks pending sv flag, so when the cpu
returns
> from interrupt(s), it also process pending sv code, so updates events,
and
> semaphores. This is very cheap.
>
> When I use UART as terminal -one character comes, one character goes-,
I use
> a middle layer FIFO, that carries out the UART incoming and outgoing
data
> between interrupt and application code. That restricts all the state
machine
> on app layer.. If you use C++, it is just a declaration of variable
such
> that:
>
> UartPipe myPipe;
>
> that's it.. But when I use framed transportation, here I mean, data is
> carried out in frames, I again use FIFO but this time fifo caries
frame
> struct instead of char itself.. Here the state machine works at
interrupt
> level, because framed transportation is structured data spread over
time..
> Result is in FIFO, app code may have separate state machine..
>
> From: l... [mailto:l...] On
Behalf Of
> mjames_doveridge
> Sent: 01 March, 2010 16:11
> To: l...
> Subject: [lpc2000] Re: LPC17xx question - ISR driven Serial possible?
>
> --- In l... ,
David
> Smead david.smead@ wrote:
> >
> > I wanted to add my $.02 to the issue of state machines. States
should
> > only be set inside the state machine, never from external functions
> > such as an interrupt handler, or another task. "Jamming" a state
> > machine may work 99.999% of the time. You'll spend a lot of time on
> > the .001% failures.
> >
> > Dave
> > You are correct 99.999% of the time:)
>
> Unfortunately, these cases where interrupt interaction is the primary
> function, 'nice, safe' rules sometimes have to be broken. I can agree
that
> great care has to be taken! This case is a very simple state machine,
hardly
> worthy of the name, and serialization of the events, (by disabling the
> interrupt - often the only means available), is only necessary when
testing
> the state for NOT IDLE.
>
> There are doubtless more 'visibly secure' means of attaining the same
ends.
> If an OS is being used, a thread on top could handle semaphore
signalling
> from the interrupt to control buffering, but this puts another layer
of
> processing on top of the UART - probably not justified unless whole
buffer
> pointers, rather than single chars, are being passed to the
tx.interrupt
> handler. On these simple controllers, semaphore handling often
requires a
> brief interrupt-disable anyway if the controller thread is to be set
> running.
>
> Rgds,
> Martin
>
I would recommend a software fifo that works as a carrier between sender and
ISR. You do not need a task to process TX data, its waste of resources..
Also you are not using 16 level fifo of the hardware..

My put char code is like this:

1. Disable Thre Irq, this wont hurt, it is just thre not the whole
interrupt system

2. Check if there is room in hardware FIFO, if there is room, push the
data into it, goto step 5

3. If software fifo is full, register txEvent, enable Thre IRQ, wait
for txEvent, goto step 1

4. Push data to software fifo

5. Enable Thre Irq

The interrupt routin is like this:

1. Read IIR

2. for thre:

a. move upto 16 bytes of data from software fifo into hardware fifo

b. if registered, trigger txEvent

This will use both of 16 level hardware fifo, and additional software fifo.
Also no thread to handle tx flow, interrupt driven such that the output is
continuous if you have more than 16 bytes to transmit.. Realy low resource
to waste..

From: l... [mailto:l...] On Behalf Of
Ron
Sent: 01 March, 2010 19:17
To: l...
Subject: [lpc2000] Re: LPC17xx question - ISR driven Serial possible?

Let me know what you think about this, because I have a solution I have
tried, but it isn't quite working either. Take a look:

So I created this task to processes characters from the ISR:

void xUARTsendTask(void *pvParameter)
{
uint8_t sendChar;
vSemaphoreCreateBinary(xSendSemaphore);
xSemaphoreTake(xSendSemaphore, 0);
for( ;; ){
xQueueReceive(xCharsForTx, &sendChar, portMAX_DELAY); // block
waiting for main to send a character
UART1->THR = sendChar; // send character when
it is received
xSemaphoreTake(xSendSemaphore, portMAX_DELAY); //
block waiting for ISR signal Semaphore
}
}

The new putchar Call is this:

signed portBASE_TYPE serial_putChar(signed portCHAR cOutChar, portTickType
xBlockTime )
{
signed portBASE_TYPE xReturn = pdFAIL;
if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) == pdPASS ){
// place character into buffer - this will trigger the send task
xReturn = pdPASS;
}
return xReturn;
}
The ISR would be very simple
void serialISR(void)

{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
portCHAR cChar;
if(UART1->IIR & UART_IIR_INTID_THRE){ // when there is a Transmit
Hold Register Empty Interrtupt
{
xSemaphoreGiveFromISR(xSendSemaphore, &xHigherPriorityTaskWoken);
// signal semaphore that THR is ready for char
}
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}

I think the concept is right, but I am still having problems with it - the
Transmitter sends to characters and then bombs out.

--- In l..., "Deniz Can Cigsar" wrote:

>
> ". If an OS is being used, a thread on top could handle semaphore
signalling
> from the interrupt to control buffering, but this puts another layer of
> processing on top of the UART - probably not justified unless whole buffer
> pointers, rather than single chars, are being passed to the tx.interrupt
> handler."
>
> I do not agree with that.. Semaphores and events are system level
entities,
> and they can be processed by SVC Pending SV. Here I mean, no interrupt
> disabling. Interrupt just marks pending sv flag, so when the cpu returns
> from interrupt(s), it also process pending sv code, so updates events, and
> semaphores. This is very cheap.
>
> When I use UART as terminal -one character comes, one character goes-, I
use
> a middle layer FIFO, that carries out the UART incoming and outgoing data
> between interrupt and application code. That restricts all the state
machine
> on app layer.. If you use C++, it is just a declaration of variable such
> that:
>
> UartPipe myPipe;
>
> that's it.. But when I use framed transportation, here I mean, data is
> carried out in frames, I again use FIFO but this time fifo caries frame
> struct instead of char itself.. Here the state machine works at interrupt
> level, because framed transportation is structured data spread over time..
> Result is in FIFO, app code may have separate state machine..
>
> From: l... [mailto:l...] On Behalf
Of
> mjames_doveridge
> Sent: 01 March, 2010 16:11
> To: l...
> Subject: [lpc2000] Re: LPC17xx question - ISR driven Serial possible?
>
> --- In l... , David
> Smead david.smead@ wrote:
> >
> > I wanted to add my $.02 to the issue of state machines. States should
> > only be set inside the state machine, never from external functions
> > such as an interrupt handler, or another task. "Jamming" a state
> > machine may work 99.999% of the time. You'll spend a lot of time on
> > the .001% failures.
> >
> > Dave
> > You are correct 99.999% of the time:)
>
> Unfortunately, these cases where interrupt interaction is the primary
> function, 'nice, safe' rules sometimes have to be broken. I can agree that
> great care has to be taken! This case is a very simple state machine,
hardly
> worthy of the name, and serialization of the events, (by disabling the
> interrupt - often the only means available), is only necessary when
testing
> the state for NOT IDLE.
>
> There are doubtless more 'visibly secure' means of attaining the same
ends.
> If an OS is being used, a thread on top could handle semaphore signalling
> from the interrupt to control buffering, but this puts another layer of
> processing on top of the UART - probably not justified unless whole buffer
> pointers, rather than single chars, are being passed to the tx.interrupt
> handler. On these simple controllers, semaphore handling often requires a
> brief interrupt-disable anyway if the controller thread is to be set
> running.
>
> Rgds,
> Martin
>
Thanks for the advice. I am working with this now, to see what kind of results I have. The one issue I might with utilizing the FIFO is the system that I am trying to connect to.

There is a driver on the Tx line that has to be turned on and off on a real time comm system, so if I wanted to transmit 1234 the driver system would have to be this:

_____1234_____ TXed characters
|____| driver !enable line

I have this working for 1 character, beyond that, not as much luck.

--- In l..., "Deniz Can Cigsar" wrote:
>
> I would recommend a software fifo that works as a carrier between sender and
> ISR. You do not need a task to process TX data, its waste of resources..
> Also you are not using 16 level fifo of the hardware..
>
>
>
> My put char code is like this:
>
>
>
> 1. Disable Thre Irq, this wont hurt, it is just thre not the whole
> interrupt system
>
> 2. Check if there is room in hardware FIFO, if there is room, push the
> data into it, goto step 5
>
> 3. If software fifo is full, register txEvent, enable Thre IRQ, wait
> for txEvent, goto step 1
>
> 4. Push data to software fifo
>
> 5. Enable Thre Irq
>
>
>
> The interrupt routin is like this:
>
> 1. Read IIR
>
> 2. for thre:
>
> a. move upto 16 bytes of data from software fifo into hardware fifo
>
> b. if registered, trigger txEvent
>
>
>
> This will use both of 16 level hardware fifo, and additional software fifo.
> Also no thread to handle tx flow, interrupt driven such that the output is
> continuous if you have more than 16 bytes to transmit.. Realy low resource
> to waste..
>
>
>
>