Implementing SPI on an OMAP-based board design - Embedded.com

Implementing SPI on an OMAP-based board design

Dmitry Pervushin describes how to add to the functionality of an OMAP-based PandaBoard by connecting a real time clock chip to the design via a Serial Peripheral Interface (SPI) created using the board’s expansion connector.

Once the OMAP-based PandaBoard became available to developers as the first open mobile development platform included by Android Open Source Platform (AOSP), the enthusiastic open source community started building exciting projects on the platform, and asking questions about extending the platform’s capabilities beyond traditional means.

While experience levels of developers working with the PandaBoard vary – from seekers of programming advice to advanced designers choosing an interface to connect to – their mission is always the same: create a compelling open-source product or application, and share knowledge with the PandaBoard community.

In that vein, this how-to article outlines the Serial Programming Interface available through the PandaBoard’s expansion connector, and as an illustration explains how to use it to connect to a real-time clock (RTC) chip.

What is SPI?
SPI stands for “Serial Programming Interface,” a simple standard that originally came from Motorola. Sometimes it is called “4-wire” interface, contrasting with 1-wire, 2-wire and 3-wire serial buses, because the interface defines four wires: MOSI (master-out-slave-in), MISO (master-in-slave-out), SCLK(serial clock from the master) and chipselect signal (CS#). Using multiple chipselects allows several devices to be connected to the same master. (Editor's Note : To learn more about SPI, go to: “The future of the Serial Peripheral Interface.” )

Why SPI ? There are multiple interfaces available to connect a device to the PandaBoard: SPI, I2C, USB and others. However, using SPI to connect the peripheral device (Figure 1, below ) is probably the best option because it is less expensive, easy to connect and debug, and demonstrates relatively good performance.

Figure 1. Setting up the PandaBoard SPI connection

What kind of devices can be connected to SPI?
Almost any type of device can be connected to the SPI bus, from simple analog-digital converters to ethernet adapters. Even SD cards have an “SPI mode,” which allows a developer to connect them to the SPI bus.

The SPI subsystem in Linux is well supported and offers drivers for a wide range of SPI devices. There are drivers for MTDs (SPI flash memory), SD controllers and ethernet adapters. The SPI subsystem in the Linux kernel also provides the generic “spidev” driver that exposes an API to the userspace and allows control of the peripheral outside the kernel.

How to connect an SPI device to the PandaBoard
The PandaBoard provides an expansion connector where one can find pins that are multi-channel serial port interface (MCSPI)-related. These pins are:

Pin 4 MCSPI_CS3
Pin 10 MCSPI_CS1
Pin 12 MCSPI_SIMO
Pin 14 MCSPI_CS2
Pin 16 MCSPI_CS0
Pin 18 MCSPI_SOMI
Pin 20 MCSPI_CLK

Figure 2 below shows the CS0/CS1/CS2/CS3 lines, which allow up to four devices to be connected to this master. A device will most likely be connected to MCSPI_SIMO, MCSPI_SOMI, MCSPI_CLK and MCSPI_CS0.


Figure 2: PandaBoard CS0/CS1/CS2/CS3 lines

Also of importance, the PandaBoard has 1.8V on control pins, but the sample DS3234 chip above wants 5V. Fortunately, there are “level shifters” such as the PCA9306, which allows a bidirectional voltage-level translator. Having applied 1.8V and 5V as the VREF1 and VREF2 pins of the PCA9306, and driving the ENABLE pin to high, the chip translates levels between pins SDA1/SCL1 and SDA2/SCL2.

To use DS3234 with PandaBoard, a developer should buy at least two level translators and connect pins MCSPI_SOMI, MCSPI_SIMO, MCSPI_CS0 and MCSPI_CLK from the PandaBoard expansion connector to one side of the level shifters and MISO, MOSI, SS and CLK to another side. The sample schematics are shown in Figure 3 below.


Clickon image to enlarge.

Figure 3. Schematic for using the DS3234 chip

SPI drivers stack in Linux
The Linux kernel provides the following pieces of code that relate to the SPI subsystem:

* SPI master drivers
* SPI functional device drivers, often called “protocol masters”
* SPI board information

SPI master drivers are drivers that deal with the SPI controller itself. They know “how” to send bytes to any peripheral device.

Fortunately, the Linux kernel already provides drivers for the OMAP processor’s MCSPI controller, so one will likely not need to create or re-implement this one, unless an error is found or performance needs to be significantly improved.

In either case, however, it’s best to think twice before starting to change it. These kinds of device drivers provide kernel-level API that is visible for and used only by kernel drivers.

SPI functional device drivers, or SPI protocol masters, deal with the peripheral device attached to the SPI bus. These drivers know “what” to send/receive to/from the device. These device drivers usually expose user-level API (like spidev does) or kernel-level API that can be used by another subsystem.

For example, the SPI flash driver creates the MTD (memory-technology device) driver, which is visible as the regular block device (mtdblockN ) and character device (mtdN ) so the user can utilize MTD utilities to access the flash in a uniform way.

Another example that is discussed later is the real time clock chip DS3243, which is connected to the SPI bus and provides a generic RTC interface to the userspace, so it can be read/written with the hwclock utility.

SPI board information is part of the machine-depended code that performs registration of SPI devices with the SPI subsystem. Because SPI devices are usually hardwired to the board and rarely have an ability to enumerate them, they have to be hardcoded somewhere in the Linux kernel.

One will likely have to add similar code snippet to the board definition file, which in our sample would be to arch/arm/mach-omap2/board-omap4panda.c .

What are the necessary kernel changes?
The SPI bus and USB, for example, are different. USB devices provide information about themselves and can be queried by the bus controller driver. SPI devices, on the other hand, cannot do this. The SPI master driver never has a chance to know what is connected to some specific CS# (and even know if there is something connected ) unless it is provided with some information.

The board-dependent code does the registration by calling the function spi_register_board_info . It takes two parameters: list of devices connected and the size of this list. The example might look similar to the following:

static const struct spi_board_info panda_spi[] __initconst = {
      {
                     .modalias = “spidev”,
                     .bus_num = 1,
                     .chip_select = 1,
                     .max_speed_hz = 1000,
                     .mode = SPI_MODE_1,
       },    {
                    .modalias = “ds3234”,
                    .bus_num = 1,
                    .chip_select = 0,
                    .max_speed_hz = 400000,
      },
};

Also, register this spi_board_info array in the function omap4_panda_init :

spi_register_board_info(panda_spi, ARRAY_SIZE(panda_spi));

In this code snippet, two SPI devices are registered. The first device is something generic attached to CS#1 on the SPI bus 1 (McSPI1) controlled by the “spidev.” It has a maximum speed of 1kHz, which is just for the sample because the real speed can be up to 48MHz.

The second device is connected to CS#0, has a maximum speed of 400kHz (enough for RTC) and is controlled by the “ds3234” driver. It is a good “SPI functional device driver,” and is doing all the work to exchange data over the SPI bus.

Working with a device attached to the PandaBoard
First of all, because the kernel has been modified, it has to be recompiled. It is a good idea to make sure that all the necessary options are switched on:

CONFIG_SPI = y
CONFIG_SPI_MASTER = y
CONFIG_SPI_OMAP_24xx = y
CONFIG_RTC_DRV_DS3234 = y
CONFIG_SPI_SPIDEV = y

The last option can be set to “m,” meaning this will be a separate module. In this case, do not forget to update the modules directory. After the kernel is compiled and started, the following messages will appear:

ds3234   spi1.0:   rtc core:   registered   ds3234 as  rtc0

This means that the ds3234 driver has been attached to the SPI device, and exposes its RTC interface via /dev/rtc0 . Now one can run any tools to access the RTC (hwclock is a very useful utility ):

# hwclock   –show

You can also access the device via the spidev interface, which allows userspace application to send/receive bytes on its own, for example, using this script:

import  os,  sys

def spidev_test(devnode):
          #
          # very simple. no exception handling, just five steps:
          #

           # 1. Open device
         spi = os.open(devnode, os.O_RDWR, 0777)

           # 2. Write single zero byte
          write_bytes = “x00”
          w_res = os.write(spi, write_bytes, len(write_bytes))
          if written != len(write_bytes):
                        raise Exception(“Wrote less bytes than requested”)

            # 3. read 8 bytes back
           rd = 8
           rd_bytes = os.read(spi, rd)
           if len(rd_bytes) != rd:
                        raise Exception(“Read less than expected”)

            # 4. print the result
           print [“%x” % ord(rd) for rd in rd_bytes]

            # 5. close the handle
           os.close(spi)
if __name__ == “__main__”:
          spidev_test (“/dev/spidev1.0”)
          sys.exit(0)
else:

            print “How do you want to use spidev-test?”

The script will try to send a zero byte to the device, and print the response of 8 bytes. If one attaches the spidev driver instead of ds3234 to the DS3234 device (in the code snipped above, just replace “ds3234” with “spidev ), the output might look as follows:

# python spi-sample.py
['5', '10', '12', '2', '1', '5', '12']

This is just a sample to demonstrate the SPI exchange. Parsing of this data is usually done by the ds3234 driver. However, it might be a good starting point to start development of one’s own protocol driver, especially if something new and unknown was connected to the Linux kernel.

Debugging the SPI exchange
Sometimes things go wrong, and the expected results do not happen. Here are a few simple rules that could help reveal the cause of the problem with the SPI exchange:

* Double check the wiring. It could save a lot of debugging time!
* Make sure the device is properly powered.
* Lower the clock to the minimal value (1 kHz is a good setting).
* Enable CONFIG_DEBUG_KERNEL and CONFIG_SPI_DEBUG , which will increase the amount of debugging information printed by the kernel.
* Put a line “#define VERBOSE ” on the top in the SPI master driver source (drivers/spi/omap2_mcspi.c ). This will print all data that is sent/received from the SPI devices.

Thanks and references
I would like to thank David Brownell for implementing the SPI subsystem in the Linux kernel, along with everyone else who helped to keep it in the mature state. Here are some links I found helpful:

* Kernel documentation about spi subsystem.
* SPI on BeagleBoard:

* SPI on Wikipedia.

Dmitry Pervushin joined Texas Instruments in France in April 2012 as an applications engineer. He has worked with the Linux kernel, primarily focused on ARM processors, since 2002 in a variety of roles at MontaVista and Mentor Graphics and holds a master of science in applied mathematics from Kazan State Technical University, Russia. His email address is .

1 thought on “Implementing SPI on an OMAP-based board design

  1. “New powerful feature appeared in 2012-2014 Device Tree and its Overlays. Maybe this article can be linked with appropriate new material. Thanks)”

    Log in to Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.