From 90ca98ef399759290bc11b8bed82f29e1bf0f1e7 Mon Sep 17 00:00:00 2001 From: oganigl Date: Wed, 28 Jan 2026 18:09:19 +0100 Subject: [PATCH 01/41] add all enums needed, I hope --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 147 +++++++++++++++++++++++++++++ Src/HALAL/Services/DFSDM/DFSDM.cpp | 0 2 files changed, 147 insertions(+) create mode 100644 Inc/HALAL/Services/DFSDM/DFSDM.hpp create mode 100644 Src/HALAL/Services/DFSDM/DFSDM.cpp diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp new file mode 100644 index 000000000..c517dcc1c --- /dev/null +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include "HALAL/Models/GPIO.hpp" +using ST_LIB::GPIODomain; +/* +Not added extremes detector +It doesn't make sense with the use that we have. + +This table lists all the DFSDM interrupt sources: +• End of conversion events, with separate flags for +regular and injected conversions. +• Data overrun events, with separate flags for regular +and injected conversions. +• Analog watchdog events. +• Short-circuit detector events. +• Channel clock absence event.Interrupts: + +*/ + +namespace ST_LIB { +struct DFSDMDomain{ + //Recommend to use Run please. + enum class Modes: uint8_t{ + Run, //Active + Sleep, //Active. Peripheral interrupts cause the device to exit Sleep Mode + Low_pw_run, //Active but low power + Low_pw_sleep, //Sleep + low power + Stop, //Frozen, peripheral register content is kept + Standby,//Powered-down, the peripheral must be reinitialized after exiting Standby Mode + Shutdown //Powered-down. Same as Standby + }; + enum class Filters: uint8_t{ + Sinc1, + Sinc2, + Sinc3, + Sinc4, + Sinc5, + FastSinc + }; + enum class Use_Pin: uint8_t{ + Clock_Out, + Serial_Channel + }; + //only expected to use SPI-Like mode + //20MHz max = max 80 Mhz sys clock divider by 4 + enum class SPI_Mode: uint8_t{ + Falling, + Rising + }; + enum class Data_Write: uint8_t{ + DMA, + CPU + }; + enum class Analog_Watchdog: uint8_t{ + Active, + Inactive + }; + //What happens when data exceed with watchdog + enum class Watchdog_Action: uint8_t { + interrupt, + Break_signal + } + //what data is monitoring the watchdog + enum class Watchdog_Type: uint8_t { + WD1, + WD2, + WD3 + }; + enum class Short_circuit: uint8_t{ + Active, + Inactive + }; + enum class Conversion_Type: uint8_t{ + Regular, //Less priority and can be interrupted by injected, in that case starts again + Injected + }; + enum class Regular_mode{ + Single, + Continuous + }; + enum class Injected_mode: uint8_t{ + scan, //all channels from the injected mode are converted when a trigger occurs + single //only one channel is converted + }; + struct Entry { + size_t gpio_idx; + uint16_t oversampling; + uint8_t integrator; + uint32_t offset; + uint32_t right_bit_shifting; + uint32_t high_threshold; + uint32_t low_threshold; + uint32_t detection_signal_saturated; // Number of 1s or 0s to detect configurable time + void* short_circuit_handler; + }; + struct DigitalInput { + GPIODomain::GPIO gpio; + using domain = DFSDMDomain; + + consteval DFSDM(const GPIODomain::Pin &pin,) + : gpio{pin, GPIODomain::OperationMode::INPUT, pull, speed} {} + + template consteval std::size_t inscribe(Ctx &ctx) const { + const auto gpio_idx = gpio.inscribe(ctx); + Entry e{.gpio_idx = gpio_idx}; + return ctx.template add(e, this); + } + }; + + static constexpr std::size_t max_instances{110}; + + struct Config { + size_t gpio_idx; + }; + + template + static consteval array build(span outputs) { + array cfgs{}; + + for (std::size_t i = 0; i < N; ++i) { + cfgs[i].gpio_idx = outputs[i].gpio_idx; + } + return cfgs; + } + + struct Instance { + GPIODomain::Instance *gpio_instance; + + GPIO_PinState read() { return gpio_instance->read(); } + }; + + template struct Init { + static inline std::array instances{}; + + static void init(std::span cfgs, + std::span gpio_instances) { + for (std::size_t i = 0; i < N; ++i) { + const auto &e = cfgs[i]; + + instances[i].gpio_instance = &gpio_instances[e.gpio_idx]; + } + } + }; +}; +} // namespace ST_LIB + +//Sinc1,2,3 -> oversampling from 1 to 32 diff --git a/Src/HALAL/Services/DFSDM/DFSDM.cpp b/Src/HALAL/Services/DFSDM/DFSDM.cpp new file mode 100644 index 000000000..e69de29bb From 8732116eeb10b8a69958e479099f0ab86d3e7ab3 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 13 Feb 2026 19:55:30 +0100 Subject: [PATCH 02/41] CLK finished, half way from finishing DFSDM.HPP --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 936 +++++++++++++++++++++++++---- Inc/ST-LIB_LOW/ST-LIB_LOW.hpp | 2 +- 2 files changed, 812 insertions(+), 126 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index c517dcc1c..bc5137b33 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -1,147 +1,833 @@ #pragma once #include "HALAL/Models/GPIO.hpp" +#include "HALAL/Models/Pin.hpp" +#define Oversampling_MAX 1024 +#define Oversampling_MAX_Filter_4 215 +#define Oversampling_MAX_Filter_5 73 +#define Possible_Pin_Channel 20 using ST_LIB::GPIODomain; -/* -Not added extremes detector -It doesn't make sense with the use that we have. -This table lists all the DFSDM interrupt sources: -• End of conversion events, with separate flags for -regular and injected conversions. -• Data overrun events, with separate flags for regular -and injected conversions. -• Analog watchdog events. -• Short-circuit detector events. -• Channel clock absence event.Interrupts: +namespace ST_LIB { + using Callback = void(*)(void); + extern void compile_error(const char *msg); -*/ + struct DFSDM_DOMAIN{ + /* Constant Values of Register*/ + //DatPack = 0 Standar + //DatMPx = 0 // External input + //ChinSel = 0 // It make sense for our use case. + //SPICKSel = ¿? + enum class SPICKSel: uint8_t{ + CLK_IN = 0, + NORMAL_CLK_OUT = 1, + CLK_DIVIDED_2_RISING = 2, + CLK_DIVIDED_2_FALLING = 3 + }; + enum class SPI_Type: uint8_t{ + SPI_RISING = 0, + SPI_FALLING = 1 + }; -namespace ST_LIB { -struct DFSDMDomain{ - //Recommend to use Run please. - enum class Modes: uint8_t{ - Run, //Active - Sleep, //Active. Peripheral interrupts cause the device to exit Sleep Mode - Low_pw_run, //Active but low power - Low_pw_sleep, //Sleep + low power - Stop, //Frozen, peripheral register content is kept - Standby,//Powered-down, the peripheral must be reinitialized after exiting Standby Mode - Shutdown //Powered-down. Same as Standby - }; - enum class Filters: uint8_t{ - Sinc1, - Sinc2, - Sinc3, - Sinc4, - Sinc5, - FastSinc - }; - enum class Use_Pin: uint8_t{ - Clock_Out, - Serial_Channel - }; - //only expected to use SPI-Like mode - //20MHz max = max 80 Mhz sys clock divider by 4 - enum class SPI_Mode: uint8_t{ - Falling, - Rising - }; - enum class Data_Write: uint8_t{ - DMA, - CPU - }; - enum class Analog_Watchdog: uint8_t{ - Active, - Inactive - }; - //What happens when data exceed with watchdog - enum class Watchdog_Action: uint8_t { - interrupt, - Break_signal + enum class Filter_Type : uint8_t { + FastSinc = 0, + Sinc1 = 1, + Sinc2 = 2, + Sinc3 = 3, + Sinc4 = 4, + Sinc5 = 5 + }; + + enum Fast_Conversion : uint8_t { + Disable = 0, + Enable = 1 + }; + + enum class Data_Write : uint8_t { + CPU = 0, + DMA = 1 + }; + + enum class Sync_Conversion : uint8_t { + Independent = 0, + Sync_With_Flt0 = 1 + }; + + enum class Regular_Mode : uint8_t { + Single = 0, + Continuous = 1 + }; + + enum class Analog_Watchdog_Fast_Mode : uint8_t { + After_Filter = 0, // AWFSEL = 0 + Channel_Data = 1 // AWFSEL = 1 + }; + //AWFSEL = 0 high precision, slow speed <- Ideally for our case, + //AWFSEL = 1 16 bits precision ultra high speed oversampling ratio 1..32 filter (1..3) 8 relojes de clock + + enum class Analog_Watchdog_Sinc: uint8_t{ + FastSinc = 0, + sinc1 = 1, + Sinc2 = 2, + Sinc3 = 3, + Sinc4 = 4, + Sinc5 = 5 + }; + + enum class Analog_Watchdog : uint8_t { + Disable = 0, + Enable = 1 + }; + + enum class Overrun : uint8_t { + Enable = 0, + Disable = 1 + }; + enum class Clock_Absence : uint8_t{ + Disable = 0, + Enable = 1 + }; + enum class Short_Circuit : uint8_t{ + Disable = 0, + Enable = 1 + }; + enum class Extreme_Detector : uint8_t{ + Disable = 0, + Enable = 1 + }; + + struct Entry{ + uint8_t channel; + uint32_t offset; + uint32_t right_shift; // right shift + SPICKSel spi_clock_sel; + SPI_Type spi_type; + Filter_Type filter_type; + uint16_t oversampling; //1..1024 + uint16_t integrator; //1..256 + uint8_t Short_Circuit_Count; // Number of bits with the same value to guess that has been a Short Circuit + + Data_Write rdma; + + Fast_Conversion fast; + Sync_Conversion rsync; + Regular_Mode rcont; + + Clock_Absence ckabie; + Overrun rovrie; + Short_Circuit scdie; + + Extreme_Detector exch; + uint8_t pulse_skip; // 0..63 Skip invalid values don't use please. + }; + static constexpr std::array,Possible_Pin_Channel> pin_to_channel = + {{ + {PE4,3},{PC0,4},{PC1,0},{PC3,1},{PC5,2}, + {PB1,1},{PF13,6},{PE7,2},{PE10,4},{PE12,5},{PB10,7},{PB12,1}, + {PB14,2},{PD9,3},{PC7,3},{PC11,5},{PD1,6},{PD6,1},{PD7,4}, + {PB6,5} + }}; + + static consteval uint8_t get_channel(const GPIODomain::Pin& pin){ + for(int i = 0; i < Possible_Pin_Channel;i++){ + if(pin_to_channel[i].first == pin){ + return pin_to_channel[i].second; + } + } + compile_error("This pin cannot be used as a DFSDM_Channel") + }; + + + + static consteval bool is_correct_oversampling(Filter_Type f, uint16_t osr) { + switch (f) { + case Filter_Type::FastSinc: + case Filter_Type::Sinc1: + case Filter_Type::Sinc2: + case Filter_Type::Sinc3: + return osr >= 1 && osr <= Oversampling_MAX; + + case Filter_Type::Sinc4: + return osr >= 1 && osr <= Oversampling_MAX_Filter_4; + + case Filter_Type::Sinc5: + return osr >= 1 && osr <= Oversampling_MAX_Filter_5; + } + return false; } - //what data is monitoring the watchdog - enum class Watchdog_Type: uint8_t { - WD1, - WD2, - WD3 - }; - enum class Short_circuit: uint8_t{ - Active, - Inactive - }; - enum class Conversion_Type: uint8_t{ - Regular, //Less priority and can be interrupted by injected, in that case starts again - Injected - }; - enum class Regular_mode{ - Single, - Continuous - }; - enum class Injected_mode: uint8_t{ - scan, //all channels from the injected mode are converted when a trigger occurs - single //only one channel is converted - }; - struct Entry { - size_t gpio_idx; - uint16_t oversampling; - uint8_t integrator; - uint32_t offset; - uint32_t right_bit_shifting; - uint32_t high_threshold; - uint32_t low_threshold; - uint32_t detection_signal_saturated; // Number of 1s or 0s to detect configurable time - void* short_circuit_handler; - }; - struct DigitalInput { - GPIODomain::GPIO gpio; - using domain = DFSDMDomain; - - consteval DFSDM(const GPIODomain::Pin &pin,) - : gpio{pin, GPIODomain::OperationMode::INPUT, pull, speed} {} - - template consteval std::size_t inscribe(Ctx &ctx) const { - const auto gpio_idx = gpio.inscribe(ctx); - Entry e{.gpio_idx = gpio_idx}; - return ctx.template add(e, this); + + + + + struct DFSDM{ + using domain = DFSDM_DOMAIN; + Entry e; + static constexpr size_t max_instances{8}; + consteval DFSDM(GPIODomain::Pin& pin,uint32_t offset,uint32_t right_shift, + SPICKSel spi_clock_sel,SPI_Type spi_type,Filter_Type filter_type = Filter_Type::FastSinc, + uint16_t oversampling = 1,uint8_t integrator = 1,uint8_t Short_Circuit_Count = 0xFF, + Data_Write rdma = Data_Write::DMA, Fast_Conversion fast = Fast_Conversion::Enable, + Sync_Conversion rsync = Sync_Conversion::Sync_With_Flt0, Regular_Mode rcont = Regular_Mode::Continuous, + Clock_Absence ckabie = Clock_Absence::Enable, Overrun rovrie = Overrun::Enable, + Short_Circuit scdie = Short_Circuit::Enable,Extreme_Detector exch = Extreme_Detector::Enable, + uint8_t pulse_skip = 0) + : e{ + .channel = get_channel(pin), + .right_shift = right_shift, + .spi_clock_sel = spi_clock_sel, + .spi_type = spi_type, + .filter_type = filter_type, + .oversampling = oversampling, + .integrator = integrator, + .Short_Circuit_Count = Short_Circuit_Count, + .rdma = rdma, + .fast = fast, + .rsync = rsync, + .rcont = rcont, + .ckabie = ckabie, + .rovrie = rovrie, + .scdie = scdie, + .exch = exch, + .pulse_skip = pulse_skip + } + { + if(integrator <= 0){ + compile_error("DFSDM_FILTER: Integrator out of range"); + } + if (!is_correct_oversampling(filter_type, oversampling)) + compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); + } - }; + template + consteval std::size_t inscribe(Ctx &ctx) const { + return ctx.template add(e, this); + } + struct Config { + uint8_t filter; + uint8_t channel; + DFSDM_Filter_TypeDef init_data_filter; + DFSDM_Channel_TypeDef init_data_channel; + + uint32_t latency_cycles; + }; + static consteval uint32_t compute_latency(const Entry& e){ + const uint32_t fosr = e.oversampling; + const uint32_t iosr = e.integrator; - static constexpr std::size_t max_instances{110}; + if (e.fast == Fast_Conversion::Enable && + e.rcont == Regular_Mode::Continuous) + { + return fosr * iosr; + } - struct Config { - size_t gpio_idx; - }; + if (e.filter_type == Filter_Type::FastSinc) { + return fosr * (iosr - 1 + 4) + 2; + } - template - static consteval array build(span outputs) { - array cfgs{}; + const uint32_t ford = static_cast(e.filter_type); + return fosr * (iosr - 1 + ford) + ford; + } + static consteval uint32_t make_fltfcr(const Entry& e) + { + return + (uint32_t(e.filter_type) << DFSDM_FLTFCR_FORD_Pos) | + (uint32_t(e.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | + (uint32_t(e.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); + } + static consteval uint32_t make_fltcr1(const Entry& e) + { + return + (uint32_t(e.fast) << DFSDM_FLTCR1_FAST_Pos) | + (uint32_t(e.channel)<< DFSDM_FLTCR1_RCH_Pos) | + (uint32_t(e.rdma) << DFSDM_FLTCR1_RDMAEN_Pos) | + (uint32_t(e.rsync) << DFSDM_FLTCR1_RSYNC_Pos) | + (uint32_t(e.rcont) << DFSDM_FLTCR1_RCONT_Pos) | + DFSDM_FLTCR1_DFEN; + } + static consteval uint32_t make_fltcr2(const Entry& e) + { + uint32_t v = 0; - for (std::size_t i = 0; i < N; ++i) { - cfgs[i].gpio_idx = outputs[i].gpio_idx; + // Extreme detector enable (bitmap) + if (e.exch == Extreme_Detector::Enable) + v |= (1u << (DFSDM_FLTCR2_EXCH_Pos + e.channel)); + // Global interrupts + v |= (uint32_t(e.rovrie) << DFSDM_FLTCR2_ROVRIE_Pos); + return v; } - return cfgs; - } + template + static consteval std::array build(std::span entries) { + std::array cfgs{}; + std::array channels_used; + std::array filters_used{0}; + bool filter_per_channel = (N <= 4) ? true : false; + for (size_t i = 0; i < N; ++i) { + DFSDM_Filter_TypeDef filter_config{}; + DFSDM_Channel_TypeDef channel_config{}; + const auto &e = entries[i]; - struct Instance { - GPIODomain::Instance *gpio_instance; + if(channels_used[e.channel] == true){ + compile_error("You have two pins using the same channel"); + } + channels_used[e.channel] = true; + auto& cfg = cfgs[i]; - GPIO_PinState read() { return gpio_instance->read(); } - }; + cfg.channel = e.channel; + if(filter_per_channel){ + cfg.filter = i; + }else{ + cfg.filter = e.channel / 4; + } + filter_config.FLTCR1 |= make_fltcr1(e); + filter_config.FLTCR2 |= make_fltcr2(e); + filter_config.FLTC |= make_fltfcr(e); + if(filters_used[cfg.filter] != 0 && cfgs[filters_used[cfg.filter]].filter_config != cfg.filter_config){ + compile_error("You have two channels that goes to the same filter with different filter configuration"); + } + filters_used[cfg.filter] = i; + + } + return cfgs; + }; - template struct Init { - static inline std::array instances{}; + struct Instance { + DFSDM_Filter_TypeDef *regs{}; + Callback watchdog_cb{}; + private: + bool is_enabled() const { + return (regs->FLTCR1 & DFSDM_FLTCR1_DFEN); + } - static void init(std::span cfgs, - std::span gpio_instances) { - for (std::size_t i = 0; i < N; ++i) { - const auto &e = cfgs[i]; + public: + bool enable() { + regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; + return is_enabled(); + } - instances[i].gpio_instance = &gpio_instances[e.gpio_idx]; - } + bool disable() { + regs->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; + return !is_enabled(); + } + + bool start() { + if(!enable()) return false; + regs->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; + return true; + } + + void modify_sync_conversion(Sync_Conversion type) { + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + + regs->FLTCR1 &= ~DFSDM_FLTCR1_RSYNC_Msk; + regs->FLTCR1 |= (uint32_t(type) << DFSDM_FLTCR1_RSYNC_Pos); + + if (was_enabled) enable(); + } + void modify_mode(Regular_Mode mode) { + regs->FLTCR1 &= ~DFSDM_FLTCR1_RCONT_Msk; + regs->FLTCR1 |= (uint32_t(mode) << DFSDM_FLTCR1_RCONT_Pos); + } + + bool modify_oversampling(uint16_t oversampling) { + + if (oversampling == 0) return false; + + uint32_t ford = (regs->FLTFCR & DFSDM_FLTFCR_FORD_Msk) >> DFSDM_FLTFCR_FORD_Pos; + + if (ford <= 3 && oversampling > Oversampling_MAX) return false; + if (ford == 4 && oversampling > Oversampling_MAX_Filter_4) return false; + if (ford == 5 && oversampling > Oversampling_MAX_Filter_5) return false; + + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + + regs->FLTFCR &= ~DFSDM_FLTFCR_FOSR_Msk; + regs->FLTFCR |= ((uint32_t)(oversampling -1) << DFSDM_FLTFCR_FOSR_Pos); + + if (was_enabled) enable(); + + return true; + } + + bool modify_integrator(uint8_t integrator) { + + if (integrator == 0 || integrator > 256) return false; + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + + regs->FLTFCR &= ~DFSDM_FLTFCR_IOSR_Msk; + regs->FLTFCR |= ((integrator -1) << DFSDM_FLTFCR_IOSR_Pos); + + if (was_enabled) enable(); + return true; + } + + bool modify_filter_order(Filter_Type type) { + + uint32_t fosr =((regs->FLTFCR & DFSDM_FLTFCR_FOSR_Msk) >> DFSDM_FLTFCR_FOSR_Pos); + + if (type == Filter_Type::Sinc4 && fosr > Oversampling_MAX_Filter_4) return false; + if (type == Filter_Type::Sinc5 && fosr > Oversampling_MAX_Filter_5) return false; + + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + + regs->FLTFCR &= ~DFSDM_FLTFCR_FORD_Msk; + regs->FLTFCR |= (uint32_t(type) + << DFSDM_FLTFCR_FORD_Pos); + + if (was_enabled) enable(); + return true; + } + + uint32_t check_latency_cycles() { + return regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; + } + + uint32_t check_min_extreme_detector() { + return regs->FLTEXMIN >> DFSDM_FLTEXMIN_EXMIN_Pos; + } + + uint32_t check_max_extreme_detector() { + return regs->FLTEXMAX >> DFSDM_FLTEXMAX_EXMAX_Pos; + } + + bool clear_watchdog_high() { + regs->FLTAWCFR |= DFSDM_FLTAWCFR_CLRAWHTF; + return true; + } + + bool clear_watchdog_low() { + regs->FLTAWCFR |= DFSDM_FLTAWCFR_CLRAWLTF; + return true; + } + + bool modify_analog_watchdog_lth(uint32_t value) { + + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; + bool fast = (regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); + + if (fast) + regs->FLTAWLTR = (value & 0xFFFF) << (DFSDM_FLTAWLTR_AWLT_Pos + DFSDM_FLTAWLTR_AWLT_Pos); // Only 16 bits + else + regs->FLTAWLTR = (value & 0xFFFFFF) << DFSDM_FLTAWLTR_AWLT_Pos; // 24 bits + + if (was_enabled) enable(); + return true; + } + + bool modify_analog_watchdog_hth(uint32_t value) { + + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; + bool fast = (regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); + + if (fast) + regs->FLTAWHTR = (value & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + else + regs->FLTAWHTR = (value & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; + + if (was_enabled) enable(); + return true; + } + + uint32_t read_data_from_filter_register() { + + if (!(regs->FLTISR & DFSDM_FLTISR_REOCF)) + return 0xFFFFFFFFu; + + return regs->FLTRDATAR >> DFSDM_FLTRDATAR_RDATA_Pos; + } + + bool activate_dma_reading() { + + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + + regs->FLTCR1 |= DFSDM_FLTCR1_RDMAEN; + + if (was_enabled) enable(); + return true; + } + + bool activate_cpu() { + + bool was_enabled = is_enabled(); + if (was_enabled) disable(); + + regs->FLTCR1 &= ~DFSDM_FLTCR1_RDMAEN; + + if (was_enabled) enable(); + return true; + } +}; + + + template struct Init { + static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { + DFSDM1_Filter0, + DFSDM1_Filter1, + DFSDM1_Filter2, + DFSDM1_Filter3 + }; + static inline std::array instances{}; + + static void init(std::span cfgs) { + + for (std::size_t i = 0; i < N; ++i) { + const auto &cfg = cfgs[i]; + auto &inst = instances[i]; + inst.regs = filter_hw[i]; + if (!cfg.used_filter) + continue; + + inst.regs = filter_hw[i]; + inst.latency_cycles = cfg.latency_cycles; + inst.watchdog_cb = nullptr; + + inst.regs->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; + + inst.regs->FLTCR1 = cfg.init_data.FLTCR1; + inst.regs->FLTCR2 = cfg.init_data.FLTCR2; + inst.regs->FLTFCR = cfg.init_data.FLTFCR; + + //clean flags + inst.regs->FLTISR = 0xFFFFFFFFu; + //enable the filter + inst.regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; + switch(i){ + case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; + case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; + case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; + case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + } + + } + //activate NVIC for every filter + + } + }; +}; +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct DFSDM_CLK_DOMAIN{ + + static constexpr GPIODomain::Pin valid_clk_pins[] = { + {GPIODomain::Port::C, 2}, + {GPIODomain::Port::B, 0}, + {GPIODomain::Port::E, 9}, + {GPIODomain::Port::D, 3}, + {GPIODomain::Port::D, 10} + }; + static consteval void is_valid_dfsdm_clk_pin(const GPIODomain::Port port, uint32_t pin) { + bool found = false; + for (auto &p : valid_clk_pins) { + if (p.port == port && p.pin == pin) { + found = true; + break; + } + } + if (!found) { + compile_error("This pin cannot be used as DFSDM CLK OUT"); + } + } + + static consteval GPIODomain::AlternateFunction dfsdm_clk_af(const GPIODomain::Pin& pin) { + if (pin.port == GPIODomain::Port::C && pin.pin == 2) + return GPIODomain::AlternateFunction::AF4; + return GPIODomain::AlternateFunction::AF6; //In every other case } - }; + struct Entry{ + size_t gpio_idx; + uint8_t clk_divider; + }; + + struct DFSDM_CLK{ + using domain = DFSDM_CLK_DOMAIN; + GPIODomain::GPIO gpio; + Entry e; + uint8_t clk_divider; + consteval DFSDM_CLK(const GPIODomain::Pin &pin,uint8_t clk_divider = 4): + gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None, GPIODomain::Speed::VeryHigh,dfsdm_clk_af(pin)}, + clk_divider(clk_divider) + { + is_valid_dfsdm_clk_pin(pin.port,pin.pin); + if(clk_divider < 4){ + compile_error("The divider must be at least frequency/4"); + } + } + template consteval std::size_t inscribe(Ctx &ctx) const{ + const auto gpio_idx = gpio.inscribe(ctx); + Entry e{.gpio_idx = gpio_idx,.clk_divider = clk_divider}; + return ctx.template add(e,this); + } + static constexpr std::size_t max_instances{1}; + struct Config{ + size_t gpio_idx; + uint8_t clk_divider; + }; + template + static consteval std::array build(std::span entries) { + std::array cfgs{}; + static_assert(N == 1,"You can't have more than one clock_out"); + for (std::size_t i = 0; i < N; ++i) { + cfgs[i] = { + .gpio_idx = entries[i].gpio_idx, + .clk_divider = entries[i].clk_divider + }; + } + return cfgs; + } + struct Instance{ + GPIODomain::Instance *gpio_instance; + uint8_t clk_divider; + /*Already called in init()*/ + void init(){ + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM Clocki OUT in RCC by default it uses rcc_pclk2(80MH) + //CKOUT = kernell clock (rcc_pclk2) + //The channel 0 also allows to configure the CKOUT + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTSRC; + + //CKOUT DivideR. Divider = CKOUTDIV + 1 + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTDIV; + DFSDM1_Channel0->CHCFGR1 |= (clk_divider -1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; + + //enable the CKOUT + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + } + bool disable(){ + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN; + return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN) == 0; + } + bool enable(){ + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + return(DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN); + } + bool change_divider(uint8_t div){ + if(div < 4) return false; + clk_divider = div; + if(disable()){ + init(); + return true; + } + return false; + } + + }; + template + struct Init { + static inline std::array instances{}; + static void init(std::span cfgs,std::span gpio_instances) { + //add ckaie scdie + const auto &c = cfgs[0]; + auto &inst = instances[0]; + inst.gpio = &gpio_instances[c.gpio_idx]; + inst.clk_divider = c.clk_divider; + inst.init(); + } + }; + }; + +}; }; -} // namespace ST_LIB +// /*Only is going to use filter fastsync and from 1..3*/ + //bool choose_latency(uint32_t latency) + //{ + //if (latency == 0) return false; + + //bool was_enabled = is_enabled(); + //if (was_enabled) disable(); + + //uint32_t best_error = 0xFFFFFFFF; + //uint32_t best_ford = 0; + //uint32_t best_fosr = 0; + //uint32_t best_iosr = 0; + + //// Solo filtros 0..3 + //for (uint32_t ford = 0; ford <= 3; ++ford) + //{ + //for (uint32_t iosr = 1; iosr <= 256; ++iosr) + //{ + //uint32_t denom = (iosr - 1 + ford); + //if (denom == 0) continue; + + //uint32_t fosr = (latency > ford) + //? (latency - ford) / denom + //: 0; + + //if (fosr < 1 || fosr > 1024) continue; + + //uint32_t real_latency = + //fosr * denom + ford; + + //uint32_t error = + //(real_latency > latency) + //? (real_latency - latency) + //: (latency - real_latency); + + //if (error < best_error) + //{ + //best_error = error; + //best_ford = ford; + //best_fosr = fosr; + //best_iosr = iosr; + + //if (error == 0) break; + //} + //} + + //if (best_error == 0) break; + //} + + //if (best_fosr == 0) + //{ + //if (was_enabled) enable(); + //return false; + //} + + //regs->FLTFCR &= + //~(DFSDM_FLTFCR_FORD_Msk | + //DFSDM_FLTFCR_FOSR_Msk | + //DFSDM_FLTFCR_IOSR_Msk); + + //regs->FLTFCR |= (best_ford << DFSDM_FLTFCR_FORD_Pos); + //regs->FLTFCR |= ((best_fosr - 1) << DFSDM_FLTFCR_FOSR_Pos); + //regs->FLTFCR |= ((best_iosr - 1) << DFSDM_FLTFCR_IOSR_Pos); + + //if (was_enabled) enable(); + //return true; + //} + + //bool choose_latency(uint32_t latency,Filter_Type filter_type) { + //if (latency == 0) return false; + //uint32_t ford = static_cast(filter_type); + //uint32_t oversampling_max = (ford <= 3) ? Oversampling_MAX : (ford == 4) ? Oversampling_MAX_Filter_4 : Oversampling_MAX_Filter_5; + //bool was_enabled = is_enabled(); + + //uint32_t best_error = 0xFFFFFFFF; + //uint32_t best_fosr = 0; + //uint32_t best_iosr = 0; + + //for (uint32_t iosr = 1; iosr <= 256; ++iosr) + //{ + //uint32_t denom = (iosr - 1 + ford); + //if (denom == 0) continue; + //uint32_t fosr = + //(latency > ford) + //? (latency - ford) / denom + //: 0; + + //if (fosr < 1 || fosr > oversampling_max) continue; + + //uint32_t real_latency = + //fosr * denom + ford; + + //uint32_t error = + //(real_latency > latency) + //? (real_latency - latency) + //: (latency - real_latency); + + //if (error < best_error) + //{ + //best_error = error; + //best_fosr = fosr; + //best_iosr = iosr; + + //if (error == 0) break; + //} + //} + + //if (best_fosr == 0) return false; + //if (was_enabled) disable(); + //regs->FLTFCR &= + //~(DFSDM_FLTFCR_FORD_Msk | + //DFSDM_FLTFCR_FOSR_Msk | + //DFSDM_FLTFCR_IOSR_Msk); + + //regs->FLTFCR |= (ford << DFSDM_FLTFCR_FORD_Pos); + //regs->FLTFCR |= ((best_fosr - 1) << DFSDM_FLTFCR_FOSR_Pos); + //regs->FLTFCR |= ((best_iosr - 1) << DFSDM_FLTFCR_IOSR_Pos); + + //if (was_enabled) enable(); + //return true; + //} + //bool choose_latency(uint32_t latency,Filter_Type filter_type, uint16_t oversampling){ + //if (latency == 0) return false; + + //uint32_t ford = static_cast(filter_type); + //if (ford > 3) return false; + //uint32_t oversampling_max = (ford <= 3) ? Oversampling_MAX : (ford == 4) ? Oversampling_MAX_Filter_4 : Oversampling_MAX_Filter_5; + //if (oversampling < 1 || oversampling > oversampling_max) return false; + //bool was_enabled = is_enabled(); + + //uint32_t best_error = 0xFFFFFFFF; + //uint32_t best_iosr = 0; + + //for (uint32_t iosr = 1; iosr <= 256; ++iosr) + //{ + //uint32_t real_latency = + //oversampling * + //(iosr - 1 + ford) + //+ ford; + + //uint32_t error = + //(real_latency > latency) + //? (real_latency - latency) + //: (latency - real_latency); + + //if (error < best_error) + //{ + //best_error = error; + //best_iosr = iosr; + + //if (error == 0) break; + //} + //} + + //if (best_iosr == 0) return false; + //if(was_enabled) disable(); + //regs->FLTFCR &= + //~(DFSDM_FLTFCR_FORD_Msk | + //DFSDM_FLTFCR_FOSR_Msk | + //DFSDM_FLTFCR_IOSR_Msk); + + //regs->FLTFCR |= (ford << DFSDM_FLTFCR_FORD_Pos); + //regs->FLTFCR |= ((oversampling - 1) + //<< DFSDM_FLTFCR_FOSR_Pos); + //regs->FLTFCR |= ((best_iosr - 1) + //<< DFSDM_FLTFCR_IOSR_Pos); + + //if (was_enabled) enable(); + //return true; + //} -//Sinc1,2,3 -> oversampling from 1 to 32 diff --git a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp index b73165c24..7ef11d46e 100644 --- a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp +++ b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp @@ -2,7 +2,7 @@ #include "ST-LIB_LOW/DigitalOutput2.hpp" #include "ST-LIB_LOW/DigitalInput2.hpp" - +#include "HALAL/Services/DFSDM/DFSDM.hpp" #include "Clocks/Counter.hpp" #include "Clocks/Stopwatch.hpp" #include "Sensors/LinearSensor/LinearSensor.hpp" From 3445aa2904a936728636fdff0d747bf149817dd3 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 27 Feb 2026 12:15:49 +0100 Subject: [PATCH 03/41] First total draft of dfsdm finished --- Inc/HALAL/HALAL.hpp | 2 + Inc/HALAL/Services/DFSDM/DFSDM.hpp | 911 +++++++++++++++-------------- Inc/ST-LIB.hpp | 14 +- Src/HALAL/Services/DFSDM/DFSDM.cpp | 23 + 4 files changed, 501 insertions(+), 449 deletions(-) diff --git a/Inc/HALAL/HALAL.hpp b/Inc/HALAL/HALAL.hpp index 19a7b7c87..3268088c2 100644 --- a/Inc/HALAL/HALAL.hpp +++ b/Inc/HALAL/HALAL.hpp @@ -47,6 +47,8 @@ #include "HALAL/HardFault/HardfaultTrace.h" #include "HALAL/Benchmarking_toolkit/DataWatchpointTrace/DataWatchpointTrace.hpp" + +#include "HALAL/Services/DFSDM/DFSDM.hpp" #ifdef STLIB_ETH #include "HALAL/Models/Packets/Packet.hpp" #include "HALAL/Models/Packets/Order.hpp" diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index bc5137b33..cd0bf6838 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -6,6 +6,8 @@ #define Oversampling_MAX_Filter_4 215 #define Oversampling_MAX_Filter_5 73 #define Possible_Pin_Channel 20 +#define OFFSET_MAX (1 << 23) - 1 +#define OFFSET_MIN -(1 << 23) using ST_LIB::GPIODomain; namespace ST_LIB { @@ -64,20 +66,6 @@ namespace ST_LIB { }; //AWFSEL = 0 high precision, slow speed <- Ideally for our case, //AWFSEL = 1 16 bits precision ultra high speed oversampling ratio 1..32 filter (1..3) 8 relojes de clock - - enum class Analog_Watchdog_Sinc: uint8_t{ - FastSinc = 0, - sinc1 = 1, - Sinc2 = 2, - Sinc3 = 3, - Sinc4 = 4, - Sinc5 = 5 - }; - - enum class Analog_Watchdog : uint8_t { - Disable = 0, - Enable = 1 - }; enum class Overrun : uint8_t { Enable = 0, @@ -95,30 +83,47 @@ namespace ST_LIB { Disable = 0, Enable = 1 }; - + enum class Trigger_Timer_Source : uint8_t { + Unused, + Tim1, + Tim3, + Tim4, + Tim7, + Tim8, + Tim16, + Tim23, + Tim24, + Exti11, + Exti15, + Lptim1, + Lptim2, + Lptim3 + }; + enum class Type_Conversion: uint8_t{ + Regular, + Injected + }; struct Entry{ uint8_t channel; - uint32_t offset; + int32_t offset; uint32_t right_shift; // right shift SPICKSel spi_clock_sel; - SPI_Type spi_type; + SPI_Type spi_type; + Type_Conversion type_conv; + Trigger_Timer_Source trigger_conv; Filter_Type filter_type; uint16_t oversampling; //1..1024 uint16_t integrator; //1..256 uint8_t Short_Circuit_Count; // Number of bits with the same value to guess that has been a Short Circuit Data_Write rdma; - + + Fast_Conversion fast; Sync_Conversion rsync; Regular_Mode rcont; - - Clock_Absence ckabie; - Overrun rovrie; - Short_Circuit scdie; - Extreme_Detector exch; - uint8_t pulse_skip; // 0..63 Skip invalid values don't use please. + uint8_t clock_divider; //has to be the same between ckout and here }; static constexpr std::array,Possible_Pin_Channel> pin_to_channel = {{ @@ -134,8 +139,8 @@ namespace ST_LIB { return pin_to_channel[i].second; } } - compile_error("This pin cannot be used as a DFSDM_Channel") - }; + compile_error("This pin cannot be used as a DFSDM_Channel"); + } @@ -158,57 +163,67 @@ namespace ST_LIB { - + static constexpr size_t max_instances{8}; struct DFSDM{ using domain = DFSDM_DOMAIN; Entry e; - static constexpr size_t max_instances{8}; - consteval DFSDM(GPIODomain::Pin& pin,uint32_t offset,uint32_t right_shift, - SPICKSel spi_clock_sel,SPI_Type spi_type,Filter_Type filter_type = Filter_Type::FastSinc, + + consteval DFSDM(GPIODomain::Pin& pin,SPICKSel spi_clock_sel,SPI_Type spi_type, Type_Conversion type_conv = Type_Conversion::Regular, + int32_t offset = 0,uint8_t right_shift = 0,Trigger_Timer_Source trigger_conv = Trigger_Timer_Source::Unused,Filter_Type filter_type = Filter_Type::FastSinc, uint16_t oversampling = 1,uint8_t integrator = 1,uint8_t Short_Circuit_Count = 0xFF, Data_Write rdma = Data_Write::DMA, Fast_Conversion fast = Fast_Conversion::Enable, Sync_Conversion rsync = Sync_Conversion::Sync_With_Flt0, Regular_Mode rcont = Regular_Mode::Continuous, - Clock_Absence ckabie = Clock_Absence::Enable, Overrun rovrie = Overrun::Enable, - Short_Circuit scdie = Short_Circuit::Enable,Extreme_Detector exch = Extreme_Detector::Enable, - uint8_t pulse_skip = 0) - : e{ - .channel = get_channel(pin), - .right_shift = right_shift, - .spi_clock_sel = spi_clock_sel, - .spi_type = spi_type, - .filter_type = filter_type, - .oversampling = oversampling, - .integrator = integrator, - .Short_Circuit_Count = Short_Circuit_Count, - .rdma = rdma, - .fast = fast, - .rsync = rsync, - .rcont = rcont, - .ckabie = ckabie, - .rovrie = rovrie, - .scdie = scdie, - .exch = exch, - .pulse_skip = pulse_skip - } - { - if(integrator <= 0){ - compile_error("DFSDM_FILTER: Integrator out of range"); + uint8_t clock_divider = 4) + : e{ + .channel = get_channel(pin), + .offset = offset, + .right_shift = right_shift, + .spi_clock_sel = spi_clock_sel, + .spi_type = spi_type, + .type_conv = type_conv, + .trigger_conv = trigger_conv, + .filter_type = filter_type, + .oversampling = oversampling, + .integrator = integrator, + .Short_Circuit_Count = Short_Circuit_Count, + .rdma = rdma, + .fast = fast, + .rsync = rsync, + .rcont = rcont, + .clock_divider = clock_divider, } - if (!is_correct_oversampling(filter_type, oversampling)) - compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); - - } - template - consteval std::size_t inscribe(Ctx &ctx) const { - return ctx.template add(e, this); - } + { + if(offset > OFFSET_MAX || offset < OFFSET_MIN){ + compile_error("Your offset is bigger than the maximum size"); + } + if(right_shift > 0x000000FF){ + compile_error("Your right_shift is bigger than the maximum size"); + } + if(integrator <= 0){ + compile_error("DFSDM_FILTER: Integrator out of range"); + } + if (!is_correct_oversampling(filter_type, oversampling)){ + compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); + } + if (clock_divider < 4){ + compile_error("DFSDM_CLKOUTDIV has to be at least 4"); + } + + } + template + consteval std::size_t inscribe(Ctx &ctx) const { + return ctx.template add(e, this); + } + }; struct Config { - uint8_t filter; - uint8_t channel; DFSDM_Filter_TypeDef init_data_filter; DFSDM_Channel_TypeDef init_data_channel; - + uint32_t latency_cycles; + Type_Conversion type_conv; + + uint8_t filter; + uint8_t channel; }; static consteval uint32_t compute_latency(const Entry& e){ const uint32_t fosr = e.oversampling; @@ -227,6 +242,25 @@ namespace ST_LIB { const uint32_t ford = static_cast(e.filter_type); return fosr * (iosr - 1 + ford) + ford; } + static consteval uint32_t get_trigger(Trigger_Timer_Source trig){ + switch(trig){ + case Trigger_Timer_Source::Tim1 : return 0; + case Trigger_Timer_Source::Tim3 : return 4; + case Trigger_Timer_Source::Tim4 : return 5; + case Trigger_Timer_Source::Tim7 : return 8; + case Trigger_Timer_Source::Tim8 : return 2; + case Trigger_Timer_Source::Tim16 : return 6; + case Trigger_Timer_Source::Tim23 : return 11; + case Trigger_Timer_Source::Tim24 : return 12; + case Trigger_Timer_Source::Exti11 : return 24; + case Trigger_Timer_Source::Exti15 : return 25; + case Trigger_Timer_Source::Lptim1 : return 26; + case Trigger_Timer_Source::Lptim2 : return 27; + case Trigger_Timer_Source::Lptim3 : return 28; + case Trigger_Timer_Source::Unused : break; + } + return 0; + } static consteval uint32_t make_fltfcr(const Entry& e) { return @@ -234,314 +268,466 @@ namespace ST_LIB { (uint32_t(e.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | (uint32_t(e.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); } - static consteval uint32_t make_fltcr1(const Entry& e) - { - return - (uint32_t(e.fast) << DFSDM_FLTCR1_FAST_Pos) | - (uint32_t(e.channel)<< DFSDM_FLTCR1_RCH_Pos) | - (uint32_t(e.rdma) << DFSDM_FLTCR1_RDMAEN_Pos) | - (uint32_t(e.rsync) << DFSDM_FLTCR1_RSYNC_Pos) | - (uint32_t(e.rcont) << DFSDM_FLTCR1_RCONT_Pos) | - DFSDM_FLTCR1_DFEN; + static consteval uint32_t make_fltcr2_global(const Entry& e){ + //Activate the interrupt, to activate the continous detection, activate in execution the channel + return + DFSDM_FLTCR2_CKABIE | + DFSDM_FLTCR2_SCDIE; + } + static consteval uint32_t make_fltcr2(const Entry& e){ + uint32_t v = 0; + if(e.rdma == Data_Write::CPU){ + v |= DFSDM_FLTCR2_REOCIE; + v |= DFSDM_FLTCR2_JEOCIE; + } + //Activate the interrupt AWDIE,in case of using CPU also activating REOCIE + v |= DFSDM_FLTCR2_AWDIE; + return v; } - static consteval uint32_t make_fltcr2(const Entry& e) + static consteval uint32_t make_fltcr1(const Entry& e,uint32_t filter) { uint32_t v = 0; + + if(e.type_conv == Type_Conversion::Regular){ + v |= (uint32_t(e.rdma) << DFSDM_FLTCR1_RDMAEN_Pos); + v |= (uint32_t(e.fast) << DFSDM_FLTCR1_FAST_Pos); + v |= (uint32_t(e.rsync) << DFSDM_FLTCR1_RSYNC_Pos); + v |= (uint32_t(e.rcont) << DFSDM_FLTCR1_RCONT_Pos); + }else if(e.type_conv == Type_Conversion::Injected){ + v |= DFSDM_FLTCR1_JSCAN; // activate conversion of the entire group + v |= (uint32_t)(e.rdma) << DFSDM_FLTCR1_JDMAEN_Pos; + if(e.trigger_conv != Trigger_Timer_Source::Unused){ + v |= DFSDM_FLTCR1_JEXTEN_0; //with the risings + if(filter == 0){ + v |= get_trigger(e.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; + } + if(e.rsync == Sync_Conversion::Sync_With_Flt0){ + v |= DFSDM_FLTCR1_JSYNC; + + }else{ + v |= get_trigger(e.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; + } + } + } + return v; + + } + static consteval uint32_t make_chcfgr1(const Entry& e){ + uint32_t v = 0; + //DATPACK = 0 -> Standard mode + //DATMPX = 0 -> Comes from an external serial input + //Chinsel = 0 -> channel input are taken from pin of the same channel y + v |= uint32_t(e.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; + v |= uint32_t (e.spi_type) << DFSDM_CHCFGR1_SITP_Pos; + return v; + + } + static consteval uint32_t make_chcfgr2(const Entry& e){ + uint32_t v = 0; + v |= (e.offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; + v |= uint8_t(e.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; + return v; + } + static consteval uint32_t make_chawscdr(const Entry& e){ + uint32_t v = 0; + v |= uint32_t(e.Short_Circuit_Count) << DFSDM_CHAWSCDR_SCDT_Pos; + return v; + } - // Extreme detector enable (bitmap) - if (e.exch == Extreme_Detector::Enable) - v |= (1u << (DFSDM_FLTCR2_EXCH_Pos + e.channel)); - // Global interrupts - v |= (uint32_t(e.rovrie) << DFSDM_FLTCR2_ROVRIE_Pos); + static consteval uint32_t global_config_via_channel_0(const Entry& e){ + uint32_t v = 0; + //ckoutsrc = 0 + v |= uint32_t(e.clock_divider - 1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; return v; } template static consteval std::array build(std::span entries) { std::array cfgs{}; - std::array channels_used; - std::array filters_used{0}; + std::array channels_used{false}; + std::array filters_used{-1,-1,-1,-1}; + cfgs[0].init_data_channel |= global_config_via_channel_0(entries[0]); + cfgs[0].init_data_filter |= make_fltcr2_global(entries[0]); bool filter_per_channel = (N <= 4) ? true : false; for (size_t i = 0; i < N; ++i) { - DFSDM_Filter_TypeDef filter_config{}; - DFSDM_Channel_TypeDef channel_config{}; - const auto &e = entries[i]; + const Entry &e = entries[i]; if(channels_used[e.channel] == true){ compile_error("You have two pins using the same channel"); } channels_used[e.channel] = true; - auto& cfg = cfgs[i]; + Config& cfg = cfgs[i]; cfg.channel = e.channel; if(filter_per_channel){ cfg.filter = i; }else{ - cfg.filter = e.channel / 4; + cfg.filter = e.channel / 2; } - filter_config.FLTCR1 |= make_fltcr1(e); - filter_config.FLTCR2 |= make_fltcr2(e); - filter_config.FLTC |= make_fltfcr(e); - if(filters_used[cfg.filter] != 0 && cfgs[filters_used[cfg.filter]].filter_config != cfg.filter_config){ + cfg.init_data_filter.FLTCR1 |= make_fltcr1(e,cfg.filter); + cfg.init_data_filter.FLTCR2 |= make_fltcr2(e); + cfg.init_data_filter.FLTFCR |= make_fltfcr(e); + cfg.init_data_channel.CHCFGR1 |= make_chcfgr1(e); + cfg.init_data_channel.CHCFGR2 |= make_chcfgr2(e); + cfg.init_data_channel.CHAWSCDR |= make_chawscdr(e); + cfg.latency_cycles = compute_latency(e); + if(filters_used[cfg.filter] != -1 && cfgs[filters_used[cfg.filter]].init_data_filter != cfg.init_data_filter){ compile_error("You have two channels that goes to the same filter with different filter configuration"); } filters_used[cfg.filter] = i; - } return cfgs; - }; + } struct Instance { - DFSDM_Filter_TypeDef *regs{}; + DFSDM_Filter_TypeDef *filter_regs{}; + DFSDM_Channel_TypeDef *channel_regs{}; + Callback watchdog_cb{}; + Callback short_circuit_cb{}; + Callback clock_absence_cb{}; + + uint32_t latency_cycles; + uint8_t channel; + uint8_t filter; + Type_Conversion type_conv; + + int32_t* buffer{}; + size_t length_buffer{}; + size_t idx{}; private: - bool is_enabled() const { - return (regs->FLTCR1 & DFSDM_FLTCR1_DFEN); + //split in differents categories the enable to clearness + bool is_enabled_channel() const{ + return (channel_regs->CHCFGR1 & DFSDM_CHCFGR1_CHEN_Msk); + } + bool is_enabled_filter() const { + return (filter_regs->FLTCR1 & DFSDM_FLTCR1_DFEN_Msk); + } + bool is_enabled_DFSDM() const{ + return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN_Msk); + } + void enable_filter() { + filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; + } + void enable_channel(){ + channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + } + void enable_DFSDM_Peripheral(){ + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + } + void disable_filter() { + filter_regs->FLTCR1 &= ~(DFSDM_FLTCR1_DFEN_Msk); + } + void disable_channel(){ + channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN); + } + void disable_DFSDM_Peripheral(){ + DFSDM1_Channel0->CHCFGR1 &= ~(DFSDM_CHCFGR1_DFSDMEN); } public: - bool enable() { - regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; - return is_enabled(); + DFSDM_Filter_TypeDef* get_filter_struct() const{ + return filter_regs; } - - bool disable() { - regs->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; - return !is_enabled(); + DFSDM_Channel_TypeDef* get_channel_struct() const{ + return channel_regs; + } + bool is_enabled(){ + return is_enabled_DFSDM() && is_enabled_channel() && is_enabled_filter(); + } + void enable() { + //just in case enable everything to work + enable_DFSDM_Peripheral(); + enable_filter(); + enable_channel(); } - bool start() { - if(!enable()) return false; - regs->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; - return true; + void disable() { + //only disable channel + channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN_Msk); + } + /*channel functions */ + void enable_clock_absence_detector(){ + channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CKABEN; + } + void enable_short_circuit_detector(){ + channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_SCDEN; + } + void change_offset(int32_t offset){ + channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_OFFSET_Msk); + channel_regs->CHCFGR2 |= (offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; + } + void change_right_bit_shift(uint8_t right_bit_shift){ + channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_DTRBS_Msk); + channel_regs->CHCFGR2 |= uint8_t(right_bit_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; + } + + /*Filter functions*/ + void keep_data_in_buffer(int32_t* buffer,size_t length){ + buffer = buffer; + length_buffer = length; + } + void start() + { + if (!is_enabled()) enable(); + + if(type_conv == Type_Conversion::Regular) { + filter_regs->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; // regular + } else { + filter_regs->FLTCR1 |= DFSDM_FLTCR1_JSWSTART; // injected + } } void modify_sync_conversion(Sync_Conversion type) { - bool was_enabled = is_enabled(); - if (was_enabled) disable(); + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) disable_filter(); - regs->FLTCR1 &= ~DFSDM_FLTCR1_RSYNC_Msk; - regs->FLTCR1 |= (uint32_t(type) << DFSDM_FLTCR1_RSYNC_Pos); + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RSYNC_Msk; + filter_regs->FLTCR1 |= (uint32_t(type) << DFSDM_FLTCR1_RSYNC_Pos); - if (was_enabled) enable(); + if (was_enabled_filter) enable_filter(); } - void modify_mode(Regular_Mode mode) { - regs->FLTCR1 &= ~DFSDM_FLTCR1_RCONT_Msk; - regs->FLTCR1 |= (uint32_t(mode) << DFSDM_FLTCR1_RCONT_Pos); + void modify_mode(Regular_Mode mode) { + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCONT_Msk; + filter_regs->FLTCR1 |= (uint32_t(mode) << DFSDM_FLTCR1_RCONT_Pos); } bool modify_oversampling(uint16_t oversampling) { - if (oversampling == 0) return false; - uint32_t ford = (regs->FLTFCR & DFSDM_FLTFCR_FORD_Msk) >> DFSDM_FLTFCR_FORD_Pos; + uint32_t ford = (filter_regs->FLTFCR & DFSDM_FLTFCR_FORD_Msk) >> DFSDM_FLTFCR_FORD_Pos; if (ford <= 3 && oversampling > Oversampling_MAX) return false; if (ford == 4 && oversampling > Oversampling_MAX_Filter_4) return false; if (ford == 5 && oversampling > Oversampling_MAX_Filter_5) return false; - bool was_enabled = is_enabled(); - if (was_enabled) disable(); - - regs->FLTFCR &= ~DFSDM_FLTFCR_FOSR_Msk; - regs->FLTFCR |= ((uint32_t)(oversampling -1) << DFSDM_FLTFCR_FOSR_Pos); - - if (was_enabled) enable(); + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) disable_filter(); + filter_regs->FLTFCR &= ~DFSDM_FLTFCR_FOSR_Msk; + filter_regs->FLTFCR |= ((uint32_t)(oversampling -1) << DFSDM_FLTFCR_FOSR_Pos); + if (was_enabled_filter) enable_filter(); return true; } bool modify_integrator(uint8_t integrator) { if (integrator == 0 || integrator > 256) return false; - bool was_enabled = is_enabled(); - if (was_enabled) disable(); + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) disable_filter(); - regs->FLTFCR &= ~DFSDM_FLTFCR_IOSR_Msk; - regs->FLTFCR |= ((integrator -1) << DFSDM_FLTFCR_IOSR_Pos); + filter_regs->FLTFCR &= ~DFSDM_FLTFCR_IOSR_Msk; + filter_regs->FLTFCR |= ((integrator -1) << DFSDM_FLTFCR_IOSR_Pos); - if (was_enabled) enable(); + if (was_enabled_filter) enable_filter(); return true; } bool modify_filter_order(Filter_Type type) { - uint32_t fosr =((regs->FLTFCR & DFSDM_FLTFCR_FOSR_Msk) >> DFSDM_FLTFCR_FOSR_Pos); + uint32_t fosr =((filter_regs->FLTFCR & DFSDM_FLTFCR_FOSR_Msk) >> DFSDM_FLTFCR_FOSR_Pos); if (type == Filter_Type::Sinc4 && fosr > Oversampling_MAX_Filter_4) return false; if (type == Filter_Type::Sinc5 && fosr > Oversampling_MAX_Filter_5) return false; - bool was_enabled = is_enabled(); - if (was_enabled) disable(); + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) disable_filter(); - regs->FLTFCR &= ~DFSDM_FLTFCR_FORD_Msk; - regs->FLTFCR |= (uint32_t(type) + filter_regs->FLTFCR &= ~DFSDM_FLTFCR_FORD_Msk; + filter_regs->FLTFCR |= (uint32_t(type) << DFSDM_FLTFCR_FORD_Pos); - if (was_enabled) enable(); + if (was_enabled_filter) enable_filter(); return true; } uint32_t check_latency_cycles() { - return regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; + return filter_regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; } uint32_t check_min_extreme_detector() { - return regs->FLTEXMIN >> DFSDM_FLTEXMIN_EXMIN_Pos; + return filter_regs->FLTEXMIN >> DFSDM_FLTEXMIN_EXMIN_Pos; } uint32_t check_max_extreme_detector() { - return regs->FLTEXMAX >> DFSDM_FLTEXMAX_EXMAX_Pos; + return filter_regs->FLTEXMAX >> DFSDM_FLTEXMAX_EXMAX_Pos; } - - bool clear_watchdog_high() { - regs->FLTAWCFR |= DFSDM_FLTAWCFR_CLRAWHTF; - return true; - } - - bool clear_watchdog_low() { - regs->FLTAWCFR |= DFSDM_FLTAWCFR_CLRAWLTF; + //watchdog + bool activate_watchdog(Callback callback_wdg,uint32_t low_watchdog,uint32_t high_watchdog, + Filter_Type watchdog_filter = Filter_Type::FastSinc,uint32_t oversampling = 1,Analog_Watchdog_Fast_Mode awfsel = Analog_Watchdog_Fast_Mode::After_Filter) + { + if (oversampling == 0 || oversampling > 32) return false; + if (low_watchdog >= high_watchdog) return false; + watchdog_cb = callback_wdg; + bool was_enabled_channel = is_enabled_channel(); + if (was_enabled_channel) disable_channel(); + + /* ---------------- CHANNEL CONFIG ---------------- */ + channel_regs->CHAWSCDR &= ~(DFSDM_CHAWSCDR_AWFORD_Msk | DFSDM_CHAWSCDR_AWFOSR_Msk); + channel_regs->CHAWSCDR |= (uint32_t(watchdog_filter) << DFSDM_CHAWSCDR_AWFORD_Pos); + channel_regs->CHAWSCDR |= ((oversampling - 1) << DFSDM_CHAWSCDR_AWFOSR_Pos); + + /* ---------------- FILTER CONFIG ---------------- */ + bool was_enabled_filter = is_enabled_filter(); + if(was_enabled_filter) disable_filter(); + if (awfsel == Analog_Watchdog_Fast_Mode::Channel_Data) + filter_regs->FLTCR1 |= DFSDM_FLTCR1_AWFSEL; + else + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_AWFSEL; + + // Set thresholds + filter_regs->FLTAWLTR = low_watchdog; + filter_regs->FLTAWHTR = high_watchdog; + // add this channel to the Analog watchdog channel selection + filter_regs->FLTCR2 |= (this->channel << DFSDM_FLTCR2_AWDCH_Pos); + // Enable analog watchdog interrupt + filter_regs->FLTCR2 |= DFSDM_FLTCR2_AWDIE; + if (was_enabled_channel || was_enabled_filter) enable(); return true; } - bool modify_analog_watchdog_lth(uint32_t value) { + void modify_watchdog_lth(uint32_t value) { - bool was_enabled = is_enabled(); - if (was_enabled) disable(); - regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; - bool fast = (regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); + filter_regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; + bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); if (fast) - regs->FLTAWLTR = (value & 0xFFFF) << (DFSDM_FLTAWLTR_AWLT_Pos + DFSDM_FLTAWLTR_AWLT_Pos); // Only 16 bits + filter_regs->FLTAWLTR = (value & 0xFFFF) << (DFSDM_FLTAWLTR_AWLT_Pos + DFSDM_FLTAWLTR_AWLT_Pos); // Only 16 bits else - regs->FLTAWLTR = (value & 0xFFFFFF) << DFSDM_FLTAWLTR_AWLT_Pos; // 24 bits - - if (was_enabled) enable(); - return true; + filter_regs->FLTAWLTR = (value & 0xFFFFFF) << DFSDM_FLTAWLTR_AWLT_Pos; // 24 bits } - bool modify_analog_watchdog_hth(uint32_t value) { - - bool was_enabled = is_enabled(); - if (was_enabled) disable(); - regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; - bool fast = (regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); + void modify_watchdog_hth(uint32_t value) { + filter_regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; + bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); if (fast) - regs->FLTAWHTR = (value & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + filter_regs->FLTAWHTR = (value & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); else - regs->FLTAWHTR = (value & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; - - if (was_enabled) enable(); - return true; - } - - uint32_t read_data_from_filter_register() { - - if (!(regs->FLTISR & DFSDM_FLTISR_REOCF)) - return 0xFFFFFFFFu; - - return regs->FLTRDATAR >> DFSDM_FLTRDATAR_RDATA_Pos; - } - - bool activate_dma_reading() { - - bool was_enabled = is_enabled(); - if (was_enabled) disable(); - - regs->FLTCR1 |= DFSDM_FLTCR1_RDMAEN; - - if (was_enabled) enable(); - return true; - } - - bool activate_cpu() { - - bool was_enabled = is_enabled(); - if (was_enabled) disable(); - - regs->FLTCR1 &= ~DFSDM_FLTCR1_RDMAEN; - - if (was_enabled) enable(); - return true; + filter_regs->FLTAWHTR = (value & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; } -}; - - - template struct Init { - static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { + // void activate_ckabie(Callback ckabie_cb){ + // DFSDM1_Filter0->FLTCR2 &= ~(DFSDM_FLTCR2_CKABIE_Msk); + // } + // void activate_scdie(Callback scdie_cb){ + // DFSDM1_Filter0->FLTCR2 &= ~(DFSDM_FLTCR2_SCDIE_Msk); + // } + }; + static Instance* channel_instances[DFSDM_DOMAIN::max_instances]; + static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { DFSDM1_Filter0, DFSDM1_Filter1, DFSDM1_Filter2, DFSDM1_Filter3 }; + static constexpr DFSDM_Channel_TypeDef* channel_hw[8] = { + DFSDM1_Channel0, + DFSDM1_Channel1, + DFSDM1_Channel2, + DFSDM1_Channel3, + DFSDM1_Channel4, + DFSDM1_Channel5, + DFSDM1_Channel6, + DFSDM1_Channel7 + }; + template struct Init { + static inline std::array instances{}; - + static void init(std::span cfgs) { - - for (std::size_t i = 0; i < N; ++i) { - const auto &cfg = cfgs[i]; - auto &inst = instances[i]; - inst.regs = filter_hw[i]; - if (!cfg.used_filter) - continue; - - inst.regs = filter_hw[i]; - inst.latency_cycles = cfg.latency_cycles; - inst.watchdog_cb = nullptr; - - inst.regs->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; - - inst.regs->FLTCR1 = cfg.init_data.FLTCR1; - inst.regs->FLTCR2 = cfg.init_data.FLTCR2; - inst.regs->FLTFCR = cfg.init_data.FLTFCR; - - //clean flags - inst.regs->FLTISR = 0xFFFFFFFFu; - //enable the filter - inst.regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; - switch(i){ - case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; - case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; - case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; - case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; - } - - } - //activate NVIC for every filter + for (std::size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + Instance &inst = instances[i]; + + inst.filter_regs = filter_hw[cfg.filter]; + inst.channel_regs = channel_hw[cfg.channel]; + + inst.latency_cycles = cfg.latency_cycles; + inst.type_conv = cfg.type_conv; + inst.filter = cfg.filter; + inst.channel = cfg.channel; + //add everything to the register of the filter + + inst.filter_regs->FLTCR1 = cfg.init_data_filter.FLTCR1; + if(inst.type_conv == Type_Conversion::Regular){ + inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; + inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; + }else{ + inst.filter_regs->FLTJCHGR |= 1 << inst.channel; + } + inst.filter_regs->FLTCR2 = cfg.init_data_filter.FLTCR2; + inst.filter_regs->FLTFCR = cfg.init_data_filter.FLTFCR; + + //add everything to the channel register + inst.channel_regs->CHCFGR1 = cfg.init_data_channel.CHCFGR1; + inst.channel_regs->CHCFGR2 = cfg.init_data_channel.CHCFGR2; + + //enable the filter + inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; + //enable the channel + inst.channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + + //activate the NVIC + switch(inst.filter){ + case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; + case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; + case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; + case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + } + //update channel_instances + channel_instances[inst.channel] = &inst; } + //Activate the DFSDM GLOBAL Interface + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + } }; -}; -}; - - - - - - - - - - - - - - - - - - - - - + static void handle_irq(uint8_t filter_index) + { + DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; + uint32_t isr = filter->FLTISR; + if(isr & DFSDM_FLTISR_REOCF_Msk){ + Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; + inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + return; + } + if(isr & DFSDM_FLTISR_JEOCF_Msk){ + //GUARDARLO EN LA DIRECCIÓN DE MEMORIA QUE ME PORPORCIONE EL USUARIO + Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; + inst->buffer[inst->idx] = (filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + return; + } + if(isr & DFSDM_FLTISR_SCDF_Msk){ + uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos) + 1; + if(channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); + //clear + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + } + if(isr & DFSDM_FLTISR_CKABF_Msk){ + uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos) + 1; + if(channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); + //cleaR + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + } + //Analog watchdog + if (isr & DFSDM_FLTISR_AWDF) + { + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + //mirar como saber el canal que ha lanzado el watchdog + // if(filter->watchdog_cb != nullptr)inst->watchdog_cb(); + return; + } + } +}; struct DFSDM_CLK_DOMAIN{ - static constexpr GPIODomain::Pin valid_clk_pins[] = { {GPIODomain::Port::C, 2}, {GPIODomain::Port::B, 0}, @@ -591,23 +777,24 @@ struct DFSDM_CLK_DOMAIN{ Entry e{.gpio_idx = gpio_idx,.clk_divider = clk_divider}; return ctx.template add(e,this); } - static constexpr std::size_t max_instances{1}; - struct Config{ - size_t gpio_idx; - uint8_t clk_divider; - }; - template - static consteval std::array build(std::span entries) { - std::array cfgs{}; - static_assert(N == 1,"You can't have more than one clock_out"); - for (std::size_t i = 0; i < N; ++i) { - cfgs[i] = { - .gpio_idx = entries[i].gpio_idx, - .clk_divider = entries[i].clk_divider - }; - } - return cfgs; + }; + static constexpr std::size_t max_instances{1}; + struct Config{ + size_t gpio_idx; + uint8_t clk_divider; + }; + template + static consteval std::array build(std::span entries) { + std::array cfgs{}; + static_assert(N == 1,"You can't have more than one clock_out"); + for (std::size_t i = 0; i < N; ++i) { + cfgs[i] = { + .gpio_idx = entries[i].gpio_idx, + .clk_divider = entries[i].clk_divider + }; } + return cfgs; + } struct Instance{ GPIODomain::Instance *gpio_instance; uint8_t clk_divider; @@ -658,176 +845,4 @@ struct DFSDM_CLK_DOMAIN{ }; }; -}; -}; -// /*Only is going to use filter fastsync and from 1..3*/ - //bool choose_latency(uint32_t latency) - //{ - //if (latency == 0) return false; - - //bool was_enabled = is_enabled(); - //if (was_enabled) disable(); - - //uint32_t best_error = 0xFFFFFFFF; - //uint32_t best_ford = 0; - //uint32_t best_fosr = 0; - //uint32_t best_iosr = 0; - - //// Solo filtros 0..3 - //for (uint32_t ford = 0; ford <= 3; ++ford) - //{ - //for (uint32_t iosr = 1; iosr <= 256; ++iosr) - //{ - //uint32_t denom = (iosr - 1 + ford); - //if (denom == 0) continue; - - //uint32_t fosr = (latency > ford) - //? (latency - ford) / denom - //: 0; - - //if (fosr < 1 || fosr > 1024) continue; - - //uint32_t real_latency = - //fosr * denom + ford; - - //uint32_t error = - //(real_latency > latency) - //? (real_latency - latency) - //: (latency - real_latency); - - //if (error < best_error) - //{ - //best_error = error; - //best_ford = ford; - //best_fosr = fosr; - //best_iosr = iosr; - - //if (error == 0) break; - //} - //} - - //if (best_error == 0) break; - //} - - //if (best_fosr == 0) - //{ - //if (was_enabled) enable(); - //return false; - //} - - //regs->FLTFCR &= - //~(DFSDM_FLTFCR_FORD_Msk | - //DFSDM_FLTFCR_FOSR_Msk | - //DFSDM_FLTFCR_IOSR_Msk); - - //regs->FLTFCR |= (best_ford << DFSDM_FLTFCR_FORD_Pos); - //regs->FLTFCR |= ((best_fosr - 1) << DFSDM_FLTFCR_FOSR_Pos); - //regs->FLTFCR |= ((best_iosr - 1) << DFSDM_FLTFCR_IOSR_Pos); - - //if (was_enabled) enable(); - //return true; - //} - - //bool choose_latency(uint32_t latency,Filter_Type filter_type) { - //if (latency == 0) return false; - //uint32_t ford = static_cast(filter_type); - //uint32_t oversampling_max = (ford <= 3) ? Oversampling_MAX : (ford == 4) ? Oversampling_MAX_Filter_4 : Oversampling_MAX_Filter_5; - //bool was_enabled = is_enabled(); - - //uint32_t best_error = 0xFFFFFFFF; - //uint32_t best_fosr = 0; - //uint32_t best_iosr = 0; - - //for (uint32_t iosr = 1; iosr <= 256; ++iosr) - //{ - //uint32_t denom = (iosr - 1 + ford); - //if (denom == 0) continue; - //uint32_t fosr = - //(latency > ford) - //? (latency - ford) / denom - //: 0; - - //if (fosr < 1 || fosr > oversampling_max) continue; - - //uint32_t real_latency = - //fosr * denom + ford; - - //uint32_t error = - //(real_latency > latency) - //? (real_latency - latency) - //: (latency - real_latency); - - //if (error < best_error) - //{ - //best_error = error; - //best_fosr = fosr; - //best_iosr = iosr; - - //if (error == 0) break; - //} - //} - - //if (best_fosr == 0) return false; - //if (was_enabled) disable(); - //regs->FLTFCR &= - //~(DFSDM_FLTFCR_FORD_Msk | - //DFSDM_FLTFCR_FOSR_Msk | - //DFSDM_FLTFCR_IOSR_Msk); - - //regs->FLTFCR |= (ford << DFSDM_FLTFCR_FORD_Pos); - //regs->FLTFCR |= ((best_fosr - 1) << DFSDM_FLTFCR_FOSR_Pos); - //regs->FLTFCR |= ((best_iosr - 1) << DFSDM_FLTFCR_IOSR_Pos); - - //if (was_enabled) enable(); - //return true; - //} - //bool choose_latency(uint32_t latency,Filter_Type filter_type, uint16_t oversampling){ - //if (latency == 0) return false; - - //uint32_t ford = static_cast(filter_type); - //if (ford > 3) return false; - //uint32_t oversampling_max = (ford <= 3) ? Oversampling_MAX : (ford == 4) ? Oversampling_MAX_Filter_4 : Oversampling_MAX_Filter_5; - //if (oversampling < 1 || oversampling > oversampling_max) return false; - //bool was_enabled = is_enabled(); - - //uint32_t best_error = 0xFFFFFFFF; - //uint32_t best_iosr = 0; - - //for (uint32_t iosr = 1; iosr <= 256; ++iosr) - //{ - //uint32_t real_latency = - //oversampling * - //(iosr - 1 + ford) - //+ ford; - - //uint32_t error = - //(real_latency > latency) - //? (real_latency - latency) - //: (latency - real_latency); - - //if (error < best_error) - //{ - //best_error = error; - //best_iosr = iosr; - - //if (error == 0) break; - //} - //} - - //if (best_iosr == 0) return false; - //if(was_enabled) disable(); - //regs->FLTFCR &= - //~(DFSDM_FLTFCR_FORD_Msk | - //DFSDM_FLTFCR_FOSR_Msk | - //DFSDM_FLTFCR_IOSR_Msk); - - //regs->FLTFCR |= (ford << DFSDM_FLTFCR_FORD_Pos); - //regs->FLTFCR |= ((oversampling - 1) - //<< DFSDM_FLTFCR_FOSR_Pos); - //regs->FLTFCR |= ((best_iosr - 1) - //<< DFSDM_FLTFCR_IOSR_Pos); - - //if (was_enabled) enable(); - //return true; - //} - +}; \ No newline at end of file diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index a9acb61f2..44e42abbb 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -85,7 +85,7 @@ template struct BuildCtx { using DomainsCtx = BuildCtx; + DigitalInputDomain,DFSDM_DOMAIN,DFSDM_CLK_DOMAIN /*, ADCDomain, PWMDomain, ...*/>; template struct Board { static consteval auto build_ctx() { @@ -106,6 +106,8 @@ template struct Board { constexpr std::size_t doutN = domain_size(); constexpr std::size_t dinN = domain_size(); constexpr std::size_t mpuN = domain_size(); + constexpr std::size_t dfsdmN = domain_size(); + constexpr std::size_t dfsdm_clkN = domain_size(); // ... struct ConfigBundle { @@ -114,6 +116,8 @@ template struct Board { std::array dout_cfgs; std::array din_cfgs; std::array mpu_cfgs; + std::array dfsdm_cfgs; + std::array dfsdm_clk_cfgs; // ... }; @@ -128,6 +132,10 @@ template struct Board { ctx.template span()), .mpu_cfgs = MPUDomain::template build( ctx.template span()) + .dfsdm_cfgs = DFSDM_DOMAIN::template build( + ctx.template span()) + .dfsdm_clk_cfgs = DFSDM_CLK_DOMAIN::template build( + ctx.template span()) // ... }; } @@ -140,6 +148,8 @@ template struct Board { constexpr std::size_t doutN = domain_size(); constexpr std::size_t dinN = domain_size(); constexpr std::size_t mpuN = domain_size(); + constexpr std::size_t dfsdmN = domain_size(); + constexpr std::size_t dfsdm_clkN = domain_size(); // ... GPIODomain::Init::init(cfg.gpio_cfgs); @@ -149,6 +159,8 @@ template struct Board { DigitalInputDomain::Init::init(cfg.din_cfgs, GPIODomain::Init::instances); MPUDomain::Init::init(); + DFSDM_DOMAIN::Init::init(cfg.dfsdm_cfgs); + DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_cfgs); // ... } diff --git a/Src/HALAL/Services/DFSDM/DFSDM.cpp b/Src/HALAL/Services/DFSDM/DFSDM.cpp index e69de29bb..69253f72a 100644 --- a/Src/HALAL/Services/DFSDM/DFSDM.cpp +++ b/Src/HALAL/Services/DFSDM/DFSDM.cpp @@ -0,0 +1,23 @@ +#include "HALAL/Services/DFSDM/DFSDM.hpp" + + + +extern "C"{ + +void DFSDM1_FLT0_IRQHandler(void) +{ + ST_LIB::DFSDM_DOMAIN::handle_irq(0); +} +void DFSDM1_FLT1_IRQHandler(void) +{ + ST_LIB::DFSDM_DOMAIN::handle_irq(1); +} +void DFSDM1_FLT2_IRQHandler(void) +{ + ST_LIB::DFSDM_DOMAIN::handle_irq(2); +} +void DFSDM1_FLT3_IRQHandler(void) +{ + ST_LIB::DFSDM_DOMAIN::handle_irq(3); +} +} \ No newline at end of file From fd1b96721cbe74af8a184fce18062b4756b96c15 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 27 Feb 2026 12:37:05 +0100 Subject: [PATCH 04/41] fix some errors from the merge, mia culpa --- Inc/ST-LIB.hpp | 10 ++++++++++ Inc/ST-LIB_LOW/ST-LIB_LOW.hpp | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index d5a4ceb8a..cab4df088 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -118,6 +118,16 @@ template struct Board { constexpr std::size_t doutN = domain_size(); constexpr std::size_t dinN = domain_size(); constexpr std::size_t mpuN = domain_size(); + constexpr std::size_t dmaN = domain_size(); + constexpr std::size_t spiN = domain_size(); + constexpr std::size_t mdmaPacketN = domain_size(); + constexpr std::size_t sdN = domain_size(); + constexpr std::size_t ethN = domain_size(); + constexpr std::size_t adcN = domain_size(); + constexpr std::size_t extiN = domain_size(); + constexpr std::size_t dfsdmN = domain_size(); + constexpr std::size_t dfsdm_clkN = domain_size(); + // ... struct ConfigBundle { diff --git a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp index d253623c4..1ec225bbd 100644 --- a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp +++ b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp @@ -3,8 +3,8 @@ #include "ST-LIB_LOW/DigitalOutput2.hpp" #include "ST-LIB_LOW/DigitalInput2.hpp" #include "HALAL/Services/DFSDM/DFSDM.hpp" -#include "Clocks/Counter.hpp" -#include "Clocks/Stopwatch.hpp" +//#include "Clocks/Counter.hpp" +//#include "Clocks/Stopwatch.hpp" #include "ST-LIB_LOW/Sd/Sd.hpp" From 894d60e7bfb84a4442ca0d8e641bf906ad0eb48f Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 27 Feb 2026 16:37:22 +0100 Subject: [PATCH 05/41] eliminate right bit shift in execution time --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index cd0bf6838..35a843a9e 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -456,10 +456,6 @@ namespace ST_LIB { channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_OFFSET_Msk); channel_regs->CHCFGR2 |= (offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; } - void change_right_bit_shift(uint8_t right_bit_shift){ - channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_DTRBS_Msk); - channel_regs->CHCFGR2 |= uint8_t(right_bit_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; - } /*Filter functions*/ void keep_data_in_buffer(int32_t* buffer,size_t length){ From e8216f7d5594b7d4b5431c27ceb2f50ee8056978 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sat, 28 Feb 2026 13:40:36 +0100 Subject: [PATCH 06/41] added some changes to compile clk --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 59 +++++++++++++++++------------- Inc/ST-LIB.hpp | 53 +++++++++++++++++++++------ 2 files changed, 76 insertions(+), 36 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 35a843a9e..5809a7b25 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -343,10 +343,11 @@ namespace ST_LIB { template static consteval std::array build(std::span entries) { std::array cfgs{}; + if(N == 0) return cfgs; std::array channels_used{false}; - std::array filters_used{-1,-1,-1,-1}; - cfgs[0].init_data_channel |= global_config_via_channel_0(entries[0]); - cfgs[0].init_data_filter |= make_fltcr2_global(entries[0]); + std::array filters_used{-1,-1,-1,-1}; + cfgs[0].init_data_channel.CHCFGR1 |= global_config_via_channel_0(entries[0]); + cfgs[0].init_data_filter.FLTCR2 |= make_fltcr2_global(entries[0]); bool filter_per_channel = (N <= 4) ? true : false; for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -370,8 +371,13 @@ namespace ST_LIB { cfg.init_data_channel.CHCFGR2 |= make_chcfgr2(e); cfg.init_data_channel.CHAWSCDR |= make_chawscdr(e); cfg.latency_cycles = compute_latency(e); - if(filters_used[cfg.filter] != -1 && cfgs[filters_used[cfg.filter]].init_data_filter != cfg.init_data_filter){ - compile_error("You have two channels that goes to the same filter with different filter configuration"); + if(filters_used[cfg.filter] != -1){ + if(cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR1 != cfg.init_data_filter.FLTCR1 || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 != cfg.init_data_filter.FLTCR2 || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != cfg.init_data_filter.FLTFCR){ + compile_error("You have two channels that goes to the same filter with different filter configuration"); + } + } filters_used[cfg.filter] = i; } @@ -725,13 +731,14 @@ namespace ST_LIB { struct DFSDM_CLK_DOMAIN{ static constexpr GPIODomain::Pin valid_clk_pins[] = { - {GPIODomain::Port::C, 2}, - {GPIODomain::Port::B, 0}, - {GPIODomain::Port::E, 9}, - {GPIODomain::Port::D, 3}, - {GPIODomain::Port::D, 10} + {GPIODomain::Port::C,GPIO_PIN_2}, + {GPIODomain::Port::B, GPIO_PIN_0}, + {GPIODomain::Port::E, GPIO_PIN_9}, + {GPIODomain::Port::D, GPIO_PIN_3}, + {GPIODomain::Port::D, GPIO_PIN_10} }; - static consteval void is_valid_dfsdm_clk_pin(const GPIODomain::Port port, uint32_t pin) { + + static consteval bool is_valid_dfsdm_clk_pin(GPIODomain::Port port, uint32_t pin) { bool found = false; for (auto &p : valid_clk_pins) { if (p.port == port && p.pin == pin) { @@ -739,15 +746,13 @@ struct DFSDM_CLK_DOMAIN{ break; } } - if (!found) { - compile_error("This pin cannot be used as DFSDM CLK OUT"); - } + return found; } static consteval GPIODomain::AlternateFunction dfsdm_clk_af(const GPIODomain::Pin& pin) { - if (pin.port == GPIODomain::Port::C && pin.pin == 2) - return GPIODomain::AlternateFunction::AF4; - return GPIODomain::AlternateFunction::AF6; //In every other case + if ((pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_2)|| (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_0)) + return GPIODomain::AlternateFunction::AF6; + return GPIODomain::AlternateFunction::AF3; //In every other case } struct Entry{ size_t gpio_idx; @@ -757,19 +762,23 @@ struct DFSDM_CLK_DOMAIN{ struct DFSDM_CLK{ using domain = DFSDM_CLK_DOMAIN; GPIODomain::GPIO gpio; - Entry e; + GPIODomain::Pin pin; uint8_t clk_divider; consteval DFSDM_CLK(const GPIODomain::Pin &pin,uint8_t clk_divider = 4): gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None, GPIODomain::Speed::VeryHigh,dfsdm_clk_af(pin)}, + pin(pin), clk_divider(clk_divider) - { - is_valid_dfsdm_clk_pin(pin.port,pin.pin); + {} + + + template consteval std::size_t inscribe(Ctx &ctx) const{ + const auto gpio_idx = gpio.inscribe(ctx); + if(!is_valid_dfsdm_clk_pin(pin.port,pin.pin)){ + compile_error("Invalid clk dfsdm pin used"); + } if(clk_divider < 4){ compile_error("The divider must be at least frequency/4"); } - } - template consteval std::size_t inscribe(Ctx &ctx) const{ - const auto gpio_idx = gpio.inscribe(ctx); Entry e{.gpio_idx = gpio_idx,.clk_divider = clk_divider}; return ctx.template add(e,this); } @@ -801,7 +810,7 @@ struct DFSDM_CLK_DOMAIN{ //The channel 0 also allows to configure the CKOUT DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTSRC; - //CKOUT DivideR. Divider = CKOUTDIV + 1 + //CKOUT Divider. Divider = CKOUTDIV + 1 DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTDIV; DFSDM1_Channel0->CHCFGR1 |= (clk_divider -1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; @@ -834,7 +843,7 @@ struct DFSDM_CLK_DOMAIN{ //add ckaie scdie const auto &c = cfgs[0]; auto &inst = instances[0]; - inst.gpio = &gpio_instances[c.gpio_idx]; + inst.gpio_instance = &gpio_instances[c.gpio_idx]; inst.clk_divider = c.clk_divider; inst.init(); } diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index cab4df088..7c82e721a 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -148,18 +148,49 @@ template struct Board { // ... }; - return ConfigBundle{ + return ConfigBundle{ + .mpu_cfgs = + MPUDomain::template build( + ctx.template span()), .gpio_cfgs = - GPIODomain::template build(ctx.template span()), + GPIODomain::template build( + ctx.template span()), .tim_cfgs = - TimerDomain::template build(ctx.template span()), - .dout_cfgs = DigitalOutputDomain::template build( - ctx.template span()), - .din_cfgs = DigitalInputDomain::template build( - ctx.template span()), - .mpu_cfgs = MPUDomain::template build( - ctx.template span()) - // ... + TimerDomain::template build( + ctx.template span()), + .dma_cfgs = + DMA_Domain::template build( + ctx.template span()), + .spi_cfgs = + SPIDomain::template build( + ctx.template span()), + .dout_cfgs = + DigitalOutputDomain::template build( + ctx.template span()), + .din_cfgs = + DigitalInputDomain::template build( + ctx.template span()), + .mdma_packet_cfgs = + MdmaPacketDomain::template build( + ctx.template span()), + .sd_cfgs = + SdDomain::template build( + ctx.template span()), + .eth_cfgs = + EthernetDomain::template build( + ctx.template span()), + .adc_cfgs = + ADCDomain::template build( + ctx.template span()), + .exti_cfgs = + EXTIDomain::template build( + ctx.template span()), + .dfsdm_cfgs = + DFSDM_DOMAIN::template build( + ctx.template span()), + .dfsdm_clk_cfgs = + DFSDM_CLK_DOMAIN::template build( + ctx.template span()) }; } @@ -213,7 +244,7 @@ template struct Board { ADCDomain::Init::init(cfg.adc_cfgs, GPIODomain::Init::instances); EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); DFSDM_DOMAIN::Init::init(cfg.dfsdm_cfgs); - DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_cfgs); + DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); // ... } From 5cc4af898bb42061dea12f77befb139e7961084f Mon Sep 17 00:00:00 2001 From: oganigl Date: Sat, 28 Feb 2026 13:42:47 +0100 Subject: [PATCH 07/41] change a bit of AF in PB0 for dfsdm_clk_out --- Inc/HALAL/Models/Pin.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Inc/HALAL/Models/Pin.hpp b/Inc/HALAL/Models/Pin.hpp index f4881abe3..f1f84f519 100644 --- a/Inc/HALAL/Models/Pin.hpp +++ b/Inc/HALAL/Models/Pin.hpp @@ -25,7 +25,7 @@ constexpr GPIODomain::Pin PA14{A, GPIO_PIN_14, 0b1000000000000001}; constexpr GPIODomain::Pin PA15{A, GPIO_PIN_15, 0b1100111111010011}; // Port B -constexpr GPIODomain::Pin PB0{B, GPIO_PIN_0, 0b0111110011111011}; +constexpr GPIODomain::Pin PB0{B, GPIO_PIN_0, 0b0111111011111011}; constexpr GPIODomain::Pin PB1{B, GPIO_PIN_1, 0b0111110011111011}; constexpr GPIODomain::Pin PB2{B, GPIO_PIN_2, 0b0110111110111011}; constexpr GPIODomain::Pin PB3{B, GPIO_PIN_3, 0b0111110110111011}; From 27f2b93a0f3c5d36c13c1a3da15dc806d28112d4 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sat, 28 Feb 2026 17:20:55 +0100 Subject: [PATCH 08/41] clock works for every possible pin --- Inc/HALAL/Models/Pin.hpp | 4 +-- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 50 ++++++++++++------------------ 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/Inc/HALAL/Models/Pin.hpp b/Inc/HALAL/Models/Pin.hpp index f1f84f519..b819c7b2d 100644 --- a/Inc/HALAL/Models/Pin.hpp +++ b/Inc/HALAL/Models/Pin.hpp @@ -64,14 +64,14 @@ constexpr GPIODomain::Pin PC15{C, GPIO_PIN_15, 0b1000000000000001}; constexpr GPIODomain::Pin PD0{D, GPIO_PIN_0, 0b0111110110011011}; constexpr GPIODomain::Pin PD1{D, GPIO_PIN_1, 0b0111110110011011}; constexpr GPIODomain::Pin PD2{D, GPIO_PIN_2, 0b1110110110011011}; -constexpr GPIODomain::Pin PD3{D, GPIO_PIN_3, 0b0110110111110011}; +constexpr GPIODomain::Pin PD3{D, GPIO_PIN_3, 0b0111110111110011}; constexpr GPIODomain::Pin PD4{D, GPIO_PIN_4, 0b0010110010010011}; constexpr GPIODomain::Pin PD5{D, GPIO_PIN_5, 0b0010110010010011}; constexpr GPIODomain::Pin PD6{D, GPIO_PIN_6, 0b0111110110111011}; constexpr GPIODomain::Pin PD7{D, GPIO_PIN_7, 0b0110110111111011}; constexpr GPIODomain::Pin PD8{D, GPIO_PIN_8, 0b0110110110010011}; constexpr GPIODomain::Pin PD9{D, GPIO_PIN_9, 0b0110110110010011}; -constexpr GPIODomain::Pin PD10{D, GPIO_PIN_10, 0b0110110110010011}; +constexpr GPIODomain::Pin PD10{D, GPIO_PIN_10, 0b0111110110010011}; constexpr GPIODomain::Pin PD11{D, GPIO_PIN_11, 0b0111100110110011}; constexpr GPIODomain::Pin PD12{D, GPIO_PIN_12, 0b0111110110110011}; constexpr GPIODomain::Pin PD13{D, GPIO_PIN_13, 0b0111110110110011}; diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 5809a7b25..05dd06e8d 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -122,8 +122,6 @@ namespace ST_LIB { Fast_Conversion fast; Sync_Conversion rsync; Regular_Mode rcont; - - uint8_t clock_divider; //has to be the same between ckout and here }; static constexpr std::array,Possible_Pin_Channel> pin_to_channel = {{ @@ -172,8 +170,7 @@ namespace ST_LIB { int32_t offset = 0,uint8_t right_shift = 0,Trigger_Timer_Source trigger_conv = Trigger_Timer_Source::Unused,Filter_Type filter_type = Filter_Type::FastSinc, uint16_t oversampling = 1,uint8_t integrator = 1,uint8_t Short_Circuit_Count = 0xFF, Data_Write rdma = Data_Write::DMA, Fast_Conversion fast = Fast_Conversion::Enable, - Sync_Conversion rsync = Sync_Conversion::Sync_With_Flt0, Regular_Mode rcont = Regular_Mode::Continuous, - uint8_t clock_divider = 4) + Sync_Conversion rsync = Sync_Conversion::Sync_With_Flt0, Regular_Mode rcont = Regular_Mode::Continuous) : e{ .channel = get_channel(pin), .offset = offset, @@ -190,7 +187,6 @@ namespace ST_LIB { .fast = fast, .rsync = rsync, .rcont = rcont, - .clock_divider = clock_divider, } { if(offset > OFFSET_MAX || offset < OFFSET_MIN){ @@ -204,11 +200,7 @@ namespace ST_LIB { } if (!is_correct_oversampling(filter_type, oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); - } - if (clock_divider < 4){ - compile_error("DFSDM_CLKOUTDIV has to be at least 4"); - } - + } } template consteval std::size_t inscribe(Ctx &ctx) const { @@ -334,21 +326,15 @@ namespace ST_LIB { return v; } - static consteval uint32_t global_config_via_channel_0(const Entry& e){ - uint32_t v = 0; - //ckoutsrc = 0 - v |= uint32_t(e.clock_divider - 1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; - return v; - } template static consteval std::array build(std::span entries) { std::array cfgs{}; if(N == 0) return cfgs; std::array channels_used{false}; std::array filters_used{-1,-1,-1,-1}; - cfgs[0].init_data_channel.CHCFGR1 |= global_config_via_channel_0(entries[0]); + bool filter_per_channel = (N <= 4) ? true : false; + cfgs[0].init_data_filter.FLTCR2 |= make_fltcr2_global(entries[0]); - bool filter_per_channel = (N <= 4) ? true : false; for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -756,7 +742,7 @@ struct DFSDM_CLK_DOMAIN{ } struct Entry{ size_t gpio_idx; - uint8_t clk_divider; + uint16_t clk_divider; }; struct DFSDM_CLK{ @@ -764,7 +750,7 @@ struct DFSDM_CLK_DOMAIN{ GPIODomain::GPIO gpio; GPIODomain::Pin pin; uint8_t clk_divider; - consteval DFSDM_CLK(const GPIODomain::Pin &pin,uint8_t clk_divider = 4): + consteval DFSDM_CLK(const GPIODomain::Pin &pin,uint8_t clk_divider = 100): // clk_divider = 100 -> 1Mhz gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None, GPIODomain::Speed::VeryHigh,dfsdm_clk_af(pin)}, pin(pin), clk_divider(clk_divider) @@ -776,8 +762,8 @@ struct DFSDM_CLK_DOMAIN{ if(!is_valid_dfsdm_clk_pin(pin.port,pin.pin)){ compile_error("Invalid clk dfsdm pin used"); } - if(clk_divider < 4){ - compile_error("The divider must be at least frequency/4"); + if(clk_divider < 7 || clk_divider > 256){ + compile_error("The clk_divider has to be between 7 and 256"); } Entry e{.gpio_idx = gpio_idx,.clk_divider = clk_divider}; return ctx.template add(e,this); @@ -786,12 +772,12 @@ struct DFSDM_CLK_DOMAIN{ static constexpr std::size_t max_instances{1}; struct Config{ size_t gpio_idx; - uint8_t clk_divider; + uint16_t clk_divider; }; template static consteval std::array build(std::span entries) { std::array cfgs{}; - static_assert(N == 1,"You can't have more than one clock_out"); + static_assert(N <= 1,"You can't have more than one clock_out"); for (std::size_t i = 0; i < N; ++i) { cfgs[i] = { .gpio_idx = entries[i].gpio_idx, @@ -802,17 +788,19 @@ struct DFSDM_CLK_DOMAIN{ } struct Instance{ GPIODomain::Instance *gpio_instance; - uint8_t clk_divider; + uint16_t clk_divider; /*Already called in init()*/ void init(){ - RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM Clocki OUT in RCC by default it uses rcc_pclk2(80MH) - //CKOUT = kernell clock (rcc_pclk2) - //The channel 0 also allows to configure the CKOUT + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM Clock OUT in RCC by default it uses rcc_pclk2 + //Disable DFSDMEN to change parameters + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN; + + //CKOUTSRC = 0 -> kernel clock (rcc_pclk2) It works 137,5 Mhz, DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTSRC; - //CKOUT Divider. Divider = CKOUTDIV + 1 DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTDIV; - DFSDM1_Channel0->CHCFGR1 |= (clk_divider -1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; + + DFSDM1_Channel0->CHCFGR1 |= uint32_t(clk_divider -1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; //enable the CKOUT DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; @@ -845,7 +833,7 @@ struct DFSDM_CLK_DOMAIN{ auto &inst = instances[0]; inst.gpio_instance = &gpio_instances[c.gpio_idx]; inst.clk_divider = c.clk_divider; - inst.init(); + inst.init(); } }; }; From 71661b144c19f673054e3ae6764bfaafa9c18a0f Mon Sep 17 00:00:00 2001 From: oganigl Date: Sun, 1 Mar 2026 20:06:39 +0100 Subject: [PATCH 09/41] added a config, you're welcome Boris --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 171 +++++++++++++++-------------- Inc/ST-LIB.hpp | 16 ++- 2 files changed, 96 insertions(+), 91 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 05dd06e8d..8366df323 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -14,7 +14,7 @@ namespace ST_LIB { using Callback = void(*)(void); extern void compile_error(const char *msg); - struct DFSDM_DOMAIN{ + struct DFSDM_CHANNEL_DOMAIN{ /* Constant Values of Register*/ //DatPack = 0 Standar //DatMPx = 0 // External input @@ -103,26 +103,42 @@ namespace ST_LIB { Regular, Injected }; - struct Entry{ - uint8_t channel; - int32_t offset; - uint32_t right_shift; // right shift - SPICKSel spi_clock_sel; - SPI_Type spi_type; - Type_Conversion type_conv; - Trigger_Timer_Source trigger_conv; - Filter_Type filter_type; - uint16_t oversampling; //1..1024 - uint16_t integrator; //1..256 - uint8_t Short_Circuit_Count; // Number of bits with the same value to guess that has been a Short Circuit - - Data_Write rdma; - + struct Config_Channel{ + int32_t offset{0}; + uint32_t right_shift{0}; // right shift + SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; + SPI_Type spi_type{SPI_Type::SPI_RISING}; + Type_Conversion type_conv{Type_Conversion::Regular}; + Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; + Filter_Type filter_type{Filter_Type::FastSinc}; + uint16_t oversampling{1}; //1..1024 + uint16_t integrator{1}; //1..256 + uint8_t Short_Circuit_Count{0xFF}; // Number of bits with the same value to guess that has been a Short Circuit + + Data_Write rdma{Data_Write::CPU}; - Fast_Conversion fast; - Sync_Conversion rsync; - Regular_Mode rcont; + Fast_Conversion fast{Fast_Conversion::Enable}; + Sync_Conversion rsync{Sync_Conversion::Sync_With_Flt0}; + Regular_Mode rcont{Regular_Mode::Continuous}; + + constexpr void operator =(const Config_Channel& other){ + offset = other.offset; + right_shift = other.right_shift; + spi_clock_sel = other.spi_clock_sel; + spi_type = other.spi_type; + type_conv = other.type_conv; + trigger_conv = other.trigger_conv; + filter_type = other.filter_type; + oversampling = other.oversampling; + integrator = other.integrator; + Short_Circuit_Count = other.Short_Circuit_Count; + rdma = other.rdma; + fast = other.fast; + rsync = other.rsync; + rcont = other.rcont; + } }; + static constexpr std::array,Possible_Pin_Channel> pin_to_channel = {{ {PE4,3},{PC0,4},{PC1,0},{PC3,1},{PC5,2}, @@ -159,56 +175,47 @@ namespace ST_LIB { return false; } - + struct Entry{ + Config_Channel config; + uint8_t channel; + }; static constexpr size_t max_instances{8}; - struct DFSDM{ - using domain = DFSDM_DOMAIN; + struct DFSDM_CHANNEL{ + using domain = DFSDM_CHANNEL_DOMAIN; Entry e; - consteval DFSDM(GPIODomain::Pin& pin,SPICKSel spi_clock_sel,SPI_Type spi_type, Type_Conversion type_conv = Type_Conversion::Regular, - int32_t offset = 0,uint8_t right_shift = 0,Trigger_Timer_Source trigger_conv = Trigger_Timer_Source::Unused,Filter_Type filter_type = Filter_Type::FastSinc, - uint16_t oversampling = 1,uint8_t integrator = 1,uint8_t Short_Circuit_Count = 0xFF, - Data_Write rdma = Data_Write::DMA, Fast_Conversion fast = Fast_Conversion::Enable, - Sync_Conversion rsync = Sync_Conversion::Sync_With_Flt0, Regular_Mode rcont = Regular_Mode::Continuous) - : e{ - .channel = get_channel(pin), - .offset = offset, - .right_shift = right_shift, - .spi_clock_sel = spi_clock_sel, - .spi_type = spi_type, - .type_conv = type_conv, - .trigger_conv = trigger_conv, - .filter_type = filter_type, - .oversampling = oversampling, - .integrator = integrator, - .Short_Circuit_Count = Short_Circuit_Count, - .rdma = rdma, - .fast = fast, - .rsync = rsync, - .rcont = rcont, - } + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin, Config_Channel config) { - if(offset > OFFSET_MAX || offset < OFFSET_MIN){ + e.channel = get_channel(pin); + e.config = config; + if(e.config.offset > OFFSET_MAX || e.config.offset < OFFSET_MIN){ compile_error("Your offset is bigger than the maximum size"); } - if(right_shift > 0x000000FF){ + if(e.config.right_shift > 0x000000FF){ compile_error("Your right_shift is bigger than the maximum size"); } - if(integrator <= 0){ + if(e.config.integrator <= 0){ compile_error("DFSDM_FILTER: Integrator out of range"); } - if (!is_correct_oversampling(filter_type, oversampling)){ + if (!is_correct_oversampling(e.config.filter_type, e.config.oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); } } template consteval std::size_t inscribe(Ctx &ctx) const { - return ctx.template add(e, this); + return ctx.template add(e, this); } }; + // I hate stm32, DFSDM_FILTER_TYPEDEF has volatile in the struct. + struct FilterConfig{ + uint32_t FLTCR1; + uint32_t FLTCR2; + uint32_t FLTFCR; + uint32_t CHCFGR1; + }; struct Config { - DFSDM_Filter_TypeDef init_data_filter; + FilterConfig init_data_filter; DFSDM_Channel_TypeDef init_data_channel; uint32_t latency_cycles; @@ -218,20 +225,20 @@ namespace ST_LIB { uint8_t channel; }; static consteval uint32_t compute_latency(const Entry& e){ - const uint32_t fosr = e.oversampling; - const uint32_t iosr = e.integrator; + const uint32_t fosr = e.config.oversampling; + const uint32_t iosr = e.config.integrator; - if (e.fast == Fast_Conversion::Enable && - e.rcont == Regular_Mode::Continuous) + if (e.config.fast == Fast_Conversion::Enable && + e.config.rcont == Regular_Mode::Continuous) { return fosr * iosr; } - if (e.filter_type == Filter_Type::FastSinc) { + if (e.config.filter_type == Filter_Type::FastSinc) { return fosr * (iosr - 1 + 4) + 2; } - const uint32_t ford = static_cast(e.filter_type); + const uint32_t ford = static_cast(e.config.filter_type); return fosr * (iosr - 1 + ford) + ford; } static consteval uint32_t get_trigger(Trigger_Timer_Source trig){ @@ -256,19 +263,19 @@ namespace ST_LIB { static consteval uint32_t make_fltfcr(const Entry& e) { return - (uint32_t(e.filter_type) << DFSDM_FLTFCR_FORD_Pos) | - (uint32_t(e.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | - (uint32_t(e.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); + (uint32_t(e.config.filter_type) << DFSDM_FLTFCR_FORD_Pos) | + (uint32_t(e.config.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | + (uint32_t(e.config.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); } - static consteval uint32_t make_fltcr2_global(const Entry& e){ + static consteval uint32_t make_fltcr2_global(){ //Activate the interrupt, to activate the continous detection, activate in execution the channel return - DFSDM_FLTCR2_CKABIE | - DFSDM_FLTCR2_SCDIE; + (uint32_t)(DFSDM_FLTCR2_CKABIE | + DFSDM_FLTCR2_SCDIE); } static consteval uint32_t make_fltcr2(const Entry& e){ uint32_t v = 0; - if(e.rdma == Data_Write::CPU){ + if(e.config.rdma == Data_Write::CPU){ v |= DFSDM_FLTCR2_REOCIE; v |= DFSDM_FLTCR2_JEOCIE; } @@ -280,24 +287,24 @@ namespace ST_LIB { { uint32_t v = 0; - if(e.type_conv == Type_Conversion::Regular){ - v |= (uint32_t(e.rdma) << DFSDM_FLTCR1_RDMAEN_Pos); - v |= (uint32_t(e.fast) << DFSDM_FLTCR1_FAST_Pos); - v |= (uint32_t(e.rsync) << DFSDM_FLTCR1_RSYNC_Pos); - v |= (uint32_t(e.rcont) << DFSDM_FLTCR1_RCONT_Pos); - }else if(e.type_conv == Type_Conversion::Injected){ + if(e.config.type_conv == Type_Conversion::Regular){ + v |= (uint32_t(e.config.rdma) << DFSDM_FLTCR1_RDMAEN_Pos); + v |= (uint32_t(e.config.fast) << DFSDM_FLTCR1_FAST_Pos); + v |= (uint32_t(e.config.rsync) << DFSDM_FLTCR1_RSYNC_Pos); + v |= (uint32_t(e.config.rcont) << DFSDM_FLTCR1_RCONT_Pos); + }else if(e.config.type_conv == Type_Conversion::Injected){ v |= DFSDM_FLTCR1_JSCAN; // activate conversion of the entire group - v |= (uint32_t)(e.rdma) << DFSDM_FLTCR1_JDMAEN_Pos; - if(e.trigger_conv != Trigger_Timer_Source::Unused){ + v |= (uint32_t)(e.config.rdma) << DFSDM_FLTCR1_JDMAEN_Pos; + if(e.config.trigger_conv != Trigger_Timer_Source::Unused){ v |= DFSDM_FLTCR1_JEXTEN_0; //with the risings if(filter == 0){ - v |= get_trigger(e.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; + v |= get_trigger(e.config.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; } - if(e.rsync == Sync_Conversion::Sync_With_Flt0){ + if(e.config.rsync == Sync_Conversion::Sync_With_Flt0){ v |= DFSDM_FLTCR1_JSYNC; }else{ - v |= get_trigger(e.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; + v |= get_trigger(e.config.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; } } } @@ -309,32 +316,32 @@ namespace ST_LIB { //DATPACK = 0 -> Standard mode //DATMPX = 0 -> Comes from an external serial input //Chinsel = 0 -> channel input are taken from pin of the same channel y - v |= uint32_t(e.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; - v |= uint32_t (e.spi_type) << DFSDM_CHCFGR1_SITP_Pos; + v |= uint32_t(e.config.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; + v |= uint32_t (e.config.spi_type) << DFSDM_CHCFGR1_SITP_Pos; return v; } static consteval uint32_t make_chcfgr2(const Entry& e){ uint32_t v = 0; - v |= (e.offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; - v |= uint8_t(e.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; + v |= (e.config.offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; + v |= uint8_t(e.config.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; return v; } static consteval uint32_t make_chawscdr(const Entry& e){ uint32_t v = 0; - v |= uint32_t(e.Short_Circuit_Count) << DFSDM_CHAWSCDR_SCDT_Pos; + v |= uint32_t(e.config.Short_Circuit_Count) << DFSDM_CHAWSCDR_SCDT_Pos; return v; } template static consteval std::array build(std::span entries) { + if (N == 0) return {}; std::array cfgs{}; - if(N == 0) return cfgs; std::array channels_used{false}; std::array filters_used{-1,-1,-1,-1}; bool filter_per_channel = (N <= 4) ? true : false; - cfgs[0].init_data_filter.FLTCR2 |= make_fltcr2_global(entries[0]); + cfgs[0].init_data_filter.FLTCR2 |= make_fltcr2_global(); for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -600,7 +607,7 @@ namespace ST_LIB { // DFSDM1_Filter0->FLTCR2 &= ~(DFSDM_FLTCR2_SCDIE_Msk); // } }; - static Instance* channel_instances[DFSDM_DOMAIN::max_instances]; + static Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances]; static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { DFSDM1_Filter0, DFSDM1_Filter1, diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 7c82e721a..e0515187a 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -95,7 +95,7 @@ using DomainsCtx = BuildCtx< EthernetDomain, ADCDomain, EXTIDomain, - DFSDM_DOMAIN, + DFSDM_CHANNEL_DOMAIN, DFSDM_CLK_DOMAIN /* PWMDomain, ...*/>; @@ -125,11 +125,9 @@ template struct Board { constexpr std::size_t ethN = domain_size(); constexpr std::size_t adcN = domain_size(); constexpr std::size_t extiN = domain_size(); - constexpr std::size_t dfsdmN = domain_size(); + constexpr std::size_t dfsdmN = domain_size(); constexpr std::size_t dfsdm_clkN = domain_size(); - // ... - struct ConfigBundle { std::array mpu_cfgs; std::array gpio_cfgs; @@ -143,7 +141,7 @@ template struct Board { std::array eth_cfgs; std::array adc_cfgs; std::array exti_cfgs; - std::array dfsdm_cfgs; + std::array dfsdm_cfgs; std::array dfsdm_clk_cfgs; // ... }; @@ -186,8 +184,8 @@ template struct Board { EXTIDomain::template build( ctx.template span()), .dfsdm_cfgs = - DFSDM_DOMAIN::template build( - ctx.template span()), + DFSDM_CHANNEL_DOMAIN::template build( + ctx.template span()), .dfsdm_clk_cfgs = DFSDM_CLK_DOMAIN::template build( ctx.template span()) @@ -209,7 +207,7 @@ template struct Board { constexpr std::size_t ethN = domain_size(); constexpr std::size_t adcN = domain_size(); constexpr std::size_t extiN = domain_size(); - constexpr std::size_t dfsdmN = domain_size(); + constexpr std::size_t dfsdmN = domain_size(); constexpr std::size_t dfsdm_clkN = domain_size(); // ... @@ -243,7 +241,7 @@ template struct Board { EthernetDomain::Init::init(cfg.eth_cfgs, DigitalOutputDomain::Init::instances); ADCDomain::Init::init(cfg.adc_cfgs, GPIODomain::Init::instances); EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); - DFSDM_DOMAIN::Init::init(cfg.dfsdm_cfgs); + DFSDM_CHANNEL_DOMAIN::Init::init(cfg.dfsdm_cfgs); DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); // ... } From 7b07dcd8a22289d3bb4089cde8080806ff0b712f Mon Sep 17 00:00:00 2001 From: oganigl Date: Sun, 1 Mar 2026 21:00:24 +0100 Subject: [PATCH 10/41] compiles let's see if it works --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 12 +++++++++--- Src/HALAL/Services/DFSDM/DFSDM.cpp | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 8366df323..b519e7de3 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -207,16 +207,21 @@ namespace ST_LIB { return ctx.template add(e, this); } }; - // I hate stm32, DFSDM_FILTER_TYPEDEF has volatile in the struct. + // I hate stm32,has volatile in the DFSDM structs. struct FilterConfig{ uint32_t FLTCR1; uint32_t FLTCR2; uint32_t FLTFCR; uint32_t CHCFGR1; }; + struct ChannelConfig{ + uint32_t CHCFGR1; + uint32_t CHCFGR2; + uint32_t CHAWSCDR; + }; struct Config { FilterConfig init_data_filter; - DFSDM_Channel_TypeDef init_data_channel; + ChannelConfig init_data_channel; uint32_t latency_cycles; Type_Conversion type_conv; @@ -607,7 +612,8 @@ namespace ST_LIB { // DFSDM1_Filter0->FLTCR2 &= ~(DFSDM_FLTCR2_SCDIE_Msk); // } }; - static Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances]; + static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; + static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { DFSDM1_Filter0, DFSDM1_Filter1, diff --git a/Src/HALAL/Services/DFSDM/DFSDM.cpp b/Src/HALAL/Services/DFSDM/DFSDM.cpp index 69253f72a..e50c9b63c 100644 --- a/Src/HALAL/Services/DFSDM/DFSDM.cpp +++ b/Src/HALAL/Services/DFSDM/DFSDM.cpp @@ -1,4 +1,5 @@ -#include "HALAL/Services/DFSDM/DFSDM.hpp" +#include "HALAL/Services/DFSDM.hpp" + From 342c9a9ece6b34737189b6f1ee7c075054a11099 Mon Sep 17 00:00:00 2001 From: oganigl Date: Wed, 4 Mar 2026 23:49:55 +0100 Subject: [PATCH 11/41] add buffer and callbacks in compile time --- CMakeLists.txt | 1 + Inc/HALAL/Models/Pin.hpp | 28 +-- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 380 +++++++++++++++++------------ Inc/ST-LIB.hpp | 2 +- Src/HALAL/Services/DFSDM/DFSDM.cpp | 10 +- 5 files changed, 250 insertions(+), 171 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dfdc9a58..c782db637 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -289,6 +289,7 @@ set(HALAL_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Time/RTC.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Time/Scheduler.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Watchdog/Watchdog.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DFSDM/DFSDM.cpp ) diff --git a/Inc/HALAL/Models/Pin.hpp b/Inc/HALAL/Models/Pin.hpp index b819c7b2d..a9407cbd9 100644 --- a/Inc/HALAL/Models/Pin.hpp +++ b/Inc/HALAL/Models/Pin.hpp @@ -26,7 +26,7 @@ constexpr GPIODomain::Pin PA15{A, GPIO_PIN_15, 0b1100111111010011}; // Port B constexpr GPIODomain::Pin PB0{B, GPIO_PIN_0, 0b0111111011111011}; -constexpr GPIODomain::Pin PB1{B, GPIO_PIN_1, 0b0111110011111011}; +constexpr GPIODomain::Pin PB1{B, GPIO_PIN_1, 0b0111111011111011}; constexpr GPIODomain::Pin PB2{B, GPIO_PIN_2, 0b0110111110111011}; constexpr GPIODomain::Pin PB3{B, GPIO_PIN_3, 0b0111110110111011}; constexpr GPIODomain::Pin PB4{B, GPIO_PIN_4, 0b0111111110110011}; @@ -35,26 +35,26 @@ constexpr GPIODomain::Pin PB6{B, GPIO_PIN_6, 0b0111110111111111}; constexpr GPIODomain::Pin PB7{B, GPIO_PIN_7, 0b0111110111111011}; constexpr GPIODomain::Pin PB8{B, GPIO_PIN_8, 0b0110111111111111}; constexpr GPIODomain::Pin PB9{B, GPIO_PIN_9, 0b0110111111111111}; -constexpr GPIODomain::Pin PB10{B, GPIO_PIN_10, 0b0111110111111111}; +constexpr GPIODomain::Pin PB10{B, GPIO_PIN_10, 0b0111111111111111}; constexpr GPIODomain::Pin PB11{B, GPIO_PIN_11, 0b0111110111111011}; -constexpr GPIODomain::Pin PB12{B, GPIO_PIN_12, 0b0111110111111011}; +constexpr GPIODomain::Pin PB12{B, GPIO_PIN_12, 0b0111111111111011}; constexpr GPIODomain::Pin PB13{B, GPIO_PIN_13, 0b0111110111111011}; -constexpr GPIODomain::Pin PB14{B, GPIO_PIN_14, 0b0111110011111011}; +constexpr GPIODomain::Pin PB14{B, GPIO_PIN_14, 0b0111111011111011}; constexpr GPIODomain::Pin PB15{B, GPIO_PIN_15, 0b0111110011111011}; // Port C -constexpr GPIODomain::Pin PC0{C, GPIO_PIN_0, 0b0110110110110011}; -constexpr GPIODomain::Pin PC1{C, GPIO_PIN_1, 0b0110111111111111}; +constexpr GPIODomain::Pin PC0{C, GPIO_PIN_0, 0b0110111110110011}; +constexpr GPIODomain::Pin PC1{C, GPIO_PIN_1, 0b0111111111111111}; constexpr GPIODomain::Pin PC2{C, GPIO_PIN_2, 0b0110111110110011}; -constexpr GPIODomain::Pin PC3{C, GPIO_PIN_3, 0b0110111110110011}; +constexpr GPIODomain::Pin PC3{C, GPIO_PIN_3, 0b0111111110110011}; constexpr GPIODomain::Pin PC4{C, GPIO_PIN_4, 0b0110110110111011}; -constexpr GPIODomain::Pin PC5{C, GPIO_PIN_5, 0b0110110110111111}; +constexpr GPIODomain::Pin PC5{C, GPIO_PIN_5, 0b0111110110111111}; constexpr GPIODomain::Pin PC6{C, GPIO_PIN_6, 0b0111110011111111}; constexpr GPIODomain::Pin PC7{C, GPIO_PIN_7, 0b0111110011111111}; constexpr GPIODomain::Pin PC8{C, GPIO_PIN_8, 0b0111110011011111}; constexpr GPIODomain::Pin PC9{C, GPIO_PIN_9, 0b0111110110111111}; constexpr GPIODomain::Pin PC10{C, GPIO_PIN_10, 0b0110111111111111}; -constexpr GPIODomain::Pin PC11{C, GPIO_PIN_11, 0b0110111111111111}; +constexpr GPIODomain::Pin PC11{C, GPIO_PIN_11, 0b0111111111111111}; constexpr GPIODomain::Pin PC12{C, GPIO_PIN_12, 0b0110111111011111}; constexpr GPIODomain::Pin PC13{C, GPIO_PIN_13, 0b1000000000000001}; constexpr GPIODomain::Pin PC14{C, GPIO_PIN_14, 0b1000000000000001}; @@ -67,10 +67,10 @@ constexpr GPIODomain::Pin PD2{D, GPIO_PIN_2, 0b1110110110011011}; constexpr GPIODomain::Pin PD3{D, GPIO_PIN_3, 0b0111110111110011}; constexpr GPIODomain::Pin PD4{D, GPIO_PIN_4, 0b0010110010010011}; constexpr GPIODomain::Pin PD5{D, GPIO_PIN_5, 0b0010110010010011}; -constexpr GPIODomain::Pin PD6{D, GPIO_PIN_6, 0b0111110110111011}; -constexpr GPIODomain::Pin PD7{D, GPIO_PIN_7, 0b0110110111111011}; +constexpr GPIODomain::Pin PD6{D, GPIO_PIN_6, 0b0111111110111011}; +constexpr GPIODomain::Pin PD7{D, GPIO_PIN_7, 0b0111110111111011}; constexpr GPIODomain::Pin PD8{D, GPIO_PIN_8, 0b0110110110010011}; -constexpr GPIODomain::Pin PD9{D, GPIO_PIN_9, 0b0110110110010011}; +constexpr GPIODomain::Pin PD9{D, GPIO_PIN_9, 0b0111110110010011}; constexpr GPIODomain::Pin PD10{D, GPIO_PIN_10, 0b0111110110010011}; constexpr GPIODomain::Pin PD11{D, GPIO_PIN_11, 0b0111100110110011}; constexpr GPIODomain::Pin PD12{D, GPIO_PIN_12, 0b0111110110110011}; @@ -83,7 +83,7 @@ constexpr GPIODomain::Pin PE0{E, GPIO_PIN_0, 0b0111110010110011}; constexpr GPIODomain::Pin PE1{E, GPIO_PIN_1, 0b0111110010011011}; constexpr GPIODomain::Pin PE2{E, GPIO_PIN_2, 0b0110110111110011}; constexpr GPIODomain::Pin PE3{E, GPIO_PIN_3, 0b0110110111110011}; -constexpr GPIODomain::Pin PE4{E, GPIO_PIN_4, 0b0110110111111111}; +constexpr GPIODomain::Pin PE4{E, GPIO_PIN_4, 0b0111110111111111}; constexpr GPIODomain::Pin PE5{E, GPIO_PIN_5, 0b0110110111111011}; constexpr GPIODomain::Pin PE6{E, GPIO_PIN_6, 0b0110110111111111}; constexpr GPIODomain::Pin PE7{E, GPIO_PIN_7, 0b0111110010010011}; @@ -110,7 +110,7 @@ constexpr GPIODomain::Pin PF9{F, GPIO_PIN_9, 0b0111110010111011}; constexpr GPIODomain::Pin PF10{F, GPIO_PIN_10, 0b0110110010110011}; constexpr GPIODomain::Pin PF11{F, GPIO_PIN_11, 0b0010110010010011}; constexpr GPIODomain::Pin PF12{F, GPIO_PIN_12, 0b0010010010010011}; -constexpr GPIODomain::Pin PF13{F, GPIO_PIN_13, 0b0110110010110011}; +constexpr GPIODomain::Pin PF13{F, GPIO_PIN_13, 0b0111110010110011}; constexpr GPIODomain::Pin PF14{F, GPIO_PIN_14, 0b0110110010110011}; constexpr GPIODomain::Pin PF15{F, GPIO_PIN_15, 0b0010110010010011}; diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index b519e7de3..9256d96ea 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -2,6 +2,7 @@ #include "HALAL/Models/GPIO.hpp" #include "HALAL/Models/Pin.hpp" + #define Oversampling_MAX 1024 #define Oversampling_MAX_Filter_4 215 #define Oversampling_MAX_Filter_5 73 @@ -64,7 +65,7 @@ namespace ST_LIB { After_Filter = 0, // AWFSEL = 0 Channel_Data = 1 // AWFSEL = 1 }; - //AWFSEL = 0 high precision, slow speed <- Ideally for our case, + //AWFSEL = 0 high precision, slow speed <- Ideally for our case //AWFSEL = 1 16 bits precision ultra high speed oversampling ratio 1..32 filter (1..3) 8 relojes de clock enum class Overrun : uint8_t { @@ -103,41 +104,47 @@ namespace ST_LIB { Regular, Injected }; - struct Config_Channel{ - int32_t offset{0}; - uint32_t right_shift{0}; // right shift - SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; - SPI_Type spi_type{SPI_Type::SPI_RISING}; - Type_Conversion type_conv{Type_Conversion::Regular}; - Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; - Filter_Type filter_type{Filter_Type::FastSinc}; - uint16_t oversampling{1}; //1..1024 - uint16_t integrator{1}; //1..256 - uint8_t Short_Circuit_Count{0xFF}; // Number of bits with the same value to guess that has been a Short Circuit - - Data_Write rdma{Data_Write::CPU}; - - Fast_Conversion fast{Fast_Conversion::Enable}; - Sync_Conversion rsync{Sync_Conversion::Sync_With_Flt0}; - Regular_Mode rcont{Regular_Mode::Continuous}; - - constexpr void operator =(const Config_Channel& other){ - offset = other.offset; - right_shift = other.right_shift; - spi_clock_sel = other.spi_clock_sel; - spi_type = other.spi_type; - type_conv = other.type_conv; - trigger_conv = other.trigger_conv; - filter_type = other.filter_type; - oversampling = other.oversampling; - integrator = other.integrator; - Short_Circuit_Count = other.Short_Circuit_Count; - rdma = other.rdma; - fast = other.fast; - rsync = other.rsync; - rcont = other.rcont; - } - }; + struct Config_Channel { + + int32_t offset{0}; + uint32_t right_shift{0}; + SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; + SPI_Type spi_type{SPI_Type::SPI_RISING}; + Type_Conversion type_conv{Type_Conversion::Regular}; + Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; + Filter_Type filter_type{Filter_Type::FastSinc}; + uint16_t oversampling{1}; + uint16_t integrator{1}; + + Data_Write rdma{Data_Write::CPU}; + Fast_Conversion fast{Fast_Conversion::Disable}; + Sync_Conversion rsync{Sync_Conversion::Independent}; + Regular_Mode rcont{Regular_Mode::Single}; + + /* -------- Runtime protections -------- */ + + Overrun overrun{Overrun::Disable}; + Clock_Absence clock_absence{Clock_Absence::Disable}; + Short_Circuit short_circuit{Short_Circuit::Disable}; + Extreme_Detector extreme_detector{Extreme_Detector::Disable}; + + uint8_t short_circuit_count{0xFF}; + + /* -------- Analog watchdog -------- */ + + bool watchdog_enable{false}; + Analog_Watchdog_Fast_Mode watchdog_mode{Analog_Watchdog_Fast_Mode::After_Filter}; + + int32_t watchdog_low_threshold{0x10000000}; + int32_t watchdog_high_threshold{0x7FFFFFFF}; + + //callbacks + Callback watchdog_callback{nullptr}; + Callback conversion_complete_callback{nullptr}; + Callback overrun_callback{nullptr}; + Callback clock_absence_callback{nullptr}; + Callback short_circuit_callback{nullptr}; +}; static constexpr std::array,Possible_Pin_Channel> pin_to_channel = {{ @@ -174,36 +181,75 @@ namespace ST_LIB { } return false; } - + static consteval GPIODomain::AlternateFunction dfsdm_channel_af(const GPIODomain::Pin& pin) { + if ((pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_1) || + (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_10) || + (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_12) || + (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_14) || + (pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_0)) + { + return GPIODomain::AlternateFunction::AF6; + } + if((pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_7) || + (pin.port == GPIODomain::Port::D && pin.pin == GPIO_PIN_6)) + { + return GPIODomain::AlternateFunction::AF4; + } + if((pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_6)){ + return GPIODomain::AlternateFunction::AF11; + } + return GPIODomain::AlternateFunction::AF3; //In any other case + } struct Entry{ - Config_Channel config; - uint8_t channel; + Config_Channel config; + uint8_t channel; + size_t gpio_idx; + int32_t* buffer; + size_t buffer_size; + }; static constexpr size_t max_instances{8}; + template struct DFSDM_CHANNEL{ using domain = DFSDM_CHANNEL_DOMAIN; - Entry e; - - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin, Config_Channel config) - { - e.channel = get_channel(pin); - e.config = config; - if(e.config.offset > OFFSET_MAX || e.config.offset < OFFSET_MIN){ + const GPIODomain::Pin& pin; + GPIODomain::GPIO gpio; + Config_Channel config; + uint8_t channel; + int32_t* buffer; + size_t buffer_size; + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin, Config_Channel config, int32_t (&buffer)[N]) + : pin(pin), + gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, + config(config), + buffer_size(N) + { + this->buffer = buffer; + channel = get_channel(pin); + if(config.offset > OFFSET_MAX || config.offset < OFFSET_MIN){ compile_error("Your offset is bigger than the maximum size"); } - if(e.config.right_shift > 0x000000FF){ + if(config.right_shift > 0x000000FF){ compile_error("Your right_shift is bigger than the maximum size"); } - if(e.config.integrator <= 0){ + if(config.integrator <= 0){ compile_error("DFSDM_FILTER: Integrator out of range"); } - if (!is_correct_oversampling(e.config.filter_type, e.config.oversampling)){ + if (!is_correct_oversampling(config.filter_type, config.oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); } } template consteval std::size_t inscribe(Ctx &ctx) const { + const auto gpio_idx = gpio.inscribe(ctx); + Entry e{ + .config = config, + .channel = channel, + .gpio_idx = gpio_idx, + .buffer = buffer, + .buffer_size = buffer_size + }; return ctx.template add(e, this); } }; @@ -212,7 +258,6 @@ namespace ST_LIB { uint32_t FLTCR1; uint32_t FLTCR2; uint32_t FLTFCR; - uint32_t CHCFGR1; }; struct ChannelConfig{ uint32_t CHCFGR1; @@ -220,14 +265,25 @@ namespace ST_LIB { uint32_t CHAWSCDR; }; struct Config { - FilterConfig init_data_filter; - ChannelConfig init_data_channel; - - uint32_t latency_cycles; - Type_Conversion type_conv; + size_t gpio_idx; + FilterConfig init_data_filter; + ChannelConfig init_data_channel; + + uint32_t latency_cycles; + Type_Conversion type_conv; - uint8_t filter; - uint8_t channel; + uint8_t filter; + uint8_t channel; + + size_t buffer_size; + int32_t* buffer; + + //callbacks + Callback watchdog_callback{nullptr}; + Callback conversion_complete_callback{nullptr}; + Callback overrun_callback{nullptr}; + Callback clock_absence_callback{nullptr}; + Callback short_circuit_callback{nullptr}; }; static consteval uint32_t compute_latency(const Entry& e){ const uint32_t fosr = e.config.oversampling; @@ -334,7 +390,7 @@ namespace ST_LIB { } static consteval uint32_t make_chawscdr(const Entry& e){ uint32_t v = 0; - v |= uint32_t(e.config.Short_Circuit_Count) << DFSDM_CHAWSCDR_SCDT_Pos; + v |= uint32_t(e.config.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; return v; } @@ -346,7 +402,6 @@ namespace ST_LIB { std::array filters_used{-1,-1,-1,-1}; bool filter_per_channel = (N <= 4) ? true : false; - cfgs[0].init_data_filter.FLTCR2 |= make_fltcr2_global(); for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -355,8 +410,16 @@ namespace ST_LIB { } channels_used[e.channel] = true; Config& cfg = cfgs[i]; - + + cfg.gpio_idx = e.gpio_idx; cfg.channel = e.channel; + cfg.buffer_size = e.buffer_size; + cfg.buffer = e.buffer; + //add the callbacks + cfg.overrun_callback = e.config.overrun_callback; + cfg.clock_absence_callback = e.config.clock_absence_callback; + cfg.short_circuit_callback = e.config.short_circuit_callback; + cfg.watchdog_callback = e.config.watchdog_callback; if(filter_per_channel){ cfg.filter = i; }else{ @@ -383,6 +446,8 @@ namespace ST_LIB { } struct Instance { + GPIODomain::Instance *gpio_instance; + DFSDM_Filter_TypeDef *filter_regs{}; DFSDM_Channel_TypeDef *channel_regs{}; @@ -461,11 +526,7 @@ namespace ST_LIB { channel_regs->CHCFGR2 |= (offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; } - /*Filter functions*/ - void keep_data_in_buffer(int32_t* buffer,size_t length){ - buffer = buffer; - length_buffer = length; - } + /*Filter functions*/ void start() { if (!is_enabled()) enable(); @@ -634,96 +695,113 @@ namespace ST_LIB { static inline std::array instances{}; - static void init(std::span cfgs) { - for (std::size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; - Instance &inst = instances[i]; - - inst.filter_regs = filter_hw[cfg.filter]; - inst.channel_regs = channel_hw[cfg.channel]; - - inst.latency_cycles = cfg.latency_cycles; - inst.type_conv = cfg.type_conv; - inst.filter = cfg.filter; - inst.channel = cfg.channel; - //add everything to the register of the filter - - inst.filter_regs->FLTCR1 = cfg.init_data_filter.FLTCR1; - if(inst.type_conv == Type_Conversion::Regular){ - inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; - inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; - }else{ - inst.filter_regs->FLTJCHGR |= 1 << inst.channel; + static void init(std::span cfgs,std::span gpio_instances) { + std::array filters_configured = {false,false,false,false}; + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock + for (size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + filter_hw[cfg.filter]->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; + channel_hw[cfg.channel]->CHCFGR1 &= ~DFSDM_CHCFGR1_CHEN; + } + for (std::size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + Instance &inst = instances[i]; + + inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; + + inst.filter_regs = filter_hw[cfg.filter]; + inst.channel_regs = channel_hw[cfg.channel]; + + inst.latency_cycles = cfg.latency_cycles; + inst.type_conv = cfg.type_conv; + inst.filter = cfg.filter; + inst.channel = cfg.channel; + + inst.buffer = cfg.buffer; + inst.length_buffer = cfg.buffer_size; + if(!filters_configured[cfg.filter]){ + //add everything to the register of the filter + inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; + if(inst.type_conv == Type_Conversion::Regular){ + inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; + inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; + } + inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; + inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; + } + if(inst.type_conv == Type_Conversion::Injected){ + inst.filter_regs->FLTJCHGR |= 1 << inst.channel; + } + //add everything to the channel register + inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; + inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; + filters_configured[cfg.filter] = true; + + //enable the filter + inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; + //enable the channel + inst.channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + + //activate the NVIC + if(filters_configured[cfg.filter] == true){ + switch(inst.filter){ + case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; + case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; + case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; + case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + } + } + //update channel_instances + channel_instances[inst.channel] = &inst; } - inst.filter_regs->FLTCR2 = cfg.init_data_filter.FLTCR2; - inst.filter_regs->FLTFCR = cfg.init_data_filter.FLTFCR; + DFSDM1_Filter0->FLTCR2 |= make_fltcr2_global(); - //add everything to the channel register - inst.channel_regs->CHCFGR1 = cfg.init_data_channel.CHCFGR1; - inst.channel_regs->CHCFGR2 = cfg.init_data_channel.CHCFGR2; - - //enable the filter - inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; - //enable the channel - inst.channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; - - - //activate the NVIC - switch(inst.filter){ - case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; - case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; - case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; - case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; - } - //update channel_instances - channel_instances[inst.channel] = &inst; + //Activate the DFSDM GLOBAL Interface + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; } - //Activate the DFSDM GLOBAL Interface - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - } }; - static void handle_irq(uint8_t filter_index) - { + static void handle_irq(uint8_t filter_index) + { - DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; - - uint32_t isr = filter->FLTISR; - - if(isr & DFSDM_FLTISR_REOCF_Msk){ - Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; - inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; - return; - } - if(isr & DFSDM_FLTISR_JEOCF_Msk){ - //GUARDARLO EN LA DIRECCIÓN DE MEMORIA QUE ME PORPORCIONE EL USUARIO - Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; - inst->buffer[inst->idx] = (filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; - return; - } - if(isr & DFSDM_FLTISR_SCDF_Msk){ - uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos) + 1; - if(channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; - } - if(isr & DFSDM_FLTISR_CKABF_Msk){ - uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos) + 1; - if(channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); - //cleaR - filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; - } - //Analog watchdog - if (isr & DFSDM_FLTISR_AWDF) - { - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; - //mirar como saber el canal que ha lanzado el watchdog - // if(filter->watchdog_cb != nullptr)inst->watchdog_cb(); - return; - } + DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; + + uint32_t isr = filter->FLTISR; + + if(isr & DFSDM_FLTISR_REOCF_Msk){ + Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; + inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + return; } + if(isr & DFSDM_FLTISR_JEOCF_Msk){ + //GUARDARLO EN LA DIRECCIÓN DE MEMORIA QUE ME PORPORCIONE EL USUARIO + Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; + inst->buffer[inst->idx] = (filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + return; + } + if(isr & DFSDM_FLTISR_SCDF_Msk){ + uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos) + 1; + if(channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); + //clear + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + } + if(isr & DFSDM_FLTISR_CKABF_Msk){ + uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos) + 1; + if(channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); + //cleaR + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + } + //Analog watchdog + if (isr & DFSDM_FLTISR_AWDF) + { + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + //mirar como saber el canal que ha lanzado el watchdog + // if(filter->watchdog_cb != nullptr)inst->watchdog_cb(); + return; + } + } }; @@ -764,7 +842,7 @@ struct DFSDM_CLK_DOMAIN{ GPIODomain::Pin pin; uint8_t clk_divider; consteval DFSDM_CLK(const GPIODomain::Pin &pin,uint8_t clk_divider = 100): // clk_divider = 100 -> 1Mhz - gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None, GPIODomain::Speed::VeryHigh,dfsdm_clk_af(pin)}, + gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None, GPIODomain::Speed::High,dfsdm_clk_af(pin)}, pin(pin), clk_divider(clk_divider) {} @@ -815,7 +893,7 @@ struct DFSDM_CLK_DOMAIN{ DFSDM1_Channel0->CHCFGR1 |= uint32_t(clk_divider -1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; - //enable the CKOUT + //enable the DFSDM Global Interface DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; } bool disable(){ diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index e0515187a..4ece18339 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -241,7 +241,7 @@ template struct Board { EthernetDomain::Init::init(cfg.eth_cfgs, DigitalOutputDomain::Init::instances); ADCDomain::Init::init(cfg.adc_cfgs, GPIODomain::Init::instances); EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); - DFSDM_CHANNEL_DOMAIN::Init::init(cfg.dfsdm_cfgs); + DFSDM_CHANNEL_DOMAIN::Init::init(cfg.dfsdm_cfgs,GPIODomain::Init::instances); DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); // ... } diff --git a/Src/HALAL/Services/DFSDM/DFSDM.cpp b/Src/HALAL/Services/DFSDM/DFSDM.cpp index e50c9b63c..20f6e0569 100644 --- a/Src/HALAL/Services/DFSDM/DFSDM.cpp +++ b/Src/HALAL/Services/DFSDM/DFSDM.cpp @@ -1,4 +1,4 @@ -#include "HALAL/Services/DFSDM.hpp" +#include "HALAL/Services/DFSDM/DFSDM.hpp" @@ -7,18 +7,18 @@ extern "C"{ void DFSDM1_FLT0_IRQHandler(void) { - ST_LIB::DFSDM_DOMAIN::handle_irq(0); + ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(0); } void DFSDM1_FLT1_IRQHandler(void) { - ST_LIB::DFSDM_DOMAIN::handle_irq(1); + ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(1); } void DFSDM1_FLT2_IRQHandler(void) { - ST_LIB::DFSDM_DOMAIN::handle_irq(2); + ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(2); } void DFSDM1_FLT3_IRQHandler(void) { - ST_LIB::DFSDM_DOMAIN::handle_irq(3); + ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(3); } } \ No newline at end of file From 6c67c0ce4f4fc96e44de5aebd8712224b05675e4 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 6 Mar 2026 17:20:54 +0100 Subject: [PATCH 12/41] some more changes in the dfsdm --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 238 +++++++++++++++++++---------- 1 file changed, 155 insertions(+), 83 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 9256d96ea..8e4a0c643 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -3,6 +3,7 @@ #include "HALAL/Models/GPIO.hpp" #include "HALAL/Models/Pin.hpp" + #define Oversampling_MAX 1024 #define Oversampling_MAX_Filter_4 215 #define Oversampling_MAX_Filter_5 73 @@ -61,7 +62,7 @@ namespace ST_LIB { Continuous = 1 }; - enum class Analog_Watchdog_Fast_Mode : uint8_t { + enum class Analog_Watchdog_Mode : uint8_t { After_Filter = 0, // AWFSEL = 0 Channel_Data = 1 // AWFSEL = 1 }; @@ -84,6 +85,10 @@ namespace ST_LIB { Disable = 0, Enable = 1 }; + enum class Analog_Watchdog : uint8_t{ + Disable = 0, + Enable = 1 + }; enum class Trigger_Timer_Source : uint8_t { Unused, Tim1, @@ -132,9 +137,11 @@ namespace ST_LIB { /* -------- Analog watchdog -------- */ - bool watchdog_enable{false}; - Analog_Watchdog_Fast_Mode watchdog_mode{Analog_Watchdog_Fast_Mode::After_Filter}; - + Analog_Watchdog watchdog{Analog_Watchdog::Disable}; + Analog_Watchdog_Mode watchdog_mode{Analog_Watchdog_Mode::After_Filter}; + //If Mode Watchdog == After filter + Filter_Type filter_wathdog{Filter_Type::Sinc2}; + uint8_t watchdog_oversampling{5}; int32_t watchdog_low_threshold{0x10000000}; int32_t watchdog_high_threshold{0x7FFFFFFF}; @@ -239,6 +246,12 @@ namespace ST_LIB { if (!is_correct_oversampling(config.filter_type, config.oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); } + if(config.watchdog_oversampling > 31){ + compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); + } + if(static_cast(config.filter_wathdog) > 3){ + compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); + } } template consteval std::size_t inscribe(Ctx &ctx) const { @@ -258,6 +271,9 @@ namespace ST_LIB { uint32_t FLTCR1; uint32_t FLTCR2; uint32_t FLTFCR; + uint32_t FLTAWHTR; + uint32_t FLTAWLTR; + uint32_t FLTJCHGR; }; struct ChannelConfig{ uint32_t CHCFGR1; @@ -321,6 +337,7 @@ namespace ST_LIB { } return 0; } + //Filter registers static consteval uint32_t make_fltfcr(const Entry& e) { return @@ -329,19 +346,26 @@ namespace ST_LIB { (uint32_t(e.config.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); } static consteval uint32_t make_fltcr2_global(){ - //Activate the interrupt, to activate the continous detection, activate in execution the channel - return - (uint32_t)(DFSDM_FLTCR2_CKABIE | - DFSDM_FLTCR2_SCDIE); + //Activate the interrupt, to activate the continous detection, then the channel can + return (uint32_t)(DFSDM_FLTCR2_CKABIE | DFSDM_FLTCR2_SCDIE); } static consteval uint32_t make_fltcr2(const Entry& e){ uint32_t v = 0; - if(e.config.rdma == Data_Write::CPU){ + if(e.config.rdma == Data_Write::CPU || e.config.conversion_complete_callback != nullptr){ v |= DFSDM_FLTCR2_REOCIE; v |= DFSDM_FLTCR2_JEOCIE; } - //Activate the interrupt AWDIE,in case of using CPU also activating REOCIE - v |= DFSDM_FLTCR2_AWDIE; + if(e.config.watchdog == Analog_Watchdog::Enable){ + v |= DFSDM_FLTCR2_AWDIE; + v |= 1 << (DFSDM_FLTCR2_AWDCH_Pos + e.channel); + } + if (e.config.extreme_detector == Extreme_Detector::Enable){ + v |= 1 << (DFSDM_FLTCR2_EXCH_Pos + e.channel); + } + if(e.config.overrun == Overrun::Enable){ + v |= DFSDM_FLTCR2_JOVRIE; + v |= DFSDM_FLTCR2_ROVRIE; + } return v; } static consteval uint32_t make_fltcr1(const Entry& e,uint32_t filter) @@ -369,8 +393,34 @@ namespace ST_LIB { } } } + v |= uint32_t(e.config.watchdog_mode) << DFSDM_FLTCR1_AWFSEL_Pos; + return v; + } + static consteval uint32_t make_fltawhtr(const Entry& e){ + uint32_t v = 0; + if (e.config.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) + v |= (e.config.watchdog_high_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + else + v |= (e.config.watchdog_high_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; + return v; + } + static consteval uint32_t make_fltawltr(const Entry& e){ + uint32_t v = 0; + if (e.config.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) + v |= (e.config.watchdog_low_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + else + v |= (e.config.watchdog_low_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; + return v; + } + //Channel + static consteval uint32_t make_chawscdr(const Entry& e){ + uint32_t v = 0; + v |= uint32_t(e.config.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; + if(e.config.watchdog == Analog_Watchdog::Enable){ + v |= static_cast(e.config.filter_wathdog) << DFSDM_CHAWSCDR_AWFORD_Pos; + v |= static_cast(e.config.watchdog_oversampling & 0xF) << DFSDM_CHAWSCDR_AWFOSR_Pos; + } return v; - } static consteval uint32_t make_chcfgr1(const Entry& e){ uint32_t v = 0; @@ -379,6 +429,9 @@ namespace ST_LIB { //Chinsel = 0 -> channel input are taken from pin of the same channel y v |= uint32_t(e.config.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; v |= uint32_t (e.config.spi_type) << DFSDM_CHCFGR1_SITP_Pos; + v |= uint32_t(e.config.clock_absence) << DFSDM_CHCFGR1_CKABEN_Pos; + v |= uint32_t(e.config.short_circuit) << DFSDM_CHCFGR1_SCDEN_Pos; + return v; } @@ -388,11 +441,6 @@ namespace ST_LIB { v |= uint8_t(e.config.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; return v; } - static consteval uint32_t make_chawscdr(const Entry& e){ - uint32_t v = 0; - v |= uint32_t(e.config.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; - return v; - } template static consteval std::array build(std::span entries) { @@ -431,14 +479,28 @@ namespace ST_LIB { cfg.init_data_channel.CHCFGR1 |= make_chcfgr1(e); cfg.init_data_channel.CHCFGR2 |= make_chcfgr2(e); cfg.init_data_channel.CHAWSCDR |= make_chawscdr(e); + cfg.init_data_filter.FLTAWHTR |= make_fltawhtr(e); + cfg.init_data_filter.FLTAWLTR |= make_fltawltr(e); + if(e.config.type_conv == Type_Conversion::Injected) cfg.init_data_filter.FLTJCHGR |= 1 << e.channel; + if(cfg.filter == 0) cfg.init_data_filter.FLTCR2 |= make_fltcr2_global(); + cfg.latency_cycles = compute_latency(e); if(filters_used[cfg.filter] != -1){ if(cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR1 != cfg.init_data_filter.FLTCR1 || - cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 != cfg.init_data_filter.FLTCR2 || - cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != cfg.init_data_filter.FLTFCR){ + (cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 & 0xFF) != (cfg.init_data_filter.FLTCR2 & 0xFF) || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != cfg.init_data_filter.FLTFCR || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWLTR != cfg.init_data_filter.FLTAWLTR || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWHTR != cfg.init_data_filter.FLTAWHTR){ compile_error("You have two channels that goes to the same filter with different filter configuration"); - } - + } + //have the same thing in every register of the filter + //Channel group conversion in injected mode + cfgs[filters_used[cfg.filter]].init_data_filter.FLTJCHGR |= cfg.init_data_filter.FLTJCHGR; + cfg.init_data_filter.FLTJCHGR = cfgs[filters_used[cfg.filter]].init_data_filter.FLTJCHGR; + //Watchdog and Extreme detector channel enabled + cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 |= cfg.init_data_filter.FLTCR2; + cfg.init_data_filter.FLTCR2 = cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2; + //Watchdog and Extreme detector channel enabled } filters_used[cfg.filter] = i; } @@ -454,6 +516,8 @@ namespace ST_LIB { Callback watchdog_cb{}; Callback short_circuit_cb{}; Callback clock_absence_cb{}; + Callback overrun_cb{}; + Callback end_conversion_cb{}; uint32_t latency_cycles; uint8_t channel; @@ -612,40 +676,6 @@ namespace ST_LIB { uint32_t check_max_extreme_detector() { return filter_regs->FLTEXMAX >> DFSDM_FLTEXMAX_EXMAX_Pos; } - //watchdog - bool activate_watchdog(Callback callback_wdg,uint32_t low_watchdog,uint32_t high_watchdog, - Filter_Type watchdog_filter = Filter_Type::FastSinc,uint32_t oversampling = 1,Analog_Watchdog_Fast_Mode awfsel = Analog_Watchdog_Fast_Mode::After_Filter) - { - if (oversampling == 0 || oversampling > 32) return false; - if (low_watchdog >= high_watchdog) return false; - watchdog_cb = callback_wdg; - bool was_enabled_channel = is_enabled_channel(); - if (was_enabled_channel) disable_channel(); - - /* ---------------- CHANNEL CONFIG ---------------- */ - channel_regs->CHAWSCDR &= ~(DFSDM_CHAWSCDR_AWFORD_Msk | DFSDM_CHAWSCDR_AWFOSR_Msk); - channel_regs->CHAWSCDR |= (uint32_t(watchdog_filter) << DFSDM_CHAWSCDR_AWFORD_Pos); - channel_regs->CHAWSCDR |= ((oversampling - 1) << DFSDM_CHAWSCDR_AWFOSR_Pos); - - /* ---------------- FILTER CONFIG ---------------- */ - bool was_enabled_filter = is_enabled_filter(); - if(was_enabled_filter) disable_filter(); - if (awfsel == Analog_Watchdog_Fast_Mode::Channel_Data) - filter_regs->FLTCR1 |= DFSDM_FLTCR1_AWFSEL; - else - filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_AWFSEL; - - // Set thresholds - filter_regs->FLTAWLTR = low_watchdog; - filter_regs->FLTAWHTR = high_watchdog; - // add this channel to the Analog watchdog channel selection - filter_regs->FLTCR2 |= (this->channel << DFSDM_FLTCR2_AWDCH_Pos); - // Enable analog watchdog interrupt - filter_regs->FLTCR2 |= DFSDM_FLTCR2_AWDIE; - if (was_enabled_channel || was_enabled_filter) enable(); - return true; - } - void modify_watchdog_lth(uint32_t value) { filter_regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; @@ -666,15 +696,46 @@ namespace ST_LIB { else filter_regs->FLTAWHTR = (value & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; } - // void activate_ckabie(Callback ckabie_cb){ - // DFSDM1_Filter0->FLTCR2 &= ~(DFSDM_FLTCR2_CKABIE_Msk); - // } - // void activate_scdie(Callback scdie_cb){ - // DFSDM1_Filter0->FLTCR2 &= ~(DFSDM_FLTCR2_SCDIE_Msk); - // } + //get the last conversion from a filter + static uint8_t get_last_conversion_from_filter(uint8_t filter, Type_Conversion conv){ + uint8_t channel = 0xFF; + switch(filter){ + case 0: + if(conv == Type_Conversion::Injected){ + channel = (DFSDM1_Filter0->FLTJDATAR & 0x7); + }else{ + channel = (DFSDM1_Filter0->FLTRDATAR & 0x7); + } + break; + case 1: + if(conv == Type_Conversion::Injected){ + channel = (DFSDM1_Filter1->FLTJDATAR & 0x7); + }else{ + channel = (DFSDM1_Filter1->FLTRDATAR & 0x7); + } + break; + + case 2: + if(conv == Type_Conversion::Injected){ + channel = (DFSDM1_Filter2->FLTJDATAR & 0x7); + }else{ + channel = (DFSDM1_Filter2->FLTRDATAR & 0x7); + } + break; + case 3: + if(conv == Type_Conversion::Injected){ + channel = (DFSDM1_Filter3->FLTJDATAR & 0x7); + }else{ + channel = (DFSDM1_Filter3->FLTRDATAR & 0x7); + } + break; + default: break; + } + return channel; + } }; static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; - + static inline uint8_t channels_enabled{}; static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { DFSDM1_Filter0, DFSDM1_Filter1, @@ -694,7 +755,6 @@ namespace ST_LIB { template struct Init { static inline std::array instances{}; - static void init(std::span cfgs,std::span gpio_instances) { std::array filters_configured = {false,false,false,false}; RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock @@ -719,6 +779,12 @@ namespace ST_LIB { inst.buffer = cfg.buffer; inst.length_buffer = cfg.buffer_size; + + //callbacks + inst.overrun_cb = cfg.overrun_callback; + inst.short_circuit_cb = cfg.short_circuit_callback; + inst.watchdog_cb = cfg.watchdog_callback; + inst.end_conversion_cb = cfg.conversion_complete_callback; if(!filters_configured[cfg.filter]){ //add everything to the register of the filter inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; @@ -728,14 +794,17 @@ namespace ST_LIB { } inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; + inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; + inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; + inst.filter_regs->FLTJCHGR |= cfg.init_data_filter.FLTJCHGR; + + filters_configured[cfg.filter] = true; } - if(inst.type_conv == Type_Conversion::Injected){ - inst.filter_regs->FLTJCHGR |= 1 << inst.channel; - } //add everything to the channel register inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; - filters_configured[cfg.filter] = true; + inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; + //enable the filter inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; @@ -753,13 +822,13 @@ namespace ST_LIB { } //update channel_instances channel_instances[inst.channel] = &inst; + channels_enabled |= 1 << inst.channel; } - DFSDM1_Filter0->FLTCR2 |= make_fltcr2_global(); - //Activate the DFSDM GLOBAL Interface DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; } }; + static void handle_irq(uint8_t filter_index) { @@ -771,35 +840,38 @@ namespace ST_LIB { Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; inst->idx = (inst->idx + 1) % inst->length_buffer; - return; + if(inst->end_conversion_cb != nullptr){ + inst->end_conversion_cb(); + } } if(isr & DFSDM_FLTISR_JEOCF_Msk){ //GUARDARLO EN LA DIRECCIÓN DE MEMORIA QUE ME PORPORCIONE EL USUARIO Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; inst->buffer[inst->idx] = (filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; inst->idx = (inst->idx + 1) % inst->length_buffer; - return; + if(inst->end_conversion_cb != nullptr){ + inst->end_conversion_cb(); + } } - if(isr & DFSDM_FLTISR_SCDF_Msk){ - uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos) + 1; - if(channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); + if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ + uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; + if(channel_instances[ch] != nullptr && channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); //clear - filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + filter->FLTICR = DFSDM_FLTICR_CLRSCDF; } - if(isr & DFSDM_FLTISR_CKABF_Msk){ - uint32_t ch = __builtin_ctz((isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos) + 1; - if(channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); - //cleaR - filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + if(isr & (channels_enabled << DFSDM_FLTISR_CKABF_Pos)){ + uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos; + if(channel_instances[ch] != nullptr && channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); + //clear + filter->FLTICR = DFSDM_FLTICR_CLRCKABF; } //Analog watchdog - if (isr & DFSDM_FLTISR_AWDF) + if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) { filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; //mirar como saber el canal que ha lanzado el watchdog - // if(filter->watchdog_cb != nullptr)inst->watchdog_cb(); - return; + // if(filter->watchdog_cb != nullptr) watchdog_cb(); } } }; From 12d51ca628197d4badf7c5d337ddd6bb484bf680 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 6 Mar 2026 19:17:47 +0100 Subject: [PATCH 13/41] fix some minor errors --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 39 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 8e4a0c643..50e6ce5cc 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -208,7 +208,7 @@ namespace ST_LIB { return GPIODomain::AlternateFunction::AF3; //In any other case } struct Entry{ - Config_Channel config; + const Config_Channel* config; uint8_t channel; size_t gpio_idx; int32_t* buffer; @@ -216,17 +216,17 @@ namespace ST_LIB { }; - static constexpr size_t max_instances{8}; + static constexpr size_t mastances{8}; template struct DFSDM_CHANNEL{ using domain = DFSDM_CHANNEL_DOMAIN; const GPIODomain::Pin& pin; GPIODomain::GPIO gpio; - Config_Channel config; + const Config_Channel config; uint8_t channel; int32_t* buffer; size_t buffer_size; - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin, Config_Channel config, int32_t (&buffer)[N]) + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel& config, int32_t (&buffer)[N]) : pin(pin), gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, config(config), @@ -246,7 +246,7 @@ namespace ST_LIB { if (!is_correct_oversampling(config.filter_type, config.oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); } - if(config.watchdog_oversampling > 31){ + if(config.watchdog_oversampling > 32){ compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); } if(static_cast(config.filter_wathdog) > 3){ @@ -257,7 +257,7 @@ namespace ST_LIB { consteval std::size_t inscribe(Ctx &ctx) const { const auto gpio_idx = gpio.inscribe(ctx); Entry e{ - .config = config, + .config = &config, .channel = channel, .gpio_idx = gpio_idx, .buffer = buffer, @@ -418,7 +418,7 @@ namespace ST_LIB { v |= uint32_t(e.config.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; if(e.config.watchdog == Analog_Watchdog::Enable){ v |= static_cast(e.config.filter_wathdog) << DFSDM_CHAWSCDR_AWFORD_Pos; - v |= static_cast(e.config.watchdog_oversampling & 0xF) << DFSDM_CHAWSCDR_AWFOSR_Pos; + v |= static_cast((e.config.watchdog_oversampling-1) & 0xF) << DFSDM_CHAWSCDR_AWFOSR_Pos; } return v; } @@ -463,6 +463,7 @@ namespace ST_LIB { cfg.channel = e.channel; cfg.buffer_size = e.buffer_size; cfg.buffer = e.buffer; + cfg.type_conv = e.config.type_conv; //add the callbacks cfg.overrun_callback = e.config.overrun_callback; cfg.clock_absence_callback = e.config.clock_absence_callback; @@ -570,8 +571,8 @@ namespace ST_LIB { void enable() { //just in case enable everything to work enable_DFSDM_Peripheral(); - enable_filter(); enable_channel(); + enable_filter(); } void disable() { @@ -611,11 +612,25 @@ namespace ST_LIB { if (was_enabled_filter) enable_filter(); } - void modify_mode(Regular_Mode mode) { + void modify_regular_mode(Regular_Mode mode) { + bool was_enabled_filter = is_enabled_filter(); + if(was_enabled_filter) disable_filter(); + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCONT_Msk; filter_regs->FLTCR1 |= (uint32_t(mode) << DFSDM_FLTCR1_RCONT_Pos); + + if(was_enabled_filter) enable_filter(); } + void read_this_channel_in_regular_mode(){ + bool was_enabled_filter = is_enabled_filter(); + if(was_enabled_filter) disable_filter(); + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; + filter_regs->FLTCR1 |= (uint32_t(this->channel) << DFSDM_FLTCR1_RCONT_Pos); + + if(was_enabled_filter) enable_filter(); + start(); + } bool modify_oversampling(uint16_t oversampling) { if (oversampling == 0) return false; @@ -688,7 +703,7 @@ namespace ST_LIB { } void modify_watchdog_hth(uint32_t value) { - filter_regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; + filter_regs->FLTAWHTR &= ~DFSDM_FLTAWHTR_AWHT_Msk; bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); if (fast) @@ -857,13 +872,13 @@ namespace ST_LIB { uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; if(channel_instances[ch] != nullptr && channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); //clear - filter->FLTICR = DFSDM_FLTICR_CLRSCDF; + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; } if(isr & (channels_enabled << DFSDM_FLTISR_CKABF_Pos)){ uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos; if(channel_instances[ch] != nullptr && channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); //clear - filter->FLTICR = DFSDM_FLTICR_CLRCKABF; + filter->FLTICR |= DFSDM_FLTICR_CLRCKABF; } //Analog watchdog if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) From 691b39695abc5e30d82b9467d472c5a534c1fc92 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 6 Mar 2026 19:27:29 +0100 Subject: [PATCH 14/41] add again max_instances --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 50e6ce5cc..f9a53aeea 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -749,6 +749,7 @@ namespace ST_LIB { return channel; } }; + static constexpr std::size_t max_instances{8}; static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; static inline uint8_t channels_enabled{}; static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { From 0cb2cbed34dcdb1c2f665aafffb450f5dacabe09 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 6 Mar 2026 19:44:33 +0100 Subject: [PATCH 15/41] Add dma safety, not implemented yet --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 69 +++++++++++++++++++----------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index f9a53aeea..d66472e53 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -47,9 +47,9 @@ namespace ST_LIB { Enable = 1 }; - enum class Data_Write : uint8_t { - CPU = 0, - DMA = 1 + enum class Rdma: uint8_t { + Enable = 0, + Disable = 1 }; enum class Sync_Conversion : uint8_t { @@ -121,7 +121,7 @@ namespace ST_LIB { uint16_t oversampling{1}; uint16_t integrator{1}; - Data_Write rdma{Data_Write::CPU}; + Rdma rdma{Rdma::Disable}; Fast_Conversion fast{Fast_Conversion::Disable}; Sync_Conversion rsync{Sync_Conversion::Independent}; Regular_Mode rcont{Regular_Mode::Single}; @@ -208,7 +208,7 @@ namespace ST_LIB { return GPIODomain::AlternateFunction::AF3; //In any other case } struct Entry{ - const Config_Channel* config; + Config_Channel config; uint8_t channel; size_t gpio_idx; int32_t* buffer; @@ -226,14 +226,20 @@ namespace ST_LIB { uint8_t channel; int32_t* buffer; size_t buffer_size; - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel& config, int32_t (&buffer)[N]) + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config, int32_t (&buffer)[N]) : pin(pin), gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, config(config), buffer_size(N) { + static_assert(N != 0, "N must be bigger than 0"); this->buffer = buffer; channel = get_channel(pin); + //remove in a future + if(config.rdma == Rdma::Enable){ + compile_error("Not implemented DMA yet"); + } + if(config.offset > OFFSET_MAX || config.offset < OFFSET_MIN){ compile_error("Your offset is bigger than the maximum size"); } @@ -257,7 +263,7 @@ namespace ST_LIB { consteval std::size_t inscribe(Ctx &ctx) const { const auto gpio_idx = gpio.inscribe(ctx); Entry e{ - .config = &config, + .config = config, .channel = channel, .gpio_idx = gpio_idx, .buffer = buffer, @@ -268,17 +274,17 @@ namespace ST_LIB { }; // I hate stm32,has volatile in the DFSDM structs. struct FilterConfig{ - uint32_t FLTCR1; - uint32_t FLTCR2; - uint32_t FLTFCR; - uint32_t FLTAWHTR; - uint32_t FLTAWLTR; - uint32_t FLTJCHGR; + uint32_t FLTCR1{}; + uint32_t FLTCR2{}; + uint32_t FLTFCR{}; + uint32_t FLTAWHTR{}; + uint32_t FLTAWLTR{}; + uint32_t FLTJCHGR{}; }; struct ChannelConfig{ - uint32_t CHCFGR1; - uint32_t CHCFGR2; - uint32_t CHAWSCDR; + uint32_t CHCFGR1{}; + uint32_t CHCFGR2{}; + uint32_t CHAWSCDR{}; }; struct Config { size_t gpio_idx; @@ -287,6 +293,7 @@ namespace ST_LIB { uint32_t latency_cycles; Type_Conversion type_conv; + Rdma rdma; uint8_t filter; uint8_t channel; @@ -351,7 +358,7 @@ namespace ST_LIB { } static consteval uint32_t make_fltcr2(const Entry& e){ uint32_t v = 0; - if(e.config.rdma == Data_Write::CPU || e.config.conversion_complete_callback != nullptr){ + if(e.config.rdma == Rdma::Disable || e.config.conversion_complete_callback != nullptr){ v |= DFSDM_FLTCR2_REOCIE; v |= DFSDM_FLTCR2_JEOCIE; } @@ -464,6 +471,8 @@ namespace ST_LIB { cfg.buffer_size = e.buffer_size; cfg.buffer = e.buffer; cfg.type_conv = e.config.type_conv; + cfg.rdma = e.config.rdma; + //add the callbacks cfg.overrun_callback = e.config.overrun_callback; cfg.clock_absence_callback = e.config.clock_absence_callback; @@ -524,6 +533,7 @@ namespace ST_LIB { uint8_t channel; uint8_t filter; Type_Conversion type_conv; + Rdma rdma; int32_t* buffer{}; size_t length_buffer{}; @@ -792,6 +802,7 @@ namespace ST_LIB { inst.type_conv = cfg.type_conv; inst.filter = cfg.filter; inst.channel = cfg.channel; + inst.rdma = cfg.rdma; inst.buffer = cfg.buffer; inst.length_buffer = cfg.buffer_size; @@ -854,19 +865,27 @@ namespace ST_LIB { if(isr & DFSDM_FLTISR_REOCF_Msk){ Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; - inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; - if(inst->end_conversion_cb != nullptr){ - inst->end_conversion_cb(); + if(inst != nullptr && inst->buffer != nullptr){ + if(inst->rdma == Rdma::Disable){ + inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + } + if(inst->end_conversion_cb != nullptr){ + inst->end_conversion_cb(); + } } } if(isr & DFSDM_FLTISR_JEOCF_Msk){ //GUARDARLO EN LA DIRECCIÓN DE MEMORIA QUE ME PORPORCIONE EL USUARIO Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; - inst->buffer[inst->idx] = (filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; - if(inst->end_conversion_cb != nullptr){ - inst->end_conversion_cb(); + if(inst != nullptr && inst->buffer != nullptr){ + if(inst->rdma == Rdma::Disable){ + inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + } + if(inst->end_conversion_cb != nullptr){ + inst->end_conversion_cb(); + } } } if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ From 412e133aa6a71d64ee5e1fb68f26f32577da09a5 Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 6 Mar 2026 19:46:06 +0100 Subject: [PATCH 16/41] Masinstances updated to max_instances --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index d66472e53..05f7d63d7 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -216,7 +216,7 @@ namespace ST_LIB { }; - static constexpr size_t mastances{8}; + static constexpr size_t max_instances{8}; template struct DFSDM_CHANNEL{ using domain = DFSDM_CHANNEL_DOMAIN; @@ -759,7 +759,6 @@ namespace ST_LIB { return channel; } }; - static constexpr std::size_t max_instances{8}; static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; static inline uint8_t channels_enabled{}; static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { From 22ab26d57c36b3f795b9c2c7905b1dea5f257cbd Mon Sep 17 00:00:00 2001 From: oganigl Date: Fri, 6 Mar 2026 19:51:09 +0100 Subject: [PATCH 17/41] now the callbacks works --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 05f7d63d7..7e6a60ce0 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -478,6 +478,7 @@ namespace ST_LIB { cfg.clock_absence_callback = e.config.clock_absence_callback; cfg.short_circuit_callback = e.config.short_circuit_callback; cfg.watchdog_callback = e.config.watchdog_callback; + cfg.conversion_complete_callback = e.config.conversion_complete_callback; if(filter_per_channel){ cfg.filter = i; }else{ From 0e0235bdc861817e10e9c22f984466d984edc309 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sat, 7 Mar 2026 13:49:01 +0100 Subject: [PATCH 18/41] some more more changes --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 79 +++++++++++++++++++----------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 7e6a60ce0..582dde038 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -47,9 +47,9 @@ namespace ST_LIB { Enable = 1 }; - enum class Rdma: uint8_t { - Enable = 0, - Disable = 1 + enum class Dma: uint8_t { + Disable = 0, + Enable = 1 }; enum class Sync_Conversion : uint8_t { @@ -121,7 +121,7 @@ namespace ST_LIB { uint16_t oversampling{1}; uint16_t integrator{1}; - Rdma rdma{Rdma::Disable}; + Dma dma{Dma::Disable}; Fast_Conversion fast{Fast_Conversion::Disable}; Sync_Conversion rsync{Sync_Conversion::Independent}; Regular_Mode rcont{Regular_Mode::Single}; @@ -236,7 +236,7 @@ namespace ST_LIB { this->buffer = buffer; channel = get_channel(pin); //remove in a future - if(config.rdma == Rdma::Enable){ + if(config.dma == Dma::Enable){ compile_error("Not implemented DMA yet"); } @@ -293,7 +293,7 @@ namespace ST_LIB { uint32_t latency_cycles; Type_Conversion type_conv; - Rdma rdma; + Dma dma; uint8_t filter; uint8_t channel; @@ -358,7 +358,7 @@ namespace ST_LIB { } static consteval uint32_t make_fltcr2(const Entry& e){ uint32_t v = 0; - if(e.config.rdma == Rdma::Disable || e.config.conversion_complete_callback != nullptr){ + if(e.config.dma == Dma::Disable || e.config.conversion_complete_callback != nullptr){ v |= DFSDM_FLTCR2_REOCIE; v |= DFSDM_FLTCR2_JEOCIE; } @@ -380,13 +380,13 @@ namespace ST_LIB { uint32_t v = 0; if(e.config.type_conv == Type_Conversion::Regular){ - v |= (uint32_t(e.config.rdma) << DFSDM_FLTCR1_RDMAEN_Pos); + v |= (uint32_t(e.config.dma) << DFSDM_FLTCR1_RDMAEN_Pos); v |= (uint32_t(e.config.fast) << DFSDM_FLTCR1_FAST_Pos); v |= (uint32_t(e.config.rsync) << DFSDM_FLTCR1_RSYNC_Pos); v |= (uint32_t(e.config.rcont) << DFSDM_FLTCR1_RCONT_Pos); }else if(e.config.type_conv == Type_Conversion::Injected){ v |= DFSDM_FLTCR1_JSCAN; // activate conversion of the entire group - v |= (uint32_t)(e.config.rdma) << DFSDM_FLTCR1_JDMAEN_Pos; + v |= (uint32_t)(e.config.dma) << DFSDM_FLTCR1_JDMAEN_Pos; if(e.config.trigger_conv != Trigger_Timer_Source::Unused){ v |= DFSDM_FLTCR1_JEXTEN_0; //with the risings if(filter == 0){ @@ -471,7 +471,7 @@ namespace ST_LIB { cfg.buffer_size = e.buffer_size; cfg.buffer = e.buffer; cfg.type_conv = e.config.type_conv; - cfg.rdma = e.config.rdma; + cfg.dma = e.config.dma; //add the callbacks cfg.overrun_callback = e.config.overrun_callback; @@ -534,7 +534,7 @@ namespace ST_LIB { uint8_t channel; uint8_t filter; Type_Conversion type_conv; - Rdma rdma; + Dma dma; int32_t* buffer{}; size_t length_buffer{}; @@ -802,7 +802,7 @@ namespace ST_LIB { inst.type_conv = cfg.type_conv; inst.filter = cfg.filter; inst.channel = cfg.channel; - inst.rdma = cfg.rdma; + inst.dma = cfg.dma; inst.buffer = cfg.buffer; inst.length_buffer = cfg.buffer_size; @@ -823,7 +823,7 @@ namespace ST_LIB { inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; - inst.filter_regs->FLTJCHGR |= cfg.init_data_filter.FLTJCHGR; + inst.filter_regs->FLTJCHGR = cfg.init_data_filter.FLTJCHGR; filters_configured[cfg.filter] = true; } @@ -832,11 +832,11 @@ namespace ST_LIB { inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; - - //enable the filter - inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; //enable the channel inst.channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + //enable the filter + inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; + //activate the NVIC if(filters_configured[cfg.filter] == true){ @@ -864,10 +864,12 @@ namespace ST_LIB { uint32_t isr = filter->FLTISR; if(isr & DFSDM_FLTISR_REOCF_Msk){ - Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; + //Save it in the address provide by the user + int32_t data = filter->FLTRDATAR; + Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ - if(inst->rdma == Rdma::Disable){ - inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; + if(inst->dma == Dma::Disable){ + inst->buffer[inst->idx] = (data & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; inst->idx = (inst->idx + 1) % inst->length_buffer; } if(inst->end_conversion_cb != nullptr){ @@ -876,11 +878,12 @@ namespace ST_LIB { } } if(isr & DFSDM_FLTISR_JEOCF_Msk){ - //GUARDARLO EN LA DIRECCIÓN DE MEMORIA QUE ME PORPORCIONE EL USUARIO - Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; + //Save it in the address provide by the user + int32_t data = filter->FLTJDATAR; + Instance* inst = channel_instances[(data & DFSDM_FLTJDATAR_JDATACH_Msk) >> DFSDM_FLTJDATAR_JDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ - if(inst->rdma == Rdma::Disable){ - inst->buffer[inst->idx] = (filter->FLTRDATAR & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; + if(inst->dma == Dma::Disable){ + inst->buffer[inst->idx] = (data & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; inst->idx = (inst->idx + 1) % inst->length_buffer; } if(inst->end_conversion_cb != nullptr){ @@ -888,6 +891,18 @@ namespace ST_LIB { } } } + if(isr & DFSDM_FLTISR_ROVRF_Msk){ + Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; + if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); + //clear + filter->FLTICR |= DFSDM_FLTISR_ROVRF; + } + if(isr & DFSDM_FLTISR_JOVRF_Msk){ + Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; + if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); + //clear + filter->FLTICR |= DFSDM_FLTISR_JOVRF; + } if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; if(channel_instances[ch] != nullptr && channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); @@ -903,16 +918,22 @@ namespace ST_LIB { //Analog watchdog if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) { - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; - //mirar como saber el canal que ha lanzado el watchdog - // if(filter->watchdog_cb != nullptr) watchdog_cb(); + if(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk){ + uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk); + if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); + //clear + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + } + if(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk){ + uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk); + if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); + //clear + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + } } } }; - - struct DFSDM_CLK_DOMAIN{ static constexpr GPIODomain::Pin valid_clk_pins[] = { {GPIODomain::Port::C,GPIO_PIN_2}, From fbfc0d611212f1e0cef3cca87e78feadcdbf00a5 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sun, 8 Mar 2026 19:42:58 +0100 Subject: [PATCH 19/41] the trigger works gg --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 56 +++++++++++++++++++- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 12 +++-- Inc/HALAL/Services/Time/TimerWrapper.hpp | 2 +- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 98af03b94..118031b54 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -125,6 +125,32 @@ TimerXList Basic_6 = 6, Basic_7 = 7, }; + enum class SelectionTrigger1: uint32_t { + Reset = TIM_TRGO_RESET, + Enable = TIM_TRGO_ENABLE, + Update = TIM_TRGO_UPDATE, + General_Compare = TIM_TRGO_OC1, + Compare_channel1 = TIM_TRGO_OC1REF, + Compare_channel2 = TIM_TRGO_OC2REF, + Compare_channel3 = TIM_TRGO_OC3REF, + Compare_channel4 = TIM_TRGO_OC4REF + }; + enum class SelectionTrigger2: uint32_t { + Reset = TIM_TRGO2_RESET, + Enable = TIM_TRGO2_ENABLE, + Update = TIM_TRGO2_UPDATE, + General_Compare = TIM_TRGO2_OC1, + Compare_channel1 = TIM_TRGO2_OC1REF, + Compare_channel2 = TIM_TRGO2_OC2REF, + Compare_channel3 = TIM_TRGO2_OC3REF, + Compare_channel4 = TIM_TRGO2_OC4REF, + Compare_channel5 = TIM_TRGO2_OC5REF, + Compare_channel6 = TIM_TRGO2_OC6REF, + Compare_channel4_R_channel6_F = TIM_TRGO2_OC4REF_RISING_OC6REF_FALLING, + Compare_channel4_R_channel6_R = TIM_TRGO2_OC4REF_RISING_OC6REF_RISING, + Compare_channel5_R_channel6_F = TIM_TRGO2_OC5REF_RISING_OC6REF_FALLING, + Compare_channel5_R_channel6_R = TIM_TRGO2_OC5REF_RISING_OC6REF_RISING + }; // Alternate functions for timers enum class TimerAF { @@ -220,10 +246,14 @@ TimerXList TimerRequest request; uint8_t pin_count; std::array pins; /* this won't be read in Timer constructor */ + SelectionTrigger1 trgo1{SelectionTrigger1::Reset}; + SelectionTrigger2 trgo2{SelectionTrigger2::Reset}; }; struct Config { uint8_t timer_idx; + SelectionTrigger1 trgo1; + SelectionTrigger2 trgo2; }; static constexpr TIM_HandleTypeDef* hal_handles[16] = {// general purpose timers @@ -464,8 +494,12 @@ TimerXList GetPinFromIdx(pinargs, 3), GetPinFromIdx(pinargs, 4), GetPinFromIdx(pinargs, 5), - GetPinFromIdx(pinargs, 6)} - )), + GetPinFromIdx(pinargs, 6), + } + ), + ent.trgo1, + ent.trgo2 + ), gpio0(GetGPIOFromIdx(pinargs, ent.request, 0)), gpio1(GetGPIOFromIdx(pinargs, ent.request, 1)), gpio2(GetGPIOFromIdx(pinargs, ent.request, 2)), @@ -510,6 +544,8 @@ TimerXList .request = e.request, .pin_count = e.pin_count, .pins = e.pins, + .trgo1 = e.trgo1, + .trgo2 = e.trgo2 }; ctx.template add(local_entry, this); } @@ -560,6 +596,8 @@ TimerXList Config cfg = { .timer_idx = timer_idxmap[reqint], + .trgo1 = requests[i].trgo1, + .trgo2 = requests[i].trgo2 }; cfgs[cfg_idx++] = cfg; @@ -592,6 +630,8 @@ TimerXList uint8_t reqint = remaining_32bit_timers[count_32bit_requests]; Config cfg = { .timer_idx = timer_idxmap[reqint], + .trgo1 = requests[i].trgo1, + .trgo2 = requests[i].trgo2 }; cfgs[cfg_idx++] = cfg; @@ -641,6 +681,8 @@ TimerXList uint8_t reqint = remaining_timers[i]; Config cfg = { .timer_idx = timer_idxmap[reqint], + .trgo1 = requests[i].trgo1, + .trgo2 = requests[i].trgo2 }; cfgs[cfg_idx++] = cfg; } @@ -652,6 +694,7 @@ TimerXList struct Instance { TIM_TypeDef* tim; TIM_HandleTypeDef* hal_tim; + TIM_MasterConfigTypeDef master{}; uint8_t timer_idx; }; @@ -667,6 +710,7 @@ TimerXList TIM_HandleTypeDef* handle = hal_handles[e.timer_idx]; TIM_TypeDef* tim = cmsis_timers[e.timer_idx]; + handle->Instance = tim; handle->Init.Period = 0; handle->Init.Prescaler = 0; @@ -696,6 +740,14 @@ TimerXList inst->tim = tim; inst->hal_tim = handle; inst->timer_idx = e.timer_idx; + TIM_MasterConfigTypeDef sMasterConfig = {}; + sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); + sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + inst->master = sMasterConfig; + if(HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != HAL_OK) { + ErrorHandler("Unable to configure master synch"); + } } } }; diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 582dde038..4684f0ce7 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -109,6 +109,10 @@ namespace ST_LIB { Regular, Injected }; + enum class Injected_Mode: uint8_t{ + Single, + Scan + }; struct Config_Channel { int32_t offset{0}; @@ -125,7 +129,7 @@ namespace ST_LIB { Fast_Conversion fast{Fast_Conversion::Disable}; Sync_Conversion rsync{Sync_Conversion::Independent}; Regular_Mode rcont{Regular_Mode::Single}; - + Injected_Mode jscan{Injected_Mode::Scan}; /* -------- Runtime protections -------- */ Overrun overrun{Overrun::Disable}; @@ -385,7 +389,7 @@ namespace ST_LIB { v |= (uint32_t(e.config.rsync) << DFSDM_FLTCR1_RSYNC_Pos); v |= (uint32_t(e.config.rcont) << DFSDM_FLTCR1_RCONT_Pos); }else if(e.config.type_conv == Type_Conversion::Injected){ - v |= DFSDM_FLTCR1_JSCAN; // activate conversion of the entire group + v |= uint32_t(e.config.jscan) << DFSDM_FLTCR1_JSCAN_Pos; // activate conversion of the entire group v |= (uint32_t)(e.config.dma) << DFSDM_FLTCR1_JDMAEN_Pos; if(e.config.trigger_conv != Trigger_Timer_Source::Unused){ v |= DFSDM_FLTCR1_JEXTEN_0; //with the risings @@ -869,7 +873,7 @@ namespace ST_LIB { Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ if(inst->dma == Dma::Disable){ - inst->buffer[inst->idx] = (data & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; + inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; inst->idx = (inst->idx + 1) % inst->length_buffer; } if(inst->end_conversion_cb != nullptr){ @@ -883,7 +887,7 @@ namespace ST_LIB { Instance* inst = channel_instances[(data & DFSDM_FLTJDATAR_JDATACH_Msk) >> DFSDM_FLTJDATAR_JDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ if(inst->dma == Dma::Disable){ - inst->buffer[inst->idx] = (data & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; + inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; inst->idx = (inst->idx + 1) % inst->length_buffer; } if(inst->end_conversion_cb != nullptr){ diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index 0a81056a9..8425c520e 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -247,7 +247,7 @@ template struct TimerWrapper { "to get_pwm() [this method]"); } } - + template inline DualPWM get_dual_pwm( uint32_t polarity = TIM_OCPOLARITY_HIGH, From 574abd82f3a263cabf046fc6ec55c8a3a3d816df Mon Sep 17 00:00:00 2001 From: oganigl Date: Tue, 10 Mar 2026 00:03:45 +0100 Subject: [PATCH 20/41] okay this might work --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 73 +++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 4684f0ce7..f5cd87265 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -263,6 +263,39 @@ namespace ST_LIB { compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); } } + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config, int32_t*buffer) + : pin(pin), + gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, + config(config), + buffer(buffer), + buffer_size(N) + { + static_assert(N == 1, "N must be bigger than 0"); + channel = get_channel(pin); + //remove in a future + if(config.dma == Dma::Enable){ + compile_error("Not implemented DMA yet"); + } + + if(config.offset > OFFSET_MAX || config.offset < OFFSET_MIN){ + compile_error("Your offset is bigger than the maximum size"); + } + if(config.right_shift > 0x000000FF){ + compile_error("Your right_shift is bigger than the maximum size"); + } + if(config.integrator <= 0){ + compile_error("DFSDM_FILTER: Integrator out of range"); + } + if (!is_correct_oversampling(config.filter_type, config.oversampling)){ + compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); + } + if(config.watchdog_oversampling > 32){ + compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); + } + if(static_cast(config.filter_wathdog) > 3){ + compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); + } + } template consteval std::size_t inscribe(Ctx &ctx) const { const auto gpio_idx = gpio.inscribe(ctx); @@ -786,6 +819,7 @@ namespace ST_LIB { static inline std::array instances{}; static void init(std::span cfgs,std::span gpio_instances) { + if(N == 0) return; std::array filters_configured = {false,false,false,false}; RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock for (size_t i = 0; i < N; ++i) { @@ -836,27 +870,32 @@ namespace ST_LIB { inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; - //enable the channel - inst.channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; - //enable the filter - inst.filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; - - //activate the NVIC - if(filters_configured[cfg.filter] == true){ - switch(inst.filter){ - case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; - case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; - case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; - case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; - } - } //update channel_instances channel_instances[inst.channel] = &inst; channels_enabled |= 1 << inst.channel; } - //Activate the DFSDM GLOBAL Interface - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + if(N > 0){ + //Activate the DFSDM GLOBAL Interface + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + for(int i = 0; i < 8; i++){ + channel_hw[i]->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + } + for(int i = 0; i < 4;i++){ + filter_hw[i]->FLTCR1 |= DFSDM_FLTCR1_DFEN; + } + //activate the NVIC + for(int i = 0; i < 4; i++){ + if(filters_configured[i] == true){ + switch(i){ + case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; + case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; + case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; + case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + } + } + } + } } }; @@ -1051,7 +1090,7 @@ struct DFSDM_CLK_DOMAIN{ struct Init { static inline std::array instances{}; static void init(std::span cfgs,std::span gpio_instances) { - //add ckaie scdie + if(N == 0) return; const auto &c = cfgs[0]; auto &inst = instances[0]; inst.gpio_instance = &gpio_instances[c.gpio_idx]; From 63cb9e897dff8ef5cad79eba5f1f84b736820955 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sat, 14 Mar 2026 02:47:40 +0100 Subject: [PATCH 21/41] okay dma works, something to look is how to allow to use the 16 entries of the DMA without removing double entries with the dfsdm. --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 587 ++++++++++++++++------------- 1 file changed, 317 insertions(+), 270 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index f5cd87265..73966ab4a 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -2,7 +2,7 @@ #include "HALAL/Models/GPIO.hpp" #include "HALAL/Models/Pin.hpp" - +#include "HALAL/Models/DMA/DMA2.hpp" #define Oversampling_MAX 1024 #define Oversampling_MAX_Filter_4 215 @@ -11,7 +11,7 @@ #define OFFSET_MAX (1 << 23) - 1 #define OFFSET_MIN -(1 << 23) using ST_LIB::GPIODomain; - +using ST_LIB::DMA_Domain; namespace ST_LIB { using Callback = void(*)(void); extern void compile_error(const char *msg); @@ -114,41 +114,47 @@ namespace ST_LIB { Scan }; struct Config_Channel { - int32_t offset{0}; uint32_t right_shift{0}; SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; SPI_Type spi_type{SPI_Type::SPI_RISING}; - Type_Conversion type_conv{Type_Conversion::Regular}; + + /* -------- Runtime protections -------- */ + Clock_Absence clock_absence{Clock_Absence::Disable}; + Short_Circuit short_circuit{Short_Circuit::Disable}; + Extreme_Detector extreme_detector{Extreme_Detector::Disable}; + + uint8_t short_circuit_count{0xFF}; + + /* -------- Analog watchdog -------- */ + Analog_Watchdog watchdog{Analog_Watchdog::Disable}; + + + //If Mode Watchdog == After filter + Filter_Type filter_watchdog{Filter_Type::Sinc2}; + uint8_t watchdog_oversampling{5}; + + +}; +struct Config_Filter{ + uint8_t filter{0}; Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; Filter_Type filter_type{Filter_Type::FastSinc}; uint16_t oversampling{1}; uint16_t integrator{1}; - + Type_Conversion type_conv{Type_Conversion::Regular}; Dma dma{Dma::Disable}; Fast_Conversion fast{Fast_Conversion::Disable}; Sync_Conversion rsync{Sync_Conversion::Independent}; Regular_Mode rcont{Regular_Mode::Single}; Injected_Mode jscan{Injected_Mode::Scan}; - /* -------- Runtime protections -------- */ Overrun overrun{Overrun::Disable}; - Clock_Absence clock_absence{Clock_Absence::Disable}; - Short_Circuit short_circuit{Short_Circuit::Disable}; - Extreme_Detector extreme_detector{Extreme_Detector::Disable}; - - uint8_t short_circuit_count{0xFF}; - /* -------- Analog watchdog -------- */ - - Analog_Watchdog watchdog{Analog_Watchdog::Disable}; + Analog_Watchdog watchdog{Analog_Watchdog::Enable}; Analog_Watchdog_Mode watchdog_mode{Analog_Watchdog_Mode::After_Filter}; - //If Mode Watchdog == After filter - Filter_Type filter_wathdog{Filter_Type::Sinc2}; - uint8_t watchdog_oversampling{5}; int32_t watchdog_low_threshold{0x10000000}; int32_t watchdog_high_threshold{0x7FFFFFFF}; - //callbacks Callback watchdog_callback{nullptr}; Callback conversion_complete_callback{nullptr}; @@ -156,7 +162,6 @@ namespace ST_LIB { Callback clock_absence_callback{nullptr}; Callback short_circuit_callback{nullptr}; }; - static constexpr std::array,Possible_Pin_Channel> pin_to_channel = {{ {PE4,3},{PC0,4},{PC1,0},{PC3,1},{PC5,2}, @@ -211,98 +216,122 @@ namespace ST_LIB { } return GPIODomain::AlternateFunction::AF3; //In any other case } + static consteval DMA_Domain::Peripheral get_dma_peripheral(uint8_t filter){ + switch (filter){ + case 0: return DMA_Domain::Peripheral::dfsdm_filter0; + case 1: return DMA_Domain::Peripheral::dfsdm_filter1; + case 2: return DMA_Domain::Peripheral::dfsdm_filter2; + case 3: return DMA_Domain::Peripheral::dfsdm_filter3; + } + compile_error("Cannot be different than 0,1,2,3"); + } struct Entry{ - Config_Channel config; + Config_Channel config_channel; + Config_Filter config_filter; uint8_t channel; size_t gpio_idx; + size_t dma_idx; int32_t* buffer; size_t buffer_size; - }; - + static constexpr size_t max_instances{8}; - template + template struct DFSDM_CHANNEL{ using domain = DFSDM_CHANNEL_DOMAIN; const GPIODomain::Pin& pin; + DMA_Domain::DMA dma; GPIODomain::GPIO gpio; - const Config_Channel config; + const Config_Channel config_channel; + const Config_Filter config_filter; uint8_t channel; int32_t* buffer; size_t buffer_size; - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config, int32_t (&buffer)[N]) + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter, int32_t (&buffer)[N]) : pin(pin), + dma(get_dma_peripheral(config_filter.filter)), gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, - config(config), + config_channel(config_channel), + config_filter(config_filter), buffer_size(N) { static_assert(N != 0, "N must be bigger than 0"); this->buffer = buffer; channel = get_channel(pin); //remove in a future - if(config.dma == Dma::Enable){ + + if(config_filter.dma == Dma::Enable){ compile_error("Not implemented DMA yet"); } - - if(config.offset > OFFSET_MAX || config.offset < OFFSET_MIN){ + if(config_filter.filter > 3){ + compile_error("Solo hay 4 filtros [0..3]"); + } + if(config_channel.offset > OFFSET_MAX || config_channel.offset < OFFSET_MIN){ compile_error("Your offset is bigger than the maximum size"); } - if(config.right_shift > 0x000000FF){ + if(config_channel.right_shift > 0x000000FF){ compile_error("Your right_shift is bigger than the maximum size"); } - if(config.integrator <= 0){ + if(config_filter.integrator <= 0){ compile_error("DFSDM_FILTER: Integrator out of range"); } - if (!is_correct_oversampling(config.filter_type, config.oversampling)){ + if (!is_correct_oversampling(config_filter.filter_type, config_filter.oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); } - if(config.watchdog_oversampling > 32){ + if(config_channel.watchdog_oversampling > 32){ compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); } - if(static_cast(config.filter_wathdog) > 3){ + if(static_cast(config_channel.filter_watchdog) > 3){ compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); } } - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config, int32_t*buffer) + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter, int32_t*buffer) : pin(pin), + dma{get_dma_peripheral(config_filter.filter)}, gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, - config(config), + config_channel(config_channel), + config_filter(config_filter), buffer(buffer), buffer_size(N) { static_assert(N == 1, "N must be bigger than 0"); channel = get_channel(pin); //remove in a future - if(config.dma == Dma::Enable){ + if(config_filter.dma == Dma::Enable){ compile_error("Not implemented DMA yet"); } - - if(config.offset > OFFSET_MAX || config.offset < OFFSET_MIN){ + if(config_filter.filter > 3){ + compile_error("Filter can not be bigger than 3"); + } + if(config_channel.offset > OFFSET_MAX || config_channel.offset < OFFSET_MIN){ compile_error("Your offset is bigger than the maximum size"); } - if(config.right_shift > 0x000000FF){ + if(config_channel.right_shift > 0x000000FF){ compile_error("Your right_shift is bigger than the maximum size"); } - if(config.integrator <= 0){ + if(config_filter.integrator <= 0){ compile_error("DFSDM_FILTER: Integrator out of range"); } - if (!is_correct_oversampling(config.filter_type, config.oversampling)){ + if (!is_correct_oversampling(config_filter.filter_type, config_filter.oversampling)){ compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); } - if(config.watchdog_oversampling > 32){ + if(config_channel.watchdog_oversampling > 32){ compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); } - if(static_cast(config.filter_wathdog) > 3){ + if(static_cast(config_channel.filter_watchdog) > 3){ compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); } } template consteval std::size_t inscribe(Ctx &ctx) const { const auto gpio_idx = gpio.inscribe(ctx); + const auto dma_idx = dma.inscribe(ctx); Entry e{ - .config = config, + .config_channel = config_channel, + .config_filter = config_filter, .channel = channel, .gpio_idx = gpio_idx, + .dma_idx = dma_idx, .buffer = buffer, .buffer_size = buffer_size }; @@ -325,12 +354,13 @@ namespace ST_LIB { }; struct Config { size_t gpio_idx; + size_t dma_idx; FilterConfig init_data_filter; ChannelConfig init_data_channel; uint32_t latency_cycles; Type_Conversion type_conv; - Dma dma; + Dma dma_enable; uint8_t filter; uint8_t channel; @@ -346,20 +376,20 @@ namespace ST_LIB { Callback short_circuit_callback{nullptr}; }; static consteval uint32_t compute_latency(const Entry& e){ - const uint32_t fosr = e.config.oversampling; - const uint32_t iosr = e.config.integrator; + const uint32_t fosr = e.config_filter.oversampling; + const uint32_t iosr = e.config_filter.integrator; - if (e.config.fast == Fast_Conversion::Enable && - e.config.rcont == Regular_Mode::Continuous) + if (e.config_filter.fast == Fast_Conversion::Enable && + e.config_filter.rcont == Regular_Mode::Continuous) { return fosr * iosr; } - if (e.config.filter_type == Filter_Type::FastSinc) { + if (e.config_filter.filter_type == Filter_Type::FastSinc) { return fosr * (iosr - 1 + 4) + 2; } - const uint32_t ford = static_cast(e.config.filter_type); + const uint32_t ford = static_cast(e.config_filter.filter_type); return fosr * (iosr - 1 + ford) + ford; } static consteval uint32_t get_trigger(Trigger_Timer_Source trig){ @@ -385,9 +415,9 @@ namespace ST_LIB { static consteval uint32_t make_fltfcr(const Entry& e) { return - (uint32_t(e.config.filter_type) << DFSDM_FLTFCR_FORD_Pos) | - (uint32_t(e.config.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | - (uint32_t(e.config.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); + (uint32_t(e.config_filter.filter_type) << DFSDM_FLTFCR_FORD_Pos) | + (uint32_t(e.config_filter.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | + (uint32_t(e.config_filter.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); } static consteval uint32_t make_fltcr2_global(){ //Activate the interrupt, to activate the continous detection, then the channel can @@ -395,18 +425,18 @@ namespace ST_LIB { } static consteval uint32_t make_fltcr2(const Entry& e){ uint32_t v = 0; - if(e.config.dma == Dma::Disable || e.config.conversion_complete_callback != nullptr){ + if(e.config_filter.dma == Dma::Disable || e.config_filter.conversion_complete_callback != nullptr){ v |= DFSDM_FLTCR2_REOCIE; v |= DFSDM_FLTCR2_JEOCIE; } - if(e.config.watchdog == Analog_Watchdog::Enable){ + if(e.config_filter.watchdog == Analog_Watchdog::Enable){ v |= DFSDM_FLTCR2_AWDIE; v |= 1 << (DFSDM_FLTCR2_AWDCH_Pos + e.channel); } - if (e.config.extreme_detector == Extreme_Detector::Enable){ + if (e.config_channel.extreme_detector == Extreme_Detector::Enable){ v |= 1 << (DFSDM_FLTCR2_EXCH_Pos + e.channel); } - if(e.config.overrun == Overrun::Enable){ + if(e.config_filter.overrun == Overrun::Enable){ v |= DFSDM_FLTCR2_JOVRIE; v |= DFSDM_FLTCR2_ROVRIE; } @@ -416,53 +446,53 @@ namespace ST_LIB { { uint32_t v = 0; - if(e.config.type_conv == Type_Conversion::Regular){ - v |= (uint32_t(e.config.dma) << DFSDM_FLTCR1_RDMAEN_Pos); - v |= (uint32_t(e.config.fast) << DFSDM_FLTCR1_FAST_Pos); - v |= (uint32_t(e.config.rsync) << DFSDM_FLTCR1_RSYNC_Pos); - v |= (uint32_t(e.config.rcont) << DFSDM_FLTCR1_RCONT_Pos); - }else if(e.config.type_conv == Type_Conversion::Injected){ - v |= uint32_t(e.config.jscan) << DFSDM_FLTCR1_JSCAN_Pos; // activate conversion of the entire group - v |= (uint32_t)(e.config.dma) << DFSDM_FLTCR1_JDMAEN_Pos; - if(e.config.trigger_conv != Trigger_Timer_Source::Unused){ + if(e.config_filter.type_conv == Type_Conversion::Regular){ + v |= (uint32_t(e.config_filter.dma) << DFSDM_FLTCR1_RDMAEN_Pos); + v |= (uint32_t(e.config_filter.fast) << DFSDM_FLTCR1_FAST_Pos); + v |= (uint32_t(e.config_filter.rsync) << DFSDM_FLTCR1_RSYNC_Pos); + v |= (uint32_t(e.config_filter.rcont) << DFSDM_FLTCR1_RCONT_Pos); + }else if(e.config_filter.type_conv == Type_Conversion::Injected){ + v |= uint32_t(e.config_filter.jscan) << DFSDM_FLTCR1_JSCAN_Pos; // activate conversion of the entire group + v |= (uint32_t)(e.config_filter.dma) << DFSDM_FLTCR1_JDMAEN_Pos; + if(e.config_filter.trigger_conv != Trigger_Timer_Source::Unused){ v |= DFSDM_FLTCR1_JEXTEN_0; //with the risings if(filter == 0){ - v |= get_trigger(e.config.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; + v |= get_trigger(e.config_filter.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; } - if(e.config.rsync == Sync_Conversion::Sync_With_Flt0){ + if(e.config_filter.rsync == Sync_Conversion::Sync_With_Flt0){ v |= DFSDM_FLTCR1_JSYNC; }else{ - v |= get_trigger(e.config.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; + v |= get_trigger(e.config_filter.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; } } } - v |= uint32_t(e.config.watchdog_mode) << DFSDM_FLTCR1_AWFSEL_Pos; + v |= uint32_t(e.config_filter.watchdog_mode) << DFSDM_FLTCR1_AWFSEL_Pos; return v; } static consteval uint32_t make_fltawhtr(const Entry& e){ uint32_t v = 0; - if (e.config.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) - v |= (e.config.watchdog_high_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + if (e.config_filter.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) + v |= (e.config_filter.watchdog_high_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); else - v |= (e.config.watchdog_high_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; + v |= (e.config_filter.watchdog_high_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; return v; } static consteval uint32_t make_fltawltr(const Entry& e){ uint32_t v = 0; - if (e.config.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) - v |= (e.config.watchdog_low_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + if (e.config_filter.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) + v |= (e.config_filter.watchdog_low_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); else - v |= (e.config.watchdog_low_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; + v |= (e.config_filter.watchdog_low_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; return v; } //Channel static consteval uint32_t make_chawscdr(const Entry& e){ uint32_t v = 0; - v |= uint32_t(e.config.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; - if(e.config.watchdog == Analog_Watchdog::Enable){ - v |= static_cast(e.config.filter_wathdog) << DFSDM_CHAWSCDR_AWFORD_Pos; - v |= static_cast((e.config.watchdog_oversampling-1) & 0xF) << DFSDM_CHAWSCDR_AWFOSR_Pos; + v |= uint32_t(e.config_channel.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; + if(e.config_channel.watchdog == Analog_Watchdog::Enable){ + v |= static_cast(e.config_channel.filter_watchdog) << DFSDM_CHAWSCDR_AWFORD_Pos; + v |= static_cast((e.config_channel.watchdog_oversampling-1) & 0xF) << DFSDM_CHAWSCDR_AWFOSR_Pos; } return v; } @@ -471,18 +501,18 @@ namespace ST_LIB { //DATPACK = 0 -> Standard mode //DATMPX = 0 -> Comes from an external serial input //Chinsel = 0 -> channel input are taken from pin of the same channel y - v |= uint32_t(e.config.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; - v |= uint32_t (e.config.spi_type) << DFSDM_CHCFGR1_SITP_Pos; - v |= uint32_t(e.config.clock_absence) << DFSDM_CHCFGR1_CKABEN_Pos; - v |= uint32_t(e.config.short_circuit) << DFSDM_CHCFGR1_SCDEN_Pos; + v |= uint32_t(e.config_channel.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; + v |= uint32_t (e.config_channel.spi_type) << DFSDM_CHCFGR1_SITP_Pos; + v |= uint32_t(e.config_channel.clock_absence) << DFSDM_CHCFGR1_CKABEN_Pos; + v |= uint32_t(e.config_channel.short_circuit) << DFSDM_CHCFGR1_SCDEN_Pos; return v; } static consteval uint32_t make_chcfgr2(const Entry& e){ uint32_t v = 0; - v |= (e.config.offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; - v |= uint8_t(e.config.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; + v |= (e.config_channel.offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; + v |= uint8_t(e.config_channel.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; return v; } @@ -492,7 +522,6 @@ namespace ST_LIB { std::array cfgs{}; std::array channels_used{false}; std::array filters_used{-1,-1,-1,-1}; - bool filter_per_channel = (N <= 4) ? true : false; for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -504,23 +533,21 @@ namespace ST_LIB { Config& cfg = cfgs[i]; cfg.gpio_idx = e.gpio_idx; + cfg.dma_idx = e.dma_idx; cfg.channel = e.channel; cfg.buffer_size = e.buffer_size; cfg.buffer = e.buffer; - cfg.type_conv = e.config.type_conv; - cfg.dma = e.config.dma; - + cfg.type_conv = e.config_filter.type_conv; + cfg.dma_enable = e.config_filter.dma; + cfg.filter = e.config_filter.filter; //add the callbacks - cfg.overrun_callback = e.config.overrun_callback; - cfg.clock_absence_callback = e.config.clock_absence_callback; - cfg.short_circuit_callback = e.config.short_circuit_callback; - cfg.watchdog_callback = e.config.watchdog_callback; - cfg.conversion_complete_callback = e.config.conversion_complete_callback; - if(filter_per_channel){ - cfg.filter = i; - }else{ - cfg.filter = e.channel / 2; - } + cfg.overrun_callback = e.config_filter.overrun_callback; + cfg.clock_absence_callback = e.config_filter.clock_absence_callback; + cfg.short_circuit_callback = e.config_filter.short_circuit_callback; + cfg.watchdog_callback = e.config_filter.watchdog_callback; + cfg.conversion_complete_callback = e.config_filter.conversion_complete_callback; + + cfg.init_data_filter.FLTCR1 |= make_fltcr1(e,cfg.filter); cfg.init_data_filter.FLTCR2 |= make_fltcr2(e); cfg.init_data_filter.FLTFCR |= make_fltfcr(e); @@ -529,7 +556,7 @@ namespace ST_LIB { cfg.init_data_channel.CHAWSCDR |= make_chawscdr(e); cfg.init_data_filter.FLTAWHTR |= make_fltawhtr(e); cfg.init_data_filter.FLTAWLTR |= make_fltawltr(e); - if(e.config.type_conv == Type_Conversion::Injected) cfg.init_data_filter.FLTJCHGR |= 1 << e.channel; + if(e.config_filter.type_conv == Type_Conversion::Injected) cfg.init_data_filter.FLTJCHGR |= 1 << e.channel; if(cfg.filter == 0) cfg.init_data_filter.FLTCR2 |= make_fltcr2_global(); cfg.latency_cycles = compute_latency(e); @@ -555,9 +582,26 @@ namespace ST_LIB { return cfgs; } + static inline uint8_t channels_enabled{}; + static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { + DFSDM1_Filter0, + DFSDM1_Filter1, + DFSDM1_Filter2, + DFSDM1_Filter3 + }; + static constexpr DFSDM_Channel_TypeDef* channel_hw[8] = { + DFSDM1_Channel0, + DFSDM1_Channel1, + DFSDM1_Channel2, + DFSDM1_Channel3, + DFSDM1_Channel4, + DFSDM1_Channel5, + DFSDM1_Channel6, + DFSDM1_Channel7 + }; struct Instance { GPIODomain::Instance *gpio_instance; - + DMA_Domain::Instance *dma_instance; DFSDM_Filter_TypeDef *filter_regs{}; DFSDM_Channel_TypeDef *channel_regs{}; @@ -571,13 +615,95 @@ namespace ST_LIB { uint8_t channel; uint8_t filter; Type_Conversion type_conv; - Dma dma; + Dma dma_enable; int32_t* buffer{}; size_t length_buffer{}; size_t idx{}; + + template struct Init { + static inline std::array instances{}; + static void init(std::span cfgs,std::span gpio_instances,std::span dma_instances) { + if(N == 0) return; + std::array filters_configured = {false,false,false,false}; + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock + for (size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + filter_hw[cfg.filter]->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; + channel_hw[cfg.channel]->CHCFGR1 &= ~DFSDM_CHCFGR1_CHEN; + } + for (std::size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + Instance &inst = instances[i]; + + inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; + + inst.filter_regs = filter_hw[cfg.filter]; + inst.channel_regs = channel_hw[cfg.channel]; + + inst.latency_cycles = cfg.latency_cycles; + inst.type_conv = cfg.type_conv; + inst.filter = cfg.filter; + inst.channel = cfg.channel; + inst.dma_enable = cfg.dma_enable; + + inst.buffer = cfg.buffer; + inst.length_buffer = cfg.buffer_size; + + //callbacks + inst.overrun_cb = cfg.overrun_callback; + inst.short_circuit_cb = cfg.short_circuit_callback; + inst.watchdog_cb = cfg.watchdog_callback; + inst.end_conversion_cb = cfg.conversion_complete_callback; + if(!filters_configured[cfg.filter]){ + //add everything to the register of the filter + inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; + if(inst.type_conv == Type_Conversion::Regular){ + inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; + inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; + } + inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; + inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; + inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; + inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; + inst.filter_regs->FLTJCHGR = cfg.init_data_filter.FLTJCHGR; + + filters_configured[cfg.filter] = true; + } + //add everything to the channel register + inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; + inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; + inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; + + + //update channel_instances + channel_instances[inst.channel] = &inst; + channels_enabled |= 1 << inst.channel; + } + if(N > 0){ + //Activate the DFSDM GLOBAL Interface + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + for(int i = 0; i < 8; i++){ + channel_hw[i]->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + } + for(int i = 0; i < 4;i++){ + filter_hw[i]->FLTCR1 |= DFSDM_FLTCR1_DFEN; + } + //activate the NVIC + for(int i = 0; i < 4; i++){ + if(filters_configured[i] == true){ + switch(i){ + case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; + case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; + case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; + case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + } + } + } + } + } private: - //split in differents categories the enable to clearness + bool is_enabled_channel() const{ return (channel_regs->CHCFGR1 & DFSDM_CHCFGR1_CHEN_Msk); } @@ -627,13 +753,32 @@ namespace ST_LIB { //only disable channel channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN_Msk); } - /*channel functions */ + void enable_clock_absence_detector(){ channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CKABEN; } void enable_short_circuit_detector(){ channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_SCDEN; } + void disable_clock_absence_detector(){ + channel_regs->CHCFGR1 &= (~DFSDM_CHCFGR1_CKABEN); + } + void disable_short_circuit_detector(){ + channel_regs->CHCFGR1 &= (~DFSDM_CHCFGR1_SCDEN); + } + void enable_overrun(){ + filter_regs->FLTCR2 |= DFSDM_FLTCR2_ROVRIE; + } + void disable_overrun(){ + filter_regs->FLTCR2 &= (~DFSDM_FLTCR2_ROVRIE); + } + void enable_watchdog(){ + filter_regs->FLTCR2 |= DFSDM_FLTCR2_AWDCH; + } + void disable_watchdog(){ + filter_regs->FLTCR2 &= (~DFSDM_FLTCR2_AWDCH); + } + /*channel functions */ void change_offset(int32_t offset){ channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_OFFSET_Msk); channel_regs->CHCFGR2 |= (offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; @@ -796,185 +941,87 @@ namespace ST_LIB { } return channel; } + }; + }; static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; - static inline uint8_t channels_enabled{}; - static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { - DFSDM1_Filter0, - DFSDM1_Filter1, - DFSDM1_Filter2, - DFSDM1_Filter3 - }; - static constexpr DFSDM_Channel_TypeDef* channel_hw[8] = { - DFSDM1_Channel0, - DFSDM1_Channel1, - DFSDM1_Channel2, - DFSDM1_Channel3, - DFSDM1_Channel4, - DFSDM1_Channel5, - DFSDM1_Channel6, - DFSDM1_Channel7 - }; - template struct Init { - - static inline std::array instances{}; - static void init(std::span cfgs,std::span gpio_instances) { - if(N == 0) return; - std::array filters_configured = {false,false,false,false}; - RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock - for (size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; - filter_hw[cfg.filter]->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; - channel_hw[cfg.channel]->CHCFGR1 &= ~DFSDM_CHCFGR1_CHEN; - } - for (std::size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; - Instance &inst = instances[i]; - - inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; - - inst.filter_regs = filter_hw[cfg.filter]; - inst.channel_regs = channel_hw[cfg.channel]; + static void handle_irq(uint8_t filter_index) + { - inst.latency_cycles = cfg.latency_cycles; - inst.type_conv = cfg.type_conv; - inst.filter = cfg.filter; - inst.channel = cfg.channel; - inst.dma = cfg.dma; + DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; - inst.buffer = cfg.buffer; - inst.length_buffer = cfg.buffer_size; + uint32_t isr = filter->FLTISR; - //callbacks - inst.overrun_cb = cfg.overrun_callback; - inst.short_circuit_cb = cfg.short_circuit_callback; - inst.watchdog_cb = cfg.watchdog_callback; - inst.end_conversion_cb = cfg.conversion_complete_callback; - if(!filters_configured[cfg.filter]){ - //add everything to the register of the filter - inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; - if(inst.type_conv == Type_Conversion::Regular){ - inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; - inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; + if(isr & DFSDM_FLTISR_REOCF_Msk){ + //Save it in the address provide by the user + int32_t data = filter->FLTRDATAR; + Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; + if(inst != nullptr && inst->buffer != nullptr){ + if(inst->dma_enable == Dma::Disable){ + inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; } - inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; - inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; - inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; - inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; - inst.filter_regs->FLTJCHGR = cfg.init_data_filter.FLTJCHGR; - - filters_configured[cfg.filter] = true; - } - //add everything to the channel register - inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; - inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; - inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; - - - //update channel_instances - channel_instances[inst.channel] = &inst; - channels_enabled |= 1 << inst.channel; - } - if(N > 0){ - //Activate the DFSDM GLOBAL Interface - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - for(int i = 0; i < 8; i++){ - channel_hw[i]->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; - } - for(int i = 0; i < 4;i++){ - filter_hw[i]->FLTCR1 |= DFSDM_FLTCR1_DFEN; - } - //activate the NVIC - for(int i = 0; i < 4; i++){ - if(filters_configured[i] == true){ - switch(i){ - case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; - case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; - case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; - case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; - } + if(inst->end_conversion_cb != nullptr){ + inst->end_conversion_cb(); } - } - } - } - }; - - static void handle_irq(uint8_t filter_index) - { - - DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; - - uint32_t isr = filter->FLTISR; - - if(isr & DFSDM_FLTISR_REOCF_Msk){ - //Save it in the address provide by the user - int32_t data = filter->FLTRDATAR; - Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; - if(inst != nullptr && inst->buffer != nullptr){ - if(inst->dma == Dma::Disable){ - inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; - } - if(inst->end_conversion_cb != nullptr){ - inst->end_conversion_cb(); } } - } - if(isr & DFSDM_FLTISR_JEOCF_Msk){ - //Save it in the address provide by the user - int32_t data = filter->FLTJDATAR; - Instance* inst = channel_instances[(data & DFSDM_FLTJDATAR_JDATACH_Msk) >> DFSDM_FLTJDATAR_JDATACH_Pos]; - if(inst != nullptr && inst->buffer != nullptr){ - if(inst->dma == Dma::Disable){ - inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; - } - if(inst->end_conversion_cb != nullptr){ - inst->end_conversion_cb(); + if(isr & DFSDM_FLTISR_JEOCF_Msk){ + //Save it in the address provide by the user + int32_t data = filter->FLTJDATAR; + Instance* inst = channel_instances[(data & DFSDM_FLTJDATAR_JDATACH_Msk) >> DFSDM_FLTJDATAR_JDATACH_Pos]; + if(inst != nullptr && inst->buffer != nullptr){ + if(inst->dma_enable == Dma::Disable){ + inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; + inst->idx = (inst->idx + 1) % inst->length_buffer; + } + if(inst->end_conversion_cb != nullptr){ + inst->end_conversion_cb(); + } } } - } - if(isr & DFSDM_FLTISR_ROVRF_Msk){ - Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; - if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); - //clear - filter->FLTICR |= DFSDM_FLTISR_ROVRF; - } - if(isr & DFSDM_FLTISR_JOVRF_Msk){ - Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; - if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); - //clear - filter->FLTICR |= DFSDM_FLTISR_JOVRF; - } - if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ - uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; - if(channel_instances[ch] != nullptr && channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; - } - if(isr & (channels_enabled << DFSDM_FLTISR_CKABF_Pos)){ - uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos; - if(channel_instances[ch] != nullptr && channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRCKABF; - } - //Analog watchdog - if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) - { - if(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk){ - uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk); - if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); + if(isr & DFSDM_FLTISR_ROVRF_Msk){ + Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; + if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); + //clear + filter->FLTICR |= DFSDM_FLTISR_ROVRF; + } + if(isr & DFSDM_FLTISR_JOVRF_Msk){ + Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; + if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); //clear - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + filter->FLTICR |= DFSDM_FLTISR_JOVRF; } - if(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk){ - uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk); - if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); + if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ + uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; + if(channel_instances[ch] != nullptr && channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); //clear - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + } + if(isr & (channels_enabled << DFSDM_FLTISR_CKABF_Pos)){ + uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos; + if(channel_instances[ch] != nullptr && channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); + //clear + filter->FLTICR |= DFSDM_FLTICR_CLRCKABF; + } + //Analog watchdog + if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) + { + if(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk){ + uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk); + if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); + //clear + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + } + if(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk){ + uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk); + if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); + //clear + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + } } } - } + }; struct DFSDM_CLK_DOMAIN{ From 457a7916a08bbf0a44395ec6ceafdb77a604cd83 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sat, 14 Mar 2026 02:48:33 +0100 Subject: [PATCH 22/41] dma tested, don't ask how, don't ask why but it works fine. Or at least in my test has worked fine. --- Inc/HALAL/Models/DMA/DMA2.hpp | 55 ++++- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 371 +++++++++++++++++------------ Inc/ST-LIB.hpp | 4 +- Src/HALAL/Services/DFSDM/DFSDM.cpp | 2 +- 4 files changed, 262 insertions(+), 170 deletions(-) diff --git a/Inc/HALAL/Models/DMA/DMA2.hpp b/Inc/HALAL/Models/DMA/DMA2.hpp index ec153e9ea..680b52474 100644 --- a/Inc/HALAL/Models/DMA/DMA2.hpp +++ b/Inc/HALAL/Models/DMA/DMA2.hpp @@ -39,7 +39,11 @@ struct DMA_Domain { spi4, spi5, spi6, - fmac + fmac, + dfsdm_filter0, + dfsdm_filter1, + dfsdm_filter2, + dfsdm_filter3 }; enum class Stream : uint8_t { @@ -102,7 +106,9 @@ struct DMA_Domain { } return nullptr; } - + static inline consteval bool shares_dma(Peripheral p){ + return is_dfsdm(p); + } struct Entry { Peripheral instance; Stream stream; @@ -133,10 +139,25 @@ struct DMA_Domain { } } - template consteval array inscribe(Ctx& ctx) const { + template + consteval array inscribe(Ctx& ctx) const { array indices{}; - for (size_t i = 0; i < sizeof...(Ss); i++) { - indices[i] = ctx.template add(e[i], this); + for(size_t i = 0; i < sizeof...(Ss); ++i){ + bool found = false; + if(shares_dma(e[i].instance)){ + auto existing = ctx.template span(); + + for(size_t j = 0; j < existing.size(); ++j){ + if(existing[j].instance == e[i].instance){ + indices[i] = j; + found = true; + break; + } + } + } + if(!found){ + indices[i] = ctx.template add(e[i], this); + } } return indices; } @@ -223,7 +244,9 @@ struct DMA_Domain { static constexpr inline bool is_none(Peripheral instance) { return instance == Peripheral::none; } - + static constexpr inline bool is_dfsdm(Peripheral instance){ + return is_one_of(instance,Peripheral::dfsdm_filter0,Peripheral::dfsdm_filter1,Peripheral::dfsdm_filter2,Peripheral::dfsdm_filter3); + } static consteval inline uint32_t get_Request(Peripheral instance, uint8_t i) { if (instance == Peripheral::none) return DMA_REQUEST_MEM2MEM; @@ -279,8 +302,20 @@ struct DMA_Domain { return DMA_REQUEST_FMAC_WRITE; if (instance == Peripheral::fmac && i == 2) return DMA_REQUEST_FMAC_READ; - + if(instance == Peripheral::dfsdm_filter0){ + return DMA_REQUEST_DFSDM1_FLT0; + } + if(instance == Peripheral::dfsdm_filter1){ + return DMA_REQUEST_DFSDM1_FLT1; + } + if(instance == Peripheral::dfsdm_filter2){ + return DMA_REQUEST_DFSDM1_FLT2; + } + if(instance == Peripheral::dfsdm_filter3){ + return DMA_REQUEST_DFSDM1_FLT3; + } compile_error("Invalid DMA request configuration"); + return 0; } @@ -308,16 +343,16 @@ struct DMA_Domain { } static consteval inline uint32_t get_PeriphDataAlignment(Peripheral instance, uint8_t i) { - if (is_spi(instance) || is_i2c(instance)) { + if (is_spi(instance) || is_i2c(instance) ) { return DMA_PDATAALIGN_BYTE; - } else if (is_none(instance)) { + } else if (is_none(instance) || (is_dfsdm(instance))) { return DMA_PDATAALIGN_WORD; } return DMA_PDATAALIGN_HALFWORD; } static consteval inline uint32_t get_MemDataAlignment(Peripheral instance, uint8_t i) { - if (is_i2c(instance)) { + if (is_i2c(instance) || is_dfsdm(instance)) { return DMA_MDATAALIGN_WORD; } else if (is_spi(instance)) { return DMA_MDATAALIGN_BYTE; diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 73966ab4a..14db51f76 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -10,6 +10,7 @@ #define Possible_Pin_Channel 20 #define OFFSET_MAX (1 << 23) - 1 #define OFFSET_MIN -(1 << 23) +#define MAX_BUFFER_SIZE_TOTAL 1024 using ST_LIB::GPIODomain; using ST_LIB::DMA_Domain; namespace ST_LIB { @@ -137,10 +138,11 @@ namespace ST_LIB { }; struct Config_Filter{ + size_t length_buffer{1}; uint8_t filter{0}; Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; Filter_Type filter_type{Filter_Type::FastSinc}; - uint16_t oversampling{1}; + uint16_t oversampling{32}; uint16_t integrator{1}; Type_Conversion type_conv{Type_Conversion::Regular}; Dma dma{Dma::Disable}; @@ -151,7 +153,7 @@ struct Config_Filter{ Overrun overrun{Overrun::Disable}; - Analog_Watchdog watchdog{Analog_Watchdog::Enable}; + Analog_Watchdog watchdog{Analog_Watchdog::Disable}; Analog_Watchdog_Mode watchdog_mode{Analog_Watchdog_Mode::After_Filter}; int32_t watchdog_low_threshold{0x10000000}; int32_t watchdog_high_threshold{0x7FFFFFFF}; @@ -231,12 +233,11 @@ struct Config_Filter{ uint8_t channel; size_t gpio_idx; size_t dma_idx; - int32_t* buffer; size_t buffer_size; }; static constexpr size_t max_instances{8}; - template + template struct DFSDM_CHANNEL{ using domain = DFSDM_CHANNEL_DOMAIN; const GPIODomain::Pin& pin; @@ -245,24 +246,17 @@ struct Config_Filter{ const Config_Channel config_channel; const Config_Filter config_filter; uint8_t channel; - int32_t* buffer; size_t buffer_size; - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter, int32_t (&buffer)[N]) + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter) : pin(pin), dma(get_dma_peripheral(config_filter.filter)), gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, config_channel(config_channel), config_filter(config_filter), - buffer_size(N) + buffer_size(config_filter.length_buffer) { - static_assert(N != 0, "N must be bigger than 0"); - this->buffer = buffer; channel = get_channel(pin); - //remove in a future - - if(config_filter.dma == Dma::Enable){ - compile_error("Not implemented DMA yet"); - } + if(config_filter.filter > 3){ compile_error("Solo hay 4 filtros [0..3]"); } @@ -285,43 +279,7 @@ struct Config_Filter{ compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); } } - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter, int32_t*buffer) - : pin(pin), - dma{get_dma_peripheral(config_filter.filter)}, - gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, - config_channel(config_channel), - config_filter(config_filter), - buffer(buffer), - buffer_size(N) - { - static_assert(N == 1, "N must be bigger than 0"); - channel = get_channel(pin); - //remove in a future - if(config_filter.dma == Dma::Enable){ - compile_error("Not implemented DMA yet"); - } - if(config_filter.filter > 3){ - compile_error("Filter can not be bigger than 3"); - } - if(config_channel.offset > OFFSET_MAX || config_channel.offset < OFFSET_MIN){ - compile_error("Your offset is bigger than the maximum size"); - } - if(config_channel.right_shift > 0x000000FF){ - compile_error("Your right_shift is bigger than the maximum size"); - } - if(config_filter.integrator <= 0){ - compile_error("DFSDM_FILTER: Integrator out of range"); - } - if (!is_correct_oversampling(config_filter.filter_type, config_filter.oversampling)){ - compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); - } - if(config_channel.watchdog_oversampling > 32){ - compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); - } - if(static_cast(config_channel.filter_watchdog) > 3){ - compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); - } - } + template consteval std::size_t inscribe(Ctx &ctx) const { const auto gpio_idx = gpio.inscribe(ctx); @@ -331,8 +289,7 @@ struct Config_Filter{ .config_filter = config_filter, .channel = channel, .gpio_idx = gpio_idx, - .dma_idx = dma_idx, - .buffer = buffer, + .dma_idx = dma_idx[0],//There can only be one stream .buffer_size = buffer_size }; return ctx.template add(e, this); @@ -363,6 +320,7 @@ struct Config_Filter{ Dma dma_enable; uint8_t filter; + uint8_t filter_samples; uint8_t channel; size_t buffer_size; @@ -536,10 +494,10 @@ struct Config_Filter{ cfg.dma_idx = e.dma_idx; cfg.channel = e.channel; cfg.buffer_size = e.buffer_size; - cfg.buffer = e.buffer; cfg.type_conv = e.config_filter.type_conv; cfg.dma_enable = e.config_filter.dma; cfg.filter = e.config_filter.filter; + cfg.filter_samples = e.buffer_size; //add the callbacks cfg.overrun_callback = e.config_filter.overrun_callback; cfg.clock_absence_callback = e.config_filter.clock_absence_callback; @@ -565,7 +523,8 @@ struct Config_Filter{ (cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 & 0xFF) != (cfg.init_data_filter.FLTCR2 & 0xFF) || cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != cfg.init_data_filter.FLTFCR || cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWLTR != cfg.init_data_filter.FLTAWLTR || - cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWHTR != cfg.init_data_filter.FLTAWHTR){ + cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWHTR != cfg.init_data_filter.FLTAWHTR || + cfgs[filters_used[cfg.filter]].filter_samples != cfg.filter_samples){ compile_error("You have two channels that goes to the same filter with different filter configuration"); } //have the same thing in every register of the filter @@ -581,7 +540,41 @@ struct Config_Filter{ } return cfgs; } - + struct FilterBufferSizes{ + size_t filter_0_total = 0; + size_t filter_1_total = 0; + size_t filter_2_total = 0; + size_t filter_3_total = 0; + }; + static consteval FilterBufferSizes calculate_total_sizes(std::span configs){ + size_t filter0,filter1,filter2,filter3; + filter0 = filter1 = filter2 = filter3 = 0; + for(const auto& cfg : configs){ // must be the same the buffer_size for the same filter + switch(cfg.filter){ + case 0: + filter0 = cfg.buffer_size; + break; + case 1: + filter1 = cfg.buffer_size; + break; + case 2: + filter2 = cfg.buffer_size; + break; + case 3: + filter3 = cfg.buffer_size; + break; + } + } + if((filter0 + filter1 + filter2 + filter3) > MAX_BUFFER_SIZE_TOTAL){ + compile_error("Has superado el tamaño maximo total permitido"); + } + FilterBufferSizes sizes; + sizes.filter_0_total = filter0; + sizes.filter_1_total = filter1; + sizes.filter_2_total = filter2; + sizes.filter_3_total = filter3; + return sizes; + } static inline uint8_t channels_enabled{}; static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { DFSDM1_Filter0, @@ -619,89 +612,7 @@ struct Config_Filter{ int32_t* buffer{}; size_t length_buffer{}; - size_t idx{}; - - template struct Init { - static inline std::array instances{}; - static void init(std::span cfgs,std::span gpio_instances,std::span dma_instances) { - if(N == 0) return; - std::array filters_configured = {false,false,false,false}; - RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock - for (size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; - filter_hw[cfg.filter]->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; - channel_hw[cfg.channel]->CHCFGR1 &= ~DFSDM_CHCFGR1_CHEN; - } - for (std::size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; - Instance &inst = instances[i]; - - inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; - inst.filter_regs = filter_hw[cfg.filter]; - inst.channel_regs = channel_hw[cfg.channel]; - - inst.latency_cycles = cfg.latency_cycles; - inst.type_conv = cfg.type_conv; - inst.filter = cfg.filter; - inst.channel = cfg.channel; - inst.dma_enable = cfg.dma_enable; - - inst.buffer = cfg.buffer; - inst.length_buffer = cfg.buffer_size; - - //callbacks - inst.overrun_cb = cfg.overrun_callback; - inst.short_circuit_cb = cfg.short_circuit_callback; - inst.watchdog_cb = cfg.watchdog_callback; - inst.end_conversion_cb = cfg.conversion_complete_callback; - if(!filters_configured[cfg.filter]){ - //add everything to the register of the filter - inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; - if(inst.type_conv == Type_Conversion::Regular){ - inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; - inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; - } - inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; - inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; - inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; - inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; - inst.filter_regs->FLTJCHGR = cfg.init_data_filter.FLTJCHGR; - - filters_configured[cfg.filter] = true; - } - //add everything to the channel register - inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; - inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; - inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; - - - //update channel_instances - channel_instances[inst.channel] = &inst; - channels_enabled |= 1 << inst.channel; - } - if(N > 0){ - //Activate the DFSDM GLOBAL Interface - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - for(int i = 0; i < 8; i++){ - channel_hw[i]->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; - } - for(int i = 0; i < 4;i++){ - filter_hw[i]->FLTCR1 |= DFSDM_FLTCR1_DFEN; - } - //activate the NVIC - for(int i = 0; i < 4; i++){ - if(filters_configured[i] == true){ - switch(i){ - case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; - case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; - case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; - case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; - } - } - } - } - } private: bool is_enabled_channel() const{ @@ -814,16 +725,7 @@ struct Config_Filter{ if(was_enabled_filter) enable_filter(); } - void read_this_channel_in_regular_mode(){ - bool was_enabled_filter = is_enabled_filter(); - if(was_enabled_filter) disable_filter(); - - filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; - filter_regs->FLTCR1 |= (uint32_t(this->channel) << DFSDM_FLTCR1_RCONT_Pos); - - if(was_enabled_filter) enable_filter(); - start(); - } + bool modify_oversampling(uint16_t oversampling) { if (oversampling == 0) return false; @@ -872,7 +774,12 @@ struct Config_Filter{ if (was_enabled_filter) enable_filter(); return true; } - + int32_t read(size_t pos){ + if(pos >= this->length_buffer){ + return 0; + } + return ((this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos); // The constants values are the same for regular than injected + } uint32_t check_latency_cycles() { return filter_regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; } @@ -942,11 +849,161 @@ struct Config_Filter{ return channel; } }; + __attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32) + static inline int32_t DFSDM_Buffer_Pool[MAX_BUFFER_SIZE_TOTAL]; + static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; + template cfgs> struct Init { + static constexpr FilterBufferSizes Sizes = calculate_total_sizes(cfgs); + //Filter Buffers + static inline int32_t* Buffer_Filter0; + static inline int32_t* Buffer_Filter1; + static inline int32_t* Buffer_Filter2; + static inline int32_t* Buffer_Filter3; + + static inline std::array instances{}; + + static uint32_t get_buffer(uint8_t filter){ + switch(filter){ + case 0: + return reinterpret_cast(Buffer_Filter0); + case 1: + return reinterpret_cast(Buffer_Filter1); + case 2: + return reinterpret_cast(Buffer_Filter2); + case 3: + return reinterpret_cast(Buffer_Filter3); + } + return 0; + } + static int32_t* get_buffer_pointer(uint8_t filter){ + switch(filter){ + case 0: + return Buffer_Filter0; + case 1: + return Buffer_Filter1; + case 2: + return Buffer_Filter2; + case 3: + return Buffer_Filter3; + } + return 0; + } + static void assign_buffers(){ + uint32_t offset = 0; + Buffer_Filter0 = &DFSDM_Buffer_Pool[offset]; + offset += Sizes.filter_0_total; + Buffer_Filter1 = &DFSDM_Buffer_Pool[offset]; + offset += Sizes.filter_1_total; + Buffer_Filter2 = &DFSDM_Buffer_Pool[offset]; + offset += Sizes.filter_2_total; + Buffer_Filter3 = &DFSDM_Buffer_Pool[offset]; + offset += Sizes.filter_3_total; + } + static void init(std::span gpio_instances,std::span dma_instances) { + if(N == 0) return; + std::array filters_configured = {false,false,false,false}; + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock + for (size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + filter_hw[cfg.filter]->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; + channel_hw[cfg.channel]->CHCFGR1 &= ~DFSDM_CHCFGR1_CHEN; + } + for (std::size_t i = 0; i < N; ++i) { + const Config &cfg = cfgs[i]; + Instance &inst = instances[i]; + + inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; + inst.dma_instance = &dma_instances[cfg.dma_idx]; + + + inst.filter_regs = filter_hw[cfg.filter]; + inst.channel_regs = channel_hw[cfg.channel]; + + inst.latency_cycles = cfg.latency_cycles; + inst.type_conv = cfg.type_conv; + inst.filter = cfg.filter; + inst.channel = cfg.channel; + inst.dma_enable = cfg.dma_enable; + + inst.length_buffer = cfg.buffer_size; + assign_buffers(); + inst.buffer = get_buffer_pointer(cfg.filter); + //callbacks + inst.overrun_cb = cfg.overrun_callback; + inst.short_circuit_cb = cfg.short_circuit_callback; + inst.watchdog_cb = cfg.watchdog_callback; + inst.end_conversion_cb = cfg.conversion_complete_callback; + + if(!filters_configured[cfg.filter]){ + //add everything to the register of the filter + inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; + if(inst.type_conv == Type_Conversion::Regular){ + inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; + inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; + } + inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; + inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; + inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; + inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; + inst.filter_regs->FLTJCHGR = cfg.init_data_filter.FLTJCHGR; + + filters_configured[cfg.filter] = true; + + //add dma + if(cfg.dma_enable == Dma::Enable){ + uint32_t SrcAddress; + if(inst.type_conv == Type_Conversion::Regular){ + SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTRDATAR; + }else{ + SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTJDATAR; + } + uint32_t DstAddress = get_buffer(inst.filter); + inst.dma_instance->start(SrcAddress,DstAddress,inst.length_buffer); + } + } + //add everything to the channel register + inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; + inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; + inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; + + + //update channel_instances + channel_instances[inst.channel] = &inst; + channels_enabled |= 1 << inst.channel; + } + if(N > 0){; + //Activate the DFSDM GLOBAL Interface + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + for(int i = 0; i < 8; i++){ + if(channels_enabled & (1 << i)){ + channel_hw[i]->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; + } + + } + for(int i = 0; i < 4;i++){ + if(filters_configured[i] == true){ + filter_hw[i]->FLTCR1 |= DFSDM_FLTCR1_DFEN; + } + + } + //activate the NVIC + for(int i = 0; i < 4; i++){ + if(filters_configured[i] == true){ + switch(i){ + case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; + case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; + case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; + case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + } + } + } + } + } }; - static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; + static inline volatile size_t idx_filter[4] = {}; static void handle_irq(uint8_t filter_index) - { + { DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; @@ -958,8 +1015,8 @@ struct Config_Filter{ Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ if(inst->dma_enable == Dma::Disable){ - inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTRDATAR_RDATA_Msk) >> DFSDM_FLTRDATAR_RDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; + inst->buffer[idx_filter[inst->filter]] = data; + idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; } if(inst->end_conversion_cb != nullptr){ inst->end_conversion_cb(); @@ -972,8 +1029,8 @@ struct Config_Filter{ Instance* inst = channel_instances[(data & DFSDM_FLTJDATAR_JDATACH_Msk) >> DFSDM_FLTJDATAR_JDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ if(inst->dma_enable == Dma::Disable){ - inst->buffer[inst->idx] = int32_t(data & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos; - inst->idx = (inst->idx + 1) % inst->length_buffer; + inst->buffer[idx_filter[inst->filter]] = data; + idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; } if(inst->end_conversion_cb != nullptr){ inst->end_conversion_cb(); @@ -1021,9 +1078,7 @@ struct Config_Filter{ } } } - -}; - + }; struct DFSDM_CLK_DOMAIN{ static constexpr GPIODomain::Pin valid_clk_pins[] = { {GPIODomain::Port::C,GPIO_PIN_2}, diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 4ece18339..d5f59a015 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -241,7 +241,7 @@ template struct Board { EthernetDomain::Init::init(cfg.eth_cfgs, DigitalOutputDomain::Init::instances); ADCDomain::Init::init(cfg.adc_cfgs, GPIODomain::Init::instances); EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); - DFSDM_CHANNEL_DOMAIN::Init::init(cfg.dfsdm_cfgs,GPIODomain::Init::instances); + DFSDM_CHANNEL_DOMAIN::Init::init(GPIODomain::Init::instances,DMA_Domain::Init::instances); DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); // ... } @@ -268,6 +268,8 @@ template struct Board { if constexpr (std::is_same_v) { return Domain::template Init::instances[idx]; + }else if constexpr(std::is_same_v){ + return Domain::template Init::instances[idx]; } else { return Domain::template Init::instances[idx]; } diff --git a/Src/HALAL/Services/DFSDM/DFSDM.cpp b/Src/HALAL/Services/DFSDM/DFSDM.cpp index 20f6e0569..13d9571b4 100644 --- a/Src/HALAL/Services/DFSDM/DFSDM.cpp +++ b/Src/HALAL/Services/DFSDM/DFSDM.cpp @@ -1,7 +1,7 @@ #include "HALAL/Services/DFSDM/DFSDM.hpp" - +//ST_LIB::DFSDM_CHANNEL_DOMAIN::Init:: extern "C"{ From dfd5340e9686d5f4cc6d08891e9181ac72aa981a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= <125664643+jorgesg82@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:57:39 +0100 Subject: [PATCH 23/41] Robust TCP/IP Hardening (#582) * Fixed copy pointers leading with possible missalignment * fix(tcp): handle fragmented order streams and queue backpressure * fix(server): own and recycle ServerSocket instances safely * fix(udp): harden DatagramSocket lifecycle and pbuf parsing * fix(lwip): align ICMP checksum settings with hw offload * fix(net): harden socket teardown and unify TCP order parsing * Applied formatter * perf(net): benchmark-driven TCP TX fast path * feat(error): decouple transport from legacy update * Modified warning to decouple from protections * Better timestamp in error and warning * Protection manager more robust * Always enqueuing msgs even when there are no sockets * sorry, I forgot one file * SNTP set * minor fix on protections * Being able to compile without ehternet * formatter --- CMakeLists.txt | 1 + Inc/HALAL/Models/Packets/OrderProtocol.hpp | 1 + Inc/HALAL/Models/Packets/PacketValue.hpp | 6 +- .../Ethernet/LWIP/TCP/ServerSocket.hpp | 35 +- .../Ethernet/LWIP/TCP/Socket.hpp | 40 +- .../LWIP/TCP/TcpOrderStreamParser.hpp | 53 ++ .../Ethernet/LWIP/UDP/DatagramSocket.hpp | 9 +- .../Communication/Ethernet/NewEthernet.hpp | 24 +- .../Services/Communication/SNTP/SNTP.hpp | 4 +- .../Services/InfoWarning/InfoWarning.hpp | 1 + Inc/HALAL/Services/Time/RTC.hpp | 2 + Inc/ST-LIB.hpp | 4 + Inc/ST-LIB_HIGH/Protections/Boundary.hpp | 22 +- Inc/ST-LIB_HIGH/Protections/Protection.hpp | 3 +- .../Protections/ProtectionManager.hpp | 2 +- .../Communication/Server/Server.hpp | 2 +- LWIP/Target/lwipopts.h | 16 + .../Ethernet/LWIP/TCP/ServerSocket.cpp | 424 +++++++++---- .../Ethernet/LWIP/TCP/Socket.cpp | 562 +++++++++++++----- .../Ethernet/LWIP/UDP/DatagramSocket.cpp | 76 ++- .../Services/Communication/SNTP/SNTP.cpp | 58 +- .../Services/Communication/UART/UART.cpp | 20 +- .../Services/InfoWarning/InfoWarning.cpp | 262 +++++++- Src/HALAL/Services/Time/RTC.cpp | 52 +- .../Protections/ProtectionManager.cpp | 50 +- .../Communication/Server/Server.cpp | 87 ++- Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp | 254 +++++++- 27 files changed, 1644 insertions(+), 426 deletions(-) create mode 100644 Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/TcpOrderStreamParser.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c782db637..52aeba82e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,6 +243,7 @@ if(CMAKE_CROSSCOMPILING) ${CMAKE_CURRENT_LIST_DIR}/STM32CubeH7/Middlewares/Third_Party/LwIP/src/core/ipv4/ip4.c ${CMAKE_CURRENT_LIST_DIR}/STM32CubeH7/Middlewares/Third_Party/LwIP/src/core/ipv4/ip4_addr.c ${CMAKE_CURRENT_LIST_DIR}/STM32CubeH7/Middlewares/Third_Party/LwIP/src/core/ipv4/ip4_frag.c + ${CMAKE_CURRENT_LIST_DIR}/STM32CubeH7/Middlewares/Third_Party/LwIP/src/apps/sntp/sntp.c ${CMAKE_CURRENT_LIST_DIR}/STM32CubeH7/Middlewares/Third_Party/LwIP/src/netif/ethernet.c ) diff --git a/Inc/HALAL/Models/Packets/OrderProtocol.hpp b/Inc/HALAL/Models/Packets/OrderProtocol.hpp index 5e07d39f4..4ea3f1d69 100644 --- a/Inc/HALAL/Models/Packets/OrderProtocol.hpp +++ b/Inc/HALAL/Models/Packets/OrderProtocol.hpp @@ -5,6 +5,7 @@ class Order; class OrderProtocol { public: + virtual ~OrderProtocol() = default; virtual bool send_order(Order& order) = 0; static vector sockets; diff --git a/Inc/HALAL/Models/Packets/PacketValue.hpp b/Inc/HALAL/Models/Packets/PacketValue.hpp index 529000d93..1e9d373c4 100644 --- a/Inc/HALAL/Models/Packets/PacketValue.hpp +++ b/Inc/HALAL/Models/Packets/PacketValue.hpp @@ -33,8 +33,8 @@ class PacketValue : public PacketValue<> { void set_pointer(void* pointer) { src = (Type*)pointer; } size_t get_size() override { return sizeof(Type); } - void parse(uint8_t* data) override { *src = *((Type*)data); } - void copy_to(uint8_t* data) override { *((Type*)data) = *src; } + void parse(uint8_t* data) override { memcpy(src, data, get_size()); } + void copy_to(uint8_t* data) override { memcpy(data, src, get_size()); } }; template <> class PacketValue : public PacketValue<> { @@ -49,7 +49,7 @@ template <> class PacketValue : public PacketValue<> { void set_pointer(void* pointer) { src = (double*)pointer; } size_t get_size() override { return sizeof(double); } - void parse(uint8_t* data) override { *src = *((double*)data); } + void parse(uint8_t* data) override { memcpy(src, data, get_size()); } void copy_to(uint8_t* data) override { memcpy(data, src, get_size()); } }; diff --git a/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.hpp b/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.hpp index daa2742ae..cf6be5936 100644 --- a/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.hpp +++ b/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.hpp @@ -41,7 +41,7 @@ class ServerSocket : public OrderProtocol { public: enum ServerState { INACTIVE, LISTENING, ACCEPTED, CLOSING, CLOSED }; - static constexpr size_t MAX_TX_QUEUE_DEPTH = 24; + static constexpr size_t MAX_TX_QUEUE_DEPTH = 64; static unordered_map listening_sockets; IPV4 local_ip; @@ -110,32 +110,7 @@ class ServerSocket : public OrderProtocol { * message * @return true if the data was sent successfully, false otherwise */ - bool send_order(Order& order) override { - if (state != ACCEPTED || client_control_block == nullptr) { - return false; - } - send(); - if (tx_packet_buffer.size() >= MAX_TX_QUEUE_DEPTH) { - return false; - } - - uint8_t* order_buffer = order.build(); - if (order.get_size() > tcp_sndbuf(client_control_block)) { - return false; - } - - struct pbuf* packet = pbuf_alloc(PBUF_TRANSPORT, order.get_size(), PBUF_RAM); - if (packet == nullptr) { - return false; - } - if (pbuf_take(packet, order_buffer, order.get_size()) != ERR_OK) { - pbuf_free(packet); - return false; - } - tx_packet_buffer.push(packet); - send(); - return true; - } + bool send_order(Order& order) override; /** * @brief sends all the binary data saved in the tx_packet_buffer to the @@ -160,12 +135,16 @@ class ServerSocket : public OrderProtocol { * otherwise */ bool is_connected(); + bool is_listening() const; private: struct tcp_pcb* server_control_block = nullptr; queue tx_packet_buffer; queue rx_packet_buffer; - struct tcp_pcb* client_control_block; + vector rx_stream_buffer; + struct tcp_pcb* client_control_block = nullptr; + void clear_packet_queues(); + bool try_send_immediately(Order& order); /** * @brief process the data received by the client orders. It is meant to be diff --git a/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.hpp b/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.hpp index 8cb38a1e7..df18693fe 100644 --- a/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.hpp +++ b/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.hpp @@ -17,11 +17,14 @@ class Socket : public OrderProtocol { private: - tcp_pcb* connection_control_block; - tcp_pcb* socket_control_block; + tcp_pcb* connection_control_block = nullptr; + tcp_pcb* socket_control_block = nullptr; queue tx_packet_buffer; queue rx_packet_buffer; + vector rx_stream_buffer; + void clear_packet_queues(); void process_data(); + bool try_send_immediately(Order& order); static err_t connect_callback(void* arg, struct tcp_pcb* client_control_block, err_t error); static err_t receive_callback( void* arg, @@ -39,6 +42,7 @@ class Socket : public OrderProtocol { public: enum SocketState { INACTIVE, CONNECTED, CLOSING }; + static constexpr size_t MAX_TX_QUEUE_DEPTH = 64; IPV4 local_ip; uint32_t local_port; @@ -49,6 +53,7 @@ class Socket : public OrderProtocol { static unordered_map connecting_sockets; bool pending_connection_reset = false; + uint16_t connect_poll_ticks = 0; bool use_keep_alives{true}; struct KeepaliveConfig { uint32_t inactivity_time_until_keepalive_ms = TCP_INACTIVITY_TIME_UNTIL_KEEPALIVE_MS; @@ -95,36 +100,7 @@ class Socket : public OrderProtocol { * @return true if the data was sent successfully, false otherwise */ - bool send_order(Order& order) override { - if (state != CONNECTED) { - reconnect(); - return false; - } - struct memp* next_memory_pointer_in_packet_buffer_pool = - (*(memp_pools[PBUF_POOL_MEMORY_DESC_POSITION]->tab))->next; - if (next_memory_pointer_in_packet_buffer_pool == nullptr) { - if (socket_control_block->unsent != nullptr) { - tcp_output(socket_control_block); - } else { - memp_free_pool( - memp_pools[PBUF_POOL_MEMORY_DESC_POSITION], - next_memory_pointer_in_packet_buffer_pool - ); - } - return false; - } - - uint8_t* order_buffer = order.build(); - if (order.get_size() > tcp_sndbuf(socket_control_block)) { - return false; - } - - struct pbuf* packet = pbuf_alloc(PBUF_TRANSPORT, order.get_size(), PBUF_POOL); - pbuf_take(packet, order_buffer, order.get_size()); - tx_packet_buffer.push(packet); - send(); - return true; - } + bool send_order(Order& order) override; void send(); bool is_connected(); }; diff --git a/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/TcpOrderStreamParser.hpp b/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/TcpOrderStreamParser.hpp new file mode 100644 index 000000000..bb5e08089 --- /dev/null +++ b/Inc/HALAL/Services/Communication/Ethernet/LWIP/TCP/TcpOrderStreamParser.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "HALAL/Models/IPV4/IPV4.hpp" +#include "HALAL/Models/Packets/Order.hpp" + +#ifdef HAL_ETH_MODULE_ENABLED + +namespace TcpOrderStreamParser { + +inline constexpr size_t MAX_RX_STREAM_BUFFER_BYTES = 8192; + +inline void process(OrderProtocol* protocol, IPV4& remote_ip, vector& stream_buffer) { + if (stream_buffer.empty()) { + return; + } + size_t parsed_bytes = 0; + + while (stream_buffer.size() - parsed_bytes >= sizeof(uint16_t)) { + uint8_t* packet_ptr = stream_buffer.data() + parsed_bytes; + uint16_t order_id = Packet::get_id(packet_ptr); + auto order_it = Order::orders.find(order_id); + if (order_it == Order::orders.end()) { + parsed_bytes += 1; + continue; + } + + const size_t order_size = order_it->second->get_size(); + if (order_size < sizeof(uint16_t)) { + parsed_bytes += 1; + continue; + } + if (stream_buffer.size() - parsed_bytes < order_size) { + break; + } + + order_it->second->store_ip_order(remote_ip.string_address); + Order::process_data(protocol, packet_ptr); + parsed_bytes += order_size; + } + + if (parsed_bytes > 0) { + stream_buffer.erase(stream_buffer.begin(), stream_buffer.begin() + parsed_bytes); + } + + if (stream_buffer.size() > MAX_RX_STREAM_BUFFER_BYTES) { + const size_t trim_count = stream_buffer.size() - MAX_RX_STREAM_BUFFER_BYTES; + stream_buffer.erase(stream_buffer.begin(), stream_buffer.begin() + trim_count); + } +} + +} // namespace TcpOrderStreamParser + +#endif diff --git a/Inc/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp b/Inc/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp index f0eb78a79..9473bcb03 100644 --- a/Inc/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp +++ b/Inc/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp @@ -8,7 +8,7 @@ class DatagramSocket { public: - struct udp_pcb* udp_control_block; + struct udp_pcb* udp_control_block = nullptr; IPV4 local_ip; uint32_t local_port; @@ -36,16 +36,17 @@ class DatagramSocket { if (is_disconnected || udp_control_block == nullptr) { return false; } + const size_t packet_size = packet.get_size(); uint8_t* packet_buffer = packet.build(); - if (packet_buffer == nullptr || packet.size == 0) { + if (packet_buffer == nullptr || packet_size == 0) { return false; } - struct pbuf* tx_buffer = pbuf_alloc(PBUF_TRANSPORT, packet.size, PBUF_RAM); + struct pbuf* tx_buffer = pbuf_alloc(PBUF_TRANSPORT, packet_size, PBUF_RAM); if (tx_buffer == nullptr) { return false; } - if (pbuf_take(tx_buffer, packet_buffer, packet.size) != ERR_OK) { + if (pbuf_take(tx_buffer, packet_buffer, packet_size) != ERR_OK) { pbuf_free(tx_buffer); return false; } diff --git a/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp b/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp index dbc3b9d6c..16a91a374 100644 --- a/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp +++ b/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp @@ -10,6 +10,9 @@ #include "HALAL/Services/Communication/Ethernet/LWIP/Ethernet.hpp" #include "HALAL/Services/Communication/Ethernet/LWIP/EthernetHelper.hpp" #include "HALAL/Services/Communication/Ethernet/LWIP/EthernetNode.hpp" +#include "HALAL/Services/Communication/SNTP/SNTP.hpp" +#include "ErrorHandler/ErrorHandler.hpp" +#include "HALAL/Services/InfoWarning/InfoWarning.hpp" extern "C" { #include "ethernetif.h" #include "lwip.h" @@ -79,6 +82,7 @@ struct EthernetDomain { const char* local_ip; const char* subnet_mask; const char* gateway; + const char* sntp_server; size_t phy_reset_id; }; @@ -97,9 +101,10 @@ struct EthernetDomain { const char* local_mac, const char* local_ip, const char* subnet_mask = "255.255.0.0", - const char* gateway = "192.168.1.1" + const char* gateway = "192.168.1.1", + const char* sntp_server = SNTP::DEFAULT_SERVER_IP ) - : pins{pins}, e{local_mac, local_ip, subnet_mask, gateway}, + : pins{pins}, e{local_mac, local_ip, subnet_mask, gateway, sntp_server}, rmii_gpios{ GPIODomain::GPIO( pins.MDC, @@ -185,6 +190,7 @@ struct EthernetDomain { .local_ip = this->e.local_ip, .subnet_mask = this->e.subnet_mask, .gateway = this->e.gateway, + .sntp_server = this->e.sntp_server, .phy_reset_id = phy_reset_id, }; @@ -199,6 +205,7 @@ struct EthernetDomain { const char* local_ip; const char* subnet_mask; const char* gateway; + const char* sntp_server; size_t phy_reset_id; }; @@ -214,16 +221,22 @@ struct EthernetDomain { cfgs[0].local_ip = e.local_ip; cfgs[0].subnet_mask = e.subnet_mask; cfgs[0].gateway = e.gateway; + cfgs[0].sntp_server = e.sntp_server; cfgs[0].phy_reset_id = e.phy_reset_id; return cfgs; } // Runtime object struct Instance { + const char* sntp_server{nullptr}; + bool sntp_started{false}; + constexpr Instance() {} void update() { ethernetif_input(&gnetif); sys_check_timeouts(); + ErrorHandlerModel::ErrorHandlerUpdate(); + InfoWarning::InfoWarningUpdate(); if (HAL_GetTick() - EthernetLinkTimer >= 100) { EthernetLinkTimer = HAL_GetTick(); @@ -233,6 +246,12 @@ struct EthernetDomain { netif_set_up(&gnetif); } } + + if (!sntp_started && sntp_server != nullptr && sntp_server[0] != '\0' && + netif_is_link_up(&gnetif)) { + SNTP::sntp_update(sntp_server); + sntp_started = true; + } }; }; @@ -316,6 +335,7 @@ struct EthernetDomain { ::Ethernet::is_running = true; instances[0] = Instance{}; + instances[0].sntp_server = e.sntp_server; } }; }; diff --git a/Inc/HALAL/Services/Communication/SNTP/SNTP.hpp b/Inc/HALAL/Services/Communication/SNTP/SNTP.hpp index 16e793910..a87592016 100644 --- a/Inc/HALAL/Services/Communication/SNTP/SNTP.hpp +++ b/Inc/HALAL/Services/Communication/SNTP/SNTP.hpp @@ -7,12 +7,14 @@ #pragma once -#include "sntp.h" +#include "lwip/apps/sntp.h" #include "HALAL/Models/IPV4/IPV4.hpp" #include "C++Utilities/CppUtils.hpp" class SNTP { public: + static constexpr const char* DEFAULT_SERVER_IP = "192.168.0.9"; + static void sntp_update( uint8_t address_head, uint8_t address_second, diff --git a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp index 60bf49bf8..ad00091fe 100644 --- a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp +++ b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp @@ -18,6 +18,7 @@ class InfoWarning { public: static bool warning_triggered; + static bool warning_to_communicate; /** * @brief Triggers WarningHandler and format the warning message. The format works diff --git a/Inc/HALAL/Services/Time/RTC.hpp b/Inc/HALAL/Services/Time/RTC.hpp index f912fc096..baf02c639 100644 --- a/Inc/HALAL/Services/Time/RTC.hpp +++ b/Inc/HALAL/Services/Time/RTC.hpp @@ -21,6 +21,8 @@ class Global_RTC { public: static RTCData global_RTC; static void start_rtc(); + static bool ensure_started(); + static bool has_valid_time(); static void update_rtc_data(); static RTCData get_rtc_timestamp(); static void set_rtc_data( diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index d5f59a015..8f1f01ae1 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -218,6 +218,10 @@ template struct Board { HALconfig::system_clock(); HALconfig::peripheral_clock(); +#ifdef HAL_RTC_MODULE_ENABLED + (void)Global_RTC::ensure_started(); +#endif + MPUDomain::Init::init(); GPIODomain::Init::init(cfg.gpio_cfgs); TimerDomain::Init::init(cfg.tim_cfgs); diff --git a/Inc/ST-LIB_HIGH/Protections/Boundary.hpp b/Inc/ST-LIB_HIGH/Protections/Boundary.hpp index d24779816..1f6539dc9 100644 --- a/Inc/ST-LIB_HIGH/Protections/Boundary.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Boundary.hpp @@ -27,16 +27,24 @@ enum ProtectionType : uint8_t { struct BoundaryInterface { public: + static constexpr uint8_t ERROR_HANDLER_BOUNDARY_TYPE_ID = ERROR_HANDLER; + static constexpr uint8_t INFO_WARNING_BOUNDARY_TYPE_ID = INFO_WARNING - 2; + virtual Protections::FaultType check_bounds() = 0; HeapOrder* fault_message{nullptr}; HeapOrder* warn_message{nullptr}; HeapOrder* ok_message{nullptr}; void update_name(char* n) { - name = n; - if (strlen(n) > NAME_MAX_LEN) { - ErrorHandler("Variable name is too long, max length is %d", NAME_MAX_LEN); + if (n == nullptr) { + name.clear(); + string_len = 0; return; } + + name = n; + if (name.size() > NAME_MAX_LEN) { + name.resize(NAME_MAX_LEN); + } string_len = name.size(); } virtual void update_error_handler_message([[maybe_unused]] const char* err_message) {} @@ -490,7 +498,7 @@ template struct Boundary : public BoundaryInter template <> struct Boundary : public BoundaryInterface { static constexpr ProtectionType Protector = ERROR_HANDLER; Boundary(void*) { - boundary_type_id = Protector; + boundary_type_id = ERROR_HANDLER_BOUNDARY_TYPE_ID; error_handler_string.reserve(ERROR_HANDLER_MSG_MAX_LEN); fault_message = new HeapOrder( uint16_t{1555}, @@ -509,7 +517,7 @@ template <> struct Boundary : public BoundaryInterface { } uint8_t padding{}; Boundary(void*, Boundary) { - boundary_type_id = Protector; + boundary_type_id = ERROR_HANDLER_BOUNDARY_TYPE_ID; error_handler_string.reserve(ERROR_HANDLER_MSG_MAX_LEN); fault_message = new HeapOrder( uint16_t{1555}, @@ -551,7 +559,7 @@ template <> struct Boundary : public BoundaryInterface { template <> struct Boundary : public BoundaryInterface { static constexpr ProtectionType Protector = INFO_WARNING; Boundary(void*) { - boundary_type_id = Protector - 2; + boundary_type_id = INFO_WARNING_BOUNDARY_TYPE_ID; warning_string.reserve(WARNING_HANDLER_MSG_MAX_LEN); warn_message = new HeapOrder( uint16_t{2555}, @@ -571,7 +579,7 @@ template <> struct Boundary : public BoundaryInterface { uint8_t padding{}; Boundary(void*, Boundary) { // SW are crybabies - boundary_type_id = Protector - 2; + boundary_type_id = INFO_WARNING_BOUNDARY_TYPE_ID; warning_string.reserve(WARNING_HANDLER_MSG_MAX_LEN); warn_message = new HeapOrder( uint16_t{2555}, diff --git a/Inc/ST-LIB_HIGH/Protections/Protection.hpp b/Inc/ST-LIB_HIGH/Protections/Protection.hpp index 089b3f4ff..b8371ce4b 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -69,7 +69,8 @@ class Protection { bound->back_to_normal = true; } bound->warning_already_triggered = false; - if (bound->back_to_normal && bound->boundary_type_id != INFO_WARNING - 2) { + if (bound->back_to_normal && + bound->boundary_type_id != BoundaryInterface::INFO_WARNING_BOUNDARY_TYPE_ID) { triggered_oks_idx[oks_count] = idx - 1; oks_count++; bound->back_to_normal = false; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp index 5a69cf235..ec0459073 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp @@ -39,7 +39,7 @@ class ProtectionManager { typedef uint8_t state_id; static bool external_trigger; - static const uint64_t notify_delay_in_nanoseconds = 2000'000'000; + static const uint64_t notify_delay_in_microseconds = 2'000'000; static uint64_t last_notify; static void set_id(Boards::ID id); diff --git a/Inc/ST-LIB_LOW/Communication/Server/Server.hpp b/Inc/ST-LIB_LOW/Communication/Server/Server.hpp index e1cf1e7a9..1cfc07d5a 100644 --- a/Inc/ST-LIB_LOW/Communication/Server/Server.hpp +++ b/Inc/ST-LIB_LOW/Communication/Server/Server.hpp @@ -36,7 +36,7 @@ class Server { Server(IPV4 local_ip, uint32_t local_port); ~Server(); void update(); - void broadcast_order(Order& order); + bool broadcast_order(Order& order); void close_all(); uint32_t connections_count(); diff --git a/LWIP/Target/lwipopts.h b/LWIP/Target/lwipopts.h index e319dd6a2..e29c56086 100644 --- a/LWIP/Target/lwipopts.h +++ b/LWIP/Target/lwipopts.h @@ -93,6 +93,8 @@ #define CHECKSUM_GEN_UDP 0 /*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/ #define CHECKSUM_GEN_TCP 0 +/*----- Value in opt.h for CHECKSUM_GEN_ICMP: 1 -----*/ +#define CHECKSUM_GEN_ICMP 0 /*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/ #define CHECKSUM_GEN_ICMP6 0 /*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/ @@ -101,6 +103,8 @@ #define CHECKSUM_CHECK_UDP 0 /*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/ #define CHECKSUM_CHECK_TCP 0 +/*----- Value in opt.h for CHECKSUM_CHECK_ICMP: 1 -----*/ +#define CHECKSUM_CHECK_ICMP 0 /*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/ #define CHECKSUM_CHECK_ICMP6 0 /*-----------------------------------------------------------------------------*/ @@ -109,12 +113,24 @@ #define LWIP_DHCP 0 #define LWIP_AUTOIP 0 #define LWIP_DNS 0 +#define LWIP_SNTP 1 #define LWIP_IGMP 0 #define LWIP_IPV6_MLD 0 #define LWIP_IPV6_DHCP6 0 #define LWIP_IPV6_REASS 0 #define LWIP_IPV6_FRAG 0 +void stlib_sntp_set_time(uint32_t sec, uint32_t us); +uint32_t stlib_sntp_get_rtc_seconds(void); +uint32_t stlib_sntp_get_rtc_microseconds(void); + +#define SNTP_SET_SYSTEM_TIME_US(sec, us) stlib_sntp_set_time((sec), (us)) +#define SNTP_GET_SYSTEM_TIME(sec, us) \ + do { \ + (sec) = stlib_sntp_get_rtc_seconds(); \ + (us) = stlib_sntp_get_rtc_microseconds(); \ + } while (0) + /* USER CODE END 1 */ #ifdef __cplusplus diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp index 2e35aba6f..344d88073 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp @@ -6,6 +6,7 @@ */ #ifdef STLIB_ETH #include "HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.hpp" +#include "HALAL/Services/Communication/Ethernet/LWIP/TCP/TcpOrderStreamParser.hpp" #include "ErrorHandler/ErrorHandler.hpp" #include "lwip/priv/tcp_priv.h" #ifdef HAL_ETH_MODULE_ENABLED @@ -18,27 +19,43 @@ ServerSocket::ServerSocket() = default; ServerSocket::ServerSocket(IPV4 local_ip, uint32_t local_port) : local_ip(local_ip), local_port(local_port) { if (not Ethernet::is_running) { - ErrorHandler("Cannot declare UDP socket before Ethernet::start()"); + ErrorHandler("Cannot declare TCP server socket before Ethernet::start()"); return; } tx_packet_buffer = {}; rx_packet_buffer = {}; + rx_stream_buffer = {}; + rx_stream_buffer.reserve(TcpOrderStreamParser::MAX_RX_STREAM_BUFFER_BYTES); state = INACTIVE; server_control_block = tcp_new(); + if (server_control_block == nullptr) { + ErrorHandler("Cannot allocate TCP server control block"); + return; + } tcp_nagle_disable(server_control_block); ip_set_option(server_control_block, SOF_REUSEADDR); err_t error = tcp_bind(server_control_block, &local_ip.address, local_port); if (error == ERR_OK) { server_control_block = tcp_listen(server_control_block); + if (server_control_block == nullptr) { + ErrorHandler("Cannot switch TCP server socket into LISTEN mode"); + return; + } state = LISTENING; listening_sockets[local_port] = this; + tcp_arg(server_control_block, this); tcp_accept(server_control_block, accept_callback); } else { - memp_free(MEMP_TCP_PCB, server_control_block); + tcp_abort(server_control_block); + server_control_block = nullptr; ErrorHandler("Cannot bind server socket, error %d", (int16_t)error); + return; + } + if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == + OrderProtocol::sockets.end()) { + OrderProtocol::sockets.push_back(this); } - OrderProtocol::sockets.push_back(this); } ServerSocket::ServerSocket( @@ -55,48 +72,102 @@ ServerSocket::ServerSocket( } ServerSocket::ServerSocket(ServerSocket&& other) - : local_ip(move(other.local_ip)), local_port(move(other.local_port)), state(other.state), - server_control_block(move(other.server_control_block)) { - listening_sockets[local_port] = this; - tx_packet_buffer = {}; - rx_packet_buffer = {}; + : local_ip(move(other.local_ip)), local_port(other.local_port), + remote_ip(move(other.remote_ip)), state(other.state), + keepalive_config(other.keepalive_config), server_control_block(other.server_control_block), + tx_packet_buffer(move(other.tx_packet_buffer)), + rx_packet_buffer(move(other.rx_packet_buffer)), + rx_stream_buffer(move(other.rx_stream_buffer)), + client_control_block(other.client_control_block) { + other.server_control_block = nullptr; + other.client_control_block = nullptr; + other.state = INACTIVE; + + if (server_control_block != nullptr) { + tcp_arg(server_control_block, this); + } + if (client_control_block != nullptr) { + tcp_arg(client_control_block, this); + } + + auto it = listening_sockets.find(local_port); + if (it != listening_sockets.end() && it->second == &other) { + it->second = this; + } + + if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == + OrderProtocol::sockets.end()) { + OrderProtocol::sockets.push_back(this); + } } void ServerSocket::operator=(ServerSocket&& other) { + if (this == &other) { + return; + } + close(); + local_ip = move(other.local_ip); - local_port = move(other.local_port); - server_control_block = move(other.server_control_block); + local_port = other.local_port; + remote_ip = move(other.remote_ip); + server_control_block = other.server_control_block; + client_control_block = other.client_control_block; + tx_packet_buffer = move(other.tx_packet_buffer); + rx_packet_buffer = move(other.rx_packet_buffer); + rx_stream_buffer = move(other.rx_stream_buffer); + keepalive_config = other.keepalive_config; state = other.state; - listening_sockets[local_port] = this; - tx_packet_buffer = {}; - rx_packet_buffer = {}; - if (not(std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) != - OrderProtocol::sockets.end())) - OrderProtocol::sockets.push_back(this); -} -ServerSocket::~ServerSocket() { - // el destructor no destruye - auto it = std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this); - if (it == OrderProtocol::sockets.end()) - return; - else - OrderProtocol::sockets.erase(it); + other.server_control_block = nullptr; + other.client_control_block = nullptr; + other.state = INACTIVE; + + if (server_control_block != nullptr) { + tcp_arg(server_control_block, this); + } if (client_control_block != nullptr) { - tcp_abort(client_control_block); - client_control_block = nullptr; + tcp_arg(client_control_block, this); } - if (server_control_block != nullptr) { - tcp_abort(server_control_block); - server_control_block = nullptr; + + auto it = listening_sockets.find(local_port); + if (it != listening_sockets.end() && it->second == &other) { + it->second = this; } + + if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == + OrderProtocol::sockets.end()) { + OrderProtocol::sockets.push_back(this); + } +} + +void ServerSocket::clear_packet_queues() { while (!tx_packet_buffer.empty()) { - pbuf_free(tx_packet_buffer.front()); + pbuf* packet = tx_packet_buffer.front(); tx_packet_buffer.pop(); + if (packet != nullptr) { + pbuf_free(packet); + } } while (!rx_packet_buffer.empty()) { - pbuf_free(rx_packet_buffer.front()); + pbuf* packet = rx_packet_buffer.front(); rx_packet_buffer.pop(); + if (packet != nullptr) { + pbuf_free(packet); + } + } +} + +ServerSocket::~ServerSocket() { + close(); + + auto listener_it = listening_sockets.find(local_port); + if (listener_it != listening_sockets.end() && listener_it->second == this) { + listening_sockets.erase(listener_it); + } + + auto it = std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this); + if (it != OrderProtocol::sockets.end()) { + OrderProtocol::sockets.erase(it); } } @@ -118,31 +189,53 @@ void ServerSocket::close() { } client_control_block = nullptr; } - while (!tx_packet_buffer.empty()) { - pbuf_free(tx_packet_buffer.front()); - tx_packet_buffer.pop(); - } - while (!rx_packet_buffer.empty()) { - pbuf_free(rx_packet_buffer.front()); - rx_packet_buffer.pop(); + if (server_control_block != nullptr) { + tcp_arg(server_control_block, nullptr); + tcp_accept(server_control_block, nullptr); + err_t close_error = tcp_close(server_control_block); + if (close_error != ERR_OK) { + tcp_abort(server_control_block); + } + server_control_block = nullptr; } - - listening_sockets[local_port] = this; + clear_packet_queues(); + rx_stream_buffer.clear(); state = CLOSED; - priority--; + auto listener_it = listening_sockets.find(local_port); + if (listener_it != listening_sockets.end() && listener_it->second == this) { + listening_sockets.erase(listener_it); + } + + if (priority > 1) { + priority--; + } } void ServerSocket::process_data() { while (!rx_packet_buffer.empty()) { - struct pbuf* packet = rx_packet_buffer.front(); + pbuf* packet = rx_packet_buffer.front(); rx_packet_buffer.pop(); - uint8_t* new_data = (uint8_t*)(packet->payload); - tcp_recved(client_control_block, packet->tot_len); - uint16_t id = Packet::get_id(new_data); - if (Order::orders.contains(id)) { - Order::orders[id]->store_ip_order(remote_ip.string_address); - Order::process_data(this, new_data); + if (packet == nullptr) { + continue; + } + + if (client_control_block != nullptr) { + tcp_recved(client_control_block, packet->tot_len); + } + + const size_t previous_size = rx_stream_buffer.size(); + const size_t append_size = packet->tot_len; + rx_stream_buffer.resize(previous_size + append_size); + if (pbuf_copy_partial( + packet, + rx_stream_buffer.data() + previous_size, + packet->tot_len, + 0 + ) == static_cast(packet->tot_len)) { + TcpOrderStreamParser::process(this, remote_ip, rx_stream_buffer); + } else { + rx_stream_buffer.resize(previous_size); } pbuf_free(packet); } @@ -152,23 +245,22 @@ bool ServerSocket::add_order_to_queue(Order& order) { if (state != ACCEPTED || client_control_block == nullptr) { return false; } - send(); if (tx_packet_buffer.size() >= MAX_TX_QUEUE_DEPTH) { return false; } - struct memp* next_memory_pointer_in_packet_buffer_pool = - (*(memp_pools[PBUF_POOL_MEMORY_DESC_POSITION]->tab))->next; - if (next_memory_pointer_in_packet_buffer_pool == nullptr) { + + const size_t order_size = order.get_size(); + if (order_size == 0 || order_size > TCP_SND_BUF) { return false; } uint8_t* order_buffer = order.build(); - struct pbuf* packet = pbuf_alloc(PBUF_TRANSPORT, order.get_size(), PBUF_RAM); + pbuf* packet = pbuf_alloc(PBUF_RAW, order_size, PBUF_RAM); if (packet == nullptr) { return false; } - if (pbuf_take(packet, order_buffer, order.get_size()) != ERR_OK) { + if (pbuf_take(packet, order_buffer, order_size) != ERR_OK) { pbuf_free(packet); return false; } @@ -176,73 +268,153 @@ bool ServerSocket::add_order_to_queue(Order& order) { return true; } -void ServerSocket::send() { - if (client_control_block == nullptr || state != ACCEPTED) { - return; +bool ServerSocket::try_send_immediately(Order& order) { + if (state != ACCEPTED || client_control_block == nullptr || !tx_packet_buffer.empty()) { + return false; } - if (tx_packet_buffer.empty()) { - return; + const size_t order_size = order.get_size(); + if (order_size == 0 || order_size > TCP_SND_BUF || + order_size > tcp_sndbuf(client_control_block)) { + return false; } - pbuf* temporal_packet_buffer = tx_packet_buffer.front(); - if (temporal_packet_buffer->tot_len > tcp_sndbuf(client_control_block)) { - return; + uint8_t* order_buffer = order.build(); + if (order_buffer == nullptr) { + return false; } - err_t error = tcp_write( - client_control_block, - temporal_packet_buffer->payload, - temporal_packet_buffer->tot_len, - TCP_WRITE_FLAG_COPY - ); + err_t error = tcp_write(client_control_block, order_buffer, order_size, TCP_WRITE_FLAG_COPY); if (error == ERR_OK) { - tx_packet_buffer.pop(); - tcp_output(client_control_block); - pbuf_free(temporal_packet_buffer); - } else if (error == ERR_MEM) { - // TX queue full on lwIP side: keep packet enqueued and retry later. - tcp_output(client_control_block); - return; - } else { - // Connection/state error: request graceful close instead of hard-stopping firmware. - state = CLOSING; + if (client_control_block != nullptr) { + tcp_output(client_control_block); + } + return true; + } + if (error == ERR_MEM) { + return false; + } + + state = CLOSING; + return false; +} + +bool ServerSocket::send_order(Order& order) { + if (state != ACCEPTED || client_control_block == nullptr) { + return false; + } + + if (try_send_immediately(order)) { + return true; + } + + if (!add_order_to_queue(order)) { + // One opportunistic flush avoids false negatives when TX queue is momentarily full. + send(); + if (!add_order_to_queue(order)) { + return false; + } + } + send(); + return true; +} + +void ServerSocket::send() { + if (client_control_block == nullptr || state != ACCEPTED) { return; } + + while (!tx_packet_buffer.empty()) { + pbuf* temporal_packet_buffer = tx_packet_buffer.front(); + if (temporal_packet_buffer == nullptr) { + tx_packet_buffer.pop(); + continue; + } + + if (temporal_packet_buffer->tot_len > tcp_sndbuf(client_control_block)) { + break; + } + + err_t error = tcp_write( + client_control_block, + temporal_packet_buffer->payload, + temporal_packet_buffer->tot_len, + TCP_WRITE_FLAG_COPY + ); + if (error == ERR_OK) { + tx_packet_buffer.pop(); + pbuf_free(temporal_packet_buffer); + } else if (error == ERR_MEM) { + break; + } else { + state = CLOSING; + break; + } + } + if (client_control_block != nullptr) { + tcp_output(client_control_block); + } } bool ServerSocket::is_connected() { return state == ServerSocket::ServerState::ACCEPTED; } +bool ServerSocket::is_listening() const { return state == ServerSocket::ServerState::LISTENING; } + err_t ServerSocket::accept_callback( void* arg, struct tcp_pcb* incomming_control_block, err_t error ) { - if (listening_sockets.contains(incomming_control_block->local_port)) { - ServerSocket* server_socket = listening_sockets[incomming_control_block->local_port]; - - server_socket->state = ACCEPTED; - server_socket->client_control_block = incomming_control_block; - server_socket->remote_ip = IPV4(incomming_control_block->remote_ip); - server_socket->rx_packet_buffer = {}; - - tcp_setprio(incomming_control_block, priority); - tcp_nagle_disable(incomming_control_block); - ip_set_option(incomming_control_block, SOF_REUSEADDR); - - tcp_arg(incomming_control_block, server_socket); - tcp_recv(incomming_control_block, receive_callback); - tcp_sent(incomming_control_block, send_callback); - tcp_err(incomming_control_block, error_callback); - tcp_poll(incomming_control_block, poll_callback, 1); - config_keepalive(incomming_control_block, server_socket); - - tcp_close(server_socket->server_control_block); + if (error != ERR_OK || incomming_control_block == nullptr) { + if (incomming_control_block != nullptr) { + tcp_abort(incomming_control_block); + } + return error; + } + + ServerSocket* server_socket = static_cast(arg); + if (server_socket == nullptr) { + auto it = listening_sockets.find(incomming_control_block->local_port); + if (it == listening_sockets.end()) { + tcp_abort(incomming_control_block); + return ERR_ABRT; + } + server_socket = it->second; + } + + server_socket->state = ACCEPTED; + server_socket->client_control_block = incomming_control_block; + server_socket->remote_ip = IPV4(incomming_control_block->remote_ip); + server_socket->rx_packet_buffer = {}; + server_socket->rx_stream_buffer.clear(); + server_socket->rx_stream_buffer.reserve(TcpOrderStreamParser::MAX_RX_STREAM_BUFFER_BYTES); + + tcp_setprio(incomming_control_block, priority); + tcp_nagle_disable(incomming_control_block); + ip_set_option(incomming_control_block, SOF_REUSEADDR); + + tcp_arg(incomming_control_block, server_socket); + tcp_recv(incomming_control_block, receive_callback); + tcp_sent(incomming_control_block, send_callback); + tcp_err(incomming_control_block, error_callback); + tcp_poll(incomming_control_block, poll_callback, 1); + config_keepalive(incomming_control_block, server_socket); + + if (server_socket->server_control_block != nullptr) { + tcp_arg(server_socket->server_control_block, nullptr); + tcp_accept(server_socket->server_control_block, nullptr); + err_t close_error = tcp_close(server_socket->server_control_block); + if (close_error != ERR_OK) { + tcp_abort(server_socket->server_control_block); + } + server_socket->server_control_block = nullptr; + } + + if (priority < 255) { priority++; + } - return ERR_OK; - } else - return ERROR; + return ERR_OK; } err_t ServerSocket::receive_callback( @@ -251,7 +423,13 @@ err_t ServerSocket::receive_callback( struct pbuf* packet_buffer, err_t error ) { - ServerSocket* server_socket = (ServerSocket*)arg; + ServerSocket* server_socket = static_cast(arg); + if (server_socket == nullptr) { + if (packet_buffer != nullptr) { + pbuf_free(packet_buffer); + } + return ERR_VAL; + } server_socket->client_control_block = client_control_block; if (packet_buffer == nullptr) { // FIN has been received @@ -263,39 +441,40 @@ err_t ServerSocket::receive_callback( if (packet_buffer != nullptr) { pbuf_free(packet_buffer); } - return error; - } else if (server_socket->state == ACCEPTED) { + // Keep the socket alive on transient lwIP receive errors. + return ERR_OK; + } + if (server_socket->state == ACCEPTED) { server_socket->rx_packet_buffer.push(packet_buffer); server_socket->process_data(); return ERR_OK; } - else if (server_socket->state == CLOSING) { // Socket is already closed - while (not server_socket->rx_packet_buffer.empty()) { - pbuf_free(server_socket->rx_packet_buffer.front()); - server_socket->rx_packet_buffer.pop(); - } - server_socket->rx_packet_buffer = {}; - pbuf_free(packet_buffer); - return ERR_OK; - } + pbuf_free(packet_buffer); return ERR_OK; } void ServerSocket::error_callback(void* arg, err_t error) { - ServerSocket* server_socket = (ServerSocket*)arg; - server_socket->close(); - ErrorHandler("Socket error: %d. Socket closed", error); + (void)error; + ServerSocket* server_socket = static_cast(arg); + if (server_socket == nullptr) { + return; + } + + // tcp_err is called once the pcb is already freed by lwIP. + server_socket->client_control_block = nullptr; + server_socket->state = CLOSED; + server_socket->clear_packet_queues(); + server_socket->rx_stream_buffer.clear(); } err_t ServerSocket::poll_callback(void* arg, struct tcp_pcb* client_control_block) { - ServerSocket* server_socket = (ServerSocket*)arg; - server_socket->client_control_block = client_control_block; - + ServerSocket* server_socket = static_cast(arg); if (server_socket == nullptr) { // Polling non existing pcb, fatal error tcp_abort(client_control_block); return ERR_ABRT; } + server_socket->client_control_block = client_control_block; if (!server_socket->tx_packet_buffer.empty()) { // TX FIFO is not empty server_socket->send(); @@ -313,7 +492,12 @@ err_t ServerSocket::poll_callback(void* arg, struct tcp_pcb* client_control_bloc } err_t ServerSocket::send_callback(void* arg, struct tcp_pcb* client_control_block, u16_t len) { - ServerSocket* server_socket = (ServerSocket*)arg; + (void)len; + ServerSocket* server_socket = static_cast(arg); + if (server_socket == nullptr) { + tcp_abort(client_control_block); + return ERR_ABRT; + } server_socket->client_control_block = client_control_block; if (!server_socket->tx_packet_buffer.empty()) { server_socket->send(); diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp index 1666cde53..8adb232c7 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp @@ -5,6 +5,7 @@ * Author: stefa */ #include "HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.hpp" +#include "HALAL/Services/Communication/Ethernet/LWIP/TCP/TcpOrderStreamParser.hpp" #include "ErrorHandler/ErrorHandler.hpp" #ifdef HAL_ETH_MODULE_ENABLED @@ -13,29 +14,102 @@ unordered_map Socket::connecting_sockets = {}; Socket::Socket() = default; Socket::Socket(Socket&& other) - : connection_control_block(move(other.connection_control_block)), - remote_port(move(remote_port)), state(other.state) { - EthernetNode remote_node(other.remote_ip, other.remote_port); - connecting_sockets[remote_node] = this; + : connection_control_block(other.connection_control_block), + socket_control_block(other.socket_control_block), + tx_packet_buffer(move(other.tx_packet_buffer)), + rx_packet_buffer(move(other.rx_packet_buffer)), + rx_stream_buffer(move(other.rx_stream_buffer)), local_ip(move(other.local_ip)), + local_port(other.local_port), remote_ip(move(other.remote_ip)), + remote_port(other.remote_port), state(other.state), + pending_connection_reset(other.pending_connection_reset), + connect_poll_ticks(other.connect_poll_ticks), use_keep_alives(other.use_keep_alives), + keepalive_config(other.keepalive_config) { + other.connection_control_block = nullptr; + other.socket_control_block = nullptr; + other.state = INACTIVE; + other.pending_connection_reset = false; + other.connect_poll_ticks = 0; + + if (connection_control_block != nullptr) { + tcp_arg(connection_control_block, this); + } + if (socket_control_block != nullptr) { + tcp_arg(socket_control_block, this); + } + + EthernetNode remote_node(remote_ip, remote_port); + auto it = connecting_sockets.find(remote_node); + if (it != connecting_sockets.end() && it->second == &other) { + it->second = this; + } + + if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == + OrderProtocol::sockets.end()) { + OrderProtocol::sockets.push_back(this); + } } void Socket::operator=(Socket&& other) { - connection_control_block = move(other.connection_control_block); - remote_port = move(other.remote_port); + if (this == &other) { + return; + } + close(); + + connection_control_block = other.connection_control_block; + socket_control_block = other.socket_control_block; + tx_packet_buffer = move(other.tx_packet_buffer); + rx_packet_buffer = move(other.rx_packet_buffer); + rx_stream_buffer = move(other.rx_stream_buffer); + local_ip = move(other.local_ip); + local_port = other.local_port; + remote_ip = move(other.remote_ip); + remote_port = other.remote_port; state = other.state; - EthernetNode remote_node(other.remote_ip, other.remote_port); - connecting_sockets[remote_node] = this; + pending_connection_reset = other.pending_connection_reset; + connect_poll_ticks = other.connect_poll_ticks; + use_keep_alives = other.use_keep_alives; + keepalive_config = other.keepalive_config; + + other.connection_control_block = nullptr; + other.socket_control_block = nullptr; + other.state = INACTIVE; + other.pending_connection_reset = false; + other.connect_poll_ticks = 0; + + if (connection_control_block != nullptr) { + tcp_arg(connection_control_block, this); + } + if (socket_control_block != nullptr) { + tcp_arg(socket_control_block, this); + } + + EthernetNode remote_node(remote_ip, remote_port); + auto it = connecting_sockets.find(remote_node); + if (it != connecting_sockets.end() && it->second == &other) { + it->second = this; + } + if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == - OrderProtocol::sockets.end()) + OrderProtocol::sockets.end()) { OrderProtocol::sockets.push_back(this); + } } Socket::~Socket() { + close(); + + for (auto it = connecting_sockets.begin(); it != connecting_sockets.end();) { + if (it->second == this) { + it = connecting_sockets.erase(it); + } else { + ++it; + } + } + auto it = std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this); - if (it == OrderProtocol::sockets.end()) - return; - else + if (it != OrderProtocol::sockets.end()) { OrderProtocol::sockets.erase(it); + } } Socket::Socket( @@ -54,18 +128,44 @@ Socket::Socket( state = INACTIVE; tx_packet_buffer = {}; rx_packet_buffer = {}; + rx_stream_buffer = {}; + rx_stream_buffer.reserve(TcpOrderStreamParser::MAX_RX_STREAM_BUFFER_BYTES); EthernetNode remote_node(remote_ip, remote_port); connection_control_block = tcp_new(); - tcp_bind(connection_control_block, &local_ip.address, local_port); + if (connection_control_block == nullptr) { + ErrorHandler("Cannot allocate TCP control block"); + return; + } + ip_set_option(connection_control_block, SOF_REUSEADDR); + + err_t bind_error = tcp_bind(connection_control_block, &local_ip.address, local_port); + if (bind_error != ERR_OK) { + tcp_abort(connection_control_block); + connection_control_block = nullptr; + ErrorHandler("Cannot bind TCP socket. Error code: %d", bind_error); + return; + } tcp_nagle_disable(connection_control_block); tcp_arg(connection_control_block, this); tcp_poll(connection_control_block, connection_poll_callback, 1); tcp_err(connection_control_block, connection_error_callback); connecting_sockets[remote_node] = this; - tcp_connect(connection_control_block, &remote_ip.address, remote_port, connect_callback); - OrderProtocol::sockets.push_back(this); + err_t connect_error = + tcp_connect(connection_control_block, &remote_ip.address, remote_port, connect_callback); + if (connect_error != ERR_OK && connect_error != ERR_ISCONN) { + connecting_sockets.erase(remote_node); + tcp_abort(connection_control_block); + connection_control_block = nullptr; + ErrorHandler("Cannot connect TCP socket. Error code: %d", connect_error); + return; + } + + if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == + OrderProtocol::sockets.end()) { + OrderProtocol::sockets.push_back(this); + } } Socket::Socket( @@ -86,113 +186,261 @@ Socket::Socket( Socket::Socket(EthernetNode local_node, EthernetNode remote_node) : Socket(local_node.ip, local_node.port, remote_node.ip, remote_node.port) {} -void Socket::close() { - tcp_arg(socket_control_block, nullptr); - tcp_sent(socket_control_block, nullptr); - tcp_recv(socket_control_block, nullptr); - tcp_err(socket_control_block, nullptr); - tcp_poll(socket_control_block, nullptr, 0); - +void Socket::clear_packet_queues() { while (!tx_packet_buffer.empty()) { - pbuf_free(tx_packet_buffer.front()); + pbuf* packet = tx_packet_buffer.front(); tx_packet_buffer.pop(); + if (packet != nullptr) { + pbuf_free(packet); + } } while (!rx_packet_buffer.empty()) { - pbuf_free(rx_packet_buffer.front()); + pbuf* packet = rx_packet_buffer.front(); rx_packet_buffer.pop(); + if (packet != nullptr) { + pbuf_free(packet); + } } +} - tcp_close(socket_control_block); +void Socket::close() { + if (socket_control_block != nullptr) { + tcp_arg(socket_control_block, nullptr); + tcp_sent(socket_control_block, nullptr); + tcp_recv(socket_control_block, nullptr); + tcp_err(socket_control_block, nullptr); + tcp_poll(socket_control_block, nullptr, 0); + + err_t close_error = tcp_close(socket_control_block); + if (close_error != ERR_OK) { + tcp_abort(socket_control_block); + } + } + if (connection_control_block != nullptr && connection_control_block != socket_control_block) { + tcp_arg(connection_control_block, nullptr); + tcp_poll(connection_control_block, nullptr, 0); + tcp_err(connection_control_block, nullptr); + tcp_abort(connection_control_block); + } + socket_control_block = nullptr; + connection_control_block = nullptr; + clear_packet_queues(); + rx_stream_buffer.clear(); + connect_poll_ticks = 0; + pending_connection_reset = false; state = INACTIVE; + + EthernetNode remote_node(remote_ip, remote_port); + auto it = connecting_sockets.find(remote_node); + if (it != connecting_sockets.end() && it->second == this) { + connecting_sockets.erase(it); + } } void Socket::reconnect() { + if (state == CONNECTED) { + return; + } + if (pending_connection_reset || connection_control_block == nullptr) { + reset(); + return; + } + + if (connection_control_block->state == SYN_SENT) { + return; + } + EthernetNode remote_node(remote_ip, remote_port); - if (!connecting_sockets.contains(remote_node)) { - connecting_sockets[remote_node] = this; + connecting_sockets[remote_node] = this; + + err_t connect_error = + tcp_connect(connection_control_block, &remote_ip.address, remote_port, connect_callback); + if (connect_error != ERR_OK && connect_error != ERR_ISCONN) { + pending_connection_reset = true; } - tcp_connect(connection_control_block, &remote_ip.address, remote_port, connect_callback); } void Socket::reset() { - EthernetNode remote_node(remote_ip, remote_port); - if (!connecting_sockets.contains(remote_node)) { - connecting_sockets[remote_node] = this; - } state = INACTIVE; - tcp_abort(connection_control_block); + socket_control_block = nullptr; + clear_packet_queues(); + rx_stream_buffer.clear(); + connect_poll_ticks = 0; + + if (connection_control_block != nullptr) { + tcp_arg(connection_control_block, nullptr); + tcp_poll(connection_control_block, nullptr, 0); + tcp_err(connection_control_block, nullptr); + tcp_abort(connection_control_block); + } connection_control_block = tcp_new(); + if (connection_control_block == nullptr) { + pending_connection_reset = true; + return; + } + ip_set_option(connection_control_block, SOF_REUSEADDR); - tcp_bind(connection_control_block, &local_ip.address, local_port); + err_t bind_error = tcp_bind(connection_control_block, &local_ip.address, local_port); + if (bind_error != ERR_OK) { + tcp_abort(connection_control_block); + connection_control_block = nullptr; + pending_connection_reset = true; + return; + } tcp_nagle_disable(connection_control_block); tcp_arg(connection_control_block, this); tcp_poll(connection_control_block, connection_poll_callback, 1); tcp_err(connection_control_block, connection_error_callback); - tcp_connect(connection_control_block, &remote_ip.address, remote_port, connect_callback); + EthernetNode remote_node(remote_ip, remote_port); + connecting_sockets[remote_node] = this; + + err_t connect_error = + tcp_connect(connection_control_block, &remote_ip.address, remote_port, connect_callback); + pending_connection_reset = !(connect_error == ERR_OK || connect_error == ERR_ISCONN); } void Socket::send() { - pbuf* temporal_packet_buffer; - err_t error = ERR_OK; - while (error == ERR_OK && !tx_packet_buffer.empty() && - tx_packet_buffer.front()->len <= tcp_sndbuf(socket_control_block)) { - temporal_packet_buffer = tx_packet_buffer.front(); - error = tcp_write( + if (state != CONNECTED || socket_control_block == nullptr) { + return; + } + + while (!tx_packet_buffer.empty()) { + pbuf* temporal_packet_buffer = tx_packet_buffer.front(); + if (temporal_packet_buffer == nullptr) { + tx_packet_buffer.pop(); + continue; + } + + if (temporal_packet_buffer->tot_len > tcp_sndbuf(socket_control_block)) { + break; + } + + err_t error = tcp_write( socket_control_block, temporal_packet_buffer->payload, - temporal_packet_buffer->len, + temporal_packet_buffer->tot_len, TCP_WRITE_FLAG_COPY ); if (error == ERR_OK) { tx_packet_buffer.pop(); - tcp_output(socket_control_block); - memp_free_pool(memp_pools[PBUF_POOL_MEMORY_DESC_POSITION], temporal_packet_buffer); + pbuf_free(temporal_packet_buffer); + } else if (error == ERR_MEM) { + break; } else { - if (error == ERR_MEM) { - close(); - ErrorHandler("Too many unacked messages on client socket, disconnecting..."); - } else { - ErrorHandler("Cannot write to client socket. Error code: %d", error); - } + state = CLOSING; + break; } } + if (socket_control_block != nullptr) { + tcp_output(socket_control_block); + } } void Socket::process_data() { while (!rx_packet_buffer.empty()) { - struct pbuf* packet = rx_packet_buffer.front(); + pbuf* packet = rx_packet_buffer.front(); rx_packet_buffer.pop(); - uint8_t* new_data = (uint8_t*)(packet->payload); - tcp_recved(socket_control_block, packet->tot_len); - uint16_t id = Packet::get_id(new_data); - if (Order::orders.contains(id)) { - Order::orders[id]->store_ip_order(remote_ip.string_address); - Order::process_data(this, new_data); + if (packet == nullptr) { + continue; + } + + if (socket_control_block != nullptr) { + tcp_recved(socket_control_block, packet->tot_len); } + const size_t previous_size = rx_stream_buffer.size(); + const size_t append_size = packet->tot_len; + rx_stream_buffer.resize(previous_size + append_size); + if (pbuf_copy_partial( + packet, + rx_stream_buffer.data() + previous_size, + packet->tot_len, + 0 + ) == static_cast(packet->tot_len)) { + TcpOrderStreamParser::process(this, remote_ip, rx_stream_buffer); + } else { + rx_stream_buffer.resize(previous_size); + } pbuf_free(packet); } } -bool Socket::add_order_to_queue(Order& order) { - if (state == Socket::SocketState::CONNECTED) { +bool Socket::try_send_immediately(Order& order) { + if (state != CONNECTED || socket_control_block == nullptr || !tx_packet_buffer.empty()) { return false; } - struct memp* next_memory_pointer_in_packet_buffer_pool = - (*(memp_pools[PBUF_POOL_MEMORY_DESC_POSITION]->tab))->next; - if (next_memory_pointer_in_packet_buffer_pool == nullptr) { - memp_free_pool( - memp_pools[PBUF_POOL_MEMORY_DESC_POSITION], - next_memory_pointer_in_packet_buffer_pool - ); + + const size_t order_size = order.get_size(); + if (order_size == 0 || order_size > TCP_SND_BUF || + order_size > tcp_sndbuf(socket_control_block)) { return false; } uint8_t* order_buffer = order.build(); + if (order_buffer == nullptr) { + return false; + } + + err_t error = tcp_write(socket_control_block, order_buffer, order_size, TCP_WRITE_FLAG_COPY); + if (error == ERR_OK) { + if (socket_control_block != nullptr) { + tcp_output(socket_control_block); + } + return true; + } + if (error == ERR_MEM) { + return false; + } + + state = CLOSING; + return false; +} + +bool Socket::send_order(Order& order) { + if (state != CONNECTED || socket_control_block == nullptr) { + reconnect(); + return false; + } + + if (try_send_immediately(order)) { + return true; + } + + if (!add_order_to_queue(order)) { + // One opportunistic flush avoids false negatives when TX queue is momentarily full. + send(); + if (!add_order_to_queue(order)) { + return false; + } + } + send(); + return true; +} + +bool Socket::add_order_to_queue(Order& order) { + if (state != Socket::SocketState::CONNECTED || socket_control_block == nullptr) { + return false; + } + + if (tx_packet_buffer.size() >= MAX_TX_QUEUE_DEPTH) { + return false; + } + + const size_t order_size = order.get_size(); + if (order_size == 0 || order_size > TCP_SND_BUF) { + return false; + } - struct pbuf* packet = pbuf_alloc(PBUF_TRANSPORT, order.get_size(), PBUF_POOL); - pbuf_take(packet, order_buffer, order.get_size()); + uint8_t* order_buffer = order.build(); + pbuf* packet = pbuf_alloc(PBUF_RAW, order_size, PBUF_RAM); + if (packet == nullptr) { + return false; + } + if (pbuf_take(packet, order_buffer, order_size) != ERR_OK) { + pbuf_free(packet); + return false; + } Socket::tx_packet_buffer.push(packet); return true; } @@ -200,29 +448,43 @@ bool Socket::add_order_to_queue(Order& order) { bool Socket::is_connected() { return state == Socket::SocketState::CONNECTED; } err_t Socket::connect_callback(void* arg, struct tcp_pcb* client_control_block, err_t error) { - IPV4 remote_ip; - remote_ip.address = client_control_block->remote_ip; - EthernetNode remote_node(remote_ip, client_control_block->remote_port); - - if (connecting_sockets.contains(remote_node)) { - Socket* socket = connecting_sockets[remote_node]; - connecting_sockets.erase(remote_node); + Socket* socket = static_cast(arg); + if (socket == nullptr || client_control_block == nullptr) { + return ERR_VAL; + } + if (error != ERR_OK) { + socket->state = INACTIVE; + socket->pending_connection_reset = true; + return error; + } - socket->socket_control_block = client_control_block; - socket->state = CONNECTED; + socket->connection_control_block = client_control_block; + socket->socket_control_block = client_control_block; + socket->rx_stream_buffer.clear(); + socket->state = CONNECTED; + socket->pending_connection_reset = false; + socket->connect_poll_ticks = 0; + + tcp_nagle_disable(client_control_block); + tcp_arg(client_control_block, socket); + tcp_recv(client_control_block, receive_callback); + tcp_poll(client_control_block, poll_callback, 1); + tcp_sent(client_control_block, send_callback); + tcp_err(client_control_block, error_callback); + if (socket->use_keep_alives) { + config_keepalive(client_control_block, socket); + } - tcp_nagle_disable(client_control_block); - tcp_arg(client_control_block, socket); - tcp_recv(client_control_block, receive_callback); - tcp_poll(client_control_block, poll_callback, 0); - tcp_sent(client_control_block, send_callback); - tcp_err(client_control_block, error_callback); - if (socket->use_keep_alives) - config_keepalive(client_control_block, socket); + EthernetNode remote_node(socket->remote_ip, socket->remote_port); + auto it = connecting_sockets.find(remote_node); + if (it != connecting_sockets.end() && it->second == socket) { + connecting_sockets.erase(it); + } - return ERR_OK; - } else - return ERROR; + if (!socket->tx_packet_buffer.empty()) { + socket->send(); + } + return ERR_OK; } err_t Socket::receive_callback( @@ -231,7 +493,13 @@ err_t Socket::receive_callback( struct pbuf* packet_buffer, err_t error ) { - Socket* socket = (Socket*)arg; + Socket* socket = static_cast(arg); + if (socket == nullptr) { + if (packet_buffer != nullptr) { + pbuf_free(packet_buffer); + } + return ERR_VAL; + } socket->socket_control_block = client_control_block; if (packet_buffer == nullptr) { // FIN is received socket->state = CLOSING; @@ -241,47 +509,45 @@ err_t Socket::receive_callback( if (packet_buffer != nullptr) { pbuf_free(packet_buffer); } - return error; - } else if (socket->state == CONNECTED) { + // Keep the socket alive on transient lwIP receive errors. + return ERR_OK; + } + if (socket->state == CONNECTED) { socket->rx_packet_buffer.push(packet_buffer); - tcp_recved(client_control_block, packet_buffer->tot_len); socket->process_data(); - pbuf_free(packet_buffer); - return ERR_OK; - } else { - tcp_recved(client_control_block, packet_buffer->tot_len); - socket->tx_packet_buffer = {}; - pbuf_free(packet_buffer); return ERR_OK; } + pbuf_free(packet_buffer); + return ERR_OK; } err_t Socket::poll_callback(void* arg, struct tcp_pcb* client_control_block) { - Socket* socket = (Socket*)arg; - socket->socket_control_block = client_control_block; - if (socket != nullptr) { - while (not socket->tx_packet_buffer.empty()) { - socket->send(); - } - if (socket->state == CLOSING) { - socket->close(); - } else if (socket->state == INACTIVE) { - tcp_connect( - socket->connection_control_block, - &socket->remote_ip.address, - socket->remote_port, - connect_callback - ); - } - return ERR_OK; - } else { + Socket* socket = static_cast(arg); + if (socket == nullptr) { tcp_abort(client_control_block); return ERR_ABRT; } + socket->socket_control_block = client_control_block; + + if (!socket->tx_packet_buffer.empty()) { + socket->send(); + } + if (!socket->rx_packet_buffer.empty()) { + socket->process_data(); + } + if (socket->state == CLOSING) { + socket->close(); + } + return ERR_OK; } err_t Socket::send_callback(void* arg, struct tcp_pcb* client_control_block, uint16_t length) { - Socket* socket = (Socket*)arg; + (void)length; + Socket* socket = static_cast(arg); + if (socket == nullptr) { + tcp_abort(client_control_block); + return ERR_ABRT; + } socket->socket_control_block = client_control_block; if (not socket->tx_packet_buffer.empty()) { socket->send(); @@ -292,35 +558,61 @@ err_t Socket::send_callback(void* arg, struct tcp_pcb* client_control_block, uin } void Socket::error_callback(void* arg, err_t error) { - Socket* socket = (Socket*)arg; - socket->close(); - ErrorHandler( - "Client socket error: %d. Socket closed, remote ip: %s", - error, - socket->remote_ip.string_address.c_str() - ); + (void)error; + Socket* socket = static_cast(arg); + if (socket == nullptr) { + return; + } + + // tcp_err is called once the pcb is already freed by lwIP. + socket->socket_control_block = nullptr; + socket->connection_control_block = nullptr; + socket->state = INACTIVE; + socket->pending_connection_reset = true; + socket->connect_poll_ticks = 0; + socket->clear_packet_queues(); + socket->rx_stream_buffer.clear(); } void Socket::connection_error_callback(void* arg, err_t error) { - if (error == ERR_RST) { - Socket* socket = (Socket*)arg; - - socket->pending_connection_reset = true; - return; - } else if (error == ERR_ABRT) { + (void)error; + Socket* socket = static_cast(arg); + if (socket == nullptr) { return; } - ErrorHandler("Connection socket error: %d. Couldn t start client socket ", error); + + socket->connection_control_block = nullptr; + socket->socket_control_block = nullptr; + socket->state = INACTIVE; + socket->pending_connection_reset = true; + socket->connect_poll_ticks = 0; + socket->rx_stream_buffer.clear(); } err_t Socket::connection_poll_callback(void* arg, struct tcp_pcb* connection_control_block) { - Socket* socket = (Socket*)arg; + Socket* socket = static_cast(arg); + if (socket == nullptr) { + if (connection_control_block != nullptr) { + tcp_abort(connection_control_block); + } + return ERR_ABRT; + } + + socket->connection_control_block = connection_control_block; if (socket->pending_connection_reset) { - socket->reset(); socket->pending_connection_reset = false; + socket->reset(); return ERR_ABRT; - } else if (socket->connection_control_block->state == SYN_SENT) { - socket->pending_connection_reset = true; + } + + if (socket->state != CONNECTED && connection_control_block != nullptr && + connection_control_block->state == SYN_SENT) { + socket->connect_poll_ticks++; + if (socket->connect_poll_ticks >= 20) { + socket->pending_connection_reset = true; + } + } else { + socket->connect_poll_ticks = 0; } return ERR_OK; } diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp index eac562494..a433d4807 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp @@ -13,9 +13,12 @@ DatagramSocket::DatagramSocket() = default; DatagramSocket::DatagramSocket(DatagramSocket&& other) - : udp_control_block(move(other.udp_control_block)), local_ip(move(other.local_ip)), - local_port(move(other.local_port)), remote_ip(move(other.remote_ip)), - remote_port(move(remote_port)) {} + : udp_control_block(other.udp_control_block), local_ip(move(other.local_ip)), + local_port(other.local_port), remote_ip(move(other.remote_ip)), + remote_port(other.remote_port), is_disconnected(other.is_disconnected) { + other.udp_control_block = nullptr; + other.is_disconnected = true; +} DatagramSocket::DatagramSocket( IPV4 local_ip, @@ -29,6 +32,10 @@ DatagramSocket::DatagramSocket( return; } udp_control_block = udp_new(); + if (udp_control_block == nullptr) { + ErrorHandler("Cannot allocate UDP control block"); + return; + } err_t error = udp_bind(udp_control_block, &local_ip.address, local_port); if (error == ERR_OK) { @@ -38,6 +45,7 @@ DatagramSocket::DatagramSocket( Ethernet::update(); } else { udp_remove(udp_control_block); + udp_control_block = nullptr; is_disconnected = true; ErrorHandler("Error binding UDP socket"); } @@ -52,16 +60,40 @@ DatagramSocket::~DatagramSocket() { } void DatagramSocket::operator=(DatagramSocket&& other) { - udp_control_block = move(other.udp_control_block); + if (this == &other) { + return; + } + close(); + + udp_control_block = other.udp_control_block; local_ip = move(other.local_ip); - local_port = move(other.local_port); - remote_ip = other.remote_ip; + local_port = other.local_port; + remote_ip = move(other.remote_ip); remote_port = other.remote_port; + is_disconnected = other.is_disconnected; + + other.udp_control_block = nullptr; other.is_disconnected = true; } void DatagramSocket::reconnect() { - udp_disconnect(udp_control_block); + if (not Ethernet::is_running) { + is_disconnected = true; + return; + } + + if (udp_control_block != nullptr) { + udp_disconnect(udp_control_block); + udp_remove(udp_control_block); + udp_control_block = nullptr; + } + + udp_control_block = udp_new(); + if (udp_control_block == nullptr) { + is_disconnected = true; + return; + } + is_disconnected = true; err_t error = udp_bind(udp_control_block, &local_ip.address, local_port); @@ -72,14 +104,18 @@ void DatagramSocket::reconnect() { Ethernet::update(); } else { udp_remove(udp_control_block); + udp_control_block = nullptr; is_disconnected = true; ErrorHandler("Error binding UDP socket"); } } void DatagramSocket::close() { - udp_disconnect(udp_control_block); - udp_remove(udp_control_block); + if (udp_control_block != nullptr) { + udp_disconnect(udp_control_block); + udp_remove(udp_control_block); + udp_control_block = nullptr; + } is_disconnected = true; } @@ -90,8 +126,26 @@ void DatagramSocket::receive_callback( const ip_addr_t* remote_address, u16_t port ) { - uint8_t* received_data = (uint8_t*)packet_buffer->payload; - Packet::parse_data(received_data); + (void)args; + (void)udp_control_block; + (void)remote_address; + (void)port; + + if (packet_buffer == nullptr) { + return; + } + + if (packet_buffer->tot_len >= sizeof(uint16_t)) { + if (packet_buffer->len == packet_buffer->tot_len && packet_buffer->payload != nullptr) { + Packet::parse_data(static_cast(packet_buffer->payload)); + } else { + vector data(packet_buffer->tot_len); + if (pbuf_copy_partial(packet_buffer, data.data(), packet_buffer->tot_len, 0) == + static_cast(packet_buffer->tot_len)) { + Packet::parse_data(data.data()); + } + } + } pbuf_free(packet_buffer); } diff --git a/Src/HALAL/Services/Communication/SNTP/SNTP.cpp b/Src/HALAL/Services/Communication/SNTP/SNTP.cpp index 3c37b415b..4b531be44 100644 --- a/Src/HALAL/Services/Communication/SNTP/SNTP.cpp +++ b/Src/HALAL/Services/Communication/SNTP/SNTP.cpp @@ -10,7 +10,20 @@ #define SUBSECONDS_PER_SECOND 32767 #define TRANSFORMATION_FACTOR (SUBSECONDS_PER_SECOND / 999999.0) -#define TARGET_IP "192.168.0.9" + +namespace { + +void configure_sntp_server(const ip_addr_t& address) { + if (sntp_enabled()) { + sntp_stop(); + } + + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setserver(0, &address); + sntp_init(); +} + +} // namespace void SNTP::sntp_update( uint8_t address_head, @@ -18,28 +31,19 @@ void SNTP::sntp_update( uint8_t address_third, uint8_t address_last ) { - // sntp_setoperatingmode(SNTP_OPMODE_POLL); - // ip4_addr_t* address; - // IP_ADDR4(address,address_head,address_second,address_third,address_last); - // sntp_setserver(0,address); - // sntp_init(); + ip_addr_t address; + IP_ADDR4(&address, address_head, address_second, address_third, address_last); + configure_sntp_server(address); } void SNTP::sntp_update(string ip) { - // sntp_setoperatingmode(SNTP_OPMODE_POLL); - // IPV4 target(ip); - // sntp_setserver(0,&target.address); - // sntp_init(); + IPV4 target(ip.c_str()); + configure_sntp_server(target.address); } -void SNTP::sntp_update() { - // sntp_setoperatingmode(SNTP_OPMODE_POLL); - // IPV4 target(TARGET_IP); - // sntp_setserver(0,&target.address); - // sntp_init(); -} +void SNTP::sntp_update() { sntp_update(DEFAULT_SERVER_IP); } -void set_rtc( +extern "C" void stlib_sntp_set_rtc( uint16_t counter, uint8_t second, uint8_t minute, @@ -51,8 +55,12 @@ void set_rtc( Global_RTC::set_rtc_data(counter, second, minute, hour, day, month, year); } -u32_t get_rtc_s() { - RTCData rtc_time = Global_RTC::get_rtc_timestamp(); +extern "C" u32_t stlib_sntp_get_rtc_seconds() { + if (!Global_RTC::has_valid_time()) { + return 0; + } + + const RTCData rtc_time = Global_RTC::get_rtc_timestamp(); time_t nowtime = 0; struct tm* nowtm; nowtm = gmtime(&nowtime); @@ -66,19 +74,23 @@ u32_t get_rtc_s() { return sec; } -u32_t get_rtc_us() { - RTCData rtc_time = Global_RTC::get_rtc_timestamp(); +extern "C" u32_t stlib_sntp_get_rtc_microseconds() { + if (!Global_RTC::has_valid_time()) { + return 0; + } + + const RTCData rtc_time = Global_RTC::get_rtc_timestamp(); return rtc_time.counter / TRANSFORMATION_FACTOR; } -void set_time(uint32_t sec, uint32_t us) { +extern "C" void stlib_sntp_set_time(uint32_t sec, uint32_t us) { struct timeval tv; tv.tv_sec = sec; tv.tv_usec = us; time_t nowtime = sec; struct tm* nowtm = localtime(&nowtime); uint32_t subsecond = (uint32_t)(TRANSFORMATION_FACTOR * tv.tv_usec); - set_rtc( + stlib_sntp_set_rtc( subsecond, nowtm->tm_sec, nowtm->tm_min, diff --git a/Src/HALAL/Services/Communication/UART/UART.cpp b/Src/HALAL/Services/Communication/UART/UART.cpp index ba613e899..c820b8758 100644 --- a/Src/HALAL/Services/Communication/UART/UART.cpp +++ b/Src/HALAL/Services/Communication/UART/UART.cpp @@ -152,6 +152,10 @@ bool UART::set_up_printf(UART::Peripheral& uart) { return false; } + if (!UART::available_uarts.contains(uart)) { + return false; + } + UART::printf_uart = UART::inscribe(uart); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); @@ -162,12 +166,11 @@ bool UART::set_up_printf(UART::Peripheral& uart) { } void UART::print_by_uart(char* ptr, int len) { - if (!UART::printf_ready) { + if (!UART::printf_ready || ptr == nullptr || len <= 0) { return; } - vector data(ptr, ptr + len); - + span data(reinterpret_cast(ptr), static_cast(len)); UART::transmit_polling(UART::printf_uart, data); } @@ -220,6 +223,17 @@ UART_HandleTypeDef* UART::get_handle(uint8_t id) { return registered_uart[id]->h extern "C" { #endif +int __io_putchar(int ch) { + if (!UART::printf_ready) { + return ch; + } + + UART::transmit_polling(UART::printf_uart, static_cast(ch)); + return ch; +} + +int __io_getchar(void) { return -1; } + #ifdef __cplusplus } #endif diff --git a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp index fb3e8cbc6..ad7da2caf 100644 --- a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp +++ b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp @@ -6,13 +6,237 @@ */ #include "HALAL/Services/InfoWarning/InfoWarning.hpp" +#include "HALAL/Services/Communication/UART/UART.hpp" +#include "HALAL/Services/Time/RTC.hpp" #include "HALAL/Services/Time/Scheduler.hpp" +#ifdef STLIB_ETH +#include "HALAL/Models/Packets/Order.hpp" +#endif + +namespace { + +#ifdef STLIB_ETH +constexpr uint16_t INFO_WARNING_TCP_ORDER_ID = 2555; +constexpr uint8_t INFO_WARNING_BOUNDARY_TYPE_ID = 5; +#endif + +bool warning_sent_via_tcp = false; +bool warning_sent_via_uart = false; +bool tcp_delivery_required = false; +bool uart_delivery_required = false; + +#ifdef STLIB_ETH +uint8_t warning_padding = 0; +uint8_t warning_boundary_type = INFO_WARNING_BOUNDARY_TYPE_ID; +string warning_name = "info_warning"; +string warning_message = "Warning-No-Description-Found"; +uint16_t warning_counter = 0; +uint8_t warning_second = 0; +uint8_t warning_minute = 0; +uint8_t warning_hour = 0; +uint8_t warning_day = 0; +uint8_t warning_month = 0; +uint16_t warning_year = 0; + +class InfoWarningOrder final : public Order { +public: + void set_callback(void (*)(void)) override {} + + void process() override {} + + void parse(OrderProtocol* socket, uint8_t* data) override { + (void)socket; + (void)data; + } + + uint8_t* build() override { + const size_t total_size = get_size(); + if (buffer.size() != total_size) { + buffer.resize(total_size); + } + + uint8_t* data = buffer.data(); + append(data, &id, sizeof(id)); + append(data, &warning_padding, sizeof(warning_padding)); + append(data, &warning_boundary_type, sizeof(warning_boundary_type)); + append(data, warning_name.c_str(), warning_name.size() + 1); + append(data, warning_message.c_str(), warning_message.size() + 1); + append(data, &warning_counter, sizeof(warning_counter)); + append(data, &warning_second, sizeof(warning_second)); + append(data, &warning_minute, sizeof(warning_minute)); + append(data, &warning_hour, sizeof(warning_hour)); + append(data, &warning_day, sizeof(warning_day)); + append(data, &warning_month, sizeof(warning_month)); + append(data, &warning_year, sizeof(warning_year)); + return buffer.data(); + } + + size_t get_size() override { + size = sizeof(id) + sizeof(warning_padding) + sizeof(warning_boundary_type) + + warning_name.size() + 1 + warning_message.size() + 1 + sizeof(warning_counter) + + sizeof(warning_second) + sizeof(warning_minute) + sizeof(warning_hour) + + sizeof(warning_day) + sizeof(warning_month) + sizeof(warning_year); + return size; + } + + uint16_t get_id() override { return id; } + + void set_pointer(size_t index, void* pointer) override { + (void)index; + (void)pointer; + } + +private: + static void append(uint8_t*& dst, const void* src, size_t count) { + memcpy(dst, src, count); + dst += count; + } + + static constexpr uint16_t id = INFO_WARNING_TCP_ORDER_ID; + vector buffer{}; +}; + +InfoWarningOrder info_warning_order; + +void refresh_warning_transport_state(const string& description) { + warning_message = description; + +#ifdef HAL_RTC_MODULE_ENABLED + if (Global_RTC::ensure_started()) { + Global_RTC::update_rtc_data(); + warning_counter = Global_RTC::global_RTC.counter; + warning_second = Global_RTC::global_RTC.second; + warning_minute = Global_RTC::global_RTC.minute; + warning_hour = Global_RTC::global_RTC.hour; + warning_day = Global_RTC::global_RTC.day; + warning_month = Global_RTC::global_RTC.month; + warning_year = Global_RTC::global_RTC.year; + return; + } +#endif + + warning_counter = 0; + warning_second = 0; + warning_minute = 0; + warning_hour = 0; + warning_day = 0; + warning_month = 0; + warning_year = 0; +} +#endif + +bool try_send_warning_via_tcp(const string& description) { +#ifdef STLIB_ETH + if (!tcp_delivery_required || warning_sent_via_tcp) { + return true; + } + + refresh_warning_transport_state(description); -string InfoWarning::description = "Error-No-Description-Found"; -string InfoWarning::line = "Error-No-Line-Found"; -string InfoWarning::func = "Error-No-Func-Found"; -string InfoWarning::file = "Error-No-File-Found"; + bool delivered = false; + for (OrderProtocol* socket : OrderProtocol::sockets) { + if (socket == nullptr) { + continue; + } + delivered = socket->send_order(info_warning_order) || delivered; + } + + if (delivered) { + warning_sent_via_tcp = true; + } + return warning_sent_via_tcp; +#else + (void)description; + return true; +#endif +} + +bool try_send_warning_via_uart(const string& description) { +#ifdef HAL_UART_MODULE_ENABLED + if (!uart_delivery_required || warning_sent_via_uart) { + return true; + } + + if (!UART::printf_ready) { + return false; + } + + printf("Warning: %s%s", description.c_str(), endl); + warning_sent_via_uart = true; + return true; +#else + (void)description; + return true; +#endif +} + +void append_readable_timestamp(string& message) { +#ifdef HAL_RTC_MODULE_ENABLED + if (Global_RTC::ensure_started() && Global_RTC::has_valid_time()) { + Global_RTC::update_rtc_data(); + const RTCData& timestamp = Global_RTC::global_RTC; + char buffer[80]{}; + snprintf( + buffer, + sizeof(buffer), + " | Timestamp: %04u-%02u-%02u %02u:%02u:%02u.%05u", + static_cast(timestamp.year), + static_cast(timestamp.month), + static_cast(timestamp.day), + static_cast(timestamp.hour), + static_cast(timestamp.minute), + static_cast(timestamp.second), + static_cast(timestamp.counter) + ); + message += buffer; + return; + } +#endif + +#ifdef HAL_TIM_MODULE_ENABLED + const uint64_t uptime_us = Scheduler::get_global_tick(); + const uint64_t total_seconds = uptime_us / 1'000'000ULL; + const uint64_t days = total_seconds / 86'400ULL; + const unsigned hours = static_cast((total_seconds / 3'600ULL) % 24ULL); + const unsigned minutes = static_cast((total_seconds / 60ULL) % 60ULL); + const unsigned seconds = static_cast(total_seconds % 60ULL); + const unsigned micros = static_cast(uptime_us % 1'000'000ULL); + + char buffer[80]{}; + if (days > 0) { + snprintf( + buffer, + sizeof(buffer), + " | Uptime: %llud %02u:%02u:%02u.%06u", + static_cast(days), + hours, + minutes, + seconds, + micros + ); + } else { + snprintf( + buffer, + sizeof(buffer), + " | Uptime: %02u:%02u:%02u.%06u", + hours, + minutes, + seconds, + micros + ); + } + message += buffer; +#endif +} + +} // namespace + +string InfoWarning::description = "Warning-No-Description-Found"; +string InfoWarning::line = "Warning-No-Line-Found"; +string InfoWarning::func = "Warning-No-Func-Found"; +string InfoWarning::file = "Warning-No-File-Found"; bool InfoWarning::warning_triggered = false; +bool InfoWarning::warning_to_communicate = false; void InfoWarning::SetMetaData(int line, const char* func, const char* file) { InfoWarning::line = to_string(line); @@ -26,6 +250,19 @@ void InfoWarning::InfoWarningTrigger(string format, ...) { } InfoWarning::warning_triggered = true; + InfoWarning::warning_to_communicate = true; + warning_sent_via_tcp = false; + warning_sent_via_uart = false; +#ifdef STLIB_ETH + tcp_delivery_required = true; +#else + tcp_delivery_required = false; +#endif +#ifdef HAL_UART_MODULE_ENABLED + uart_delivery_required = UART::printf_ready; +#else + uart_delivery_required = false; +#endif if (format.length() != 0) { description = ""; @@ -46,14 +283,21 @@ void InfoWarning::InfoWarningTrigger(string format, ...) { description += string(buffer.get(), buffer.get() + size - 1) + " | Line: " + InfoWarning::line + " Function: '" + InfoWarning::func + "' File: " + InfoWarning::file; -#ifdef HAL_TIM_MODULE_ENABLED - description += " | TimeStamp: " + to_string(Scheduler::get_global_tick()); -#endif + append_readable_timestamp(description); + + InfoWarning::InfoWarningUpdate(); } void InfoWarning::InfoWarningUpdate() { - if (!InfoWarning::warning_triggered) { + if (!InfoWarning::warning_to_communicate) { return; } - printf("Warning: %s\n", InfoWarning::description.c_str()); + + const bool tcp_done = try_send_warning_via_tcp(InfoWarning::description); + const bool uart_done = try_send_warning_via_uart(InfoWarning::description); + + if (tcp_done && uart_done) { + InfoWarning::warning_to_communicate = false; + InfoWarning::warning_triggered = false; + } } diff --git a/Src/HALAL/Services/Time/RTC.cpp b/Src/HALAL/Services/Time/RTC.cpp index a5700a978..6fbe0edb8 100644 --- a/Src/HALAL/Services/Time/RTC.cpp +++ b/Src/HALAL/Services/Time/RTC.cpp @@ -3,7 +3,18 @@ RTC_HandleTypeDef hrtc; RTCData Global_RTC::global_RTC; +namespace { +bool rtc_started = false; +bool rtc_start_in_progress = false; +bool rtc_time_valid = false; +} // namespace + void Global_RTC::start_rtc() { + if (rtc_started || rtc_start_in_progress) { + return; + } + + rtc_start_in_progress = true; RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; @@ -17,7 +28,9 @@ void Global_RTC::start_rtc() { hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE; if (HAL_RTC_Init(&hrtc) != HAL_OK) { + rtc_start_in_progress = false; ErrorHandler("Error on RTC Init"); + return; } sTime.Hours = 0x0; sTime.Minutes = 0x0; @@ -26,7 +39,9 @@ void Global_RTC::start_rtc() { sTime.StoreOperation = RTC_STOREOPERATION_RESET; if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { + rtc_start_in_progress = false; ErrorHandler("Error while setting time at RTC start"); + return; } sDate.WeekDay = RTC_WEEKDAY_MONDAY; @@ -35,10 +50,25 @@ void Global_RTC::start_rtc() { sDate.Year = 23; if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) { + rtc_start_in_progress = false; ErrorHandler("Error while setting date at RTC start"); + return; + } + + rtc_time_valid = false; + rtc_started = true; + rtc_start_in_progress = false; +} + +bool Global_RTC::ensure_started() { + if (!rtc_started && !rtc_start_in_progress) { + start_rtc(); } + return rtc_started; } +bool Global_RTC::has_valid_time() { return rtc_time_valid; } + RTCData Global_RTC::get_rtc_timestamp() { RTCData ret; RTC_TimeTypeDef gTime; @@ -64,6 +94,10 @@ void Global_RTC::set_rtc_data( uint8_t month, uint16_t year ) { + if (!ensure_started()) { + return; + } + RTC_TimeTypeDef gTime; RTC_DateTypeDef gDate; gTime.SubSeconds = counter; @@ -76,11 +110,27 @@ void Global_RTC::set_rtc_data( gDate.Date = day; gDate.Month = month; gDate.Year = year - 2000; + bool write_ok = true; if (HAL_RTC_SetTime(&hrtc, &gTime, RTC_FORMAT_BIN) != HAL_OK) { + write_ok = false; ErrorHandler("Error on writing Time on the RTC"); } if (HAL_RTC_SetDate(&hrtc, &gDate, RTC_FORMAT_BIN) != HAL_OK) { + write_ok = false; ErrorHandler("Error on writing Date on the RTC"); } + rtc_time_valid = write_ok; + if (write_ok) { + global_RTC = get_rtc_timestamp(); + } +} +void Global_RTC::update_rtc_data() { + if (!ensure_started()) { + return; + } + if (!rtc_time_valid) { + global_RTC = {}; + return; + } + global_RTC = get_rtc_timestamp(); } -void Global_RTC::update_rtc_data() { global_RTC = get_rtc_timestamp(); } diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp index 45dfccf37..6ca063ffa 100644 --- a/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp +++ b/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp @@ -16,7 +16,7 @@ void* error_handler; void* info_warning; void ProtectionManager::initialize() { - Global_RTC::start_rtc(); + Global_RTC::ensure_started(); for (Protection& protection : low_frequency_protections) { for (auto& boundary : protection.boundaries) { boundary->update_name(protection.get_name()); @@ -84,7 +84,7 @@ void ProtectionManager::check_protections() { } Global_RTC::update_rtc_data(); if (Scheduler::get_global_tick() > - protection.get_last_notify_tick() + notify_delay_in_nanoseconds) { + protection.get_last_notify_tick() + notify_delay_in_microseconds) { ProtectionManager::notify(protection); protection.update_last_notify_tick(Scheduler::get_global_tick()); } @@ -93,18 +93,21 @@ void ProtectionManager::check_protections() { void ProtectionManager::check_high_frequency_protections() { for (Protection& protection : high_frequency_protections) { + auto protection_status = protection.check_state(); + if (general_state_machine == nullptr) { ErrorHandler("Protection Manager does not have General State Machine " "Linked"); return; } - if (protection.fault_type == Protections::FAULT) { + if (protection.fault_type == Protections::FAULT && + protection_status == Protections::FAULT) { ProtectionManager::to_fault(); } Global_RTC::update_rtc_data(); if (Scheduler::get_global_tick() > - protection.get_last_notify_tick() + notify_delay_in_nanoseconds) { + protection.get_last_notify_tick() + notify_delay_in_microseconds) { ProtectionManager::notify(protection); protection.update_last_notify_tick(Scheduler::get_global_tick()); } @@ -114,20 +117,37 @@ void ProtectionManager::check_high_frequency_protections() { void ProtectionManager::warn(string message) { warning_notification.notify(message); } void ProtectionManager::notify(Protection& protection) { + const bool is_error_handler_fault = + protection.fault_protection != nullptr && + protection.fault_protection->boundary_type_id == ERROR_HANDLER; + const bool should_send_fault = + protection.fault_protection != nullptr && + (!is_error_handler_fault || ErrorHandlerModel::error_to_communicate); + bool error_handler_delivered = false; + bool info_warning_delivered = false; + for (OrderProtocol* socket : OrderProtocol::sockets) { - if (protection.fault_protection) { - if (protection.fault_protection->boundary_type_id == ERROR_HANDLER) { + if (should_send_fault) { + if (is_error_handler_fault) { protection.fault_protection->update_error_handler_message( protection.fault_protection->get_error_handler_string() ); + error_handler_delivered = + socket->send_order(*protection.fault_protection->fault_message) || + error_handler_delivered; + } else { + socket->send_order(*protection.fault_protection->fault_message); } - socket->send_order(*protection.fault_protection->fault_message); - ErrorHandlerModel::error_to_communicate = false; } for (auto& warning : protection.warnings_triggered) { - if (warning->boundary_type_id == INFO_WARNING - 2) { + if (warning->boundary_type_id == BoundaryInterface::INFO_WARNING_BOUNDARY_TYPE_ID) { + if (!InfoWarning::warning_to_communicate) { + continue; + } warning->update_warning_message(warning->get_warning_string()); - InfoWarning::warning_triggered = false; + info_warning_delivered = + socket->send_order(*warning->warn_message) || info_warning_delivered; + continue; } socket->send_order(*warning->warn_message); } @@ -135,6 +155,16 @@ void ProtectionManager::notify(Protection& protection) { socket->send_order(*ok->ok_message); } } + + if (error_handler_delivered) { + ErrorHandlerModel::error_to_communicate = false; + } + + if (info_warning_delivered) { + InfoWarning::warning_triggered = false; + InfoWarning::warning_to_communicate = false; + } + protection.oks_triggered.clear(); protection.warnings_triggered.clear(); } diff --git a/Src/ST-LIB_LOW/Communication/Server/Server.cpp b/Src/ST-LIB_LOW/Communication/Server/Server.cpp index 800c54afb..0a54b2ebc 100644 --- a/Src/ST-LIB_LOW/Communication/Server/Server.cpp +++ b/Src/ST-LIB_LOW/Communication/Server/Server.cpp @@ -18,57 +18,94 @@ Server::Server(IPV4 local_ip, uint32_t local_port) } Server::~Server() { - open_connection->~ServerSocket(); + if (open_connection != nullptr) { + delete open_connection; + open_connection = nullptr; + } - for (ServerSocket* s : running_connections) { - s->~ServerSocket(); + for (uint16_t s = 0; s < running_connections_count; s++) { + if (running_connections[s] != nullptr) { + delete running_connections[s]; + running_connections[s] = nullptr; + } } - running_servers.erase(find(running_servers.begin(), running_servers.end(), this)); + auto it = find(running_servers.begin(), running_servers.end(), this); + if (it != running_servers.end()) { + running_servers.erase(it); + } } void Server::update() { - if (open_connection->is_connected()) { - running_connections[running_connections_count] = open_connection; - running_connections_count++; + if (status == CLOSED) { + return; + } + + if (open_connection == nullptr) { + open_connection = new ServerSocket(local_ip, local_port); + } else if (!open_connection->is_connected() && !open_connection->is_listening()) { + // Recover from startup/driver races where listener was not created successfully. + delete open_connection; open_connection = new ServerSocket(local_ip, local_port); } + if (open_connection->is_connected()) { + if (running_connections_count < MAX_CONNECTIONS_TCP_SERVER) { + running_connections[running_connections_count] = open_connection; + running_connections_count++; + open_connection = new ServerSocket(local_ip, local_port); + } else { + // Capacity reached: close the new connection and keep current sessions untouched. + delete open_connection; + open_connection = new ServerSocket(local_ip, local_port); + } + } + + uint16_t write_index = 0; for (uint16_t s = 0; s < running_connections_count; s++) { - if (status == RUNNING && !running_connections[s]->is_connected()) { - ErrorHandler( - "ip %s disconnected, going to FAULT", - running_connections[s]->remote_ip.string_address.c_str() - ); - status = CLOSING; - break; + ServerSocket* current = running_connections[s]; + if (current != nullptr && current->is_connected()) { + running_connections[write_index++] = current; + } else { + if (current != nullptr) { + delete current; + } } } + for (uint16_t s = write_index; s < running_connections_count; s++) { + running_connections[s] = nullptr; + } + running_connections_count = write_index; if (status == CLOSING) { close_all(); } } -void Server::broadcast_order(Order& order) { +bool Server::broadcast_order(Order& order) { + bool sent = false; for (uint16_t s = 0; s < running_connections_count; s++) { - if (!running_connections[s]->send_order(order)) { - ErrorHandler( - "Couldn t put Order %d into buffer of ip's %s ServerSocket, buffer may be full or " - "the ServerSocket may be ill formed", - order.get_id(), - running_connections[s]->remote_ip.string_address.c_str() - ); + ServerSocket* connection = running_connections[s]; + if (connection != nullptr) { + sent = connection->send_order(order) || sent; } } + return sent; } void Server::close_all() { for (uint16_t s = 0; s < running_connections_count; s++) { - running_connections[s]->close(); + if (running_connections[s] == nullptr) { + continue; + } + delete running_connections[s]; running_connections[s] = nullptr; } running_connections_count = 0; + if (open_connection != nullptr) { + delete open_connection; + open_connection = nullptr; + } status = CLOSED; } @@ -76,7 +113,9 @@ uint32_t Server::connections_count() { return running_connections_count; } void Server::update_servers() { for (Server* s : running_servers) { - s->update(); + if (s != nullptr) { + s->update(); + } } } diff --git a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp index 9b7b8282f..b0c4897da 100644 --- a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp +++ b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp @@ -7,6 +7,229 @@ #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Services/Time/Scheduler.hpp" +#include "HALAL/Services/Time/RTC.hpp" +#ifdef STLIB_ETH +#include "HALAL/Models/Packets/Order.hpp" +#endif + +namespace { + +#ifdef STLIB_ETH +constexpr uint16_t ERROR_HANDLER_TCP_ORDER_ID = 1555; +constexpr uint8_t ERROR_HANDLER_BOUNDARY_TYPE_ID = 5; +#endif + +bool error_sent_via_tcp = false; +bool error_sent_via_uart = false; +bool tcp_delivery_required = false; +bool uart_delivery_required = false; + +#ifdef STLIB_ETH +uint8_t error_handler_padding = 0; +uint8_t error_handler_boundary_type = ERROR_HANDLER_BOUNDARY_TYPE_ID; +string error_handler_name = "error_handler"; +string error_handler_message = "Error-No-Description-Found"; +uint16_t error_handler_counter = 0; +uint8_t error_handler_second = 0; +uint8_t error_handler_minute = 0; +uint8_t error_handler_hour = 0; +uint8_t error_handler_day = 0; +uint8_t error_handler_month = 0; +uint16_t error_handler_year = 0; + +class ErrorHandlerOrder final : public Order { +public: + void set_callback(void (*)(void)) override {} + + void process() override {} + + void parse(OrderProtocol* socket, uint8_t* data) override { + (void)socket; + (void)data; + } + + uint8_t* build() override { + const size_t total_size = get_size(); + if (buffer.size() != total_size) { + buffer.resize(total_size); + } + + uint8_t* data = buffer.data(); + append(data, &id, sizeof(id)); + append(data, &error_handler_padding, sizeof(error_handler_padding)); + append(data, &error_handler_boundary_type, sizeof(error_handler_boundary_type)); + append(data, error_handler_name.c_str(), error_handler_name.size() + 1); + append(data, error_handler_message.c_str(), error_handler_message.size() + 1); + append(data, &error_handler_counter, sizeof(error_handler_counter)); + append(data, &error_handler_second, sizeof(error_handler_second)); + append(data, &error_handler_minute, sizeof(error_handler_minute)); + append(data, &error_handler_hour, sizeof(error_handler_hour)); + append(data, &error_handler_day, sizeof(error_handler_day)); + append(data, &error_handler_month, sizeof(error_handler_month)); + append(data, &error_handler_year, sizeof(error_handler_year)); + return buffer.data(); + } + + size_t get_size() override { + size = sizeof(id) + sizeof(error_handler_padding) + sizeof(error_handler_boundary_type) + + error_handler_name.size() + 1 + error_handler_message.size() + 1 + + sizeof(error_handler_counter) + sizeof(error_handler_second) + + sizeof(error_handler_minute) + sizeof(error_handler_hour) + + sizeof(error_handler_day) + sizeof(error_handler_month) + sizeof(error_handler_year); + return size; + } + + uint16_t get_id() override { return id; } + + void set_pointer(size_t index, void* pointer) override { + (void)index; + (void)pointer; + } + +private: + static void append(uint8_t*& dst, const void* src, size_t count) { + memcpy(dst, src, count); + dst += count; + } + + static constexpr uint16_t id = ERROR_HANDLER_TCP_ORDER_ID; + vector buffer{}; +}; + +ErrorHandlerOrder error_handler_order; + +void refresh_error_handler_transport_state(const string& description) { + error_handler_message = description; + +#ifdef HAL_RTC_MODULE_ENABLED + if (Global_RTC::ensure_started()) { + Global_RTC::update_rtc_data(); + error_handler_counter = Global_RTC::global_RTC.counter; + error_handler_second = Global_RTC::global_RTC.second; + error_handler_minute = Global_RTC::global_RTC.minute; + error_handler_hour = Global_RTC::global_RTC.hour; + error_handler_day = Global_RTC::global_RTC.day; + error_handler_month = Global_RTC::global_RTC.month; + error_handler_year = Global_RTC::global_RTC.year; + return; + } +#endif + + error_handler_counter = 0; + error_handler_second = 0; + error_handler_minute = 0; + error_handler_hour = 0; + error_handler_day = 0; + error_handler_month = 0; + error_handler_year = 0; +} +#endif + +bool try_send_error_via_tcp(const string& description) { +#ifdef STLIB_ETH + if (!tcp_delivery_required || error_sent_via_tcp) { + return true; + } + + refresh_error_handler_transport_state(description); + + bool delivered = false; + for (OrderProtocol* socket : OrderProtocol::sockets) { + if (socket == nullptr) { + continue; + } + delivered = socket->send_order(error_handler_order) || delivered; + } + + if (delivered) { + error_sent_via_tcp = true; + } + return error_sent_via_tcp; +#else + (void)description; + return true; +#endif +} + +bool try_send_error_via_uart(const string& description) { +#ifdef HAL_UART_MODULE_ENABLED + if (!uart_delivery_required || error_sent_via_uart) { + return true; + } + + if (!UART::printf_ready) { + return false; + } + + printf("Error: %s%s", description.c_str(), endl); + error_sent_via_uart = true; + return true; +#else + (void)description; + return true; +#endif +} + +void append_readable_timestamp(string& message) { +#ifdef HAL_RTC_MODULE_ENABLED + if (Global_RTC::ensure_started() && Global_RTC::has_valid_time()) { + Global_RTC::update_rtc_data(); + const RTCData& timestamp = Global_RTC::global_RTC; + char buffer[80]{}; + snprintf( + buffer, + sizeof(buffer), + " | Timestamp: %04u-%02u-%02u %02u:%02u:%02u.%05u", + static_cast(timestamp.year), + static_cast(timestamp.month), + static_cast(timestamp.day), + static_cast(timestamp.hour), + static_cast(timestamp.minute), + static_cast(timestamp.second), + static_cast(timestamp.counter) + ); + message += buffer; + return; + } +#endif + +#ifdef HAL_TIM_MODULE_ENABLED + const uint64_t uptime_us = Scheduler::get_global_tick(); + const uint64_t total_seconds = uptime_us / 1'000'000ULL; + const uint64_t days = total_seconds / 86'400ULL; + const unsigned hours = static_cast((total_seconds / 3'600ULL) % 24ULL); + const unsigned minutes = static_cast((total_seconds / 60ULL) % 60ULL); + const unsigned seconds = static_cast(total_seconds % 60ULL); + const unsigned micros = static_cast(uptime_us % 1'000'000ULL); + + char buffer[80]{}; + if (days > 0) { + snprintf( + buffer, + sizeof(buffer), + " | Uptime: %llud %02u:%02u:%02u.%06u", + static_cast(days), + hours, + minutes, + seconds, + micros + ); + } else { + snprintf( + buffer, + sizeof(buffer), + " | Uptime: %02u:%02u:%02u.%06u", + hours, + minutes, + seconds, + micros + ); + } + message += buffer; +#endif +} + +} // namespace string ErrorHandlerModel::description = "Error-No-Description-Found"; string ErrorHandlerModel::line = "Error-No-Line-Found"; @@ -30,6 +253,18 @@ void ErrorHandlerModel::ErrorHandlerTrigger(string format, ...) { ErrorHandlerModel::error_to_communicate = true; // This flag is marked so the ProtectionManager can know if it already consumed the // error in question. + error_sent_via_tcp = false; + error_sent_via_uart = false; +#ifdef STLIB_ETH + tcp_delivery_required = true; +#else + tcp_delivery_required = false; +#endif +#ifdef HAL_UART_MODULE_ENABLED + uart_delivery_required = UART::printf_ready; +#else + uart_delivery_required = false; +#endif if (format.length() != 0) { description = ""; @@ -51,21 +286,20 @@ void ErrorHandlerModel::ErrorHandlerTrigger(string format, ...) { " | Line: " + ErrorHandlerModel::line + " Function: '" + ErrorHandlerModel::func + "' File: " + ErrorHandlerModel::file; -#ifdef HAL_TIM_MODULE_ENABLED - description += " | TimeStamp: " + to_string(Scheduler::get_global_tick()); -#endif + append_readable_timestamp(description); + + ErrorHandlerModel::ErrorHandlerUpdate(); } void ErrorHandlerModel::ErrorHandlerUpdate() { - if (!ErrorHandlerModel::error_triggered) { + if (!ErrorHandlerModel::error_triggered || !ErrorHandlerModel::error_to_communicate) { return; } -#ifdef HAL_UART_MODULE_ENABLED - if (!UART::printf_ready) { - return; - } + const bool tcp_done = try_send_error_via_tcp(ErrorHandlerModel::description); + const bool uart_done = try_send_error_via_uart(ErrorHandlerModel::description); - // printf("Error: %s%s", ErrorHandlerModel::description.c_str(), endl); -#endif + if (tcp_done && uart_done) { + ErrorHandlerModel::error_to_communicate = false; + } } From 2dfdf3852710b0b57c48c5cd821dedf1eab50684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Cant=C3=B3=20Catal=C3=A1n?= <144663567+Cantonplas@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:51:14 +0100 Subject: [PATCH 24/41] Prescaler was not initialized in init (#584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prescaler was not initialized in init * formatting: Remove spaces * formatting * hardened a lot the encoder, being sure that instances are properly set * minor fix, not setting properly the encoder start * added tests for the encoder * reverted nullptr checks, and made builder private * applied formatter * Make constructors private in Encoder, PWM, DualPWM * applied formatter * fixed merge mess --------- Co-authored-by: Víctor López <120128034+victor-Lopez25@users.noreply.github.com> Co-authored-by: Jorge Sáez --- Inc/HALAL/Services/Encoder/Encoder.hpp | 231 +++++++++--------- Inc/HALAL/Services/PWM/DualPWM.hpp | 4 +- Inc/HALAL/Services/PWM/PWM.hpp | 4 +- .../EncoderSensor/NewEncoderSensor.hpp | 17 +- Tests/CMakeLists.txt | 1 + Tests/Time/encoder_test.cpp | 139 +++++++++++ 6 files changed, 271 insertions(+), 125 deletions(-) create mode 100644 Tests/Time/encoder_test.cpp diff --git a/Inc/HALAL/Services/Encoder/Encoder.hpp b/Inc/HALAL/Services/Encoder/Encoder.hpp index b41370031..24f3fa30b 100644 --- a/Inc/HALAL/Services/Encoder/Encoder.hpp +++ b/Inc/HALAL/Services/Encoder/Encoder.hpp @@ -1,114 +1,117 @@ -#pragma once - -#include "HALAL/Models/TimerDomain/TimerDomain.hpp" - -#ifdef HAL_TIM_MODULE_ENABLED - -#define NANO_SECOND 1000000000.0 -#define CLOCK_MAX_VALUE 4294967295 // here goes the tim23 counter period - -namespace ST_LIB { - -template struct TimerWrapper; - -template class Encoder { - static_assert( - dev.e.pin_count == 2, - "Encoder must have exactly 2 encoder pins, as it uses the whole timer" - ); - static_assert(dev.e.pins[0].af == TimerAF::Encoder, "Pin 0 must be declared as encoder"); - static_assert(dev.e.pins[1].af == TimerAF::Encoder, "Pin 1 must be declared as encoder"); - static_assert( - dev.e.pins[0].channel != dev.e.pins[1].channel, - "Pins must be of different channels" - ); - - inline static TimerWrapper* timer; - inline static bool is_on = false; - -public: - Encoder(TimerWrapper* tim) { - if (timer == nullptr) { - init(tim); - } - } - - static void init(TimerWrapper* tim) { - timer = tim; - TIM_Encoder_InitTypeDef sConfig = {0}; - TIM_MasterConfigTypeDef sMasterConfig = {0}; - - tim->instance->hal_tim->Init.Prescaler = 5; - tim->instance->hal_tim->Init.CounterMode = TIM_COUNTERMODE_UP; - tim->instance->hal_tim->Init.Period = 55000; - tim->instance->hal_tim->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - tim->instance->hal_tim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - - sConfig.EncoderMode = TIM_ENCODERMODE_TI12; - sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; - sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; - sConfig.IC1Prescaler = TIM_ICPSC_DIV1; - sConfig.IC1Filter = 0; - sConfig.IC2Polarity = TIM_ICPOLARITY_RISING; - sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; - sConfig.IC2Prescaler = TIM_ICPSC_DIV1; - sConfig.IC2Filter = 0; - - if (HAL_TIM_Encoder_Init(tim->instance->hal_tim, &sConfig) != HAL_OK) { - ErrorHandler("Unable to init encoder"); - } - - sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(tim->instance->hal_tim, &sMasterConfig) != - HAL_OK) { - ErrorHandler("Unable to config master synchronization in encoder"); - } - } - - static void turn_on() { - if (is_on) - return; - - if (HAL_TIM_Encoder_GetState(timer->instance->hal_tim) == HAL_TIM_STATE_RESET) { - ErrorHandler("Unable to get state from encoder"); - return; - } - if (HAL_TIM_Encoder_Start(timer->instance->hal_tim, TIM_CHANNEL_ALL) != HAL_OK) { - ErrorHandler("Unable to start encoder"); - return; - } - is_on = true; - reset(); - } - - static void turn_off() { - if (!is_on) - return; - if (HAL_TIM_Encoder_Stop(timer->instance->hal_tim, TIM_CHANNEL_ALL) != HAL_OK) { - ErrorHandler("Unable to stop encoder"); - } - is_on = false; - } - - static inline void reset() { timer->instance->tim->CNT = UINT32_MAX / 2; } - - static inline uint32_t get_counter() { return timer->instance->tim->CNT; } - - static inline bool get_direction() { return ((timer->instance->tim->CR1 & 0b10000) >> 4); } - - static inline uint32_t get_initial_counter_value() { return timer->instance->tim->ARR / 2; } - - static int64_t get_delta_clock(uint64_t clock_time, uint64_t last_clock_time) { - int64_t delta_clock = clock_time - last_clock_time; - if (clock_time < last_clock_time) { // overflow handle - delta_clock = clock_time + - CLOCK_MAX_VALUE * NANO_SECOND / timer->get_clock_frequency() - - last_clock_time; - } - return delta_clock; - } -}; - -} // namespace ST_LIB -#endif +#pragma once + +#include "HALAL/Models/TimerDomain/TimerDomain.hpp" + +#ifdef HAL_TIM_MODULE_ENABLED + +#define NANO_SECOND 1000000000.0 +#define CLOCK_MAX_VALUE 4294967295 // here goes the tim23 counter period + +namespace ST_LIB { + +template struct TimerWrapper; + +template class Encoder { + friend struct TimerWrapper; + + static_assert( + dev.e.pin_count == 2, + "Encoder must have exactly 2 encoder pins, as it uses the whole timer" + ); + static_assert(dev.e.pins[0].af == TimerAF::Encoder, "Pin 0 must be declared as encoder"); + static_assert(dev.e.pins[1].af == TimerAF::Encoder, "Pin 1 must be declared as encoder"); + static_assert( + dev.e.pins[0].channel != dev.e.pins[1].channel, + "Pins must be of different channels" + ); + + inline static TimerWrapper* timer; + inline static bool is_on = false; + + Encoder(TimerWrapper* tim) { + if (timer == nullptr) { + init(tim); + } + } + +public: + static void init(TimerWrapper* tim) { + TIM_Encoder_InitTypeDef sConfig = {0}; + TIM_MasterConfigTypeDef sMasterConfig = {0}; + + sConfig.EncoderMode = TIM_ENCODERMODE_TI12; + sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; + sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; + sConfig.IC1Prescaler = TIM_ICPSC_DIV1; + sConfig.IC1Filter = 0; + sConfig.IC2Polarity = TIM_ICPOLARITY_RISING; + sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; + sConfig.IC2Prescaler = TIM_ICPSC_DIV1; + sConfig.IC2Filter = 0; + + if (HAL_TIM_Encoder_Init(tim->instance->hal_tim, &sConfig) != HAL_OK) { + ErrorHandler("Unable to init encoder"); + return; + } + + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(tim->instance->hal_tim, &sMasterConfig) != + HAL_OK) { + ErrorHandler("Unable to config master synchronization in encoder"); + return; + } + + tim->instance->tim->PSC = 5; + tim->instance->tim->ARR = 55000; + timer = tim; + } + + static void turn_on() { + if (is_on) + return; + + if (HAL_TIM_Encoder_GetState(timer->instance->hal_tim) == HAL_TIM_STATE_RESET) { + ErrorHandler("Unable to get state from encoder"); + return; + } + if (HAL_TIM_Encoder_Start(timer->instance->hal_tim, TIM_CHANNEL_ALL) != HAL_OK) { + ErrorHandler("Unable to start encoder"); + return; + } + is_on = true; + reset(); + } + + static void turn_off() { + if (!is_on) + return; + + if (HAL_TIM_Encoder_Stop(timer->instance->hal_tim, TIM_CHANNEL_ALL) != HAL_OK) { + ErrorHandler("Unable to stop encoder"); + return; + } + is_on = false; + } + + static inline void reset() { timer->instance->tim->CNT = get_initial_counter_value(); } + + static inline uint32_t get_counter() { return timer->instance->tim->CNT; } + + static inline bool get_direction() { return ((timer->instance->tim->CR1 & 0b10000) >> 4); } + + static inline uint32_t get_initial_counter_value() { return timer->instance->tim->ARR / 2; } + + static int64_t get_delta_clock(uint64_t clock_time, uint64_t last_clock_time) { + int64_t delta_clock = clock_time - last_clock_time; + if (clock_time < last_clock_time) { // overflow handle + delta_clock = clock_time + + CLOCK_MAX_VALUE * NANO_SECOND / timer->get_clock_frequency() - + last_clock_time; + } + return delta_clock; + } +}; + +} // namespace ST_LIB +#endif diff --git a/Inc/HALAL/Services/PWM/DualPWM.hpp b/Inc/HALAL/Services/PWM/DualPWM.hpp index 26c36d073..f4a315535 100644 --- a/Inc/HALAL/Services/PWM/DualPWM.hpp +++ b/Inc/HALAL/Services/PWM/DualPWM.hpp @@ -19,6 +19,8 @@ template < const ST_LIB::TimerPin pin, const ST_LIB::TimerPin negated_pin> class DualPWM { + friend TimerWrapper; + static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { switch (ch) { case TimerChannel::CHANNEL_1: @@ -70,7 +72,6 @@ class DualPWM { bool is_on_positive = false; bool is_on_negative = false; -public: DualPWM( TimerWrapper* tim, uint32_t polarity, @@ -98,6 +99,7 @@ class DualPWM { timer->template set_output_compare_preload_enable(); } +public: inline void turn_on() { turn_on_positive(); turn_on_negative(); diff --git a/Inc/HALAL/Services/PWM/PWM.hpp b/Inc/HALAL/Services/PWM/PWM.hpp index b1d4cdb3a..9c3da2ff0 100644 --- a/Inc/HALAL/Services/PWM/PWM.hpp +++ b/Inc/HALAL/Services/PWM/PWM.hpp @@ -15,6 +15,8 @@ namespace ST_LIB { template struct TimerWrapper; template class PWM { + friend TimerWrapper; + static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { switch (ch) { case TimerChannel::CHANNEL_1: @@ -65,7 +67,6 @@ template class PWM { float* duty_cycle = nullptr; bool is_on = false; -public: PWM(TimerWrapper* tim, uint32_t polarity, uint32_t negated_polarity, @@ -90,6 +91,7 @@ template class PWM { timer->template set_output_compare_preload_enable(); } +public: void turn_on() { if (this->is_on) return; diff --git a/Inc/ST-LIB_LOW/Sensors/EncoderSensor/NewEncoderSensor.hpp b/Inc/ST-LIB_LOW/Sensors/EncoderSensor/NewEncoderSensor.hpp index f8d52c1bf..7bcf98eb7 100644 --- a/Inc/ST-LIB_LOW/Sensors/EncoderSensor/NewEncoderSensor.hpp +++ b/Inc/ST-LIB_LOW/Sensors/EncoderSensor/NewEncoderSensor.hpp @@ -6,8 +6,7 @@ */ #pragma once -#include "HALAL/HALAL.hpp" -// #include "HALAL/Services/Encoder/NewEncoder.hpp" +#include "C++Utilities/CppUtils.hpp" namespace ST_LIB { @@ -15,7 +14,8 @@ template struct EncoderSensor { enum Direction : uint8_t { FORWARD = 0, BACKWARDS = 1 }; private: - constexpr static int64_t START_COUNTER{UINT32_MAX / 2}; + constexpr static size_t WINDOW_SIZE{(SAMPLES / 2) * 2}; + static_assert(WINDOW_SIZE >= 2, "EncoderSensor requires at least two samples"); const double counter_distance_m; const double sample_time_s; @@ -41,7 +41,7 @@ template struct EncoderSensor { ) : counter_distance_m(counter_distance_m), sample_time_s(sample_time_s), encoder(enc), direction(direction), position(position), speed(speed), acceleration(acceleration) { - for (size_t i{0}; i < SAMPLES; i++) + for (size_t i{0}; i < WINDOW_SIZE; i++) past_delta_counters.push(0); } @@ -50,7 +50,7 @@ template struct EncoderSensor { void reset() { encoder.reset(); - for (size_t i{0}; i < SAMPLES; ++i) + for (size_t i{0}; i < WINDOW_SIZE; ++i) past_delta_counters.push_pop(0); } @@ -58,7 +58,7 @@ template struct EncoderSensor { void read() { uint32_t counter{encoder.get_counter()}; - int64_t delta_counter{(int64_t)counter - START_COUNTER}; + int64_t delta_counter{(int64_t)counter - (int64_t)encoder.get_initial_counter_value()}; const int64_t& previous_delta_counter{ past_delta_counters[past_delta_counters.size() / 2 - 1] }; @@ -71,13 +71,12 @@ template struct EncoderSensor { // https://en.wikipedia.org/wiki/Finite_difference_coefficient#Backward_finite_difference *speed = ((3.0 * delta_counter / 2.0) - (2.0 * previous_delta_counter) + (previous_previous_delta_counter / 2.0)) * - counter_distance_m / (sample_time_s * past_delta_counters.size() / 2); + counter_distance_m / (sample_time_s * WINDOW_SIZE / 2); *acceleration = (delta_counter - (2.0 * previous_delta_counter) + previous_previous_delta_counter) * counter_distance_m / - ((sample_time_s * past_delta_counters.size() / 2) * - (sample_time_s * past_delta_counters.size() / 2)); + ((sample_time_s * WINDOW_SIZE / 2) * (sample_time_s * WINDOW_SIZE / 2)); *direction = encoder.get_direction() ? FORWARD : BACKWARDS; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 0c882b8a7..845f52058 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -19,6 +19,7 @@ message(STATUS "Generating test executable for ST-LIB") add_executable(${STLIB_TEST_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/SPI/SPI2.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/DMA/DMA2.cpp + ${CMAKE_CURRENT_LIST_DIR}/Time/encoder_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/scheduler_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/timer_wrapper_test.cpp ${CMAKE_CURRENT_LIST_DIR}/adc_test.cpp diff --git a/Tests/Time/encoder_test.cpp b/Tests/Time/encoder_test.cpp new file mode 100644 index 000000000..9d73cf27c --- /dev/null +++ b/Tests/Time/encoder_test.cpp @@ -0,0 +1,139 @@ +#include + +#include "HALAL/Services/Time/TimerWrapper.hpp" +#include "ST-LIB_LOW/Sensors/EncoderSensor/NewEncoderSensor.hpp" + +namespace ST_LIB::TestErrorHandler { +void reset(); +void set_fail_on_error(bool enabled); +extern int call_count; +} // namespace ST_LIB::TestErrorHandler + +namespace { + +constexpr ST_LIB::TimerDomain::Timer encoder_timer_decl{ + ST_LIB::TimerRequest::GeneralPurpose32bit_2, + ST_LIB::TimerDomain::EMPTY_TIMER_NAME, + ST_LIB::TimerPin{ + .af = ST_LIB::TimerAF::Encoder, + .pin = ST_LIB::PA0, + .channel = ST_LIB::TimerChannel::CHANNEL_1, + }, + ST_LIB::TimerPin{ + .af = ST_LIB::TimerAF::Encoder, + .pin = ST_LIB::PA1, + .channel = ST_LIB::TimerChannel::CHANNEL_2, + }, +}; + +struct MockEncoder { + uint32_t counter = 1234; + uint32_t initial_counter = 1234; + bool direction = true; + int turn_on_calls = 0; + int turn_off_calls = 0; + int reset_calls = 0; + + void turn_on() { ++turn_on_calls; } + void turn_off() { ++turn_off_calls; } + void reset() { + ++reset_calls; + counter = initial_counter; + } + uint32_t get_counter() { return counter; } + bool get_direction() { return direction; } + uint32_t get_initial_counter_value() { return initial_counter; } +}; + +using MockSensor = ST_LIB::EncoderSensor; + +} // namespace + +class EncoderTest : public ::testing::Test { +protected: + TIM_HandleTypeDef hal_tim{}; + ST_LIB::TimerDomain::Instance instance{}; + ST_LIB::TimerWrapper wrapper{}; + + void SetUp() override { + ST_LIB::TestErrorHandler::reset(); + + TIM2_BASE->CNT = 0U; + TIM2_BASE->ARR = 0U; + TIM2_BASE->PSC = 0U; + TIM2_BASE->CR1 = 0U; + + hal_tim = {}; + hal_tim.Instance = TIM2_BASE; + hal_tim.State = HAL_TIM_STATE_READY; + + instance.tim = TIM2_BASE; + instance.hal_tim = &hal_tim; + instance.timer_idx = 0U; + + wrapper = ST_LIB::TimerWrapper(&instance); + ST_LIB::Encoder::init(&wrapper); + ST_LIB::Encoder::turn_off(); + } +}; + +TEST_F(EncoderTest, ResetUsesConfiguredInitialCounterValue) { + TIM2_BASE->CNT = 17U; + + ST_LIB::Encoder::reset(); + + EXPECT_EQ(TIM2_BASE->ARR, 55000U); + EXPECT_EQ(TIM2_BASE->CNT, ST_LIB::Encoder::get_initial_counter_value()); +} + +TEST_F(EncoderTest, TurnOffKeepsTryingIfHALStopFails) { + ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::Encoder::turn_on(); + + instance.hal_tim = nullptr; + + ST_LIB::Encoder::turn_off(); + ST_LIB::Encoder::turn_off(); + + EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 2); +} + +TEST(EncoderSensorTest, ReadTreatsEncoderInitialCounterAsZeroPosition) { + MockEncoder encoder{}; + double position = -1.0; + double speed = -1.0; + double acceleration = -1.0; + MockSensor::Direction direction = MockSensor::BACKWARDS; + + MockSensor sensor(encoder, 0.5, 0.1, &direction, &position, &speed, &acceleration); + + sensor.read(); + + EXPECT_DOUBLE_EQ(position, 0.0); + EXPECT_DOUBLE_EQ(speed, 0.0); + EXPECT_DOUBLE_EQ(acceleration, 0.0); + EXPECT_EQ(direction, MockSensor::FORWARD); +} + +TEST(EncoderSensorTest, ResetForwardsToEncoderAndClearsHistory) { + MockEncoder encoder{}; + encoder.counter = 1240U; + + double position = 0.0; + double speed = 0.0; + double acceleration = 0.0; + MockSensor::Direction direction = MockSensor::BACKWARDS; + + MockSensor sensor(encoder, 1.0, 1.0, &direction, &position, &speed, &acceleration); + + sensor.read(); + ASSERT_NE(position, 0.0); + + sensor.reset(); + sensor.read(); + + EXPECT_EQ(encoder.reset_calls, 1); + EXPECT_DOUBLE_EQ(position, 0.0); + EXPECT_DOUBLE_EQ(speed, 0.0); + EXPECT_DOUBLE_EQ(acceleration, 0.0); +} From 3a12e3915c1f34f8a2104fcea61c1e10de801710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez?= <120128034+victor-Lopez25@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:49:31 +0100 Subject: [PATCH 25/41] Fix/scheduler timerdomain (#585) * Scheduler now "reserves" a timer from timerdomain * Also remove possibility of getting scheduler timer from bits32_timers * stupid change to get precommit to work (?) * fix formatting (?) * more format fix * fix segfault in tests (?) * fix formatting * move Scheduler_global_timer in scheduler_test.cpp --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 18 +++++++++++++++++- Inc/HALAL/Services/Time/Scheduler.hpp | 20 +++++--------------- Src/HALAL/Services/Time/Scheduler.cpp | 19 ++++++------------- Tests/Time/scheduler_test.cpp | 1 + 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 118031b54..f48b57da9 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -19,6 +19,16 @@ #include "ErrorHandler/ErrorHandler.hpp" +#ifndef SCHEDULER_TIMER_DOMAIN +/* default is tim2 */ +#define SCHEDULER_TIMER_DOMAIN 2 +#elif (SCHEDULER_TIMER_DOMAIN != 2) && (SCHEDULER_TIMER_DOMAIN != 3) && \ + (SCHEDULER_TIMER_DOMAIN != 23) && (SCHEDULER_TIMER_DOMAIN != 24) +#error Scheduler timer must be a 32 bit timer +#endif + +#define SCHEDULER_GLOBAL_TIMER_IRQn glue(TIM, glue(SCHEDULER_TIMER_DOMAIN, _IRQn)) + // NOTE: only works for static arrays #define ARRAY_LENGTH(a) (sizeof(a) / sizeof(*a)) @@ -567,6 +577,10 @@ TimerXList remaining_requests[i] = i; for (int i = 0; i < (int)requests.size(); i++) { + if (static_cast(requests[i].request) == SCHEDULER_TIMER_DOMAIN) { + ST_LIB::compile_error("This timer is used by the scheduler"); + } + if (requests[i].request != TimerRequest::AnyGeneralPurpose && (requests[i].request < 1 || requests[i].request > 24 || (requests[i].request > 17 && requests[i].request < 23))) { @@ -614,7 +628,7 @@ TimerXList uint8_t count_32bit_requests = 0; for (int i = 0; i < (int)ARRAY_LENGTH(bits32_timers); i++) { - if (!used_timers[bits32_timers[i]]) + if (!used_timers[bits32_timers[i]] && (bits32_timers[i] != SCHEDULER_TIMER_DOMAIN)) remaining_32bit_timers[count_remaining_32bit_timers++] = bits32_timers[i]; } @@ -705,6 +719,8 @@ TimerXList static inline std::array instances{}; static void init(std::span cfgs) { + rcc_enable_timer(cmsis_timers[static_cast(SCHEDULER_TIMER_DOMAIN)]); + for (std::size_t i = 0; i < N; i++) { const Config& e = cfgs[i]; diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index ae7bcdb1c..dbe965891 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -15,31 +15,21 @@ #include #include -/* NOTE(vic): Esto cambiará pronto */ -#ifndef SCHEDULER_TIMER_IDX -#define SCHEDULER_TIMER_IDX 2 -#endif - -#ifndef glue -#define glue_(a, b) a##b -#define glue(a, b) glue_(a, b) -#endif -#define SCHEDULER_TIMER_BASE glue(TIM, glue(SCHEDULER_TIMER_IDX, _BASE)) - -// Used to reserve a TimerPeripheral #ifndef SIM_ON #include "stm32h7xx_hal_tim.h" -#define SCHEDULER_HAL_TIM glue(htim, SCHEDULER_TIMER_IDX) -extern TIM_HandleTypeDef SCHEDULER_HAL_TIM; #endif +extern TIM_TypeDef* Scheduler_global_timer; + struct Scheduler { using callback_t = void (*)(); static constexpr uint32_t INVALID_ID = 0xFFu; static void start(); static void update(); - static inline uint64_t get_global_tick() { return global_tick_us_; } + static inline uint64_t get_global_tick() { + return global_tick_us_ + Scheduler_global_timer->CNT; + } static uint16_t register_task(uint32_t period_us, callback_t func); static bool unregister_task(uint16_t id); diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index 9754d10ee..59892a0fa 100644 --- a/Src/HALAL/Services/Time/Scheduler.cpp +++ b/Src/HALAL/Services/Time/Scheduler.cpp @@ -10,11 +10,8 @@ #include -/* NOTE(vic): Pido perdón a Boris pero es la mejor manera que se me ha ocurrido hacer esto */ -#define SCHEDULER_RCC_TIMER_ENABLE glue(glue(RCC_APB1LENR_TIM, SCHEDULER_TIMER_IDX), EN) -#define SCHEDULER_GLOBAL_TIMER_IRQn glue(TIM, glue(SCHEDULER_TIMER_IDX, _IRQn)) +TIM_TypeDef* Scheduler_global_timer = nullptr; -#define Scheduler_global_timer ((TIM_TypeDef*)SCHEDULER_TIMER_BASE) namespace { constexpr uint64_t kMaxIntervalUs = static_cast(UINT32_MAX) / 2 + 1ULL; } @@ -68,6 +65,8 @@ void scheduler_global_timer_callback(void* raw) { void Scheduler::start() { static_assert((Scheduler::FREQUENCY % 1'000'000) == 0u, "frequenct must be a multiple of 1MHz"); + Scheduler_global_timer = + ST_LIB::TimerDomain::cmsis_timers[ST_LIB::timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; uint32_t prescaler = (SystemCoreClock / Scheduler::FREQUENCY); // setup prescaler @@ -133,23 +132,17 @@ void Scheduler::start() { if (prescaler == 0 || prescaler > 0xFFFF) { ErrorHandler("Invalid prescaler value: %u", prescaler); + return; } - // static_assert(prescaler < 0xFFFF, "Prescaler is 16 bit, so it must be in that range"); - // static_assert(prescaler != 0, "Prescaler must be in the range [1, 65535]"); -#ifndef SIM_ON - RCC->APB1LENR |= SCHEDULER_RCC_TIMER_ENABLE; -#endif - Scheduler_global_timer->PSC = (uint16_t)prescaler; Scheduler_global_timer->ARR = 0; Scheduler_global_timer->DIER |= LL_TIM_DIER_UIE; Scheduler_global_timer->CR1 = LL_TIM_CLOCKDIVISION_DIV1 | (Scheduler_global_timer->CR1 & ~TIM_CR1_CKD); - // Temporary solution for TimerDomain - ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[SCHEDULER_TIMER_IDX]] = - scheduler_global_timer_callback; + ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[static_cast(SCHEDULER_TIMER_DOMAIN + )]] = scheduler_global_timer_callback; Scheduler_global_timer->CNT = 0; /* Clear counter value */ diff --git a/Tests/Time/scheduler_test.cpp b/Tests/Time/scheduler_test.cpp index 600fdba0a..7376abe5b 100644 --- a/Tests/Time/scheduler_test.cpp +++ b/Tests/Time/scheduler_test.cpp @@ -16,6 +16,7 @@ class SchedulerTests : public ::testing::Test { Scheduler::sorted_task_ids_ = 0; Scheduler::global_tick_us_ = 0; Scheduler::current_interval_us_ = 0; + Scheduler_global_timer = TIM2_BASE; // Reset global callback task count count = 0; From 47efb88b6f539de629e1d954834f281484d5660b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez?= <120128034+victor-Lopez25@users.noreply.github.com> Date: Thu, 5 Mar 2026 00:56:02 +0100 Subject: [PATCH 26/41] Fixed issues with MSVC on windows in tests (#583) * Fixed issues with MSVC on windows * Move implementation into cpp * Return to inlining clz and ctz * fix formatting (hopefully) * more formatting fixes * fix formatting final please --- Inc/MockedDrivers/compiler_specific.hpp | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Inc/MockedDrivers/compiler_specific.hpp b/Inc/MockedDrivers/compiler_specific.hpp index 1524c886a..3e62d63c7 100644 --- a/Inc/MockedDrivers/compiler_specific.hpp +++ b/Inc/MockedDrivers/compiler_specific.hpp @@ -1,6 +1,12 @@ #pragma once #include +#if defined(_MSC_VER) +#define WIN32_LEAN_AND_MEAN +#include /* _BitScanForward, _BitScanReverse */ +#include /* _byteswap_ulong */ +#endif + /* * This file contains implementations or alternate names for * ARM GCC intrinsics that don't work on x86_64 / host platforms. @@ -38,7 +44,37 @@ static inline uint32_t __RBIT(uint32_t val) { return val; } -#define __CLZ __builtin_clz +inline uint32_t __CLZ(uint32_t val) { +#if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) + return __builtin_clz(val); +#elif defined(_MSC_VER) + DWORD idx = 0; + _BitScanReverse(&idx, val); + return 31 - idx; +#else + for (uint32_t i = 0; i < 32; i++) { + if ((val & (1 << (31 - i))) != 0) + return i; + } + return 32; +#endif +} + +inline uint32_t __CTZ(uint32_t val) { +#if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) + return __builtin_ctz(val); +#elif defined(_MSC_VER) + DWORD idx = 0; + _BitScanForward(&idx, val); + return idx; +#else + for (uint32_t i = 0; i < 32; i++) { + if ((val & (1 << i)) != 0) + return i; + } + return 32; +#endif +} #if defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__) void _ReadWriteBarrier(void); From 9cfa2404867fa774c47dd5310b4be4f5eb013f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez?= <120128034+victor-Lopez25@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:43:29 +0100 Subject: [PATCH 27/41] Fix: rcc_enable_timer for scheduler timer (#586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: rcc_enable_timer for scheduler timer * formatting nº1 --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index f48b57da9..508765a0b 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -719,7 +719,8 @@ TimerXList static inline std::array instances{}; static void init(std::span cfgs) { - rcc_enable_timer(cmsis_timers[static_cast(SCHEDULER_TIMER_DOMAIN)]); + TIM_TypeDef* sched_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; + rcc_enable_timer(sched_timer); for (std::size_t i = 0; i < N; i++) { const Config& e = cfgs[i]; From 10688b59b9c6e6d311096894ff81302623cbc234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Cant=C3=B3=20Catal=C3=A1n?= <144663567+Cantonplas@users.noreply.github.com> Date: Sat, 14 Mar 2026 00:30:27 +0100 Subject: [PATCH 28/41] Feat/no virtual on State Machine (#581) * No more fixed_vector(idk why I did that) * First version implementing std::tupple * No more copy constructor bullshit * Equal operator fixed, now it compiles * Compiling version, tests to be done * Okay now we check for duplicates * Some formating changes * Clanker made me do this so the start and exit functions stop when the condition is true * Made nesting consteval * Fix short circuit no use on return warning * Added state machine tests * Formating errors fixed * Ok now format errors are fixed * More tests need to be addes * Compile testing added, as well as reduced task array to only allow 16 task * Formating errores fixed * Delete tests for the tests * Now you cant use state machine without start * Fixed logic for nested machine start * Formating error fix * clanker commit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Now we use a concept for a correct tuple in state machine helper function * Formating fix --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../StateMachine/HeapStateOrder.hpp | 5 +- .../StateMachine/StackStateOrder.hpp | 5 +- Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp | 320 +++++++++----- Tests/CMakeLists.txt | 1 + Tests/StateMachine/state_machine_test.cpp | 400 ++++++++++++++++++ 5 files changed, 612 insertions(+), 119 deletions(-) create mode 100644 Tests/StateMachine/state_machine_test.cpp diff --git a/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp b/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp index 6d71b8d7a..502ee5779 100644 --- a/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp @@ -24,13 +24,12 @@ template class HeapStateOrder : public HeapOrder { } void process() override { - if (callback != nullptr && state_machine.is_on && - state_machine.get_current_state_id() == state) + if (callback != nullptr && state_machine.get_current_state_id() == state) callback(); } void parse(OrderProtocol* socket, uint8_t* data) override { - if (state_machine.is_on && state_machine.get_current_state_id() == state) + if (state_machine.get_current_state_id() == state) HeapOrder::parse(data); } }; diff --git a/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp b/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp index 83cffb9c4..98c6d6d55 100644 --- a/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp @@ -25,13 +25,12 @@ class StackStateOrder : public StackOrder { } void process() override { - if (this->callback != nullptr && state_machine.is_on && - state_machine.get_current_state_id() == state) + if (this->callback != nullptr && state_machine.get_current_state_id() == state) this->callback(); } void parse(OrderProtocol* socket, uint8_t* data) override { - if (state_machine.is_on && state_machine.get_current_state_id() == state) + if (state_machine.get_current_state_id() == state) StackOrder::parse(data); } }; diff --git a/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp b/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp index 0e038f573..4b90187ca 100644 --- a/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef STLIB_ETH #include "StateMachine/StateOrder.hpp" @@ -19,8 +20,6 @@ using ms = std::chrono::milliseconds; using us = std::chrono::microseconds; using s = std::chrono::seconds; -template using FixedVector = StaticVector; - template concept IsEnum = std::is_enum_v; @@ -31,7 +30,7 @@ concept ValidTime = using Callback = void (*)(); using Guard = bool (*)(); -static constexpr size_t NUMBER_OF_ACTIONS = 20; +static constexpr size_t NUMBER_OF_ACTIONS = 16; enum AlarmType { Milliseconds = 0, Microseconds = 1 }; @@ -58,19 +57,22 @@ concept are_transitions = (std::same_as> && ...); template class State { private: - FixedVector cyclic_actions = {}; - FixedVector on_enter_actions = {}; - FixedVector on_exit_actions = {}; + StaticVector cyclic_actions = {}; + StaticVector on_enter_actions = {}; + StaticVector on_exit_actions = {}; StateEnum state = {}; - FixedVector, NTransitions> transitions = {}; + StaticVector, NTransitions> transitions = {}; public: - [[no_unique_address]] FixedVector state_orders_ids = {}; + [[no_unique_address]] StaticVector state_orders_ids = {}; static constexpr size_t transition_count = NTransitions; template requires are_transitions consteval State(StateEnum state, T... transitions) : state(state) { + if (((transitions.target == state) || ...)) { + ErrorHandler("Current state cannot be the target of a transition"); + } (this->transitions.push_back(transitions), ...); } @@ -195,7 +197,13 @@ template is_on = false; } - constexpr void add_state_order(uint16_t id) { state_orders_ids.push_back(id); } + constexpr void add_state_order(uint16_t id) { +#ifdef STLIB_ETH + state_orders_ids.push_back(id); +#else + (void)id; +#endif + } template consteval TimedAction* add_cyclic_action(Callback action, TimeUnit period) { @@ -231,13 +239,24 @@ concept IsState = is_state::value; template concept are_states = (IsState && ...); +template +class StateMachine; + +template struct is_state_machine : std::false_type {}; + +template +struct is_state_machine> + : std::true_type {}; + +template +concept IsStateMachineClass = is_state_machine>::value; + /// Interface for State Machines to allow other classes to interact with the state machine without /// knowing its implementation class IStateMachine { public: virtual constexpr ~IStateMachine() = default; virtual void check_transitions() = 0; - virtual void set_on(bool is_on) = 0; virtual void force_change_state(size_t state) = 0; virtual size_t get_current_state_id() const = 0; constexpr bool operator==(const IStateMachine&) const = default; @@ -246,50 +265,111 @@ class IStateMachine { virtual void enter() = 0; virtual void exit() = 0; virtual void start() = 0; - template friend class StateMachine; + template friend class StateMachine; +}; + +template struct NestedMachineBinding { + StateEnum state; + NestedSMType* machine; + + constexpr bool operator==(const NestedMachineBinding&) const = default; }; -template +template struct is_nested_machine_binding : std::false_type {}; + +template +struct is_nested_machine_binding> : std::true_type {}; + +template +concept IsNestedMachineBinding = is_nested_machine_binding::value; +template struct is_nested_machine_binding_for : std::false_type {}; + +template +struct is_nested_machine_binding_for, StateEnum> + : std::true_type {}; + +template +concept IsNestedMachineBindingFor = is_nested_machine_binding_for::value; + +namespace StateMachineHelper { + +template +static consteval auto add_nesting(const State& state, NestedSMType& machine) { + return NestedMachineBinding{state.get_state(), &machine}; +} + +template + requires(IsNestedMachineBinding && ...) +static consteval auto add_nested_machines(Bindings... bindings) { + constexpr std::size_t count = sizeof...(Bindings); + if constexpr (count > 1) { + auto states = std::array{bindings.state...}; + for (std::size_t i = 0; i < count; ++i) { + for (std::size_t j = i + 1; j < count; ++j) { + if (states[i] == states[j]) { + ErrorHandler("Duplicate state found in add_nested_machines"); + } + } + } + } + return std::make_tuple(bindings...); +} + +} // namespace StateMachineHelper + +template class StateMachine : public IStateMachine { -private: - struct NestedPair { - StateEnum state; - IStateMachine* machine; - constexpr bool operator==(const NestedPair&) const = default; - }; + static_assert((IsEnum), "StateEnum must be an enum type"); + static_assert( + (IsStateMachineClass && ...), + "All nested machines must be of type StateMachine" + ); - StateEnum current_state; + template friend class StateMachine; -public: - constexpr ~StateMachine() override = default; + StateEnum current_state; + std::tuple...> nested_machines; + StaticVector, NStates> states; + StaticVector, NTransitions> transitions = {}; + std::array, NStates> transitions_assoc = {}; + bool called_start = false; - void force_change_state(size_t state) override { - StateEnum new_state = static_cast(state); + void perform_state_change(StateEnum new_state) { if (current_state == new_state) { return; } + + exit(); + std::apply( + [this](auto&... nested) { + (void)((nested.state == this->current_state && nested.machine != nullptr + ? (nested.machine->exit(), true) + : false) || + ...); + }, + nested_machines + ); + #ifdef STLIB_ETH remove_state_orders(); #endif - exit(); current_state = new_state; enter(); + std::apply( + [this](auto&... nested) { + (void)((nested.state == this->current_state && nested.machine != nullptr + ? (nested.machine->enter(), true) + : false) || + ...); + }, + nested_machines + ); + #ifdef STLIB_ETH refresh_state_orders(); #endif } - size_t get_current_state_id() const override { return static_cast(current_state); } - - bool is_on = true; - void set_on(bool is_on) override { this->is_on = is_on; } - -private: - FixedVector, NStates> states; - FixedVector, NTransitions> transitions = {}; - std::array, NStates> transitions_assoc = {}; - FixedVector nested_state_machine = {}; - constexpr bool operator==(const StateMachine&) const = default; template consteval void process_state(const State& state, size_t offset) { @@ -316,12 +396,17 @@ class StateMachine : public IStateMachine { public: template ... S> - consteval StateMachine(StateEnum initial_state, S... states) : current_state(initial_state) { - // Sort states by their enum value + consteval StateMachine( + StateEnum initial_state, + const std::tuple...>& nested_machines_tuple, + S... states_input + ) + : current_state(initial_state), nested_machines(nested_machines_tuple) { + using StateType = State; std::array sorted_states; size_t index = 0; - ((sorted_states[index++] = StateType(states)), ...); + ((sorted_states[index++] = StateType(states_input)), ...); for (size_t i = 0; i < sorted_states.size(); i++) { for (size_t j = 0; j < sorted_states.size() - 1; j++) { @@ -333,6 +418,15 @@ class StateMachine : public IStateMachine { } } + // Check for duplicate states + for (size_t i = 0; i < sorted_states.size() - 1; i++) { + for (size_t j = i + 1; j < sorted_states.size(); j++) { + if (sorted_states[i].get_state() == sorted_states[j].get_state()) { + ErrorHandler("Duplicate state found in StateMachine constructor"); + } + } + } + // Check that states are contiguous and start from 0 for (size_t i = 0; i < sorted_states.size(); i++) { if (static_cast(sorted_states[i].get_state()) != i) { @@ -350,82 +444,64 @@ class StateMachine : public IStateMachine { offset += s.get_transitions().size(); } } + constexpr ~StateMachine() override = default; void check_transitions() override { + if (!called_start) [[unlikely]] { + ErrorHandler("Error: check_transitions called before StateMachine.start()"); + return; + } auto& [i, n] = transitions_assoc[static_cast(current_state)]; + for (auto index = i; index < i + n; ++index) { const auto& t = transitions[index]; if (t.predicate()) { - exit(); - for (auto& nested : nested_state_machine) { - if (nested.state == current_state) { - nested.machine->exit(); - break; - } - } -#ifdef STLIB_ETH - remove_state_orders(); -#endif - current_state = t.target; - enter(); - for (auto& nested : nested_state_machine) { - if (nested.state == current_state) { - nested.machine->enter(); - break; - } - } -#ifdef STLIB_ETH - refresh_state_orders(); -#endif + perform_state_change(t.target); break; } } - for (auto& nested : nested_state_machine) { - if (nested.state == current_state) { - nested.machine->check_transitions(); - break; - } - } + std::apply( + [this](auto&... nested) { + (void)((nested.state == this->current_state && nested.machine != nullptr + ? (nested.machine->check_transitions(), true) + : false) || + ...); + }, + nested_machines + ); } void start() override { + called_start = true; enter(); - for (auto& nested : nested_state_machine) { - if (nested.state == current_state) { - nested.machine->start(); - break; - } - } + std::apply( + [this](auto&... nested) { + (void)((nested.state == this->current_state && nested.machine != nullptr + ? (nested.machine->start(), true) + : false) || + ...); + }, + nested_machines + ); + std::apply( + [this](auto&... nested) { + (void)((nested.machine != nullptr ? (nested.machine->called_start = true, true) + : false) || + ...); + }, + nested_machines + ); } - template void force_change_state(const State& state) { - StateEnum new_state = state.get_state(); - if (current_state == new_state) { - return; - } + void force_change_state(size_t state) override { + perform_state_change(static_cast(state)); + } - exit(); - for (auto& nested : nested_state_machine) { - if (nested.state == current_state) { - nested.machine->exit(); - break; - } - } -#ifdef STLIB_ETH - remove_state_orders(); -#endif - current_state = new_state; - enter(); - for (auto& nested : nested_state_machine) { - if (nested.state == current_state) { - nested.machine->enter(); - break; - } - } -#ifdef STLIB_ETH - refresh_state_orders(); -#endif + size_t get_current_state_id() const override { return static_cast(current_state); } + + template void force_change_state(const State& state) { + perform_state_change(state.get_state()); } template @@ -473,22 +549,6 @@ class StateMachine : public IStateMachine { ErrorHandler("Error: The state is not added to the state machine"); } - template - constexpr void - add_state_machine(IStateMachine& state_machine, const State& state) { - for (auto& nested : nested_state_machine) { - if (nested.state == state.get_state()) { - ErrorHandler( - "Only one Nested State Machine can be added per state, tried to add to state: " - "%d", - static_cast(state.get_state()) - ); - return; - } - } - nested_state_machine.push_back({state.get_state(), &state_machine}); - } - StateEnum get_current_state() const { return current_state; } inline void refresh_state_orders() { @@ -552,6 +612,40 @@ consteval auto make_state_machine(StateEnum initial_state, States... states) { return StateMachine( initial_state, + std::tuple<>{}, + states... + ); +} +/* @brief Helper function to create a StateMachine instance + * + * @tparam States Variadic template parameter pack representing the states + * @param initial_state The initial state enum value + * @param nested_machines Tuple of NestedMachineBinding representing the nested state machines to + * its corresponding state + * @param states The states to be included in the state machine + * @return A StateMachine instance initialized with the provided initial state and states, as well + * as the nested state machines + */ + +template + requires are_states && + (IsNestedMachineBindingFor && ...) +consteval auto make_state_machine( + StateEnum initial_state, + std::tuple nested_machines, + States... states +) { + constexpr size_t number_of_states = sizeof...(states); + constexpr size_t number_of_transitions = + (std::remove_reference_t::transition_count + ... + 0); + + return StateMachine< + StateEnum, + number_of_states, + number_of_transitions, + typename std::remove_pointer().machine)>::type...>( + initial_state, + nested_machines, states... ); } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 845f52058..a92d1373c 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable(${STLIB_TEST_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/Time/encoder_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/scheduler_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/timer_wrapper_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/StateMachine/state_machine_test.cpp ${CMAKE_CURRENT_LIST_DIR}/adc_test.cpp ${CMAKE_CURRENT_LIST_DIR}/spi2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/dma2_test.cpp diff --git a/Tests/StateMachine/state_machine_test.cpp b/Tests/StateMachine/state_machine_test.cpp new file mode 100644 index 000000000..2cd1119f2 --- /dev/null +++ b/Tests/StateMachine/state_machine_test.cpp @@ -0,0 +1,400 @@ +#include +#include "ST-LIB_LOW/StateMachine/StateMachine.hpp" +#include "HALAL/Services/Time/Scheduler.hpp" + +enum class MasterState { A, B, C }; + +enum class SubState { S1, S2 }; + +static int a_enter_count = 0; +static int a_exit_count = 0; +static int a_cyclic_count = 0; + +static int b_enter_count = 0; +static int b_exit_count = 0; +static int b_cyclic_count = 0; + +static int c_enter_count = 0; +static int c_exit_count = 0; + +static int s1_enter_count = 0; +static int s1_exit_count = 0; +static int s1_cyclic_count = 0; + +static int s2_enter_count = 0; +static int s2_exit_count = 0; + +static bool condition_a_to_b = false; +static bool condition_b_to_c = false; +static bool condition_c_to_a = false; +static bool condition_s1_to_s2 = false; + +static void reset_test_state() { + a_enter_count = 0; + a_exit_count = 0; + a_cyclic_count = 0; + + b_enter_count = 0; + b_exit_count = 0; + b_cyclic_count = 0; + + c_enter_count = 0; + c_exit_count = 0; + + s1_enter_count = 0; + s1_exit_count = 0; + s1_cyclic_count = 0; + + s2_enter_count = 0; + s2_exit_count = 0; + + condition_a_to_b = false; + condition_b_to_c = false; + condition_c_to_a = false; + condition_s1_to_s2 = false; + + Scheduler::active_task_count_ = 0; + Scheduler::free_bitmap_ = 0xFFFF'FFFF; + Scheduler::ready_bitmap_ = 0; + Scheduler::sorted_task_ids_ = 0; + Scheduler::global_tick_us_ = 0; + Scheduler::current_interval_us_ = 0; + + TIM2_BASE->CNT = 0; + TIM2_BASE->ARR = 0; + TIM2_BASE->SR = 0; + TIM2_BASE->CR1 = 0; + TIM2_BASE->DIER = 0; +} + +static void tick_scheduler(int ticks) { + TIM2_BASE->PSC = 2; + for (int i = 0; i < ticks; i++) { + for (int j = 0; j <= TIM2_BASE->PSC; j++) + TIM2_BASE->inc_cnt_and_check(1); + Scheduler::update(); + } +} + +static constexpr auto state_s1 = + make_state(SubState::S1, Transition{SubState::S2, []() { + return condition_s1_to_s2; + }}); +static constexpr auto state_s2 = make_state(SubState::S2); + +static inline auto test_nested_machine = []() consteval { + auto sm = make_state_machine(SubState::S1, state_s1, state_s2); + using namespace std::chrono_literals; + + sm.add_enter_action([]() { s1_enter_count++; }, state_s1); + sm.add_exit_action([]() { s1_exit_count++; }, state_s1); + sm.add_cyclic_action([]() { s1_cyclic_count++; }, 10ms, state_s1); + + sm.add_enter_action([]() { s2_enter_count++; }, state_s2); + sm.add_exit_action([]() { s2_exit_count++; }, state_s2); + + return sm; +}(); + +static constexpr auto state_a = + make_state(MasterState::A, Transition{MasterState::B, []() { + return condition_a_to_b; + }}); +static constexpr auto state_b = + make_state(MasterState::B, Transition{MasterState::C, []() { + return condition_b_to_c; + }}); +static constexpr auto state_c = + make_state(MasterState::C, Transition{MasterState::A, []() { + return condition_c_to_a; + }}); + +static inline auto test_machine = []() consteval { + auto nested = StateMachineHelper::add_nesting(state_b, test_nested_machine); + auto sm = make_state_machine( + MasterState::A, + StateMachineHelper::add_nested_machines(nested), + state_a, + state_b, + state_c + ); + using namespace std::chrono_literals; + + sm.add_enter_action([]() { a_enter_count++; }, state_a); + sm.add_exit_action([]() { a_exit_count++; }, state_a); + sm.add_cyclic_action([]() { a_cyclic_count++; }, 10ms, state_a); + + sm.add_enter_action([]() { b_enter_count++; }, state_b); + sm.add_exit_action([]() { b_exit_count++; }, state_b); + sm.add_cyclic_action([]() { b_cyclic_count++; }, 20ms, state_b); + + sm.add_enter_action([]() { c_enter_count++; }, state_c); + sm.add_exit_action([]() { c_exit_count++; }, state_c); + + return sm; +}(); + +class StateMachineTest : public ::testing::Test { +protected: + void SetUp() override { + reset_test_state(); + + test_machine.force_change_state((size_t)MasterState::A); + test_nested_machine.force_change_state((size_t)SubState::S1); + + test_machine.force_change_state((size_t)MasterState::A); + test_nested_machine.force_change_state((size_t)SubState::S1); + test_machine.get_states()[0].unregister_all_timed_actions(); + test_machine.get_states()[1].unregister_all_timed_actions(); + test_machine.get_states()[2].unregister_all_timed_actions(); + test_nested_machine.get_states()[0].unregister_all_timed_actions(); + test_nested_machine.get_states()[1].unregister_all_timed_actions(); + + reset_test_state(); + } +}; + +TEST_F(StateMachineTest, StartTriggersEnterActions) { + test_machine.start(); + EXPECT_EQ(a_enter_count, 1); + EXPECT_EQ(a_exit_count, 0); + EXPECT_EQ(b_enter_count, 0); + EXPECT_EQ(test_machine.get_current_state(), MasterState::A); +} + +TEST_F(StateMachineTest, BasicTransition) { + test_machine.start(); + a_enter_count = 0; + + condition_a_to_b = true; + test_machine.check_transitions(); + + EXPECT_EQ(test_machine.get_current_state(), MasterState::B); + EXPECT_EQ(a_exit_count, 1); + EXPECT_EQ(b_enter_count, 1); + EXPECT_EQ(s1_enter_count, 1); // Nested machine should also enter its initial state +} + +TEST_F(StateMachineTest, NestedTransition) { + test_machine.start(); + condition_a_to_b = true; + test_machine.check_transitions(); + + EXPECT_EQ(test_machine.get_current_state(), MasterState::B); + EXPECT_EQ(test_nested_machine.get_current_state(), SubState::S1); + + condition_s1_to_s2 = true; + test_machine.check_transitions(); + + EXPECT_EQ(test_nested_machine.get_current_state(), SubState::S2); + EXPECT_EQ(s1_exit_count, 1); + EXPECT_EQ(s2_enter_count, 1); +} + +TEST_F(StateMachineTest, MasterStateChangeExitsNested) { + test_machine.start(); + condition_a_to_b = true; + test_machine.check_transitions(); + + EXPECT_EQ(test_nested_machine.get_current_state(), SubState::S1); + s1_enter_count = 0; + b_exit_count = 0; + + condition_b_to_c = true; + test_machine.check_transitions(); + + EXPECT_EQ(test_machine.get_current_state(), MasterState::C); + EXPECT_EQ(b_exit_count, 1); + EXPECT_EQ(s1_exit_count, 1); + EXPECT_EQ(c_enter_count, 1); +} + +TEST_F(StateMachineTest, CyclicActionsRun) { + test_machine.start(); + + Scheduler::start(); + + tick_scheduler(100); + tick_scheduler(10000); + + EXPECT_GE(a_cyclic_count, 1); + EXPECT_EQ(b_cyclic_count, 0); + + condition_a_to_b = true; + test_machine.check_transitions(); + + a_cyclic_count = 0; + + tick_scheduler(20000); + + EXPECT_EQ(a_cyclic_count, 0); // A cyclic shouldn't run + EXPECT_GE(b_cyclic_count, 1); // B cyclic should run + EXPECT_GE(s1_cyclic_count, 1); // Nested S1 cyclic should run +} + +static int custom_task_count = 0; + +static int check_transition_task_count = 0; + +TEST_F(StateMachineTest, StressTestWithScheduler) { + test_machine.start(); + Scheduler::start(); + + custom_task_count = 0; + check_transition_task_count = 0; + + auto custom_task_id = Scheduler::register_task(999, []() { custom_task_count++; }); + + auto check_task_id = Scheduler::register_task(499, []() { + test_machine.check_transitions(); + check_transition_task_count++; + }); + + for (int i = 0; i < 50; i++) { + // A -> B + condition_a_to_b = true; + condition_b_to_c = false; + condition_c_to_a = false; + tick_scheduler(1500); + + // B -> C + condition_a_to_b = false; + condition_b_to_c = true; + condition_c_to_a = false; + tick_scheduler(1500); + + // C -> A + condition_a_to_b = false; + condition_b_to_c = false; + condition_c_to_a = true; + tick_scheduler(1500); + } + + condition_a_to_b = false; + condition_b_to_c = false; + condition_c_to_a = false; + + EXPECT_GT(custom_task_count, 100); + EXPECT_GT(check_transition_task_count, 300); + + // Because state changes were strictly faster than 10ms, no cyclic tasks should have fired + EXPECT_EQ(a_cyclic_count, 0); + EXPECT_EQ(b_cyclic_count, 0); + + tick_scheduler(20000); // 20ms + EXPECT_GE(a_cyclic_count, 1); + + condition_a_to_b = true; + tick_scheduler(40000); // 40ms in State B + EXPECT_GE(b_cyclic_count, 1); + + EXPECT_LE(Scheduler::active_task_count_, 5); + + Scheduler::unregister_task(custom_task_id); + Scheduler::unregister_task(check_task_id); +} + +enum class StartAtOne { A = 1, B = 2 }; +enum class NonContiguous { A = 0, B = 2 }; + +template struct constant_eval {}; + +template +concept CanCompile = requires { typename constant_eval; }; + +struct DuplicateNestedCheck { + static consteval bool invoke() { + auto sm_nested_1 = make_state_machine(SubState::S1, state_s1, state_s2); + auto sm_nested_2 = make_state_machine(SubState::S1, state_s1, state_s2); + + auto nested1 = StateMachineHelper::add_nesting(state_a, sm_nested_1); + auto nested2 = StateMachineHelper::add_nesting(state_a, sm_nested_2); + + auto sm = make_state_machine( + MasterState::A, + StateMachineHelper::add_nested_machines(nested1, nested2), + state_a, + state_b + ); + return true; + } +}; + +struct ValidNestedCheck { + static consteval bool invoke() { + auto sm_nested_1 = make_state_machine(SubState::S1, state_s1, state_s2); + auto sm_nested_2 = make_state_machine(SubState::S1, state_s1, state_s2); + + auto nested1 = StateMachineHelper::add_nesting(state_a, sm_nested_1); + auto nested2 = StateMachineHelper::add_nesting(state_b, sm_nested_2); + + auto sm = make_state_machine( + MasterState::A, + StateMachineHelper::add_nested_machines(nested1, nested2), + state_a, + state_b + ); + return true; + } +}; + +struct StartAtOneCheck { + static consteval bool invoke() { + auto stA = make_state(StartAtOne::A); + auto stB = make_state(StartAtOne::B); + auto sm = make_state_machine(StartAtOne::A, stA, stB); + return true; + } +}; + +struct NonContiguousCheck { + static consteval bool invoke() { + auto stA = make_state(NonContiguous::A); + auto stB = make_state(NonContiguous::B); + auto sm = make_state_machine(NonContiguous::A, stA, stB); + return true; + } +}; + +struct DuplicateStateInConstructorCheck { + static consteval bool invoke() { + auto stA = make_state(MasterState::A); + auto sm = make_state_machine(MasterState::A, stA, stA); + return true; + } +}; + +struct SelfTransitionCheck { + static consteval bool invoke() { + auto stA = make_state(MasterState::A, Transition{MasterState::A, [] { + return true; + }}); + return true; + } +}; + +struct InvalidActionStateCheck { + static consteval bool invoke() { + auto stA = make_state(MasterState::A); + auto sm = make_state_machine(MasterState::A, stA); + auto stB = make_state(MasterState::B); + sm.add_enter_action([] {}, stB); + return true; + } +}; + +TEST(StateMachineCompileCheck, ValidatesSFINAEOntoS_M) { + static_assert(CanCompile, "Valid nested mapping should compile."); + static_assert(!CanCompile, "Duplicate state mappings must not compile."); + static_assert(!CanCompile, "State Enums must start at 0."); + static_assert(!CanCompile, "State Enums must be contiguous."); + static_assert( + !CanCompile, + "Must not allow duplicate states in Constructor." + ); + static_assert(!CanCompile, "Self transition must not compile."); + static_assert( + !CanCompile, + "Adding action to invalid state must not compile." + ); +} From b5846fd9ccce893fec68b153991b831e491b3ae0 Mon Sep 17 00:00:00 2001 From: Boris Mladenov Beslimov Date: Sun, 15 Mar 2026 15:51:38 +0100 Subject: [PATCH 29/41] fix(MDMA): Fix, MDMA AHBS is 32-bit only (#588) --- Inc/HALAL/Models/MDMA/MDMA.hpp | 108 ++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 9 deletions(-) diff --git a/Inc/HALAL/Models/MDMA/MDMA.hpp b/Inc/HALAL/Models/MDMA/MDMA.hpp index 91ceafd71..f9fb97eda 100644 --- a/Inc/HALAL/Models/MDMA/MDMA.hpp +++ b/Inc/HALAL/Models/MDMA/MDMA.hpp @@ -47,6 +47,7 @@ class MDMA { } else { CLEAR_BIT(node.CTBR, MDMA_CTBR_DBUS); } + reconfigure_ctcr(); } void set_source(void* source) { uint32_t source_address = reinterpret_cast(source); @@ -59,6 +60,7 @@ class MDMA { } else { CLEAR_BIT(node.CTBR, MDMA_CTBR_SBUS); } + reconfigure_ctcr(); } auto get_node() -> MDMA_LinkNodeTypeDef* { return &node; } auto get_size() -> uint32_t { return node.CBNDTR; } @@ -70,13 +72,88 @@ class MDMA { private: alignas(8) MDMA_LinkNodeTypeDef node; + size_t transfer_size{0}; + + void reconfigure_ctcr() { + const uintptr_t src = node.CSAR; + const uintptr_t dst = node.CDAR; + const size_t size = transfer_size; + + if (size == 0) + return; + + const size_t effective_size = compute_elem_size(src, dst, size); + + uint32_t source_data_size, dest_data_size, source_inc, dest_inc; + switch (static_cast(effective_size)) { + case 2: + source_data_size = MDMA_SRC_DATASIZE_HALFWORD; + dest_data_size = MDMA_DEST_DATASIZE_HALFWORD; + source_inc = MDMA_SRC_INC_HALFWORD; + dest_inc = MDMA_DEST_INC_HALFWORD; + break; + case 4: + source_data_size = MDMA_SRC_DATASIZE_WORD; + dest_data_size = MDMA_DEST_DATASIZE_WORD; + source_inc = MDMA_SRC_INC_WORD; + dest_inc = MDMA_DEST_INC_WORD; + break; + case 8: + source_data_size = MDMA_SRC_DATASIZE_DOUBLEWORD; + dest_data_size = MDMA_DEST_DATASIZE_DOUBLEWORD; + source_inc = MDMA_SRC_INC_DOUBLEWORD; + dest_inc = MDMA_DEST_INC_DOUBLEWORD; + break; + default: + source_data_size = MDMA_SRC_DATASIZE_BYTE; + dest_data_size = MDMA_DEST_DATASIZE_BYTE; + source_inc = MDMA_SRC_INC_BYTE; + dest_inc = MDMA_DEST_INC_BYTE; + break; + } + + const uint32_t elem_size = static_cast(effective_size); + uint32_t buf_len = static_cast(std::min(size, static_cast(128U))); + buf_len = (buf_len / elem_size) * elem_size; + if (buf_len == 0) + buf_len = elem_size; + + // SINCOS/DINCOS must be included: MDMA_SRC_INC_* constants encode both + // SINC and SINCOS (increment offset size) bits together. + MODIFY_REG( + node.CTCR, + MDMA_CTCR_SINC | MDMA_CTCR_SINCOS | MDMA_CTCR_DINC | MDMA_CTCR_DINCOS | + MDMA_CTCR_SSIZE | MDMA_CTCR_DSIZE | MDMA_CTCR_TLEN, + source_inc | dest_inc | source_data_size | dest_data_size | + ((buf_len - 1U) << MDMA_CTCR_TLEN_Pos) + ); + } + + static bool is_tcm(uintptr_t addr) { + return (addr < 0x00010000U) || (addr >= 0x20000000U && addr < 0x20020000U); + } + + // Returns the largest power-of-2 element size (1/2/4/8) valid for both addresses and size. + // addr_or==0 (both null) is treated as maximally aligned rather than causing div-by-zero. + static size_t compute_elem_size(uintptr_t src, uintptr_t dst, size_t size) { + const size_t max_elem = (is_tcm(src) || is_tcm(dst)) ? 4u : 8u; + const size_t size_gran = size & -size; + const uintptr_t addr_or = src | dst; + const size_t addr_gran = + (addr_or != 0u) ? static_cast(addr_or & -addr_or) : max_elem; + return std::min({size_gran, addr_gran, max_elem}); + } void init_node(void* src, void* dst, size_t size) { + if (size == 0) { + ErrorHandler("MDMA: zero-length transfer is invalid"); + return; + } + MDMA_LinkNodeConfTypeDef nodeConfig{}; nodeConfig.Init.DataAlignment = MDMA_DATAALIGN_RIGHT; nodeConfig.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE; nodeConfig.Init.DestBurst = MDMA_DEST_BURST_SINGLE; - nodeConfig.Init.BufferTransferLength = 128; nodeConfig.Init.TransferTriggerMode = MDMA_FULL_TRANSFER; nodeConfig.Init.SourceBlockAddressOffset = 0; nodeConfig.Init.DestBlockAddressOffset = 0; @@ -86,6 +163,7 @@ class MDMA { nodeConfig.Init.Request = MDMA_REQUEST_SW; this->node = {}; + this->transfer_size = size; nodeConfig.SrcAddress = reinterpret_cast(src); nodeConfig.DstAddress = reinterpret_cast(dst); nodeConfig.BlockDataLength = static_cast(size); @@ -95,14 +173,11 @@ class MDMA { uint32_t source_inc; uint32_t dest_inc; - size_t effective_size = size; - if (effective_size == 2 && - ((reinterpret_cast(src) | reinterpret_cast(dst)) & 1)) - effective_size = 1; // Odd address, so fallback to byte-wise - else if (effective_size == 4 && ((reinterpret_cast(src) | reinterpret_cast(dst)) & 3)) - effective_size = 1; // Not word-aligned, so fallback to byte-wise - else if (effective_size == 8 && ((reinterpret_cast(src) | reinterpret_cast(dst)) & 7)) - effective_size = 1; // Not doubleword-aligned, so fallback to byte-wise + const size_t effective_size = compute_elem_size( + reinterpret_cast(src), + reinterpret_cast(dst), + size + ); switch (static_cast(effective_size)) { case 2: @@ -136,9 +211,24 @@ class MDMA { nodeConfig.Init.SourceInc = source_inc; nodeConfig.Init.DestinationInc = dest_inc; + // BufferTransferLength must be <= BlockDataLength and a multiple of the element size. + const uint32_t elem_size = static_cast(effective_size); + uint32_t buf_len = static_cast(std::min(size, static_cast(128))); + buf_len = (buf_len / elem_size) * elem_size; + if (buf_len == 0) + buf_len = elem_size; + nodeConfig.Init.BufferTransferLength = buf_len; + if (HAL_MDMA_LinkedList_CreateNode(&node, &nodeConfig) != HAL_OK) { ErrorHandler("Error creating linked list in MDMA"); } + + // HAL_MDMA_LinkedList_CreateNode only sets the request field in CTBR; + // bus routing bits must be set explicitly for TCM addresses. + if (is_tcm(reinterpret_cast(src))) + SET_BIT(node.CTBR, MDMA_CTBR_SBUS); + if (is_tcm(reinterpret_cast(dst))) + SET_BIT(node.CTBR, MDMA_CTBR_DBUS); } }; From cc4e9bcd0b1c46a3ab72614ecb66ee927c272e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez?= <120128034+victor-Lopez25@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:52:43 +0100 Subject: [PATCH 30/41] Fix Scheduler race conditions (#591) --- .vscode/launch.json | 23 +++++++++++- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 7 ++-- Inc/HALAL/Services/Time/Scheduler.hpp | 19 +++++----- Src/HALAL/Services/Time/Scheduler.cpp | 38 +++++++++++++++----- Tests/StateMachine/state_machine_test.cpp | 4 +++ 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4643e2cb5..7c7f584d5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -26,6 +26,27 @@ "stopAtEntry": false, "MIMode": "lldb", "preLaunchTask": "CMake: build" - } + }, + { + "name": "Debug ST-LIB tests (simulator) [gdb]", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/out/build/simulator/Tests/st-lib-test", + "args": [ + "--gtest_break_on_failure" + ], + "cwd": "${workspaceFolder}", + "externalConsole": false, + "stopAtEntry": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "CMake: build" + }, ] } diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 508765a0b..9382fe656 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -26,8 +26,7 @@ (SCHEDULER_TIMER_DOMAIN != 23) && (SCHEDULER_TIMER_DOMAIN != 24) #error Scheduler timer must be a 32 bit timer #endif - -#define SCHEDULER_GLOBAL_TIMER_IRQn glue(TIM, glue(SCHEDULER_TIMER_DOMAIN, _IRQn)) +extern TIM_TypeDef* Scheduler_global_timer; // NOTE: only works for static arrays #define ARRAY_LENGTH(a) (sizeof(a) / sizeof(*a)) @@ -719,8 +718,8 @@ TimerXList static inline std::array instances{}; static void init(std::span cfgs) { - TIM_TypeDef* sched_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; - rcc_enable_timer(sched_timer); + Scheduler_global_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; + rcc_enable_timer(Scheduler_global_timer); for (std::size_t i = 0; i < N; i++) { const Config& e = cfgs[i]; diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index dbe965891..fd6b414d4 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -23,7 +23,10 @@ extern TIM_TypeDef* Scheduler_global_timer; struct Scheduler { using callback_t = void (*)(); - static constexpr uint32_t INVALID_ID = 0xFFu; + static constexpr uint32_t kMaxTasks = 16; + // INVALID_ID must be an even multiple of kMaxTasks. + // if it isn't it could theoretically be used as an id in set_timeout + static constexpr uint32_t INVALID_ID = 2 * kMaxTasks; static void start(); static void update(); @@ -37,13 +40,8 @@ struct Scheduler { static uint16_t set_timeout(uint32_t microseconds, callback_t func); static bool cancel_timeout(uint16_t id); - // static void global_timer_callback(); - - // Have to be public because SCHEDULER_GLOBAL_TIMER_CALLBACK won't work - // otherwise - // static const uint32_t global_timer_base = SCHEDULER_TIMER_BASE; + // internal static void on_timer_update(); - #ifndef SIM_ON private: #endif @@ -55,7 +53,12 @@ struct Scheduler { bool repeating{false}; }; - static constexpr std::size_t kMaxTasks = 16; + static_assert( + ((INVALID_ID / kMaxTasks) % 2) == 0, + "INVALID_ID must be an even multiple of kMaxTasks" + ); + static_assert(INVALID_ID >= kMaxTasks, "INVALID_ID must not be a possible task id"); + static_assert((kMaxTasks & (kMaxTasks - 1)) == 0, "kMaxTasks must be a power of two"); static constexpr uint32_t FREQUENCY = 1'000'000u; // 1 MHz -> 1us precision diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index 59892a0fa..9f01f4124 100644 --- a/Src/HALAL/Services/Time/Scheduler.cpp +++ b/Src/HALAL/Services/Time/Scheduler.cpp @@ -10,6 +10,11 @@ #include +#define SCHEDULER_GLOBAL_TIMER_IRQn glue(TIM, glue(SCHEDULER_TIMER_DOMAIN, _IRQn)) + +#define SchedLock() NVIC_DisableIRQ(SCHEDULER_GLOBAL_TIMER_IRQn) +#define SchedUnlock() NVIC_EnableIRQ(SCHEDULER_GLOBAL_TIMER_IRQn) + TIM_TypeDef* Scheduler_global_timer = nullptr; namespace { @@ -35,8 +40,6 @@ inline void Scheduler::set_at(uint8_t idx, uint8_t id) { uint32_t shift = idx * 4; uint64_t clearmask = ~(0xFF << shift); Scheduler::sorted_task_ids_ = (sorted_task_ids_ & clearmask) | (id << shift); - // sorted_task_ids_ |= ((id & 0x0F) << shift); // This is also an option in case id is - // incorrect, I don't think it's necessary though } inline uint8_t Scheduler::front_id() { return *((uint8_t*)&sorted_task_ids_) & 0xF; } inline void Scheduler::pop_front() { @@ -68,6 +71,7 @@ void Scheduler::start() { Scheduler_global_timer = ST_LIB::TimerDomain::cmsis_timers[ST_LIB::timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; + // TODO: change this to use TimerDomain::get_timer_clock()? uint32_t prescaler = (SystemCoreClock / Scheduler::FREQUENCY); // setup prescaler { @@ -155,11 +159,15 @@ void Scheduler::start() { void Scheduler::update() { while (ready_bitmap_ != 0u) { uint32_t bit_index = static_cast(__builtin_ctz(ready_bitmap_)); - ready_bitmap_ &= ~(1u << bit_index); // Clear the bit + + CLEAR_BIT(ready_bitmap_, 1u << bit_index); + Task& task = tasks_[bit_index]; task.callback(); if (!task.repeating) [[unlikely]] { + SchedLock(); release_slot(static_cast(bit_index)); + SchedUnlock(); } } } @@ -299,7 +307,9 @@ void Scheduler::on_timer_update() { break; // Task is in the future, stop processing } pop_front(); - ready_bitmap_ |= (1u << candidate_id); // mark task as ready + + // mark task as ready + SET_BIT(ready_bitmap_, 1u << candidate_id); if (task.repeating) [[likely]] { task.next_fire_us = static_cast(global_tick_us_ + task.period_us); @@ -319,18 +329,21 @@ uint16_t Scheduler::register_task(uint32_t period_us, callback_t func) { return static_cast(Scheduler::INVALID_ID); uint8_t slot = allocate_slot(); - if (slot == Scheduler::INVALID_ID) + if (slot == Scheduler::INVALID_ID) [[unlikely]] return slot; Task& task = tasks_[slot]; task.callback = func; task.period_us = period_us; task.repeating = true; + task.id = static_cast(slot); + + SchedLock(); task.next_fire_us = static_cast(global_tick_us_ + Scheduler_global_timer->CNT + period_us); - task.id = static_cast(slot); insert_sorted(slot); schedule_next_interval(); + SchedUnlock(); return task.id; } @@ -343,22 +356,25 @@ uint16_t Scheduler::set_timeout(uint32_t microseconds, callback_t func) { return static_cast(Scheduler::INVALID_ID); uint8_t slot = allocate_slot(); - if (slot == Scheduler::INVALID_ID) + if (slot == Scheduler::INVALID_ID) [[unlikely]] return slot; Task& task = tasks_[slot]; task.callback = func; task.period_us = microseconds; task.repeating = false; - task.next_fire_us = static_cast(global_tick_us_ + microseconds); task.id = slot + Scheduler::timeout_idx_ * Scheduler::kMaxTasks; - // Add 2 instead of 1 so overflow doesn't make timeout_idx == 0, // we need it to never be 0 Scheduler::timeout_idx_ += 2; + SchedLock(); + task.next_fire_us = + static_cast(global_tick_us_ + Scheduler_global_timer->CNT + microseconds); insert_sorted(slot); schedule_next_interval(); + SchedUnlock(); + return task.id; } @@ -368,9 +384,11 @@ bool Scheduler::unregister_task(uint16_t id) { if (free_bitmap_ & (1UL << id)) return false; + SchedLock(); remove_sorted(id); release_slot(id); schedule_next_interval(); + SchedUnlock(); return true; } @@ -384,8 +402,10 @@ bool Scheduler::cancel_timeout(uint16_t id) { if (free_bitmap_ & (1UL << idx)) return false; + SchedLock(); remove_sorted(idx); release_slot(idx); schedule_next_interval(); + SchedUnlock(); return true; } diff --git a/Tests/StateMachine/state_machine_test.cpp b/Tests/StateMachine/state_machine_test.cpp index 2cd1119f2..017c5a0d6 100644 --- a/Tests/StateMachine/state_machine_test.cpp +++ b/Tests/StateMachine/state_machine_test.cpp @@ -1,6 +1,7 @@ #include #include "ST-LIB_LOW/StateMachine/StateMachine.hpp" #include "HALAL/Services/Time/Scheduler.hpp" +#include "HALAL/Models/TimerDomain/TimerDomain.hpp" enum class MasterState { A, B, C }; @@ -65,6 +66,9 @@ static void reset_test_state() { TIM2_BASE->SR = 0; TIM2_BASE->CR1 = 0; TIM2_BASE->DIER = 0; + + Scheduler_global_timer = + ST_LIB::TimerDomain::cmsis_timers[ST_LIB::timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; } static void tick_scheduler(int ticks) { From 24738ff413e1c374fea31cf66f57d3dd040bf76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez?= <120128034+victor-Lopez25@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:35:46 +0100 Subject: [PATCH 31/41] Max out ARR for encoder (#587) * Max ARR * fix encoder test * actually fix tests now * fix formatting --- Inc/HALAL/Services/Encoder/Encoder.hpp | 6 +++++- Tests/Time/encoder_test.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Inc/HALAL/Services/Encoder/Encoder.hpp b/Inc/HALAL/Services/Encoder/Encoder.hpp index 24f3fa30b..e0529ead5 100644 --- a/Inc/HALAL/Services/Encoder/Encoder.hpp +++ b/Inc/HALAL/Services/Encoder/Encoder.hpp @@ -63,7 +63,11 @@ template class Encoder { } tim->instance->tim->PSC = 5; - tim->instance->tim->ARR = 55000; + if constexpr (tim->is_32bit_instance) { + tim->instance->tim->ARR = UINT32_MAX; + } else { + tim->instance->tim->ARR = UINT16_MAX; + } timer = tim; } diff --git a/Tests/Time/encoder_test.cpp b/Tests/Time/encoder_test.cpp index 9d73cf27c..eaf3b8b13 100644 --- a/Tests/Time/encoder_test.cpp +++ b/Tests/Time/encoder_test.cpp @@ -82,7 +82,7 @@ TEST_F(EncoderTest, ResetUsesConfiguredInitialCounterValue) { ST_LIB::Encoder::reset(); - EXPECT_EQ(TIM2_BASE->ARR, 55000U); + EXPECT_EQ(TIM2_BASE->ARR, UINT32_MAX); EXPECT_EQ(TIM2_BASE->CNT, ST_LIB::Encoder::get_initial_counter_value()); } From 5cbb06514c28d9cad963ebd7a816432c146fb659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez?= <120128034+victor-Lopez25@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:46:20 +0100 Subject: [PATCH 32/41] Hotfix/sched register b4 start (#593) --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 5 +++++ Inc/HALAL/Services/Time/Scheduler.hpp | 10 ++++++---- Src/HALAL/Services/Time/Scheduler.cpp | 9 ++++----- Tests/StateMachine/state_machine_test.cpp | 5 ++--- Tests/Time/scheduler_test.cpp | 2 ++ 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 9382fe656..3011cafd6 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -27,6 +27,8 @@ #error Scheduler timer must be a 32 bit timer #endif extern TIM_TypeDef* Scheduler_global_timer; +void Scheduler_global_timer_callback(void* raw); +void Scheduler_start(void); // NOTE: only works for static arrays #define ARRAY_LENGTH(a) (sizeof(a) / sizeof(*a)) @@ -719,7 +721,10 @@ TimerXList static void init(std::span cfgs) { Scheduler_global_timer = cmsis_timers[timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; + callbacks[ST_LIB::timer_idxmap[SCHEDULER_TIMER_DOMAIN]] = + Scheduler_global_timer_callback; rcc_enable_timer(Scheduler_global_timer); + Scheduler_start(); for (std::size_t i = 0; i < N; i++) { const Config& e = cfgs[i]; diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index fd6b414d4..dfabc0bdb 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -20,6 +20,8 @@ #endif extern TIM_TypeDef* Scheduler_global_timer; +void Scheduler_global_timer_callback(void* raw); +void Scheduler_start(void); struct Scheduler { using callback_t = void (*)(); @@ -28,7 +30,8 @@ struct Scheduler { // if it isn't it could theoretically be used as an id in set_timeout static constexpr uint32_t INVALID_ID = 2 * kMaxTasks; - static void start(); + // temporary, will be removed + [[deprecated]] static inline void start() {} static void update(); static inline uint64_t get_global_tick() { return global_tick_us_ + Scheduler_global_timer->CNT; @@ -42,6 +45,8 @@ struct Scheduler { // internal static void on_timer_update(); + static void schedule_next_interval(); + static constexpr uint32_t FREQUENCY = 1'000'000u; // 1 MHz -> 1us precision #ifndef SIM_ON private: #endif @@ -60,7 +65,6 @@ struct Scheduler { static_assert(INVALID_ID >= kMaxTasks, "INVALID_ID must not be a possible task id"); static_assert((kMaxTasks & (kMaxTasks - 1)) == 0, "kMaxTasks must be a power of two"); - static constexpr uint32_t FREQUENCY = 1'000'000u; // 1 MHz -> 1us precision static std::array tasks_; static_assert( @@ -86,8 +90,6 @@ struct Scheduler { static inline void release_slot(uint8_t id); static void insert_sorted(uint8_t id); static void remove_sorted(uint8_t id); - static void schedule_next_interval(); - static inline void configure_timer_for_interval(uint32_t microseconds); // helpers static inline uint8_t get_at(uint8_t idx); diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index 9f01f4124..62a2333ad 100644 --- a/Src/HALAL/Services/Time/Scheduler.cpp +++ b/Src/HALAL/Services/Time/Scheduler.cpp @@ -60,16 +60,18 @@ inline void Scheduler::global_timer_enable() { } // ---------------------------- -void scheduler_global_timer_callback(void* raw) { +void Scheduler_global_timer_callback(void* raw) { (void)raw; Scheduler::on_timer_update(); } // ---------------------------- -void Scheduler::start() { +void Scheduler_start(void) { static_assert((Scheduler::FREQUENCY % 1'000'000) == 0u, "frequenct must be a multiple of 1MHz"); Scheduler_global_timer = ST_LIB::TimerDomain::cmsis_timers[ST_LIB::timer_idxmap[SCHEDULER_TIMER_DOMAIN]]; + ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[static_cast(SCHEDULER_TIMER_DOMAIN + )]] = Scheduler_global_timer_callback; // TODO: change this to use TimerDomain::get_timer_clock()? uint32_t prescaler = (SystemCoreClock / Scheduler::FREQUENCY); @@ -145,9 +147,6 @@ void Scheduler::start() { Scheduler_global_timer->CR1 = LL_TIM_CLOCKDIVISION_DIV1 | (Scheduler_global_timer->CR1 & ~TIM_CR1_CKD); - ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[static_cast(SCHEDULER_TIMER_DOMAIN - )]] = scheduler_global_timer_callback; - Scheduler_global_timer->CNT = 0; /* Clear counter value */ NVIC_EnableIRQ(SCHEDULER_GLOBAL_TIMER_IRQn); diff --git a/Tests/StateMachine/state_machine_test.cpp b/Tests/StateMachine/state_machine_test.cpp index 017c5a0d6..0e991f782 100644 --- a/Tests/StateMachine/state_machine_test.cpp +++ b/Tests/StateMachine/state_machine_test.cpp @@ -155,6 +155,8 @@ class StateMachineTest : public ::testing::Test { test_nested_machine.get_states()[1].unregister_all_timed_actions(); reset_test_state(); + + Scheduler_start(); } }; @@ -216,8 +218,6 @@ TEST_F(StateMachineTest, MasterStateChangeExitsNested) { TEST_F(StateMachineTest, CyclicActionsRun) { test_machine.start(); - Scheduler::start(); - tick_scheduler(100); tick_scheduler(10000); @@ -242,7 +242,6 @@ static int check_transition_task_count = 0; TEST_F(StateMachineTest, StressTestWithScheduler) { test_machine.start(); - Scheduler::start(); custom_task_count = 0; check_transition_task_count = 0; diff --git a/Tests/Time/scheduler_test.cpp b/Tests/Time/scheduler_test.cpp index 7376abe5b..7f468b870 100644 --- a/Tests/Time/scheduler_test.cpp +++ b/Tests/Time/scheduler_test.cpp @@ -27,6 +27,8 @@ class SchedulerTests : public ::testing::Test { TIM2_BASE->SR = 0; TIM2_BASE->CR1 = 0; TIM2_BASE->DIER = 0; + + Scheduler_start(); } }; From 21bc1b805dd38cb90e48a359dfe9551e4a4e6cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Gand=C3=ADa=20Iglesias?= <145703854+oganigl@users.noreply.github.com> Date: Fri, 20 Mar 2026 19:34:19 +0100 Subject: [PATCH 33/41] Change the frequency if this can be called frequency of the leds (#580) --- Src/HALAL/HardFault/HardfaultTrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/HALAL/HardFault/HardfaultTrace.c b/Src/HALAL/HardFault/HardfaultTrace.c index 5162d3d2e..64dd3f6fb 100644 --- a/Src/HALAL/HardFault/HardfaultTrace.c +++ b/Src/HALAL/HardFault/HardfaultTrace.c @@ -4,7 +4,7 @@ #endif #ifdef BOARD -#define REPS 600000 // Three times faster because the board frequency +#define REPS 800000 // Three times faster because the board frequency #endif #ifndef HARDFAULT_BOOT_BLINK_CYCLES From 1466802811c284f045e22516480b1b1fa7378c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= <125664643+jorgesg82@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:06:40 +0100 Subject: [PATCH 34/41] Releases are back bitches (#596) * chore(release): add semver foundation and tooling * ci(release): require changesets on pull requests * ci(release): automate release preparation and publishing * chore(release): add bootstrap changeset for release infra * fix(release): address Copilot review feedback --- .changesets/README.md | 22 + .changesets/TEMPLATE.md | 4 + .changesets/revive-releases-bootstrap.md | 5 + .github/workflows/changeset-required.yml | 82 ++++ .github/workflows/publish-release.yml | 80 ++++ .github/workflows/release-pr.yml | 104 +++++ CHANGELOG.md | 7 + CMakeLists.txt | 13 +- README.md | 1 + VERSION | 1 + docs/releases.md | 44 ++ tools/release.py | 515 +++++++++++++++++++++++ 12 files changed, 876 insertions(+), 2 deletions(-) create mode 100644 .changesets/README.md create mode 100644 .changesets/TEMPLATE.md create mode 100644 .changesets/revive-releases-bootstrap.md create mode 100644 .github/workflows/changeset-required.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/release-pr.yml create mode 100644 CHANGELOG.md create mode 100644 VERSION create mode 100644 docs/releases.md create mode 100644 tools/release.py diff --git a/.changesets/README.md b/.changesets/README.md new file mode 100644 index 000000000..6e396b59f --- /dev/null +++ b/.changesets/README.md @@ -0,0 +1,22 @@ +# Changesets + +Each pull request targeting `development` must add exactly one file to this directory. + +Use this format: + +```text +release: patch +summary: Fix scheduler race condition when registering timers + +Optional extra context in markdown. +``` + +Allowed values for `release:` are: + +- `major` for breaking API or contract changes +- `minor` for backwards-compatible features +- `patch` for backwards-compatible fixes +- `none` for changes that should wait for the next real release without bumping the version on their own + +The release workflows aggregate all pending changesets, calculate the next semantic version, update +`VERSION`, prepend `CHANGELOG.md`, and archive the processed files under `.changesets/archive/`. diff --git a/.changesets/TEMPLATE.md b/.changesets/TEMPLATE.md new file mode 100644 index 000000000..5016fe268 --- /dev/null +++ b/.changesets/TEMPLATE.md @@ -0,0 +1,4 @@ +release: patch +summary: Describe the user-visible change in one line + +Optional extra context in markdown. diff --git a/.changesets/revive-releases-bootstrap.md b/.changesets/revive-releases-bootstrap.md new file mode 100644 index 000000000..18803d6fb --- /dev/null +++ b/.changesets/revive-releases-bootstrap.md @@ -0,0 +1,5 @@ +release: none +summary: Introduce semver tooling and release automation infrastructure + +Bootstrap the semiautomated release flow for ST-LIB without publishing a new +library version yet. diff --git a/.github/workflows/changeset-required.yml b/.github/workflows/changeset-required.yml new file mode 100644 index 000000000..2550727b5 --- /dev/null +++ b/.github/workflows/changeset-required.yml @@ -0,0 +1,82 @@ +name: Changeset Required + +on: + pull_request: + branches: + - development + types: + - opened + - synchronize + - reopened + - ready_for_review + - edited + +permissions: + contents: read + pull-requests: write + +concurrency: + group: release-plan-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + changeset-required: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Skip validation for automated release PRs + if: github.head_ref == 'release/next' + run: | + echo "Automated release PR detected; changeset validation is skipped." >> "$GITHUB_STEP_SUMMARY" + + - name: Validate PR changeset + if: github.head_ref != 'release/next' + run: | + python3 tools/release.py validate-pr \ + --base "${{ github.event.pull_request.base.sha }}" \ + --head "${{ github.event.pull_request.head.sha }}" + + - name: Build release preview + if: github.head_ref != 'release/next' + run: | + python3 tools/release.py preview --format markdown > .release-plan.md + cat .release-plan.md >> "$GITHUB_STEP_SUMMARY" + + - name: Upsert PR comment + if: github.head_ref != 'release/next' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const marker = ''; + const body = `${marker}\n${fs.readFileSync('.release-plan.md', 'utf8')}`; + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.issue.number; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + per_page: 100, + }); + const existing = comments.find((comment) => comment.body && comment.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 000000000..320b0f553 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,80 @@ +name: Publish Release + +on: + pull_request_target: + branches: + - development + types: + - closed + +permissions: + contents: write + +jobs: + publish-release: + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'release/next' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + + steps: + - name: Checkout merged release commit + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + fetch-depth: 0 + + - name: Read release version + id: version + run: | + version="$(tr -d '\n' < VERSION)" + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "tag=v${version}" >> "$GITHUB_OUTPUT" + + - name: Export release notes + run: | + python3 tools/release.py latest-notes > .release-notes.md + cat .release-notes.md >> "$GITHUB_STEP_SUMMARY" + + - name: Check whether the tag already exists + id: tag + run: | + if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create git tag + if: steps.tag.outputs.exists != 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag "${{ steps.version.outputs.tag }}" "${{ github.event.pull_request.merge_commit_sha }}" + git push origin "${{ steps.version.outputs.tag }}" + + - name: Check whether the GitHub release already exists + id: release + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh release view "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub release + if: steps.release.outputs.exists != 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + prerelease_flag="" + case "${{ steps.version.outputs.version }}" in + *-*) + prerelease_flag="--prerelease" + ;; + esac + + gh release create "${{ steps.version.outputs.tag }}" \ + --title "${{ steps.version.outputs.tag }}" \ + --notes-file .release-notes.md \ + ${prerelease_flag} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 000000000..266789d40 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,104 @@ +name: Prepare Release PR + +on: + workflow_dispatch: + push: + branches: + - development + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-pr-${{ github.ref }} + cancel-in-progress: true + +jobs: + prepare-release-pr: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Compute release state + id: versions + run: | + current_version="$(tr -d '\n' < VERSION)" + next_version="$(python3 tools/release.py next-version)" + echo "current_version=${current_version}" >> "$GITHUB_OUTPUT" + echo "next_version=${next_version}" >> "$GITHUB_OUTPUT" + + - name: Skip when nothing releasable is pending + if: steps.versions.outputs.current_version == steps.versions.outputs.next_version + run: | + echo "No releasable changesets are pending." + python3 tools/release.py preview --format markdown >> "$GITHUB_STEP_SUMMARY" + + - name: Prepare release branch + if: steps.versions.outputs.current_version != steps.versions.outputs.next_version + id: prepare + run: | + python3 tools/release.py apply --output .release-version + release_version="$(tr -d '\n' < .release-version)" + echo "release_version=${release_version}" >> "$GITHUB_OUTPUT" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git switch -C release/next + git add VERSION CHANGELOG.md .changesets + git commit -m "chore(release): prepare v${release_version}" + git push --force --set-upstream origin release/next + + - name: Create or update release PR + if: steps.versions.outputs.current_version != steps.versions.outputs.next_version + uses: actions/github-script@v7 + env: + RELEASE_VERSION: ${{ steps.prepare.outputs.release_version }} + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const head = `${owner}:release/next`; + const base = 'development'; + const title = `chore(release): prepare v${process.env.RELEASE_VERSION}`; + const marker = ''; + const body = [ + marker, + '', + 'This PR was prepared automatically from the pending ST-LIB changesets.', + '', + `- Version: \`v${process.env.RELEASE_VERSION}\``, + '- Source of truth: `VERSION`', + '- Release notes source: `CHANGELOG.md`', + ].join('\n'); + + const { data: pulls } = await github.rest.pulls.list({ + owner, + repo, + state: 'open', + head, + base, + }); + + if (pulls.length > 0) { + await github.rest.pulls.update({ + owner, + repo, + pull_number: pulls[0].number, + title, + body, + }); + } else { + await github.rest.pulls.create({ + owner, + repo, + title, + head: 'release/next', + base, + body, + }); + } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..0c37563d5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +This file tracks ST-LIB releases prepared by the semiautomated release flow. +The revived semantic-versioning baseline starts at `v5.0.0`. + +Historical releases that predate this file remain available in Git tags such as +`v1.0.0`, `v3.0.0`, `v4.0.0-beta`, and `h10`. diff --git a/CMakeLists.txt b/CMakeLists.txt index 52aeba82e..4e783d2ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,19 @@ cmake_minimum_required(VERSION 3.14) +file(STRINGS "${CMAKE_CURRENT_LIST_DIR}/VERSION" STLIB_VERSION_FULL LIMIT_COUNT 1) +if(NOT STLIB_VERSION_FULL) + message(FATAL_ERROR "Could not read ST-LIB version from VERSION") +endif() +string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" STLIB_PROJECT_VERSION "${STLIB_VERSION_FULL}") +if(NOT STLIB_PROJECT_VERSION) + message(FATAL_ERROR "VERSION must start with a semantic version core, got: ${STLIB_VERSION_FULL}") +endif() + if(DEFINED STLIB_NAME_SUFFIX) - project(ST-LIB-${STLIB_NAME_SUFFIX} C CXX) + project(ST-LIB-${STLIB_NAME_SUFFIX} VERSION ${STLIB_PROJECT_VERSION} LANGUAGES C CXX) set(STLIB_LIBRARY st-lib-${STLIB_NAME_SUFFIX}) else() - project(ST-LIB C CXX) + project(ST-LIB VERSION ${STLIB_PROJECT_VERSION} LANGUAGES C CXX) set(STLIB_LIBRARY st-lib) endif() if(NOT PROJECT_IS_TOP_LEVEL) diff --git a/README.md b/README.md index 028e091e5..7cb8d4a57 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ ctest --preset simulator-all - Setup: [`docs/setup.md`](docs/setup.md) - Build and presets: [`docs/build-and-presets.md`](docs/build-and-presets.md) - Testing: [`docs/testing.md`](docs/testing.md) +- Releases: [`docs/releases.md`](docs/releases.md) ## Recommended Presets diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..0062ac971 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +5.0.0 diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 000000000..9d281a082 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,44 @@ +# ST-LIB Releases + +ST-LIB uses a semiautomated release flow based on per-PR changesets. + +## Pull Requests + +Each PR into `development` must add exactly one file under `.changesets/`. + +Minimal example: + +```text +release: minor +summary: Add compile-time Board owner mapping contract checks +``` + +Rules: + +- `major`: breaking API or contract change +- `minor`: backwards-compatible feature +- `patch`: backwards-compatible fix +- `none`: internal/docs/CI change that should be included in the next real release notes without forcing a version bump + +The PR workflow validates the file and comments with the projected next semantic version. + +## Preparing A Release + +When pending changesets reach `development`, the release workflow updates a branch named +`release/next` with: + +- `VERSION` +- `CHANGELOG.md` +- archived processed changesets + +That branch is exposed as a normal PR back into `development`. + +## Publishing + +When the `release/next` PR is merged into `development`, the publish workflow creates: + +- a git tag named `vX.Y.Z` +- a GitHub Release using the latest changelog entry + +If `VERSION` contains a prerelease suffix such as `-beta`, the GitHub Release is marked as a +prerelease. diff --git a/tools/release.py b/tools/release.py new file mode 100644 index 000000000..23b4c862d --- /dev/null +++ b/tools/release.py @@ -0,0 +1,515 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import json +import re +import shutil +import subprocess +import sys +from collections import defaultdict +from dataclasses import dataclass +from datetime import date +from pathlib import Path + + +ALLOWED_RELEASE_TYPES = ("none", "patch", "minor", "major") +RELEASE_PRIORITY = {name: index for index, name in enumerate(ALLOWED_RELEASE_TYPES)} +VERSION_PATTERN = re.compile( + r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)" + r"(?:-(?P[0-9A-Za-z.-]+))?$" +) +COMMENT_MARKER = "" +IGNORED_CHANGESET_FILES = {"README.md", "TEMPLATE.md"} +SECTION_TITLES = { + "major": "Breaking Changes", + "minor": "Features", + "patch": "Fixes", + "none": "Internal", +} + + +@dataclass(frozen=True) +class SemVer: + major: int + minor: int + patch: int + prerelease: str | None = None + + @property + def core(self) -> str: + return f"{self.major}.{self.minor}.{self.patch}" + + def __str__(self) -> str: + if self.prerelease: + return f"{self.core}-{self.prerelease}" + return self.core + + +@dataclass(frozen=True) +class Changeset: + path: Path + release: str + summary: str + details: str + + +def repo_root(explicit_root: str | None) -> Path: + if explicit_root: + return Path(explicit_root).resolve() + return Path(__file__).resolve().parents[1] + + +def version_path(root: Path) -> Path: + return root / "VERSION" + + +def changelog_path(root: Path) -> Path: + return root / "CHANGELOG.md" + + +def changeset_dir(root: Path) -> Path: + return root / ".changesets" + + +def parse_version(raw_version: str) -> SemVer: + match = VERSION_PATTERN.fullmatch(raw_version.strip()) + if not match: + raise ValueError(f"Invalid semantic version: {raw_version!r}") + return SemVer( + major=int(match.group("major")), + minor=int(match.group("minor")), + patch=int(match.group("patch")), + prerelease=match.group("prerelease"), + ) + + +def read_version(root: Path) -> str: + return version_path(root).read_text(encoding="utf-8").strip() + + +def bump_version(current_version: str, release_type: str) -> str: + if release_type not in RELEASE_PRIORITY: + raise ValueError(f"Unknown release type: {release_type}") + + current = parse_version(current_version) + if release_type == "none": + return str(current) + + if current.prerelease: + if release_type == "patch": + return current.core + if release_type == "minor": + return f"{current.major}.{current.minor + 1}.0" + if release_type == "major": + return f"{current.major + 1}.0.0" + + if release_type == "patch": + return f"{current.major}.{current.minor}.{current.patch + 1}" + if release_type == "minor": + return f"{current.major}.{current.minor + 1}.0" + return f"{current.major + 1}.0.0" + + +def active_changeset_paths(root: Path) -> list[Path]: + directory = changeset_dir(root) + if not directory.exists(): + return [] + + return sorted( + path + for path in directory.glob("*.md") + if path.name not in IGNORED_CHANGESET_FILES + ) + + +def parse_changeset(path: Path) -> Changeset: + lines = path.read_text(encoding="utf-8").splitlines() + non_empty_indexes = [index for index, line in enumerate(lines) if line.strip()] + if len(non_empty_indexes) < 2: + raise ValueError(f"{path.name}: expected at least two non-empty lines") + + release_line = lines[non_empty_indexes[0]].strip() + summary_line = lines[non_empty_indexes[1]].strip() + if not release_line.startswith("release: "): + raise ValueError(f"{path.name}: first non-empty line must start with 'release: '") + if not summary_line.startswith("summary: "): + raise ValueError(f"{path.name}: second non-empty line must start with 'summary: '") + + release = release_line.split(": ", 1)[1].strip().lower() + if release not in RELEASE_PRIORITY: + allowed = ", ".join(ALLOWED_RELEASE_TYPES) + raise ValueError(f"{path.name}: release must be one of {allowed}") + + summary = summary_line.split(": ", 1)[1].strip() + if not summary: + raise ValueError(f"{path.name}: summary cannot be empty") + + details_start = non_empty_indexes[1] + 1 + details = "\n".join(lines[details_start:]).strip() + return Changeset(path=path, release=release, summary=summary, details=details) + + +def load_changesets(root: Path) -> list[Changeset]: + return [parse_changeset(path) for path in active_changeset_paths(root)] + + +def highest_release(changesets: list[Changeset]) -> str: + if not changesets: + return "none" + return max(changesets, key=lambda item: RELEASE_PRIORITY[item.release]).release + + +def next_version(root: Path) -> str: + current_version = read_version(root) + return bump_version(current_version, highest_release(load_changesets(root))) + + +def release_ready(root: Path) -> bool: + return next_version(root) != read_version(root) + + +def build_preview_payload(root: Path) -> dict[str, object]: + current_version = read_version(root) + changesets = load_changesets(root) + highest = highest_release(changesets) + computed_next_version = bump_version(current_version, highest) + return { + "current_version": current_version, + "next_version": computed_next_version, + "highest_release": highest, + "release_ready": computed_next_version != current_version, + "pending_changesets": len(changesets), + "changesets": [ + { + "path": str(changeset.path.relative_to(root)), + "release": changeset.release, + "summary": changeset.summary, + } + for changeset in changesets + ], + } + + +def build_preview_markdown(root: Path) -> str: + payload = build_preview_payload(root) + lines = [ + "## ST-LIB Release Plan", + "", + f"- Current version: `{payload['current_version']}`", + f"- Pending changesets: `{payload['pending_changesets']}`", + f"- Highest requested bump: `{payload['highest_release']}`", + ] + + if payload["release_ready"]: + lines.append(f"- Next version if merged now: `{payload['next_version']}`") + else: + lines.append( + f"- Next releasable version: no bump yet, current stays at `{payload['current_version']}`" + ) + + if payload["changesets"]: + lines.extend(["", "### Pending changes", ""]) + for item in payload["changesets"]: + lines.append(f"- `{item['release']}` {item['summary']} ({item['path']})") + else: + lines.extend(["", "No pending changesets were found."]) + + return "\n".join(lines).strip() + "\n" + + +def relevant_changeset_path(root: Path, path_str: str) -> Path | None: + path = Path(path_str) + if ( + len(path.parts) == 2 + and path.parts[0] == ".changesets" + and path.suffix == ".md" + and path.name not in IGNORED_CHANGESET_FILES + ): + return root / path + return None + + +def git_changed_changesets(root: Path, base: str, head: str) -> tuple[list[Path], list[Path]]: + result = subprocess.run( + ["git", "-c", "diff.renames=false", "diff", "--name-status", f"{base}...{head}", "--"], + cwd=root, + check=True, + capture_output=True, + text=True, + ) + changed_files: list[Path] = [] + deleted_files: list[Path] = [] + + for raw_line in result.stdout.splitlines(): + if not raw_line.strip(): + continue + + parts = raw_line.split("\t") + status = parts[0] + + if status.startswith(("R", "C")): + if len(parts) < 3: + continue + old_path = relevant_changeset_path(root, parts[1]) + new_path = relevant_changeset_path(root, parts[2]) + if old_path is not None and new_path is None: + deleted_files.append(old_path) + elif new_path is not None: + changed_files.append(new_path) + continue + + if len(parts) < 2: + continue + + path = relevant_changeset_path(root, parts[1]) + if path is None: + continue + if status == "D": + deleted_files.append(path) + else: + changed_files.append(path) + + return sorted(set(changed_files)), sorted(set(deleted_files)) + + +def validate_pr_changeset(root: Path, base: str, head: str) -> int: + changed_changesets, deleted_changesets = git_changed_changesets(root, base, head) + if deleted_changesets: + joined = ", ".join(str(path.relative_to(root)) for path in deleted_changesets) + raise ValueError( + "PRs must not delete changeset files under .changesets/. " + f"Deleted changesets: {joined}" + ) + if len(changed_changesets) != 1: + joined = ", ".join(str(path.relative_to(root)) for path in changed_changesets) or "none" + raise ValueError( + "PRs must add or update exactly one changeset file under .changesets/. " + f"Changed changesets: {joined}" + ) + + parse_changeset(changed_changesets[0]) + return 0 + + +def build_changelog_entry(version: str, changesets: list[Changeset]) -> str: + grouped: dict[str, list[Changeset]] = defaultdict(list) + for changeset in changesets: + grouped[changeset.release].append(changeset) + + lines = [f"## v{version} - {date.today().isoformat()}", ""] + for release_type in ("major", "minor", "patch", "none"): + items = grouped.get(release_type) + if not items: + continue + lines.append(f"### {SECTION_TITLES[release_type]}") + lines.append("") + for item in items: + lines.append(f"- {item.summary}") + if item.details: + for detail_line in item.details.splitlines(): + lines.append(f" {detail_line}" if detail_line else " ") + lines.append("") + + return "\n".join(lines).rstrip() + + +def render_changelog_with_entry(existing: str, entry: str) -> str: + heading_match = re.search(r"^## ", existing, flags=re.MULTILINE) + if heading_match: + insertion_point = heading_match.start() + return ( + existing[:insertion_point].rstrip() + + "\n\n" + + entry + + "\n\n" + + existing[insertion_point:].lstrip() + ) + else: + return existing.rstrip() + "\n\n" + entry + "\n" + + +def archive_changesets(root: Path, version: str, changesets: list[Changeset]) -> list[tuple[Path, Path]]: + archive_directory = changeset_dir(root) / "archive" / f"v{version}" + archive_directory.mkdir(parents=True, exist_ok=True) + destinations = [(changeset.path, archive_directory / changeset.path.name) for changeset in changesets] + + for _, destination in destinations: + if destination.exists(): + raise FileExistsError(f"Archive destination already exists: {destination}") + + moved_changesets: list[tuple[Path, Path]] = [] + try: + for changeset in changesets: + destination = archive_directory / changeset.path.name + shutil.move(str(changeset.path), destination) + moved_changesets.append((changeset.path, destination)) + except Exception: + rollback_archived_changesets(root, moved_changesets) + raise + return moved_changesets + + +def rollback_archived_changesets(root: Path, moved_changesets: list[tuple[Path, Path]]) -> None: + archive_root = changeset_dir(root) / "archive" + for source, destination in reversed(moved_changesets): + if destination.exists(): + shutil.move(str(destination), source) + + current = destination.parent + while current != archive_root.parent: + try: + current.rmdir() + except OSError: + break + current = current.parent + if current == archive_root.parent: + break + + +def apply_release(root: Path) -> str: + changesets = load_changesets(root) + if not changesets: + raise ValueError("No pending changesets found") + + current_version = read_version(root) + release_type = highest_release(changesets) + computed_next_version = bump_version(current_version, release_type) + if computed_next_version == current_version: + raise ValueError( + "Pending changesets exist, but all are marked 'none'; nothing to release yet" + ) + + version_file = version_path(root) + changelog_file = changelog_path(root) + original_version = version_file.read_text(encoding="utf-8") + original_changelog = changelog_file.read_text(encoding="utf-8") + updated_changelog = render_changelog_with_entry( + original_changelog, build_changelog_entry(computed_next_version, changesets) + ) + + moved_changesets: list[tuple[Path, Path]] = [] + try: + moved_changesets = archive_changesets(root, computed_next_version, changesets) + changelog_file.write_text(updated_changelog, encoding="utf-8") + version_file.write_text(computed_next_version + "\n", encoding="utf-8") + except Exception: + if moved_changesets: + rollback_archived_changesets(root, moved_changesets) + changelog_file.write_text(original_changelog, encoding="utf-8") + version_file.write_text(original_version, encoding="utf-8") + raise + return computed_next_version + + +def latest_release_notes(root: Path) -> str: + lines = changelog_path(root).read_text(encoding="utf-8").splitlines() + start = None + end = None + for index, line in enumerate(lines): + if line.startswith("## "): + if start is None: + start = index + else: + end = index + break + if start is None: + raise ValueError("No release entries found in CHANGELOG.md") + selected = lines[start:end] + return "\n".join(selected).strip() + "\n" + + +def command_preview(args: argparse.Namespace) -> int: + root = repo_root(args.repo_root) + if args.format == "markdown": + sys.stdout.write(build_preview_markdown(root)) + return 0 + payload = build_preview_payload(root) + if args.format == "json": + json.dump(payload, sys.stdout, indent=2) + sys.stdout.write("\n") + return 0 + + lines = [ + f"current_version={payload['current_version']}", + f"next_version={payload['next_version']}", + f"highest_release={payload['highest_release']}", + f"release_ready={str(payload['release_ready']).lower()}", + f"pending_changesets={payload['pending_changesets']}", + ] + sys.stdout.write("\n".join(lines) + "\n") + return 0 + + +def command_validate_pr(args: argparse.Namespace) -> int: + return validate_pr_changeset(repo_root(args.repo_root), args.base, args.head) + + +def command_next_version(args: argparse.Namespace) -> int: + sys.stdout.write(next_version(repo_root(args.repo_root)) + "\n") + return 0 + + +def command_pending_count(args: argparse.Namespace) -> int: + sys.stdout.write(str(len(load_changesets(repo_root(args.repo_root)))) + "\n") + return 0 + + +def command_apply(args: argparse.Namespace) -> int: + root = repo_root(args.repo_root) + version = apply_release(root) + if args.output: + Path(args.output).write_text(version + "\n", encoding="utf-8") + sys.stdout.write(version + "\n") + return 0 + + +def command_latest_notes(args: argparse.Namespace) -> int: + sys.stdout.write(latest_release_notes(repo_root(args.repo_root))) + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="ST-LIB release helper") + parser.add_argument("--repo-root", help="Repository root override") + + subparsers = parser.add_subparsers(dest="command", required=True) + + preview_parser = subparsers.add_parser("preview", help="Show pending release information") + preview_parser.add_argument("--format", choices=("text", "markdown", "json"), default="text") + preview_parser.set_defaults(func=command_preview) + + validate_parser = subparsers.add_parser("validate-pr", help="Validate the changeset touched by a PR") + validate_parser.add_argument("--base", required=True, help="PR base commit or ref") + validate_parser.add_argument("--head", required=True, help="PR head commit or ref") + validate_parser.set_defaults(func=command_validate_pr) + + next_version_parser = subparsers.add_parser("next-version", help="Compute the next semantic version") + next_version_parser.set_defaults(func=command_next_version) + + pending_count_parser = subparsers.add_parser("pending-count", help="Count pending changesets") + pending_count_parser.set_defaults(func=command_pending_count) + + apply_parser = subparsers.add_parser("apply", help="Apply the next release to VERSION and CHANGELOG") + apply_parser.add_argument("--output", help="Optional file where the computed version will be written") + apply_parser.set_defaults(func=command_apply) + + notes_parser = subparsers.add_parser("latest-notes", help="Print the latest changelog entry") + notes_parser.set_defaults(func=command_latest_notes) + + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + try: + return args.func(args) + except Exception as exc: # noqa: BLE001 + print(f"error: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) From c088c609d8ba5ee481fa2a21da9fb29ee412d8f9 Mon Sep 17 00:00:00 2001 From: oganigl Date: Sun, 22 Mar 2026 22:35:25 +0100 Subject: [PATCH 35/41] merge development to main --- .changesets/adc-dma-minor.md | 4 + CMakeLists.txt | 4 +- Inc/HALAL/HALAL.hpp | 2 +- Inc/HALAL/Models/DMA/DMA2.hpp | 130 +- Inc/HALAL/Models/SPI/SPI2.hpp | 24 +- Inc/HALAL/Services/ADC/ADC.hpp | 1155 ++++++++++++++--- Inc/HALAL/Services/ADC/NewADC.hpp | 733 ----------- Inc/MockedDrivers/mocked_hal_adc.hpp | 34 +- Inc/MockedDrivers/mocked_hal_dma.hpp | 9 +- Inc/MockedDrivers/stm32h7xx_hal_mock.h | 29 +- Inc/ST-LIB.hpp | 183 +-- Inc/ST-LIB_LOW/ST-LIB_LOW.hpp | 1 - Inc/ST-LIB_LOW/Sensors/Common/ADCSensor.hpp | 62 + Inc/ST-LIB_LOW/Sensors/Common/PT100.hpp | 30 +- .../LinearSensor/FilteredLinearSensor.hpp | 8 +- .../Sensors/LinearSensor/LinearSensor.hpp | 46 +- .../Sensors/LookupSensor/LookupSensor.hpp | 50 +- Inc/ST-LIB_LOW/Sensors/NTC/NTC.hpp | 22 +- STM32H723ZGTX_FLASH.ld | 12 +- STM32H723ZGTX_RAM.ld | 12 +- Src/HALAL/Models/MPUManager/MPUManager.cpp | 4 + Src/HALAL/Services/ADC/ADC.cpp | 190 --- Src/MockedDrivers/mocked_hal_adc.cpp | 673 +++++++++- Src/MockedDrivers/mocked_hal_dma.cpp | 54 +- .../Sensors/LookupSensor/LookupSensor.cpp | 31 - Src/ST-LIB_LOW/Sensors/NTC/NTC.cpp | 16 - Tests/CMakeLists.txt | 1 + Tests/adc_sensor_test.cpp | 279 ++++ Tests/adc_test.cpp | 763 +++++++++-- Tests/dma2_test.cpp | 273 +++- Tests/spi2_test.cpp | 36 +- ci/Dockerfile | 23 +- docs/st-lib-board-contract.md | 194 +++ toolchains/stm32.cmake | 119 +- 34 files changed, 3572 insertions(+), 1634 deletions(-) create mode 100644 .changesets/adc-dma-minor.md delete mode 100644 Inc/HALAL/Services/ADC/NewADC.hpp create mode 100644 Inc/ST-LIB_LOW/Sensors/Common/ADCSensor.hpp delete mode 100644 Src/HALAL/Services/ADC/ADC.cpp delete mode 100644 Src/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.cpp delete mode 100644 Src/ST-LIB_LOW/Sensors/NTC/NTC.cpp create mode 100644 Tests/adc_sensor_test.cpp create mode 100644 docs/st-lib-board-contract.md diff --git a/.changesets/adc-dma-minor.md b/.changesets/adc-dma-minor.md new file mode 100644 index 000000000..54e37b0d9 --- /dev/null +++ b/.changesets/adc-dma-minor.md @@ -0,0 +1,4 @@ +release: minor +summary: Refactor the ADC stack around DMA-backed acquisition using new MPU + +Includes the DMA-backed `ADCDomain` migration, the shared ADC sensor base, and the updated ADC integration/tests in the `adc-dma` branch. diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e783d2ba..409c22148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,7 +282,6 @@ set(HALAL_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/DMA/DMA2.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/PinModel/Pin.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/TimerDomain/TimerDomain.cpp - # ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/ADC/ADC.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/CORDIC/CORDIC.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/FDCAN/FDCAN.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/I2C/I2C.cpp @@ -372,8 +371,6 @@ set(STLIB_LOW_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ST-LIB_LOW.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sd/Sd.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/DigitalSensor/DigitalSensor.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/NTC/NTC.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/SensorInterrupt/SensorInterrupt.cpp ) @@ -434,6 +431,7 @@ add_library(${STLIB_LIBRARY} OBJECT $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Time/Scheduler.cpp> $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/TimerDomain/TimerDomain.cpp> + $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp> $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/MockedDrivers/mocked_hal_adc.cpp> $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/MockedDrivers/mocked_hal_dma.cpp> $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/MockedDrivers/mocked_hal_spi.cpp> diff --git a/Inc/HALAL/HALAL.hpp b/Inc/HALAL/HALAL.hpp index cc64f0e44..e8cec2fdb 100644 --- a/Inc/HALAL/HALAL.hpp +++ b/Inc/HALAL/HALAL.hpp @@ -12,7 +12,7 @@ #include "HALAL/Services/Flash/Flash.hpp" #include "HALAL/Services/Flash/FlashTests/Flash_Test.hpp" -#include "HALAL/Services/ADC/NewADC.hpp" +#include "HALAL/Services/ADC/ADC.hpp" // To be implemented // #include "HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp" diff --git a/Inc/HALAL/Models/DMA/DMA2.hpp b/Inc/HALAL/Models/DMA/DMA2.hpp index 680b52474..a2e56f51c 100644 --- a/Inc/HALAL/Models/DMA/DMA2.hpp +++ b/Inc/HALAL/Models/DMA/DMA2.hpp @@ -1,13 +1,9 @@ #pragma once #include "C++Utilities/CppUtils.hpp" +#include "ErrorHandler/ErrorHandler.hpp" #include "stm32h7xx_hal.h" #include "main.h" -#include "HALAL/Models/MPUManager/MPUManager.hpp" -#include #include -#include -#include -#include using std::array; using std::size_t; @@ -22,7 +18,7 @@ inline DMA_HandleTypeDef* dma_irq_table[16] = {nullptr}; namespace ST_LIB { extern void compile_error(const char* msg); -struct DMA_Domain { +struct DMADomain { enum class Peripheral : uint8_t { none, @@ -117,7 +113,7 @@ struct DMA_Domain { }; template struct DMA { - using domain = DMA_Domain; + using domain = DMADomain; std::array e{}; @@ -142,22 +138,8 @@ struct DMA_Domain { template consteval array inscribe(Ctx& ctx) const { array indices{}; - for(size_t i = 0; i < sizeof...(Ss); ++i){ - bool found = false; - if(shares_dma(e[i].instance)){ - auto existing = ctx.template span(); - - for(size_t j = 0; j < existing.size(); ++j){ - if(existing[j].instance == e[i].instance){ - indices[i] = j; - found = true; - break; - } - } - } - if(!found){ - indices[i] = ctx.template add(e[i], this); - } + for (size_t i = 0; i < sizeof...(Ss); i++) { + indices[i] = ctx.template add(e[i], this); } return indices; } @@ -166,7 +148,7 @@ struct DMA_Domain { static constexpr std::size_t max_instances{MAX_STREAMS}; static_assert(max_instances > 0, "The number of instances must be greater than 0"); - static inline constexpr IRQn_Type get_irqn(Stream stream) { + static consteval IRQn_Type get_irqn(Stream stream) { if (stream == Stream::dma1_stream0) return DMA1_Stream0_IRQn; else if (stream == Stream::dma1_stream1) @@ -202,16 +184,17 @@ struct DMA_Domain { return DMA2_Stream7_IRQn; else if (stream == Stream::none) return (IRQn_Type)0; - else - compile_error("No tiene que llegar aqui nunca, creo"); + else { + compile_error("Invalid DMA stream"); + } return (IRQn_Type)0; } - static constexpr inline bool is_one_of(Peripheral instance, auto... bases) { + static consteval bool is_one_of(Peripheral instance, auto... bases) { return ((instance == bases) || ...); } - static constexpr inline bool is_spi(Peripheral instance) { + static consteval bool is_spi(Peripheral instance) { return is_one_of( instance, Peripheral::spi1, @@ -223,7 +206,7 @@ struct DMA_Domain { ); } - static constexpr inline bool is_i2c(Peripheral instance) { + static consteval bool is_i2c(Peripheral instance) { return is_one_of( instance, Peripheral::i2c1, @@ -233,21 +216,15 @@ struct DMA_Domain { ); } - static constexpr inline bool is_adc(Peripheral instance) { + static consteval bool is_adc(Peripheral instance) { return is_one_of(instance, Peripheral::adc1, Peripheral::adc2, Peripheral::adc3); } - static constexpr inline bool is_fmac(Peripheral instance) { - return instance == Peripheral::fmac; - } + static consteval bool is_fmac(Peripheral instance) { return instance == Peripheral::fmac; } - static constexpr inline bool is_none(Peripheral instance) { - return instance == Peripheral::none; - } - static constexpr inline bool is_dfsdm(Peripheral instance){ - return is_one_of(instance,Peripheral::dfsdm_filter0,Peripheral::dfsdm_filter1,Peripheral::dfsdm_filter2,Peripheral::dfsdm_filter3); - } - static consteval inline uint32_t get_Request(Peripheral instance, uint8_t i) { + static consteval bool is_none(Peripheral instance) { return instance == Peripheral::none; } + + static consteval uint32_t get_Request(Peripheral instance, uint8_t i) { if (instance == Peripheral::none) return DMA_REQUEST_MEM2MEM; @@ -319,7 +296,7 @@ struct DMA_Domain { return 0; } - static consteval inline uint32_t get_Direction(Peripheral instance, uint8_t i) { + static consteval uint32_t get_Direction(Peripheral instance, uint8_t i) { if ((is_fmac(instance) && i == 0) || instance == Peripheral::none) { return DMA_MEMORY_TO_MEMORY; } else if ((is_i2c(instance) && i == 1) || (is_spi(instance) && i == 1) || (is_fmac(instance) && i == 1)) { @@ -328,22 +305,22 @@ struct DMA_Domain { return DMA_PERIPH_TO_MEMORY; } - static consteval inline uint32_t get_PeriphInc(Peripheral instance, uint8_t i) { + static consteval uint32_t get_PeriphInc(Peripheral instance, uint8_t i) { if ((is_fmac(instance) && i == 0) || is_none(instance)) { return DMA_PINC_ENABLE; } return DMA_PINC_DISABLE; } - static consteval inline uint32_t get_MemInc(Peripheral instance, uint8_t i) { + static consteval uint32_t get_MemInc(Peripheral instance, uint8_t i) { if (is_fmac(instance) && i == 0) { return DMA_MINC_DISABLE; } return DMA_MINC_ENABLE; } - static consteval inline uint32_t get_PeriphDataAlignment(Peripheral instance, uint8_t i) { - if (is_spi(instance) || is_i2c(instance) ) { + static consteval uint32_t get_PeriphDataAlignment(Peripheral instance, uint8_t i) { + if (is_spi(instance) || is_i2c(instance)) { return DMA_PDATAALIGN_BYTE; } else if (is_none(instance) || (is_dfsdm(instance))) { return DMA_PDATAALIGN_WORD; @@ -351,8 +328,8 @@ struct DMA_Domain { return DMA_PDATAALIGN_HALFWORD; } - static consteval inline uint32_t get_MemDataAlignment(Peripheral instance, uint8_t i) { - if (is_i2c(instance) || is_dfsdm(instance)) { + static consteval uint32_t get_MemDataAlignment(Peripheral instance, uint8_t i) { + if (is_i2c(instance)) { return DMA_MDATAALIGN_WORD; } else if (is_spi(instance)) { return DMA_MDATAALIGN_BYTE; @@ -361,7 +338,7 @@ struct DMA_Domain { return DMA_MDATAALIGN_HALFWORD; } - static consteval inline uint32_t get_Mode(Peripheral instance, uint8_t i) { + static consteval uint32_t get_Mode(Peripheral instance, uint8_t i) { if (is_spi(instance) || is_fmac(instance) || is_none(instance)) { return DMA_NORMAL; } @@ -369,7 +346,7 @@ struct DMA_Domain { return DMA_CIRCULAR; } - static consteval inline uint32_t get_Priority(Peripheral instance, uint8_t i) { + static consteval uint32_t get_Priority(Peripheral instance, uint8_t i) { if (is_fmac(instance)) { return DMA_PRIORITY_HIGH; } @@ -377,33 +354,42 @@ struct DMA_Domain { return DMA_PRIORITY_LOW; } - static consteval inline uint32_t get_FIFOMode(Peripheral instance, uint8_t i) { + static consteval uint32_t get_FIFOMode(Peripheral instance, uint8_t i) { if (is_fmac(instance)) { return DMA_FIFOMODE_ENABLE; } return DMA_FIFOMODE_DISABLE; } - static consteval inline uint32_t get_FIFOThreshold(Peripheral instance, uint8_t i) { + static consteval uint32_t get_FIFOThreshold(Peripheral instance, uint8_t i) { if (is_spi(instance)) { return DMA_FIFO_THRESHOLD_FULL; } return DMA_FIFO_THRESHOLD_HALFFULL; } - static consteval inline uint32_t get_MemBurst(Peripheral instance, uint8_t i) { + static consteval uint32_t get_MemBurst(Peripheral instance, uint8_t i) { return DMA_MBURST_SINGLE; } - static consteval inline uint32_t get_PeriphBurst(Peripheral instance, uint8_t i) { + static consteval uint32_t get_PeriphBurst(Peripheral instance, uint8_t i) { return DMA_PBURST_SINGLE; } + static consteval bool get_NVICEnabled(Peripheral instance, uint8_t i) { + (void)i; + return !is_adc(instance); + } + struct Config { - std::tuple init_data{}; + std::tuple init_data{}; }; template static consteval std::array build(span instances) { + if (instances.size() != N) { + compile_error("DMA entry count mismatch"); + } + std::array cfgs{}; std::array ents; for (size_t i = 0; i < N; ++i) @@ -467,7 +453,14 @@ struct DMA_Domain { DMA_InitStruct.MemBurst = get_MemBurst(e.instance, e.id); DMA_InitStruct.PeriphBurst = get_PeriphBurst(e.instance, e.id); - cfgs[i].init_data = std::make_tuple(e.instance, DMA_InitStruct, e.stream, e.irqn, e.id); + cfgs[i].init_data = std::make_tuple( + e.instance, + DMA_InitStruct, + e.stream, + e.irqn, + e.id, + get_NVICEnabled(e.instance, e.id) + ); } return cfgs; } @@ -486,25 +479,42 @@ struct DMA_Domain { static void init(std::span cfgs) { if (N == 0) return; + __HAL_RCC_DMA1_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); + for (std::size_t i = 0; i < N; ++i) { const auto& e = cfgs[i]; - auto [instance, dma_init, stream, irqn, id] = e.init_data; + auto [instance, dma_init, stream, irqn, id, nvic_enabled] = e.init_data; + (void)instance; + (void)id; instances[i].dma = {}; + if (stream == Stream::none) { + ErrorHandler("DMA stream must be selected before init"); + continue; + } + instances[i].dma.Instance = stream_to_DMA_StreamTypeDef(stream); instances[i].dma.Init = dma_init; if (HAL_DMA_Init(&instances[i].dma) != HAL_OK) { + instances[i].dma = {}; ErrorHandler("DMA Init failed"); - } else { - HAL_NVIC_SetPriority(irqn, 0, 0); - HAL_NVIC_EnableIRQ(irqn); - dma_irq_table[static_cast(stream) - 1] = &instances[i].dma; + continue; } + + dma_irq_table[static_cast(stream) - 1] = &instances[i].dma; + + if (!nvic_enabled) { + continue; + } + + HAL_NVIC_SetPriority(irqn, 0, 0); + HAL_NVIC_EnableIRQ(irqn); } } }; }; +using DMA_Domain = DMADomain; } // namespace ST_LIB diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index ec9177441..02b1433fd 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -16,7 +16,7 @@ #include "HALAL/Models/DMA/DMA2.hpp" #include "HALAL/Models/SPI/SPIConfig.hpp" -using ST_LIB::DMA_Domain; +using ST_LIB::DMADomain; using ST_LIB::GPIODomain; using ST_LIB::SPIConfigTypes; @@ -195,7 +195,7 @@ struct SPIDomain { * Request Object * ========================================= */ - template struct Device { + template struct Device { using domain = SPIDomain; SPIPeripheral peripheral; @@ -208,7 +208,7 @@ struct SPIDomain { GPIODomain::GPIO mosi_gpio; std::optional nss_gpio; - DMA_Domain::DMA dma_rx_tx; + DMADomain::DMA dma_rx_tx; consteval Device( SPIMode mode, @@ -475,20 +475,20 @@ struct SPIDomain { } } - static consteval DMA_Domain::Peripheral dma_peripheral(SPIPeripheral peripheral) { + static consteval DMADomain::Peripheral dma_peripheral(SPIPeripheral peripheral) { switch (peripheral) { case SPIPeripheral::spi1: - return DMA_Domain::Peripheral::spi1; + return DMADomain::Peripheral::spi1; case SPIPeripheral::spi2: - return DMA_Domain::Peripheral::spi2; + return DMADomain::Peripheral::spi2; case SPIPeripheral::spi3: - return DMA_Domain::Peripheral::spi3; + return DMADomain::Peripheral::spi3; case SPIPeripheral::spi4: - return DMA_Domain::Peripheral::spi4; + return DMADomain::Peripheral::spi4; case SPIPeripheral::spi5: - return DMA_Domain::Peripheral::spi5; + return DMADomain::Peripheral::spi5; case SPIPeripheral::spi6: - return DMA_Domain::Peripheral::spi6; + return DMADomain::Peripheral::spi6; default: compile_error("Invalid SPI peripheral specified in SPIDomain::Device"); } @@ -1331,7 +1331,7 @@ struct SPIDomain { static void init( std::span cfgs, std::span gpio_instances, - std::span dma_peripherals + std::span dma_peripherals ) { for (std::size_t i = 0; i < N; ++i) { const auto& e = cfgs[i]; @@ -1398,7 +1398,7 @@ struct SPIDomain { auto& dma_rx = dma_peripherals[e.dma_rx_idx]; auto& dma_tx = dma_peripherals[e.dma_tx_idx]; - // DMA handles are already configured and initialized by DMA_Domain + // DMA handles are already configured and initialized by DMADomain hspi.hdmarx = &dma_rx.dma; hspi.hdmatx = &dma_tx.dma; diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 9ee5a07a5..6b28ebbe5 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -1,179 +1,1008 @@ -/* - * ADC.hpp - * - * Created on: 20 oct. 2022 - * Author: alejandro - */ - #pragma once -#include -#include "HALAL/Models/PinModel/Pin.hpp" -#include "HALAL/Models/LowPowerTimer/LowPowerTimer.hpp" +#include "hal_wrapper.h" + +#include +#include +#include +#include + +#include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Models/DMA/DMA2.hpp" +#include "HALAL/Models/GPIO.hpp" -#if defined(HAL_ADC_MODULE_ENABLED) && defined(HAL_LPTIM_MODULE_ENABLED) - -using std::string; - -#define ADC_BUF_LEN 16 -#define LPTIM1_PERIOD 6875 -#define LPTIM2_PERIOD 6875 -#define LPTIM3_PERIOD 6875 - -#define ADC_MAX_VOLTAGE 3.3 -#define MAX_12BIT 4095.0 -#define MAX_16BIT 65535.0 - -/** - * @brief A utility class that controls ADC inputs. - * - * This class handles the configuration, read, and correct casting of the ADC values in the ADC - * inputs using DMA to make cyclic reads, and the code pulls from the space given to the DMA memory. - * For the class to work, the pins used as parameters in each inscribe has to be declared on Runes - * and on Pins as ADCs (change adc_instances and AF of pins, respectively). Moreover, if the ADC - * used is not on the code generated by the .ioc you may need to change HAL_ADC_MspInit (on - * stm32h7xx_hal_msp.c) to configure the ADC pins and channels and their DMAs. Template-project has - * all the adcs declared on runes and the .ioc correctly configured, so unless more are needed these - * configurations can be assumed. - */ -class ADC { -public: - struct InitData { - public: - ADC_TypeDef* adc; - uint32_t resolution; - uint32_t external_trigger; - vector channels; - ST_LIB::DMA_Domain::Instance* dma_instance; - string name; - - InitData() = default; - InitData( - ADC_TypeDef* adc, - uint32_t resolution, - uint32_t external_trigger, - vector& channels, - ST_LIB::DMA_Domain::Instance* dma_instance, - string name - ); +#include "HALAL/Models/Pin.hpp" +#ifdef SIM_ON +#define STLIB_ADC_DMA_BUFFER_ATTR +#else +#include "HALAL/Models/MPU.hpp" +#define STLIB_ADC_DMA_BUFFER_ATTR D1_NC +#endif + +using std::array; +using std::size_t; +using std::span; + +#ifdef HAL_ADC_MODULE_ENABLED +extern ADC_HandleTypeDef hadc1; +extern ADC_HandleTypeDef hadc2; +extern ADC_HandleTypeDef hadc3; +#endif + +namespace ST_LIB { +extern void compile_error(const char* msg); + +#ifdef HAL_ADC_MODULE_ENABLED +using ::hadc1; +using ::hadc2; +using ::hadc3; + +struct ADCDomain { + static constexpr std::size_t max_channels_per_peripheral = 16; + enum class Peripheral : uint8_t { AUTO, ADC_1, ADC_2, ADC_3 }; + + enum class Resolution : uint32_t { + BITS_16 = ADC_RESOLUTION_16B, + BITS_14 = ADC_RESOLUTION_14B, + BITS_12 = ADC_RESOLUTION_12B, + BITS_10 = ADC_RESOLUTION_10B, + BITS_8 = ADC_RESOLUTION_8B, }; - class Peripheral { - public: - ADC_HandleTypeDef* handle; - uint16_t* dma_data_buffer; - LowPowerTimer timer; - InitData init_data; - bool is_on = false; + enum class SampleTime : uint32_t { + CYCLES_1_5 = ADC_SAMPLETIME_1CYCLE_5, + CYCLES_2_5 = ADC_SAMPLETIME_2CYCLES_5, + CYCLES_8_5 = ADC_SAMPLETIME_8CYCLES_5, + CYCLES_16_5 = ADC_SAMPLETIME_16CYCLES_5, + CYCLES_32_5 = ADC_SAMPLETIME_32CYCLES_5, + CYCLES_64_5 = ADC_SAMPLETIME_64CYCLES_5, + CYCLES_387_5 = ADC_SAMPLETIME_387CYCLES_5, + CYCLES_810_5 = ADC_SAMPLETIME_810CYCLES_5, + }; - Peripheral() = default; - Peripheral(ADC_HandleTypeDef* handle, LowPowerTimer& timer, InitData& init_data); + enum class ClockPrescaler : uint32_t { + DIV1 = ADC_CLOCK_ASYNC_DIV1, + DIV2 = ADC_CLOCK_ASYNC_DIV2, + DIV4 = ADC_CLOCK_ASYNC_DIV4, + DIV6 = ADC_CLOCK_ASYNC_DIV6, + DIV8 = ADC_CLOCK_ASYNC_DIV8, + DIV10 = ADC_CLOCK_ASYNC_DIV10, + DIV12 = ADC_CLOCK_ASYNC_DIV12, + DIV16 = ADC_CLOCK_ASYNC_DIV16, + DIV32 = ADC_CLOCK_ASYNC_DIV32, + DIV64 = ADC_CLOCK_ASYNC_DIV64, + DIV128 = ADC_CLOCK_ASYNC_DIV128, + DIV256 = ADC_CLOCK_ASYNC_DIV256, + }; - bool is_registered(); + enum class Channel : uint32_t { + AUTO = 0xFFFFFFFFu, + CH0 = ADC_CHANNEL_0, + CH1 = ADC_CHANNEL_1, + CH2 = ADC_CHANNEL_2, + CH3 = ADC_CHANNEL_3, + CH4 = ADC_CHANNEL_4, + CH5 = ADC_CHANNEL_5, + CH6 = ADC_CHANNEL_6, + CH7 = ADC_CHANNEL_7, + CH8 = ADC_CHANNEL_8, + CH9 = ADC_CHANNEL_9, + CH10 = ADC_CHANNEL_10, + CH11 = ADC_CHANNEL_11, + CH12 = ADC_CHANNEL_12, + CH13 = ADC_CHANNEL_13, + CH14 = ADC_CHANNEL_14, + CH15 = ADC_CHANNEL_15, + CH16 = ADC_CHANNEL_16, + CH17 = ADC_CHANNEL_17, + CH18 = ADC_CHANNEL_18, + CH19 = ADC_CHANNEL_19, + VREFINT = ADC_CHANNEL_VREFINT, + TEMPSENSOR = ADC_CHANNEL_TEMPSENSOR, + VBAT = ADC_CHANNEL_VBAT, }; - class Instance { + struct Entry { + size_t gpio_idx; + GPIODomain::Pin pin; + Peripheral peripheral; + Channel channel; + Resolution resolution; + SampleTime sample_time; + ClockPrescaler prescaler; + uint32_t sample_rate_hz; + float* output; + }; + + struct ADC { + GPIODomain::GPIO gpio; + using domain = ADCDomain; + + Entry e; + + consteval ADC( + const GPIODomain::Pin& pin, + float& output, + Resolution resolution = Resolution::BITS_12, + SampleTime sample_time = SampleTime::CYCLES_8_5, + ClockPrescaler prescaler = ClockPrescaler::DIV1, + uint32_t sample_rate_hz = 0, + Peripheral peripheral = Peripheral::AUTO, + Channel channel = Channel::AUTO + ) + : gpio{pin, GPIODomain::OperationMode::ANALOG, GPIODomain::Pull::None, GPIODomain::Speed::Low}, + e{.gpio_idx = 0, + .pin = pin, + .peripheral = peripheral, + .channel = channel, + .resolution = resolution, + .sample_time = sample_time, + .prescaler = prescaler, + .sample_rate_hz = sample_rate_hz, + .output = &output} {} + + consteval ADC( + const GPIODomain::Pin& pin, + Peripheral peripheral, + Channel channel, + float& output, + Resolution resolution = Resolution::BITS_12, + SampleTime sample_time = SampleTime::CYCLES_8_5, + ClockPrescaler prescaler = ClockPrescaler::DIV1, + uint32_t sample_rate_hz = 0 + ) + : ADC(pin, + output, + resolution, + sample_time, + prescaler, + sample_rate_hz, + peripheral, + channel) {} + + template consteval std::size_t inscribe(Ctx& ctx) const { + const auto gpio_idx = gpio.inscribe(ctx); + Entry entry = e; + entry.gpio_idx = gpio_idx; + const auto resolved = resolve_mapping(entry); + entry.peripheral = resolved.first; + entry.channel = resolved.second; + return ctx.template add(entry, this); + } + }; + + static constexpr std::size_t max_instances{32}; + + struct Config { + size_t gpio_idx; + Peripheral peripheral; + Channel channel; + Resolution resolution; + SampleTime sample_time; + ClockPrescaler prescaler; + uint32_t sample_rate_hz; + uint32_t dma_request; + float* output; + }; + + static constexpr uint8_t peripheral_index(Peripheral p) { + switch (p) { + case Peripheral::AUTO: + return 0; + case Peripheral::ADC_1: + return 0; + case Peripheral::ADC_2: + return 1; + case Peripheral::ADC_3: + return 2; + } + return 0; + } + + static constexpr Peripheral peripheral_from_index(uint8_t idx) { + switch (idx) { + case 0: + return Peripheral::ADC_1; + case 1: + return Peripheral::ADC_2; + case 2: + return Peripheral::ADC_3; + default: + return Peripheral::ADC_1; + } + } + + static consteval DMADomain::Peripheral dma_peripheral(Peripheral p) { + switch (p) { + case Peripheral::ADC_1: + return DMADomain::Peripheral::adc1; + case Peripheral::ADC_2: + return DMADomain::Peripheral::adc2; + case Peripheral::ADC_3: + return DMADomain::Peripheral::adc3; + case Peripheral::AUTO: + break; + } + return DMADomain::Peripheral::adc1; + } + + static consteval uint32_t dma_request(Peripheral p) { + return DMADomain::get_Request(dma_peripheral(p), 0); + } + + static constexpr std::array regular_ranks{{ + ADC_REGULAR_RANK_1, + ADC_REGULAR_RANK_2, + ADC_REGULAR_RANK_3, + ADC_REGULAR_RANK_4, + ADC_REGULAR_RANK_5, + ADC_REGULAR_RANK_6, + ADC_REGULAR_RANK_7, + ADC_REGULAR_RANK_8, + ADC_REGULAR_RANK_9, + ADC_REGULAR_RANK_10, + ADC_REGULAR_RANK_11, + ADC_REGULAR_RANK_12, + ADC_REGULAR_RANK_13, + ADC_REGULAR_RANK_14, + ADC_REGULAR_RANK_15, + ADC_REGULAR_RANK_16, + }}; + + static constexpr uint32_t regular_rank(uint8_t zero_based_rank) { + if (zero_based_rank >= regular_ranks.size()) { + return ADC_REGULAR_RANK_1; + } + return regular_ranks[zero_based_rank]; + } + + struct BufferSizes { + std::size_t adc1_size = 0; + std::size_t adc2_size = 0; + std::size_t adc3_size = 0; + }; + + static consteval BufferSizes calculate_buffer_sizes(span cfgs) { + BufferSizes sizes; + for (const auto& cfg : cfgs) { + switch (cfg.peripheral) { + case Peripheral::ADC_1: + sizes.adc1_size++; + break; + case Peripheral::ADC_2: + sizes.adc2_size++; + break; + case Peripheral::ADC_3: + sizes.adc3_size++; + break; + case Peripheral::AUTO: + compile_error("ADC: peripheral not resolved"); + break; + } + } + return sizes; + } + + static consteval bool is_valid_channel(Channel ch) { + switch (ch) { + case Channel::AUTO: + return true; + case Channel::CH0: + case Channel::CH1: + case Channel::CH2: + case Channel::CH3: + case Channel::CH4: + case Channel::CH5: + case Channel::CH6: + case Channel::CH7: + case Channel::CH8: + case Channel::CH9: + case Channel::CH10: + case Channel::CH11: + case Channel::CH12: + case Channel::CH13: + case Channel::CH14: + case Channel::CH15: + case Channel::CH16: + case Channel::CH17: + case Channel::CH18: + case Channel::CH19: + case Channel::VREFINT: + case Channel::TEMPSENSOR: + case Channel::VBAT: + return true; + } + return false; + } + + static consteval uint8_t resolution_bits(Resolution r) { + switch (r) { + case Resolution::BITS_16: + return 16; + case Resolution::BITS_14: + return 14; + case Resolution::BITS_12: + return 12; + case Resolution::BITS_10: + return 10; + case Resolution::BITS_8: + return 8; + } + return 12; + } + + static consteval bool is_internal_channel(Channel ch) { + switch (ch) { + case Channel::VREFINT: + case Channel::TEMPSENSOR: + case Channel::VBAT: + return true; + default: + return false; + } + } + + static consteval bool internal_channel_allowed(Peripheral p, Channel ch) { + if (!is_internal_channel(ch)) { + return true; + } +#if STLIB_HAS_ADC3 + return p == Peripheral::ADC_3; +#else + return p == Peripheral::ADC_2; +#endif + } + + static consteval bool resolution_supported(Peripheral p, Resolution r) { + if (p == Peripheral::ADC_3) { + return resolution_bits(r) <= 12; + } + return true; + } + + struct PinMapping { + GPIODomain::Pin pin; + Peripheral peripheral; + Channel channel; + uint8_t max_bits; + }; + + static constexpr uint8_t max_bits_adc12 = 16; + static constexpr uint8_t max_bits_adc3 = 12; + + static constexpr std::array pin_map{{ + {PA0, Peripheral::ADC_1, Channel::CH16, max_bits_adc12}, + {PA1, Peripheral::ADC_1, Channel::CH17, max_bits_adc12}, + {PA2, Peripheral::ADC_1, Channel::CH14, max_bits_adc12}, + {PA3, Peripheral::ADC_1, Channel::CH15, max_bits_adc12}, + {PA4, Peripheral::ADC_1, Channel::CH18, max_bits_adc12}, + {PA5, Peripheral::ADC_1, Channel::CH19, max_bits_adc12}, + {PA6, Peripheral::ADC_1, Channel::CH3, max_bits_adc12}, + {PA7, Peripheral::ADC_1, Channel::CH7, max_bits_adc12}, + {PB0, Peripheral::ADC_1, Channel::CH9, max_bits_adc12}, + {PB1, Peripheral::ADC_1, Channel::CH5, max_bits_adc12}, + {PC0, Peripheral::ADC_1, Channel::CH10, max_bits_adc12}, + {PC1, Peripheral::ADC_1, Channel::CH11, max_bits_adc12}, + {PC4, Peripheral::ADC_1, Channel::CH4, max_bits_adc12}, + {PC5, Peripheral::ADC_1, Channel::CH8, max_bits_adc12}, + {PF11, Peripheral::ADC_1, Channel::CH2, max_bits_adc12}, + {PF12, Peripheral::ADC_1, Channel::CH6, max_bits_adc12}, + + {PA2, Peripheral::ADC_2, Channel::CH14, max_bits_adc12}, + {PA3, Peripheral::ADC_2, Channel::CH15, max_bits_adc12}, + {PA4, Peripheral::ADC_2, Channel::CH18, max_bits_adc12}, + {PA5, Peripheral::ADC_2, Channel::CH19, max_bits_adc12}, + {PA6, Peripheral::ADC_2, Channel::CH3, max_bits_adc12}, + {PA7, Peripheral::ADC_2, Channel::CH7, max_bits_adc12}, + {PB0, Peripheral::ADC_2, Channel::CH9, max_bits_adc12}, + {PB1, Peripheral::ADC_2, Channel::CH5, max_bits_adc12}, + {PC0, Peripheral::ADC_2, Channel::CH10, max_bits_adc12}, + {PC1, Peripheral::ADC_2, Channel::CH11, max_bits_adc12}, + {PC4, Peripheral::ADC_2, Channel::CH4, max_bits_adc12}, + {PC5, Peripheral::ADC_2, Channel::CH8, max_bits_adc12}, + {PF13, Peripheral::ADC_2, Channel::CH2, max_bits_adc12}, + {PF14, Peripheral::ADC_2, Channel::CH6, max_bits_adc12}, + + {PC0, Peripheral::ADC_3, Channel::CH10, max_bits_adc3}, + {PC1, Peripheral::ADC_3, Channel::CH11, max_bits_adc3}, + {PC2, Peripheral::ADC_3, Channel::CH0, max_bits_adc3}, + {PC3, Peripheral::ADC_3, Channel::CH1, max_bits_adc3}, + {PF3, Peripheral::ADC_3, Channel::CH5, max_bits_adc3}, + {PF4, Peripheral::ADC_3, Channel::CH9, max_bits_adc3}, + {PF5, Peripheral::ADC_3, Channel::CH4, max_bits_adc3}, + {PF6, Peripheral::ADC_3, Channel::CH8, max_bits_adc3}, + {PF7, Peripheral::ADC_3, Channel::CH3, max_bits_adc3}, + {PF8, Peripheral::ADC_3, Channel::CH7, max_bits_adc3}, + {PF9, Peripheral::ADC_3, Channel::CH2, max_bits_adc3}, + {PF10, Peripheral::ADC_3, Channel::CH6, max_bits_adc3}, + }}; + + static consteval uint8_t preference_score(Resolution r, Peripheral p) { + const uint8_t bits = resolution_bits(r); + if (bits > 12) { + switch (p) { + case Peripheral::ADC_1: + return 3; + case Peripheral::ADC_2: + return 2; + case Peripheral::ADC_3: + return 0; + case Peripheral::AUTO: + return 0; + } + } else { + switch (p) { + case Peripheral::ADC_3: + return 3; + case Peripheral::ADC_1: + return 2; + case Peripheral::ADC_2: + return 1; + case Peripheral::AUTO: + return 0; + } + } + return 0; + } + + static consteval std::pair resolve_mapping(const Entry& e) { + if (e.peripheral != Peripheral::AUTO && e.channel != Channel::AUTO) { + if (!is_valid_channel(e.channel)) { + compile_error("ADC: invalid channel"); + } + if (!internal_channel_allowed(e.peripheral, e.channel)) { + compile_error("ADC: internal channel must use dedicated ADC"); + } + if (!resolution_supported(e.peripheral, e.resolution)) { + compile_error("ADC: resolution not supported by selected ADC"); + } + return {e.peripheral, e.channel}; + } + + if (e.channel != Channel::AUTO && is_internal_channel(e.channel)) { + const Peripheral p = (e.peripheral == Peripheral::AUTO) +#if STLIB_HAS_ADC3 + ? Peripheral::ADC_3 +#else + ? Peripheral::ADC_2 +#endif + : e.peripheral; + + if (!internal_channel_allowed(p, e.channel)) { + compile_error("ADC: internal channel must use dedicated ADC"); + } + if (!resolution_supported(p, e.resolution)) { + compile_error("ADC: resolution not supported by selected ADC"); + } + return {p, e.channel}; + } + + bool found = false; + Peripheral best_peripheral = Peripheral::AUTO; + Channel best_channel = Channel::AUTO; + uint8_t best_score = 0; + + for (const auto& m : pin_map) { + if (m.pin != e.pin) { + continue; + } + if (e.peripheral != Peripheral::AUTO && m.peripheral != e.peripheral) { + continue; + } + if (e.channel != Channel::AUTO && m.channel != e.channel) { + continue; + } + if (resolution_bits(e.resolution) > m.max_bits) { + continue; + } + + const uint8_t score = preference_score(e.resolution, m.peripheral); + if (!found || score > best_score) { + found = true; + best_score = score; + best_peripheral = m.peripheral; + best_channel = m.channel; + } + } + + if (!found) { + compile_error("ADC: no valid ADC mapping for pin/resolution"); + } + + return {best_peripheral, best_channel}; + } + + template static consteval array build(span entries) { + static_assert(N <= max_instances, "ADCDomain: too many instances"); + if (entries.size() != N) { + compile_error("ADC: build entries size mismatch"); + } + + array cfgs{}; + array periph_seen{}; + array periph_resolution{}; + array periph_prescaler{}; + array periph_rate{}; + array periph_counts{}; + + for (std::size_t i = 0; i < N; ++i) { + const auto& e = entries[i]; + const auto [peripheral, channel] = resolve_mapping(e); + + if (!is_valid_channel(channel)) { + compile_error("ADC: invalid channel"); + } + if (!internal_channel_allowed(peripheral, channel)) { + compile_error("ADC: internal channel must use dedicated ADC"); + } + if (!resolution_supported(peripheral, e.resolution)) { + compile_error("ADC: resolution not supported by selected ADC"); + } + if (e.sample_rate_hz != 0) { + compile_error("ADC: sample_rate_hz is not supported in DMA mode"); + } + + const auto pidx = peripheral_index(peripheral); + + if (!periph_seen[pidx]) { + periph_seen[pidx] = true; + periph_resolution[pidx] = e.resolution; + periph_prescaler[pidx] = e.prescaler; + periph_rate[pidx] = e.sample_rate_hz; + } else { + if (periph_resolution[pidx] != e.resolution) { + compile_error("ADC: resolution mismatch on same peripheral"); + } + if (periph_prescaler[pidx] != e.prescaler) { + compile_error("ADC: prescaler mismatch on same peripheral"); + } + if (periph_rate[pidx] != e.sample_rate_hz) { + compile_error("ADC: sample rate mismatch on same peripheral"); + } + } + + ++periph_counts[pidx]; + if (periph_counts[pidx] > max_channels_per_peripheral) { + compile_error("ADC: too many channels on same peripheral"); + } + + for (std::size_t j = 0; j < i; ++j) { + const auto& prev = entries[j]; + if (prev.gpio_idx == e.gpio_idx) { + compile_error("ADC: GPIO already used"); + } + const auto [prev_peripheral, prev_channel] = resolve_mapping(prev); + if (prev_peripheral == peripheral && prev_channel == channel) { + compile_error("ADC: duplicate channel on same peripheral"); + } + } + + cfgs[i] = { + .gpio_idx = e.gpio_idx, + .peripheral = peripheral, + .channel = channel, + .resolution = e.resolution, + .sample_time = e.sample_time, + .prescaler = e.prescaler, + .sample_rate_hz = e.sample_rate_hz, + .dma_request = dma_request(peripheral), + .output = e.output, + }; + } + + return cfgs; + } + + static consteval bool uses_peripheral(Peripheral peripheral, span cfgs) { + for (const auto& cfg : cfgs) { + if (cfg.peripheral == peripheral) { + return true; + } + } + return false; + } + + static consteval std::size_t + dma_entries_for_peripheral(Peripheral peripheral, span dma_entries) { + std::size_t count = 0; + for (const auto& entry : dma_entries) { + if (entry.instance != dma_peripheral(peripheral)) { + continue; + } + if (entry.id != 0U) { + compile_error("ADC: DMA for ADC peripherals must use stream id 0"); + } + ++count; + } + return count; + } + + static consteval std::size_t + dma_contribution_count(span cfgs, span dma_entries) { + std::size_t count = 0; + for (uint8_t pidx = 0; pidx < 3U; ++pidx) { + const Peripheral peripheral = peripheral_from_index(pidx); + if (!uses_peripheral(peripheral, cfgs)) { + continue; + } + + const auto existing = dma_entries_for_peripheral(peripheral, dma_entries); + if (existing > 1U) { + compile_error("ADC: multiple DMA streams configured for the same ADC peripheral"); + } + if (existing == 0U) { + ++count; + } + } + return count; + } + + template + static consteval std::array + build_dma_contributions(span dma_entries, span cfgs) { + std::array extra{}; + std::size_t cursor = 0; + + for (uint8_t pidx = 0; pidx < 3U; ++pidx) { + const Peripheral peripheral = peripheral_from_index(pidx); + if (!uses_peripheral(peripheral, cfgs)) { + continue; + } + + const auto existing = dma_entries_for_peripheral(peripheral, dma_entries); + if (existing == 0U) { + extra[cursor++] = { + .instance = dma_peripheral(peripheral), + .stream = DMADomain::Stream::none, + .irqn = static_cast(0), + .id = 0, + }; + } + } + + if (cursor != ExtraN) { + compile_error("ADC: DMA contribution size mismatch"); + } + + return extra; + } + + static DMADomain::Instance* + find_dma_instance(uint32_t request, std::span dma_peripherals) { + for (auto& dma_instance : dma_peripherals) { + if (dma_instance.dma.Init.Request == request) { + return &dma_instance; + } + } + return nullptr; + } + + struct Instance { + ADC_HandleTypeDef* handle = nullptr; + Channel channel = Channel::CH0; + SampleTime sample_time = SampleTime::CYCLES_8_5; + Resolution resolution = Resolution::BITS_12; + float* output = nullptr; + volatile uint16_t* dma_slot = nullptr; + + static constexpr uint32_t max_raw_for_resolution(Resolution r) { + switch (r) { + case Resolution::BITS_16: + return 65535U; + case Resolution::BITS_14: + return 16383U; + case Resolution::BITS_12: + return 4095U; + case Resolution::BITS_10: + return 1023U; + case Resolution::BITS_8: + return 255U; + } + return 4095U; + } + + private: + uint32_t sample_raw() const { + if (dma_slot == nullptr) { + return 0; + } + const uint32_t raw = *dma_slot; + return std::min(raw, max_raw_for_resolution(resolution)); + } + public: - Peripheral* peripheral; - uint32_t channel; - uint32_t rank; + float get_raw(uint32_t timeout_ms = 2) { + (void)timeout_ms; + return static_cast(sample_raw()); + } - Instance() = default; - Instance(Peripheral* peripheral, uint32_t channel); + float get_value_from_raw(float raw, float vref = 3.3f) const { + const float max_val = static_cast(max_raw_for_resolution(resolution)); + if (max_val <= 0.0f) { + return 0.0f; + } + return (raw / max_val) * vref; + } + + float get_value(uint32_t timeout_ms = 2, float vref = 3.3f) { + (void)timeout_ms; + const float raw = get_raw(timeout_ms); + return get_value_from_raw(raw, vref); + } + + void read(float vref = 3.3f, uint32_t timeout_ms = 2) { + if (output == nullptr) { + return; + } + *output = get_value(timeout_ms, vref); + } }; - /** - * @brief A method to add a pin as an ADC input on the ST-LIB. - - * This method has to be invoked before the ST-LIB::start(), as it marks the pin to be - configured as an ADC, and that configuration is made in the ST-LIB::start(). - * As said on the class description, only correctly configured ADC pins will work correctly when - declared as an ADC. - * It is forbidden to declare a pin as an ADC and as anything else on services at the same time, - and it will result in undefined behavior. - * - * @param pin the Pin to be added as an ADC - * - * @return the id that represents the ADC inside this utility class, used in all its functions. - */ - static uint8_t inscribe(Pin pin); - - /** - * @brief Method used in ST-LIBstart() to configure pins inscribed as ADCs. - * - * The start methods of the HALAL are made to be invoked in an specific order. - * As such, its recommended to not use them isolated, and instead use the ST-LIB::start(). - * The If for any reason not starting a service from HALAL was desired, removing the definition - * of its HAL parent module would deactivate it. For example, if ADC was needed out of the code, - * removing define HAL_ADC_MODULE_ENABLED on stm32h7xx_hal_conf.h would result in that behavior. - */ - static void start(); - - /** - * @brief Activates the ADC represented by the id. - * - * After the ADC is configured in the ST-LIBstart::() it needs to be activated with turn_on to - * start reading. This is made this way so the user can control exactly when the measures start - * to be taken. The get_value() method will only work on a Pin that has both been inscribed, - * started, and turned on. - * - * @param id the id of ADC to be activated. - */ - static void turn_on(uint8_t id); - - /** - * @brief Returns the value of the last DMA read made by the ADC. - * - * The get_value function doesn t issue a read, but rather pulls the memory where the last read - * made is saved, transforms the value with the reference voltage and returns the voltage - * represented with that value. The capture of the value is made automatically by the DMA - * configured for the respective ADC channel, and the frequency of the reads is dependant on the - * configuration of the DMA itself. - * - * @param id the id of the ADC to be read. - * - * @return the value of the ADC in volts. The ADC_MAX_VOLTAGE needs to be correctly configured - * in order for this function to work. - */ - static float get_value(uint8_t id); - - /** - * @brief Returns the value of the last DMA read made by the ADC. - * - * The get_int_value function doesn t issue a read, but rather pulls the memory where the last - * read made is saved and returns that value. The capture of the value is made automatically by - * the DMA configured for the respective ADC channel, and the frequency of the reads is - * dependant on the configuration of the DMA itself. - * - * @param id the id of the ADC to be read. - * - * @return the value of the ADC, in uint16_t format. 0 is minimum possible value and - * max_uint16_t is the maximum. - */ - static uint16_t get_int_value(uint8_t id); - - /** - * @brief Function that returns the pointer where the DMA of the ADC writes its value, for - * maximum efficiency on the access - * - * This function has no protection of any kind, other that checking that an adc exists before - * giving the pointer back. If the ADC is running or not should be handled by the user. The adcs - * of the adc3 peripheral are not aligned in the buffer, and are instead aligned in the get - * functions. If the values are accessed from the buffer, is the responsibility of the user to - * handle the shift problems. - */ - static uint16_t* get_value_pointer(uint8_t id); - - static Peripheral peripherals[3]; - -private: - static uint32_t ranks[16]; - static map available_instances; - static unordered_map active_instances; - static uint8_t id_counter; - - static void init(Peripheral& peripheral); -}; + template cfgs> struct Init { + static inline std::array instances{}; + + static constexpr auto buffer_sizes = calculate_buffer_sizes(cfgs); + static constexpr std::size_t total_dma_slots = + buffer_sizes.adc1_size + buffer_sizes.adc2_size + buffer_sizes.adc3_size; + static_assert( + total_dma_slots <= max_instances, + "ADC DMA buffer size exceeds max_instances" + ); + + alignas(32) STLIB_ADC_DMA_BUFFER_ATTR + static inline uint16_t dma_buffer_pool[total_dma_slots > 0 ? total_dma_slots : 1]{}; + + static constexpr bool is_resolved_config(const Config& cfg) { + return cfg.peripheral != Peripheral::AUTO && cfg.channel != Channel::AUTO; + } + + static ADC_HandleTypeDef* handle_for(Peripheral p) { + switch (p) { + case Peripheral::ADC_1: + return &hadc1; + case Peripheral::ADC_2: + return &hadc2; + case Peripheral::ADC_3: + return &hadc3; + case Peripheral::AUTO: + break; + } + return &hadc1; + } + + static constexpr std::size_t buffer_size_for(Peripheral peripheral) { + switch (peripheral) { + case Peripheral::ADC_1: + return buffer_sizes.adc1_size; + case Peripheral::ADC_2: + return buffer_sizes.adc2_size; + case Peripheral::ADC_3: + return buffer_sizes.adc3_size; + case Peripheral::AUTO: + break; + } + return 0; + } + + static constexpr std::size_t buffer_offset_for(Peripheral peripheral) { + switch (peripheral) { + case Peripheral::ADC_1: + return 0; + case Peripheral::ADC_2: + return buffer_sizes.adc1_size; + case Peripheral::ADC_3: + return buffer_sizes.adc1_size + buffer_sizes.adc2_size; + case Peripheral::AUTO: + break; + } + return 0; + } + static uint16_t* get_dma_buffer(Peripheral peripheral) { + const auto buffer_size = buffer_size_for(peripheral); + const auto buffer_offset = buffer_offset_for(peripheral); + if (buffer_size == 0U) { + ErrorHandler("ADC DMA buffer not available"); + return nullptr; + } + if ((buffer_offset + buffer_size) > total_dma_slots) { + ErrorHandler("ADC DMA pool overflow"); + return nullptr; + } + + uint16_t* buffer = &dma_buffer_pool[buffer_offset]; + std::fill_n(buffer, buffer_size, uint16_t{0}); + return buffer; + } + static uint16_t* get_dma_slot(Peripheral peripheral, uint8_t index) { + if (index >= buffer_size_for(peripheral)) { + return nullptr; + } + const auto buffer_offset = buffer_offset_for(peripheral); + if ((buffer_offset + index) >= total_dma_slots) { + return nullptr; + } + return &dma_buffer_pool[buffer_offset + index]; + } + + static void configure_peripheral(const Config& cfg, uint8_t channel_count) { + ADC_HandleTypeDef* hadc = handle_for(cfg.peripheral); + + if (cfg.peripheral == Peripheral::ADC_1 || cfg.peripheral == Peripheral::ADC_2) { + __HAL_RCC_ADC12_CLK_ENABLE(); + hadc->Instance = (cfg.peripheral == Peripheral::ADC_1) ? ADC1 : ADC2; + } else { + __HAL_RCC_ADC3_CLK_ENABLE(); + hadc->Instance = ADC3; + HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC2, SYSCFG_SWITCH_PC2_OPEN); + HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC3, SYSCFG_SWITCH_PC3_OPEN); + } + + hadc->Init.ClockPrescaler = static_cast(cfg.prescaler); + hadc->Init.Resolution = static_cast(cfg.resolution); + hadc->Init.ScanConvMode = (channel_count > 1U) ? ADC_SCAN_ENABLE : ADC_SCAN_DISABLE; + hadc->Init.EOCSelection = (channel_count > 1U) ? ADC_EOC_SEQ_CONV : ADC_EOC_SINGLE_CONV; + hadc->Init.LowPowerAutoWait = DISABLE; + hadc->Init.ContinuousConvMode = ENABLE; + hadc->Init.NbrOfConversion = channel_count; + hadc->Init.DiscontinuousConvMode = DISABLE; + hadc->Init.NbrOfDiscConversion = 0; + hadc->Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; +#if defined(ADC_VER_V5_V90) + hadc->Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL; + hadc->Init.DMAContinuousRequests = ENABLE; +#endif + hadc->Init.Overrun = ADC_OVR_DATA_PRESERVED; + hadc->Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; + hadc->Init.OversamplingMode = DISABLE; + } + + static void init( + std::span runtime_cfgs, + std::span gpio_instances = std::span{}, + std::span dma_peripherals = std::span{} + ) { + std::array periph_ready{false, false, false}; + std::array periph_channel_counts{0, 0, 0}; + std::array instance_cfg_valid{}; + std::array instance_ranks{}; + (void)gpio_instances; + + for (auto& instance : instances) { + instance = {}; + } + + for (std::size_t i = 0; i < N; ++i) { + const auto& cfg = runtime_cfgs[i]; + if (!is_resolved_config(cfg)) { + ErrorHandler("ADC config unresolved (AUTO)"); + continue; + } + instance_cfg_valid[i] = true; + instance_ranks[i] = periph_channel_counts[peripheral_index(cfg.peripheral)]++; + } + + for (uint8_t pidx = 0; pidx < 3U; ++pidx) { + const auto channel_count = periph_channel_counts[pidx]; + if (channel_count == 0U) { + continue; + } + + const Peripheral peripheral = peripheral_from_index(pidx); + const Config* first_cfg = nullptr; + for (const auto& cfg : runtime_cfgs) { + if (cfg.peripheral == peripheral) { + first_cfg = &cfg; + break; + } + } + + if (first_cfg == nullptr) { + continue; + } + + ADC_HandleTypeDef* hadc = handle_for(peripheral); + if (hadc->Instance != nullptr && hadc->DMA_Handle != nullptr) { + (void)HAL_ADC_Stop_DMA(hadc); + } + + DMADomain::Instance* dma_instance = + find_dma_instance(first_cfg->dma_request, dma_peripherals); + if (dma_instance == nullptr) { + ErrorHandler("ADC DMA instance unavailable"); + continue; + } + uint16_t* buffer = get_dma_buffer(peripheral); + if (buffer == nullptr) { + continue; + } + + hadc->DMA_Handle = &dma_instance->dma; + dma_instance->dma.Parent = hadc; + configure_peripheral(*first_cfg, channel_count); + + if (HAL_ADC_Init(hadc) != HAL_OK) { + ErrorHandler("ADC Init failed"); + continue; + } + + uint8_t rank = 0; + bool config_error = false; + for (const auto& cfg : runtime_cfgs) { + if (cfg.peripheral != peripheral) { + continue; + } + + ADC_ChannelConfTypeDef sConfig{}; + sConfig.Channel = static_cast(cfg.channel); + sConfig.Rank = regular_rank(rank); + sConfig.SamplingTime = static_cast(cfg.sample_time); + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; +#if defined(ADC_VER_V5_V90) + sConfig.OffsetSignedSaturation = DISABLE; #endif + + if (HAL_ADC_ConfigChannel(hadc, &sConfig) != HAL_OK) { + ErrorHandler("ADC channel configuration failed"); + config_error = true; + break; + } + rank++; + } + + if (config_error) { + continue; + } + + if (HAL_ADC_Start_DMA(hadc, reinterpret_cast(buffer), channel_count) != + HAL_OK) { + ErrorHandler("ADC DMA start failed"); + continue; + } + + periph_ready[pidx] = true; + } + + for (std::size_t i = 0; i < N; ++i) { + const auto& cfg = runtime_cfgs[i]; + if (!instance_cfg_valid[i]) { + continue; + } + const auto pidx = peripheral_index(cfg.peripheral); + instances[i].handle = periph_ready[pidx] ? handle_for(cfg.peripheral) : nullptr; + instances[i].channel = cfg.channel; + instances[i].sample_time = cfg.sample_time; + instances[i].resolution = cfg.resolution; + instances[i].output = cfg.output; + instances[i].dma_slot = + periph_ready[pidx] ? get_dma_slot(cfg.peripheral, instance_ranks[i]) : nullptr; + } + } + }; +}; + +#else // HAL_ADC_MODULE_ENABLED + +struct ADCDomain { + static constexpr std::size_t max_instances{0}; + struct Entry {}; + struct Config {}; + template static consteval array build(span) { return {}; } + template struct Init { + static void init( + std::span, + std::span = std::span{}, + std::span = std::span{} + ) {} + }; +}; + +#endif // HAL_ADC_MODULE_ENABLED + +#undef STLIB_ADC_DMA_BUFFER_ATTR + +} // namespace ST_LIB diff --git a/Inc/HALAL/Services/ADC/NewADC.hpp b/Inc/HALAL/Services/ADC/NewADC.hpp deleted file mode 100644 index 2933637d5..000000000 --- a/Inc/HALAL/Services/ADC/NewADC.hpp +++ /dev/null @@ -1,733 +0,0 @@ -#pragma once - -#include "hal_wrapper.h" - -#include -#include -#include -#include - -#include "ErrorHandler/ErrorHandler.hpp" -#include "HALAL/Models/GPIO.hpp" -#include "HALAL/Models/Pin.hpp" - -using std::array; -using std::size_t; -using std::span; - -#ifdef HAL_ADC_MODULE_ENABLED -extern ADC_HandleTypeDef hadc1; -extern ADC_HandleTypeDef hadc2; -extern ADC_HandleTypeDef hadc3; -#endif - -namespace ST_LIB { -extern void compile_error(const char* msg); - -#ifdef HAL_ADC_MODULE_ENABLED -using ::hadc1; -using ::hadc2; -using ::hadc3; - -struct ADCDomain { - static constexpr std::size_t max_channels_per_peripheral = 16; - enum class Peripheral : uint8_t { AUTO, ADC_1, ADC_2, ADC_3 }; - - enum class Resolution : uint32_t { - BITS_16 = ADC_RESOLUTION_16B, - BITS_14 = ADC_RESOLUTION_14B, - BITS_12 = ADC_RESOLUTION_12B, - BITS_10 = ADC_RESOLUTION_10B, - BITS_8 = ADC_RESOLUTION_8B, - }; - - enum class SampleTime : uint32_t { - CYCLES_1_5 = ADC_SAMPLETIME_1CYCLE_5, - CYCLES_2_5 = ADC_SAMPLETIME_2CYCLES_5, - CYCLES_8_5 = ADC_SAMPLETIME_8CYCLES_5, - CYCLES_16_5 = ADC_SAMPLETIME_16CYCLES_5, - CYCLES_32_5 = ADC_SAMPLETIME_32CYCLES_5, - CYCLES_64_5 = ADC_SAMPLETIME_64CYCLES_5, - CYCLES_387_5 = ADC_SAMPLETIME_387CYCLES_5, - CYCLES_810_5 = ADC_SAMPLETIME_810CYCLES_5, - }; - - enum class ClockPrescaler : uint32_t { - DIV1 = ADC_CLOCK_ASYNC_DIV1, - DIV2 = ADC_CLOCK_ASYNC_DIV2, - DIV4 = ADC_CLOCK_ASYNC_DIV4, - DIV6 = ADC_CLOCK_ASYNC_DIV6, - DIV8 = ADC_CLOCK_ASYNC_DIV8, - DIV10 = ADC_CLOCK_ASYNC_DIV10, - DIV12 = ADC_CLOCK_ASYNC_DIV12, - DIV16 = ADC_CLOCK_ASYNC_DIV16, - DIV32 = ADC_CLOCK_ASYNC_DIV32, - DIV64 = ADC_CLOCK_ASYNC_DIV64, - DIV128 = ADC_CLOCK_ASYNC_DIV128, - DIV256 = ADC_CLOCK_ASYNC_DIV256, - }; - - enum class Channel : uint32_t { - AUTO = 0xFFFFFFFFu, - CH0 = ADC_CHANNEL_0, - CH1 = ADC_CHANNEL_1, - CH2 = ADC_CHANNEL_2, - CH3 = ADC_CHANNEL_3, - CH4 = ADC_CHANNEL_4, - CH5 = ADC_CHANNEL_5, - CH6 = ADC_CHANNEL_6, - CH7 = ADC_CHANNEL_7, - CH8 = ADC_CHANNEL_8, - CH9 = ADC_CHANNEL_9, - CH10 = ADC_CHANNEL_10, - CH11 = ADC_CHANNEL_11, - CH12 = ADC_CHANNEL_12, - CH13 = ADC_CHANNEL_13, - CH14 = ADC_CHANNEL_14, - CH15 = ADC_CHANNEL_15, - CH16 = ADC_CHANNEL_16, - CH17 = ADC_CHANNEL_17, - CH18 = ADC_CHANNEL_18, - CH19 = ADC_CHANNEL_19, - VREFINT = ADC_CHANNEL_VREFINT, - TEMPSENSOR = ADC_CHANNEL_TEMPSENSOR, - VBAT = ADC_CHANNEL_VBAT, - }; - - struct Entry { - size_t gpio_idx; - GPIODomain::Pin pin; - Peripheral peripheral; - Channel channel; - Resolution resolution; - SampleTime sample_time; - ClockPrescaler prescaler; - uint32_t sample_rate_hz; - float* output; - }; - - struct ADC { - GPIODomain::GPIO gpio; - using domain = ADCDomain; - - Entry e; - - consteval ADC( - const GPIODomain::Pin& pin, - float& output, - Resolution resolution = Resolution::BITS_12, - SampleTime sample_time = SampleTime::CYCLES_8_5, - ClockPrescaler prescaler = ClockPrescaler::DIV1, - uint32_t sample_rate_hz = 0, - Peripheral peripheral = Peripheral::AUTO, - Channel channel = Channel::AUTO - ) - : gpio{pin, GPIODomain::OperationMode::ANALOG, GPIODomain::Pull::None, GPIODomain::Speed::Low}, - e{.gpio_idx = 0, - .pin = pin, - .peripheral = peripheral, - .channel = channel, - .resolution = resolution, - .sample_time = sample_time, - .prescaler = prescaler, - .sample_rate_hz = sample_rate_hz, - .output = &output} {} - - consteval ADC( - const GPIODomain::Pin& pin, - Peripheral peripheral, - Channel channel, - float& output, - Resolution resolution = Resolution::BITS_12, - SampleTime sample_time = SampleTime::CYCLES_8_5, - ClockPrescaler prescaler = ClockPrescaler::DIV1, - uint32_t sample_rate_hz = 0 - ) - : ADC(pin, - output, - resolution, - sample_time, - prescaler, - sample_rate_hz, - peripheral, - channel) {} - - template consteval std::size_t inscribe(Ctx& ctx) const { - const auto gpio_idx = gpio.inscribe(ctx); - Entry entry = e; - entry.gpio_idx = gpio_idx; - const auto resolved = resolve_mapping(entry); - entry.peripheral = resolved.first; - entry.channel = resolved.second; - return ctx.template add(entry, this); - } - }; - - static constexpr std::size_t max_instances{32}; - - struct Config { - size_t gpio_idx; - Peripheral peripheral; - Channel channel; - Resolution resolution; - SampleTime sample_time; - ClockPrescaler prescaler; - uint32_t sample_rate_hz; - float* output; - }; - - static constexpr uint8_t peripheral_index(Peripheral p) { - switch (p) { - case Peripheral::AUTO: - return 0; - case Peripheral::ADC_1: - return 0; - case Peripheral::ADC_2: - return 1; - case Peripheral::ADC_3: - return 2; - } - return 0; - } - - static uint8_t peripheral_index_from_handle(ADC_HandleTypeDef* handle) { - if (handle == &hadc1) { - return peripheral_index(Peripheral::ADC_1); - } - if (handle == &hadc2) { - return peripheral_index(Peripheral::ADC_2); - } - if (handle == &hadc3) { - return peripheral_index(Peripheral::ADC_3); - } - return 0; - } - - static inline std::array peripheral_running{false, false, false}; - static inline std::array active_channel{ - Channel::AUTO, - Channel::AUTO, - Channel::AUTO - }; - - static consteval bool is_valid_channel(Channel ch) { - switch (ch) { - case Channel::AUTO: - return true; - case Channel::CH0: - case Channel::CH1: - case Channel::CH2: - case Channel::CH3: - case Channel::CH4: - case Channel::CH5: - case Channel::CH6: - case Channel::CH7: - case Channel::CH8: - case Channel::CH9: - case Channel::CH10: - case Channel::CH11: - case Channel::CH12: - case Channel::CH13: - case Channel::CH14: - case Channel::CH15: - case Channel::CH16: - case Channel::CH17: - case Channel::CH18: - case Channel::CH19: - case Channel::VREFINT: - case Channel::TEMPSENSOR: - case Channel::VBAT: - return true; - } - return false; - } - - static consteval uint8_t resolution_bits(Resolution r) { - switch (r) { - case Resolution::BITS_16: - return 16; - case Resolution::BITS_14: - return 14; - case Resolution::BITS_12: - return 12; - case Resolution::BITS_10: - return 10; - case Resolution::BITS_8: - return 8; - } - return 12; - } - - static consteval bool is_internal_channel(Channel ch) { - switch (ch) { - case Channel::VREFINT: - case Channel::TEMPSENSOR: - case Channel::VBAT: - return true; - default: - return false; - } - } - - static consteval bool internal_channel_allowed(Peripheral p, Channel ch) { - if (!is_internal_channel(ch)) { - return true; - } -#if STLIB_HAS_ADC3 - return p == Peripheral::ADC_3; -#else - return p == Peripheral::ADC_2; -#endif - } - - static consteval bool resolution_supported(Peripheral p, Resolution r) { - if (p == Peripheral::ADC_3) { - return resolution_bits(r) <= 12; - } - return true; - } - - struct PinMapping { - GPIODomain::Pin pin; - Peripheral peripheral; - Channel channel; - uint8_t max_bits; - }; - - static constexpr uint8_t max_bits_adc12 = 16; - static constexpr uint8_t max_bits_adc3 = 12; - - static constexpr std::array pin_map{{ - {PA0, Peripheral::ADC_1, Channel::CH16, max_bits_adc12}, - {PA1, Peripheral::ADC_1, Channel::CH17, max_bits_adc12}, - {PA2, Peripheral::ADC_1, Channel::CH14, max_bits_adc12}, - {PA3, Peripheral::ADC_1, Channel::CH15, max_bits_adc12}, - {PA4, Peripheral::ADC_1, Channel::CH18, max_bits_adc12}, - {PA5, Peripheral::ADC_1, Channel::CH19, max_bits_adc12}, - {PA6, Peripheral::ADC_1, Channel::CH3, max_bits_adc12}, - {PA7, Peripheral::ADC_1, Channel::CH7, max_bits_adc12}, - {PB0, Peripheral::ADC_1, Channel::CH9, max_bits_adc12}, - {PB1, Peripheral::ADC_1, Channel::CH5, max_bits_adc12}, - {PC0, Peripheral::ADC_1, Channel::CH10, max_bits_adc12}, - {PC1, Peripheral::ADC_1, Channel::CH11, max_bits_adc12}, - {PC4, Peripheral::ADC_1, Channel::CH4, max_bits_adc12}, - {PC5, Peripheral::ADC_1, Channel::CH8, max_bits_adc12}, - {PF11, Peripheral::ADC_1, Channel::CH2, max_bits_adc12}, - {PF12, Peripheral::ADC_1, Channel::CH6, max_bits_adc12}, - - {PA2, Peripheral::ADC_2, Channel::CH14, max_bits_adc12}, - {PA3, Peripheral::ADC_2, Channel::CH15, max_bits_adc12}, - {PA4, Peripheral::ADC_2, Channel::CH18, max_bits_adc12}, - {PA5, Peripheral::ADC_2, Channel::CH19, max_bits_adc12}, - {PA6, Peripheral::ADC_2, Channel::CH3, max_bits_adc12}, - {PA7, Peripheral::ADC_2, Channel::CH7, max_bits_adc12}, - {PB0, Peripheral::ADC_2, Channel::CH9, max_bits_adc12}, - {PB1, Peripheral::ADC_2, Channel::CH5, max_bits_adc12}, - {PC0, Peripheral::ADC_2, Channel::CH10, max_bits_adc12}, - {PC1, Peripheral::ADC_2, Channel::CH11, max_bits_adc12}, - {PC4, Peripheral::ADC_2, Channel::CH4, max_bits_adc12}, - {PC5, Peripheral::ADC_2, Channel::CH8, max_bits_adc12}, - {PF13, Peripheral::ADC_2, Channel::CH2, max_bits_adc12}, - {PF14, Peripheral::ADC_2, Channel::CH6, max_bits_adc12}, - - {PC0, Peripheral::ADC_3, Channel::CH10, max_bits_adc3}, - {PC1, Peripheral::ADC_3, Channel::CH11, max_bits_adc3}, - {PC2, Peripheral::ADC_3, Channel::CH0, max_bits_adc3}, - {PC3, Peripheral::ADC_3, Channel::CH1, max_bits_adc3}, - {PF3, Peripheral::ADC_3, Channel::CH5, max_bits_adc3}, - {PF4, Peripheral::ADC_3, Channel::CH9, max_bits_adc3}, - {PF5, Peripheral::ADC_3, Channel::CH4, max_bits_adc3}, - {PF6, Peripheral::ADC_3, Channel::CH8, max_bits_adc3}, - {PF7, Peripheral::ADC_3, Channel::CH3, max_bits_adc3}, - {PF8, Peripheral::ADC_3, Channel::CH7, max_bits_adc3}, - {PF9, Peripheral::ADC_3, Channel::CH2, max_bits_adc3}, - {PF10, Peripheral::ADC_3, Channel::CH6, max_bits_adc3}, - }}; - - static consteval uint8_t preference_score(Resolution r, Peripheral p) { - const uint8_t bits = resolution_bits(r); - if (bits > 12) { - switch (p) { - case Peripheral::ADC_1: - return 3; - case Peripheral::ADC_2: - return 2; - case Peripheral::ADC_3: - return 0; - case Peripheral::AUTO: - return 0; - } - } else { - switch (p) { - case Peripheral::ADC_3: - return 3; - case Peripheral::ADC_1: - return 2; - case Peripheral::ADC_2: - return 1; - case Peripheral::AUTO: - return 0; - } - } - return 0; - } - - static consteval std::pair resolve_mapping(const Entry& e) { - if (e.peripheral != Peripheral::AUTO && e.channel != Channel::AUTO) { - if (!is_valid_channel(e.channel)) { - compile_error("ADC: invalid channel"); - } - if (!internal_channel_allowed(e.peripheral, e.channel)) { - compile_error("ADC: internal channel must use dedicated ADC"); - } - if (!resolution_supported(e.peripheral, e.resolution)) { - compile_error("ADC: resolution not supported by selected ADC"); - } - return {e.peripheral, e.channel}; - } - - if (e.channel != Channel::AUTO && is_internal_channel(e.channel)) { - const Peripheral p = (e.peripheral == Peripheral::AUTO) -#if STLIB_HAS_ADC3 - ? Peripheral::ADC_3 -#else - ? Peripheral::ADC_2 -#endif - : e.peripheral; - - if (!internal_channel_allowed(p, e.channel)) { - compile_error("ADC: internal channel must use dedicated ADC"); - } - if (!resolution_supported(p, e.resolution)) { - compile_error("ADC: resolution not supported by selected ADC"); - } - return {p, e.channel}; - } - - bool found = false; - Peripheral best_peripheral = Peripheral::AUTO; - Channel best_channel = Channel::AUTO; - uint8_t best_score = 0; - - for (const auto& m : pin_map) { - if (m.pin != e.pin) { - continue; - } - if (e.peripheral != Peripheral::AUTO && m.peripheral != e.peripheral) { - continue; - } - if (e.channel != Channel::AUTO && m.channel != e.channel) { - continue; - } - if (resolution_bits(e.resolution) > m.max_bits) { - continue; - } - - const uint8_t score = preference_score(e.resolution, m.peripheral); - if (!found || score > best_score) { - found = true; - best_score = score; - best_peripheral = m.peripheral; - best_channel = m.channel; - } - } - - if (!found) { - compile_error("ADC: no valid ADC mapping for pin/resolution"); - } - - return {best_peripheral, best_channel}; - } - - template static consteval array build(span entries) { - static_assert(N <= max_instances, "ADCDomain: too many instances"); - if (entries.size() != N) { - compile_error("ADC: build entries size mismatch"); - } - - array cfgs{}; - array periph_seen{}; - array periph_resolution{}; - array periph_prescaler{}; - array periph_rate{}; - array periph_counts{}; - - for (std::size_t i = 0; i < N; ++i) { - const auto& e = entries[i]; - const auto [peripheral, channel] = resolve_mapping(e); - - if (!is_valid_channel(channel)) { - compile_error("ADC: invalid channel"); - } - if (!internal_channel_allowed(peripheral, channel)) { - compile_error("ADC: internal channel must use dedicated ADC"); - } - if (!resolution_supported(peripheral, e.resolution)) { - compile_error("ADC: resolution not supported by selected ADC"); - } - if (e.sample_rate_hz != 0) { - compile_error("ADC: sample_rate_hz is not supported in polling mode"); - } - - const auto pidx = peripheral_index(peripheral); - - if (!periph_seen[pidx]) { - periph_seen[pidx] = true; - periph_resolution[pidx] = e.resolution; - periph_prescaler[pidx] = e.prescaler; - periph_rate[pidx] = e.sample_rate_hz; - } else { - if (periph_resolution[pidx] != e.resolution) { - compile_error("ADC: resolution mismatch on same peripheral"); - } - if (periph_prescaler[pidx] != e.prescaler) { - compile_error("ADC: prescaler mismatch on same peripheral"); - } - if (periph_rate[pidx] != e.sample_rate_hz) { - compile_error("ADC: sample rate mismatch on same peripheral"); - } - } - - ++periph_counts[pidx]; - if (periph_counts[pidx] > max_channels_per_peripheral) { - compile_error("ADC: too many channels on same peripheral"); - } - - for (std::size_t j = 0; j < i; ++j) { - const auto& prev = entries[j]; - if (prev.gpio_idx == e.gpio_idx) { - compile_error("ADC: GPIO already used"); - } - const auto [prev_peripheral, prev_channel] = resolve_mapping(prev); - if (prev_peripheral == peripheral && prev_channel == channel) { - compile_error("ADC: duplicate channel on same peripheral"); - } - } - - cfgs[i] = { - .gpio_idx = e.gpio_idx, - .peripheral = peripheral, - .channel = channel, - .resolution = e.resolution, - .sample_time = e.sample_time, - .prescaler = e.prescaler, - .sample_rate_hz = e.sample_rate_hz, - .output = e.output, - }; - } - - return cfgs; - } - - struct Instance { - ADC_HandleTypeDef* handle = nullptr; - Channel channel = Channel::CH0; - SampleTime sample_time = SampleTime::CYCLES_8_5; - Resolution resolution = Resolution::BITS_12; - float* output = nullptr; - - static constexpr uint32_t max_raw_for_resolution(Resolution r) { - switch (r) { - case Resolution::BITS_16: - return 65535U; - case Resolution::BITS_14: - return 16383U; - case Resolution::BITS_12: - return 4095U; - case Resolution::BITS_10: - return 1023U; - case Resolution::BITS_8: - return 255U; - } - return 4095U; - } - - private: - uint32_t sample_raw(uint32_t timeout_ms = 2) { - if (handle == nullptr) { - return 0; - } - - const uint8_t pidx = peripheral_index_from_handle(handle); - const bool channel_change = active_channel[pidx] != channel; - - if (channel_change && peripheral_running[pidx]) { - (void)HAL_ADC_Stop(handle); - peripheral_running[pidx] = false; - } - - if (channel_change) { - ADC_ChannelConfTypeDef sConfig{}; - sConfig.Channel = static_cast(channel); - sConfig.Rank = ADC_REGULAR_RANK_1; - sConfig.SamplingTime = static_cast(sample_time); - sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.OffsetNumber = ADC_OFFSET_NONE; - sConfig.Offset = 0; -#if defined(ADC_VER_V5_V90) - sConfig.OffsetSignedSaturation = DISABLE; -#endif - - if (HAL_ADC_ConfigChannel(handle, &sConfig) != HAL_OK) { - return 0; - } - active_channel[pidx] = channel; - } - - if (!peripheral_running[pidx]) { - const HAL_StatusTypeDef start_status = HAL_ADC_Start(handle); - if (start_status != HAL_OK && start_status != HAL_BUSY) { - return 0; - } - peripheral_running[pidx] = true; - } - - if (HAL_ADC_PollForConversion(handle, timeout_ms) != HAL_OK) { - // Recover from a stalled peripheral (timeout/error) by re-arming once. - (void)HAL_ADC_Stop(handle); - peripheral_running[pidx] = false; - - const HAL_StatusTypeDef restart_status = HAL_ADC_Start(handle); - if (restart_status != HAL_OK && restart_status != HAL_BUSY) { - return 0; - } - peripheral_running[pidx] = true; - - if (HAL_ADC_PollForConversion(handle, timeout_ms) != HAL_OK) { - return 0; - } - } - return HAL_ADC_GetValue(handle); - } - - public: - float get_raw(uint32_t timeout_ms = 2) { - return static_cast(sample_raw(timeout_ms)); - } - - float get_value_from_raw(float raw, float vref = 3.3f) const { - const float max_val = static_cast(max_raw_for_resolution(resolution)); - if (max_val <= 0.0f) { - return 0.0f; - } - return (raw / max_val) * vref; - } - - float get_value(uint32_t timeout_ms = 2, float vref = 3.3f) { - const float raw = get_raw(timeout_ms); - return get_value_from_raw(raw, vref); - } - - void read(float vref = 3.3f, uint32_t timeout_ms = 2) { - if (output == nullptr) { - return; - } - *output = get_value(timeout_ms, vref); - } - }; - - template struct Init { - static inline std::array instances{}; - - static ADC_HandleTypeDef* handle_for(Peripheral p) { - switch (p) { - case Peripheral::ADC_1: - return &hadc1; - case Peripheral::ADC_2: - return &hadc2; - case Peripheral::ADC_3: - return &hadc3; - case Peripheral::AUTO: - break; - } - return &hadc1; - } - - static void configure_peripheral(const Config& cfg) { - ADC_HandleTypeDef* hadc = handle_for(cfg.peripheral); - - if (cfg.peripheral == Peripheral::ADC_1 || cfg.peripheral == Peripheral::ADC_2) { - __HAL_RCC_ADC12_CLK_ENABLE(); - hadc->Instance = (cfg.peripheral == Peripheral::ADC_1) ? ADC1 : ADC2; - } else { - __HAL_RCC_ADC3_CLK_ENABLE(); - hadc->Instance = ADC3; - HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC2, SYSCFG_SWITCH_PC2_OPEN); - HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC3, SYSCFG_SWITCH_PC3_OPEN); - } - - hadc->Init.ClockPrescaler = static_cast(cfg.prescaler); - hadc->Init.Resolution = static_cast(cfg.resolution); - hadc->Init.ScanConvMode = ADC_SCAN_DISABLE; - hadc->Init.EOCSelection = ADC_EOC_SINGLE_CONV; - hadc->Init.LowPowerAutoWait = DISABLE; - hadc->Init.ContinuousConvMode = ENABLE; - hadc->Init.NbrOfConversion = 1; - hadc->Init.DiscontinuousConvMode = DISABLE; - hadc->Init.NbrOfDiscConversion = 0; - hadc->Init.ExternalTrigConv = ADC_SOFTWARE_START; - hadc->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; - hadc->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; -#if defined(ADC_VER_V5_V90) - hadc->Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL; - hadc->Init.DMAContinuousRequests = DISABLE; -#endif - hadc->Init.Overrun = ADC_OVR_DATA_PRESERVED; - hadc->Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; - hadc->Init.OversamplingMode = DISABLE; - } - - static void init( - std::span cfgs, - std::span gpio_instances = std::span{} - ) { - bool periph_configured[3] = {false, false, false}; - (void)gpio_instances; - - for (std::size_t i = 0; i < N; ++i) { - const auto& cfg = cfgs[i]; - if (cfg.peripheral == Peripheral::AUTO || cfg.channel == Channel::AUTO) { - ErrorHandler("ADC config unresolved (AUTO)"); - } - const auto pidx = peripheral_index(cfg.peripheral); - if (!periph_configured[pidx]) { - configure_peripheral(cfg); - ADC_HandleTypeDef* hadc = handle_for(cfg.peripheral); - hadc->Init.NbrOfConversion = 1; - hadc->Init.ScanConvMode = ADC_SCAN_DISABLE; - hadc->Init.ContinuousConvMode = ENABLE; - if (HAL_ADC_Init(hadc) != HAL_OK) { - ErrorHandler("ADC Init failed"); - } - - periph_configured[pidx] = true; - } - - instances[i].handle = handle_for(cfg.peripheral); - instances[i].channel = cfg.channel; - instances[i].sample_time = cfg.sample_time; - instances[i].resolution = cfg.resolution; - instances[i].output = cfg.output; - } - } - }; -}; - -#else // HAL_ADC_MODULE_ENABLED - -struct ADCDomain { - static constexpr std::size_t max_instances{0}; - struct Entry {}; - struct Config {}; - template static consteval array build(span) { return {}; } - template struct Init { - static void init( - std::span, - std::span = std::span{} - ) {} - }; -}; - -#endif // HAL_ADC_MODULE_ENABLED - -} // namespace ST_LIB diff --git a/Inc/MockedDrivers/mocked_hal_adc.hpp b/Inc/MockedDrivers/mocked_hal_adc.hpp index cb3dc2f3b..007b79cee 100644 --- a/Inc/MockedDrivers/mocked_hal_adc.hpp +++ b/Inc/MockedDrivers/mocked_hal_adc.hpp @@ -2,18 +2,50 @@ #include "hal_wrapper.h" -#include +#include namespace ST_LIB::MockedHAL { +enum class ADCOperation : uint8_t { + Init = 0, + ConfigChannel, + Start, + PollForConversion, + GetValue, + Stop, + StartDMA, + StopDMA, +}; + +using ADCSignalGenerator = std::function; + void adc_reset(); void adc_set_channel_raw(ADC_TypeDef* adc, uint32_t channel, uint32_t raw_value); +void adc_set_channel_generator(ADC_TypeDef* adc, uint32_t channel, ADCSignalGenerator generator); void adc_set_poll_timeout(ADC_TypeDef* adc, bool enabled); +void adc_enable_timed_dma(ADC_TypeDef* adc, bool enabled); +void adc_set_kernel_clock_hz(ADC_TypeDef* adc, uint64_t kernel_clock_hz); +void adc_advance_time_ns(uint64_t delta_ns); +uint64_t adc_get_time_ns(); uint32_t adc_get_last_channel(ADC_TypeDef* adc); bool adc_is_running(ADC_TypeDef* adc); +bool adc_is_dma_running(ADC_TypeDef* adc); + +std::size_t adc_get_call_count(ADCOperation op); + +uint32_t adc_get_dma_length(ADC_TypeDef* adc); + +uint32_t adc_get_dma_value(ADC_TypeDef* adc, std::size_t index); +uint32_t adc_get_rank_channel(ADC_TypeDef* adc, std::size_t rank_index); +uint32_t adc_get_rank_count(ADC_TypeDef* adc); +uint64_t adc_get_sequence_period_ns(ADC_TypeDef* adc); +uint64_t adc_get_pending_dma_completion_time_ns(ADC_TypeDef* adc); +uint64_t adc_get_completed_sequence_count(ADC_TypeDef* adc); +uint64_t adc_get_overrun_count(ADC_TypeDef* adc); + } // namespace ST_LIB::MockedHAL diff --git a/Inc/MockedDrivers/mocked_hal_dma.hpp b/Inc/MockedDrivers/mocked_hal_dma.hpp index 4d06719af..760964744 100644 --- a/Inc/MockedDrivers/mocked_hal_dma.hpp +++ b/Inc/MockedDrivers/mocked_hal_dma.hpp @@ -2,19 +2,19 @@ #include "hal_wrapper.h" -#include - namespace ST_LIB::MockedHAL { enum class DMAOperation : uint8_t { Init = 0, StartIT, IRQHandler, + ScheduleTransfer, }; void dma_reset(); void dma_set_init_status(HAL_StatusTypeDef status); void dma_set_start_status(HAL_StatusTypeDef status); +void dma_set_transfer_timing(uint64_t setup_time_ns, uint64_t per_byte_time_ns); std::size_t dma_get_call_count(DMAOperation op); DMA_HandleTypeDef* dma_get_last_init_handle(); @@ -23,5 +23,10 @@ DMA_HandleTypeDef* dma_get_last_irq_handle(); uint32_t dma_get_last_start_src(); uint32_t dma_get_last_start_dst(); uint32_t dma_get_last_start_length(); +DMA_HandleTypeDef* dma_get_last_scheduled_handle(); +uint64_t dma_get_last_schedule_request_time_ns(); +uint64_t dma_get_last_schedule_completion_time_ns(); +uint32_t dma_get_last_schedule_bytes(); +uint64_t dma_schedule_transfer(DMA_HandleTypeDef* hdma, uint64_t request_time_ns, uint32_t bytes); } // namespace ST_LIB::MockedHAL diff --git a/Inc/MockedDrivers/stm32h7xx_hal_mock.h b/Inc/MockedDrivers/stm32h7xx_hal_mock.h index 8ae94e74f..5efcd616c 100644 --- a/Inc/MockedDrivers/stm32h7xx_hal_mock.h +++ b/Inc/MockedDrivers/stm32h7xx_hal_mock.h @@ -244,6 +244,8 @@ typedef enum { typedef enum { HAL_UNLOCKED = 0x00U, HAL_LOCKED = 0x01U } HAL_LockTypeDef; +typedef struct __DMA_HandleTypeDef DMA_HandleTypeDef; + typedef struct { uint32_t ClockPrescaler; uint32_t Resolution; @@ -267,6 +269,7 @@ typedef struct { typedef struct __ADC_HandleTypeDef { ADC_TypeDef* Instance; ADC_InitTypeDef Init; + DMA_HandleTypeDef* DMA_Handle; HAL_LockTypeDef Lock; volatile uint32_t State; volatile uint32_t ErrorCode; @@ -336,14 +339,32 @@ typedef struct { #define ADC_CHANNEL_VBAT 32U #define ADC_SCAN_DISABLE 0U +#define ADC_SCAN_ENABLE 1U #define ADC_EOC_SINGLE_CONV 0U +#define ADC_EOC_SEQ_CONV 1U #define ADC_SOFTWARE_START 0U #define ADC_EXTERNALTRIGCONVEDGE_NONE 0U #define ADC_CONVERSIONDATA_DR 0U +#define ADC_CONVERSIONDATA_DMA_CIRCULAR 1U #define ADC_SAMPLING_MODE_NORMAL 0U #define ADC_OVR_DATA_PRESERVED 0U #define ADC_LEFTBITSHIFT_NONE 0U -#define ADC_REGULAR_RANK_1 1U +#define ADC_REGULAR_RANK_1 0x100U +#define ADC_REGULAR_RANK_2 0x101U +#define ADC_REGULAR_RANK_3 0x102U +#define ADC_REGULAR_RANK_4 0x103U +#define ADC_REGULAR_RANK_5 0x104U +#define ADC_REGULAR_RANK_6 0x105U +#define ADC_REGULAR_RANK_7 0x106U +#define ADC_REGULAR_RANK_8 0x107U +#define ADC_REGULAR_RANK_9 0x108U +#define ADC_REGULAR_RANK_10 0x109U +#define ADC_REGULAR_RANK_11 0x10AU +#define ADC_REGULAR_RANK_12 0x10BU +#define ADC_REGULAR_RANK_13 0x10CU +#define ADC_REGULAR_RANK_14 0x10DU +#define ADC_REGULAR_RANK_15 0x10EU +#define ADC_REGULAR_RANK_16 0x10FU #define ADC_SINGLE_ENDED 0U #define ADC_OFFSET_NONE 0U @@ -369,11 +390,11 @@ typedef struct { uint32_t PeriphBurst; } DMA_InitTypeDef; -typedef struct __DMA_HandleTypeDef { +struct __DMA_HandleTypeDef { DMA_Stream_TypeDef* Instance; DMA_InitTypeDef Init; void* Parent; -} DMA_HandleTypeDef; +}; #define DMA_REQUEST_MEM2MEM 0x000U #define DMA_REQUEST_ADC1 0x001U @@ -800,9 +821,11 @@ HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_DeInit(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig); HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc); +HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length); HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout); uint32_t HAL_ADC_GetValue(const ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc); +HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc); uint32_t HAL_ADC_GetState(const ADC_HandleTypeDef* hadc); uint32_t HAL_ADC_GetError(const ADC_HandleTypeDef* hadc); diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 8f1f01ae1..92eb71743 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -28,6 +28,8 @@ class STLIB { namespace ST_LIB { extern void compile_error(const char* msg); + +// The contract of BuildCtx/Board is documented in docs/st-lib-board-contract.md. template struct BuildCtx { template using Decl = typename D::Entry; template static constexpr std::size_t max_count_v = D::max_instances; @@ -86,7 +88,7 @@ using DomainsCtx = BuildCtx< MPUDomain, GPIODomain, TimerDomain, - DMA_Domain, + DMADomain, SPIDomain, DigitalOutputDomain, DigitalInputDomain, @@ -94,10 +96,47 @@ using DomainsCtx = BuildCtx< SdDomain, EthernetDomain, ADCDomain, - EXTIDomain, - DFSDM_CHANNEL_DOMAIN, - DFSDM_CLK_DOMAIN - /* PWMDomain, ...*/>; + EXTIDomain /* PWMDomain, ...*/>; + +namespace BuildUtils { + +template +consteval std::array merge_dma_entries( + std::span base_entries, + const std::array&... extra_entries +) { + if (base_entries.size() + (ExtraNs + ...) != TotalN) { + compile_error("DMA merged entry count mismatch"); + } + + std::array merged{}; + std::size_t cursor = 0; + + for (const auto& entry : base_entries) { + merged[cursor++] = entry; + } + + auto append = [&](const std::array& entries) { + for (const auto& entry : entries) { + merged[cursor++] = entry; + } + }; + (append(extra_entries), ...); + + return merged; +} + +template +consteval std::array build_dma_configs( + std::span base_entries, + const std::array&... extra_entries +) { + return DMADomain::template build(std::span{ + merge_dma_entries(base_entries, extra_entries...) + }); +} + +} // namespace BuildUtils template struct Board { static consteval auto build_ctx() { @@ -112,27 +151,36 @@ template struct Board { return ctx.template span().size(); } - static consteval auto build() { - constexpr std::size_t gpioN = domain_size(); - constexpr std::size_t timN = domain_size(); - constexpr std::size_t doutN = domain_size(); - constexpr std::size_t dinN = domain_size(); - constexpr std::size_t mpuN = domain_size(); - constexpr std::size_t dmaN = domain_size(); - constexpr std::size_t spiN = domain_size(); - constexpr std::size_t mdmaPacketN = domain_size(); - constexpr std::size_t sdN = domain_size(); - constexpr std::size_t ethN = domain_size(); - constexpr std::size_t adcN = domain_size(); - constexpr std::size_t extiN = domain_size(); - constexpr std::size_t dfsdmN = domain_size(); - constexpr std::size_t dfsdm_clkN = domain_size(); - // ... + static consteval auto build() { + constexpr std::size_t mpuN = domain_size(); + constexpr std::size_t gpioN = domain_size(); + constexpr std::size_t timN = domain_size(); + constexpr std::size_t adcN = domain_size(); + constexpr auto adc_cfgs = ADCDomain::template build(ctx.template span()); + constexpr std::size_t adc_dma_extraN = ADCDomain::dma_contribution_count( + std::span{adc_cfgs}, + ctx.template span() + ); + constexpr auto adc_dma_entries = + ADCDomain::template build_dma_contributions( + ctx.template span(), + std::span{adc_cfgs} + ); + constexpr std::size_t spiN = domain_size(); + constexpr std::size_t doutN = domain_size(); + constexpr std::size_t dinN = domain_size(); + constexpr std::size_t mdmaPacketN = domain_size(); + constexpr std::size_t sdN = domain_size(); + constexpr std::size_t ethN = domain_size(); + constexpr std::size_t dmaN = domain_size() + adc_dma_extraN; + constexpr std::size_t extiN = domain_size(); + // ... + struct ConfigBundle { std::array mpu_cfgs; std::array gpio_cfgs; std::array tim_cfgs; - std::array dma_cfgs; + std::array dma_cfgs; std::array spi_cfgs; std::array dout_cfgs; std::array din_cfgs; @@ -146,51 +194,30 @@ template struct Board { // ... }; - return ConfigBundle{ - .mpu_cfgs = - MPUDomain::template build( - ctx.template span()), - .gpio_cfgs = - GPIODomain::template build( - ctx.template span()), - .tim_cfgs = - TimerDomain::template build( - ctx.template span()), - .dma_cfgs = - DMA_Domain::template build( - ctx.template span()), - .spi_cfgs = - SPIDomain::template build( - ctx.template span()), - .dout_cfgs = - DigitalOutputDomain::template build( - ctx.template span()), - .din_cfgs = - DigitalInputDomain::template build( - ctx.template span()), - .mdma_packet_cfgs = - MdmaPacketDomain::template build( - ctx.template span()), - .sd_cfgs = - SdDomain::template build( - ctx.template span()), - .eth_cfgs = - EthernetDomain::template build( - ctx.template span()), - .adc_cfgs = - ADCDomain::template build( - ctx.template span()), - .exti_cfgs = - EXTIDomain::template build( - ctx.template span()), - .dfsdm_cfgs = - DFSDM_CHANNEL_DOMAIN::template build( - ctx.template span()), - .dfsdm_clk_cfgs = - DFSDM_CLK_DOMAIN::template build( - ctx.template span()) - }; - } + return ConfigBundle{ + .mpu_cfgs = MPUDomain::template build(ctx.template span()), + .gpio_cfgs = GPIODomain::template build(ctx.template span()), + .tim_cfgs = TimerDomain::template build(ctx.template span()), + .dma_cfgs = BuildUtils::build_dma_configs( + ctx.template span(), + adc_dma_entries + ), + .spi_cfgs = SPIDomain::template build(ctx.template span()), + .dout_cfgs = + DigitalOutputDomain::template build(ctx.template span() + ), + .din_cfgs = + DigitalInputDomain::template build(ctx.template span()), + .mdma_packet_cfgs = + MdmaPacketDomain::template build(ctx.template span() + ), + .sd_cfgs = SdDomain::template build(ctx.template span()), + .eth_cfgs = EthernetDomain::template build(ctx.template span()), + .adc_cfgs = adc_cfgs, + .exti_cfgs = EXTIDomain::template build(ctx.template span()), + // ... + }; + } static constexpr auto cfg = build(); @@ -198,7 +225,7 @@ template struct Board { constexpr std::size_t mpuN = domain_size(); constexpr std::size_t gpioN = domain_size(); constexpr std::size_t timN = domain_size(); - constexpr std::size_t dmaN = domain_size(); + constexpr std::size_t dmaN = std::tuple_size_v; constexpr std::size_t spiN = domain_size(); constexpr std::size_t doutN = domain_size(); constexpr std::size_t dinN = domain_size(); @@ -225,11 +252,11 @@ template struct Board { MPUDomain::Init::init(); GPIODomain::Init::init(cfg.gpio_cfgs); TimerDomain::Init::init(cfg.tim_cfgs); - DMA_Domain::Init::init(cfg.dma_cfgs); + DMADomain::Init::init(cfg.dma_cfgs); SPIDomain::Init::init( cfg.spi_cfgs, GPIODomain::Init::instances, - DMA_Domain::Init::instances + DMADomain::Init::instances ); DigitalOutputDomain::Init::init(cfg.dout_cfgs, GPIODomain::Init::instances); DigitalInputDomain::Init::init(cfg.din_cfgs, GPIODomain::Init::instances); @@ -243,11 +270,13 @@ template struct Board { DigitalInputDomain::Init::instances ); EthernetDomain::Init::init(cfg.eth_cfgs, DigitalOutputDomain::Init::instances); - ADCDomain::Init::init(cfg.adc_cfgs, GPIODomain::Init::instances); - EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); - DFSDM_CHANNEL_DOMAIN::Init::init(GPIODomain::Init::instances,DMA_Domain::Init::instances); - DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); - // ... + ADCDomain::Init::init( + cfg.adc_cfgs, + GPIODomain::Init::instances, + DMADomain::Init::instances + ); + EXTIDomain::Init::init(cfg.exti_cfgs, + GPIODomain::Init::instances); // ... } template @@ -272,8 +301,8 @@ template struct Board { if constexpr (std::is_same_v) { return Domain::template Init::instances[idx]; - }else if constexpr(std::is_same_v){ - return Domain::template Init::instances[idx]; + } else if constexpr (std::is_same_v) { + return Domain::template Init::instances[idx]; } else { return Domain::template Init::instances[idx]; } diff --git a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp index 1ec225bbd..6a285cdf1 100644 --- a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp +++ b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp @@ -18,7 +18,6 @@ // #include "Sensors/Sensor/Sensor.hpp" // To be implemented when InputCapture is done #include "Sensors/DigitalSensor/DigitalSensor.hpp" #include "Sensors/SensorInterrupt/SensorInterrupt.hpp" -#include "Sensors/LinearSensor/LinearSensor.hpp" #include "Sensors/LookupSensor/LookupSensor.hpp" #include "Sensors/EncoderSensor/NewEncoderSensor.hpp" // #include "Sensors/PWMSensor/PWMSensor.hpp" // To be implemented when InputCapture is done diff --git a/Inc/ST-LIB_LOW/Sensors/Common/ADCSensor.hpp b/Inc/ST-LIB_LOW/Sensors/Common/ADCSensor.hpp new file mode 100644 index 000000000..d2d1a9687 --- /dev/null +++ b/Inc/ST-LIB_LOW/Sensors/Common/ADCSensor.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include "HALAL/Services/ADC/ADC.hpp" + +namespace ST_LIB::Sensors { + +inline constexpr float kDefaultADCReferenceVoltage = 3.3f; + +template class ADCValueSensor { +protected: + using ADCInstance = ST_LIB::ADCDomain::Instance; + + constexpr ADCValueSensor() = default; + constexpr ADCValueSensor(ADCInstance& adc, ValueType* value) : adc(&adc), value(value) {} + + [[nodiscard]] bool is_configured() const { return adc != nullptr && value != nullptr; } + + [[nodiscard]] float read_raw() const { return (adc == nullptr) ? 0.0f : adc->get_raw(); } + + [[nodiscard]] float read_voltage(float vref = kDefaultADCReferenceVoltage) const { + if (adc == nullptr) { + return 0.0f; + } + return adc->get_value_from_raw(read_raw(), vref); + } + + [[nodiscard]] float normalized_raw() const { + if (adc == nullptr) { + return 0.0f; + } + + const float max_raw = + static_cast(ADCInstance::max_raw_for_resolution(adc->resolution)); + if (max_raw <= 0.0f) { + return 0.0f; + } + + return read_raw() / max_raw; + } + + [[nodiscard]] std::size_t bucket_index(std::size_t bucket_count) const { + if (bucket_count == 0U) { + return 0U; + } + + const auto index = static_cast(normalized_raw() * bucket_count); + return std::min(index, bucket_count - 1U); + } + + [[nodiscard]] std::size_t scaled_index(std::size_t max_index) const { + const auto index = static_cast(normalized_raw() * max_index); + return std::min(index, max_index); + } + + ADCInstance* adc = nullptr; + ValueType* value = nullptr; +}; + +} // namespace ST_LIB::Sensors diff --git a/Inc/ST-LIB_LOW/Sensors/Common/PT100.hpp b/Inc/ST-LIB_LOW/Sensors/Common/PT100.hpp index 252629dc5..436e5eefd 100644 --- a/Inc/ST-LIB_LOW/Sensors/Common/PT100.hpp +++ b/Inc/ST-LIB_LOW/Sensors/Common/PT100.hpp @@ -1,14 +1,14 @@ #pragma once #include -#include #include "Control/Blocks/MovingAverage.hpp" -#include "HALAL/Services/ADC/NewADC.hpp" +#include "Sensors/Common/ADCSensor.hpp" -template class PT100 { +template class PT100 : protected ST_LIB::Sensors::ADCValueSensor { public: static constexpr float k = 841.836735; static constexpr float offset = -492.204082; + static constexpr float reference_voltage = ST_LIB::Sensors::kDefaultADCReferenceVoltage; MovingAverage* filter = nullptr; PT100(ST_LIB::ADCDomain::Instance& adc, float* value, MovingAverage& filter); @@ -20,34 +20,34 @@ template class PT100 { void read(); protected: - ST_LIB::ADCDomain::Instance* adc = nullptr; - float* value = nullptr; + using Base = ST_LIB::Sensors::ADCValueSensor; }; template PT100::PT100(ST_LIB::ADCDomain::Instance& adc, float* value, MovingAverage& filter) - : adc(&adc), value(value), filter(&filter) {} + : Base(adc, value), filter(&filter) {} template PT100::PT100(ST_LIB::ADCDomain::Instance& adc, float& value, MovingAverage& filter) - : adc(&adc), value(&value), filter(&filter) {} + : Base(adc, &value), filter(&filter) {} template -PT100::PT100(ST_LIB::ADCDomain::Instance& adc, float* value) : adc(&adc), value(value) {} +PT100::PT100(ST_LIB::ADCDomain::Instance& adc, float* value) : Base(adc, value) {} template -PT100::PT100(ST_LIB::ADCDomain::Instance& adc, float& value) : adc(&adc), value(&value) {} +PT100::PT100(ST_LIB::ADCDomain::Instance& adc, float& value) : Base(adc, &value) {} template void PT100::read() { - if (adc == nullptr || value == nullptr) { + if (!this->is_configured()) { return; } - const float raw = adc->get_raw(); - const float val = adc->get_value_from_raw(raw, 3.3f); + + const float val = this->read_voltage(reference_voltage); if (filter != nullptr) { filter->input(k / val + offset); filter->execute(); - *value = filter->output_value; - } else - *value = k / val + offset; + *this->value = filter->output_value; + } else { + *this->value = k / val + offset; + } } diff --git a/Inc/ST-LIB_LOW/Sensors/LinearSensor/FilteredLinearSensor.hpp b/Inc/ST-LIB_LOW/Sensors/LinearSensor/FilteredLinearSensor.hpp index 4227cf2f7..d6374cc8d 100644 --- a/Inc/ST-LIB_LOW/Sensors/LinearSensor/FilteredLinearSensor.hpp +++ b/Inc/ST-LIB_LOW/Sensors/LinearSensor/FilteredLinearSensor.hpp @@ -26,12 +26,12 @@ template class FilteredLinearSensor : public LinearSensor : LinearSensor(adc, slope, offset, value), filter(filter) {} void read() { - if (this->adc == nullptr || this->value == nullptr) { + if (!this->is_configured()) { return; } - const float raw = this->adc->get_raw(); - const float val = this->adc->get_value_from_raw(raw, this->vref); - *this->value = filter.compute(this->slope * static_cast(val) + this->offset); + + *this->value = + filter.compute(this->compute_value_from_voltage(this->read_voltage(this->vref))); } }; diff --git a/Inc/ST-LIB_LOW/Sensors/LinearSensor/LinearSensor.hpp b/Inc/ST-LIB_LOW/Sensors/LinearSensor/LinearSensor.hpp index c0fbe8a4a..3d71e7b8f 100644 --- a/Inc/ST-LIB_LOW/Sensors/LinearSensor/LinearSensor.hpp +++ b/Inc/ST-LIB_LOW/Sensors/LinearSensor/LinearSensor.hpp @@ -6,14 +6,13 @@ */ #pragma once -#include #include -#include "HALAL/Services/ADC/NewADC.hpp" +#include "Sensors/Common/ADCSensor.hpp" template requires std::is_integral_v || std::is_floating_point_v -class LinearSensor { +class LinearSensor : protected ST_LIB::Sensors::ADCValueSensor { public: LinearSensor() = default; LinearSensor( @@ -34,19 +33,21 @@ class LinearSensor { void read(); void set_offset(Type new_offset); - Type get_offset(); + [[nodiscard]] Type get_offset() const; void set_gain(Type new_gain); - Type get_gain(); + [[nodiscard]] Type get_gain() const; - Type* get_value_pointer() const; + [[nodiscard]] Type* get_value_pointer() const; protected: - ST_LIB::ADCDomain::Instance* adc = nullptr; - Type slope; - Type offset; - Type* value = nullptr; - float vref = 3.3f; + using Base = ST_LIB::Sensors::ADCValueSensor; + + [[nodiscard]] Type compute_value_from_voltage(float voltage) const; + + Type slope{}; + Type offset{}; + float vref = ST_LIB::Sensors::kDefaultADCReferenceVoltage; }; template @@ -58,7 +59,7 @@ LinearSensor::LinearSensor( Type* value, float vref ) - : adc(&adc), slope(slope), offset(offset), value(value), vref(vref) {} + : Base(adc, value), slope(slope), offset(offset), vref(vref) {} template requires std::is_integral_v || std::is_floating_point_v @@ -69,29 +70,34 @@ LinearSensor::LinearSensor( Type& value, float vref ) - : LinearSensor::LinearSensor(adc, slope, offset, &value, vref) {} + : LinearSensor(adc, slope, offset, &value, vref) {} template requires std::is_integral_v || std::is_floating_point_v void LinearSensor::read() { - if (adc == nullptr || value == nullptr) { + if (!this->is_configured()) { return; } - const float raw = adc->get_raw(); - const float val = adc->get_value_from_raw(raw, vref); - *value = slope * (Type)val + offset; + *this->value = compute_value_from_voltage(this->read_voltage(vref)); +} + +template + requires std::is_integral_v || std::is_floating_point_v +Type LinearSensor::compute_value_from_voltage(float voltage) const { + const Type sensor_voltage = static_cast(voltage); + return slope * sensor_voltage + offset; } template requires std::is_integral_v || std::is_floating_point_v -Type LinearSensor::get_offset() { +Type LinearSensor::get_offset() const { return offset; } template requires std::is_integral_v || std::is_floating_point_v -Type LinearSensor::get_gain() { +Type LinearSensor::get_gain() const { return slope; } @@ -110,5 +116,5 @@ void LinearSensor::set_gain(Type new_gain) { template requires std::is_integral_v || std::is_floating_point_v Type* LinearSensor::get_value_pointer() const { - return value; + return this->value; } diff --git a/Inc/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.hpp b/Inc/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.hpp index 7422c462a..b3a0bfe0d 100644 --- a/Inc/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.hpp +++ b/Inc/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.hpp @@ -6,23 +6,47 @@ */ #pragma once -#include -#include "HALAL/Services/ADC/NewADC.hpp" -#include "ErrorHandler/ErrorHandler.hpp" -#include "C++Utilities/CppUtils.hpp" +#include +#include -#define REFERENCE_VOLTAGE 3.3 +#include "Sensors/Common/ADCSensor.hpp" -class LookupSensor { +class LookupSensor : protected ST_LIB::Sensors::ADCValueSensor { public: LookupSensor() = default; - LookupSensor(ST_LIB::ADCDomain::Instance& adc, double* table, int table_size, double* value); - LookupSensor(ST_LIB::ADCDomain::Instance& adc, double* table, int table_size, double& value); - void read(); + LookupSensor(ST_LIB::ADCDomain::Instance& adc, std::span table, double* value) + : Base(adc, value), table(table) {} + LookupSensor(ST_LIB::ADCDomain::Instance& adc, std::span table, double& value) + : LookupSensor(adc, table, &value) {} + LookupSensor( + ST_LIB::ADCDomain::Instance& adc, + const double* table, + std::size_t table_size, + double* value + ) + : LookupSensor( + adc, + (table == nullptr) ? std::span{} + : std::span(table, table_size), + value + ) {} + LookupSensor( + ST_LIB::ADCDomain::Instance& adc, + const double* table, + std::size_t table_size, + double& value + ) + : LookupSensor(adc, table, table_size, &value) {} + void read() { + if (!this->is_configured() || table.empty()) { + return; + } + + *this->value = table[this->bucket_index(table.size())]; + } protected: - ST_LIB::ADCDomain::Instance* adc = nullptr; - double* table = nullptr; - int table_size = 0; - double* value = nullptr; + using Base = ST_LIB::Sensors::ADCValueSensor; + + std::span table{}; }; diff --git a/Inc/ST-LIB_LOW/Sensors/NTC/NTC.hpp b/Inc/ST-LIB_LOW/Sensors/NTC/NTC.hpp index 8de9475c5..933fc3434 100644 --- a/Inc/ST-LIB_LOW/Sensors/NTC/NTC.hpp +++ b/Inc/ST-LIB_LOW/Sensors/NTC/NTC.hpp @@ -9,16 +9,21 @@ This NTC class is not generic. It is only for 10k Ohm, 1976Beta value NTCs. */ #pragma once -#include -#include "HALAL/Services/ADC/NewADC.hpp" +#include "Sensors/Common/ADCSensor.hpp" -class NTC { +class NTC : protected ST_LIB::Sensors::ADCValueSensor { public: NTC() = default; - NTC(ST_LIB::ADCDomain::Instance& adc, float* src); - NTC(ST_LIB::ADCDomain::Instance& adc, float& src); - void read(); + NTC(ST_LIB::ADCDomain::Instance& adc, float* src) : Base(adc, src) {} + NTC(ST_LIB::ADCDomain::Instance& adc, float& src) : Base(adc, &src) {} + void read() { + if (!this->is_configured()) { + return; + } + + *this->value = static_cast(NTC_table[this->scaled_index(table_max_index)]) * 0.1f; + } private: static constexpr int NTC_table[4096] = { @@ -297,6 +302,7 @@ class NTC { -625, -632, -640, -648, -657, -666, -677, -688, -700, -714, -729, -748, -770, -797, -835, -873 }; - float* value = nullptr; - ST_LIB::ADCDomain::Instance* adc = nullptr; + using Base = ST_LIB::Sensors::ADCValueSensor; + + static constexpr std::size_t table_max_index = 4095U; }; diff --git a/STM32H723ZGTX_FLASH.ld b/STM32H723ZGTX_FLASH.ld index 591a9946f..73c7f2126 100644 --- a/STM32H723ZGTX_FLASH.ld +++ b/STM32H723ZGTX_FLASH.ld @@ -146,7 +146,7 @@ SECTIONS } >FLASH /* MPU D1 Non-Cached Section: Placed at start of RAM_D1 for alignment */ - .mpu_ram_d1_nc : + .mpu_ram_d1_nc (NOLOAD) : { . = ALIGN(32); __mpu_d1_nc_start = ABSOLUTE(.); @@ -239,7 +239,7 @@ SECTIONS } >DTCMRAM /* MPU D2 Non-Cached Section: Contains Ethernet Descriptors and Generic Buffers */ - .mpu_ram_d2_nc : + .mpu_ram_d2_nc (NOLOAD) : { . = ALIGN(32); __mpu_d2_nc_start = ABSOLUTE(.); @@ -266,7 +266,7 @@ SECTIONS __mpu_d2_nc_end = ABSOLUTE(.); /* MPU D3 Non-Cached Section */ - .mpu_ram_d3_nc : + .mpu_ram_d3_nc (NOLOAD) : { . = ALIGN(32); __mpu_d3_nc_start = ABSOLUTE(.); @@ -315,7 +315,7 @@ SECTIONS .ARM.attributes 0 : { *(.ARM.attributes) } /* MPU D1 Cached Section */ - .ram_d1 : + .ram_d1 (NOLOAD) : { . = ALIGN(32); @@ -327,7 +327,7 @@ SECTIONS } >RAM_D1 /* MPU D2 Cached Section */ - .ram_d2 : + .ram_d2 (NOLOAD) : { . = ALIGN(32); @@ -339,7 +339,7 @@ SECTIONS } >RAM_D2 /* MPU D3 Cached Section */ - .ram_d3 : + .ram_d3 (NOLOAD) : { . = ALIGN(32); diff --git a/STM32H723ZGTX_RAM.ld b/STM32H723ZGTX_RAM.ld index 4b6dccc97..9cc0683a4 100644 --- a/STM32H723ZGTX_RAM.ld +++ b/STM32H723ZGTX_RAM.ld @@ -142,7 +142,7 @@ SECTIONS } >RAM_D1 /* MPU D1 Non-Cached Section */ - .mpu_ram_d1_nc : + .mpu_ram_d1_nc (NOLOAD) : { . = ALIGN(32); __mpu_d1_nc_start = ABSOLUTE(.); @@ -211,7 +211,7 @@ SECTIONS } >DTCMRAM /* MPU D2 Non-Cached Section */ - .mpu_ram_d2_nc : + .mpu_ram_d2_nc (NOLOAD) : { . = ALIGN(32); __mpu_d2_nc_start = ABSOLUTE(.); @@ -237,7 +237,7 @@ SECTIONS __mpu_d2_nc_end = ABSOLUTE(.); /* MPU D3 Non-Cached Section */ - .mpu_ram_d3_nc : + .mpu_ram_d3_nc (NOLOAD) : { . = ALIGN(32); __mpu_d3_nc_start = ABSOLUTE(.); @@ -273,7 +273,7 @@ SECTIONS } >ITCMRAM /* MPU D1 Cached Section */ - .ram_d1 : + .ram_d1 (NOLOAD) : { . = ALIGN(32); @@ -285,7 +285,7 @@ SECTIONS } >RAM_D1 /* MPU D2 Cached Section */ - .ram_d2 : + .ram_d2 (NOLOAD) : { . = ALIGN(32); @@ -297,7 +297,7 @@ SECTIONS } >RAM_D2 /* MPU D3 Cached Section */ - .ram_d3 : + .ram_d3 (NOLOAD) : { . = ALIGN(32); diff --git a/Src/HALAL/Models/MPUManager/MPUManager.cpp b/Src/HALAL/Models/MPUManager/MPUManager.cpp index dad7395d0..f652e9795 100644 --- a/Src/HALAL/Models/MPUManager/MPUManager.cpp +++ b/Src/HALAL/Models/MPUManager/MPUManager.cpp @@ -1,7 +1,11 @@ #include "HALAL/Models/MPUManager/MPUManager.hpp" +#ifdef SIM_ON +alignas(32) uint8_t mpu_manager_memory_pool[NO_CACHED_RAM_MAXIMUM_SPACE]; +#else __attribute__((section(".mpu_ram_d3_nc.legacy"))) alignas(32 ) uint8_t mpu_manager_memory_pool[NO_CACHED_RAM_MAXIMUM_SPACE]; +#endif void* MPUManager::no_cached_ram_start = mpu_manager_memory_pool; uint32_t MPUManager::no_cached_ram_occupied_bytes = 0; diff --git a/Src/HALAL/Services/ADC/ADC.cpp b/Src/HALAL/Services/ADC/ADC.cpp deleted file mode 100644 index 6d18a688f..000000000 --- a/Src/HALAL/Services/ADC/ADC.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* - * ADC.cpp - * - * Created on: 20 oct. 2022 - * Author: alejandro - */ - -#include "HALAL/Services/ADC/ADC.hpp" -#include "ErrorHandler/ErrorHandler.hpp" - -#if defined(HAL_ADC_MODULE_ENABLED) && defined(HAL_LPTIM_MODULE_ENABLED) - -extern ADC_HandleTypeDef hadc3; - -uint8_t ADC::id_counter = 0; -unordered_map ADC::active_instances = {}; - -ADC::InitData::InitData( - ADC_TypeDef* adc, - uint32_t resolution, - uint32_t external_trigger, - vector& channels, - ST_LIB::DMA_Domain::Instance* dma_instance, - string name -) - : adc(adc), resolution(resolution), external_trigger(external_trigger), channels(channels), - dma_instance(dma_instance), name(name) {} - -ADC::Peripheral::Peripheral(ADC_HandleTypeDef* handle, LowPowerTimer& timer, InitData& init_data) - : handle(handle), timer(timer), init_data(init_data) { - dma_data_buffer = (uint16_t*)MPUManager::allocate_non_cached_memory(2 * ADC_BUF_LEN); -} - -bool ADC::Peripheral::is_registered() { return init_data.channels.size(); } - -ADC::Instance::Instance(ADC::Peripheral* peripheral, uint32_t channel) - : peripheral(peripheral), channel(channel) {} - -uint8_t ADC::inscribe(Pin pin) { - if (not available_instances.contains(pin)) { - ErrorHandler( - "Pin %s is already used or isn t available for ADC usage", - pin.to_string().c_str() - ); - return 0; - } - - Pin::inscribe(pin, ANALOG); - active_instances[id_counter] = available_instances[pin]; - - InitData& init_data = active_instances[id_counter].peripheral->init_data; - // DMA::inscribe_stream(init_data.dma_stream); - active_instances[id_counter].rank = init_data.channels.size(); - init_data.channels.push_back(active_instances[id_counter].channel); - return id_counter++; -} - -void ADC::start() { - for (Peripheral& peripheral : peripherals) { - if (peripheral.is_registered()) { - ADC::init(peripheral); - } - } -} - -void ADC::turn_on(uint8_t id) { - if (not active_instances.contains(id)) { - return; - } - - Peripheral* peripheral = active_instances[id].peripheral; - if (peripheral->is_on) { - return; - } - - uint32_t buffer_length = peripheral->init_data.channels.size(); - if (HAL_ADC_Start_DMA( - peripheral->handle, - (uint32_t*)peripheral->dma_data_buffer, - buffer_length - ) != HAL_OK) { - ErrorHandler( - "DMA - %d - of ADC - %d - did not start correctly", - peripheral->init_data.dma_instance, - id - ); - return; - } - - LowPowerTimer& timer = peripheral->timer; - if (HAL_LPTIM_TimeOut_Start_IT(&timer.handle, timer.period, timer.period / 2) != HAL_OK) { - ErrorHandler( - "LPTIM - %d - of ADC - %d - did not start correctly", - timer.name, - peripheral->handle - ); - return; - } - peripheral->is_on = true; -} - -float ADC::get_value(uint8_t id) { - Instance& instance = active_instances[id]; - uint16_t raw = instance.peripheral->dma_data_buffer[instance.rank]; - if (instance.peripheral->handle == &hadc3) { - return raw / MAX_12BIT * ADC_MAX_VOLTAGE; - } else { - return raw / MAX_16BIT * ADC_MAX_VOLTAGE; - } -} - -uint16_t ADC::get_int_value(uint8_t id) { - Instance& instance = active_instances[id]; - uint16_t raw = instance.peripheral->dma_data_buffer[instance.rank]; - - if (instance.peripheral->handle == &hadc3) { - return raw << 4; - } else { - return raw; - } -} - -uint16_t* ADC::get_value_pointer(uint8_t id) { - Instance& instance = active_instances[id]; - return &instance.peripheral->dma_data_buffer[instance.rank]; -} - -void ADC::init(Peripheral& peripheral) { - ADC_MultiModeTypeDef multimode = {0}; - ADC_ChannelConfTypeDef sConfig = {0}; - ADC_HandleTypeDef& adc_handle = *peripheral.handle; - InitData init_data = peripheral.init_data; - - adc_handle.Instance = init_data.adc; - adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; - adc_handle.Init.Resolution = init_data.resolution; - adc_handle.Init.ScanConvMode = ADC_SCAN_ENABLE; - adc_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV; - adc_handle.Init.LowPowerAutoWait = DISABLE; - adc_handle.Init.ContinuousConvMode = DISABLE; - adc_handle.Init.NbrOfConversion = init_data.channels.size(); - adc_handle.Init.DiscontinuousConvMode = DISABLE; - adc_handle.Init.ExternalTrigConv = init_data.external_trigger; - adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; - if (adc_handle.Instance == ADC3) { - adc_handle.Init.DMAContinuousRequests = ENABLE; - } else { - adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; - } - adc_handle.Init.Overrun = ADC_OVR_DATA_PRESERVED; - adc_handle.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; - adc_handle.Init.OversamplingMode = DISABLE; - if (HAL_ADC_Init(&adc_handle) != HAL_OK) { - ErrorHandler("ADC - %d - did not start correctly", init_data.name); - return; - } - - multimode.Mode = ADC_MODE_INDEPENDENT; - if (adc_handle.Instance == ADC1) { - if (HAL_ADCEx_MultiModeConfigChannel(&adc_handle, &multimode) != HAL_OK) { - ErrorHandler( - "ADC MultiModeConfigChannel - %d - did not start correctly", - init_data.name - ); - return; - } - } - - __HAL_LINKDMA(&adc_handle, DMA_Handle, init_data.dma_instance->dma); - - uint8_t counter = 0; - for (uint32_t channel : peripheral.init_data.channels) { - sConfig.Channel = channel; - sConfig.Rank = ranks[counter]; - sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; - sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.OffsetNumber = ADC_OFFSET_NONE; - sConfig.Offset = 0; - sConfig.OffsetSignedSaturation = DISABLE; - if (HAL_ADC_ConfigChannel(&adc_handle, &sConfig) != HAL_OK) { - ErrorHandler("ADC ConfigChannel - %d - did not start correctly", init_data.name); - } - counter++; - } - - peripheral.timer.init(); -} - -void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {} -#endif diff --git a/Src/MockedDrivers/mocked_hal_adc.cpp b/Src/MockedDrivers/mocked_hal_adc.cpp index 22c4aa183..9a46360cd 100644 --- a/Src/MockedDrivers/mocked_hal_adc.cpp +++ b/Src/MockedDrivers/mocked_hal_adc.cpp @@ -1,7 +1,11 @@ #include "MockedDrivers/mocked_hal_adc.hpp" +#include "MockedDrivers/mocked_hal_dma.hpp" +#include #include +#include #include +#include ADC_HandleTypeDef hadc1{}; ADC_HandleTypeDef hadc2{}; @@ -9,15 +13,44 @@ ADC_HandleTypeDef hadc3{}; namespace { +constexpr std::size_t kMaxAdcRanks = 16; +constexpr std::size_t kAdcOperationCount = + static_cast(ST_LIB::MockedHAL::ADCOperation::StopDMA) + 1U; + struct ADCPeripheralState { + struct PendingDMAWrite { + bool active = false; + uint64_t completion_time_ns = 0; + uint32_t length = 0; + std::array samples{}; + }; + bool initialized = false; bool configured = false; bool running = false; - bool conversion_ready = false; + bool dma_running = false; bool force_poll_timeout = false; + bool timed_dma_enabled = false; uint32_t active_channel = ADC_CHANNEL_0; uint32_t last_raw = 0; + void* dma_buffer = nullptr; + uint32_t dma_length = 0; + uint32_t dma_mem_alignment = DMA_MDATAALIGN_HALFWORD; + uint64_t kernel_clock_hz = 64'000'000ULL; + uint64_t sequence_period_ns = 0; + uint64_t next_sequence_time_ns = 0; + uint64_t completed_sequence_count = 0; + uint64_t overrun_count = 0; + uint8_t rank_count = 0; + std::array rank_channels{}; + std::array rank_sample_times{}; std::unordered_map channel_raw_values{}; + std::unordered_map channel_generators{}; + PendingDMAWrite pending_dma{}; +}; + +struct ADCMockState { + std::array calls{}; }; #if STLIB_HAS_ADC3 @@ -29,14 +62,29 @@ static std::array adc_instances{ADC1, ADC2}; #endif static std::array adc_states{}; +static ADCMockState g_mock_state{}; +static uint64_t g_sim_time_ns = 0; + +static ADC_HandleTypeDef* handle_for(ADC_TypeDef* instance) { + if (instance == ADC1) { + return &hadc1; + } + if (instance == ADC2) { + return &hadc2; + } + if (instance == ADC3) { + return &hadc3; + } + return nullptr; +} -static ADCPeripheralState& state_for(ADC_TypeDef* instance) { +static ADCPeripheralState* try_state_for(ADC_TypeDef* instance) { for (std::size_t i = 0; i < kAdcCount; ++i) { if (adc_instances[i] == instance) { - return adc_states[i]; + return &adc_states[i]; } } - return adc_states[0]; + return nullptr; } static uint32_t resolution_mask(uint32_t resolution) { @@ -56,6 +104,287 @@ static uint32_t resolution_mask(uint32_t resolution) { } } +static uint8_t decode_regular_rank(uint32_t rank) { + switch (rank) { + case ADC_REGULAR_RANK_1: + return 1U; + case ADC_REGULAR_RANK_2: + return 2U; + case ADC_REGULAR_RANK_3: + return 3U; + case ADC_REGULAR_RANK_4: + return 4U; + case ADC_REGULAR_RANK_5: + return 5U; + case ADC_REGULAR_RANK_6: + return 6U; + case ADC_REGULAR_RANK_7: + return 7U; + case ADC_REGULAR_RANK_8: + return 8U; + case ADC_REGULAR_RANK_9: + return 9U; + case ADC_REGULAR_RANK_10: + return 10U; + case ADC_REGULAR_RANK_11: + return 11U; + case ADC_REGULAR_RANK_12: + return 12U; + case ADC_REGULAR_RANK_13: + return 13U; + case ADC_REGULAR_RANK_14: + return 14U; + case ADC_REGULAR_RANK_15: + return 15U; + case ADC_REGULAR_RANK_16: + return 16U; + default: + return 0U; + } +} + +static uint32_t sample_time_cycles_x2(uint32_t sample_time) { + switch (sample_time) { + case ADC_SAMPLETIME_1CYCLE_5: + return 3U; + case ADC_SAMPLETIME_2CYCLES_5: + return 5U; + case ADC_SAMPLETIME_8CYCLES_5: + return 17U; + case ADC_SAMPLETIME_16CYCLES_5: + return 33U; + case ADC_SAMPLETIME_32CYCLES_5: + return 65U; + case ADC_SAMPLETIME_64CYCLES_5: + return 129U; + case ADC_SAMPLETIME_387CYCLES_5: + return 775U; + case ADC_SAMPLETIME_810CYCLES_5: + return 1621U; + default: + return 17U; + } +} + +static uint32_t conversion_cycles_x2(uint32_t resolution) { + switch (resolution) { + case ADC_RESOLUTION_8B: + return 17U; + case ADC_RESOLUTION_10B: + return 21U; + case ADC_RESOLUTION_12B: + return 25U; + case ADC_RESOLUTION_14B: + return 29U; + case ADC_RESOLUTION_16B: + return 33U; + default: + return 25U; + } +} + +static uint32_t prescaler_divisor(uint32_t prescaler) { + switch (prescaler) { + case ADC_CLOCK_ASYNC_DIV1: + return 1U; + case ADC_CLOCK_ASYNC_DIV2: + return 2U; + case ADC_CLOCK_ASYNC_DIV4: + return 4U; + case ADC_CLOCK_ASYNC_DIV6: + return 6U; + case ADC_CLOCK_ASYNC_DIV8: + return 8U; + case ADC_CLOCK_ASYNC_DIV10: + return 10U; + case ADC_CLOCK_ASYNC_DIV12: + return 12U; + case ADC_CLOCK_ASYNC_DIV16: + return 16U; + case ADC_CLOCK_ASYNC_DIV32: + return 32U; + case ADC_CLOCK_ASYNC_DIV64: + return 64U; + case ADC_CLOCK_ASYNC_DIV128: + return 128U; + case ADC_CLOCK_ASYNC_DIV256: + return 256U; + default: + return 1U; + } +} + +static uint32_t alignment_bytes(uint32_t alignment) { + switch (alignment) { + case DMA_MDATAALIGN_BYTE: + return 1U; + case DMA_MDATAALIGN_WORD: + return 4U; + case DMA_MDATAALIGN_HALFWORD: + default: + return 2U; + } +} + +static uint64_t ceil_div_u64(uint64_t numerator, uint64_t denominator) { + return (numerator + denominator - 1ULL) / denominator; +} + +static void write_dma_sample(ADCPeripheralState& state, std::size_t index, uint32_t raw) { + if (state.dma_buffer == nullptr) { + return; + } + + switch (state.dma_mem_alignment) { + case DMA_MDATAALIGN_BYTE: + reinterpret_cast(state.dma_buffer)[index] = static_cast(raw); + break; + case DMA_MDATAALIGN_WORD: + reinterpret_cast(state.dma_buffer)[index] = raw; + break; + case DMA_MDATAALIGN_HALFWORD: + default: + reinterpret_cast(state.dma_buffer)[index] = static_cast(raw); + break; + } +} + +static uint32_t read_dma_sample(const ADCPeripheralState& state, std::size_t index) { + if (state.dma_buffer == nullptr) { + return 0U; + } + + switch (state.dma_mem_alignment) { + case DMA_MDATAALIGN_BYTE: + return reinterpret_cast(state.dma_buffer)[index]; + case DMA_MDATAALIGN_WORD: + return reinterpret_cast(state.dma_buffer)[index]; + case DMA_MDATAALIGN_HALFWORD: + default: + return reinterpret_cast(state.dma_buffer)[index]; + } +} + +static uint32_t sample_channel_raw( + const ADCPeripheralState& state, + ADC_HandleTypeDef* hadc, + uint32_t channel, + uint64_t time_ns +) { + const auto generator_it = state.channel_generators.find(channel); + const uint32_t raw = + (generator_it == state.channel_generators.end()) ? [&]() { + const auto raw_it = state.channel_raw_values.find(channel); + return (raw_it == state.channel_raw_values.end()) ? 0U : raw_it->second; + }() + : generator_it->second(time_ns); + return raw & resolution_mask(hadc->Init.Resolution); +} + +static void refresh_last_raw_from_active_channel( + ADCPeripheralState& state, + ADC_HandleTypeDef* hadc, + uint64_t time_ns +) { + state.last_raw = sample_channel_raw(state, hadc, state.active_channel, time_ns); +} + +static uint64_t +compute_sequence_period_ns(const ADCPeripheralState& state, ADC_HandleTypeDef* hadc) { + if (hadc == nullptr || state.rank_count == 0U || state.kernel_clock_hz == 0U) { + return 0U; + } + + uint64_t total_cycles_x2 = 0; + for (uint8_t i = 0; i < state.rank_count; ++i) { + const uint32_t sample_cycles = sample_time_cycles_x2(state.rank_sample_times[i]); + total_cycles_x2 += sample_cycles + conversion_cycles_x2(hadc->Init.Resolution); + } + + const uint64_t scaled_cycles_x2 = + total_cycles_x2 * static_cast(prescaler_divisor(hadc->Init.ClockPrescaler)); + return ceil_div_u64(scaled_cycles_x2 * 1'000'000'000ULL, 2ULL * state.kernel_clock_hz); +} + +static void apply_pending_dma_if_due(ADCPeripheralState& state, uint64_t up_to_time_ns) { + if (!state.pending_dma.active || state.pending_dma.completion_time_ns > up_to_time_ns) { + return; + } + + for (std::size_t i = 0; i < state.pending_dma.length; ++i) { + write_dma_sample(state, i, state.pending_dma.samples[i]); + } + if (state.pending_dma.length != 0U) { + state.last_raw = state.pending_dma.samples[0]; + } + state.pending_dma = {}; + state.completed_sequence_count++; +} + +static void process_timed_sequence( + ADCPeripheralState& state, + ADC_HandleTypeDef* hadc, + uint64_t sequence_time_ns +) { + if (!state.dma_running || state.dma_buffer == nullptr || state.rank_count == 0U) { + return; + } + + apply_pending_dma_if_due(state, sequence_time_ns); + + if (state.pending_dma.active) { + state.overrun_count++; + return; + } + + const uint32_t length = std::min(state.rank_count, state.dma_length); + std::array samples{}; + for (uint32_t i = 0; i < length; ++i) { + samples[i] = static_cast( + sample_channel_raw(state, hadc, state.rank_channels[i], sequence_time_ns) + ); + } + + const uint32_t bytes = length * alignment_bytes(state.dma_mem_alignment); + const uint64_t completion_time = + ST_LIB::MockedHAL::dma_schedule_transfer(hadc->DMA_Handle, sequence_time_ns, bytes); + + if (completion_time <= sequence_time_ns) { + for (uint32_t i = 0; i < length; ++i) { + write_dma_sample(state, i, samples[i]); + } + if (length != 0U) { + state.last_raw = samples[0]; + } + state.completed_sequence_count++; + return; + } + + state.pending_dma.active = true; + state.pending_dma.completion_time_ns = completion_time; + state.pending_dma.length = length; + state.pending_dma.samples = samples; +} + +static void refresh_dma_buffer(ADCPeripheralState& state, ADC_HandleTypeDef* hadc) { + if (state.dma_buffer == nullptr || state.dma_length == 0) { + refresh_last_raw_from_active_channel(state, hadc, g_sim_time_ns); + return; + } + + const auto mask = resolution_mask(hadc->Init.Resolution); + const auto length = std::min(state.dma_length, kMaxAdcRanks); + for (std::size_t i = 0; i < length; ++i) { + const auto channel = state.rank_channels[i]; + write_dma_sample(state, i, sample_channel_raw(state, hadc, channel, g_sim_time_ns) & mask); + } + refresh_last_raw_from_active_channel(state, hadc, g_sim_time_ns); +} + +static void increment_call(ST_LIB::MockedHAL::ADCOperation op) { + g_mock_state.calls[static_cast(op)]++; +} + } // namespace namespace ST_LIB::MockedHAL { @@ -64,34 +393,209 @@ void adc_reset() { for (auto& state : adc_states) { state = {}; } + hadc1 = {}; + hadc2 = {}; + hadc3 = {}; + g_mock_state = {}; + g_sim_time_ns = 0; } void adc_set_channel_raw(ADC_TypeDef* adc, uint32_t channel, uint32_t raw_value) { - auto& state = state_for(adc); - state.channel_raw_values[channel] = raw_value; + auto* state = try_state_for(adc); + if (state == nullptr) { + return; + } + state->channel_raw_values[channel] = raw_value; + + ADC_HandleTypeDef* hadc = handle_for(adc); + + if (hadc != nullptr && state->dma_running && !state->timed_dma_enabled) { + refresh_dma_buffer(*state, hadc); + } +} + +void adc_set_channel_generator(ADC_TypeDef* adc, uint32_t channel, ADCSignalGenerator generator) { + auto* state = try_state_for(adc); + if (state == nullptr) { + return; + } + state->channel_generators[channel] = std::move(generator); } void adc_set_poll_timeout(ADC_TypeDef* adc, bool enabled) { - state_for(adc).force_poll_timeout = enabled; + auto* state = try_state_for(adc); + if (state == nullptr) { + return; + } + state->force_poll_timeout = enabled; +} + +void adc_enable_timed_dma(ADC_TypeDef* adc, bool enabled) { + auto* state = try_state_for(adc); + if (state == nullptr) { + return; + } + state->timed_dma_enabled = enabled; +} + +void adc_set_kernel_clock_hz(ADC_TypeDef* adc, uint64_t kernel_clock_hz) { + auto* state = try_state_for(adc); + if (state == nullptr) { + return; + } + state->kernel_clock_hz = kernel_clock_hz; +} + +void adc_advance_time_ns(uint64_t delta_ns) { + const uint64_t end_time_ns = g_sim_time_ns + delta_ns; + + for (std::size_t i = 0; i < kAdcCount; ++i) { + auto* adc = adc_instances[i]; + auto& state = adc_states[i]; + if (adc == nullptr || !state.timed_dma_enabled || !state.dma_running || + state.sequence_period_ns == 0U) { + continue; + } + + ADC_HandleTypeDef* hadc = handle_for(adc); + if (hadc == nullptr) { + continue; + } + + while (true) { + const uint64_t next_dma_completion = state.pending_dma.active + ? state.pending_dma.completion_time_ns + : std::numeric_limits::max(); + const uint64_t next_sequence = state.next_sequence_time_ns == 0U + ? std::numeric_limits::max() + : state.next_sequence_time_ns; + const uint64_t next_event = std::min(next_dma_completion, next_sequence); + + if (next_event > end_time_ns) { + break; + } + + if (next_dma_completion <= next_sequence) { + apply_pending_dma_if_due(state, next_dma_completion); + } else { + process_timed_sequence(state, hadc, next_sequence); + state.next_sequence_time_ns += state.sequence_period_ns; + } + } + + apply_pending_dma_if_due(state, end_time_ns); + } + + g_sim_time_ns = end_time_ns; +} + +uint64_t adc_get_time_ns() { return g_sim_time_ns; } + +uint32_t adc_get_last_channel(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return (state == nullptr) ? 0U : state->active_channel; } -uint32_t adc_get_last_channel(ADC_TypeDef* adc) { return state_for(adc).active_channel; } +bool adc_is_running(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return state != nullptr && state->running; +} + +bool adc_is_dma_running(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return state != nullptr && state->dma_running; +} + +std::size_t adc_get_call_count(ADCOperation op) { + return g_mock_state.calls[static_cast(op)]; +} + +uint32_t adc_get_dma_length(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return (state == nullptr) ? 0U : state->dma_length; +} + +uint32_t adc_get_dma_value(ADC_TypeDef* adc, std::size_t index) { + const auto* state = try_state_for(adc); + if (state == nullptr || state->dma_buffer == nullptr || index >= state->dma_length) { + return 0U; + } + return read_dma_sample(*state, index); +} -bool adc_is_running(ADC_TypeDef* adc) { return state_for(adc).running; } +uint32_t adc_get_rank_channel(ADC_TypeDef* adc, std::size_t rank_index) { + const auto* state = try_state_for(adc); + if (state == nullptr || rank_index >= state->rank_count) { + return 0U; + } + return state->rank_channels[rank_index]; +} + +uint32_t adc_get_rank_count(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return (state == nullptr) ? 0U : state->rank_count; +} + +uint64_t adc_get_sequence_period_ns(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return (state == nullptr) ? 0U : state->sequence_period_ns; +} + +uint64_t adc_get_pending_dma_completion_time_ns(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + if (state == nullptr) { + return 0U; + } + return state->pending_dma.active ? state->pending_dma.completion_time_ns : 0U; +} + +uint64_t adc_get_completed_sequence_count(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return (state == nullptr) ? 0U : state->completed_sequence_count; +} + +uint64_t adc_get_overrun_count(ADC_TypeDef* adc) { + const auto* state = try_state_for(adc); + return (state == nullptr) ? 0U : state->overrun_count; +} } // namespace ST_LIB::MockedHAL extern "C" HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc) { + increment_call(ST_LIB::MockedHAL::ADCOperation::Init); + if (hadc == nullptr || hadc->Instance == nullptr) { return HAL_ERROR; } - auto& state = state_for(hadc->Instance); - state.initialized = true; - state.running = false; - state.configured = false; - state.conversion_ready = false; - state.last_raw = 0; + auto* state = try_state_for(hadc->Instance); + if (state == nullptr) { + return HAL_ERROR; + } + const bool timed_dma_enabled = state->timed_dma_enabled; + const uint64_t kernel_clock_hz = state->kernel_clock_hz; + const auto channel_raw_values = state->channel_raw_values; + const auto channel_generators = state->channel_generators; + state->initialized = true; + state->running = false; + state->dma_running = false; + state->configured = false; + state->last_raw = 0; + state->dma_buffer = nullptr; + state->dma_length = 0; + state->dma_mem_alignment = DMA_MDATAALIGN_HALFWORD; + state->sequence_period_ns = 0; + state->next_sequence_time_ns = 0; + state->completed_sequence_count = 0; + state->overrun_count = 0; + state->rank_count = 0; + state->rank_channels.fill(ADC_CHANNEL_0); + state->rank_sample_times.fill(ADC_SAMPLETIME_1CYCLE_5); + state->pending_dma = {}; + state->timed_dma_enabled = timed_dma_enabled; + state->kernel_clock_hz = kernel_clock_hz; + state->channel_raw_values = channel_raw_values; + state->channel_generators = channel_generators; hadc->State = HAL_ADC_STATE_READY; hadc->ErrorCode = HAL_ADC_ERROR_NONE; @@ -103,72 +607,136 @@ extern "C" HAL_StatusTypeDef HAL_ADC_DeInit(ADC_HandleTypeDef* hadc) { return HAL_ERROR; } - auto& state = state_for(hadc->Instance); - state = {}; + auto* state = try_state_for(hadc->Instance); + if (state == nullptr) { + return HAL_ERROR; + } + *state = {}; hadc->State = HAL_ADC_STATE_RESET; hadc->ErrorCode = HAL_ADC_ERROR_NONE; + hadc->DMA_Handle = nullptr; return HAL_OK; } extern "C" HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig) { + increment_call(ST_LIB::MockedHAL::ADCOperation::ConfigChannel); + if (hadc == nullptr || hadc->Instance == nullptr || sConfig == nullptr) { return HAL_ERROR; } - auto& state = state_for(hadc->Instance); - if (!state.initialized) { + auto* state = try_state_for(hadc->Instance); + if (state == nullptr || !state->initialized) { return HAL_ERROR; } - state.active_channel = sConfig->Channel; - state.configured = true; + const uint8_t decoded_rank = decode_regular_rank(sConfig->Rank); + if (decoded_rank == 0U) { + return HAL_ERROR; + } + + state->active_channel = sConfig->Channel; + state->rank_channels[decoded_rank - 1U] = sConfig->Channel; + state->rank_sample_times[decoded_rank - 1U] = sConfig->SamplingTime; + state->rank_count = std::max(state->rank_count, decoded_rank); + state->configured = true; hadc->ErrorCode = HAL_ADC_ERROR_NONE; return HAL_OK; } extern "C" HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc) { + increment_call(ST_LIB::MockedHAL::ADCOperation::Start); + if (hadc == nullptr || hadc->Instance == nullptr) { return HAL_ERROR; } - auto& state = state_for(hadc->Instance); - if (!state.initialized || !state.configured) { + auto* state = try_state_for(hadc->Instance); + if (state == nullptr || !state->initialized || !state->configured) { return HAL_ERROR; } - if (state.running) { + if (state->running) { return HAL_BUSY; } - state.running = true; - state.conversion_ready = false; + state->running = true; hadc->State |= HAL_ADC_STATE_REG_BUSY; hadc->State &= ~HAL_ADC_STATE_REG_EOC; + hadc->State &= ~HAL_ADC_STATE_TIMEOUT; + hadc->ErrorCode = HAL_ADC_ERROR_NONE; + return HAL_OK; +} + +extern "C" HAL_StatusTypeDef +HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length) { + increment_call(ST_LIB::MockedHAL::ADCOperation::StartDMA); + + if (hadc == nullptr || hadc->Instance == nullptr || pData == nullptr || + hadc->DMA_Handle == nullptr || Length == 0U) { + return HAL_ERROR; + } + + auto* state = try_state_for(hadc->Instance); + if (state == nullptr || !state->initialized || !state->configured) { + return HAL_ERROR; + } + + state->running = true; + state->dma_running = true; + state->dma_buffer = pData; + state->dma_length = Length; + state->dma_mem_alignment = hadc->DMA_Handle->Init.MemDataAlignment; + state->pending_dma = {}; + state->completed_sequence_count = 0; + state->overrun_count = 0; + state->sequence_period_ns = compute_sequence_period_ns(*state, hadc); + state->next_sequence_time_ns = + state->timed_dma_enabled ? (g_sim_time_ns + state->sequence_period_ns) : 0U; + + const auto dma_status = HAL_DMA_Start_IT( + hadc->DMA_Handle, + static_cast(reinterpret_cast(&hadc->Instance->DR)), + static_cast(reinterpret_cast(pData)), + Length + ); + if (dma_status != HAL_OK) { + state->running = false; + state->dma_running = false; + state->dma_buffer = nullptr; + state->dma_length = 0; + return dma_status; + } + + if (!state->timed_dma_enabled) { + refresh_dma_buffer(*state, hadc); + } + + hadc->State |= HAL_ADC_STATE_REG_BUSY; + hadc->State &= ~HAL_ADC_STATE_TIMEOUT; hadc->ErrorCode = HAL_ADC_ERROR_NONE; return HAL_OK; } extern "C" HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout) { + increment_call(ST_LIB::MockedHAL::ADCOperation::PollForConversion); (void)Timeout; if (hadc == nullptr || hadc->Instance == nullptr) { return HAL_ERROR; } - auto& state = state_for(hadc->Instance); - if (!state.running) { + auto* state = try_state_for(hadc->Instance); + if (state == nullptr || !state->running) { return HAL_ERROR; } - if (state.force_poll_timeout) { + if (state->force_poll_timeout) { hadc->State |= HAL_ADC_STATE_TIMEOUT; return HAL_TIMEOUT; } - const auto it = state.channel_raw_values.find(state.active_channel); - const uint32_t raw = (it == state.channel_raw_values.end()) ? 0U : it->second; - state.last_raw = raw & resolution_mask(hadc->Init.Resolution); - state.conversion_ready = true; + refresh_last_raw_from_active_channel(*state, hadc, g_sim_time_ns); hadc->State &= ~HAL_ADC_STATE_TIMEOUT; hadc->State |= HAL_ADC_STATE_REG_EOC; @@ -176,21 +744,50 @@ extern "C" HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, } extern "C" uint32_t HAL_ADC_GetValue(const ADC_HandleTypeDef* hadc) { + increment_call(ST_LIB::MockedHAL::ADCOperation::GetValue); + if (hadc == nullptr || hadc->Instance == nullptr) { return 0; } - auto& state = state_for(hadc->Instance); - return state.last_raw; + const auto* state = try_state_for(hadc->Instance); + return (state == nullptr) ? 0U : state->last_raw; } extern "C" HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc) { + increment_call(ST_LIB::MockedHAL::ADCOperation::Stop); + if (hadc == nullptr || hadc->Instance == nullptr) { return HAL_ERROR; } - auto& state = state_for(hadc->Instance); - state.running = false; - state.conversion_ready = false; + auto* state = try_state_for(hadc->Instance); + if (state == nullptr) { + return HAL_ERROR; + } + state->running = false; + state->pending_dma = {}; + state->next_sequence_time_ns = 0; + + hadc->State &= ~HAL_ADC_STATE_REG_BUSY; + hadc->State &= ~HAL_ADC_STATE_REG_EOC; + return HAL_OK; +} + +extern "C" HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc) { + increment_call(ST_LIB::MockedHAL::ADCOperation::StopDMA); + + if (hadc == nullptr || hadc->Instance == nullptr) { + return HAL_ERROR; + } + + auto* state = try_state_for(hadc->Instance); + if (state == nullptr) { + return HAL_ERROR; + } + state->running = false; + state->dma_running = false; + state->pending_dma = {}; + state->next_sequence_time_ns = 0; hadc->State &= ~HAL_ADC_STATE_REG_BUSY; hadc->State &= ~HAL_ADC_STATE_REG_EOC; diff --git a/Src/MockedDrivers/mocked_hal_dma.cpp b/Src/MockedDrivers/mocked_hal_dma.cpp index 53ff7dea4..f93e3d56c 100644 --- a/Src/MockedDrivers/mocked_hal_dma.cpp +++ b/Src/MockedDrivers/mocked_hal_dma.cpp @@ -1,19 +1,32 @@ #include "MockedDrivers/mocked_hal_dma.hpp" +#include #include +#include namespace { +constexpr std::size_t kDmaOperationCount = + static_cast(ST_LIB::MockedHAL::DMAOperation::ScheduleTransfer) + 1U; + struct DMAState { HAL_StatusTypeDef init_status = HAL_OK; HAL_StatusTypeDef start_status = HAL_OK; - std::array calls{}; + std::array calls{}; DMA_HandleTypeDef* last_init_handle = nullptr; DMA_HandleTypeDef* last_start_handle = nullptr; DMA_HandleTypeDef* last_irq_handle = nullptr; uint32_t last_start_src = 0; uint32_t last_start_dst = 0; uint32_t last_start_length = 0; + uint64_t transfer_setup_time_ns = 0; + uint64_t transfer_per_byte_time_ns = 0; + uint64_t bus_available_time_ns = 0; + DMA_HandleTypeDef* last_scheduled_handle = nullptr; + uint64_t last_schedule_request_time_ns = 0; + uint64_t last_schedule_completion_time_ns = 0; + uint32_t last_schedule_bytes = 0; + std::unordered_map stream_available_time_ns{}; }; DMAState g_state{}; @@ -28,6 +41,11 @@ void dma_set_init_status(HAL_StatusTypeDef status) { g_state.init_status = statu void dma_set_start_status(HAL_StatusTypeDef status) { g_state.start_status = status; } +void dma_set_transfer_timing(uint64_t setup_time_ns, uint64_t per_byte_time_ns) { + g_state.transfer_setup_time_ns = setup_time_ns; + g_state.transfer_per_byte_time_ns = per_byte_time_ns; +} + std::size_t dma_get_call_count(DMAOperation op) { return g_state.calls[static_cast(op)]; } @@ -44,6 +62,40 @@ uint32_t dma_get_last_start_dst() { return g_state.last_start_dst; } uint32_t dma_get_last_start_length() { return g_state.last_start_length; } +DMA_HandleTypeDef* dma_get_last_scheduled_handle() { return g_state.last_scheduled_handle; } + +uint64_t dma_get_last_schedule_request_time_ns() { return g_state.last_schedule_request_time_ns; } + +uint64_t dma_get_last_schedule_completion_time_ns() { + return g_state.last_schedule_completion_time_ns; +} + +uint32_t dma_get_last_schedule_bytes() { return g_state.last_schedule_bytes; } + +uint64_t dma_schedule_transfer(DMA_HandleTypeDef* hdma, uint64_t request_time_ns, uint32_t bytes) { + g_state.calls[static_cast(ST_LIB::MockedHAL::DMAOperation::ScheduleTransfer)]++; + g_state.last_scheduled_handle = hdma; + g_state.last_schedule_request_time_ns = request_time_ns; + g_state.last_schedule_bytes = bytes; + + if (hdma == nullptr) { + g_state.last_schedule_completion_time_ns = request_time_ns; + return request_time_ns; + } + + // The timing model is intentionally simple: one shared DMA bus plus per-stream serialization. + auto& stream_available = g_state.stream_available_time_ns[hdma]; + const uint64_t start_time = + std::max(request_time_ns, std::max(stream_available, g_state.bus_available_time_ns)); + const uint64_t completion_time = + start_time + g_state.transfer_setup_time_ns + g_state.transfer_per_byte_time_ns * bytes; + + stream_available = completion_time; + g_state.bus_available_time_ns = completion_time; + g_state.last_schedule_completion_time_ns = completion_time; + return completion_time; +} + } // namespace ST_LIB::MockedHAL extern "C" HAL_StatusTypeDef MockedHAL_DMA_Init_Impl(DMA_HandleTypeDef* hdma) { diff --git a/Src/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.cpp b/Src/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.cpp deleted file mode 100644 index 5d557ecd1..000000000 --- a/Src/ST-LIB_LOW/Sensors/LookupSensor/LookupSensor.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "Sensors/LookupSensor/LookupSensor.hpp" - -LookupSensor::LookupSensor( - ST_LIB::ADCDomain::Instance& adc, - double* table, - int table_size, - double* value -) - : adc(&adc), table(table), table_size(table_size), value(value) {} - -LookupSensor::LookupSensor( - ST_LIB::ADCDomain::Instance& adc, - double* table, - int table_size, - double& value -) - : LookupSensor::LookupSensor(adc, table, table_size, &value) {} - -void LookupSensor::read() { - if (adc == nullptr || value == nullptr) { - return; - } - const float raw = adc->get_raw(); - const float adc_voltage = adc->get_value_from_raw(raw, REFERENCE_VOLTAGE); - - int table_index = (int)(adc_voltage * table_size / REFERENCE_VOLTAGE); - if (table_index >= table_size) { - table_index = table_size - 1; - } - *value = table[table_index]; -} diff --git a/Src/ST-LIB_LOW/Sensors/NTC/NTC.cpp b/Src/ST-LIB_LOW/Sensors/NTC/NTC.cpp deleted file mode 100644 index 922a1573a..000000000 --- a/Src/ST-LIB_LOW/Sensors/NTC/NTC.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "Sensors/NTC/NTC.hpp" - -NTC::NTC(ST_LIB::ADCDomain::Instance& adc, float* src) : value(src), adc(&adc) {} -NTC::NTC(ST_LIB::ADCDomain::Instance& adc, float& src) : value(&src), adc(&adc) {} - -void NTC::read() { - if (adc == nullptr || value == nullptr) { - return; - } - const float raw = adc->get_raw(); - uint16_t val = static_cast(adc->get_value_from_raw(raw, 4095.0f)); - if (val > 4095u) { - val = 4095u; - } - *value = static_cast(NTC_table[val]) * 0.1f; -} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index a92d1373c..e6c538a61 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(${STLIB_TEST_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/Time/timer_wrapper_test.cpp ${CMAKE_CURRENT_LIST_DIR}/StateMachine/state_machine_test.cpp ${CMAKE_CURRENT_LIST_DIR}/adc_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/adc_sensor_test.cpp ${CMAKE_CURRENT_LIST_DIR}/spi2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/dma2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/common_tests.cpp diff --git a/Tests/adc_sensor_test.cpp b/Tests/adc_sensor_test.cpp new file mode 100644 index 000000000..907bb246b --- /dev/null +++ b/Tests/adc_sensor_test.cpp @@ -0,0 +1,279 @@ +#include +#include + +#include + +#include "Control/Blocks/MovingAverage.hpp" +#include "HALAL/Models/DMA/DMA2.hpp" +#include "HALAL/Services/ADC/ADC.hpp" +#include "MockedDrivers/NVIC.hpp" +#include "MockedDrivers/mocked_hal_adc.hpp" +#include "MockedDrivers/mocked_hal_dma.hpp" +#include "Sensors/Common/PT100.hpp" +#include "Sensors/LinearSensor/FilteredLinearSensor.hpp" +#include "Sensors/LinearSensor/LinearSensor.hpp" +#include "Sensors/LookupSensor/LookupSensor.hpp" +#include "Sensors/NTC/NTC.hpp" + +namespace { + +inline float adc_sensor_template_output_0 = 0.0f; +inline float adc_sensor_template_output_1 = 0.0f; + +constexpr std::array adc_dma_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::adc1, + .stream = ST_LIB::DMADomain::Stream::dma1_stream0, + .irqn = DMA1_Stream0_IRQn, + .id = 0}, + {.instance = ST_LIB::DMADomain::Peripheral::adc2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream1, + .irqn = DMA1_Stream1_IRQn, + .id = 0}, +}}; + +constexpr auto adc_dma_cfg = + ST_LIB::DMADomain::build<2>(std::span{adc_dma_entries}); + +constexpr std::array single_adc1_init_cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &adc_sensor_template_output_0}, +}}; + +constexpr std::array split_adc12_init_cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &adc_sensor_template_output_0}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, + .channel = ST_LIB::ADCDomain::Channel::CH2, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC2, + .output = &adc_sensor_template_output_1}, +}}; + +template & Cfgs> +using ADCInit = ST_LIB::ADCDomain::Init; + +using SingleADCInit = ADCInit<1, single_adc1_init_cfgs>; +using SplitADCInit = ADCInit<2, split_adc12_init_cfgs>; + +void clear_nvic_enables() { + for (auto& reg : NVIC->ISER) { + reg = 0U; + } +} + +void clear_dma_irq_table() { + for (auto& slot : dma_irq_table) { + slot = nullptr; + } +} + +class ADCSensorTest : public ::testing::Test { +protected: + void SetUp() override { + ST_LIB::MockedHAL::adc_reset(); + ST_LIB::MockedHAL::dma_reset(); + clear_nvic_enables(); + clear_dma_irq_table(); + } + + template & InitCfgs> + void init_adc_with_dma(const std::array& cfgs) { + ST_LIB::DMADomain::Init<2>::init(adc_dma_cfg); + ADCInit::init( + cfgs, + std::span{}, + std::span(ST_LIB::DMADomain::Init<2>::instances) + ); + } +}; + +TEST_F(ADCSensorTest, LinearSensorUsesNormalizedADCVoltageForItsTransferFunction) { + float output = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_10, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &output}, + }}; + + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 512U); + + LinearSensor sensor(SingleADCInit::instances[0], 2.0f, -1.0f, output, 5.0f); + sensor.read(); + + EXPECT_NEAR(output, 2.0f * ((512.0f / 1023.0f) * 5.0f) - 1.0f, 0.001f); +} + +TEST_F(ADCSensorTest, FilteredLinearSensorReusesTheSameADCConversionPath) { + float output = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &output}, + }}; + + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 2048U); + + MovingAverage<1> filter; + FilteredLinearSensor sensor(SingleADCInit::instances[0], 2.0f, 1.0f, output, filter); + + sensor.read(); + sensor.read(); + + EXPECT_NEAR(output, 2.0f * ((2048.0f / 4095.0f) * 3.3f) + 1.0f, 0.001f); +} + +TEST_F(ADCSensorTest, LookupSensorMapsEquivalentNormalizedReadingsAcrossResolutions) { + double out12 = -1.0; + double out16 = -1.0; + constexpr std::array table{10.0, 20.0, 30.0, 40.0}; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = nullptr}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, + .channel = ST_LIB::ADCDomain::Channel::CH2, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC2, + .output = nullptr}, + }}; + + init_adc_with_dma<2, split_adc12_init_cfgs>(cfgs); + + LookupSensor span_sensor(SplitADCInit::instances[0], std::span(table), out12); + LookupSensor ptr_sensor(SplitADCInit::instances[1], table.data(), table.size(), out16); + + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 2048U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 32768U); + span_sensor.read(); + ptr_sensor.read(); + + EXPECT_DOUBLE_EQ(out12, 30.0); + EXPECT_DOUBLE_EQ(out16, 30.0); + + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 4095U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 65535U); + span_sensor.read(); + ptr_sensor.read(); + + EXPECT_DOUBLE_EQ(out12, 40.0); + EXPECT_DOUBLE_EQ(out16, 40.0); +} + +TEST_F(ADCSensorTest, PT100ReadsFromADCVoltageAndSupportsFilteredMode) { + float direct_output = -1.0f; + float filtered_output = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &direct_output}, + }}; + + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 2048U); + + PT100<1> direct_sensor(SingleADCInit::instances[0], direct_output); + MovingAverage<1> filter; + PT100<1> filtered_sensor(SingleADCInit::instances[0], filtered_output, filter); + + direct_sensor.read(); + filtered_sensor.read(); + filtered_sensor.read(); + + const float voltage = (2048.0f / 4095.0f) * 3.3f; + const float expected_temperature = PT100<1>::k / voltage + PT100<1>::offset; + + EXPECT_NEAR(direct_output, expected_temperature, 0.001f); + EXPECT_NEAR(filtered_output, expected_temperature, 0.001f); +} + +TEST_F(ADCSensorTest, NTCUsesNormalizedADCCountsAcrossResolutions) { + float out12 = -1.0f; + float out16 = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = nullptr}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, + .channel = ST_LIB::ADCDomain::Channel::CH2, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC2, + .output = nullptr}, + }}; + + init_adc_with_dma<2, split_adc12_init_cfgs>(cfgs); + + NTC ntc12(SplitADCInit::instances[0], out12); + NTC ntc16(SplitADCInit::instances[1], out16); + + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 2047U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 32767U); + ntc12.read(); + ntc16.read(); + EXPECT_FLOAT_EQ(out12, out16); + + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 4095U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 65535U); + ntc12.read(); + ntc16.read(); + EXPECT_FLOAT_EQ(out12, out16); +} + +} // namespace diff --git a/Tests/adc_test.cpp b/Tests/adc_test.cpp index 870a2ba55..6003512b3 100644 --- a/Tests/adc_test.cpp +++ b/Tests/adc_test.cpp @@ -2,12 +2,57 @@ #include -#include "HALAL/Services/ADC/NewADC.hpp" +#include "HALAL/Models/DMA/DMA2.hpp" +#include "HALAL/Services/ADC/ADC.hpp" +#include "MockedDrivers/NVIC.hpp" #include "MockedDrivers/mocked_hal_adc.hpp" +#include "MockedDrivers/mocked_hal_dma.hpp" + +namespace ST_LIB::TestErrorHandler { +void reset(); +void set_fail_on_error(bool enabled); +extern int call_count; +} // namespace ST_LIB::TestErrorHandler namespace { +template +consteval std::array merge_dma_entries( + std::span base_entries, + const std::array&... extra_entries +) { + std::array merged{}; + std::size_t cursor = 0; + + for (const auto& entry : base_entries) { + merged[cursor++] = entry; + } + + auto append = [&](const std::array& entries) { + for (const auto& entry : entries) { + merged[cursor++] = entry; + } + }; + (append(extra_entries), ...); + + return merged; +} + +template +consteval std::array build_dma_cfgs( + std::span base_entries, + const std::array&... extra_entries +) { + const auto merged = merge_dma_entries(base_entries, extra_entries...); + return ST_LIB::DMADomain::build(std::span{merged + }); +} + inline float compile_time_output = 0.0f; +inline float synthesized_dma_output_0 = 0.0f; +inline float synthesized_dma_output_1 = 0.0f; +inline float adc_test_template_output_0 = 0.0f; +inline float adc_test_template_output_1 = 0.0f; constexpr std::array auto_entry{{ {.gpio_idx = 0, @@ -45,68 +90,281 @@ constexpr auto auto_pf13_cfg = static_assert(auto_pf13_cfg[0].peripheral == ST_LIB::ADCDomain::Peripheral::ADC_2); static_assert(auto_pf13_cfg[0].channel == ST_LIB::ADCDomain::Channel::CH2); -constexpr std::array auto_pc0_12bit_entry{{ +constexpr std::array auto_pc0_16bit_entry{{ {.gpio_idx = 0, .pin = ST_LIB::PC0, .peripheral = ST_LIB::ADCDomain::Peripheral::AUTO, .channel = ST_LIB::ADCDomain::Channel::AUTO, - .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, .output = &compile_time_output}, }}; -constexpr auto auto_pc0_12bit_cfg = - ST_LIB::ADCDomain::build<1>(std::span{auto_pc0_12bit_entry}); -static_assert(auto_pc0_12bit_cfg[0].peripheral == ST_LIB::ADCDomain::Peripheral::ADC_3); -static_assert(auto_pc0_12bit_cfg[0].channel == ST_LIB::ADCDomain::Channel::CH10); +constexpr auto auto_pc0_16bit_cfg = + ST_LIB::ADCDomain::build<1>(std::span{auto_pc0_16bit_entry}); +static_assert(auto_pc0_16bit_cfg[0].peripheral == ST_LIB::ADCDomain::Peripheral::ADC_1); +static_assert(auto_pc0_16bit_cfg[0].channel == ST_LIB::ADCDomain::Channel::CH10); -constexpr std::array auto_pc0_16bit_entry{{ +constexpr std::array no_adc_entries{}; +constexpr std::array preexisting_spi_dma_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream6, + .irqn = DMA1_Stream6_IRQn, + .id = 0}, + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma2_stream3, + .irqn = DMA2_Stream3_IRQn, + .id = 1}, +}}; +constexpr std::array no_adc_cfgs{}; +static_assert( + ST_LIB::ADCDomain::dma_contribution_count( + std::span{no_adc_cfgs}, + std::span{preexisting_spi_dma_entries} + ) == 0 +); +constexpr auto passthrough_dma_cfg = + build_dma_cfgs<2>(std::span{preexisting_spi_dma_entries}); +static_assert(std::get<1>(passthrough_dma_cfg[0].init_data).Request == DMA_REQUEST_SPI2_RX); +static_assert(std::get<1>(passthrough_dma_cfg[1].init_data).Request == DMA_REQUEST_SPI2_TX); +static_assert( + std::get<2>(passthrough_dma_cfg[0].init_data) == ST_LIB::DMADomain::Stream::dma1_stream6 +); +static_assert( + std::get<2>(passthrough_dma_cfg[1].init_data) == ST_LIB::DMADomain::Stream::dma2_stream3 +); + +constexpr std::array shared_adc_cfgs{{ {.gpio_idx = 0, - .pin = ST_LIB::PC0, - .peripheral = ST_LIB::ADCDomain::Peripheral::AUTO, - .channel = ST_LIB::ADCDomain::Channel::AUTO, - .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, - .output = &compile_time_output}, + .dma_request = DMA_REQUEST_ADC1, + .output = &synthesized_dma_output_0}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH15, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &synthesized_dma_output_1}, }}; -constexpr auto auto_pc0_16bit_cfg = - ST_LIB::ADCDomain::build<1>(std::span{auto_pc0_16bit_entry}); -static_assert(auto_pc0_16bit_cfg[0].peripheral == ST_LIB::ADCDomain::Peripheral::ADC_1); -static_assert(auto_pc0_16bit_cfg[0].channel == ST_LIB::ADCDomain::Channel::CH10); +constexpr std::array no_adc_dma_entries{}; +static_assert( + ST_LIB::ADCDomain::dma_contribution_count( + std::span{shared_adc_cfgs}, + std::span{no_adc_dma_entries} + ) == 1 +); +constexpr auto synthesized_shared_adc_dma_entries = ST_LIB::ADCDomain::build_dma_contributions<1>( + std::span{no_adc_dma_entries}, + std::span{shared_adc_cfgs} +); +constexpr auto synthesized_shared_adc_dma_cfg = build_dma_cfgs<1>( + std::span{no_adc_dma_entries}, + synthesized_shared_adc_dma_entries +); +static_assert(std::get<1>(synthesized_shared_adc_dma_cfg[0].init_data).Request == DMA_REQUEST_ADC1); +static_assert( + std::get<2>(synthesized_shared_adc_dma_cfg[0].init_data) == + ST_LIB::DMADomain::Stream::dma1_stream0 +); +static_assert(!std::get<5>(synthesized_shared_adc_dma_cfg[0].init_data)); + +constexpr std::array explicit_adc1_dma_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::adc1, + .stream = ST_LIB::DMADomain::Stream::dma1_stream5, + .irqn = DMA1_Stream5_IRQn, + .id = 0}, +}}; + +static_assert( + ST_LIB::ADCDomain::dma_contribution_count( + std::span{shared_adc_cfgs}, + std::span{explicit_adc1_dma_entries} + ) == 0 +); +constexpr auto explicit_shared_adc_dma_cfg = + build_dma_cfgs<1>(std::span{explicit_adc1_dma_entries}); +static_assert( + std::get<2>(explicit_shared_adc_dma_cfg[0].init_data) == ST_LIB::DMADomain::Stream::dma1_stream5 +); +static_assert( + ST_LIB::ADCDomain::dma_contribution_count( + std::span{shared_adc_cfgs}, + std::span{preexisting_spi_dma_entries} + ) == 1 +); +constexpr auto merged_dma_entries = ST_LIB::ADCDomain::build_dma_contributions<1>( + std::span{preexisting_spi_dma_entries}, + std::span{shared_adc_cfgs} +); +constexpr auto merged_dma_cfg = build_dma_cfgs<3>( + std::span{preexisting_spi_dma_entries}, + merged_dma_entries +); +static_assert(std::get<1>(merged_dma_cfg[0].init_data).Request == DMA_REQUEST_SPI2_RX); +static_assert(std::get<1>(merged_dma_cfg[1].init_data).Request == DMA_REQUEST_SPI2_TX); +static_assert(std::get<1>(merged_dma_cfg[2].init_data).Request == DMA_REQUEST_ADC1); + +constexpr std::array adc_dma_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::adc1, + .stream = ST_LIB::DMADomain::Stream::dma1_stream0, + .irqn = DMA1_Stream0_IRQn, + .id = 0}, + {.instance = ST_LIB::DMADomain::Peripheral::adc2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream1, + .irqn = DMA1_Stream1_IRQn, + .id = 0}, + {.instance = ST_LIB::DMADomain::Peripheral::adc3, + .stream = ST_LIB::DMADomain::Stream::dma1_stream2, + .irqn = DMA1_Stream2_IRQn, + .id = 0}, +}}; -constexpr std::array internal_entry{{ +constexpr auto adc_dma_cfg = + ST_LIB::DMADomain::build<3>(std::span{adc_dma_entries}); +static_assert(std::get<1>(adc_dma_cfg[0].init_data).Request == DMA_REQUEST_ADC1); +static_assert(std::get<1>(adc_dma_cfg[1].init_data).Request == DMA_REQUEST_ADC2); +static_assert(std::get<1>(adc_dma_cfg[2].init_data).Request == DMA_REQUEST_ADC3); +static_assert(std::get<1>(adc_dma_cfg[0].init_data).Mode == DMA_CIRCULAR); +static_assert(std::get<1>(adc_dma_cfg[0].init_data).PeriphDataAlignment == DMA_PDATAALIGN_HALFWORD); +static_assert(!std::get<5>(adc_dma_cfg[0].init_data)); +static_assert(!std::get<5>(adc_dma_cfg[1].init_data)); +static_assert(!std::get<5>(adc_dma_cfg[2].init_data)); +static_assert(std::get<1>(adc_dma_cfg[0].init_data).MemDataAlignment == DMA_MDATAALIGN_HALFWORD); + +constexpr std::array single_adc1_init_cfgs{{ {.gpio_idx = 0, - .pin = ST_LIB::PA0, - .peripheral = ST_LIB::ADCDomain::Peripheral::AUTO, - .channel = ST_LIB::ADCDomain::Channel::VREFINT, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, - .output = &compile_time_output}, + .dma_request = DMA_REQUEST_ADC1, + .output = &adc_test_template_output_0}, +}}; + +constexpr std::array shared_adc1_init_cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &adc_test_template_output_0}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH15, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &adc_test_template_output_1}, +}}; + +constexpr std::array split_adc12_init_cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &adc_test_template_output_0}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, + .channel = ST_LIB::ADCDomain::Channel::CH2, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC2, + .output = &adc_test_template_output_1}, }}; -constexpr auto internal_cfg = - ST_LIB::ADCDomain::build<1>(std::span{internal_entry}); -#if STLIB_HAS_ADC3 -static_assert(internal_cfg[0].peripheral == ST_LIB::ADCDomain::Peripheral::ADC_3); -#else -static_assert(internal_cfg[0].peripheral == ST_LIB::ADCDomain::Peripheral::ADC_2); -#endif +template & Cfgs> +using ADCInit = ST_LIB::ADCDomain::Init; + +using SingleADCInit = ADCInit<1, single_adc1_init_cfgs>; +using SharedADCInit = ADCInit<2, shared_adc1_init_cfgs>; +using SplitADCInit = ADCInit<2, split_adc12_init_cfgs>; + +void clear_nvic_enables() { + for (auto& reg : NVIC->ISER) { + reg = 0U; + } +} + +void clear_dma_irq_table() { + for (auto& slot : dma_irq_table) { + slot = nullptr; + } +} } // namespace class ADCTest : public ::testing::Test { protected: - void SetUp() override { ST_LIB::MockedHAL::adc_reset(); } + void reset_runtime_state() { + ST_LIB::MockedHAL::adc_reset(); + ST_LIB::MockedHAL::dma_reset(); + ST_LIB::TestErrorHandler::reset(); + clear_nvic_enables(); + clear_dma_irq_table(); + } + + void SetUp() override { reset_runtime_state(); } + + template & InitCfgs> + void init_adc_with_dma(const std::array& cfgs) { + ST_LIB::DMADomain::Init<3>::init(adc_dma_cfg); + ADCInit::init( + cfgs, + std::span{}, + std::span(ST_LIB::DMADomain::Init<3>::instances) + ); + } + + void advance_time_to(uint64_t target_time_ns) { + const uint64_t now = ST_LIB::MockedHAL::adc_get_time_ns(); + if (target_time_ns > now) { + ST_LIB::MockedHAL::adc_advance_time_ns(target_time_ns - now); + } + } }; -TEST_F(ADCTest, PollingReadUpdatesOutputValue) { +TEST_F(ADCTest, BuildTimeDMASynthesisCreatesSingleDMAForSharedADCPeripheral) { + constexpr std::size_t dmaN = std::tuple_size_v; + constexpr std::size_t adcN = std::tuple_size_v; + + ST_LIB::DMADomain::Init::init(synthesized_shared_adc_dma_cfg); + ADCInit::init( + shared_adc_cfgs, + std::span{}, + std::span(ST_LIB::DMADomain::Init::instances) + ); + + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 1U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_call_count(ST_LIB::MockedHAL::ADCOperation::StartDMA), 1U); + EXPECT_EQ(hadc1.DMA_Handle, &ST_LIB::DMADomain::Init::instances[0].dma); + EXPECT_EQ(hadc1.Init.NbrOfConversion, 2U); +} + +TEST_F(ADCTest, InitWithExternalDMAStartsCircularTransferAndLinksHandle) { float output = -1.0f; const std::array cfgs{{ {.gpio_idx = 0, @@ -116,23 +374,34 @@ TEST_F(ADCTest, PollingReadUpdatesOutputValue) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &output}, }}; - ST_LIB::ADCDomain::Init<1>::init(cfgs); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 2048U); - - auto& adc = ST_LIB::ADCDomain::Init<1>::instances[0]; - adc.read(3.3, 1); - - const float expected = (2048.0f / 4095.0f) * 3.3f; - EXPECT_NEAR(output, expected, 0.001f); - EXPECT_EQ(ST_LIB::MockedHAL::adc_get_last_channel(ADC1), ADC_CHANNEL_16); - EXPECT_TRUE(ST_LIB::MockedHAL::adc_is_running(ADC1)); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + EXPECT_EQ(hadc1.Instance, ADC1); + EXPECT_EQ(hadc1.Init.ConversionDataManagement, ADC_CONVERSIONDATA_DMA_CIRCULAR); + EXPECT_EQ(hadc1.Init.NbrOfConversion, 1U); + EXPECT_EQ(hadc1.DMA_Handle, &ST_LIB::DMADomain::Init<3>::instances[0].dma); + ASSERT_NE(hadc1.DMA_Handle, nullptr); + EXPECT_EQ(hadc1.DMA_Handle->Parent, &hadc1); + EXPECT_EQ(NVIC_GetEnableIRQ(DMA1_Stream0_IRQn), 0U); + + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_call_count(ST_LIB::MockedHAL::ADCOperation::StartDMA), 1U); + EXPECT_EQ( + ST_LIB::MockedHAL::adc_get_call_count(ST_LIB::MockedHAL::ADCOperation::PollForConversion), + 0U + ); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_rank_count(ADC1), 1U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_rank_channel(ADC1, 0), ADC_CHANNEL_16); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::StartIT), 1U); + EXPECT_TRUE(ST_LIB::MockedHAL::adc_is_dma_running(ADC1)); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_length(ADC1), 1U); } -TEST_F(ADCTest, PollTimeoutMapsToZeroReading) { - float output = 1.0f; +TEST_F(ADCTest, ReadUsesLatestDMABufferValueWithoutPolling) { + float output = -1.0f; const std::array cfgs{{ {.gpio_idx = 0, .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, @@ -141,20 +410,30 @@ TEST_F(ADCTest, PollTimeoutMapsToZeroReading) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &output}, }}; - ST_LIB::ADCDomain::Init<1>::init(cfgs); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 4095U); - ST_LIB::MockedHAL::adc_set_poll_timeout(ADC1, true); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 2048U); - auto& adc = ST_LIB::ADCDomain::Init<1>::instances[0]; - adc.read(3.3, 1); + auto& adc = SingleADCInit::instances[0]; + adc.read(3.3f, 1U); - EXPECT_FLOAT_EQ(output, 0.0f); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 2048U); + EXPECT_NEAR(output, (2048.0f / 4095.0f) * 3.3f, 0.001f); + EXPECT_EQ( + ST_LIB::MockedHAL::adc_get_call_count(ST_LIB::MockedHAL::ADCOperation::PollForConversion), + 0U + ); + + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 1024U); + adc.read(3.3f, 1U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 1024U); + EXPECT_NEAR(output, (1024.0f / 4095.0f) * 3.3f, 0.001f); } -TEST_F(ADCTest, MultiChannelInstancesReadTheirOwnChannel) { +TEST_F(ADCTest, MultiChannelDMAUsesSequenceSlotsPerPeripheral) { float out0 = -1.0f; float out1 = -1.0f; const std::array cfgs{{ @@ -165,6 +444,7 @@ TEST_F(ADCTest, MultiChannelInstancesReadTheirOwnChannel) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &out0}, {.gpio_idx = 1, .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, @@ -173,48 +453,74 @@ TEST_F(ADCTest, MultiChannelInstancesReadTheirOwnChannel) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &out1}, }}; - ST_LIB::ADCDomain::Init<2>::init(cfgs); + init_adc_with_dma<2, shared_adc1_init_cfgs>(cfgs); ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 1024U); ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_15, 3072U); - auto& adc0 = ST_LIB::ADCDomain::Init<2>::instances[0]; - auto& adc1 = ST_LIB::ADCDomain::Init<2>::instances[1]; - adc0.read(3.3, 1); - adc1.read(3.3, 1); - - const float expected0 = (1024.0f / 4095.0f) * 3.3f; - const float expected1 = (3072.0f / 4095.0f) * 3.3f; - EXPECT_NEAR(out0, expected0, 0.001f); - EXPECT_NEAR(out1, expected1, 0.001f); - EXPECT_EQ(ST_LIB::MockedHAL::adc_get_last_channel(ADC1), ADC_CHANNEL_15); + auto& adc0 = SharedADCInit::instances[0]; + auto& adc1 = SharedADCInit::instances[1]; + adc0.read(3.3f, 1U); + adc1.read(3.3f, 1U); + + EXPECT_EQ(hadc1.Init.ScanConvMode, ADC_SCAN_ENABLE); + EXPECT_EQ(hadc1.Init.EOCSelection, ADC_EOC_SEQ_CONV); + EXPECT_EQ(hadc1.Init.NbrOfConversion, 2U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_rank_count(ADC1), 2U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_rank_channel(ADC1, 0), ADC_CHANNEL_16); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_rank_channel(ADC1, 1), ADC_CHANNEL_15); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_length(ADC1), 2U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 1024U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 1), 3072U); + EXPECT_NEAR(out0, (1024.0f / 4095.0f) * 3.3f, 0.001f); + EXPECT_NEAR(out1, (3072.0f / 4095.0f) * 3.3f, 0.001f); } -TEST_F(ADCTest, Resolution8BitUses8BitScaling) { - float output = -1.0f; - const std::array cfgs{{ +TEST_F(ADCTest, SeparatePeripheralsUseIndependentDMAHandlesAndBuffers) { + float out1 = -1.0f; + float out2 = -1.0f; + const std::array cfgs{{ {.gpio_idx = 0, .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, .channel = ST_LIB::ADCDomain::Channel::CH16, - .resolution = ST_LIB::ADCDomain::Resolution::BITS_8, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, - .output = &output}, + .dma_request = DMA_REQUEST_ADC1, + .output = &out1}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, + .channel = ST_LIB::ADCDomain::Channel::CH2, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC2, + .output = &out2}, }}; - ST_LIB::ADCDomain::Init<1>::init(cfgs); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 255U); + init_adc_with_dma<2, split_adc12_init_cfgs>(cfgs); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 500U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 3000U); - auto& adc = ST_LIB::ADCDomain::Init<1>::instances[0]; - adc.read(3.3, 1); + auto& adc1 = SplitADCInit::instances[0]; + auto& adc2 = SplitADCInit::instances[1]; + adc1.read(3.3f, 1U); + adc2.read(3.3f, 1U); - EXPECT_NEAR(output, 3.3f, 0.001f); + EXPECT_EQ(hadc1.DMA_Handle, &ST_LIB::DMADomain::Init<3>::instances[0].dma); + EXPECT_EQ(hadc2.DMA_Handle, &ST_LIB::DMADomain::Init<3>::instances[1].dma); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 500U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC2, 0), 3000U); + EXPECT_NEAR(out1, (500.0f / 4095.0f) * 3.3f, 0.001f); + EXPECT_NEAR(out2, (3000.0f / 4095.0f) * 3.3f, 0.001f); } -TEST_F(ADCTest, Resolution10BitClampsRawTo10BitRange) { +TEST_F(ADCTest, Resolution10BitDMAClampsRawBufferToResolutionRange) { float output = -1.0f; const std::array cfgs{{ {.gpio_idx = 0, @@ -224,19 +530,23 @@ TEST_F(ADCTest, Resolution10BitClampsRawTo10BitRange) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &output}, }}; - ST_LIB::ADCDomain::Init<1>::init(cfgs); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 4095U); - auto& adc = ST_LIB::ADCDomain::Init<1>::instances[0]; - adc.read(3.3, 1); + auto& adc = SingleADCInit::instances[0]; + adc.read(3.3f, 1U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 1023U); EXPECT_NEAR(output, 3.3f, 0.001f); } -TEST_F(ADCTest, TimeoutRecoversOnNextSuccessfulRead) { +TEST_F(ADCTest, InitWithoutDMAInstancesFailsInsteadOfConfiguringDMAAtRuntime) { + ST_LIB::TestErrorHandler::set_fail_on_error(false); + float output = -1.0f; const std::array cfgs{{ {.gpio_idx = 0, @@ -246,28 +556,24 @@ TEST_F(ADCTest, TimeoutRecoversOnNextSuccessfulRead) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &output}, }}; - ST_LIB::ADCDomain::Init<1>::init(cfgs); - auto& adc = ST_LIB::ADCDomain::Init<1>::instances[0]; + SingleADCInit::init(cfgs); - ST_LIB::MockedHAL::adc_set_poll_timeout(ADC1, true); - adc.read(3.3, 1); - EXPECT_NE(output, -1.0f); - EXPECT_NE((HAL_ADC_GetState(&hadc1) & HAL_ADC_STATE_TIMEOUT), 0U); - - ST_LIB::MockedHAL::adc_set_poll_timeout(ADC1, false); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 1000U); - adc.read(3.3, 1); - EXPECT_NEAR(output, (1000.0f / 4095.0f) * 3.3f, 0.001f); - EXPECT_EQ((HAL_ADC_GetState(&hadc1) & HAL_ADC_STATE_TIMEOUT), 0U); + EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(hadc1.DMA_Handle, nullptr); + EXPECT_FALSE(ST_LIB::MockedHAL::adc_is_dma_running(ADC1)); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_call_count(ST_LIB::MockedHAL::ADCOperation::StartDMA), 0U); } -TEST_F(ADCTest, SeparatePeripheralsAreIndependent) { - float out1 = -1.0f; - float out2 = -1.0f; - const std::array cfgs{{ +TEST_F(ADCTest, DMAStartFailureTriggersErrorPathAndLeavesInstanceUnreadable) { + ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::MockedHAL::dma_set_start_status(HAL_ERROR); + + float output = -1.0f; + const std::array cfgs{{ {.gpio_idx = 0, .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, .channel = ST_LIB::ADCDomain::Channel::CH16, @@ -275,33 +581,54 @@ TEST_F(ADCTest, SeparatePeripheralsAreIndependent) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, - .output = &out1}, + .dma_request = DMA_REQUEST_ADC1, + .output = &output}, + }}; + + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_FALSE(ST_LIB::MockedHAL::adc_is_dma_running(ADC1)); + EXPECT_EQ(SingleADCInit::instances[0].handle, nullptr); + EXPECT_FLOAT_EQ(SingleADCInit::instances[0].get_raw(), 0.0f); +} + +TEST_F(ADCTest, UnresolvedConfigDoesNotAliasAResolvedPeripheralInstance) { + ST_LIB::TestErrorHandler::set_fail_on_error(false); + + float unresolved = -1.0f; + float resolved = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::AUTO, + .channel = ST_LIB::ADCDomain::Channel::AUTO, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &unresolved}, {.gpio_idx = 1, - .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, - .channel = ST_LIB::ADCDomain::Channel::CH2, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, - .output = &out2}, + .dma_request = DMA_REQUEST_ADC1, + .output = &resolved}, }}; - ST_LIB::ADCDomain::Init<2>::init(cfgs); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 500U); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 3000U); - - auto& adc1 = ST_LIB::ADCDomain::Init<2>::instances[0]; - auto& adc2 = ST_LIB::ADCDomain::Init<2>::instances[1]; - adc1.read(3.3, 1); - adc2.read(3.3, 1); + init_adc_with_dma<2, shared_adc1_init_cfgs>(cfgs); - EXPECT_NEAR(out1, (500.0f / 4095.0f) * 3.3f, 0.001f); - EXPECT_NEAR(out2, (3000.0f / 4095.0f) * 3.3f, 0.001f); - EXPECT_EQ(ST_LIB::MockedHAL::adc_get_last_channel(ADC1), ADC_CHANNEL_16); - EXPECT_EQ(ST_LIB::MockedHAL::adc_get_last_channel(ADC2), ADC_CHANNEL_2); + EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(SharedADCInit::instances[0].handle, nullptr); + EXPECT_EQ(SharedADCInit::instances[0].dma_slot, nullptr); + EXPECT_EQ(SharedADCInit::instances[1].handle, &hadc1); + EXPECT_NE(SharedADCInit::instances[1].dma_slot, nullptr); } -TEST_F(ADCTest, PreStartedPeripheralStillReadsValue) { +TEST_F(ADCTest, TimedDMAWaitsForSequenceAndTransferCompletionBeforeUpdatingBuffer) { float output = -1.0f; const std::array cfgs{{ {.gpio_idx = 0, @@ -311,28 +638,204 @@ TEST_F(ADCTest, PreStartedPeripheralStillReadsValue) { .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, .output = &output}, }}; - ST_LIB::ADCDomain::Init<1>::init(cfgs); - ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 3000U); - - // Simulate external start so ADCDomain internal running cache is stale. - ADC_ChannelConfTypeDef sConfig{}; - sConfig.Channel = ADC_CHANNEL_16; - sConfig.Rank = ADC_REGULAR_RANK_1; - sConfig.SamplingTime = ADC_SAMPLETIME_8CYCLES_5; - sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.OffsetNumber = ADC_OFFSET_NONE; - sConfig.Offset = 0; -#if defined(ADC_VER_V5_V90) - sConfig.OffsetSignedSaturation = DISABLE; -#endif - ASSERT_EQ(HAL_ADC_ConfigChannel(&hadc1, &sConfig), HAL_OK); - ASSERT_EQ(HAL_ADC_Start(&hadc1), HAL_OK); - - auto& adc = ST_LIB::ADCDomain::Init<1>::instances[0]; - adc.read(3.3f, 1); - - EXPECT_NEAR(output, (3000.0f / 4095.0f) * 3.3f, 0.001f); + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::dma_set_transfer_timing(50ULL, 25ULL); + ST_LIB::MockedHAL::adc_set_channel_generator(ADC1, ADC_CHANNEL_16, [](uint64_t time_ns) { + return (time_ns < 1'000ULL) ? 321U : 654U; + }); + + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + auto& adc = SingleADCInit::instances[0]; + const uint64_t sequence_period_ns = ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ASSERT_GT(sequence_period_ns, 0U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 0U); + + advance_time_to(sequence_period_ns - 1ULL); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 0U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 0U); + + advance_time_to(sequence_period_ns); + const uint64_t completion_time_ns = + ST_LIB::MockedHAL::adc_get_pending_dma_completion_time_ns(ADC1); + ASSERT_GT(completion_time_ns, sequence_period_ns); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 0U); + + advance_time_to(completion_time_ns); + adc.read(3.3f, 1U); + + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_dma_value(ADC1, 0), 321U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 1U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); + EXPECT_NEAR(output, (321.0f / 4095.0f) * 3.3f, 0.001f); +} + +TEST_F(ADCTest, TimedDMADetectsOverrunWhenTransferCannotKeepUp) { + float out0 = -1.0f; + float out1 = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &out0}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH15, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_16, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &out1}, + }}; + + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 1'000U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_15, 2'000U); + + init_adc_with_dma<2, shared_adc1_init_cfgs>(cfgs); + + const uint64_t sequence_period_ns = ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ASSERT_GT(sequence_period_ns, 0U); + + ST_LIB::MockedHAL::dma_set_transfer_timing(sequence_period_ns * 2ULL, 0ULL); + ST_LIB::MockedHAL::adc_advance_time_ns(sequence_period_ns * 12ULL); + + EXPECT_LT(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 12U); + EXPECT_GT(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_scheduled_handle(), hadc1.DMA_Handle); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_schedule_bytes(), 4U); +} + +TEST_F(ADCTest, TimedDMASharedBusContentionShowsUpWithSimultaneousADCs) { + float out1 = -1.0f; + float out2 = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &out1}, + {.gpio_idx = 1, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_2, + .channel = ST_LIB::ADCDomain::Channel::CH2, + .resolution = ST_LIB::ADCDomain::Resolution::BITS_12, + .sample_time = ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, + .prescaler = ST_LIB::ADCDomain::ClockPrescaler::DIV1, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC2, + .output = &out2}, + }}; + + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC2, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC2, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 1'111U); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC2, ADC_CHANNEL_2, 2'222U); + + init_adc_with_dma<2, split_adc12_init_cfgs>(cfgs); + + const uint64_t sequence_period_ns = ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ASSERT_EQ(sequence_period_ns, ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC2)); + ASSERT_GT(sequence_period_ns, 0U); + + ST_LIB::MockedHAL::dma_set_transfer_timing(sequence_period_ns * 3ULL / 4ULL, 0ULL); + ST_LIB::MockedHAL::adc_advance_time_ns(sequence_period_ns * 10ULL); + + const uint64_t total_completed = ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1) + + ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC2); + const uint64_t total_overruns = ST_LIB::MockedHAL::adc_get_overrun_count(ADC1) + + ST_LIB::MockedHAL::adc_get_overrun_count(ADC2); + + EXPECT_LT(total_completed, 20U); + EXPECT_GT(total_overruns, 0U); +} + +TEST_F(ADCTest, TimedDMAFrequencySweepSeparatesStableAndUnstableOperatingRegions) { + constexpr std::array resolutions{ + ST_LIB::ADCDomain::Resolution::BITS_16, + ST_LIB::ADCDomain::Resolution::BITS_12, + ST_LIB::ADCDomain::Resolution::BITS_10, + ST_LIB::ADCDomain::Resolution::BITS_8, + }; + constexpr std::array sample_times{ + ST_LIB::ADCDomain::SampleTime::CYCLES_1_5, + ST_LIB::ADCDomain::SampleTime::CYCLES_8_5, + ST_LIB::ADCDomain::SampleTime::CYCLES_32_5, + ST_LIB::ADCDomain::SampleTime::CYCLES_387_5, + }; + constexpr std::array prescalers{ + ST_LIB::ADCDomain::ClockPrescaler::DIV1, + ST_LIB::ADCDomain::ClockPrescaler::DIV4, + ST_LIB::ADCDomain::ClockPrescaler::DIV16, + }; + + for (const auto resolution : resolutions) { + for (const auto sample_time : sample_times) { + for (const auto prescaler : prescalers) { + SCOPED_TRACE(static_cast(resolution)); + SCOPED_TRACE(static_cast(sample_time)); + SCOPED_TRACE(static_cast(prescaler)); + + reset_runtime_state(); + + float output = -1.0f; + const std::array cfgs{{ + {.gpio_idx = 0, + .peripheral = ST_LIB::ADCDomain::Peripheral::ADC_1, + .channel = ST_LIB::ADCDomain::Channel::CH16, + .resolution = resolution, + .sample_time = sample_time, + .prescaler = prescaler, + .sample_rate_hz = 0, + .dma_request = DMA_REQUEST_ADC1, + .output = &output}, + }}; + + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 777U); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + const uint64_t sequence_period_ns = + ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ASSERT_GT(sequence_period_ns, 0U); + + ST_LIB::MockedHAL::dma_set_transfer_timing(0ULL, 0ULL); + ST_LIB::MockedHAL::adc_advance_time_ns(sequence_period_ns * 8ULL); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); + EXPECT_EQ(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); + + reset_runtime_state(); + + ST_LIB::MockedHAL::adc_enable_timed_dma(ADC1, true); + ST_LIB::MockedHAL::adc_set_kernel_clock_hz(ADC1, 64'000'000ULL); + ST_LIB::MockedHAL::adc_set_channel_raw(ADC1, ADC_CHANNEL_16, 777U); + init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); + + const uint64_t unstable_period_ns = + ST_LIB::MockedHAL::adc_get_sequence_period_ns(ADC1); + ST_LIB::MockedHAL::dma_set_transfer_timing(unstable_period_ns * 2ULL, 0ULL); + ST_LIB::MockedHAL::adc_advance_time_ns(unstable_period_ns * 8ULL); + EXPECT_GT(ST_LIB::MockedHAL::adc_get_overrun_count(ADC1), 0U); + EXPECT_LT(ST_LIB::MockedHAL::adc_get_completed_sequence_count(ADC1), 8U); + } + } + } } diff --git a/Tests/dma2_test.cpp b/Tests/dma2_test.cpp index 4c2436c55..f7906f209 100644 --- a/Tests/dma2_test.cpp +++ b/Tests/dma2_test.cpp @@ -20,19 +20,63 @@ void DMA2_Stream7_IRQHandler(void); namespace { -constexpr std::array spi_dma_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::spi2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream0, +template struct DMAOnlyCtx { + std::array entries{}; + std::size_t size = 0; + + template + consteval std::size_t add(typename D::Entry e, const Owner*) { + const auto idx = size; + entries[size++] = e; + return idx; + } +}; + +struct DMARequestInscribeResult { + std::array entries{}; + std::array indices{}; +}; + +consteval DMARequestInscribeResult make_dma_request_inscribe_result() { + DMAOnlyCtx<3> ctx{}; + ST_LIB::DMADomain::DMA< + ST_LIB::DMADomain::Stream::dma1_stream6, + ST_LIB::DMADomain::Stream::dma2_stream3, + ST_LIB::DMADomain::Stream::none> + dma_request(ST_LIB::DMADomain::Peripheral::spi4); + + const auto indices = dma_request.inscribe(ctx); + return {.entries = ctx.entries, .indices = indices}; +} + +constexpr auto request_inscribe_result = make_dma_request_inscribe_result(); +static_assert(request_inscribe_result.indices[0] == 0); +static_assert(request_inscribe_result.indices[1] == 1); +static_assert(request_inscribe_result.indices[2] == 2); +static_assert(request_inscribe_result.entries[0].instance == ST_LIB::DMADomain::Peripheral::spi4); +static_assert(request_inscribe_result.entries[0].stream == ST_LIB::DMADomain::Stream::dma1_stream6); +static_assert(request_inscribe_result.entries[0].irqn == DMA1_Stream6_IRQn); +static_assert(request_inscribe_result.entries[0].id == 0); +static_assert(request_inscribe_result.entries[1].stream == ST_LIB::DMADomain::Stream::dma2_stream3); +static_assert(request_inscribe_result.entries[1].irqn == DMA2_Stream3_IRQn); +static_assert(request_inscribe_result.entries[1].id == 1); +static_assert(request_inscribe_result.entries[2].stream == ST_LIB::DMADomain::Stream::none); +static_assert(request_inscribe_result.entries[2].irqn == static_cast(0)); +static_assert(request_inscribe_result.entries[2].id == 2); + +constexpr std::array spi_dma_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream0, .irqn = DMA1_Stream0_IRQn, .id = 0}, - {.instance = ST_LIB::DMA_Domain::Peripheral::spi2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream1, + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream1, .irqn = DMA1_Stream1_IRQn, .id = 1}, }}; constexpr auto spi_dma_cfg = - ST_LIB::DMA_Domain::build<2>(std::span{spi_dma_entries}); + ST_LIB::DMADomain::build<2>(std::span{spi_dma_entries}); static_assert(std::get<1>(spi_dma_cfg[0].init_data).Request == DMA_REQUEST_SPI2_RX); static_assert(std::get<1>(spi_dma_cfg[1].init_data).Request == DMA_REQUEST_SPI2_TX); @@ -42,47 +86,91 @@ static_assert(std::get<1>(spi_dma_cfg[0].init_data).FIFOThreshold == DMA_FIFO_TH static_assert(std::get<1>(spi_dma_cfg[1].init_data).FIFOThreshold == DMA_FIFO_THRESHOLD_FULL); static_assert(std::get<3>(spi_dma_cfg[0].init_data) == DMA1_Stream0_IRQn); static_assert(std::get<3>(spi_dma_cfg[1].init_data) == DMA1_Stream1_IRQn); +static_assert(std::get<5>(spi_dma_cfg[0].init_data)); +static_assert(std::get<5>(spi_dma_cfg[1].init_data)); -constexpr std::array auto_stream_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::adc1, - .stream = ST_LIB::DMA_Domain::Stream::none, +constexpr std::array auto_stream_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::adc1, + .stream = ST_LIB::DMADomain::Stream::none, .irqn = 0, .id = 0}, - {.instance = ST_LIB::DMA_Domain::Peripheral::adc2, - .stream = ST_LIB::DMA_Domain::Stream::none, + {.instance = ST_LIB::DMADomain::Peripheral::adc2, + .stream = ST_LIB::DMADomain::Stream::none, .irqn = 0, .id = 0}, }}; constexpr auto auto_stream_cfg = - ST_LIB::DMA_Domain::build<2>(std::span{auto_stream_entries} - ); + ST_LIB::DMADomain::build<2>(std::span{auto_stream_entries}); +static_assert(std::get<2>(auto_stream_cfg[0].init_data) == ST_LIB::DMADomain::Stream::dma1_stream0); +static_assert(std::get<2>(auto_stream_cfg[1].init_data) == ST_LIB::DMADomain::Stream::dma1_stream1); +static_assert(std::get<3>(auto_stream_cfg[0].init_data) == DMA1_Stream0_IRQn); +static_assert(std::get<3>(auto_stream_cfg[1].init_data) == DMA1_Stream1_IRQn); + +constexpr std::array mixed_stream_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::spi3, + .stream = ST_LIB::DMADomain::Stream::dma1_stream4, + .irqn = DMA1_Stream4_IRQn, + .id = 0}, + {.instance = ST_LIB::DMADomain::Peripheral::adc1, + .stream = ST_LIB::DMADomain::Stream::none, + .irqn = 0, + .id = 0}, + {.instance = ST_LIB::DMADomain::Peripheral::adc2, + .stream = ST_LIB::DMADomain::Stream::none, + .irqn = 0, + .id = 0}, +}}; + +constexpr auto mixed_stream_cfg = + ST_LIB::DMADomain::build<3>(std::span{mixed_stream_entries}); static_assert( - std::get<2>(auto_stream_cfg[0].init_data) == ST_LIB::DMA_Domain::Stream::dma1_stream0 + std::get<2>(mixed_stream_cfg[0].init_data) == ST_LIB::DMADomain::Stream::dma1_stream4 ); static_assert( - std::get<2>(auto_stream_cfg[1].init_data) == ST_LIB::DMA_Domain::Stream::dma1_stream1 + std::get<2>(mixed_stream_cfg[1].init_data) == ST_LIB::DMADomain::Stream::dma1_stream0 ); -static_assert(std::get<3>(auto_stream_cfg[0].init_data) == DMA1_Stream0_IRQn); -static_assert(std::get<3>(auto_stream_cfg[1].init_data) == DMA1_Stream1_IRQn); +static_assert( + std::get<2>(mixed_stream_cfg[2].init_data) == ST_LIB::DMADomain::Stream::dma1_stream1 +); + +constexpr std::array adc_entry{{ + {.instance = ST_LIB::DMADomain::Peripheral::adc3, + .stream = ST_LIB::DMADomain::Stream::dma2_stream4, + .irqn = DMA2_Stream4_IRQn, + .id = 0}, +}}; -constexpr std::array fmac_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::fmac, - .stream = ST_LIB::DMA_Domain::Stream::dma2_stream0, +constexpr auto adc_cfg = + ST_LIB::DMADomain::build<1>(std::span{adc_entry}); +static_assert(std::get<1>(adc_cfg[0].init_data).Request == DMA_REQUEST_ADC3); +static_assert(std::get<1>(adc_cfg[0].init_data).Direction == DMA_PERIPH_TO_MEMORY); +static_assert(std::get<1>(adc_cfg[0].init_data).PeriphInc == DMA_PINC_DISABLE); +static_assert(std::get<1>(adc_cfg[0].init_data).MemInc == DMA_MINC_ENABLE); +static_assert(std::get<1>(adc_cfg[0].init_data).PeriphDataAlignment == DMA_PDATAALIGN_HALFWORD); +static_assert(std::get<1>(adc_cfg[0].init_data).MemDataAlignment == DMA_MDATAALIGN_HALFWORD); +static_assert(std::get<1>(adc_cfg[0].init_data).Mode == DMA_CIRCULAR); +static_assert(std::get<1>(adc_cfg[0].init_data).Priority == DMA_PRIORITY_LOW); +static_assert(std::get<1>(adc_cfg[0].init_data).FIFOMode == DMA_FIFOMODE_DISABLE); +static_assert(!std::get<5>(adc_cfg[0].init_data)); + +constexpr std::array fmac_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::fmac, + .stream = ST_LIB::DMADomain::Stream::dma2_stream0, .irqn = DMA2_Stream0_IRQn, .id = 0}, - {.instance = ST_LIB::DMA_Domain::Peripheral::fmac, - .stream = ST_LIB::DMA_Domain::Stream::dma2_stream1, + {.instance = ST_LIB::DMADomain::Peripheral::fmac, + .stream = ST_LIB::DMADomain::Stream::dma2_stream1, .irqn = DMA2_Stream1_IRQn, .id = 1}, - {.instance = ST_LIB::DMA_Domain::Peripheral::fmac, - .stream = ST_LIB::DMA_Domain::Stream::dma2_stream2, + {.instance = ST_LIB::DMADomain::Peripheral::fmac, + .stream = ST_LIB::DMADomain::Stream::dma2_stream2, .irqn = DMA2_Stream2_IRQn, .id = 2}, }}; constexpr auto fmac_cfg = - ST_LIB::DMA_Domain::build<3>(std::span{fmac_entries}); + ST_LIB::DMADomain::build<3>(std::span{fmac_entries}); static_assert(std::get<1>(fmac_cfg[0].init_data).Direction == DMA_MEMORY_TO_MEMORY); static_assert(std::get<1>(fmac_cfg[1].init_data).Direction == DMA_MEMORY_TO_PERIPH); static_assert(std::get<1>(fmac_cfg[2].init_data).Direction == DMA_PERIPH_TO_MEMORY); @@ -90,43 +178,43 @@ static_assert(std::get<1>(fmac_cfg[0].init_data).Request == DMA_REQUEST_MEM2MEM) static_assert(std::get<1>(fmac_cfg[1].init_data).Request == DMA_REQUEST_FMAC_WRITE); static_assert(std::get<1>(fmac_cfg[2].init_data).Request == DMA_REQUEST_FMAC_READ); -constexpr std::array irq_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::spi2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream0, +constexpr std::array irq_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream0, .irqn = DMA1_Stream0_IRQn, .id = 0}, - {.instance = ST_LIB::DMA_Domain::Peripheral::spi2, - .stream = ST_LIB::DMA_Domain::Stream::dma2_stream7, + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma2_stream7, .irqn = DMA2_Stream7_IRQn, .id = 1}, }}; constexpr auto irq_cfg = - ST_LIB::DMA_Domain::build<2>(std::span{irq_entries}); + ST_LIB::DMADomain::build<2>(std::span{irq_entries}); -constexpr std::array i2c_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::i2c2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream2, +constexpr std::array i2c_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::i2c2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream2, .irqn = DMA1_Stream2_IRQn, .id = 0}, - {.instance = ST_LIB::DMA_Domain::Peripheral::i2c2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream3, + {.instance = ST_LIB::DMADomain::Peripheral::i2c2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream3, .irqn = DMA1_Stream3_IRQn, .id = 1}, }}; constexpr auto i2c_cfg = - ST_LIB::DMA_Domain::build<2>(std::span{i2c_entries}); + ST_LIB::DMADomain::build<2>(std::span{i2c_entries}); -constexpr std::array none_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::none, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream4, +constexpr std::array none_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::none, + .stream = ST_LIB::DMADomain::Stream::dma1_stream4, .irqn = DMA1_Stream4_IRQn, .id = 0}, }}; constexpr auto none_cfg = - ST_LIB::DMA_Domain::build<1>(std::span{none_entries}); + ST_LIB::DMADomain::build<1>(std::span{none_entries}); void clear_nvic_enables() { for (auto& reg : NVIC->ISER) { @@ -153,12 +241,12 @@ class DMA2Test : public ::testing::Test { }; TEST_F(DMA2Test, InitConfiguresStreamsNVICAndLookupTable) { - ST_LIB::DMA_Domain::Init<2>::init(spi_dma_cfg); + ST_LIB::DMADomain::Init<2>::init(spi_dma_cfg); EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 2U); - auto& dma0 = ST_LIB::DMA_Domain::Init<2>::instances[0].dma; - auto& dma1 = ST_LIB::DMA_Domain::Init<2>::instances[1].dma; + auto& dma0 = ST_LIB::DMADomain::Init<2>::instances[0].dma; + auto& dma1 = ST_LIB::DMADomain::Init<2>::instances[1].dma; EXPECT_EQ(dma0.Instance, DMA1_Stream0); EXPECT_EQ(dma1.Instance, DMA1_Stream1); @@ -171,9 +259,20 @@ TEST_F(DMA2Test, InitConfiguresStreamsNVICAndLookupTable) { EXPECT_EQ(dma_irq_table[1], &dma1); } +TEST_F(DMA2Test, InitKeepsADCDMARegisteredButLeavesItsNVICDisabled) { + ST_LIB::DMADomain::Init<1>::init(adc_cfg); + + auto& dma = ST_LIB::DMADomain::Init<1>::instances[0].dma; + + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 1U); + EXPECT_EQ(dma.Instance, DMA2_Stream4); + EXPECT_EQ(dma_irq_table[12], &dma); + EXPECT_EQ(NVIC_GetEnableIRQ(DMA2_Stream4_IRQn), 0U); +} + TEST_F(DMA2Test, StartForwardsTransferParametersToHALDMA) { - ST_LIB::DMA_Domain::Init<2>::init(spi_dma_cfg); - auto& instance = ST_LIB::DMA_Domain::Init<2>::instances[0]; + ST_LIB::DMADomain::Init<2>::init(spi_dma_cfg); + auto& instance = ST_LIB::DMADomain::Init<2>::instances[0]; instance.start(0x1111U, 0x2222U, 128U); @@ -184,11 +283,32 @@ TEST_F(DMA2Test, StartForwardsTransferParametersToHALDMA) { EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_start_length(), 128U); } +TEST_F(DMA2Test, ScheduledTransferTimingSerializesSharedBusUsage) { + ST_LIB::DMADomain::Init<2>::init(spi_dma_cfg); + auto& rx = ST_LIB::DMADomain::Init<2>::instances[0].dma; + auto& tx = ST_LIB::DMADomain::Init<2>::instances[1].dma; + ST_LIB::MockedHAL::dma_set_transfer_timing(100ULL, 10ULL); + + const uint64_t first_completion = ST_LIB::MockedHAL::dma_schedule_transfer(&rx, 1'000ULL, 4U); + const uint64_t second_completion = ST_LIB::MockedHAL::dma_schedule_transfer(&tx, 1'000ULL, 4U); + + EXPECT_EQ(first_completion, 1'140ULL); + EXPECT_EQ(second_completion, 1'280ULL); + EXPECT_EQ( + ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::ScheduleTransfer), + 2U + ); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_scheduled_handle(), &tx); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_schedule_request_time_ns(), 1'000ULL); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_schedule_completion_time_ns(), 1'280ULL); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_last_schedule_bytes(), 4U); +} + TEST_F(DMA2Test, InitFailureTriggersErrorAndSkipsRegistration) { ST_LIB::TestErrorHandler::set_fail_on_error(false); ST_LIB::MockedHAL::dma_set_init_status(HAL_ERROR); - ST_LIB::DMA_Domain::Init<2>::init(spi_dma_cfg); + ST_LIB::DMADomain::Init<2>::init(spi_dma_cfg); EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 2U); EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 2); @@ -197,10 +317,10 @@ TEST_F(DMA2Test, InitFailureTriggersErrorAndSkipsRegistration) { } TEST_F(DMA2Test, InitAppliesI2CDirectionAndAlignmentAtRuntime) { - ST_LIB::DMA_Domain::Init<2>::init(i2c_cfg); + ST_LIB::DMADomain::Init<2>::init(i2c_cfg); - auto& rx = ST_LIB::DMA_Domain::Init<2>::instances[0].dma; - auto& tx = ST_LIB::DMA_Domain::Init<2>::instances[1].dma; + auto& rx = ST_LIB::DMADomain::Init<2>::instances[0].dma; + auto& tx = ST_LIB::DMADomain::Init<2>::instances[1].dma; EXPECT_EQ(rx.Init.Request, DMA_REQUEST_I2C2_RX); EXPECT_EQ(tx.Init.Request, DMA_REQUEST_I2C2_TX); @@ -212,10 +332,45 @@ TEST_F(DMA2Test, InitAppliesI2CDirectionAndAlignmentAtRuntime) { EXPECT_EQ(tx.Init.Mode, DMA_CIRCULAR); } +TEST_F(DMA2Test, InitUsesPrecomputedDMAInitDataWithoutRuntimeReconfiguration) { + auto mutated_cfg = spi_dma_cfg; + std::get<0>(mutated_cfg[0].init_data) = ST_LIB::DMADomain::Peripheral::adc1; + std::get<4>(mutated_cfg[0].init_data) = 0; + std::get<0>(mutated_cfg[1].init_data) = ST_LIB::DMADomain::Peripheral::adc2; + std::get<4>(mutated_cfg[1].init_data) = 0; + std::get<5>(mutated_cfg[0].init_data) = true; + std::get<5>(mutated_cfg[1].init_data) = true; + + ST_LIB::DMADomain::Init<2>::init(mutated_cfg); + + auto& rx = ST_LIB::DMADomain::Init<2>::instances[0].dma; + auto& tx = ST_LIB::DMADomain::Init<2>::instances[1].dma; + + EXPECT_EQ(rx.Init.Request, DMA_REQUEST_SPI2_RX); + EXPECT_EQ(tx.Init.Request, DMA_REQUEST_SPI2_TX); + EXPECT_EQ(rx.Init.Direction, DMA_PERIPH_TO_MEMORY); + EXPECT_EQ(tx.Init.Direction, DMA_MEMORY_TO_PERIPH); +} + +TEST_F(DMA2Test, InitRejectsCorruptedConfigWithNoAssignedStream) { + ST_LIB::TestErrorHandler::set_fail_on_error(false); + + auto corrupted_cfg = spi_dma_cfg; + std::get<2>(corrupted_cfg[0].init_data) = ST_LIB::DMADomain::Stream::none; + std::get<3>(corrupted_cfg[0].init_data) = static_cast(0); + + ST_LIB::DMADomain::Init<2>::init(corrupted_cfg); + + EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 1U); + EXPECT_EQ(dma_irq_table[0], nullptr); + EXPECT_EQ(dma_irq_table[1], &ST_LIB::DMADomain::Init<2>::instances[1].dma); +} + TEST_F(DMA2Test, InitAppliesNonePeripheralSettingsAtRuntime) { - ST_LIB::DMA_Domain::Init<1>::init(none_cfg); + ST_LIB::DMADomain::Init<1>::init(none_cfg); - auto& inst = ST_LIB::DMA_Domain::Init<1>::instances[0].dma; + auto& inst = ST_LIB::DMADomain::Init<1>::instances[0].dma; EXPECT_EQ(inst.Init.Request, DMA_REQUEST_MEM2MEM); EXPECT_EQ(inst.Init.Direction, DMA_MEMORY_TO_MEMORY); EXPECT_EQ(inst.Init.PeriphInc, DMA_PINC_ENABLE); @@ -224,11 +379,11 @@ TEST_F(DMA2Test, InitAppliesNonePeripheralSettingsAtRuntime) { } TEST_F(DMA2Test, InitAppliesFMACSpecialSettingsAtRuntime) { - ST_LIB::DMA_Domain::Init<3>::init(fmac_cfg); + ST_LIB::DMADomain::Init<3>::init(fmac_cfg); - auto& m2m = ST_LIB::DMA_Domain::Init<3>::instances[0].dma; - auto& write = ST_LIB::DMA_Domain::Init<3>::instances[1].dma; - auto& read = ST_LIB::DMA_Domain::Init<3>::instances[2].dma; + auto& m2m = ST_LIB::DMADomain::Init<3>::instances[0].dma; + auto& write = ST_LIB::DMADomain::Init<3>::instances[1].dma; + auto& read = ST_LIB::DMADomain::Init<3>::instances[2].dma; EXPECT_EQ(m2m.Init.Request, DMA_REQUEST_MEM2MEM); EXPECT_EQ(write.Init.Request, DMA_REQUEST_FMAC_WRITE); @@ -248,9 +403,9 @@ TEST_F(DMA2Test, InitAppliesFMACSpecialSettingsAtRuntime) { } TEST_F(DMA2Test, IRQHandlersDispatchMappedDMAHandles) { - ST_LIB::DMA_Domain::Init<2>::init(irq_cfg); - auto& dma0 = ST_LIB::DMA_Domain::Init<2>::instances[0].dma; - auto& dma1 = ST_LIB::DMA_Domain::Init<2>::instances[1].dma; + ST_LIB::DMADomain::Init<2>::init(irq_cfg); + auto& dma0 = ST_LIB::DMADomain::Init<2>::instances[0].dma; + auto& dma1 = ST_LIB::DMADomain::Init<2>::instances[1].dma; DMA1_Stream0_IRQHandler(); EXPECT_EQ( diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index e0d50fb6b..ad081703f 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -62,19 +62,19 @@ constexpr auto compile_time_cfg = static_assert(compile_time_cfg[0].peripheral == ST_LIB::SPIDomain::SPIPeripheral::spi2); static_assert(compile_time_cfg[0].mode == ST_LIB::SPIDomain::SPIMode::MASTER); -constexpr std::array dma_entries{{ - {.instance = ST_LIB::DMA_Domain::Peripheral::spi2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream0, +constexpr std::array dma_entries{{ + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream0, .irqn = DMA1_Stream0_IRQn, .id = 0}, - {.instance = ST_LIB::DMA_Domain::Peripheral::spi2, - .stream = ST_LIB::DMA_Domain::Stream::dma1_stream1, + {.instance = ST_LIB::DMADomain::Peripheral::spi2, + .stream = ST_LIB::DMADomain::Stream::dma1_stream1, .irqn = DMA1_Stream1_IRQn, .id = 1}, }}; constexpr auto dma_cfg = - ST_LIB::DMA_Domain::build<2>(std::span{dma_entries}); + ST_LIB::DMADomain::build<2>(std::span{dma_entries}); void clear_nvic_enables() { for (auto& reg : NVIC->ISER) { @@ -102,7 +102,7 @@ class SPI2Test : public ::testing::Test { for (auto& inst : ST_LIB::SPIDomain::spi_instances) { inst = nullptr; } - ST_LIB::DMA_Domain::Init<2>::init(dma_cfg); + ST_LIB::DMADomain::Init<2>::init(dma_cfg); } template @@ -128,7 +128,7 @@ class SPI2Test : public ::testing::Test { ST_LIB::SPIDomain::Init<1>::init( cfgs, std::span{}, - std::span(ST_LIB::DMA_Domain::Init<2>::instances) + std::span(ST_LIB::DMADomain::Init<2>::instances) ); return ST_LIB::SPIDomain::Init<1>::instances[0]; } @@ -148,25 +148,25 @@ TEST_F(SPI2Test, InitMasterConfiguresPeripheralDMAAndNVIC) { EXPECT_EQ(hspi->Init.DataSize, 7U); EXPECT_EQ(hspi->Init.BaudRatePrescaler, SPI_BAUDRATEPRESCALER_4); - EXPECT_EQ(hspi->hdmarx, &ST_LIB::DMA_Domain::Init<2>::instances[0].dma); - EXPECT_EQ(hspi->hdmatx, &ST_LIB::DMA_Domain::Init<2>::instances[1].dma); + EXPECT_EQ(hspi->hdmarx, &ST_LIB::DMADomain::Init<2>::instances[0].dma); + EXPECT_EQ(hspi->hdmatx, &ST_LIB::DMADomain::Init<2>::instances[1].dma); EXPECT_EQ(hspi->hdmarx->Parent, hspi); EXPECT_EQ(hspi->hdmatx->Parent, hspi); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[0].dma.Init.PeriphDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[0].dma.Init.PeriphDataAlignment, DMA_PDATAALIGN_BYTE ); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[0].dma.Init.MemDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[0].dma.Init.MemDataAlignment, DMA_MDATAALIGN_BYTE ); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[1].dma.Init.PeriphDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[1].dma.Init.PeriphDataAlignment, DMA_PDATAALIGN_BYTE ); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[1].dma.Init.MemDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[1].dma.Init.MemDataAlignment, DMA_MDATAALIGN_BYTE ); @@ -186,19 +186,19 @@ TEST_F(SPI2Test, InitWith32BitDataUsesWordAlignmentAndPrescaler) { EXPECT_EQ(hspi->Init.BaudRatePrescaler, SPI_BAUDRATEPRESCALER_8); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[0].dma.Init.PeriphDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[0].dma.Init.PeriphDataAlignment, DMA_PDATAALIGN_WORD ); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[0].dma.Init.MemDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[0].dma.Init.MemDataAlignment, DMA_MDATAALIGN_WORD ); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[1].dma.Init.PeriphDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[1].dma.Init.PeriphDataAlignment, DMA_PDATAALIGN_WORD ); EXPECT_EQ( - ST_LIB::DMA_Domain::Init<2>::instances[1].dma.Init.MemDataAlignment, + ST_LIB::DMADomain::Init<2>::instances[1].dma.Init.MemDataAlignment, DMA_MDATAALIGN_WORD ); } diff --git a/ci/Dockerfile b/ci/Dockerfile index a44409128..62ca6205b 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -1,32 +1,45 @@ FROM ubuntu:24.04 ARG DEBIAN_FRONTEND=noninteractive -ARG CUBECLT_VERSION=1.16.0 +ARG CUBECLT_VERSION=1.21.0 ARG CUBECLT_INSTALLER=cubeclt_${CUBECLT_VERSION}_installer.sh +ARG UV_INSTALLER_URL=https://astral.sh/uv/install.sh RUN echo "deb http://security.ubuntu.com/ubuntu focal-security main universe" > /etc/apt/sources.list.d/ubuntu-focal-sources.list && \ apt-get update && \ apt-get install -y --no-install-recommends \ - git git-lfs curl \ + bash ca-certificates curl git git-lfs \ python3 python3-pip python3-venv \ cmake ninja-build \ g++ build-essential \ libncurses5 libusb-1.0-0 \ gdb && \ - git lfs install && \ + git lfs install --system && \ rm -rf /var/lib/apt/lists/* +RUN curl -LsSf "${UV_INSTALLER_URL}" | sh && \ + ln -sf /root/.local/bin/uv /usr/local/bin/uv && \ + python3 -m pip install --break-system-packages \ + Jinja2==3.1.5 \ + GitPython==3.1.43 + # Install cubeclt (STM32CubeCLT) WORKDIR /tmp RUN git clone --depth 1 https://github.com/Hyperloop-UPV/cubeclt.git && \ cd cubeclt && \ - git lfs pull && \ + git lfs pull --include="${CUBECLT_INSTALLER}" && \ chmod +x ${CUBECLT_INSTALLER} && \ echo | LICENSE_ALREADY_ACCEPTED=1 ./${CUBECLT_INSTALLER} && \ + actual_root="$(find /opt/st -maxdepth 1 -type d -name "stm32cubeclt_${CUBECLT_VERSION}*" | head -n1)" && \ + test -n "${actual_root}" && \ + mkdir -p /opt/ST && \ + ln -s "${actual_root}" "/opt/ST/STM32CubeCLT_${CUBECLT_VERSION}" && \ cd / && \ rm -rf /tmp/cubeclt -ENV PATH="$PATH:/opt/st/stm32cubeclt_${CUBECLT_VERSION}/GNU-tools-for-STM32/bin:/opt/st/stm32cubeclt_${CUBECLT_VERSION}/CMake/bin:/opt/st/stm32cubeclt_${CUBECLT_VERSION}/Ninja/bin" +ENV STM32_CLT_ROOT=/opt/ST/STM32CubeCLT_${CUBECLT_VERSION} +ENV HYPER_STM32CLT_ROOT=/opt/ST/STM32CubeCLT_${CUBECLT_VERSION} +ENV PATH="/root/.local/bin:${STM32_CLT_ROOT}/GNU-tools-for-STM32/bin:${STM32_CLT_ROOT}/STM32CubeProgrammer/bin:${STM32_CLT_ROOT}/CMake/bin:${STM32_CLT_ROOT}/Ninja/bin:${PATH}" WORKDIR /workspace diff --git a/docs/st-lib-board-contract.md b/docs/st-lib-board-contract.md new file mode 100644 index 000000000..404d99871 --- /dev/null +++ b/docs/st-lib-board-contract.md @@ -0,0 +1,194 @@ +# ST-LIB Board Contract + +This document defines the contract of [`Inc/ST-LIB.hpp`](../Inc/ST-LIB.hpp), especially the +`ST_LIB::BuildCtx` and `ST_LIB::Board` machinery. + +If you change a domain, add a new domain, or add a cross-domain composition rule, read this first. + +## 1. Mental Model + +`Board<...>` is a compile-time build pipeline plus a runtime init pipeline. + +- Compile time decides what exists and how it must be configured. +- Runtime only materializes already-built configurations and links HAL handles. + +`Board` is intentionally declarative. Request objects describe intent; domains convert that intent +into concrete configs. + +## 2. Domain Contract + +Every domain used by `BuildCtx` and `Board` must provide: + +- `static constexpr std::size_t max_instances` +- `struct Entry` +- `struct Config` +- `template static consteval std::array build(std::span)` +- `template struct Init` + +`Init` must provide: + +- `static inline std::array instances` +- `static void init(std::span ...)` + +The exact runtime dependencies of `init(...)` are domain-specific, but they must be explicit in the +signature. + +## 3. Request Object Contract + +A request object that can be used inside `Board<...>` must provide: + +- `using domain = ;` +- `template consteval std::size_t inscribe(Ctx&) const` + +or any compatible return type if it naturally inscribes multiple dependent entries. + +`inscribe(ctx)` must: + +- be `consteval` +- append the domain entries needed by the request +- return indices only for later compile-time wiring +- never depend on runtime state + +## 4. BuildCtx Contract + +`BuildCtx` is append-only. + +Important consequences: + +- `BuildCtx::add(...)` does not deduplicate. +- If two requests emit the same physical resource twice, `BuildCtx` will keep both. +- Preventing duplicates is the responsibility of request objects or the destination domain build + logic. + +This is a deliberate design choice. `BuildCtx` is a storage and ownership map, not a resolver. + +## 5. Board Build Contract + +`Board::build_ctx()`: + +- creates a `DomainsCtx` +- evaluates every request object's `inscribe(ctx)` in declaration order + +`Board::build()`: + +- computes domain sizes +- runs each domain `build(...)` +- assembles a `ConfigBundle` + +`Board::cfg` is the compile-time result of that process. + +No runtime-only configuration logic belongs here. + +## 6. Board Init Contract + +`Board::init()`: + +- must only consume `cfg` +- must initialize domains in dependency order +- must not invent new hardware resources +- must not re-resolve compile-time relationships + +Permitted runtime work: + +- HAL init +- clock enable +- IRQ enable +- handle linking +- buffer allocation if the buffer is inherently runtime memory +- starting peripherals using already-built configs + +Forbidden runtime work: + +- choosing a DMA request dynamically +- choosing a stream dynamically +- changing a domain topology +- creating extra domain entries not represented in `cfg` + +## 7. Owner Mapping Contract + +`Board::instance_of()` relies on owner pointers captured during `BuildCtx::add(...)`. + +Therefore: + +- each stored `Entry` must be associated with the request object that owns it +- owner identity must remain stable across compilation +- `instance_of` is only valid for request objects actually inscribed into `Board` + +## 8. DMA Composition Contract + +There are two valid ways for a domain to use `DMADomain`. + +### 8.1 Direct inscription + +Use this when one request object maps directly to one or more DMA resources. + +Example: SPI. + +The request object may hold `DMADomain::DMA<...>` and inscribe it directly. + +### 8.2 Compile-time DMA contributions + +Use this when multiple request objects share one physical DMA resource and direct inscription would +duplicate it. + +Example: ADC, where many channel requests can belong to one ADC peripheral and one DMA stream. + +In this case, the domain must expose compile-time contribution helpers: + +- `*_contribution_count(...)` +- `build_*_contributions<...>(...)` + +Those helpers must: + +- be `consteval` +- be additive only +- preserve all pre-existing `DMADomain` entries +- synthesize only the missing DMA entries for that domain + +`Board` may then merge base DMA entries with these contributions through generic helpers such as +`BuildUtils::merge_dma_entries(...)` and `BuildUtils::build_dma_configs(...)`. + +## 9. ADC-Specific Rule + +`ADCDomain` currently uses the DMA contribution pattern. + +Reason: + +- request granularity is one ADC channel request +- physical DMA granularity is one DMA stream per ADC peripheral + +If ADC ever changes to a request type that directly represents a full ADC peripheral, it could move +to the direct inscription pattern used by SPI. + +## 10. How To Add a New DMA-Using Domain + +If the domain has a 1:1 request-to-DMA mapping: + +1. Inscribe `DMADomain::DMA<...>` directly from the request object. +2. Store DMA indices in the domain `Entry`. +3. Use those indices during domain `build/init`. + +If the domain has a many:1 request-to-DMA mapping: + +1. Keep the domain request objects focused on their logical resource. +2. Build the domain configs first. +3. Expose compile-time DMA contribution helpers for the missing shared DMA entries. +4. Merge those contributions into the base `DMADomain` entries in `Board::build()`. + +## 11. Non-Negotiable Invariants + +- Topology is compile-time. +- `BuildCtx` is append-only and non-deduplicating. +- `cfg` is the full source of truth for runtime init. +- Runtime init may materialize resources, but not invent topology. +- Cross-domain composition must be explicit and compile-time. + +## 12. Practical Review Checklist + +When reviewing a change to `Board`, a domain, or a DMA-using peripheral, verify: + +- Does the request object inscribe only compile-time information? +- Can duplicate physical resources be produced? If yes, where are they prevented? +- Does runtime init consume only `cfg`? +- Is any stream/request/allocation decision being made too late? +- If the domain uses shared DMA, is it contributing only missing entries and preserving the rest? diff --git a/toolchains/stm32.cmake b/toolchains/stm32.cmake index 0c1e00475..ceb0c22be 100644 --- a/toolchains/stm32.cmake +++ b/toolchains/stm32.cmake @@ -1,31 +1,114 @@ set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR ARM) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +set(STM32_CLT_REQUIRED_VERSION "1.21.0" CACHE STRING "Required STM32CubeCLT version") +set(TOOLCHAIN_PREFIX arm-none-eabi-) + +set(_stm32_clt_candidate_roots "") -if(MINGW OR CYGWIN OR WIN32) - set(UTIL_SEARCH_CMD where) -elseif(UNIX OR APPLE) - set(UTIL_SEARCH_CMD which) +if(DEFINED ENV{STM32_CLT_ROOT} AND NOT "$ENV{STM32_CLT_ROOT}" STREQUAL "") + list(APPEND _stm32_clt_candidate_roots "$ENV{STM32_CLT_ROOT}") +endif() +if(DEFINED ENV{HYPER_STM32CLT_ROOT} AND NOT "$ENV{HYPER_STM32CLT_ROOT}" STREQUAL "") + list(APPEND _stm32_clt_candidate_roots "$ENV{HYPER_STM32CLT_ROOT}") endif() -set(TOOLCHAIN_PREFIX arm-none-eabi-) +if(WIN32) + if(DEFINED ENV{ProgramFiles} AND NOT "$ENV{ProgramFiles}" STREQUAL "") + list(APPEND _stm32_clt_candidate_roots + "$ENV{ProgramFiles}/STMicroelectronics/STM32Cube/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}" + "$ENV{ProgramFiles}/STMicroelectronics/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}" + ) + file(GLOB _stm32_clt_globbed LIST_DIRECTORIES true + "$ENV{ProgramFiles}/STMicroelectronics/STM32Cube/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}*" + "$ENV{ProgramFiles}/STMicroelectronics/STM32Cube/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}*" + "$ENV{ProgramFiles}/STMicroelectronics/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}*" + "$ENV{ProgramFiles}/STMicroelectronics/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}*" + ) + list(APPEND _stm32_clt_candidate_roots ${_stm32_clt_globbed}) + endif() + list(APPEND _stm32_clt_candidate_roots + "C:/ST/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}" + "C:/ST/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}" + ) + file(GLOB _stm32_clt_globbed_c LIST_DIRECTORIES true + "C:/ST/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}*" + "C:/ST/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}*" + ) + list(APPEND _stm32_clt_candidate_roots ${_stm32_clt_globbed_c}) +else() + list(APPEND _stm32_clt_candidate_roots + "/opt/ST/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}" + "/opt/ST/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}" + "$ENV{HOME}/ST/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}" + "$ENV{HOME}/ST/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}" + ) + file(GLOB _stm32_clt_globbed LIST_DIRECTORIES true + "/opt/ST/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}*" + "/opt/ST/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}*" + "$ENV{HOME}/ST/STM32CubeCLT_${STM32_CLT_REQUIRED_VERSION}*" + "$ENV{HOME}/ST/STM32CubeCLT-${STM32_CLT_REQUIRED_VERSION}*" + ) + list(APPEND _stm32_clt_candidate_roots ${_stm32_clt_globbed}) +endif() -execute_process( - COMMAND ${UTIL_SEARCH_CMD} ${TOOLCHAIN_PREFIX}gcc - OUTPUT_VARIABLE BINUTILS_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE -) +list(REMOVE_DUPLICATES _stm32_clt_candidate_roots) -get_filename_component(ARM_TOOLCHAIN_DIR ${BINUTILS_PATH} DIRECTORY) -set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(STM32_CLT_ROOT "") +foreach(_candidate IN LISTS _stm32_clt_candidate_roots) + if(EXISTS "${_candidate}/GNU-tools-for-STM32/bin/${TOOLCHAIN_PREFIX}gcc") + set(STM32_CLT_ROOT "${_candidate}") + break() + endif() +endforeach() + +if(NOT STM32_CLT_ROOT) + find_program(_stm32_arm_gcc NAMES ${TOOLCHAIN_PREFIX}gcc) + if(NOT _stm32_arm_gcc) + message(FATAL_ERROR + "STM32CubeCLT ${STM32_CLT_REQUIRED_VERSION} is required, but arm-none-eabi-gcc was not found. " + "Install the required CLT and/or set STM32_CLT_ROOT." + ) + endif() + + get_filename_component(_stm32_arm_gcc_realpath "${_stm32_arm_gcc}" REALPATH) + string(REGEX MATCH "STM32CubeCLT[_-]([0-9]+\\.[0-9]+\\.[0-9]+)" _stm32_path_match "${_stm32_arm_gcc_realpath}") + + if(NOT CMAKE_MATCH_1) + message(FATAL_ERROR + "STM32CubeCLT ${STM32_CLT_REQUIRED_VERSION} is required, but the compiler in PATH " + "does not point to a versioned STM32CubeCLT installation:\n ${_stm32_arm_gcc_realpath}" + ) + endif() + + if(NOT CMAKE_MATCH_1 STREQUAL STM32_CLT_REQUIRED_VERSION) + message(FATAL_ERROR + "STM32CubeCLT ${STM32_CLT_REQUIRED_VERSION} is required, but found ${CMAKE_MATCH_1}:\n ${_stm32_arm_gcc_realpath}" + ) + endif() + + get_filename_component(_stm32_bin_dir "${_stm32_arm_gcc_realpath}" DIRECTORY) + get_filename_component(_stm32_gnu_dir "${_stm32_bin_dir}" DIRECTORY) + get_filename_component(STM32_CLT_ROOT "${_stm32_gnu_dir}" DIRECTORY) +endif() + +string(REGEX MATCH "STM32CubeCLT[_-]([0-9]+\\.[0-9]+\\.[0-9]+)" _stm32_root_match "${STM32_CLT_ROOT}") +if(CMAKE_MATCH_1 AND NOT CMAKE_MATCH_1 STREQUAL STM32_CLT_REQUIRED_VERSION) + message(FATAL_ERROR + "STM32CubeCLT ${STM32_CLT_REQUIRED_VERSION} is required, but selected root is ${CMAKE_MATCH_1}:\n ${STM32_CLT_ROOT}" + ) +endif() -set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) -set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) -set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++) +set(ARM_TOOLCHAIN_DIR "${STM32_CLT_ROOT}/GNU-tools-for-STM32/bin") +set(CMAKE_C_COMPILER "${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}gcc") +set(CMAKE_ASM_COMPILER "${CMAKE_C_COMPILER}") +set(CMAKE_CXX_COMPILER "${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}g++") -set(CMAKE_OBJCOPY ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objcopy CACHE INTERNAL "objcopy tool") -set(CMAKE_SIZE_UTIL ${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}size CACHE INTERNAL "size tool") +set(CMAKE_OBJCOPY "${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}objcopy" CACHE INTERNAL "objcopy tool") +set(CMAKE_SIZE_UTIL "${ARM_TOOLCHAIN_DIR}/${TOOLCHAIN_PREFIX}size" CACHE INTERNAL "size tool") -set(CMAKE_FIND_ROOT_PATH ${BINUTILS_PATH}) +set(CMAKE_FIND_ROOT_PATH "${STM32_CLT_ROOT}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) From 683b056b4565c8470b381c727d989b259fac2d60 Mon Sep 17 00:00:00 2001 From: oganigl Date: Wed, 25 Mar 2026 01:01:00 +0100 Subject: [PATCH 36/41] compiles, but not tested --- Inc/HALAL/Models/DMA/DMA2.hpp | 6 +- Inc/HALAL/Services/ADC/ADC.hpp | 4 +- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 304 +++++++++++++++++++---------- Inc/ST-LIB.hpp | 33 +++- 4 files changed, 241 insertions(+), 106 deletions(-) diff --git a/Inc/HALAL/Models/DMA/DMA2.hpp b/Inc/HALAL/Models/DMA/DMA2.hpp index a2e56f51c..ce2a50322 100644 --- a/Inc/HALAL/Models/DMA/DMA2.hpp +++ b/Inc/HALAL/Models/DMA/DMA2.hpp @@ -223,7 +223,11 @@ struct DMADomain { static consteval bool is_fmac(Peripheral instance) { return instance == Peripheral::fmac; } static consteval bool is_none(Peripheral instance) { return instance == Peripheral::none; } - + + static consteval bool is_dfsdm(Peripheral instance) { + return is_one_of(instance,Peripheral::dfsdm_filter0,Peripheral::dfsdm_filter1,Peripheral::dfsdm_filter2,Peripheral::dfsdm_filter3); + } + static consteval uint32_t get_Request(Peripheral instance, uint8_t i) { if (instance == Peripheral::none) return DMA_REQUEST_MEM2MEM; diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 6b28ebbe5..4aa3ca276 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -262,7 +262,7 @@ struct ADCDomain { std::size_t adc3_size = 0; }; - static consteval BufferSizes calculate_buffer_sizes(span cfgs) { + static consteval BufferSizes _buffer_sizes(span cfgs) { BufferSizes sizes; for (const auto& cfg : cfgs) { switch (cfg.peripheral) { @@ -741,7 +741,7 @@ struct ADCDomain { template cfgs> struct Init { static inline std::array instances{}; - static constexpr auto buffer_sizes = calculate_buffer_sizes(cfgs); + static constexpr auto buffer_sizes = _buffer_sizes(cfgs); static constexpr std::size_t total_dma_slots = buffer_sizes.adc1_size + buffer_sizes.adc2_size + buffer_sizes.adc3_size; static_assert( diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 14db51f76..3dc1e893d 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -1,8 +1,12 @@ #pragma once +#include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Models/GPIO.hpp" #include "HALAL/Models/Pin.hpp" #include "HALAL/Models/DMA/DMA2.hpp" +#include "HALAL/Models/MPU.hpp" + +#define STLIB_DFSDM_DMA_BUFFER_ATTR D1_NC #define Oversampling_MAX 1024 #define Oversampling_MAX_Filter_4 215 @@ -232,16 +236,13 @@ struct Config_Filter{ Config_Filter config_filter; uint8_t channel; size_t gpio_idx; - size_t dma_idx; size_t buffer_size; }; static constexpr size_t max_instances{8}; - template struct DFSDM_CHANNEL{ using domain = DFSDM_CHANNEL_DOMAIN; const GPIODomain::Pin& pin; - DMA_Domain::DMA dma; GPIODomain::GPIO gpio; const Config_Channel config_channel; const Config_Filter config_filter; @@ -249,14 +250,12 @@ struct Config_Filter{ size_t buffer_size; consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter) : pin(pin), - dma(get_dma_peripheral(config_filter.filter)), gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, config_channel(config_channel), config_filter(config_filter), buffer_size(config_filter.length_buffer) - { + { channel = get_channel(pin); - if(config_filter.filter > 3){ compile_error("Solo hay 4 filtros [0..3]"); } @@ -283,13 +282,11 @@ struct Config_Filter{ template consteval std::size_t inscribe(Ctx &ctx) const { const auto gpio_idx = gpio.inscribe(ctx); - const auto dma_idx = dma.inscribe(ctx); Entry e{ .config_channel = config_channel, .config_filter = config_filter, .channel = channel, .gpio_idx = gpio_idx, - .dma_idx = dma_idx[0],//There can only be one stream .buffer_size = buffer_size }; return ctx.template add(e, this); @@ -311,7 +308,7 @@ struct Config_Filter{ }; struct Config { size_t gpio_idx; - size_t dma_idx; + size_t dma_request; FilterConfig init_data_filter; ChannelConfig init_data_channel; @@ -320,11 +317,10 @@ struct Config_Filter{ Dma dma_enable; uint8_t filter; - uint8_t filter_samples; uint8_t channel; size_t buffer_size; - int32_t* buffer; + size_t buffer_pos_ini; //callbacks Callback watchdog_callback{nullptr}; @@ -333,6 +329,49 @@ struct Config_Filter{ Callback clock_absence_callback{nullptr}; Callback short_circuit_callback{nullptr}; }; + static consteval DMADomain::Peripheral dma_filter(uint8_t filter){ + switch(filter){ + case 0: return DMADomain::Peripheral::dfsdm_filter0; + case 1: return DMADomain::Peripheral::dfsdm_filter1; + case 2: return DMADomain::Peripheral::dfsdm_filter2; + case 3: return DMADomain::Peripheral::dfsdm_filter3; + default: + compile_error("There is not other filter"); + break; + } + return DMADomain::Peripheral::dfsdm_filter0; + } + + static consteval uint32_t dma_request(uint8_t filter){ + return DMADomain::get_Request(dma_filter(filter),0); + } + struct BufferSizes{ + std::size_t filter0 = 0; + std::size_t filter1 = 0; + std::size_t filter2 = 0; + std::size_t filter3 = 0; + }; + static consteval BufferSizes calculate_buffer_sizes(span cfgs){ + BufferSizes sizes; + for(const auto& cfg : cfgs){ + switch(cfg.filter){ + case 0: + sizes.filter0 += cfg.buffer_size; + break; + case 1: + sizes.filter1 += cfg.buffer_size; + break; + case 2: + sizes.filter2 += cfg.buffer_size; + break; + case 3: + sizes.filter3 += cfg.buffer_size; + break; + } + } + return sizes; + } + static consteval uint32_t compute_latency(const Entry& e){ const uint32_t fosr = e.config_filter.oversampling; const uint32_t iosr = e.config_filter.integrator; @@ -480,7 +519,6 @@ struct Config_Filter{ std::array cfgs{}; std::array channels_used{false}; std::array filters_used{-1,-1,-1,-1}; - for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -491,13 +529,13 @@ struct Config_Filter{ Config& cfg = cfgs[i]; cfg.gpio_idx = e.gpio_idx; - cfg.dma_idx = e.dma_idx; + cfg.dma_request = dma_request(e.config_filter.filter); cfg.channel = e.channel; cfg.buffer_size = e.buffer_size; cfg.type_conv = e.config_filter.type_conv; cfg.dma_enable = e.config_filter.dma; cfg.filter = e.config_filter.filter; - cfg.filter_samples = e.buffer_size; + //add the callbacks cfg.overrun_callback = e.config_filter.overrun_callback; cfg.clock_absence_callback = e.config_filter.clock_absence_callback; @@ -524,8 +562,9 @@ struct Config_Filter{ cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != cfg.init_data_filter.FLTFCR || cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWLTR != cfg.init_data_filter.FLTAWLTR || cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWHTR != cfg.init_data_filter.FLTAWHTR || - cfgs[filters_used[cfg.filter]].filter_samples != cfg.filter_samples){ - compile_error("You have two channels that goes to the same filter with different filter configuration"); + cfgs[filters_used[cfg.filter]].dma_enable != cfg.dma_enable) + { + compile_error("You have two channels that goes to the same filter with different filter configuration"); } //have the same thing in every register of the filter //Channel group conversion in injected mode @@ -536,62 +575,131 @@ struct Config_Filter{ cfg.init_data_filter.FLTCR2 = cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2; //Watchdog and Extreme detector channel enabled } - filters_used[cfg.filter] = i; + filters_used[cfg.filter] = i; + } + //check that dma buffer size is 1 if there is more than one channel per filter and add the buffer_pos_ini to every channel + std::array,4> channels_filter = {}; + for(size_t i = 0; i < N; i++){ + channels_filter[cfgs[i].filter][cfgs[i].channel] = cfgs[i].buffer_size; + uint8_t active_channels = 0; + //active channels + for(size_t j = 0; j < 8; j++){ + if(channels_filter[cfgs[i].filter][j] != 0){ + active_channels++; + } + } + // validate buffers + if(active_channels > 1 && cfgs[i].dma_enable == Dma::Enable){ + //Look the entry because the data that I want to check is easier to access + if(entries[i].config_filter.type_conv == Type_Conversion::Regular){ + compile_error("Not allowed Regular conversion + DMA + Multiple channel in the same filter"); + } + for(size_t j = 0; j < 8; j++){ + if(channels_filter[cfgs[i].filter][j] > 1){ + compile_error("Only allowed buffer_size = 1 when multiple DMA channels are used in the same filter"); + } + } + } + } + //give the buffer_pos to every channel + std::array buffer_pos{0,0,0,0}; + for(std::size_t i = 0; i < N; i++){ + cfgs[i].buffer_pos_ini = buffer_pos[cfgs[i].filter]; + buffer_pos[cfgs[i].filter] += cfgs[i].buffer_size; } return cfgs; } - struct FilterBufferSizes{ - size_t filter_0_total = 0; - size_t filter_1_total = 0; - size_t filter_2_total = 0; - size_t filter_3_total = 0; - }; - static consteval FilterBufferSizes calculate_total_sizes(std::span configs){ - size_t filter0,filter1,filter2,filter3; - filter0 = filter1 = filter2 = filter3 = 0; - for(const auto& cfg : configs){ // must be the same the buffer_size for the same filter - switch(cfg.filter){ - case 0: - filter0 = cfg.buffer_size; - break; - case 1: - filter1 = cfg.buffer_size; - break; - case 2: - filter2 = cfg.buffer_size; - break; - case 3: - filter3 = cfg.buffer_size; - break; + static consteval std::size_t dma_entries_for_filter(uint8_t filter, span dma_entries) { + std::size_t count = 0; + for (const auto& entry : dma_entries) { + if (entry.instance != dma_filter(filter)) { + continue; } + if (entry.id != 0U) { + compile_error("DFSDM: DMA for DFSDM filters must use stream id 0"); + } + ++count; } - if((filter0 + filter1 + filter2 + filter3) > MAX_BUFFER_SIZE_TOTAL){ - compile_error("Has superado el tamaño maximo total permitido"); + return count; + } + static consteval bool uses_filter_dma(uint8_t filter, span cfgs){ + for(const auto& cfg : cfgs) { + if(cfg.filter == filter && cfg.dma_enable == Dma::Enable){ + return true; + } } - FilterBufferSizes sizes; - sizes.filter_0_total = filter0; - sizes.filter_1_total = filter1; - sizes.filter_2_total = filter2; - sizes.filter_3_total = filter3; - return sizes; + return false; + } + static consteval std::size_t dma_contribution_count(span cfgs, span dma_entries) { + std::size_t count = 0; + for (uint8_t fidx = 0; fidx < 4U; ++fidx) { + if (!uses_filter_dma(fidx, cfgs)) { + continue; + } + + const auto existing = dma_entries_for_filter(fidx, dma_entries); + if (existing > 1U) { + compile_error("DFSDM: multiple DMA streams configured for the same DFSDM filter"); + } + if (existing == 0U) { + ++count; + } + } + return count; + } + template + static consteval std::array + build_dma_contributions(span dma_entries, span cfgs) { + std::array extra{}; + std::size_t cursor = 0; + + for (uint8_t fidx = 0; fidx < 4U; ++fidx) { + if (!uses_filter_dma(fidx, cfgs)) { + continue; + } + const auto existing = dma_entries_for_filter(fidx, dma_entries); + if (existing == 0U) { + extra[cursor++] = { + .instance = dma_filter(fidx), + .stream = DMADomain::Stream::none, + .irqn = static_cast(0), + .id = 0, + }; + } + } + + if (cursor != ExtraN) { + compile_error("DFSDM: DMA contribution size mismatch"); + } + + return extra; + } + static inline uint8_t channels_enabled{}; + static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { + DFSDM1_Filter0, + DFSDM1_Filter1, + DFSDM1_Filter2, + DFSDM1_Filter3 + }; + static constexpr DFSDM_Channel_TypeDef* channel_hw[8] = { + DFSDM1_Channel0, + DFSDM1_Channel1, + DFSDM1_Channel2, + DFSDM1_Channel3, + DFSDM1_Channel4, + DFSDM1_Channel5, + DFSDM1_Channel6, + DFSDM1_Channel7 + }; + static DMADomain::Instance* + find_dma_instance(uint32_t request, std::span dma_peripherals) { + for (auto& dma_instance : dma_peripherals) { + if (dma_instance.dma.Init.Request == request) { + return &dma_instance; + } + } + return nullptr; } - static inline uint8_t channels_enabled{}; - static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { - DFSDM1_Filter0, - DFSDM1_Filter1, - DFSDM1_Filter2, - DFSDM1_Filter3 - }; - static constexpr DFSDM_Channel_TypeDef* channel_hw[8] = { - DFSDM1_Channel0, - DFSDM1_Channel1, - DFSDM1_Channel2, - DFSDM1_Channel3, - DFSDM1_Channel4, - DFSDM1_Channel5, - DFSDM1_Channel6, - DFSDM1_Channel7 - }; struct Instance { GPIODomain::Instance *gpio_instance; DMA_Domain::Instance *dma_instance; @@ -610,11 +718,10 @@ struct Config_Filter{ Type_Conversion type_conv; Dma dma_enable; - int32_t* buffer{}; + volatile int32_t* buffer = nullptr; size_t length_buffer{}; private: - bool is_enabled_channel() const{ return (channel_regs->CHCFGR1 & DFSDM_CHCFGR1_CHEN_Msk); } @@ -776,7 +883,7 @@ struct Config_Filter{ } int32_t read(size_t pos){ if(pos >= this->length_buffer){ - return 0; + ErrorHandler("DFSDM: Trying to access to a memory section that is not from the channel buffer"); } return ((this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos); // The constants values are the same for regular than injected } @@ -853,32 +960,39 @@ struct Config_Filter{ static inline int32_t DFSDM_Buffer_Pool[MAX_BUFFER_SIZE_TOTAL]; static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; template cfgs> struct Init { - static constexpr FilterBufferSizes Sizes = calculate_total_sizes(cfgs); + static constexpr auto sizes = calculate_buffer_sizes(cfgs); + //calculamos tamaño tanto para filtros con DMA como para los que no tienen + static constexpr std::size_t total_slots = sizes.filter0 + sizes.filter1 + sizes.filter2 + sizes.filter3; + + static_assert(total_slots <= max_instances, "DFSDM DMA buffer size exceeds max_instances"); //Filter Buffers - static inline int32_t* Buffer_Filter0; - static inline int32_t* Buffer_Filter1; - static inline int32_t* Buffer_Filter2; - static inline int32_t* Buffer_Filter3; + alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR + static inline int32_t Buffer_Filter0[sizes.filter0 > 0 ? sizes.filter0 : 1]{}; + alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR + static inline int32_t Buffer_Filter1[sizes.filter1 > 0 ? sizes.filter1 : 1]{}; + alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR + static inline int32_t Buffer_Filter2[sizes.filter2 > 0 ? sizes.filter2 : 1]{}; + alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR + static inline int32_t Buffer_Filter3[sizes.filter3 > 0 ? sizes.filter3 : 1]{}; static inline std::array instances{}; - static uint32_t get_buffer(uint8_t filter){ + static constexpr std::size_t buffer_size_for(uint8_t filter){ switch(filter){ - case 0: - return reinterpret_cast(Buffer_Filter0); + case 0: + return sizes.filter0; case 1: - return reinterpret_cast(Buffer_Filter1); + return sizes.filter1; case 2: - return reinterpret_cast(Buffer_Filter2); + return sizes.filter2; case 3: - return reinterpret_cast(Buffer_Filter3); + return sizes.filter3; } - return 0; } - static int32_t* get_buffer_pointer(uint8_t filter){ - switch(filter){ - case 0: + static int32_t* get_buffer_filter(uint8_t filter){ + switch (filter){ + case 0: return Buffer_Filter0; case 1: return Buffer_Filter1; @@ -887,19 +1001,12 @@ struct Config_Filter{ case 3: return Buffer_Filter3; } - return 0; } - static void assign_buffers(){ - uint32_t offset = 0; - Buffer_Filter0 = &DFSDM_Buffer_Pool[offset]; - offset += Sizes.filter_0_total; - Buffer_Filter1 = &DFSDM_Buffer_Pool[offset]; - offset += Sizes.filter_1_total; - Buffer_Filter2 = &DFSDM_Buffer_Pool[offset]; - offset += Sizes.filter_2_total; - Buffer_Filter3 = &DFSDM_Buffer_Pool[offset]; - offset += Sizes.filter_3_total; + + static int32_t* get_buffer_pointer(const Config cfg){ + return get_buffer_filter(cfg.filter) + cfg.buffer_pos_ini; } + static void init(std::span gpio_instances,std::span dma_instances) { if(N == 0) return; std::array filters_configured = {false,false,false,false}; @@ -914,7 +1021,7 @@ struct Config_Filter{ Instance &inst = instances[i]; inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; - inst.dma_instance = &dma_instances[cfg.dma_idx]; + inst.dma_instance = find_dma_instance(cfg.dma_request,dma_instances); inst.filter_regs = filter_hw[cfg.filter]; @@ -927,8 +1034,7 @@ struct Config_Filter{ inst.dma_enable = cfg.dma_enable; inst.length_buffer = cfg.buffer_size; - assign_buffers(); - inst.buffer = get_buffer_pointer(cfg.filter); + inst.buffer = get_buffer_pointer(cfg); //callbacks inst.overrun_cb = cfg.overrun_callback; inst.short_circuit_cb = cfg.short_circuit_callback; @@ -958,8 +1064,8 @@ struct Config_Filter{ }else{ SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTJDATAR; } - uint32_t DstAddress = get_buffer(inst.filter); - inst.dma_instance->start(SrcAddress,DstAddress,inst.length_buffer); + uint32_t DstAddress = reinterpret_cast(get_buffer_filter(inst.filter));//Transform the pointer to a value + inst.dma_instance->start(SrcAddress,DstAddress,buffer_size_for(inst.filter)); } } //add everything to the channel register diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 92eb71743..83bd77f09 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -96,7 +96,9 @@ using DomainsCtx = BuildCtx< SdDomain, EthernetDomain, ADCDomain, - EXTIDomain /* PWMDomain, ...*/>; + EXTIDomain, + DFSDM_CHANNEL_DOMAIN, + DFSDM_CLK_DOMAIN/* PWMDomain, ...*/>; namespace BuildUtils { @@ -166,14 +168,27 @@ template struct Board { ctx.template span(), std::span{adc_cfgs} ); + constexpr std::size_t dfsdmN = domain_size(); + constexpr auto dfsdm_cfgs = DFSDM_CHANNEL_DOMAIN::template build(ctx.template span()); + constexpr std::size_t dfsdm_dma_extraN = DFSDM_CHANNEL_DOMAIN::dma_contribution_count( + std::span{dfsdm_cfgs}, + ctx.template span() + ); + constexpr auto dfsdm_dma_entries = + DFSDM_CHANNEL_DOMAIN::template build_dma_contributions( + ctx.template span(), + std::span{dfsdm_cfgs} + ); + constexpr std::size_t dfsdm_clkN = domain_size(); constexpr std::size_t spiN = domain_size(); constexpr std::size_t doutN = domain_size(); constexpr std::size_t dinN = domain_size(); constexpr std::size_t mdmaPacketN = domain_size(); constexpr std::size_t sdN = domain_size(); constexpr std::size_t ethN = domain_size(); - constexpr std::size_t dmaN = domain_size() + adc_dma_extraN; + constexpr std::size_t dmaN = domain_size() + adc_dma_extraN + dfsdm_dma_extraN; constexpr std::size_t extiN = domain_size(); + // ... struct ConfigBundle { @@ -200,7 +215,8 @@ template struct Board { .tim_cfgs = TimerDomain::template build(ctx.template span()), .dma_cfgs = BuildUtils::build_dma_configs( ctx.template span(), - adc_dma_entries + adc_dma_entries, + dfsdm_dma_entries ), .spi_cfgs = SPIDomain::template build(ctx.template span()), .dout_cfgs = @@ -215,6 +231,8 @@ template struct Board { .eth_cfgs = EthernetDomain::template build(ctx.template span()), .adc_cfgs = adc_cfgs, .exti_cfgs = EXTIDomain::template build(ctx.template span()), + .dfsdm_cfgs = dfsdm_cfgs, + .dfsdm_clk_cfgs = DFSDM_CLK_DOMAIN::template build(ctx.template span()) // ... }; } @@ -276,7 +294,14 @@ template struct Board { DMADomain::Init::instances ); EXTIDomain::Init::init(cfg.exti_cfgs, - GPIODomain::Init::instances); // ... + GPIODomain::Init::instances); + + DFSDM_CHANNEL_DOMAIN::Init::init( + GPIODomain::Init::instances, + DMADomain::Init::instances + ); + DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); + // ... } template From 12a4c0f47d5d88b2e8e7d4b361d372b88571dbfd Mon Sep 17 00:00:00 2001 From: oganigl Date: Thu, 26 Mar 2026 20:03:14 +0100 Subject: [PATCH 37/41] OKay tested this works fine with dma and without. --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 78 ++++++++++++++++++++++-------- Inc/ST-LIB.hpp | 4 +- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 3dc1e893d..bace7dc16 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -123,7 +123,7 @@ namespace ST_LIB { uint32_t right_shift{0}; SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; SPI_Type spi_type{SPI_Type::SPI_RISING}; - + /* -------- Runtime protections -------- */ Clock_Absence clock_absence{Clock_Absence::Disable}; Short_Circuit short_circuit{Short_Circuit::Disable}; @@ -142,7 +142,6 @@ namespace ST_LIB { }; struct Config_Filter{ - size_t length_buffer{1}; uint8_t filter{0}; Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; Filter_Type filter_type{Filter_Type::FastSinc}; @@ -248,12 +247,12 @@ struct Config_Filter{ const Config_Filter config_filter; uint8_t channel; size_t buffer_size; - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter) + consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter,std::size_t buffer_size) : pin(pin), gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, config_channel(config_channel), config_filter(config_filter), - buffer_size(config_filter.length_buffer) + buffer_size(buffer_size) { channel = get_channel(pin); if(config_filter.filter > 3){ @@ -356,16 +355,32 @@ struct Config_Filter{ for(const auto& cfg : cfgs){ switch(cfg.filter){ case 0: - sizes.filter0 += cfg.buffer_size; + if(cfg.type_conv == Type_Conversion::Regular){ + sizes.filter0 = std::max(cfg.buffer_size,sizes.filter0); + }else{ + sizes.filter0 += cfg.buffer_size; + } break; case 1: - sizes.filter1 += cfg.buffer_size; + if(cfg.type_conv == Type_Conversion::Regular){ + sizes.filter1 = std::max(cfg.buffer_size,sizes.filter1); + }else{ + sizes.filter1 += cfg.buffer_size; + } break; case 2: - sizes.filter2 += cfg.buffer_size; + if(cfg.type_conv == Type_Conversion::Regular){ + sizes.filter2 = std::max(cfg.buffer_size,sizes.filter2); + }else{ + sizes.filter2 += cfg.buffer_size; + } break; case 3: - sizes.filter3 += cfg.buffer_size; + if(cfg.type_conv == Type_Conversion::Regular){ + sizes.filter3 = std::max(cfg.buffer_size,sizes.filter3); + }else{ + sizes.filter3 += cfg.buffer_size; + } break; } } @@ -519,6 +534,8 @@ struct Config_Filter{ std::array cfgs{}; std::array channels_used{false}; std::array filters_used{-1,-1,-1,-1}; + std::array channel_order{-1,-1,-1,-1,-1,-1,-1,-1}; + for (size_t i = 0; i < N; ++i) { const Entry &e = entries[i]; @@ -530,7 +547,10 @@ struct Config_Filter{ cfg.gpio_idx = e.gpio_idx; cfg.dma_request = dma_request(e.config_filter.filter); + cfg.channel = e.channel; + channel_order[cfg.channel] = i; + cfg.buffer_size = e.buffer_size; cfg.type_conv = e.config_filter.type_conv; cfg.dma_enable = e.config_filter.dma; @@ -601,11 +621,17 @@ struct Config_Filter{ } } } - //give the buffer_pos to every channel + //give the buffer_pos to every channel I'll give the pos by channel order starting from the low std::array buffer_pos{0,0,0,0}; - for(std::size_t i = 0; i < N; i++){ - cfgs[i].buffer_pos_ini = buffer_pos[cfgs[i].filter]; - buffer_pos[cfgs[i].filter] += cfgs[i].buffer_size; + for(std::size_t i = 0; i < 8; i++){ + if(channel_order[i] == -1) continue; + auto& cfg = cfgs[channel_order[i]]; + // If regular conversion give the whole buffer to the channels. + if(cfg.type_conv == Type_Conversion::Regular){ + cfg.buffer_pos_ini = 0; + } + cfg.buffer_pos_ini = buffer_pos[cfg.filter]; + buffer_pos[cfg.filter] += cfg.buffer_size; } return cfgs; } @@ -887,6 +913,9 @@ struct Config_Filter{ } return ((this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos); // The constants values are the same for regular than injected } + int32_t read(){ + return (static_cast(this->buffer[0] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos); // The constants values are the same for regular than injected + } uint32_t check_latency_cycles() { return filter_regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; } @@ -964,7 +993,6 @@ struct Config_Filter{ //calculamos tamaño tanto para filtros con DMA como para los que no tienen static constexpr std::size_t total_slots = sizes.filter0 + sizes.filter1 + sizes.filter2 + sizes.filter3; - static_assert(total_slots <= max_instances, "DFSDM DMA buffer size exceeds max_instances"); //Filter Buffers alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR static inline int32_t Buffer_Filter0[sizes.filter0 > 0 ? sizes.filter0 : 1]{}; @@ -989,6 +1017,8 @@ struct Config_Filter{ case 3: return sizes.filter3; } + ErrorHandler("Filter cannot be bigger than 3"); + return 0; } static int32_t* get_buffer_filter(uint8_t filter){ switch (filter){ @@ -1001,6 +1031,9 @@ struct Config_Filter{ case 3: return Buffer_Filter3; } + ErrorHandler("Filter cannot be bigger than 3"); + return 0; + } static int32_t* get_buffer_pointer(const Config cfg){ @@ -1019,10 +1052,12 @@ struct Config_Filter{ for (std::size_t i = 0; i < N; ++i) { const Config &cfg = cfgs[i]; Instance &inst = instances[i]; - inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; - inst.dma_instance = find_dma_instance(cfg.dma_request,dma_instances); - + if(cfg.dma_enable == Dma::Enable){ + inst.dma_instance = find_dma_instance(cfg.dma_request,dma_instances); + }else{ + inst.dma_instance = nullptr; + } inst.filter_regs = filter_hw[cfg.filter]; inst.channel_regs = channel_hw[cfg.channel]; @@ -1120,7 +1155,7 @@ struct Config_Filter{ int32_t data = filter->FLTRDATAR; Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; if(inst != nullptr && inst->buffer != nullptr){ - if(inst->dma_enable == Dma::Disable){ + if(inst->dma_enable == Dma::Disable)[[likely]]{ inst->buffer[idx_filter[inst->filter]] = data; idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; } @@ -1132,9 +1167,10 @@ struct Config_Filter{ if(isr & DFSDM_FLTISR_JEOCF_Msk){ //Save it in the address provide by the user int32_t data = filter->FLTJDATAR; - Instance* inst = channel_instances[(data & DFSDM_FLTJDATAR_JDATACH_Msk) >> DFSDM_FLTJDATAR_JDATACH_Pos]; + uint8_t channel = (data & DFSDM_FLTJDATAR_JDATACH_Msk); + Instance* inst = channel_instances[channel]; if(inst != nullptr && inst->buffer != nullptr){ - if(inst->dma_enable == Dma::Disable){ + if(inst->dma_enable == Dma::Disable)[[likely]]{ inst->buffer[idx_filter[inst->filter]] = data; idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; } @@ -1147,13 +1183,13 @@ struct Config_Filter{ Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); //clear - filter->FLTICR |= DFSDM_FLTISR_ROVRF; + filter->FLTICR |= DFSDM_FLTICR_CLRROVRF_Msk; } if(isr & DFSDM_FLTISR_JOVRF_Msk){ Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); //clear - filter->FLTICR |= DFSDM_FLTISR_JOVRF; + filter->FLTICR |= DFSDM_FLTICR_CLRJOVRF_Msk; } if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 83bd77f09..01b473749 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -328,7 +328,9 @@ template struct Board { return Domain::template Init::instances[idx]; } else if constexpr (std::is_same_v) { return Domain::template Init::instances[idx]; - } else { + } else if constexpr (std::is_same_v){ + return Domain::template Init::instances[idx]; + } else{ return Domain::template Init::instances[idx]; } } From 0e3d73bfc26c422a3b619da70d081c4531695048 Mon Sep 17 00:00:00 2001 From: oganigl Date: Thu, 26 Mar 2026 20:10:27 +0100 Subject: [PATCH 38/41] readme version --- .changesets/dfsdm-module-minor.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changesets/dfsdm-module-minor.md diff --git a/.changesets/dfsdm-module-minor.md b/.changesets/dfsdm-module-minor.md new file mode 100644 index 000000000..720387dcb --- /dev/null +++ b/.changesets/dfsdm-module-minor.md @@ -0,0 +1 @@ +Added DFSDM Module \ No newline at end of file From 8683e315e15a86adf113ce5975415270d5189e48 Mon Sep 17 00:00:00 2001 From: oganigl Date: Thu, 26 Mar 2026 20:12:15 +0100 Subject: [PATCH 39/41] indentation done --- .changesets/dfsdm-module-minor.md | 2 +- Inc/HALAL/Models/DMA/DMA2.hpp | 29 +- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 33 +- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 1662 +++++++++--------- Inc/HALAL/Services/Time/TimerWrapper.hpp | 2 +- Inc/ST-LIB.hpp | 36 +- Inc/ST-LIB_LOW/ST-LIB_LOW.hpp | 4 +- Src/HALAL/Services/DFSDM/DFSDM.cpp | 25 +- 8 files changed, 903 insertions(+), 890 deletions(-) diff --git a/.changesets/dfsdm-module-minor.md b/.changesets/dfsdm-module-minor.md index 720387dcb..01e51c061 100644 --- a/.changesets/dfsdm-module-minor.md +++ b/.changesets/dfsdm-module-minor.md @@ -1 +1 @@ -Added DFSDM Module \ No newline at end of file +Added DFSDM Module diff --git a/Inc/HALAL/Models/DMA/DMA2.hpp b/Inc/HALAL/Models/DMA/DMA2.hpp index ce2a50322..2afc5b89d 100644 --- a/Inc/HALAL/Models/DMA/DMA2.hpp +++ b/Inc/HALAL/Models/DMA/DMA2.hpp @@ -102,9 +102,7 @@ struct DMADomain { } return nullptr; } - static inline consteval bool shares_dma(Peripheral p){ - return is_dfsdm(p); - } + static inline consteval bool shares_dma(Peripheral p) { return is_dfsdm(p); } struct Entry { Peripheral instance; Stream stream; @@ -135,8 +133,7 @@ struct DMADomain { } } - template - consteval array inscribe(Ctx& ctx) const { + template consteval array inscribe(Ctx& ctx) const { array indices{}; for (size_t i = 0; i < sizeof...(Ss); i++) { indices[i] = ctx.template add(e[i], this); @@ -223,11 +220,17 @@ struct DMADomain { static consteval bool is_fmac(Peripheral instance) { return instance == Peripheral::fmac; } static consteval bool is_none(Peripheral instance) { return instance == Peripheral::none; } - - static consteval bool is_dfsdm(Peripheral instance) { - return is_one_of(instance,Peripheral::dfsdm_filter0,Peripheral::dfsdm_filter1,Peripheral::dfsdm_filter2,Peripheral::dfsdm_filter3); + + static consteval bool is_dfsdm(Peripheral instance) { + return is_one_of( + instance, + Peripheral::dfsdm_filter0, + Peripheral::dfsdm_filter1, + Peripheral::dfsdm_filter2, + Peripheral::dfsdm_filter3 + ); } - + static consteval uint32_t get_Request(Peripheral instance, uint8_t i) { if (instance == Peripheral::none) return DMA_REQUEST_MEM2MEM; @@ -283,16 +286,16 @@ struct DMADomain { return DMA_REQUEST_FMAC_WRITE; if (instance == Peripheral::fmac && i == 2) return DMA_REQUEST_FMAC_READ; - if(instance == Peripheral::dfsdm_filter0){ + if (instance == Peripheral::dfsdm_filter0) { return DMA_REQUEST_DFSDM1_FLT0; } - if(instance == Peripheral::dfsdm_filter1){ + if (instance == Peripheral::dfsdm_filter1) { return DMA_REQUEST_DFSDM1_FLT1; } - if(instance == Peripheral::dfsdm_filter2){ + if (instance == Peripheral::dfsdm_filter2) { return DMA_REQUEST_DFSDM1_FLT2; } - if(instance == Peripheral::dfsdm_filter3){ + if (instance == Peripheral::dfsdm_filter3) { return DMA_REQUEST_DFSDM1_FLT3; } compile_error("Invalid DMA request configuration"); diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 3011cafd6..59f3b15c1 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -136,7 +136,7 @@ TimerXList Basic_6 = 6, Basic_7 = 7, }; - enum class SelectionTrigger1: uint32_t { + enum class SelectionTrigger1 : uint32_t { Reset = TIM_TRGO_RESET, Enable = TIM_TRGO_ENABLE, Update = TIM_TRGO_UPDATE, @@ -146,7 +146,7 @@ TimerXList Compare_channel3 = TIM_TRGO_OC3REF, Compare_channel4 = TIM_TRGO_OC4REF }; - enum class SelectionTrigger2: uint32_t { + enum class SelectionTrigger2 : uint32_t { Reset = TIM_TRGO2_RESET, Enable = TIM_TRGO2_ENABLE, Update = TIM_TRGO2_UPDATE, @@ -498,19 +498,17 @@ TimerXList : e(ent.name, ent.request, sizeof...(pinargs), - std::array( - {GetPinFromIdx(pinargs, 0), - GetPinFromIdx(pinargs, 1), - GetPinFromIdx(pinargs, 2), - GetPinFromIdx(pinargs, 3), - GetPinFromIdx(pinargs, 4), - GetPinFromIdx(pinargs, 5), - GetPinFromIdx(pinargs, 6), - } - ), + std::array({ + GetPinFromIdx(pinargs, 0), + GetPinFromIdx(pinargs, 1), + GetPinFromIdx(pinargs, 2), + GetPinFromIdx(pinargs, 3), + GetPinFromIdx(pinargs, 4), + GetPinFromIdx(pinargs, 5), + GetPinFromIdx(pinargs, 6), + }), ent.trgo1, - ent.trgo2 - ), + ent.trgo2), gpio0(GetGPIOFromIdx(pinargs, ent.request, 0)), gpio1(GetGPIOFromIdx(pinargs, ent.request, 1)), gpio2(GetGPIOFromIdx(pinargs, ent.request, 2)), @@ -731,7 +729,7 @@ TimerXList TIM_HandleTypeDef* handle = hal_handles[e.timer_idx]; TIM_TypeDef* tim = cmsis_timers[e.timer_idx]; - + handle->Instance = tim; handle->Init.Period = 0; handle->Init.Prescaler = 0; @@ -762,11 +760,12 @@ TimerXList inst->hal_tim = handle; inst->timer_idx = e.timer_idx; TIM_MasterConfigTypeDef sMasterConfig = {}; - sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); + sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; inst->master = sMasterConfig; - if(HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != HAL_OK) { + if (HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != + HAL_OK) { ErrorHandler("Unable to configure master synch"); } } diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index bace7dc16..6a56dfc6b 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -6,94 +6,65 @@ #include "HALAL/Models/DMA/DMA2.hpp" #include "HALAL/Models/MPU.hpp" -#define STLIB_DFSDM_DMA_BUFFER_ATTR D1_NC +#define STLIB_DFSDM_DMA_BUFFER_ATTR D1_NC #define Oversampling_MAX 1024 #define Oversampling_MAX_Filter_4 215 #define Oversampling_MAX_Filter_5 73 #define Possible_Pin_Channel 20 -#define OFFSET_MAX (1 << 23) - 1 -#define OFFSET_MIN -(1 << 23) +#define OFFSET_MAX (1 << 23) - 1 +#define OFFSET_MIN -(1 << 23) #define MAX_BUFFER_SIZE_TOTAL 1024 -using ST_LIB::GPIODomain; using ST_LIB::DMA_Domain; +using ST_LIB::GPIODomain; namespace ST_LIB { - using Callback = void(*)(void); - extern void compile_error(const char *msg); - - struct DFSDM_CHANNEL_DOMAIN{ - /* Constant Values of Register*/ - //DatPack = 0 Standar - //DatMPx = 0 // External input - //ChinSel = 0 // It make sense for our use case. - //SPICKSel = ¿? - enum class SPICKSel: uint8_t{ - CLK_IN = 0, +using Callback = void (*)(void); +extern void compile_error(const char* msg); + +struct DFSDM_CHANNEL_DOMAIN { + /* Constant Values of Register*/ + // DatPack = 0 Standar + // DatMPx = 0 // External input + // ChinSel = 0 // It make sense for our use case. + // SPICKSel = ¿? + enum class SPICKSel : uint8_t { + CLK_IN = 0, NORMAL_CLK_OUT = 1, CLK_DIVIDED_2_RISING = 2, CLK_DIVIDED_2_FALLING = 3 - }; - enum class SPI_Type: uint8_t{ - SPI_RISING = 0, - SPI_FALLING = 1 - }; - - enum class Filter_Type : uint8_t { - FastSinc = 0, - Sinc1 = 1, - Sinc2 = 2, - Sinc3 = 3, - Sinc4 = 4, - Sinc5 = 5 }; + enum class SPI_Type : uint8_t { SPI_RISING = 0, SPI_FALLING = 1 }; - enum Fast_Conversion : uint8_t { - Disable = 0, - Enable = 1 + enum class Filter_Type : uint8_t { + FastSinc = 0, + Sinc1 = 1, + Sinc2 = 2, + Sinc3 = 3, + Sinc4 = 4, + Sinc5 = 5 }; - enum class Dma: uint8_t { - Disable = 0, - Enable = 1 - }; + enum Fast_Conversion : uint8_t { Disable = 0, Enable = 1 }; - enum class Sync_Conversion : uint8_t { - Independent = 0, - Sync_With_Flt0 = 1 - }; + enum class Dma : uint8_t { Disable = 0, Enable = 1 }; - enum class Regular_Mode : uint8_t { - Single = 0, - Continuous = 1 - }; + enum class Sync_Conversion : uint8_t { Independent = 0, Sync_With_Flt0 = 1 }; - enum class Analog_Watchdog_Mode : uint8_t { - After_Filter = 0, // AWFSEL = 0 - Channel_Data = 1 // AWFSEL = 1 - }; - //AWFSEL = 0 high precision, slow speed <- Ideally for our case - //AWFSEL = 1 16 bits precision ultra high speed oversampling ratio 1..32 filter (1..3) 8 relojes de clock + enum class Regular_Mode : uint8_t { Single = 0, Continuous = 1 }; - enum class Overrun : uint8_t { - Enable = 0, - Disable = 1 - }; - enum class Clock_Absence : uint8_t{ - Disable = 0, - Enable = 1 - }; - enum class Short_Circuit : uint8_t{ - Disable = 0, - Enable = 1 - }; - enum class Extreme_Detector : uint8_t{ - Disable = 0, - Enable = 1 - }; - enum class Analog_Watchdog : uint8_t{ - Disable = 0, - Enable = 1 + enum class Analog_Watchdog_Mode : uint8_t { + After_Filter = 0, // AWFSEL = 0 + Channel_Data = 1 // AWFSEL = 1 }; + // AWFSEL = 0 high precision, slow speed <- Ideally for our case + // AWFSEL = 1 16 bits precision ultra high speed oversampling ratio 1..32 filter (1..3) 8 + // relojes de clock + + enum class Overrun : uint8_t { Enable = 0, Disable = 1 }; + enum class Clock_Absence : uint8_t { Disable = 0, Enable = 1 }; + enum class Short_Circuit : uint8_t { Disable = 0, Enable = 1 }; + enum class Extreme_Detector : uint8_t { Disable = 0, Enable = 1 }; + enum class Analog_Watchdog : uint8_t { Disable = 0, Enable = 1 }; enum class Trigger_Timer_Source : uint8_t { Unused, Tim1, @@ -110,136 +81,125 @@ namespace ST_LIB { Lptim2, Lptim3 }; - enum class Type_Conversion: uint8_t{ - Regular, - Injected + enum class Type_Conversion : uint8_t { Regular, Injected }; + enum class Injected_Mode : uint8_t { Single, Scan }; + struct Config_Channel { + int32_t offset{0}; + uint32_t right_shift{0}; + SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; + SPI_Type spi_type{SPI_Type::SPI_RISING}; + + /* -------- Runtime protections -------- */ + Clock_Absence clock_absence{Clock_Absence::Disable}; + Short_Circuit short_circuit{Short_Circuit::Disable}; + Extreme_Detector extreme_detector{Extreme_Detector::Disable}; + + uint8_t short_circuit_count{0xFF}; + + /* -------- Analog watchdog -------- */ + Analog_Watchdog watchdog{Analog_Watchdog::Disable}; + + // If Mode Watchdog == After filter + Filter_Type filter_watchdog{Filter_Type::Sinc2}; + uint8_t watchdog_oversampling{5}; }; - enum class Injected_Mode: uint8_t{ - Single, - Scan + struct Config_Filter { + uint8_t filter{0}; + Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; + Filter_Type filter_type{Filter_Type::FastSinc}; + uint16_t oversampling{32}; + uint16_t integrator{1}; + Type_Conversion type_conv{Type_Conversion::Regular}; + Dma dma{Dma::Disable}; + Fast_Conversion fast{Fast_Conversion::Disable}; + Sync_Conversion rsync{Sync_Conversion::Independent}; + Regular_Mode rcont{Regular_Mode::Single}; + Injected_Mode jscan{Injected_Mode::Scan}; + + Overrun overrun{Overrun::Disable}; + + Analog_Watchdog watchdog{Analog_Watchdog::Disable}; + Analog_Watchdog_Mode watchdog_mode{Analog_Watchdog_Mode::After_Filter}; + int32_t watchdog_low_threshold{0x10000000}; + int32_t watchdog_high_threshold{0x7FFFFFFF}; + // callbacks + Callback watchdog_callback{nullptr}; + Callback conversion_complete_callback{nullptr}; + Callback overrun_callback{nullptr}; + Callback clock_absence_callback{nullptr}; + Callback short_circuit_callback{nullptr}; }; - struct Config_Channel { - int32_t offset{0}; - uint32_t right_shift{0}; - SPICKSel spi_clock_sel{SPICKSel::CLK_DIVIDED_2_RISING}; - SPI_Type spi_type{SPI_Type::SPI_RISING}; - - /* -------- Runtime protections -------- */ - Clock_Absence clock_absence{Clock_Absence::Disable}; - Short_Circuit short_circuit{Short_Circuit::Disable}; - Extreme_Detector extreme_detector{Extreme_Detector::Disable}; - - uint8_t short_circuit_count{0xFF}; - - /* -------- Analog watchdog -------- */ - Analog_Watchdog watchdog{Analog_Watchdog::Disable}; - - - //If Mode Watchdog == After filter - Filter_Type filter_watchdog{Filter_Type::Sinc2}; - uint8_t watchdog_oversampling{5}; - - -}; -struct Config_Filter{ - uint8_t filter{0}; - Trigger_Timer_Source trigger_conv{Trigger_Timer_Source::Unused}; - Filter_Type filter_type{Filter_Type::FastSinc}; - uint16_t oversampling{32}; - uint16_t integrator{1}; - Type_Conversion type_conv{Type_Conversion::Regular}; - Dma dma{Dma::Disable}; - Fast_Conversion fast{Fast_Conversion::Disable}; - Sync_Conversion rsync{Sync_Conversion::Independent}; - Regular_Mode rcont{Regular_Mode::Single}; - Injected_Mode jscan{Injected_Mode::Scan}; - - Overrun overrun{Overrun::Disable}; - - Analog_Watchdog watchdog{Analog_Watchdog::Disable}; - Analog_Watchdog_Mode watchdog_mode{Analog_Watchdog_Mode::After_Filter}; - int32_t watchdog_low_threshold{0x10000000}; - int32_t watchdog_high_threshold{0x7FFFFFFF}; - //callbacks - Callback watchdog_callback{nullptr}; - Callback conversion_complete_callback{nullptr}; - Callback overrun_callback{nullptr}; - Callback clock_absence_callback{nullptr}; - Callback short_circuit_callback{nullptr}; -}; - static constexpr std::array,Possible_Pin_Channel> pin_to_channel = - {{ - {PE4,3},{PC0,4},{PC1,0},{PC3,1},{PC5,2}, - {PB1,1},{PF13,6},{PE7,2},{PE10,4},{PE12,5},{PB10,7},{PB12,1}, - {PB14,2},{PD9,3},{PC7,3},{PC11,5},{PD1,6},{PD6,1},{PD7,4}, - {PB6,5} - }}; - - static consteval uint8_t get_channel(const GPIODomain::Pin& pin){ - for(int i = 0; i < Possible_Pin_Channel;i++){ - if(pin_to_channel[i].first == pin){ + static constexpr std::array, Possible_Pin_Channel> + pin_to_channel = {{{PE4, 3}, {PC0, 4}, {PC1, 0}, {PC3, 1}, {PC5, 2}, + {PB1, 1}, {PF13, 6}, {PE7, 2}, {PE10, 4}, {PE12, 5}, + {PB10, 7}, {PB12, 1}, {PB14, 2}, {PD9, 3}, {PC7, 3}, + {PC11, 5}, {PD1, 6}, {PD6, 1}, {PD7, 4}, {PB6, 5}}}; + + static consteval uint8_t get_channel(const GPIODomain::Pin& pin) { + for (int i = 0; i < Possible_Pin_Channel; i++) { + if (pin_to_channel[i].first == pin) { return pin_to_channel[i].second; } } compile_error("This pin cannot be used as a DFSDM_Channel"); } - - static consteval bool is_correct_oversampling(Filter_Type f, uint16_t osr) { switch (f) { - case Filter_Type::FastSinc: - case Filter_Type::Sinc1: - case Filter_Type::Sinc2: - case Filter_Type::Sinc3: - return osr >= 1 && osr <= Oversampling_MAX; + case Filter_Type::FastSinc: + case Filter_Type::Sinc1: + case Filter_Type::Sinc2: + case Filter_Type::Sinc3: + return osr >= 1 && osr <= Oversampling_MAX; - case Filter_Type::Sinc4: - return osr >= 1 && osr <= Oversampling_MAX_Filter_4; + case Filter_Type::Sinc4: + return osr >= 1 && osr <= Oversampling_MAX_Filter_4; - case Filter_Type::Sinc5: - return osr >= 1 && osr <= Oversampling_MAX_Filter_5; + case Filter_Type::Sinc5: + return osr >= 1 && osr <= Oversampling_MAX_Filter_5; } return false; } static consteval GPIODomain::AlternateFunction dfsdm_channel_af(const GPIODomain::Pin& pin) { - if ((pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_1) || + if ((pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_1) || (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_10) || (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_12) || (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_14) || - (pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_0)) - { + (pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_0)) { return GPIODomain::AlternateFunction::AF6; } - if((pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_7) || - (pin.port == GPIODomain::Port::D && pin.pin == GPIO_PIN_6)) - { + if ((pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_7) || + (pin.port == GPIODomain::Port::D && pin.pin == GPIO_PIN_6)) { return GPIODomain::AlternateFunction::AF4; - } - if((pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_6)){ + } + if ((pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_6)) { return GPIODomain::AlternateFunction::AF11; - } - return GPIODomain::AlternateFunction::AF3; //In any other case + } + return GPIODomain::AlternateFunction::AF3; // In any other case } - static consteval DMA_Domain::Peripheral get_dma_peripheral(uint8_t filter){ - switch (filter){ - case 0: return DMA_Domain::Peripheral::dfsdm_filter0; - case 1: return DMA_Domain::Peripheral::dfsdm_filter1; - case 2: return DMA_Domain::Peripheral::dfsdm_filter2; - case 3: return DMA_Domain::Peripheral::dfsdm_filter3; + static consteval DMA_Domain::Peripheral get_dma_peripheral(uint8_t filter) { + switch (filter) { + case 0: + return DMA_Domain::Peripheral::dfsdm_filter0; + case 1: + return DMA_Domain::Peripheral::dfsdm_filter1; + case 2: + return DMA_Domain::Peripheral::dfsdm_filter2; + case 3: + return DMA_Domain::Peripheral::dfsdm_filter3; } compile_error("Cannot be different than 0,1,2,3"); } - struct Entry{ + struct Entry { Config_Channel config_channel; Config_Filter config_filter; uint8_t channel; size_t gpio_idx; size_t buffer_size; }; - + static constexpr size_t max_instances{8}; - struct DFSDM_CHANNEL{ + struct DFSDM_CHANNEL { using domain = DFSDM_CHANNEL_DOMAIN; const GPIODomain::Pin& pin; GPIODomain::GPIO gpio; @@ -247,40 +207,50 @@ struct Config_Filter{ const Config_Filter config_filter; uint8_t channel; size_t buffer_size; - consteval DFSDM_CHANNEL(const GPIODomain::Pin& pin,const Config_Channel config_channel,const Config_Filter config_filter,std::size_t buffer_size) - : pin(pin), - gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None,GPIODomain::Speed::High,dfsdm_channel_af(pin)}, - config_channel(config_channel), - config_filter(config_filter), - buffer_size(buffer_size) - { + consteval DFSDM_CHANNEL( + const GPIODomain::Pin& pin, + const Config_Channel config_channel, + const Config_Filter config_filter, + std::size_t buffer_size + ) + : pin(pin), + gpio{ + pin, + GPIODomain::OperationMode::ALT_PP, + GPIODomain::Pull::None, + GPIODomain::Speed::High, + dfsdm_channel_af(pin) + }, + config_channel(config_channel), config_filter(config_filter), + buffer_size(buffer_size) { channel = get_channel(pin); - if(config_filter.filter > 3){ + if (config_filter.filter > 3) { compile_error("Solo hay 4 filtros [0..3]"); } - if(config_channel.offset > OFFSET_MAX || config_channel.offset < OFFSET_MIN){ + if (config_channel.offset > OFFSET_MAX || config_channel.offset < OFFSET_MIN) { compile_error("Your offset is bigger than the maximum size"); } - if(config_channel.right_shift > 0x000000FF){ + if (config_channel.right_shift > 0x000000FF) { compile_error("Your right_shift is bigger than the maximum size"); } - if(config_filter.integrator <= 0){ + if (config_filter.integrator <= 0) { compile_error("DFSDM_FILTER: Integrator out of range"); } - if (!is_correct_oversampling(config_filter.filter_type, config_filter.oversampling)){ + if (!is_correct_oversampling(config_filter.filter_type, config_filter.oversampling)) { compile_error("DFSDM_FILTER: invalid oversampling for selected filter type"); - } - if(config_channel.watchdog_oversampling > 32){ + } + if (config_channel.watchdog_oversampling > 32) { compile_error("DFSDM_Watchdog oversampling is bigger than the maximum allowed"); } - if(static_cast(config_channel.filter_watchdog) > 3){ - compile_error("Why would a sane person need a filter of the watchdog higher than sinc3"); + if (static_cast(config_channel.filter_watchdog) > 3) { + compile_error( + "Why would a sane person need a filter of the watchdog higher than sinc3" + ); } } - - template - consteval std::size_t inscribe(Ctx &ctx) const { - const auto gpio_idx = gpio.inscribe(ctx); + + template consteval std::size_t inscribe(Ctx& ctx) const { + const auto gpio_idx = gpio.inscribe(ctx); Entry e{ .config_channel = config_channel, .config_filter = config_filter, @@ -292,7 +262,7 @@ struct Config_Filter{ } }; // I hate stm32,has volatile in the DFSDM structs. - struct FilterConfig{ + struct FilterConfig { uint32_t FLTCR1{}; uint32_t FLTCR2{}; uint32_t FLTFCR{}; @@ -300,7 +270,7 @@ struct Config_Filter{ uint32_t FLTAWLTR{}; uint32_t FLTJCHGR{}; }; - struct ChannelConfig{ + struct ChannelConfig { uint32_t CHCFGR1{}; uint32_t CHCFGR2{}; uint32_t CHAWSCDR{}; @@ -309,91 +279,94 @@ struct Config_Filter{ size_t gpio_idx; size_t dma_request; FilterConfig init_data_filter; - ChannelConfig init_data_channel; - + ChannelConfig init_data_channel; + uint32_t latency_cycles; Type_Conversion type_conv; Dma dma_enable; uint8_t filter; uint8_t channel; - + size_t buffer_size; size_t buffer_pos_ini; - //callbacks + // callbacks Callback watchdog_callback{nullptr}; Callback conversion_complete_callback{nullptr}; Callback overrun_callback{nullptr}; Callback clock_absence_callback{nullptr}; Callback short_circuit_callback{nullptr}; }; - static consteval DMADomain::Peripheral dma_filter(uint8_t filter){ - switch(filter){ - case 0: return DMADomain::Peripheral::dfsdm_filter0; - case 1: return DMADomain::Peripheral::dfsdm_filter1; - case 2: return DMADomain::Peripheral::dfsdm_filter2; - case 3: return DMADomain::Peripheral::dfsdm_filter3; - default: - compile_error("There is not other filter"); - break; + static consteval DMADomain::Peripheral dma_filter(uint8_t filter) { + switch (filter) { + case 0: + return DMADomain::Peripheral::dfsdm_filter0; + case 1: + return DMADomain::Peripheral::dfsdm_filter1; + case 2: + return DMADomain::Peripheral::dfsdm_filter2; + case 3: + return DMADomain::Peripheral::dfsdm_filter3; + default: + compile_error("There is not other filter"); + break; } return DMADomain::Peripheral::dfsdm_filter0; } - static consteval uint32_t dma_request(uint8_t filter){ - return DMADomain::get_Request(dma_filter(filter),0); + static consteval uint32_t dma_request(uint8_t filter) { + return DMADomain::get_Request(dma_filter(filter), 0); } - struct BufferSizes{ + struct BufferSizes { std::size_t filter0 = 0; - std::size_t filter1 = 0; + std::size_t filter1 = 0; std::size_t filter2 = 0; std::size_t filter3 = 0; }; - static consteval BufferSizes calculate_buffer_sizes(span cfgs){ + static consteval BufferSizes calculate_buffer_sizes(span cfgs) { BufferSizes sizes; - for(const auto& cfg : cfgs){ - switch(cfg.filter){ - case 0: - if(cfg.type_conv == Type_Conversion::Regular){ - sizes.filter0 = std::max(cfg.buffer_size,sizes.filter0); - }else{ - sizes.filter0 += cfg.buffer_size; - } - break; - case 1: - if(cfg.type_conv == Type_Conversion::Regular){ - sizes.filter1 = std::max(cfg.buffer_size,sizes.filter1); - }else{ - sizes.filter1 += cfg.buffer_size; - } - break; - case 2: - if(cfg.type_conv == Type_Conversion::Regular){ - sizes.filter2 = std::max(cfg.buffer_size,sizes.filter2); - }else{ - sizes.filter2 += cfg.buffer_size; - } - break; - case 3: - if(cfg.type_conv == Type_Conversion::Regular){ - sizes.filter3 = std::max(cfg.buffer_size,sizes.filter3); - }else{ - sizes.filter3 += cfg.buffer_size; - } - break; + for (const auto& cfg : cfgs) { + switch (cfg.filter) { + case 0: + if (cfg.type_conv == Type_Conversion::Regular) { + sizes.filter0 = std::max(cfg.buffer_size, sizes.filter0); + } else { + sizes.filter0 += cfg.buffer_size; + } + break; + case 1: + if (cfg.type_conv == Type_Conversion::Regular) { + sizes.filter1 = std::max(cfg.buffer_size, sizes.filter1); + } else { + sizes.filter1 += cfg.buffer_size; + } + break; + case 2: + if (cfg.type_conv == Type_Conversion::Regular) { + sizes.filter2 = std::max(cfg.buffer_size, sizes.filter2); + } else { + sizes.filter2 += cfg.buffer_size; + } + break; + case 3: + if (cfg.type_conv == Type_Conversion::Regular) { + sizes.filter3 = std::max(cfg.buffer_size, sizes.filter3); + } else { + sizes.filter3 += cfg.buffer_size; + } + break; } } return sizes; } - static consteval uint32_t compute_latency(const Entry& e){ + static consteval uint32_t compute_latency(const Entry& e) { const uint32_t fosr = e.config_filter.oversampling; const uint32_t iosr = e.config_filter.integrator; if (e.config_filter.fast == Fast_Conversion::Enable && - e.config_filter.rcont == Regular_Mode::Continuous) - { + e.config_filter.rcont == Regular_Mode::Continuous) { return fosr * iosr; } @@ -404,124 +377,140 @@ struct Config_Filter{ const uint32_t ford = static_cast(e.config_filter.filter_type); return fosr * (iosr - 1 + ford) + ford; } - static consteval uint32_t get_trigger(Trigger_Timer_Source trig){ - switch(trig){ - case Trigger_Timer_Source::Tim1 : return 0; - case Trigger_Timer_Source::Tim3 : return 4; - case Trigger_Timer_Source::Tim4 : return 5; - case Trigger_Timer_Source::Tim7 : return 8; - case Trigger_Timer_Source::Tim8 : return 2; - case Trigger_Timer_Source::Tim16 : return 6; - case Trigger_Timer_Source::Tim23 : return 11; - case Trigger_Timer_Source::Tim24 : return 12; - case Trigger_Timer_Source::Exti11 : return 24; - case Trigger_Timer_Source::Exti15 : return 25; - case Trigger_Timer_Source::Lptim1 : return 26; - case Trigger_Timer_Source::Lptim2 : return 27; - case Trigger_Timer_Source::Lptim3 : return 28; - case Trigger_Timer_Source::Unused : break; + static consteval uint32_t get_trigger(Trigger_Timer_Source trig) { + switch (trig) { + case Trigger_Timer_Source::Tim1: + return 0; + case Trigger_Timer_Source::Tim3: + return 4; + case Trigger_Timer_Source::Tim4: + return 5; + case Trigger_Timer_Source::Tim7: + return 8; + case Trigger_Timer_Source::Tim8: + return 2; + case Trigger_Timer_Source::Tim16: + return 6; + case Trigger_Timer_Source::Tim23: + return 11; + case Trigger_Timer_Source::Tim24: + return 12; + case Trigger_Timer_Source::Exti11: + return 24; + case Trigger_Timer_Source::Exti15: + return 25; + case Trigger_Timer_Source::Lptim1: + return 26; + case Trigger_Timer_Source::Lptim2: + return 27; + case Trigger_Timer_Source::Lptim3: + return 28; + case Trigger_Timer_Source::Unused: + break; } return 0; } - //Filter registers - static consteval uint32_t make_fltfcr(const Entry& e) - { - return - (uint32_t(e.config_filter.filter_type) << DFSDM_FLTFCR_FORD_Pos) | - (uint32_t(e.config_filter.oversampling -1) << DFSDM_FLTFCR_FOSR_Pos) | - (uint32_t(e.config_filter.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); + // Filter registers + static consteval uint32_t make_fltfcr(const Entry& e) { + return (uint32_t(e.config_filter.filter_type) << DFSDM_FLTFCR_FORD_Pos) | + (uint32_t(e.config_filter.oversampling - 1) << DFSDM_FLTFCR_FOSR_Pos) | + (uint32_t(e.config_filter.integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); } - static consteval uint32_t make_fltcr2_global(){ - //Activate the interrupt, to activate the continous detection, then the channel can + static consteval uint32_t make_fltcr2_global() { + // Activate the interrupt, to activate the continous detection, then the channel can return (uint32_t)(DFSDM_FLTCR2_CKABIE | DFSDM_FLTCR2_SCDIE); } - static consteval uint32_t make_fltcr2(const Entry& e){ + static consteval uint32_t make_fltcr2(const Entry& e) { uint32_t v = 0; - if(e.config_filter.dma == Dma::Disable || e.config_filter.conversion_complete_callback != nullptr){ + if (e.config_filter.dma == Dma::Disable || + e.config_filter.conversion_complete_callback != nullptr) { v |= DFSDM_FLTCR2_REOCIE; v |= DFSDM_FLTCR2_JEOCIE; } - if(e.config_filter.watchdog == Analog_Watchdog::Enable){ + if (e.config_filter.watchdog == Analog_Watchdog::Enable) { v |= DFSDM_FLTCR2_AWDIE; v |= 1 << (DFSDM_FLTCR2_AWDCH_Pos + e.channel); - } - if (e.config_channel.extreme_detector == Extreme_Detector::Enable){ + } + if (e.config_channel.extreme_detector == Extreme_Detector::Enable) { v |= 1 << (DFSDM_FLTCR2_EXCH_Pos + e.channel); } - if(e.config_filter.overrun == Overrun::Enable){ - v |= DFSDM_FLTCR2_JOVRIE; - v |= DFSDM_FLTCR2_ROVRIE; + if (e.config_filter.overrun == Overrun::Enable) { + v |= DFSDM_FLTCR2_JOVRIE; + v |= DFSDM_FLTCR2_ROVRIE; } return v; } - static consteval uint32_t make_fltcr1(const Entry& e,uint32_t filter) - { + static consteval uint32_t make_fltcr1(const Entry& e, uint32_t filter) { uint32_t v = 0; - - if(e.config_filter.type_conv == Type_Conversion::Regular){ - v |= (uint32_t(e.config_filter.dma) << DFSDM_FLTCR1_RDMAEN_Pos); - v |= (uint32_t(e.config_filter.fast) << DFSDM_FLTCR1_FAST_Pos); - v |= (uint32_t(e.config_filter.rsync) << DFSDM_FLTCR1_RSYNC_Pos); - v |= (uint32_t(e.config_filter.rcont) << DFSDM_FLTCR1_RCONT_Pos); - }else if(e.config_filter.type_conv == Type_Conversion::Injected){ - v |= uint32_t(e.config_filter.jscan) << DFSDM_FLTCR1_JSCAN_Pos; // activate conversion of the entire group + + if (e.config_filter.type_conv == Type_Conversion::Regular) { + v |= (uint32_t(e.config_filter.dma) << DFSDM_FLTCR1_RDMAEN_Pos); + v |= (uint32_t(e.config_filter.fast) << DFSDM_FLTCR1_FAST_Pos); + v |= (uint32_t(e.config_filter.rsync) << DFSDM_FLTCR1_RSYNC_Pos); + v |= (uint32_t(e.config_filter.rcont) << DFSDM_FLTCR1_RCONT_Pos); + } else if (e.config_filter.type_conv == Type_Conversion::Injected) { + v |= uint32_t(e.config_filter.jscan) + << DFSDM_FLTCR1_JSCAN_Pos; // activate conversion of the entire group v |= (uint32_t)(e.config_filter.dma) << DFSDM_FLTCR1_JDMAEN_Pos; - if(e.config_filter.trigger_conv != Trigger_Timer_Source::Unused){ - v |= DFSDM_FLTCR1_JEXTEN_0; //with the risings - if(filter == 0){ + if (e.config_filter.trigger_conv != Trigger_Timer_Source::Unused) { + v |= DFSDM_FLTCR1_JEXTEN_0; // with the risings + if (filter == 0) { v |= get_trigger(e.config_filter.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; } - if(e.config_filter.rsync == Sync_Conversion::Sync_With_Flt0){ + if (e.config_filter.rsync == Sync_Conversion::Sync_With_Flt0) { v |= DFSDM_FLTCR1_JSYNC; - - }else{ + + } else { v |= get_trigger(e.config_filter.trigger_conv) << DFSDM_FLTCR1_JEXTSEL_Pos; } - } + } } v |= uint32_t(e.config_filter.watchdog_mode) << DFSDM_FLTCR1_AWFSEL_Pos; return v; } - static consteval uint32_t make_fltawhtr(const Entry& e){ - uint32_t v = 0; + static consteval uint32_t make_fltawhtr(const Entry& e) { + uint32_t v = 0; if (e.config_filter.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) - v |= (e.config_filter.watchdog_high_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + v |= (e.config_filter.watchdog_high_threshold & 0xFFFF) + << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); else v |= (e.config_filter.watchdog_high_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; return v; } - static consteval uint32_t make_fltawltr(const Entry& e){ - uint32_t v = 0; + static consteval uint32_t make_fltawltr(const Entry& e) { + uint32_t v = 0; if (e.config_filter.watchdog_mode == Analog_Watchdog_Mode::Channel_Data) - v |= (e.config_filter.watchdog_low_threshold & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + v |= (e.config_filter.watchdog_low_threshold & 0xFFFF) + << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); else v |= (e.config_filter.watchdog_low_threshold & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; return v; } - //Channel - static consteval uint32_t make_chawscdr(const Entry& e){ + // Channel + static consteval uint32_t make_chawscdr(const Entry& e) { uint32_t v = 0; v |= uint32_t(e.config_channel.short_circuit_count) << DFSDM_CHAWSCDR_SCDT_Pos; - if(e.config_channel.watchdog == Analog_Watchdog::Enable){ - v |= static_cast(e.config_channel.filter_watchdog) << DFSDM_CHAWSCDR_AWFORD_Pos; - v |= static_cast((e.config_channel.watchdog_oversampling-1) & 0xF) << DFSDM_CHAWSCDR_AWFOSR_Pos; + if (e.config_channel.watchdog == Analog_Watchdog::Enable) { + v |= static_cast(e.config_channel.filter_watchdog) + << DFSDM_CHAWSCDR_AWFORD_Pos; + v |= static_cast((e.config_channel.watchdog_oversampling - 1) & 0xF) + << DFSDM_CHAWSCDR_AWFOSR_Pos; } return v; } - static consteval uint32_t make_chcfgr1(const Entry& e){ + static consteval uint32_t make_chcfgr1(const Entry& e) { uint32_t v = 0; - //DATPACK = 0 -> Standard mode - //DATMPX = 0 -> Comes from an external serial input - //Chinsel = 0 -> channel input are taken from pin of the same channel y + // DATPACK = 0 -> Standard mode + // DATMPX = 0 -> Comes from an external serial input + // Chinsel = 0 -> channel input are taken from pin of the same channel y v |= uint32_t(e.config_channel.spi_clock_sel) << DFSDM_CHCFGR1_SPICKSEL_Pos; - v |= uint32_t (e.config_channel.spi_type) << DFSDM_CHCFGR1_SITP_Pos; + v |= uint32_t(e.config_channel.spi_type) << DFSDM_CHCFGR1_SITP_Pos; v |= uint32_t(e.config_channel.clock_absence) << DFSDM_CHCFGR1_CKABEN_Pos; v |= uint32_t(e.config_channel.short_circuit) << DFSDM_CHCFGR1_SCDEN_Pos; return v; - } - static consteval uint32_t make_chcfgr2(const Entry& e){ + static consteval uint32_t make_chcfgr2(const Entry& e) { uint32_t v = 0; v |= (e.config_channel.offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; v |= uint8_t(e.config_channel.right_shift & 0x0F) << DFSDM_CHCFGR2_DTRBS_Pos; @@ -530,104 +519,121 @@ struct Config_Filter{ template static consteval std::array build(std::span entries) { - if (N == 0) return {}; + if (N == 0) + return {}; std::array cfgs{}; - std::array channels_used{false}; - std::array filters_used{-1,-1,-1,-1}; - std::array channel_order{-1,-1,-1,-1,-1,-1,-1,-1}; + std::array channels_used{false}; + std::array filters_used{-1, -1, -1, -1}; + std::array channel_order{-1, -1, -1, -1, -1, -1, -1, -1}; for (size_t i = 0; i < N; ++i) { - const Entry &e = entries[i]; + const Entry& e = entries[i]; - if(channels_used[e.channel] == true){ + if (channels_used[e.channel] == true) { compile_error("You have two pins using the same channel"); } channels_used[e.channel] = true; Config& cfg = cfgs[i]; - + cfg.gpio_idx = e.gpio_idx; cfg.dma_request = dma_request(e.config_filter.filter); cfg.channel = e.channel; channel_order[cfg.channel] = i; - + cfg.buffer_size = e.buffer_size; cfg.type_conv = e.config_filter.type_conv; cfg.dma_enable = e.config_filter.dma; cfg.filter = e.config_filter.filter; - //add the callbacks + // add the callbacks cfg.overrun_callback = e.config_filter.overrun_callback; cfg.clock_absence_callback = e.config_filter.clock_absence_callback; cfg.short_circuit_callback = e.config_filter.short_circuit_callback; cfg.watchdog_callback = e.config_filter.watchdog_callback; cfg.conversion_complete_callback = e.config_filter.conversion_complete_callback; - - - cfg.init_data_filter.FLTCR1 |= make_fltcr1(e,cfg.filter); + + cfg.init_data_filter.FLTCR1 |= make_fltcr1(e, cfg.filter); cfg.init_data_filter.FLTCR2 |= make_fltcr2(e); - cfg.init_data_filter.FLTFCR |= make_fltfcr(e); + cfg.init_data_filter.FLTFCR |= make_fltfcr(e); cfg.init_data_channel.CHCFGR1 |= make_chcfgr1(e); cfg.init_data_channel.CHCFGR2 |= make_chcfgr2(e); cfg.init_data_channel.CHAWSCDR |= make_chawscdr(e); cfg.init_data_filter.FLTAWHTR |= make_fltawhtr(e); cfg.init_data_filter.FLTAWLTR |= make_fltawltr(e); - if(e.config_filter.type_conv == Type_Conversion::Injected) cfg.init_data_filter.FLTJCHGR |= 1 << e.channel; - if(cfg.filter == 0) cfg.init_data_filter.FLTCR2 |= make_fltcr2_global(); + if (e.config_filter.type_conv == Type_Conversion::Injected) + cfg.init_data_filter.FLTJCHGR |= 1 << e.channel; + if (cfg.filter == 0) + cfg.init_data_filter.FLTCR2 |= make_fltcr2_global(); cfg.latency_cycles = compute_latency(e); - if(filters_used[cfg.filter] != -1){ - if(cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR1 != cfg.init_data_filter.FLTCR1 || - (cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 & 0xFF) != (cfg.init_data_filter.FLTCR2 & 0xFF) || - cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != cfg.init_data_filter.FLTFCR || - cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWLTR != cfg.init_data_filter.FLTAWLTR || - cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWHTR != cfg.init_data_filter.FLTAWHTR || - cfgs[filters_used[cfg.filter]].dma_enable != cfg.dma_enable) - { - compile_error("You have two channels that goes to the same filter with different filter configuration"); + if (filters_used[cfg.filter] != -1) { + if (cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR1 != + cfg.init_data_filter.FLTCR1 || + (cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 & 0xFF) != + (cfg.init_data_filter.FLTCR2 & 0xFF) || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTFCR != + cfg.init_data_filter.FLTFCR || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWLTR != + cfg.init_data_filter.FLTAWLTR || + cfgs[filters_used[cfg.filter]].init_data_filter.FLTAWHTR != + cfg.init_data_filter.FLTAWHTR || + cfgs[filters_used[cfg.filter]].dma_enable != cfg.dma_enable) { + compile_error("You have two channels that goes to the same filter with " + "different filter configuration"); + } + // have the same thing in every register of the filter + // Channel group conversion in injected mode + cfgs[filters_used[cfg.filter]].init_data_filter.FLTJCHGR |= + cfg.init_data_filter.FLTJCHGR; + cfg.init_data_filter.FLTJCHGR = + cfgs[filters_used[cfg.filter]].init_data_filter.FLTJCHGR; + // Watchdog and Extreme detector channel enabled + cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 |= + cfg.init_data_filter.FLTCR2; + cfg.init_data_filter.FLTCR2 = + cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2; + // Watchdog and Extreme detector channel enabled + } + filters_used[cfg.filter] = i; + } + // check that dma buffer size is 1 if there is more than one channel per filter and add the + // buffer_pos_ini to every channel + std::array, 4> channels_filter = {}; + for (size_t i = 0; i < N; i++) { + channels_filter[cfgs[i].filter][cfgs[i].channel] = cfgs[i].buffer_size; + uint8_t active_channels = 0; + // active channels + for (size_t j = 0; j < 8; j++) { + if (channels_filter[cfgs[i].filter][j] != 0) { + active_channels++; } - //have the same thing in every register of the filter - //Channel group conversion in injected mode - cfgs[filters_used[cfg.filter]].init_data_filter.FLTJCHGR |= cfg.init_data_filter.FLTJCHGR; - cfg.init_data_filter.FLTJCHGR = cfgs[filters_used[cfg.filter]].init_data_filter.FLTJCHGR; - //Watchdog and Extreme detector channel enabled - cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2 |= cfg.init_data_filter.FLTCR2; - cfg.init_data_filter.FLTCR2 = cfgs[filters_used[cfg.filter]].init_data_filter.FLTCR2; - //Watchdog and Extreme detector channel enabled } - filters_used[cfg.filter] = i; - } - //check that dma buffer size is 1 if there is more than one channel per filter and add the buffer_pos_ini to every channel - std::array,4> channels_filter = {}; - for(size_t i = 0; i < N; i++){ - channels_filter[cfgs[i].filter][cfgs[i].channel] = cfgs[i].buffer_size; - uint8_t active_channels = 0; - //active channels - for(size_t j = 0; j < 8; j++){ - if(channels_filter[cfgs[i].filter][j] != 0){ - active_channels++; - } + // validate buffers + if (active_channels > 1 && cfgs[i].dma_enable == Dma::Enable) { + // Look the entry because the data that I want to check is easier to access + if (entries[i].config_filter.type_conv == Type_Conversion::Regular) { + compile_error( + "Not allowed Regular conversion + DMA + Multiple channel in the same filter" + ); } - // validate buffers - if(active_channels > 1 && cfgs[i].dma_enable == Dma::Enable){ - //Look the entry because the data that I want to check is easier to access - if(entries[i].config_filter.type_conv == Type_Conversion::Regular){ - compile_error("Not allowed Regular conversion + DMA + Multiple channel in the same filter"); - } - for(size_t j = 0; j < 8; j++){ - if(channels_filter[cfgs[i].filter][j] > 1){ - compile_error("Only allowed buffer_size = 1 when multiple DMA channels are used in the same filter"); - } + for (size_t j = 0; j < 8; j++) { + if (channels_filter[cfgs[i].filter][j] > 1) { + compile_error("Only allowed buffer_size = 1 when multiple DMA channels are " + "used in the same filter"); } } + } } - //give the buffer_pos to every channel I'll give the pos by channel order starting from the low - std::array buffer_pos{0,0,0,0}; - for(std::size_t i = 0; i < 8; i++){ - if(channel_order[i] == -1) continue; + // give the buffer_pos to every channel I'll give the pos by channel order starting from the + // low + std::array buffer_pos{0, 0, 0, 0}; + for (std::size_t i = 0; i < 8; i++) { + if (channel_order[i] == -1) + continue; auto& cfg = cfgs[channel_order[i]]; // If regular conversion give the whole buffer to the channels. - if(cfg.type_conv == Type_Conversion::Regular){ + if (cfg.type_conv == Type_Conversion::Regular) { cfg.buffer_pos_ini = 0; } cfg.buffer_pos_ini = buffer_pos[cfg.filter]; @@ -635,28 +641,30 @@ struct Config_Filter{ } return cfgs; } - static consteval std::size_t dma_entries_for_filter(uint8_t filter, span dma_entries) { + static consteval std::size_t + dma_entries_for_filter(uint8_t filter, span dma_entries) { std::size_t count = 0; for (const auto& entry : dma_entries) { if (entry.instance != dma_filter(filter)) { continue; } - if (entry.id != 0U) { + if (entry.id != 0U) { compile_error("DFSDM: DMA for DFSDM filters must use stream id 0"); } ++count; } return count; } - static consteval bool uses_filter_dma(uint8_t filter, span cfgs){ - for(const auto& cfg : cfgs) { - if(cfg.filter == filter && cfg.dma_enable == Dma::Enable){ + static consteval bool uses_filter_dma(uint8_t filter, span cfgs) { + for (const auto& cfg : cfgs) { + if (cfg.filter == filter && cfg.dma_enable == Dma::Enable) { return true; } } return false; } - static consteval std::size_t dma_contribution_count(span cfgs, span dma_entries) { + static consteval std::size_t + dma_contribution_count(span cfgs, span dma_entries) { std::size_t count = 0; for (uint8_t fidx = 0; fidx < 4U; ++fidx) { if (!uses_filter_dma(fidx, cfgs)) { @@ -701,12 +709,8 @@ struct Config_Filter{ return extra; } static inline uint8_t channels_enabled{}; - static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = { - DFSDM1_Filter0, - DFSDM1_Filter1, - DFSDM1_Filter2, - DFSDM1_Filter3 - }; + static constexpr DFSDM_Filter_TypeDef* filter_hw[4] = + {DFSDM1_Filter0, DFSDM1_Filter1, DFSDM1_Filter2, DFSDM1_Filter3}; static constexpr DFSDM_Channel_TypeDef* channel_hw[8] = { DFSDM1_Channel0, DFSDM1_Channel1, @@ -727,338 +731,332 @@ struct Config_Filter{ return nullptr; } struct Instance { - GPIODomain::Instance *gpio_instance; - DMA_Domain::Instance *dma_instance; - DFSDM_Filter_TypeDef *filter_regs{}; - DFSDM_Channel_TypeDef *channel_regs{}; + GPIODomain::Instance* gpio_instance; + DMA_Domain::Instance* dma_instance; + DFSDM_Filter_TypeDef* filter_regs{}; + DFSDM_Channel_TypeDef* channel_regs{}; Callback watchdog_cb{}; Callback short_circuit_cb{}; Callback clock_absence_cb{}; Callback overrun_cb{}; Callback end_conversion_cb{}; - - uint32_t latency_cycles; + + uint32_t latency_cycles; uint8_t channel; uint8_t filter; Type_Conversion type_conv; Dma dma_enable; - + volatile int32_t* buffer = nullptr; size_t length_buffer{}; - private: - bool is_enabled_channel() const{ - return (channel_regs->CHCFGR1 & DFSDM_CHCFGR1_CHEN_Msk); - } - bool is_enabled_filter() const { - return (filter_regs->FLTCR1 & DFSDM_FLTCR1_DFEN_Msk); - } - bool is_enabled_DFSDM() const{ - return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN_Msk); - } - void enable_filter() { - filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; - } - void enable_channel(){ - channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; - } - void enable_DFSDM_Peripheral(){ - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - } - void disable_filter() { - filter_regs->FLTCR1 &= ~(DFSDM_FLTCR1_DFEN_Msk); - } - void disable_channel(){ - channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN); - } - void disable_DFSDM_Peripheral(){ - DFSDM1_Channel0->CHCFGR1 &= ~(DFSDM_CHCFGR1_DFSDMEN); - } + private: + bool is_enabled_channel() const { return (channel_regs->CHCFGR1 & DFSDM_CHCFGR1_CHEN_Msk); } + bool is_enabled_filter() const { return (filter_regs->FLTCR1 & DFSDM_FLTCR1_DFEN_Msk); } + bool is_enabled_DFSDM() const { + return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN_Msk); + } + void enable_filter() { filter_regs->FLTCR1 |= DFSDM_FLTCR1_DFEN; } + void enable_channel() { channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; } + void enable_DFSDM_Peripheral() { DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; } + void disable_filter() { filter_regs->FLTCR1 &= ~(DFSDM_FLTCR1_DFEN_Msk); } + void disable_channel() { channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN); } + void disable_DFSDM_Peripheral() { DFSDM1_Channel0->CHCFGR1 &= ~(DFSDM_CHCFGR1_DFSDMEN); } + + public: + DFSDM_Filter_TypeDef* get_filter_struct() const { return filter_regs; } + DFSDM_Channel_TypeDef* get_channel_struct() const { return channel_regs; } + bool is_enabled() { + return is_enabled_DFSDM() && is_enabled_channel() && is_enabled_filter(); + } + void enable() { + // just in case enable everything to work + enable_DFSDM_Peripheral(); + enable_channel(); + enable_filter(); + } - public: - DFSDM_Filter_TypeDef* get_filter_struct() const{ - return filter_regs; - } - DFSDM_Channel_TypeDef* get_channel_struct() const{ - return channel_regs; - } - bool is_enabled(){ - return is_enabled_DFSDM() && is_enabled_channel() && is_enabled_filter(); + void disable() { + // only disable channel + channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN_Msk); + } + + void enable_clock_absence_detector() { channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CKABEN; } + void enable_short_circuit_detector() { channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_SCDEN; } + void disable_clock_absence_detector() { channel_regs->CHCFGR1 &= (~DFSDM_CHCFGR1_CKABEN); } + void disable_short_circuit_detector() { channel_regs->CHCFGR1 &= (~DFSDM_CHCFGR1_SCDEN); } + void enable_overrun() { filter_regs->FLTCR2 |= DFSDM_FLTCR2_ROVRIE; } + void disable_overrun() { filter_regs->FLTCR2 &= (~DFSDM_FLTCR2_ROVRIE); } + void enable_watchdog() { filter_regs->FLTCR2 |= DFSDM_FLTCR2_AWDCH; } + void disable_watchdog() { filter_regs->FLTCR2 &= (~DFSDM_FLTCR2_AWDCH); } + /*channel functions */ + void change_offset(int32_t offset) { + channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_OFFSET_Msk); + channel_regs->CHCFGR2 |= (offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; + } + + /*Filter functions*/ + void start() { + if (!is_enabled()) + enable(); + + if (type_conv == Type_Conversion::Regular) { + filter_regs->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; // regular + } else { + filter_regs->FLTCR1 |= DFSDM_FLTCR1_JSWSTART; // injected } - void enable() { - //just in case enable everything to work - enable_DFSDM_Peripheral(); - enable_channel(); + } + + void modify_sync_conversion(Sync_Conversion type) { + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) + disable_filter(); + + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RSYNC_Msk; + filter_regs->FLTCR1 |= (uint32_t(type) << DFSDM_FLTCR1_RSYNC_Pos); + + if (was_enabled_filter) enable_filter(); - } + } + void modify_regular_mode(Regular_Mode mode) { + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) + disable_filter(); - void disable() { - //only disable channel - channel_regs->CHCFGR1 &= ~(DFSDM_CHCFGR1_CHEN_Msk); - } - - void enable_clock_absence_detector(){ - channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_CKABEN; - } - void enable_short_circuit_detector(){ - channel_regs->CHCFGR1 |= DFSDM_CHCFGR1_SCDEN; - } - void disable_clock_absence_detector(){ - channel_regs->CHCFGR1 &= (~DFSDM_CHCFGR1_CKABEN); - } - void disable_short_circuit_detector(){ - channel_regs->CHCFGR1 &= (~DFSDM_CHCFGR1_SCDEN); - } - void enable_overrun(){ - filter_regs->FLTCR2 |= DFSDM_FLTCR2_ROVRIE; - } - void disable_overrun(){ - filter_regs->FLTCR2 &= (~DFSDM_FLTCR2_ROVRIE); - } - void enable_watchdog(){ - filter_regs->FLTCR2 |= DFSDM_FLTCR2_AWDCH; - } - void disable_watchdog(){ - filter_regs->FLTCR2 &= (~DFSDM_FLTCR2_AWDCH); - } - /*channel functions */ - void change_offset(int32_t offset){ - channel_regs->CHCFGR2 &= ~(DFSDM_CHCFGR2_OFFSET_Msk); - channel_regs->CHCFGR2 |= (offset & 0x00FFFFFF) << DFSDM_CHCFGR2_OFFSET_Pos; - } - - /*Filter functions*/ - void start() - { - if (!is_enabled()) enable(); - - if(type_conv == Type_Conversion::Regular) { - filter_regs->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; // regular - } else { - filter_regs->FLTCR1 |= DFSDM_FLTCR1_JSWSTART; // injected - } - } + filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCONT_Msk; + filter_regs->FLTCR1 |= (uint32_t(mode) << DFSDM_FLTCR1_RCONT_Pos); - void modify_sync_conversion(Sync_Conversion type) { - bool was_enabled_filter = is_enabled_filter(); - if (was_enabled_filter) disable_filter(); + if (was_enabled_filter) + enable_filter(); + } - filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RSYNC_Msk; - filter_regs->FLTCR1 |= (uint32_t(type) << DFSDM_FLTCR1_RSYNC_Pos); + bool modify_oversampling(uint16_t oversampling) { + if (oversampling == 0) + return false; - if (was_enabled_filter) enable_filter(); - } - void modify_regular_mode(Regular_Mode mode) { - bool was_enabled_filter = is_enabled_filter(); - if(was_enabled_filter) disable_filter(); - - filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCONT_Msk; - filter_regs->FLTCR1 |= (uint32_t(mode) << DFSDM_FLTCR1_RCONT_Pos); - - if(was_enabled_filter) enable_filter(); - } - - bool modify_oversampling(uint16_t oversampling) { - if (oversampling == 0) return false; + uint32_t ford = (filter_regs->FLTFCR & DFSDM_FLTFCR_FORD_Msk) >> DFSDM_FLTFCR_FORD_Pos; - uint32_t ford = (filter_regs->FLTFCR & DFSDM_FLTFCR_FORD_Msk) >> DFSDM_FLTFCR_FORD_Pos; + if (ford <= 3 && oversampling > Oversampling_MAX) + return false; + if (ford == 4 && oversampling > Oversampling_MAX_Filter_4) + return false; + if (ford == 5 && oversampling > Oversampling_MAX_Filter_5) + return false; - if (ford <= 3 && oversampling > Oversampling_MAX) return false; - if (ford == 4 && oversampling > Oversampling_MAX_Filter_4) return false; - if (ford == 5 && oversampling > Oversampling_MAX_Filter_5) return false; + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) + disable_filter(); - bool was_enabled_filter = is_enabled_filter(); - if (was_enabled_filter) disable_filter(); + filter_regs->FLTFCR &= ~DFSDM_FLTFCR_FOSR_Msk; + filter_regs->FLTFCR |= ((uint32_t)(oversampling - 1) << DFSDM_FLTFCR_FOSR_Pos); + if (was_enabled_filter) + enable_filter(); + return true; + } - filter_regs->FLTFCR &= ~DFSDM_FLTFCR_FOSR_Msk; - filter_regs->FLTFCR |= ((uint32_t)(oversampling -1) << DFSDM_FLTFCR_FOSR_Pos); - if (was_enabled_filter) enable_filter(); - return true; - } + bool modify_integrator(uint8_t integrator) { - bool modify_integrator(uint8_t integrator) { + if (integrator == 0 || integrator > 256) + return false; + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) + disable_filter(); - if (integrator == 0 || integrator > 256) return false; - bool was_enabled_filter = is_enabled_filter(); - if (was_enabled_filter) disable_filter(); + filter_regs->FLTFCR &= ~DFSDM_FLTFCR_IOSR_Msk; + filter_regs->FLTFCR |= ((integrator - 1) << DFSDM_FLTFCR_IOSR_Pos); - filter_regs->FLTFCR &= ~DFSDM_FLTFCR_IOSR_Msk; - filter_regs->FLTFCR |= ((integrator -1) << DFSDM_FLTFCR_IOSR_Pos); + if (was_enabled_filter) + enable_filter(); + return true; + } - if (was_enabled_filter) enable_filter(); - return true; - } + bool modify_filter_order(Filter_Type type) { - bool modify_filter_order(Filter_Type type) { + uint32_t fosr = + ((filter_regs->FLTFCR & DFSDM_FLTFCR_FOSR_Msk) >> DFSDM_FLTFCR_FOSR_Pos); - uint32_t fosr =((filter_regs->FLTFCR & DFSDM_FLTFCR_FOSR_Msk) >> DFSDM_FLTFCR_FOSR_Pos); + if (type == Filter_Type::Sinc4 && fosr > Oversampling_MAX_Filter_4) + return false; + if (type == Filter_Type::Sinc5 && fosr > Oversampling_MAX_Filter_5) + return false; - if (type == Filter_Type::Sinc4 && fosr > Oversampling_MAX_Filter_4) return false; - if (type == Filter_Type::Sinc5 && fosr > Oversampling_MAX_Filter_5) return false; + bool was_enabled_filter = is_enabled_filter(); + if (was_enabled_filter) + disable_filter(); - bool was_enabled_filter = is_enabled_filter(); - if (was_enabled_filter) disable_filter(); + filter_regs->FLTFCR &= ~DFSDM_FLTFCR_FORD_Msk; + filter_regs->FLTFCR |= (uint32_t(type) << DFSDM_FLTFCR_FORD_Pos); - filter_regs->FLTFCR &= ~DFSDM_FLTFCR_FORD_Msk; - filter_regs->FLTFCR |= (uint32_t(type) - << DFSDM_FLTFCR_FORD_Pos); + if (was_enabled_filter) + enable_filter(); + return true; + } + int32_t read(size_t pos) { + if (pos >= this->length_buffer) { + ErrorHandler("DFSDM: Trying to access to a memory section that is not from the " + "channel buffer"); + } + return ( + (this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos + ); // The constants values are the same for regular than injected + } + int32_t read() { + return ( + static_cast(this->buffer[0] & DFSDM_FLTJDATAR_JDATA_Msk) >> + DFSDM_FLTJDATAR_JDATA_Pos + ); // The constants values are the same for regular than injected + } + uint32_t check_latency_cycles() { + return filter_regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; + } - if (was_enabled_filter) enable_filter(); - return true; - } - int32_t read(size_t pos){ - if(pos >= this->length_buffer){ - ErrorHandler("DFSDM: Trying to access to a memory section that is not from the channel buffer"); - } - return ((this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos); // The constants values are the same for regular than injected - } - int32_t read(){ - return (static_cast(this->buffer[0] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos); // The constants values are the same for regular than injected - } - uint32_t check_latency_cycles() { - return filter_regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; - } + uint32_t check_min_extreme_detector() { + return filter_regs->FLTEXMIN >> DFSDM_FLTEXMIN_EXMIN_Pos; + } - uint32_t check_min_extreme_detector() { - return filter_regs->FLTEXMIN >> DFSDM_FLTEXMIN_EXMIN_Pos; - } + uint32_t check_max_extreme_detector() { + return filter_regs->FLTEXMAX >> DFSDM_FLTEXMAX_EXMAX_Pos; + } + void modify_watchdog_lth(uint32_t value) { - uint32_t check_max_extreme_detector() { - return filter_regs->FLTEXMAX >> DFSDM_FLTEXMAX_EXMAX_Pos; - } - void modify_watchdog_lth(uint32_t value) { + filter_regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; + bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); - filter_regs->FLTAWLTR &= ~DFSDM_FLTAWLTR_AWLT_Msk; - bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); + if (fast) + filter_regs->FLTAWLTR = + (value & 0xFFFF) + << (DFSDM_FLTAWLTR_AWLT_Pos + DFSDM_FLTAWLTR_AWLT_Pos); // Only 16 bits + else + filter_regs->FLTAWLTR = (value & 0xFFFFFF) << DFSDM_FLTAWLTR_AWLT_Pos; // 24 bits + } - if (fast) - filter_regs->FLTAWLTR = (value & 0xFFFF) << (DFSDM_FLTAWLTR_AWLT_Pos + DFSDM_FLTAWLTR_AWLT_Pos); // Only 16 bits - else - filter_regs->FLTAWLTR = (value & 0xFFFFFF) << DFSDM_FLTAWLTR_AWLT_Pos; // 24 bits - } + void modify_watchdog_hth(uint32_t value) { + filter_regs->FLTAWHTR &= ~DFSDM_FLTAWHTR_AWHT_Msk; + bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); - void modify_watchdog_hth(uint32_t value) { - filter_regs->FLTAWHTR &= ~DFSDM_FLTAWHTR_AWHT_Msk; - bool fast = (filter_regs->FLTCR1 & DFSDM_FLTCR1_AWFSEL); - - if (fast) - filter_regs->FLTAWHTR = (value & 0xFFFF) << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); - else - filter_regs->FLTAWHTR = (value & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; - } - //get the last conversion from a filter - static uint8_t get_last_conversion_from_filter(uint8_t filter, Type_Conversion conv){ - uint8_t channel = 0xFF; - switch(filter){ - case 0: - if(conv == Type_Conversion::Injected){ - channel = (DFSDM1_Filter0->FLTJDATAR & 0x7); - }else{ - channel = (DFSDM1_Filter0->FLTRDATAR & 0x7); - } - break; - case 1: - if(conv == Type_Conversion::Injected){ - channel = (DFSDM1_Filter1->FLTJDATAR & 0x7); - }else{ - channel = (DFSDM1_Filter1->FLTRDATAR & 0x7); - } - break; + if (fast) + filter_regs->FLTAWHTR = (value & 0xFFFF) + << (DFSDM_FLTAWHTR_AWHT_Pos + DFSDM_FLTAWHTR_AWHT_Pos); + else + filter_regs->FLTAWHTR = (value & 0xFFFFFF) << DFSDM_FLTAWHTR_AWHT_Pos; + } + // get the last conversion from a filter + static uint8_t get_last_conversion_from_filter(uint8_t filter, Type_Conversion conv) { + uint8_t channel = 0xFF; + switch (filter) { + case 0: + if (conv == Type_Conversion::Injected) { + channel = (DFSDM1_Filter0->FLTJDATAR & 0x7); + } else { + channel = (DFSDM1_Filter0->FLTRDATAR & 0x7); + } + break; + case 1: + if (conv == Type_Conversion::Injected) { + channel = (DFSDM1_Filter1->FLTJDATAR & 0x7); + } else { + channel = (DFSDM1_Filter1->FLTRDATAR & 0x7); + } + break; - case 2: - if(conv == Type_Conversion::Injected){ - channel = (DFSDM1_Filter2->FLTJDATAR & 0x7); - }else{ - channel = (DFSDM1_Filter2->FLTRDATAR & 0x7); - } - break; - case 3: - if(conv == Type_Conversion::Injected){ - channel = (DFSDM1_Filter3->FLTJDATAR & 0x7); - }else{ - channel = (DFSDM1_Filter3->FLTRDATAR & 0x7); - } - break; - default: break; + case 2: + if (conv == Type_Conversion::Injected) { + channel = (DFSDM1_Filter2->FLTJDATAR & 0x7); + } else { + channel = (DFSDM1_Filter2->FLTRDATAR & 0x7); + } + break; + case 3: + if (conv == Type_Conversion::Injected) { + channel = (DFSDM1_Filter3->FLTJDATAR & 0x7); + } else { + channel = (DFSDM1_Filter3->FLTRDATAR & 0x7); } - return channel; + break; + default: + break; } - }; - __attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32) - static inline int32_t DFSDM_Buffer_Pool[MAX_BUFFER_SIZE_TOTAL]; - static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; - template cfgs> struct Init { + return channel; + } + }; + __attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32 + ) static inline int32_t DFSDM_Buffer_Pool[MAX_BUFFER_SIZE_TOTAL]; + static inline Instance* channel_instances[DFSDM_CHANNEL_DOMAIN::max_instances] = {nullptr}; + template cfgs> struct Init { static constexpr auto sizes = calculate_buffer_sizes(cfgs); - //calculamos tamaño tanto para filtros con DMA como para los que no tienen - static constexpr std::size_t total_slots = sizes.filter0 + sizes.filter1 + sizes.filter2 + sizes.filter3; - - //Filter Buffers + // calculamos tamaño tanto para filtros con DMA como para los que no tienen + static constexpr std::size_t total_slots = + sizes.filter0 + sizes.filter1 + sizes.filter2 + sizes.filter3; + + // Filter Buffers alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR - static inline int32_t Buffer_Filter0[sizes.filter0 > 0 ? sizes.filter0 : 1]{}; + static inline int32_t Buffer_Filter0[sizes.filter0 > 0 ? sizes.filter0 : 1]{}; alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR - static inline int32_t Buffer_Filter1[sizes.filter1 > 0 ? sizes.filter1 : 1]{}; + static inline int32_t Buffer_Filter1[sizes.filter1 > 0 ? sizes.filter1 : 1]{}; alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR - static inline int32_t Buffer_Filter2[sizes.filter2 > 0 ? sizes.filter2 : 1]{}; + static inline int32_t Buffer_Filter2[sizes.filter2 > 0 ? sizes.filter2 : 1]{}; alignas(32) STLIB_DFSDM_DMA_BUFFER_ATTR - static inline int32_t Buffer_Filter3[sizes.filter3 > 0 ? sizes.filter3 : 1]{}; + static inline int32_t Buffer_Filter3[sizes.filter3 > 0 ? sizes.filter3 : 1]{}; - static inline std::array instances{}; - static constexpr std::size_t buffer_size_for(uint8_t filter){ - switch(filter){ - case 0: - return sizes.filter0; - case 1: - return sizes.filter1; - case 2: - return sizes.filter2; - case 3: - return sizes.filter3; + static constexpr std::size_t buffer_size_for(uint8_t filter) { + switch (filter) { + case 0: + return sizes.filter0; + case 1: + return sizes.filter1; + case 2: + return sizes.filter2; + case 3: + return sizes.filter3; } ErrorHandler("Filter cannot be bigger than 3"); return 0; } - static int32_t* get_buffer_filter(uint8_t filter){ - switch (filter){ - case 0: - return Buffer_Filter0; - case 1: - return Buffer_Filter1; - case 2: - return Buffer_Filter2; - case 3: - return Buffer_Filter3; + static int32_t* get_buffer_filter(uint8_t filter) { + switch (filter) { + case 0: + return Buffer_Filter0; + case 1: + return Buffer_Filter1; + case 2: + return Buffer_Filter2; + case 3: + return Buffer_Filter3; } ErrorHandler("Filter cannot be bigger than 3"); return 0; - } - - static int32_t* get_buffer_pointer(const Config cfg){ + + static int32_t* get_buffer_pointer(const Config cfg) { return get_buffer_filter(cfg.filter) + cfg.buffer_pos_ini; } - static void init(std::span gpio_instances,std::span dma_instances) { - if(N == 0) return; - std::array filters_configured = {false,false,false,false}; - RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM clock + static void init( + std::span gpio_instances, + std::span dma_instances + ) { + if (N == 0) + return; + std::array filters_configured = {false, false, false, false}; + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; // Activate the DFSDM clock for (size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; + const Config& cfg = cfgs[i]; filter_hw[cfg.filter]->FLTCR1 &= ~DFSDM_FLTCR1_DFEN; channel_hw[cfg.channel]->CHCFGR1 &= ~DFSDM_CHCFGR1_CHEN; } for (std::size_t i = 0; i < N; ++i) { - const Config &cfg = cfgs[i]; - Instance &inst = instances[i]; + const Config& cfg = cfgs[i]; + Instance& inst = instances[i]; inst.gpio_instance = &gpio_instances[cfg.gpio_idx]; - if(cfg.dma_enable == Dma::Enable){ - inst.dma_instance = find_dma_instance(cfg.dma_request,dma_instances); - }else{ + if (cfg.dma_enable == Dma::Enable) { + inst.dma_instance = find_dma_instance(cfg.dma_request, dma_instances); + } else { inst.dma_instance = nullptr; } - + inst.filter_regs = filter_hw[cfg.filter]; inst.channel_regs = channel_hw[cfg.channel]; @@ -1070,169 +1068,187 @@ struct Config_Filter{ inst.length_buffer = cfg.buffer_size; inst.buffer = get_buffer_pointer(cfg); - //callbacks + // callbacks inst.overrun_cb = cfg.overrun_callback; inst.short_circuit_cb = cfg.short_circuit_callback; inst.watchdog_cb = cfg.watchdog_callback; inst.end_conversion_cb = cfg.conversion_complete_callback; - if(!filters_configured[cfg.filter]){ - //add everything to the register of the filter + if (!filters_configured[cfg.filter]) { + // add everything to the register of the filter inst.filter_regs->FLTCR1 |= cfg.init_data_filter.FLTCR1; - if(inst.type_conv == Type_Conversion::Regular){ + if (inst.type_conv == Type_Conversion::Regular) { inst.filter_regs->FLTCR1 &= ~DFSDM_FLTCR1_RCH_Msk; inst.filter_regs->FLTCR1 |= uint32_t(inst.channel) << DFSDM_FLTCR1_RCH_Pos; } inst.filter_regs->FLTCR2 |= cfg.init_data_filter.FLTCR2; - inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; + inst.filter_regs->FLTFCR |= cfg.init_data_filter.FLTFCR; inst.filter_regs->FLTAWHTR |= cfg.init_data_filter.FLTAWHTR; inst.filter_regs->FLTAWLTR |= cfg.init_data_filter.FLTAWLTR; inst.filter_regs->FLTJCHGR = cfg.init_data_filter.FLTJCHGR; - + filters_configured[cfg.filter] = true; - //add dma - if(cfg.dma_enable == Dma::Enable){ + // add dma + if (cfg.dma_enable == Dma::Enable) { uint32_t SrcAddress; - if(inst.type_conv == Type_Conversion::Regular){ + if (inst.type_conv == Type_Conversion::Regular) { SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTRDATAR; - }else{ + } else { SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTJDATAR; } - uint32_t DstAddress = reinterpret_cast(get_buffer_filter(inst.filter));//Transform the pointer to a value - inst.dma_instance->start(SrcAddress,DstAddress,buffer_size_for(inst.filter)); + uint32_t DstAddress = + reinterpret_cast(get_buffer_filter(inst.filter) + ); // Transform the pointer to a value + inst.dma_instance + ->start(SrcAddress, DstAddress, buffer_size_for(inst.filter)); } - } - //add everything to the channel register + } + // add everything to the channel register inst.channel_regs->CHCFGR1 |= cfg.init_data_channel.CHCFGR1; inst.channel_regs->CHCFGR2 |= cfg.init_data_channel.CHCFGR2; inst.channel_regs->CHAWSCDR |= cfg.init_data_channel.CHAWSCDR; - - - //update channel_instances + + // update channel_instances channel_instances[inst.channel] = &inst; channels_enabled |= 1 << inst.channel; } - if(N > 0){; - //Activate the DFSDM GLOBAL Interface + if (N > 0) { + ; + // Activate the DFSDM GLOBAL Interface DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - for(int i = 0; i < 8; i++){ - if(channels_enabled & (1 << i)){ + for (int i = 0; i < 8; i++) { + if (channels_enabled & (1 << i)) { channel_hw[i]->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; } - } - for(int i = 0; i < 4;i++){ - if(filters_configured[i] == true){ + for (int i = 0; i < 4; i++) { + if (filters_configured[i] == true) { filter_hw[i]->FLTCR1 |= DFSDM_FLTCR1_DFEN; } - } - //activate the NVIC - for(int i = 0; i < 4; i++){ - if(filters_configured[i] == true){ - switch(i){ - case 0: NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); break; - case 1: NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); break; - case 2: NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); break; - case 3: NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); break; + // activate the NVIC + for (int i = 0; i < 4; i++) { + if (filters_configured[i] == true) { + switch (i) { + case 0: + NVIC_EnableIRQ(DFSDM1_FLT0_IRQn); + break; + case 1: + NVIC_EnableIRQ(DFSDM1_FLT1_IRQn); + break; + case 2: + NVIC_EnableIRQ(DFSDM1_FLT2_IRQn); + break; + case 3: + NVIC_EnableIRQ(DFSDM1_FLT3_IRQn); + break; } } - } + } } } }; static inline volatile size_t idx_filter[4] = {}; - static void handle_irq(uint8_t filter_index) - { + static void handle_irq(uint8_t filter_index) { - DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; + DFSDM_Filter_TypeDef* filter = filter_hw[filter_index]; - uint32_t isr = filter->FLTISR; + uint32_t isr = filter->FLTISR; - if(isr & DFSDM_FLTISR_REOCF_Msk){ - //Save it in the address provide by the user - int32_t data = filter->FLTRDATAR; - Instance* inst = channel_instances[(data & DFSDM_FLTRDATAR_RDATACH_Msk)>>DFSDM_FLTRDATAR_RDATACH_Pos]; - if(inst != nullptr && inst->buffer != nullptr){ - if(inst->dma_enable == Dma::Disable)[[likely]]{ - inst->buffer[idx_filter[inst->filter]] = data; - idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; - } - if(inst->end_conversion_cb != nullptr){ - inst->end_conversion_cb(); - } + if (isr & DFSDM_FLTISR_REOCF_Msk) { + // Save it in the address provide by the user + int32_t data = filter->FLTRDATAR; + Instance* inst = channel_instances + [(data & DFSDM_FLTRDATAR_RDATACH_Msk) >> DFSDM_FLTRDATAR_RDATACH_Pos]; + if (inst != nullptr && inst->buffer != nullptr) { + if (inst->dma_enable == Dma::Disable) [[likely]] { + inst->buffer[idx_filter[inst->filter]] = data; + idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; } - } - if(isr & DFSDM_FLTISR_JEOCF_Msk){ - //Save it in the address provide by the user - int32_t data = filter->FLTJDATAR; - uint8_t channel = (data & DFSDM_FLTJDATAR_JDATACH_Msk); - Instance* inst = channel_instances[channel]; - if(inst != nullptr && inst->buffer != nullptr){ - if(inst->dma_enable == Dma::Disable)[[likely]]{ - inst->buffer[idx_filter[inst->filter]] = data; - idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; - } - if(inst->end_conversion_cb != nullptr){ - inst->end_conversion_cb(); - } + if (inst->end_conversion_cb != nullptr) { + inst->end_conversion_cb(); } } - if(isr & DFSDM_FLTISR_ROVRF_Msk){ - Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; - if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRROVRF_Msk; - } - if(isr & DFSDM_FLTISR_JOVRF_Msk){ - Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; - if(inst != nullptr && inst->overrun_cb != nullptr) inst->overrun_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRJOVRF_Msk; - } - if(isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)){ - uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; - if(channel_instances[ch] != nullptr && channel_instances[ch]->short_circuit_cb != nullptr) channel_instances[ch]->short_circuit_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; - } - if(isr & (channels_enabled << DFSDM_FLTISR_CKABF_Pos)){ - uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_CKABF_Msk)>> DFSDM_FLTISR_CKABF_Pos; - if(channel_instances[ch] != nullptr && channel_instances[ch]->clock_absence_cb != nullptr) channel_instances[ch]->clock_absence_cb(); - //clear - filter->FLTICR |= DFSDM_FLTICR_CLRCKABF; - } - //Analog watchdog - if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) - { - if(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk){ - uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk); - if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); - //clear - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + } + if (isr & DFSDM_FLTISR_JEOCF_Msk) { + // Save it in the address provide by the user + int32_t data = filter->FLTJDATAR; + uint8_t channel = (data & DFSDM_FLTJDATAR_JDATACH_Msk); + Instance* inst = channel_instances[channel]; + if (inst != nullptr && inst->buffer != nullptr) { + if (inst->dma_enable == Dma::Disable) [[likely]] { + inst->buffer[idx_filter[inst->filter]] = data; + idx_filter[inst->filter] = (idx_filter[inst->filter] + 1) % inst->length_buffer; } - if(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk){ - uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk); - if(channel_instances[ch] != nullptr && channel_instances[ch]->watchdog_cb != nullptr) channel_instances[ch]->watchdog_cb(); - //clear - filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + if (inst->end_conversion_cb != nullptr) { + inst->end_conversion_cb(); } } } - }; -struct DFSDM_CLK_DOMAIN{ + if (isr & DFSDM_FLTISR_ROVRF_Msk) { + Instance* inst = channel_instances[filter->FLTRDATAR & DFSDM_FLTRDATAR_RDATACH_Msk]; + if (inst != nullptr && inst->overrun_cb != nullptr) + inst->overrun_cb(); + // clear + filter->FLTICR |= DFSDM_FLTICR_CLRROVRF_Msk; + } + if (isr & DFSDM_FLTISR_JOVRF_Msk) { + Instance* inst = channel_instances[filter->FLTJDATAR & DFSDM_FLTJDATAR_JDATACH_Msk]; + if (inst != nullptr && inst->overrun_cb != nullptr) + inst->overrun_cb(); + // clear + filter->FLTICR |= DFSDM_FLTICR_CLRJOVRF_Msk; + } + if (isr & (channels_enabled << DFSDM_FLTICR_CLRSCDF_Pos)) { + uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_SCDF_Msk) >> DFSDM_FLTISR_SCDF_Pos; + if (channel_instances[ch] != nullptr && + channel_instances[ch]->short_circuit_cb != nullptr) + channel_instances[ch]->short_circuit_cb(); + // clear + filter->FLTICR |= DFSDM_FLTICR_CLRSCDF; + } + if (isr & (channels_enabled << DFSDM_FLTISR_CKABF_Pos)) { + uint32_t ch = __builtin_ctz(isr & DFSDM_FLTISR_CKABF_Msk) >> DFSDM_FLTISR_CKABF_Pos; + if (channel_instances[ch] != nullptr && + channel_instances[ch]->clock_absence_cb != nullptr) + channel_instances[ch]->clock_absence_cb(); + // clear + filter->FLTICR |= DFSDM_FLTICR_CLRCKABF; + } + // Analog watchdog + if (isr & (DFSDM_FLTISR_AWDF << DFSDM_FLTISR_AWDF_Pos)) { + if (filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk) { + uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWHTF_Msk); + if (channel_instances[ch] != nullptr && + channel_instances[ch]->watchdog_cb != nullptr) + channel_instances[ch]->watchdog_cb(); + // clear + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWHTF; + } + if (filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk) { + uint32_t ch = __builtin_ctz(filter->FLTAWSR & DFSDM_FLTAWSR_AWLTF_Msk); + if (channel_instances[ch] != nullptr && + channel_instances[ch]->watchdog_cb != nullptr) + channel_instances[ch]->watchdog_cb(); + // clear + filter->FLTAWCFR = DFSDM_FLTAWCFR_CLRAWLTF; + } + } + } +}; +struct DFSDM_CLK_DOMAIN { static constexpr GPIODomain::Pin valid_clk_pins[] = { - {GPIODomain::Port::C,GPIO_PIN_2}, - {GPIODomain::Port::B, GPIO_PIN_0}, - {GPIODomain::Port::E, GPIO_PIN_9}, - {GPIODomain::Port::D, GPIO_PIN_3}, - {GPIODomain::Port::D, GPIO_PIN_10} + {GPIODomain::Port::C, GPIO_PIN_2}, + {GPIODomain::Port::B, GPIO_PIN_0}, + {GPIODomain::Port::E, GPIO_PIN_9}, + {GPIODomain::Port::D, GPIO_PIN_3}, + {GPIODomain::Port::D, GPIO_PIN_10} }; static consteval bool is_valid_dfsdm_clk_pin(GPIODomain::Port port, uint32_t pin) { bool found = false; - for (auto &p : valid_clk_pins) { + for (auto& p : valid_clk_pins) { if (p.port == port && p.pin == pin) { found = true; break; @@ -1242,106 +1258,110 @@ struct DFSDM_CLK_DOMAIN{ } static consteval GPIODomain::AlternateFunction dfsdm_clk_af(const GPIODomain::Pin& pin) { - if ((pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_2)|| (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_0)) - return GPIODomain::AlternateFunction::AF6; - return GPIODomain::AlternateFunction::AF3; //In every other case + if ((pin.port == GPIODomain::Port::C && pin.pin == GPIO_PIN_2) || + (pin.port == GPIODomain::Port::B && pin.pin == GPIO_PIN_0)) + return GPIODomain::AlternateFunction::AF6; + return GPIODomain::AlternateFunction::AF3; // In every other case } - struct Entry{ + struct Entry { size_t gpio_idx; uint16_t clk_divider; }; - - struct DFSDM_CLK{ + + struct DFSDM_CLK { using domain = DFSDM_CLK_DOMAIN; GPIODomain::GPIO gpio; GPIODomain::Pin pin; uint8_t clk_divider; - consteval DFSDM_CLK(const GPIODomain::Pin &pin,uint8_t clk_divider = 100): // clk_divider = 100 -> 1Mhz - gpio{pin,GPIODomain::OperationMode::ALT_PP,GPIODomain::Pull::None, GPIODomain::Speed::High,dfsdm_clk_af(pin)}, - pin(pin), - clk_divider(clk_divider) - {} - - - template consteval std::size_t inscribe(Ctx &ctx) const{ + consteval DFSDM_CLK(const GPIODomain::Pin& pin, uint8_t clk_divider = 100) + : // clk_divider = 100 -> 1Mhz + gpio{ + pin, + GPIODomain::OperationMode::ALT_PP, + GPIODomain::Pull::None, + GPIODomain::Speed::High, + dfsdm_clk_af(pin) + }, + pin(pin), clk_divider(clk_divider) {} + + template consteval std::size_t inscribe(Ctx& ctx) const { const auto gpio_idx = gpio.inscribe(ctx); - if(!is_valid_dfsdm_clk_pin(pin.port,pin.pin)){ + if (!is_valid_dfsdm_clk_pin(pin.port, pin.pin)) { compile_error("Invalid clk dfsdm pin used"); } - if(clk_divider < 7 || clk_divider > 256){ + if (clk_divider < 7 || clk_divider > 256) { compile_error("The clk_divider has to be between 7 and 256"); } - Entry e{.gpio_idx = gpio_idx,.clk_divider = clk_divider}; - return ctx.template add(e,this); + Entry e{.gpio_idx = gpio_idx, .clk_divider = clk_divider}; + return ctx.template add(e, this); } }; static constexpr std::size_t max_instances{1}; - struct Config{ + struct Config { size_t gpio_idx; uint16_t clk_divider; }; template static consteval std::array build(std::span entries) { std::array cfgs{}; - static_assert(N <= 1,"You can't have more than one clock_out"); + static_assert(N <= 1, "You can't have more than one clock_out"); for (std::size_t i = 0; i < N; ++i) { - cfgs[i] = { - .gpio_idx = entries[i].gpio_idx, - .clk_divider = entries[i].clk_divider - }; + cfgs[i] = {.gpio_idx = entries[i].gpio_idx, .clk_divider = entries[i].clk_divider}; } return cfgs; } - struct Instance{ - GPIODomain::Instance *gpio_instance; - uint16_t clk_divider; - /*Already called in init()*/ - void init(){ - RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; //Activate the DFSDM Clock OUT in RCC by default it uses rcc_pclk2 - //Disable DFSDMEN to change parameters - DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN; - - //CKOUTSRC = 0 -> kernel clock (rcc_pclk2) It works 137,5 Mhz, - DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTSRC; - //CKOUT Divider. Divider = CKOUTDIV + 1 - DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTDIV; - - DFSDM1_Channel0->CHCFGR1 |= uint32_t(clk_divider -1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; - - //enable the DFSDM Global Interface - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - } - bool disable(){ - DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN; - return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN) == 0; - } - bool enable(){ - DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; - return(DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN); - } - bool change_divider(uint8_t div){ - if(div < 4) return false; - clk_divider = div; - if(disable()){ - init(); - return true; - } + struct Instance { + GPIODomain::Instance* gpio_instance; + uint16_t clk_divider; + /*Already called in init()*/ + void init() { + RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; // Activate the DFSDM Clock OUT in RCC by default + // it uses rcc_pclk2 + // Disable DFSDMEN to change parameters + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN; + + // CKOUTSRC = 0 -> kernel clock (rcc_pclk2) It works 137,5 Mhz, + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTSRC; + // CKOUT Divider. Divider = CKOUTDIV + 1 + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_CKOUTDIV; + + DFSDM1_Channel0->CHCFGR1 |= uint32_t(clk_divider - 1) << DFSDM_CHCFGR1_CKOUTDIV_Pos; + + // enable the DFSDM Global Interface + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + } + bool disable() { + DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN; + return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN) == 0; + } + bool enable() { + DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN; + return (DFSDM1_Channel0->CHCFGR1 & DFSDM_CHCFGR1_DFSDMEN); + } + bool change_divider(uint8_t div) { + if (div < 4) return false; - } - - }; - template - struct Init { - static inline std::array instances{}; - static void init(std::span cfgs,std::span gpio_instances) { - if(N == 0) return; - const auto &c = cfgs[0]; - auto &inst = instances[0]; - inst.gpio_instance = &gpio_instances[c.gpio_idx]; - inst.clk_divider = c.clk_divider; - inst.init(); + clk_divider = div; + if (disable()) { + init(); + return true; } - }; + return false; + } + }; + template struct Init { + static inline std::array instances{}; + static void + init(std::span cfgs, std::span gpio_instances) { + if (N == 0) + return; + const auto& c = cfgs[0]; + auto& inst = instances[0]; + inst.gpio_instance = &gpio_instances[c.gpio_idx]; + inst.clk_divider = c.clk_divider; + inst.init(); + } }; +}; -}; \ No newline at end of file +}; // namespace ST_LIB diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index 8425c520e..0a81056a9 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -247,7 +247,7 @@ template struct TimerWrapper { "to get_pwm() [this method]"); } } - + template inline DualPWM get_dual_pwm( uint32_t polarity = TIM_OCPOLARITY_HIGH, diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 01b473749..b2f75885c 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -98,7 +98,7 @@ using DomainsCtx = BuildCtx< ADCDomain, EXTIDomain, DFSDM_CHANNEL_DOMAIN, - DFSDM_CLK_DOMAIN/* PWMDomain, ...*/>; + DFSDM_CLK_DOMAIN /* PWMDomain, ...*/>; namespace BuildUtils { @@ -169,7 +169,8 @@ template struct Board { std::span{adc_cfgs} ); constexpr std::size_t dfsdmN = domain_size(); - constexpr auto dfsdm_cfgs = DFSDM_CHANNEL_DOMAIN::template build(ctx.template span()); + constexpr auto dfsdm_cfgs = + DFSDM_CHANNEL_DOMAIN::template build(ctx.template span()); constexpr std::size_t dfsdm_dma_extraN = DFSDM_CHANNEL_DOMAIN::dma_contribution_count( std::span{dfsdm_cfgs}, ctx.template span() @@ -178,7 +179,7 @@ template struct Board { DFSDM_CHANNEL_DOMAIN::template build_dma_contributions( ctx.template span(), std::span{dfsdm_cfgs} - ); + ); constexpr std::size_t dfsdm_clkN = domain_size(); constexpr std::size_t spiN = domain_size(); constexpr std::size_t doutN = domain_size(); @@ -188,7 +189,7 @@ template struct Board { constexpr std::size_t ethN = domain_size(); constexpr std::size_t dmaN = domain_size() + adc_dma_extraN + dfsdm_dma_extraN; constexpr std::size_t extiN = domain_size(); - + // ... struct ConfigBundle { @@ -204,9 +205,9 @@ template struct Board { std::array eth_cfgs; std::array adc_cfgs; std::array exti_cfgs; - std::array dfsdm_cfgs; - std::array dfsdm_clk_cfgs; - // ... + std::array dfsdm_cfgs; + std::array dfsdm_clk_cfgs; + // ... }; return ConfigBundle{ @@ -232,7 +233,8 @@ template struct Board { .adc_cfgs = adc_cfgs, .exti_cfgs = EXTIDomain::template build(ctx.template span()), .dfsdm_cfgs = dfsdm_cfgs, - .dfsdm_clk_cfgs = DFSDM_CLK_DOMAIN::template build(ctx.template span()) + .dfsdm_clk_cfgs = + DFSDM_CLK_DOMAIN::template build(ctx.template span()) // ... }; } @@ -254,7 +256,7 @@ template struct Board { constexpr std::size_t extiN = domain_size(); constexpr std::size_t dfsdmN = domain_size(); constexpr std::size_t dfsdm_clkN = domain_size(); - // ... + // ... #ifdef HAL_IWDG_MODULE_ENABLED Watchdog::check_reset_flag(); @@ -293,14 +295,16 @@ template struct Board { GPIODomain::Init::instances, DMADomain::Init::instances ); - EXTIDomain::Init::init(cfg.exti_cfgs, - GPIODomain::Init::instances); - + EXTIDomain::Init::init(cfg.exti_cfgs, GPIODomain::Init::instances); + DFSDM_CHANNEL_DOMAIN::Init::init( GPIODomain::Init::instances, DMADomain::Init::instances ); - DFSDM_CLK_DOMAIN::Init::init(cfg.dfsdm_clk_cfgs,GPIODomain::Init::instances); + DFSDM_CLK_DOMAIN::Init::init( + cfg.dfsdm_clk_cfgs, + GPIODomain::Init::instances + ); // ... } @@ -328,9 +332,9 @@ template struct Board { return Domain::template Init::instances[idx]; } else if constexpr (std::is_same_v) { return Domain::template Init::instances[idx]; - } else if constexpr (std::is_same_v){ - return Domain::template Init::instances[idx]; - } else{ + } else if constexpr (std::is_same_v) { + return Domain::template Init::instances[idx]; + } else { return Domain::template Init::instances[idx]; } } diff --git a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp index 6a285cdf1..68c1f7163 100644 --- a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp +++ b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp @@ -3,8 +3,8 @@ #include "ST-LIB_LOW/DigitalOutput2.hpp" #include "ST-LIB_LOW/DigitalInput2.hpp" #include "HALAL/Services/DFSDM/DFSDM.hpp" -//#include "Clocks/Counter.hpp" -//#include "Clocks/Stopwatch.hpp" +// #include "Clocks/Counter.hpp" +// #include "Clocks/Stopwatch.hpp" #include "ST-LIB_LOW/Sd/Sd.hpp" diff --git a/Src/HALAL/Services/DFSDM/DFSDM.cpp b/Src/HALAL/Services/DFSDM/DFSDM.cpp index 13d9571b4..eabbbd48a 100644 --- a/Src/HALAL/Services/DFSDM/DFSDM.cpp +++ b/Src/HALAL/Services/DFSDM/DFSDM.cpp @@ -1,24 +1,11 @@ #include "HALAL/Services/DFSDM/DFSDM.hpp" +// ST_LIB::DFSDM_CHANNEL_DOMAIN::Init:: -//ST_LIB::DFSDM_CHANNEL_DOMAIN::Init:: +extern "C" { -extern "C"{ - -void DFSDM1_FLT0_IRQHandler(void) -{ - ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(0); -} -void DFSDM1_FLT1_IRQHandler(void) -{ - ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(1); -} -void DFSDM1_FLT2_IRQHandler(void) -{ - ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(2); -} -void DFSDM1_FLT3_IRQHandler(void) -{ - ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(3); +void DFSDM1_FLT0_IRQHandler(void) { ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(0); } +void DFSDM1_FLT1_IRQHandler(void) { ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(1); } +void DFSDM1_FLT2_IRQHandler(void) { ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(2); } +void DFSDM1_FLT3_IRQHandler(void) { ST_LIB::DFSDM_CHANNEL_DOMAIN::handle_irq(3); } } -} \ No newline at end of file From 9df31684ded948193c90bacd3a95044ac528793d Mon Sep 17 00:00:00 2001 From: oganigl Date: Thu, 26 Mar 2026 20:40:10 +0100 Subject: [PATCH 40/41] change start() to static functions --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 6a56dfc6b..2af1ce4de 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -721,6 +721,14 @@ struct DFSDM_CHANNEL_DOMAIN { DFSDM1_Channel6, DFSDM1_Channel7 }; + static void inline start_reg_conv_filter(uint8_t filter){ + if(filter > 3) ErrorHandler("Only filters from 0..3"); + filter_hw[filter]->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; // regular + } + static void inline start_inj_conv_filter(uint8_t filter){ + if(filter > 3) ErrorHandler("Only filters from 0..3"); + filter_hw[filter]->FLTCR1 |= DFSDM_FLTCR1_JSWSTART; // injected + } static DMADomain::Instance* find_dma_instance(uint32_t request, std::span dma_peripherals) { for (auto& dma_instance : dma_peripherals) { @@ -765,8 +773,6 @@ struct DFSDM_CHANNEL_DOMAIN { void disable_DFSDM_Peripheral() { DFSDM1_Channel0->CHCFGR1 &= ~(DFSDM_CHCFGR1_DFSDMEN); } public: - DFSDM_Filter_TypeDef* get_filter_struct() const { return filter_regs; } - DFSDM_Channel_TypeDef* get_channel_struct() const { return channel_regs; } bool is_enabled() { return is_enabled_DFSDM() && is_enabled_channel() && is_enabled_filter(); } @@ -797,16 +803,6 @@ struct DFSDM_CHANNEL_DOMAIN { } /*Filter functions*/ - void start() { - if (!is_enabled()) - enable(); - - if (type_conv == Type_Conversion::Regular) { - filter_regs->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; // regular - } else { - filter_regs->FLTCR1 |= DFSDM_FLTCR1_JSWSTART; // injected - } - } void modify_sync_conversion(Sync_Conversion type) { bool was_enabled_filter = is_enabled_filter(); @@ -898,14 +894,14 @@ struct DFSDM_CHANNEL_DOMAIN { "channel buffer"); } return ( - (this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos + static_cast(this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos ); // The constants values are the same for regular than injected } int32_t read() { return ( static_cast(this->buffer[0] & DFSDM_FLTJDATAR_JDATA_Msk) >> DFSDM_FLTJDATAR_JDATA_Pos - ); // The constants values are the same for regular than injected + ); } uint32_t check_latency_cycles() { return filter_regs->FLTCNVTIMR >> DFSDM_FLTCNVTIMR_CNVCNT_Pos; From 18705cedf1f2e96a306186da79ec8fbfc578590f Mon Sep 17 00:00:00 2001 From: oganigl Date: Thu, 26 Mar 2026 20:56:21 +0100 Subject: [PATCH 41/41] add more safety --- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index 2af1ce4de..55b23a274 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -610,11 +610,14 @@ struct DFSDM_CHANNEL_DOMAIN { } } // validate buffers + if(active_channels > 1 && entries[i].config_filter.type_conv == Type_Conversion::Regular){ + compile_error("Not allowed more than 1 channel per filter in Regular Mode"); + } if (active_channels > 1 && cfgs[i].dma_enable == Dma::Enable) { // Look the entry because the data that I want to check is easier to access - if (entries[i].config_filter.type_conv == Type_Conversion::Regular) { + if (entries[i].config_filter.jscan == Injected_Mode::Single) { compile_error( - "Not allowed Regular conversion + DMA + Multiple channel in the same filter" + "Not allowed more than 1 channel per filter + Injected_Mode::Single_conversion" ); } for (size_t j = 0; j < 8; j++) {