Distributed systems require protocols for communication between microcontrollers. Controller Area Networks (CAN) and Serial Peripheral Interfaces (SPI) are two of the most common such protocols.

The beauty of using multiple processors in a single system is that the timing requirements of one processor can be divorced from the timing requirements of the other. In a real-time system, this quality can make the programming a lot easier and reduce the potential for race conditions. The price you pay is that you then have to get information from one processor to the other.

If you use one fast processor instead of two slow ones, passing information from one part of the software to another may be as simple as passing parameters to a function or storing the data in a global location. However, when the pieces of software that need to communicate are located on different processors, you have to figure out how to bundle the information into a packet and pass it across some sort of link. In this article, we'll look at two standard protocols, SPI and CAN, that can be used to communicate between processors, and also at some of the issues that arise in designing ad hoc protocols for small systems.

Building Protocols

When I want two identical processors to communicate, I like to express messages as structs. For example, a setting message could be expressed as:

typedef enum 
} SettingType;

typedef struct 
   SettingType type;
   int settingValue;
} SettingMessage;

Such a structure can be passed byte by byte. As long as the same compiler with the same options is used on the sender and receiver, the enumerated type and int will be compatible (endianness). The number of bytes that must be transmitted is sizeof(SettingMessage).

If the processor architectures are different, this approach is not a good one. It's better to have a document that specifies the meaning of each byte, so byte ordering and size will always be explicit. This also means that more processing happens on both sides as bytes are combined to form larger types. It can get messy if a floating-point format has to be defined.

Another option is to define a text-based protocol. This is how most of the Internet works; HTTP and SMTP are both built on text protocols. This approach allows the protocol to remain architecture agnostic. Text is less efficient than a protocol where each byte is given a meaning, but the upside is a protocol that's easy for a human to read and debug.

Serial Peripheral Interface (SPI)

Serial Peripheral Interface (SPI) is a clocked serial link. There are Rx and Tx lines, as in a standard serial link, and there is also a clock line. Clocking the data allows greater data transfer speeds. The clock is driven by one side of the interface, which is called the master. Each time the master drives a pulse on the clock line, one bit is transferred in each direction. The Tx line sends out a bit, while the Rx line receives a bit. While this means that the amount of data sent and the amount of data received must be equal, it's trivial to provide dummy data when you don't have anything interesting to send. In fact, SPI is common in applications where the data only goes in one direction and the opposite direction always passes a dummy value.

Since the master controls the clock, the master is in charge of flow control. If the master doesn't currently have time to process a byte of received data, the master can make sure that no data is received by not providing any clock pulses. This reduces the need for interrupts on the master and generally makes real-time management easier. However, there's a price to be paid on the slave side.

The frequency at which the slave transmits is controlled by the master. The slave must always have a valid byte ready to send, since it does not have any control over when the next clock pulse will appear, effectively requesting more data. If the slave device is dedicated to a single job, this may not be difficult. Consider a thermistor that communicates as an SPI slave. It could provide a single buffer of one byte that is always populated with the last temperature reading. Whenever clock pulses appear, that byte is transmitted and the master gets a reading.

In one project I worked on, we used SPI to communicate between two microcontrollers. In this case, some of the slave's responsibilities were quite troublesome. The microcontrollers were both PICs with built-in SPI controllers. Sequences of each 16 bytes were treated as a packet and included a checksum. While the master could communicate via polling, the slave needed to be interrupt-driven.

Bear in mind that as a single character is transmitted, so too is a single character received. The interrupt is generated at the end of each character. The slave needs to place the next character in the buffer before the master starts pulsing the clock line again. This gives the slave a window that may be very short. If you miss your deadline, the last character will be transmitted again, and, at the end of the 16 byte packet, the checksum will fail. The master and slave are no longer synchronized, and some recovery must take place.

Getting the master to pause after each byte is transmitted would give the slave a longer window, but this tactic can compromise the master; it also doesn't solve the problem completely, since the slave still has a hard deadline. One of the great advantages of using multiple processors is that the real-time issues from one portion of the software can be handled independently of the real-time requirements of any other part. Ironically, in this case, we have a situation where the real-time performance of the slave depends on the timing characteristics of the master, so we've done our design a disservice.

Faced with these timing issues, one solution is to avoid using the SPI's ability to transmit and receive at the same time. We can provide an extra signal that the slave asserts when it wants to transmit. When the master sees this signal, it knows that the slave has a byte ready, and the master then provides the clock to fetch that byte. When the master has something to send, it checks that the slave is not sending before clocking out its own byte; anything simultaneously received from the slave is ignored. Taking it in turns like this means that a large fraction of the potential bandwidth is lost. In exchange, you get more reliable and flexible software.

The serial peripheral interface, as the name suggests, is good for dedicated peripherals with a simple job to do, but causes some frustration when used for general purpose communications between independent processors.

Controller Area Network (CAN)

Controller Area Network (CAN) is a multi-drop bus protocol, so it can support many communicating nodes. 1 The advantages are obvious. The disadvantage of moving to more than two nodes is that you now require some addressing mechanism to indicate who sent a message, and who should receive it. The CAN protocol is based on two signals shared by all nodes on the network. The CAN_High and CAN_Low signals provide a differential signal and allow collision detection. If both lines go high, two different nodes must be trying to drive two different signals, and one will then back off and allow the other to continue.

