mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-04 12:24:11 +01:00
20fa15c24a
SPI block transfers were broken in commit r759. Block transfers are primarily used by the SD card interface so this also broke accesses to the SD card. SD card accesses should be fixed now. Verified by writing config objects in the GCS and confirming that they survived a reboot of the OP board. git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@808 ebee16cc-31ac-478f-84a7-5cbb03baadba
384 lines
13 KiB
C
384 lines
13 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
|
|
* J19 provides two SSEL (alias Chip Select) lines.<BR>
|
|
* It's a software emulated SPI, therefore the selected spi_prescaler has no
|
|
* effect! Bytes are transfered so fast as possible. The usage of
|
|
* PIOS_SPI_PIN_DRIVER_STRONG is strongly recommended ;)<BR>
|
|
* DMA transfers are not supported by the emulation, so that
|
|
* PIOS_SPI_TransferBlock() will consume CPU time (but the callback handling does work).
|
|
*
|
|
* 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:
|
|
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));
|
|
|
|
/* 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
|
|
* \return >= 0: the read byte
|
|
* \return -1 if disabled SPI port selected
|
|
* \return -2 if unsupported SPI port selected
|
|
* \return -3 if unsupported SPI mode configured via PIOS_SPI_TransferModeInit()
|
|
*/
|
|
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);
|
|
|
|
if (!spi_dev) {
|
|
/* Undefined SPI port for this board (see pios_board.c) */
|
|
return -2;
|
|
}
|
|
|
|
/*
|
|
* 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, 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(spi_dev->cfg->dma.rx.channel->CNDTR) {
|
|
return -3;
|
|
}
|
|
|
|
/* Set callback function */
|
|
spi_dev->callback = callback;
|
|
|
|
/* Configure Rx channel */
|
|
DMA_Cmd(spi_dev->cfg->dma.rx.channel, DISABLE);
|
|
|
|
/* 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;
|
|
}
|
|
dma_init.DMA_BufferSize = len;
|
|
DMA_Init(spi_dev->cfg->dma.rx.channel, &(dma_init));
|
|
DMA_Cmd(spi_dev->cfg->dma.rx.channel, ENABLE);
|
|
|
|
/* Configure Tx channel */
|
|
DMA_Cmd(spi_dev->cfg->dma.tx.channel, DISABLE);
|
|
|
|
/* 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;
|
|
}
|
|
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);
|
|
|
|
/* Start DMA Tx transfer */
|
|
DMA_Cmd(spi_dev->cfg->dma.tx.channel, ENABLE);
|
|
|
|
/* Wait until all bytes have been transmitted/received */
|
|
while(DMA_GetCurrDataCounter(spi_dev->cfg->dma.tx.channel));
|
|
|
|
/* 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);
|
|
|
|
if(spi_dev->callback != NULL) {
|
|
spi_dev->callback();
|
|
}
|
|
}
|
|
|
|
#endif
|