Interrupt Driven Serial

Hi guys,

To give you some background about half a year ago my programming skills were shocking however thanks to arduino (Duemilanove) I feel like I know have a good grasp on the language. I have finally however found a frustrating limitation in that it seems that it is not possible to use an interrupt driven serial communication in arduino (correct me if im wrong here).

The project that I am working on is an autopilot and the main loop runs at 100Hz which is now collecting Pitch, Roll and Yaw data from the 6-DOF IMU as well as GPS data at 10Hz as well as pressure sensor data at 1Hz. I am then sending this data out of the micro to another micro which will use it to control the place. This happens currently at 10Hz but will happen at 50Hz when I sort out this communication.

In this situation Serial.print() causes too bigger delay in the program as the function waits until the character is sent. I want to be able to implement a ring buffer for the transmit line which is interrupt driven. This will allow the code in my main loop to continue to run 'while' the character is sent. From what I have read this does not seem possible with arduino so I am looking for some advice.

I am currently playing with the idea of porting my arduino into standard C and using AVR Studio to program which will give me more access to the Serial on the Atmega328.

Thanks in advance,

Will

From what I have read this does not seem possible with arduino

Welcome to the wonderful world of Open Source Software, where that which "does not seem possible" usually is when you put your mind to it ;)

There's some discussion on the developers' mailing list about doing this. I haven't followed it, but I noticed the topic in one of the "list by subject" summary displays I browsed the other day.

Even if it's not done officially, it's not all that hard to do yourself: just open up the HardwareSerial.h and .cpp files, add that ring buffer, replace write() with a function that stashes outbound data in the buffer, and write an interrupt routine that empties it.

If you feel comfortable migrating to another runtime environment, you probably have the background needed to modify the Arduino library.

You could also do a ring buffer at the application level, and dribble out a byte or two every time you go through your loop() function. Although I think it's better to do it once in the library than to tweak every program that needs overlap.

Ran

Will, this has been done 'big time'.

The NewSoftSerial library is located at http://arduiniana.org/libraries/newsoftserial/

Hopefully it suits your purpose.

Hi guys,

Thanks for the useful information. Ill have a look at both those solutions now. Ill let you know how I go.

If there is any more information or help people would like to donate then feel free. It is all appreciated.

Cheers

UPDATE: So I just looked at the newSoftSerial library. The library looks very good however the baudrate (with no errors) is limited to 57.6K.. I can probably get away with this thanks to its excellent use of interrupts and and a rather large 64byte input/output buffer. I might also look at modifying the Hardware serial arduino core file if the newSoftSerial doesnt work.

Hi Will,

I am also interested in interrupt based serial for the HW port(s) - I am using the Mega. Thanks for the post, let me know if you make any progress on adding this capability.

-RB

UPDATE: I looked through HardwareSerial.cpp in the cores/Arduino folder and it appears that receiving serial characters is already based on HW interrupts. The default buffer size is 128 characters. Is this correct?

Is this correct?

Yes: the receive side of the hardware-based serial library is interrupt-driven and buffered.

NewSoftSerial appears to be a very nice improvement on the current SoftSerial library, but it isn't going to do the job for Will, because it's still bit-banging the transmit data.

Ran

Thank you for the clarification. Regarding the transmit side of the hardware serial interface:

I noticed the ATmega includes a 'Transmit complete interrupt' for the USART. I think I could use this interrupt as follows:

  • Create a buffer to hold multiple transmit characters
  • Use this interrupt to call a function send a character from my buffer to the transmit data register.
  • Monitor the buffer fullness in the main loop to make sure it doesn't overflow/underflow

Is this correct?

Thanks, RB

That's the gist of it, RB.

But watch out for the devil hiding in the details. You have to be careful when writing interrupt-driven code, to make sure that you don't have the mainline and the interrupt routine updating shared variables at the same time. I'm sure you can find examples on the web of open-source drivers other people have written that avoid that problem.

Ran

NewSoftSerial appears to be a very nice improvement on the current SoftSerial library, but it isn't going to do the job for Will, because it's still bit-banging the transmit data.

Ran

Hi, what do you mean by "bit-banging"?.

"bit-banging" means to generate a serial data stream by twiddling an I/O pin with software, instead of using hardware like a UART or shift register.

The Arduino's shiftOut() function is another example.

It saves money, but costs CPU time that Will doesn't have to spare for this particular, very busy, application.

Ran

Hi guys, again, thanks for the feedback. Im back to work today so ill look more closely at this. Ive got a feeling your right Ran :) Anyway keep me posted on your progress RB and i'll try do the same. I am by no means an expert when it comes to interrupts but i'll give it a shot.

