I've been a bit lax with the blog updating. In Part 1 of this post I promised Part 2 "soon" and here it is, eight weeks later. Oops.

This two-post series, aimed at embedded device beginners, explains some differences between Arduino and Raspberry Pi. In this second part we're going to focus on one particular issue - "real-time" constraints. We'll also quickly look over some of the alternative devices available.

This series is based on a short talk I gave at LCA2014 Arduino MiniConf.

Real-time

In embedded programming it's common to talk about "Real-time" problems. A "real-time" problem is something where the computer has to do some computation in a limited amount of time. For example if you make a call on your mobile phone it has to activate the microphone within a few seconds, or you won't be able to talk to the other person. Sometimes real-time constraints can be very fast, for example if your car starts skidding then the ABS anti-lock brake system has to respond within a couple of milliseconds.

Honda Integra DC5R Brembo brakes w/work wheels

With real-time systems there's a task (ie activate the microphone, or release the brake caliper) and there's a deadline (ie within a couple of seconds, or within a few milliseconds). Most real-time problems can be decomposed into tasks and deadlines in this way.

Hard vs Soft

When talking about real-time tasks and deadlines it's common to divide them into "hard" and "soft" deadlines. "Hard" real-time deadlines are ones where a failure to complete a task before the deadline means the system has totally failed. An example is a vehicle safety airbag. A late airbag is a useless airbag - if it doesn't deploy in time then the entire airbag system is effectively useless.

"Soft" real-time systems are ones where a late task is less useful, but doesn't lead to complete failure. If your mobile phone microphone activates a few seconds late then you can still make your phone call, it's just annoying that you lose the first few seconds of speech.

Most personal computer systems are "soft" real-time. If you click a link in your browser and it takes ten seconds to respond then this is still degraded performance, even if the system still functions.

For this reason a third category is usually discussed, "firm" real-time systems. This is where the system can tolerate missed deadlines, but a task that misses its deadline is effectively useless. An example is streaming audio or video live - if some of the stream data arrives late then it's still no use to the system, it's better to skip that data and continue streaming.

Real-time constraints also matter in embedded computing when dealing with devices or sensors that use a time sensitive communications protocol. A lot of wire signalling protocols use particular sequences and timings of electrical pulses to transfer information. For example, WS2812 "NeoPixel" LED drivers or sensors using the Dallas 1-wire bus. The computer has to be available to read or write data at the precise times required by the protocol, or it won't work.

A Quick Test

To make a quick and simple comparison of real-time suitability of Arduino & Raspberry Pi, we can use an oscilloscope and a simple program that toggles an output pin over and over, and compare the timing with an oscilloscope.

Even though toggling the pin in itself is not productive, it demonstrates absolute the maximum tolerance timing-sensitive I/O code. In a perfect world, everything would be perfectly real-time and toggling a pin would happen regularly - the time between transitions would never change, and the transitions would be rapid. In reality the timings will vary, and we can use this to work out what the realistic real-time constraints on our system might be.

Python & WiringPi

For a first example, we can use a Raspberry Pi with Python & the WiringPi 2 library.

PIN=4  
wiringpi2.pinMode(PIN,1)

while True:  
    wiringpi2.digitalWrite(PIN, 1)  
    wiringpi2.digitalWrite(PIN, 0)

Here's the output trace viewed on an oscilloscope:

54kHz square wave trace

54kHz output rate when updating a pin as fast as it can (and doing nothing else). Not particularly quick for a 700MHz computer!

When viewed live on the display, you can see the transition timing is actually jumping around rather than producing a regular square wave. By enabling the "Persist" feature in my Rigol oscilloscope, the pattern of each old trace "fades out" but stays overlaid on the current trace:

54kHz square wave trace

Viewed in this way you can see there's a lot of jitter - the average square wave might be 54kHz (17 microseconds between each transition) but the actual timing is jumping around all over the place.

Zoom out on the scope to show a slower time scale and things are even worse. If another process is running on the Pi, there are huge 10 millisecond gaps where nothing happens at all!

10ms gaps in trace

The jitter and gaps could be caused by another process running. Linux's process scheduler on the Pi has to "context switch" to run the other process, leaving our pin-toggle process in the "waiting" state (not allowed to run) until it gets another chance.

Maybe we can bump the schedule priority on our edge toggling process with the 'chrt' realtime priority command, so it always gets to run:

