Skip to content

Commit 161ed77

Browse files
committed
drivers: stepper: introduce rudimentary ramp control
Introduce rudimentary control using following paper: https://www.boost.org/doc/libs/1_81_0/libs/safe_numerics/example/stepper-motor.pdf Signed-off-by: Jilay Pandya <[email protected]>
1 parent 207a048 commit 161ed77

File tree

14 files changed

+522
-11
lines changed

14 files changed

+522
-11
lines changed

drivers/stepper/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/stepper.h)
77
add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC adi_tmc)
88
add_subdirectory_ifdef(CONFIG_STEPPER_ALLEGRO allegro)
99
add_subdirectory_ifdef(CONFIG_STEPPER_TI ti)
10+
# zephyr-keep-sorted-stop
11+
12+
# zephyr-keep-sorted-start
13+
add_subdirectory_ifdef(CONFIG_STEPPER_RAMP ramp)
1014
add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir)
1115
# zephyr-keep-sorted-stop
1216

drivers/stepper/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ config STEPPER_SHELL
2626

2727
comment "Stepper Driver Common"
2828

29+
rsource "ramp/Kconfig"
2930
rsource "step_dir/Kconfig"
3031

3132
comment "Stepper Drivers"

drivers/stepper/gpio_stepper_controller.c

Lines changed: 133 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
#include <zephyr/drivers/stepper.h>
1313
#include <zephyr/sys/__assert.h>
1414

15+
#ifdef CONFIG_STEPPER_RAMP
16+
#include "ramp/ramp.h"
17+
#endif
18+
1519
#include <zephyr/logging/log.h>
1620
LOG_MODULE_REGISTER(gpio_stepper_motor_controller, CONFIG_STEPPER_LOG_LEVEL);
1721

@@ -26,6 +30,9 @@ static const uint8_t
2630
struct gpio_stepper_config {
2731
const struct gpio_dt_spec *control_pins;
2832
bool invert_direction;
33+
#ifdef CONFIG_STEPPER_RAMP
34+
const struct stepper_ramp_config ramp_config;
35+
#endif
2936
};
3037

3138
struct gpio_stepper_data {
@@ -42,6 +49,9 @@ struct gpio_stepper_data {
4249
bool is_enabled;
4350
stepper_event_callback_t callback;
4451
void *event_cb_user_data;
52+
#ifdef CONFIG_STEPPER_RAMP
53+
struct stepper_ramp_common ramp_common;
54+
#endif
4555
};
4656

4757
static int stepper_motor_set_coil_charge(const struct device *dev)
@@ -98,6 +108,24 @@ static void update_coil_charge(const struct device *dev)
98108
const struct gpio_stepper_config *config = dev->config;
99109
struct gpio_stepper_data *data = dev->data;
100110

111+
#ifdef CONFIG_STEPPER_RAMP
112+
if (data->ramp_common.ramp_data.current_ramp_state ==
113+
STEPPER_RAMP_STATE_FORCED_DECELERATION) {
114+
if (data->direction == STEPPER_DIRECTION_NEGATIVE) {
115+
config->invert_direction ? decrement_coil_charge(dev)
116+
: increment_coil_charge(dev);
117+
data->actual_position++;
118+
return;
119+
}
120+
if (data->direction == STEPPER_DIRECTION_POSITIVE) {
121+
config->invert_direction ? increment_coil_charge(dev)
122+
: decrement_coil_charge(dev);
123+
data->actual_position--;
124+
return;
125+
}
126+
}
127+
#endif
128+
101129
if (data->direction == STEPPER_DIRECTION_POSITIVE) {
102130
config->invert_direction ? decrement_coil_charge(dev) : increment_coil_charge(dev);
103131
data->actual_position++;
@@ -109,6 +137,24 @@ static void update_coil_charge(const struct device *dev)
109137

110138
static void update_remaining_steps(struct gpio_stepper_data *data)
111139
{
140+
#ifdef CONFIG_STEPPER_RAMP
141+
if (data->step_count != 0) {
142+
data->delay_in_ns = trapezoidal_ramp_api.get_next_step_interval(&data->ramp_common,
143+
data->delay_in_ns);
144+
145+
if (data->ramp_common.ramp_data.current_ramp_state ==
146+
STEPPER_RAMP_STATE_FORCED_DECELERATION) {
147+
if (data->step_count > 0) {
148+
data->step_count++;
149+
} else {
150+
data->step_count--;
151+
}
152+
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
153+
}
154+
} else {
155+
data->ramp_common.ramp_data.current_ramp_state = STEPPER_RAMP_STATE_NOT_MOVING;
156+
}
157+
#endif
112158
if (data->step_count > 0) {
113159
data->step_count--;
114160
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
@@ -124,9 +170,10 @@ static void update_remaining_steps(struct gpio_stepper_data *data)
124170
}
125171
}
126172

127-
static void update_direction_from_step_count(const struct device *dev)
173+
static bool update_direction_from_step_count(const struct device *dev)
128174
{
129175
struct gpio_stepper_data *data = dev->data;
176+
enum stepper_direction direction = data->direction;
130177

131178
if (data->step_count > 0) {
132179
data->direction = STEPPER_DIRECTION_POSITIVE;
@@ -135,6 +182,10 @@ static void update_direction_from_step_count(const struct device *dev)
135182
} else {
136183
LOG_ERR("Step count is zero");
137184
}
185+
if (data->direction != direction) {
186+
return true;
187+
}
188+
return false;
138189
}
139190

140191
static void position_mode_task(const struct device *dev)
@@ -178,6 +229,23 @@ static void stepper_work_step_handler(struct k_work *work)
178229
}
179230
}
180231

