Quality RTOS & Embedded Software

 Real time embedded FreeRTOS RSS feed 
Real time embedded FreeRTOS mailing list 
Quick Start Supported MCUs PDF Books Trace Tools Ecosystem TCP & FAT Training




Loading

Call xQueueReceiveFromISR() in task, LPC1114

Posted by henning larsen on January 9, 2013
I am writing a serial.c for the LPC1114 port under LPCXpresso.

LPC11xx has the feature that the interrupt from UART TX empty transmit register (THRE=1) is only edge sensitive and not level sensitive. This means that if interrupt from THRE is masked off AND THRE=1, then no interrupt will be generated as a result of enabling the THRE interrupt. This would correspond to irq handler seeing a level of THRE=1 and not a change of THRE from 0 to 1 (an edge). The latter _would_ generate an interrupt not the former.

The often used method in may ports of serial.c is to have the TX interrupt disabled by the TX interrupt itself, when interrupt handler finds that no more data is in the TX queue and TX interrupt is re-enabled by the task which is feeding data into the TX queue. The enabled interrupt will trigger the interrupt handler which in turn sees the data in the queue and puts them in the transmit holding register. If interrupt handler does'nt find more data, it disables its own interrupt again. And so forth.

This does not work with LPC11xx due to the lack of level senisitivity of the THRE flag.

I have made a solution where the interrupt handler and the task shares a flag (global variable): THR_IsEmpty. This principle is taken from the Uart.c in the LPC11xx_DriverLib.
I use a FreeRTOS queue and in the application task is used the following code to access the queue and the flag: Note that I use the xQueueReceiveFromISR() although it is inside the application task, but IRQ's are disabled so I should'nt call xQueueReceive() - anyway thats my hyposis. The code in the task reads:

portDISABLE_INTERRUPTS(); // Here we are sure to have the queue for ourselves, IRQ handler is off
if(THR_IsEmpty=TRUE)
{//Ok tx irq is stuck. Need a kick-off by a write of the queue front byte to THR transmit register. I must NOT enable IRQ yet!
if( xQueueReceiveFromISR( xCharsForTx, &uChar, &xHigherPriorityTaskWoken_BUT_NOT_USED_HERE_AS_I_AM_ONLY_USER ) == pdTRUE )
{
/* A character was retrieved from the queue so it can be send to the THR now.... */
LPC_USART->THR = uChar; //Send it
THR_IsEmpty=FALSE; //More data
}
else
{
THR_IsEmpty=TRUE; //No more data - its empty
}
}
portENABLE_INTERRUPTS();


It appears to work but I am a bit skeptic as to the robustness due to the following:

portDISABLE_INTERRUPTS expands to __asm volatile ( " cpsid i " ) /* =Interrupt disable */
but when I see the code for xQueueReceiveFromISR() it holds the lines

uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
which expands to
uxSavedInterruptStatus = 0;__asm volatile ( " cpsid i " )
(note the current state of Interrupt is ignored! (is'nt that a port issue?) and the call is matched with the reenable..
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
which expands to
portCLEAR_INTERRUPT_MASK();(void)x
or simply
__asm volatile ( " cpsie i " ) /* =Interrupt enable - unconditionally*/

In other words at the exit of the xQueueReceiveFromISR() interrupt is enabled - this will break the logic in the above task code.

A) Is it ok to call xQueueReceiveFromISR() from a task provided interrupts are disabled?
B) Should'nt the portSET_INTERRUPT_MASK_FROM_ISR()/portCLEAR_INTERRUPT_MASK_FROM_ISR() pair in the LPC1114 port recall the
interrupt state such that it does not enable the interrupt if it was originally disabled when portSET_INTERRUPT_MASK_FROM_ISR()
was called?. I.e. properly restore the state as it was on entry.
C) Better ways?

Thanks for any advice you may have.
henning

RE: Call xQueueReceiveFromISR() in task, LPC1114

