Skip to content

Commit ca14aeb

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 f922014 commit ca14aeb

File tree

18 files changed

+795
-35
lines changed

18 files changed

+795
-35
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: 163 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
3-
* SPDX-FileCopyrightText: Copyright (c) 2024 Jilay Sandeep Pandya
3+
* SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

@@ -10,8 +10,14 @@
1010
#include <zephyr/kernel.h>
1111
#include <zephyr/sys_clock.h>
1212
#include <zephyr/drivers/stepper.h>
13+
#include "stepper_common.h"
1314
#include <zephyr/sys/__assert.h>
1415

16+
#ifdef CONFIG_STEPPER_RAMP
17+
#include <stdlib.h>
18+
#include "ramp/ramp.h"
19+
#endif
20+
1521
#include <zephyr/logging/log.h>
1622
LOG_MODULE_REGISTER(gpio_stepper_motor_controller, CONFIG_STEPPER_LOG_LEVEL);
1723

@@ -26,6 +32,10 @@ static const uint8_t
2632
struct gpio_stepper_config {
2733
const struct gpio_dt_spec *control_pins;
2834
bool invert_direction;
35+
#ifdef CONFIG_STEPPER_RAMP
36+
const struct stepper_ramp_api *ramp_api;
37+
const struct stepper_ramp_config ramp_config;
38+
#endif
2939
};
3040

3141
struct gpio_stepper_data {
@@ -42,6 +52,9 @@ struct gpio_stepper_data {
4252
bool is_enabled;
4353
stepper_event_callback_t callback;
4454
void *event_cb_user_data;
55+
#ifdef CONFIG_STEPPER_RAMP
56+
struct stepper_ramp_common ramp_common;
57+
#endif
4558
};
4659

4760
static int stepper_motor_set_coil_charge(const struct device *dev)
@@ -98,6 +111,24 @@ static void update_coil_charge(const struct device *dev)
98111
const struct gpio_stepper_config *config = dev->config;
99112
struct gpio_stepper_data *data = dev->data;
100113

114+
#ifdef CONFIG_STEPPER_RAMP
115+
if (data->ramp_common.ramp_runtime_data.current_ramp_state ==
116+
STEPPER_RAMP_STATE_PRE_DECELERATION) {
117+
if (data->direction == STEPPER_DIRECTION_NEGATIVE) {
118+
config->invert_direction ? decrement_coil_charge(dev)
119+
: increment_coil_charge(dev);
120+
data->actual_position++;
121+
return;
122+
}
123+
if (data->direction == STEPPER_DIRECTION_POSITIVE) {
124+
config->invert_direction ? increment_coil_charge(dev)
125+
: decrement_coil_charge(dev);
126+
data->actual_position--;
127+
return;
128+
}
129+
}
130+
#endif
131+
101132
if (data->direction == STEPPER_DIRECTION_POSITIVE) {
102133
config->invert_direction ? decrement_coil_charge(dev) : increment_coil_charge(dev);
103134
data->actual_position++;
@@ -107,26 +138,34 @@ static void update_coil_charge(const struct device *dev)
107138
}
108139
}
109140

110-
static void update_remaining_steps(struct gpio_stepper_data *data)
141+
static void update_remaining_steps(const struct device *dev)
111142
{
143+
struct gpio_stepper_data *data = dev->data;
144+
145+
#ifdef CONFIG_STEPPER_RAMP
146+
147+
if (data->ramp_common.ramp_runtime_data.current_ramp_state ==
148+
STEPPER_RAMP_STATE_PRE_DECELERATION) {
149+
if (data->step_count > 0) {
150+
data->step_count++;
151+
} else {
152+
data->step_count--;
153+
}
154+
return;
155+
}
156+
157+
#endif
112158
if (data->step_count > 0) {
113159
data->step_count--;
114-
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
115160
} else if (data->step_count < 0) {
116161
data->step_count++;
117-
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
118-
} else {
119-
if (!data->callback) {
120-
LOG_WRN_ONCE("No callback set");
121-
return;
122-
}
123-
data->callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED, data->event_cb_user_data);
124162
}
125163
}
126164

127-
static void update_direction_from_step_count(const struct device *dev)
165+
static bool update_direction_from_step_count(const struct device *dev)
128166
{
129167
struct gpio_stepper_data *data = dev->data;
168+
enum stepper_direction direction = data->direction;
130169

131170
if (data->step_count > 0) {
132171
data->direction = STEPPER_DIRECTION_POSITIVE;
@@ -135,17 +174,34 @@ static void update_direction_from_step_count(const struct device *dev)
135174
} else {
136175
LOG_ERR("Step count is zero");
137176
}
177+
if (data->direction != direction) {
178+
return true;
179+
}
180+
return false;
138181
}
139182

