From 80a9c666af1bbe8eb978abed48334e9310cceb0f Mon Sep 17 00:00:00 2001 From: Mike Wolfram Date: Thu, 7 May 2020 14:24:57 +0200 Subject: [PATCH] [spi] Enhance STM32 SPI with DMA support --- examples/nucleo_l432kc/spi_dma/main.cpp | 44 +++++ examples/nucleo_l432kc/spi_dma/project.xml | 12 ++ src/modm/board/nucleo_l432kc/board.hpp | 3 + src/modm/platform/spi/stm32/module.lb | 3 + src/modm/platform/spi/stm32/spi_base.hpp.in | 11 ++ src/modm/platform/spi/stm32/spi_hal.hpp.in | 6 + .../platform/spi/stm32/spi_hal_impl.hpp.in | 10 + .../platform/spi/stm32/spi_master_dma.hpp.in | 99 ++++++++++ .../spi/stm32/spi_master_dma_impl.hpp.in | 185 ++++++++++++++++++ 9 files changed, 373 insertions(+) create mode 100644 examples/nucleo_l432kc/spi_dma/main.cpp create mode 100644 examples/nucleo_l432kc/spi_dma/project.xml create mode 100644 src/modm/platform/spi/stm32/spi_master_dma.hpp.in create mode 100644 src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in diff --git a/examples/nucleo_l432kc/spi_dma/main.cpp b/examples/nucleo_l432kc/spi_dma/main.cpp new file mode 100644 index 0000000000..3c5b45d0f1 --- /dev/null +++ b/examples/nucleo_l432kc/spi_dma/main.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * + * 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 + +using Mosi = GpioOutputB5; +using Miso = GpioInputB4; +using Sck = GpioOutputB3; +using DmaRx = Dma1::Channel2; +using DmaTx = Dma1::Channel3; +using Spi = SpiMaster1_Dma; + +int main() +{ + Board::initialize(); + + // Enable DMA1 controller + Dma1::enable(); + // Enable SPI1 + Spi::connect(); + Spi::initialize(); + + while (true) + { + uint8_t sendBuffer[13] { "data to send" }; + uint8_t receiveBuffer[13]; + + // send out 12 bytes, don't care about response + Spi::transferBlocking(sendBuffer, nullptr, 12); + + // send out 12 bytes, read in 12 bytes + Spi::transferBlocking(sendBuffer, receiveBuffer, 12); + } + + return 0; +} diff --git a/examples/nucleo_l432kc/spi_dma/project.xml b/examples/nucleo_l432kc/spi_dma/project.xml new file mode 100644 index 0000000000..a7c7453465 --- /dev/null +++ b/examples/nucleo_l432kc/spi_dma/project.xml @@ -0,0 +1,12 @@ + + modm:nucleo-l432kc + + + + + modm:platform:gpio + modm:platform:dma + modm:platform:spi:1 + modm:build:scons + + diff --git a/src/modm/board/nucleo_l432kc/board.hpp b/src/modm/board/nucleo_l432kc/board.hpp index 5513d5fc0a..f1d4ed8165 100644 --- a/src/modm/board/nucleo_l432kc/board.hpp +++ b/src/modm/board/nucleo_l432kc/board.hpp @@ -40,6 +40,9 @@ struct SystemClock { static constexpr uint32_t Usart4 = Apb1; static constexpr uint32_t Usart5 = Apb1; + static constexpr uint32_t Spi1 = Apb2; + static constexpr uint32_t Spi2 = Apb2; + static bool inline enable() { diff --git a/src/modm/platform/spi/stm32/module.lb b/src/modm/platform/spi/stm32/module.lb index ae6ab449d3..783b3eb8a3 100644 --- a/src/modm/platform/spi/stm32/module.lb +++ b/src/modm/platform/spi/stm32/module.lb @@ -43,6 +43,9 @@ class Instance(Module): 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): diff --git a/src/modm/platform/spi/stm32/spi_base.hpp.in b/src/modm/platform/spi/stm32/spi_base.hpp.in index d8fad02aab..875fb3ac03 100644 --- a/src/modm/platform/spi/stm32/spi_base.hpp.in +++ b/src/modm/platform/spi/stm32/spi_base.hpp.in @@ -2,6 +2,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013-2017, Niklas Hauser * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram * * This file is part of the modm project. * @@ -41,6 +42,8 @@ public: RxBufferNotEmpty = SPI_CR2_RXNEIE, TxBufferEmpty = SPI_CR2_TXEIE, Error = SPI_CR2_ERRIE, + RxDmaEnable = SPI_CR2_RXDMAEN, + TxDmaEnable = SPI_CR2_TXDMAEN, }; MODM_FLAGS32(Interrupt); @@ -128,6 +131,14 @@ public: Div256 = SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0, }; +%% if "fifo" in features + enum class + RxFifoThreshold : uint32_t + { + HalfFull = 0, + QuarterFull = SPI_CR2_FRXTH, + }; +%% endif }; } // namespace platform diff --git a/src/modm/platform/spi/stm32/spi_hal.hpp.in b/src/modm/platform/spi/stm32/spi_hal.hpp.in index 2342300a39..cac0022289 100644 --- a/src/modm/platform/spi/stm32/spi_hal.hpp.in +++ b/src/modm/platform/spi/stm32/spi_hal.hpp.in @@ -2,6 +2,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013-2018, Niklas Hauser * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram * * This file is part of the modm project. * @@ -67,6 +68,11 @@ public: static void setMasterSelection(MasterSelection masterSelection); +%% if "fifo" in features + static void + setRxFifoThreshold(RxFifoThreshold threshold); +%% endif + /// Returns true if data has been received static bool isReceiveRegisterNotEmpty(); diff --git a/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in b/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in index f63c4658fd..75d5170a8b 100644 --- a/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in +++ b/src/modm/platform/spi/stm32/spi_hal_impl.hpp.in @@ -2,6 +2,7 @@ * Copyright (c) 2013, Kevin Läufer * Copyright (c) 2013-2017, Niklas Hauser * Copyright (c) 2014, Daniel Krebs + * Copyright (c) 2020, Mike Wolfram * * This file is part of the modm project. * @@ -97,6 +98,15 @@ modm::platform::SpiHal{{ id }}::setMasterSelection(MasterSelection masterSelecti | static_cast(masterSelection); } +%% if "fifo" in features +inline void +modm::platform::SpiHal{{ id }}::setRxFifoThreshold(RxFifoThreshold threshold) +{ + SPI{{ id }}->CR2 = (SPI{{ id }}->CR2 & ~static_cast(RxFifoThreshold::QuarterFull)) + | static_cast(threshold); +} +%% endif + inline bool modm::platform::SpiHal{{ id }}::isReceiveRegisterNotEmpty() { diff --git a/src/modm/platform/spi/stm32/spi_master_dma.hpp.in b/src/modm/platform/spi/stm32/spi_master_dma.hpp.in new file mode 100644 index 0000000000..24878b1680 --- /dev/null +++ b/src/modm/platform/spi/stm32/spi_master_dma.hpp.in @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * + * 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_STM32_SPI_MASTER{{ id }}_DMA_HPP +#define MODM_STM32_SPI_MASTER{{ id }}_DMA_HPP + +#include +#include "spi_master_{{ id }}.hpp" + +namespace modm +{ + +namespace 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 + * + * @author Mike Wolfram + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +template +class SpiMaster{{ id }}_Dma : public SpiMaster{{ id }} +{ + 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; + }; + +public: + /** + * Initialize DMA and SPI{{ id }} + */ + template + static void + initialize(); + + static uint8_t + transferBlocking(uint8_t data) + { + return RF_CALL_BLOCKING(transfer(data)); + } + + static void + transferBlocking(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(uint8_t *tx, uint8_t *rx, std::size_t length); + +private: + static void + handleDmaTransferError(); + static void + handleDmaReceiveComplete(); + static void + handleDmaTransmitComplete(); + + static inline bool dmaError { false }; + static inline bool dmaTransmitComplete { false }; + static inline bool dmaReceiveComplete { false }; + + // needed for transfers where no RX or TX buffers are given + static inline uint8_t dmaDummy { 0 }; +}; + +} // namespace platform + +} // namespace modm + +#include "spi_master_{{ id }}_dma_impl.hpp" + +#endif // MODM_STM32_SPI_MASTER{{ id }}_DMA_HPP diff --git a/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in new file mode 100644 index 0000000000..5ca44002db --- /dev/null +++ b/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * + * 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_STM32_SPI_MASTER{{ id }}_DMA_HPP +# error "Don't include this file directly, use 'spi_master_{{ id }}_dma.hpp' instead!" +#endif + +template +template +void +modm::platform::SpiMaster{{ id }}_Dma::initialize() +{ + // Configure the DMA channels, then calls SpiMaster{{ id }}::initialzie(). + Dma::RxChannel::configure(DmaBase::DataTransferDirection::PeripheralToMemory, + DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, + DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, + DmaBase::Priority::High); + Dma::RxChannel::setPeripheralAddress(SPI{{ id }}_BASE + 0x0c); + Dma::RxChannel::setTransferErrorIrqHandler(handleDmaTransferError); + Dma::RxChannel::setTransferCompleteIrqHandler(handleDmaReceiveComplete); + Dma::RxChannel::enableInterruptVector(); + Dma::RxChannel::enableInterrupt(DmaBase::Interrupt::Error | + DmaBase::Interrupt::TransferComplete); + Dma::RxChannel::template setPeripheralRequest(); + + Dma::TxChannel::configure(DmaBase::DataTransferDirection::MemoryToPeripheral, + DmaBase::MemoryDataSize::Byte, DmaBase::PeripheralDataSize::Byte, + DmaBase::MemoryIncrementMode::Increment, DmaBase::PeripheralIncrementMode::Fixed, + DmaBase::Priority::High); + Dma::TxChannel::setPeripheralAddress(SPI{{ id }}_BASE + 0x0c); + Dma::TxChannel::setTransferErrorIrqHandler(handleDmaTransferError); + Dma::TxChannel::setTransferCompleteIrqHandler(handleDmaTransmitComplete); + Dma::TxChannel::enableInterruptVector(); + Dma::TxChannel::enableInterrupt(DmaBase::Interrupt::Error | + DmaBase::Interrupt::TransferComplete); + Dma::TxChannel::template setPeripheralRequest(); + + SpiMaster{{ id }}::initialize(); + +%% if "fifo" in features + SpiHal{{ id }}::setRxFifoThreshold(SpiHal{{ id }}::RxFifoThreshold::QuarterFull); +%% endif +} + +template +modm::ResumableResult +modm::platform::SpiMaster{{ id }}_Dma::transfer(uint8_t data) +{ + // 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) ) + { + // disable DMA for single byte transfer + SpiHal{{ id }}::disableInterrupt(SpiBase::Interrupt::TxDmaEnable | + SpiBase::Interrupt::RxDmaEnable); + + // wait for previous transfer to finish + if (!SpiHal{{ id }}::isTransmitRegisterEmpty()) + return {modm::rf::Running}; + + // start transfer by copying data into register + SpiHal{{ id }}::write(data); + + // set LSB = Bit0 + state |= Bit0; + } + + if (!SpiHal{{ id }}::isReceiveRegisterNotEmpty()) + return {modm::rf::Running}; + + SpiHal{{ id }}::read(data); + + // transfer finished + state &= ~Bit0; + return {modm::rf::Stop, data}; +} + +template +modm::ResumableResult +modm::platform::SpiMaster{{ id }}_Dma::transfer(uint8_t *tx, + uint8_t *rx, std::size_t length) +{ + // 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; + dmaError = false; + SpiHal{{ id }}::enableInterrupt(SpiBase::Interrupt::TxDmaEnable | + SpiBase::Interrupt::RxDmaEnable); + + if (tx) { + Dma::TxChannel::setMemoryAddress(uint32_t(tx)); + Dma::TxChannel::setMemoryIncrementMode(true); + } else { + Dma::TxChannel::setMemoryAddress(uint32_t(&dmaDummy)); + Dma::TxChannel::setMemoryIncrementMode(false); + } + if (rx) { + Dma::RxChannel::setMemoryAddress(uint32_t(rx)); + Dma::RxChannel::setMemoryIncrementMode(true); + } else { + Dma::RxChannel::setMemoryAddress(uint32_t(&dmaDummy)); + Dma::RxChannel::setMemoryIncrementMode(false); + } + + Dma::RxChannel::setDataLength(length); + dmaReceiveComplete = false; + Dma::RxChannel::start(); + + Dma::TxChannel::setDataLength(length); + dmaTransmitComplete = false; + Dma::TxChannel::start(); + + [[fallthrough]]; + + default: + while (true) { + if (dmaError) + break; + if (not dmaTransmitComplete and not dmaReceiveComplete) + return { modm::rf::Running }; + if (SpiHal{{ id }}::getInterruptFlags() & SpiBase::InterruptFlag::FifoTxLevel) + return { modm::rf::Running }; + if (SpiHal{{ id }}::getInterruptFlags() & SpiBase::InterruptFlag::Busy) + return { modm::rf::Running }; + if (SpiHal{{ id }}::getInterruptFlags() & SpiBase::InterruptFlag::FifoRxLevel) + return { modm::rf::Running }; + + break; + } + + SpiHal{{ id }}::disableInterrupt(SpiBase::Interrupt::TxDmaEnable | + SpiBase::Interrupt::RxDmaEnable); + // clear the state + state &= ~Bit1; + return {modm::rf::Stop}; + } +} + +template +void +modm::platform::SpiMaster{{ id }}_Dma::handleDmaTransferError() +{ + SpiHal{{ id }}::disableInterrupt(SpiBase::Interrupt::TxDmaEnable | + SpiBase::Interrupt::RxDmaEnable); + Dma::RxChannel::stop(); + Dma::TxChannel::stop(); + dmaError = true; +} + +template +void +modm::platform::SpiMaster{{ id }}_Dma::handleDmaReceiveComplete() +{ + Dma::RxChannel::stop(); + dmaReceiveComplete = true; +} + +template +void +modm::platform::SpiMaster{{ id }}_Dma::handleDmaTransmitComplete() +{ + Dma::TxChannel::stop(); + dmaTransmitComplete = true; +}