Link here

Serial Peripheral Interface (SPI)

How to use the SPI for fast, synchronous serial data exchange between the PDQ Board and peripheral devices

The Serial Peripheral Interface, SPI, is a fast synchronous serial interface. It provides a convenient means of connecting the PDQ Single Board Computer (SBC) to a variety of peripheral devices, including analog to digital and digital to analog converters, real time clocks, sensors, and actuators. Because the SPI bus is implemented using separate clock, transmit and receive data lines, each communicating device can simultaneously send and receive data over the SPI link.

 

Using the SPI bus for fast serial data exchange

The Freescale 9S12 (HCS12) processor includes three synchronous SPI ports named SPI0, SPI1, and SPI2. SPI0 is available for general use, and is brought out to the Wildcard expansion bus. It is also used to interface to the real-time battery-backed clock/calendar on the PDQ Board.

SPI1 and SPI2 are used for inter-processor communications on custom parallel-processing systems, and are brought out to a dedicated 10-pin SPI header. If parallel processing is not required, these ports can be used to interface to other peripheral devices.

A set of software drivers supports the SPI channels, making it easy for C language application programs to initialize the ports, change their baud rates and clock edge specifications, and exchange data with peripherals.

The SPI links on the HCS12 processor can transfer data over short distances much more rapidly than the RS232/485 or IIC serial links on the HCS12. The HCS12 can run as an SPI bus master at data clock rates up to 10 MHz, and the maximum data transfer rate for an SPI slave is 5 megabits/second.

This section describes the SPI pins, how to connect them, and how to use the pre-coded driver functions to control these fast serial links for instrument control applications.

 

SPI bus pins: SCK, MOSI, and MISO

Hardware is interfaced to the SPI0 (SPI channel 0) via three pins named SCK, MOSI, and MISO brought out to pins 7, 8, and 10 on the Wildcard port headers (see Appendix B). The SCK (serial clock) pin is a configurable synchronous data clock output. This signal synchronizes the exchange of data between the PDQ Board and its peripherals.

Serial Peripheral Interface (SPI) ports SPI1 and SPI2 are brought out to a custom 10-pin header which provides the three signals MOSI, MISO, and SCK for both SPI1 and SPI2, plus the /SS signal for both channels. After each startup, one of these two SPI ports is configured as a bus master, and the other as a slave. The SPI #2 jumper determines this configuration: if the jumper is not installed, SPI1 is the bus master and SPI2 is the slave. If the jumper is installed, SPI1 is the slave and SPI2 is the master. This allows pin-to-pin connections between the SPI headers of a PDQ Board without the SPI #2 jumper installed, and another PDQ Board that does have the SPI #2 jumper installed; each board would be a master on one channel and a slave on the other. The following table illustrates the SPI header pinout on the PDQ Board.

H5: SPI Header
Signal Pins Signal
/SS1 —1 2— /SS2
SCK1 —3 4— /SCK2
MOSI1 —5 6— MOSI2
MISO1 —7 8— MISO2
DGND —9 10— /XIRQ

The byte-sized messages are transmitted and received via the MOSI (Master Out/Slave In) and MISO (Master In/Slave Out) pins. The /SS (active-low slave select input) can be used to enable data transfers by slave devices when it is active low. The SPI0 /SS pin is not brought out to the Wildcard bus, and is instead used to control the chip select for the onboard battery-backed clock. The HCS12 is typically the SPI0 bus master on the Wildcard bus, and in this configuration the /SS pin is not needed. The /SS1 and /SS2 pins of the SPI1 and SPI2 channels are brought out to the dedicated 10-pin SPI header on the PDQ Board. Note that a common ground connection is necessary among the devices that communicate via the SPI.

