diff --git a/drivers/stepper/CMakeLists.txt b/drivers/stepper/CMakeLists.txt index 7f135001497c0..b24a8e17704cc 100644 --- a/drivers/stepper/CMakeLists.txt +++ b/drivers/stepper/CMakeLists.txt @@ -7,6 +7,7 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/stepper.h) add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC adi_tmc) add_subdirectory_ifdef(CONFIG_STEPPER_TI ti) add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir) +add_subdirectory_ifdef(CONFIG_STEPPER_ALLEGRO allegro) # zephyr-keep-sorted-stop zephyr_library() diff --git a/drivers/stepper/Kconfig b/drivers/stepper/Kconfig index f8dd4a43b1b07..dbf51614cca19 100644 --- a/drivers/stepper/Kconfig +++ b/drivers/stepper/Kconfig @@ -35,6 +35,7 @@ rsource "Kconfig.fake" rsource "Kconfig.gpio" rsource "adi_tmc/Kconfig" rsource "ti/Kconfig" +rsource "allegro/Kconfig" # zephyr-keep-sorted-stop endif diff --git a/drivers/stepper/allegro/CMakeLists.txt b/drivers/stepper/allegro/CMakeLists.txt new file mode 100644 index 0000000000000..a17c955df88dd --- /dev/null +++ b/drivers/stepper/allegro/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_property(ALLOW_EMPTY TRUE) + +zephyr_library_sources_ifdef(CONFIG_A4988 a4988.c) diff --git a/drivers/stepper/allegro/Kconfig b/drivers/stepper/allegro/Kconfig new file mode 100644 index 0000000000000..b520918b2b2f2 --- /dev/null +++ b/drivers/stepper/allegro/Kconfig @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma +# SPDX-License-Identifier: Apache-2.0 + +config STEPPER_ALLEGRO + bool + depends on STEPPER + +rsource "Kconfig.a4988" diff --git a/drivers/stepper/allegro/Kconfig.a4988 b/drivers/stepper/allegro/Kconfig.a4988 new file mode 100644 index 0000000000000..d4192316904dd --- /dev/null +++ b/drivers/stepper/allegro/Kconfig.a4988 @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma +# SPDX-License-Identifier: Apache-2.0 + +config A4988 + bool "A4988 DMOS Microstepping Driver" + default y + depends on DT_HAS_ALLEGRO_A4988_ENABLED + select STEPPER_ALLEGRO + select STEP_DIR_STEPPER + select STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS + help + Enable A4988 DMOS Microstepping Driver. diff --git a/drivers/stepper/allegro/a4988.c b/drivers/stepper/allegro/a4988.c new file mode 100644 index 0000000000000..ee470f034729a --- /dev/null +++ b/drivers/stepper/allegro/a4988.c @@ -0,0 +1,220 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT allegro_a4988 + +#include +#include +#include +#include +#include "../step_dir/step_dir_stepper_common.h" + +#include +LOG_MODULE_REGISTER(a4988, CONFIG_STEPPER_LOG_LEVEL); + +#define MSX_PIN_COUNT 3 +#define MSX_PIN_STATE_COUNT 5 + +static enum stepper_micro_step_resolution a4988_msx_resolutions[MSX_PIN_STATE_COUNT] = { + STEPPER_MICRO_STEP_1, STEPPER_MICRO_STEP_2, STEPPER_MICRO_STEP_4, + STEPPER_MICRO_STEP_8, STEPPER_MICRO_STEP_16, +}; + +struct a4988_config { + struct step_dir_stepper_common_config common; + struct gpio_dt_spec sleep_pin; + struct gpio_dt_spec enable_pin; + struct gpio_dt_spec reset; + struct gpio_dt_spec *msx_pins; + enum stepper_micro_step_resolution *msx_resolutions; +}; + +struct a4988_data { + struct step_dir_stepper_common_data common; + enum stepper_micro_step_resolution resolution; +}; + +STEP_DIR_STEPPER_STRUCT_CHECK(struct a4988_config, struct a4988_data); + +static int a4988_stepper_enable(const struct device *dev, const bool enable) +{ + /* enable and sleep pins need to be set logic low to be on */ + const struct a4988_config *config = dev->config; + int ret; + + if (config->sleep_pin.port != NULL) { + ret = gpio_pin_set_dt(&config->sleep_pin, enable); + if (ret < 0) { + LOG_WRN("Failed to set sleep pin %d", ret); + } + } + + LOG_DBG("Stepper motor controller %s %s", dev->name, enable ? "enabled" : "disabled"); + return gpio_pin_set_dt(&config->enable_pin, !enable); +} + +/* Set microstepping mode + * + */ +static int a4988_stepper_set_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution micro_step_res) +{ + struct a4988_data *data = dev->data; + const struct a4988_config *config = dev->config; + int ret; + + if (!config->msx_pins) { + LOG_ERR("Microstep resolution pins are not configured"); + return 0; // NOT CONNECTED + } + + for (uint8_t i = 0; i < MSX_PIN_STATE_COUNT; ++i) { + if (micro_step_res != config->msx_resolutions[i]) { + continue; + } + + ret = gpio_pin_set_dt(&config->msx_pins[0], i & 0x01); + if (ret < 0) { + LOG_ERR("Failed to set MS1 pin: %d", ret); + return ret; + } + + ret = gpio_pin_set_dt(&config->msx_pins[1], (i & 0x02) >> 1); + if (ret < 0) { + LOG_ERR("Failed to set MS2 pin: %d", ret); + return ret; + } + + ret = gpio_pin_set_dt(&config->msx_pins[2], (i & 0x04) >> 1); + if (ret < 0) { + LOG_ERR("Failed to set MS3 pin: %d", ret); + return ret; + } + + data->resolution = micro_step_res; + return 0; + } + + LOG_ERR("Unsupported microstep resolution: %d", micro_step_res); + return -EINVAL; +} + +static int a4988_stepper_configure_msx_pins(const struct device *dev) +{ + const struct a4988_config *config = dev->config; + int ret; + + for (uint8_t i = 0; i < MSX_PIN_COUNT; i++) { + if (!gpio_is_ready_dt(&config->msx_pins[i])) { + LOG_ERR("MSX pin %u is not ready", i); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->msx_pins[i], GPIO_OUTPUT); + if (ret < 0) { + LOG_ERR("Failed to configure msx pin %u", i); + return ret; + } + } + return 0; +} + +static int a4988_stepper_get_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution *micro_step_res) +{ + + struct a4988_data *data = dev->data; + *micro_step_res = data->resolution; + return 0; +} + +static int a4988_stepper_init(const struct device *dev) +{ + const struct a4988_config *config = dev->config; + const struct a4988_data *data = dev->data; + int ret; + + if (config->sleep_pin.port != NULL) { + ret = gpio_pin_configure_dt(&config->sleep_pin, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + LOG_ERR("Failed to configure sleep pin: %d", ret); + return -ENODEV; + } + } + + if (!gpio_is_ready_dt(&config->enable_pin)) { + LOG_ERR("GPIO pins are not ready"); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->enable_pin, GPIO_OUTPUT); + if (ret < 0) { + LOG_ERR("Failed to configure enable pin: %d", ret); + return ret; + } + + if (config->msx_pins) { + ret = a4988_stepper_configure_msx_pins(dev); + if (ret < 0) { + LOG_ERR("Failed to configure MSX pins: %d", ret); + return ret; + } + + ret = a4988_stepper_set_micro_step_res(dev, data->resolution); + if (ret < 0) { + LOG_ERR("Failed to set microstep resolution: %d", ret); + return ret; + } + } + + ret = step_dir_stepper_common_init(dev); + if (ret < 0) { + LOG_ERR("Failed to init step dir common stepper: %d", ret); + return ret; + } + return 0; +} + +static DEVICE_API(stepper, a4988_stepper_api) = { + .enable = a4988_stepper_enable, + .move_by = step_dir_stepper_common_move_by, + .is_moving = step_dir_stepper_common_is_moving, + .set_reference_position = step_dir_stepper_common_set_reference_position, + .get_actual_position = step_dir_stepper_common_get_actual_position, + .move_to = step_dir_stepper_common_move_to, + .set_microstep_interval = step_dir_stepper_common_set_microstep_interval, + .run = step_dir_stepper_common_run, + .set_event_callback = step_dir_stepper_common_set_event_callback, + .set_micro_step_res = a4988_stepper_set_micro_step_res, + .get_micro_step_res = a4988_stepper_get_micro_step_res, +}; + +#define A4988_STEPPER_DEFINE(inst, msx_table) \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, msx_gpios), ( \ + static const struct gpio_dt_spec a4988_stepper_msx_pins_##inst[] = { \ + DT_INST_FOREACH_PROP_ELEM_SEP( \ + inst, msx_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,) \ + ), \ + }; \ + BUILD_ASSERT( \ + ARRAY_SIZE(a4988_stepper_msx_pins_##inst) == MSX_PIN_COUNT, \ + "Three microstep config pins needed"); \ + )) \ + \ + static const struct a4988_config a4988_config_##inst = { \ + .common = STEP_DIR_STEPPER_DT_INST_COMMON_CONFIG_INIT(inst), \ + .enable_pin = GPIO_DT_SPEC_INST_GET(inst, en_gpios), \ + .msx_resolutions = msx_table, \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, msx_gpios), \ + (.msx_pins = (struct gpio_dt_spec *)a4988_stepper_msx_pins_##inst)) }; \ + static struct a4988_data a4988_data_##inst = { \ + .common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \ + .resolution = DT_INST_PROP(inst, micro_step_res), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, a4988_stepper_init, NULL, &a4988_data_##inst, \ + &a4988_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \ + &a4988_stepper_api); + +DT_INST_FOREACH_STATUS_OKAY_VARGS(A4988_STEPPER_DEFINE, a4988_msx_resolutions) diff --git a/dts/bindings/stepper/allegro/allegro,a4988.yaml b/dts/bindings/stepper/allegro/allegro,a4988.yaml new file mode 100644 index 0000000000000..dbf9405c35aa3 --- /dev/null +++ b/dts/bindings/stepper/allegro/allegro,a4988.yaml @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma +# SPDX-License-Identifier: Apache-2.0 + +description: | + Allegro A4988 stepper motor driver. + + Example: + a4988: a4988 { + status = "okay"; + compatible = "allegro,a4988"; + + step-gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; + dir-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; + en-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + msx-gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>, + <&gpio0 17 GPIO_ACTIVE_HIGH>, + <&gpio0 5 GPIO_ACTIVE_HIGH>; + }; + +compatible: "allegro,a4988" + +include: + - name: stepper-controller.yaml + property-allowlist: + - micro-step-res + - step-gpios + - dir-gpios + - en-gpios + + +properties: + msx-gpios: + type: phandle-array + description: | + An array of GPIO pins for configuring the microstep resolution of the driver. + The pins should be listed in the following order: + - MS1 + - MS2 + - MS3 + + sleep-gpios: + type: phandle-array + description: Sleep pin + + reset-gpios: + type: phandle-array + description: Reset pin diff --git a/samples/drivers/stepper/a4988/CMakeLists.txt b/samples/drivers/stepper/a4988/CMakeLists.txt new file mode 100644 index 0000000000000..ed4d57bddafaa --- /dev/null +++ b/samples/drivers/stepper/a4988/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(a4988) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/stepper/a4988/Kconfig b/samples/drivers/stepper/a4988/Kconfig new file mode 100644 index 0000000000000..0e9925c0cbe61 --- /dev/null +++ b/samples/drivers/stepper/a4988/Kconfig @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "A4988 sample application" + +config STEPS_PER_REV + int "Steps per revolution" + default 200 + + +source "Kconfig.zephyr" diff --git a/samples/drivers/stepper/a4988/README.rst b/samples/drivers/stepper/a4988/README.rst new file mode 100644 index 0000000000000..6af1fb55c98d4 --- /dev/null +++ b/samples/drivers/stepper/a4988/README.rst @@ -0,0 +1,23 @@ +.. zephyr:code-sample:: stepper/a4988 + :name: A4988 Stepper Driver + :relevant-api: stepper_interface + + Rotate a A4988 stepper motor. + +Overview +******** + +This sample applications rotates the A4988 stepper motor at a constant speed. + + +Building and Running +******************** + +The sample applications spins the stepper and outputs the events to the console. It requires +an A4988 stepper driver. It should work with any platform with enough gpios to spare. +It does not work on QEMU. + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/stepper/a4988 + :board: stm32_min_dev + :goals: build flash diff --git a/samples/drivers/stepper/a4988/boards/stm32_min_dev.overlay b/samples/drivers/stepper/a4988/boards/stm32_min_dev.overlay new file mode 100644 index 0000000000000..ee767b607c439 --- /dev/null +++ b/samples/drivers/stepper/a4988/boards/stm32_min_dev.overlay @@ -0,0 +1,32 @@ +/ { + + chosen { + zephyr,console=&cdc_acm_uart0; + }; + + aliases { + stepper = &a4988; + }; + + a4988: a4988 { + compatible = "allegro,a4988"; + status = "okay"; + + step-gpios = <&gpiob 45 GPIO_ACTIVE_HIGH>; + dir-gpios = <&gpiob 46 GPIO_ACTIVE_HIGH>; + en-gpios = <&gpioa 38 GPIO_ACTIVE_LOW>; + sleep-gpios = <&gpiob 43 GPIO_ACTIVE_LOW>; + reset-gpios = <&gpiob 42 GPIO_ACTIVE_HIGH>; + msx-gpios = <&gpiob 39 GPIO_ACTIVE_HIGH>, + <&gpiob 40 GPIO_ACTIVE_HIGH>, + <&gpiob 41 GPIO_ACTIVE_HIGH>; + micro-step-res = <16>; + }; +}; + +&zephyr_udc0 { + cdc_acm_uart0: cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + label = "CDC_ACM_0"; + }; +}; diff --git a/samples/drivers/stepper/a4988/prj.conf b/samples/drivers/stepper/a4988/prj.conf new file mode 100644 index 0000000000000..13cce23209850 --- /dev/null +++ b/samples/drivers/stepper/a4988/prj.conf @@ -0,0 +1,14 @@ +CONFIG_STDOUT_CONSOLE=y +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_PRODUCT="Zephyr CDC ACM sample" +CONFIG_USB_DEVICE_PID=0x0001 +CONFIG_LOG=y +CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_UART_LINE_CTRL=y +CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n + +CONFIG_STEPPER=y +CONFIG_STEP_DIR_STEPPER_COUNTER_TIMING=y diff --git a/samples/drivers/stepper/a4988/sample.yaml b/samples/drivers/stepper/a4988/sample.yaml new file mode 100644 index 0000000000000..42d30256c209e --- /dev/null +++ b/samples/drivers/stepper/a4988/sample.yaml @@ -0,0 +1,7 @@ +sample: + name: A4988 Stepper Sample + +tests: + sample.stepper.a4988: + harness: stepper + tags: stepper diff --git a/samples/drivers/stepper/a4988/src/main.c b/samples/drivers/stepper/a4988/src/main.c new file mode 100644 index 0000000000000..6025fee287c1a --- /dev/null +++ b/samples/drivers/stepper/a4988/src/main.c @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Marvin Ouma + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(a4988log); + +const struct device *stepper = DEVICE_DT_GET(DT_ALIAS(stepper)); +const struct device *const console = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); +uint32_t dtr = 0; + +int32_t ping_pong_target_position = + CONFIG_STEPS_PER_REV * 1 * DT_PROP(DT_ALIAS(stepper), micro_step_res); + +K_SEM_DEFINE(steps_completed_sem, 0, 1); + +void stepper_callback(const struct device *dev, const enum stepper_event event, void *user_data) +{ + switch (event) { + case STEPPER_EVENT_STEPS_COMPLETED: + k_sem_give(&steps_completed_sem); + break; + default: + break; + } +} + +int main(void) +{ + if (usb_enable(NULL)) { + return 0; + } + + while (!dtr) { + uart_line_ctrl_get(console, UART_LINE_CTRL_DTR, &dtr); + k_sleep(K_MSEC(100)); + } + + LOG_INF("Starting A4988 stepper sample"); + if (!device_is_ready(stepper)) { + LOG_INF("Device %s is not ready", stepper->name); + return 0; + } + LOG_INF("stepper is %p, name is %s", stepper, stepper->name); + LOG_INF("target position %d", ping_pong_target_position); + stepper_set_event_callback(stepper, stepper_callback, NULL); + stepper_enable(stepper, true); + stepper_set_reference_position(stepper, 0); + stepper_set_microstep_interval(stepper, 200); + stepper_move_by(stepper, ping_pong_target_position); + + for (;;) { + if (k_sem_take(&steps_completed_sem, K_FOREVER) == 0) { + ping_pong_target_position = 1; + stepper_run(stepper, 1); + } + } + return 0; +}