Skip to content

Commit 14bb550

Browse files
committed
drivers: stepper: a4988
Add a4998 2-phase motor controller driver Signed-off-by: Marvin Ouma <[email protected]>
1 parent 1903a8f commit 14bb550

File tree

13 files changed

+400
-0
lines changed

13 files changed

+400
-0
lines changed

drivers/stepper/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ 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_TI ti)
99
add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir)
10+
add_subdirectory_ifdef(CONFIG_STEPPER_ALLEGRO allegro)
1011
# zephyr-keep-sorted-stop
1112

1213
zephyr_library()

drivers/stepper/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ rsource "Kconfig.fake"
3535
rsource "Kconfig.gpio"
3636
rsource "adi_tmc/Kconfig"
3737
rsource "ti/Kconfig"
38+
rsource "allegro/Kconfig"
3839
# zephyr-keep-sorted-stop
3940

4041
endif
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
zephyr_library()
5+
zephyr_library_property(ALLOW_EMPTY TRUE)
6+
7+
zephyr_library_sources_ifdef(CONFIG_A4988 a4988.c)

drivers/stepper/allegro/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config STEPPER_ALLEGRO
5+
bool
6+
depends on STEPPER
7+
8+
rsource "Kconfig.a4988"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config A4988
5+
bool "A4988 DMOS Microstepping Driver"
6+
default y
7+
depends on DT_HAS_ALLEGRO_A4988_ENABLED
8+
select STEPPER_ALLEGRO
9+
select STEP_DIR_STEPPER
10+
select STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS
11+
help
12+
Enable A4988 DMOS Microstepping Driver.

