summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCYFS <[email protected]>2026-08-26 11:02:37 +0800
committerRbb666 <[email protected]>2026-09-01 09:33:32 +0800
commitf62b0ad85d9482f460338e718d9a892b69de9fe8 (patch)
tree1b469df5b26e65a1c57421320d1e128d48b5245c
parent7a2af55ffb3632b73ebfc29f01bf9889ed9d1979 (diff)
[utest][pwm] Add configurable PWM matrix test
-rw-r--r--components/drivers/misc/SConscript6
-rw-r--r--components/drivers/misc/utest/Kconfig39
-rw-r--r--components/drivers/misc/utest/SConscript13
-rw-r--r--components/drivers/misc/utest/pwm_tc.c629
-rw-r--r--components/drivers/misc/utest/pwm_tc_config.h177
-rw-r--r--components/utilities/utest/Kconfig1
6 files changed, 865 insertions, 0 deletions
diff --git a/components/drivers/misc/SConscript b/components/drivers/misc/SConscript
index 23727a5ce9..83494a6bab 100644
--- a/components/drivers/misc/SConscript
+++ b/components/drivers/misc/SConscript
@@ -1,3 +1,4 @@
+import os
from building import *
cwd = GetCurrentDir()
@@ -29,4 +30,9 @@ if GetDepend(['RT_USING_RANDOM']):
if len(src):
group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+list = os.listdir(cwd)
+for item in list:
+ if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
+ group = group + SConscript(os.path.join(item, 'SConscript'))
+
Return('group')
diff --git a/components/drivers/misc/utest/Kconfig b/components/drivers/misc/utest/Kconfig
new file mode 100644
index 0000000000..f8f8f873e4
--- /dev/null
+++ b/components/drivers/misc/utest/Kconfig
@@ -0,0 +1,39 @@
+menu "PWM Test"
+
+config RT_UTEST_PWM
+ bool "PWM Frequency and Duty-cycle Matrix Test"
+ default n
+ select RT_USING_PWM
+ help
+ Run configurable PWM groups. Channels in one group run concurrently,
+ while groups run in order. Each channel can keep a fixed output, sweep
+ once and hold its last point, or repeat its sweep for the group duration.
+
+if RT_UTEST_PWM
+
+config RT_PWM_TC_CONFIG_FILE
+ string "PWM test configuration header"
+ default "pwm_tc_config.h"
+ help
+ The header defines PWM_TC_GROUPS and PWM_TC_GROUP_COUNT. Keep the
+ default for the built-in example, or provide a board-specific header
+ from a compiler include path. Channel enable switches, device names,
+ channel numbers, output modes, and waveform points are kept in that
+ header because Kconfig cannot represent dynamic group/channel arrays.
+
+config RT_PWM_TC_MAX_CHANNELS
+ int "Maximum concurrent channels in one PWM test group"
+ range 1 64
+ default 8
+ help
+ This limits enabled channels, not the total number of configured and
+ disabled channel entries in a group.
+
+config RT_PWM_TC_TIMEOUT
+ int "PWM test timeout in seconds"
+ range 1 3600
+ default 30
+
+endif
+
+endmenu
diff --git a/components/drivers/misc/utest/SConscript b/components/drivers/misc/utest/SConscript
new file mode 100644
index 0000000000..5dbf8b4629
--- /dev/null
+++ b/components/drivers/misc/utest/SConscript
@@ -0,0 +1,13 @@
+Import('rtconfig')
+from building import *
+
+cwd = GetCurrentDir()
+src = []
+CPPPATH = [cwd, cwd + '/../../include']
+
+if GetDepend(['RT_UTEST_PWM']):
+ src += ['pwm_tc.c']
+
+group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_PWM'], CPPPATH = CPPPATH)
+
+Return('group')
diff --git a/components/drivers/misc/utest/pwm_tc.c b/components/drivers/misc/utest/pwm_tc.c
new file mode 100644
index 0000000000..d4f0ab66f5
--- /dev/null
+++ b/components/drivers/misc/utest/pwm_tc.c
@@ -0,0 +1,629 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-08-25 RT-Thread add configurable PWM matrix utest
+ */
+
+/**
+ * Test Case Name: PWM Frequency and Duty-cycle Matrix Test
+ *
+ * Test Objectives:
+ * - Validate PWM set, enable, optional get, and disable operations over a
+ * configurable frequency and duty-cycle matrix.
+ * - Run multiple PWM channels concurrently and multiple test groups in order.
+ * - Support fixed output, one-shot sweep with final-value hold, and repeated
+ * sweep modes independently for every channel.
+ *
+ * Dependencies:
+ * - Requires RT_UTEST_PWM and board PWM devices/pinmux matching the selected
+ * configuration header.
+ * - Channels that share one hardware timer period must use compatible
+ * frequencies while they are active in the same group.
+ * - Physical frequency and duty-cycle accuracy still requires an oscilloscope,
+ * logic analyzer, or external capture input.
+ */
+
+#include <limits.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include "utest.h"
+
+#ifdef RT_UTEST_PWM
+
+#define PWM_TC_NSEC_PER_SEC 1000000000ULL
+#define PWM_TC_DUTY_SCALE 10000U
+#define PWM_TC_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+#define PWM_TC_POINT(frequency, duty) \
+ { (frequency), (duty) }
+#define PWM_TC_POINT_PERCENT(frequency, duty_percent) \
+ PWM_TC_POINT((frequency), (duty_percent) * 100U)
+#define PWM_TC_CHANNEL_CONFIG(is_enabled, device, channel_number, output_mode, \
+ point_table, interval_ms, tolerance) \
+ { \
+ (is_enabled), (device), (channel_number), (output_mode), \
+ (point_table), PWM_TC_ARRAY_SIZE(point_table), (interval_ms), \
+ (tolerance) \
+ }
+
+enum pwm_tc_mode
+{
+ PWM_TC_MODE_FIXED = 0,
+ PWM_TC_MODE_SWEEP_ONCE,
+ PWM_TC_MODE_SWEEP_LOOP,
+};
+
+struct pwm_tc_point
+{
+ rt_uint32_t frequency_hz;
+ rt_uint32_t duty_permyriad;
+};
+
+struct pwm_tc_channel
+{
+ rt_bool_t enabled;
+ const char *device_name;
+ int channel;
+ enum pwm_tc_mode mode;
+ const struct pwm_tc_point *points;
+ rt_size_t point_count;
+ rt_uint32_t step_interval_ms;
+ rt_uint32_t readback_tolerance;
+};
+
+struct pwm_tc_group
+{
+ const char *name;
+ const struct pwm_tc_channel *channels;
+ rt_size_t channel_count;
+ rt_uint32_t duration_ms;
+};
+
+#include RT_PWM_TC_CONFIG_FILE
+
+#ifndef PWM_TC_GROUPS
+#error "The PWM test configuration must define PWM_TC_GROUPS"
+#endif
+
+#ifndef PWM_TC_GROUP_COUNT
+#error "The PWM test configuration must define PWM_TC_GROUP_COUNT"
+#endif
+
+struct pwm_tc_runtime
+{
+ const struct pwm_tc_channel *config;
+ struct rt_device_pwm *device;
+ rt_size_t point_index;
+ rt_tick_t last_update_tick;
+ rt_bool_t enabled;
+};
+
+static struct pwm_tc_runtime pwm_tc_runtime[RT_PWM_TC_MAX_CHANNELS];
+static rt_size_t pwm_tc_active_count;
+
+static rt_tick_t pwm_tc_millisecond_to_tick(rt_uint32_t millisecond)
+{
+ rt_tick_t tick;
+
+ tick = rt_tick_from_millisecond(millisecond);
+ if (tick == 0)
+ {
+ tick = 1;
+ }
+
+ return tick;
+}
+
+static rt_uint32_t pwm_tc_channel_number(int channel)
+{
+ if (channel < 0)
+ {
+ return (rt_uint32_t)(-channel);
+ }
+
+ return (rt_uint32_t)channel;
+}
+
+static rt_err_t pwm_tc_point_to_output(const struct pwm_tc_point *point,
+ rt_uint32_t *period,
+ rt_uint32_t *pulse)
+{
+ rt_uint64_t calculated_period;
+ rt_uint64_t calculated_pulse;
+
+ if (point->frequency_hz == 0 || point->duty_permyriad > PWM_TC_DUTY_SCALE)
+ {
+ return -RT_EINVAL;
+ }
+
+ calculated_period = (PWM_TC_NSEC_PER_SEC + point->frequency_hz / 2U) /
+ point->frequency_hz;
+ if (calculated_period == 0 || calculated_period > 0xFFFFFFFFULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ calculated_pulse = (calculated_period * point->duty_permyriad +
+ PWM_TC_DUTY_SCALE / 2U) /
+ PWM_TC_DUTY_SCALE;
+ if (calculated_pulse > calculated_period)
+ {
+ calculated_pulse = calculated_period;
+ }
+
+ *period = (rt_uint32_t)calculated_period;
+ *pulse = (rt_uint32_t)calculated_pulse;
+
+ return RT_EOK;
+}
+
+static rt_bool_t pwm_tc_value_in_tolerance(rt_uint32_t actual,
+ rt_uint32_t expected,
+ rt_uint32_t tolerance)
+{
+ rt_uint64_t difference;
+
+ if (expected == 0)
+ {
+ return actual == 0;
+ }
+
+ difference = (actual > expected) ? (actual - expected) : (expected - actual);
+
+ return difference * PWM_TC_DUTY_SCALE <=
+ (rt_uint64_t)expected * tolerance;
+}
+
+static rt_err_t pwm_tc_verify_output(struct pwm_tc_runtime *runtime,
+ rt_uint32_t period,
+ rt_uint32_t pulse)
+{
+ rt_err_t result;
+ struct rt_pwm_configuration configuration;
+ const struct pwm_tc_channel *channel;
+
+ channel = runtime->config;
+ if (channel->readback_tolerance == 0)
+ {
+ return RT_EOK;
+ }
+
+ rt_memset(&configuration, 0, sizeof(configuration));
+ configuration.channel = pwm_tc_channel_number(channel->channel);
+ result = rt_pwm_get(runtime->device, &configuration);
+ if (result != RT_EOK)
+ {
+ LOG_E("PWM readback failed: device=%s channel=%d result=%d",
+ channel->device_name, channel->channel, result);
+ return result;
+ }
+
+ if (!pwm_tc_value_in_tolerance(configuration.period, period,
+ channel->readback_tolerance) ||
+ !pwm_tc_value_in_tolerance(configuration.pulse, pulse,
+ channel->readback_tolerance))
+ {
+ LOG_E("PWM readback mismatch: device=%s channel=%d expected=%u/%u ns actual=%u/%u ns",
+ channel->device_name, channel->channel,
+ period, pulse, configuration.period, configuration.pulse);
+ return -RT_ERROR;
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t pwm_tc_apply_point(struct pwm_tc_runtime *runtime,
+ rt_size_t point_index)
+{
+ rt_err_t result;
+ rt_uint32_t period;
+ rt_uint32_t pulse;
+ const struct pwm_tc_channel *channel;
+ const struct pwm_tc_point *point;
+
+ channel = runtime->config;
+ point = &channel->points[point_index];
+ result = pwm_tc_point_to_output(point, &period, &pulse);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = rt_pwm_set(runtime->device, channel->channel, period, pulse);
+ if (result != RT_EOK)
+ {
+ LOG_E("PWM set failed: device=%s channel=%d frequency=%u Hz duty=%u.%02u%% result=%d",
+ channel->device_name, channel->channel, point->frequency_hz,
+ point->duty_permyriad / 100U, point->duty_permyriad % 100U,
+ result);
+ return result;
+ }
+
+ result = pwm_tc_verify_output(runtime, period, pulse);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ if (!runtime->enabled)
+ {
+ result = rt_pwm_enable(runtime->device, channel->channel);
+ if (result != RT_EOK)
+ {
+ LOG_E("PWM enable failed: device=%s channel=%d result=%d",
+ channel->device_name, channel->channel, result);
+ return result;
+ }
+ runtime->enabled = RT_TRUE;
+ }
+
+ runtime->point_index = point_index;
+ runtime->last_update_tick = rt_tick_get();
+ LOG_I("PWM output: device=%s channel=%d frequency=%u Hz duty=%u.%02u%%",
+ channel->device_name, channel->channel, point->frequency_hz,
+ point->duty_permyriad / 100U, point->duty_permyriad % 100U);
+
+ return RT_EOK;
+}
+
+static rt_bool_t pwm_tc_channel_has_next(const struct pwm_tc_runtime *runtime)
+{
+ const struct pwm_tc_channel *channel;
+
+ channel = runtime->config;
+ if (channel->mode == PWM_TC_MODE_SWEEP_LOOP)
+ {
+ return channel->point_count > 1;
+ }
+ if (channel->mode == PWM_TC_MODE_SWEEP_ONCE)
+ {
+ return runtime->point_index + 1 < channel->point_count;
+ }
+
+ return RT_FALSE;
+}
+
+static rt_size_t pwm_tc_next_point_index(const struct pwm_tc_runtime *runtime)
+{
+ rt_size_t next;
+
+ next = runtime->point_index + 1;
+ if (next >= runtime->config->point_count)
+ {
+ next = 0;
+ }
+
+ return next;
+}
+
+static rt_err_t pwm_tc_stop_active_channels(void)
+{
+ rt_err_t result;
+ rt_err_t operation_result;
+ rt_size_t index;
+ rt_bool_t all_stopped;
+
+ result = RT_EOK;
+ all_stopped = RT_TRUE;
+ for (index = 0; index < pwm_tc_active_count; index++)
+ {
+ if (!pwm_tc_runtime[index].enabled)
+ {
+ continue;
+ }
+
+ operation_result = rt_pwm_disable(pwm_tc_runtime[index].device,
+ pwm_tc_runtime[index].config->channel);
+ if (operation_result != RT_EOK)
+ {
+ LOG_E("PWM disable failed: device=%s channel=%d result=%d",
+ pwm_tc_runtime[index].config->device_name,
+ pwm_tc_runtime[index].config->channel,
+ operation_result);
+ if (result == RT_EOK)
+ {
+ result = operation_result;
+ }
+ all_stopped = RT_FALSE;
+ }
+ else
+ {
+ pwm_tc_runtime[index].enabled = RT_FALSE;
+ }
+ }
+ if (all_stopped)
+ {
+ pwm_tc_active_count = 0;
+ }
+
+ return result;
+}
+
+static rt_err_t pwm_tc_start_group(const struct pwm_tc_group *group)
+{
+ rt_err_t result;
+ rt_size_t index;
+ rt_device_t device;
+ struct pwm_tc_runtime *runtime;
+
+ rt_memset(pwm_tc_runtime, 0, sizeof(pwm_tc_runtime));
+ pwm_tc_active_count = 0;
+
+ for (index = 0; index < group->channel_count; index++)
+ {
+ if (!group->channels[index].enabled)
+ {
+ continue;
+ }
+
+ device = rt_device_find(group->channels[index].device_name);
+ runtime = &pwm_tc_runtime[pwm_tc_active_count];
+ runtime->config = &group->channels[index];
+ runtime->device = (struct rt_device_pwm *)device;
+ pwm_tc_active_count++;
+
+ result = pwm_tc_apply_point(runtime, 0);
+ if (result != RT_EOK)
+ {
+ pwm_tc_stop_active_channels();
+ return result;
+ }
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t pwm_tc_run_group(const struct pwm_tc_group *group)
+{
+ rt_err_t result;
+ rt_err_t stop_result;
+ rt_size_t index;
+ rt_tick_t duration_tick;
+ rt_tick_t elapsed_tick;
+ rt_tick_t interval_tick;
+ rt_tick_t since_update_tick;
+ rt_tick_t sleep_tick;
+ rt_tick_t start_tick;
+
+ result = pwm_tc_start_group(group);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ LOG_I("PWM group started: name=%s enabled=%u configured=%u duration=%u ms",
+ group->name, (unsigned int)pwm_tc_active_count,
+ (unsigned int)group->channel_count, group->duration_ms);
+
+ start_tick = rt_tick_get();
+ duration_tick = pwm_tc_millisecond_to_tick(group->duration_ms);
+ while ((elapsed_tick = rt_tick_get_delta(start_tick)) < duration_tick)
+ {
+ sleep_tick = duration_tick - elapsed_tick;
+ for (index = 0; index < pwm_tc_active_count; index++)
+ {
+ if (!pwm_tc_channel_has_next(&pwm_tc_runtime[index]))
+ {
+ continue;
+ }
+
+ interval_tick = pwm_tc_millisecond_to_tick(
+ pwm_tc_runtime[index].config->step_interval_ms);
+ since_update_tick = rt_tick_get_delta(
+ pwm_tc_runtime[index].last_update_tick);
+ if (since_update_tick >= interval_tick)
+ {
+ result = pwm_tc_apply_point(&pwm_tc_runtime[index],
+ pwm_tc_next_point_index(
+ &pwm_tc_runtime[index]));
+ if (result != RT_EOK)
+ {
+ goto __exit;
+ }
+ since_update_tick = 0;
+ }
+
+ if (interval_tick - since_update_tick < sleep_tick)
+ {
+ sleep_tick = interval_tick - since_update_tick;
+ }
+ }
+
+ if (sleep_tick == 0)
+ {
+ sleep_tick = 1;
+ }
+ rt_thread_delay(sleep_tick);
+ }
+
+__exit:
+ stop_result = pwm_tc_stop_active_channels();
+ if (result == RT_EOK)
+ {
+ result = stop_result;
+ }
+ LOG_I("PWM group finished: name=%s result=%d", group->name, result);
+
+ return result;
+}
+
+static rt_err_t pwm_tc_validate_channel(const struct pwm_tc_channel *channel)
+{
+ rt_err_t result;
+ rt_size_t index;
+ rt_uint32_t period;
+ rt_uint32_t pulse;
+ rt_device_t device;
+
+ if (channel->device_name == RT_NULL || channel->device_name[0] == '\0' ||
+ channel->points == RT_NULL || channel->point_count == 0 ||
+ channel->channel == INT_MIN ||
+ channel->mode < PWM_TC_MODE_FIXED ||
+ channel->mode > PWM_TC_MODE_SWEEP_LOOP ||
+ channel->readback_tolerance > PWM_TC_DUTY_SCALE)
+ {
+ return -RT_EINVAL;
+ }
+ if (channel->mode == PWM_TC_MODE_FIXED && channel->point_count != 1)
+ {
+ return -RT_EINVAL;
+ }
+ if (channel->mode != PWM_TC_MODE_FIXED && channel->point_count > 1 &&
+ channel->step_interval_ms == 0)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = rt_device_find(channel->device_name);
+ if (device == RT_NULL)
+ {
+ LOG_E("PWM device not found: %s", channel->device_name);
+ return -RT_ENOSYS;
+ }
+ if (device->type != RT_Device_Class_PWM)
+ {
+ LOG_E("Device is not PWM: %s type=%d", channel->device_name, device->type);
+ return -RT_EINVAL;
+ }
+
+ for (index = 0; index < channel->point_count; index++)
+ {
+ result = pwm_tc_point_to_output(&channel->points[index], &period, &pulse);
+ if (result != RT_EOK)
+ {
+ LOG_E("Invalid PWM point: device=%s channel=%d point=%u",
+ channel->device_name, channel->channel, (unsigned int)index);
+ return result;
+ }
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t pwm_tc_validate_group(const struct pwm_tc_group *group)
+{
+ rt_err_t result;
+ rt_size_t index;
+ rt_size_t peer_index;
+ rt_size_t enabled_count;
+
+ if (group->name == RT_NULL || group->name[0] == '\0' ||
+ group->channels == RT_NULL || group->channel_count == 0 ||
+ group->duration_ms == 0)
+ {
+ return -RT_EINVAL;
+ }
+
+ enabled_count = 0;
+ for (index = 0; index < group->channel_count; index++)
+ {
+ if (!group->channels[index].enabled)
+ {
+ continue;
+ }
+
+ enabled_count++;
+ if (enabled_count > RT_PWM_TC_MAX_CHANNELS)
+ {
+ LOG_E("Too many enabled PWM channels in group %s: enabled=%u max=%u",
+ group->name, (unsigned int)enabled_count,
+ (unsigned int)RT_PWM_TC_MAX_CHANNELS);
+ return -RT_EINVAL;
+ }
+
+ result = pwm_tc_validate_channel(&group->channels[index]);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ for (peer_index = 0; peer_index < index; peer_index++)
+ {
+ if (!group->channels[peer_index].enabled)
+ {
+ continue;
+ }
+
+ if (group->channels[index].channel ==
+ group->channels[peer_index].channel &&
+ rt_strcmp(group->channels[index].device_name,
+ group->channels[peer_index].device_name) == 0)
+ {
+ LOG_E("Duplicate PWM channel in group %s: device=%s channel=%d",
+ group->name, group->channels[index].device_name,
+ group->channels[index].channel);
+ return -RT_EINVAL;
+ }
+ }
+ }
+
+ if (enabled_count == 0)
+ {
+ LOG_E("No PWM channel enabled in group %s", group->name);
+ return -RT_EINVAL;
+ }
+
+ return RT_EOK;
+}
+
+static void pwm_tc_configuration_matrix(void)
+{
+ rt_err_t result;
+ rt_size_t index;
+
+ for (index = 0; index < PWM_TC_GROUP_COUNT; index++)
+ {
+ result = pwm_tc_run_group(&PWM_TC_GROUPS[index]);
+ uassert_int_equal(result, RT_EOK);
+ if (result != RT_EOK)
+ {
+ return;
+ }
+ }
+}
+
+static rt_err_t utest_tc_init(void)
+{
+ rt_err_t result;
+ rt_size_t index;
+
+ pwm_tc_active_count = 0;
+ if (PWM_TC_GROUP_COUNT == 0)
+ {
+ return -RT_EINVAL;
+ }
+
+ for (index = 0; index < PWM_TC_GROUP_COUNT; index++)
+ {
+ result = pwm_tc_validate_group(&PWM_TC_GROUPS[index]);
+ if (result != RT_EOK)
+ {
+ LOG_E("Invalid PWM group: index=%u result=%d",
+ (unsigned int)index, result);
+ return result;
+ }
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t utest_tc_cleanup(void)
+{
+ return pwm_tc_stop_active_channels();
+}
+
+static void testcase(void)
+{
+ UTEST_UNIT_RUN(pwm_tc_configuration_matrix);
+}
+
+UTEST_TC_EXPORT(testcase,
+ "components.drivers.pwm.configuration_matrix",
+ utest_tc_init,
+ utest_tc_cleanup,
+ RT_PWM_TC_TIMEOUT);
+
+#endif /* RT_UTEST_PWM */
diff --git a/components/drivers/misc/utest/pwm_tc_config.h b/components/drivers/misc/utest/pwm_tc_config.h
new file mode 100644
index 0000000000..6eb0cd40b2
--- /dev/null
+++ b/components/drivers/misc/utest/pwm_tc_config.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-08-25 RT-Thread add the default PWM matrix test configuration
+ */
+
+#ifndef __PWM_TC_CONFIG_H__
+#define __PWM_TC_CONFIG_H__
+
+/*
+ * Channel catalog
+ *
+ * Set ENABLED to RT_TRUE to include a channel in every group below, or
+ * RT_FALSE to skip it. Disabled channels are not looked up or started.
+ * READBACK_TOLERANCE is in permyriad; 0 disables PWM_CMD_GET verification.
+ * The default enables only channel 1, which sweeps continuously in each group.
+ */
+#define PWM_TC_CFG_CHANNEL_1_ENABLED RT_TRUE
+#define PWM_TC_CFG_CHANNEL_1_DEVICE_NAME "pwm12"
+#define PWM_TC_CFG_CHANNEL_1_NUMBER 0
+#define PWM_TC_CFG_CHANNEL_1_READBACK_TOLERANCE 500U
+
+#define PWM_TC_CFG_CHANNEL_2_ENABLED RT_FALSE
+#define PWM_TC_CFG_CHANNEL_2_DEVICE_NAME "pwm2"
+#define PWM_TC_CFG_CHANNEL_2_NUMBER 1
+#define PWM_TC_CFG_CHANNEL_2_READBACK_TOLERANCE 0U
+
+#define PWM_TC_CFG_CHANNEL_3_ENABLED RT_FALSE
+#define PWM_TC_CFG_CHANNEL_3_DEVICE_NAME "pwm3"
+#define PWM_TC_CFG_CHANNEL_3_NUMBER 1
+#define PWM_TC_CFG_CHANNEL_3_READBACK_TOLERANCE 0U
+
+#define PWM_TC_CFG_CHANNEL_4_ENABLED RT_FALSE
+#define PWM_TC_CFG_CHANNEL_4_DEVICE_NAME "pwm4"
+#define PWM_TC_CFG_CHANNEL_4_NUMBER 1
+#define PWM_TC_CFG_CHANNEL_4_READBACK_TOLERANCE 0U
+
+/* Group timing */
+#define PWM_TC_CFG_GROUP_1_DURATION_MS 2000U
+#define PWM_TC_CFG_GROUP_2_DURATION_MS 1000U
+
+/*
+ * Waveform point tables
+ *
+ * PWM_TC_POINT_PERCENT takes frequency in Hz and duty cycle in percent.
+ * PWM_TC_POINT takes frequency in Hz and duty cycle in permyriad.
+ */
+static const struct pwm_tc_point pwm_tc_channel_1_sweep_points[] = {
+ PWM_TC_POINT_PERCENT(2500, 10),
+ PWM_TC_POINT_PERCENT(2500, 50),
+ PWM_TC_POINT_PERCENT(5000, 25),
+ PWM_TC_POINT_PERCENT(5000, 75),
+};
+
+static const struct pwm_tc_point pwm_tc_channel_2_sweep_points[] = {
+ PWM_TC_POINT_PERCENT(500, 20),
+ PWM_TC_POINT_PERCENT(1500, 40),
+ PWM_TC_POINT_PERCENT(4000, 60),
+};
+
+static const struct pwm_tc_point pwm_tc_channel_2_fixed_points[] = {
+ PWM_TC_POINT_PERCENT(10000, 65),
+};
+
+static const struct pwm_tc_point pwm_tc_channel_3_sweep_points[] = {
+ PWM_TC_POINT_PERCENT(800, 15),
+ PWM_TC_POINT_PERCENT(1800, 35),
+ PWM_TC_POINT_PERCENT(3200, 55),
+};
+
+static const struct pwm_tc_point pwm_tc_channel_3_fixed_points[] = {
+ PWM_TC_POINT_PERCENT(6000, 30),
+};
+
+static const struct pwm_tc_point pwm_tc_channel_4_sweep_points[] = {
+ PWM_TC_POINT_PERCENT(1200, 25),
+ PWM_TC_POINT_PERCENT(2400, 50),
+ PWM_TC_POINT_PERCENT(4800, 75),
+};
+
+static const struct pwm_tc_point pwm_tc_channel_4_fixed_points[] = {
+ PWM_TC_POINT_PERCENT(8000, 70),
+};
+
+/*
+ * Group 1 channel behavior
+ *
+ * Channels in a group run concurrently. Each channel selects its own output
+ * mode, point table, and step interval in milliseconds.
+ */
+static const struct pwm_tc_channel pwm_tc_group_1_channels[] = {
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_1_ENABLED,
+ PWM_TC_CFG_CHANNEL_1_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_1_NUMBER,
+ PWM_TC_MODE_SWEEP_LOOP,
+ pwm_tc_channel_1_sweep_points,
+ 200U,
+ PWM_TC_CFG_CHANNEL_1_READBACK_TOLERANCE),
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_2_ENABLED,
+ PWM_TC_CFG_CHANNEL_2_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_2_NUMBER,
+ PWM_TC_MODE_FIXED,
+ pwm_tc_channel_2_fixed_points,
+ 0U,
+ PWM_TC_CFG_CHANNEL_2_READBACK_TOLERANCE),
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_3_ENABLED,
+ PWM_TC_CFG_CHANNEL_3_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_3_NUMBER,
+ PWM_TC_MODE_SWEEP_ONCE,
+ pwm_tc_channel_3_sweep_points,
+ 250U,
+ PWM_TC_CFG_CHANNEL_3_READBACK_TOLERANCE),
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_4_ENABLED,
+ PWM_TC_CFG_CHANNEL_4_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_4_NUMBER,
+ PWM_TC_MODE_FIXED,
+ pwm_tc_channel_4_fixed_points,
+ 0U,
+ PWM_TC_CFG_CHANNEL_4_READBACK_TOLERANCE),
+};
+
+/* Group 2 channel behavior */
+static const struct pwm_tc_channel pwm_tc_group_2_channels[] = {
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_1_ENABLED,
+ PWM_TC_CFG_CHANNEL_1_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_1_NUMBER,
+ PWM_TC_MODE_SWEEP_LOOP,
+ pwm_tc_channel_1_sweep_points,
+ 200U,
+ PWM_TC_CFG_CHANNEL_1_READBACK_TOLERANCE),
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_2_ENABLED,
+ PWM_TC_CFG_CHANNEL_2_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_2_NUMBER,
+ PWM_TC_MODE_SWEEP_ONCE,
+ pwm_tc_channel_2_sweep_points,
+ 300U,
+ PWM_TC_CFG_CHANNEL_2_READBACK_TOLERANCE),
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_3_ENABLED,
+ PWM_TC_CFG_CHANNEL_3_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_3_NUMBER,
+ PWM_TC_MODE_FIXED,
+ pwm_tc_channel_3_fixed_points,
+ 0U,
+ PWM_TC_CFG_CHANNEL_3_READBACK_TOLERANCE),
+ PWM_TC_CHANNEL_CONFIG(PWM_TC_CFG_CHANNEL_4_ENABLED,
+ PWM_TC_CFG_CHANNEL_4_DEVICE_NAME,
+ PWM_TC_CFG_CHANNEL_4_NUMBER,
+ PWM_TC_MODE_SWEEP_LOOP,
+ pwm_tc_channel_4_sweep_points,
+ 350U,
+ PWM_TC_CFG_CHANNEL_4_READBACK_TOLERANCE),
+};
+
+/* Test group list; groups run in array order. */
+static const struct pwm_tc_group pwm_tc_groups[] = {
+ {
+ "mixed-output-loop",
+ pwm_tc_group_1_channels,
+ PWM_TC_ARRAY_SIZE(pwm_tc_group_1_channels),
+ PWM_TC_CFG_GROUP_1_DURATION_MS,
+ },
+ {
+ "mixed-output-sweep",
+ pwm_tc_group_2_channels,
+ PWM_TC_ARRAY_SIZE(pwm_tc_group_2_channels),
+ PWM_TC_CFG_GROUP_2_DURATION_MS,
+ },
+};
+
+#define PWM_TC_GROUPS pwm_tc_groups
+#define PWM_TC_GROUP_COUNT PWM_TC_ARRAY_SIZE(pwm_tc_groups)
+
+#endif /* __PWM_TC_CONFIG_H__ */
diff --git a/components/utilities/utest/Kconfig b/components/utilities/utest/Kconfig
index f2bb5f6f88..456a9e4651 100644
--- a/components/utilities/utest/Kconfig
+++ b/components/utilities/utest/Kconfig
@@ -16,6 +16,7 @@ menu "RT-Thread Utestcases"
rsource "../../../components/drivers/core/utest/Kconfig"
rsource "../../../components/drivers/audio/utest/Kconfig"
rsource "../../../components/drivers/ipc/utest/Kconfig"
+ rsource "../../../components/drivers/misc/utest/Kconfig"
rsource "../../../components/drivers/pin/utest/Kconfig"
rsource "../../../components/drivers/serial/utest/Kconfig"
rsource "../../../components/drivers/smp_call/utest/Kconfig"