232+
static bool is_step_timing_valid(const struct device *dev)
233+
{
234+
struct gpio_stepper_data *data = dev->data;
235+
#ifdef CONFIG_STEPPER_RAMP
236+
237+
if (data->ramp_common.ramp_profile.max_velocity > 0) {
238+
return true;
239+
}
240+
return false;
241+
#else
242+
if (data->delay_in_ns > 0) {
243+
return true;
244+
}
245+
return false;
246+
#endif
247+
}
248+
181249
static int gpio_stepper_move_by(const struct device *dev, int32_t micro_steps)
182250
{
183251
struct gpio_stepper_data *data = dev->data;
@@ -187,14 +255,29 @@ static int gpio_stepper_move_by(const struct device *dev, int32_t micro_steps)
187255
return -ECANCELED;
188256
}
189257

190-
if (data->delay_in_ns == 0) {
191-
LOG_ERR("Step interval not set or invalid step interval set");
258+
if (!is_step_timing_valid(dev)) {
192259
return -EINVAL;
193260
}
261+
262+
if (micro_steps == 0) {
263+
LOG_WRN("Step count is zero");
264+
return 0;
265+
}
266+
194267
K_SPINLOCK(&data->lock) {
195268
data->run_mode = STEPPER_RUN_MODE_POSITION;
196269
data->step_count = micro_steps;
197-
update_direction_from_step_count(dev);
270+
bool is_dir_changed = update_direction_from_step_count(dev);
271+
#ifdef CONFIG_STEPPER_RAMP
272+
const struct gpio_stepper_config *config = dev->config;
273+
uint32_t steps_to_move = abs(micro_steps);
274+
275+
trapezoidal_ramp_api.reset_ramp_data(&config->ramp_config, &data->ramp_common,
276+
&data->delay_in_ns, is_dir_changed,
277+
steps_to_move);
278+
#else
279+
ARG_UNUSED(is_dir_changed);
280+
#endif
198281
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
199282
}
200283
return 0;
@@ -229,14 +312,24 @@ static int gpio_stepper_move_to(const struct device *dev, int32_t micro_steps)
229312
return -ECANCELED;
230313
}
231314

