From b5423b80dcbdb8d9a04874cc7f66019b08caca98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Waldh=C3=A4usl?= Date: Thu, 20 Jul 2023 01:26:51 -0700 Subject: [PATCH] [stm32] add dma capabilities to G0 adc --- examples/nucleo_g070rb/adc_dma/adc_dma.hpp | 97 +++++++++++++ examples/nucleo_g070rb/adc_dma/main.cpp | 92 ++++++++++++ examples/nucleo_g070rb/adc_dma/project.xml | 12 ++ .../nucleo_g070rb/adc_dma/timer_handler.hpp | 47 +++++++ src/modm/platform/adc/stm32f0/adc.hpp.in | 102 ++++++++++++++ src/modm/platform/adc/stm32f0/adc_impl.hpp.in | 131 ++++++++++++++++++ .../platform/adc/stm32f0/adc_interrupt.cpp.in | 27 ++++ .../platform/adc/stm32f0/adc_interrupt.hpp.in | 56 ++++++++ src/modm/platform/adc/stm32f0/module.lb | 2 + 9 files changed, 566 insertions(+) create mode 100644 examples/nucleo_g070rb/adc_dma/adc_dma.hpp create mode 100644 examples/nucleo_g070rb/adc_dma/main.cpp create mode 100644 examples/nucleo_g070rb/adc_dma/project.xml create mode 100644 examples/nucleo_g070rb/adc_dma/timer_handler.hpp create mode 100644 src/modm/platform/adc/stm32f0/adc_interrupt.cpp.in create mode 100644 src/modm/platform/adc/stm32f0/adc_interrupt.hpp.in diff --git a/examples/nucleo_g070rb/adc_dma/adc_dma.hpp b/examples/nucleo_g070rb/adc_dma/adc_dma.hpp new file mode 100644 index 0000000000..c6e52f7459 --- /dev/null +++ b/examples/nucleo_g070rb/adc_dma/adc_dma.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024, Zühlke Engineering (Austria) GmbH + * + * 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 EXAMPLE_ADCDMA_HPP +#define EXAMPLE_ADCDMA_HPP + +#include + +template +class AdcDma +{ + struct Dma + { + using AdcChannel = + typename DmaChannel::template RequestMapping::Channel; + static constexpr modm::platform::DmaBase::Request AdcRequest = + DmaChannel::template RequestMapping::Request; + }; + +public: + /** + * \brief initialize both adc and dma + * @tparam SystemClock + */ + template + static void + initialize(uintptr_t destination_ptr, size_t length, + modm::platform::DmaBase::Priority priority = modm::platform::DmaBase::Priority::Low, + modm::platform::DmaBase::CircularMode circularMode = + modm::platform::DmaBase::CircularMode::Enabled, + modm::platform::DmaBase::IrqHandler transferErrorCallback = nullptr, + modm::platform::DmaBase::IrqHandler halfCompletedCallback = nullptr, + modm::platform::DmaBase::IrqHandler completedCallback = nullptr) + { + Dma::AdcChannel::configure( + modm::platform::DmaBase::DataTransferDirection::PeripheralToMemory, + modm::platform::DmaBase::MemoryDataSize::HalfWord, + modm::platform::DmaBase::PeripheralDataSize::HalfWord, + modm::platform::DmaBase::MemoryIncrementMode::Increment, + modm::platform::DmaBase::PeripheralIncrementMode::Fixed, priority, circularMode); + Dma::AdcChannel::setPeripheralAddress(Adc::getDataRegisterAddress()); + Dma::AdcChannel::setDataLength(length); + Dma::AdcChannel::setMemoryAddress(destination_ptr); + + setTransferErrorCallback(transferErrorCallback); + setHalfCompletedConversionCallback(halfCompletedCallback); + setCompletedConversionCallback(completedCallback); + + Dma::AdcChannel::template setPeripheralRequest(); + Adc::disableDmaMode(); + } + + static void + startDma() + { + Adc::enableDmaRequests(); + Adc::enableDmaMode(); + DmaChannel::start(); + } + + static void + setTransferErrorCallback(modm::platform::DmaBase::IrqHandler transferErrorCallback) + { + if (transferErrorCallback == nullptr) { return; } + Dma::AdcChannel::enableInterruptVector(); + Dma::AdcChannel::enableInterrupt(modm::platform::DmaBase::InterruptEnable::TransferError); + Dma::AdcChannel::setTransferErrorIrqHandler(transferErrorCallback); + } + + static void + setHalfCompletedConversionCallback(modm::platform::DmaBase::IrqHandler halfCompletedCallback) + { + if (halfCompletedCallback == nullptr) { return; } + Dma::AdcChannel::enableInterruptVector(); + Dma::AdcChannel::enableInterrupt(modm::platform::DmaBase::InterruptEnable::HalfTransfer); + Dma::AdcChannel::setHalfTransferCompleteIrqHandler(halfCompletedCallback); + } + + static void + setCompletedConversionCallback(modm::platform::DmaBase::IrqHandler completedCallback) + { + if (completedCallback == nullptr) { return; } + Dma::AdcChannel::enableInterruptVector(); + Dma::AdcChannel::enableInterrupt( + modm::platform::DmaBase::InterruptEnable::TransferComplete); + Dma::AdcChannel::setTransferCompleteIrqHandler(completedCallback); + } +}; + +#endif // EXAMPLE_ADCDMA_HPP diff --git a/examples/nucleo_g070rb/adc_dma/main.cpp b/examples/nucleo_g070rb/adc_dma/main.cpp new file mode 100644 index 0000000000..068b4999f6 --- /dev/null +++ b/examples/nucleo_g070rb/adc_dma/main.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Zühlke Engineering (Austria) GmbH + * + * 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/. + */ + +// This Example uses adc to sample s channels 50 times every 0.5s and store the value +// in a buffer via dma. The adc is set to be triggered by the Timer1 compare event and then to +// sample both channels using scan mode. The compare event is triggered for a defined number of +// times by configuring the Timer1 to use one pulse mode with an repetition count. For more +// information on the timer please refer to the timer register count example. In this configuration, +// the adc sampling process is started by the cpu but handled completely by peripherals clearing CPU +// time for other tasks. + +#include +#include +#include + +#include "adc_dma.hpp" +#include "timer_handler.hpp" + +using namespace Board; +using namespace modm::platform; +using Adc1Dma = AdcDma; + +std::array adc_results; +volatile bool dma_completed = false; + +void +completedCallback() +{ + LedD13::toggle(); + dma_completed = true; +} + +int +main() +{ + Board::initialize(); + + // Use the logging streams to print some messages. + // Change MODM_LOG_LEVEL above to enable or disable these messages + MODM_LOG_INFO << "Start Setup" << modm::endl; + + LedD13::setOutput(); + LedD13::reset(); + + adc_results.fill(0); + + Adc1::connect(); + Adc1::connect(); + + const auto a0_channel = Adc1::getPinChannel(); + const auto a1_channel = Adc1::getPinChannel(); + Adc1::setSampleTime(Adc1::SampleTime::Cycles3_5); + Adc1::initialize(); + Adc1::enableScanMode(); + modm::delay(500ms); + // On STM32G0 Event1 means TIM1's channel 4 capture and compare event. + // Each controller has a different trigger mapping, check the reference + // manual for more information on the trigger mapping of your controller. + Adc1::enableRegularConversionExternalTrigger(Adc1::ExternalTriggerPolarity::RisingEdge, + Adc1::RegularConversionExternalTrigger::Event1); + Adc1::addChannel(a0_channel); + Adc1::addChannel(a1_channel); + Dma1::enable(); + Adc1Dma::initialize((uintptr_t)(&adc_results[0]), adc_results.size()); + Adc1Dma::setCompletedConversionCallback(completedCallback); + Adc1Dma::startDma(); + Adc1::startConversion(); + + advancedTimerConfig(adc_results.size() / 2 - 1); + timerStart(); + + while (true) + { + modm::delay(0.5s); + if (!dma_completed) { continue; } + dma_completed = false; + MODM_LOG_INFO << "Measurements" + << "\r" << modm::endl; + for (const uint16_t& sample : adc_results) { MODM_LOG_INFO << sample << ", "; } + MODM_LOG_INFO << "\r" << modm::endl; + adc_results.fill(0); + timerStart(); + } + return 0; +} diff --git a/examples/nucleo_g070rb/adc_dma/project.xml b/examples/nucleo_g070rb/adc_dma/project.xml new file mode 100644 index 0000000000..c974c3fa5a --- /dev/null +++ b/examples/nucleo_g070rb/adc_dma/project.xml @@ -0,0 +1,12 @@ + + modm:nucleo-g070rb + + + + + modm:build:scons + modm:platform:timer:1 + modm:platform:adc + modm:platform:dma + + diff --git a/examples/nucleo_g070rb/adc_dma/timer_handler.hpp b/examples/nucleo_g070rb/adc_dma/timer_handler.hpp new file mode 100644 index 0000000000..28d9976f2b --- /dev/null +++ b/examples/nucleo_g070rb/adc_dma/timer_handler.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, Zühlke Engineering (Austria) GmbH + * + * 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 EXAMPLE_TimerHANDLER_HPP +#define EXAMPLE_TimerHANDLER_HPP + +#include +#include +#include + +#include "adc_dma.hpp" + +using namespace Board; +using namespace modm::platform; +using namespace std::chrono_literals; + +template +void +advancedTimerConfig(uint8_t repetitionCount) +{ + Timer::enable(); + Timer::setMode(Timer::Mode::UpCounter, Timer::SlaveMode::Disabled, + Timer::SlaveModeTrigger::Internal0, Timer::MasterMode::Update, true); + Timer::setPrescaler(84); + Timer::setOverflow(9999); + Timer::setRepetitionCount(repetitionCount); + + Timer::enableOutput(); + Timer::configureOutputChannel(4, Timer::OutputCompareMode::Pwm, 999, Timer::PinState::Enable); +} + +template +static void +timerStart() +{ + Timer::applyAndReset(); + Timer::start(); +} + +#endif // EXAMPLE_TimerHANDLER_HPP diff --git a/src/modm/platform/adc/stm32f0/adc.hpp.in b/src/modm/platform/adc/stm32f0/adc.hpp.in index aece0ff1e9..3fa82e1fcf 100644 --- a/src/modm/platform/adc/stm32f0/adc.hpp.in +++ b/src/modm/platform/adc/stm32f0/adc.hpp.in @@ -3,6 +3,7 @@ * Copyright (c) 2018, Álan Crístoffer * Copyright (c) 2018, Carl Treudler * Copyright (c) 2018-2019, Niklas Hauser + * Copyright (c) 2023, Daniel Waldhaeusl (Zuehlke Engineering) * * This file is part of the modm project. * @@ -140,6 +141,32 @@ public: }; MODM_FLAGS32(InterruptFlag); + enum class ExternalTriggerPolarity + { + NoTriggerDetection = 0x0u, + RisingEdge = 0x1u, + FallingEdge = 0x2u, + RisingAndFallingEdge = 0x3u, + }; + + /** + * Enum mapping all events on a external trigger converter. + * The source mapped to each event varies on controller family, + * refer to the ADC external trigger section on reference manual + * of your controller for more information + */ + enum class RegularConversionExternalTrigger + { + Event0 = 0x0u, + Event1 = 0x1u, + Event2 = 0x2u, + Event3 = 0x3u, + Event4 = 0x4u, + Event5 = 0x5u, + Event6 = 0x6u, + Event7 = 0x7u, + }; + public: // start inherited documentation template< class... Signals > @@ -169,6 +196,9 @@ public: static void initialize(); + static inline void + enable(); + static inline void disable(); @@ -314,6 +344,78 @@ public: static inline void acknowledgeInterruptFlags(InterruptFlag_t flags); +%% if target.family in ["g0"] + static inline uintptr_t + getDataRegisterAddress(); + + static inline void + enableRegularConversionExternalTrigger( + ExternalTriggerPolarity externalTriggerPolarity, + RegularConversionExternalTrigger regularConversionExternalTrigger); + + /// Add a channel to conversion group. + static inline bool + addChannel(const Channel channel); + + /** + * Enable Dma mode for the ADC + */ + static inline void + enableDmaMode(); + + /** + * Disable Dma mode for the ADC + */ + static inline void + disableDmaMode(); + + /** + * get if adc is enabled + * @return true if ADC_CR2_ADON bit is set, false otherwise + */ + static inline bool + getAdcEnabled(); + + /** + * enable DMA selection for the ADC. If this is enabled DMA + * requests are issued as long as data are converted and + * the adc has dma enabled + */ + static inline void + enableDmaRequests(); + + /** + * disable dma selection for the ADC. If this is disabled + * no new DMA requests are issued after last transfer + */ + static inline void + disableDmaRequests(); + + /** + * Waits until the CCR bit is set. + */ + static inline void + waitChannelConfigReady(); + + /** + * Resets the CCR bit. + */ + static inline void + resetChannelConfigReady(); + + /** + * Enables scan mode + */ + static inline void + enableScanMode(); + + /** + * Disables scan mode + */ + static inline void + disableScanMode(); +%% endif + public: static constexpr uint8_t TS_CAL1_TEMP{30}; %% if target.family in ["f0"] diff --git a/src/modm/platform/adc/stm32f0/adc_impl.hpp.in b/src/modm/platform/adc/stm32f0/adc_impl.hpp.in index 09c503cf5a..8f178c0cda 100644 --- a/src/modm/platform/adc/stm32f0/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f0/adc_impl.hpp.in @@ -3,6 +3,7 @@ * Copyright (c) 2018, Álan Crístoffer * Copyright (c) 2018, Carl Treudler * Copyright (c) 2019, Niklas Hauser + * Copyright (c) 2023, Daniel Waldhaeusl (Zuehlke Engineering) * * This file is part of the modm project. * @@ -67,6 +68,12 @@ modm::platform::Adc{{ id }}::initialize() while (not (ADC1->ISR & ADC_ISR_ADRDY)) ; } +void +modm::platform::Adc{{ id }}::enable() +{ + ADC1->CR |= ADC_CR_ADEN; // switch on ADC +} + void modm::platform::Adc{{ id }}::disable() { @@ -360,3 +367,127 @@ modm::platform::Adc{{ id }}::setWaitMode(const bool enable) ADC1->CFGR1 &= ~ADC_CFGR1_WAIT; } } + +%% if target.family in ["g0"] +uintptr_t +modm::platform::Adc{{ id }}::getDataRegisterAddress() +{ + return (uintptr_t) &(ADC1->DR); +} + +void +modm::platform::Adc{{ id }}::enableRegularConversionExternalTrigger( + ExternalTriggerPolarity externalTriggerPolarity, + RegularConversionExternalTrigger regularConversionExternalTrigger) +{ + const auto polarity = + (static_cast(externalTriggerPolarity) << ADC_CFGR1_EXTEN_Pos); + const auto externalTrigger = + (static_cast(regularConversionExternalTrigger) << ADC_CFGR1_EXTSEL_Pos); + const auto mask = ADC_CFGR1_EXTEN_Msk | ADC_CFGR1_EXTSEL_Msk; + ADC1->CFGR1 = (ADC1->CFGR1 & ~mask) | polarity | externalTrigger; +} + +void +modm::platform::Adc{{ id }}::enableDmaMode() +{ + ADC1->CFGR1 |= ADC_CFGR1_DMAEN; +} + +void +modm::platform::Adc{{ id }}::disableDmaMode() +{ + ADC1->CFGR1 &= ~ADC_CFGR1_DMAEN; +} + +bool +modm::platform::Adc{{ id }}::getAdcEnabled() +{ + return (ADC1->CR & ADC_CR_ADEN); +} + + +void +modm::platform::Adc{{ id }}::enableDmaRequests() +{ + ADC1->CFGR1 |= ADC_CFGR1_DMACFG; +} + +void +modm::platform::Adc{{ id }}::disableDmaRequests() +{ + ADC1->CFGR1 &= ~ADC_CFGR1_DMACFG; +} + +void +modm::platform::Adc{{ id }}::waitChannelConfigReady() +{ + while (!(ADC1->ISR & ADC_ISR_CCRDY)); +} + +void +modm::platform::Adc{{ id }}::resetChannelConfigReady(){ADC1->ISR &= ~ADC_ISR_CCRDY;} + +void +modm::platform::Adc{{ id }}::enableScanMode() +{ + resetChannelConfigReady(); + ADC1->CFGR1 |= ADC_CFGR1_CHSELRMOD; + waitChannelConfigReady(); + + resetChannelConfigReady(); + ADC1->CHSELR = uint32_t(0xFFFFFFFF); + waitChannelConfigReady(); +} + +void +modm::platform::Adc{{ id }}::disableScanMode() +{ + ADC1->CFGR1 &= ~ADC_CFGR1_CHSELRMOD; +} + +bool +modm::platform::Adc{{ id }}::addChannel(const Channel channel) +{ + uint32_t position = 0; + uint32_t position_value = 0; + + if (channel == Channel::InternalReference and + not (ADC1_COMMON->CCR & ADC_CCR_VREFEN)) + { +%% if target.family in ["g0"] + ADC1->CR |= ADC_CR_ADVREGEN; + modm::delay_us(30); +%% endif + ADC1_COMMON->CCR |= ADC_CCR_VREFEN; + } +%% if "Battery" in channels.values() + else if (channel == Channel::Battery) { + ADC1_COMMON->CCR |= ADC_CCR_VBATEN; + } +%% endif + else if (channel == Channel::Temperature and + not (ADC1_COMMON->CCR & ADC_CCR_TSEN)) + { + ADC1_COMMON->CCR |= ADC_CCR_TSEN; + modm::delay_us(135); + } + + /* Find empty sequence slot */ + while (0x0F != position_value) + { + if (position > 7) + return false; + position_value = ADC1->CHSELR & (ADC_CHSELR_SQ1_Msk << (4 * position)); + position_value = position_value >> (4 * position); + ++position; + } + --position; + uint32_t shift = 4 * position; + uint32_t mask = uint32_t(ADC_CHSELR_SQ1_Msk) << shift; + resetChannelConfigReady(); + ADC1->CHSELR &= (~mask | (uint32_t(channel) << shift)); + waitChannelConfigReady(); + return true; +} +%% endif \ No newline at end of file diff --git a/src/modm/platform/adc/stm32f0/adc_interrupt.cpp.in b/src/modm/platform/adc/stm32f0/adc_interrupt.cpp.in new file mode 100644 index 0000000000..3ae7310920 --- /dev/null +++ b/src/modm/platform/adc/stm32f0/adc_interrupt.cpp.in @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2009-2010, Fabian Greif + * Copyright (c) 2009-2010, Martin Rosekeit + * Copyright (c) 2012, 2014-2015, 2017, Niklas Hauser + * + * 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 "adc_interrupt.hpp" +#include +// ---------------------------------------------------------------------------- +modm::platform::AdcInterrupt{{ id }}::Handler +modm::platform::AdcInterrupt{{ id }}::handler([](){}); + + +MODM_ISR({{ irq }}) +{ + if (modm::platform::AdcInterrupt{{ id }}::getInterruptFlags()) { + modm::platform::AdcInterrupt{{ id }}::handler(); + } +} + diff --git a/src/modm/platform/adc/stm32f0/adc_interrupt.hpp.in b/src/modm/platform/adc/stm32f0/adc_interrupt.hpp.in new file mode 100644 index 0000000000..d2cc04a1a8 --- /dev/null +++ b/src/modm/platform/adc/stm32f0/adc_interrupt.hpp.in @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015-2017, Niklas Hauser + * Copyright (c) 2017, Fabian Greif + * + * 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_ADC_INTERRUPT_{{ id }}_HPP +#define MODM_STM32_ADC_INTERRUPT_{{ id }}_HPP + +#include "adc.hpp" +#include + + +namespace modm +{ + +namespace platform +{ + +/** + * ADC Interrupt module + * + * This class allows you to attach functions to the ADC Conversion + * Complete Interrupt via function pointers. + * Be aware however, that this implementation is slower and requires + * more resources than writing the function code directly into + * the interrupt service routines. + * + * @see AnalogSensors uses this implemenation. + * + * @ingroup modm_platform_adc_{{id}} + * @author Niklas Hauser + */ +class AdcInterrupt{{ id }} : public Adc{{ id }}, public modm::AdcInterrupt +{ +public: + static inline void + attachInterruptHandler(Handler handler) + { + AdcInterrupt{{ id }}::handler = handler; + } + + static Handler handler; +}; + +} // namespace platform + +} // namespace modm + +#endif // MODM_STM32_ADC_INTERRUPT_HPP diff --git a/src/modm/platform/adc/stm32f0/module.lb b/src/modm/platform/adc/stm32f0/module.lb index dc763098df..64e089a779 100644 --- a/src/modm/platform/adc/stm32f0/module.lb +++ b/src/modm/platform/adc/stm32f0/module.lb @@ -62,3 +62,5 @@ def build(env): env.template("adc.hpp.in") env.template("adc_impl.hpp.in") + env.template("adc_interrupt.hpp.in", "adc_interrupt.hpp") + env.template("adc_interrupt.cpp.in", "adc_interrupt.cpp")