UART ISR has buffer overrun error when higher frequency higher priority interrupt is running

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Processor: AVR32UC3A0512
System clock: 12 MHz

 

USART Baud Rate: 57600 

USART Flow Control: Test conducted with flow control enabled and disabled

Start/Stop Bits: 1

 

ISR shown below. 

 

I have implemented a interrupt-driven UART with lower priority. I have another higher priority interrupt occurring @ 10kHz. 

I am possibly running into a race condition/interrupt servicing contention when sending command over serial with 0 inter-byte delay WHEN another higher priority interrupt is coming through @ 10kHz rate. 

 

I would see that the routine constantly runs into overrun buffer errors, as it would echo missing bytes in the command sent. For example, I loop through "DATALCD" with 1s delay between each iteration of the command. 

Routine echoes "dad", "dalcd", daalc" etc...What is the limitation on the baud rate for the UART given the set-up? Should I NOT use interrupt-driven UART or should I spec the user interface such that there is a higher inter-byte delay? 

Enabling flow control did not make a difference in the behavior. Please advise! Thanks!!

ISR(usart0_int_handler, AVR32_USART0_IRQ , 1)
{
	int c;
	int usart0_ret = 0;
  
	// Read the character 
	usart0_ret = usart_read_char(serial_usb_DATA_PORT, &c);
	
	if(usart0_ret == USART_RX_ERROR)
	{
		c = (serial_usb_DATA_PORT->rhr & AVR32_USART_RHR_RXCHR_MASK) >> AVR32_USART_RHR_RXCHR_OFFSET;
		serial_usb_DATA_PORT->cr = AVR32_USART_CR_RSTSTA_MASK;
		usart0_ret = usart_putchar(serial_usb_DATA_PORT,c);		
	}
	else 
	{
		// Echo the character 
		usart_putchar(serial_usb_DATA_PORT,c);
		// Puts the character into a command 
		if(commandCount < MAX_USART_CHARS	&& c != -1) 
		{
			command[commandCount] = c;
			commandCount = commandCount + 1;
		}
		else commandCount = 0;
		if (c == '\r')
		{
			cmdi = 0;
			command[commandCount] = '\0';
		}  
		// No character left 
		c=-1;
  
		// Once the entire command is received, copy into temporary variable and process 
		if (cmdi==0)
		{
			// Reset the count to 0
			count_gl=0;
			// Detect end of line character for enter sign on the USART
			while(command[count_gl]!='\0')
			{
				// Move character from command to temp char
				temp_2[count_gl]=command[count_gl];
				// Put a space into command to clear it
				command[count_gl]=' ';
				// Move to the next character by advancing count
				count_gl++;
			}
		// Clear the final command
		command[count_gl]=' ';
		temp_2[count_gl]='\0';
		// Clear the command count position holder
		commandCount=0;
		}
	}
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It could be that your higher priority isr is stealing too many cpu cycles.
Also, are the usb functions safe to be called from an interrupt context?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Kartman.

 

Yes, it is highly likely that the higher priority ISR is stealing too many CPU cycles because within that ISR, data is read on the SPI bus and a look-up table is implemented. 

By usb functions, do you mean serial_usb_DATA_PORT? That is the USART register, as in the overrun error scenario, I need to read RHR to clear the interrupt flag.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A 56.7 kbaud serial stream is one character every 173 microseconds, at a 12 MHz clock that represents 2083 cpu cycles.
Most AVR32 interrupts need between 25 and 30 cycles and the ASF INTC driver adds at least another 20.



Use a faster system clock, and/or optimise the code in your 10 kHz interrupt-handler.

(Edit : Fixed my baudrate/character calculations which were low by a factor of 10)

Last Edited: Thu. Oct 6, 2016 - 01:10 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How many SPI bytes are you reading inside that 10 kHz routine ?, and what is the SCK frequency ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you mikech! I will try and optimize the ISR. 

 

Reading 2 bytes in the 10kHz routine, and SCK = 1 MHz

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also, here's the 10kHz ISR. I was thinking if filling up FIFO in the ISR, and processing in the main() would optimize the LUT parts at least...?

	if(/*timer/counter register in range*/)
	{
	    // Read SPI 
		spi_selectChip(EXTADC_SPI, EXTADC_SPI_NPCS);
		do {
			#ifdef _ASSERT_ENABLE_
			spi_status =
			#endif
			spi_write(EXTADC_SPI, 0b0);
			Assert( SPI_OK==spi_status );
			#ifdef _ASSERT_ENABLE_
			spi_status =
			#endif
			spi_read(EXTADC_SPI, &particle_detect_val);
			Assert( SPI_OK==spi_status );
		} while (status_spi & 0x80);
		
		// Reject any 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF as 
		// these might be a remnant of the FW/HW interaction 
		// where the ADC gives up and holds DOUT high 	
		if(/*Certain DATA read*/)
		{
			// Evaluate LUT bins
			// Call LUT functions 
			// Assert CS
			spi_unselectChip(EXTADC_SPI, EXTADC_SPI_NPCS);
		}
	}
	else 
	{
		spi_selectChip(EXTADC_SPI, EXTADC_SPI_NPCS);
		do{
		} while (status_spi & 0x80);
		spi_unselectChip(EXTADC_SPI, EXTADC_SPI_NPCS);
	}
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That
do {
...
} while (status_spi & 0x80);

might be looping several times which would cause the isr to take a long time.



You could use the 10 kHz to just set a flag and do all the ADC and LUT work in main().

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks mikech. Looks like the ADC and LUT work is taking up quite a few CPU cycles. Here's my understanding:

 

30 cycles   => 3 us      // AVR32 ISR 

20 cycles   => 20 us    // SPI read

200 cycles => 20 us    // 256 bin LUT 

20   cycles => 2 us      // Comparator 

20   cycles => 2 us      // Accumulators 

 

So it looks like setting a flag in ISR and moving data processing function into main() will cut the overhead by half! 

 

Regarding the while loop, it only enters it once as the condition is a remnant of old code. I should probably clean that up. 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mtandon wrote:
So it looks like setting a flag in ISR and moving data processing function into main() will cut the overhead by half!

That's a general rule - you only ever put what is absolutely necessary and must happen "right then" in the ISR code on any micro. Any "slow" work that need not be part of the immediate interrupt response should simply be flagged and triggered later in non critical code. A typical ISR() should be about 5 lines. It usually only involves sending or reading a value and perhaps acknowledging the source of the interrupt. In the case of USART for example it's usually only about adding or taking one byte to/from a FIFO.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Agreed clawson. That was my oversight, thanks for the feedback! Because of our HW/FW handshaking, SPI read is absolutely essential within ISR, but not the processing itself. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So it looks like setting a flag in ISR and moving data processing function into main() will cut the overhead by half! 

For the USART0 ISR as well.

Greg Muth

Portland, OR, US

Atmel Studio 7.0 on Windows 10

Xplained/Pro/Mini Boards mostly

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just wanted to provide an update for this one: 

- I sped up the clock from 12MHz to 36MHz using PLL

- I write to a 128 deep circular queue in the 10kHz ISR, and read from it whenever the main loop gets around to it. 

- Baud rate is left at 57600 with hardware flow control enabled. 

 

System works well now. Thanks for all your help.