Skip to content

Commit a4be436

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 3ba09ba commit a4be436

File tree

4 files changed

+263
-0
lines changed

4 files changed

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