232-
if (data->delay_in_ns == 0) {
233-
LOG_ERR("Step interval not set or invalid step interval set");
315+
if (!is_step_timing_valid(dev)) {
234316
return -EINVAL;
235317
}
318+
236319
K_SPINLOCK(&data->lock) {
237320
data->run_mode = STEPPER_RUN_MODE_POSITION;
238321
data->step_count = micro_steps - data->actual_position;
239-
update_direction_from_step_count(dev);
322+
bool is_dir_changed = update_direction_from_step_count(dev);
323+
#ifdef CONFIG_STEPPER_RAMP
324+
const struct gpio_stepper_config *config = dev->config;
325+
uint32_t steps_to_move = abs(micro_steps - data->actual_position);
326+
327+
trapezoidal_ramp_api.reset_ramp_data(&config->ramp_config, &data->ramp_common,
328+
&data->delay_in_ns, is_dir_changed,
329+
steps_to_move);
330+
#else
331+
ARG_UNUSED(is_dir_changed);
332+
#endif
240333
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
241334
}
242335
return 0;
@@ -268,6 +361,23 @@ static int gpio_stepper_set_microstep_interval(const struct device *dev,
268361
return 0;
269362
}
270363

364+
#ifdef CONFIG_STEPPER_RAMP
365+
366+
static int gpio_stepper_set_ramp_profile(const struct device *dev,
367+
const struct stepper_ramp_profile *const ramp_profile)
368+
{
369+
struct gpio_stepper_data *data = dev->data;
370+
371+
K_SPINLOCK(&data->lock) {
372+
data->ramp_common.ramp_profile.acceleration = ramp_profile->acceleration;
373+
data->ramp_common.ramp_profile.max_velocity = ramp_profile->max_velocity;
374+
data->ramp_common.ramp_profile.deceleration = ramp_profile->deceleration;
375+
}
376+
return 0;
377+
}
378+
379+
#endif
380+
271381
static int gpio_stepper_run(const struct device *dev, const enum stepper_direction direction)
272382
{
273383
struct gpio_stepper_data *data = dev->data;
@@ -350,6 +460,9 @@ static int gpio_stepper_disable(const struct device *dev)
350460
int err;
351461

352462
K_SPINLOCK(&data->lock) {
463+
#ifdef CONFIG_STEPPER_RAMP
464+
data->ramp_common.ramp_data.current_ramp_state = STEPPER_RAMP_STATE_NOT_MOVING;
465+
#endif
353466
(void)k_work_cancel_delayable(&data->stepper_dwork);
354467
err = energize_coils(dev, false);
355468
if (err == 0) {
@@ -385,6 +498,7 @@ static int gpio_stepper_init(const struct device *dev)
385498
for (uint8_t n_pin = 0; n_pin < NUM_CONTROL_PINS; n_pin++) {
386499
(void)gpio_pin_configure_dt(&config->control_pins[n_pin], GPIO_OUTPUT_INACTIVE);
387500
}
501+
388502
k_work_init_delayable(&data->stepper_dwork, stepper_work_step_handler);
389503
return 0;
390504
}
@@ -403,20 +517,29 @@ static DEVICE_API(stepper, gpio_stepper_api) = {
403517
.run = gpio_stepper_run,
404518
.stop = gpio_stepper_stop,
405519
.is_moving = gpio_stepper_is_moving,
520+
IF_ENABLED(CONFIG_STEPPER_RAMP, (.set_ramp_profile = gpio_stepper_set_ramp_profile,))
521+
406522
};
407523

408524
#define GPIO_STEPPER_DEFINE(inst) \
409525
static const struct gpio_dt_spec gpio_stepper_motor_control_pins_##inst[] = { \
410526
DT_INST_FOREACH_PROP_ELEM_SEP(inst, gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)), \
411527
}; \
412528
BUILD_ASSERT(ARRAY_SIZE(gpio_stepper_motor_control_pins_##inst) == 4, \
413-
"gpio_stepper_controller driver currently supports only 4 wire configuration"); \
529+
"gpio_stepper_controller driver currently supports only 4 wire configuration"); \
414530
static const struct gpio_stepper_config gpio_stepper_config_##inst = { \
415531
.invert_direction = DT_INST_PROP(inst, invert_direction), \
416-
.control_pins = gpio_stepper_motor_control_pins_##inst}; \
532+
.control_pins = gpio_stepper_motor_control_pins_##inst, \
533+
IF_ENABLED(CONFIG_STEPPER_RAMP, \
534+
(.ramp_config.forced_deceleration_steps = DT_INST_PROP(inst, forced_decel_steps))) }; \
417535
static struct gpio_stepper_data gpio_stepper_data_##inst = { \
418536
.step_gap = MAX_MICRO_STEP_RES >> (DT_INST_PROP(inst, micro_step_res) - 1), \
419-
}; \
537+
IF_ENABLED(CONFIG_STEPPER_RAMP, \
538+
(.ramp_common.ramp_data.ramp_stop_step_interval_threshold_in_ns = \
539+
DT_INST_PROP(inst, ramp_stop_step_interval), \
540+
.ramp_common.ramp_profile.acceleration = DT_INST_PROP(inst, acceleration), \
541+
.ramp_common.ramp_profile.deceleration = DT_INST_PROP(inst, deceleration), \
542+
.ramp_common.ramp_profile.max_velocity = DT_INST_PROP(inst, max_speed),)) }; \
420543
BUILD_ASSERT(DT_INST_PROP(inst, micro_step_res) <= STEPPER_MICRO_STEP_2, \
421544
"gpio_stepper_controller driver supports up to 2 micro steps"); \
422545
DEVICE_DT_INST_DEFINE(inst, gpio_stepper_init, NULL, &gpio_stepper_data_##inst, \
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
zephyr_library()
5+
6+
zephyr_library_sources_ifdef(CONFIG_STEPPER_RAMP ramp_trapezoidal.c)

drivers/stepper/ramp/Kconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
menuconfig STEPPER_RAMP
5+
bool "Ramp control"
6+
help
7+
Enable ramp control for stepper drivers.
8+
9+
if STEPPER_RAMP
10+
11+
config STEPPER_TRAPEZOIDAL_RAMP
12+
bool "Trapezoidal ramp control"
13+
help
14+
Enable trapezoidal ramp control for stepper drivers.
15+
The trapezoidal ramp control is implemented as a state machine
16+
and can be used with any stepper driver.
17+
18+
endif

drivers/stepper/ramp/ramp.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef ZEPHYR_DRIVERS_STEPPER_RAMP_H_
7+
#define ZEPHYR_DRIVERS_STEPPER_RAMP_H_
8+
9+
#include <stdlib.h>
10+
#include <zephyr/drivers/stepper.h>
11+
12+
struct stepper_ramp_distance_profile {
13+
/* Distance to accelerate in µsteps */
14+
uint32_t acceleration;
15+
/* Distance to travel at constant velocity in µsteps */
16+
uint32_t const_speed;
17+
/* Distance to decelerate in µsteps */
18+
uint32_t deceleration;
19+
};
20+
21+
enum stepper_ramp_state {
22+
/* Ramp state for not moving */
23+
STEPPER_RAMP_STATE_NOT_MOVING,
24+
/* Ramp state for acceleration */
25+
STEPPER_RAMP_STATE_ACCELERATION,
26+
/* Ramp state for constant speed */
27+
STEPPER_RAMP_STATE_CONSTANT_SPEED,
28+
/* Ramp state for deceleration */
29+
STEPPER_RAMP_STATE_DECELERATION,
30+
/* Ramp state for forced deceleration while switching direction */
31+
STEPPER_RAMP_STATE_FORCED_DECELERATION,
32+
};
33+
34+
struct stepper_ramp_data {
35+
/* Actual position in µsteps */
36+
uint32_t ramp_actual_position;
37+
/* Target position in µsteps */
38+
uint32_t ramp_target_position;
39+
/* Steps to be used while forced deceleration */
40+
int32_t forced_deceleration_steps;
41+
/* Ramping is allowed once this velocity threshold is crossed */
42+
const uint64_t ramp_stop_step_interval_threshold_in_ns;
43+
/* Ramp state */
44+
enum stepper_ramp_state current_ramp_state;
45+
};
46+
47+
struct stepper_ramp_config {
48+
const uint32_t forced_deceleration_steps;
49+
};
50+
51+
struct stepper_ramp_common {
52+
struct stepper_ramp_data ramp_data;
53+
struct stepper_ramp_distance_profile ramp_distance_profile;
54+
struct stepper_ramp_profile ramp_profile;
55+
};
56+
57+
typedef void (*stepper_ramp_reset_ramp_data)(const struct stepper_ramp_config *config,
58+
struct stepper_ramp_common *ramp_common,
59+
uint64_t *step_interval_in_ns,
60+
bool is_stepper_dir_changed,
61+
const uint32_t steps_to_move);
62+
63+
typedef uint64_t (*stepper_ramp_get_next_step_interval)(struct stepper_ramp_common *ramp_common,
64+
const uint64_t current_step_interval_in_ns);
65+
66+
struct stepper_ramp_api {
67+
stepper_ramp_reset_ramp_data reset_ramp_data;
68+
stepper_ramp_get_next_step_interval get_next_step_interval;
69+
};
70+
71+
extern const struct stepper_ramp_api trapezoidal_ramp_api;
72+
73+
#endif

0 commit comments

Comments
 (0)