helarsen wrote on Wednesday, January 09, 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