Posted by Richard on January 9, 2013
I would be interested in replies to this myself as I have struggled with the same issue. You can look at the serial driver in the LPC2106 demo to see a convoluted solution that probably works. Note that generally using queues to send data into and out of interrupts is done in demos to demonstrate queues being used from interrupts - it is not an efficient solution.

You might also want to look at the UART driver in the FreeRTOS+IO port for the LPC1768, I can't remember how I did it there.

Regards.

RE: Call xQueueReceiveFromISR() in task, LPC1114

Posted by henning larsen on January 9, 2013
The code snippet in my post was garbled up. I try again.
By the way, this web interface at sourceforge.net is lame! There is no preview facility - or am I blind?

portDISABLE_INTERRUPTS(); // Here we are sure to have the queue for ourselves, IRQ handler is off
if(THR_IsEmpty=TRUE)
{ //Ok tx irq is stuck. Need a kick-off by a write of the queue front byte to THR transmit register. Do NOT enable IRQ yet!
if( xQueueReceiveFromISR( xCharsForTx, &uChar, &xHigherPriorityTaskWoken_XX ) == pdTRUE )
{
/* A character was retrieved from the queue so it can be send to the THR now.... */
LPC_USART->THR = uChar; //Send it
THR_IsEmpty=FALSE; //More data
}
else
{
THR_IsEmpty=TRUE; //No more data - its empty
}
}
portENABLE_INTERRUPTS();

RE: Call xQueueReceiveFromISR() in task, LPC1114

Posted by henning larsen on January 9, 2013
Thanks for the tip to look at LPC2106 and LPC1768. The latter code for serial.c I could not find however.
Can you give a path?

Anyway I reviewed the code for xSerialPutChar() for LPC2106 and LPC2129 and
they are both made along the same lines as I described in the initial post.
There is however a subtle flaw in the way the transmitter is kicked back on again:
xQueueReceive() is called to get the dangling byte at head of the queue, but this may fail because the queue and transmitter holding register THR could be empty right after the xQueueSend(). Never the less, the byte read from
the queue is sent to the transmitter holding register THR irrespective of the call's return value This will give spurious
added bytes in the stream.

The original code is this - with my added explanations.

signed portBASE_TYPE xSerialPutChar( xComPortHandle pxPort,
signed char cOutChar, portTickType xBlockTime )
{
signed portBASE_TYPE xReturn;

/* The port handle is not required as this driver only supports UART0. */
( void ) pxPort;

portENTER_CRITICAL();
{
/* Is there space to write directly to the UART? */
if( lTHREEmpty == ( long ) pdTRUE )
{
/* We wrote the character directly to the UART, so was
successful. */
lTHREEmpty = pdFALSE;
U0THR = cOutChar;
xReturn = pdPASS;
}
else //HL lTHREEmpty may become pdTRUE anytime from here
{
/* We cannot write directly to the UART, so queue the character.
Block for a maximum of xBlockTime if there is no space in the
queue. It is ok to block within a critical section as each
task has it's own critical section management. */
xReturn = xQueueSend( xCharsForTx, &cOutChar, xBlockTime );
//HL A
/* Depending on queue sizing and task prioritisation: While we
were blocked waiting to post interrupts were not disabled. It is
possible that the serial ISR has emptied the Tx queue, in which
case we need to start the Tx off again. */
if( lTHREEmpty == ( long ) pdTRUE )
{
xQueueReceive( xCharsForTx, &cOutChar, serNO_BLOCK );
//HL Here is a flaw..
//HL What if the queue and THR became empty right after
//HL xQueueSend (line A above). Then this code will run,
//HL but xQueueReceive will fail as the queue holds
//HL no data. It is already sent by handler!
//HL Never the less, a byte is written - a false extra byte.
lTHREEmpty = pdFALSE;
U0THR = cOutChar;
}
}
}
portEXIT_CRITICAL();

return xReturn;
}

This is what I think it should look like:


