Go Down

Topic: Uart: interrupt driven reception: How? (Read 2815 times) previous topic - next topic

tuxedo

Hi there,

i'm creating a sketch that is reading/writing data with I2C as well as 1wire. Beside that, I have a communication interface connected to the hardware serial.

Sending/Reading the hardware serial has a higher priority as reading/writing I2C and 1wire. I have to get each and every byte on UART.

The problem now is:
Reading/writing I2C and 1wire takes a lot of time. depending on the connected device, up to several milliseconds. At 19200baud, a single bytes need ~580µs... So doing something else for several milliseconds does not sound like a good idea. But how do I overcome this? Maybe there is a receive buffer? But how much "time" can be bufferred when data is coming in with 19200 baud?
My first thought: Use an interrupt. That will "interrupt" any other "blocking" thing in loop(), let me receive the data, and continue with loop().

Arduino for 328p and 32u4 is looking in the main() for new data and calls SerialEvent() before/after loop() is called. So there is no real "interrupt driven data reception". If loop blocks for 10ms, I may miss a lot of bytes on UART.

I guess this is the same for samd21?

So my question is: Is there, for SAMD21, a way to get notified by a real interrupt when data is received on UART? If yes: Could you please point me to some helpful - beginner friendly - documentation?

MartinL

#1
Oct 22, 2016, 10:39 am Last Edit: Oct 22, 2016, 10:41 am by MartinL
Hi Tuxedo,

Every time the SERCOM (configured as a UART) receives a character, it calls its associated SERCOM interrupt service routine (ISR).

Code: [Select]
void SERCOM5_Handler()
{
  Serial.IrqHandler();
}

This interrupt handler function then in turn calls on Serial's IrqHandler function that places the character in a 64 byte buffer. This buffer can then be read using Serial.read(). Note that the Serial object is created (instantiated) from the UART class:

