diff --git a/examples/nucleo_g474re/adc_sequence_dma/main.cpp b/examples/nucleo_g474re/adc_sequence_dma/main.cpp new file mode 100644 index 0000000000..9a04683e9c --- /dev/null +++ b/examples/nucleo_g474re/adc_sequence_dma/main.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2024, 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 +#include + +int main() +{ + Board::initialize(); + + MODM_LOG_INFO << "STM32G4 ADC regular conversion sequence example" << modm::endl; + + // max. ADC clock for STM32G474: 60 MHz + // 170 MHz AHB clock / 4 = 42.5 MHz + Adc1::initialize( + Adc1::ClockMode::SynchronousPrescaler4, + Adc1::ClockSource::SystemClock, + Adc1::Prescaler::Disabled, + Adc1::CalibrationMode::SingleEndedInputsMode, + true); + + Adc1::connect(); + + constexpr auto sampleTime = Adc1::SampleTime::Cycles641; + constexpr std::array sequence{{ + // Perform dummy conversion (ADC errata 2.7.7 and 2.7.8) + {Adc1::getPinChannel(), sampleTime}, + // Read A0, A1, A3, A4 + {Adc1::getPinChannel(), sampleTime}, + {Adc1::getPinChannel(), sampleTime}, + {Adc1::getPinChannel(), sampleTime}, + {Adc1::getPinChannel(), sampleTime} + }}; + Adc1::setChannelSequence(sequence); + Adc1::setDmaMode(Adc1::DmaMode::OneShot); + + Dma1::enable(); + using DmaChannel = Dma1::Channel1; + DmaChannel::configure(Dma1::DataTransferDirection::PeripheralToMemory, Dma1::MemoryDataSize::Bit16, + Dma1::PeripheralDataSize::Bit16, Dma1::MemoryIncrementMode::Increment, + Dma1::PeripheralIncrementMode::Fixed); + DmaChannel::setPeripheralAddress(reinterpret_cast(Adc1::dataRegister())); + constexpr auto request = DmaChannel::RequestMapping::Request; + DmaChannel::setPeripheralRequest(); + + std::array samples{}; + + while (true) + { + DmaChannel::setMemoryAddress(reinterpret_cast(samples.data())); + DmaChannel::setDataLength(samples.size()); + DmaChannel::start(); + Adc1::startConversion(); + + while (!(DmaChannel::getInterruptFlags() & Dma1::InterruptFlags::TransferComplete)); + DmaChannel::stop(); + + const double factor = 3.3 / 4095; + MODM_LOG_INFO.printf("A0: %1.3f V\n", samples[1] * factor); + MODM_LOG_INFO.printf("A1: %1.3f V\n", samples[2] * factor); + MODM_LOG_INFO.printf("A3: %1.3f V\n", samples[3] * factor); + MODM_LOG_INFO.printf("A4: %1.3f V\n", samples[4] * factor); + MODM_LOG_INFO << '\n'; + modm::delay(500ms); + } + + return 0; +} diff --git a/examples/nucleo_g474re/adc_sequence_dma/project.xml b/examples/nucleo_g474re/adc_sequence_dma/project.xml new file mode 100644 index 0000000000..35d6e6add3 --- /dev/null +++ b/examples/nucleo_g474re/adc_sequence_dma/project.xml @@ -0,0 +1,12 @@ + + modm:nucleo-g474re + + + + + modm:debug + modm:platform:adc:1 + modm:platform:dma + modm:build:scons + + diff --git a/src/modm/platform/adc/stm32f3/adc.hpp.in b/src/modm/platform/adc/stm32f3/adc.hpp.in index e2814af448..893a0f64a2 100644 --- a/src/modm/platform/adc/stm32f3/adc.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc.hpp.in @@ -15,10 +15,13 @@ #ifndef MODM_STM32F3_ADC{{ id }}_HPP #define MODM_STM32F3_ADC{{ id }}_HPP -#include +#include +#include +#include #include "../device.hpp" #include #include +#include namespace modm { @@ -260,6 +263,13 @@ public: Cycles602 = 0b111, //< 601.5 ADC clock cycles }; %% endif + + struct SequenceChannel + { + Channel channel{}; + SampleTime sampleTime{}; + }; + enum class CalibrationMode : uint32_t { SingleEndedInputsMode = 0, @@ -280,6 +290,25 @@ public: %% endif }; + enum class DmaMode : uint32_t + { + Disabled = 0, +%% if target["family"] in ["h7"] and resolution == 12: + OneShot = ADC3_CFGR_DMAEN, + Circular = ADC3_CFGR_DMACFG | ADC3_CFGR_DMAEN, + Mask = Circular +%% elif target["family"] in ["h7"] and resolution == 16: + OneShot = ADC_CFGR_DMNGT_0, + Dfsdm = ADC_CFGR_DMNGT_1, + Circular = ADC_CFGR_DMNGT_1 | ADC_CFGR_DMNGT_0, + Mask = ADC_CFGR_DMNGT_Msk +%% else + OneShot = ADC_CFGR_DMAEN, + Circular = ADC_CFGR_DMACFG | ADC_CFGR_DMAEN, + Mask = Circular +%% endif + }; + enum class Interrupt : uint32_t { Ready = ADC_IER_ADRDYIE, @@ -414,6 +443,9 @@ public: setChannel(const Channel channel, const SampleTime sampleTime=static_cast(0b000)); + static inline bool + setChannelSequence(std::span sequence); + /// Setting the channel for a Pin template< class Gpio > static inline bool @@ -464,6 +496,9 @@ public: static inline void startConversion(); + static inline void + stopConversion(); + /** * @return If the conversion is finished. * @pre A conversion should have been started with startConversion() @@ -471,6 +506,9 @@ public: static inline bool isConversionFinished(); + static inline bool + isConversionSequenceFinished(); + /** * Start a new injected conversion sequence. * @@ -562,6 +600,16 @@ public: static inline void acknowledgeInterruptFlags(const InterruptFlag_t flags); + /// @return ADC data register pointer, for DMA use only. + static inline volatile uint32_t* + dataRegister() + { + return &(ADC{{ id }}->DR); + } + + static inline void + setDmaMode(DmaMode mode); + private: static inline bool configureChannel(Channel channel, SampleTime sampleTime); diff --git a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in index b273c02852..a8b59115bf 100644 --- a/src/modm/platform/adc/stm32f3/adc_impl.hpp.in +++ b/src/modm/platform/adc/stm32f3/adc_impl.hpp.in @@ -211,7 +211,6 @@ modm::platform::Adc{{ id }}::configureChannel(Channel channel, } - bool modm::platform::Adc{{ id }}::setChannel(Channel channel, SampleTime sampleTime) @@ -224,6 +223,42 @@ modm::platform::Adc{{ id }}::setChannel(Channel channel, return true; } +bool +modm::platform::Adc{{ id }}::setChannelSequence(std::span sequence) +{ + if (sequence.size() > 16 || sequence.size() == 0) { + return false; + } + + ADC{{ id }}->SQR1 = sequence.size() - 1; + + for (const auto [i, config] : modm::enumerate(sequence)) { + const auto [channel, sampleTime] = config; + if (!configureChannel(channel, sampleTime)) { + return false; + } + + if (i < 4) { + const auto shift = (i + 1) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR1 = (ADC{{ id }}->SQR1 & ~mask) | (std::to_underlying(channel) << shift); + } else if (i < 9) { + const auto shift = (i - 4) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR2 = (ADC{{ id }}->SQR2 & ~mask) | (std::to_underlying(channel) << shift); + } else if (i < 14) { + const auto shift = (i - 9) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR3 = (ADC{{ id }}->SQR3 & ~mask) | (std::to_underlying(channel) << shift); + } else { + const auto shift = (i - 14) * 6; + const auto mask = 0b111111u << shift; + ADC{{ id }}->SQR4 = (ADC{{ id }}->SQR4 & ~mask) | (std::to_underlying(channel) << shift); + } + } + return true; +} + void modm::platform::Adc{{ id }}::setFreeRunningMode(const bool enable) { @@ -239,17 +274,34 @@ modm::platform::Adc{{ id }}::startConversion() { // TODO: maybe add more interrupt flags acknowledgeInterruptFlags(InterruptFlag::EndOfRegularConversion | - InterruptFlag::EndOfSampling | InterruptFlag::Overrun); + InterruptFlag::EndOfSampling | InterruptFlag::Overrun | + InterruptFlag::EndOfRegularSequenceOfConversions); // starts single conversion for the regular group ADC{{ id }}->CR |= ADC_CR_ADSTART; } +void +modm::platform::Adc{{ id }}::stopConversion() +{ + ADC{{ id }}->CR |= ADC_CR_ADSTP | ADC_CR_JADSTP; + while ((ADC{{ id }}->CR & (ADC_CR_ADSTP | ADC_CR_JADSTP)) != 0); + + acknowledgeInterruptFlags(InterruptFlag::EndOfRegularConversion | + InterruptFlag::EndOfSampling | InterruptFlag::Overrun); +} + bool modm::platform::Adc{{ id }}::isConversionFinished() { return static_cast(getInterruptFlags() & InterruptFlag::EndOfRegularConversion); } +bool +modm::platform::Adc{{ id }}::isConversionSequenceFinished() +{ + return static_cast(getInterruptFlags() & InterruptFlag::EndOfRegularSequenceOfConversions); +} + void modm::platform::Adc{{ id }}::startInjectedConversionSequence() { @@ -305,6 +357,13 @@ modm::platform::Adc{{ id }}::isInjectedConversionFinished() return static_cast(getInterruptFlags() & InterruptFlag::EndOfInjectedSequenceOfConversions); } +void +modm::platform::Adc{{ id }}::setDmaMode(DmaMode mode) +{ + constexpr uint32_t mask = std::to_underlying(DmaMode::Mask); + ADC{{ id }}->CFGR = (ADC{{ id }}->CFGR & ~mask) | std::to_underlying(mode); +} + // ---------------------------------------------------------------------------- void modm::platform::Adc{{ id }}::enableInterruptVector(const uint32_t priority, diff --git a/src/modm/platform/adc/stm32f3/module.lb b/src/modm/platform/adc/stm32f3/module.lb index ca8fc074bc..636966a0e1 100644 --- a/src/modm/platform/adc/stm32f3/module.lb +++ b/src/modm/platform/adc/stm32f3/module.lb @@ -21,7 +21,7 @@ class Instance(Module): module.description = "Instance {}".format(self.instance) def prepare(self, module, options): - module.depends(":platform:adc") + module.depends(":platform:adc", ":math:algorithm") return True def build(self, env):