UART Interrupt priority: FIFO overruns! NETMF suspends interrupts for too long?

I’m writing a big-buffered RLP uart driver to fight my performance issues at high baudrate. But i’m running into Overrun errors once a lot of characters are send at the same time. At 921600 baud i expect rawly 100k bytes per second, the interrupt has to run at least every 16 characters to prevent overrun: that means, for a FIFO overrun the interrupt wasnt called for some more then 100us!
I tried changing VIC priority but no luck… How can I make sure the interrupt gets called as regular as it should be?


//ring buffer for com3
unsigned char* com3ReadBuf;
int com3ReadBufStart;
int com3ReadBufLength;

void Com3Rx_ISR(void* arg)
{
	unsigned char intId = (U2IIR >> 1) & 0b111; //get Id of the interrupt
	unsigned char data;
	
	switch(intId)
	{
		case UART_INTID_RLS: //Todo: handle RLS if required to detect line errors (Interrupt must be enabled first)
			data = (U2LSR & 0b1110) >> 1;
			com3ReadBufLength = (-1) - data;			
		
		case UART_INTID_RDA:
		case UART_INTID_CTI:
			while (U2LSR & 0x01) //while RDR 
			{
				data = U2RBR;
				if (com3ReadBufLength < 0)
				{
					//error in buffer! skip byte...
				}
				else if (com3ReadBufLength == UART_READ_BUFFER_SIZE)
				{
					com3ReadBufLength = UART_BUFFER_ERROR_OVFLW;
				}
				else
				{				
					com3ReadBuf[(com3ReadBufStart + com3ReadBufLength) % UART_READ_BUFFER_SIZE] = data;
					com3ReadBufLength++;
				}				
			}
			break;
			
		case UART_INTID_THRE:
				//not enabled/used as sending in sync mode
			break;
	}
 
	VICVectAddr = 0;  //acknowledge interrupt
}


//Inits com3 uart, no arguments used
int InitCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	//init buffers
	com3ReadBuf = RLPext->malloc_CustomHeap(UART_READ_BUFFER_SIZE);
	com3ReadBufStart = 0;
	com3ReadBufLength = 0;

	PCONP |= (1 << PCUART2); //power on UART2 
	PCLKSEL1 |= 0b1 << PCLK_UART2; //UART peripherial clock = full 72 Mhz
	U2LCR |= (1 << DLAB) | 0b11; //enable Divisor latches access, 8bit word length, 1 stop bit no parity
	
	U2DLL |= 3; 
	//925925 baud by measurement, 921600 target (<0.5% error)
	U2FDR = 0xD8; 
	
	U2FCR |= (1 << FIFO_ENABLE) | (0b10 << FIFO_TRIGGERLEVEL); // enable Tx/Rx FiFos, 8 (0b10) characters in Rx FIFO for trig
		
	PINSEL9 |= (0b10 << 12) | (0b10 << 14); // TXD2 and RXD2 pins enable, PINMODE on default (0 = on-chip pullup)
	
	//setup interrupts
	U2LCR &= ~(1 << DLAB); //clear Devisor latches access to enable interrupt register access
	U2IER |= (1 << 2) | 1;   //enable RBR+CRI and RXLineStatus interrupts 
		
	//VICVectAddr28 = Com3Rx_ISR;
	//VICIntEnable |= 1 << UART2_IRQ;
	RLPext->Interrupt.Install(UART2_IRQ, Com3Rx_ISR, NULL);
	RLPext->Interrupt.Enable(UART2_IRQ);
	VICVectPriority28 = 0x01; //high interrupt priority
	
	return (int)com3ReadBuf;
}


//Usage: general array = byte[], first argument int arrayLength
int TransmitCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	int length = *(int*)args[0];

	while (length)
	{
		while(!(U2LSR & (1 << THRE)));
		U2THR = *(generalArray++); 
		length--;
	}
	
	return 0;
}


//Usage: general array = byte[] buffer, first argument: int bufLength, return: count of bytes really received (rest of buf unchanged)
int ReceiveCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	int length = *(int*)args[0];
	int readBytes = 0;
	
	//will not execure loop if com3ReadBufLength contains error (<0)
	while (length && (com3ReadBufLength > 0))
	{
		//critical path - make sure ring buffer stays consistent
		RLPext->Interrupt.GlobalInterruptDisable();
		generalArray[readBytes] = com3ReadBuf[com3ReadBufStart];
		com3ReadBufStart = (com3ReadBufStart + 1) % UART_READ_BUFFER_SIZE;
		com3ReadBufLength --;
		RLPext->Interrupt.GlobalInterruptEnable();
		length--;
		readBytes++;
	}
	
	if (com3ReadBufLength < 0) //error: reset buffer and return id
	{
		readBytes = com3ReadBufLength; //save error code
		com3ReadBufStart = 0;
		com3ReadBufLength = 0;		
	}
	
	return readBytes;
}


int BytesToReadCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	return com3ReadBufLength;
}

int DiscardInBufferCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	com3ReadBufLength = 0;
	return 0;
}


You’ll have an error somewhere. I successfuly did run a timer interrupt at 22050kHz with RLP on EMX. So one interrupt each 100µs should not be a problem.

You’re missing a break statement in the “case UART_INTID_RLS” as RLS interrupt is cleared by reading out U2LSR

It’s ment to be like that: once error; I want to skip the rest of the Rx FIFO as I’m not recording data until the error is read. So I let it fall through to
"while (U2LSR & 0x01) data = U2RBR;" to clear the fifo completely.

Why is the disable/enable global interrupts inside the while loop? I don’t think that one additional byte comming through will make a big difference :slight_smile: I don’t know what these enable/disable functions do, maybe they have a huge overhead? Also you can just disable the UART IRQ instead of all interrupts.

What is the value of UART_BUFFER_ERROR_OVFLW?

Also beware that a InvokeEx on the EMX takes little less then 10ms and a normal Invoke even takes longer (IIRC)! And I don’t know what happens with the RLP code during that managed-to-native switching, but that could also be an issue if you’re calling InvokeEx too much.

InvokeEx is called about once in 3000 bytes to pick them up.

The interrupt disable is required coz interrupt in the middle of com3BufLength calculation could result in missing increment = lost byte, so I put the buffer operations inside unterruptable path to prevent random buffering errors. Disabling the UART interrupt would result in it being missing, while on Interrupt Disable it would be saved and called right after InterruptEnable as far as I understand ARM7. The uninterruptable path is quite small here, I guess something like 6-10 integer instructions, so should not be even near a us.

Interrupt flags are set regardless of the interrupt maak. So re-enabling user interrupt should result in a IRQ when the flag was set in the meantime.

About the enable/disable I ment that you can disable before the while loop and enable after the loop. No need to do it inside the loop.

Also: What is the value of UART_BUFFER_ERROR_OVFLW?

UART_BUFFER_ERROR_OVFLW is put into BufLength to indicate buffer overflow error.

If I put interrupt disabling around the whole while loop, then it would stop rx fifo processing for entire sending duration, which can be milliseconds if it’s long enough.

You’re ignoring my question about the value of that define.

Also the while loop stops when com3ReadBufLength reaches 0 so putting the enable /disable interrupt outside the loop will really make no difference. The only thing that could change is reducing the overhead. Also I would not use a circular buffer, but I would use memcpy and get rid of that while loop.

A memcpy version would look like this: (memcpy will just be ways faster then a while loop, especially on larger buffers, as ARM has some great block copy instructions)


unsigned char com3ReadBuf[UART_READ_BUFFER_SIZE];
int com3ReadBufLength = 0;

void Com3Rx_ISR(void* arg)
{
    unsigned char intId = (U2IIR >> 1) & 0b111; //get Id of the interrupt
    unsigned char data;
    
    switch(intId)
    {
        case UART_INTID_RLS: //Todo: handle RLS if required to detect line errors (Interrupt must be enabled first)
        data = (U2LSR & 0b1110) >> 1;
        com3ReadBufLength = (-1) - data;
        
        case UART_INTID_RDA:
        case UART_INTID_CTI:
            while (U2LSR & 0x01) //while RDR
            {
                data = U2RBR;
                if (com3ReadBufLength < 0)
                {
                    //error in buffer! skip byte...
                }
                else if (com3ReadBufLength >= UART_READ_BUFFER_SIZE)
                {
                    com3ReadBufLength = UART_BUFFER_ERROR_OVFLW;
                }
                else
                {
                    com3ReadBuf[com3ReadBufLength++] = data;
                }
            }
            break;
        
        case UART_INTID_THRE:
            //not enabled/used as sending in sync mode
            break;
    }
    
    //VICVectAddr = 0; //acknowledge interrupt <- no need to do this
}


