Ethernut Home Hardware Firmware Tools Download Community
 
 
Search | Legals | Deutsch

SPI Bus Support

This document describes the SPI driver model used in Nut/OS 4.8 or later. A good explanation of the Serial Peripheral Interface Bus is provided in the related Wikipedia article. The OpenAVR Wiki presents some general code snippets for using SPI with AVR microcontrollers.

Introduction

The basic driver model used in Nut/OS is sufficient for simple devices like RS-232 interfaces or parallel LCDs. However, in order to reduce the pin count of microcontroller chips, serial busses are prefered in today's embedded system to attach all kind of peripherals. Several devices share one or more busses, which adds an additional level of complexity. Previous Nut/OS drivers either included their own bus controller handling or used some generally available code, but leave it to the application programmer to take care of possible conflicts.

Since Nut/OS 4.7.5 new API functions had been implemented to provide a framework for device drivers that control devices attached to this bus. Undoubtly its biggest advantage is the separation of bus control and device control. This allows to write platform independent drivers to control SPI devices, while the SPI controller hardware is handled for all devices in a single, target specific bus controller driver. This paper describes the internal structure and how it can be used by Nut/OS drivers and applications.

Device Registration

As explained in the Nut/OS Software Manual each device must be registered before being used. This creates a reference for the linker, which allows to include only the code that is actually used by the final firmware.

While normal devices are registered with

int NutRegisterDevice(NUTDEVICE * dev, uintptr_t base, uint8_t irq);

a similar API function had been added for device drivers using the new SPI bus framework:

int NutRegisterSpiDevice(NUTDEVICE * dev, NUTSPIBUS * bus, int cs);

Both functions return 0 on success, or -1 if device initialization failed for any reason.

The first parameter hasn't changed. It's a pointer to the NUTDEVICE structure, provided by the driver.

The second parameter, however, completely differs. While the simple device registration uses this for the base address of the device, SPI device registration expects a pointer to the new NUTSPIBUS structure.

In the first place this structure pointer also serves purpose of creating a link reference, but a link to a bus controller driver, not to a device driver. The relationship between these two drivers will be explained in the next chapter.

Last not least, the last parameter also has a different meaning. While used to specify an interrupt number for normal devices, SPI device registration expects a chip select index here. Actually it is not sure, whether this parameter will survive, because chip selects are typically configured when building the system. They are rarely changed during runtime.

SPI Bus Controller Driver

This is a completely new driver type used by Nut/OS. While normal device drivers offer a few functions similar to the C runtime library I/O, the bus controller driver offers his service to other device drivers, which control a specific device attached to a bus.

As explained above, the bus controller deals with the SPI hardware, removing this burden from the SPI device driver. However, only parts of the controller driver are platform dependent. The file /dev/spibus.c contains those parts, which are not. Beside some optional routines, it contains the function NutRegisterSpiDevice, which updates the NUTSPINODE structure and then calls the bus controller initialization and the device initialization. We will look to all this one after the other.

Let's first return to the NUTSPIBUS structure. It mainly contains a number of function pointers, to be used by the device driver to communicate with the hardware chip that is connected to the bus. A certain sequence has to be followed here. First, the device driver must allocate the bus to make sure that no other driver will use it concurrently. If successful, it can call the bus transfer routine, specifying a read buffer, write buffer and the number of bytes to transfer. This call may be repeated and finally the driver must release the bus.

Access to the SPI controller hardware as well as bus arbitration is implemented in the bus controller driver.

SPI Bus Device Driver

In general an SPI bus device driver is designed like any other Nut/OS device driver, providing functions for open, read, write, close and ioctl.

However, the new structure NUTSPINODE had been defined. It contains several items, which let the device driver and the bus controller driver communicate with each other. Some items of the NUTSPINODE structure are statically set, like the SPI mode, the SPI data rate etc. Others are not, like the chip select or the pointer to the NUTSPIBUS structure. These items are set in when calling NutRegisterSpiDevice. Note, that the statically set values are initial values only and may change during runtime.

The NUTSPINODE structure is actually part of the NUTDEVICE structure. Without this structure, which is attached to the interface control block pointer dev_icb of NUTDEVICE, the driver is not ready for the new framework. In many drivers this pointer is not used and set to NULL. Network drivers use it to store some network specific values.

Implementations

Although well tested, this is still in early stage.

Bus controller drivers exists for AVR and AT91 SPI hardware. There is also a platform independent version, which uses the Nut/OS GPIO interface to implement software SPI, a.k.a. bit banging.

