mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-02 10:24:11 +01:00
a3a2dbd634
PIOS SPI devices may now make use of automatic CRC generation and checking on block transfers. Only supports CRC8 for now. Since the SPI interface CRC calculation continues across message boundaries (ie. not reset on every transfer), we must manually reset the CRC registers for every transfer to allow the two sides of the link to resynchronize. Unfortunately, resetting the CRC registers requires disabling the SPI peripheral which must now be done on every block transfer. Note: The last byte of the tx buffer is never sent and is assumed to be a place holder for the tx CRC8. Note: The last byte of the rx buffer is expected to hold the rx CRC8. git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@1011 ebee16cc-31ac-478f-84a7-5cbb03baadba
448 lines
15 KiB
C
448 lines
15 KiB
C
/**
|
|
******************************************************************************
|
|
*
|
|
* @file pios_spi.c
|
|
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
|
* Parts by Thorsten Klose (tk@midibox.org) (tk@midibox.org)
|
|
* @brief Hardware Abstraction Layer for SPI ports of STM32
|
|
* @see The GNU Public License (GPL) Version 3
|
|
* @defgroup PIOS_SPI SPI Functions
|
|
* @notes
|
|
*
|
|
* Note that additional chip select lines can be easily added by using
|
|
* the remaining free GPIOs of the core module. Shared SPI ports should be
|
|
* arbitrated with (FreeRTOS based) Mutexes to avoid collisions!
|
|
* @{
|
|
*
|
|
*****************************************************************************/
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <pios.h>
|
|
|
|
#if defined(PIOS_INCLUDE_SPI)
|
|
|
|
#include <pios_spi_priv.h>
|
|
|
|
static struct pios_spi_dev * find_spi_dev_by_id (uint8_t spi)
|
|
{
|
|
if (spi >= pios_spi_num_devices) {
|
|
/* Undefined SPI port for this board (see pios_board.c) */
|
|
return NULL;
|
|
}
|
|
|
|
/* Get a handle for the device configuration */
|
|
return &(pios_spi_devs[spi]);
|
|
}
|
|
|
|
/**
|
|
* Initialises SPI pins
|
|
* \param[in] mode currently only mode 0 supported
|
|
* \return < 0 if initialisation failed
|
|
*/
|
|
int32_t PIOS_SPI_Init(void)
|
|
{
|
|
struct pios_spi_dev * spi_dev;
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < pios_spi_num_devices; i++) {
|
|
/* Get a handle for the device configuration */
|
|
spi_dev = find_spi_dev_by_id(i);
|
|
PIOS_DEBUG_Assert(spi_dev);
|
|
|
|
/* Disable callback function */
|
|
spi_dev->callback = NULL;
|
|
|
|
/* Set rx/tx dummy bytes to a known value */
|
|
spi_dev->rx_dummy_byte = 0xFF;
|
|
spi_dev->tx_dummy_byte = 0xFF;
|
|
|
|
switch (spi_dev->cfg->init.SPI_NSS) {
|
|
case SPI_NSS_Soft:
|
|
if (spi_dev->cfg->init.SPI_Mode == SPI_Mode_Master) {
|
|
/* We're a master in soft NSS mode, make sure we see NSS high at all times. */
|
|
SPI_NSSInternalSoftwareConfig(spi_dev->cfg->regs, SPI_NSSInternalSoft_Set);
|
|
/* Since we're driving the SSEL pin in software, ensure that the slave is deselected */
|
|
GPIO_SetBits(spi_dev->cfg->ssel.gpio, spi_dev->cfg->ssel.init.GPIO_Pin);
|
|
GPIO_Init(spi_dev->cfg->ssel.gpio, &(spi_dev->cfg->ssel.init));
|
|
} else {
|
|
/* We're a slave in soft NSS mode, make sure we see NSS low at all times. */
|
|
SPI_NSSInternalSoftwareConfig(spi_dev->cfg->regs, SPI_NSSInternalSoft_Reset);
|
|
}
|
|
break;
|
|
case SPI_NSS_Hard:
|
|
/* FIXME: Should this also call SPI_SSOutputCmd()? */
|
|
GPIO_Init(spi_dev->cfg->ssel.gpio, &(spi_dev->cfg->ssel.init));
|
|
break;
|
|
default:
|
|
PIOS_DEBUG_Assert(0);
|
|
}
|
|
|
|
/* Initialize the GPIO pins */
|
|
GPIO_Init(spi_dev->cfg->sclk.gpio, &(spi_dev->cfg->sclk.init));
|
|
GPIO_Init(spi_dev->cfg->mosi.gpio, &(spi_dev->cfg->mosi.init));
|
|
GPIO_Init(spi_dev->cfg->miso.gpio, &(spi_dev->cfg->miso.init));
|
|
|
|
/* Enable the associated peripheral clock */
|
|
switch ((uint32_t)spi_dev->cfg->regs) {
|
|
case (uint32_t)SPI1:
|
|
/* Enable SPI peripheral clock (APB2 == high speed) */
|
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
|
|
break;
|
|
case (uint32_t)SPI2:
|
|
/* Enable SPI peripheral clock (APB1 == slow speed) */
|
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
|
|
break;
|
|
case (uint32_t)SPI3:
|
|
/* Enable SPI peripheral clock (APB1 == slow speed) */
|
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
|
|
break;
|
|
}
|
|
|
|
/* Enable DMA clock */
|
|
RCC_AHBPeriphClockCmd(spi_dev->cfg->dma.ahb_clk, ENABLE);
|
|
|
|
/* Configure DMA for SPI Rx */
|
|
DMA_Cmd(spi_dev->cfg->dma.rx.channel, DISABLE);
|
|
DMA_Init(spi_dev->cfg->dma.rx.channel, &(spi_dev->cfg->dma.rx.init));
|
|
|
|
/* Configure DMA for SPI Tx */
|
|
DMA_Cmd(spi_dev->cfg->dma.tx.channel, DISABLE);
|
|
DMA_Init(spi_dev->cfg->dma.tx.channel, &(spi_dev->cfg->dma.tx.init));
|
|
|
|
/* Initialize the SPI block */
|
|
SPI_Init(spi_dev->cfg->regs, &(spi_dev->cfg->init));
|
|
|
|
/* Configure CRC calculation */
|
|
if (spi_dev->cfg->use_crc) {
|
|
SPI_CalculateCRC(spi_dev->cfg->regs, ENABLE);
|
|
} else {
|
|
SPI_CalculateCRC(spi_dev->cfg->regs, DISABLE);
|
|
}
|
|
|
|
/* Enable SPI */
|
|
SPI_Cmd(spi_dev->cfg->regs, ENABLE);
|
|
|
|
/* Enable SPI interrupts to DMA */
|
|
SPI_I2S_DMACmd(spi_dev->cfg->regs, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE);
|
|
|
|
/* Configure DMA interrupt */
|
|
NVIC_Init(&(spi_dev->cfg->dma.irq.init));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* (Re-)initialises SPI peripheral clock rate
|
|
*
|
|
* \param[in] spi SPI number (0 or 1)
|
|
* \param[in] spi_prescaler configures the SPI speed:
|
|
* <UL>
|
|
* <LI>PIOS_SPI_PRESCALER_2: sets clock rate 27.7~ nS @ 72 MHz (36 MBit/s) (only supported for spi==0, spi1 uses 4 instead)
|
|
* <LI>PIOS_SPI_PRESCALER_4: sets clock rate 55.5~ nS @ 72 MHz (18 MBit/s)
|
|
* <LI>PIOS_SPI_PRESCALER_8: sets clock rate 111.1~ nS @ 72 MHz (9 MBit/s)
|
|
* <LI>PIOS_SPI_PRESCALER_16: sets clock rate 222.2~ nS @ 72 MHz (4.5 MBit/s)
|
|
* <LI>PIOS_SPI_PRESCALER_32: sets clock rate 444.4~ nS @ 72 MHz (2.25 MBit/s)
|
|
* <LI>PIOS_SPI_PRESCALER_64: sets clock rate 888.8~ nS @ 72 MHz (1.125 MBit/s)
|
|
* <LI>PIOS_SPI_PRESCALER_128: sets clock rate 1.7~ nS @ 72 MHz (0.562 MBit/s)
|
|
* <LI>PIOS_SPI_PRESCALER_256: sets clock rate 3.5~ nS @ 72 MHz (0.281 MBit/s)
|
|
* </UL>
|
|
* \return 0 if no error
|
|
* \return -1 if disabled SPI port selected
|
|
* \return -2 if unsupported SPI port selected
|
|
* \return -3 if invalid spi_prescaler selected
|
|
*/
|
|
int32_t PIOS_SPI_SetClockSpeed(uint8_t spi, SPIPrescalerTypeDef spi_prescaler)
|
|
{
|
|
struct pios_spi_dev * spi_dev;
|
|
SPI_InitTypeDef SPI_InitStructure;
|
|
|
|
/* Get a handle for the device configuration */
|
|
spi_dev = find_spi_dev_by_id(spi);
|
|
|
|
if (!spi_dev) {
|
|
/* Undefined SPI port for this board (see pios_board.c) */
|
|
return -2;
|
|
}
|
|
|
|
if(spi_prescaler >= 8) {
|
|
/* Invalid prescaler selected */
|
|
return -3;
|
|
}
|
|
|
|
/* Start with a copy of the default configuration for the peripheral */
|
|
SPI_InitStructure = spi_dev->cfg->init;
|
|
|
|
/* Adjust the prescaler for the peripheral's clock */
|
|
SPI_InitStructure.SPI_BaudRatePrescaler = ((uint16_t)spi_prescaler & 7) << 3;
|
|
|
|
/* Write back the new configuration */
|
|
SPI_Init(spi_dev->cfg->regs, &SPI_InitStructure);
|
|
|
|
PIOS_SPI_TransferByte(spi, 0xFF);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Controls the RC (Register Clock alias Chip Select) pin of a SPI port
|
|
* \param[in] spi SPI number (0 or 1)
|
|
* \param[in] pin_value 0 or 1
|
|
* \return 0 if no error
|
|
* \return -1 if disabled SPI port selected
|
|
* \return -2 if unsupported SPI port selected
|
|
*/
|
|
int32_t PIOS_SPI_RC_PinSet(uint8_t spi, uint8_t pin_value)
|
|
{
|
|
struct pios_spi_dev * spi_dev;
|
|
|
|
/* Get a handle for the device configuration */
|
|
spi_dev = find_spi_dev_by_id(spi);
|
|
|
|
if (!spi_dev) {
|
|
/* Undefined SPI port for this board (see pios_board.c) */
|
|
return -2;
|
|
}
|
|
|
|
if (pin_value) {
|
|
GPIO_SetBits(spi_dev->cfg->ssel.gpio, spi_dev->cfg->ssel.init.GPIO_Pin);
|
|
} else {
|
|
GPIO_ResetBits(spi_dev->cfg->ssel.gpio, spi_dev->cfg->ssel.init.GPIO_Pin);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Transfers a byte to SPI output and reads back the return value from SPI input
|
|
* \param[in] spi SPI number (0 or 1)
|
|
* \param[in] b the byte which should be transfered
|
|
*/
|
|
int32_t PIOS_SPI_TransferByte(uint8_t spi, uint8_t b)
|
|
{
|
|
struct pios_spi_dev * spi_dev;
|
|
uint8_t dummy;
|
|
uint8_t rx_byte;
|
|
|
|
/* Get a handle for the device configuration */
|
|
spi_dev = find_spi_dev_by_id(spi);
|
|
PIOS_DEBUG_Assert(spi_dev);
|
|
|
|
/*
|
|
* Procedure taken from STM32F10xxx Reference Manual section 23.3.5
|
|
*/
|
|
|
|
/* Make sure the RXNE flag is cleared by reading the DR register */
|
|
dummy = spi_dev->cfg->regs->DR;
|
|
|
|
/* Start the transfer */
|
|
spi_dev->cfg->regs->DR = b;
|
|
|
|
/* Wait until there is a byte to read */
|
|
while(!(spi_dev->cfg->regs->SR & SPI_I2S_FLAG_RXNE));
|
|
|
|
/* Read the rx'd byte */
|
|
rx_byte = spi_dev->cfg->regs->DR;
|
|
|
|
/* Wait until the TXE goes high */
|
|
while (!(spi_dev->cfg->regs->SR & SPI_I2S_FLAG_TXE));
|
|
|
|
/* Wait for SPI transfer to have fully completed */
|
|
while (spi_dev->cfg->regs->SR & SPI_I2S_FLAG_BSY);
|
|
|
|
/* Return received byte */
|
|
return rx_byte;
|
|
}
|
|
|
|
|
|
/**
|
|
* Transfers a block of bytes via DMA.
|
|
* \param[in] spi SPI number (0 or 1)
|
|
* \param[in] send_buffer pointer to buffer which should be sent.<BR>
|
|
* If NULL, 0xff (all-one) will be sent.
|
|
* \param[in] receive_buffer pointer to buffer which should get the received values.<BR>
|
|
* If NULL, received bytes will be discarded.
|
|
* \param[in] len number of bytes which should be transfered
|
|
* \param[in] callback pointer to callback function which will be executed
|
|
* from DMA channel interrupt once the transfer is finished.
|
|
* If NULL, no callback function will be used, and PIOS_SPI_TransferBlock() will
|
|
* block until the transfer is finished.
|
|
* \return >= 0 if no error during transfer
|
|
* \return -1 if disabled SPI port selected
|
|
* \return -2 if unsupported SPI port selected
|
|
* \return -3 if function has been called during an ongoing DMA transfer
|
|
*/
|
|
int32_t PIOS_SPI_TransferBlock(uint8_t spi, const uint8_t *send_buffer, uint8_t *receive_buffer, uint16_t len, void *callback)
|
|
{
|
|
struct pios_spi_dev * spi_dev;
|
|
DMA_InitTypeDef dma_init;
|
|
|
|
/* Get a handle for the device configuration */
|
|
spi_dev = find_spi_dev_by_id(spi);
|
|
|
|
if (!spi_dev) {
|
|
/* Undefined SPI port for this board (see pios_board.c) */
|
|
return -2;
|
|
}
|
|
|
|
|
|
/* Exit if ongoing transfer */
|
|
if (DMA_GetCurrDataCounter(spi_dev->cfg->dma.rx.channel)) {
|
|
return -3;
|
|
}
|
|
|
|
/* Disable the SPI peripheral */
|
|
SPI_Cmd(spi_dev->cfg->regs, DISABLE);
|
|
|
|
/* Disable the DMA channels */
|
|
DMA_Cmd(spi_dev->cfg->dma.rx.channel, DISABLE);
|
|
DMA_Cmd(spi_dev->cfg->dma.tx.channel, DISABLE);
|
|
|
|
/* Set callback function */
|
|
spi_dev->callback = callback;
|
|
|
|
/*
|
|
* Configure Rx channel
|
|
*/
|
|
|
|
/* Start with the default configuration for this peripheral */
|
|
dma_init = spi_dev->cfg->dma.rx.init;
|
|
|
|
if(receive_buffer != NULL) {
|
|
/* Enable memory addr. increment - bytes written into receive buffer */
|
|
dma_init.DMA_MemoryBaseAddr = (uint32_t)receive_buffer;
|
|
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
|
|
} else {
|
|
/* Disable memory addr. increment - bytes written into dummy buffer */
|
|
spi_dev->rx_dummy_byte = 0xFF;
|
|
dma_init.DMA_MemoryBaseAddr = (uint32_t)&spi_dev->rx_dummy_byte;
|
|
dma_init.DMA_MemoryInc = DMA_MemoryInc_Disable;
|
|
}
|
|
if (spi_dev->cfg->use_crc) {
|
|
/* Make sure the CRC error flag is cleared before we start */
|
|
SPI_I2S_ClearFlag(spi_dev->cfg->regs, SPI_FLAG_CRCERR);
|
|
}
|
|
|
|
dma_init.DMA_BufferSize = len;
|
|
DMA_Init(spi_dev->cfg->dma.rx.channel, &(dma_init));
|
|
|
|
/*
|
|
* Configure Tx channel
|
|
*/
|
|
|
|
/* Start with the default configuration for this peripheral */
|
|
dma_init = spi_dev->cfg->dma.tx.init;
|
|
|
|
if(send_buffer != NULL) {
|
|
/* Enable memory addr. increment - bytes written into receive buffer */
|
|
dma_init.DMA_MemoryBaseAddr = (uint32_t)send_buffer;
|
|
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
|
|
} else {
|
|
/* Disable memory addr. increment - bytes written into dummy buffer */
|
|
spi_dev->tx_dummy_byte = 0xFF;
|
|
dma_init.DMA_MemoryBaseAddr = (uint32_t)&spi_dev->tx_dummy_byte;
|
|
dma_init.DMA_MemoryInc = DMA_MemoryInc_Disable;
|
|
}
|
|
|
|
if (spi_dev->cfg->use_crc) {
|
|
/* The last byte of the payload will be replaced with the CRC8 */
|
|
dma_init.DMA_BufferSize = len - 1;
|
|
} else {
|
|
dma_init.DMA_BufferSize = len;
|
|
}
|
|
|
|
DMA_Init(spi_dev->cfg->dma.tx.channel, &(dma_init));
|
|
|
|
/* Enable DMA interrupt if callback function active */
|
|
DMA_ITConfig(spi_dev->cfg->dma.rx.channel, DMA_IT_TC, (callback != NULL) ? ENABLE : DISABLE);
|
|
|
|
/* Flush out the CRC registers */
|
|
SPI_CalculateCRC(spi_dev->cfg->regs, DISABLE);
|
|
(void) SPI_GetCRC(spi_dev->cfg->regs, SPI_CRC_Rx);
|
|
SPI_I2S_ClearFlag(spi_dev->cfg->regs, SPI_FLAG_CRCERR);
|
|
|
|
/* Make sure to flush out the receive buffer */
|
|
(void) SPI_I2S_ReceiveData(spi_dev->cfg->regs);
|
|
|
|
if (spi_dev->cfg->use_crc) {
|
|
/* Need a 0->1 transition to reset the CRC logic */
|
|
SPI_CalculateCRC(spi_dev->cfg->regs, ENABLE);
|
|
}
|
|
|
|
/* Start DMA transfers */
|
|
DMA_Cmd(spi_dev->cfg->dma.rx.channel, ENABLE);
|
|
DMA_Cmd(spi_dev->cfg->dma.tx.channel, ENABLE);
|
|
|
|
/* Reenable the SPI device */
|
|
SPI_Cmd(spi_dev->cfg->regs, ENABLE);
|
|
|
|
if (callback) {
|
|
/* User has requested a callback, don't wait for the transfer to complete. */
|
|
return 0;
|
|
}
|
|
|
|
/* Wait until all bytes have been transmitted/received */
|
|
while(DMA_GetCurrDataCounter(spi_dev->cfg->dma.rx.channel));
|
|
|
|
/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
|
|
while(!(SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_TXE)));
|
|
|
|
/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
|
|
while(SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_BSY));
|
|
|
|
/* Check the CRC on the transfer if enabled. */
|
|
if (spi_dev->cfg->use_crc) {
|
|
/* Check the SPI CRC error flag */
|
|
if (SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_FLAG_CRCERR)) {
|
|
return -4;
|
|
}
|
|
}
|
|
|
|
/* No error */
|
|
return 0;
|
|
}
|
|
|
|
void PIOS_SPI_IRQ_Handler(uint8_t spi)
|
|
{
|
|
struct pios_spi_dev * spi_dev;
|
|
|
|
spi_dev = find_spi_dev_by_id (spi);
|
|
PIOS_DEBUG_Assert(spi_dev);
|
|
|
|
DMA_ClearFlag(spi_dev->cfg->dma.irq.flags);
|
|
|
|
/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
|
|
while(!(SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_TXE)));
|
|
|
|
/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
|
|
while(SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_BSY));
|
|
|
|
if(spi_dev->callback != NULL) {
|
|
bool crc_ok = TRUE;
|
|
uint8_t crc_val;
|
|
|
|
if (SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_FLAG_CRCERR)) {
|
|
crc_ok = FALSE;
|
|
SPI_I2S_ClearFlag(spi_dev->cfg->regs, SPI_FLAG_CRCERR);
|
|
}
|
|
crc_val = SPI_GetCRC(spi_dev->cfg->regs, SPI_CRC_Rx);
|
|
spi_dev->callback(crc_ok, crc_val);
|
|
}
|
|
}
|
|
|
|
#endif
|