Code: [Select]
void Uart::IrqHandler()
{
  if (sercom->availableDataUART()) {
    rxBuffer.store_char(sercom->readDataUART());
  }...

The SERCOM handler functions can be found in the "variant.cpp" file located (on my Windows machine) at:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.8\variants\arduino_zero\variant.cpp

To use the interrupt handler for a given Serial port you need to simply comment it out in "variant.cpp" file, paste it into your own sketch and add your own handler code.

Note that a new update to the Arduino core (currently version 1.6.8) will overwrite your changes to the "variant.cpp" file.

tuxedo

Thanks for your reply.
That gives some good insight.

My sketch will result in a library. So changing variant.cpp is not a good idea, as the user has to change it too :-(

So, the current conclusion is: There's no callback/trigger, driven by interrupt to inform the sketch about new data?

br,
Alex

tuxedo

I did some calculations:

My rs232 device is connected to the arduino with 19200 baud. I have 8 databits + 1 start + 1 stop bit.
That makes about 520µs per received byte.

64byte rxBuffer then means: 64 * 520µs = ~33ms of "buffer time" when the serial is under full load.

That improves the situation a bit.

One more thing: My rs232 device itself received data from another system with 9600 baud:


Slow system <----9600baud-----> rs232-device <----19200baud------> arduino

So one byte is about 1,04ms. This would about double the buffer-time.

As many sensor librarries may block up to 100ms, this is still an issue...

So I would be glad about more details or hints.

tuxedo

I further investigated the core-code ...

in variant.cpp, at end of file:

Code: [Select]

// Multi-serial objects instantiation
SERCOM sercom0( SERCOM0 ) ;
SERCOM sercom1( SERCOM1 ) ;
SERCOM sercom2( SERCOM2 ) ;
SERCOM sercom3( SERCOM3 ) ;
SERCOM sercom4( SERCOM4 ) ;
SERCOM sercom5( SERCOM5 ) ;

Uart Serial1( &sercom0, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;
Uart Serial( &sercom5, PIN_SERIAL_RX, PIN_SERIAL_TX, PAD_SERIAL_RX, PAD_SERIAL_TX ) ;
void SERCOM0_Handler()
{
  Serial1.IrqHandler();
}

void SERCOM5_Handler()
{
  Serial.IrqHandler();
}



My current guess is:

SERCOM0_Handler and SERCOM5_Handler() is triggered by the underlying IRQ of the serial port.


If I now use one of the other available serial ports, I have to write my "own" SERCOMx_Handler() funktion and forward the call to SerialX.IrqHandler()..

So at least for non-default serials/sercoms, it seems that I can add an own function-call to the interrupt, but just adding to SERCOMx_Handler() function.

The question is: Is it somehow possible to "overwrite" the default SERCOMx_Handler in variant.cpp, without modifying the core files?!

br,
Alex

MartinL

#5
Oct 24, 2016, 10:52 am Last Edit: Oct 24, 2016, 11:02 am by MartinL
Quote
The question is: Is it somehow possible to "overwrite" the default SERCOMx_Handler in variant.cpp, without modifying the core files?!
No, unfortunately there isn't. However, it's possible if you configure one of the free sercom perhipherals, that aren't already configured by the Arduino Zero's core. In this case the unused Sercom1 and Sercom2 peripherals. This allows you to implement the sercom's interrupt handler in your sketch without resorting to changing the "variant.cpp" file.

It's detailed on this forum here: https://forum.arduino.cc/index.php?topic=330559.0.

and there's a more up-to-date article on Adafruit's website here: https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-serial.

To prevent buffer overflow it's necessary to read the Serial's 64 byte ring buffer quicker than it's being filled with incoming data. One method that I've used for a GPS running at 9600bps, is to set-up one of the SAMD21's TC timers to call an interrupt service routine that reads the buffer (with Serial.read()) slightly faster than the time it takes to receive a character. It's less efficient than using the sercom's interrupt service routine though.

tuxedo

Thanks. I will either use a non-default sercom, or go for the timer solution.

Issue solved for now...

br,
Alex

tuxedo

One further question:

Libraries may use "noInterrupts()"... Will that only disable things like timer or also the internal SERCOMx_Handler() interrupt call?


MartinL

#8
Oct 24, 2016, 07:30 pm Last Edit: Oct 24, 2016, 07:41 pm by MartinL
Quote
Will that only disable things like timer or also the internal SERCOMx_Handler() interrupt call?
noInterrupts() prevents any interrupts from occuring. It should be OK, as long as it's used only briefly. Pending interrupts are serviced once the interrupts() function reinstates them.

westfw

Quote
As many sensor librarries may block up to 100ms
Maybe it's the libraries that need fixed...

tuxedo


letaft

#11
Feb 08, 2018, 03:45 pm Last Edit: Feb 08, 2018, 03:53 pm by letaft
I know this is an old post but just in case someone wants to implement an interrupt driven reception with Arduino and SAMD21 variants. The following is what worked for me:

as suggested by http://forum.arduino.cc/index.php?topic=198754.msg1547640#msg1547640

you have to add the __attribute__((weak)) attribute to the SERCOM you want to use if you want to keep you variant.cpp compatible with other codes:
Code: [Select]
SERCOM sercom0( SERCOM0 ) __attribute__((weak));
In the case of arduino:
Code: [Select]
void UART_Handler(void) __attribute__((weak));

To add a new sercom to samd you can start reading:
https://www.arduino.cc/en/Tutorial/SamdSercom
https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports?view=all

After adding the weak attribute you have to link the sercom you want to use in your main code and override the interrupt service routine (ISR) by using:
Code: [Select]
extern SERCOM sercom0; //put your sercom here to use it in your main code
Then override the ISR:
Code: [Select]
void SERCOM0_Handler()
{
  if (sercom0.availableDataUART())
  {
      sercom0.readDataUART(); //here you get the new char from uart, use your handler here
  }
}

As an example, I'm using the TinyGPS++ Library and I wanted to updates the GPS using an interrupt to prevent character loss so in my ISR I have:
Code: [Select]
void SERCOM0_Handler()
{
  if (sercom0.availableDataUART())
  {
    gps.encode(sercom0.readDataUART());
  }
}


I hope this can help.

PD. If you need a full code using the TinyGPS++ just let me know.

Regards.


Go Up