140183
static void position_mode_task(const struct device *dev)
141184
{
142185
struct gpio_stepper_data *data = dev->data;
143186

187+
update_remaining_steps(dev);
188+
(void)stepper_motor_set_coil_charge(dev);
189+
update_coil_charge(dev);
144190
if (data->step_count) {
145-
(void)stepper_motor_set_coil_charge(dev);
146-
update_coil_charge(dev);
191+
#ifdef CONFIG_STEPPER_RAMP
192+
const struct gpio_stepper_config *config = dev->config;
193+
194+
data->delay_in_ns = config->ramp_api->get_next_step_interval(
195+
&data->ramp_common, data->delay_in_ns, STEPPER_RUN_MODE_POSITION);
196+
#endif
197+
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
198+
} else {
199+
if (data->callback) {
200+
data->callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED,
201+
data->event_cb_user_data);
202+
}
203+
(void)k_work_cancel_delayable(&data->stepper_dwork);
147204
}
148-
update_remaining_steps(dev->data);
149205
}
150206

151207
static void velocity_mode_task(const struct device *dev)
@@ -154,6 +210,14 @@ static void velocity_mode_task(const struct device *dev)
154210

155211
(void)stepper_motor_set_coil_charge(dev);
156212
update_coil_charge(dev);
213+
214+
#ifdef CONFIG_STEPPER_RAMP
215+
const struct gpio_stepper_config *config = dev->config;
216+
217+
data->delay_in_ns = config->ramp_api->get_next_step_interval(
218+
&data->ramp_common, data->delay_in_ns, STEPPER_RUN_MODE_VELOCITY);
219+
#endif
220+
157221
(void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns));
158222
}
159223

@@ -178,6 +242,23 @@ static void stepper_work_step_handler(struct k_work *work)
178242
}
179243
}
180244

245+
static bool is_step_timing_valid(const struct device *dev)
246+
{
247+
struct gpio_stepper_data *data = dev->data;
248+
#ifdef CONFIG_STEPPER_RAMP
249+
250+
if (data->ramp_common.ramp_profile.max_velocity > 0) {
251+
return true;
252+
}
253+
return false;
254+
#else
255+
if (data->delay_in_ns > 0) {
256+
return true;
257+
}
258+
return false;
259+
#endif
260+
}
261+
181262
static int gpio_stepper_move_by(const struct device *dev, int32_t micro_steps)
182263
{
183264
struct gpio_stepper_data *data = dev->data;
@@ -187,14 +268,35 @@ static int gpio_stepper_move_by(const struct device *dev, int32_t micro_steps)
187268
return -ECANCELED;
188269
}
189270