signed portBASE_TYPE xSerialPutCharCorrected( xComPortHandle pxPort,
signed char cOutChar, portTickType xBlockTime )
{
signed portBASE_TYPE xReturn;

/* The port handle is not required as this driver only supports UART0. */
( void ) pxPort;

portENTER_CRITICAL();
{
/* Is there space to write directly to the UART? */
if( lTHREEmpty == ( long ) pdTRUE )
{
/* We wrote the character directly to the UART, so was
successful. */
lTHREEmpty = pdFALSE;
U0THR = cOutChar;
xReturn = pdPASS;
}
else //HL ...Any time from here, lTHREEmpty may become pdTRUE;
{
/* We cannot write directly to the UART, so queue the character.
Block for a maximum of xBlockTime if there is no space in the
queue. It is ok to block within a critical section as each
task has it's own critical section management. */
xReturn = xQueueSend( xCharsForTx, &cOutChar, xBlockTime );
/* Depending on queue sizing and task prioritisation: While we
were blocked waiting to post interrupts were not disabled. It is
possible that the serial ISR has emptied the Tx queue, in which
case we need to start the Tx off again. */
if ( ( lTHREEmpty == ( long ) pdTRUE ) && (xReturn==pdPASS) )
{ //HL ..need xReturn check as also LPC2106 shows
if( xQueueReceive( xCharsForTx, &cOutChar, serNO_BLOCK )==pdPASS)
{ //HL .. must check for valid result!
lTHREEmpty = pdFALSE;
U0THR = cOutChar;
}
}
}
}
portEXIT_CRITICAL();

return xReturn;
}

I am in the process of testing this on LPC11U35.
best regards
Henning

RE: Call xQueueReceiveFromISR() in task, LPC1114

Posted by Richard on January 10, 2013
I will have to check into this, it was many, many years ago that I wrote that code.

Regards.

RE: Call xQueueReceiveFromISR() in task, LPC1114

Posted by henning larsen on January 11, 2013
I have been testing the ideas brought forward previously about the code from the
serial.c -> xSerialPutChar() from LPC2106 (in copy below).
This revealed another problem in the code apart from the alleged race condition
between the ISR and the task's access to the lTHREEmpty flag:

The line
xReturn = xQueueSend( xCharsForTx, &cOutChar, xBlockTime );
which sends data to the queue is called from within a critical section, which means
- in the LPC1114 port that interrupts are effectively disabled using

__asm volatile ( " cpsid i " )

If xQueueSend() is called at a moment where the queue is full, the task is suspended,
but this is "illegal" with interrupts disabled. Also confirmed by the API reference
- "do not call from an ISR". In the LPC114 port enterCRITICAL_SECTION() disables
interrupts which effectivly make the code section look like an ISR.

Because - the xQueueSend will, when queue is full, at a certain time during the
course to suspension hit the vTaskPlaceOnEventList() which, according to the note
in the code says:

/* THIS FUNCTION MUST BE CALLED WITH INTERRUPTS DISABLED OR THE
SCHEDULER SUSPENDED. */

This is clearly violated. As result, the xQueueSend() is never resumed despite the
blocking time being finite and the serial transmission is stalled.
This is confirmed in tests.

Replacing the xQueueSend() with xQueueSendFromISR() makes the code work without hangup,
but the time-out has to be dealt with separately, as the xQueueSendFromISR()
can not block.

To summarize: Isn't it true that when inside a critical section guarded by
disabled interrupts, it is illegal to call API's which may block?
Maybe this is only true for the LPC1114 kind of ports which has a brute-force
way of creating CRITICAL_SECTION's using a simple disable of all interrupts.

In any case the comments about the race between the ISR and the task,
outlined in the code below - which started all this - is of course moot in this case,
as the ISR _cannot_ run inside a critical section when interrupts are disabled.
So race is not an issue in this case. Maybe in other implementations of critical sections.

Any comments are appreciated.
Henning


