Skip to content

Commit 12d8d44

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 12d8d44

File tree

4 files changed

+262
-0
lines changed

4 files changed

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