From 53796b08c5989b8f380aa99267f21ece6681bdf4 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Thu, 13 Jul 2023 21:48:47 +0200 Subject: [PATCH] [stm32] Add STM32H7 SPI driver with DMA support --- README.md | 2 +- src/modm/platform/spi/stm32h7/module.lb | 93 +++++ src/modm/platform/spi/stm32h7/spi_base.hpp.in | 204 ++++++++++ src/modm/platform/spi/stm32h7/spi_hal.hpp.in | 209 +++++++++++ .../platform/spi/stm32h7/spi_hal_impl.hpp.in | 353 ++++++++++++++++++ .../platform/spi/stm32h7/spi_master.cpp.in | 159 ++++++++ .../platform/spi/stm32h7/spi_master.hpp.in | 138 +++++++ .../spi/stm32h7/spi_master_dma.hpp.in | 132 +++++++ .../spi/stm32h7/spi_master_dma_impl.hpp.in | 254 +++++++++++++ 9 files changed, 1543 insertions(+), 1 deletion(-) create mode 100644 src/modm/platform/spi/stm32h7/module.lb create mode 100644 src/modm/platform/spi/stm32h7/spi_base.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_hal.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master.cpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in create mode 100644 src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in diff --git a/README.md b/README.md index 80bf649f33..2f33b19e22 100644 --- a/README.md +++ b/README.md @@ -454,7 +454,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -○ +✅ ✅ ✅ ✅ diff --git a/src/modm/platform/spi/stm32h7/module.lb b/src/modm/platform/spi/stm32h7/module.lb new file mode 100644 index 0000000000..6056bc2c83 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/module.lb @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2023, Christopher Durand +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def get_properties(env): + device = env[":target"] + driver = device.get_driver("spi") + properties = {} + properties["use_fiber"] = env.query(":processing:fiber:__enabled", False) + properties["target"] = device.identifier + + return properties + +class Instance(Module): + def __init__(self, driver, instance): + self.instance = int(instance) + self.driver = driver + + def init(self, module): + module.name = str(self.instance) + module.description = "Instance {}".format(self.instance) + + def prepare(self, module, options): + module.depends(":platform:spi") + return True + + def build(self, env): + properties = get_properties(env) + properties["id"] = self.instance + + device = env[":target"] + if device.identifier.family in ["h7"]: + properties["fifo_size"] = 16 if self.instance in (1, 2, 3) else 8 + properties["max_data_bits"] = 32 if self.instance in (1, 2, 3) else 16 + properties["max_crc_bits"] = properties["max_data_bits"] + properties["max_transfer_size"] = 2**16 - 1 # is smaller for some instances on U5 + properties["i2s"] = self.instance in (1, 2, 3, 6) + else: + raise NotImplementedError("Unsupported device") + + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/spi" + + env.template("spi_hal.hpp.in", "spi_hal_{}.hpp".format(self.instance)) + env.template("spi_hal_impl.hpp.in", "spi_hal_{}_impl.hpp".format(self.instance)) + env.template("spi_master.hpp.in", "spi_master_{}.hpp".format(self.instance)) + env.template("spi_master.cpp.in", "spi_master_{}.cpp".format(self.instance)) + if env.has_module(":platform:dma"): + env.template("spi_master_dma.hpp.in", "spi_master_{}_dma.hpp".format(self.instance)) + env.template("spi_master_dma_impl.hpp.in", "spi_master_{}_dma_impl.hpp".format(self.instance)) + + +def init(module): + module.name = ":platform:spi" + module.description = "Serial Peripheral Interface (SPI)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("spi:stm32-extended"): + return False + if device.identifier.family not in ["h7"]: + # U5 is not supported yet + return False + + module.depends( + ":architecture:register", + ":architecture:spi", + ":cmsis:device", + ":math:algorithm", + ":platform:gpio", + ":platform:rcc") + + for driver in device.get_all_drivers("spi:stm32-extended"): + for instance in driver["instance"]: + module.add_submodule(Instance(driver, instance)) + + return True + +def build(env): + env.substitutions = get_properties(env) + env.outbasepath = "modm/src/modm/platform/spi" + + env.template("spi_base.hpp.in") diff --git a/src/modm/platform/spi/stm32h7/spi_base.hpp.in b/src/modm/platform/spi/stm32h7/spi_base.hpp.in new file mode 100644 index 0000000000..2c6a1e8480 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_base.hpp.in @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32H7_SPI_BASE_HPP +#define MODM_STM32H7_SPI_BASE_HPP + +#include +#include "../device.hpp" +#include + +namespace modm::platform +{ + +/** + * Base class for the SPI classes + * + * Provides common definitions that do not depend on the specific SPI instance. + * + * @ingroup modm_platform_spi + */ +class SpiBase +{ +public: + enum class + Interrupt : uint32_t + { + RxPacketAvailable = SPI_IER_RXPIE, + TxPacketSpaceAvailable = SPI_IER_TXPIE, + DuplexPacket = SPI_IER_DXPIE, + EndOfTransfer = SPI_IER_EOTIE, + TxTransferFilled = SPI_IER_TXTFIE, + Underrun = SPI_IER_UDRIE, + Overrun = SPI_IER_OVRIE, + CrcError = SPI_IER_CRCEIE, + TiFrameError = SPI_IER_TIFREIE, + ModeFault = SPI_IER_MODFIE, + Reload = SPI_IER_TSERFIE + }; + MODM_FLAGS32(Interrupt); + + enum class + StatusFlag : uint32_t + { + RxPacketAvailable = SPI_SR_RXP, + TxPacketSpaceAvailable = SPI_SR_TXP, + DuplexPacket = SPI_SR_DXP, + EndOfTransfer = SPI_SR_EOT, + TxTransferFilled = SPI_SR_TXTF, + Underrun = SPI_SR_UDR, + Overrun = SPI_SR_OVR, + CrcError = SPI_SR_CRCE, + TiFrameError = SPI_SR_TIFRE, + ModeFault = SPI_SR_MODF, + Reload = SPI_SR_TSERF, + Suspension = SPI_SR_SUSP, + TxTransferComplete = SPI_SR_TXC, + RxFifoLevel1 = SPI_SR_RXPLVL_1, + RxFifoLevel0 = SPI_SR_RXPLVL_0, + RxFifoWordNotEmpty = SPI_SR_RXWNE + }; + MODM_FLAGS32(StatusFlag); + + enum class + MasterSelection : uint32_t + { + Slave = 0, + Master = SPI_CFG2_MASTER, + Mask = Master, + }; + + enum class + DataMode : uint32_t + { + Mode0 = 0b00, ///< clock normal, sample on rising edge + Mode1 = SPI_CFG2_CPHA, ///< clock normal, sample on falling edge + Mode2 = SPI_CFG2_CPOL, ///< clock inverted, sample on falling edge + /// clock inverted, sample on rising edge + Mode3 = SPI_CFG2_CPOL | SPI_CFG2_CPHA, + Mask = Mode3 + }; + + enum class + DataOrder : uint32_t + { + MsbFirst = 0b0, + LsbFirst = SPI_CFG2_LSBFRST, + Mask = LsbFirst, + }; + + enum class + Prescaler : uint32_t + { + Div2 = 0, + Div4 = SPI_CFG1_MBR_0, + Div8 = SPI_CFG1_MBR_1, + Div16 = SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, + Div32 = SPI_CFG1_MBR_2, + Div64 = SPI_CFG1_MBR_2 | SPI_CFG1_MBR_0, + Div128 = SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1, + Div256 = SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0 + }; + + enum class + DataSize : uint32_t + { + Bit4 = 3, + Bit5 = 4, + Bit6 = 5, + Bit7 = 6, + Bit8 = 7, + Bit9 = 8, + Bit10 = 9, + Bit11 = 10, + Bit12 = 11, + Bit13 = 12, + Bit14 = 13, + Bit15 = 14, + Bit16 = 15, + Bit17 = 16, + Bit18 = 17, + Bit19 = 18, + Bit20 = 19, + Bit21 = 20, + Bit22 = 21, + Bit23 = 22, + Bit24 = 23, + Bit25 = 24, + Bit26 = 25, + Bit27 = 26, + Bit28 = 27, + Bit29 = 28, + Bit30 = 29, + Bit31 = 30, + Bit32 = 31 + }; + static_assert(SPI_CFG1_DSIZE_Pos == 0); + + enum class DmaMode : uint32_t + { + None = 0, + Tx = SPI_CFG1_TXDMAEN, + Rx = SPI_CFG1_RXDMAEN, + Mask = SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN + }; + MODM_FLAGS32(DmaMode); + + enum class DuplexMode : uint32_t + { + FullDuplex = 0, + TransmitOnly = SPI_CFG2_COMM_0, + ReceiveOnly = SPI_CFG2_COMM_1, + HalfDuplex = SPI_CFG2_COMM_1 | SPI_CFG2_COMM_0, + Mask = HalfDuplex + }; + + enum class SlaveSelectMode + { + HardwareGpio, + Software = SPI_CFG2_SSM + }; + + enum class SlaveSelectPolarity + { + ActiveLow = 0, + ActiveHigh = SPI_CFG2_SSIOP + }; + + enum class CrcInit : uint32_t + { + AllZeros = 0, + AllOnes = SPI_CR1_TCRCINI | SPI_CR1_RCRCINI, + Mask = AllOnes + }; +}; + +constexpr auto +operator<=>(SpiBase::DataSize s0, SpiBase::DataSize s1) +{ + const auto v0 = static_cast(s0); + const auto v1 = static_cast(s1); + if (v0 < v1) { + return std::strong_ordering::less; + } else if (v0 > v1) { + return std::strong_ordering::greater; + } else { + return std::strong_ordering::equal; + } +} + +} // namespace modm::platform + +#endif // MODM_STM32H7_SPI_BASE_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_hal.hpp.in b/src/modm/platform/spi/stm32h7/spi_hal.hpp.in new file mode 100644 index 0000000000..214cb1d296 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_hal.hpp.in @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2018, Niklas Hauser + * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32H7_SPI_HAL{{ id }}_HPP +#define MODM_STM32H7_SPI_HAL{{ id }}_HPP + +#include "spi_base.hpp" + +namespace modm::platform +{ + +/** + * Serial peripheral interface (SPI{{ id }}) + * + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +class SpiHal{{ id }} : public SpiBase +{ +public: + static constexpr auto peripheral = Peripheral::Spi{{ id }}; + + static constexpr DataSize MaxDataBits = DataSize::Bit{{ max_data_bits }}; + static constexpr DataSize MaxCrcBits = DataSize::Bit{{ max_crc_bits }}; + + /// Maximum permitted size for fixed size transfers + static constexpr std::size_t MaxTransferSize = {{ max_transfer_size }}; + + /// Size of FIFO in bytes + static constexpr std::size_t FifoSize = {{ fifo_size }}; + + static void + enableClock(); + + static void + disableClock(); + + static void + initialize(Prescaler prescaler, + MasterSelection masterSelection = MasterSelection::Master, + DataMode dataMode = DataMode::Mode0, + DataOrder dataOrder = DataOrder::MsbFirst, + DataSize dataSize = DataSize::Bit8); + + static void + enableTransfer(); + + static void + disableTransfer(); + + static bool + isTransferEnabled(); + + static void + startMasterTransfer(); + + static void + suspendMasterTransfer(); + + static void + setDataMode(DataMode dataMode); + + static void + setDataOrder(DataOrder dataOrder); + + static void + setDataSize(DataSize dataSize); + + static void + setMasterSelection(MasterSelection masterSelection); + + static void + setDuplexMode(DuplexMode mode); + + static void + setCrcEnabled(bool enabled); + + static void + setCrcSize(DataSize crcSize); + + static void + setCrcPolynomial(uint32_t poly); + + static void + setCrcInitialValue(CrcInit init); + + static void + setDmaMode(DmaMode_t mode); + + /** + * Configure total amount of data items of transfer + * Set size to 0 for unlimited transfers. + * @param reload Next transfer size after reload + */ + static void + setTransferSize(uint16_t size, uint16_t reload = 0); + + static void + setSlaveSelectMode(SlaveSelectMode mode); + + static void + setSlaveSelectPolarity(SlaveSelectPolarity polarity); + + static void + setSlaveSelectState(bool state); + + static uint32_t + transmitCrc(); + + static uint32_t + receiveCrc(); + + static volatile uint32_t* + transmitRegister(); + + static const volatile uint32_t* + receiveRegister(); + + static StatusFlag_t + status(); + + /// @return true if SPI_SR_EOT is set + static bool + isTransferCompleted(); + + /// @return true if SPI_SR_TXC is set + static bool + isTxCompleted(); + + /// @return true if SPI_SR_TXP is not set + static bool + isTxFifoFull(); + + /// @return true if SPI_SR_RXP is set + static bool + isRxDataAvailable(); + + /** + * Write up to 8 Bit to the transmit data register + * + * @warning Writing with a size smaller than the configured data size is not allowed. + */ + static void + write(uint8_t data); + + /** + * Write up to 16 Bit to the data register + * @warning Writing with a size smaller than the configured data size is not allowed. + */ + static void + write16(uint16_t data); + + /** + * Write up to 32 Bit to the transmit data register + */ + static void + write32(uint32_t data); + + /** + * Read an 8-bit value from the receive data register. + * + * @warning Reading with a size smaller than the configured data size is not allowed. + */ + static uint8_t + read(); + + /** + * Read a 16-bit value from the receive data register. + * + * @warning Reading with a size smaller than the configured data size is not allowed. + */ + static uint16_t + read16(); + + /** + * Read a 32-bit value from the receive data register. + */ + static uint32_t + read32(); + + static void + enableInterruptVector(bool enable, uint32_t priority); + + static void + enableInterrupt(Interrupt_t interrupt); + + static void + disableInterrupt(Interrupt_t interrupt); + + static void + acknowledgeInterruptFlags(StatusFlag_t flags); +}; + +} // namespace modm::platform + +#include "spi_hal_{{ id }}_impl.hpp" + +#endif // MODM_STM32H7_SPI_HAL{{ id }}_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in new file mode 100644 index 0000000000..77770cf4bd --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in @@ -0,0 +1,353 @@ +/* +* Copyright (c) 2013, Kevin Läufer +* Copyright (c) 2013-2017, Niklas Hauser +* Copyright (c) 2014, Daniel Krebs +* Copyright (c) 2020, Mike Wolfram +* Copyright (c) 2023, Christopher Durand +* +* This file is part of the modm project. +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32H7_SPI_HAL{{ id }}_HPP +# error "Don't include this file directly, use 'spi_hal{{ id }}.hpp' instead!" +#endif +#include + +namespace modm::platform +{ + +inline void +SpiHal{{ id }}::enableClock() +{ + Rcc::enable(); +} + +inline void +SpiHal{{ id }}::disableClock() +{ + Rcc::disable(); +} + +inline void +SpiHal{{ id }}::initialize(Prescaler prescaler, + MasterSelection masterSelection, DataMode dataMode, + DataOrder dataOrder, DataSize dataSize) +{ + enableClock(); + disableTransfer(); + + acknowledgeInterruptFlags( + StatusFlag::EndOfTransfer | + StatusFlag::TxTransferFilled | + StatusFlag::Underrun | + StatusFlag::Overrun | + StatusFlag::CrcError | + StatusFlag::TiFrameError | + StatusFlag::ModeFault | + StatusFlag::Reload | + StatusFlag::Suspension + ); + + // initialize with unlimited transfer size + setTransferSize(0); + + // Pause master transfer if RX FIFO is full + SPI{{ id }}->CR1 = SPI_CR1_MASRX; + + SPI{{ id }}->CFG2 = static_cast(dataMode) + | static_cast(dataOrder) + | static_cast(masterSelection) + | SPI_CFG2_SSOE; // disable multi-master support to prevent spurious mode fault + + SPI{{ id }}->CFG1 = static_cast(prescaler) + | static_cast(dataSize); +} + +inline void +SpiHal{{ id }}::setDataMode(DataMode dataMode) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(DataMode::Mask)) + | static_cast(dataMode); +} + +inline void +SpiHal{{ id }}::setDataOrder(DataOrder dataOrder) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(DataOrder::Mask)) + | static_cast(dataOrder); +} + +inline void +SpiHal{{ id }}::setDataSize(DataSize dataSize) +{ + SPI{{ id }}->CFG1 = (SPI{{ id }}->CFG1 & ~SPI_CFG1_DSIZE_Msk) + | static_cast(dataSize); +} + +inline void +SpiHal{{ id }}::setMasterSelection(MasterSelection masterSelection) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(MasterSelection::Mask)) + | static_cast(masterSelection); +} + +inline void +SpiHal{{ id }}::setDuplexMode(DuplexMode mode) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~static_cast(DuplexMode::Mask)) + | static_cast(mode); +} + +inline void +SpiHal{{ id }}::setCrcEnabled(bool enabled) +{ + if (enabled) { + SPI{{ id }}->CFG1 |= SPI_CFG1_CRCEN; + } else { + SPI{{ id }}->CFG1 &= ~SPI_CFG1_CRCEN; + } +} + +inline void +SpiHal{{ id }}::setCrcSize(DataSize crcSize) +{ + if ((crcSize == DataSize::Bit16) or (crcSize == DataSize::Bit32)) { + SPI{{ id }}->CR1 |= SPI_CR1_CRC33_17; + } else { + SPI{{ id }}->CR1 &= ~SPI_CR1_CRC33_17; + } + SPI{{ id }}->CFG1 = (SPI{{ id }}->CFG1 & ~SPI_CFG1_CRCSIZE_Msk) + | (static_cast(crcSize) << SPI_CFG1_CRCSIZE_Pos); +} + +inline void +SpiHal{{ id }}::setCrcPolynomial(uint32_t poly) +{ + SPI{{ id }}->CRCPOLY = poly; +} + +inline void +SpiHal{{ id }}::setCrcInitialValue(CrcInit init) +{ + SPI{{ id }}->CR1 = (SPI{{ id }}->CR1 & ~static_cast(CrcInit::Mask)) | + static_cast(init); +} + +inline void +SpiHal{{ id }}::setTransferSize(uint16_t size, uint16_t reload) +{ + static_assert(SPI_CR2_TSIZE_Pos == 0); + SPI{{ id }}->CR2 = (reload << SPI_CR2_TSER_Pos) | size; +} + +inline void +SpiHal{{ id }}::setDmaMode(DmaMode_t mode) +{ + SPI{{ id }}->CFG1 = (SPI{{ id }}->CFG1 & ~(DmaMode::Tx | DmaMode::Rx).value) + | mode.value; +} + +inline void +SpiHal{{ id }}::setSlaveSelectMode(SlaveSelectMode mode) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~SPI_CFG2_SSM_Msk) + | uint32_t(mode); +} + +inline void +SpiHal{{ id }}::setSlaveSelectPolarity(SlaveSelectPolarity polarity) +{ + SPI{{ id }}->CFG2 = (SPI{{ id }}->CFG2 & ~SPI_CFG2_SSIOP_Msk) + | uint32_t(polarity); +} + +inline void +SpiHal{{ id }}::setSlaveSelectState(bool state) +{ + if (state) { + SPI{{ id }}->CR1 |= SPI_CR1_SSI; + } else { + SPI{{ id }}->CR1 &= ~SPI_CR1_SSI; + } +} + +inline void +SpiHal{{ id }}::write(uint8_t data) +{ + // Write with 8-bit access + auto* const ptr = reinterpret_cast<__IO uint8_t*>(&SPI{{ id }}->TXDR); + *ptr = data; +} + +inline void +SpiHal{{ id }}::write16(uint16_t data) +{ + // Write with 16-bit access + // SPI{{ id }}->TXDR is of type "volatile uint32_t". + // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. + auto* const [[gnu::may_alias]] ptr = reinterpret_cast<__IO uint16_t*>(&SPI{{ id }}->TXDR); + *ptr = data; +} + +inline void +SpiHal{{ id }}::write32(uint32_t data) +{ + SPI{{ id }}->TXDR = data; +} + +inline uint8_t +SpiHal{{ id }}::read() +{ + // Read with 8-bit access + return *reinterpret_cast(&SPI{{ id }}->RXDR); +} + +inline uint16_t +SpiHal{{ id }}::read16() +{ + // Read with 16-bit access + // SPI{{ id }}->RXDR is of type "const volatile uint32_t". + // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. + auto* const [[gnu::may_alias]] ptr = reinterpret_cast(&SPI{{ id }}->RXDR); + return *ptr; +} + +inline uint32_t +SpiHal{{ id }}::read32() +{ + return SPI{{ id }}->RXDR; +} + +inline void +SpiHal{{ id }}::enableInterruptVector(bool enable, uint32_t priority) +{ + if (enable) { + // Set priority for the interrupt vector + NVIC_SetPriority(SPI{{ id }}_IRQn, priority); + // register IRQ at the NVIC + NVIC_EnableIRQ(SPI{{ id }}_IRQn); + } + else { + NVIC_DisableIRQ(SPI{{ id }}_IRQn); + } +} + +inline void +SpiHal{{ id }}::enableInterrupt(Interrupt_t interrupt) +{ + SPI{{ id }}->IER |= interrupt.value; +} + +inline void +SpiHal{{ id }}::disableInterrupt(Interrupt_t interrupt) +{ + SPI{{ id }}->IER &= ~interrupt.value; +} + +inline void +SpiHal{{ id }}::acknowledgeInterruptFlags(StatusFlag_t flags) +{ + constexpr auto mask = + SPI_IFCR_EOTC | + SPI_IFCR_TXTFC | + SPI_IFCR_UDRC | + SPI_IFCR_OVRC | + SPI_IFCR_CRCEC | + SPI_IFCR_TIFREC | + SPI_IFCR_MODFC | + SPI_IFCR_TSERFC | + SPI_IFCR_SUSPC; + + SPI{{ id }}->IFCR = flags.value & mask; +} + +inline SpiHal{{ id }}::StatusFlag_t +SpiHal{{ id }}::status() +{ + return StatusFlag_t(SPI{{ id }}->SR); +} + +inline bool +SpiHal{{ id }}::isTransferCompleted() +{ + return bool(status() & StatusFlag::EndOfTransfer); +} + +inline bool +SpiHal{{ id }}::isTxCompleted() +{ + return bool(status() & StatusFlag::TxTransferComplete); +} + +inline bool +SpiHal{{ id }}::isTxFifoFull() +{ + return !(status() & StatusFlag::TxPacketSpaceAvailable); +} + +inline bool +SpiHal{{ id }}::isRxDataAvailable() +{ + return bool(status() & StatusFlag::RxPacketAvailable); +} + +inline void +SpiHal{{ id }}::enableTransfer() +{ + SPI{{ id }}->CR1 |= SPI_CR1_SPE; +} + +inline void +SpiHal{{ id }}::disableTransfer() +{ + SPI{{ id }}->CR1 &= ~SPI_CR1_SPE; +} + +inline bool +SpiHal{{ id }}::isTransferEnabled() +{ + return (SPI{{ id }}->CR1 & SPI_CR1_SPE); +} + +inline void +SpiHal{{ id }}::startMasterTransfer() +{ + SPI{{ id }}->CR1 |= SPI_CR1_CSTART; +} + +inline void +SpiHal{{ id }}::suspendMasterTransfer() +{ + SPI{{ id }}->CR1 |= SPI_CR1_CSUSP; +} + +inline uint32_t +SpiHal{{ id }}::transmitCrc() +{ + return SPI{{ id }}->TXCRC; +} + +inline uint32_t +SpiHal{{ id }}::receiveCrc() +{ + return SPI{{ id }}->RXCRC; +} + +inline volatile uint32_t* +SpiHal{{ id }}::transmitRegister() +{ + return &SPI{{ id }}->TXDR; +} + +inline const volatile uint32_t* +SpiHal{{ id }}::receiveRegister() +{ + return &SPI{{ id }}->RXDR; +} + +} // namespace modm::platform diff --git a/src/modm/platform/spi/stm32h7/spi_master.cpp.in b/src/modm/platform/spi/stm32h7/spi_master.cpp.in new file mode 100644 index 0000000000..02b498a617 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master.cpp.in @@ -0,0 +1,159 @@ +/* +* Copyright (c) 2009, Martin Rosekeit +* Copyright (c) 2009-2012, Fabian Greif +* Copyright (c) 2010, Georgi Grinshpun +* Copyright (c) 2012-2017, Niklas Hauser +* Copyright (c) 2013, Kevin Läufer +* Copyright (c) 2014, Sascha Schade +* Copyright (c) 2023, Christopher Durand +* +* This file is part of the modm project. +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +// ---------------------------------------------------------------------------- + +#include "spi_master_{{id}}.hpp" + +namespace modm::platform +{ + +modm::ResumableResult +SpiMaster{{ id }}::transfer(uint8_t data) +{ +%% if use_fiber + while (Hal::isTxFifoFull()) + modm::fiber::yield(); + + Hal::write(data); + + while (!Hal::isRxDataAvailable()) + modm::fiber::yield(); + + return Hal::read(); +%% else + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (LSB of state): + // 1. waiting to start, and + // 2. waiting to finish. + + if (!(state & Bit0)) + { + // wait for previous transfer to finish + if (Hal::isTxFifoFull()) + return {modm::rf::Running}; + + Hal::write(data); + + // set LSB = Bit0 + state |= Bit0; + } + + if (!Hal::isRxDataAvailable()) + return {modm::rf::Running}; + + data = Hal::read(); + + state &= ~Bit0; + return {modm::rf::Stop, data}; +%% endif +} + +modm::ResumableResult +SpiMaster{{ id }}::transfer( + const uint8_t* tx, uint8_t* rx, std::size_t length) +{ +%% if use_fiber + std::size_t rxIndex = 0; + std::size_t txIndex = 0; + + while (rxIndex < length) { + while ((txIndex < length) and !Hal::isTxFifoFull()) { + Hal::write(tx ? tx[txIndex] : 0); + ++txIndex; + } + while ((rxIndex < length) and Hal::isRxDataAvailable()) { + const uint8_t data = Hal::read(); + if (rx) { + rx[rxIndex] = data; + } + ++rxIndex; + } + if (rxIndex < length) { + modm::fiber::yield(); + } + } +%% else + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (Bit1 of state): + // 1. initialize index, and + // 2. wait for transfer to finish. + + // we need to globally remember which byte we are currently transferring + static std::size_t rxIndex = 0; + static std::size_t txIndex = 0; + + // we are only interested in Bit1 + switch(state & Bit1) + { + case 0: + // we will only visit this state once + state |= Bit1; + + // initialize index and check range + rxIndex = 0; + txIndex = 0; + while (rxIndex < length) { + default: + { + while ((txIndex < length) and !Hal::isTxFifoFull()) { + Hal::write(tx ? tx[txIndex] : 0); + ++txIndex; + } + while ((rxIndex < length) and Hal::isRxDataAvailable()) { + if (rx) { + rx[rxIndex] = Hal::read(); + } else { + Hal::read(); + } + ++rxIndex; + } + if (rxIndex < length) { + return {modm::rf::Running}; + } + } + } + + // clear the state + state &= ~Bit1; + return {modm::rf::Stop}; + } +%% endif +} + +void +SpiMaster{{ id }}::finishTransfer() +{ + if (Hal::isTransferEnabled()) { + while (!(Hal::status() & Hal::StatusFlag::TxTransferComplete)); + Hal::disableTransfer(); + } + + Hal::acknowledgeInterruptFlags( + Hal::StatusFlag::EndOfTransfer | + Hal::StatusFlag::TxTransferFilled | + Hal::StatusFlag::Underrun | + Hal::StatusFlag::Overrun | + Hal::StatusFlag::CrcError | + Hal::StatusFlag::TiFrameError | + Hal::StatusFlag::ModeFault | + Hal::StatusFlag::Reload | + Hal::StatusFlag::Suspension + ); +} + +} // namespace modm::platform diff --git a/src/modm/platform/spi/stm32h7/spi_master.hpp.in b/src/modm/platform/spi/stm32h7/spi_master.hpp.in new file mode 100644 index 0000000000..f44a0883a9 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master.hpp.in @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2011, Fabian Greif + * Copyright (c) 2010, Martin Rosekeit + * Copyright (c) 2011-2017, Niklas Hauser + * Copyright (c) 2012, Georgi Grinshpun + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2014, Sascha Schade + * Copyright (c) 2022-2023, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32H7_SPI_MASTER{{ id }}_HPP +#define MODM_STM32H7_SPI_MASTER{{ id }}_HPP + +#include +#include +#include +#include +%% if use_fiber +#include +%% endif +#include "spi_hal_{{ id }}.hpp" + +namespace modm::platform +{ + +/** + * Serial peripheral interface (SPI{{ id }}). + * + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +class SpiMaster{{ id }} : public modm::SpiMaster, public SpiLock +{ +%% if not use_fiber + // Bit0: single transfer state + // Bit1: block transfer state + static inline uint8_t state{0}; +%% endif +public: + using Hal = SpiHal{{ id }}; + + using DataMode = Hal::DataMode; + using DataOrder = Hal::DataOrder; + using DataSize = Hal::DataSize; + + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Sck = typename Connector::template GetSignal; + using Mosi = typename Connector::template GetSignal; + using Miso = typename Connector::template GetSignal; + + Sck::setOutput(Gpio::OutputType::PushPull); + Mosi::setOutput(Gpio::OutputType::PushPull); + Miso::setInput(Gpio::InputType::Floating); + Connector::connect(); + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(5) > + static void + initialize() + { + constexpr auto result = modm::Prescaler::from_power(SystemClock::Spi{{ id }}, baudrate, 2, 256); + assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); + + // translate the prescaler into the bitmapping + constexpr Hal::Prescaler prescaler{result.index << SPI_CFG1_MBR_Pos}; +%% if not use_fiber + state = 0; +%% endif + + Hal::initialize(prescaler); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static void + setDataMode(DataMode mode) + { + finishTransfer(); + Hal::setDataMode(mode); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static void + setDataOrder(DataOrder order) + { + finishTransfer(); + Hal::setDataOrder(order); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static void + setDataSize(DataSize size) + { + finishTransfer(); + Hal::setDataSize(static_cast(size)); + Hal::enableTransfer(); + Hal::startMasterTransfer(); + } + + static uint8_t + transferBlocking(uint8_t data) + { + return RF_CALL_BLOCKING(transfer(data)); + } + + static void + transferBlocking(const uint8_t* tx, uint8_t* rx, std::size_t length) + { + RF_CALL_BLOCKING(transfer(tx, rx, length)); + } + + + static modm::ResumableResult + transfer(uint8_t data); + + static modm::ResumableResult + transfer(const uint8_t* tx, uint8_t* rx, std::size_t length); + +private: + static void + finishTransfer(); +}; + +} // namespace modm::platform + +#endif // MODM_STM32H7_SPI_MASTER{{ id }}_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in new file mode 100644 index 0000000000..009fabe238 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master_dma.hpp.in @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32H7_SPI_MASTER{{ id }}_DMA_HPP +#define MODM_STM32H7_SPI_MASTER{{ id }}_DMA_HPP + +#include +#include "spi_master_{{ id }}.hpp" + +namespace modm::platform +{ + +/** + * Serial peripheral interface (SPI{{ id }}) with DMA support. + * + * This class uses the DMA controller for sending and receiving data, which + * greatly reduces the CPU load. Beside passing the DMA channels as template + * parameters the class can be used in the same way like the SpiMaster{{ id }}. + * + * @tparam DmaChannelRX DMA channel for receiving + * @tparam DmaChannelTX DMA channel for sending + * + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +template +class SpiMaster{{ id }}_Dma : public modm::SpiMaster, + public SpiLock> +{ +protected: + struct Dma { + using RxChannel = typename DmaChannelRx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Rx>::Channel; + using TxChannel = typename DmaChannelTx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Tx>::Channel; + static constexpr DmaBase::Request RxRequest = DmaChannelRx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Rx>::Request; + static constexpr DmaBase::Request TxRequest = DmaChannelTx::template RequestMapping< + Peripheral::Spi{{ id }}, DmaBase::Signal::Tx>::Request; + }; + +%% if not use_fiber + // Bit0: single transfer state + // Bit1: block transfer state + // Bit2: block transfer rx dma transfer completed + static inline uint8_t state{0}; +%% endif +public: + using Hal = SpiHal{{ id }}; + + using DataMode = Hal::DataMode; + using DataOrder = Hal::DataOrder; + using DataSize = Hal::DataSize; + + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Sck = typename Connector::template GetSignal; + using Mosi = typename Connector::template GetSignal; + using Miso = typename Connector::template GetSignal; + + Sck::setOutput(Gpio::OutputType::PushPull); + Mosi::setOutput(Gpio::OutputType::PushPull); + Miso::setInput(Gpio::InputType::Floating); + Connector::connect(); + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(5) > + static void + initialize(); + + static void + setDataMode(DataMode mode) + { + Hal::setDataMode(mode); + } + + static void + setDataOrder(DataOrder order) + { + Hal::setDataOrder(order); + } + + static void + setDataSize(DataSize size) + { + Hal::setDataSize(static_cast(size)); + } + + static uint8_t + transferBlocking(uint8_t data) + { + return RF_CALL_BLOCKING(transfer(data)); + } + + /// @pre At least one of tx or rx must not be nullptr + static void + transferBlocking(const uint8_t* tx, uint8_t* rx, std::size_t length) + { + RF_CALL_BLOCKING(transfer(tx, rx, length)); + } + + static modm::ResumableResult + transfer(uint8_t data); + + /// @pre At least one of tx or rx must not be nullptr + static modm::ResumableResult + transfer(const uint8_t* tx, uint8_t* rx, std::size_t length); + +protected: + static void + finishTransfer(); + + static void + startDmaTransfer(const uint8_t* tx, uint8_t* rx, std::size_t length); +}; + +} // namespace modm::platform + +#include "spi_master_{{ id }}_dma_impl.hpp" + +#endif // MODM_STM32_SPI_MASTER{{ id }}_DMA_HPP diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in new file mode 100644 index 0000000000..10aa8d2ab2 --- /dev/null +++ b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2023, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32H7_SPI_MASTER{{ id }}_DMA_HPP +# error "Don't include this file directly, use 'spi_master_{{ id }}_dma.hpp' instead!" +#endif + +namespace modm::platform +{ + +template +template +void +SpiMaster{{ id }}_Dma::initialize() +{ + Dma::RxChannel::configure(DmaBase::DataTransferDirection::PeripheralToMemory, + DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, + DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, + DmaBase::Priority::Medium); + Dma::RxChannel::disableInterruptVector(); + Dma::RxChannel::setPeripheralAddress(reinterpret_cast(Hal::receiveRegister())); + Dma::RxChannel::template setPeripheralRequest(); + + Dma::TxChannel::configure(DmaBase::DataTransferDirection::MemoryToPeripheral, + DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, + DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, + DmaBase::Priority::Medium); + Dma::TxChannel::disableInterruptVector(); + Dma::TxChannel::setPeripheralAddress(reinterpret_cast(Hal::transmitRegister())); + Dma::TxChannel::template setPeripheralRequest(); + +%% if not use_fiber + state = 0; +%% endif + + constexpr auto result = modm::Prescaler::from_power(SystemClock::Spi{{ id }}, baudrate, 2, 256); + assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); + + constexpr Hal::Prescaler prescaler{result.index << SPI_CFG1_MBR_Pos}; + Hal::initialize(prescaler); +} + +template +modm::ResumableResult +SpiMaster{{ id }}_Dma::transfer(uint8_t data) +{ + // DMA is not used for single byte transfers +%% if use_fiber + Hal::setDuplexMode(Hal::DuplexMode::FullDuplex); + Hal::setTransferSize(1); + Hal::enableTransfer(); + Hal::write(data); + Hal::startMasterTransfer(); + + // wait for transfer to complete + while (!Hal::isTransferCompleted()) + modm::fiber::yield(); + + data = SpiHal{{ id }}::read(); + finishTransfer(); + + return data; +%% else + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (LSB of state): + // 1. waiting to start, and + // 2. waiting to finish. + // LSB != Bit0 ? + if ( !(state & Bit0) ) + { + Hal::setDuplexMode(Hal::DuplexMode::FullDuplex); + Hal::setTransferSize(1); + Hal::enableTransfer(); + Hal::write(data); + Hal::startMasterTransfer(); + + // set LSB = Bit0 + state |= Bit0; + } + + if (!Hal::isTransferCompleted()) + return {modm::rf::Running}; + + data = SpiHal{{ id }}::read(); + finishTransfer(); + + // transfer finished + state &= ~Bit0; + return {modm::rf::Stop, data}; +%% endif +} + +template +void +SpiMaster{{ id }}_Dma::startDmaTransfer( + const uint8_t* tx, uint8_t* rx, std::size_t length) +{ + Hal::setTransferSize(length); + + if (tx and rx) { + Hal::setDuplexMode(Hal::DuplexMode::FullDuplex); + } else if (rx) { + Hal::setDuplexMode(Hal::DuplexMode::ReceiveOnly); + } else { // tx only + Hal::setDuplexMode(Hal::DuplexMode::TransmitOnly); + } + + /* + * Required order of operations according to the reference manual: + * 1. Enable SPI RX DMA + * 2. Enable DMA channels + * 3. Enable SPI TX DMA + * 4. Start transfer + */ + if (rx) { + Dma::RxChannel::setMemoryAddress(reinterpret_cast(rx)); + Dma::RxChannel::setDataLength(length); + Hal::setDmaMode(Hal::DmaMode::Rx); + Dma::RxChannel::start(); + } + + if (tx) { + Dma::TxChannel::setMemoryAddress(reinterpret_cast(tx)); + Dma::TxChannel::setDataLength(length); + Dma::TxChannel::start(); + if (rx) { + Hal::setDmaMode(Hal::DmaMode::Tx | Hal::DmaMode::Rx); + } else { + Hal::setDmaMode(Hal::DmaMode::Tx); + } + } + + Hal::enableTransfer(); + Hal::startMasterTransfer(); +} + + +template +modm::ResumableResult +SpiMaster{{ id }}_Dma::transfer( + const uint8_t* tx, uint8_t* rx, std::size_t length) +{ + using Flags = DmaBase::InterruptFlags; +%% if use_fiber + startDmaTransfer(tx, rx, length); + + bool dmaRxFinished = (rx == nullptr); + while (!Hal::isTransferCompleted() or !dmaRxFinished) { + if(rx) { + const auto flags = DmaChannelRx::getInterruptFlags(); + if (flags & Flags::Error) { + break; + } + if (flags & Flags::TransferComplete) { + dmaRxFinished = true; + } + } + if(tx) { + const auto flags = DmaChannelTx::getInterruptFlags(); + if (flags & Flags::Error) { + break; + } + } + modm::fiber::yield(); + } + finishTransfer(); +%% else + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (Bit1 of state): + // 1. initialize index, and + // 2. wait for 1-byte transfer to finish. + + // we are only interested in Bit1 + switch(state & Bit1) + { + case 0: + // we will only visit this state once + state |= Bit1; + startDmaTransfer(tx, rx, length); + if (!rx) { + state |= Bit2; + } + [[fallthrough]]; + + default: + if (!Hal::isTransferCompleted() or !(state & Bit2)) { + if(rx) { + static DmaBase::InterruptFlags_t flags; + flags = DmaChannelRx::getInterruptFlags(); + if (flags & Flags::Error) { + // abort on DMA error + finishTransfer(); + state &= ~(Bit2 | Bit1); + return {modm::rf::Stop}; + } + if (flags & Flags::TransferComplete) { + state |= Bit2; + } + } + if(tx) { + if (DmaChannelTx::getInterruptFlags() & Flags::Error) { + // abort on DMA error + finishTransfer(); + state &= ~(Bit2 | Bit1); + return {modm::rf::Stop}; + } + } + return { modm::rf::Running }; + } + + finishTransfer(); + + // clear the state + state &= ~(Bit2 | Bit1); + return {modm::rf::Stop}; + } +%% endif +} + +template +void +SpiMaster{{ id }}_Dma::finishTransfer() +{ + DmaChannelTx::stop(); + DmaChannelRx::stop(); + + Hal::setDmaMode(Hal::DmaMode::None); + Hal::disableTransfer(); + + Hal::acknowledgeInterruptFlags( + Hal::StatusFlag::EndOfTransfer | + Hal::StatusFlag::TxTransferFilled | + Hal::StatusFlag::Underrun | + Hal::StatusFlag::Overrun | + Hal::StatusFlag::CrcError | + Hal::StatusFlag::TiFrameError | + Hal::StatusFlag::ModeFault | + Hal::StatusFlag::Reload | + Hal::StatusFlag::Suspension + ); +} + +} // namespace modm::platform