summaryrefslogtreecommitdiff
path: root/components/drivers
diff options
context:
space:
mode:
authorwdfk-prog <[email protected]>2026-07-07 08:59:22 +0800
committerGitHub <[email protected]>2026-07-07 08:59:22 +0800
commit937be2c97dc69f8dcdc804f56fdad7665547896d (patch)
treefefbe7153a37391a3dc3f62c686e8696e3d7a33a /components/drivers
parent8a0fe09c659b74ed2543d87a36a12355053b2630 (diff)
feat[ADC]: 新增 ADC V2 驱动支持 (#11440)
* feat[ADC V2]: add ADC V2 driver support add ADC V2 core APIs, session state management, and sequence read support add STM32 ADC V2 HAL backend with internal-channel and VREF handling add per-series STM32 ADC V2 default configuration headers wire ADC V2 Kconfig and SConscript integration for components and STM32 BSP drivers keep ADC V2 mutually exclusive with the legacy ADC driver path * feat[ADC V2]: add ADC V2 MSH special channel read support add generic ADC V2 MSH commands for probing, configuring, raw reads, voltage reads, and sequence reads add STM32 ADC V2 backend special commands for VREFINT, temperature sensor, and VBAT reads add STM32 helper APIs for special logical channels, sampling time, resolution, and temperature calculation wire ADC V2 MSH sources through Kconfig and SConscript * feat[ADC V2]: add ADC V2 stream DMA support add ADC V2 stream APIs with latest-frame and FIFO buffering policies wire STM32 ADC V2 to circular DMA stream mode with per-instance Kconfig options extend STM32 ADC config headers and DMA helpers for stream DMA configuration add FinSH stream commands for start, read, cancel, and stop operations handle Cortex-M7 cache-safe DMA buffers for STM32 stream sampling * feat[ADC V2]: add timer trigger support for ADC V2 streams add ADC V2 trigger configuration, validation, and lifecycle coordination add timer update trigger support through clock timer TRGO controls map STM32 ADC V2 timer update triggers to HAL external trigger selector fields add Kconfig, SConscript, and MSH entries for ADC trigger setup and inspection * feat[ADCV2]: add timer and comparator trigger selectors add ADC V2 timer compare and analog comparator trigger configuration support extend clock timer trigger config with compare event and channel fields wire STM32 ADC external trigger selector mappings for TIM update, TIM compare, and COMP output add FinSH trigger_set commands and Kconfig switches for timer and comparator trigger backends * fix[ADC V2]: resolve ADC V1 compatibility Kconfig loop * ci[ADC V2][stm32]: add ADC v2 peripheral config for stm32f407-rt-spark BSP
Diffstat (limited to 'components/drivers')
-rwxr-xr-xcomponents/drivers/Kconfig1
-rw-r--r--components/drivers/adc/Kconfig121
-rw-r--r--components/drivers/adc/SConscript23
-rw-r--r--components/drivers/adc/adc.c (renamed from components/drivers/misc/adc.c)0
-rw-r--r--components/drivers/adc/adc_v2.c1779
-rw-r--r--components/drivers/adc/adc_v2_internal.h62
-rw-r--r--components/drivers/adc/adc_v2_msh.c1225
-rw-r--r--components/drivers/adc/adc_v2_trigger.c411
-rw-r--r--components/drivers/clock_time/Kconfig11
-rw-r--r--components/drivers/include/drivers/adc_v2.h622
-rw-r--r--components/drivers/include/drivers/adc_v2_trigger.h171
-rw-r--r--components/drivers/include/drivers/clock_time.h34
-rw-r--r--components/drivers/include/rtdevice.h6
-rw-r--r--components/drivers/misc/Kconfig8
-rw-r--r--components/drivers/misc/SConscript3
15 files changed, 4463 insertions, 14 deletions
diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig
index db0334f42b..7ceb0720b4 100755
--- a/components/drivers/Kconfig
+++ b/components/drivers/Kconfig
@@ -5,6 +5,7 @@ rsource "ipc/Kconfig"
rsource "serial/Kconfig"
rsource "can/Kconfig"
+rsource "adc/Kconfig"
rsource "clock_time/Kconfig"
rsource "i2c/Kconfig"
rsource "phy/Kconfig"
diff --git a/components/drivers/adc/Kconfig b/components/drivers/adc/Kconfig
new file mode 100644
index 0000000000..28d9ec47a0
--- /dev/null
+++ b/components/drivers/adc/Kconfig
@@ -0,0 +1,121 @@
+menuconfig RT_USING_ADC
+ bool "Using ADC device drivers"
+ default n
+
+if RT_USING_ADC
+ config RT_USING_ADC_V2
+ bool "Using ADC V2 device drivers"
+ default n
+ help
+ Enable the standalone ADC V2 framework. ADC V2 uses the legacy ADC
+ public namespace, so it cannot be enabled together with ADC V1.
+
+ config RT_USING_ADC_V1
+ bool
+ default y if !RT_USING_ADC_V2
+
+if RT_USING_DM && RT_USING_ADC_V1
+ osource "$(SOC_DM_ADC_DIR)/Kconfig"
+endif
+
+if RT_USING_ADC_V2
+
+
+config RT_ADC_USING_TRIGGER
+ bool "Enable ADC trigger framework"
+ default n
+ help
+ Enable device-level ADC trigger requests, framework validation, caching,
+ and stream lifecycle coordination. Timer trigger frequency and TRGO
+ start/stop are delegated to the timer trigger backend.
+
+config RT_ADC_TRIGGER_USING_TIMER
+ bool "Enable ADC timer trigger selector support"
+ depends on RT_ADC_USING_TRIGGER
+ default n
+ help
+ Enable timer update/TRGO and timer compare events as ADC stream
+ trigger sources.
+
+config RT_ADC_TRIGGER_USING_COMPARE
+ bool "Enable ADC analog comparator trigger selector support"
+ depends on RT_ADC_USING_TRIGGER
+ default n
+ help
+ Enable analog comparator output events as ADC stream trigger sources.
+
+
+config RT_ADC_USING_STREAM
+ bool "Enable ADC stream framework"
+ default n
+ help
+ Enable long-running ADC stream sessions.
+
+ Disable this option to remove ADC stream runtime state, stream APIs,
+ and stream backend hooks from the ADC V2 framework.
+
+if RT_ADC_USING_STREAM
+
+menu "ADC stream buffering policies"
+
+config RT_ADC_STREAM_USING_LATEST
+ bool "Enable ADC stream latest-frame policy"
+ default y
+ help
+ Enable the latest-frame ADC stream policy.
+
+ This mode is intended for slow-changing or status-like ADC values, such
+ as battery voltage, temperature, potentiometer input, resistor-divider
+ voltage, or board health monitoring.
+
+ The DMA buffer stores exactly one complete ADC scan frame. Old values
+ may be overwritten. This mode does not require DMA half-transfer or
+ transfer-complete data interrupts, and it does not allocate a framework
+ FIFO.
+
+ This option may be enabled together with RT_ADC_STREAM_USING_FIFO. The
+ active policy is selected per stream at runtime.
+
+config RT_ADC_STREAM_USING_FIFO
+ bool "Enable ADC stream FIFO policy"
+ default n
+ help
+ Enable the FIFO ADC stream policy.
+
+ This mode is intended for continuous and loss-sensitive sampling. The
+ DMA buffer is used as a staging buffer, and completed DMA data events
+ are copied into a ringbuffer for ordered blocking reads.
+
+ FIFO mode requires DMA data events. The default DMA event mode uses both
+ half-transfer and transfer-complete events to reduce read latency. A
+ stream configuration may select full-only events to reduce interrupt
+ frequency, at the cost of higher latency and larger data bursts.
+
+ This option may be enabled together with RT_ADC_STREAM_USING_LATEST. The
+ active policy is selected per stream at runtime.
+
+comment "At least one ADC stream buffering policy should be enabled"
+ depends on !RT_ADC_STREAM_USING_LATEST && !RT_ADC_STREAM_USING_FIFO
+
+endmenu
+
+endif
+
+config RT_ADC_V2_USING_MSH
+ bool "Enable ADC V2 MSH command"
+ depends on RT_USING_FINSH
+ default y
+ help
+ Enable the ADC V2 MSH command.
+
+ This option exports the "adc" shell command for probing ADC devices,
+ configuring channels, reading raw samples, reading voltage values, and
+ reading one ADC sequence. When RT_ADC_USING_STREAM is enabled, stream
+ subcommands are also available.
+
+ Disable this option to remove the ADC V2 shell command while keeping
+ the ADC V2 framework and driver APIs available.
+
+endif
+
+endif
diff --git a/components/drivers/adc/SConscript b/components/drivers/adc/SConscript
new file mode 100644
index 0000000000..94101138f0
--- /dev/null
+++ b/components/drivers/adc/SConscript
@@ -0,0 +1,23 @@
+from building import *
+
+cwd = GetCurrentDir()
+src = []
+CPPPATH = [cwd + '/../include']
+group = []
+
+if GetDepend(['RT_USING_ADC']) and not GetDepend(['RT_USING_ADC_V2']):
+ src += ['adc.c']
+
+if GetDepend(['RT_USING_ADC_V2']):
+ src += ['adc_v2.c']
+
+if GetDepend(['RT_USING_ADC_V2', 'RT_ADC_USING_TRIGGER']):
+ src += ['adc_v2_trigger.c']
+
+if GetDepend(['RT_USING_ADC_V2', 'RT_USING_FINSH', 'RT_ADC_V2_USING_MSH']):
+ src += ['adc_v2_msh.c']
+
+if len(src):
+ group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_ADC'], CPPPATH = CPPPATH)
+
+Return('group')
diff --git a/components/drivers/misc/adc.c b/components/drivers/adc/adc.c
index 056c1f6c30..056c1f6c30 100644
--- a/components/drivers/misc/adc.c
+++ b/components/drivers/adc/adc.c
diff --git a/components/drivers/adc/adc_v2.c b/components/drivers/adc/adc_v2.c
new file mode 100644
index 0000000000..9ec3b6ac11
--- /dev/null
+++ b/components/drivers/adc/adc_v2.c
@@ -0,0 +1,1779 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-04-21 wdfk-prog add standalone adc v2 framework implementation
+ */
+
+/**
+ * @file adc_v2.c
+ * @brief Standalone ADC V2 framework implementation.
+ */
+
+#include <rtconfig.h>
+
+#if defined(RT_USING_ADC_V2)
+
+#include <rtdevice.h>
+#include <rtthread.h>
+
+#define DBG_TAG "adc.v2"
+#define DBG_LVL DBG_LOG
+#include <rtdbg.h>
+
+#include <drivers/adc_v2.h>
+#include "adc_v2_internal.h"
+
+/**
+ * @brief Get the current ADC framework state atomically.
+ * @param device Pointer to the ADC device object.
+ * @return Current ADC runtime state.
+ */
+enum rt_adc_state adc_get_state(rt_adc_device_t device)
+{
+ return (enum rt_adc_state)rt_atomic_load(&device->state);
+}
+
+#if DBG_LVL >= DBG_LOG
+/**
+ * @brief Convert one ADC framework state to a debug log string.
+ * @param state ADC runtime state.
+ * @return Constant state name string.
+ */
+static const char *adc_state_name(enum rt_adc_state state)
+{
+ switch (state)
+ {
+ case RT_ADC_STATE_IDLE:
+ return "IDLE";
+
+ case RT_ADC_STATE_LOCKED:
+ return "BUSY";
+
+#ifdef RT_ADC_USING_STREAM
+ case RT_ADC_STATE_STREAM:
+ return "STREAM";
+#endif /* RT_ADC_USING_STREAM */
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/** @brief ADC control command string table offset base. */
+#define RT_ADC_CMD_NAME_BASE RT_ADC_CMD_GET_RESOLUTION
+
+/** @brief ADC control command string table entry count. */
+#define RT_ADC_CMD_NAME_COUNT (RT_ADC_CMD_CLEAR_CHANNEL_CONFIG - RT_ADC_CMD_GET_RESOLUTION + 1)
+
+/**
+ * @brief ADC control command name table.
+ */
+static const char * const rt_adc_control_cmd_names[RT_ADC_CMD_NAME_COUNT] =
+{
+ [RT_ADC_CMD_GET_RESOLUTION - RT_ADC_CMD_NAME_BASE] = "GET_RESOLUTION",
+ [RT_ADC_CMD_CALIBRATE - RT_ADC_CMD_NAME_BASE] = "CALIBRATE",
+ [RT_ADC_CMD_SET_VREF - RT_ADC_CMD_NAME_BASE] = "SET_VREF",
+ [RT_ADC_CMD_CALC_VREF - RT_ADC_CMD_NAME_BASE] = "CALC_VREF",
+ [RT_ADC_CMD_GET_VREF_CHANNEL - RT_ADC_CMD_NAME_BASE] = "GET_VREF_CHANNEL",
+ [RT_ADC_CMD_SET_SESSION - RT_ADC_CMD_NAME_BASE] = "SET_SESSION",
+ [RT_ADC_CMD_SET_CONFIG - RT_ADC_CMD_NAME_BASE] = "SET_CONFIG",
+ [RT_ADC_CMD_GET_CONFIG - RT_ADC_CMD_NAME_BASE] = "GET_CONFIG",
+ [RT_ADC_CMD_CLEAR_CHANNEL_CONFIG - RT_ADC_CMD_NAME_BASE] = "CLEAR_CHANNEL_CONFIG",
+};
+
+/**
+ * @brief Get ADC control command name.
+ * @param cmd ADC control command identifier.
+ * @return ADC control command name string.
+ */
+static const char *rt_adc_control_cmd_name(int cmd)
+{
+ int index;
+
+ index = cmd - RT_ADC_CMD_NAME_BASE;
+ if ((index < 0) || (index >= RT_ADC_CMD_NAME_COUNT) || (rt_adc_control_cmd_names[index] == RT_NULL))
+ {
+ return "UNKNOWN";
+ }
+
+ return rt_adc_control_cmd_names[index];
+}
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Convert one ADC stream policy to a debug log string.
+ * @param policy ADC stream buffering policy.
+ * @return Constant stream policy name string.
+ */
+static const char *adc_stream_policy_name(enum rt_adc_stream_policy policy)
+{
+ switch (policy)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ case RT_ADC_STREAM_POLICY_LATEST:
+ return "LATEST";
+#endif /* RT_ADC_STREAM_USING_LATEST */
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ case RT_ADC_STREAM_POLICY_FIFO:
+ return "FIFO";
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ default:
+ return "UNKNOWN";
+ }
+}
+#endif /* RT_ADC_USING_STREAM */
+
+#endif /* DBG_LVL >= DBG_LOG */
+
+/**
+ * @brief Store one ADC framework state atomically.
+ * @param device Pointer to the ADC device object.
+ * @param state New ADC runtime state.
+ */
+static void adc_set_state(rt_adc_device_t device, enum rt_adc_state state)
+{
+ LOG_D("state set: device=%s state=%s", device->parent.parent.name, adc_state_name(state));
+ rt_atomic_store(&device->state, (rt_atomic_t)state);
+}
+
+/**
+ * @brief Clear the cached active ADC session configuration.
+ * @param device Pointer to the ADC device object.
+ */
+static void adc_clear_session_ctrl(rt_adc_device_t device)
+{
+ rt_memset(&device->session_ctrl, 0, sizeof(device->session_ctrl));
+}
+
+/**
+ * @brief Cache one ADC session while the caller owns the device BUSY state.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @return Operation status.
+ */
+static rt_err_t adc_cache_session_ctrl(rt_adc_device_t device, rt_uint32_t channels)
+{
+ rt_size_t channel_count;
+
+ if ((device == RT_NULL) || (channels == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ channel_count = rt_adc_channel_mask_count(channels);
+ if (channel_count == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ device->session_ctrl.channels = channels;
+ device->session_ctrl.channel_count = channel_count;
+ device->session_ctrl.configured = RT_TRUE;
+
+ return RT_EOK;
+}
+
+/**
+ * @brief Try to change the ADC framework state atomically.
+ * @param device Pointer to the ADC device object.
+ * @param expected Expected current ADC runtime state.
+ * @param target Target ADC runtime state.
+ * @return RT_TRUE if the state transition succeeded.
+ */
+static rt_bool_t adc_try_set_state(rt_adc_device_t device, enum rt_adc_state expected, enum rt_adc_state target)
+{
+ rt_atomic_t old_state;
+ rt_bool_t matched;
+
+ old_state = (rt_atomic_t)expected;
+ matched = (rt_atomic_compare_exchange_strong(&device->state, &old_state, (rt_atomic_t)target) != 0) ? RT_TRUE : RT_FALSE;
+ if (matched == RT_TRUE)
+ {
+ LOG_D("state transition: device=%s %s -> %s", device->parent.parent.name, adc_state_name(expected), adc_state_name(target));
+ }
+ else
+ {
+ LOG_W("state transition rejected: device=%s expected=%d actual=%d target=%d", device->parent.parent.name, (int)expected, (int)old_state, (int)target);
+ }
+
+ return matched;
+}
+
+/**
+ * @brief Apply one ADC conversion session to the backend driver.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @return Operation status.
+ */
+static rt_err_t adc_session_config(rt_adc_device_t device, rt_uint32_t channels)
+{
+ rt_size_t channel_count;
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (channels == 0U) ||
+ (device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->session_config == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ channel_count = rt_adc_channel_mask_count(channels);
+ LOG_D("session config: device=%s channels=0x%08x count=%d",
+ device->parent.parent.name, channels, channel_count);
+
+ result = device->ops->core->session_config(device, channels);
+ if (result != RT_EOK)
+ {
+ goto fail;
+ }
+
+ result = adc_cache_session_ctrl(device, channels);
+ if (result != RT_EOK)
+ {
+ goto fail;
+ }
+
+ LOG_D("session configure done: device=%s", device->parent.parent.name);
+ return RT_EOK;
+
+fail:
+ adc_clear_session_ctrl(device);
+ LOG_E("session configure failed: device=%s result=%d", device->parent.parent.name, result);
+
+ return result;
+}
+
+/**
+ * @brief Get the raw sample index of one channel in a configured session.
+ * @param session_channels Configured ADC session channel mask.
+ * @param channel Single-channel mask to locate.
+ * @param index Pointer to the output raw sample index.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_session_channel_index(rt_uint32_t session_channels, rt_uint32_t channel, rt_size_t *index)
+{
+ if ((index == RT_NULL) || (session_channels == 0U) || (rt_adc_channel_mask_count(channel) != 1U) || ((session_channels & channel) == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ *index = rt_adc_channel_mask_count(session_channels & (channel - 1U));
+ return RT_EOK;
+}
+
+/**
+ * @brief Transfer ADC sequence samples after the session has been selected.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the sequence request object.
+ * @param sample_total Number of samples to read.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ */
+static rt_err_t adc_sequence_transfer_locked(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_size_t sample_total, rt_size_t *read_count)
+{
+ rt_err_t result;
+ rt_err_t stop_result;
+ rt_size_t index;
+
+ *read_count = 0U;
+ result = device->ops->sequence->start(device, channels, cfg);
+ if (result != RT_EOK)
+ {
+ LOG_E("sequence start failed: device=%s result=%d", device->parent.parent.name, result);
+ return result;
+ }
+
+ for (index = 0U; index < sample_total; index++)
+ {
+ result = device->ops->sequence->read(device, &cfg->buffer[index], cfg->timeout_ms);
+ if (result != RT_EOK)
+ {
+ LOG_E("sequence read failed: device=%s index=%u result=%d",
+ device->parent.parent.name, (unsigned int)index, result);
+ break;
+ }
+
+ (*read_count)++;
+ }
+
+ stop_result = device->ops->sequence->stop(device);
+ if (stop_result != RT_EOK)
+ {
+ LOG_E("sequence stop failed: device=%s result=%d", device->parent.parent.name, stop_result);
+ return (result != RT_EOK) ? result : stop_result;
+ }
+
+ return result;
+}
+
+/**
+ * @brief Open the ADC device.
+ * @param dev Pointer to the RT-Thread device object.
+ * @param oflag Open flags.
+ * @return Operation status.
+ */
+static rt_err_t adc_open_device(rt_device_t dev, rt_uint16_t oflag)
+{
+ rt_adc_device_t device;
+ rt_err_t result;
+
+ RT_UNUSED(oflag);
+
+ if (dev == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->open == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ LOG_D("open start: device=%s", device->parent.parent.name);
+ result = device->ops->core->open(device);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ if (result == RT_EOK)
+ {
+ LOG_I("open done: device=%s", device->parent.parent.name);
+ }
+ else
+ {
+ LOG_E("open failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+ return result;
+}
+
+/**
+ * @brief Close the ADC device.
+ * @param dev Pointer to the RT-Thread device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_close_device(rt_device_t dev)
+{
+ rt_adc_device_t device;
+ rt_err_t result;
+
+ if (dev == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) || (device->ops->core->close == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+#ifdef RT_ADC_USING_STREAM
+ enum rt_adc_state state = adc_get_state(device);
+ if (state == RT_ADC_STATE_STREAM)
+ {
+ result = rt_adc_stream_stop(device);
+ if (result != RT_EOK)
+ {
+ LOG_E("stop stream before close failed: device=%s result=%d", device->parent.parent.name, result);
+ return result;
+ }
+ }
+ else if (state != RT_ADC_STATE_IDLE)
+ {
+ return -RT_EBUSY;
+ }
+#endif /* RT_ADC_USING_STREAM */
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ LOG_D("close start: device=%s", device->parent.parent.name);
+ result = device->ops->core->close(device);
+ if (result == RT_EOK)
+ {
+ adc_clear_session_ctrl(device);
+ }
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ if (result == RT_EOK)
+ {
+ LOG_I("close done: device=%s", device->parent.parent.name);
+ }
+ else
+ {
+ LOG_E("close failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+ return result;
+}
+
+/**
+ * @brief Read ADC raw samples from the configured session.
+ * @param dev Pointer to the RT-Thread device object.
+ * @param pos Unused read position.
+ * @param buffer Pointer to the output sample buffer.
+ * @param size Output sample buffer length in samples.
+ * @return Number of samples read, or negative error code on failure.
+ *
+ * @note @p size must match the configured session channel count. The output
+ * order follows the ascending bit order of the configured channel mask.
+ */
+static rt_ssize_t adc_read_device(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
+{
+ rt_adc_device_t device;
+ rt_uint32_t *values;
+ rt_err_t result;
+
+ RT_UNUSED(pos);
+
+ if ((dev == RT_NULL) || (buffer == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ values = (rt_uint32_t *)buffer;
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U) ||
+ (size != device->session_ctrl.channel_count))
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+
+ struct rt_adc_sequence_cfg sequence_cfg;
+ rt_size_t read_count;
+
+ rt_memset(&sequence_cfg, 0, sizeof(sequence_cfg));
+ sequence_cfg.buffer = values;
+ sequence_cfg.buffer_length = size;
+ sequence_cfg.timeout_ms = -1;
+
+ result = adc_sequence_transfer_locked(device, device->session_ctrl.channels,
+ &sequence_cfg, size, &read_count);
+ if ((result == RT_EOK) && (read_count != size))
+ {
+ result = -RT_ERROR;
+ }
+
+out:
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+
+ return (result == RT_EOK) ? (rt_ssize_t)size : (rt_ssize_t)result;
+}
+
+/**
+ * @brief Handle ADC control commands.
+ * @param dev Pointer to the RT-Thread device object.
+ * @param cmd Control command.
+ * @param args Pointer to the control argument buffer.
+ * @return Operation status.
+ */
+static rt_err_t adc_control_device(rt_device_t dev, int cmd, void *args)
+{
+ rt_adc_device_t device;
+ rt_err_t result;
+
+ if (dev == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ LOG_D("control request: device=%s cmd=%s(0x%x)", device->parent.parent.name, rt_adc_control_cmd_name(cmd), cmd);
+ switch (cmd)
+ {
+ case RT_ADC_CMD_GET_RESOLUTION:
+ case RT_ADC_CMD_GET_VREF_CHANNEL:
+ case RT_ADC_CMD_CALC_VREF:
+ case RT_ADC_CMD_CALIBRATE:
+ case RT_ADC_CMD_SET_CONFIG:
+ case RT_ADC_CMD_GET_CONFIG:
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ result = device->ops->core->control(device, cmd, args);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+
+ case RT_ADC_CMD_CLEAR_CHANNEL_CONFIG:
+ if (args != RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((dev->open_flag & RT_DEVICE_OFLAG_OPEN) == 0U)
+ {
+ return -RT_EIO;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ result = device->ops->core->control(device, cmd, RT_NULL);
+ if (result == RT_EOK)
+ {
+ adc_clear_session_ctrl(device);
+ }
+
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+
+ case RT_ADC_CMD_SET_VREF:
+ if (args == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ rt_uint32_t vref_mv = *((rt_uint32_t *)args);
+ if (vref_mv == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ device->default_vref_mv = vref_mv;
+ LOG_I("fallback vref updated: %lu mV", (unsigned long)device->default_vref_mv);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return RT_EOK;
+
+ case RT_ADC_CMD_SET_SESSION:
+ if (args == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ result = adc_session_config(device, *((const rt_uint32_t *)args));
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+
+ default:
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ result = device->ops->core->control(device, cmd, args);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+ }
+}
+
+#ifdef RT_USING_DEVICE_OPS
+/**
+ * @brief ADC device-framework operation table.
+ */
+static const struct rt_device_ops adc_device_ops = {
+ RT_NULL,
+ adc_open_device,
+ adc_close_device,
+ adc_read_device,
+ RT_NULL,
+ adc_control_device,
+};
+#endif /* RT_USING_DEVICE_OPS */
+
+/**
+ * @brief Register an ADC device.
+ * @param device Pointer to the ADC device object.
+ * @param name Pointer to the device name string.
+ * @param ops Pointer to the ADC driver operation table.
+ * @param user_data Pointer to the driver private data.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_register(rt_adc_device_t device, const char *name, const struct rt_adc_ops *ops, const void *user_data)
+{
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (name == RT_NULL) || (ops == RT_NULL) ||
+ (ops->core == RT_NULL) || (ops->core->session_config == RT_NULL) ||
+ (ops->sequence == RT_NULL) || (ops->sequence->start == RT_NULL) ||
+ (ops->sequence->read == RT_NULL) || (ops->sequence->stop == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ device->parent.type = RT_Device_Class_ADC;
+ device->parent.rx_indicate = RT_NULL;
+ device->parent.tx_complete = RT_NULL;
+#ifdef RT_USING_DEVICE_OPS
+ device->parent.ops = &adc_device_ops;
+#else
+ device->parent.init = RT_NULL;
+ device->parent.open = adc_open_device;
+ device->parent.close = adc_close_device;
+ device->parent.read = adc_read_device;
+ device->parent.write = RT_NULL;
+ device->parent.control = adc_control_device;
+#endif /* RT_USING_DEVICE_OPS */
+ device->ops = ops;
+ rt_spin_lock_init(&device->spinlock);
+ rt_atomic_store(&device->state, (rt_atomic_t)RT_ADC_STATE_IDLE);
+ device->default_vref_mv = 0;
+ adc_clear_session_ctrl(device);
+#if defined(RT_ADC_USING_TRIGGER)
+ adc_trigger_init(device);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+#ifdef RT_ADC_USING_STREAM
+ rt_memset(&device->stream_ctrl, 0, sizeof(device->stream_ctrl));
+#endif /* RT_ADC_USING_STREAM */
+ device->parent.user_data = (void *)user_data;
+
+ result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR);
+ if (result == RT_EOK)
+ {
+ LOG_I("device registered: %s", name);
+ }
+ else
+ {
+ LOG_E("device register failed: %s result=%d", name, result);
+ }
+
+ return result;
+}
+
+/**
+ * @brief Read one validated ADC sequence while the caller owns the device state.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the validated sequence request object.
+ * @param keep_state_on_success Whether to keep BUSY state after a successful read.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ *
+ * @note A synchronous sequence request reads one scan frame only. Partial
+ * samples are reported through @p read_count when a read error or timeout
+ * happens after at least one sample is stored.
+ */
+static rt_err_t adc_read_sequence_locked(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_bool_t keep_state_on_success, rt_size_t *read_count)
+{
+ rt_err_t result;
+ rt_size_t channel_count;
+
+ *read_count = 0U;
+ channel_count = rt_adc_channel_mask_count(channels);
+
+ result = adc_session_config(device, channels);
+ if (result != RT_EOK)
+ {
+ LOG_E("sequence config failed: device=%s result=%d", device->parent.parent.name, result);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+ }
+
+ result = adc_sequence_transfer_locked(device, channels, cfg, channel_count, read_count);
+
+ if ((result != RT_EOK) || (keep_state_on_success != RT_TRUE))
+ {
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ }
+
+ return result;
+}
+
+/**
+ * @brief Read one ADC sequence frame.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the sequence request object.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_read_sequence(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_size_t *read_count)
+{
+ rt_size_t channel_count;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL) || (cfg->buffer == RT_NULL) ||
+ (read_count == RT_NULL) || (channels == 0U) ||
+ (device->ops == RT_NULL) || (device->ops->sequence == RT_NULL) || (device->ops->sequence->start == RT_NULL) ||
+ (device->ops->sequence->read == RT_NULL) || (device->ops->sequence->stop == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ *read_count = 0U;
+ channel_count = rt_adc_channel_mask_count(channels);
+ if ((channel_count == 0U) || (cfg->buffer_length < channel_count))
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ return adc_read_sequence_locked(device, channels, cfg, RT_FALSE, read_count);
+}
+
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Check whether an ADC stream policy is enabled by configuration.
+ * @param policy ADC stream buffering policy.
+ * @return RT_TRUE if the policy is supported, otherwise RT_FALSE.
+ */
+static rt_bool_t adc_stream_policy_is_supported(enum rt_adc_stream_policy policy)
+{
+ switch (policy)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ case RT_ADC_STREAM_POLICY_LATEST:
+ return RT_TRUE;
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ case RT_ADC_STREAM_POLICY_FIFO:
+ return RT_TRUE;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ default:
+ return RT_FALSE;
+ }
+}
+
+/**
+ * @brief Resolve the effective DMA data event mode for one stream request.
+ * @param policy Active stream buffering policy.
+ * @param request Requested DMA event mode.
+ * @param effective Pointer to the output effective event mode.
+ * @return Operation status.
+ */
+static rt_err_t adc_stream_resolve_dma_event_mode(enum rt_adc_stream_policy policy,
+ enum rt_adc_stream_dma_event_mode request,
+ enum rt_adc_stream_dma_event_mode *effective)
+{
+ if (request == RT_ADC_STREAM_DMA_EVENT_AUTO)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ if (policy == RT_ADC_STREAM_POLICY_LATEST)
+ {
+ *effective = RT_ADC_STREAM_DMA_EVENT_NONE;
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if (policy == RT_ADC_STREAM_POLICY_FIFO)
+ {
+ *effective = RT_ADC_STREAM_DMA_EVENT_HALF_FULL;
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ return -RT_ENOSYS;
+ }
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if ((policy == RT_ADC_STREAM_POLICY_FIFO) && (request == RT_ADC_STREAM_DMA_EVENT_NONE))
+ {
+ return -RT_EINVAL;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ if ((request != RT_ADC_STREAM_DMA_EVENT_NONE) &&
+ (request != RT_ADC_STREAM_DMA_EVENT_FULL_ONLY) &&
+ (request != RT_ADC_STREAM_DMA_EVENT_HALF_FULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ *effective = request;
+ return RT_EOK;
+}
+
+/**
+ * @brief Reset one ADC stream runtime control block.
+ * @param ctrl Pointer to the stream control block.
+ */
+static void adc_stream_ctrl_reset(struct rt_adc_stream_ctrl *ctrl)
+{
+ rt_memset(ctrl, 0, sizeof(*ctrl));
+}
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Wake FIFO stream readers when the active stream owns a FIFO.
+ * @param ctrl Pointer to the stream control block.
+ */
+static void adc_stream_fifo_wakeup(struct rt_adc_stream_ctrl *ctrl)
+{
+ if ((ctrl->policy == RT_ADC_STREAM_POLICY_FIFO) && (ctrl->fifo_enabled == RT_TRUE))
+ {
+ rt_completion_done(&ctrl->rx_cpt);
+ }
+}
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Validate a stream request and initialize the stream control block.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the stream configuration object.
+ * @param frame_length Samples in one ADC scan frame.
+ * @return Operation status.
+ */
+static rt_err_t adc_stream_ctrl_configure(rt_adc_device_t device, const struct rt_adc_stream_cfg *cfg, rt_size_t frame_length)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ enum rt_adc_stream_dma_event_mode dma_event_mode;
+ rt_err_t result;
+
+ if ((cfg->dma_buffer == RT_NULL) || (cfg->dma_buffer_length == 0U) || (frame_length == 0U) ||
+ (adc_stream_policy_is_supported(cfg->policy) != RT_TRUE))
+ {
+ LOG_E("stream ctrl config invalid args: device=%s buffer=%p len=%u frame=%u",
+ device->parent.parent.name, cfg->dma_buffer, (unsigned int)cfg->dma_buffer_length, (unsigned int)frame_length);
+ return -RT_EINVAL;
+ }
+
+ result = adc_stream_resolve_dma_event_mode(cfg->policy, cfg->dma_event_mode, &dma_event_mode);
+ if (result != RT_EOK)
+ {
+ LOG_E("stream ctrl config invalid event mode: device=%s policy=%d mode=%d",
+ device->parent.parent.name, (int)cfg->policy, (int)cfg->dma_event_mode);
+ return result;
+ }
+
+ LOG_D("stream ctrl config start: device=%s policy=%s frame=%u dma=%u event_mode=%d",
+ device->parent.parent.name, adc_stream_policy_name(cfg->policy),
+ (unsigned int)frame_length, (unsigned int)cfg->dma_buffer_length, (int)dma_event_mode);
+ ctrl = &device->stream_ctrl;
+ adc_stream_ctrl_reset(ctrl);
+
+#ifdef RT_ADC_STREAM_USING_LATEST
+ if (cfg->policy == RT_ADC_STREAM_POLICY_LATEST)
+ {
+ if (cfg->dma_buffer_length != frame_length)
+ {
+ LOG_E("latest stream invalid length: device=%s dma=%u frame=%u",
+ device->parent.parent.name, (unsigned int)cfg->dma_buffer_length, (unsigned int)frame_length);
+ return -RT_EINVAL;
+ }
+
+ ctrl->active = RT_TRUE;
+ ctrl->policy = cfg->policy;
+ ctrl->dma_event_mode = dma_event_mode;
+ ctrl->dma_buffer = cfg->dma_buffer;
+ ctrl->dma_buffer_length = cfg->dma_buffer_length;
+ ctrl->frame_length = frame_length;
+ LOG_D("latest stream configured: device=%s frame=%u", device->parent.parent.name, (unsigned int)frame_length);
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_LATEST */
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if (cfg->policy == RT_ADC_STREAM_POLICY_FIFO)
+ {
+ if ((cfg->fifo_buffer == RT_NULL) || (cfg->fifo_buffer_length == 0U))
+ {
+ LOG_E("fifo stream invalid fifo buffer: device=%s len=%u",
+ device->parent.parent.name, (unsigned int)cfg->fifo_buffer_length);
+ return -RT_EINVAL;
+ }
+
+ if ((cfg->watermark != 0U) && (cfg->watermark > cfg->fifo_buffer_length))
+ {
+ LOG_E("fifo stream invalid watermark: device=%s watermark=%u fifo=%u",
+ device->parent.parent.name, (unsigned int)cfg->watermark, (unsigned int)cfg->fifo_buffer_length);
+ return -RT_EINVAL;
+ }
+
+ if (cfg->dma_buffer_length < frame_length)
+ {
+ LOG_E("fifo stream invalid dma length: device=%s dma=%u frame=%u",
+ device->parent.parent.name, (unsigned int)cfg->dma_buffer_length, (unsigned int)frame_length);
+ return -RT_EINVAL;
+ }
+
+ /* The default FIFO start path accepts generic DMA staging buffers and
+ * reports runtime overflow through last_error. Use
+ * rt_adc_stream_start_frame_aligned_fifo() when the caller requires a DMA
+ * data-event block layout that never splits ADC scan frames.
+ */
+ ctrl->active = RT_TRUE;
+ ctrl->policy = cfg->policy;
+ ctrl->dma_event_mode = dma_event_mode;
+ ctrl->dma_buffer = cfg->dma_buffer;
+ ctrl->dma_buffer_length = cfg->dma_buffer_length;
+ ctrl->frame_length = frame_length;
+ ctrl->watermark = cfg->watermark;
+ ctrl->callback = cfg->callback;
+ ctrl->user_data = cfg->user_data;
+ ctrl->overflow_count = 0U;
+ rt_atomic_store(&ctrl->last_error, (rt_atomic_t)RT_EOK);
+ rt_ringbuffer_init(&ctrl->fifo, (rt_uint8_t *)cfg->fifo_buffer, (rt_int32_t)(cfg->fifo_buffer_length * sizeof(rt_uint32_t)));
+ rt_completion_init(&ctrl->rx_cpt);
+ ctrl->fifo_enabled = RT_TRUE;
+ LOG_D("fifo stream configured: device=%s frame=%u dma=%u fifo=%u watermark=%u event_mode=%d",
+ device->parent.parent.name, (unsigned int)frame_length, (unsigned int)cfg->dma_buffer_length,
+ (unsigned int)cfg->fifo_buffer_length, (unsigned int)cfg->watermark, (int)dma_event_mode);
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ LOG_E("stream policy unsupported: device=%s policy=%d", device->parent.parent.name, (int)cfg->policy);
+ return -RT_ENOSYS;
+}
+
+#ifdef RT_ADC_STREAM_USING_LATEST
+/**
+ * @brief Read the latest ADC rank values from the DMA buffer.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Destination buffer length in samples.
+ * @param timeout_ms Unused timeout parameter.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ *
+ * @note Latest mode is a lockless DMA-buffer view. CPU-side atomic operations,
+ * spin locks, mutexes, or read-write locks cannot prevent the DMA engine
+ * from updating memory concurrently, because DMA does not participate in
+ * CPU locking protocols. This mode only guarantees that each buffer index
+ * maps to the corresponding ADC rank latest value; it does not guarantee
+ * that the copied frame is a single hardware scan-cycle snapshot. Use FIFO
+ * mode when ordered and frame-accurate samples are required.
+ */
+static rt_ssize_t rt_adc_stream_read_latest(rt_adc_device_t device, rt_uint32_t *buffer,
+ rt_size_t sample_count, rt_int32_t timeout_ms)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_err_t result;
+
+ RT_UNUSED(timeout_ms);
+
+ ctrl = &device->stream_ctrl;
+ if (sample_count < ctrl->frame_length)
+ {
+ LOG_E("latest read buffer too small: device=%s samples=%u frame=%u",
+ device->parent.parent.name, (unsigned int)sample_count, (unsigned int)ctrl->frame_length);
+ return -RT_EINVAL;
+ }
+
+ result = (rt_err_t)rt_atomic_load(&ctrl->last_error);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ if ((device->ops != RT_NULL) && (device->ops->stream != RT_NULL) && (device->ops->stream->sync != RT_NULL))
+ {
+ result = device->ops->stream->sync(device, ctrl->dma_buffer, ctrl->frame_length);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+
+ result = (rt_err_t)rt_atomic_load(&ctrl->last_error);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ rt_memcpy(buffer, ctrl->dma_buffer, ctrl->frame_length * sizeof(rt_uint32_t));
+ return (rt_ssize_t)ctrl->frame_length;
+}
+#endif /* RT_ADC_STREAM_USING_LATEST */
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Read ordered ADC samples from the framework FIFO.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Number of samples to read before returning unless timeout or error occurs.
+ * @param timeout_ms Total read timeout in milliseconds; negative means wait forever.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ * @note In blocking mode, this function waits until the requested sample count is read or timeout occurs.
+ * On timeout, the partial sample count is returned if any samples were read.
+ */
+static rt_ssize_t rt_adc_stream_read_fifo(rt_adc_device_t device, rt_uint32_t *buffer, rt_size_t sample_count, rt_int32_t timeout_ms)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_size_t read_count;
+ rt_tick_t timeout_tick;
+ rt_tick_t begin_tick;
+
+ ctrl = &device->stream_ctrl;
+ read_count = 0U;
+ timeout_tick = (timeout_ms < 0) ? RT_WAITING_FOREVER : rt_tick_from_millisecond(timeout_ms);
+ begin_tick = rt_tick_get();
+
+ while (read_count < sample_count)
+ {
+ enum rt_adc_state state;
+ rt_base_t level;
+ rt_size_t got;
+ rt_err_t result;
+ rt_tick_t wait_tick;
+
+ state = adc_get_state(device);
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : ((state == RT_ADC_STATE_IDLE) ? -RT_EINVAL : -RT_EBUSY);
+ }
+
+ level = rt_spin_lock_irqsave(&device->spinlock);
+ got = rt_ringbuffer_get(&ctrl->fifo, (rt_uint8_t *)&buffer[read_count],
+ (rt_uint32_t)((sample_count - read_count) * sizeof(rt_uint32_t)));
+ rt_spin_unlock_irqrestore(&device->spinlock, level);
+
+ read_count += got / sizeof(rt_uint32_t);
+ if (read_count >= sample_count)
+ {
+ return (rt_ssize_t)read_count;
+ }
+
+ result = (rt_err_t)rt_atomic_load(&ctrl->last_error);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ if (timeout_ms == RT_WAITING_NO)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : -RT_ETIMEOUT;
+ }
+
+ if (timeout_ms < 0)
+ {
+ wait_tick = RT_WAITING_FOREVER;
+ }
+ else
+ {
+ rt_tick_t delta_tick;
+
+ delta_tick = rt_tick_get_delta(begin_tick);
+ if (delta_tick >= timeout_tick)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : -RT_ETIMEOUT;
+ }
+
+ wait_tick = timeout_tick - delta_tick;
+ }
+
+ if (rt_completion_wait(&ctrl->rx_cpt, wait_tick) != RT_EOK)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : -RT_ETIMEOUT;
+ }
+ }
+
+ return (rt_ssize_t)read_count;
+}
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Start one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the stream configuration object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_start(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg)
+{
+ rt_size_t frame_length;
+ rt_err_t result;
+ rt_bool_t start_called;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL) || (channels == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->stream == RT_NULL) ||
+ (device->ops->stream->start == RT_NULL) || (device->ops->stream->stop == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ start_called = RT_FALSE;
+#if defined(RT_ADC_USING_TRIGGER)
+ rt_bool_t trigger_started = RT_FALSE;
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ frame_length = rt_adc_channel_mask_count(channels);
+ result = adc_cache_session_ctrl(device, channels);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+ result = adc_stream_ctrl_configure(device, cfg, frame_length);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ result = adc_trigger_preconfig(device);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ start_called = RT_TRUE;
+ result = device->ops->stream->start(device, channels, cfg);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+ result = (rt_err_t)rt_atomic_load(&device->stream_ctrl.last_error);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ result = adc_trigger_source_control(device, ADC_TRIGGER_SOURCE_START, &trigger_started, RT_EOK);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ adc_set_state(device, RT_ADC_STATE_STREAM);
+ return RT_EOK;
+
+fail_after_lock:
+ if(result != RT_EOK)
+ {
+ LOG_E("stream start failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+#if defined(RT_ADC_USING_TRIGGER)
+ result = adc_trigger_source_control(device, ADC_TRIGGER_SOURCE_STOP, &trigger_started, result);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ if (start_called == RT_TRUE)
+ {
+ rt_err_t stop_result;
+ rt_bool_t hardware_stopped;
+
+ hardware_stopped = RT_FALSE;
+ stop_result = device->ops->stream->stop(device, &hardware_stopped);
+ if ((stop_result != RT_EOK) && (hardware_stopped != RT_TRUE))
+ {
+ adc_set_state(device, RT_ADC_STATE_STREAM);
+ return stop_result;
+ }
+ }
+
+ adc_stream_ctrl_reset(&device->stream_ctrl);
+ adc_clear_session_ctrl(device);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+}
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Start a FIFO ADC stream with strict frame-aligned buffer layout.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the stream configuration object.
+ * @return Operation status.
+ *
+ * @note This helper is intended for continuous FIFO sampling where DMA
+ * data-event blocks must not split ADC scan frames. It validates that
+ * each DMA event block contains complete frames and that the FIFO can
+ * accept at least one complete event block.
+ * @note Passing this validation does not prove that samples can never be lost.
+ * The reader must still drain the FIFO fast enough for the selected
+ * sample rate and scheduler latency. Runtime overflow is still reported
+ * through the stream error state.
+ */
+rt_err_t rt_adc_stream_start_frame_aligned_fifo(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg)
+{
+ rt_size_t frame_length;
+ rt_size_t block_length;
+ enum rt_adc_stream_dma_event_mode dma_event_mode;
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL) || (channels == 0U) ||
+ (cfg->policy != RT_ADC_STREAM_POLICY_FIFO) ||
+ (cfg->dma_buffer == RT_NULL) || (cfg->dma_buffer_length == 0U) ||
+ (cfg->fifo_buffer == RT_NULL) || (cfg->fifo_buffer_length == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ frame_length = rt_adc_channel_mask_count(channels);
+ if (frame_length == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ result = adc_stream_resolve_dma_event_mode(cfg->policy, cfg->dma_event_mode, &dma_event_mode);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ block_length = (dma_event_mode == RT_ADC_STREAM_DMA_EVENT_FULL_ONLY) ? cfg->dma_buffer_length : (cfg->dma_buffer_length / 2U);
+ if ((cfg->dma_buffer_length < frame_length) || /* DMA buffer must contain at least one complete scan frame. */
+ ((cfg->dma_buffer_length % frame_length) != 0U) || /* DMA buffer length must be aligned to complete scan frames. */
+ (block_length == 0U) || /* The selected DMA event mode must produce a non-empty staging block. */
+ ((block_length % frame_length) != 0U) || /* Each staging block copied to FIFO must contain complete scan frames only. */
+ (cfg->fifo_buffer_length < block_length) || /* FIFO must be able to store at least one completed DMA staging block. */
+ ((cfg->fifo_buffer_length % frame_length) != 0U)) /* FIFO capacity must also be aligned to complete scan frames. */
+ {
+ LOG_E("frame-aligned fifo stream invalid layout: device=%s dma=%u block=%u frame=%u fifo=%u",
+ device->parent.parent.name, (unsigned int)cfg->dma_buffer_length,
+ (unsigned int)block_length, (unsigned int)frame_length,
+ (unsigned int)cfg->fifo_buffer_length);
+ return -RT_EINVAL;
+ }
+
+ if ((dma_event_mode == RT_ADC_STREAM_DMA_EVENT_HALF_FULL) && ((cfg->dma_buffer_length % 2U) != 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ return rt_adc_stream_start(device, channels, cfg);
+}
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Read converted samples from an active ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Number of samples to read.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ */
+rt_ssize_t rt_adc_stream_read(rt_adc_device_t device, rt_uint32_t *buffer, rt_size_t sample_count, rt_int32_t timeout_ms)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_ssize_t read_count;
+
+ if ((device == RT_NULL) || (buffer == RT_NULL) || (sample_count == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_get_state(device) != RT_ADC_STATE_STREAM)
+ {
+ return -RT_EINVAL;
+ }
+
+ ctrl = &device->stream_ctrl;
+ switch (ctrl->policy)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ case RT_ADC_STREAM_POLICY_LATEST:
+ read_count = rt_adc_stream_read_latest(device, buffer, sample_count, timeout_ms);
+ break;
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ case RT_ADC_STREAM_POLICY_FIFO:
+ read_count = rt_adc_stream_read_fifo(device, buffer, sample_count, timeout_ms);
+ break;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ default:
+ read_count = -RT_EINVAL;
+ break;
+ }
+
+ return read_count;
+}
+
+/**
+ * @brief Cancel a blocked ADC stream reader before stream shutdown.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_cancel(rt_adc_device_t device)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_atomic_t old_error;
+ enum rt_adc_state state;
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ state = adc_get_state(device);
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return (state == RT_ADC_STATE_IDLE) ? -RT_EINVAL : -RT_EBUSY;
+ }
+
+ ctrl = &device->stream_ctrl;
+ old_error = (rt_atomic_t)RT_EOK;
+ (void)rt_atomic_compare_exchange_strong(&ctrl->last_error, &old_error, (rt_atomic_t)-RT_EINTR);
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ adc_stream_fifo_wakeup(ctrl);
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ return RT_EOK;
+}
+
+/**
+ * @brief Stop one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_stop(rt_adc_device_t device)
+{
+ rt_bool_t hardware_stopped;
+ rt_err_t result;
+ rt_err_t trigger_result;
+ enum rt_adc_state state;
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->stream == RT_NULL) || (device->ops->stream->stop == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ state = adc_get_state(device);
+ if (state == RT_ADC_STATE_IDLE)
+ {
+ return RT_EOK;
+ }
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return -RT_EBUSY;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_STREAM, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ rt_bool_t trigger_started = RT_TRUE;
+ trigger_result = adc_trigger_source_control(device, ADC_TRIGGER_SOURCE_STOP, &trigger_started, RT_EOK);
+#else
+ trigger_result = RT_EOK;
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ hardware_stopped = RT_FALSE;
+ result = device->ops->stream->stop(device, &hardware_stopped);
+ if ((result == RT_EOK) && (trigger_result != RT_EOK))
+ {
+ result = trigger_result;
+ }
+
+ if ((result == RT_EOK) || (hardware_stopped == RT_TRUE))
+ {
+#ifdef RT_ADC_STREAM_USING_FIFO
+ rt_atomic_store(&device->stream_ctrl.last_error, (rt_atomic_t)((result == RT_EOK) ? -RT_EINVAL : result));
+ adc_stream_fifo_wakeup(&device->stream_ctrl);
+ device->stream_ctrl.fifo_enabled = RT_FALSE;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ adc_stream_ctrl_reset(&device->stream_ctrl);
+ adc_clear_session_ctrl(device);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ }
+ else
+ {
+ adc_set_state(device, RT_ADC_STATE_STREAM);
+ }
+
+ return result;
+}
+
+/**
+ * @brief ADC stream ISR event handler used by low-level drivers.
+ * @param device Pointer to the ADC device object.
+ * @param event Stream event.
+ * @param sample_buffer Pointer to the completed DMA sample block.
+ * @param sample_count Completed sample count.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_stream_isr(rt_adc_device_t device, enum rt_adc_stream_event event,
+ const rt_uint32_t *sample_buffer, rt_size_t sample_count)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ enum rt_adc_state state;
+#ifdef RT_ADC_STREAM_USING_FIFO
+ const rt_uint8_t *src;
+ rt_size_t bytes;
+ rt_size_t put_len;
+ rt_size_t data_len;
+ rt_base_t level;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ ctrl = &device->stream_ctrl;
+ if (event == RT_ADC_STREAM_EVENT_ERROR)
+ {
+ rt_atomic_t old_error;
+
+ old_error = (rt_atomic_t)RT_EOK;
+ if (rt_atomic_compare_exchange_strong(&ctrl->last_error, &old_error, (rt_atomic_t)-RT_ERROR) == RT_TRUE)
+ {
+ LOG_W("stream error: device=%s event=%d", device->parent.parent.name, event);
+ }
+#ifdef RT_ADC_STREAM_USING_FIFO
+ adc_stream_fifo_wakeup(ctrl);
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ return RT_EOK;
+ }
+
+ state = adc_get_state(device);
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if ((state != RT_ADC_STATE_STREAM) && !((state == RT_ADC_STATE_LOCKED) && (ctrl->fifo_enabled == RT_TRUE)))
+ {
+ return RT_EOK;
+ }
+#else
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ if ((event != RT_ADC_STREAM_EVENT_DMA_HALF) && (event != RT_ADC_STREAM_EVENT_DMA_DONE))
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((rt_err_t)rt_atomic_load(&ctrl->last_error) != RT_EOK)
+ {
+ return RT_EOK;
+ }
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if ((ctrl->policy != RT_ADC_STREAM_POLICY_FIFO) || (ctrl->fifo_enabled != RT_TRUE))
+ {
+ return RT_EOK;
+ }
+
+ if ((sample_buffer == RT_NULL) || (sample_count == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ src = (const rt_uint8_t *)sample_buffer;
+ bytes = sample_count * sizeof(rt_uint32_t);
+
+ level = rt_spin_lock_irqsave(&device->spinlock);
+ put_len = rt_ringbuffer_put(&ctrl->fifo, src, (rt_uint32_t)bytes);
+ data_len = rt_ringbuffer_data_len(&ctrl->fifo);
+ rt_spin_unlock_irqrestore(&device->spinlock, level);
+
+ if (put_len < bytes)
+ {
+ rt_atomic_t old_error;
+ ctrl->overflow_count++;
+ old_error = (rt_atomic_t)RT_EOK;
+ if (rt_atomic_compare_exchange_strong(&ctrl->last_error, &old_error, (rt_atomic_t)-RT_EFULL) == RT_TRUE)
+ {
+ LOG_W("stream fifo overflow: device=%s put=%u expected=%u data_len=%u",
+ device->parent.parent.name, (unsigned int)put_len, (unsigned int)bytes, (unsigned int)data_len);
+ }
+ adc_stream_fifo_wakeup(ctrl);
+ }
+
+ if ((put_len > 0U) && ((ctrl->watermark == 0U) || (data_len >= (ctrl->watermark * sizeof(rt_uint32_t)))))
+ {
+ adc_stream_fifo_wakeup(ctrl);
+
+ if (ctrl->callback != RT_NULL)
+ {
+ ctrl->callback(device, put_len / sizeof(rt_uint32_t), ctrl->user_data);
+ }
+ }
+
+ return RT_EOK;
+#else
+ RT_UNUSED(sample_buffer);
+ RT_UNUSED(sample_count);
+ return RT_EOK;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+}
+#endif /* RT_ADC_USING_STREAM */
+
+/**
+ * @brief Convert one ADC raw sample to millivolts.
+ * @param raw_value Raw ADC sample code.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param voltage_mv Pointer to the output voltage in millivolts.
+ * @return Operation status.
+ *
+ * ADC codes are treated as an inclusive range from 0 to
+ * ``(1 << resolution_bits) - 1``. The calculation uses a 64-bit
+ * intermediate value and rounds to the nearest integer millivolt.
+ */
+rt_err_t rt_adc_raw_to_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv,
+ rt_uint8_t resolution_bits, rt_uint32_t *voltage_mv)
+{
+ rt_uint64_t full_scale_code;
+
+ if ((voltage_mv == RT_NULL) || (vref_mv == 0U) || (resolution_bits == 0U) || (resolution_bits > 32U))
+ {
+ return -RT_EINVAL;
+ }
+
+ full_scale_code = ((rt_uint64_t)1 << resolution_bits) - 1ULL;
+ if ((rt_uint64_t)raw_value > full_scale_code)
+ {
+ return -RT_EINVAL;
+ }
+
+ /*
+ * Convert ADC code to millivolts:
+ *
+ * voltage_mv = raw * vref_mv / full_scale_code
+ * full_scale_code = (1 << resolution_bits) - 1
+ *
+ * ADC codes are treated as an inclusive range from 0 to full scale.
+ * The multiplication is promoted to 64-bit first, then half of the
+ * denominator is added to round to the nearest integer millivolt instead
+ * of truncating non-exact divisions toward zero.
+ */
+ *voltage_mv = (rt_uint32_t)(((rt_uint64_t)raw_value * (rt_uint64_t)vref_mv + (full_scale_code / 2ULL)) / full_scale_code);
+
+ return RT_EOK;
+}
+
+/**
+ * @brief Convert one ADC raw sample to a scaled voltage in millivolts.
+ * @param raw_value Raw ADC sample code.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param scale_num Voltage scaling numerator.
+ * @param scale_den Voltage scaling denominator.
+ * @param voltage_mv Pointer to the output scaled voltage in millivolts.
+ * @return Operation status.
+ *
+ * The scaled voltage is calculated as:
+ *
+ * raw_to_voltage_mv(raw_value) * scale_num / scale_den
+ *
+ * @note @p scale_num and @p scale_den may describe any positive rational
+ * scaling ratio that does not overflow the internal calculation. The
+ * final scaled millivolt value must fit in rt_uint32_t.
+ *
+ * @retval RT_EOK The conversion completed successfully.
+ * @retval -RT_EINVAL A pointer is null, a scale argument is zero, the ADC
+ * conversion arguments are invalid, or the scaled result overflows
+ * rt_uint32_t.
+ */
+rt_err_t rt_adc_raw_to_scaled_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv,
+ rt_uint8_t resolution_bits, rt_uint32_t scale_num,
+ rt_uint32_t scale_den, rt_uint32_t *voltage_mv)
+{
+ rt_uint32_t input_mv;
+ rt_uint64_t numerator;
+ rt_uint64_t rounding;
+ rt_uint64_t scaled_mv;
+ rt_err_t result;
+
+ if ((voltage_mv == RT_NULL) || (scale_num == 0U) || (scale_den == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_raw_to_voltage_mv(raw_value, vref_mv, resolution_bits, &input_mv);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ numerator = (rt_uint64_t)input_mv * (rt_uint64_t)scale_num;
+ rounding = (rt_uint64_t)scale_den / 2ULL;
+
+ if (numerator > (((rt_uint64_t)~0ULL) - rounding))
+ {
+ return -RT_EINVAL;
+ }
+
+ scaled_mv = (numerator + rounding) / (rt_uint64_t)scale_den;
+ if (scaled_mv > RT_UINT32_MAX)
+ {
+ return -RT_EINVAL;
+ }
+
+ *voltage_mv = (rt_uint32_t)scaled_mv;
+ return RT_EOK;
+}
+
+/**
+ * @brief Read voltage values from the configured ADC session.
+ * @param device Pointer to the ADC device object.
+ * @param voltages_mv Pointer to the output voltage buffer in millivolts.
+ * @param size Output voltage buffer length in samples.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Operation status.
+ *
+ * @note @p size must match the configured session channel count. The output
+ * order follows the ascending bit order of the configured channel mask.
+ * The configured default_vref_mv is preferred for voltage conversion. If
+ * default_vref_mv is not configured, the backend VREF channel is used for
+ * VDDA calculation only when it is present in the configured session.
+ */
+rt_err_t rt_adc_voltage(rt_adc_device_t device, rt_uint32_t *voltages_mv, rt_size_t size, rt_int32_t timeout_ms)
+{
+ rt_uint32_t vref_mv;
+ rt_uint32_t vref_value;
+ rt_uint8_t resolution_bits;
+ rt_uint8_t vref_channel;
+ rt_size_t vref_index;
+ rt_size_t index;
+ struct rt_adc_sequence_cfg sequence_cfg;
+ rt_size_t read_count;
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (voltages_mv == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) || (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ LOG_D("voltage request: device=%s count=%u timeout=%ld", device->parent.parent.name, (unsigned int)size, (long)timeout_ms);
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U) || (size != device->session_ctrl.channel_count))
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+
+ rt_memset(&sequence_cfg, 0, sizeof(sequence_cfg));
+ sequence_cfg.buffer = voltages_mv;
+ sequence_cfg.buffer_length = size;
+ sequence_cfg.timeout_ms = timeout_ms;
+ result = adc_sequence_transfer_locked(device, device->session_ctrl.channels,
+ &sequence_cfg, size, &read_count);
+ if ((result == RT_EOK) && (read_count != size))
+ {
+ result = -RT_ERROR;
+ }
+ if (result != RT_EOK)
+ {
+ LOG_E("voltage sample read failed: device=%s result=%d", device->parent.parent.name, result);
+ goto out;
+ }
+
+ result = device->ops->core->control(device, RT_ADC_CMD_GET_RESOLUTION, &resolution_bits);
+ if (result != RT_EOK)
+ {
+ goto out;
+ }
+
+ vref_mv = 0U;
+ if (device->default_vref_mv != 0U)
+ {
+ vref_mv = device->default_vref_mv;
+ result = RT_EOK;
+ }
+ else
+ {
+ vref_channel = 0U;
+ result = device->ops->core->control(device, RT_ADC_CMD_GET_VREF_CHANNEL, &vref_channel);
+ if ((result == RT_EOK) && (vref_channel < 32U) && ((device->session_ctrl.channels & RT_ADC_CHANNEL_MASK(vref_channel)) != 0U))
+ {
+ result = rt_adc_session_channel_index(device->session_ctrl.channels, RT_ADC_CHANNEL_MASK(vref_channel), &vref_index);
+ if (result != RT_EOK)
+ {
+ goto out;
+ }
+
+ vref_value = voltages_mv[vref_index];
+ result = device->ops->core->control(device, RT_ADC_CMD_CALC_VREF, &vref_value);
+ if (result != RT_EOK)
+ {
+ LOG_E("vref calculate failed: device=%s raw=%lu result=%d",
+ device->parent.parent.name, (unsigned long)voltages_mv[vref_index], result);
+ goto out;
+ }
+
+ if (vref_value == 0U)
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+
+ vref_mv = vref_value;
+ }
+ else
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+ }
+
+ for (index = 0U; index < size; index++)
+ {
+ result = rt_adc_raw_to_voltage_mv(voltages_mv[index], vref_mv, resolution_bits, &voltages_mv[index]);
+ if (result != RT_EOK)
+ {
+ goto out;
+ }
+ }
+
+ LOG_D("voltage done: device=%s count=%u vref=%lu resolution=%u",
+ device->parent.parent.name, (unsigned int)size,
+ (unsigned long)vref_mv, (unsigned int)resolution_bits);
+ result = RT_EOK;
+
+out:
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+}
+
+#endif /* defined(RT_USING_ADC_V2) */
diff --git a/components/drivers/adc/adc_v2_internal.h b/components/drivers/adc/adc_v2_internal.h
new file mode 100644
index 0000000000..1f97f17dd8
--- /dev/null
+++ b/components/drivers/adc/adc_v2_internal.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file adc_v2_internal.h
+ * @brief Internal ADC V2 framework declarations.
+ */
+
+#ifndef __ADC_V2_INTERNAL_H__
+#define __ADC_V2_INTERNAL_H__
+
+#include <rtconfig.h>
+#include <drivers/adc_v2.h>
+
+#if defined(RT_ADC_USING_TRIGGER)
+/**
+ * @brief Initialize the cached ADC trigger state to the default software-started mode.
+ * @param device Pointer to the ADC device object.
+ */
+void adc_trigger_init(rt_adc_device_t device);
+
+/**
+ * @brief Preconfigure the active device-level ADC trigger before hardware configuration.
+ * @param device Pointer to the ADC device object.
+ * @return RT_EOK on success, otherwise an RT-Thread error code.
+ */
+rt_err_t adc_trigger_preconfig(rt_adc_device_t device);
+
+/**
+ * @brief Get the active device-level ADC trigger configuration.
+ * @param device Pointer to the ADC device object.
+ * @return Pointer to the cached trigger configuration, or RT_NULL when no hardware trigger is configured.
+ */
+const struct rt_adc_trigger_cfg *adc_trigger_active_get(rt_adc_device_t device);
+
+/**
+ * @brief Trigger-source lifecycle actions used after ADC hardware is armed.
+ */
+enum adc_trigger_source_action
+{
+ ADC_TRIGGER_SOURCE_START = 0, /**< Start the active trigger source. */
+ ADC_TRIGGER_SOURCE_STOP /**< Stop the previously started trigger source. */
+};
+
+/**
+ * @brief Control the active trigger source around an armed ADC conversion path.
+ * @param device Pointer to the ADC device object.
+ * @param action Trigger-source lifecycle action.
+ * @param started Pointer to the trigger-start state.
+ * @param current_result Transfer result before trigger stop, ignored for start.
+ * @return For start, trigger-source start result; for stop, @p current_result unless trigger stop is the first
+ * observed failure.
+ */
+rt_err_t adc_trigger_source_control(rt_adc_device_t device, enum adc_trigger_source_action action,
+ rt_bool_t *started, rt_err_t current_result);
+
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#endif /* __ADC_V2_INTERNAL_H__ */
diff --git a/components/drivers/adc/adc_v2_msh.c b/components/drivers/adc/adc_v2_msh.c
new file mode 100644
index 0000000000..7bc42f6cbf
--- /dev/null
+++ b/components/drivers/adc/adc_v2_msh.c
@@ -0,0 +1,1225 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-09 wdfk-prog split adc v2 msh command implementation
+ */
+
+/**
+ * @file adc_v2_msh.c
+ * @brief ADC V2 FinSH/MSH command implementation.
+ */
+
+#include <rtconfig.h>
+
+#if defined(RT_USING_ADC_V2) && defined(RT_USING_FINSH) && defined(RT_ADC_V2_USING_MSH)
+
+#include <stdlib.h>
+
+#include <rtdevice.h>
+#include <rtthread.h>
+#include <finsh.h>
+
+#include <drivers/adc_v2.h>
+
+/** @brief Default FinSH temporary read buffer capacity in samples. */
+#define RT_ADC_MSH_VALUE_BUFFER_SIZE 32U
+
+/** @brief Default timeout in milliseconds for ADC MSH sequence read command. */
+#define RT_ADC_MSH_SEQ_DEFAULT_TIMEOUT_MS 1000
+
+/** @brief Default FinSH stream read timeout in milliseconds. */
+#define RT_ADC_MSH_STREAM_READ_TIMEOUT_MS 1000
+
+/** @brief Default FinSH stream DMA buffer capacity in samples. */
+#define RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE 32U
+
+/** @brief Default FIFO storage capacity in samples for the ADC MSH FIFO stream test. */
+#define RT_ADC_MSH_FIFO_TEST_FIFO_BUFFER_SIZE 512U
+
+/**
+ * @brief Print backend-specific ADC FinSH command usage.
+ */
+rt_weak void rt_adc_msh_special_help(void)
+{
+}
+
+/**
+ * @brief Handle backend-specific ADC FinSH commands.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding the top-level adc command.
+ * @param argv Argument vector excluding the top-level adc command.
+ * @return Operation status.
+ */
+rt_weak rt_err_t rt_adc_msh_special(rt_adc_device_t device, int argc, char **argv)
+{
+ RT_UNUSED(device);
+ RT_UNUSED(argc);
+ RT_UNUSED(argv);
+
+ return -RT_ENOSYS;
+}
+
+#if defined(RT_ADC_USING_TRIGGER)
+/**
+ * @brief Print ADC trigger MSH command usage.
+ */
+static void adc_msh_usage_trigger(void)
+{
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ rt_kprintf(" adc trigger set timer_update <timer_device> <freq_hz>\n");
+ rt_kprintf(" adc trigger set timer_compare <timer_device> <timer_ch> <freq_hz>\n");
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+ rt_kprintf(" adc trigger set compare [comparator_device] <num> <rising|falling|both>, num: 0~31\n");
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+ rt_kprintf(" adc trigger clear\n");
+ rt_kprintf(" adc trigger status\n");
+}
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#if defined(RT_ADC_USING_STREAM)
+/**
+ * @brief Print ADC stream MSH command usage.
+ */
+static void adc_msh_usage_stream(void)
+{
+ rt_kprintf(" adc stream start <num0> [num1 ...], num: 0~31\n");
+ rt_kprintf(" adc stream read [timeout_ms]\n");
+ rt_kprintf(" adc stream cancel\n");
+ rt_kprintf(" adc stream stop\n");
+#if defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+ rt_kprintf(" adc fifo <timeout_ms> <num0> [num1 ...]\n");
+#endif /* defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+}
+#endif /* defined(RT_ADC_USING_STREAM) */
+
+/**
+ * @brief Print ADC FinSH command usage.
+ */
+static void adc_msh_usage(void)
+{
+ rt_kprintf("Usage:\n");
+ rt_kprintf(" adc probe <device>\n");
+ rt_kprintf(" adc close\n");
+ rt_kprintf(" adc config <num0> [num1 ...], num: 0~31, eg: 0 1 10\n");
+ rt_kprintf(" adc read\n");
+ rt_kprintf(" adc voltage <timeout_ms>\n");
+ rt_kprintf(" voltage reads the current session configured by adc config\n");
+ rt_kprintf(" if default_vref is absent, adc config must include the backend VREF channel\n");
+ rt_kprintf(" adc seq <timeout_ms> <num0> [num1 ...], num: 0~31, eg: 0 1 10\n");
+#if defined(RT_ADC_USING_TRIGGER)
+ adc_msh_usage_trigger();
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#if defined(RT_ADC_USING_STREAM)
+ adc_msh_usage_stream();
+#endif /* defined(RT_ADC_USING_STREAM) */
+
+ rt_adc_msh_special_help();
+}
+
+/**
+ * @brief Convert one shell argument to a single-channel mask.
+ * @param arg Pointer to the shell argument string.
+ * @param channels Pointer to the output channel mask.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_parse_channel_mask(const char *arg, rt_uint32_t *channels)
+{
+ char *endptr;
+ unsigned long value;
+
+ if ((*arg == '\0') || (*arg == '-'))
+ {
+ return -RT_EINVAL;
+ }
+
+ endptr = RT_NULL;
+ value = strtoul(arg, &endptr, 0);
+ if ((endptr == arg) || (endptr == RT_NULL) || (*endptr != '\0') || (value >= 32UL))
+ {
+ return -RT_EINVAL;
+ }
+
+ *channels = RT_ADC_CHANNEL_MASK(value);
+ return RT_EOK;
+}
+
+/**
+ * @brief Convert one shell argument to a timeout value.
+ * @param arg Pointer to the shell argument string.
+ * @return Timeout in milliseconds; negative means wait forever.
+ *
+ * @note This command keeps the permissive MSH parsing convention: strtol()
+ * is used without endptr validation, so a non-numeric string with no
+ * converted digits is treated as 0 by the C library conversion path. A
+ * timeout value of 0 means non-blocking/immediate timeout.
+ */
+static rt_int32_t adc_msh_parse_timeout(const char *arg)
+{
+ return (rt_int32_t)strtol(arg, RT_NULL, 0);
+}
+
+#if defined(RT_ADC_USING_TRIGGER) && (defined(RT_ADC_TRIGGER_USING_TIMER) || defined(RT_ADC_TRIGGER_USING_COMPARE))
+/**
+ * @brief Convert one shell argument to an unsigned integer.
+ * @param arg Pointer to the shell argument string.
+ * @param max_value Maximum accepted value.
+ * @param value Pointer to the output value.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_parse_ulong(const char *arg, unsigned long max_value, unsigned long *value)
+{
+ char *endptr;
+ unsigned long parsed;
+
+ if ((arg == RT_NULL) || (value == RT_NULL) || (*arg == '\0') || (*arg == '-'))
+ {
+ return -RT_EINVAL;
+ }
+
+ endptr = RT_NULL;
+ parsed = strtoul(arg, &endptr, 0);
+ if ((endptr == arg) || (endptr == RT_NULL) || (*endptr != '\0') || (parsed > max_value))
+ {
+ return -RT_EINVAL;
+ }
+
+ *value = parsed;
+ return RT_EOK;
+}
+
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+/**
+ * @brief Convert one shell argument to an ADC trigger edge selector.
+ * @param arg Pointer to the shell argument string.
+ * @param edge Pointer to the output trigger edge selector.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_parse_trigger_edge(const char *arg, enum rt_adc_trigger_edge *edge)
+{
+ if ((arg == RT_NULL) || (edge == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(arg, "rising"))
+ {
+ *edge = RT_ADC_TRIGGER_EDGE_RISING;
+ return RT_EOK;
+ }
+
+ if (!rt_strcmp(arg, "falling"))
+ {
+ *edge = RT_ADC_TRIGGER_EDGE_FALLING;
+ return RT_EOK;
+ }
+
+ if (!rt_strcmp(arg, "both"))
+ {
+ *edge = RT_ADC_TRIGGER_EDGE_BOTH;
+ return RT_EOK;
+ }
+
+ return -RT_EINVAL;
+}
+
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+
+/**
+ * @brief Find one trigger source device by shell argument.
+ * @param name Pointer to the source device name string.
+ * @param source Pointer to the output source device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_find_trigger_source(const char *name, rt_device_t *source)
+{
+ rt_device_t dev;
+
+ if ((name == RT_NULL) || (source == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ dev = rt_device_find(name);
+ if (dev == RT_NULL)
+ {
+ rt_kprintf("trigger source %s not found\n", name);
+ return -RT_ERROR;
+ }
+
+ *source = dev;
+ return RT_EOK;
+}
+#endif /* defined(RT_ADC_USING_TRIGGER) && (defined(RT_ADC_TRIGGER_USING_TIMER) || defined(RT_ADC_TRIGGER_USING_COMPARE)) */
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Clear the FinSH stream scratch buffer owner.
+ * @param stream_device Pointer to the saved stream device owner.
+ */
+static void adc_msh_clear_stream_owner(rt_adc_device_t *stream_device)
+{
+ if (stream_device != RT_NULL)
+ {
+ *stream_device = RT_NULL;
+ }
+}
+
+/**
+ * @brief Get the current ADC framework state for the FinSH command.
+ * @param device Pointer to the ADC device object.
+ * @return Current ADC runtime state.
+ */
+static enum rt_adc_state adc_msh_get_state(rt_adc_device_t device)
+{
+ return (enum rt_adc_state)rt_atomic_load(&device->state);
+}
+
+/**
+ * @brief Check whether the FinSH stream scratch buffer is still owned by an active stream.
+ * @param stream_device Saved stream device owner.
+ * @return RT_TRUE if the stream buffer is still owned by an active stream.
+ */
+static rt_bool_t adc_msh_stream_buffer_is_active(rt_adc_device_t stream_device)
+{
+ if (stream_device == RT_NULL)
+ {
+ return RT_FALSE;
+ }
+
+ return (adc_msh_get_state(stream_device) == RT_ADC_STATE_STREAM) ? RT_TRUE : RT_FALSE;
+}
+#endif /* RT_ADC_USING_STREAM */
+
+static rt_adc_device_t device = RT_NULL;
+#ifdef RT_ADC_USING_STREAM
+static rt_adc_device_t stream_device = RT_NULL;
+static rt_uint32_t stream_dma_buffer[RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE];
+#if defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+static rt_uint32_t stream_fifo_buffer[RT_ADC_MSH_FIFO_TEST_FIFO_BUFFER_SIZE];
+#endif /* defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+#endif /* RT_ADC_USING_STREAM */
+
+
+#if defined(RT_ADC_USING_TRIGGER)
+/**
+ * @brief Handle ADC trigger subcommands.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding "adc trigger".
+ * @param argv Argument vector excluding "adc trigger".
+ * @return Command status.
+ */
+static rt_err_t adc_msh_trigger(rt_adc_device_t device, int argc, char **argv)
+{
+ rt_err_t result;
+
+ if (argc < 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(argv[0], "clear"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_trigger_clear(device);
+ if (result == RT_EOK)
+ {
+ rt_kprintf("adc trigger cleared\n");
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[0], "status"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ rt_kprintf("adc trigger: %s\n", rt_adc_trigger_is_set(device) == RT_TRUE ? "set" : "clear");
+ return RT_EOK;
+ }
+
+ if (!rt_strcmp(argv[0], "set"))
+ {
+ struct rt_adc_trigger_cfg trigger_cfg;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ rt_memset(&trigger_cfg, 0, sizeof(trigger_cfg));
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ if (!rt_strcmp(argv[1], "timer_update"))
+ {
+ unsigned long freq_hz;
+
+ if (argc != 4)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = adc_msh_find_trigger_source(argv[2], &trigger_cfg.event.timer.timer);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = adc_msh_parse_ulong(argv[3], 0xffffffffUL, &freq_hz);
+ if ((result != RT_EOK) || (freq_hz == 0UL))
+ {
+ rt_kprintf("invalid adc trigger timer frequency: %s\n", argv[3]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ trigger_cfg.type = RT_ADC_TRIGGER_TIMER_UPDATE;
+ trigger_cfg.event.timer.freq_hz = (rt_uint32_t)freq_hz;
+ trigger_cfg.event.timer.channel = 0U;
+ }
+ else if (!rt_strcmp(argv[1], "timer_compare"))
+ {
+ unsigned long channel;
+ unsigned long freq_hz;
+
+ if (argc != 5)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = adc_msh_find_trigger_source(argv[2], &trigger_cfg.event.timer.timer);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = adc_msh_parse_ulong(argv[3], 65535UL, &channel);
+ if ((result != RT_EOK) || (channel == 0UL))
+ {
+ rt_kprintf("invalid adc trigger timer channel: %s\n", argv[3]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ result = adc_msh_parse_ulong(argv[4], 0xffffffffUL, &freq_hz);
+ if ((result != RT_EOK) || (freq_hz == 0UL))
+ {
+ rt_kprintf("invalid adc trigger timer frequency: %s\n", argv[4]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ trigger_cfg.type = RT_ADC_TRIGGER_TIMER_COMPARE;
+ trigger_cfg.event.timer.freq_hz = (rt_uint32_t)freq_hz;
+ trigger_cfg.event.timer.channel = (rt_uint16_t)channel;
+ }
+ else
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+ if (!rt_strcmp(argv[1], "compare"))
+ {
+ unsigned long channel;
+ int channel_arg;
+ int edge_arg;
+
+ if ((argc != 4) && (argc != 5))
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (argc == 5)
+ {
+ result = adc_msh_find_trigger_source(argv[2], &trigger_cfg.event.compare.comparator);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ channel_arg = 3;
+ edge_arg = 4;
+ }
+ else
+ {
+ channel_arg = 2;
+ edge_arg = 3;
+ }
+
+ result = adc_msh_parse_ulong(argv[channel_arg], 65535UL, &channel);
+ if ((result != RT_EOK) || (channel == 0UL))
+ {
+ rt_kprintf("invalid adc trigger compare channel: %s\n", argv[channel_arg]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ result = adc_msh_parse_trigger_edge(argv[edge_arg], &trigger_cfg.event.compare.edge);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc trigger edge: %s\n", argv[edge_arg]);
+ return result;
+ }
+
+ trigger_cfg.type = RT_ADC_TRIGGER_ANALOG_COMPARE;
+ trigger_cfg.event.compare.channel = (rt_uint16_t)channel;
+ }
+ else
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_trigger_set(device, &trigger_cfg);
+ if (result == RT_EOK)
+ {
+ rt_kprintf("adc trigger set: %s\n", argv[1]);
+ }
+ return result;
+ }
+
+ adc_msh_usage();
+ return -RT_EINVAL;
+}
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Handle ADC stream subcommands.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding "adc stream".
+ * @param argv Argument vector excluding "adc stream".
+ * @return Command status.
+ */
+static rt_err_t adc_msh_stream(rt_adc_device_t device, int argc, char **argv)
+{
+ rt_uint32_t values[RT_ADC_MSH_VALUE_BUFFER_SIZE];
+ rt_size_t count;
+ rt_size_t index;
+ rt_err_t result;
+
+ if (argc < 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(argv[0], "start"))
+ {
+#ifndef RT_ADC_STREAM_USING_LATEST
+ return -RT_ENOSYS;
+#else
+ struct rt_adc_stream_cfg stream_cfg;
+ rt_uint32_t channels;
+ rt_uint32_t channel_mask;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (adc_msh_get_state(device) == RT_ADC_STATE_STREAM)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 1); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 1], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 1]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ count = rt_adc_channel_mask_count(channels);
+ if (count == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_msh_stream_buffer_is_active(stream_device) == RT_TRUE)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+ adc_msh_clear_stream_owner(&stream_device);
+
+ if (count > RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE)
+ {
+ rt_kprintf("adc stream buffer too large, max %u samples\n", (unsigned int)RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ rt_memset(&stream_cfg, 0, sizeof(stream_cfg));
+ stream_cfg.policy = RT_ADC_STREAM_POLICY_LATEST;
+ stream_cfg.dma_event_mode = RT_ADC_STREAM_DMA_EVENT_AUTO;
+ stream_cfg.dma_buffer = stream_dma_buffer;
+ stream_cfg.dma_buffer_length = count;
+
+ result = rt_adc_stream_start(device, channels, &stream_cfg);
+ if (result == RT_EOK)
+ {
+ stream_device = device;
+ }
+ else if (adc_msh_get_state(device) == RT_ADC_STATE_STREAM)
+ {
+ stream_device = device;
+ rt_kprintf("adc stream start failed but stream hardware may still be running, stop it first\n");
+ }
+ else
+ {
+ adc_msh_clear_stream_owner(&stream_device);
+ }
+ return result;
+#endif /* RT_ADC_STREAM_USING_LATEST */
+ }
+
+ if (!rt_strcmp(argv[0], "read"))
+ {
+ rt_ssize_t read_count;
+ rt_int32_t timeout_ms;
+
+ if (argc > 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((stream_device != RT_NULL) && (stream_device != device))
+ {
+ rt_kprintf("adc stream belongs to another device\n");
+ return -RT_EBUSY;
+ }
+
+ if (adc_msh_get_state(device) != RT_ADC_STATE_STREAM)
+ {
+ rt_kprintf("adc stream is not running\n");
+ return -RT_EINVAL;
+ }
+
+ count = device->stream_ctrl.frame_length;
+ if (count == 0U)
+ {
+ rt_kprintf("adc stream channel count is zero\n");
+ return -RT_EINVAL;
+ }
+
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc stream read count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ timeout_ms = (argc == 2) ? adc_msh_parse_timeout(argv[1]) : RT_ADC_MSH_STREAM_READ_TIMEOUT_MS;
+ read_count = rt_adc_stream_read(device, values, count, timeout_ms);
+ if (read_count > 0)
+ {
+ for (index = 0U; index < (rt_size_t)read_count; index++)
+ {
+ rt_kprintf("[%u]=%lu\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+ }
+
+ return (read_count >= 0) ? RT_EOK : (rt_err_t)read_count;
+ }
+
+ if (!rt_strcmp(argv[0], "stop"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((stream_device != RT_NULL) && (stream_device != device))
+ {
+ rt_kprintf("adc stream belongs to another device\n");
+ return -RT_EBUSY;
+ }
+
+ result = rt_adc_stream_stop(device);
+ if (result == RT_EOK)
+ {
+ adc_msh_clear_stream_owner(&stream_device);
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[0], "cancel"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((stream_device != RT_NULL) && (stream_device != device))
+ {
+ rt_kprintf("adc stream belongs to another device\n");
+ return -RT_EBUSY;
+ }
+
+ return rt_adc_stream_cancel(device);
+ }
+
+ adc_msh_usage();
+ return -RT_EINVAL;
+}
+#endif /* RT_ADC_USING_STREAM */
+
+#if defined(RT_ADC_USING_STREAM) && defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+#define RT_ADC_MSH_FIFO_TEST_READ_FRAME_COUNT 16U
+#define RT_ADC_MSH_FIFO_TEST_READ_TIMEOUT_MS 1000
+/**
+ * @brief Handle the ADC FIFO stream test command.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding "adc fifo".
+ * @param argv Argument vector excluding "adc fifo".
+ * @return Command status.
+ */
+static rt_err_t adc_msh_fifo_test(rt_adc_device_t device, int argc, char **argv)
+{
+ struct rt_adc_stream_cfg stream_cfg;
+ rt_uint32_t *buffer;
+ rt_uint32_t channels;
+ rt_uint32_t channel_mask;
+ rt_size_t channel_count;
+ rt_size_t sample_count;
+ rt_size_t dma_buffer_length;
+ rt_size_t fifo_buffer_length;
+ rt_size_t read_times;
+ rt_size_t index;
+ rt_size_t full_count;
+ rt_size_t partial_count;
+ rt_size_t timeout_count;
+ rt_size_t error_count;
+ rt_size_t total_samples;
+ rt_tick_t begin_tick;
+ rt_tick_t end_tick;
+ rt_tick_t timeout_tick;
+ rt_int32_t timeout_ms;
+ rt_err_t result;
+ rt_err_t stop_result;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ timeout_ms = adc_msh_parse_timeout(argv[0]);
+ if (timeout_ms <= 0)
+ {
+ rt_kprintf("fifo test timeout_ms must be greater than 0\n");
+ return -RT_EINVAL;
+ }
+
+ if (adc_msh_get_state(device) == RT_ADC_STATE_STREAM)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+
+ if (adc_msh_stream_buffer_is_active(stream_device) == RT_TRUE)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+ adc_msh_clear_stream_owner(&stream_device);
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 1); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 1], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 1]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ channel_count = rt_adc_channel_mask_count(channels);
+ if (channel_count == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE < (channel_count * 2U))
+ {
+ rt_kprintf("adc fifo test dma buffer too small, min %u samples\n", (unsigned int)(channel_count * 2U));
+ return -RT_EINVAL;
+ }
+
+ sample_count = channel_count * RT_ADC_MSH_FIFO_TEST_READ_FRAME_COUNT;
+ if ((sample_count == 0U) || (sample_count > (((rt_size_t)-1) / sizeof(rt_uint32_t))))
+ {
+ rt_kprintf("adc fifo test read sample count is too large\n");
+ return -RT_EINVAL;
+ }
+
+ dma_buffer_length = (RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE / (channel_count * 2U)) * (channel_count * 2U);
+ fifo_buffer_length = (RT_ADC_MSH_FIFO_TEST_FIFO_BUFFER_SIZE / channel_count) * channel_count;
+ if (fifo_buffer_length < (dma_buffer_length / 2U))
+ {
+ rt_kprintf("adc fifo test fifo buffer too small, min %u samples\n", (unsigned int)(dma_buffer_length / 2U));
+ return -RT_EINVAL;
+ }
+
+ buffer = (rt_uint32_t *)rt_malloc(sample_count * sizeof(rt_uint32_t));
+ if (buffer == RT_NULL)
+ {
+ rt_kprintf("no memory for fifo test buffer\n");
+ return -RT_ENOMEM;
+ }
+
+ rt_memset(&stream_cfg, 0, sizeof(stream_cfg));
+ stream_cfg.policy = RT_ADC_STREAM_POLICY_FIFO;
+ stream_cfg.dma_event_mode = RT_ADC_STREAM_DMA_EVENT_HALF_FULL;
+ stream_cfg.dma_buffer = stream_dma_buffer;
+ stream_cfg.dma_buffer_length = dma_buffer_length;
+ stream_cfg.fifo_buffer = stream_fifo_buffer;
+ stream_cfg.fifo_buffer_length = fifo_buffer_length;
+ stream_cfg.watermark = dma_buffer_length / 2U;
+
+ result = rt_adc_stream_start_frame_aligned_fifo(device, channels, &stream_cfg);
+ if (result != RT_EOK)
+ {
+ rt_free(buffer);
+ return result;
+ }
+ stream_device = device;
+
+ result = RT_EOK;
+ read_times = 0U;
+ full_count = 0U;
+ partial_count = 0U;
+ timeout_count = 0U;
+ error_count = 0U;
+ total_samples = 0U;
+ begin_tick = rt_tick_get();
+ timeout_tick = rt_tick_from_millisecond(timeout_ms);
+
+ while (rt_tick_get_delta(begin_tick) < timeout_tick)
+ {
+ rt_ssize_t read_count;
+
+ read_count = rt_adc_stream_read(device, buffer, sample_count, RT_ADC_MSH_FIFO_TEST_READ_TIMEOUT_MS);
+ read_times++;
+ if (read_count < 0)
+ {
+ if (read_count == -RT_ETIMEOUT)
+ {
+ timeout_count++;
+ continue;
+ }
+
+ result = (rt_err_t)read_count;
+ error_count++;
+ rt_kprintf("stream fifo test failed at %u: %d\n", (unsigned int)read_times, (int)read_count);
+ break;
+ }
+
+ total_samples += (rt_size_t)read_count;
+ if ((rt_size_t)read_count == sample_count)
+ {
+ full_count++;
+ }
+ else
+ {
+ partial_count++;
+ }
+ }
+
+ end_tick = rt_tick_get();
+ stop_result = rt_adc_stream_stop(device);
+ if (stop_result == RT_EOK)
+ {
+ adc_msh_clear_stream_owner(&stream_device);
+ }
+
+ rt_size_t elapsed_tick;
+ rt_size_t total_frames;
+ rt_size_t read_frames;
+ rt_size_t dma_frames;
+ rt_size_t fifo_frames;
+ rt_size_t frame_rate;
+ rt_size_t sample_rate_per_channel;
+
+ elapsed_tick = end_tick - begin_tick;
+ total_frames = total_samples / channel_count;
+ read_frames = sample_count / channel_count;
+ dma_frames = dma_buffer_length / channel_count;
+ fifo_frames = fifo_buffer_length / channel_count;
+
+ if (elapsed_tick > 0U)
+ {
+ frame_rate = (total_frames * RT_TICK_PER_SECOND) / elapsed_tick;
+ sample_rate_per_channel = frame_rate;
+ }
+ else
+ {
+ frame_rate = 0U;
+ sample_rate_per_channel = 0U;
+ }
+
+ rt_kprintf("adc fifo_test: %s\n", (error_count == 0U && timeout_count == 0U && partial_count == 0U) ? "PASS" : "FAIL");
+ rt_kprintf(" duration : %d ms\n", (int)timeout_ms);
+ rt_kprintf(" channels : %u\n", (unsigned int)channel_count);
+ rt_kprintf(" read block : %u frames, %u samples\n",
+ (unsigned int)read_frames, (unsigned int)sample_count);
+ rt_kprintf(" dma buffer : %u frames, %u samples\n",
+ (unsigned int)dma_frames, (unsigned int)dma_buffer_length);
+ rt_kprintf(" fifo buffer : %u frames, %u samples\n",
+ (unsigned int)fifo_frames, (unsigned int)fifo_buffer_length);
+ rt_kprintf(" read count : %u\n", (unsigned int)read_times);
+ rt_kprintf(" full reads : %u\n", (unsigned int)full_count);
+ rt_kprintf(" partial reads : %u\n", (unsigned int)partial_count);
+ rt_kprintf(" timeouts : %u\n", (unsigned int)timeout_count);
+ rt_kprintf(" errors : %u\n", (unsigned int)error_count);
+ rt_kprintf(" total frames : %u\n", (unsigned int)total_frames);
+ rt_kprintf(" total samples : %u\n", (unsigned int)total_samples);
+ rt_kprintf(" frame rate : %u frames/s\n", (unsigned int)frame_rate);
+ rt_kprintf(" sample rate/ch : %u samples/s\n", (unsigned int)sample_rate_per_channel);
+ rt_kprintf(" elapsed tick : %u\n", (unsigned int)elapsed_tick);
+
+ rt_free(buffer);
+ if (error_count > 0U)
+ {
+ return result;
+ }
+
+ if (timeout_count > 0U)
+ {
+ return -RT_ETIMEOUT;
+ }
+
+ return stop_result;
+}
+#endif /* defined(RT_ADC_USING_STREAM) && defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+
+/**
+ * @brief Handle the ADC FinSH command.
+ * @param argc Argument count.
+ * @param argv Argument vector.
+ * @return Command status.
+ */
+static int adc(int argc, char **argv)
+{
+ rt_uint32_t values_buf[RT_ADC_MSH_VALUE_BUFFER_SIZE];
+ rt_uint32_t *values;
+ rt_uint32_t channels;
+ rt_uint32_t channel_mask;
+ rt_size_t index;
+ rt_size_t count;
+ rt_ssize_t read_count;
+ rt_err_t result;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ values = values_buf;
+ if (!rt_strcmp(argv[1], "probe"))
+ {
+ rt_device_t new_dev;
+ rt_adc_device_t new_device;
+
+ if (argc != 3)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+#ifdef RT_ADC_USING_STREAM
+ if (adc_msh_stream_buffer_is_active(stream_device) == RT_TRUE)
+ {
+ rt_kprintf("adc stream is running, stop it first\n");
+ return -RT_EBUSY;
+ }
+ adc_msh_clear_stream_owner(&stream_device);
+#endif /* RT_ADC_USING_STREAM */
+
+ new_dev = rt_device_find(argv[2]);
+ if (new_dev == RT_NULL)
+ {
+ rt_kprintf("adc device %s not found\n", argv[2]);
+ return -RT_ERROR;
+ }
+
+ if (new_dev->type != RT_Device_Class_ADC)
+ {
+ rt_kprintf("%s is not an adc device\n", argv[2]);
+ return -RT_EINVAL;
+ }
+
+ new_device = (rt_adc_device_t)new_dev;
+ if (device == new_device)
+ {
+ rt_kprintf("probe %s: %d\n", argv[2], RT_EOK);
+ return RT_EOK;
+ }
+
+ result = rt_device_open(new_dev, RT_DEVICE_OFLAG_RDWR);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("probe %s failed: %d\n", argv[2], result);
+ return result;
+ }
+
+ if (device != RT_NULL)
+ {
+ rt_err_t close_result;
+
+ close_result = rt_device_close(&device->parent);
+ if (close_result != RT_EOK)
+ {
+ (void)rt_device_close(new_dev);
+ rt_kprintf("close previous adc device %s failed: %d\n", device->parent.parent.name, close_result);
+ return close_result;
+ }
+ }
+
+ device = new_device;
+ rt_kprintf("probe %s: %d\n", argv[2], result);
+ return result;
+ }
+
+ if (device == RT_NULL)
+ {
+ rt_kprintf("no adc device selected\n");
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(argv[1], "config"))
+ {
+ if (argc < 3)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 2); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 2], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 2]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ result = rt_device_control(&device->parent, RT_ADC_CMD_SET_SESSION, &channels);
+ if (result == RT_EOK)
+ {
+ rt_kprintf("config channels=0x%08lx count=%u\n",
+ (unsigned long)channels, (unsigned int)rt_adc_channel_mask_count(channels));
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[1], "read"))
+ {
+ if (argc > 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U))
+ {
+ rt_kprintf("adc session is not configured, run adc config first\n");
+ return -RT_EINVAL;
+ }
+
+ count = device->session_ctrl.channel_count;
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc read count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ read_count = rt_device_read(&device->parent, 0, values, count);
+ if (read_count < 0)
+ {
+ result = (rt_err_t)read_count;
+ }
+ else if ((rt_size_t)read_count != count)
+ {
+ result = -RT_ERROR;
+ }
+ else
+ {
+ result = RT_EOK;
+ for (index = 0U; index < count; index++)
+ {
+ rt_kprintf("[%u]=%lu\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[1], "voltage"))
+ {
+ rt_int32_t timeout_ms;
+
+ if (argc != 3)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U))
+ {
+ rt_kprintf("adc session is not configured, run adc config first\n");
+ return -RT_EINVAL;
+ }
+
+ timeout_ms = adc_msh_parse_timeout(argv[2]);
+ count = device->session_ctrl.channel_count;
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc voltage count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_voltage(device, values, count, timeout_ms);
+ if (result == RT_EOK)
+ {
+ for (index = 0U; index < count; index++)
+ {
+ rt_kprintf("[%u]=%lu mV\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+ }
+ else if (result == -RT_EINVAL)
+ {
+ rt_kprintf("adc voltage failed, check adc config and VREF channel/default_vref\n");
+ }
+
+ return result;
+ }
+
+ if (!rt_strcmp(argv[1], "seq"))
+ {
+ struct rt_adc_sequence_cfg sequence_cfg;
+ rt_size_t sequence_read_count;
+
+ if (argc < 4)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 3); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 3], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 3]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ count = rt_adc_channel_mask_count(channels);
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc seq count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ rt_memset(&sequence_cfg, 0, sizeof(sequence_cfg));
+ sequence_cfg.buffer = values;
+ sequence_cfg.buffer_length = count;
+ sequence_cfg.timeout_ms = adc_msh_parse_timeout(argv[2]);
+
+ result = rt_adc_read_sequence(device, channels, &sequence_cfg, &sequence_read_count);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ for (index = 0U; index < sequence_read_count; index++)
+ {
+ rt_kprintf("[%u]=%lu\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+
+ return RT_EOK;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ if (!rt_strcmp(argv[1], "trigger"))
+ {
+ return adc_msh_trigger(device, argc - 2, &argv[2]);
+ }
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ if (!rt_strcmp(argv[1], "close"))
+ {
+ rt_err_t close_result;
+
+ if (argc > 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ close_result = rt_device_close(&device->parent);
+ if (close_result != RT_EOK)
+ {
+ rt_kprintf("close adc device %s failed: %d\n", device->parent.parent.name, close_result);
+ return close_result;
+ }
+
+ rt_kprintf("close %s: %d\n", device->parent.parent.name, close_result);
+ device = RT_NULL;
+ return RT_EOK;
+ }
+
+#ifdef RT_ADC_USING_STREAM
+ if (!rt_strcmp(argv[1], "stream"))
+ {
+ return adc_msh_stream(device, argc - 2, &argv[2]);
+ }
+
+#if defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+ if (!rt_strcmp(argv[1], "fifo"))
+ {
+ return adc_msh_fifo_test(device, argc - 2, &argv[2]);
+ }
+#endif /* defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+#endif /* RT_ADC_USING_STREAM */
+
+ result = rt_adc_msh_special(device, argc - 1, &argv[1]);
+ if (result != -RT_ENOSYS)
+ {
+ return result;
+ }
+
+ adc_msh_usage();
+ return -RT_EINVAL;
+}
+MSH_CMD_EXPORT(adc, adc v2 command);
+#endif /* defined(RT_USING_ADC_V2) && defined(RT_USING_FINSH) && defined(RT_ADC_V2_USING_MSH) */
diff --git a/components/drivers/adc/adc_v2_trigger.c b/components/drivers/adc/adc_v2_trigger.c
new file mode 100644
index 0000000000..0521201c49
--- /dev/null
+++ b/components/drivers/adc/adc_v2_trigger.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-20 wdfk-prog add ADC V2 timer trigger framework implementation
+ */
+
+/**
+ * @file adc_v2_trigger.c
+ * @brief ADC V2 trigger framework implementation.
+ */
+
+#include <rtconfig.h>
+
+#if defined(RT_USING_ADC_V2) && defined(RT_ADC_USING_TRIGGER)
+
+#include <rtdevice.h>
+#include <rtthread.h>
+#if defined(RT_ADC_TRIGGER_USING_TIMER) && defined(RT_USING_CLOCK_TIMER_TRIGGER)
+#include <drivers/clock_time.h>
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) && defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+
+#define DBG_TAG "adc.v2.trigger"
+#define DBG_LVL DBG_LOG
+#include <rtdbg.h>
+
+#include "adc_v2_internal.h"
+
+/**
+ * @brief Validate an ADC hardware trigger configuration at framework level.
+ * @param cfg Pointer to the trigger configuration object.
+ * @return Operation status.
+ */
+static rt_err_t adc_validate_trigger_cfg_common(const struct rt_adc_trigger_cfg *cfg)
+{
+ switch (cfg->type)
+ {
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ if ((cfg->event.timer.timer == RT_NULL) || (cfg->event.timer.timer->type != RT_Device_Class_Timer) ||
+ (cfg->event.timer.freq_hz == 0U))
+ {
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+#else
+ return -RT_ENOSYS;
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ if ((cfg->event.timer.timer == RT_NULL) || (cfg->event.timer.timer->type != RT_Device_Class_Timer) ||
+ (cfg->event.timer.channel == 0U) || (cfg->event.timer.freq_hz == 0U))
+ {
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+#else
+ return -RT_ENOSYS;
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ case RT_ADC_TRIGGER_ANALOG_COMPARE:
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+ if ((cfg->event.compare.channel == 0U) || (cfg->event.compare.edge == RT_ADC_TRIGGER_EDGE_NONE))
+ {
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+#else
+ return -RT_ENOSYS;
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+
+ case RT_ADC_TRIGGER_PWM_EDGE:
+ case RT_ADC_TRIGGER_EXTI_EDGE:
+ case RT_ADC_TRIGGER_BACKEND:
+ return -RT_ENOSYS;
+
+ default:
+ return -RT_EINVAL;
+ }
+}
+
+/**
+ * @brief Initialize the ADC trigger control state to software-started mode.
+ * @param device Pointer to the ADC device object.
+ */
+void adc_trigger_init(rt_adc_device_t device)
+{
+ rt_memset(&device->trigger_ctrl, 0, sizeof(device->trigger_ctrl));
+}
+
+/**
+ * @brief Get the active device-level ADC trigger configuration.
+ * @param device Pointer to the ADC device object.
+ * @return Pointer to the cached trigger configuration, or RT_NULL when no hardware trigger is configured.
+ */
+const struct rt_adc_trigger_cfg *adc_trigger_active_get(rt_adc_device_t device)
+{
+ if (device->trigger_ctrl.configured != RT_TRUE)
+ {
+ return RT_NULL;
+ }
+
+ return &device->trigger_ctrl.cfg;
+}
+
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+/**
+ * @brief Configure and start a timer-based ADC trigger source.
+ * @param cfg Pointer to the active ADC trigger configuration object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_timer_source_start(const struct rt_adc_trigger_cfg *cfg)
+{
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+ struct rt_clock_timer_trigger_cfg timer_cfg = {0};
+ rt_err_t result;
+
+ timer_cfg.freq_hz = cfg->event.timer.freq_hz;
+ timer_cfg.channel = cfg->event.timer.channel;
+
+ switch (cfg->type)
+ {
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+ timer_cfg.event = CLOCK_TIMER_TRIGGER_EVENT_UPDATE;
+ timer_cfg.channel = 0U;
+ break;
+
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+ timer_cfg.event = CLOCK_TIMER_TRIGGER_EVENT_COMPARE;
+ break;
+
+ default:
+ return -RT_ENOSYS;
+ }
+
+ result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_CONFIG, &timer_cfg);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_START, RT_NULL);
+ if (result != RT_EOK)
+ {
+ (void)rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_RELEASE, RT_NULL);
+ return result;
+ }
+
+ return RT_EOK;
+#else
+ RT_UNUSED(cfg);
+ return -RT_ENOSYS;
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+}
+
+/**
+ * @brief Stop and release a timer-based ADC trigger source.
+ * @param cfg Pointer to the active ADC trigger configuration object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_timer_source_stop(const struct rt_adc_trigger_cfg *cfg)
+{
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+ rt_err_t stop_result;
+ rt_err_t release_result;
+
+ if ((cfg->type != RT_ADC_TRIGGER_TIMER_UPDATE) && (cfg->type != RT_ADC_TRIGGER_TIMER_COMPARE))
+ {
+ return -RT_ENOSYS;
+ }
+
+ stop_result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_STOP, RT_NULL);
+ release_result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_RELEASE, RT_NULL);
+
+ return (release_result != RT_EOK) ? release_result : stop_result;
+#else
+ RT_UNUSED(cfg);
+ return -RT_ENOSYS;
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+}
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+/**
+ * @brief Start the active trigger source after the ADC conversion path is armed.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_source_start(rt_adc_device_t device)
+{
+ const struct rt_adc_trigger_cfg *cfg;
+
+ cfg = adc_trigger_active_get(device);
+ if (cfg == RT_NULL)
+ {
+ return RT_EOK;
+ }
+
+ switch (cfg->type)
+ {
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+ return adc_trigger_timer_source_start(cfg);
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ default:
+ return RT_EOK;
+ }
+}
+
+/**
+ * @brief Stop the active trigger source before the ADC conversion path is disarmed.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_source_stop(rt_adc_device_t device)
+{
+ const struct rt_adc_trigger_cfg *cfg;
+
+ cfg = adc_trigger_active_get(device);
+ if (cfg == RT_NULL)
+ {
+ return RT_EOK;
+ }
+
+ switch (cfg->type)
+ {
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+ return adc_trigger_timer_source_stop(cfg);
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ default:
+ return RT_EOK;
+ }
+}
+
+/**
+ * @brief Control the active trigger source around an armed ADC conversion path.
+ * @param device Pointer to the ADC device object.
+ * @param action Trigger-source lifecycle action.
+ * @param started Pointer to the trigger-start state.
+ * @param current_result Transfer result before trigger stop, ignored for start.
+ * @return For start, trigger-source start result; for stop, @p current_result unless trigger stop is the first
+ * observed failure.
+ */
+rt_err_t adc_trigger_source_control(rt_adc_device_t device, enum adc_trigger_source_action action,
+ rt_bool_t *started, rt_err_t current_result)
+{
+ rt_err_t stop_result;
+ rt_err_t result;
+
+ switch (action)
+ {
+ case ADC_TRIGGER_SOURCE_START:
+ *started = RT_FALSE;
+ if (adc_trigger_active_get(device) == RT_NULL)
+ {
+ return RT_EOK;
+ }
+
+ result = adc_trigger_source_start(device);
+ if (result != RT_EOK)
+ {
+ LOG_E("trigger source start failed: device=%s result=%d", device->parent.parent.name, result);
+ return result;
+ }
+
+ *started = RT_TRUE;
+ return RT_EOK;
+
+ case ADC_TRIGGER_SOURCE_STOP:
+ if (*started != RT_TRUE)
+ {
+ return current_result;
+ }
+
+ stop_result = adc_trigger_source_stop(device);
+ if (stop_result != RT_EOK)
+ {
+ LOG_E("trigger source stop failed: device=%s result=%d", device->parent.parent.name, stop_result);
+ if (current_result == RT_EOK)
+ {
+ current_result = stop_result;
+ }
+ }
+
+ *started = RT_FALSE;
+ return current_result;
+
+ default:
+ return (current_result != RT_EOK) ? current_result : -RT_EINVAL;
+ }
+}
+
+/**
+ * @brief Preconfigure the active device-level ADC trigger before hardware configuration.
+ * @param device Pointer to the ADC device object.
+ * @return RT_EOK on success, otherwise an RT-Thread error code.
+ *
+ * @note A null active trigger still has to be passed to the backend when the
+ * backend provides trigger_prepare(), because some backends cache the
+ * previously prepared hardware trigger selector. Passing RT_NULL asks the
+ * backend to prepare the software-start/default trigger path.
+ */
+rt_err_t adc_trigger_preconfig(rt_adc_device_t device)
+{
+ const struct rt_adc_trigger_cfg *cfg;
+
+ cfg = adc_trigger_active_get(device);
+
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) || (device->ops->core->trigger_prepare == RT_NULL))
+ {
+ return (cfg == RT_NULL) ? RT_EOK : -RT_ENOSYS;
+ }
+
+ return device->ops->core->trigger_prepare(device, cfg);
+}
+
+/**
+ * @brief Set one device-level ADC hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the ADC trigger configuration object.
+ * @return Operation status.
+ * @note This function only checks the framework-level trigger structure and
+ * caches the request. It does not write ADC trigger registers immediately.
+ * Backend compatibility is reported when the cached trigger is
+ * preconfigured before a stream hardware configuration.
+ */
+rt_err_t rt_adc_trigger_set(rt_adc_device_t device, const struct rt_adc_trigger_cfg *cfg)
+{
+ struct rt_adc_trigger_cfg trigger_cfg;
+ rt_err_t result;
+ rt_atomic_t old_state;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ trigger_cfg = *cfg;
+ old_state = (rt_atomic_t)RT_ADC_STATE_IDLE;
+ if (rt_atomic_compare_exchange_strong(&device->state, &old_state, (rt_atomic_t)RT_ADC_STATE_LOCKED) == 0)
+ {
+ return -RT_EBUSY;
+ }
+
+ result = adc_validate_trigger_cfg_common(&trigger_cfg);
+ if (result == RT_EOK)
+ {
+ device->trigger_ctrl.cfg = trigger_cfg;
+ device->trigger_ctrl.configured = RT_TRUE;
+ }
+
+ rt_atomic_store(&device->state, (rt_atomic_t)RT_ADC_STATE_IDLE);
+ if (result != RT_EOK)
+ {
+ LOG_E("trigger set cache failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+ return result;
+}
+
+/**
+ * @brief Clear the cached ADC trigger configuration.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ * @note The ADC device must be idle so the cached trigger cannot be cleared while an active conversion is using it.
+ */
+rt_err_t rt_adc_trigger_clear(rt_adc_device_t device)
+{
+ rt_atomic_t old_state;
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ old_state = (rt_atomic_t)RT_ADC_STATE_IDLE;
+ if (rt_atomic_compare_exchange_strong(&device->state, &old_state, (rt_atomic_t)RT_ADC_STATE_LOCKED) == 0)
+ {
+ return -RT_EBUSY;
+ }
+
+ rt_memset(&device->trigger_ctrl, 0, sizeof(device->trigger_ctrl));
+ rt_atomic_store(&device->state, (rt_atomic_t)RT_ADC_STATE_IDLE);
+
+ LOG_D("trigger cleared: device=%s", device->parent.parent.name);
+ return RT_EOK;
+}
+
+/**
+ * @brief Check whether one ADC device has a cached hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @return RT_TRUE if a hardware trigger request is set, otherwise RT_FALSE.
+ */
+rt_bool_t rt_adc_trigger_is_set(rt_adc_device_t device)
+{
+ if (device == RT_NULL)
+ {
+ return RT_FALSE;
+ }
+
+ return device->trigger_ctrl.configured;
+}
+
+#endif /* defined(RT_USING_ADC_V2) && defined(RT_ADC_USING_TRIGGER) */
diff --git a/components/drivers/clock_time/Kconfig b/components/drivers/clock_time/Kconfig
index d6259617aa..46dd271483 100644
--- a/components/drivers/clock_time/Kconfig
+++ b/components/drivers/clock_time/Kconfig
@@ -2,6 +2,17 @@ menuconfig RT_USING_CLOCK_TIME
bool "Clock time subsystem"
default n
+config RT_USING_CLOCK_TIMER_TRIGGER
+ bool "Enable clock timer hardware trigger output"
+ depends on RT_USING_CLOCK_TIME
+ default n
+ help
+ Enable clock timer hardware trigger output controls. Timer backends
+ may use this to expose update/TRGO trigger events to ADC drivers. The
+ selected timer must be dedicated to trigger output; the framework does
+ not arbitrate shared use with clock-time timeouts, PWM, or other timer
+ consumers.
+
if RT_USING_CLOCK_TIME
config CLOCK_TIMER_FREQ
int "Clock time timer freq"
diff --git a/components/drivers/include/drivers/adc_v2.h b/components/drivers/include/drivers/adc_v2.h
new file mode 100644
index 0000000000..bf369e3a50
--- /dev/null
+++ b/components/drivers/include/drivers/adc_v2.h
@@ -0,0 +1,622 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-04-21 wdfk-prog add standalone adc v2 framework interface
+ */
+
+/**
+ * @file adc_v2.h
+ * @brief Standalone ADC V2 framework declarations.
+ */
+
+#ifndef __ADC_V2_H__
+#define __ADC_V2_H__
+
+#include <rtconfig.h>
+#include "misc.h"
+#if !defined(RT_USING_ADC_V2)
+#error "ADC V2 interface requires RT_USING_ADC_V2."
+#endif /* !defined(RT_USING_ADC_V2) */
+
+#include <rtatomic.h>
+#include <rtdevice.h>
+#include <rtthread.h>
+
+#if defined(RT_ADC_USING_TRIGGER)
+#include <drivers/adc_v2_trigger.h>
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#if defined(RT_ADC_USING_STREAM) && defined(RT_ADC_STREAM_USING_FIFO)
+#include <ipc/completion.h>
+#include <ipc/ringbuffer.h>
+#endif /* defined(RT_ADC_USING_STREAM) && defined(RT_ADC_STREAM_USING_FIFO) */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @brief ADC control command identifiers.
+ */
+enum rt_adc_control_cmd
+{
+ RT_ADC_CMD_GET_RESOLUTION = RT_DEVICE_CTRL_BASE(ADC) + 1, /**< Get resolution in bits. */
+ RT_ADC_CMD_CALIBRATE, /**< Run ADC calibration. */
+ RT_ADC_CMD_SET_VREF, /**< Set fallback VREF from rt_uint32_t mV. */
+ RT_ADC_CMD_CALC_VREF, /**< Calculate VDDA from a raw VREF sample. */
+ RT_ADC_CMD_GET_VREF_CHANNEL, /**< Get the backend VREF sample channel as rt_uint8_t. */
+ RT_ADC_CMD_SET_SESSION, /**< Configure and cache conversion channels from rt_uint32_t mask. */
+ RT_ADC_CMD_SET_CONFIG, /**< Set backend private device configuration. */
+ RT_ADC_CMD_GET_CONFIG, /**< Get backend private device configuration. */
+ RT_ADC_CMD_CLEAR_CHANNEL_CONFIG /**< Clear backend channel/rank configuration explicitly. */
+};
+
+/**
+ * @brief ADC input mode request flags.
+ */
+enum rt_adc_input_mode
+{
+ RT_ADC_INPUT_MODE_SINGLE_ENDED = 0x01U, /**< Single-ended ADC input mode. */
+ RT_ADC_INPUT_MODE_DIFFERENTIAL = 0x02U /**< Differential ADC input mode. */
+};
+
+/**
+ * @brief Build an ADC channel mask from one logical channel index.
+ * @param _channel Logical ADC channel index.
+ */
+#define RT_ADC_CHANNEL_MASK(_channel) (1UL << (rt_uint32_t)(_channel))
+
+/**
+ * @brief ADC framework runtime state.
+ */
+enum rt_adc_state
+{
+ RT_ADC_STATE_IDLE = 0, /**< The ADC device is idle. */
+ RT_ADC_STATE_LOCKED, /**< The ADC device is temporarily locked by a framework operation. */
+ RT_ADC_STATE_STREAM /**< The ADC device is owned by an active stream session. */
+};
+
+struct rt_adc_device;
+typedef struct rt_adc_device *rt_adc_device_t;
+
+struct rt_adc_sequence_cfg;
+struct rt_adc_stream_cfg;
+
+/**
+ * @brief ADC core driver operation table.
+ */
+struct rt_adc_core_ops
+{
+ /**
+ * @brief Open and initialize the ADC hardware.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+ rt_err_t (*open)(struct rt_adc_device *device);
+
+ /**
+ * @brief Close and deinitialize the ADC hardware.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+ rt_err_t (*close)(struct rt_adc_device *device);
+
+
+ /**
+ * @brief Configure one ADC conversion session before data transfer.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @return Operation status.
+ */
+ rt_err_t (*session_config)(struct rt_adc_device *device, rt_uint32_t channels);
+
+#if defined(RT_ADC_USING_TRIGGER)
+ /**
+ * @brief Preconfigure the ADC backend trigger state for the next hardware configuration pass.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the active ADC trigger configuration object.
+ * @return Operation status.
+ * @note The implementation may only validate @p cfg, update pending driver
+ * state, or fill HAL initialization fields. It must not start the
+ * trigger source or arm ADC conversions.
+ */
+ rt_err_t (*trigger_prepare)(struct rt_adc_device *device, const struct rt_adc_trigger_cfg *cfg);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ /**
+ * @brief Handle ADC control commands.
+ * @param device Pointer to the ADC device object.
+ * @param cmd Control command.
+ * @param args Pointer to the control argument buffer.
+ * @return Operation status.
+ */
+ rt_err_t (*control)(struct rt_adc_device *device, int cmd, void *args);
+};
+
+/**
+ * @brief ADC finite-sequence operation table.
+ */
+struct rt_adc_sequence_ops
+{
+ /**
+ * @brief Start one active finite transfer session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the sequence request object.
+ * @return Operation status.
+ */
+ rt_err_t (*start)(struct rt_adc_device *device, rt_uint32_t channels, const struct rt_adc_sequence_cfg *cfg);
+
+ /**
+ * @brief Read one converted sample from an active finite transfer session.
+ * @param device Pointer to the ADC device object.
+ * @param value Pointer to the output sample value.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Operation status.
+ */
+ rt_err_t (*read)(struct rt_adc_device *device, rt_uint32_t *value, rt_int32_t timeout_ms);
+
+ /**
+ * @brief Stop one active finite transfer session.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+ rt_err_t (*stop)(struct rt_adc_device *device);
+};
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief ADC stream operation table.
+ */
+struct rt_adc_stream_ops
+{
+ /**
+ * @brief Start one active stream session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the stream configuration object.
+ * @return Operation status.
+ */
+ rt_err_t (*start)(struct rt_adc_device *device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg);
+
+ /**
+ * @brief Synchronize a backend DMA sample range before CPU access.
+ * @param device Pointer to the ADC device object.
+ * @param sample_buffer Pointer to the DMA sample buffer.
+ * @param sample_count Number of samples to synchronize.
+ * @return Operation status.
+ */
+ rt_err_t (*sync)(struct rt_adc_device *device, const rt_uint32_t *sample_buffer, rt_size_t sample_count);
+
+ /**
+ * @brief Stop one active stream session.
+ * @param device Pointer to the ADC device object.
+ * @param hardware_stopped Pointer to the hardware-stop state output.
+ * @return Operation status.
+ *
+ * The backend sets @p hardware_stopped to RT_TRUE after the ADC hardware
+ * conversion path has stopped. When this callback returns an error with
+ * @p hardware_stopped set to RT_TRUE, the framework treats it as a
+ * post-stop cleanup failure and moves the stream to IDLE.
+ */
+ rt_err_t (*stop)(struct rt_adc_device *device, rt_bool_t *hardware_stopped);
+};
+#endif /* RT_ADC_USING_STREAM */
+
+
+/**
+ * @brief ADC driver operation table.
+ */
+struct rt_adc_ops
+{
+ const struct rt_adc_core_ops *core; /**< Core operation table. */
+ const struct rt_adc_sequence_ops *sequence; /**< Sequence-session operations. */
+#ifdef RT_ADC_USING_STREAM
+ const struct rt_adc_stream_ops *stream; /**< Stream-session operations. */
+#endif /* RT_ADC_USING_STREAM */
+};
+
+/**
+ * @brief ADC synchronous one-frame sequence request.
+ */
+struct rt_adc_sequence_cfg
+{
+ rt_uint32_t *buffer; /**< Destination sample buffer. */
+ rt_size_t buffer_length; /**< Destination buffer length in samples. */
+ rt_int32_t timeout_ms; /**< Per-sample timeout; negative means wait forever. */
+};
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief ADC stream backend event.
+ */
+enum rt_adc_stream_event
+{
+ RT_ADC_STREAM_EVENT_DMA_HALF = 0x01U, /**< ADC stream DMA half-transfer event. */
+ RT_ADC_STREAM_EVENT_DMA_DONE = 0x02U, /**< ADC stream DMA transfer-complete event. */
+ RT_ADC_STREAM_EVENT_ERROR = 0x03U /**< ADC stream backend error event. */
+};
+
+/**
+ * @brief ADC stream buffering policy.
+ */
+enum rt_adc_stream_policy
+{
+#ifdef RT_ADC_STREAM_USING_LATEST
+ /**
+ * @brief Keep only the latest ADC rank values.
+ *
+ * This policy is intended for slow-changing ADC values such as battery
+ * voltage, temperature, potentiometer input, resistor-divider voltage, or
+ * board status monitoring. Old values may be overwritten.
+ *
+ * In this policy, @ref rt_adc_stream_cfg::dma_buffer_length must be exactly
+ * equal to the active ADC frame length, namely the number of enabled ADC
+ * ranks. The backend must expose the DMA buffer as a single latest-frame
+ * layout:
+ *
+ * - dma_buffer[0] stores the latest value of ADC rank 1.
+ * - dma_buffer[1] stores the latest value of ADC rank 2.
+ * - dma_buffer[n] stores the latest value of ADC rank n + 1.
+ *
+ * STM32 regular scan conversion with circular DMA naturally satisfies this
+ * layout when the DMA length equals the ADC rank count. Backends for other
+ * platforms may also support this policy if their DMA engine writes samples
+ * in scan-rank order. If a backend produces packed, interleaved,
+ * descriptor-based, hardware-FIFO, or otherwise non-rank-ordered data, it
+ * must either convert the data into this latest-frame layout internally or
+ * reject the configuration with -RT_ENOSYS.
+ *
+ * @note Latest mode is a low-overhead lockless DMA-buffer view. CPU-side
+ * atomic operations, spin locks, mutexes, or read-write locks cannot
+ * prevent the DMA engine from updating memory concurrently, because
+ * DMA does not participate in CPU locking protocols. This mode only
+ * guarantees that each buffer index maps to the corresponding ADC rank
+ * latest value; it does not guarantee that the copied frame is a
+ * single hardware scan-cycle snapshot. Use RT_ADC_STREAM_POLICY_FIFO
+ * when ordered and frame-accurate samples are required.
+ */
+ RT_ADC_STREAM_POLICY_LATEST = 0,
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ /**
+ * @brief Preserve ADC samples in FIFO order.
+ *
+ * This policy is intended for continuous and loss-sensitive sampling such
+ * as waveform, current, vibration, audio-like, motor-control, or
+ * control-loop data where each sample matters.
+ *
+ * The backend reports completed DMA blocks to the ADC framework. The
+ * framework copies those blocks into a ringbuffer and wakes blocking readers
+ * through a completion object.
+ */
+ RT_ADC_STREAM_POLICY_FIFO,
+#endif /* RT_ADC_STREAM_USING_FIFO */
+};
+
+/**
+ * @brief ADC stream DMA data event mode.
+ */
+enum rt_adc_stream_dma_event_mode
+{
+ RT_ADC_STREAM_DMA_EVENT_AUTO = 0, /**< Use the default event mode for the selected stream policy. */
+ RT_ADC_STREAM_DMA_EVENT_NONE, /**< Disable DMA half/full data events; DMA error events remain enabled. */
+ RT_ADC_STREAM_DMA_EVENT_FULL_ONLY, /**< Report only DMA transfer-complete data events to reduce interrupt rate. */
+ RT_ADC_STREAM_DMA_EVENT_HALF_FULL /**< Report both DMA half-transfer and transfer-complete data events. */
+};
+
+/**
+ * @brief ADC stream FIFO data callback type.
+ * @param device Pointer to the ADC device object.
+ * @param sample_count Number of newly accepted samples.
+ * @param user_data Pointer to the user context.
+ * @note This callback is invoked from ADC stream ISR context. It must not
+ * block, sleep, or call APIs that may suspend the current context.
+ */
+typedef void (*rt_adc_stream_callback_t)(struct rt_adc_device *device, rt_size_t sample_count, void *user_data);
+
+/**
+ * @brief ADC stream configuration.
+ * @note The DMA buffer belongs to the caller and must remain valid until
+ * rt_adc_stream_stop() returns. The stream configuration object only
+ * needs to remain valid for the duration of rt_adc_stream_start(). A
+ * backend may use an internal cache-aligned DMA buffer when the caller
+ * buffer is not safe for platform cache maintenance. Backend drivers must
+ * copy scalar configuration fields they need after rt_adc_stream_start()
+ * returns.
+ */
+struct rt_adc_stream_cfg
+{
+ enum rt_adc_stream_policy policy; /**< Stream buffering policy. */
+ enum rt_adc_stream_dma_event_mode dma_event_mode; /**< DMA half/full data event mode. */
+
+ /**
+ * @brief DMA circular staging buffer.
+ *
+ * For RT_ADC_STREAM_POLICY_LATEST, this buffer stores exactly one latest
+ * ADC scan frame. dma_buffer_length must equal the active frame length.
+ * Each element maps to one configured ADC rank.
+ *
+ * For RT_ADC_STREAM_POLICY_FIFO, this buffer is a DMA staging buffer. The
+ * default rt_adc_stream_start() entry validates only basic buffer presence
+ * and capacity. It does not require each DMA data-event block to end on an
+ * ADC scan-frame boundary.
+ *
+ * Use rt_adc_stream_start_frame_aligned_fifo() when continuous FIFO sampling
+ * requires a strict frame-aligned DMA/FIFO layout before the stream starts.
+ */
+ rt_uint32_t *dma_buffer;
+
+ /**
+ * @brief DMA buffer length in samples.
+ */
+ rt_size_t dma_buffer_length;
+#ifdef RT_ADC_STREAM_USING_FIFO
+ rt_uint32_t *fifo_buffer; /**< FIFO storage for FIFO policy. */
+ rt_size_t fifo_buffer_length; /**< FIFO buffer length in samples. */
+ rt_size_t watermark; /**< FIFO wakeup or callback threshold in samples. */
+ rt_adc_stream_callback_t callback; /**< Optional ISR-context FIFO data callback. */
+ void *user_data; /**< User data passed to the FIFO data callback. */
+#endif /* RT_ADC_STREAM_USING_FIFO */
+};
+
+/**
+ * @brief ADC stream runtime control block owned by the ADC framework.
+ */
+struct rt_adc_stream_ctrl
+{
+ rt_bool_t active; /**< Whether a stream backend session is active. */
+ enum rt_adc_stream_policy policy; /**< Active stream buffering policy. */
+ enum rt_adc_stream_dma_event_mode dma_event_mode; /**< Active DMA data event mode. */
+ rt_uint32_t *dma_buffer; /**< DMA circular staging buffer. */
+ rt_size_t dma_buffer_length; /**< DMA buffer length in samples. */
+ rt_size_t frame_length; /**< Samples in one ADC scan frame. */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ struct rt_ringbuffer fifo; /**< FIFO storage for FIFO policy. */
+ rt_bool_t fifo_enabled; /**< FIFO initialized flag. */
+ /**
+ * @brief Reader wake completion for FIFO stream.
+ *
+ * The FIFO ringbuffer is the source of truth for available data. This
+ * completion object is only a wakeup edge for blocking reads.
+ */
+ struct rt_completion rx_cpt;
+ rt_size_t overflow_count; /**< FIFO overrun count. */
+ rt_size_t watermark; /**< FIFO wakeup or callback threshold. */
+ rt_adc_stream_callback_t callback; /**< Optional FIFO data callback. */
+ void *user_data; /**< Callback private data. */
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ rt_atomic_t last_error; /**< Atomic last stream backend error. */
+};
+#endif /* RT_ADC_USING_STREAM */
+
+/**
+ * @brief ADC active session runtime control block.
+ */
+struct rt_adc_session_ctrl
+{
+ rt_uint32_t channels; /**< Cached active ADC channel selection mask. */
+ rt_size_t channel_count; /**< Cached active session channel count. */
+ rt_bool_t configured; /**< Whether the cached session configuration is valid. */
+};
+
+/**
+ * @brief ADC device object.
+ */
+struct rt_adc_device
+{
+ struct rt_device parent; /**< RT-Thread device object. */
+ const struct rt_adc_ops *ops; /**< ADC driver operation table. */
+ rt_atomic_t state; /**< Atomic ADC runtime state. */
+ struct rt_spinlock spinlock; /**< Protects ADC stream FIFO shared by thread context and ISR. */
+ rt_uint32_t default_vref_mv; /**< Default reference voltage in millivolts. */
+ struct rt_adc_session_ctrl session_ctrl; /**< ADC active session control block. */
+#if defined(RT_ADC_USING_TRIGGER)
+ struct rt_adc_trigger_ctrl trigger_ctrl; /**< ADC trigger runtime control block. */
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+#ifdef RT_ADC_USING_STREAM
+ struct rt_adc_stream_ctrl stream_ctrl; /**< ADC stream runtime control block. */
+#endif /* RT_ADC_USING_STREAM */
+};
+
+/**
+ * @brief Count selected ADC channels in one channel mask.
+ * @param channels ADC channel selection mask.
+ * @return Number of selected channels.
+ */
+rt_inline rt_size_t rt_adc_channel_mask_count(rt_uint32_t channels)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ return (rt_size_t)__builtin_popcount((unsigned int)channels);
+#else
+ rt_size_t count;
+
+ count = 0U;
+ while (channels != 0U)
+ {
+ channels &= channels - 1U;
+ count++;
+ }
+
+ return count;
+#endif /* defined(__GNUC__) || defined(__clang__) */
+}
+
+/**
+ * @brief Take the lowest selected ADC channel from one channel mask.
+ * @param channels Pointer to the ADC channel selection mask.
+ * @return Selected channel index, or 0xff if the mask is empty.
+ */
+rt_inline rt_uint8_t rt_adc_channel_mask_take_lsb(rt_uint32_t *channels)
+{
+ rt_uint8_t bit;
+
+ if ((channels == RT_NULL) || (*channels == 0U))
+ {
+ return 0xffU;
+ }
+
+#if defined(__GNUC__) || defined(__clang__)
+ bit = (rt_uint8_t)__builtin_ctz((unsigned int)*channels);
+#else
+ for (bit = 0U; bit < 32U; bit++)
+ {
+ if ((*channels & RT_ADC_CHANNEL_MASK(bit)) != 0U)
+ {
+ break;
+ }
+ }
+#endif /* defined(__GNUC__) || defined(__clang__) */
+
+ *channels &= ~RT_ADC_CHANNEL_MASK(bit);
+ return bit;
+}
+
+/**
+ * @brief Register an ADC device with the RT-Thread device framework.
+ * @param device Pointer to the ADC device object.
+ * @param name Pointer to the ADC device name string.
+ * @param ops Pointer to the ADC driver operation table.
+ * @param user_data Pointer to driver private data.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_register(rt_adc_device_t device, const char *name, const struct rt_adc_ops *ops, const void *user_data);
+
+/**
+ * @brief Get the raw sample index of one channel in an ADC session mask.
+ * @param session_channels Configured ADC session channel mask.
+ * @param channel Single-channel mask to locate.
+ * @param index Pointer to the output raw sample index.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_session_channel_index(rt_uint32_t session_channels, rt_uint32_t channel, rt_size_t *index);
+
+
+/**
+ * @brief Read one ADC conversion sequence frame.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the ADC sequence request object.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_read_sequence(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_size_t *read_count);
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Start one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the ADC stream configuration object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_start(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg);
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Start one FIFO ADC stream with strict frame-aligned buffer layout.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the ADC stream configuration object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_start_frame_aligned_fifo(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg);
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Read converted samples from an active ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Number of samples to read.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ */
+rt_ssize_t rt_adc_stream_read(rt_adc_device_t device, rt_uint32_t *buffer, rt_size_t sample_count, rt_int32_t timeout_ms);
+
+/**
+ * @brief Cancel a blocked ADC stream reader before stream shutdown.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_cancel(rt_adc_device_t device);
+
+/**
+ * @brief Stop one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_stop(rt_adc_device_t device);
+
+/**
+ * @brief Notify the ADC framework of one stream backend event.
+ * @param device Pointer to the ADC device object.
+ * @param event ADC stream event reported by the backend or ISR.
+ * @param sample_buffer Pointer to the completed sample buffer block.
+ * @param sample_count Number of samples in @p sample_buffer.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_stream_isr(rt_adc_device_t device, enum rt_adc_stream_event event,
+ const rt_uint32_t *sample_buffer, rt_size_t sample_count);
+#endif /* RT_ADC_USING_STREAM */
+
+/**
+ * @brief Convert one raw ADC sample to millivolts.
+ * @param raw_value Raw ADC sample value.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param voltage_mv Pointer to the output voltage in millivolts.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_raw_to_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv, rt_uint8_t resolution_bits, rt_uint32_t *voltage_mv);
+
+/**
+ * @brief Convert one ADC raw sample to a scaled voltage in millivolts.
+ * @param raw_value Raw ADC sample code.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param scale_num Voltage scaling numerator.
+ * @param scale_den Voltage scaling denominator.
+ * @param voltage_mv Pointer to the output scaled voltage in millivolts.
+ * @return Operation status.
+ *
+ * The scaled voltage is calculated as:
+ *
+ * raw_to_voltage_mv(raw_value) * scale_num / scale_den
+ *
+ * @note @p scale_num and @p scale_den may describe any positive rational
+ * scaling ratio that does not overflow the internal calculation. The
+ * final scaled millivolt value must fit in rt_uint32_t.
+ *
+ * @retval RT_EOK The conversion completed successfully.
+ * @retval -RT_EINVAL A pointer is null, a scale argument is zero, the ADC
+ * conversion arguments are invalid, or the scaled result overflows
+ * rt_uint32_t.
+ */
+rt_err_t rt_adc_raw_to_scaled_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv, rt_uint8_t resolution_bits, rt_uint32_t scale_num, rt_uint32_t scale_den, rt_uint32_t *voltage_mv);
+
+/**
+ * @brief Read ADC samples and convert them to millivolts.
+ * @param device Pointer to the ADC device object.
+ * @param voltages_mv Pointer to the output voltage buffer in millivolts.
+ * @param size Output voltage buffer length.
+ * @param timeout_ms Per-sample timeout in milliseconds; negative means wait forever.
+ * @return Operation status.
+ *
+ * @note This API reads the currently configured ADC session and does not modify
+ * the active channel mask. If default_vref_mv is zero, callers that need
+ * backend-calculated VDDA must include the backend VREF channel in the
+ * configured session. The VREF sample is used only as the reference for
+ * conversion and is not auto-inserted by this API.
+ */
+rt_err_t rt_adc_voltage(rt_adc_device_t device, rt_uint32_t *voltages_mv, rt_size_t size, rt_int32_t timeout_ms);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __ADC_V2_H__ */
diff --git a/components/drivers/include/drivers/adc_v2_trigger.h b/components/drivers/include/drivers/adc_v2_trigger.h
new file mode 100644
index 0000000000..bb328b8dba
--- /dev/null
+++ b/components/drivers/include/drivers/adc_v2_trigger.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-20 wdfk-prog add ADC V2 trigger declarations
+ */
+
+/**
+ * @file adc_v2_trigger.h
+ * @brief ADC V2 trigger framework declarations.
+ */
+
+#ifndef __ADC_V2_TRIGGER_H__
+#define __ADC_V2_TRIGGER_H__
+
+#include <rtconfig.h>
+#include <rtthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#if defined(RT_ADC_USING_TRIGGER)
+struct rt_adc_device;
+struct rt_device;
+
+/**
+ * @brief ADC hardware trigger event type.
+ *
+ * @note A platform backend may support only part of these trigger types.
+ * Unsupported trigger types shall be rejected by the framework or backend
+ * with an error such as -RT_ENOSYS.
+ */
+enum rt_adc_trigger_type
+{
+ RT_ADC_TRIGGER_TIMER_UPDATE = 0, /**< Use a timer update/TRGO event. */
+ RT_ADC_TRIGGER_TIMER_COMPARE, /**< Use a timer compare event. */
+ RT_ADC_TRIGGER_PWM_EDGE, /**< Use a PWM edge event. */
+ RT_ADC_TRIGGER_EXTI_EDGE, /**< Use an external interrupt edge event. */
+ RT_ADC_TRIGGER_ANALOG_COMPARE, /**< Use an analog comparator event. */
+ RT_ADC_TRIGGER_BACKEND, /**< Use a backend-specific trigger event. */
+};
+
+/**
+ * @brief ADC trigger edge selector.
+ */
+enum rt_adc_trigger_edge
+{
+ RT_ADC_TRIGGER_EDGE_NONE = 0, /**< No edge selection is required. */
+ RT_ADC_TRIGGER_EDGE_RISING, /**< Use the rising edge. */
+ RT_ADC_TRIGGER_EDGE_FALLING, /**< Use the falling edge. */
+ RT_ADC_TRIGGER_EDGE_BOTH, /**< Use both rising and falling edges. */
+};
+
+/**
+ * @brief Timer-based ADC trigger event.
+ */
+struct rt_adc_trigger_timer_event
+{
+ struct rt_device *timer; /**< Timer device used as trigger source. */
+ rt_uint32_t freq_hz; /**< Timer trigger output frequency in hertz. */
+ rt_uint16_t channel; /**< Timer channel, or 0 for update/TRGO event. */
+ rt_uint16_t reserved; /**< Reserved for future extension. */
+};
+
+/**
+ * @brief PWM-based ADC trigger event.
+ */
+struct rt_adc_trigger_pwm_event
+{
+ struct rt_device *pwm; /**< PWM device used as trigger source. */
+ rt_uint16_t channel; /**< PWM channel. */
+ enum rt_adc_trigger_edge edge; /**< PWM edge used as trigger event. */
+};
+
+/**
+ * @brief External interrupt ADC trigger event.
+ */
+struct rt_adc_trigger_exti_event
+{
+ struct rt_device *pin; /**< Pin or GPIO controller device. */
+ rt_uint16_t line; /**< External interrupt line or pin number. */
+ enum rt_adc_trigger_edge edge; /**< Interrupt edge used as trigger event. */
+};
+
+/**
+ * @brief Analog comparator ADC trigger event.
+ */
+struct rt_adc_trigger_compare_event
+{
+ struct rt_device *comparator; /**< Optional comparator device used as trigger source. */
+ rt_uint16_t channel; /**< Comparator channel or instance index. */
+ enum rt_adc_trigger_edge edge; /**< Comparator output edge used as trigger event. */
+};
+
+/**
+ * @brief Backend-specific ADC trigger event.
+ */
+struct rt_adc_trigger_backend_event
+{
+ rt_uint32_t selector; /**< Backend-specific trigger selector. */
+ rt_uint32_t edge; /**< Backend-specific edge selector. */
+ const void *data; /**< Optional backend-specific configuration. */
+ rt_size_t data_size; /**< Size of backend-specific configuration. */
+};
+
+/**
+ * @brief ADC trigger configuration.
+ */
+struct rt_adc_trigger_cfg
+{
+ enum rt_adc_trigger_type type; /**< Trigger event type. */
+ rt_uint32_t flags; /**< Common trigger flags, reserved for future extension. */
+
+ union
+ {
+ struct rt_adc_trigger_timer_event timer; /**< Timer trigger event. */
+ struct rt_adc_trigger_pwm_event pwm; /**< PWM trigger event. */
+ struct rt_adc_trigger_exti_event exti; /**< External interrupt trigger event. */
+ struct rt_adc_trigger_compare_event compare; /**< Comparator trigger event. */
+ struct rt_adc_trigger_backend_event backend; /**< Backend-specific trigger event. */
+ } event; /**< Trigger event payload. */
+};
+
+/**
+ * @brief ADC trigger runtime control block.
+ */
+struct rt_adc_trigger_ctrl
+{
+ struct rt_adc_trigger_cfg cfg; /**< Cached device-level hardware trigger configuration. */
+ rt_bool_t configured; /**< Whether cfg contains a cached hardware trigger request. */
+};
+
+/**
+ * @brief Set one device-level ADC hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the ADC trigger configuration object.
+ * @return Operation status.
+ * @note This function only checks the framework-level trigger structure and
+ * caches the request. It does not write ADC trigger registers immediately.
+ * Backend compatibility is reported when the cached trigger is
+ * preconfigured before a conversion hardware configuration.
+ */
+rt_err_t rt_adc_trigger_set(struct rt_adc_device *device, const struct rt_adc_trigger_cfg *cfg);
+
+/**
+ * @brief Clear the cached device-level ADC hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ * @note This function does not write ADC trigger registers immediately. The
+ * next conversion hardware configuration preconfigures the backend
+ * software/default trigger state.
+ */
+rt_err_t rt_adc_trigger_clear(struct rt_adc_device *device);
+
+/**
+ * @brief Check whether one ADC device has a cached hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @return RT_TRUE if a hardware trigger request is set, otherwise RT_FALSE.
+ */
+rt_bool_t rt_adc_trigger_is_set(struct rt_adc_device *device);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __ADC_V2_TRIGGER_H__ */
diff --git a/components/drivers/include/drivers/clock_time.h b/components/drivers/include/drivers/clock_time.h
index 7374b97d00..c43c079eb0 100644
--- a/components/drivers/include/drivers/clock_time.h
+++ b/components/drivers/include/drivers/clock_time.h
@@ -47,9 +47,41 @@ typedef enum
CLOCK_TIMER_CTRL_FREQ_SET = RT_DEVICE_CTRL_BASE(Timer) + 0x01,
CLOCK_TIMER_CTRL_STOP = RT_DEVICE_CTRL_BASE(Timer) + 0x02,
CLOCK_TIMER_CTRL_INFO_GET = RT_DEVICE_CTRL_BASE(Timer) + 0x03,
- CLOCK_TIMER_CTRL_MODE_SET = RT_DEVICE_CTRL_BASE(Timer) + 0x04
+ CLOCK_TIMER_CTRL_MODE_SET = RT_DEVICE_CTRL_BASE(Timer) + 0x04,
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+ CLOCK_TIMER_CTRL_TRIGGER_CONFIG = RT_DEVICE_CTRL_BASE(Timer) + 0x05,
+ CLOCK_TIMER_CTRL_TRIGGER_START = RT_DEVICE_CTRL_BASE(Timer) + 0x06,
+ CLOCK_TIMER_CTRL_TRIGGER_STOP = RT_DEVICE_CTRL_BASE(Timer) + 0x07,
+ CLOCK_TIMER_CTRL_TRIGGER_RELEASE = RT_DEVICE_CTRL_BASE(Timer) + 0x08
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
} rt_clock_timer_ctrl_t;
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+/**
+ * @brief Hardware timer trigger output event type.
+ */
+enum rt_clock_timer_trigger_event
+{
+ CLOCK_TIMER_TRIGGER_EVENT_UPDATE = 0, /**< Generate trigger on timer update/overflow event. */
+ CLOCK_TIMER_TRIGGER_EVENT_COMPARE, /**< Generate trigger on timer compare match event. */
+ CLOCK_TIMER_TRIGGER_EVENT_PWM_EDGE, /**< Generate trigger on timer PWM output reference edge. */
+ CLOCK_TIMER_TRIGGER_EVENT_BACKEND /**< Generate trigger from a backend-specific timer event. */
+};
+
+/**
+ * @brief Hardware trigger output configuration.
+ * @note This interface does not arbitrate timer ownership. Callers must
+ * provide a timer dedicated to hardware trigger output.
+ */
+struct rt_clock_timer_trigger_cfg
+{
+ rt_uint32_t freq_hz; /**< Trigger output frequency in hertz. */
+ enum rt_clock_timer_trigger_event event; /**< Timer event used as trigger output. */
+ rt_uint16_t channel; /**< Timer compare channel, or 0 for update event. */
+ rt_uint16_t reserved; /**< Reserved for future extension. */
+};
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+
typedef enum
{
CLOCK_TIMER_MODE_ONESHOT = 0x01,
diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h
index e6eb43bc57..a9da14d322 100644
--- a/components/drivers/include/rtdevice.h
+++ b/components/drivers/include/rtdevice.h
@@ -272,9 +272,11 @@ extern "C" {
#include "drivers/dev_audio.h"
#endif /* RT_USING_AUDIO */
-#ifdef RT_USING_ADC
+#ifdef RT_USING_ADC_V2
+#include "drivers/adc_v2.h"
+#elif defined(RT_USING_ADC)
#include "drivers/adc.h"
-#endif /* RT_USING_ADC */
+#endif /* RT_USING_ADC_V2 */
#ifdef RT_USING_DAC
#include "drivers/dac.h"
diff --git a/components/drivers/misc/Kconfig b/components/drivers/misc/Kconfig
index 0a7788bce5..71f42ee67b 100644
--- a/components/drivers/misc/Kconfig
+++ b/components/drivers/misc/Kconfig
@@ -1,11 +1,3 @@
-menuconfig RT_USING_ADC
- bool "Using ADC device drivers"
- default n
-
-if RT_USING_DM && RT_USING_ADC
- osource "$(SOC_DM_ADC_DIR)/Kconfig"
-endif
-
config RT_USING_DAC
bool "Using DAC device drivers"
default n
diff --git a/components/drivers/misc/SConscript b/components/drivers/misc/SConscript
index 5b74ac93e5..23727a5ce9 100644
--- a/components/drivers/misc/SConscript
+++ b/components/drivers/misc/SConscript
@@ -5,9 +5,6 @@ src = []
CPPPATH = [cwd + '/../include']
group = []
-if GetDepend(['RT_USING_ADC']):
- src = src + ['adc.c']
-
if GetDepend(['RT_USING_DAC']):
src = src + ['dac.c']