339 lines
11 KiB
C
339 lines
11 KiB
C
/**************************************************************************/
|
||
/*!
|
||
@file ssp.c
|
||
@author K. Townsend (microBuilder.eu)
|
||
@date 22 March 2010
|
||
@version 0.10
|
||
|
||
@section DESCRIPTION
|
||
|
||
Generic code for SSP/SPI communications. By default, the SSP block
|
||
is initialised in SPI master mode.
|
||
|
||
@section Example
|
||
|
||
@code
|
||
#include "core/cpu/cpu.h"
|
||
#include "core/ssp/ssp.h"
|
||
...
|
||
cpuInit();
|
||
sspInit(0, sspClockPolarity_High, sspClockPhase_RisingEdge);
|
||
...
|
||
uint8_t request[SSP_FIFOSIZE];
|
||
uint8_t response[SSP_FIFOSIZE];
|
||
|
||
// Send 0x9C to the slave device and wait for a response
|
||
request[0] = 0x80 | 0x1C;
|
||
// Toggle the select pin
|
||
ssp0Select();
|
||
// Send 1 byte from the request buffer
|
||
sspSend(0, (uint8_t *)&request, 1);
|
||
// Receive 1 byte into the response buffer
|
||
sspReceive(0, (uint8_t *)&response, 1);
|
||
// Reset the select pin
|
||
ssp0Deselect();
|
||
// Print the results
|
||
debug_printf("Ox%x ", response[0]);
|
||
@endcode
|
||
|
||
@section LICENSE
|
||
|
||
Software License Agreement (BSD License)
|
||
|
||
Copyright (c) 2010, microBuilder SARL
|
||
All rights reserved.
|
||
|
||
Redistribution and use in source and binary forms, with or without
|
||
modification, are permitted provided that the following conditions are met:
|
||
1. Redistributions of source code must retain the above copyright
|
||
notice, this list of conditions and the following disclaimer.
|
||
2. Redistributions in binary form must reproduce the above copyright
|
||
notice, this list of conditions and the following disclaimer in the
|
||
documentation and/or other materials provided with the distribution.
|
||
3. Neither the name of the copyright holders nor the
|
||
names of its contributors may be used to endorse or promote products
|
||
derived from this software without specific prior written permission.
|
||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
|
||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
*/
|
||
/**************************************************************************/
|
||
#include "ssp.h"
|
||
#include "core/gpio/gpio.h"
|
||
|
||
/* Statistics for all interrupts */
|
||
volatile uint32_t interruptRxStat = 0;
|
||
volatile uint32_t interruptOverRunStat = 0;
|
||
volatile uint32_t interruptRxTimeoutStat = 0;
|
||
|
||
/**************************************************************************/
|
||
/*!
|
||
@brief SSP0 interrupt handler for SPI communication
|
||
|
||
The algorithm is, if RXFIFO is at least half full,
|
||
start receive until it's empty; if TXFIFO is at least
|
||
half empty, start transmit until it's full.
|
||
This will maximize the use of both FIFOs and performance.
|
||
*/
|
||
/**************************************************************************/
|
||
void SSP_IRQHandler (void)
|
||
{
|
||
uint32_t regValue;
|
||
|
||
regValue = SSP_SSP0MIS;
|
||
|
||
/* Check for overrun interrupt */
|
||
if ( regValue & SSP_SSP0MIS_RORMIS_FRMRCVD )
|
||
{
|
||
interruptOverRunStat++;
|
||
SSP_SSP0ICR = SSP_SSP0ICR_RORIC_CLEAR; // Clear interrupt
|
||
}
|
||
|
||
/* Check for timeout interrupt */
|
||
if ( regValue & SSP_SSP0MIS_RTMIS_NOTEMPTY )
|
||
{
|
||
interruptRxTimeoutStat++;
|
||
SSP_SSP0ICR = SSP_SSP0ICR_RTIC_CLEAR; // Clear interrupt
|
||
}
|
||
|
||
/* Check if Rx buffer is at least half-full */
|
||
if ( regValue & SSP_SSP0MIS_RXMIS_HALFFULL )
|
||
{
|
||
// ToDo: Receive until it's empty
|
||
interruptRxStat++;
|
||
}
|
||
return;
|
||
}
|
||
|
||
/**************************************************************************/
|
||
/*!
|
||
@brief Initialises the SSP0 port
|
||
|
||
By default, SSP0 is set to SPI frame-format with 8-bit data. Pin 2.11
|
||
is routed to serve as serial clock (SCK), and SSEL (0.2) is set to
|
||
GPIO to allow manual control of when the SPI port is enabled or
|
||
disabled. Overrun and timeout interrupts are both enabled.
|
||
|
||
@param[in] portNum
|
||
The SPI port to use (0..1)
|
||
@param[in] polarity
|
||
Indicates whether the clock should be held high
|
||
(sspClockPolarity_High) or low (sspClockPolarity_Low)
|
||
when inactive.
|
||
@param[in] phase
|
||
Indicates whether new bits start on the leading
|
||
(sspClockPhase_RisingEdge) or falling
|
||
(sspClockPhase_FallingEdge) edge of clock transitions.
|
||
|
||
@note sspSelect() and sspDeselect() macros have been defined in
|
||
ssp.h to control the SSEL line without having to know the
|
||
specific pin being used.
|
||
*/
|
||
/**************************************************************************/
|
||
void sspInit (uint8_t portNum, sspClockPolarity_t polarity, sspClockPhase_t phase)
|
||
{
|
||
gpioInit();
|
||
|
||
if (portNum == 0)
|
||
{
|
||
/* Reset SSP */
|
||
SCB_PRESETCTRL &= ~SCB_PRESETCTRL_SSP0_MASK;
|
||
SCB_PRESETCTRL |= SCB_PRESETCTRL_SSP0_RESETDISABLED;
|
||
|
||
/* Enable AHB clock to the SSP domain. */
|
||
SCB_SYSAHBCLKCTRL |= (SCB_SYSAHBCLKCTRL_SSP0);
|
||
|
||
/* Divide by 1 (SSPCLKDIV also enables to SSP CLK) */
|
||
SCB_SSP0CLKDIV = SCB_SSP0CLKDIV_DIV1;
|
||
|
||
/* Set P0.8 to SSP MISO */
|
||
IOCON_PIO0_8 &= ~IOCON_PIO0_8_FUNC_MASK;
|
||
IOCON_PIO0_8 |= IOCON_PIO0_8_FUNC_MISO0;
|
||
|
||
/* Set P0.9 to SSP MOSI */
|
||
IOCON_PIO0_9 &= ~IOCON_PIO0_9_FUNC_MASK;
|
||
IOCON_PIO0_9 |= IOCON_PIO0_9_FUNC_MOSI0;
|
||
|
||
/* Set 2.11 to SSP SCK (0.6 and 0.10 can also be used) */
|
||
#ifdef CFG_SSP0_SCKPIN_2_11
|
||
IOCON_SCKLOC = IOCON_SCKLOC_SCKPIN_PIO2_11;
|
||
IOCON_PIO2_11 = IOCON_PIO2_11_FUNC_SCK0;
|
||
#endif
|
||
|
||
/* Set 0.6 to SSP SCK (2.11 and 0.10 can also be used) */
|
||
#ifdef CFG_SSP0_SCKPIN_0_6
|
||
IOCON_SCKLOC = IOCON_SCKLOC_SCKPIN_PIO0_6;
|
||
IOCON_PIO0_6 = IOCON_PIO0_6_FUNC_SCK;
|
||
#endif
|
||
|
||
/* Set P0.2/SSEL to GPIO output and high */
|
||
IOCON_PIO0_2 &= ~IOCON_PIO0_2_FUNC_MASK;
|
||
IOCON_PIO0_2 |= IOCON_PIO0_2_FUNC_GPIO;
|
||
gpioSetDir(SSP0_CSPORT, SSP0_CSPIN, 1);
|
||
gpioSetValue(SSP0_CSPORT, SSP0_CSPIN, 1);
|
||
gpioSetPullup(&IOCON_PIO0_2, gpioPullupMode_Inactive); // Board has external pull-up
|
||
|
||
/* If SSP0CLKDIV = DIV1 -- (PCLK / (CPSDVSR <20> [SCR+1])) = (72,000,000 / (2 x [8 + 1])) = 4.0 MHz */
|
||
uint32_t configReg = ( SSP_SSP0CR0_DSS_8BIT // Data size = 8-bit
|
||
| SSP_SSP0CR0_FRF_SPI // Frame format = SPI
|
||
| SSP_SSP0CR0_SCR_8); // Serial clock rate = 8
|
||
|
||
// Set clock polarity
|
||
if (polarity == sspClockPolarity_High)
|
||
configReg |= SSP_SSP0CR0_CPOL_HIGH; // Clock polarity = High between frames
|
||
else
|
||
configReg &= ~SSP_SSP0CR0_CPOL_MASK; // Clock polarity = Low between frames
|
||
|
||
// Set edge transition
|
||
if (phase == sspClockPhase_FallingEdge)
|
||
configReg |= SSP_SSP0CR0_CPHA_SECOND; // Clock out phase = Trailing edge clock transition
|
||
else
|
||
configReg &= ~SSP_SSP0CR0_CPHA_MASK; // Clock out phase = Leading edge clock transition
|
||
|
||
// Assign config values to SSP0CR0
|
||
SSP_SSP0CR0 = configReg;
|
||
|
||
/* Clock prescale register must be even and at least 2 in master mode */
|
||
SSP_SSP0CPSR = SSP_SSP0CPSR_CPSDVSR_DIV2;
|
||
|
||
/* Clear the Rx FIFO */
|
||
uint8_t i, Dummy=Dummy;
|
||
for ( i = 0; i < SSP_FIFOSIZE; i++ )
|
||
{
|
||
Dummy = SSP_SSP0DR;
|
||
}
|
||
|
||
/* Enable the SSP Interrupt */
|
||
NVIC_EnableIRQ(SSP_IRQn);
|
||
|
||
/* Set SSPINMS registers to enable interrupts
|
||
* enable all error related interrupts */
|
||
SSP_SSP0IMSC = ( SSP_SSP0IMSC_RORIM_ENBL // Enable overrun interrupt
|
||
| SSP_SSP0IMSC_RTIM_ENBL); // Enable timeout interrupt
|
||
|
||
/* Enable device and set it to master mode, no loopback */
|
||
SSP_SSP0CR1 = SSP_SSP0CR1_SSE_ENABLED | SSP_SSP0CR1_MS_MASTER | SSP_SSP0CR1_LBM_NORMAL;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
/**************************************************************************/
|
||
/*!
|
||
@brief Sends a block of data to the SSP0 port
|
||
|
||
@param[in] portNum
|
||
The SPI port to use (0..1)
|
||
@param[in] buf
|
||
Pointer to the data buffer
|
||
@param[in] length
|
||
Block length of the data buffer
|
||
*/
|
||
/**************************************************************************/
|
||
void sspSend (uint8_t portNum, const uint8_t *buf, uint32_t length)
|
||
{
|
||
uint32_t i;
|
||
uint8_t Dummy = Dummy;
|
||
|
||
if (portNum == 0)
|
||
{
|
||
for (i = 0; i < length; i++)
|
||
{
|
||
/* Move on only if NOT busy and TX FIFO not full. */
|
||
while ((SSP_SSP0SR & (SSP_SSP0SR_TNF_NOTFULL | SSP_SSP0SR_BSY_BUSY)) != SSP_SSP0SR_TNF_NOTFULL);
|
||
SSP_SSP0DR = *buf;
|
||
buf++;
|
||
|
||
while ( (SSP_SSP0SR & (SSP_SSP0SR_BSY_BUSY|SSP_SSP0SR_RNE_NOTEMPTY)) != SSP_SSP0SR_RNE_NOTEMPTY );
|
||
/* Whenever a byte is written, MISO FIFO counter increments, Clear FIFO
|
||
on MISO. Otherwise, when SSP0Receive() is called, previous data byte
|
||
is left in the FIFO. */
|
||
Dummy = SSP_SSP0DR;
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
/**************************************************************************/
|
||
/*!
|
||
@brief Receives a block of data from the SSP0 port
|
||
|
||
@param[in] portNum
|
||
The SPI port to use (0..1)
|
||
@param[in] buf
|
||
Pointer to the data buffer
|
||
@param[in] length
|
||
Block length of the data buffer
|
||
*/
|
||
/**************************************************************************/
|
||
void sspReceive(uint8_t portNum, uint8_t *buf, uint32_t length)
|
||
{
|
||
uint32_t i;
|
||
|
||
if (portNum == 0)
|
||
{
|
||
for ( i = 0; i < length; i++ )
|
||
{
|
||
/* As long as the receive FIFO is not empty, data can be received. */
|
||
SSP_SSP0DR = 0xFF;
|
||
|
||
/* Wait until the Busy bit is cleared */
|
||
while ( (SSP_SSP0SR & (SSP_SSP0SR_BSY_BUSY|SSP_SSP0SR_RNE_NOTEMPTY)) != SSP_SSP0SR_RNE_NOTEMPTY );
|
||
|
||
*buf = SSP_SSP0DR;
|
||
buf++;
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/*!
|
||
@brief Sends a block of data to the SSP0 port and receives the
|
||
answer back into the same buffer.
|
||
|
||
@param[in] portNum
|
||
The SPI port to use (0..1)
|
||
@param[in] buf
|
||
Pointer to the data buffer
|
||
@param[in] length
|
||
Block length of the data buffer
|
||
*/
|
||
/**************************************************************************/
|
||
void sspSendReceive(uint8_t portNum, uint8_t *buf, uint32_t length)
|
||
{
|
||
uint32_t i;
|
||
uint8_t Dummy = Dummy;
|
||
|
||
if (portNum == 0)
|
||
{
|
||
for (i = 0; i < length; i++)
|
||
{
|
||
/* Move on only if NOT busy and TX FIFO not full. */
|
||
while ((SSP_SSP0SR & (SSP_SSP0SR_TNF_NOTFULL | SSP_SSP0SR_BSY_BUSY)) != SSP_SSP0SR_TNF_NOTFULL);
|
||
SSP_SSP0DR = *buf;
|
||
|
||
while ( (SSP_SSP0SR & (SSP_SSP0SR_BSY_BUSY|SSP_SSP0SR_RNE_NOTEMPTY)) != SSP_SSP0SR_RNE_NOTEMPTY );
|
||
/* Whenever a byte is written, MISO FIFO counter increments, Clear FIFO
|
||
on MISO. Otherwise, when SSP0Receive() is called, previous data byte
|
||
is left in the FIFO. */
|
||
*buf = SSP_SSP0DR;
|
||
buf++;
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|