/** ****************************************************************************** * * @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.
* 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 ;)
* 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 #if defined(PIOS_INCLUDE_SPI) #include 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: * * \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.
* If NULL, 0xff (all-one) will be sent. * \param[in] receive_buffer pointer to buffer which should get the received values.
* 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