Skip to content

Commit 402bc78

Browse files
andre-stefanovjilaypandya
authored andcommitted
tests: added unit tests for stepper ramp api
testing following criteria: - distance profile - first acceleration interval - next step intervals Signed-off-by: Andre Stefanov <[email protected]>
1 parent 2946e18 commit 402bc78

File tree

4 files changed

+259
-0
lines changed

4 files changed

+259
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Andre Stefanov
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(ramp)
7+
8+
target_sources(app PRIVATE src/main.c)

tests/drivers/stepper/ramp/prj.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Andre Stefanov
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
CONFIG_ZTEST=y
5+
CONFIG_TEST=y
6+
CONFIG_TEST_USERSPACE=y
7+
CONFIG_LOG=y
8+
CONFIG_STEPPER_LOG_LEVEL_DBG=y
9+
CONFIG_STEPPER=y
10+
CONFIG_STEPPER_RAMP=y
11+
CONFIG_STEPPER_RAMP_TRAPEZOIDAL=y
12+
CONFIG_POLL=y
13+
14+
CONFIG_MAIN_STACK_SIZE=8192
15+
CONFIG_FPU=y

tests/drivers/stepper/ramp/src/main.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 Andre Stefanov
3+
* SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <math.h>
8+
#include <zephyr/ztest.h>
9+
10+
#include "../../../drivers/stepper/ramp/ramp.h"
11+
12+
#include <zephyr/logging/log.h>
13+
LOG_MODULE_REGISTER(ramp, CONFIG_STEPPER_LOG_LEVEL);
14+
15+
#define RAMP_PROFILE(accel, velocity, decel) \
16+
{ \
17+
.acceleration = accel, \
18+
.max_velocity = velocity, \
19+
.deceleration = decel, \
20+
}
21+
22+
#define DISTANCE_PROFILE(accel, const_velocity, decel) \
23+
{ \
24+
.acceleration = accel, \
25+
.const_speed = const_velocity, \
26+
.deceleration = decel, \
27+
}
28+
29+
#define RAMP_RUNTIME_DATA(actual_pos, target_pos, pre_decel_steps, threshold, state) \
30+
(struct stepper_ramp_runtime_data) \
31+
{ \
32+
.ramp_actual_position = actual_pos, .ramp_target_position = target_pos, \
33+
.pre_deceleration_steps = pre_decel_steps, \
34+
.ramp_stop_step_interval_threshold_in_ns = threshold, .current_ramp_state = state, \
35+
}
36+
37+
#define RAMP_TEST_PARAMS(accel, velocity, decel, pre_decel, steps) \
38+
(struct test_params) \
39+
{ \
40+
.acceleration = accel, .max_velocity = velocity, .deceleration = decel, \
41+
.pre_deceleration_steps = pre_decel, .steps_to_move = steps, \
42+
}
43+
44+
struct test_params {
45+
uint16_t acceleration;
46+
uint32_t max_velocity;
47+
uint16_t deceleration;
48+
uint32_t pre_deceleration_steps;
49+
uint32_t steps_to_move;
50+
};
51+
52+
struct reset_ramp_data_expectation {
53+
struct stepper_ramp_distance_profile distance_profile;
54+
};
55+
56+
struct get_next_step_interval_expectation {
57+
uint64_t *intervals;
58+
uint32_t count;
59+
};
60+
61+
static void test_reset_ramp_data(const struct test_params params,
62+
const struct reset_ramp_data_expectation expectation)
63+
{
64+
struct stepper_ramp_common common = {
65+
.ramp_profile =
66+
RAMP_PROFILE(params.acceleration, params.max_velocity, params.deceleration),
67+
.ramp_distance_profile = DISTANCE_PROFILE(0, 0, 0),
68+
};
69+
70+
struct stepper_ramp_config config = {
71+
.pre_deceleration_steps = params.pre_deceleration_steps,
72+
};
73+
74+
trapezoidal_ramp_api.reset_ramp_runtime_data(&config, &common, params.steps_to_move);
75+
76+
trapezoidal_ramp_api.recalculate_ramp(&common, params.steps_to_move);
77+
78+
zexpect_equal(common.ramp_distance_profile.acceleration +
79+
common.ramp_distance_profile.const_speed +
80+
common.ramp_distance_profile.deceleration,
81+
params.steps_to_move, "Expected total steps %d but got %d",
82+
params.steps_to_move,
83+
common.ramp_distance_profile.acceleration +
84+
common.ramp_distance_profile.const_speed +
85+
common.ramp_distance_profile.deceleration);
86+
87+
zexpect_equal(common.ramp_distance_profile.acceleration,
88+
expectation.distance_profile.acceleration,
89+
"Expected acceleration %d but got %d",
90+
expectation.distance_profile.acceleration,
91+
common.ramp_distance_profile.acceleration);
92+
93+
zexpect_equal(
94+
common.ramp_distance_profile.const_speed, expectation.distance_profile.const_speed,
95+
"Expected const_speed %d but got %d", expectation.distance_profile.const_speed,
96+
common.ramp_distance_profile.const_speed);
97+
98+
zexpect_equal(common.ramp_distance_profile.deceleration,
99+
expectation.distance_profile.deceleration,
100+
"Expected deceleration %d but got %d",
101+
expectation.distance_profile.deceleration,
102+
common.ramp_distance_profile.deceleration);
103+
}
104+
105+
ZTEST_SUITE(ramp, NULL, NULL, NULL, NULL, NULL);
106+
107+
ZTEST(ramp, test_ramp_distance_profile)
108+
{
109+
/** motion of 0 steps should not result in steps in any ramp phase */
110+
LOG_DBG("Test zero steps");
111+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 0),
112+
(struct reset_ramp_data_expectation){
113+
.distance_profile = DISTANCE_PROFILE(0, 0, 0),
114+
});
115+
116+
/** motion of 1 step should result in acceleration of 1 step if the requested velocity is
117+
* higher than the start velocity of the ramp.
118+
*/
119+
LOG_DBG("Test 1 fast step");
120+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 1),
121+
(struct reset_ramp_data_expectation){
122+
.distance_profile = DISTANCE_PROFILE(1, 0, 0),
123+
});
124+
125+
/** motion of 1 step should result in a single constant speed step if the requested
126+
* velocityis lower than the start velocity of the ramp.
127+
*/
128+
LOG_DBG("Test 1 slow step");
129+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 1, 1000, 0, 1),
130+
(struct reset_ramp_data_expectation){
131+
.distance_profile = DISTANCE_PROFILE(0, 1, 0),
132+
});
133+
134+
LOG_DBG("Test 2 steps");
135+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 2),
136+
(struct reset_ramp_data_expectation){
137+
.distance_profile = DISTANCE_PROFILE(1, 0, 1),
138+
});
139+
140+
LOG_DBG("Test 3 steps");
141+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 3),
142+
(struct reset_ramp_data_expectation){
143+
.distance_profile = DISTANCE_PROFILE(2, 0, 1),
144+
});
145+
146+
LOG_DBG("Test 100000 steps");
147+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 1000),
148+
(struct reset_ramp_data_expectation){
149+
.distance_profile = DISTANCE_PROFILE(500, 0, 500),
150+
});
151+
152+
LOG_DBG("Test 100001 steps");
153+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 1001),
154+
(struct reset_ramp_data_expectation){
155+
.distance_profile = DISTANCE_PROFILE(501, 0, 500),
156+
});
157+
158+
LOG_DBG("Test 200000 steps");
159+
test_reset_ramp_data(RAMP_TEST_PARAMS(1000, 10000, 1000, 0, 110000),
160+
(struct reset_ramp_data_expectation){
161+
.distance_profile = DISTANCE_PROFILE(50000, 10000, 50000),
162+
});
163+
}
164+
165+
ZTEST(ramp, test_first_interval)
166+
{
167+
uint32_t test_accelerations[] = {1, 100, 1000, UINT32_MAX};
168+
169+
for (size_t i = 0; i < ARRAY_SIZE(test_accelerations); i++) {
170+
LOG_DBG("Test acceleration %u steps/s/s", test_accelerations[i]);
171+
const uint64_t start_interval =
172+
trapezoidal_ramp_api.calculate_start_interval(test_accelerations[i]);
173+
LOG_DBG("Start interval in ns: %llu", start_interval);
174+
175+
/** see AVR446 section 2.3.1, exact calculations of the inter-step delay */
176+
const double ideal_start_interval =
177+
NSEC_PER_SEC * sqrt(2.0 / test_accelerations[i]) * 0.676;
178+
LOG_DBG("Ideal start interval in ns: %f", ideal_start_interval);
179+
180+
const double ratio = fabs((double)start_interval / ideal_start_interval);
181+
182+
zassert_within(ratio, 1.0, 0.01);
183+
}
184+
185+
LOG_DBG("Test acceleration %u steps/s/s", 0);
186+
const uint64_t invalid_interval = trapezoidal_ramp_api.calculate_start_interval(0);
187+
188+
zassert_equal(invalid_interval, UINT64_MAX);
189+
}
190+
191+
ZTEST(ramp, test_get_next_step_interval)
192+
{
193+
struct stepper_ramp_common common = {
194+
.ramp_profile = RAMP_PROFILE(1000, 10000, 1000),
195+
.ramp_distance_profile = DISTANCE_PROFILE(0, 0, 0),
196+
.ramp_runtime_data = RAMP_RUNTIME_DATA(0, 0, 0, 0, STEPPER_RAMP_STATE_NOT_MOVING),
197+
};
198+
199+
const struct stepper_ramp_config config = {
200+
.pre_deceleration_steps = 0,
201+
};
202+
203+
trapezoidal_ramp_api.reset_ramp_runtime_data(&config, &common, 1000);
204+
trapezoidal_ramp_api.recalculate_ramp(&common, 1000);
205+
206+
uint64_t intervals[500] = {0};
207+
208+
intervals[0] = trapezoidal_ramp_api.calculate_start_interval(1000);
209+
210+
for (uint32_t i = 1; i < 500; i++) {
211+
intervals[i] = trapezoidal_ramp_api.get_next_step_interval(
212+
&common, intervals[i - 1], STEPPER_RUN_MODE_POSITION);
213+
}
214+
215+
for (uint32_t i = 1; i < 500; i++) {
216+
LOG_DBG("Compare %llu vs %llu", intervals[i - 1], intervals[i]);
217+
zexpect(intervals[i - 1] > intervals[i],
218+
"Acceleration intervals have to decrease over time");
219+
}
220+
221+
zexpect(NSEC_PER_SEC / 1000 < intervals[499],
222+
"Last ramp interval has to be smaller than the requested velocity");
223+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Andre Stefanov
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
common:
5+
tags:
6+
- drivers
7+
- stepper
8+
- ramp
9+
tests:
10+
drivers.stepper.ramp:
11+
platform_allow:
12+
- qemu_x86_64/atom
13+
- native_sim/native/64

0 commit comments

Comments
 (0)