avr-libc-corelib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Avr-libc-corelib] The SPI implementation


From: Ron Kreymborg
Subject: [Avr-libc-corelib] The SPI implementation
Date: Tue, 22 Sep 2009 16:30:13 +1000

Some suggestions for a SPI api. This is more a discussion paper that a
specification. Comments welcome.

=========== The Slave. 

An whole AVR acting as a slave is not going to be idly spinning on SPIF, and
as a master message may arrive while it's doing something else, we need a
receive interrupt. 

The library can make no assumptions about the master message contents, so
the slave receive interrupt uses a call-back to the user code with the
received byte as parameter:

int SpiSlaveReceive(int data);

By the way, we can have camels and underscores together at the cost of one
line, so use what you prefer:

#define spi_slave_receive SpiSlaveReceive

This function is called from within the slave receive interrupt function, so
keep that in mind. It can initiate whatever is required, typically based on
the content of the first byte received. Where the AVR was maintaining some
status in the SPDR while idle, it can indicate the master has taken that
status. It could also specify a request for a particular message from a set
of perhaps multi-byte messages the slave is responsible for.

The function can choose to return a value that will be immediately written
to the SPDR. Bit 15 must be set in the returned value for this to occur. 

Alternatively, events signalled from within this interrupt can initiate
other user code to update the SPDR using the 

SPI_RET SpiSetSlave(int data);

If there are no error returns this has simply copied the byte to the SPDR. 

Included in the master SpiMasterSend (see below) is a delay parameter that
specifies in uSecs (including zero) the delay the master must use between
bytes. For a particular system this allows the slave time between receive
events to prepare the next byte to return. This may require interpretation
of previously received bytes or collection of data and hence processing
time. For a more dynamic system, the SPDR idle byte itself could specify the
setup delay the slave needs to prepare valid data.

========== The Master

The master send function is:

SPI_RET SpiMasterSend(int count, uint8* dataOut, uint8* dataIn, int delay);

This can return various error codes. The parameters consist of a count of
the number of bytes involved in this transaction, a pointer to an array of
at least <count> bytes that will be sent, a pointer to an array of bytes of
at least <count> bytes that will receive the slave reply, and a delay the
send code will insert between bytes.

On return the app should examine the returned value for any errors. This can
include the sender is busy. It is entirely up to the application as to what
is sends and/or does with what it receives. All the library will do is
transmit and receive that many bytes.

The library manages the sending of the entire message. Like the slave it
uses the SPIE interrupt so there is no busy-waiting. The app must guarantee
the send buffer integrity during the transmission.

A callback function called

void SpiSendDone(int flags);

is called once the last byte has been sent/received or some error has
occurred (timeout, etc). The <flags> parameter will define errors. User code
defines what this function does. It need do nothing.

A function to query the sender for state is:

SPI_STATE spi_get_master_state(void);

This will return at least that the sender is busy. It could be called
SpiIsMasterBusy but there will probably be additional state as we progress.

========== Initialisation

There is a philosophical question here: Do we just send the required
contents of the SPCR and SPSR registers, or do we make it more formal? The
former is easy and not too hard for SPI, but for more complex devices I
think a formal init is safer as the init code can detect bit combinations
that may be illegal or dangerous.

So let's do formal here:

typedef enum
{
   SPI_CLK_POL_RISING = 1,   // comments here...
   SPI_CLK_POL_FALLING
} SPI_CLOCK_POL;

typedef enum
{
   SPI_PHASE_LEADING = 1,
   SPI_PHASE_TRAILING
} SPI_PHASE;

typedef enum
{
   SPI_CLK4 = 1;
   SPI_CLK8,
   SPI_CLK16,
   SPI_CLK32,
   SPI_CLK64,
   SPI_CLK128
} SPI_CLOCK;

SPI_RET SpiInitMaster(SPI_CLOCK speed, SPI_PHASE phase, SPI_CLOCK_POL
polarity, bool multi);

The first three are obvious, while the last if true will causes a call to
the call-back below immediately prior to initiating a transmission:

SPI_RET spi_set_address(void);

The application can use this to manage outputs for selecting different
slaves. An error return will abort the transfer. If <multi> is false then
this call-back is never called and the library will manage the SS pin.

My opinion is that, if you are implementing a multi-master system then you
had better roll you own. Thus the SS pin is initialised as, and should
always remains as, an output. Note that SpiSetAddress can use SS if required
- the library won't touch when <multi> is true. While awkward, the init call
can be re-called for situations where multiple slaves have different clock
requirements.

The slave init call is as you would expect:

SPI_RET SpiInitSlave(SPI_CLOCK speed, SPI_PHASE phase, SPI_CLOCK_POL
polarity);

The error returns are still to be defined and will be defined in the SPI_ERR
enum.

========== Summary

The calls are:

SPI_RET SpiInitMaster(SPI_CLOCK speed, SPI_PHASE phase, SPI_CLOCK_POL
polarity, bool multi);
SPI_RET SpiInitSlave(SPI_CLOCK speed, SPI_PHASE phase, SPI_CLOCK_POL
polarity);
SPI_RET SpiMasterSend(int count, uint8* dataOut, uint8* dataIn, int delay);
SPI_RET SpiSetSlave(int data);
SPI_STATE SpiGetMasterState(void);

The call-backs are:

int SpiSlaveReceive(int data);
void SpiSendDone(int flags);
SPI_RET SpiSetAddress(void);

A first draft, so no guarantees I have not missed something

Ron







reply via email to

[Prev in Thread] Current Thread [Next in Thread]