SPI delay
2005-01-18 by teunvandeberg
Hello all,
I have written a SPI driver for use with FreeRTOS (www.freertos.org).
I am using a LPC2292.
Using this driver I have observed some strange behavior. The driver
is interrupt driven, so I write to the SPI data register again after
the interrupt flag goes hi. Sometimes however, nothing happens on the
SPI pins. If I add a small delay, no problems occur, however this
should not be necessary. When debugging, this problem doesn't occur
so that also suggests timing problems. Is anyone familiar with this
problem? For more details I attached my code below.
Thank you in advance,
Teun
--------------------------------------------------------------------
Here's the calling code:
--------------------------------------------------------------------
unsigned portCHAR cBatmanPing()
{
unsigned portCHAR ucPingReturn;
int i;
/* Setup the spi bus for the BATMAN */
prvInitBus();
/* Now we can give our ping command. */
if( cSpiPutChar( BATMANspiDEV, BATMANPing, BATMANBlockTime ) !
= pdTRUE ) //Send the write command and the adress.
{
prvFreeBus();
return pdFALSE;
}
/* Give the data */
if( cSpiPutChar( BATMANspiDEV, BATMANpingData,
BATMANBlockTime ) != pdTRUE ) //Send the write command and the adress.
{
prvFreeBus();
return pdFALSE;
}
for( i = 0 ; i < 60 ; i++ )
; // This is the delay. Whithout this the read the comes
next will not drive the clock.
/* Read the data */
if( cSpiGetChar( BATMANspiDEV, &ucPingReturn,
BATMANBlockTime ) != pdTRUE )
{
prvFreeBus();
return pdFALSE;
}
/* Free the spi bus */
prvFreeBus();
/* Check if we received the correct data */
if(~ucPingReturn != BATMANpingData)
return pdFALSE;
return pdTRUE;
}
--------------------------------------------------------------------
spi.c:
--------------------------------------------------------------------
/*
Description: BASIC INTERRUPT DRIVER SPI BUS DRIVER FOR THE
LPC2114.
This file contains all the
SPI driver components that can be
compiled to either ARM or
THUMB mode. Components the must be
compiled to ARM mode are
contained in spiisr.c. This driver
is designed for use with
FreeRTOS v2.5.0.
Date: 18-11-2004
Author: Teun van de Berg
Version: 0.5
Todo: Debugging.
Note: The bus must always be initialized
when calling the iReserveSpiBus function.
There are more of these
logical dependencies, one example is that you should not free the bus
if you haven't reserved it.
Revision history:
0.6 Fixed the reserve and free
bus functions.
0.4A Change SPI so that it sends
dummy date when trying to read.
Some comment updates.
0.4 Todo comment update.
0.3 Changed the vFreeSpiBus so
that the calling task will block until the bus is really free.
0.2 Resolved some signed /
unsigned inconsistencies.
0.1 Added the functionality to
synchronize the bus over different
tasks.
*/
/* Standard includes. */
#include <stdlib.h>
/* Scheduler includes. */
#include "projdefs.h"
#include "portable.h"
#include "queue.h"
#include "task.h"
#include "semphr.h"
/* Demo application includes. */
#include "spi.h"
#include "interrupt_priority.h"
/* Constants to setup and access the SPI device. */
#define spiINTERRUPT_ENABLE ( ( unsigned
portCHAR ) 0x80 )
#define spiINTERRUPT_NOT_ENABLE ( ( unsigned
portCHAR ) 0x00 )
#define spiDUMMY_DATA ( ( unsigned
portCHAR ) 0x00 )
/* Constants to setup and access the VIC. */
#define spiDEV0_VIC_CHANNEL ( ( unsigned
portLONG ) 0x000A )
#define spiDEV0_VIC_CHANNEL_BIT ( ( unsigned portLONG )
0x0400 )
#define spiDEV1_VIC_CHANNEL ( ( unsigned
portLONG ) 0x000B )
#define spiDEV1_VIC_CHANNEL_BIT ( ( unsigned portLONG )
0x0800 )
#define spiVIC_ENABLE ( ( unsigned
portLONG ) 0x0020 )
#define spiCLEAR_VIC_INTERRUPT ( ( unsigned portLONG ) 0 )
/*-----------------------------------------------------------*/
/* Constants to setup and reset the UART. */
#define SPI_SPCCR_RESET ( ( unsigned
portCHAR ) 0x00)
#define SPI_SPCR_RESET ( ( unsigned
portCHAR ) 0x00)
#define SPI_DATA_RESET ( ( unsigned
portCHAR ) 0x00)
#define SPI_INT_RESET ( ( unsigned
portCHAR ) 0x01)
/* Constants to setup and reset the VIC. */
#define SPIDEV_VIC_A_AND_C_RESET ( ( unsigned
portLONG ) 0x0000 )
/*-----------------------------------------------------------*/
/* Queues used to hold received characters, and characters waiting to
be
transmitted. */
static xQueueHandle xRxedChars_SPIDEV0;
static xQueueHandle xCharsForTx_SPIDEV0;
static xQueueHandle xRxedChars_SPIDEV1;
static xQueueHandle xCharsForTx_SPIDEV1;
static xSemaphoreHandle xBusFreeSemaphore_SPIDEV0;
static xSemaphoreHandle xBusFreeSemaphore_SPIDEV1;
/* Variables to synchronize the SPI bus between the different tasks */
/* static */ unsigned portCHAR busAVAILABLE_SPIDEV0; //Indicates that
the buffers are empty and that the bus can be reserved.
/* static */ unsigned portCHAR busRESERVED_SPIDEV0; //Indicates that
the bus is currently reserved by a task.
/* static */ unsigned portCHAR busAVAILABLE_SPIDEV1; //Indicates that
the buffers are empty and that the bus can be reserved.
/* static */ unsigned portCHAR busRESERVED_SPIDEV1; //Indicates that
the bus is currently reserved by a task.
#define spiNO_BLOCK ( (
portTickType ) 0 )
/*-----------------------------------------------------------*/
extern void vSpiISRCreateQueues_SPIDEVx( xSpiHandle pxSPIDEV,
unsigned portCHAR ucQueueLength, xQueueHandle *pxRxedChars_SPIDEVx,
xQueueHandle *pxCharsForTx_SPIDEVx, portLONG volatile
**pplDataRegEmptyFlag_SPIDEVx, xSemaphoreHandle
*pxBusFreeSemaphore_SPIDEVx );
/*-----------------------------------------------------------*/
/* Communication flag between the interrupt service routine and
serial API. */
static volatile portLONG *plDataRegEmptyFlag_SPIDEV0;
static volatile portLONG *plDataRegEmptyFlag_SPIDEV1;
extern /* static */ unsigned portCHAR isrMode_SPIDEV0;
extern /* static */ unsigned portCHAR isrMode_SPIDEV1;
/*-----------------------------------------------------------*/
xSpiHandle xSpiInit( xSpiHandle spiDEV, unsigned portCHAR
cClockPhase, unsigned portCHAR cClockParity, unsigned portCHAR
cMasterMode, unsigned portCHAR cWhoisSignificantByte, unsigned
portLONG ulWantedClock, unsigned portCHAR ucQueueLength )
{
unsigned portCHAR ucClockCount;
xSpiHandle xReturn;
extern void ( vSPI_ISR_SPIDEV0 )( void );
extern void ( vSPI_ISR_SPIDEV1 )( void );
switch( (int)spiDEV )
{
case (int)spiDEV0 : /* init SPI dev 0 */
/* The queues are used in the serial ISR
routine, so are created from
serialISR.c (which is always compiled to ARM
mode. */
vSpiISRCreateQueues_SPIDEVx( (xSpiHandle)
spiDEV0, ucQueueLength, &xRxedChars_SPIDEV0, &xCharsForTx_SPIDEV0,
&plDataRegEmptyFlag_SPIDEV0, &xBusFreeSemaphore_SPIDEV0 );
/* Calculate the Clock Counter value */
ucClockCount = (portCHAR)(portCPU_CLOCK_HZ /
ulWantedClock);
ucClockCount &= 0xFE;
/* Initialize the flags for intertask
synchronization */
busAVAILABLE_SPIDEV0 = 1;
busRESERVED_SPIDEV0 = 0;
/* Initialize the flag for synchronization
with the ISR */
isrMode_SPIDEV0 = spiModeIDLE;
if(
( xRxedChars_SPIDEV0 !=
spiINVALID_QUEUE ) &&
( xCharsForTx_SPIDEV0 !=
spiINVALID_QUEUE ) &&
( ucClockCount >= (portCHAR)8 ) &&
( ucClockCount % (portCHAR)2 != 1)
)
{
portENTER_CRITICAL();
{
/* Setup the bitrate. */
SPI0_SPCCR =
ucClockCount; //Correct ?
/* Setup the control
register. */
SPI0_SPCR = cClockPhase |
cMasterMode | cClockParity | cWhoisSignificantByte |
spiINTERRUPT_ENABLE;
/* Setup the VIC for the SPI.
*/
VICIntSelect &= ~(
spiDEV0_VIC_CHANNEL_BIT );
VICIntEnable |=
spiDEV0_VIC_CHANNEL_BIT;
SPIDEV0_INTERRUPT_VICvectAddr
= ( portLONG ) vSPI_ISR_SPIDEV0;
SPIDEV0_INTERRUPT_VICvectCntl
= spiDEV0_VIC_CHANNEL | spiVIC_ENABLE;
}
portEXIT_CRITICAL();
xReturn = ( xSpiHandle ) spiDEV0;
}
else
{
xReturn = ( xSpiHandle ) spiFAULT;
}
break;
case (int)spiDEV1 : /* init SPI dev 1 */
/* The queues are used in the serial ISR
routine, so are created from
serialISR.c (which is always compiled to ARM
mode. */
vSpiISRCreateQueues_SPIDEVx( (xSpiHandle)
spiDEV1, ucQueueLength, &xRxedChars_SPIDEV1, &xCharsForTx_SPIDEV1,
&plDataRegEmptyFlag_SPIDEV1, &xBusFreeSemaphore_SPIDEV1 );
if(
( xRxedChars_SPIDEV1 !=
spiINVALID_QUEUE ) &&
( xCharsForTx_SPIDEV1 !=
spiINVALID_QUEUE ) &&
( ucClockCount >= (portCHAR)8 ) &&
( ucClockCount % (portCHAR)2 != 1)
)
{
portENTER_CRITICAL();
{
/* Setup the bitrate. */
SPI1_SPCCR =
ucClockCount; //Correct ?
/* Setup the control
register. */
SPI1_SPCR = cClockPhase |
cMasterMode | cClockParity | cWhoisSignificantByte |
spiINTERRUPT_ENABLE;
/* Setup the VIC for the SPI.
*/
VICIntSelect &= ~(
spiDEV1_VIC_CHANNEL_BIT );
VICIntEnable |=
spiDEV1_VIC_CHANNEL_BIT;
SPIDEV1_INTERRUPT_VICvectAddr
= ( portLONG ) vSPI_ISR_SPIDEV1;
SPIDEV1_INTERRUPT_VICvectCntl
= spiDEV1_VIC_CHANNEL | spiVIC_ENABLE;
}
portEXIT_CRITICAL();
xReturn = ( xSpiHandle ) spiDEV1;
}
else
{
xReturn = ( xSpiHandle ) spiFAULT;
}
break;
default: /* If a non existing SPI device is selecter
*/
xReturn = ( xSpiHandle ) spiFAULT;
break;
}
return xReturn;
}
/*-----------------------------------------------------------*/
/** Function that reads a char from the selected SPI device(s
buffer).
* This function blocks the task for xBlockTime if no char is received.
*/
unsigned portCHAR cSpiGetChar( xSpiHandle pxSPIDEV, unsigned portCHAR
*pcRxedChar, portTickType xBlockTime )
{
xQueueHandle xRxedChars_SPIDEVx;
static unsigned portCHAR *pucIsrMode_SPIDEVx;
/* Select the appropriate queue for this SPIDEV. */
switch( (int)pxSPIDEV )
{
case spiDEV0 : /* Select the SPIDEV0 queue
for SPIDEV0 */
xRxedChars_SPIDEVx = xRxedChars_SPIDEV0;
pucIsrMode_SPIDEVx = &isrMode_SPIDEV0;
break;
case spiDEV1 : /* Select the SPIDEV1 queue
for SPIDEV1 */
xRxedChars_SPIDEVx = xRxedChars_SPIDEV1;
pucIsrMode_SPIDEVx = &isrMode_SPIDEV1;
break;
default : /* If a different
SPIDEV was selected then return error */
return (
unsigned portCHAR ) pdFALSE;
}
/* Wait until no write is active */
while( *pucIsrMode_SPIDEVx != spiModeIDLE )
vTaskDelay( xBlockTime );
/* Setup the ISR so that the interrupt will cause a read of
the SPDR */
*pucIsrMode_SPIDEVx = spiModeREAD;
/* Drive the clock */
vWriteSPIDEVx_DATA( pxSPIDEV, spiDUMMY_DATA );
/* Get the next character from the buffer. Return false if
no characters
are available, or arrive before xBlockTime expires. */
if( cQueueReceive( xRxedChars_SPIDEVx, pcRxedChar,
xBlockTime ) )
{
return ( unsigned portCHAR ) pdTRUE;
}
else
{
return ( unsigned portCHAR ) pdFALSE;
}
}
/*-----------------------------------------------------------*/
/** Function that writes a char to the selected UART(s buffer).
* This function blocks the task for xBlockTime if there's no space in
the buffer.
*/
unsigned portCHAR cSpiPutChar( xSpiHandle pxSPIDEV, unsigned portCHAR
cOutChar, portTickType xBlockTime )
{
unsigned portCHAR cReturn;
volatile portLONG *plDataRegEmptyFlag_SPIDEVx;
xQueueHandle xRxedChars_SPIDEVx;
xQueueHandle xCharsForTx_SPIDEVx;
static unsigned portCHAR *pucIsrMode_SPIDEVx;
/* Select the appropriate queue, handles, etc for this
SPIDEV. */
switch( (int)pxSPIDEV )
{
case spiDEV0 : /* Select the SPIDEV0 queue
for SPIDEV0 */
xRxedChars_SPIDEVx = xRxedChars_SPIDEV0;
xCharsForTx_SPIDEVx = xCharsForTx_SPIDEV0;
plDataRegEmptyFlag_SPIDEVx = plDataRegEmptyFlag_SPIDEV0;
pucIsrMode_SPIDEVx = &isrMode_SPIDEV0;
break;
case spiDEV1 : /* Select the SPIDEV1 queue
for SPIDEV1 */
xRxedChars_SPIDEVx = xRxedChars_SPIDEV1;
xCharsForTx_SPIDEVx = xCharsForTx_SPIDEV1;
plDataRegEmptyFlag_SPIDEVx = plDataRegEmptyFlag_SPIDEV1;
pucIsrMode_SPIDEVx = &isrMode_SPIDEV1;
break;
default : /* If a different
SPIDEV was selected then return error */
return (
signed portCHAR ) pdFALSE;
}
/* Wait until no read is active */
while( *pucIsrMode_SPIDEVx == spiModeREAD )
vTaskDelay( xBlockTime );
portENTER_CRITICAL();
{
/* Is there space to write directly to the SPI
device? */
if( *plDataRegEmptyFlag_SPIDEVx == ( portLONG )
pdTRUE )
{
/* We wrote the character directly to the SPI
device, so was
successful. */
*plDataRegEmptyFlag_SPIDEVx = pdFALSE;
*pucIsrMode_SPIDEVx = spiModeWRITE;
vWriteSPIDEVx_DATA( pxSPIDEV, cOutChar );
cReturn = ( unsigned portCHAR ) pdPASS;
}
else
{
/* We cannot write directly to the SPI
device, so queue the character.
Block for a maximum of xBlockTime if there is
no space in the
queue. */
cReturn = cQueueSend( xCharsForTx_SPIDEVx,
&cOutChar, xBlockTime );
/* Depending on queue sizing and task
prioritisation: While we
were blocked waiting to post interrupts were
not disabled. It is
possible that the serial ISR has emptied the
Tx queue, in which
case we need to start the Tx off again. */
if( *plDataRegEmptyFlag_SPIDEVx == (
portLONG ) pdTRUE )
{
cQueueReceive( xCharsForTx_SPIDEVx,
&cOutChar, spiNO_BLOCK );
*plDataRegEmptyFlag_SPIDEVx = pdFALSE;
*pucIsrMode_SPIDEVx = spiModeWRITE;
vWriteSPIDEVx_DATA( pxSPIDEV,
cOutChar );
}
}
}
portEXIT_CRITICAL();
return cReturn;
}
/*-----------------------------------------------------------*/
/** Function that writes to a SPI device's DATA register. */
inline void vWriteSPIDEVx_DATA( xSpiHandle spiDEV, unsigned portCHAR
cToWrite )
{
switch( (int)spiDEV )
{
case spiDEV0 : /* Write to SPIDEV0_DATA. */
SPI0_SPDR =
cToWrite;
break;
case spiDEV1 : /* Write to SPIDEV1_DATA. */
SPI1_SPDR =
cToWrite;
break;
default : /* Don't do anyting
since no valid UART is selected. */
break;
}
}
/*-----------------------------------------------------------*/
/** Function that reads from a SPI device's DATA register. */
inline unsigned portCHAR vReadSPIDEVx_DATA( xSpiHandle spiDEV )
{
signed portCHAR cRead = 0;
switch( (int)spiDEV )
{
case spiDEV0 : /* Read from to SPIDEV0_DATA.
*/
cRead =
SPI0_SPDR;
break;
case spiDEV1 : /* Read from to SPIDEV1_DATA.
*/
cRead =
SPI1_SPDR;
break;
default : /* Don't do anyting
since no valid UART is selected. */
break;
}
return cRead;
}
/*-----------------------------------------------------------*/
/** Waits until the last char from the buffer is send.
* Then resets all the SPI hardware and related ISR registers.
* Finally destroys the queues and makes the pointers invalid.
*/
void vSpiClose( xSpiHandle spiDEV )
{
xQueueHandle xRxedChars_SPIDEVx;
xQueueHandle xCharsForTx_SPIDEVx;
/* Select the appropriate queues, handles, etc for this
SPIDEV. */
switch( (int)spiDEV )
{
case spiDEV0 : /* Select the SPIDEV0 queues
for SPIDEV0 */
xRxedChars_SPIDEVx = xRxedChars_SPIDEV0;
xCharsForTx_SPIDEVx = xCharsForTx_SPIDEV0;
break;
case spiDEV1 : /* Select the SPIDEV1 queues
for SPIDEV1 */
xRxedChars_SPIDEVx = xRxedChars_SPIDEV1;
xCharsForTx_SPIDEVx = xCharsForTx_SPIDEV1;
break;
default : /* If a different SPI
device was selected then do nothing */
return;
}
/* Wait until the send queue is empty. */
/* We do not need to wait until the receive queue is empty
because the application cannot and should not read from the SPI
device once it is closed anyway. */
while( ucQueueMessagesWaiting( xRxedChars_SPIDEVx ) )
vTaskDelay( ( portTickType ) 0x32 );
/* Reset all registers set for the selected SPI device */
switch( (int)spiDEV )
{
case spiDEV0 :
/* Reset all
registers set for SPIDEV0 */
portENTER_CRITICAL();
{
/*
Reset the divisor. */
SPI0_SPCCR = SPI_SPCCR_RESET;
/*
Reset the control register. */
SPI0_SPCR = SPI_SPCR_RESET;
/*
Reset the data register */
SPI0_SPDR = SPI_DATA_RESET;
/*
Clear any pending interrupts */
SPI0_SPINT = SPI_INT_RESET;
/*
Clear the VIC for the UART. */
VICIntSelect &= ~( spiDEV0_VIC_CHANNEL_BIT );
VICIntEnClear |= spiDEV0_VIC_CHANNEL_BIT;
SPIDEV0_INTERRUPT_VICvectAddr = SPIDEV_VIC_A_AND_C_RESET;
SPIDEV0_INTERRUPT_VICvectCntl = SPIDEV_VIC_A_AND_C_RESET;
}
portEXIT_CRITICAL();
break;
case spiDEV1 :
/* Reset all
registers set for SPIDEV0 */
portENTER_CRITICAL();
{
/*
Reset the divisor. */
SPI1_SPCCR = SPI_SPCCR_RESET;
/*
Reset the control register. */
SPI1_SPCR = SPI_SPCR_RESET;
/*
Reset the data register */
SPI1_SPDR = SPI_DATA_RESET;
/*
Clear any pending interrupts */
SPI1_SPINT = SPI_INT_RESET;
/*
Clear the VIC for the UART. */
VICIntSelect &= ~( spiDEV1_VIC_CHANNEL_BIT );
VICIntEnClear |= spiDEV1_VIC_CHANNEL_BIT;
SPIDEV1_INTERRUPT_VICvectAddr = SPIDEV_VIC_A_AND_C_RESET;
SPIDEV1_INTERRUPT_VICvectCntl = SPIDEV_VIC_A_AND_C_RESET;
}
portEXIT_CRITICAL();
break;
}
/* Delete the queues */
vQueueDelete( xRxedChars_SPIDEVx );
vQueueDelete( xCharsForTx_SPIDEVx );
/* Make the queue handles invalid. */
switch( (int)spiDEV )
{
case spiDEV0 : /* Make the queue handles for
SPIDEV0 invalid. */
xRxedChars_SPIDEV0 = spiINVALID_QUEUE;
xCharsForTx_SPIDEV0 = spiINVALID_QUEUE;
break;
case spiDEV1 : /* Make the queue handles for
SPIDEV1 invalid. */
xRxedChars_SPIDEV1 = spiINVALID_QUEUE;
xCharsForTx_SPIDEV1 = spiINVALID_QUEUE;
break;
}
}
/*-----------------------------------------------------------*/
/** Function that reserves the SPI bus if it is available.
* Returns: pdTRUE on success or pdFALSE on failure.
*/
signed portCHAR cReserveSpiBus( xSpiHandle spiDEV )
{
signed portCHAR returnValue;
switch( (int)spiDEV )
{
case spiDEV0 : /* Prevent context
switches from occurring while we are reserving the bus.
This in order
to prevent 2 different tasks from reserving the bus
simultaneously. */
portENTER_CRITICAL();
{
if
(busAVAILABLE_SPIDEV0 == pdTRUE)
{
busAVAILABLE_SPIDEV0 = pdFALSE;
busRESERVED_SPIDEV0 = pdTRUE;
returnValue = ( signed portCHAR ) pdTRUE;
}
else
{
returnValue = ( signed portCHAR ) pdFALSE;
}
}
portEXIT_CRITICAL();
break;
case spiDEV1 : /* Prevent context
switches from occurring while we are reserving the bus.
This in order
to prevent 2 different tasks from reserving the bus
simultaneously. */
portENTER_CRITICAL();
{
if
(busAVAILABLE_SPIDEV1 == pdTRUE)
{
busAVAILABLE_SPIDEV1 = pdFALSE;
busRESERVED_SPIDEV1 = pdTRUE;
returnValue = ( signed portCHAR ) pdTRUE;
}
else
{
returnValue = ( signed portCHAR ) pdFALSE;
}
}
portEXIT_CRITICAL();
break;
default : /* If a wrong SPI
device is selected */
returnValue
= ( signed portCHAR ) pdFALSE;
break;
}
return returnValue;
}
/*-----------------------------------------------------------*/
/** Function that frees the SPI bus. */
void vFreeSpiBus( xSpiHandle spiDEV, portTickType xBlockTime )
{
switch( (int)spiDEV )
{
case spiDEV0 : /* Prevent context
switches from occurring while we are freeing the bus.*/
portENTER_CRITICAL();
{
/*
Release our reservation of the bus */
busRESERVED_SPIDEV0 = pdFALSE;
/* If
there are no more messages waiting in the queue the ISR might not be
called again
and
thus we need to set the bus to AVAILABLE here */
if(
ucQueueMessagesWaiting( xBusFreeSemaphore_SPIDEV0 ) == 0 );
busAVAILABLE_SPIDEV0 = pdTRUE;
}
portEXIT_CRITICAL();
cSemaphoreTake
( xBusFreeSemaphore_SPIDEV0, xBlockTime ); //Wait until the bus is
really free.
break;
case spiDEV1 : /* Prevent context
switches from occurring while we are freeing the bus.
Just set it
to be unreserved and it will become available again from the ISR */
portENTER_CRITICAL();
{
/*
Release our reservation of the bus */
busRESERVED_SPIDEV1 = pdFALSE;
/* If
there are no more messages waiting in the queue the ISR might not be
called again
and
thus we need to set the bus to AVAILABLE here */
if(
ucQueueMessagesWaiting( xBusFreeSemaphore_SPIDEV1 ) == 0 );
busAVAILABLE_SPIDEV1 = pdTRUE;
}
portEXIT_CRITICAL();
cSemaphoreTake
( xBusFreeSemaphore_SPIDEV1, xBlockTime ); //Wait until the bus is
really free.
break;
default : /* If a wrong SPI
device is selected */
break;
}
}
--------------------------------------------------------------------
spi.h:
--------------------------------------------------------------------
/*
Description: BASIC INTERRUPT DRIVEN SPI BUS DRIVER FOR THE
LPC2114.
This driver is designed for
use with FreeRTOS v2.5.0.
Date: 6-12-2004
Author: Teun van de Berg
Version: 0.6
Todo: Debugging.
Revision history:
0.5 Changed spiMSB_FISRT to
spiMSB_FIRST
0.4 Added the I/O enable for SPI
DEV 1.
0.3 Changes to support
vFreeSpiBus blocking (interrupt driven) until the bus is really free.
0.2 Resolved some signed /
unsigned inconsistencies.
0.1 Added the functionality to
synchronize the bus over different
tasks.
*/
#ifndef SPI_H
#define SPI_H
typedef void * xSpiHandle;
typedef enum
{
spiFAULT,
spiDEV0,
spiDEV1
} eSpiDEV;
xSpiHandle xSpiInit( xSpiHandle spiDEV, unsigned portCHAR
cClockPhase, unsigned portCHAR cClockParity, unsigned portCHAR
cMasterMode, unsigned portCHAR cWhoisSignificantByte, unsigned
portLONG ulWantedClock, unsigned portCHAR ucQueueLength );
unsigned portCHAR cSpiGetChar( xSpiHandle pxSPIDEV, unsigned portCHAR
*pcRxedChar, portTickType xBlockTime );
unsigned portCHAR cSpiPutChar( xSpiHandle pxSPIDEV, unsigned portCHAR
cOutChar, portTickType xBlockTime );
inline void vWriteSPIDEVx_DATA( xSpiHandle spiDEV, unsigned portCHAR
cToWrite );
inline unsigned portCHAR vReadSPIDEVx_DATA( xSpiHandle spiDEV );
void vSpiClose( xSpiHandle spiDEV );
signed portCHAR cReserveSpiBus( xSpiHandle spiDEV );
void vFreeSpiBus( xSpiHandle spiDEV, portTickType xBlockTime );
/* Constants to setup and access the SPI device. */
#define spiCLOCK_PHASE_ON_FIST_EDGE ( ( unsigned
portCHAR ) 0x00 )
#define spiCLOCK_PHASE_ON_SECOND_EDGE ( ( unsigned portCHAR ) 0x08 )
#define spiCLOCK_ACTIVE_LOW ( ( unsigned
portCHAR ) 0x10 )
#define spiCLOCK_ACTIVE_HIGH ( ( unsigned
portCHAR ) 0x00 )
#define spiMASTER_MODE ( ( unsigned
portCHAR ) 0x20 )
#define spiSLAVE_MODE ( ( unsigned
portCHAR ) 0x00 )
#define spiLSB_FIRST ( ( unsigned
portCHAR ) 0x40 )
#define spiMSB_FIRST ( ( unsigned
portCHAR ) 0x00 )
/* Constants to use the queues. */
#define spiINVALID_QUEUE ( (
xQueueHandle ) 0 )
/* Constants to setup I/O. */
#define SCK_ENABLE_SPIDEV0 ( ( unsigned portLONG )
0x100 ) //PINSEL0 REG
#define MISO_ENABLE_SPIDEV0 ( ( unsigned portLONG ) 0x400 )
#define MOSI_ENABLE_SPIDEV0 ( ( unsigned portLONG ) 0x1000 )
#define SSEL_ENABLE_SPIDEV0 ( ( unsigned portLONG ) 0x4000 )
#define SCK_ENABLE_SPIDEV1 ( ( unsigned portLONG )
0x0004 ) //PINSEL1 REG
#define MISO_ENABLE_SPIDEV1 ( ( unsigned portLONG ) 0x0010 )
#define MOSI_ENABLE_SPIDEV1 ( ( unsigned portLONG ) 0x0040 )
#define SSEL_ENABLE_SPIDEV1 ( ( unsigned portLONG ) 0x0100 )
/* Constants to setup the ISR internal status */
#define spiModeREAD ( (
unsigned portCHAR ) 0 )
#define spiModeWRITE ( ( unsigned
portCHAR ) 1 )
#define spiModeIDLE ( (
unsigned portCHAR ) 2 )
#define spiNO_BLOCK ( (
portTickType ) 0 )
#endif
--------------------------------------------------------------------
spiisr.c:
--------------------------------------------------------------------
/*
Description: BASIC INTERRUPT DRIVEN SPI BUS DRIVER FOR THE
LPC2114.
This file contains all the
SPI driver components that must be
compiled to ARM mode.
Components that can be compiled to either
ARM or THUMB mode are
contained in spi.c. This driver is
designed for use with
FreeRTOS v2.5.0.
Date: 24-11-2004
Author: Teun van de Berg
Version: 0.6
Todo: Debugging.
Add LED error code.
Revision history:
0.5 Fixed some compiler warnings.
0.4 Change SPI so that it sends
dummy date when trying to read.
0.3 Fixed the way the interrupts
are handled and cleared.
0.2 Changes to support
vFreeSpiBus blocking (interrupt driven) until the bus is really free.
0.1 Added the functionality to
synchronize the bus over different
tasks.
Filled the ISRs.
*/
/* Standard includes. */
#include <stdlib.h>
/* Scheduler includes. */
#include "projdefs.h"
#include "portable.h"
#include "queue.h"
#include "task.h"
#include "semphr.h"
/* Demo application includes. */
#include "spi.h"
/* Constant to access the VIC. */
#define spiCLEAR_VIC_INTERRUPT ( ( unsigned
portCHAR ) 0x01 )
/* Constants to setup and access the SPI device. */
#define spiCLEAR_INTERRUPT /*( (
unsigned portLONG )*/ 0x01//)
#define spiRX_BUFFER_LENGTH ( ( unsigned
portCHAR ) 0x01 )
/* Constants to determine the ISR source. */
#define spiSOURCE_ABRT ( ( unsigned
portCHAR ) 0x08 )
#define spiSOURCE_MODF ( ( unsigned
portCHAR ) 0x10 )
#define spiSOURCE_ROVR ( ( unsigned
portCHAR ) 0x20 )
#define spiSOURCE_WCOL ( ( unsigned
portCHAR ) 0x40 )
#define spiSOURCE_SPIF ( ( unsigned
portCHAR ) 0x80 )
/* Queues used to hold received characters, and characters waiting to
be
transmitted. */
static xQueueHandle xRxedChars_SPIDEV0;
static xQueueHandle xCharsForTx_SPIDEV0;
static xQueueHandle xRxedChars_SPIDEV1;
static xQueueHandle xCharsForTx_SPIDEV1;
static xSemaphoreHandle xBusFreeSemaphore_SPIDEV0;
static xSemaphoreHandle xBusFreeSemaphore_SPIDEV1;
/* Variables to synchronize the SPI bus between the different tasks */
extern /* static */ unsigned portCHAR
busAVAILABLE_SPIDEV0; //Indicates that the buffers are empty and that
the bus can be reserved.
extern /* static */ unsigned portCHAR
busRESERVED_SPIDEV0; //Indicates that the bus is currently reserved
by a task.
extern /* static */ unsigned portCHAR
busAVAILABLE_SPIDEV1; //Indicates that the buffers are empty and that
the bus can be reserved.
extern /* static */ unsigned portCHAR
busRESERVED_SPIDEV1; //Indicates that the bus is currently reserved
by a task.
/* static */ unsigned portCHAR isrMode_SPIDEV0;
/* static */ unsigned portCHAR isrMode_SPIDEV1;
/*-----------------------------------------------------------*/
/* Communication flag between the interrupt service routine and
serial API. */
static volatile portLONG lDataRegEmptyFlag_SPIDEV0;
static volatile portLONG lDataRegEmptyFlag_SPIDEV1;
/*-----------------------------------------------------------*/
/* UART interrupt service routines. These can cause a context switch
so MUST
be declared "naked". */
void vSPI_ISR_SPIDEV0( void ) __attribute__ ((naked));
void vSPI_ISR_SPIDEV1( void ) __attribute__ ((naked));
/*-----------------------------------------------------------*/
/** Function that creates the queues and passes back a reference to
them. */
void vSpiISRCreateQueues_SPIDEVx( xSpiHandle pxSPIDEV, unsigned
portCHAR ucQueueLength, xQueueHandle *pxRxedChars_SPIDEVx,
xQueueHandle *pxCharsForTx_SPIDEVx, portLONG volatile
**pplDataRegEmptyFlag_SPIDEVx, xSemaphoreHandle
*pxBusFreeSemaphore_SPIDEVx )
{
xQueueHandle xRxedChars_SPIDEVx;
xQueueHandle xCharsForTx_SPIDEVx;
xSemaphoreHandle xBusFreeSemaphore_SPIDEVx;
/* Error handling if a bad DEV is selected */
if( ( (int)pxSPIDEV != (int)spiDEV0 ) && ( (int)pxSPIDEV !=
(int)spiDEV1 ) )
{
*pxRxedChars_SPIDEVx = spiINVALID_QUEUE;
*pxCharsForTx_SPIDEVx = spiINVALID_QUEUE;
return;
}
/* Create the queues used to hold Rx and Tx characters. */
xRxedChars_SPIDEVx = xQueueCreate( spiRX_BUFFER_LENGTH, (
unsigned portCHAR ) sizeof( signed portCHAR ) );
xCharsForTx_SPIDEVx = xQueueCreate( ucQueueLength + 1, (
unsigned portCHAR ) sizeof( signed portCHAR ) );
vSemaphoreCreateBinary( xBusFreeSemaphore_SPIDEVx );
/* Pass back a reference to the queues so the serial API file
can
post/receive characters. */
*pxRxedChars_SPIDEVx = xRxedChars_SPIDEVx;
*pxCharsForTx_SPIDEVx = xCharsForTx_SPIDEVx;
*pxBusFreeSemaphore_SPIDEVx =xBusFreeSemaphore_SPIDEVx;
/* Store the pointers to the queues & THRE in the appropriate
globals. */
switch( (int)pxSPIDEV )
{
case spiDEV0 : /* Set the UART0 queue for
UART0 */
xRxedChars_SPIDEV0 = xRxedChars_SPIDEVx;
xCharsForTx_SPIDEV0 = xCharsForTx_SPIDEVx;
/* Initialise
the THRE empty flag - and pass back a reference. */
lDataRegEmptyFlag_SPIDEV0 = ( portLONG ) pdTRUE;
*pplDataRegEmptyFlag_SPIDEVx = &lDataRegEmptyFlag_SPIDEV0;
/* Set the
local semaphore */
xBusFreeSemaphore_SPIDEV0 = xBusFreeSemaphore_SPIDEVx;
break;
case spiDEV1 : /* Set the UART1 queues for
UART1 */
xRxedChars_SPIDEV1 = xRxedChars_SPIDEVx;
xCharsForTx_SPIDEV1 = xCharsForTx_SPIDEVx;
/* Initialise
the THRE empty flag - and pass back a reference. */
lDataRegEmptyFlag_SPIDEV1 = ( portLONG ) pdTRUE;
*pplDataRegEmptyFlag_SPIDEVx = &lDataRegEmptyFlag_SPIDEV1;
/* Set the
local semaphore */
xBusFreeSemaphore_SPIDEV1 = xBusFreeSemaphore_SPIDEVx;
break;
}
}
/*-----------------------------------------------------------*/
/** Function that handles the SPI0 interrupts.
* See the comment in the code for more detailed information.
*/
void vSPI_ISR_SPIDEV0( void )
{
/* This ISR can cause a context switch, so the first
statement must be a
call to the portENTER_SWITCHING_ISR() macro. This must be
BEFORE any
variable declarations. */
portENTER_SWITCHING_ISR();
/* Now we can declare the local variables. */
signed portCHAR cTaskWokenByTx = ( signed portCHAR ) pdFALSE;
portLONG lTaskWokenByRx = ( portLONG ) pdFALSE;
unsigned portCHAR cChar;
unsigned portLONG lLocalSPI0_SPSR = SPI0_SPSR; //Some data in
this reg is cleared on read but we use it multiple times.
/* Just to stop compiler warnings. */
( void ) lLocalSPI0_SPSR;
/* Clear the interrupt flag */
SPI0_SPINT = spiCLEAR_VIC_INTERRUPT;
/* Clear the ISR in the VIC. */
VICVectAddr = spiCLEAR_VIC_INTERRUPT;
/* All the error below should actually be handled of course
but we don't check these because there is a bug in the chip.
*/
// /* Handle all the interrupts */
// if( ( lLocalSPI0_SPSR & spiSOURCE_ABRT ) == spiSOURCE_ABRT )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI0_SPSR & spiSOURCE_MODF ) == spiSOURCE_MODF )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI0_SPSR & spiSOURCE_ROVR ) == spiSOURCE_ROVR )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI0_SPSR & spiSOURCE_WCOL ) == spiSOURCE_WCOL )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI0_SPSR & spiSOURCE_SPIF ) == spiSOURCE_SPIF )
// {
switch( isrMode_SPIDEV0 )
{
case spiModeWRITE :
/* If the transmission queue is empty than
the next interrupt
must be caused by a character being
received unless a new
write was defined before. */
if (cQueueReceiveFromISR(
xCharsForTx_SPIDEV0, &cChar, &cTaskWokenByTx ) == ( signed portCHAR )
pdFALSE )
{
/* There are no more
characters */
isrMode_SPIDEV0 = spiModeIDLE;
lDataRegEmptyFlag_SPIDEV0 =
pdTRUE;
if(busRESERVED_SPIDEV0 ==
pdFALSE)
{
busAVAILABLE_SPIDEV0
= pdTRUE;
cSemaphoreGiveFromISR
( xBusFreeSemaphore_SPIDEV0, pdFALSE );
lTaskWokenByRx = (
portLONG ) pdTRUE;
//We do not use the SPI data register
here but we just read it because
//this is required to clear the interrupt.
{
int temp;
temp = (int)SPI0_SPDR;
}
}
}
else
{
/* Send the next character */
SPI0_SPDR = cChar;
}
break;
case spiModeREAD :
{
/* Read the character */
cChar = SPI0_SPDR;
cQueueSendFromISR(
xRxedChars_SPIDEV0, ( void * ) &cChar, pdFALSE );
}
lTaskWokenByRx = ( portLONG ) pdTRUE;
isrMode_SPIDEV0 = spiModeIDLE;
break;
default :
/* You should never reach here */
break;
}
// }
/* Exit the ISR. If a task was woken by either a character
being received
or transmitted then a context switch will occur. */
portEXIT_SWITCHING_ISR( ( cTaskWokenByTx ||
lTaskWokenByRx ) );
}
/*-----------------------------------------------------------*/
/** Function that handles the SPI0 interrupts.
* See the comment in the code for more detailed information.
*/
void vSPI_ISR_SPIDEV1( void )
{
/* This ISR can cause a context switch, so the first
statement must be a
call to the portENTER_SWITCHING_ISR() macro. This must be
BEFORE any
variable declarations. */
portENTER_SWITCHING_ISR();
/* Now we can declare the local variables. */
signed portCHAR cTaskWokenByTx = ( signed portCHAR ) pdFALSE;
portLONG lTaskWokenByRx = ( portLONG ) pdFALSE;
unsigned portCHAR cChar;
unsigned portLONG lLocalSPI1_SPSR = SPI1_SPSR; //Some data in
this reg is cleared on read but we use it multiple times.
/* Just to stop compiler warnings. */
( void ) lLocalSPI1_SPSR;
/* All the error below should actually be handled of course
but we don't check these because there is a bug in the chip.
*/
// /* Handle all the interrupts */
// if( ( lLocalSPI1_SPSR & spiSOURCE_ABRT ) == spiSOURCE_ABRT )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI1_SPSR & spiSOURCE_MODF ) == spiSOURCE_MODF )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI1_SPSR & spiSOURCE_ROVR ) == spiSOURCE_ROVR )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI1_SPSR & spiSOURCE_WCOL ) == spiSOURCE_WCOL )
// {
// // Should not occur.
// // Turn on error LEDs!
// }
//
// /* Handle all the interrupts */
// if( ( lLocalSPI1_SPSR & spiSOURCE_SPIF ) == spiSOURCE_SPIF )
// {
if(isrMode_SPIDEV1 == spiModeWRITE)
{
/* If the transmission queue is empty than
the next interrupt
must be caused by a character being received
unless a new
write was defined before. */
if (cQueueReceiveFromISR(
xCharsForTx_SPIDEV1, &cChar, &cTaskWokenByTx ) == ( signed portCHAR )
pdFALSE )
{
/* There are no more characters */
isrMode_SPIDEV1 = spiModeREAD;
lDataRegEmptyFlag_SPIDEV1 = pdTRUE;
if(busRESERVED_SPIDEV1 == pdFALSE)
{
busAVAILABLE_SPIDEV1 = pdTRUE;
cSemaphoreGiveFromISR(
xBusFreeSemaphore_SPIDEV1, pdFALSE );
lTaskWokenByRx = ( portLONG )
pdTRUE;
//We do not use the SPI data register here but we
just read it because
//this is required to clear the interrupt.
{
int temp;
temp = (int)SPI0_SPDR;
}
}
}
else
{
/* Send the next character */
SPI1_SPDR = cChar;
}
}
else
{
/* recieve char and place it in recieved
queue */
cChar = SPI1_SPDR;
cQueueSendFromISR( xRxedChars_SPIDEV1, ( void
* ) &cChar, pdFALSE );
lTaskWokenByRx = ( portLONG ) pdTRUE;
}
// }
/* Clear the interrupt flag */
SPI1_SPINT = spiCLEAR_VIC_INTERRUPT;
/* Clear the ISR in the VIC. */
VICVectAddr = spiCLEAR_VIC_INTERRUPT;
/* Exit the ISR. If a task was woken by either a character
being received
or transmitted then a context switch will occur. */
portEXIT_SWITCHING_ISR( ( cTaskWokenByTx ||
lTaskWokenByRx ) );
}