When the PDQ Board controls the SPI network, it is referred to as a master; otherwise, it is a slave. The device that initiates a data transfer is the master, and all other devices on the network are slaves. Only one active master may control the network at a time, and this is typically the HCS12 on the SPI0 bus, as the processor is typically controlling peripheral devices implemented on the Wildcard I/O boards. Regardless of which device is the master or slave, both communicating devices can simultaneously send and receive data over the SPI bus.

If you are using the PDQ Board as a master device, each external SPI device requires a separate chip select line. The Wildcards that use the SPI link automatically generate the required chip select signal on each Wildcard.

 

Connecting to the SPI bus

Configured as a master device, the PDQ Board transmits bytes via the “master out/slave in” pin, MOSI. It receives bytes sent by a slave device via the “master in/slave out” pin, MISO. Transmissions are always initiated by the master device, and consist of an exchange of bytes. As the master transmits a byte to an active slave (that is, a slave with its chip select input signal asserted), the master also receives a byte from the slave. It may be that only the byte sent from the master to the slave is meaningful, or that only the byte sent from the slave to the master is meaningful. Nevertheless, each device simultaneously transmits and receives one byte. The only difference between the master and slave devices is that the master initiates the transmission and generates the clock output.

Slave devices use the master in/slave out pin, MISO, for transmitting, and the master out/slave in pin, MOSI, for receiving data. The following wiring diagram illustrates how the MOSI, and MISO pins of a master and a slave would be connected to exchange data:

Master Slave
MOSI MOSI
MISO MISO
SCK SCK
/SS /SS (chip select asserted)
GND GND

The status of a device as master or slave determines how the various pins must be configured. The arrows in the diagram point to pins configured as inputs, and originate from output pins. Thus, the master has only one input, MISO, which is the slave’s only output. Note that the master device outputs the clock synchronization signal SCK to the slave’s SCK input. Also, in the diagram, the master’s /SS (slave select) is shown as an output, but this master output is not required as long as a chip select for the slave is implemented in some way. As noted above, the /SS signal is not brought out for the SPI0 link. The GND line serves as a common voltage reference for the master and slave.

There are a variety of ways the MOSI, MISO, SCK and /SS pins on the PDQ Board can be connected. The one you choose depends on the specific device, or devices you will be connecting to. In some circumstances a one-way data flow may suffice. For example, a PDQ Board connected to a serial ADC (analog to digital converter) on a Wildcard might have these connections:

Master PDQ Board Slave Serial A/D Device
MOSI not connected
MISO Conversion Output
SCK CLK
memory-mapped output /CS
GND GND

In this example, the PDQ Board selects the serial ADC by addressing a memory location on the Wildcard that contains the ADC; a logic chip on the Wildcard strobes the chip select to the active state when the specified address appears on the bus. Even though the MOSI pin is not connected to anything, the master initiates a transmission by outputting a “dummy” byte on SPI0. The SCK pin clocks the serial ADC’s CLK input which causes the ADC’s conversion result to be transferred to the master via the MISO line.

The PDQ Board allows the details of the synchronous communications protocol to be customized for compatibility with a variety of peripherals. The next section describes the software drivers that configure and control the SPI links.

 

Software drivers for the SPI

The following table summarizes the software functions that make it easy to use the SPI channels on the PDQ Board.

Driver Routines for the SPI
InitIPSPI() SPI_RESOURCE SPIEXCHANGE()
InitSPI() SPI_RISING_LEADING_EDGE SPIFrequencies()
SPI_FALLING_LEADING_EDGE SPI_RISING_TRAILING_EDGE SPIRestore()
SPI_FALLING_TRAILING_EDGE SPIConfig() SPISave()

The InitSPI() function is automatically executed upon each power-up or restart to configure and enable SPI0 only (serial peripheral interface channel 0), so that it can transfer data on the Wildcard ports and to/from the on-board battery-backed real-time clock. InitSPI() generally does not need to be explicitly called from your software application. This function initializes the HCS12 as the SPI master with 2 MHz data transfer, with clock idling low, and valid data present/sampled on the falling trailing edge of the SPI clock. Data is transferred on the rising/leading clock edge, which is one-half clock cycle before it is sampled. This is a generally useful configuration, and it can be easily changed using the SPIConfig() function, along with SPISave() and SPIRestore() as described below. InitSPI() also initializes the resource variable SPI_RESOURCE to zero.