CAN is used in almost every automobile manufactured in Europe. In the U.S., CAN is popular in factory automation, where the DeviceNet protocol uses CAN as its lower layer.

The biggest difference between CAN and SPI is that the CAN protocol defines packets. In SPI (and serial interfaces in general), only the transmission of a byte is fully defined. Given a mechanism for byte transfer, software can provide a packet layer, but no standard size or type exists for a serial packet. Since packet transfer is standardized for CAN, it's usually implemented in hardware. Implementing packets, including checksums and backoff-and-retry mechanisms, in hardware hides a whole family of low-level design issues from the software engineer.

The program can place a packet in a CAN controller's buffer and not worry about interacting with the CAN hardware until the packet is sent or an entire packet has been received. The same level of control could be built into a serial controller, but unless it was standardized, that controller could only communicate with peers of the same type.

A CAN packet consists of an identifier that comprises either 11 bits or 29 bits and 8 bytes of data, along with a few other pieces of housekeeping like the checksum. The identifier is not defined by the CAN protocol, but higher level protocols can describe how the identifier can be divided into source, destination, priority, and type information. You could also define these bits yourself if you don't have to share the bus with devices outside of your control.

When controlling transmission byte by byte, you usually have to combine a number of bytes to say anything meaningful, except in cases as trivial as the thermistor example discussed earlier. However, in eight bytes you can express commands, report on parameter values, or pass calibration results.

For debugging purposes, communicating from a microcontroller to a PC is straightforward. By snooping the CAN bus from the PC, you can monitor the communications between the microcontrollers in the system, or you can imitate one side of the conversation by inserting test messages.

A product called USBcan from Kvaser provides an interface to the CAN bus through the PC's USB port. A number of other companies offer similar products, but what I found impressive about Kvaser was the quality of the software libraries available. The CANlib library provides an API for building and receiving CAN packets. The company also provides a version of the library compiled for my favorite PC development environment, Borland C++ Builder, which enabled me to build a nice GUI that showed all bus activity. The same program can be used for calibration, inserting text messages, and even downloading a new version of software to the device.

Each Kvaser product, whether ISA, PCI, PCMCIA or USB-based, has a driver. Once the driver is installed, the applications built using Kvaser's libraries will work directly with that device. So, if I develop on a PC with a PCI card, I can still deploy my test software to a field engineer with a laptop and a PCMCIA card. Since the application I was working on was automotive, it was ideal to be able to send someone into a vehicle with a laptop. One of my few gripes with the supplied software is that it only supports the mainstream versions of Windows. Linux drivers would have been welcome, but Kvaser does not support it. (Open source drivers are available for some of the Kvaser ISA boards at the Linux CAN Project homepage.) 2

One of the most useful drivers from Kvaser is a virtual driver that doesn't require a CAN hardware interface. This allows one PC application to communicate with other PC applications running CAN software without any CAN hardware. You can therefore develop and test a PC program to communicate over the CAN bus without requiring any CAN hardware, as long as you write another PC test program to listen to whatever the first program is saying. This is useful if there isn't enough hardware to provide a system to each developer or if the prototype target is not yet available.

Higher Layer Protocols

There are a number of higher layer protocols that have been layered on top of the basic CAN specifications . These include SAE J1939, DeviceNet, and CANOpen. 3, 4, 5 The emphasis of these protocols is to define the meaning of the identifier and to encourage interoperability between CAN-based solutions from different vendors. Each standard has established a foothold in a different application domain.

If your system is closed, that is, if all nodes on the bus will be products from your company, then implementing one of the standard higher level protocols is probably unnecessary. However, examining these standards may give you ideas for some of the features that you might want to implement. For example, SAE J1939 includes a connection-oriented mechanism, which is suitable when transferring blocks of data larger than eight bytes. The standard defines a handshaking message to set up the connection and then a system of counting segments to ensure that the receiver will detect any missing packets.

Some higher level protocols define messages for particular application domains, such as a message that is sent when a car's brakes are engaged. In theory, this means that you can develop a device that integrates with your in-car electronics. In practice, the exact workings of the engine management CAN bus on any vehicle are a closely guarded secret. The CAN standards are not a ticket in; you still need the manufacturer's cooperation.


CAN is great alternative to serial communications. It could be argued that software engineers should always be prepared to write more difficult software to keep the cost of hardware to a minimum. But, in my opinion, a more comprehensive hardware solution—such as CAN—that leads to simpler software will also lead to more reliable software.

Related Barr Group Courses: 

Embedded Software Boot Camp

Hardware Interfacing with C

Embedded Linux Customization and Driver Development

For a full list of Barr Group courses, go to our Course Catalog.


1. Bosch Group CAN Homepage [back]

2. Linux CAN-bus Driver Project [back]

3. SAE J1939 Standards [back]

4. ODVA Homepage [back]

5. CAN in Automation (CiA) [back]

This article was published in the May 2003 issue of Embedded Systems Programming. If you wish to cite the article in your own work, you may find the following MLA-style information helpful:

Murphy, Niall. "CAN we Talk?" Embedded Systems Programming, May, 2003.