sudo chrt 99 ./python_toggle.py  

The 10ms gaps mostly go away, but the jitter remains:

Realtime priority version

Ah well, this is Python right? It's supposed to be slow! Let's try C. Using the WiringPi library again:

pinMode(PIN, OUTPUT);  
while(1)  
{  
    digitalWrite(PIN, HIGH);  
    digitalWrite(PIN, LOW);  
}

4.5MHz square wave

Sudden speed boost from 54kHz to 4.5MHz - nearly 100x! But how about the jitter?

Jittery 4.5MHz square wave

Still jittery, and if we zoom out we see even bigger gaps - probably where the kernel is going away to handle something (like some USB data arriving, for instance):

500ns gaps in square wave

And zooming further out there are still the same 10ms gaps, caused by another process running on the Pi:

10ms gaps in square wave

Real-Time Performance Tricks

There are some options people have explored to get better real-time performance on Raspberry Pi.

  • There are some other documented tweaks to improve real-time low-latency performance on a standard Pi installation. {=html} </p>

  • "CONFIG_PREEMPT" is a set of kernel patches allowing a real-time "preemptable" kernel, leading to much better real-time performanc.e

  • The ChibiOS Real-Time OS has been ported to Raspberry Pi. This is a much more restrictive programming environment than Linux, and not all of the Pi hardware is supported, but it is fully real-time.

All of these are valid solutions. However in some ways doing this kind of low-level real-time work on a Raspberry Pi is fitting a round peg into a square hole. Are there options that are inherently better suited?

Back to Arduino

Arduino is one of these options. It is a smaller dedicated real-time device, where a full sized operating system isn't present to cause timing problems. Such a device can always be connected a more complex system (like Raspberry Pi) to perform any high-level operations.

Let's try our simple pin-toggle test on an Arduino:

pinMode(PIN, OUTPUT);  
while(1)  
{  
    digitalWrite(PIN, HIGH);  
    digitalWrite(PIN, LOW);  
}

96kHz square wave

Looks pretty good, and the waveform looks pretty stable. The "Persist" feature shows this more clearly:

96kHz square wave with persistence

There is obviously some jitter, some event is occuring but the timing seems to be pretty predictable still (this is visible in the clear lines, no blurred blocks of timing).

Zoomed out you can see these are 10us pauses:

96kHz square wave with persistence

These are probably caused by the Arduino's internal timekeeping causing a "timer interrupt" to occur (an interrupt is where the CPU has to go away from the main body of the program to brielfy respond to another event). A 10us pause is still 1000x shorter than the 10ms pauses we were seeing on the Pi, though!

Even better, in a microcontroller environment like Arduino it is possible to directly control when and how interrupts can occur. For instance, we can disable interrupts while some time-critical real-time code executes, and turn them back on afterwards:

cli(); // Turn off all interrupts

// Do your real-time thing here

sei(); // Turn back on all interrupts

So what to do?

There are a few recommendations I'd make, based on the information in these posts.

  • Match the solution to the problem. If you have a simple problem which doesn't need a lot of computing power, don't use a full sized Linux PC like the Raspberry Pi. On the other hand if you need fast network connectivity or heavy processing or graphics, it may be a good choice. {=html} </p>

  • Combine the two. It's easy to connect a smaller real-time device like an Arduino to a larger more complex device.

Other Devices

  • Arduino Due and other ARM-based Arduino compatible boards combine Arduino compatibility with much more processing power. For instance the Due is 84MHz (rather than 16MHz) and 32-bit (rather than 8-bit). For these posts we've assumed "Arduino" means the base model 16MHz AVR Arduino, but that is starting to change.

  • The UDOO board combines an Arduino Due compatible with a very powerful Linux PC, on the same device.

  • The Arduino Yún is similar, although the Linux device is less powerful.

  • The CPU used in the Linux-based Beaglebone Black (similar to Raspberry Pi but more powerful) features two small onboard "Programmable Real-Time Units (PRUs)" bundled in with the main processor. These can be used to perform real-time tasks and hand back data to the main CPU. There are some guides to programming the PRUs online, however they are quite complex compared to Arduino.

Hopefully these two posts have given you some insight into the key differences between Arduino and Raspberry Pi, and perhaps clarified which of them is suitable for your project.

Have some more tips, or other alternatives that you think should be considered? Please leave a comment!

Thoughts on “Arduino vs Raspberry Pi (Part 2)