signed portBASE_TYPE xSerialPutChar( xComPortHandle pxPort,
signed char cOutChar, portTickType xBlockTime )
{
signed portBASE_TYPE xReturn;

* The port handle is not required as this driver only supports UART0. */
( void ) pxPort;

portENTER_CRITICAL();
{
/* Is there space to write directly to the UART? */
if( lTHREEmpty == ( long ) pdTRUE )
{
/* We wrote the character directly to the UART, so was
successful. */
lTHREEmpty = pdFALSE;
U0THR = cOutChar;
xReturn = pdPASS;
}
else
{
/* We cannot write directly to the UART, so queue the character.
Block for a maximum of xBlockTime if there is no space in the
queue. It is ok to block within a critical section as each
task has it's own critical section management. */
xReturn = xQueueSend( xCharsForTx, &cOutChar, xBlockTime );
//HL A
/* Depending on queue sizing and task prioritisation: While we
were blocked waiting to post interrupts were not disabled. It is
possible that the serial ISR has emptied the Tx queue, in which
case we need to start the Tx off again. */
if( lTHREEmpty == ( long ) pdTRUE )
{
xQueueReceive( xCharsForTx, &cOutChar, serNO_BLOCK );
//HL Here is a flaw..
//HL What if the queue and THR became empty right after
//HL xQueueSend (line A above). Then this code will run,
//HL but xQueueReceive will fail as the queue holds
//HL no data. It is already sent by handler!
//HL Never the less, a byte is written - a false extra byte.
lTHREEmpty = pdFALSE;
U0THR = cOutChar;
}
}
}
portEXIT_CRITICAL();
return xReturn;
}


RE: Call xQueueReceiveFromISR() in task, LPC1114

Posted by Richard on January 12, 2013
There are big differences in the way the LPC2106 (ARM7) and the LPC1000 handle context switching.

On the ARM7 calling a yield function is synchronous - it will cause a context switch even if you are in a critical section. You can switch to a task that is not in a critical section and interrupts will be correctly enabled, then switch back to the original task that was in a critical section and interrupts will be correctly disabled again when the task start running. I'm not sure if this makes the case you are highlighting ok or not without more investigation.

On Cortex-M parts context switching is normally done using a PendSV interrupt (M3/4 anyway) which is not synchronous in that you can (as the name suggests) pend the interrupt but it does not execute until interrupts are enabled. In this case it is definitely not ok to call the blocking queue function a critical section - so it is not ok for you.

Regards.


[ Back to the top ]    [ About FreeRTOS ]    [ Sitemap ]    [ ]




Copyright (C) 2004-2010 Richard Barry. Copyright (C) 2010-2016 Real Time Engineers Ltd.
Any and all data, files, source code, html content and documentation included in the FreeRTOSTM distribution or available on this site are the exclusive property of Real Time Engineers Ltd.. See the files license.txt (included in the distribution) and this copyright notice for more information. FreeRTOSTM and FreeRTOS.orgTM are trade marks of Real Time Engineers Ltd.

Latest News:

FreeRTOS V9.0.0 is now available for download.


Free TCP/IP and file system demos for the RTOS


Sponsored Links

⇓ Now With No Code Size Limit! ⇓
⇑ Free Download Without Registering ⇑


FreeRTOS Partners

ARM Connected RTOS partner for all ARM microcontroller cores

Renesas Electronics Gold Alliance RTOS Partner.jpg

Microchip Premier RTOS Partner

RTOS partner of NXP for all NXP ARM microcontrollers

Atmel RTOS partner supporting ARM Cortex-M3 and AVR32 microcontrollers

STMicro RTOS partner supporting ARM7, ARM Cortex-M3, ARM Cortex-M4 and ARM Cortex-M0

Xilinx Microblaze and Zynq partner

Silicon Labs low power RTOS partner

Altera RTOS partner for Nios II and Cortex-A9 SoC

Freescale Alliance RTOS Member supporting ARM and ColdFire microcontrollers

Infineon ARM Cortex-M microcontrollers

Texas Instruments MCU Developer Network RTOS partner for ARM and MSP430 microcontrollers

Cypress RTOS partner supporting ARM Cortex-M3

Fujitsu RTOS partner supporting ARM Cortex-M3 and FM3

Microsemi (previously Actel) RTOS partner supporting ARM Cortex-M3

Atollic Partner

IAR Partner

Keil ARM Partner

Embedded Artists