drivers/stepper/allegro/a4988.c

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#define DT_DRV_COMPAT allegro_a4988
7+
8+
#include <zephyr/device.h>
9+
#include <zephyr/devicetree.h>
10+
#include <zephyr/drivers/gpio.h>
11+
#include <zephyr/drivers/stepper.h>
12+
#include "../step_dir/step_dir_stepper_common.h"
13+
14+
#include <zephyr/logging/log.h>
15+
LOG_MODULE_REGISTER(a4988, CONFIG_STEPPER_LOG_LEVEL);
16+
17+
#define MSX_PIN_COUNT 3
18+
#define MSX_PIN_STATE_COUNT 5
19+
20+
static enum stepper_micro_step_resolution a4988_msx_resolutions[MSX_PIN_STATE_COUNT] = {
21+
STEPPER_MICRO_STEP_1, STEPPER_MICRO_STEP_2, STEPPER_MICRO_STEP_4,
22+
STEPPER_MICRO_STEP_8, STEPPER_MICRO_STEP_16,
23+
};
24+
25+
struct a4988_config {
26+
struct step_dir_stepper_common_config common;
27+
struct gpio_dt_spec sleep_pin;
28+
struct gpio_dt_spec enable_pin;
29+
struct gpio_dt_spec reset;
30+
struct gpio_dt_spec *msx_pins;
31+
enum stepper_micro_step_resolution *msx_resolutions;
32+
};
33+
34+
struct a4988_data {
35+
struct step_dir_stepper_common_data common;
36+
enum stepper_micro_step_resolution resolution;
37+
};
38+
39+
STEP_DIR_STEPPER_STRUCT_CHECK(struct a4988_config, struct a4988_data);
40+
41+
static int a4988_stepper_enable(const struct device *dev, const bool enable)
42+
{
43+
/* enable and sleep pins need to be set logic low to be on */
44+
const struct a4988_config *config = dev->config;
45+
int ret;
46+
47+
if (config->sleep_pin.port != NULL) {
48+
ret = gpio_pin_set_dt(&config->sleep_pin, enable);
49+
if (ret < 0) {
50+
LOG_WRN("Failed to set sleep pin %d", ret);
51+
}
52+
}
53+
54+
LOG_DBG("Stepper motor controller %s %s", dev->name, !enable ? "enabled" : "disabled");
55+
return gpio_pin_set_dt(&config->enable_pin, !enable);
56+
}
57+
58+
/* Set microstepping mode
59+
*
60+
*/
61+
static int a4988_stepper_set_micro_step_res(const struct device *dev,
62+
enum stepper_micro_step_resolution micro_step_res)
63+
{
64+
struct a4988_data *data = dev->data;
65+
const struct a4988_config *config = dev->config;
66+
int ret;
67+
68+
if (!config->msx_pins) {
69+
LOG_ERR("Microstep resolution pins are not configured");
70+
return 0; // NOT CONNECTED
71+
}
72+
73+
for (uint8_t i = 0; i < MSX_PIN_STATE_COUNT; ++i) {
74+
if (micro_step_res != config->msx_resolutions[i]) {
75+
continue;
76+
}
77+
78+
ret = gpio_pin_set_dt(&config->msx_pins[0], i & 0x01);
79+
if (ret < 0) {
80+
LOG_ERR("Failed to set MS1 pin: %d", ret);
81+
return ret;
82+
}
83+
84+
ret = gpio_pin_set_dt(&config->msx_pins[1], (i & 0x02) >> 1);
85+
if (ret < 0) {
86+
LOG_ERR("Failed to set MS2 pin: %d", ret);
87+
return ret;
88+
}
89+
90+
ret = gpio_pin_set_dt(&config->msx_pins[2], (i & 0x04) >> 1);
91+
if (ret < 0) {
92+
LOG_ERR("Failed to set MS3 pin: %d", ret);
93+
return ret;
94+
}
95+
96+
data->resolution = micro_step_res;
97+
return 0;
98+
}
99+
100+
LOG_ERR("Unsupported microstep resolution: %d", micro_step_res);
101+
return -EINVAL;
102+
}
103+
104+
static int a4988_stepper_configure_msx_pins(const struct device *dev)
105+
{
106+
const struct a4988_config *config = dev->config;
107+
int ret;
108+
109+
for (uint8_t i = 0; i < MSX_PIN_COUNT; i++) {
110+
if (!gpio_is_ready_dt(&config->msx_pins[i])) {
111+
LOG_ERR("MSX pin %u is not ready", i);
112+
return -ENODEV;
113+
}
114+
115+
ret = gpio_pin_configure_dt(&config->msx_pins[i], GPIO_OUTPUT);
116+
if (ret < 0) {
117+
LOG_ERR("Failed to configure msx pin %u", i);
118+
return ret;
119+
}
120+
}
121+
return 0;
122+
}
123+
124+
static int a4988_stepper_get_micro_step_res(const struct device *dev,
125+
enum stepper_micro_step_resolution *micro_step_res)
126+
{
127+
128+
struct a4988_data *data = dev->data;
129+
*micro_step_res = data->resolution;
130+
return 0;
131+
}
132+
133+
static int a4988_stepper_init(const struct device *dev)
134+
{
135+
const struct a4988_config *config = dev->config;
136+
const struct a4988_data *data = dev->data;
137+
int ret;
138+
139+
if (config->sleep_pin.port != NULL) {
140+
ret = gpio_pin_configure_dt(&config->sleep_pin, GPIO_OUTPUT_ACTIVE);
141+
if (ret < 0) {
142+
LOG_ERR("Failed to configure sleep pin: %d", ret);
143+
return -ENODEV;
144+
}
145+
}
146+
147+
if (!gpio_is_ready_dt(&config->enable_pin)) {
148+
LOG_ERR("GPIO pins are not ready");
149+
return -ENODEV;
150+
}
151+
152+
ret = gpio_pin_configure_dt(&config->enable_pin, GPIO_OUTPUT);
153+
if (ret < 0) {
154+
LOG_ERR("Failed to configure enable pin: %d", ret);
155+
return ret;
156+
}
157+
158+
if (config->msx_pins) {
159+
ret = a4988_stepper_configure_msx_pins(dev);
160+
if (ret < 0) {
161+
LOG_ERR("Failed to configure MSX pins: %d", ret);
162+
return ret;
163+
}
164+
165+
ret = a4988_stepper_set_micro_step_res(dev, data->resolution);
166+
if (ret < 0) {
167+
LOG_ERR("Failed to set microstep resolution: %d", ret);
168+
return ret;
169+
}
170+
}
171+
172+
ret = step_dir_stepper_common_init(dev);
173+
if (ret < 0) {
174+
LOG_ERR("Failed to init step dir common stepper: %d", ret);
175+
return ret;
176+
}
177+
return 0;
178+
}
179+
180+
static DEVICE_API(stepper, a4988_stepper_api) = {
181+
.enable = a4988_stepper_enable,
182+
.move_by = step_dir_stepper_common_move_by,
183+
.is_moving = step_dir_stepper_common_is_moving,
184+
.set_reference_position = step_dir_stepper_common_set_reference_position,
185+
.get_actual_position = step_dir_stepper_common_get_actual_position,
186+
.move_to = step_dir_stepper_common_move_to,
187+
.set_microstep_interval = step_dir_stepper_common_set_microstep_interval,
188+
.run = step_dir_stepper_common_run,
189+
.set_event_callback = step_dir_stepper_common_set_event_callback,
190+
.set_micro_step_res = a4988_stepper_set_micro_step_res,
191+
.get_micro_step_res = a4988_stepper_get_micro_step_res,
192+
};
193+
194+
#define A4988_STEPPER_DEFINE(inst, msx_table) \
195+
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, msx_gpios), ( \
196+
static const struct gpio_dt_spec a4988_stepper_msx_pins_##inst[] = { \
197+
DT_INST_FOREACH_PROP_ELEM_SEP( \
198+
inst, msx_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,) \
199+
), \
200+
}; \
201+
BUILD_ASSERT( \
202+
ARRAY_SIZE(a4988_stepper_msx_pins_##inst) == MSX_PIN_COUNT, \
203+
"Three microstep config pins needed"); \
204+
)) \
205+
\
206+
static const struct a4988_config a4988_config_##inst = { \
207+
.common = STEP_DIR_STEPPER_DT_INST_COMMON_CONFIG_INIT(inst), \
208+
.enable_pin = GPIO_DT_SPEC_INST_GET(inst, en_gpios), \
209+
.msx_resolutions = msx_table, \
210+
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, msx_gpios), \
211+
(.msx_pins = (struct gpio_dt_spec *)a4988_stepper_msx_pins_##inst)) }; \
212+
static struct a4988_data a4988_data_##inst = { \
213+
.common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \
214+
.resolution = DT_INST_PROP(inst, micro_step_res), \
215+
}; \
216+
DEVICE_DT_INST_DEFINE(inst, a4988_stepper_init, NULL, &a4988_data_##inst, \
217+
&a4988_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \
218+
&a4988_stepper_api);
219+
220+
DT_INST_FOREACH_STATUS_OKAY_VARGS(A4988_STEPPER_DEFINE, a4988_msx_resolutions)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: |
5+
Allegro A4988 stepper motor driver.
6+
7+
Example:
8+
a4988: a4988 {
9+
status = "okay";
10+
compatible = "allegro,a4988";
11+
12+
step-gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
13+
dir-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
14+
en-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
15+
msx-gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>,
16+
<&gpio0 17 GPIO_ACTIVE_HIGH>,
17+
<&gpio0 5 GPIO_ACTIVE_HIGH>;
18+
};
19+
20+
compatible: "allegro,a4988"
21+
22+
include:
23+
- name: stepper-controller.yaml
24+
property-allowlist:
25+
- micro-step-res
26+
- step-gpios
27+
- dir-gpios
28+
- en-gpios
29+
30+
31+
properties:
32+
msx-gpios:
33+
type: phandle-array
34+
description: |
35+
An array of GPIO pins for configuring the microstep resolution of the driver.
36+
The pins should be listed in the following order:
37+
- MS1
38+
- MS2
39+
- MS3
40+
41+
sleep-gpios:
42+
type: phandle-array
43+
description: Sleep pin
44+
45+
reset-gpios:
46+
type: phandle-array
47+
description: Reset pin
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
6+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
7+
project(a4988)
8+
9+
FILE(GLOB app_sources src/*.c)
10+
target_sources(app PRIVATE ${app_sources})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
mainmenu "A4988 sample application"
5+
6+
config STEPS_PER_REV
7+
int "Steps per revolution"
8+
default 200
9+
10+
11+
source "Kconfig.zephyr"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/ {
2+
aliases {
3+
stepper = &a4988;
4+
};
5+
6+
a4988: a4988 {
7+
compatible = "allegro,a4988";
8+
status = "okay";
9+
10+
step-gpios = <&gpioa 21 GPIO_ACTIVE_HIGH>;
11+
dir-gpios = <&gpiob 22 GPIO_ACTIVE_HIGH>;
12+
en-gpios = <&gpioa 14 GPIO_ACTIVE_LOW>;
13+
sleep-gpios = <&gpiob 19 GPIO_ACTIVE_LOW>;
14+
reset-gpios = <&gpiob 18 GPIO_ACTIVE_HIGH>;
15+
msx-gpios = <&gpioa 15 GPIO_ACTIVE_HIGH>,
16+
<&gpioa 16 GPIO_ACTIVE_HIGH>,
17+
<&gpioa 17 GPIO_ACTIVE_HIGH>;
18+
19+
};
20+
};

0 commit comments

Comments
 (0)