NUTSPIBUSIncludeMax. CSFeatures
spiBus0At91dev/spibus_at91.h4 Bus driver for the AT91SAM SPI hardware. Configurable for polling, interrupt and an experimental interrupt driven PDC mode.
spiBus1At91dev/spibus_at91.h4 Same as spiBus0At91, to be used for a second bus.
spiBus0Avrdev/spibus_avr.h4 Bus driver for the SPI hardware found in 8-bit AVR microcontrollers. Configurable for polling, interrupt and interrupt driven double buffer mode.
spiBus0Avr32dev/spibus_avr32.h4 Early bus driver for the AVR32 SPI hardware. Configurable for polling, interrupt and interrupt driven PDC mode.
spiBus1Avr32dev/spibus_avr32.h4 Same as spiBus0Avr32, to be used for a second bus.
spiBus0Gpiodev/spibus_gpio.h4 Platform independent bit banging driver. Available on all targets, for which the Nut/OS GPIO routines had been implemented. This driver may be used concurrently with hardware SPI drivers to add more SPI busses.
spiBusNpldev/spibus_npl.h4 Bus driver for the Verilog SPI module used on Ethernut 3. Currently supports polling mode only.

Drivers are available for the following devices:

NUTDEVICEIncludeFeatures
devSpi7SEGdev/spi_7seg.h SPI driver for 7-sgement displays.
devSpiAt45d0
devSpiAt45d1
devSpiAt45d2
devSpiAt45d3
dev/spi_at45d.h Driver for up to 4 AT45D serial flash memory chips. Automatically detects devices from 128 kBytes up to 8 MBytes.
devSpiMegaLcd  Planned driver for alphanumeric DOG-M displays made by Electronic Assembly.
devSpiVsCodec0dev/vscodec.h Audio driver for VS1001K, VS1011E, VS1002D, VS1003B, VS1033C and VS1053B audio codecs. Audio input is not yet available.

Configuration

The preferred way of configuring the SPI bus controller and attached devices is using the Nut/OS Configurator.

Bus Controller

Most bus controllers are target specific and the related settings are available in the Architecture branch of the module tree. In most cases the configuration is limited to the transfer mode (polling or interrupt driven) and the GPIO pins to be used as chip selects.

The following screenshot shows the typical configuration for Ethernut 2.1, which is automatically set when loading the related configuration file. Polling and double buffer modes are both disabled, so the bus driver runs in interrupt mode, which is the default. PB4 and PD7 are used as chip select 0 and 2. Note that a bit number had been specified for chip select 1, but no related port. In this case chip select 1 is accepted by the driver, but it is not associated to any I/O pin. Such an unassociated chip select may be used with two devices sharing the same chip select line, where the first device is selected on the low and the second on the high level of the chip select line.

The generic GPIO based bus controller uses software bit banging and doesn't require any SPI hardware. Instead it needs 2 output ports (SCK and MOSI) and 1 input port (MISO), which can be freely configured. It is platform independent and therefore the settings are in the top level device driver branch of the Configurator's module tree. The screenshot below shows the default setup for Ethernut 3. Note, that only the port bits are given. The CPU on Ethernut 3 provides a single 32 bit I/O port only, thus there is no need to explicitly specify the port.

This driver is available on all targets for which a generic GPIO interface had been implemented. This is a rather simple interface, which provides a few routines to control or monitor port I/O lines. Because of this, the bit banging driver may be the first choice when porting Nut/OS to a new hardware and SPI support is required in an early stage.

Furthermore, this software SPI driver can be run concurrently with hardware SPI drivers. This allows to implement additional SPI busses. The disadvantages are low speed and high CPU load.

Device Driver

The configuration of SPI devices is highly device type dependent. A few values are SPI specific only, like SPI transfer rate, SPI transfer mode or wheter the chip select is negated. These values are found in the settings of most SPI devices. The next screenshot shows the SPI related configuration for the AT45D DataFlash memory.

Using the SPI Bus

There are two levels, at which your application may make use of the SPI bus:

  1. Low level routines, which directly transfer data on the bus.
  2. High level routines, which directly access a specifc device attached to the SPI bus.

Both, low and high level routines can be written in a target independent manner. No code change is required when moving your software from 8-bit AVR to 32-bit ARM, for example.

Low level bus access

If no device driver is available for your SPI device or if a full device driver looks like overkill, you may decide to directly use the bus controller driver for transfering data over the SPI bus.

Two structures are required, one of which is the NUTSPIBUS structure provided by the bus controller driver you want to use. The second one, a NUTSPINODE structure must be provided by your code. Both structures are used to initialize the bus for your SPI device.

#include <dev/spibus_gpio.h>
#include <dev/spibus.h>