190-
if (data->delay_in_ns == 0) {
191-
LOG_ERR("Step interval not set or invalid step interval set");
271+
if (!is_step_timing_valid(dev)) {
192272
return -EINVAL;
193273
}
274+
275+
if (micro_steps == 0) {
276+
LOG_WRN("Step count is zero");
277+
return 0;
278+
}
279+
194280
K_SPINLOCK(&data->lock) {
195281
data->run_mode = STEPPER_RUN_MODE_POSITION;
196282
data->step_count = micro_steps;
197-
update_direction_from_step_count(dev);
283+
bool is_dir_changed = update_direction_from_step_count(dev);
284+
#ifdef CONFIG_STEPPER_RAMP
285+
const struct gpio_stepper_config *config = dev->config;
286+
uint32_t steps_to_move = abs(micro_steps);
287+
bool is_stepper_moving = k_work_delayable_is_pending(&data->stepper_dwork);
288+
289+
config->ramp_api->reset_ramp_runtime_data(&config->ramp_config, &data->ramp_common,
290+
is_dir_changed, is_stepper_moving,
291+
steps_to_move);
292+
if (!is_stepper_moving) {
293+
data->delay_in_ns = config->ramp_api->calculate_start_interval(
294+
data->ramp_common.ramp_profile.acceleration);
295+
}
296+
config->ramp_api->recalculate_ramp(&data->ramp_common, steps_to_move);
297+
#else
298+
ARG_UNUSED(is_dir_changed);
299+
#endif
198300
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
199301
}
200302
return 0;
@@ -257,6 +359,23 @@ static int gpio_stepper_set_microstep_interval(const struct device *dev,
257359
return 0;
258360
}
259361

362+
#ifdef CONFIG_STEPPER_RAMP
363+
364+
static int gpio_stepper_set_ramp_profile(const struct device *dev,
365+
const struct stepper_ramp_profile *const ramp_profile)
366+
{
367+
struct gpio_stepper_data *data = dev->data;
368+
369+
K_SPINLOCK(&data->lock) {
370+
data->ramp_common.ramp_profile.acceleration = ramp_profile->acceleration;
371+
data->ramp_common.ramp_profile.max_velocity = ramp_profile->max_velocity;
372+
data->ramp_common.ramp_profile.deceleration = ramp_profile->deceleration;
373+
}
374+
return 0;
375+
}
376+
377+
#endif
378+
260379
static int gpio_stepper_run(const struct device *dev, const enum stepper_direction direction)
261380
{
262381
struct gpio_stepper_data *data = dev->data;
@@ -268,7 +387,22 @@ static int gpio_stepper_run(const struct device *dev, const enum stepper_directi
268387

269388
K_SPINLOCK(&data->lock) {
270389
data->run_mode = STEPPER_RUN_MODE_VELOCITY;
390+
bool is_dir_changed = direction != data->direction;
271391
data->direction = direction;
392+
#ifdef CONFIG_STEPPER_RAMP
393+
const struct gpio_stepper_config *config = dev->config;
394+
const bool is_stepper_moving = k_work_delayable_is_pending(&data->stepper_dwork);
395+
396+
config->ramp_api->reset_ramp_runtime_data(&config->ramp_config, &data->ramp_common,
397+
is_dir_changed, is_stepper_moving,
398+
UINT32_MAX);
399+
if (!is_stepper_moving) {
400+
data->delay_in_ns = config->ramp_api->calculate_start_interval(
401+
data->ramp_common.ramp_profile.acceleration);
402+
}
403+
#else
404+
ARG_UNUSED(is_dir_changed);
405+
#endif
272406
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
273407
}
274408
return 0;
@@ -374,6 +508,7 @@ static int gpio_stepper_init(const struct device *dev)
374508
for (uint8_t n_pin = 0; n_pin < NUM_CONTROL_PINS; n_pin++) {
375509
(void)gpio_pin_configure_dt(&config->control_pins[n_pin], GPIO_OUTPUT_INACTIVE);
376510
}
511+
377512
k_work_init_delayable(&data->stepper_dwork, stepper_work_step_handler);
378513
return 0;
379514
}
@@ -392,20 +527,28 @@ static DEVICE_API(stepper, gpio_stepper_api) = {
392527
.run = gpio_stepper_run,
393528
.stop = gpio_stepper_stop,
394529
.is_moving = gpio_stepper_is_moving,
530+
IF_ENABLED(CONFIG_STEPPER_RAMP, (.set_ramp_profile = gpio_stepper_set_ramp_profile,))
531+
395532
};
396533

397534
#define GPIO_STEPPER_DEFINE(inst) \
398535
static const struct gpio_dt_spec gpio_stepper_motor_control_pins_##inst[] = { \
399536
DT_INST_FOREACH_PROP_ELEM_SEP(inst, gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)), \
400537
}; \
401538
BUILD_ASSERT(ARRAY_SIZE(gpio_stepper_motor_control_pins_##inst) == 4, \
402-
"gpio_stepper_controller driver currently supports only 4 wire configuration"); \
539+
"gpio_stepper_controller driver currently supports only 4 wire configuration"); \
403540
static const struct gpio_stepper_config gpio_stepper_config_##inst = { \
404541
.invert_direction = DT_INST_PROP(inst, invert_direction), \
405-
.control_pins = gpio_stepper_motor_control_pins_##inst}; \
542+
.control_pins = gpio_stepper_motor_control_pins_##inst, \
543+
IF_ENABLED(CONFIG_STEPPER_RAMP, \
544+
(.ramp_config.pre_deceleration_steps = DT_INST_PROP(inst, pre_decel_steps), \
545+
.ramp_api = RAMP_DT_SPEC_GET_API(inst))) }; \
406546
static struct gpio_stepper_data gpio_stepper_data_##inst = { \
407547
.step_gap = MAX_MICRO_STEP_RES >> (DT_INST_PROP(inst, micro_step_res) - 1), \
408-
}; \
548+
IF_ENABLED(CONFIG_STEPPER_RAMP, ( \
549+
.ramp_common.ramp_profile.acceleration = DT_INST_PROP(inst, acceleration), \
550+
.ramp_common.ramp_profile.deceleration = DT_INST_PROP(inst, deceleration), \
551+
.ramp_common.ramp_profile.max_velocity = DT_INST_PROP(inst, max_speed),)) }; \
409552
BUILD_ASSERT(DT_INST_PROP(inst, micro_step_res) <= STEPPER_MICRO_STEP_2, \
410553
"gpio_stepper_controller driver supports up to 2 micro steps"); \
411554
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_TRAPEZOIDAL 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_RAMP_TRAPEZOIDAL
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

0 commit comments

Comments
 (0)