If anyone is interested, I've just added interrupt driven serial output to the HardwareSerial.cpp that came with arduino-0018 and it seems to work fine for me. I've only tested it on my Arduino Mega board (Mega 1280 cpu), but it should work with any arduino (fingers crossed!)

Let me know if you want to try it out. It has the following features:

  • Can be turned off with a simple #undef to go back to original arduino-0018 code.
  • Since each uart requires an additional ring buffer, I have halved the size of the buffer from 128 to 64 so that the overall SRAM used stays the same.
  • Serial.write will block if the transmit buffer is full until space becomes available in the buffer.

Enjoy -Bob

Let me know if you want to try it out. It has the following features:

Sure, I would like to look it over. You have link or post the code here? Does it support all four mega hardware serial ports? Lefty

Hi Lefty, Ok you can grab the two files HardwareSerial.h and HardwareSerial.cpp from ftp://wookey.org.uk/arduino

define or #undef SERIAL_INTERRUPT_OUTPUT in HardwareSerial.h to enable or disable the behaviour (enabled by default)

Yes, it supports all four hardware serial ports on the mega

Let me know if the ftp is a problem (I've only just set up the server) and I'll get it to you another way.

And remember: its only been tested on the mega.

Cheers -Bob

Hi Bob;

I was able to download those two files OK. I installed them into the Arduino core library, replacing the original two files. I loaded up a mega sketch that uses the first serial port and it output serial data to the IDE serial monitor OK. I then changed the board to my 328 RS232 board and compiled the same program with no errors, however when going to the serial monitor no serial output data was seen.

Lefty

Ok, I’ve just dropped a new HardwareSerial.cpp into my ftp area. Can you try that? There seems to be a big inconsistency with how uart vectors are named but the bad thing is, the compiler doesn’t pick it up. I would have expected the following code not to compile:

ISR(vector_name_that_does_not_exist)
{
}

but the compiler silently passes it. Let me know if the new version works on the 325 (it still works on my mega).

-Bob

I've come up with a solution to the silent failure. If SerialHardware.h can't determine the appropriate interrupt vector name to use, it will complain and cause the compile to fail.

You'll need the latest copy of both files from my ftp area.

Fingers crossed (again!) -Bob

Bob;

Well I downloaded your latest and compiled and ran on both a mega and a 328 board. All worked fine. I will keep your files in my core and let you know if anything weird shows up.

Very cool to have both send and receive be interrupt driven, don't know why the Arduino core team hadn't worked on this obvious improvement. Anything in the core that can eliminate blocking functions is a win in my book. ;)

Thank you for your contribution.

Lefty

I have the same problem! Having an CHR-6DM AHRS/IMU and streaming 17 bytes of data in 20Hz only!

Using the Arduino Mega UART1 and a bad protocol I made I get these readings:

Yaw: 3.98 Pitch: 3.25 Roll: -2.79 Acc_Z: -568.35 Yaw: 3.97 Pitch: 324.65 Roll: 317.01 Acc_Z: 297.36 Yaw: 3.98 Pitch: 3.25 Roll: -2.82 Acc_Z: -570.06 Yaw: 3.92 Pitch: 3.27 Roll: -2.81 Acc_Z: -569.20 Yaw: 3.97 Pitch: 3.27 Roll: -2.80 Acc_Z: -569.31 Yaw: 3.97 Pitch: 3.28 Roll: -2.79 Acc_Z: -569.09 Yaw: 3.98 Pitch: 3.28 Roll: -2.80 Acc_Z: -571.23 Yaw: 4.03 Pitch: 3.27 Roll: -2.79 Acc_Z: -570.70 Yaw: 3.96 Pitch: 3.27 Roll: -2.78 Acc_Z: -569.95 Yaw: 3.98 Pitch: 324.65 Roll: 317.01 Acc_Z: 297.36 Yaw: 3.91 Pitch: 3.28 Roll: -2.81 Acc_Z: -569.09 Yaw: 3.90 Pitch: 3.30 Roll: -2.81 Acc_Z: -571.02 Yaw: 324.65 Pitch: 317.01 Roll: 30.59 Acc_Z: 54.79 Yaw: 3.98 Pitch: 3.27 Roll: -2.80 Acc_Z: -568.13

It is unacceptable! Is there anything I can do about it?

You might also take a look at the FastSerial library that I built for ArduPilot Mega.

  • Works without modifying your Arduino distribution.
  • Selectable tx/rx buffer sizes at ::begin time.
  • Efficient interrupt-driven rx and tx (< 100 instructions worst case)
  • Support for print/println of strings in program memory
  • stdio integration (i.e. printf and friends)

http://code.google.com/p/arducopter/source/browse/#svn/trunk/libraries/FastSerial