//Inits com3 uart, no arguments used
int InitCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    //init buffers
    com3ReadBufLength = 0;
    
    PCONP |= (1 << PCUART2); //power on UART2
    PCLKSEL1 |= 0b1 << PCLK_UART2; //UART peripherial clock = full 72 Mhz
    U2LCR |= (1 << DLAB) | 0b11; //enable Divisor latches access, 8bit word length, 1 stop bit no parity
    
    U2DLL |= 3;
    //925925 baud by measurement, 921600 target (<0.5% error)
    U2FDR = 0xD8;
    
    U2FCR |= (1 << FIFO_ENABLE) | (0b10 << FIFO_TRIGGERLEVEL); // enable Tx/Rx FiFos, 8 (0b10) characters in Rx FIFO for trig
    
    PINSEL9 |= (0b10 << 12) | (0b10 << 14); // TXD2 and RXD2 pins enable, PINMODE on default (0 = on-chip pullup)
    
    //setup interrupts
    U2LCR &= ~(1 << DLAB); //clear Devisor latches access to enable interrupt register access
    U2IER = 0b101; //enable RBR+CRI and RXLineStatus interrupts
    
    //VICVectAddr28 = Com3Rx_ISR;
    //VICIntEnable |= 1 << UART2_IRQ;
    RLPext->Interrupt.Install(UART2_IRQ, Com3Rx_ISR, NULL);
    RLPext->Interrupt.Enable(UART2_IRQ);
    VICVectPriority28 = 0x01; //high interrupt priority
    
    return (int)com3ReadBuf;
}

//Usage: general array = byte[], first argument int arrayLength
int TransmitCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    int length = *(int*)args[0];
    
    while (length--)
    {
        while(!(U2LSR & (1 << THRE)));
        U2THR = *(generalArray++);
    }
    
    return 0;
}


//Usage: general array = byte[] buffer, first argument: int bufLength, return: count of bytes really received (rest of buf unchanged)
int ReceiveCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    int bytesToRead = *(int*)args[0];
    int bytesProcessed;
    
    unsigned int ierBackup = U2IER;
    U2IER = 0; // disable interrupts
    if (com3ReadBufLength < 0)
    { //error: reset buffer and return id
        bytesProcessed = com3ReadBufLength; // save id for return to caller
        com3ReadBufLength = 0;
    }
    else
    {
        bytesProcessed = bytesToRead < com3ReadBufLength ? bytesToRead : com3ReadBufLength;
        memcpy(generalArray, com3ReadBuf, bytesProcessed);
        com3ReadBufLength -= bytesProcessed;
        if (com3ReadBufLength > 0)
        {    // not all bytes have been read, move the rest to beginning of the receive buffer
             memcpy(com3ReadBuf, com3ReadBuf + bytesProcessed, com3ReadBufLength);
        }
    }
    U2IER = ierBackup; // re-enable interrupts
    return bytesProcessed;
}


int BytesToReadCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    return com3ReadBufLength;
}

int DiscardInBufferCom3(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    com3ReadBufLength = 0;
    return 0;
} 

1 Like

I don’t know how this define is an issue.

The memcopy is a good idea. I’ll use it. But this doesn’t explain my overrun issues in any way…

" com3ReadBufLength -= bytesProcessed;"
using this without interrupt holding could result in assembler like this on a load-store architecture (I’m not familar with arm assembler so using mips here)
lw $reg, BufLength(0)
sub $reg, $reg, $[bytesProcesedReg]
INTERRUPT ========================> … sw $reg2, BufLength[0]; …
sw $reg, BufLength[0] <==================== RETURN

Which would cause the byte to be lost.
So I do need to disable interrupts, and as I said, doing it for the whole loop would definetely lead to an overrun.

Please check the complete code. I am disabling interrupts, see comments in the code.

U2IER = 0; // disables interrupts 

Still don’t know the value of that define :slight_smile:

:smiley: I’m checking for error value after I get the buffer length back from RLP, so i decided to do defines for error types. But i calculate all the other error type numbers instead of giving them by define.

Hey, the memcopy approach seems to have incredibly boosted the copying! It’s still not giving interrupts completely in time - I have moved to COM2 with hardware flow control (a lot of flying wires here now…), but once both were in place it seems to finally keep up with the baud :slight_smile:

Thank you so much! If you ever happen to come to Braunschweig - you get a free beer :wink:

Glad you have it running now :slight_smile:

Maybe during the beer you can whisper me the value of UART_BUFFER_ERROR_OVFLW, I just HOPE it has a negative value.

Ah :smiley: You mean like numeric value ^^ it’s -1 :wink: I didn’t get you mean it like that, sorry :smiley: