summaryrefslogtreecommitdiff
path: root/bsp
diff options
context:
space:
mode:
authorwdfk-prog <[email protected]>2026-11-05 12:08:21 +0800
committerRbb666 <[email protected]>2026-11-06 09:20:03 +0800
commit7ac0b44a8d4fdf6b21641d3ecbedbe3ec4f263ca (patch)
tree6a8851b7f3c5852cc3a90cb863a8571317b88783 /bsp
parent89c70abccd0359f9f7ba9df2e81dbe764d3e78d3 (diff)
feat[bsp][stm32]: add Input Capture support for STM32F1 and STM32H7
- Implement RT-Thread Input Capture devices using STM32 HAL TIM1-TIM4, with configurable channels, filters and initial capture polarity. - Add STM32F1 and STM32H7 configuration headers and integrate the driver into Kconfig and SConscript. - Handle APB timer clock multipliers, TIMPRE and 32-bit counter capabilities using HAL feature macros. - Clear pending capture and overcapture flags before restarting a capture channel.
Diffstat (limited to 'bsp')
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/Kconfig122
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/SConscript3
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/config/f1/inputcapture_config.h69
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/config/h7/inputcapture_config.h69
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/drv_config.h2
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.c405
-rw-r--r--bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.h53
-rw-r--r--bsp/stm32/stm32f103-atk-warshipv3/.ci/attachconfig/inputcapture.attach2
-rw-r--r--bsp/stm32/stm32h743-st-nucleo/.ci/attachconfig/ci.attachconfig.yml21
9 files changed, 746 insertions, 0 deletions
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/Kconfig b/bsp/stm32/libraries/HAL_Drivers/drivers/Kconfig
index e68f439baa..2987e112c7 100644
--- a/bsp/stm32/libraries/HAL_Drivers/drivers/Kconfig
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/Kconfig
@@ -188,3 +188,125 @@ if SOC_SERIES_STM32H7
select BSP_USING_BDMA
default n
endif
+
+menu "STM32 Input Capture BSP"
+
+ config BSP_USING_INPUT_CAPTURE
+ bool "Enable STM32 timer Input Capture devices"
+ default n
+ select RT_USING_INPUT_CAPTURE
+ help
+ Registers selected STM32 hardware timer Input Capture devices.
+ F1 and H7 have configuration headers; additional series need a
+ matching configuration header before enabling this option.
+ GPIO, clock and interrupt setup are owned by CubeMX HAL IC MSP.
+
+ if BSP_USING_INPUT_CAPTURE
+ config BSP_INPUT_CAPTURE_COUNTER_HZ
+ int "Capture timer counter frequency (Hz)"
+ range 100000 1000000
+ default 1000000
+
+ config BSP_USING_INPUT_CAPTURE_TIM1
+ bool "Enable TIM1 Input Capture"
+ default n
+
+ if BSP_USING_INPUT_CAPTURE_TIM1
+ config BSP_INPUT_CAPTURE_TIM1_NAME
+ string "TIM1 Input Capture device name"
+ default "ic1"
+
+ config BSP_INPUT_CAPTURE_TIM1_CHANNEL
+ int "TIM1 Input Capture channel (1-4)"
+ range 1 4
+ default 1
+
+ config BSP_INPUT_CAPTURE_TIM1_FILTER
+ int "TIM1 Input Capture digital filter (0-15)"
+ range 0 15
+ default 0
+
+ config BSP_INPUT_CAPTURE_TIM1_INITIAL_RISING
+ int "TIM1 initial capture edge (1 rising, 0 falling)"
+ range 0 1
+ default 1
+ endif
+
+ config BSP_USING_INPUT_CAPTURE_TIM2
+ bool "Enable TIM2 Input Capture"
+ default n
+
+ if BSP_USING_INPUT_CAPTURE_TIM2
+ config BSP_INPUT_CAPTURE_TIM2_NAME
+ string "TIM2 Input Capture device name"
+ default "ic2"
+
+ config BSP_INPUT_CAPTURE_TIM2_CHANNEL
+ int "TIM2 Input Capture channel (1-4)"
+ range 1 4
+ default 1
+
+ config BSP_INPUT_CAPTURE_TIM2_FILTER
+ int "TIM2 Input Capture digital filter (0-15)"
+ range 0 15
+ default 0
+
+ config BSP_INPUT_CAPTURE_TIM2_INITIAL_RISING
+ int "TIM2 initial capture edge (1 rising, 0 falling)"
+ range 0 1
+ default 1
+ endif
+
+ config BSP_USING_INPUT_CAPTURE_TIM3
+ bool "Enable TIM3 Input Capture"
+ default y
+
+ if BSP_USING_INPUT_CAPTURE_TIM3
+ config BSP_INPUT_CAPTURE_TIM3_NAME
+ string "TIM3 Input Capture device name"
+ default "ic3"
+
+ config BSP_INPUT_CAPTURE_TIM3_CHANNEL
+ int "TIM3 Input Capture channel (1-4)"
+ range 1 4
+ default 1
+
+ config BSP_INPUT_CAPTURE_TIM3_FILTER
+ int "TIM3 Input Capture digital filter (0-15)"
+ range 0 15
+ default 0
+
+ config BSP_INPUT_CAPTURE_TIM3_INITIAL_RISING
+ int "TIM3 initial capture edge (1 rising, 0 falling)"
+ range 0 1
+ default 1
+ endif
+
+ config BSP_USING_INPUT_CAPTURE_TIM4
+ bool "Enable TIM4 Input Capture"
+ default n
+
+ if BSP_USING_INPUT_CAPTURE_TIM4
+ config BSP_INPUT_CAPTURE_TIM4_NAME
+ string "TIM4 Input Capture device name"
+ default "ic4"
+
+ config BSP_INPUT_CAPTURE_TIM4_CHANNEL
+ int "TIM4 Input Capture channel (1-4)"
+ range 1 4
+ default 1
+
+ config BSP_INPUT_CAPTURE_TIM4_FILTER
+ int "TIM4 Input Capture digital filter (0-15)"
+ range 0 15
+ default 0
+
+ config BSP_INPUT_CAPTURE_TIM4_INITIAL_RISING
+ int "TIM4 initial capture edge (1 rising, 0 falling)"
+ range 0 1
+ default 1
+ endif
+
+ endif
+
+endmenu
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/SConscript b/bsp/stm32/libraries/HAL_Drivers/drivers/SConscript
index 0a9ceb24da..40f2ce595e 100644
--- a/bsp/stm32/libraries/HAL_Drivers/drivers/SConscript
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/SConscript
@@ -27,6 +27,9 @@ if GetDepend(['RT_USING_SERIAL']):
if GetDepend(['BSP_USING_TIM']):
src += ['drv_tim.c']
+if GetDepend(['BSP_USING_INPUT_CAPTURE']):
+ src += ['drv_inputcapture.c']
+
if GetDepend(['BSP_USING_PWM']):
src += ['drv_pwm.c', 'drv_tim.c']
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/config/f1/inputcapture_config.h b/bsp/stm32/libraries/HAL_Drivers/drivers/config/f1/inputcapture_config.h
new file mode 100644
index 0000000000..65e34effcb
--- /dev/null
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/config/f1/inputcapture_config.h
@@ -0,0 +1,69 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-11-02 wdfk-prog first version
+ */
+#ifndef __INPUTCAPTURE_CONFIG_H__
+#define __INPUTCAPTURE_CONFIG_H__
+
+#include "drv_inputcapture.h"
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM1
+/** @brief Board-selected TIM1 Input Capture configuration. */
+#define INPUTCAPTURE_TIM1_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM1_NAME, \
+ .instance = TIM1, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM1_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM1_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM1_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM1 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM2
+/** @brief Board-selected TIM2 Input Capture configuration. */
+#define INPUTCAPTURE_TIM2_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM2_NAME, \
+ .instance = TIM2, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM2_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM2_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM2_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM2 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM3
+/** @brief Board-selected TIM3 Input Capture configuration. */
+#define INPUTCAPTURE_TIM3_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM3_NAME, \
+ .instance = TIM3, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM3_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM3_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM3_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM3 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM4
+/** @brief Board-selected TIM4 Input Capture configuration. */
+#define INPUTCAPTURE_TIM4_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM4_NAME, \
+ .instance = TIM4, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM4_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM4_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM4_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM4 */
+
+#endif /* __INPUTCAPTURE_CONFIG_H__ */
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/config/h7/inputcapture_config.h b/bsp/stm32/libraries/HAL_Drivers/drivers/config/h7/inputcapture_config.h
new file mode 100644
index 0000000000..d2621b93d6
--- /dev/null
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/config/h7/inputcapture_config.h
@@ -0,0 +1,69 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-11-01 wdfk-prog first version
+ */
+#ifndef __INPUTCAPTURE_CONFIG_H__
+#define __INPUTCAPTURE_CONFIG_H__
+
+#include "drv_inputcapture.h"
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM1
+/** @brief Board-selected TIM1 Input Capture configuration. */
+#define INPUTCAPTURE_TIM1_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM1_NAME, \
+ .instance = TIM1, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM1_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM1_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM1_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM1 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM2
+/** @brief Board-selected TIM2 Input Capture configuration. */
+#define INPUTCAPTURE_TIM2_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM2_NAME, \
+ .instance = TIM2, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM2_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM2_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM2_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM2 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM3
+/** @brief Board-selected TIM3 Input Capture configuration. */
+#define INPUTCAPTURE_TIM3_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM3_NAME, \
+ .instance = TIM3, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM3_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM3_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM3_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM3 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM4
+/** @brief Board-selected TIM4 Input Capture configuration. */
+#define INPUTCAPTURE_TIM4_CONFIG \
+ { \
+ .name = BSP_INPUT_CAPTURE_TIM4_NAME, \
+ .instance = TIM4, \
+ .channel = STM32_CAPTURE_CHANNEL(BSP_INPUT_CAPTURE_TIM4_CHANNEL), \
+ .input_filter = BSP_INPUT_CAPTURE_TIM4_FILTER, \
+ .counter_period = 0xFFFFU, \
+ .initial_edge_rising = BSP_INPUT_CAPTURE_TIM4_INITIAL_RISING, \
+ .hardware_both_edges = RT_FALSE, \
+ }
+#endif /* BSP_USING_INPUT_CAPTURE_TIM4 */
+
+#endif /* __INPUTCAPTURE_CONFIG_H__ */
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_config.h b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_config.h
index 80a2897ecd..4be2d6a1e4 100644
--- a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_config.h
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_config.h
@@ -40,6 +40,7 @@ extern "C" {
#include "f1/usbd_config.h"
#include "f1/i2c_hard_config.h"
#include "f1/pulse_encoder_config.h"
+#include "f1/inputcapture_config.h"
#elif defined(SOC_SERIES_STM32F2)
#include "f2/dma_config.h"
#include "f2/uart_config.h"
@@ -142,6 +143,7 @@ extern "C" {
#include "h7/pwm_config.h"
#include "h7/usbd_config.h"
#include "h7/i2c_hard_config.h"
+#include "h7/inputcapture_config.h"
#elif defined(SOC_SERIES_STM32H7RS)
#include "h7/dma_config.h"
#include "h7/uart_config.h"
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.c b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.c
new file mode 100644
index 0000000000..e9e992bcc5
--- /dev/null
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.c
@@ -0,0 +1,405 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-11-01 wdfk-prog first version
+ */
+#include <board.h>
+#include <rtdevice.h>
+#include "drv_inputcapture.h"
+#include "drv_config.h"
+
+/**
+ * @brief Convert a 1-based timer capture channel to the HAL TIM_CHANNEL_x value.
+ * @param number Input capture channel number (1 through 4).
+ * @note STM32F1 and STM32H7 HALs use a 4-unit channel encoding stride.
+ */
+#define STM32_CAPTURE_CHANNEL(number) (((number) - 1U) * 4U)
+
+/**
+ * @brief Check whether the RCC TIMPRE clock multiplier is currently selected.
+ * @note STM32H7 exposes the TIMPRE bit in RCC->CFGR; STM32F1 has no TIMPRE.
+ * Other series may place TIMPRE in a different register and need a
+ * corresponding adapter here. Availability is detected by the CMSIS bit.
+ */
+#if defined(RCC_CFGR_TIMPRE)
+#define STM32_INPUTCAPTURE_TIMPRE_ENABLED() ((RCC->CFGR & RCC_CFGR_TIMPRE) != 0U)
+#else
+#define STM32_INPUTCAPTURE_TIMPRE_ENABLED() (0U)
+#endif /* defined(RCC_CFGR_TIMPRE) */
+
+/**
+ * @brief Check whether a specific TIM instance has a 32-bit counter.
+ * @param instance STM32 TIM peripheral instance.
+ * @note STM32H7 TIM2 has a 32-bit counter, while STM32F1 timer widths vary
+ * by device. Query the HAL/CMSIS instance capability when present;
+ * otherwise use the conservative 16-bit ARR configuration.
+ */
+#if defined(IS_TIM_32B_COUNTER_INSTANCE)
+#define STM32_INPUTCAPTURE_TIM_HAS_32BIT_COUNTER(instance) (IS_TIM_32B_COUNTER_INSTANCE(instance))
+#else
+#define STM32_INPUTCAPTURE_TIM_HAS_32BIT_COUNTER(instance) (0U)
+#endif /* defined(IS_TIM_32B_COUNTER_INSTANCE) */
+
+/**
+ * @brief Whether HAL exposes a both-edge input capture polarity constant.
+ * @note This macro exists in STM32F1, STM32H7 and some other HAL series; it is
+ * not H7-specific. Its presence alone does not guarantee every TIM/channel
+ * can capture both edges. The board remains responsible for that choice.
+ */
+#if defined(TIM_INPUTCHANNELPOLARITY_BOTHEDGE)
+#define STM32_INPUTCAPTURE_HAL_HAS_BOTHEDGE_POLARITY 1
+/** @brief HAL polarity value used for hardware both-edge capture, if available. */
+#define STM32_INPUTCAPTURE_HAL_BOTHEDGE_POLARITY TIM_INPUTCHANNELPOLARITY_BOTHEDGE
+#else
+#define STM32_INPUTCAPTURE_HAL_HAS_BOTHEDGE_POLARITY 0
+#endif /* defined(TIM_INPUTCHANNELPOLARITY_BOTHEDGE) */
+
+/** @brief Stable indices for selected physical timer instances. */
+enum stm32_capture_index
+{
+#ifdef BSP_USING_INPUT_CAPTURE_TIM1
+ STM32_CAPTURE_TIM1_INDEX,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM1 */
+#ifdef BSP_USING_INPUT_CAPTURE_TIM2
+ STM32_CAPTURE_TIM2_INDEX,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM2 */
+#ifdef BSP_USING_INPUT_CAPTURE_TIM3
+ STM32_CAPTURE_TIM3_INDEX,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM3 */
+#ifdef BSP_USING_INPUT_CAPTURE_TIM4
+ STM32_CAPTURE_TIM4_INDEX,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM4 */
+ STM32_CAPTURE_DEVICE_COUNT
+};
+
+/** @brief Board-selected physical timer capture configuration table. */
+static const struct stm32_inputcapture_config stm32_capture_configs[] = {
+#ifdef BSP_USING_INPUT_CAPTURE_TIM1
+ INPUTCAPTURE_TIM1_CONFIG,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM1 */
+#ifdef BSP_USING_INPUT_CAPTURE_TIM2
+ INPUTCAPTURE_TIM2_CONFIG,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM2 */
+#ifdef BSP_USING_INPUT_CAPTURE_TIM3
+ INPUTCAPTURE_TIM3_CONFIG,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM3 */
+#ifdef BSP_USING_INPUT_CAPTURE_TIM4
+ INPUTCAPTURE_TIM4_CONFIG,
+#endif /* BSP_USING_INPUT_CAPTURE_TIM4 */
+};
+
+/** @brief Runtime state owned by one RT-Thread physical Capture device. */
+struct stm32_capture_device
+{
+ struct rt_inputcapture_device parent; /**< RT-Thread Input Capture object. */
+ const struct stm32_inputcapture_config *config; /**< Immutable timer configuration. */
+ TIM_HandleTypeDef timer; /**< HAL timer handle owned by this driver. */
+ rt_uint32_t pulsewidth_us; /**< Last completed edge interval, in us. */
+ rt_uint32_t counter_hz; /**< Timer counter frequency, in Hz. */
+ rt_uint32_t previous_count; /**< Last captured CCR value. */
+ rt_bool_t first_edge; /**< Ignore the first partial interval. */
+ rt_bool_t next_edge_rising; /**< Edge direction tracked for each sample. */
+ volatile rt_bool_t active; /**< Capture channel is running. */
+};
+
+/** @brief One Capture device state per enabled timer instance. */
+static struct stm32_capture_device stm32_capture_devices[STM32_CAPTURE_DEVICE_COUNT];
+
+/**
+ * @brief Obtain the effective timer input clock, including APB prescaling.
+ * @param instance Physical TIM instance.
+ * @return Timer clock frequency in Hz.
+ */
+static rt_uint32_t stm32_inputcapture_timer_clock(TIM_TypeDef *instance)
+{
+ rt_uint32_t hclk = HAL_RCC_GetHCLKFreq(); /* Timer APB parent clock, in Hz. */
+ rt_uint32_t pclk; /* Selected APB peripheral clock, in Hz. */
+ rt_uint32_t divider; /* APB prescaler relative to HCLK. */
+
+ pclk = ((rt_ubase_t)instance >= (rt_ubase_t)APB2PERIPH_BASE) ? HAL_RCC_GetPCLK2Freq() : HAL_RCC_GetPCLK1Freq();
+ if (pclk == 0U)
+ {
+ return 0U;
+ }
+ divider = hclk / pclk;
+ if (divider <= 1U)
+ {
+ return pclk;
+ }
+ /* TIMPRE is an optional RCC feature; the common HAL adapter handles its absence. */
+ if (STM32_INPUTCAPTURE_TIMPRE_ENABLED() && divider >= 4U)
+ {
+ return pclk * 4U;
+ }
+ return pclk * 2U;
+}
+
+/**
+ * @brief Initialize one timer for hardware input capture with a 1-channel configuration.
+ * @param inputcapture Device whose hardware timer is initialized.
+ * @return RT_EOK, or a negative HAL/clock error code.
+ */
+static rt_err_t stm32_capture_init(struct rt_inputcapture_device *inputcapture)
+{
+ struct stm32_capture_device *device =
+ rt_container_of(inputcapture, struct stm32_capture_device, parent); /* Capture state. */
+ const struct stm32_inputcapture_config *config = device->config; /* Board mapping. */
+ TIM_HandleTypeDef *timer = &device->timer; /* Private HAL TIM handle. */
+ TIM_IC_InitTypeDef ic = { 0 }; /* Single direct-TI input channel setup. */
+ rt_uint32_t clock = stm32_inputcapture_timer_clock(config->instance); /* Effective TIM clock. */
+
+ if (clock < BSP_INPUT_CAPTURE_COUNTER_HZ || clock % BSP_INPUT_CAPTURE_COUNTER_HZ != 0U)
+ {
+ return -RT_EINVAL;
+ }
+ timer->Instance = config->instance;
+ timer->Init.Prescaler = clock / BSP_INPUT_CAPTURE_COUNTER_HZ - 1U;
+ timer->Init.CounterMode = TIM_COUNTERMODE_UP;
+ timer->Init.Period = config->counter_period;
+ /* Use the full hardware range only when this specific TIM has a 32-bit counter. */
+ if (STM32_INPUTCAPTURE_TIM_HAS_32BIT_COUNTER(config->instance))
+ {
+ timer->Init.Period = 0xFFFFFFFFU;
+ }
+ timer->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+ timer->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
+ device->counter_hz = BSP_INPUT_CAPTURE_COUNTER_HZ;
+
+ /* HAL_TIM_IC_Init invokes the HAL_TIM_IC_MspInit provided by CubeMX. */
+ if (HAL_TIM_IC_Init(timer) != HAL_OK)
+ {
+ return -RT_ERROR;
+ }
+
+ ic.ICPolarity = config->initial_edge_rising ? TIM_INPUTCHANNELPOLARITY_RISING : TIM_INPUTCHANNELPOLARITY_FALLING;
+ if (config->hardware_both_edges)
+ {
+#if STM32_INPUTCAPTURE_HAL_HAS_BOTHEDGE_POLARITY
+ ic.ICPolarity = STM32_INPUTCAPTURE_HAL_BOTHEDGE_POLARITY;
+#else
+ HAL_TIM_IC_DeInit(timer);
+ return -RT_ENOSYS;
+#endif /* STM32_INPUTCAPTURE_HAL_HAS_BOTHEDGE_POLARITY */
+ }
+ ic.ICSelection = TIM_ICSELECTION_DIRECTTI;
+ ic.ICPrescaler = TIM_ICPSC_DIV1;
+ ic.ICFilter = config->input_filter;
+ if (HAL_TIM_IC_ConfigChannel(timer, &ic, config->channel) != HAL_OK)
+ {
+ HAL_TIM_IC_DeInit(timer);
+ return -RT_ERROR;
+ }
+ return RT_EOK;
+}
+
+/**
+ * @brief Start the selected Capture channel after RT-Thread creates its FIFO.
+ * @param inputcapture RT-Thread Input Capture device to open.
+ * @return RT_EOK or a negative FIFO/HAL error code.
+ */
+static rt_err_t stm32_capture_open(struct rt_inputcapture_device *inputcapture)
+{
+ struct stm32_capture_device *device =
+ rt_container_of(inputcapture, struct stm32_capture_device, parent); /* Capture state. */
+ const struct stm32_inputcapture_config *config = device->config; /* Board mapping. */
+ TIM_HandleTypeDef *timer = &device->timer; /* Configured timer. */
+
+ if (!inputcapture->ringbuff)
+ {
+ return -RT_ENOMEM;
+ }
+ device->first_edge = RT_TRUE;
+ device->previous_count = 0U;
+ device->pulsewidth_us = 0U;
+ device->next_edge_rising = config->initial_edge_rising;
+ __HAL_TIM_SET_COUNTER(timer, 0U);
+ if (!config->hardware_both_edges)
+ {
+ __HAL_TIM_SET_CAPTUREPOLARITY(timer, config->channel,
+ device->next_edge_rising ? TIM_INPUTCHANNELPOLARITY_RISING : TIM_INPUTCHANNELPOLARITY_FALLING);
+ }
+ /* Clear stale capture and overcapture flags before enabling this channel's interrupt. */
+ switch (config->channel)
+ {
+ case TIM_CHANNEL_1:
+ __HAL_TIM_CLEAR_FLAG(timer, TIM_FLAG_CC1 | TIM_FLAG_CC1OF);
+ break;
+ case TIM_CHANNEL_2:
+ __HAL_TIM_CLEAR_FLAG(timer, TIM_FLAG_CC2 | TIM_FLAG_CC2OF);
+ break;
+ case TIM_CHANNEL_3:
+ __HAL_TIM_CLEAR_FLAG(timer, TIM_FLAG_CC3 | TIM_FLAG_CC3OF);
+ break;
+ case TIM_CHANNEL_4:
+ __HAL_TIM_CLEAR_FLAG(timer, TIM_FLAG_CC4 | TIM_FLAG_CC4OF);
+ break;
+ }
+ device->active = RT_TRUE;
+ if (HAL_TIM_IC_Start_IT(timer, config->channel) != HAL_OK)
+ {
+ device->active = RT_FALSE;
+ return -RT_ERROR;
+ }
+ return RT_EOK;
+}
+
+/**
+ * @brief Stop the Capture channel without modifying CubeMX-owned NVIC state.
+ * @param inputcapture RT-Thread device to stop.
+ * @return RT_EOK or a negative HAL stop error.
+ */
+static rt_err_t stm32_capture_close(struct rt_inputcapture_device *inputcapture)
+{
+ struct stm32_capture_device *device =
+ rt_container_of(inputcapture, struct stm32_capture_device, parent); /* Capture state. */
+ if (!device->active)
+ {
+ return RT_EOK;
+ }
+ if (HAL_TIM_IC_Stop_IT(&device->timer, device->config->channel) != HAL_OK)
+ {
+ return -RT_ERROR;
+ }
+ /* Only clear the ISR gate after HAL confirms the capture channel has stopped. */
+ device->active = RT_FALSE;
+ return RT_EOK;
+}
+
+/**
+ * @brief Return the most recently recorded pulse duration to RT-Thread's FIFO.
+ * @param inputcapture Source Capture device.
+ * @param pulsewidth_us Output pulse duration in microseconds.
+ * @return RT_EOK.
+ */
+static rt_err_t stm32_capture_get_pulsewidth(struct rt_inputcapture_device *inputcapture,
+ rt_uint32_t *pulsewidth_us)
+{
+ struct stm32_capture_device *device =
+ rt_container_of(inputcapture, struct stm32_capture_device, parent); /* Capture state. */
+ *pulsewidth_us = device->pulsewidth_us;
+ return RT_EOK;
+}
+
+/** @brief RT-Thread Input Capture HAL backend operations. */
+static const struct rt_inputcapture_ops stm32_capture_ops = {
+ .init = stm32_capture_init,
+ .open = stm32_capture_open,
+ .close = stm32_capture_close,
+ .get_pulsewidth = stm32_capture_get_pulsewidth,
+};
+
+/**
+ * @brief Push a complete interval from the only configured capture channel.
+ * @param timer HAL timer for which Input Capture completed.
+ * @note The IRQ caller must pass a driver-owned timer handle and the selected
+ * capture channel. Handle/channel validation is intentionally omitted
+ * from this hot path; a foreign callback can cause invalid memory access.
+ * @note Software polarity switching can miss short edges; hardware both-edge
+ * sampling assumes every transition is observed in order. Intervals
+ * longer than one counter period cannot be reconstructed without overflow tracking.
+ */
+void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *timer)
+{
+ struct stm32_capture_device *device =
+ rt_container_of(timer, struct stm32_capture_device, timer); /* Owner of the HAL TIM handle. */
+ const struct stm32_inputcapture_config *config = device->config; /* Selected channel mapping. */
+ rt_uint32_t captured; /* Hardware CCR value at the selected edge. */
+ rt_uint32_t delta; /* Timer ticks elapsed, permitting one wrap. */
+ rt_bool_t was_high; /* Logic level of the interval just ended. */
+
+ if (!device->active)
+ {
+ return;
+ }
+ captured = HAL_TIM_ReadCapturedValue(timer, config->channel);
+ was_high = device->next_edge_rising ? RT_FALSE : RT_TRUE;
+ device->next_edge_rising = !device->next_edge_rising;
+ if (!config->hardware_both_edges)
+ {
+ __HAL_TIM_SET_CAPTUREPOLARITY(timer, config->channel,
+ device->next_edge_rising ? TIM_INPUTCHANNELPOLARITY_RISING : TIM_INPUTCHANNELPOLARITY_FALLING);
+ }
+ if (!device->first_edge)
+ {
+ if (captured >= device->previous_count)
+ {
+ delta = captured - device->previous_count;
+ }
+ else
+ {
+ delta = (timer->Init.Period - device->previous_count + 1U) + captured;
+ }
+ device->pulsewidth_us =
+ (rt_uint32_t)(((rt_uint64_t)delta * 1000000ULL + device->counter_hz / 2U) / device->counter_hz);
+ rt_hw_inputcapture_isr(&device->parent, was_high);
+ }
+ device->previous_count = captured;
+ device->first_edge = RT_FALSE;
+}
+
+/**
+ * @brief Register every timer selected in the board's Input Capture configuration.
+ * @return RT_EOK or the first registration failure.
+ */
+int rt_hw_inputcapture_init(void)
+{
+ rt_size_t i; /* Current physical timer. */
+ rt_err_t status; /* Device registration status. */
+
+ for (i = 0; i < STM32_CAPTURE_DEVICE_COUNT; ++i)
+ {
+ stm32_capture_devices[i].config = &stm32_capture_configs[i];
+ stm32_capture_devices[i].parent.ops = &stm32_capture_ops;
+ status = rt_device_inputcapture_register(&stm32_capture_devices[i].parent,
+ stm32_capture_configs[i].name,
+ &stm32_capture_devices[i]);
+ if (status != RT_EOK)
+ {
+ return status;
+ }
+ }
+ return RT_EOK;
+}
+INIT_DEVICE_EXPORT(rt_hw_inputcapture_init);
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM1
+/** @brief Dispatch TIM1 Capture IRQ within one RT-Thread interrupt boundary. */
+void TIM1_CC_IRQHandler(void)
+{
+ rt_interrupt_enter();
+ HAL_TIM_IRQHandler(&stm32_capture_devices[STM32_CAPTURE_TIM1_INDEX].timer);
+ rt_interrupt_leave();
+}
+#endif /* BSP_USING_INPUT_CAPTURE_TIM1 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM2
+/** @brief Dispatch TIM2 Capture IRQ within one RT-Thread interrupt boundary. */
+void TIM2_IRQHandler(void)
+{
+ rt_interrupt_enter();
+ HAL_TIM_IRQHandler(&stm32_capture_devices[STM32_CAPTURE_TIM2_INDEX].timer);
+ rt_interrupt_leave();
+}
+#endif /* BSP_USING_INPUT_CAPTURE_TIM2 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM3
+/** @brief Dispatch TIM3 Capture IRQ within one RT-Thread interrupt boundary. */
+void TIM3_IRQHandler(void)
+{
+ rt_interrupt_enter();
+ HAL_TIM_IRQHandler(&stm32_capture_devices[STM32_CAPTURE_TIM3_INDEX].timer);
+ rt_interrupt_leave();
+}
+#endif /* BSP_USING_INPUT_CAPTURE_TIM3 */
+
+#ifdef BSP_USING_INPUT_CAPTURE_TIM4
+/** @brief Dispatch TIM4 Capture IRQ within one RT-Thread interrupt boundary. */
+void TIM4_IRQHandler(void)
+{
+ rt_interrupt_enter();
+ HAL_TIM_IRQHandler(&stm32_capture_devices[STM32_CAPTURE_TIM4_INDEX].timer);
+ rt_interrupt_leave();
+}
+#endif /* BSP_USING_INPUT_CAPTURE_TIM4 */
diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.h b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.h
new file mode 100644
index 0000000000..499f7fb278
--- /dev/null
+++ b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_inputcapture.h
@@ -0,0 +1,53 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-11-01 wdfk-prog first version
+ */
+#ifndef __DRV_INPUTCAPTURE_H__
+#define __DRV_INPUTCAPTURE_H__
+
+#include <board.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <rthw.h>
+#include <drv_common.h>
+#include <drv_config.h>
+#include <drivers/rt_inputcapture.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @brief Hardware mapping for one STM32 timer Input Capture device.
+ * @note GPIO, peripheral clocks and NVIC setup belong to CubeMX's HAL MSP
+ * initialization, not to this device mapping.
+ */
+struct stm32_inputcapture_config
+{
+ const char *name; /**< Registered RT-Thread device name. */
+ TIM_TypeDef *instance; /**< Physical timer peripheral instance. */
+ rt_uint32_t channel; /**< The only selected HAL TIM_CHANNEL_x. */
+ rt_uint32_t input_filter; /**< HAL input digital filter value. */
+ rt_uint32_t counter_period; /**< Default ARR for 16-bit TIM; 32-bit TIM uses its full range. */
+ rt_bool_t initial_edge_rising; /**< Initial capture edge when the line level is not sampled. */
+ rt_bool_t hardware_both_edges; /**< True only for supported hardware both-edge capture. */
+};
+
+/**
+ * @brief Register enabled STM32 Input Capture timers with RT-Thread.
+ * @return RT_EOK or the first RT-Thread registration error.
+ * @note The board must give each TIM and its IRQ a single owner. If HAL
+ * callback registration is enabled, the board must register this
+ * driver's HAL_TIM_IC_CaptureCallback explicitly.
+ */
+int rt_hw_inputcapture_init(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __DRV_INPUTCAPTURE_H__ */
diff --git a/bsp/stm32/stm32f103-atk-warshipv3/.ci/attachconfig/inputcapture.attach b/bsp/stm32/stm32f103-atk-warshipv3/.ci/attachconfig/inputcapture.attach
new file mode 100644
index 0000000000..398b10980e
--- /dev/null
+++ b/bsp/stm32/stm32f103-atk-warshipv3/.ci/attachconfig/inputcapture.attach
@@ -0,0 +1,2 @@
+CONFIG_BSP_USING_INPUT_CAPTURE=y
+CONFIG_BSP_USING_INPUT_CAPTURE_TIM3=y
diff --git a/bsp/stm32/stm32h743-st-nucleo/.ci/attachconfig/ci.attachconfig.yml b/bsp/stm32/stm32h743-st-nucleo/.ci/attachconfig/ci.attachconfig.yml
new file mode 100644
index 0000000000..825bcb4b73
--- /dev/null
+++ b/bsp/stm32/stm32h743-st-nucleo/.ci/attachconfig/ci.attachconfig.yml
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: Apache-2.0
+
+devices.inputcapture_tim3:
+ kconfig:
+ - CONFIG_BSP_USING_INPUT_CAPTURE=y
+ - CONFIG_BSP_USING_INPUT_CAPTURE_TIM3=y
+ # CI-only: enable the CubeMX HAL TIM module for Input Capture.
+ pre_build: |
+ sed -i 's@^/\*[[:space:]]*#define HAL_TIM_MODULE_ENABLED[[:space:]]*\*/@#define HAL_TIM_MODULE_ENABLED@' board/CubeMX_Config/Inc/stm32h7xx_hal_conf.h
+ grep -Fx '#define HAL_TIM_MODULE_ENABLED' board/CubeMX_Config/Inc/stm32h7xx_hal_conf.h
+ grep -Fx 'CONFIG_RT_USING_INPUT_CAPTURE=y' .config
+ grep -Fx 'CONFIG_BSP_USING_INPUT_CAPTURE=y' .config
+ grep -Fx 'CONFIG_BSP_USING_INPUT_CAPTURE_TIM3=y' .config
+ grep -Fx '#define RT_USING_INPUT_CAPTURE' rtconfig.h
+ grep -Fx '#define BSP_USING_INPUT_CAPTURE' rtconfig.h
+ grep -Fx '#define BSP_USING_INPUT_CAPTURE_TIM3' rtconfig.h
+ ! grep -Eq '^#define RT_USING_(HWTIMER|PWM|PULSE_ENCODER)([[:space:]]|$)' rtconfig.h
+ buildcheckresult: drv_inputcapture.o
+ post_build: |
+ test -s rt-thread.elf
+ find build -type f -name drv_inputcapture.o -print -quit | grep -q .