The InitIPSPI() function initializes SPI channels SPI1 and SPI2, and is not automatically called on reset and startup. In order to use SPI1 or SPI2, your code must explicitly call InitIPSPI() before calling SPIConfig() for these channels. SPI1 and SPI2 are used for inter-processor communications on custom parallel-processing systems. If parallel processing is not required, these channels may be used to communicate with other peripherals, and their baud rate and clock edge specifiers may be changed using the SPIConfig() function. The SPI #2 jumper on the PDQ Board determines which port this routine designates as the master and which as the slave. If the SPI #2 jumper cap is not installed, then SPI1 is the master SPI and SPI2 is the slave. If the SPI #2 jumper is installed, then SPI1 is the slave and SPI2 is the master. After InitIPSPI() executes, the SPI1 and SPI2 clocks idle low, data is sampled/valid on the falling/trailing clock edge, and data is transferred on the rising leading clock edge. The default baud rate is set to 5 MHz.

To select a configuration for a given SPI peripheral, consult the peripheral’s data sheet to discover the proper data-valid clock edge and baud rate, and use SPIConfig() to configure the specified SPI channel. For instance, here is the passage in the data sheet for the 2x16 character serial display provided by Mosaic describing the display's SPI interface:

SPI mode has a normally high level idle clock. When Slave Select is LOW,
data is sampled on the rising edge of the Clock.
The SPI interface is capable of receiving data at up to 100KHz clock rate.

In order to configure SPI1 to communicate with this device, SPIConfig() would be called as follows:

// Selected from the output of SPI.FREQUENCIES on the PDQ Board.
#define PDQ_SPI_BAUD_89KHZ 0x64
 
void display_initialize()
{
    InitIPSPI();
    SPIConfig( SPI_RISING_TRAILING_EDGE, PDQ_SPI_BAUD_89KHZ, 1 );
}

SPIConfig() configures the baud rate and data-valid clock edge of the specified SPI channel (0, 1 or 2). All other configuration parameters (master/slave, mode fault enable, SPI interrupt enable, bit order, /SS direction) are unchanged by this routine, and typically retain the state set by InitSPI() for the SPI0 channel, or InitIPSPI() for the SPI1 and SPI2 channels. The clock_edge_specifier passed to SPIConfig() is one of the 4 named constants SPI_FALLING_TRAILING_EDGE (the default after a restart), SPI_RISING_LEADING_EDGE, SPI_FALLING_LEADING_EDGE, or SPI_RISING_TRAILING_EDGE. The first two of these specify a clock that idles in the low state, and the latter two specify a clock that idles high. Each named constant describes the clock edge at which the data is sampled and valid; the data is typically transferred on the SPI bus one half cycle before the specified edge.

The baud rate parameter spibr_contents passed to SPIConfig() is the contents of the register that sets the desired baud rate on the specified SPI channel. The correspondence between the desired baud rate and the baud rate register contents is determined by a multivariate equation, so it is easiest to choose the correct value from a table of the available SPI frequencies. The C function SPIFrequencies() calculates and prints this table to the serial port. If you prefer to work strictly in C, you can invoke the SPIFrequencies() function from a compiled C program to create the printout. The easiest way to view the table, however, is to use the interactive Forth version of the function by typing at the terminal:

SPI.FREQUENCIES ↓

This function prints a formatted table of all 64 possible contents of the baud register. The first few lines of the resulting printout look like this:

Pass the selected SPIBR constant to SPIConfig (or to SPI.CONFIG in Forth).
0xSPIBR  SPIBR  BAUD.KHZ  (decimal)
0x0           0           10000
0x1           1            5000
0x2           2            2500
0x3           3            1250
. . .

