Skip to content

Feat/new input capture#589

Draft
victor-Lopez25 wants to merge 48 commits intodevelopmentfrom
feat/newInputCapture
Draft

Feat/new input capture#589
victor-Lopez25 wants to merge 48 commits intodevelopmentfrom
feat/newInputCapture

Conversation

@victor-Lopez25
Copy link
Contributor

No description provided.

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]);
@victor-Lopez25 victor-Lopez25 marked this pull request as ready for review March 13, 2026 22:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 InputCapture service and expose it via TimerWrapper::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.

@jorgesg82
Copy link
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

@jorgesg82 jorgesg82 marked this pull request as draft March 20, 2026 20:56
@jorgesg82 jorgesg82 linked an issue Mar 20, 2026 that may be closed by this pull request
@github-actions
Copy link

ST-LIB Release Plan

  • Current version: 5.0.0
  • Pending changesets: 3
  • Highest requested bump: minor
  • Next version if merged now: 5.1.0

Pending changes

  • minor Refactor the ADC stack around DMA-backed acquisition using new MPU (.changesets/adc-dma-minor.md)
  • minor input capture implementation (.changesets/inputcapture.md)
  • none Introduce semver tooling and release automation infrastructure (.changesets/revive-releases-bootstrap.md)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input Capture

3 participants