diff --git a/CMakeLists.txt b/CMakeLists.txt index f1ed2c694c70e53b8453119b438d14b6715bef71..ed973ac7367ae16918f03fcb9b17d1263fe6c610 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,6 +284,9 @@ sbs_target(test-usart-f4 stm32f429zi_stm32f4discovery) add_executable(test-usart-f7 src/tests/drivers/usart/test-usart.cpp) sbs_target(test-usart-f7 stm32f767zi_nucleo) +add_executable(test-dma-mem-to-mem src/tests/drivers/test-dma-mem-to-mem.cpp) +sbs_target(test-dma-mem-to-mem stm32f769ni_discovery) + add_executable(test-i2c-driver-f4 src/tests/drivers/i2c/test-i2c-driver.cpp) sbs_target(test-i2c-driver-f4 stm32f429zi_stm32f4discovery) diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake index 12c0eff4291a2b0d1f656dee99b25d96a76c083e..8bd4c6b27c57ce4b0b7cc01993586353fc3368c4 100644 --- a/cmake/boardcore.cmake +++ b/cmake/boardcore.cmake @@ -56,6 +56,7 @@ set(BOARDCORE_SRC ${BOARDCORE_PATH}/src/shared/drivers/canbus/CanDriver/CanDriver.cpp ${BOARDCORE_PATH}/src/shared/drivers/canbus/CanDriver/CanInterrupt.cpp ${BOARDCORE_PATH}/src/shared/drivers/canbus/CanProtocol/CanProtocol.cpp + ${BOARDCORE_PATH}/src/shared/drivers/dma/DMA.cpp ${BOARDCORE_PATH}/src/shared/drivers/interrupt/external_interrupts.cpp ${BOARDCORE_PATH}/src/shared/drivers/timer/PWM.cpp ${BOARDCORE_PATH}/src/shared/drivers/timer/CountedPWM.cpp diff --git a/src/shared/drivers/dma/DMA.cpp b/src/shared/drivers/dma/DMA.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3848389799c375926a0ec004c201b0b8fab0ce41 --- /dev/null +++ b/src/shared/drivers/dma/DMA.cpp @@ -0,0 +1,575 @@ +/* Copyright (c) 2023 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 "DMA.h" + +#include <kernel/logging.h> +#include <utils/ClockUtils.h> + +#include <map> + +using namespace miosix; + +namespace Boardcore +{ + +void __attribute__((naked)) DMA1_Stream0_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream0_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream0_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str0); +} + +void __attribute__((naked)) DMA1_Stream1_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream1_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream1_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str1); +} + +void __attribute__((naked)) DMA1_Stream2_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream2_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream2_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str2); +} + +void __attribute__((naked)) DMA1_Stream3_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream3_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream3_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str3); +} + +void __attribute__((naked)) DMA1_Stream4_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream4_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream4_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str4); +} + +void __attribute__((naked)) DMA1_Stream5_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream5_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream5_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str5); +} + +void __attribute__((naked)) DMA1_Stream6_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream6_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream6_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str6); +} + +void __attribute__((naked)) DMA1_Stream7_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA1_Stream7_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA1_Stream7_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA1_Str7); +} + +void __attribute__((naked)) DMA2_Stream0_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA2_Stream0_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA2_Stream0_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str0); +} + +void __attribute__((naked)) DMA2_Stream1_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA2_Stream1_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA2_Stream1_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str1); +} + +void __attribute__((naked)) DMA2_Stream2_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA2_Stream2_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA2_Stream2_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str2); +} + +// void __attribute__((naked)) DMA2_Stream3_IRQHandler() { +// saveContext(); +// asm volatile("bl _Z20DMA2_Stream3_IRQImplv"); +// restoreContext(); +// } + +// void __attribute__((used)) DMA2_Stream3_IRQImpl() { +// DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str3); +// } + +void __attribute__((naked)) DMA2_Stream4_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA2_Stream4_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA2_Stream4_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str4); +} + +// void __attribute__((naked)) DMA2_Stream5_IRQHandler() { +// saveContext(); +// asm volatile("bl _Z20DMA2_Stream5_IRQImplv"); +// restoreContext(); +// } + +// void __attribute__((used)) DMA2_Stream5_IRQImpl() { +// DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str5); +// } + +void __attribute__((naked)) DMA2_Stream6_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20DMA2_Stream6_IRQImplv"); + restoreContext(); +} + +void __attribute__((used)) DMA2_Stream6_IRQImpl() +{ + DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str6); +} + +// void __attribute__((naked)) DMA2_Stream7_IRQHandler() { +// saveContext(); +// asm volatile("bl _Z20DMA2_Stream7_IRQImplv"); +// restoreContext(); +// } + +// void __attribute__((used)) DMA2_Stream7_IRQImpl() { +// DMADriver::instance().IRQhandleInterrupt(DMAStreamId::DMA2_Str7); +// } + +void DMADriver::IRQhandleInterrupt(DMAStreamId id) +{ + auto stream = streams[id]; + + stream->readFlags(); + stream->clearAllFlags(); + + // Run the callbacks if neccessary + if (stream->halfTransferCallback && stream->halfTransferFlag) + { + stream->halfTransferCallback(); + } + if (stream->transferCompleteCallback && stream->transferCompleteFlag) + { + stream->transferCompleteCallback(); + } + if (stream->errorCallback && + (stream->transferErrorFlag || stream->fifoErrorFlag || + stream->directModeErrorFlag)) + { + stream->errorCallback(); + } + + // Wakeup the thread if the user is waiting + if (stream->waitingThread) + { + IRQwakeupThread(stream); + } +} + +void DMADriver::IRQwakeupThread(DMAStream* stream) +{ + // Wakeup the waiting thread + stream->waitingThread->wakeup(); + + // If the waiting thread has a higher priority than the current + // thread then reschedule + if (stream->waitingThread->IRQgetPriority() > + miosix::Thread::IRQgetCurrentThread()->IRQgetPriority()) + { + miosix::Scheduler::IRQfindNextThread(); + } + + // Clear the thread pointer, this way the thread will be sure it is + // not a spurious wakeup + stream->waitingThread = nullptr; +} + +DMADriver& DMADriver::instance() +{ + static DMADriver instance; + return instance; +} + +bool DMADriver::tryChannel(DMAStreamId id) +{ + Lock<FastMutex> l(mutex); + + // Return true, meaning that the channel is free, only if it is not yet + // allocated + return streams.count(id) == 0; +} + +DMAStream& DMADriver::acquireStream(DMAStreamId id) +{ + Lock<FastMutex> l(mutex); + + // Wait until the channel is free + while (streams.count(id) != 0) + cv.wait(l); + + // Enable the clock if not already done + // TODO: Enable DMA1 or DMA2 + // if (streams.size() == 0) + // RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + + return *(streams[id] = new DMAStream(id)); +} + +void DMADriver::releaseStream(DMAStreamId id) +{ + Lock<FastMutex> l(mutex); + + if (streams.count(id) != 0) + { + delete streams[id]; + streams.erase(id); + cv.broadcast(); + } + + // Disable the clock if there are no more channels + // TODO: Disable DMA1 or DMA2 + // if (streams.size() == 0) + // RCC->AHB1ENR &= ~RCC_AHB1ENR_DMA1EN; +} + +DMADriver::DMADriver() +{ + // For now enable the clock always + ClockUtils::enablePeripheralClock(DMA1); + ClockUtils::enablePeripheralClock(DMA2); + + // Reset interrupts flags + // TODO: Change this magic number + DMA1->HIFCR = 0x0f7d0f7d; + DMA1->LIFCR = 0x0f7d0f7d; + DMA2->HIFCR = 0x0f7d0f7d; + DMA2->LIFCR = 0x0f7d0f7d; +} + +void DMAStream::setup(DMATransaction transaction) +{ + currentSetup = transaction; + + // Reset the configuration + registers->CR = 0; + + // Wait for the stream to actually be disabled + while (registers->CR & DMA_SxCR_EN) + ; + + registers->CR |= static_cast<uint32_t>(transaction.channel); + registers->CR |= static_cast<uint32_t>(transaction.direction); + registers->CR |= static_cast<uint32_t>(transaction.priority); + if (transaction.circularMode) + registers->CR |= DMA_SxCR_CIRC; + registers->NDTR = transaction.numberOfDataItems; + + if (transaction.direction == DMATransaction::Direction::MEM_TO_PER) + { + // In memory to peripheral mode, the source address is the memory + // address + + registers->CR |= static_cast<uint32_t>(transaction.srcSize) + << DMA_SxCR_MSIZE_Pos; + registers->CR |= static_cast<uint32_t>(transaction.dstSize) + << DMA_SxCR_PSIZE_Pos; + + if (transaction.srcIncrement) + registers->CR |= DMA_SxCR_MINC; + if (transaction.dstIncrement) + registers->CR |= DMA_SxCR_PINC; + + registers->M0AR = reinterpret_cast<uint32_t>(transaction.srcAddress); + registers->PAR = reinterpret_cast<uint32_t>(transaction.dstAddress); + } + else + { + // In peripheral to memory or memory to memory mode, the source address + // goes into the peripheral address register + + registers->CR |= static_cast<uint32_t>(transaction.srcSize) + << DMA_SxCR_PSIZE_Pos; + registers->CR |= static_cast<uint32_t>(transaction.dstSize) + << DMA_SxCR_MSIZE_Pos; + + if (transaction.srcIncrement) + registers->CR |= DMA_SxCR_PINC; + if (transaction.dstIncrement) + registers->CR |= DMA_SxCR_MINC; + + registers->PAR = reinterpret_cast<uint32_t>(transaction.srcAddress); + registers->M0AR = reinterpret_cast<uint32_t>(transaction.dstAddress); + } + + if (transaction.doubleBufferMode) + { + registers->CR |= DMA_SxCR_DBM; + registers->M1AR = + reinterpret_cast<uint32_t>(transaction.secondMemoryAddress); + } + + bool enableInterrupt = false; + if (transaction.enableHalfTransferInterrupt) + { + clearHalfTransferFlag(); + registers->CR |= DMA_SxCR_HTIE; + enableInterrupt = true; + } + if (transaction.enableTransferCompleteInterrupt) + { + clearTransferCompleteFlag(); + registers->CR |= DMA_SxCR_TCIE; + enableInterrupt = true; + } + if (transaction.enableTransferErrorInterrupt) + { + clearTransferErrorFlag(); + registers->CR |= DMA_SxCR_TEIE; + enableInterrupt = true; + } + if (transaction.enableFifoErrorInterrupt) + { + clearFifoErrorFlag(); + registers->CR |= DMA_SxFCR_FEIE; + enableInterrupt = true; + } + if (transaction.enableDirectModeErrorInterrupt) + { + clearDirectModeErrorFlag(); + registers->CR |= DMA_SxCR_DMEIE; + enableInterrupt = true; + } + + if (enableInterrupt) + { + NVIC_SetPriority(irqNumber, 8); + NVIC_ClearPendingIRQ(irqNumber); + NVIC_EnableIRQ(irqNumber); + } + else + { + NVIC_DisableIRQ(irqNumber); + } +} + +void DMAStream::enable() +{ + // Reset all saved flags + halfTransferFlag = false; + transferCompleteFlag = false; + transferErrorFlag = false; + fifoErrorFlag = false; + directModeErrorFlag = false; + + // Enable the peripheral + registers->CR |= DMA_SxCR_EN; +} + +void DMAStream::disable() { registers->CR &= ~DMA_SxCR_EN; } + +void DMAStream::waitForHalfTransfer() +{ + waitForInterruptEventImpl( + currentSetup.enableHalfTransferInterrupt, + std::bind(&DMAStream::getHalfTransferFlagStatus, this), + std::bind(&DMAStream::clearHalfTransferFlag, this), halfTransferFlag, + -1); +} + +void DMAStream::waitForTransferComplete() +{ + waitForInterruptEventImpl( + currentSetup.enableTransferCompleteInterrupt, + std::bind(&DMAStream::getTransferCompleteFlagStatus, this), + std::bind(&DMAStream::clearTransferCompleteFlag, this), + transferCompleteFlag, -1); +} + +bool DMAStream::timedWaitForHalfTransfer(uint64_t timeout_ns) +{ + return waitForInterruptEventImpl( + currentSetup.enableHalfTransferInterrupt, + std::bind(&DMAStream::getHalfTransferFlagStatus, this), + std::bind(&DMAStream::clearHalfTransferFlag, this), halfTransferFlag, + timeout_ns); +} + +bool DMAStream::timedWaitForTransferComplete(uint64_t timeout_ns) +{ + return waitForInterruptEventImpl( + currentSetup.enableTransferCompleteInterrupt, + std::bind(&DMAStream::getTransferCompleteFlagStatus, this), + std::bind(&DMAStream::clearTransferCompleteFlag, this), + transferCompleteFlag, timeout_ns); +} + +void DMAStream::setHalfTransferCallback(std::function<void()> callback) +{ + halfTransferCallback = callback; +} + +void DMAStream::resetHalfTransferCallback() { halfTransferCallback = nullptr; } + +void DMAStream::setTransferCompleteCallback(std::function<void()> callback) +{ + transferCompleteCallback = callback; +} + +void DMAStream::resetTransferCompleteCallback() +{ + transferCompleteCallback = nullptr; +} + +void DMAStream::setErrorCallback(std::function<void()> callback) +{ + errorCallback = callback; +} + +void DMAStream::resetErrorCallback() { errorCallback = nullptr; } + +void DMAStream::readFlags() +{ + uint8_t flags = *ISR >> IFindex; + + halfTransferFlag = flags & DMA_LISR_HTIF0; + transferCompleteFlag = flags & DMA_LISR_TCIF0; + transferErrorFlag = flags & DMA_LISR_TEIF0; + fifoErrorFlag = flags & DMA_LISR_DMEIF0; + directModeErrorFlag = flags & DMA_LISR_DMEIF0; +} + +int DMAStream::getCurrentBufferNumber() +{ + return (registers->CR & DMA_SxCR_CT) != 0 ? 2 : 1; +} + +DMAStream::DMAStream(DMAStreamId id) : id(id) +{ + // Get the channel registers base address and the interrupt flags clear + // register address + if (id < DMAStreamId::DMA2_Str0) + { + registers = reinterpret_cast<DMA_Stream_TypeDef*>( + DMA1_BASE + 0x10 + 0x18 * static_cast<int>(id)); + + if (id < DMAStreamId::DMA1_Str4) + { + IFCR = &DMA1->LIFCR; + ISR = &DMA1->LISR; + } + else + { + IFCR = &DMA1->HIFCR; + ISR = &DMA1->HISR; + } + } + else + { + registers = reinterpret_cast<DMA_Stream_TypeDef*>( + DMA2_BASE + 0x10 + 0x18 * (static_cast<int>(id) - 8)); + + if (id < DMAStreamId::DMA2_Str4) + { + IFCR = &DMA2->LIFCR; + ISR = &DMA2->LISR; + } + else + { + IFCR = &DMA2->HIFCR; + ISR = &DMA2->HISR; + } + } + + // Compute the index for the interrupt flags clear register + // Refer to reference manual for the register bits structure + int offset = static_cast<int>(id) % 4; + IFindex = (offset % 2) * 6 + (offset / 2) * 16; + + // Select the interrupt + irqNumber = static_cast<IRQn_Type>( + static_cast<int>(IRQn_Type::DMA1_Stream0_IRQn) + static_cast<int>(id)); +} + +} // namespace Boardcore diff --git a/src/shared/drivers/dma/DMA.h b/src/shared/drivers/dma/DMA.h new file mode 100644 index 0000000000000000000000000000000000000000..b0e31d3df58c2936e624c61cb6f144efa6ea92ab --- /dev/null +++ b/src/shared/drivers/dma/DMA.h @@ -0,0 +1,375 @@ +/* Copyright (c) 2023 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 <kernel/scheduler/scheduler.h> +#include <kernel/sync.h> +#include <utils/TimedPollingFlag.h> + +#include <functional> +#include <map> + +namespace Boardcore +{ + +enum class DMAStreamId +{ + DMA1_Str0 = 0, + DMA1_Str1, + DMA1_Str2, + DMA1_Str3, + DMA1_Str4, + DMA1_Str5, + DMA1_Str6, + DMA1_Str7, + DMA2_Str0, + DMA2_Str1, + DMA2_Str2, + DMA2_Str3, + DMA2_Str4, + DMA2_Str5, + DMA2_Str6, + DMA2_Str7, +}; + +struct DMATransaction +{ + enum class Channel : uint32_t + { + CHANNEL0 = 0, + CHANNEL1 = DMA_SxCR_CHSEL_0, + CHANNEL2 = DMA_SxCR_CHSEL_1, + CHANNEL3 = DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0, + CHANNEL4 = DMA_SxCR_CHSEL_2, + CHANNEL5 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_0, + CHANNEL6 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1, + CHANNEL7 = DMA_SxCR_CHSEL, + }; + + enum class Direction : uint16_t + { + MEM_TO_MEM = DMA_SxCR_DIR_1, + MEM_TO_PER = DMA_SxCR_DIR_0, + PER_TO_MEM = 0, + }; + + enum class Priority : uint32_t + { + VERY_HIGH = DMA_SxCR_PL, + HIGH = DMA_SxCR_PL_1, + MEDIUM = DMA_SxCR_PL_0, + LOW = 0, + }; + + enum class DataSize : uint8_t + { + BITS_8 = 0, + BITS_16, + BITS_32, + }; + + Channel channel = Channel::CHANNEL0; + Direction direction = Direction::MEM_TO_MEM; + Priority priority = Priority::LOW; + DataSize srcSize = DataSize::BITS_32; + DataSize dstSize = DataSize::BITS_32; + volatile void* srcAddress = nullptr; + volatile void* dstAddress = nullptr; + volatile void* secondMemoryAddress = nullptr; + uint16_t numberOfDataItems = 0; + bool srcIncrement = false; + bool dstIncrement = false; + bool circularMode = false; + bool doubleBufferMode = false; + bool enableHalfTransferInterrupt = false; + bool enableTransferCompleteInterrupt = false; + bool enableTransferErrorInterrupt = false; + bool enableFifoErrorInterrupt = false; + bool enableDirectModeErrorInterrupt = false; +}; + +// Forward declaration +class DMAStream; + +class DMADriver +{ +public: + void IRQhandleInterrupt(DMAStreamId id); + + static DMADriver& instance(); + + bool tryChannel(DMAStreamId id); + + DMAStream& acquireStream(DMAStreamId id); + + void releaseStream(DMAStreamId id); + +private: + DMADriver(); + + void IRQwakeupThread(DMAStream* stream); + + miosix::FastMutex mutex; + miosix::ConditionVariable cv; + std::map<DMAStreamId, DMAStream*> streams; + +public: + DMADriver(const DMADriver&) = delete; + DMADriver& operator=(const DMADriver&) = delete; +}; + +class DMAStream +{ + friend DMADriver; + +public: + void setup(DMATransaction transaction); + + void enable(); + + void disable(); + + void waitForHalfTransfer(); + + void waitForTransferComplete(); + + bool timedWaitForHalfTransfer(uint64_t timeout_ns); + + bool timedWaitForTransferComplete(uint64_t timeout_ns); + + void setHalfTransferCallback(std::function<void()> callback); + + void resetHalfTransferCallback(); + + void setTransferCompleteCallback(std::function<void()> callback); + + void resetTransferCompleteCallback(); + + void setErrorCallback(std::function<void()> callback); + + void resetErrorCallback(); + + /** + * @brief Reads the current flags status. + * + * The values can be read with the get***FlagStatus functions. + */ + void readFlags(); + + /** + * @brief Returns the last read status of the half transfer flag. + * + * TODO: Explain what this flag intails and what to do. + */ + inline bool getHalfTransferFlagStatus() { return halfTransferFlag; } + + /** + * @brief Returns the last read status of the transfer complete flag. + * + * TODO: Explain what this flag intails and what to do. + */ + inline bool getTransferCompleteFlagStatus() { return transferCompleteFlag; } + + /** + * @brief Returns the last read status of the transfer error flag. + * + * TODO: Explain what this flag intails and what to do. + */ + inline bool getTransferErrorFlagStatus() { return transferErrorFlag; } + + /** + * @brief Returns the last read status of the fifo error flag. + * + * TODO: Explain what this flag intails and what to do. + */ + inline bool getFifoErrorFlagStatus() { return fifoErrorFlag; } + + /** + * @brief Returns the last read status of the direct mode error flag. + * + * TODO: Explain what this flag intails and what to do. + */ + inline bool getDirectModeErrorFlagStatus() { return directModeErrorFlag; } + + /** + * @brief Returns the number of the buffer currently in use. + * + * @return 1 or 2 depending on the buffer currently in use. + */ + int getCurrentBufferNumber(); + + inline void clearHalfTransferFlag() + { + *IFCR |= DMA_LIFCR_CHTIF0 << IFindex; + } + + inline void clearTransferCompleteFlag() + { + *IFCR |= DMA_LIFCR_CTCIF0 << IFindex; + } + + inline void clearTransferErrorFlag() + { + *IFCR |= DMA_LIFCR_CTEIF0 << IFindex; + } + + inline void clearFifoErrorFlag() { *IFCR |= DMA_LIFCR_CFEIF0 << IFindex; } + + inline void clearDirectModeErrorFlag() + { + *IFCR |= DMA_LIFCR_CDMEIF0 << IFindex; + } + + inline void clearAllFlags() + { + *IFCR |= (DMA_LIFCR_CHTIF0 | DMA_LIFCR_CTCIF0 | DMA_LIFCR_CTEIF0 | + DMA_LIFCR_CFEIF0 | DMA_LIFCR_CDMEIF0) + << IFindex; + } + +private: + DMAStream(DMAStreamId id); + + DMATransaction currentSetup; + miosix::Thread* waitingThread = nullptr; + + // These flags are set by the interrupt routine and tells the user + // which event were triggered + bool halfTransferFlag = false; + bool transferCompleteFlag = false; + bool transferErrorFlag = false; + bool fifoErrorFlag = false; + bool directModeErrorFlag = false; + + std::function<void()> halfTransferCallback; + std::function<void()> transferCompleteCallback; + std::function<void()> errorCallback; + + DMAStreamId id; + IRQn_Type irqNumber; + DMA_Stream_TypeDef* registers; + + volatile uint32_t* ISR; ///< Interrupt status register + volatile uint32_t* IFCR; ///< Interrupt flags clear register + int IFindex; ///< Interrupt flags index + + inline bool waitForInterruptEventImpl( + bool isInterruptEnabled, std::function<bool()> getEventStatus, + std::function<void()> clearEventStatus, bool& eventTriggered, + long long timeout_ns) + { + // Return value: true if the event was triggered, false if the timeout + // expired + bool result = false; + + // If the interrupt is enabled we can just pause and wait for it. + // Otherwise we need to pool the flag. + if (isInterruptEnabled) + { + // Here we have 2 cases: + // - This function has been called after the interrupt fired. In + // this case the interrupt saves the flags status and we check them + // - The interrupt has not yet fired. We pause the thread and the + // interrupt will wake us up + + if (eventTriggered) + { + result = true; + } + else + { + // Save the current thread pointer + waitingThread = miosix::Thread::getCurrentThread(); + + // Wait until the thread is woken up and the pointer is cleared + miosix::FastInterruptDisableLock dLock; + if (timeout_ns >= 0) + { + do + { + // TODO: Wait Miosix 2.7 or do in another way? + // if (miosix::Thread::IRQenableIrqAndTimedWait( + // dLock, timeout_ns + miosix::getTime()) == + // miosix::TimedWaitResult::Timeout) + // { + // result = false; + + // // If the timeout expired we clear the thread + // // pointer so that the interrupt, if it will + // occur, + // // will not wake up the thread (and we can exit + // the + // // while loop) + // waitingThread = nullptr; + // } + // else + // { + // result = true; + // } + } while (waitingThread); + } + else + { + do + { + miosix::Thread::IRQenableIrqAndWait(dLock); + } while (waitingThread); + result = true; + } + } + + // Before returning we need to clear the flags otherwise we could + // get misfires + eventTriggered = false; + } + else + { + // Pool the flag if the user did not enable the interrupt + if (timeout_ns >= 0) + { + result = timedPollingFlag(getEventStatus, timeout_ns); + } + else + { + while (!getEventStatus()) + ; + result = true; + } + + if (result) + { + // Clear the flag + clearEventStatus(); + } + } + + return result; + } + +public: + DMAStream(const DMAStream&) = delete; + DMAStream& operator=(const DMAStream&) = delete; +}; + +} // namespace Boardcore diff --git a/src/shared/utils/TimedPollingFlag.h b/src/shared/utils/TimedPollingFlag.h new file mode 100644 index 0000000000000000000000000000000000000000..de0574e5bdf396b876efc7d00b7a7d72ff2ade9a --- /dev/null +++ b/src/shared/utils/TimedPollingFlag.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2023 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 <kernel/kernel.h> + +#include <functional> + +namespace Boardcore +{ + +/** + * @brief Pools a flag until it is set or the timeout is reached. + * + * @return true if the flag was set, false if the timeout was reached. + */ +inline bool timedPollingFlag(std::function<bool()> readFlag, + uint64_t timeout_ns) +{ + // TODO: When Miosix 2.7 will be supported, change this with getTime() in ns + uint64_t start = miosix::getTick(); + + while (miosix::getTick() - start < timeout_ns * 1e6) + { + if (readFlag()) + { + return true; + } + } + + return false; +} + +} // namespace Boardcore \ No newline at end of file diff --git a/src/tests/drivers/test-dma-mem-to-mem.cpp b/src/tests/drivers/test-dma-mem-to-mem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97f43983c3a013a1b47eed16cf1df4af9412960e --- /dev/null +++ b/src/tests/drivers/test-dma-mem-to-mem.cpp @@ -0,0 +1,79 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Authors: 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/DMA.h> +#include <miosix.h> +#include <util/util.h> + +using namespace miosix; +using namespace Boardcore; + +DMAStream &stream = DMADriver::instance().acquireStream(DMAStreamId::DMA2_Str0); + +void printBuffer(uint8_t *buffer, size_t size); + +int main() +{ + /** + * In this test we want to copy a buffer1 into buffer2 with the DMA. + */ + + uint8_t buffer1[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + uint8_t buffer2[8] = {0}; + + printf("Buffer 1:\n"); + printBuffer(buffer1, sizeof(buffer1)); + printf("Buffer 2:\n"); + printBuffer(buffer2, sizeof(buffer2)); + + DMATransaction trn{ + .direction = DMATransaction::Direction::MEM_TO_MEM, + .srcSize = DMATransaction::DataSize::BITS_8, + .dstSize = DMATransaction::DataSize::BITS_8, + .srcAddress = buffer1, + .dstAddress = buffer2, + .numberOfDataItems = sizeof(buffer1), + .srcIncrement = true, + .dstIncrement = true, + }; + stream.setup(trn); + stream.enable(); + // stream.waitForTransferComplete(); + delayMs(10); + + delayMs(100); + + printf("Buffer 1:\n"); + printBuffer(buffer1, sizeof(buffer1)); + printf("Buffer 2:\n"); + printBuffer(buffer2, sizeof(buffer2)); +} + +void printBuffer(uint8_t *buffer, size_t size) +{ + for (size_t i = 0; i < size - 1; i++) + { + printf("%x,", buffer[i]); + } + + printf("%x\n", buffer[size - 1]); +} \ No newline at end of file