Each line of the table lists the baud register contents in both hex and decimal, followed by the corresponding decimal SPI bus frequency in kHz, with the result rounded to the nearest kHz. After executing this function, examine the resulting data table to select the spibr_contents parameter that yields the desired SPI bus clock frequency, and pass the selected value to SPIConfig(). To exchange data with a peripheral on the SPI bus, use the SPIEXCHANGE() function. Simply place the information to be transmitted into a RAM buffer and invoke SPIEXCHANGE(); any returning bytes from the slave can be placed into the buffer.

The underlying function SPIExchange() is called by the macro SPIEXCHANGE(), and allows specifying a memory page. SPIExchange() may be called directly if data from paged memory (non-common memory) is be exchanged on an SPI channel. All C variables, arrays and strings are located in common memory unless specific qualifiers such as _rom are added to their declarations to place them elsewhere, so generally only the macro SPIEXCHANGE() will be necessary.

For example, here is how a message would be sent to the serial character display mentioned above, on SPI1:

char greeting[] = "Hi!";
 
void display_show_greeting()
{
    // Strings and arrays may both be directly used as pointers to their
    // contents, in this case providing the 'base_addr' parameter.
    // 'strlen()' counts number of bytes before a C string null
    // terminator. 'readback' is zero since no data is being received.
    // '1' indicates SPI1.
    SPIEXCHANGE( greeting, strlen(greeting), FALSE, 1 );
}

SPIEXCHANGE() writes to the specified spi_channel (set to 2 above) the contents of the buffer specified by base_addr in common memory (greeting is a pointer to the string "Hi!", or memory address at which that string is located), exchanging numbytes number of bytes (0 ⇐ numbytes < 32,768, set equal to strlen(greeting) above). The buffer must not cross a page boundary. The readback flag is set to FALSE (zero) above, but if it were TRUE (nonzero) then a simultaneous readback from the specified remote SPI is performed, and the incoming bytes are written into the buffer (replacing the transmitted bytes). See the glossary entry for SPIEXCHANGE() for more information. Here is an example of reading 8 bytes from SPI1 by setting the readback flag to TRUE:

char spi_buffer[8];
 
void spidevice_getdata()
{
    // Read from the device attached to SPI1 and currently driving the MISO line.
    // 'sizeof()' only works properly on an actual array or struct. If 'spi_buffer'
    // were declared as a pointer and set equal to a specific memory location,
    // it would be necessary to specify its size in bytes explicitly.
    SPIEXCHANGE( spi_buffer, sizeof(spi_buffer), TRUE, 1 );
    // Note that regardless of whether simultaneous transmission of data is desired,
    // the prior contents of 'spi_buffer' will be sent. i.e., the prior contents
    // of 'spi_buffer' determine the state of the MOSI line during the execution of
    // SPIEXCHANGE().
}

This routine executes at approximately 7 microseconds per byte with a 2 MHz SPI clock.

 

Managing multiple devices on SPI channel 0

If you are commanding multiple peripheral devices through a single SPI port, you must control access to the SPI port as a resource. The functions described so far do not GET or RELEASE the SPI_RESOURCE, nor do they save and restore the prior configuration of the SPI link. For the SPI0 channel, these important tasks are performed by the SPISave() and SPIRestore() functions. These two functions enable “polite” reconfiguration of the clock edge and baud rate for various slaves on the SPI0 bus, and also prevent contention on the SPI0 bus because they correctly GET and RELEASE the SPI_RESOURCE. Proper use of these functions allows multiple devices (including the onboard battery-backed real-time clock and various Wildcards) to share the SPI0 channel.

