Draft
Conversation
diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 98af03b..658a200 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -146,9 +146,11 @@ TimerXList CHANNEL_NEGATED_FLAG = 8, - CHANNEL_1_NEGATED = 1 | CHANNEL_NEGATED_FLAG, - CHANNEL_2_NEGATED = 2 | CHANNEL_NEGATED_FLAG, - CHANNEL_3_NEGATED = 3 | CHANNEL_NEGATED_FLAG, + CHANNEL_1_NEGATED = CHANNEL_1 | CHANNEL_NEGATED_FLAG, + CHANNEL_2_NEGATED = CHANNEL_2 | CHANNEL_NEGATED_FLAG, + CHANNEL_3_NEGATED = CHANNEL_3 | CHANNEL_NEGATED_FLAG, + // STM32h723xx does not have a negated channel 4, but other stm microcontrollers do + CHANNEL_4_NEGATED = CHANNEL_4 | CHANNEL_NEGATED_FLAG, }; struct TimerPin { @@ -699,6 +701,53 @@ TimerXList } } }; + + static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { + switch (ch) { + case TimerChannel::CHANNEL_1: + case TimerChannel::CHANNEL_1_NEGATED: + case TimerChannel::CHANNEL_2: + case TimerChannel::CHANNEL_2_NEGATED: + case TimerChannel::CHANNEL_3: + case TimerChannel::CHANNEL_3_NEGATED: + case TimerChannel::CHANNEL_4: + case TimerChannel::CHANNEL_4_NEGATED: + case TimerChannel::CHANNEL_5: + case TimerChannel::CHANNEL_6: + return (static_cast<uint8_t>(ch) & + ~static_cast<uint8_t>(TimerChannel::CHANNEL_NEGATED_FLAG)) - + 1; + + default: + ST_LIB::compile_error("unreachable"); + return 0; + } + } + + static consteval uint8_t get_channel_mul4(const ST_LIB::TimerChannel ch) { + switch (ch) { + case TimerChannel::CHANNEL_1: + case TimerChannel::CHANNEL_1_NEGATED: + return 0x00; + case TimerChannel::CHANNEL_2: + case TimerChannel::CHANNEL_2_NEGATED: + return 0x04; + case TimerChannel::CHANNEL_3: + case TimerChannel::CHANNEL_3_NEGATED: + return 0x08; + case TimerChannel::CHANNEL_4: + case TimerChannel::CHANNEL_4_NEGATED: + return 0x0C; + case TimerChannel::CHANNEL_5: + return 0x10; + case TimerChannel::CHANNEL_6: + return 0x14; + + default: + ST_LIB::compile_error("unreachable"); + return 0; + } + } }; consteval GPIODomain::AlternateFunction TimerDomain::Timer::get_gpio_af( diff --git a/Inc/HALAL/Services/InputCapture/InputCapture.hpp b/Inc/HALAL/Services/InputCapture/InputCapture.hpp new file mode 100644 index 00000000..0100b08 --- /dev/null +++ b/Inc/HALAL/Services/InputCapture/InputCapture.hpp @@ -0,0 +1,160 @@ +/* + * InputCapture.hpp + * + * Created on: 17 feb. 2026 + * Author: victor + */ +#pragma once + +#include "HALAL/Models/TimerDomain/TimerDomain.hpp" +#ifdef HAL_TIM_MODULE_ENABLED +#include "HALAL/Models/GPIO.hpp" + +namespace ST_LIB { + +template <const TimerDomain::Timer& dev> struct TimerWrapper; + +template < + const ST_LIB::TimerDomain::Timer& dev, + const ST_LIB::TimerPin pin_rising, + const ST_LIB::TimerPin pin_falling> +class InputCapture { + TimerWrapper<dev>* timer; + uint32_t frequency; + uint8_t duty_cycle; + bool is_on; + +public: + InputCapture(TimerWrapper<dev>* tim) : timer(tim) { + TIM_IC_InitTypeDef sConfigIC = { + .ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING, + .ICPrescaler = TIM_ICPSC_DIV1, + .ICFilter = 0, + .ICSelection = TIM_ICSELECTION_DIRECTTI, + }; + timer->template config_input_compare_channel<pin_rising.channel>(&sConfigIC); + + sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; + sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; + timer->template config_input_compare_channel<pin_falling.channel>(&sConfigIC); + } + + static void turn_on(void) { + if (this->is_on) + return; + + // HAL_TIM_IC_Start_IT(instance.peripheral->handle, instance.channel_rising) + { + volatile HAL_TIM_ChannelStateTypeDef* ch_state = + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_rising.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_rising.channel)]; + if ((*ch_state != HAL_TIM_CHANNEL_STATE_READY) || (*n_ch_state != HAL_TIM_CHANNEL_STATE_READY)) { + ErrorHandler("Channels not ready"); + } + + *ch_state = HAL_TIM_CHANNEL_STATE_BUSY; + *n_ch_state = HAL_TIM_CHANNEL_STATE_BUSY; + + timer->template enable_capture_compare_interrupt<pin_rising.channel>(); + uint32_t enableCCx = TIM_CCER_CC1E + << (TimerDomain::get_channel_mul4(pin_rising.channel) & 0x1FU + ); /* 0x1FU = 31 bits max shift */ + SET_BIT(timer->instance->tim->CCER, enableCCx); + } + + // HAL_TIM_IC_Start(instance.peripheral->handle, instance.channel_falling) + { + volatile HAL_TIM_ChannelStateTypeDef* ch_state = + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + if ((*ch_state != HAL_TIM_CHANNEL_STATE_READY) || (*n_ch_state != HAL_TIM_CHANNEL_STATE_READY)) { + ErrorHandler("Channels not ready"); + } + + *ch_state = HAL_TIM_CHANNEL_STATE_BUSY; + *n_ch_state = HAL_TIM_CHANNEL_STATE_BUSY; + + uint32_t enableCCx = TIM_CCER_CC1E + << (TimerDomain::get_channel_mul4(pin_falling.channel) & 0x1FU + ); /* 0x1FU = 31 bits max shift */ + SET_BIT(timer->instance->tim->CCER, enableCCx); + } + + if constexpr (timer->is_slave_instance) { + uint32_t tmpsmcr = timer->instance->tim->SMCR & SMCR_SMS; + if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr)) { + timer->counter_enable(); + } + } else { + timer->counter_enable(); + } + + this->is_on = true; + } + + static void turn_off(void) { + if(!this->is_on) + return; + + // HAL_TIM_IC_Stop_IT(instance.peripheral->handle, instance.channel_rising) + { + timer->template disable_capture_compare_interrupt<pin_rising.channel>(); + + CLEAR_BIT( + timer->instance->tim->CCER, + (uint32_t)(TIM_CCER_CC1E << (TimerDomain::get_channel_mul4(pin_rising.channel) & 0x1FU)) + ); + + volatile HAL_TIM_ChannelStateTypeDef* ch_state = + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + *ch_state = HAL_TIM_CHANNEL_STATE_READY; + *ch_n_state = HAL_TIM_CHANNEL_STATE_READY; + } + + // HAL_TIM_IC_Stop(instance.peripheral->handle, instance.channel_falling) + { + CLEAR_BIT( + timer->instance->tim->CCER, + (uint32_t)(TIM_CCER_CC1E << (TimerDomain::get_channel_mul4(pin_rising.channel) & 0x1FU)) + ); + + volatile HAL_TIM_ChannelStateTypeDef* ch_state = + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + *ch_state = HAL_TIM_CHANNEL_STATE_READY; + *ch_n_state = HAL_TIM_CHANNEL_STATE_READY; + } + + if (timer->are_all_channels_free()) { + timer->counter_disable(); + } + +#error TODO + if (HAL_TIM_IC_Stop(instance.peripheral->handle, instance.channel_falling) != HAL_OK) { + ErrorHandler( + "Unable to stop the %s Input Capture measurement", + instance.peripheral->name.c_str() + ); + } + + + this->is_on = false; + } + + static uint32_t read_frequency(void) { + return this->frequency; + } + + static uint8_t read_duty_cycle(void) { + return this->duty_cycle; + } +}; + +}; + +#endif // HAL_TIM_MODULE_ENABLED diff --git a/Inc/HALAL/Services/PWM/DualPWM.hpp b/Inc/HALAL/Services/PWM/DualPWM.hpp index 26c36d0..84f99b0 100644 --- a/Inc/HALAL/Services/PWM/DualPWM.hpp +++ b/Inc/HALAL/Services/PWM/DualPWM.hpp @@ -19,51 +19,6 @@ template < const ST_LIB::TimerPin pin, const ST_LIB::TimerPin negated_pin> class DualPWM { - static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { - switch (ch) { - case TimerChannel::CHANNEL_1: - case TimerChannel::CHANNEL_1_NEGATED: - case TimerChannel::CHANNEL_2: - case TimerChannel::CHANNEL_2_NEGATED: - case TimerChannel::CHANNEL_3: - case TimerChannel::CHANNEL_3_NEGATED: - case TimerChannel::CHANNEL_4: - case TimerChannel::CHANNEL_5: - case TimerChannel::CHANNEL_6: - return (static_cast<uint8_t>(ch) & - ~static_cast<uint8_t>(TimerChannel::CHANNEL_NEGATED_FLAG)) - - 1; - - default: - ST_LIB::compile_error("unreachable"); - return 0; - } - } - - static consteval uint8_t get_channel_mul4(const ST_LIB::TimerChannel ch) { - switch (ch) { - case TimerChannel::CHANNEL_1: - case TimerChannel::CHANNEL_1_NEGATED: - return 0x00; - case TimerChannel::CHANNEL_2: - case TimerChannel::CHANNEL_2_NEGATED: - return 0x04; - case TimerChannel::CHANNEL_3: - case TimerChannel::CHANNEL_3_NEGATED: - return 0x08; - case TimerChannel::CHANNEL_4: - return 0x0C; - case TimerChannel::CHANNEL_5: - return 0x10; - case TimerChannel::CHANNEL_6: - return 0x14; - - default: - ST_LIB::compile_error("unreachable"); - return 0; - } - } - TimerWrapper<dev>* timer; uint32_t* frequency; float* duty_cycle = nullptr; @@ -112,18 +67,15 @@ public: if (this->is_on_positive) return; - // if(HAL_TIM_PWM_Start(timer->instance->hal_tim, channel) != HAL_OK) { ErrorHandler("", 0); - // } volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim->ChannelState[get_channel_state_idx(pin.channel)]; + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { ErrorHandler("Channel not ready"); } *state = HAL_TIM_CHANNEL_STATE_BUSY; - // enable CCx uint32_t enableCCx = TIM_CCER_CC1E - << (get_channel_mul4(pin.channel) & 0x1FU + << (TimerDomain::get_channel_mul4(pin.channel) & 0x1FU ); /* 0x1FU = 31 bits max shift */ SET_BIT(timer->instance->tim->CCER, enableCCx); @@ -148,18 +100,15 @@ public: if (this->is_on_negative) return; - // if(HAL_TIM_PWM_Start(timer->instance->hal_tim, channel) != HAL_OK) { ErrorHandler("", 0); - // } volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim->ChannelNState[get_channel_state_idx(negated_pin.channel)]; + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(negated_pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { ErrorHandler("Channel not ready"); } *state = HAL_TIM_CHANNEL_STATE_BUSY; - // enable CCNx uint32_t enableCCNx = TIM_CCER_CC1NE - << (get_channel_mul4(negated_pin.channel) & 0x1FU + << (TimerDomain::get_channel_mul4(negated_pin.channel) & 0x1FU ); /* 0x1FU = 31 bits max shift */ SET_BIT(timer->instance->tim->CCER, enableCCNx); @@ -186,11 +135,11 @@ public: CLEAR_BIT( timer->tim->CCER, - (uint32_t)(TIM_CCER_CC1E << (get_channel_mul4(pin.channel) & 0x1FU)) + (uint32_t)(TIM_CCER_CC1E << (TimerDomain::get_channel_mul4(pin.channel) & 0x1FU)) ); HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim.ChannelState[get_channel_state_idx(pin.channel)]; + &timer->instance->hal_tim.ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; *state = HAL_TIM_CHANNEL_STATE_READY; if (timer->are_all_channels_free()) { @@ -210,11 +159,11 @@ public: CLEAR_BIT( timer->tim->CCER, - (uint32_t)(TIM_CCER_CC1NE << (get_channel_mul4(negated_pin.channel) & 0x1FU)) + (uint32_t)(TIM_CCER_CC1NE << (TimerDomain::get_channel_mul4(negated_pin.channel) & 0x1FU)) ); HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim.ChannelState[get_channel_state_idx(negated_pin.channel)]; + &timer->instance->hal_tim.ChannelState[TimerDomain::get_channel_state_idx(negated_pin.channel)]; *state = HAL_TIM_CHANNEL_STATE_READY; if (timer->are_all_channels_free()) { diff --git a/Inc/HALAL/Services/PWM/PWM.hpp b/Inc/HALAL/Services/PWM/PWM.hpp index b1d4cdb..0c60f46 100644 --- a/Inc/HALAL/Services/PWM/PWM.hpp +++ b/Inc/HALAL/Services/PWM/PWM.hpp @@ -14,52 +14,10 @@ namespace ST_LIB { template <const TimerDomain::Timer& dev> struct TimerWrapper; -template <const TimerDomain::Timer& dev, const ST_LIB::TimerPin pin> class PWM { - static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { - switch (ch) { - case TimerChannel::CHANNEL_1: - case TimerChannel::CHANNEL_1_NEGATED: - case TimerChannel::CHANNEL_2: - case TimerChannel::CHANNEL_2_NEGATED: - case TimerChannel::CHANNEL_3: - case TimerChannel::CHANNEL_3_NEGATED: - case TimerChannel::CHANNEL_4: - case TimerChannel::CHANNEL_5: - case TimerChannel::CHANNEL_6: - return (static_cast<uint8_t>(ch) & - ~static_cast<uint8_t>(TimerChannel::CHANNEL_NEGATED_FLAG)) - - 1; - - default: - ST_LIB::compile_error("unreachable"); - return 0; - } - } - - static consteval uint8_t get_channel_mul4(const ST_LIB::TimerChannel ch) { - switch (ch) { - case TimerChannel::CHANNEL_1: - case TimerChannel::CHANNEL_1_NEGATED: - return 0x00; - case TimerChannel::CHANNEL_2: - case TimerChannel::CHANNEL_2_NEGATED: - return 0x04; - case TimerChannel::CHANNEL_3: - case TimerChannel::CHANNEL_3_NEGATED: - return 0x08; - case TimerChannel::CHANNEL_4: - return 0x0C; - case TimerChannel::CHANNEL_5: - return 0x10; - case TimerChannel::CHANNEL_6: - return 0x14; - - default: - ST_LIB::compile_error("unreachable"); - return 0; - } - } - +template < + const TimerDomain::Timer& dev, + const ST_LIB::TimerPin pin> +class PWM { TimerWrapper<dev>* timer; uint32_t* frequency; float* duty_cycle = nullptr; @@ -94,18 +52,15 @@ public: if (this->is_on) return; - // if(HAL_TIM_PWM_Start(timer->instance->hal_tim, channel) != HAL_OK) { ErrorHandler("", 0); - // } volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim->ChannelState[get_channel_state_idx(pin.channel)]; + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { ErrorHandler("Channel not ready"); } *state = HAL_TIM_CHANNEL_STATE_BUSY; - // enable CCx uint32_t enableCCx = TIM_CCER_CC1E - << (get_channel_mul4(pin.channel) & 0x1FU + << (TimerDomain::get_channel_mul4(pin.channel) & 0x1FU ); /* 0x1FU = 31 bits max shift */ SET_BIT(timer->instance->tim->CCER, enableCCx); @@ -132,11 +87,11 @@ public: CLEAR_BIT( timer->instance->tim->CCER, - (uint32_t)(TIM_CCER_CC1E << (get_channel_mul4(pin.channel) & 0x1FU)) + (uint32_t)(TIM_CCER_CC1E << (TimerDomain::get_channel_mul4(pin.channel) & 0x1FU)) ); volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim->ChannelState[get_channel_state_idx(pin.channel)]; + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; *state = HAL_TIM_CHANNEL_STATE_READY; if (timer->are_all_channels_free()) { diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index 0a81056..4ee79c1 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -59,6 +59,18 @@ template <const TimerDomain::Timer& dev> struct TimerWrapper { dev.e.request == TimerRequest::GeneralPurpose_15 || dev.e.request == TimerRequest::GeneralPurpose_16 || dev.e.request == TimerRequest::GeneralPurpose_17); + /* at least 2 capture/compare channels (see IS_TIM_CC2_INSTANCE for macro definition) */ + static constexpr bool is_CC2_instance = + (dev.e.request == TimerRequest::Advanced_1 || + dev.e.request == TimerRequest::GeneralPurpose32bit_2 || + dev.e.request == TimerRequest::GeneralPurpose_3 || + dev.e.request == TimerRequest::GeneralPurpose_4 || + dev.e.request == TimerRequest::GeneralPurpose32bit_5 || + dev.e.request == TimerRequest::Advanced_8 || + dev.e.request == TimerRequest::SlaveTimer_12 || + dev.e.request == TimerRequest::GeneralPurpose_15 || + dev.e.request == TimerRequest::GeneralPurpose32bit_23 || + dev.e.request == TimerRequest::GeneralPurpose32bit_24); static constexpr bool is_slave_instance = (dev.e.request == TimerRequest::Advanced_1 || dev.e.request == TimerRequest::GeneralPurpose32bit_2 || @@ -368,8 +380,34 @@ template <const TimerDomain::Timer& dev> struct TimerWrapper { } } - /////////////////////////////////////////// - // Below are methods used by other objects + /////////////////////////////////////////////////////// + // Below are methods used by other objects (internals) + + template <ST_LIB::TimerChannel ch> + inline void enable_capture_compare_interrupt(void) { + if constexpr((ch == TimerChannel::CHANNEL_1) || (ch == TimerChannel::CHANNEL_1_NEGATED)) { + SET_BIT(instance->tim->DIER, TIM_IT_CC1); + } else if constexpr((ch == TimerChannel::CHANNEL_2) || (ch == TimerChannel::CHANNEL_2_NEGATED)) { + SET_BIT(instance->tim->DIER, TIM_IT_CC2); + } else if constexpr((ch == TimerChannel::CHANNEL_3) || (ch == TimerChannel::CHANNEL_3_NEGATED)) { + SET_BIT(instance->tim->DIER, TIM_IT_CC3); + } else if constexpr((ch == TimerChannel::CHANNEL_4) || (ch == TimerChannel::CHANNEL_4_NEGATED)) { + SET_BIT(instance->tim->DIER, TIM_IT_CC4); + } + } + + template <ST_LIB::TimerChannel ch> + inline void disable_capture_compare_interrupt(void) { + if constexpr((ch == TimerChannel::CHANNEL_1) || (ch == TimerChannel::CHANNEL_1_NEGATED)) { + CLEAR_BIT(instance->tim->DIER, TIM_IT_CC1); + } else if constexpr((ch == TimerChannel::CHANNEL_2) || (ch == TimerChannel::CHANNEL_2_NEGATED)) { + CLEAR_BIT(instance->tim->DIER, TIM_IT_CC2); + } else if constexpr((ch == TimerChannel::CHANNEL_3) || (ch == TimerChannel::CHANNEL_3_NEGATED)) { + CLEAR_BIT(instance->tim->DIER, TIM_IT_CC3); + } else if constexpr((ch == TimerChannel::CHANNEL_4) || (ch == TimerChannel::CHANNEL_4_NEGATED)) { + CLEAR_BIT(instance->tim->DIER, TIM_IT_CC4); + } + } template <ST_LIB::PWM_Frequency_Mode mode = DEFAULT_PWM_FREQUENCY_MODE> void set_pwm_frequency(uint32_t frequency) { @@ -434,6 +472,8 @@ template <const TimerDomain::Timer& dev> struct TimerWrapper { } } + /* NOTE(vic): Both config_output_compare_channel and config_input_compare_channel + * Could probably be done better if not using TIM_[OC/IC]_InitTypeDef structures */ template <ST_LIB::TimerChannel ch> inline void config_output_compare_channel(const TIM_OC_InitTypeDef* OC_Config) { if constexpr (!((ch == TimerChannel::CHANNEL_1) || (ch == TimerChannel::CHANNEL_2) || @@ -587,6 +627,107 @@ template <const TimerDomain::Timer& dev> struct TimerWrapper { instance->tim->CCER = tmpccer; } + /* NOTE(vic): Both config_output_compare_channel and config_input_compare_channel + * Could probably be done better if not using TIM_[OC/IC]_InitTypeDef structures */ + template <ST_LIB::TimerChannel ch> inline void config_input_compare_channel(TIM_IC_InitTypeDef* sConfig) { + if constexpr (!((ch == TimerChannel::CHANNEL_1) || (ch == TimerChannel::CHANNEL_2) || + (ch == TimerChannel::CHANNEL_3) || (ch == TimerChannel::CHANNEL_4))) { + ST_LIB::compile_error("Only channels 1 to 4 can be configured as input compare"); + return; + } + + uint32_t tmpccmrx; + uint32_t tmpccer; + + tmpccer = instance->tim->CCER; + if constexpr (ch == TimerChannel::CHANNEL_1 || ch == TimerChannel::CHANNEL_2) { + tmpccmrx = instance->tim->CCMR1; + } else if constexpr (ch == TimerChannel::CHANNEL_3 || ch == TimerChannel::CHANNEL_4) { + tmpccmrx = instance->tim->CCMR2; + } + + if constexpr((ch == TimerChannel::CHANNEL_1) || (ch == TimerChannel::CHANNEL_1_NEGATED)) { + CLEAR_BIT(tmpccer, TIM_CCER_CC1E); + + /* Select the input */ + if constexpr(this->is_CC2_instance) { + CLEAR_BIT(tmpccmrx, TIM_CCMR1_CC1S); + SET_BIT(tmpccmrx, sConfig->ICSelection); + } else { + SET_BIT(tmpccmrx, TIM_CCMR1_CC1S_0); + } + + /* Set the filter */ + CLEAR_BIT(tmpccmrx, TIM_CCMR1_IC1F); + SET_BIT(tmpccmrx, ((sConfig->ICFilter << 4U) & TIMM_CCMR1_IC1F)); + + /* Select the Polarity and set the CC1E Bit */ + CLEAR_BIT(tmpccer, TIM_CCER_CC1P | TIM_CCER_CC1NP); + SET_BIT(tmpccer, sConfig->ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP)); + + CLEAR_BIT(tmpccmrx, TIM_CCMR1_IC1PSC); + SET_BIT(tmpccmrx, sConfig->ICPrescaler & TIM_CCMR1_IC1PSC); + } else if constexpr((ch == TimerChannel::CHANNEL_2) || (ch == TimerChannel::CHANNEL_2_NEGATED)) { + CLEAR_BIT(tmpccer, TIM_CCER_CC2E); + + /* Select the input */ + CLEAR_BIT(tmpccmrx, TIM_CCMR1_CC2S); + SET_BIT(tmpccmrx, sConfig->ICSelection << 8U); + + /* Set the filter */ + CLEAR_BIT(tmpccmrx, TIM_CCMR1_IC2F); + SET_BIT(tmpccmrx, (sConfig->ICFilter << 12U) & TIM_CCMR1_IC2F); + + /* Select the Polarity and set the CC2E Bit */ + CLEAR_BIT(tmpccer, TIM_CCER_CC2P | TIM_CCER_CC2NP); + SET_BIT(tmpccer, (sConfig->ICPolarity >> 4U) & (TIM_CCER_CC2P | TIM_CCER_CC2NP)); + + CLEAR_BIT(tmpccmrx, TIM_CCMR1_IC2PSC); + SET_BIT(tmpccmrx, sConfig->ICPrescaler << 8U); + } else if constexpr((ch == TimerChannel::CHANNEL_3) || (ch == TimerChannel::CHANNEL_3_NEGATED)) { + CLEAR_BIT(tmpccer, TIM_CCER_CC3E); + + /* Select the Input */ + CLEAR_BIT(tmpccmrx, TIM_CCMR2_CC3S); + SET_BIT(tmpccmrx, sConfig->ICSelection); + + /* Set the filter */ + CLEAR_BIT(tmpccmrx, TIM_CCMR2_IC3F); + SET_BIT(tmpccmrx, (sConfig->ICFilter << 4U) & TIM_CCMR2_IC3F); + + /* Select the polarity and set the CC3E Bit */ + CLEAR_BIT(tmpccer, TIM_CCER_CC3P | TIM_CCER_CC3NP); + SET_BIT(tmpccer, (sConfig->ICPolarity << 8U) & (TIM_CCER_CC3P | TIM_CCER_CC3NP)); + + CLEAR_BIT(tmpccmrx, TIM_CCMR2_IC3PSC); + SET_BIT(tmpccmrx, sConfig->ICPrescaler); + } else if constexpr(ch == TimerChannel::CHANNEL_4) { + CLEAR_BIT(tmpccer, TIM_CCER_CC4E); + + /* Select the Input */ + CLEAR_BIT(tmpccmrx, TIM_CCMR2_CC4S); + SET_BIT(tmpccmrx, sConfig->ICSelection << 8U); + + /* Set the filter */ + CLEAR_BIT(tmpccmrx, TIM_CCMR2_IC4F); + SET_BIT(tmpccmrx, (sConfig->ICFilter << 12U) & TIM_CCMR2_IC4F); + + /* Select the polarity and set the CC4E Bit */ + CLEAR_BIT(tmpccer, TIM_CCER_CC4P | TIM_CCER_CC4NP); + SET_BIT(tmpccer, (sConfig->ICPolarity << 12U) & (TIM_CCER_CC4P | TIM_CCER_CC4NP)); + + CLEAR_BIT(tmpccmrx, TIM_CCMR2_IC4PSC); + SET_BIT(tmpccmrx, sConfig->ICPrescaler << 8U); + } + + if constexpr (ch == TimerChannel::CHANNEL_1 || ch == TimerChannel::CHANNEL_2) { + instance->tim->CCMR1 = tmpccmrx; + } else if constexpr (ch == TimerChannel::CHANNEL_3 || ch == TimerChannel::CHANNEL_4) { + instance->tim->CCMR2 = tmpccmrx; + } + instance->tim->CCER = tmpccer; + } + template <ST_LIB::TimerChannel ch> inline void set_output_compare_preload_enable() { if constexpr ((ch == TimerChannel::CHANNEL_1) || (ch == TimerChannel::CHANNEL_1_NEGATED)) { SET_BIT(instance->tim->CCMR1, TIM_CCMR1_OC1PE);
diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 658a200..62d35fe 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -42,9 +42,22 @@ X(16, APB2ENR) \ X(17, APB2ENR) -#define X(n, b) extern TIM_HandleTypeDef htim##n; -TimerXList -#undef X +extern TIM_HandleTypeDef htim1; +extern TIM_HandleTypeDef htim2; +extern TIM_HandleTypeDef htim3; +extern TIM_HandleTypeDef htim4; +extern TIM_HandleTypeDef htim5; +extern TIM_HandleTypeDef htim6; +extern TIM_HandleTypeDef htim7; +extern TIM_HandleTypeDef htim8; +extern TIM_HandleTypeDef htim12; +extern TIM_HandleTypeDef htim13; +extern TIM_HandleTypeDef htim14; +extern TIM_HandleTypeDef htim15; +extern TIM_HandleTypeDef htim16; +extern TIM_HandleTypeDef htim17; +extern TIM_HandleTypeDef htim23; +extern TIM_HandleTypeDef htim24; #if !defined(glue) #define glue_(a, b) a##b @@ -214,8 +227,21 @@ TimerXList static constexpr std::array<uint8_t, 25> timer_idxmap = create_timer_idxmap(); struct TimerDomain { - // There are 16 timers static constexpr std::size_t max_instances = 16; + static constexpr std::size_t input_capture_channels = 4; + + struct InputCaptureInfo { + uint8_t channel_rising; + uint8_t channel_falling; + + float value_rising; + + float duty_cycle; + uint32_t frequency; + }; + /* 2x as big as necessary but this makes indexing easier */ + static InputCaptureInfo* input_capture_info[max_instances][input_capture_channels]; + static InputCaptureInfo input_capture_info_backing[max_instances][input_capture_channels]; struct Entry { std::array<char, 8> name; /* max length = 7 */ @@ -748,8 +774,47 @@ TimerXList return 0; } } + + static inline uint32_t get_timer_frequency(TIM_TypeDef* tim) { + uint32_t result; + switch(tim) { + case TIM2: + case TIM3: + case TIM4: + case TIM5: + case TIM6: + case TIM7: + case TIM12: + case TIM13: + case TIM14: + result = HAL_RCC_GetPCLK1Freq(); + if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { + result *= 2; + } + break; + + case TIM1: + case TIM8: + case TIM15: + case TIM16: + case TIM17: + case TIM23: + case TIM24: + result = HAL_RCC_GetPCLK2Freq(); + if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { + result *= 2; + } + break; + default: + ErrorHandler("Invalid timer ptr"); + break; + } + return result; + } }; + + consteval GPIODomain::AlternateFunction TimerDomain::Timer::get_gpio_af( ST_LIB::TimerRequest req, ST_LIB::TimerPin pin diff --git a/Inc/HALAL/Services/InputCapture/InputCapture.hpp b/Inc/HALAL/Services/InputCapture/InputCapture.hpp index 0100b08..32f4cf1 100644 --- a/Inc/HALAL/Services/InputCapture/InputCapture.hpp +++ b/Inc/HALAL/Services/InputCapture/InputCapture.hpp @@ -14,18 +14,33 @@ namespace ST_LIB { template <const TimerDomain::Timer& dev> struct TimerWrapper; +/* NOTE: STM32h7 supports using 2 different pins for rising/falling + * To do this, you keep TIM_ICSELECTION_DIRECTTI when configuring both + * Not an option given in this interface but easily extended if necessary + */ template < const ST_LIB::TimerDomain::Timer& dev, const ST_LIB::TimerPin pin_rising, - const ST_LIB::TimerPin pin_falling> + const ST_LIB::TimerChannel channel_falling> class InputCapture { TimerWrapper<dev>* timer; - uint32_t frequency; - uint8_t duty_cycle; + TimerDomain::InputCaptureInfo *info = nullptr; bool is_on; public: InputCapture(TimerWrapper<dev>* tim) : timer(tim) { + // Setup TimerDomain + this->info = + &TimerDomain::input_capture_info_backing[tim->instance->timer_idx][pin_rising.channel]; + TimerDomain::input_capture_info[tim->instance->timer_idx][pin_rising.channel] = info; + TimerDomain::input_capture_info[tim->instance->timer_idx][channel_falling] = info; + + this->info->channel_rising = static_cast<uint8_t>(pin_rising.channel) - 1; + this->info->channel_falling = static_cast<uint8_t>(channel_falling) - 1; + this->info->value_rising = 0.0f; + this->info->duty_cycle = 0.0f; + this->info->frequency = 0; + TIM_IC_InitTypeDef sConfigIC = { .ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING, .ICPrescaler = TIM_ICPSC_DIV1, @@ -36,7 +51,7 @@ public: sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; - timer->template config_input_compare_channel<pin_falling.channel>(&sConfigIC); + timer->template config_input_compare_channel<channel_falling>(&sConfigIC); } static void turn_on(void) { @@ -66,9 +81,9 @@ public: // HAL_TIM_IC_Start(instance.peripheral->handle, instance.channel_falling) { volatile HAL_TIM_ChannelStateTypeDef* ch_state = - &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(channel_falling)]; volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = - &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(channel_falling)]; if ((*ch_state != HAL_TIM_CHANNEL_STATE_READY) || (*n_ch_state != HAL_TIM_CHANNEL_STATE_READY)) { ErrorHandler("Channels not ready"); } @@ -77,7 +92,7 @@ public: *n_ch_state = HAL_TIM_CHANNEL_STATE_BUSY; uint32_t enableCCx = TIM_CCER_CC1E - << (TimerDomain::get_channel_mul4(pin_falling.channel) & 0x1FU + << (TimerDomain::get_channel_mul4(channel_falling) & 0x1FU ); /* 0x1FU = 31 bits max shift */ SET_BIT(timer->instance->tim->CCER, enableCCx); } @@ -108,9 +123,9 @@ public: ); volatile HAL_TIM_ChannelStateTypeDef* ch_state = - &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(channel_falling)]; volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = - &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(channel_falling)]; *ch_state = HAL_TIM_CHANNEL_STATE_READY; *ch_n_state = HAL_TIM_CHANNEL_STATE_READY; } @@ -123,9 +138,9 @@ public: ); volatile HAL_TIM_ChannelStateTypeDef* ch_state = - &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + &timer->instance->hal_tim->ChannelState[TimerDomain::get_channel_state_idx(channel_falling)]; volatile HAL_TIM_ChannelStateTypeDef* n_ch_state = - &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(pin_falling.channel)]; + &timer->instance->hal_tim->ChannelNState[TimerDomain::get_channel_state_idx(channel_falling)]; *ch_state = HAL_TIM_CHANNEL_STATE_READY; *ch_n_state = HAL_TIM_CHANNEL_STATE_READY; } @@ -146,12 +161,12 @@ public: this->is_on = false; } - static uint32_t read_frequency(void) { - return this->frequency; + static uint32_t get_frequency(void) { + return this->info->frequency; } - static uint8_t read_duty_cycle(void) { - return this->duty_cycle; + static float get_duty_cycle(void) { + return this->info->duty_cycle; } }; diff --git a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp index cdafeee..bbf6599 100644 --- a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp +++ b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp @@ -2,105 +2,188 @@ using namespace ST_LIB; -#define X(n, b) TIM_HandleTypeDef htim##n; -TimerXList -#undef X - - void (*TimerDomain::callbacks[TimerDomain::max_instances])(void*) = {nullptr}; +#define CaptureCompareInterruptMask \ + (TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE) + +TIM_HandleTypeDef htim1; +TIM_HandleTypeDef htim2; +TIM_HandleTypeDef htim3; +TIM_HandleTypeDef htim4; +TIM_HandleTypeDef htim5; +TIM_HandleTypeDef htim6; +TIM_HandleTypeDef htim7; +TIM_HandleTypeDef htim8; +TIM_HandleTypeDef htim12; +TIM_HandleTypeDef htim13; +TIM_HandleTypeDef htim14; +TIM_HandleTypeDef htim15; +TIM_HandleTypeDef htim16; +TIM_HandleTypeDef htim17; +TIM_HandleTypeDef htim23; +TIM_HandleTypeDef htim24; + +void (*TimerDomain::callbacks[TimerDomain::max_instances])(void*) = {nullptr}; void* TimerDomain::callback_data[TimerDomain::max_instances] = {nullptr}; +TimerDomain::InputCaptureInfo input_capture_info_backing[TimerDomain::max_instances][TimerDomain::input_capture_channels/2] = {}; +TimerDomain::InputCaptureInfo* input_capture_info[TimerDomain::max_instances][TimerDomain::input_capture_channels] = {nullptr}; +#error TODO: In InputCapture.hpp associate the input_capture_info to input_capture_info_backing + +static void TIM_IC_CaptureCallback(const uint32_t timer_idx, uint32_t channel) +{ + TIM_HandleTypeDef* htim = TimerDomain::hal_handles[timer_idx]; + htim->Instance->CNT = 0; + + InputCaptureInfo* info = input_capture_info[timer_idx][channel]; + if(info->channel_rising == channel) { + // NOTE: CCR1 - CCR4 are contiguous + info->value_rising = (float)(*(((uint32_t*)&htim->Instance->CCR1) + channel)); + + uint32_t ref_clock = TimerDomain::get_timer_frequency(htim->Instance) / (htim->Instance->PSC + 1); + info->frequency = (uint32_t)((ref_clock / info->value_rising) + 0.5f); + } else if(info->channel_falling == channel) { + uint32_t falling_value = *(((uint32_t*)&htim->Instance->CCR1) + channel); + + info->duty_cycle = ((float)falling_value * 100.0f) / info->value_rising; + } else [[unlikely]] { + ErrorHandler("TimerDomain::input_capture_info was modified"); + } +} + +static void TIM_InterruptCallback(const uint32_t timer_idx) +{ + TIM_TypeDef* tim = TimerDomain::cmsis_timers[timer_idx]; + uint32_t cc_channel = (tim->SR & CaptureCompareInterruptMask) - 1; + if(tim->SR & TIM_SR_UIF) { + CLEAR_BIT(tim->SR, TIM_SR_UIF); + TimerDomain::callbacks[timer_idx](TimerDomain::callback_data[timer_idx]); + } + + if(cc_channel != 0) { + TIM_IC_CaptureCallback(timer_idx, cc_channel); + } +} extern "C" void TIM1_UP_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[1]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[1]](TimerDomain::callback_data[timer_idxmap[1]]); + TIM_InterruptCallback(timer_idxmap[1]); } extern "C" void TIM2_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[2]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[2]](TimerDomain::callback_data[timer_idxmap[2]]); + TIM_InterruptCallback(timer_idxmap[2]); } extern "C" void TIM3_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[3]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[3]](TimerDomain::callback_data[timer_idxmap[3]]); + TIM_InterruptCallback(timer_idxmap[3]); } extern "C" void TIM4_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[4]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[4]](TimerDomain::callback_data[timer_idxmap[4]]); + TIM_InterruptCallback(timer_idxmap[4]); } extern "C" void TIM5_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[5]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[5]](TimerDomain::callback_data[timer_idxmap[5]]); + TIM_InterruptCallback(timer_idxmap[5]); } extern "C" void TIM6_DAC_IRQHandler(void) { + // NOTE: Basic timers have no capture compare channels CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[6]]->SR, TIM_SR_UIF); TimerDomain::callbacks[timer_idxmap[6]](TimerDomain::callback_data[timer_idxmap[6]]); } extern "C" void TIM7_IRQHandler(void) { + // NOTE: Basic timers have no capture compare channels CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[7]]->SR, TIM_SR_UIF); TimerDomain::callbacks[timer_idxmap[7]](TimerDomain::callback_data[timer_idxmap[7]]); } extern "C" void TIM8_BRK_TIM12_IRQHandler(void) { - if ((TimerDomain::cmsis_timers[timer_idxmap[12]]->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[12]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[12]](TimerDomain::callback_data[timer_idxmap[12]]); + constexpr uint32_t tim8_idx = timer_idxmap[8]; + constexpr uint32_t tim12_idx = timer_idxmap[12]; + + TIM_TypeDef* tim8 = TimerDomain::cmsis_timers[tim8_idx]; + TIM_TypeDef* tim12 = TimerDomain::cmsis_timers[tim12_idx]; + + uint32_t tim12_cc_channel = (tim12->SR & CaptureCompareInterruptMask) - 1; + if ((tim12->SR & TIM_SR_UIF) != 0) { + CLEAR_BIT(TimerDomain::cmsis_timers[tim12_idx]->SR, TIM_SR_UIF); + TimerDomain::callbacks[tim12_idx](TimerDomain::callback_data[tim12_idx]); + } + if (tim12_cc_channel != 0) { + TIM_IC_CaptureCallback(tim12_idx, tim12_cc_channel); } - if ((TimerDomain::cmsis_timers[timer_idxmap[8]]->SR & TIM_SR_BIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[8]]->SR, TIM_SR_BIF); + + if ((TimerDomain::cmsis_timers[tim8_idx]->SR & TIM_SR_BIF) != 0) { + CLEAR_BIT(TimerDomain::cmsis_timers[tim8_idx]->SR, TIM_SR_BIF); /* this could probably have some other callback */ - TimerDomain::callbacks[timer_idxmap[8]](TimerDomain::callback_data[timer_idxmap[8]]); + TimerDomain::callbacks[tim8_idx](TimerDomain::callback_data[tim8_idx]); } } extern "C" void TIM8_UP_TIM13_IRQHandler(void) { - if ((TimerDomain::cmsis_timers[timer_idxmap[13]]->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[13]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[13]](TimerDomain::callback_data[timer_idxmap[13]]); + constexpr uint32_t tim8_idx = timer_idxmap[8]; + constexpr uint32_t tim13_idx = timer_idxmap[13]; + + TIM_TypeDef* tim8 = TimerDomain::cmsis_timers[tim8_idx]; + TIM_TypeDef* tim13 = TimerDomain::cmsis_timers[tim13_idx]; + + uint32_t tim8_cc_channel = (tim8->SR & CaptureCompareInterruptMask) - 1; + uint32_t tim13_cc_channel = (tim13->SR & CaptureCompareInterruptMask) - 1; + if ((tim13->SR & TIM_SR_UIF) != 0) { + CLEAR_BIT(TimerDomain::cmsis_timers[tim13_idx]->SR, TIM_SR_UIF); + TimerDomain::callbacks[tim13_idx](TimerDomain::callback_data[tim13_idx]); + } + if (tim13_cc_channel != 0) { + TIM_IC_CaptureCallback(tim13_idx, tim13_cc_channel); + } + + if ((TimerDomain::cmsis_timers[tim8_idx]->SR & TIM_SR_UIF) != 0) { + CLEAR_BIT(TimerDomain::cmsis_timers[tim8_idx]->SR, TIM_SR_UIF); + TimerDomain::callbacks[tim8_idx](TimerDomain::callback_data[tim8_idx]); } - if ((TimerDomain::cmsis_timers[timer_idxmap[8]]->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[8]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[8]](TimerDomain::callback_data[timer_idxmap[8]]); + if (tim8_cc_channel != 0) { + TIM_IC_CaptureCallback(tim8_idx, tim8_cc_channel); } } extern "C" void TIM8_TRG_COM_TIM14_IRQHandler(void) {e if ne - if ((TimerDomain::cmsis_timers[timer_idxmap[14]]->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[14]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[14]](TimerDomain::callback_data[timer_idxmap[14]]); + constexpr uint32_t tim8_idx = timer_idxmap[8]; + constexpr uint32_t tim14_idx = timer_idxmap[14]; + + TIM_TypeDef* tim8 = TimerDomain::cmsis_timers[tim8_idx]; + TIM_TypeDef* tim14 = TimerDomain::cmsis_timers[tim14_idx]; + + uint32_t tim14_cc_channel = (tim14->SR & CaptureCompareInterruptMask) - 1; + if ((tim14->SR & TIM_SR_UIF) != 0) { + CLEAR_BIT(TimerDomain::cmsis_timers[tim14_idx]->SR, TIM_SR_UIF); + TimerDomain::callbacks[tim14_idx](TimerDomain::callback_data[tim14_idx]); } + if (tim14_cc_channel != 0) { + TIM_IC_CaptureCallback(tim14_idx, tim14_cc_channel); + } + constexpr uint32_t com_trg_flags = TIM_SR_TIF | TIM_SR_COMIF; - if ((TimerDomain::cmsis_timers[timer_idxmap[14]]->SR & com_trg_flags) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[8]]->SR, com_trg_flags); + if ((TimerDomain::cmsis_timers[tim8_idx]->SR & com_trg_flags) != 0) { + CLEAR_BIT(TimerDomain::cmsis_timers[tim8_idx]->SR, com_trg_flags); /* this could probably have some other callback */ - TimerDomain::callbacks[timer_idxmap[8]](TimerDomain::callback_data[timer_idxmap[8]]); + TimerDomain::callbacks[tim8_idx](TimerDomain::callback_data[tim8_idx]); } } extern "C" void TIM15_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[15]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[15]](TimerDomain::callback_data[timer_idxmap[15]]); + TIM_InterruptCallback(timer_idxmap[15]); } extern "C" void TIM16_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[16]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[16]](TimerDomain::callback_data[timer_idxmap[16]]); + TIM_InterruptCallback(timer_idxmap[16]); } extern "C" void TIM17_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[17]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[17]](TimerDomain::callback_data[timer_idxmap[17]]); + TIM_InterruptCallback(timer_idxmap[17]); } extern "C" void TIM23_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[23]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[23]](TimerDomain::callback_data[timer_idxmap[23]]); + TIM_InterruptCallback(timer_idxmap[23]); } extern "C" void TIM24_IRQHandler(void) { - CLEAR_BIT(TimerDomain::cmsis_timers[timer_idxmap[24]]->SR, TIM_SR_UIF); - TimerDomain::callbacks[timer_idxmap[24]](TimerDomain::callback_data[timer_idxmap[24]]); + TIM_InterruptCallback(timer_idxmap[24]); }
diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 62d35fe..82e745e 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -777,38 +777,27 @@ extern TIM_HandleTypeDef htim24; static inline uint32_t get_timer_frequency(TIM_TypeDef* tim) { uint32_t result; - switch(tim) { - case TIM2: - case TIM3: - case TIM4: - case TIM5: - case TIM6: - case TIM7: - case TIM12: - case TIM13: - case TIM14: - result = HAL_RCC_GetPCLK1Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { - result *= 2; - } - break; - - case TIM1: - case TIM8: - case TIM15: - case TIM16: - case TIM17: - case TIM23: - case TIM24: - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } - break; - default: - ErrorHandler("Invalid timer ptr"); - break; + if((tim == TIM2) || (tim == TIM3) || (tim == TIM4) || (tim == TIM5) || + (tim == TIM6) || (tim == TIM7) || (tim == TIM12) || (tim == TIM13) || + (tim == TIM14)) + { + result = HAL_RCC_GetPCLK1Freq(); + if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { + result *= 2; + } } + else if((tim == TIM1) || (tim == TIM8) || (tim == TIM15) || (tim == TIM16) || + (tim == TIM17) || (tim == TIM23) || (tim == TIM24)) + { + result = HAL_RCC_GetPCLK2Freq(); + if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { + result *= 2; + } + } + else { + ErrorHandler("Invalid timer ptr"); + } + return result; } }; diff --git a/Inc/HALAL/Services/Time/TimerWrapper.hpp b/Inc/HALAL/Services/Time/TimerWrapper.hpp index 4ee79c1..6b5a4cc 100644 --- a/Inc/HALAL/Services/Time/TimerWrapper.hpp +++ b/Inc/HALAL/Services/Time/TimerWrapper.hpp @@ -659,7 +659,7 @@ template <const TimerDomain::Timer& dev> struct TimerWrapper { /* Set the filter */ CLEAR_BIT(tmpccmrx, TIM_CCMR1_IC1F); - SET_BIT(tmpccmrx, ((sConfig->ICFilter << 4U) & TIMM_CCMR1_IC1F)); + SET_BIT(tmpccmrx, ((sConfig->ICFilter << 4U) & TIM_CCMR1_IC1F)); /* Select the Polarity and set the CC1E Bit */ CLEAR_BIT(tmpccer, TIM_CCER_CC1P | TIM_CCER_CC1NP); diff --git a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp index bbf6599..8779375 100644 --- a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp +++ b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp @@ -26,14 +26,13 @@ void (*TimerDomain::callbacks[TimerDomain::max_instances])(void*) = {nullptr}; void* TimerDomain::callback_data[TimerDomain::max_instances] = {nullptr}; TimerDomain::InputCaptureInfo input_capture_info_backing[TimerDomain::max_instances][TimerDomain::input_capture_channels/2] = {}; TimerDomain::InputCaptureInfo* input_capture_info[TimerDomain::max_instances][TimerDomain::input_capture_channels] = {nullptr}; -#error TODO: In InputCapture.hpp associate the input_capture_info to input_capture_info_backing static void TIM_IC_CaptureCallback(const uint32_t timer_idx, uint32_t channel) { TIM_HandleTypeDef* htim = TimerDomain::hal_handles[timer_idx]; htim->Instance->CNT = 0; - InputCaptureInfo* info = input_capture_info[timer_idx][channel]; + TimerDomain::InputCaptureInfo* info = input_capture_info[timer_idx][channel]; if(info->channel_rising == channel) { // NOTE: CCR1 - CCR4 are contiguous info->value_rising = (float)(*(((uint32_t*)&htim->Instance->CCR1) + channel)); @@ -104,15 +103,15 @@ extern "C" void TIM8_BRK_TIM12_IRQHandler(void) { uint32_t tim12_cc_channel = (tim12->SR & CaptureCompareInterruptMask) - 1; if ((tim12->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[tim12_idx]->SR, TIM_SR_UIF); + CLEAR_BIT(tim12->SR, TIM_SR_UIF); TimerDomain::callbacks[tim12_idx](TimerDomain::callback_data[tim12_idx]); } if (tim12_cc_channel != 0) { TIM_IC_CaptureCallback(tim12_idx, tim12_cc_channel); } - if ((TimerDomain::cmsis_timers[tim8_idx]->SR & TIM_SR_BIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[tim8_idx]->SR, TIM_SR_BIF); + if ((tim8->SR & TIM_SR_BIF) != 0) { + CLEAR_BIT(tim8->SR, TIM_SR_BIF); /* this could probably have some other callback */ TimerDomain::callbacks[tim8_idx](TimerDomain::callback_data[tim8_idx]); } @@ -128,15 +127,15 @@ extern "C" void TIM8_UP_TIM13_IRQHandler(void) { uint32_t tim8_cc_channel = (tim8->SR & CaptureCompareInterruptMask) - 1; uint32_t tim13_cc_channel = (tim13->SR & CaptureCompareInterruptMask) - 1; if ((tim13->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[tim13_idx]->SR, TIM_SR_UIF); + CLEAR_BIT(tim13->SR, TIM_SR_UIF); TimerDomain::callbacks[tim13_idx](TimerDomain::callback_data[tim13_idx]); } if (tim13_cc_channel != 0) { TIM_IC_CaptureCallback(tim13_idx, tim13_cc_channel); } - if ((TimerDomain::cmsis_timers[tim8_idx]->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[tim8_idx]->SR, TIM_SR_UIF); + if ((tim8->SR & TIM_SR_UIF) != 0) { + CLEAR_BIT(tim8->SR, TIM_SR_UIF); TimerDomain::callbacks[tim8_idx](TimerDomain::callback_data[tim8_idx]); } if (tim8_cc_channel != 0) { @@ -153,7 +152,7 @@ extern "C" void TIM8_TRG_COM_TIM14_IRQHandler(void) { uint32_t tim14_cc_channel = (tim14->SR & CaptureCompareInterruptMask) - 1; if ((tim14->SR & TIM_SR_UIF) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[tim14_idx]->SR, TIM_SR_UIF); + CLEAR_BIT(tim14->SR, TIM_SR_UIF); TimerDomain::callbacks[tim14_idx](TimerDomain::callback_data[tim14_idx]); } if (tim14_cc_channel != 0) { @@ -161,8 +160,8 @@ extern "C" void TIM8_TRG_COM_TIM14_IRQHandler(void) { } constexpr uint32_t com_trg_flags = TIM_SR_TIF | TIM_SR_COMIF; - if ((TimerDomain::cmsis_timers[tim8_idx]->SR & com_trg_flags) != 0) { - CLEAR_BIT(TimerDomain::cmsis_timers[tim8_idx]->SR, com_trg_flags); + if ((tim8->SR & com_trg_flags) != 0) { + CLEAR_BIT(tim8->SR, com_trg_flags); /* this could probably have some other callback */ TimerDomain::callbacks[tim8_idx](TimerDomain::callback_data[tim8_idx]); }
diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 82e745e..354f9b0 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -370,10 +370,10 @@ extern TIM_HandleTypeDef htim24; case TimerAF::Encoder: return GPIODomain::OperationMode::ALT_PP; - // TODO: check what this really needs to be for each case TimerAF::InputCapture: - return GPIODomain::OperationMode::OUTPUT_OPENDRAIN; + return GPIODomain::OperationMode::ALT_PP; + // TODO: check what this really needs to be for each case TimerAF::BreakInput: return GPIODomain::OperationMode::OUTPUT_OPENDRAIN; case TimerAF::BreakInputCompare: @@ -391,10 +391,10 @@ extern TIM_HandleTypeDef htim24; case TimerAF::Encoder: return GPIODomain::Pull::Up; - // TODO: check what this really needs to be for each case TimerAF::InputCapture: - return GPIODomain::Pull::Up; - + return GPIODomain::Pull::None; + + // TODO: check what this really needs to be for each case TimerAF::BreakInput: return GPIODomain::Pull::None; case TimerAF::BreakInputCompare: @@ -412,10 +412,10 @@ extern TIM_HandleTypeDef htim24; case TimerAF::Encoder: return GPIODomain::Speed::Low; - // TODO: check what this really needs to be for each case TimerAF::InputCapture: return GPIODomain::Speed::Low; - + + // TODO: check what this really needs to be for each case TimerAF::BreakInput: return GPIODomain::Speed::Low; case TimerAF::BreakInputCompare:
diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 354f9b0..7564d06 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -239,7 +239,7 @@ extern TIM_HandleTypeDef htim24; float duty_cycle; uint32_t frequency; }; - /* 2x as big as necessary but this makes indexing easier */ + /* 2x as big as necessary but this makes indexing easier & faster */ static InputCaptureInfo* input_capture_info[max_instances][input_capture_channels]; static InputCaptureInfo input_capture_info_backing[max_instances][input_capture_channels]; @@ -689,7 +689,25 @@ extern TIM_HandleTypeDef htim24; template <std::size_t N> struct Init { static inline std::array<Instance, N> instances{}; + static void TIM_Default_Callback(void* raw) { (void)raw; } + static void init(std::span<const Config, N> cfgs) { + TimerDomain::callbacks[1] = TIM_Default_Callback; + TimerDomain::callbacks[2] = TIM_Default_Callback; + TimerDomain::callbacks[3] = TIM_Default_Callback; + TimerDomain::callbacks[4] = TIM_Default_Callback; + TimerDomain::callbacks[5] = TIM_Default_Callback; + TimerDomain::callbacks[6] = TIM_Default_Callback; + TimerDomain::callbacks[7] = TIM_Default_Callback; + TimerDomain::callbacks[8] = TIM_Default_Callback; + TimerDomain::callbacks[9] = TIM_Default_Callback; + TimerDomain::callbacks[10] = TIM_Default_Callback; + TimerDomain::callbacks[11] = TIM_Default_Callback; + TimerDomain::callbacks[12] = TIM_Default_Callback; + TimerDomain::callbacks[13] = TIM_Default_Callback; + TimerDomain::callbacks[14] = TIM_Default_Callback; + TimerDomain::callbacks[15] = TIM_Default_Callback; + for (std::size_t i = 0; i < N; i++) { const Config& e = cfgs[i]; diff --git a/Inc/HALAL/Services/InputCapture/InputCapture.hpp b/Inc/HALAL/Services/InputCapture/InputCapture.hpp index 7f6f51d..23bb6f5 100644 --- a/Inc/HALAL/Services/InputCapture/InputCapture.hpp +++ b/Inc/HALAL/Services/InputCapture/InputCapture.hpp @@ -25,29 +25,33 @@ template < class InputCapture { TimerWrapper<dev>* timer = nullptr; TimerDomain::InputCaptureInfo *info = nullptr; - bool is_on; + bool is_on = false; public: InputCapture(TimerWrapper<dev>* tim) { timer = tim; // Setup TimerDomain + uint8_t ch_rising = static_cast<uint8_t>(pin_rising.channel) - 1; + uint8_t ch_falling = static_cast<uint8_t>(channel_falling) - 1; info = - &TimerDomain::input_capture_info_backing[tim->instance->timer_idx][pin_rising.channel]; - TimerDomain::input_capture_info[tim->instance->timer_idx][pin_rising.channel] = info; - TimerDomain::input_capture_info[tim->instance->timer_idx][channel_falling] = info; + &TimerDomain::input_capture_info_backing[tim->instance->timer_idx][ch_rising]; + TimerDomain::input_capture_info[tim->instance->timer_idx][ch_rising] = info; + TimerDomain::input_capture_info[tim->instance->timer_idx][ch_falling] = info; - info->channel_rising = static_cast<uint8_t>(pin_rising.channel) - 1; - info->channel_falling = static_cast<uint8_t>(channel_falling) - 1; + info->channel_rising = ch_rising; + info->channel_falling = ch_falling; info->value_rising = 0.0f; info->duty_cycle = 0.0f; info->frequency = 0; - + + timer->enable_nvic(); + TIM_IC_InitTypeDef sConfigIC = { .ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING, + .ICSelection = TIM_ICSELECTION_DIRECTTI, .ICPrescaler = TIM_ICPSC_DIV1, .ICFilter = 0, - .ICSelection = TIM_ICSELECTION_DIRECTTI, }; timer->template config_input_compare_channel<pin_rising.channel>(&sConfigIC); diff --git a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp index 8779375..caf40c2 100644 --- a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp +++ b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp @@ -24,15 +24,16 @@ TIM_HandleTypeDef htim24; void (*TimerDomain::callbacks[TimerDomain::max_instances])(void*) = {nullptr}; void* TimerDomain::callback_data[TimerDomain::max_instances] = {nullptr}; -TimerDomain::InputCaptureInfo input_capture_info_backing[TimerDomain::max_instances][TimerDomain::input_capture_channels/2] = {}; -TimerDomain::InputCaptureInfo* input_capture_info[TimerDomain::max_instances][TimerDomain::input_capture_channels] = {nullptr}; +TimerDomain::InputCaptureInfo* TimerDomain::input_capture_info[max_instances][input_capture_channels]; +TimerDomain::InputCaptureInfo TimerDomain::input_capture_info_backing[max_instances][input_capture_channels]; -static void TIM_IC_CaptureCallback(const uint32_t timer_idx, uint32_t channel) +static void TIM_IC_CaptureCallback(const uint32_t timer_idx, uint32_t cc_channel) { TIM_HandleTypeDef* htim = TimerDomain::hal_handles[timer_idx]; htim->Instance->CNT = 0; - TimerDomain::InputCaptureInfo* info = input_capture_info[timer_idx][channel]; + uint32_t channel = __builtin_ffs(cc_channel) - 2; + TimerDomain::InputCaptureInfo* info = TimerDomain::input_capture_info[timer_idx][channel]; if(info->channel_rising == channel) { // NOTE: CCR1 - CCR4 are contiguous info->value_rising = (float)(*(((uint32_t*)&htim->Instance->CCR1) + channel)); @@ -51,12 +52,12 @@ static void TIM_IC_CaptureCallback(const uint32_t timer_idx, uint32_t channel) static void TIM_InterruptCallback(const uint32_t timer_idx) { TIM_TypeDef* tim = TimerDomain::cmsis_timers[timer_idx]; - uint32_t cc_channel = (tim->SR & CaptureCompareInterruptMask) - 1; if(tim->SR & TIM_SR_UIF) { CLEAR_BIT(tim->SR, TIM_SR_UIF); TimerDomain::callbacks[timer_idx](TimerDomain::callback_data[timer_idx]); } - + + uint32_t cc_channel = tim->SR & CaptureCompareInterruptMask; if(cc_channel != 0) { TIM_IC_CaptureCallback(timer_idx, cc_channel); } @@ -101,7 +102,7 @@ extern "C" void TIM8_BRK_TIM12_IRQHandler(void) { TIM_TypeDef* tim8 = TimerDomain::cmsis_timers[tim8_idx]; TIM_TypeDef* tim12 = TimerDomain::cmsis_timers[tim12_idx]; - uint32_t tim12_cc_channel = (tim12->SR & CaptureCompareInterruptMask) - 1; + uint32_t tim12_cc_channel = tim12->SR & CaptureCompareInterruptMask; if ((tim12->SR & TIM_SR_UIF) != 0) { CLEAR_BIT(tim12->SR, TIM_SR_UIF); TimerDomain::callbacks[tim12_idx](TimerDomain::callback_data[tim12_idx]); @@ -124,8 +125,8 @@ extern "C" void TIM8_UP_TIM13_IRQHandler(void) { TIM_TypeDef* tim8 = TimerDomain::cmsis_timers[tim8_idx]; TIM_TypeDef* tim13 = TimerDomain::cmsis_timers[tim13_idx]; - uint32_t tim8_cc_channel = (tim8->SR & CaptureCompareInterruptMask) - 1; - uint32_t tim13_cc_channel = (tim13->SR & CaptureCompareInterruptMask) - 1; + uint32_t tim8_cc_channel = tim8->SR & CaptureCompareInterruptMask; + uint32_t tim13_cc_channel = tim13->SR & CaptureCompareInterruptMask; if ((tim13->SR & TIM_SR_UIF) != 0) { CLEAR_BIT(tim13->SR, TIM_SR_UIF); TimerDomain::callbacks[tim13_idx](TimerDomain::callback_data[tim13_idx]); @@ -150,7 +151,7 @@ extern "C" void TIM8_TRG_COM_TIM14_IRQHandler(void) { TIM_TypeDef* tim8 = TimerDomain::cmsis_timers[tim8_idx]; TIM_TypeDef* tim14 = TimerDomain::cmsis_timers[tim14_idx]; - uint32_t tim14_cc_channel = (tim14->SR & CaptureCompareInterruptMask) - 1; + uint32_t tim14_cc_channel = tim14->SR & CaptureCompareInterruptMask; if ((tim14->SR & TIM_SR_UIF) != 0) { CLEAR_BIT(tim14->SR, TIM_SR_UIF); TimerDomain::callbacks[tim14_idx](TimerDomain::callback_data[tim14_idx]);
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an Input Capture service on top of the existing TimerDomain/TimerWrapper infrastructure, extending timer IRQ handling to process capture/compare events and updating mocks/wrappers to support STM32H7 input-capture configuration.
Changes:
- Add
InputCaptureservice and expose it viaTimerWrapper::get_input_capture(). - Extend timer interrupt handlers to handle CC events (and refactor common IRQ logic).
- Update HAL mocks and refactor PWM helpers to use centralized channel-index/shift utilities in
TimerDomain.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| Src/HALAL/Models/TimerDomain/TimerDomain.cpp | Refactors timer ISRs and adds capture/compare handling + input-capture bookkeeping. |
| Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | Adds input-capture data structures/utilities and refactors timer globals/initialization. |
| Inc/HALAL/Services/Time/TimerWrapper.hpp | Exposes get_input_capture() and adds input-capture channel/interrupt configuration helpers. |
| Inc/HALAL/Services/InputCapture/InputCapture.hpp | New InputCapture service implementation. |
| Inc/MockedDrivers/stm32h7xx_hal_mock.h | Extends mocked TIM definitions/types for input capture usage. |
| Inc/HALAL/Services/PWM/PWM.hpp | Switches to TimerDomain helpers for channel indexing/bit shifting. |
| Inc/HALAL/Services/PWM/DualPWM.hpp | Switches to TimerDomain helpers for channel indexing/bit shifting. |
| Inc/HALAL/Services/Encoder/Encoder.hpp | Adjusts ARR initialization based on timer width. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
|
I'll put this as a draft as you don't want to include this changes until it has been validated using it to read a real IMD |
Open
ST-LIB Release Plan
Pending changes
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.