diff --git a/CMakeLists.txt b/CMakeLists.txt index c5d677191b4f9b6bcdb43eb84697bddab5116297..7aed00d9d11cfa75c05b821be18c5e53a69b2f5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,9 +187,6 @@ sbs_target(test-can-loopback stm32f429zi_skyward_death_stack_x) add_executable(test-can-protocol src/tests/drivers/canbus/CanProtocol/test-can-protocol.cpp) sbs_target(test-can-protocol stm32f429zi_skyward_death_stack_x) -add_executable(test-dma-spi src/tests/drivers/dma/test-dma-spi.cpp) -sbs_target(test-dma-spi stm32f429zi_stm32f4discovery) - add_executable(test-dsgamma src/tests/drivers/test-dsgamma.cpp) sbs_target(test-dsgamma stm32f429zi_stm32f4discovery) diff --git a/src/shared/drivers/spi/SPI.h b/src/shared/drivers/spi/SPI.h deleted file mode 100644 index e146c323c5db96c5bb5915202e848a88ad8be890..0000000000000000000000000000000000000000 --- a/src/shared/drivers/spi/SPI.h +++ /dev/null @@ -1,481 +0,0 @@ -/* Copyright (c) 2021 Skyward Experimental Rocketry - * Author: Alberto Nidasio - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include <interfaces/arch_registers.h> -#include <stddef.h> -#include <utils/ClockUtils.h> - -#ifndef USE_MOCK_PERIPHERALS -using SPIType = SPI_TypeDef; -#else -#include <utils/TestUtils/FakeSpiTypedef.h> -using SPIType = Boardcore::FakeSpiTypedef; -#endif - -namespace Boardcore -{ - -/** - * @brief Driver for STM32 low level SPI peripheral. - * - * This driver applies to the whole STM32F4xx family. - * - * The serial peripheral interface (SPI) allows half/full-duplex, synchronous, - * serial communication with external devices. The interface can be configured - * as the master and in this case it provides the communication clock (SCK) to - * the external slave device. The peripheral is also capable of reliable - * communication using CRC checking. - * - * SPI main features: - * - Full-duplex synchronous transfers on three lines - * - 8- or 16-bit transfer frame format selection - * - Master or slave operation - * - 8 master mode baud rate prescaler (f_PCLK/2 max.) - * - Programmable clock polarity and phase - * - Programmable data order with MSB-first or LSB-first shifting - * - Hardware CRC feature for reliable communication - * - DMA capability - */ -class SPI -{ -public: - enum class BitOrder : uint16_t - { - MSB_FIRST = 0, - LSB_FIRST = 0x80 - }; - - /** - * @brief SPI Clock divider. - * - * SPI clock frequency will be equal to the SPI peripheral bus clock speed - * divided by the specified value. - * - * Eg: DIV_2 --> spi clock freq = f_PCLK / 2 - */ - enum class ClockDivider : uint8_t - { - DIV_2 = 0x00, - DIV_4 = 0x08, - DIV_8 = 0x10, - DIV_16 = 0x18, - DIV_32 = 0x20, - DIV_64 = 0x28, - DIV_128 = 0x30, - DIV_256 = 0x38, - }; - - enum class Mode : uint8_t - { - MODE_0 = 0, ///< CPOL = 0, CPHA = 0 - MODE_1 = 1, ///< CPOL = 0, CPHA = 1 - MODE_2 = 2, ///< CPOL = 1, CPHA = 0 - MODE_3 = 3 ///< CPOL = 1, CPHA = 1 - }; - - /** - * @brief Automatically enables the timer peripheral clock. - */ - explicit SPI(SPIType *spi); - - /** - * @brief Resets the registers and disables the peripheral clock. - */ - ~SPI(); - - SPIType *getSpi(); - - /** - * @brief Resets the peripheral configuration. - */ - void reset(); - - /** - * @brief Enables the peripheral. - */ - void enable(); - - /** - * @brief Disables the peripheral. - */ - void disable(); - - void set8BitFrameFormat(); - - void set16BitFrameFormat(); - - void enableSoftwareSlaveManagement(); - - void disableSoftwareSlaveManagement(); - - void enableInternalSlaveSelection(); - - void disableInternalSlaveSelection(); - - void setBitOrder(BitOrder bitOrder); - - void setClockDiver(ClockDivider divider); - - void setSlaveConfiguration(); - - void setMasterConfiguration(); - - void setMode(Mode mode); - - void enableTxDMARequest(); - - void disableTxDMARequest(); - - void enableRxDMARequest(); - - void disableRxDMARequest(); - - void waitPeripheral(); - - // Read, write and transfer operations in master mode - - /** - * @brief Reads a single byte from the bus. - * - * @return Byte read from the bus. - */ - uint8_t read(); - - /** - * @brief Reads a single half word from the bus. - * - * @return Half word read from the bus. - */ - uint16_t read16(); - - /** - * @brief Reads multiple bytes from the bus - * - * @param data Buffer to be filled with received data. - * @param size Size of the buffer in bytes. - */ - void read(uint8_t *data, size_t nBytes); - - /** - * @brief Reads multiple half words from the bus - * - * @param data Buffer to be filled with received data. - * @param size Size of the buffer in bytes. - */ - void read16(uint16_t *data, size_t nBytes); - - /** - * @brief Writes a single byte to the bus. - * - * @param data Byte to write. - */ - void write(uint8_t data); - - /** - * @brief Writes a single half word to the bus. - * - * @param data Half word to write. - */ - void write16(uint16_t data); - - /** - * @brief Writes multiple bytes to the bus. - * - * @param data Buffer containing data to write. - * @param size Size of the buffer in bytes. - */ - void write(const uint8_t *data, size_t nBytes); - - /** - * @brief Writes multiple half words to the bus. - * - * @param data Buffer containing data to write. - * @param size Size of the buffer in bytes. - */ - void write16(const uint16_t *data, size_t nBytes); - - /** - * @brief Full duplex transmission of one byte on the bus. - * - * @param data Byte to write. - * @return Byte read from the bus. - */ - uint8_t transfer(uint8_t data); - - /** - * @brief Full duplex transmission of one half word on the bus. - * - * @param data Half word to write. - * @return Half word read from the bus. - */ - uint16_t transfer16(uint16_t data); - - /** - * @brief Full duplex transmission of multiple bytes on the bus. - * - * @param data Buffer containing data to trasfer. - * @param size Size of the buffer in bytes. - */ - void transfer(uint8_t *data, size_t nBytes); - - /** - * @brief Full duplex transmission of multiple half words on the bus. - * - * @param data Buffer containing data to trasfer. - * @param size Size of the buffer in bytes. - */ - void transfer16(uint16_t *data, size_t nBytes); - -private: - SPIType *spi; -}; - -inline SPI::SPI(SPIType *spi) : spi(spi) -{ - ClockUtils::enablePeripheralClock(spi); -} - -inline SPI::~SPI() { ClockUtils::disablePeripheralClock(spi); } - -inline SPIType *SPI::getSpi() { return spi; } - -inline void SPI::reset() -{ - spi->CR1 = 0; - spi->CR2 = 0; - spi->DR = 0; - spi->RXCRCR = 0; - spi->TXCRCR = 0; -} - -inline void SPI::enable() { spi->CR1 |= SPI_CR1_SPE; } - -inline void SPI::disable() { spi->CR1 &= ~SPI_CR1_SPE; } - -inline void SPI::set8BitFrameFormat() { spi->CR1 &= ~SPI_CR1_DFF; } - -inline void SPI::set16BitFrameFormat() { spi->CR1 |= SPI_CR1_DFF; } - -inline void SPI::enableSoftwareSlaveManagement() { spi->CR1 |= SPI_CR1_SSM; } - -inline void SPI::disableSoftwareSlaveManagement() { spi->CR1 &= ~SPI_CR1_SSM; } - -inline void SPI::enableInternalSlaveSelection() { spi->CR1 |= SPI_CR1_SSI; } - -inline void SPI::disableInternalSlaveSelection() { spi->CR1 &= ~SPI_CR1_SSI; } - -inline void SPI::setBitOrder(BitOrder bitOrder) -{ - // First clear the configuration - spi->CR1 &= ~SPI_CR1_LSBFIRST; - - // Set the new value - spi->CR1 |= static_cast<uint32_t>(bitOrder); -} - -inline void SPI::setClockDiver(ClockDivider divider) -{ - // First clear the configuration - spi->CR1 &= ~SPI_CR1_BR; - - // Set the new value - spi->CR1 |= static_cast<uint32_t>(divider); -} - -inline void SPI::setSlaveConfiguration() { spi->CR1 &= ~SPI_CR1_MSTR; } - -inline void SPI::setMasterConfiguration() { spi->CR1 |= SPI_CR1_MSTR; } - -inline void SPI::setMode(Mode mode) -{ - // First clear the configuration - spi->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA); - - // Set the new value - spi->CR1 |= static_cast<uint32_t>(mode); -} - -inline void SPI::enableTxDMARequest() { spi->CR2 |= SPI_CR2_TXDMAEN; } - -inline void SPI::disableTxDMARequest() { spi->CR2 &= ~SPI_CR2_TXDMAEN; } - -inline void SPI::enableRxDMARequest() { spi->CR2 |= SPI_CR2_RXDMAEN; } - -inline void SPI::disableRxDMARequest() { spi->CR2 &= ~SPI_CR2_RXDMAEN; } - -inline void SPI::waitPeripheral() -{ - while ((spi->SR & SPI_SR_TXE) == 0) - ; - while ((spi->SR & SPI_SR_BSY) > 0) - ; -} - -// Read, write and transfer operations in master mode - -inline uint8_t SPI::read() { return transfer(static_cast<uint8_t>(0)); } - -inline uint16_t SPI::read16() { return transfer(static_cast<uint16_t>(0)); } - -inline void SPI::read(uint8_t *data, size_t nBytes) -{ - for (size_t i = 0; i < nBytes; i++) - data[i] = read(); -} - -inline void SPI::read16(uint16_t *data, size_t nBytes) -{ - // Set 16 bit frame format - set16BitFrameFormat(); - - for (size_t i = 0; i < nBytes / 2; i++) - { - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - - // Write the data item to transmit - spi->DR = 0; - - // Wait until data is received - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Read the received data item - data[i] = static_cast<uint16_t>(spi->DR); - } - - // Go back to 8 bit frame format - set8BitFrameFormat(); -} - -inline void SPI::write(uint8_t data) { transfer(data); } - -inline void SPI::write16(uint16_t data) { transfer(data); } - -inline void SPI::write(const uint8_t *data, size_t nBytes) -{ - for (size_t i = 0; i < nBytes; i++) - transfer(data[i]); -} - -inline void SPI::write16(const uint16_t *data, size_t nBytes) -{ - // Set 16 bit frame format - set16BitFrameFormat(); - - for (size_t i = 0; i < nBytes / 2; i++) - { - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - - // Write the data item to transmit - spi->DR = static_cast<uint16_t>(data[i]); - - // Wait until data is received - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Read the received data item - (void)spi->DR; - } - - // Go back to 8 bit frame format - set8BitFrameFormat(); -} - -inline uint8_t SPI::transfer(uint8_t data) -{ - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - - // Write the data item to transmit - spi->DR = static_cast<uint8_t>(data); - - // Wait until data is received - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Read the received data item - return static_cast<uint8_t>(spi->DR); -} - -inline uint16_t SPI::transfer16(uint16_t data) -{ - // Set 16 bit frame format - set16BitFrameFormat(); - - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - - // Write the data item to transmit - spi->DR = static_cast<uint16_t>(data); - - // Wait until data is received - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Go back to 8 bit frame format - set8BitFrameFormat(); - - // Read the received data item - return static_cast<uint16_t>(spi->DR); -} - -inline void SPI::transfer(uint8_t *data, size_t nBytes) -{ - for (size_t i = 0; i < nBytes; i++) - data[i] = transfer(data[i]); -} - -inline void SPI::transfer16(uint16_t *data, size_t nBytes) -{ - // Set 16 bit frame format - set16BitFrameFormat(); - - for (size_t i = 0; i < nBytes / 2; i++) - { - // Wait until the peripheral is ready to transmit - while ((spi->SR & SPI_SR_TXE) == 0) - ; - - // Write the data item to transmit - spi->DR = static_cast<uint16_t>(data[i]); - - // Wait until data is received - while ((spi->SR & SPI_SR_RXNE) == 0) - ; - - // Read the received data item - data[i] = static_cast<uint16_t>(spi->DR); - } - - // Go back to 8 bit frame format - set8BitFrameFormat(); -} - -} // namespace Boardcore diff --git a/src/shared/drivers/spi/SPIBus.h b/src/shared/drivers/spi/SPIBus.h index a10af7e296aa35d8aaed5f3c14eac9119b7df802..306af46719c2d38fbeeaee1fd83fa91948d73a73 100644 --- a/src/shared/drivers/spi/SPIBus.h +++ b/src/shared/drivers/spi/SPIBus.h @@ -23,6 +23,7 @@ #pragma once #include <interfaces/delays.h> +#include <utils/ClockUtils.h> #include "SPIBusInterface.h" @@ -37,8 +38,23 @@ namespace Boardcore { /** - * @brief Main implementation of SPIBusInterface used for accessing the SPI - * peripheral in master mode + * @brief Driver for STM32 low level SPI peripheral. + * + * This driver applies to the whole STM32F4xx family. + * + * The serial peripheral interface (SPI) allows half/full-duplex, synchronous, + * serial communication with external devices. The interface can be configured + * as the master and in this case it provides the communication clock (SCK) to + * the external slave device. The peripheral is also capable of reliable + * communication using CRC checking. + * + * Supported SPI main features: + * - Full-duplex synchronous transfers on three lines + * - 8- or 16-bit transfer frame format selection + * - Master operation + * - 8 master mode baud rate prescaler (f_PCLK/2 max.) + * - Programmable clock polarity and phase + * - Programmable data order with MSB-first or LSB-first shifting */ class SPIBus : public SPIBusInterface { @@ -51,6 +67,58 @@ public: SPIBus(SPIBus&&) = delete; SPIBus& operator=(SPIBus&&) = delete; + /** + * @brief Retrieve the pointer to the peripheral currently used. + */ + SPIType* getSpi(); + + /** + * @brief Resets the peripheral configuration. + */ + void reset(); + + /** + * @brief Enables the peripheral. + */ + void enable(); + + /** + * @brief Disables the peripheral. + */ + void disable(); + + void set8BitFrameFormat(); + + void set16BitFrameFormat(); + + void enableSoftwareSlaveManagement(); + + void disableSoftwareSlaveManagement(); + + void enableInternalSlaveSelection(); + + void disableInternalSlaveSelection(); + + void setBitOrder(SPI::BitOrder bitOrder); + + void setClockDiver(SPI::ClockDivider divider); + + void setSlaveConfiguration(); + + void setMasterConfiguration(); + + void setMode(SPI::Mode mode); + + void enableTxDMARequest(); + + void disableTxDMARequest(); + + void enableRxDMARequest(); + + void disableRxDMARequest(); + + void waitPeripheral(); + /** * @brief Configures and enables the bus with the provided configuration. * @@ -123,7 +191,7 @@ public: * @param data Buffer containing data to write. * @param size Size of the buffer. */ - void write(uint8_t* data, size_t size) override; + void write(const uint8_t* data, size_t size) override; /** * @brief Writes multiple half words to the bus. @@ -131,7 +199,7 @@ public: * @param data Buffer containing data to write. * @param size Size of the buffer. */ - void write16(uint16_t* data, size_t size) override; + void write16(const uint16_t* data, size_t size) override; /** * @brief Full duplex transmission of one byte on the bus. @@ -166,12 +234,95 @@ public: void transfer16(uint16_t* data, size_t size) override; private: - SPI spi; + SPIType* spi; SPIBusConfig config{}; bool firstConfigApplied = false; }; -inline SPIBus::SPIBus(SPIType* spi) : spi(spi) {} +inline SPIBus::SPIBus(SPIType* spi) : spi(spi) +{ + ClockUtils::enablePeripheralClock(spi); +} + +inline SPIType* SPIBus::getSpi() { return spi; } + +inline void SPIBus::reset() +{ + spi->CR1 = 0; + spi->CR2 = 0; + spi->DR = 0; + spi->RXCRCR = 0; + spi->TXCRCR = 0; +} + +inline void SPIBus::enable() { spi->CR1 |= SPI_CR1_SPE; } + +inline void SPIBus::disable() { spi->CR1 &= ~SPI_CR1_SPE; } + +inline void SPIBus::set8BitFrameFormat() { spi->CR1 &= ~SPI_CR1_DFF; } + +inline void SPIBus::set16BitFrameFormat() { spi->CR1 |= SPI_CR1_DFF; } + +inline void SPIBus::enableSoftwareSlaveManagement() { spi->CR1 |= SPI_CR1_SSM; } + +inline void SPIBus::disableSoftwareSlaveManagement() +{ + spi->CR1 &= ~SPI_CR1_SSM; +} + +inline void SPIBus::enableInternalSlaveSelection() { spi->CR1 |= SPI_CR1_SSI; } + +inline void SPIBus::disableInternalSlaveSelection() +{ + spi->CR1 &= ~SPI_CR1_SSI; +} + +inline void SPIBus::setBitOrder(SPI::BitOrder bitOrder) +{ + // First clear the configuration + spi->CR1 &= ~SPI_CR1_LSBFIRST; + + // Set the new value + spi->CR1 |= static_cast<uint32_t>(bitOrder); +} + +inline void SPIBus::setClockDiver(SPI::ClockDivider divider) +{ + // First clear the configuration + spi->CR1 &= ~SPI_CR1_BR; + + // Set the new value + spi->CR1 |= static_cast<uint32_t>(divider); +} + +inline void SPIBus::setSlaveConfiguration() { spi->CR1 &= ~SPI_CR1_MSTR; } + +inline void SPIBus::setMasterConfiguration() { spi->CR1 |= SPI_CR1_MSTR; } + +inline void SPIBus::setMode(SPI::Mode mode) +{ + // First clear the configuration + spi->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA); + + // Set the new value + spi->CR1 |= static_cast<uint32_t>(mode); +} + +inline void SPIBus::enableTxDMARequest() { spi->CR2 |= SPI_CR2_TXDMAEN; } + +inline void SPIBus::disableTxDMARequest() { spi->CR2 &= ~SPI_CR2_TXDMAEN; } + +inline void SPIBus::enableRxDMARequest() { spi->CR2 |= SPI_CR2_RXDMAEN; } + +inline void SPIBus::disableRxDMARequest() { spi->CR2 &= ~SPI_CR2_RXDMAEN; } + +inline void SPIBus::waitPeripheral() +{ + while ((spi->SR & SPI_SR_TXE) == 0) + ; + while ((spi->SR & SPI_SR_BSY) > 0) + ; +} inline void SPIBus::configure(SPIBusConfig newConfig) { @@ -183,33 +334,34 @@ inline void SPIBus::configure(SPIBusConfig newConfig) firstConfigApplied = true; // Wait until the peripheral is done before changing configuration - spi.waitPeripheral(); + waitPeripheral(); // Disable the peripheral - spi.disable(); + disable(); // Configure clock polarity and phase - spi.setMode(config.mode); + setMode(config.mode); // Configure clock frequency - spi.setClockDiver(config.clockDivider); + setClockDiver(config.clockDivider); // Configure bit order - spi.setBitOrder(config.bitOrder); + setBitOrder(config.bitOrder); // Configure chip select and master mode - spi.enableSoftwareSlaveManagement(); - spi.enableInternalSlaveSelection(); - spi.setMasterConfiguration(); + enableSoftwareSlaveManagement(); + enableInternalSlaveSelection(); + setMasterConfiguration(); // Enable the peripheral - spi.enable(); + enable(); } } inline void SPIBus::select(GpioType& cs) { cs.low(); + if (config.csSetupTimeUs > 0) { miosix::delayUs(config.csSetupTimeUs); @@ -222,45 +374,152 @@ inline void SPIBus::deselect(GpioType& cs) { miosix::delayUs(config.csHoldTimeUs); } + cs.high(); } -// Read, write and transfer operations +inline uint8_t SPIBus::read() { return transfer(static_cast<uint8_t>(0)); } + +inline uint16_t SPIBus::read16() { return transfer(static_cast<uint16_t>(0)); } + +inline void SPIBus::read(uint8_t* data, size_t nBytes) +{ + for (size_t i = 0; i < nBytes; i++) + data[i] = read(); +} + +inline void SPIBus::read16(uint16_t* data, size_t nBytes) +{ + // Set 16 bit frame format + set16BitFrameFormat(); + + for (size_t i = 0; i < nBytes / 2; i++) + { + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + + // Write the data item to transmit + spi->DR = 0; -inline uint8_t SPIBus::read() { return spi.read(); } + // Wait until data is received + while ((spi->SR & SPI_SR_RXNE) == 0) + ; -inline uint16_t SPIBus::read16() { return spi.read16(); } + // Read the received data item + data[i] = static_cast<uint16_t>(spi->DR); + } + + // Go back to 8 bit frame format + set8BitFrameFormat(); +} + +inline void SPIBus::write(uint8_t data) { transfer(data); } -inline void SPIBus::read(uint8_t* data, size_t size) { spi.read(data, size); } +inline void SPIBus::write16(uint16_t data) { transfer(data); } -inline void SPIBus::read16(uint16_t* data, size_t size) +inline void SPIBus::write(const uint8_t* data, size_t nBytes) { - spi.read16(data, size); + for (size_t i = 0; i < nBytes; i++) + transfer(data[i]); } -inline void SPIBus::write(uint8_t data) { spi.write(data); } +inline void SPIBus::write16(const uint16_t* data, size_t nBytes) +{ + // Set 16 bit frame format + set16BitFrameFormat(); + + for (size_t i = 0; i < nBytes / 2; i++) + { + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + + // Write the data item to transmit + spi->DR = static_cast<uint16_t>(data[i]); + + // Wait until data is received + while ((spi->SR & SPI_SR_RXNE) == 0) + ; -inline void SPIBus::write16(uint16_t data) { spi.write(data); } + // Read the received data item + (void)spi->DR; + } -inline void SPIBus::write(uint8_t* data, size_t size) { spi.write(data, size); } + // Go back to 8 bit frame format + set8BitFrameFormat(); +} -inline void SPIBus::write16(uint16_t* data, size_t size) +inline uint8_t SPIBus::transfer(uint8_t data) { - spi.write16(data, size); + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + + // Write the data item to transmit + spi->DR = static_cast<uint8_t>(data); + + // Wait until data is received + while ((spi->SR & SPI_SR_RXNE) == 0) + ; + + // Read the received data item + return static_cast<uint8_t>(spi->DR); } -inline uint8_t SPIBus::transfer(uint8_t data) { return spi.transfer(data); } +inline uint16_t SPIBus::transfer16(uint16_t data) +{ + // Set 16 bit frame format + set16BitFrameFormat(); + + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + + // Write the data item to transmit + spi->DR = static_cast<uint16_t>(data); + + // Wait until data is received + while ((spi->SR & SPI_SR_RXNE) == 0) + ; -inline uint16_t SPIBus::transfer16(uint16_t data) { return spi.transfer(data); } + // Go back to 8 bit frame format + set8BitFrameFormat(); -inline void SPIBus::transfer(uint8_t* data, size_t size) + // Read the received data item + return static_cast<uint16_t>(spi->DR); +} + +inline void SPIBus::transfer(uint8_t* data, size_t nBytes) { - spi.transfer(data, size); + for (size_t i = 0; i < nBytes; i++) + data[i] = transfer(data[i]); } -inline void SPIBus::transfer16(uint16_t* data, size_t size) +inline void SPIBus::transfer16(uint16_t* data, size_t nBytes) { - spi.transfer16(data, size); + // Set 16 bit frame format + set16BitFrameFormat(); + + for (size_t i = 0; i < nBytes / 2; i++) + { + // Wait until the peripheral is ready to transmit + while ((spi->SR & SPI_SR_TXE) == 0) + ; + + // Write the data item to transmit + spi->DR = static_cast<uint16_t>(data[i]); + + // Wait until data is received + while ((spi->SR & SPI_SR_RXNE) == 0) + ; + + // Read the received data item + data[i] = static_cast<uint16_t>(spi->DR); + } + + // Go back to 8 bit frame format + set8BitFrameFormat(); } } // namespace Boardcore diff --git a/src/shared/drivers/spi/SPIBusInterface.h b/src/shared/drivers/spi/SPIBusInterface.h index 12fde48f221732752690b0b109f40ef55c638b01..db8158e49cc8fe950e9e2c1600cf896aa91c0f47 100644 --- a/src/shared/drivers/spi/SPIBusInterface.h +++ b/src/shared/drivers/spi/SPIBusInterface.h @@ -23,8 +23,9 @@ #pragma once #include <interfaces-impl/gpio_impl.h> +#include <stddef.h> -#include "SPI.h" +#include "SPIDefs.h" #ifndef USE_MOCK_PERIPHERALS using GpioType = miosix::GpioPin; @@ -170,7 +171,7 @@ public: * @param data Buffer containing data to write. * @param size Size of the buffer. */ - virtual void write(uint8_t* data, size_t size) = 0; + virtual void write(const uint8_t* data, size_t size) = 0; /** * @brief Writes multiple half words to the bus. @@ -178,7 +179,7 @@ public: * @param data Buffer containing data to write. * @param size Size of the buffer. */ - virtual void write16(uint16_t* data, size_t size) = 0; + virtual void write16(const uint16_t* data, size_t size) = 0; /** * @brief Full duplex transmission of one byte on the bus. diff --git a/src/shared/drivers/spi/SPIDefs.h b/src/shared/drivers/spi/SPIDefs.h new file mode 100644 index 0000000000000000000000000000000000000000..ce7a7682d56717b1e665dea540ac4b3278fd1c26 --- /dev/null +++ b/src/shared/drivers/spi/SPIDefs.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Author: Alberto Nidasio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <interfaces/arch_registers.h> +#include <stdint.h> + +namespace Boardcore +{ + +/** + * @brief Driver for STM32 low level SPI peripheral. + * + * This driver applies to the whole STM32F4xx family. + * + * The serial peripheral interface (SPI) allows half/full-duplex, synchronous, + * serial communication with external devices. The interface can be configured + * as the master and in this case it provides the communication clock (SCK) to + * the external slave device. The peripheral is also capable of reliable + * communication using CRC checking. + * + * SPI main features: + * - Full-duplex synchronous transfers on three lines + * - 8- or 16-bit transfer frame format selection + * - Master or slave operation + * - 8 master mode baud rate prescaler (f_PCLK/2 max.) + * - Programmable clock polarity and phase + * - Programmable data order with MSB-first or LSB-first shifting + * - Hardware CRC feature for reliable communication + * - DMA capability + */ +namespace SPI +{ + +enum class BitOrder : uint16_t +{ + MSB_FIRST = 0, + LSB_FIRST = 0x80 +}; + +/** + * @brief SPI Clock divider. + * + * SPI clock frequency will be equal to the SPI peripheral bus clock speed + * divided by the specified value. + * + * Eg: DIV_2 --> spi clock freq = f_PCLK / 2 + */ +enum class ClockDivider : uint8_t +{ + DIV_2 = 0x00, + DIV_4 = 0x08, + DIV_8 = 0x10, + DIV_16 = 0x18, + DIV_32 = 0x20, + DIV_64 = 0x28, + DIV_128 = 0x30, + DIV_256 = 0x38, +}; + +enum class Mode : uint8_t +{ + MODE_0 = 0, ///< CPOL = 0, CPHA = 0 + MODE_1 = 1, ///< CPOL = 0, CPHA = 1 + MODE_2 = 2, ///< CPOL = 1, CPHA = 0 + MODE_3 = 3 ///< CPOL = 1, CPHA = 1 +}; + +} // namespace SPI + +} // namespace Boardcore diff --git a/src/tests/drivers/dma/test-dma-spi-raw.cpp b/src/tests/drivers/dma/test-dma-spi-raw.cpp deleted file mode 100644 index e03af9877ffb98df2ff86f7e62433c544f58de5c..0000000000000000000000000000000000000000 --- a/src/tests/drivers/dma/test-dma-spi-raw.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/* Copyright (c) 2021 Skyward Experimental Rocketry - * Author: Alberto Nidasio - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include <miosix.h> - -using namespace miosix; - -static bool dmaComplete = false; -static bool dmaError = false; - -static uint8_t txData[] = {0xA8, 0x00}; -static uint8_t rxData[2] = {0}; - -GpioPin csPin = GpioPin(GPIOC_BASE, 1); -GpioPin sckPin = GpioPin(GPIOF_BASE, 7); -GpioPin misoPin = GpioPin(GPIOF_BASE, 8); -GpioPin mosiPin = GpioPin(GPIOF_BASE, 9); - -int main() -{ - // SPI INIT - { - csPin.mode(Mode::OUTPUT); - csPin.high(); - sckPin.mode(Mode::ALTERNATE); - sckPin.alternateFunction(5); - mosiPin.mode(Mode::ALTERNATE); - mosiPin.alternateFunction(5); - misoPin.mode(Mode::ALTERNATE); - misoPin.alternateFunction(5); - - // Enable SPI clock for SPI5 interface - RCC->APB2ENR |= RCC_APB2ENR_SPI5EN; - - // Clear configuration for SPI interface - SPI5->CR1 = 0; - SPI5->CR2 = 0; - - // 1: Set BR[2:0] bits to define the baud rate - SPI5->CR1 |= SPI_CR1_BR; - - // 2: Setup clock phase and polarity (MODE1: CPOL = 1, CPHA = 1) - SPI5->CR1 |= SPI_CR1_CPOL | SPI_CR1_CPHA; - - // 7: Set MSTR to set master mode - SPI5->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM | SPI_CR1_MSTR; - - // 8: Enable DMA for tx - SPI5->CR2 |= SPI_CR2_TXDMAEN; - SPI5->CR2 |= SPI_CR2_RXDMAEN; - - // Enable SPI - SPI5->CR1 |= SPI_CR1_SPE; - - printf("SPI interface setup completed...\n"); - } - - // 0: Enable DMA2 clock - RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; - - // DMA2 Stream4 channel 3 for SPI5 TX - { - // 1: Disable the stream - - // Disable stream - DMA2_Stream4->CR &= ~DMA_SxCR_EN; - - // Wait for the stream to be disabled - while (DMA2_Stream4->CR & DMA_SxCR_EN) - ; - - // Reset DMA configuration - DMA2_Stream4->CR = 0; - - // Ensures all the status bits are cleared by explicitly clearing them - DMA2->LIFCR = 0x0F7D0F7D; // Mask excluding reserved bits - DMA2->HIFCR = 0x0F7D0F7D; - - // 2: Set the peripheral address - DMA2_Stream4->PAR = (uint32_t) & (SPI5->DR); - - // 3: Set the memory address - DMA2_Stream4->M0AR = (uint32_t)txData; - - // 4: Configure the total number of data items - DMA2_Stream4->NDTR = 2; - - // 5: Select the DMA channel - DMA2_Stream4->CR |= DMA_SxCR_CHSEL_1; // Channel 2 for SPI5 Tx - - // 7: Configure the stream priority to very high - DMA2_Stream4->CR |= DMA_SxCR_PL; - - // 9: Other configuration - - // Data transfer memory-to-peripheral - DMA2_Stream4->CR |= DMA_SxCR_DIR_0; - - // Address increment mode - DMA2_Stream4->CR |= DMA_SxCR_MINC; - - // Interrupts (transfer complete) - DMA2_Stream4->CR |= DMA_SxCR_TCIE; - - // Register interrupt - NVIC_EnableIRQ(DMA2_Stream4_IRQn); - NVIC_SetPriority(DMA2_Stream4_IRQn, 15); - } - - // DMA2 Stream3 channel 3 for SPI5 RX - { - // 1: Disable the stream - - // Disable stream - DMA2_Stream3->CR &= ~DMA_SxCR_EN; - - // Wait for the stream to be disabled - while (DMA2_Stream3->CR & DMA_SxCR_EN) - ; - - // Reset DMA configuration - DMA2_Stream3->CR = 0; - - // Ensures all the status bits are cleared by explicitly clearing them - DMA1->LIFCR = 0x0F7D0F7D; // Mask excluding reserved bits - DMA1->HIFCR = 0x0F7D0F7D; - - // 2: Set the peripheral address - DMA2_Stream3->PAR = (uint32_t) & (SPI5->DR); - - // 3: Set the memory address - DMA2_Stream3->M0AR = (uint32_t)rxData; - - // 4: Configure the total number of data items - DMA2_Stream3->NDTR = 2; - - // 5: Select the DMA channel - DMA2_Stream3->CR |= DMA_SxCR_CHSEL_1; // Channel 2 for SPI5 Rx - - // 7: Configure the stream priority to very high - DMA2_Stream3->CR |= DMA_SxCR_PL; - - // 9: Other configuration - - // Address increment mode - DMA2_Stream3->CR |= DMA_SxCR_MINC; - } - - // Transaction 1 - while (true) - { - DMA2->LIFCR = 0x0F7D0F7D; - DMA2->HIFCR = 0x0F7D0F7D; - - csPin.low(); - - // Enable DMA to start serving requests from SPI interface - DMA2_Stream3->CR |= DMA_SxCR_EN; - DMA2_Stream4->CR |= DMA_SxCR_EN; - - delayUs(1); - - // Wait for completion - while ((SPI5->SR & SPI_SR_TXE) == 0) - ; - while (SPI5->SR & SPI_SR_BSY) - ; - - csPin.high(); - - if (dmaError) - { - dmaError = false; - printf("DMA transfer error!\n"); - } - - if (dmaComplete) - { - dmaError = false; - printf("DMA transfer completed!\n"); - } - - printf("Rx data: 0x%02X%02X\n", rxData[0], rxData[1]); - rxData[0] = 0; - rxData[1] = 0; - - delayMs(1000); - } -} - -void __attribute__((naked)) DMA2_Stream4_IRQHandler() -{ - saveContext(); - asm volatile("bl _Z27DMA2_Stream4_IRQHandlerImplv"); - restoreContext(); -} - -void __attribute__((used)) DMA2_Stream4_IRQHandlerImpl() -{ - if (DMA2->HISR & DMA_HISR_TCIF4) - { - dmaComplete = true; - - // Clear the interrupt - SET_BIT(DMA2->HIFCR, DMA_HIFCR_CTCIF4); - } - - if (DMA2->HISR & DMA_HISR_TEIF4) - { - dmaError = true; - - // Clear the interrupt - SET_BIT(DMA2->HIFCR, DMA_HIFCR_CTEIF4); - } -} diff --git a/src/tests/drivers/dma/test-dma-spi.cpp b/src/tests/drivers/dma/test-dma-spi.cpp deleted file mode 100644 index 647d2e52124e556c7953e44912fd599b47a6fbed..0000000000000000000000000000000000000000 --- a/src/tests/drivers/dma/test-dma-spi.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* Copyright (c) 2021 Skyward Experimental Rocketry - * Author: Alberto Nidasio - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include <drivers/dma/DMAStream.h> -#include <drivers/spi/SPI.h> -#include <miosix.h> - -/** - * This test reads the gyroscope on the Discovery 429 using SPI with DMA. - */ - -using namespace miosix; -using namespace Boardcore; - -GpioPin csPin = GpioPin(GPIOC_BASE, 1); -GpioPin sckPin = GpioPin(GPIOF_BASE, 7); -GpioPin misoPin = GpioPin(GPIOF_BASE, 8); -GpioPin mosiPin = GpioPin(GPIOF_BASE, 9); - -SPI spi5 = SPI(SPI5); - -DMAStream txStream = DMAStream(DMA2_Stream4); -DMAStream rxStream = DMAStream(DMA2_Stream3); - -static uint8_t txData[] = {0xA8, 0x00}; -static uint8_t rxData[2] = {0}; - -void initGPIOs(); -void initSPI(); -void initDMA(); - -int main() -{ - initGPIOs(); - initSPI(); - initDMA(); - - while (true) - { - csPin.low(); - - // Enable DMA to start serving requests from SPI interface - rxStream.enable(); - txStream.enable(); - - delayUs(1); - - // Wait for completion - spi5.waitPeripheral(); - - csPin.high(); - - printf("Rx data: 0x%02X%02X\n", rxData[0], rxData[1]); - rxData[0] = 0; - rxData[1] = 0; - - delayMs(1000); - } -} - -void initGPIOs() -{ - csPin.mode(Mode::OUTPUT); - csPin.high(); - sckPin.mode(Mode::ALTERNATE); - sckPin.alternateFunction(5); - mosiPin.mode(Mode::ALTERNATE); - mosiPin.alternateFunction(5); - misoPin.mode(Mode::ALTERNATE); - misoPin.alternateFunction(5); -} - -void initSPI() -{ - spi5.reset(); - spi5.setClockDiver(SPI::ClockDivider::DIV_256); - spi5.setMode(SPI::Mode::MODE_3); - spi5.enableInternalSlaveSelection(); - spi5.enableSoftwareSlaveManagement(); - spi5.setMasterConfiguration(); - spi5.enableTxDMARequest(); - spi5.enableRxDMARequest(); - spi5.enable(); -} - -void initDMA() -{ - // 0: Enable DMA2 clock - RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; - - // DMA2 Stream4 channel 2 for SPI5 Tx - { - txStream.reset(); - txStream.setPeripheralAddress(const_cast<uint32_t*>(&(SPI5->DR))); - txStream.setMemory0Address(reinterpret_cast<uint32_t*>(txData)); - txStream.setNumberOfDataItems(2); - txStream.setStreamChannel(DMAStream::Channel::CHANNEL2); - txStream.setStreamPriorityLevel(DMAStream::PriorityLevel::VERY_HIGH); - txStream.setDataTransferDirection( - DMAStream::DataTransferDirection::MEM_TO_PERIPH); - txStream.enableMemoryIncrement(); - } - - // DMA2 Stream3 channel 2 for SPI5 Rx - { - rxStream.reset(); - rxStream.setPeripheralAddress(const_cast<uint32_t*>(&(SPI5->DR))); - rxStream.setMemory0Address(reinterpret_cast<uint32_t*>(rxData)); - rxStream.setNumberOfDataItems(2); - rxStream.setStreamChannel(DMAStream::Channel::CHANNEL2); - rxStream.setStreamPriorityLevel(DMAStream::PriorityLevel::VERY_HIGH); - rxStream.enableMemoryIncrement(); - } -}