NUTSPINODE my_node = {
    &spiBus0Gpio, /* Pointer to the bus controller driver. */
    NULL, /* Optional pointer to device specific settings. */
    10000, /* SPI clock rate. */
    3, /* SPI mode. */
    8, /* SPI data bits. */
    0  /* Chip select index. */
};

int MyInit(void)
{
    int rc;
    
    rc = (*spiBus0Gpio.bus_initnode) (&my_node);
    if (rc == 0) {
        NutEventPost(&spiBus0Gpio.bus_mutex);
    }
    return rc;
}

It's a little bit odd, that the bus_initnode doesn't initialize the bus_mutex semaphore. But that's how it is, the caller must do this.

If bus_initnode fails, then this is most probably because the chip select specified in the NUTSPINODE structure hasn't been configured in the bus controller driver.

In order to transfer data, you must allocate the bus first, using the bus_alloc() routine of the controller driver. Then bus_transfer() may be called once or more often and finally a call to bus_release() is required to release the bus.

int MyTransfer(uint8_t *buf, int len)
{
    rc = (*spiBus0Gpio.bus_alloc) (&my_node, 1000);
    if (rc == 0) {
        rc = (*spiBus0Gpio.bus_transfer) (&my_node, buf, buf, len);
    }
    (*spiBus0Gpio.bus_release) (node);
    return rc;
}

High level bus access

If an SPI device driver is available for the chip you want to communicate with, then you don't need to deal with low level stuff. Instead you can often use standard C function to talk to the device. Only one Nut/OS specific call is required to register the device with a specific SPI bus controller.

For example, to initialize the MP3 audio decoder attached to a bit banged software SPI bus, we simply need:

#include <dev/spibus_gpio.h>
#include <dev/vscodec.h>

int MyInit(void)
{
    return NutRegisterSpiDevice(&devSpiVsCodec0, &spiBus0Gpio, 1);
}

While the audio decoder device is target independent, you will probably prefer the SPI hardware to the bit banged driver used in our example. The drivers typically differ from target to target. If you want to write code that runs on several platforms, then you should use the boards.h include file. It defines default drivers for most targets.

#include <dev/board.h>

NutRegisterSpiDevice(&devSpiVsCodec0, &DEV_SPIBUS, 1);

After having done the initialization, let's asume, that a short MP3 encoded audio sequence is stored in a buffer and should be transfered to the audio decoder. We can use the following routine:

void MyTransfer(uint8_t *buf, int len)
{
    int ad = _open("audio0", _O_WRONLY | _O_BINARY);
    _write(ad, buf, len);
    _close(ad);
}

This is pure C code and will run unmodified on any supported target.

SPI and the Nut/OS Kernel

During initialization the Nut/OS kernel may need to access SPI devices, like reading configuration data from serial Flash memory or configuring an external clock chip.

For example, the Elektor Internet Radio Board uses the last sector of an AT45DB321D DataFlash to store its system configuration.

In general the kernel will not register any NUTDEVICE drivers. Fortunately, the system provides a special non-volatile memory driver, which doesn't need a device driver, but directly accesses the SPI bus to talk to serial memory devices. The related code can be found in dev/nvmem_at45d.c. Note, that the code will not interfere with the devSpiAt45d devices, that may be registered by the application.

SPI and Nut/OS File Systems

At the time of this writing file system support for devices attached to the SPI bus controller framework is almost non-existent. There is one exception, the rather simple raw file system.

You may know, that Nut/OS offers the FAT compatible PHAT file system, which uses MMC/SD-Cards in SPI mode. However, the related low level drivers implement their own SPI routines. It would be a benefit to re-write them for using the new SPI bus interface. Work is in progress to close this gap.

Known Problems

Interference during initialization

Only one chip select is known to the bus controller driver during initialization. Depending on the hardware, this may result in floating conditions on the other chip select lines and let the device initialization / detection fail.

One solution to this problem may be to mode chip select definitions to the system configuration. Then the bus controller driver would be able to disable all chip selects druing its first init call.

Another approach could be to delay the device initialization until the first dev_open call. However, this would break the well known behaviour, that device registration returns -1 if the device is not present.

Until a decision is made on how to solve this, applications should make sure, that all chip selects are disabled before device registration.

SPI mode switching

Some bus controller drivers fail to correctly switch SPI mode while selecting a device, most notably the one for the AT91 family.

As long as all device attached to the same bus use the same SPI mode, this is no issue. Luckily, many devices like the AT45D DataFlash are able to run in different modes.

The cause of the problem is currently unknown, but may be based on the fact, that all bus controller drivers handle chip selects "manually" with GPIO.

SPI data rate

It has been reported, that the bus controller driver for the AT91 does not correctly implement data rate settings.