Name
SPI Functions — allow applications and other packages to access SPI devices
Synopsis
#include <cyg/io/spi.h>
CYG_SPI_OP_RETURN_TYPE cyg_spi_transfer(
cyg_spi_device* device, cyg_bool polled, cyg_uint32 count, const cyg_uint8* tx_data, cyg_uint8* rx_data)
;
CYG_SPI_OP_RETURN_TYPE cyg_spi_tick(
cyg_spi_device* device, cyg_bool polled, cyg_uint32 count)
;
int cyg_spi_get_config(
cyg_spi_device* device, cyg_uint32 key, void* buf, cyg_uint32* len)
;
int cyg_spi_set_config(
cyg_spi_device* device, cyg_uint32 key, const void* buf, cyg_uint32* len)
;
CYG_SPI_OP_RETURN_TYPE cyg_spi_transaction_begin(
cyg_spi_device* device)
;
cyg_bool cyg_spi_transaction_begin_nb(
cyg_spi_device* device)
;
CYG_SPI_OP_RETURN_TYPE cyg_spi_transaction_transfer(
cyg_spi_device* device, cyg_bool polled, cyg_uint32 count, const cyg_uint8* tx_data, cyg_uint8* rx_data, cyg_bool drop_cs)
;
CYG_SPI_OP_RETURN_TYPE cyg_spi_transaction_tick(
cyg_spi_device* device, cyg_bool polled, cyg_uint32 count)
;
CYG_SPI_OP_RETURN_TYPE cyg_spi_transaction_end(
cyg_spi_device* device)
;
Description
All SPI functions take a pointer to a cyg_spi_device structure as their first argument. This is an opaque data structure, usually provided by the platform HAL. It contains the information needed by the SPI bus driver to interact with the device, for example the required clock rate and polarity.
An SPI transfer involves the following stages:
- Perform thread-level locking on the bus. Only one thread at a time is allowed to access an SPI bus. This eliminates the need to worry about locking at the bus driver level. If a platform involves multiple SPI buses then each one will have its own lock. Prepare the bus for transfers to the specified device, for example by making sure it will tick at the right clock rate.
- Assert the chip select on the specified device, then transfer data to and from the device. There may be a single data transfer or a sequence. It may or may not be necessary to keep the chip select asserted throughout a sequence.
- Optionally generate some number of clock ticks without asserting a chip select, for those devices which need this to complete an operation.
- Return the bus to a quiescent state. Then unlock the bus, allowing other threads to perform SPI operations on devices attached to this bus.
The simple functions cyg_spi_transfer
and
cyg_spi_tick
perform all these steps in a single
call. These are suitable for simple I/O operations. The alternative
transaction-oriented functions each perform just one of these steps.
This makes it possible to perform multiple transfers while only
locking and unlocking the bus once, as required for more complicated
devices.
With the exception
of cyg_spi_transaction_begin_nb
all the functions
will block until completion. The type of the return values depends on
whether the underlying hardware driver can return error conditions or
not. If it can, this will be indicated with the C preprocessor
define CYGINT_IO_SPI_DRV_REPORTS_ERRORS
, and the
return type CYG_SPI_OP_RETURN_TYPE
will be a
standard error code as defined in the C/POSIX
header <errno.h>
. Alternatively
for drivers which do not return errors, the return value's type will
be void
, or in other words, no return value. If
there are any errors, they would be generated by an SPI bus peripheral,
and not by the attached SPI device. The SPI bus does not receive any
feedback from a device about possible errors, instead those have to be
handled by software at a higher level.
If a driver is capable of indicating errors, and an error value is returned,
this may imply the SPI operation requested may not have started, may not have
completed, or may only have partially completed. The exact interpretation
will depend on both hardware properties and low-level driver implementation.
SPI API users are free to ignore all return values, in which case the usage of
the SPI API will be identical, irrespective of the setting
of CYGINT_IO_SPI_DRV_REPORTS_ERRORS
.
An SPI transfer will always take a predictable amount of time, depending on the transfer size and the clock rate. If a thread cannot afford the time it will take to perform a complete large transfer then a number of smaller transfers can be used instead.
SPI operations should always be performed at thread-level or during system initialization, and not inside an ISR or DSR. This greatly simplifies locking. Also a typical ISR or DSR should not perform a blocking operation such as an SPI transfer.
SPI transfers can happen in either polled or interrupt-driven mode.
Typically polled mode should be used during system initialization,
before the scheduler has been started and interrupts have been
enabled. Polled mode should also be used in single-threaded
applications such as RedBoot. A typical multi-threaded application
should normally use interrupt-driven mode because this allows for more
efficient use of cpu cycles. Polled mode may be used in a
multi-threaded application but this is generally undesirable: the cpu
will spin while waiting for a transfer to complete, wasting cycles;
also the current thread may get preempted or timesliced, making the
timing of an SPI transfer much less predictable. On some hardware
interrupt-driven mode is impossible or would be very inefficient. In
such cases the bus drivers will only support polled mode and will
ignore the polled
argument.
Simple Transfers
cyg_spi_transfer
can be used for SPI operations
to simple devices. It takes the following arguments:
-
cyg_spi_device*
device
- This identifies the SPI device that should be used.
-
cyg_bool
polled
- Polled mode should be used during system initialization and in a single-threaded application. Interrupt-driven mode should normally be used in a multi-threaded application.
-
cyg_uint32
count
- This identifies the number of data items to be transferred. Usually each data item is a single byte, but some devices use a larger size up to 16 bits.
-
const cyg_uint8*
tx_data
-
The data to be transferred to the device. If the device will only
output data and ignore its input then a null pointer can be used.
Otherwise the array should contain
count
data items, usually bytes. For devices where each data item is larger than one byte the argument will be interpreted as an array of shorts instead, and should be aligned to a 2-byte boundary. The bottom n bits of each short will be sent to the device. The buffer need not be aligned to a cache-line boundary, even for SPI devices which use DMA transfers, but some bus drivers may provide better performance if the buffer is suitably aligned. The buffer will not be modified by the transfer. -
cyg_uint8*
rx_data
-
A buffer for the data to be received from the device. If the device
does not generate any output then a null pointer can be used.
The same size and alignment rules apply as for
tx_data
.
cyg_spi_transfer
performs all the stages of an
SPI transfer: locking the bus; setting it up correctly for the
specified device; asserting the chip select and transferring the data;
dropping the chip select at the end of the transfer; returning the bus
to a quiescent state; and unlocking the bus.
Additional Clock Ticks
Some devices require a number of clock ticks on the SPI bus between
transfers so that they can complete some internal processing. These
ticks must happen at the appropriate clock rate but no chip select
should be asserted and no data transfer will happen.
cyg_spi_tick
provides this functionality.
The device
argument identifies the SPI bus, the
required clock rate and the size of each data item. The
polled
argument has the usual meaning. The
count
argument specifies the number of data items
that would be transferred, which in conjunction with the size of each
data item determines the number of clock ticks.
Transactions
A transaction-oriented API is available for interacting with more complicated devices. This provides separate functions for each of the steps in an SPI transfer.
cyg_spi_transaction_begin
must be used at the
start of a transaction. This performs thread-level locking on the bus,
blocking if it is currently in use by another thread. Then it prepares
the bus for transfers to the specified device, for example by making
sure it will tick at the right clock rate.
cyg_spi_transaction_begin_nb
is a non-blocking
variant, useful for threads which cannot afford to block for an
indefinite period. If the bus is currently locked the function returns
false immediately. If the bus is not locked then it acts as
cyg_spi_transaction_begin
and returns true.
Once the bus has been locked it is possible to perform one or more
data transfers by calling
cyg_spi_transaction_transfer
. This takes the same
arguments as cyg_spi_transfer
, plus an additional
one drop_cs
. A non-zero value specifies that
the device's chip select should be dropped at the end of the transfer,
otherwise the chip select remains asserted. It is essential that the
chip select be dropped in the final transfer of a transaction. If the
protocol makes this difficult then
cyg_spi_transaction_tick
can be used to generate
dummy ticks with all chip selects dropped.
If the device requires additional clock ticks in the middle of a
transaction without being selected,
cyg_spi_transaction_tick
can be used. This will
drop the device's chip select if necessary, then generate the
appropriate number of ticks. The arguments are the same as for
cyg_spi_tick
.
cyg_spi_transaction_end
should be called at the
end of a transaction. It returns the SPI bus to a quiescent state,
then unlocks it so that other threads can perform I/O.
A typical transaction might involve the following. First a command should be sent to the device, consisting of four bytes. The device will then respond with a single status byte, zero for failure, non-zero for success. If successful then the device can accept another n bytes of data, and will generate a 2-byte response including a checksum. The device's chip select should remain asserted throughout. The code for this would look something like:
#include <cyg/io/spi.h> #include <cyg/hal/hal_io.h> // Defines the SPI devices … cyg_spi_transaction_begin(&hal_spi_eprom); // Interrupt-driven transfer, four bytes of command cyg_spi_transaction_transfer(&hal_spi_eprom, 0, 4, command, NULL, 0); // Read back the status cyg_spi_transaction_transfer(&hal_spi_eprom, 0, 1, NULL, status, 0); if (!status[0]) { // Command failed, generate some extra ticks to drop the chip select cyg_spi_transaction_tick(&hal_spi_eprom, 0, 1); } else { // Transfer the data, then read back the final status. The // chip select should be dropped at the end of this. cyg_spi_transaction_transfer(&hal_spi_eprom, 0, n, data, NULL, 0); cyg_spi_transaction_transfer(&hal_spi_eprom, 0, 2, NULL, status, 1); // Code for checking the final status should go here } // Transaction complete so clean up cyg_spi_transaction_end(&hal_spi_eprom);
A number of variations are possible. For example the command and status could be packed into the beginning and end of two 5-byte arrays, allowing a single transfer.
Device Configuration
The functions cyg_spi_get_config
and
cyg_spi_set_config
can be used to examine and
change parameters associated with SPI transfers. The only keys that
are defined for all devices are
CYG_IO_GET_CONFIG_SPI_CLOCKRATE
and
CYG_IO_SET_CONFIG_SPI_CLOCKRATE
. Some types of
device, for example MMC cards, support a range of clock rates. The
cyg_spi_device structure will be initialized
with a low clock rate. During system initialization the device will be
queried for the optimal clock rate, and the
cyg_spi_device should then be updated. The
argument should be a clock rate in Hertz. For example the following
code switches communication to 1Mbit/s:
cyg_uint32 new_clock_rate = 1000000; cyg_uint32 len = sizeof(cyg_uint32); if (cyg_spi_set_config(&hal_mmc_device, CYG_IO_SET_CONFIG_SPI_CLOCKRATE, (const void *)&new_clock_rate, &len)) { // Error recovery code }
If an SPI bus driver does not support the exact clock rate specified
it will normally use the nearest valid one. SPI bus drivers may define
additional keys appropriate for specific hardware. This means that the
valid keys are not known by the generic code, and theoretically it is
possible to use a key that is not valid for the SPI bus to which the
device is attached. It is also possible that the argument used with
one of these keys is invalid. Hence both
cyg_spi_get_config
and
cyg_spi_set_config
can return error codes. The
return value will be 0 for success, non-zero for failure. The SPI bus
driver's documentation should be consulted for further details.
Both configuration functions will lock the bus, in the same way as
cyg_spi_transfer
. Changing the clock rate in the
middle of a transfer or manipulating other parameters would have
unexpected consequences.
2024-03-18 | Open Publication License |