As discussed in the chapter titled “Real Time Programming”, a resource variable can be assigned to a shared resource to mediate access to the resource from various software tasks. The pre-defined resource variable SPI_RESOURCE controls access to the SPI0 channel. Software drivers for the onboard battery-backed real-time clock and for the Wildcards that use the SPI0 channel GET the SPI_RESOURCE, then configure and use the SPI0 channel, then RELEASE the SPI_RESOURCE. The SPISave() and SPIRestore() functions simplify the task of managing access to the SPI0 channel.

SPISave() GETs the SPI_RESOURCE, then fetches and returns as a 32-bit quantity the contents of the four 1-byte configuration registers of the SPI0 (serial peripheral interface 0) channel. After calling SPIConfig() to configure the SPI link and SPIEXCHANGE() to communicate with the specified peripheral, the 32-bit output parameter should be passed by the application program as the input to SPIRestore(). SPIRestore() writes to the four 1-byte configuration registers to restore the prior SPI0 configuration, and RELEASEs the SPI_RESOURCE to indicate that the SPI0 channel is available to other tasks.

 

Managing multiple devices on SPI channels 1 and 2

The functions that initialize, configure, and exchange data on the SPI1 and SPI2 channels are InitIPSPI(), SPIConfig(), and SPIEXCHANGE() as described above. If only one peripheral is connected to an SPI channel, then there is no need to save, change or restore the configuration, and the available drivers can be used. If multiple peripherals with different communication parameters are interfaced on either the SPI1 or SPI2 channel, and the same TASK is responsible for communicating with all of them, then SPIConfig() may be called as needed to set communication parameters for each device before communicating with it (and each device must have a slave select line connected to a separate digital output pin to avoid having all devices on the bus attempt to receive data not intended for them).

However, if your application design involves multiple TASKs to communicate on the same SPI channel, then the equivalent of the SPISave() and SPIRestore() functions must be created for SPI1 and/or SPI2. This will not be necessary for most applications, but if it proves necessary instructions are provided below.

Let’s assume that no jumper cap is installed on the SPI #2 jumper, so that the SPI1 channel is configured as a master by default, and two peripherals communicate with the HCS12 via SPI1 at different baud rates. Each of the peripherals must have its own chip select, typically implemented as port output bits. If two separate TASKs are used to access these two peripherals, use the RESOURCE typedef to declare a resource variable. For example,

RESOURCE SPI1_RESOURCE = 0;

The 4-byte configuration register area for SPI1 starts at 0x00F0, and the 4-byte configuration register area for SPI2 starts at 0x00F8. To define the equivalent of the SPISave() function for SPI1, the function should GET the SPI1_RESOURCE and then fetch the 32-bit contents of the SPI1 configuration register area at 0x00F0. To define the equivalent of the SPIRestore() function for SPI1, the function should accept a 32-bit input parameter (the one generated by the save routine) and store it into the configuration register area at 0x00F0, and then RELEASE the SPI1_RESOURCE. This approach will enable “polite” reconfiguration of the SPI1 channel in a manner exactly analogous to that provided by the pre-coded routines for SPI0.

 

Summary of the HCS12/9S12 SPI

The flexibility and power of the HCS12’s serial peripheral interface supports high speed communication between the HCS12 and other synchronous serial devices. The interface can be used to support analog to digital and digital to analog converters, networks of many computers controlled by a single master, or networks of devices controlled by several coordinated masters. Pre-coded device drivers configure the SPI for a standard data format, and it is easy to customize a data format and baud rate for your application. With careful design, many peripherals can communicate via the SPI, and powerful multi-processor systems can be linked using this high speed bus.



See also → Inter-IC (IIC, I²C, I2C) Serial Bus

 
This page is about: Using Serial Peripheral Interface (SPI) for Fast Serial Data Exchange – Using the SPI (serial peripheral interface) for fast serial data exchange and multi-drop serial communications between the PDQ Board and peripheral devices including analog to digital and digital to analog converters, real time clocks, sensors and actuators.
 
 
Navigation