From 98b15b140907ddc004e92602ed37a944d6d53629 Mon Sep 17 00:00:00 2001 From: Philippe Leduc Date: Sun, 15 Oct 2023 10:10:29 +0200 Subject: [PATCH] Cleanup interface and a4988 implementation Change step frequency from step/ms to step/s Add DRV8825 stepper driver --- drivers/motor/CMakeLists.txt | 4 + drivers/motor/Kconfig | 6 + drivers/motor/Make.defs | 3 + drivers/motor/a4988.c | 37 +-- drivers/motor/drv8825.c | 346 ++++++++++++++++++++++++++++ drivers/motor/stepper.c | 22 +- include/nuttx/motor/a4988.h | 2 +- include/nuttx/motor/drv8825.h | 105 +++++++++ include/nuttx/motor/stepper.h | 22 +- include/nuttx/motor/stepper_ioctl.h | 7 +- 10 files changed, 517 insertions(+), 37 deletions(-) create mode 100644 drivers/motor/drv8825.c create mode 100644 include/nuttx/motor/drv8825.h diff --git a/drivers/motor/CMakeLists.txt b/drivers/motor/CMakeLists.txt index b3f58bdbc9..ef59b5ab0f 100644 --- a/drivers/motor/CMakeLists.txt +++ b/drivers/motor/CMakeLists.txt @@ -32,3 +32,7 @@ endif() if(CONFIG_STEPPER_A4988) target_sources(drivers PRIVATE a4988.c) endif() + +if(CONFIG_STEPPER_DRV8825) + target_sources(drivers PRIVATE drv8825.c) +endif() diff --git a/drivers/motor/Kconfig b/drivers/motor/Kconfig index 8cfe8171ea..5f0943c34d 100644 --- a/drivers/motor/Kconfig +++ b/drivers/motor/Kconfig @@ -81,4 +81,10 @@ config STEPPER_A4988 ---help--- Enables A4988 stepper driver. +config STEPPER_DRV8825 + bool "DRV8825 Stepper Motor Driver" + default n + ---help--- + Enables DRV8825 stepper driver. + endif # STEPPER diff --git a/drivers/motor/Make.defs b/drivers/motor/Make.defs index b415e79295..4a683548a2 100644 --- a/drivers/motor/Make.defs +++ b/drivers/motor/Make.defs @@ -40,6 +40,9 @@ ifeq ($(CONFIG_STEPPER_A4988),y) CSRCS += a4988.c endif +ifeq ($(CONFIG_STEPPER_DRV8825),y) +CSRCS += drv8825.c +endif # Include motor drivers in the build diff --git a/drivers/motor/a4988.c b/drivers/motor/a4988.c index b5a3bbe390..84d6c625e3 100644 --- a/drivers/motor/a4988.c +++ b/drivers/motor/a4988.c @@ -39,7 +39,6 @@ struct a4988_dev_s { FAR struct a4988_ops_s *ops; /* A4988 ops */ - int32_t position; uint8_t auto_idle; /* If true, go in idle mode between movement */ }; @@ -51,8 +50,7 @@ static int a4988_setup(FAR struct stepper_lowerhalf_s *dev); static int a4988_shutdown(FAR struct stepper_lowerhalf_s *dev); static int a4988_work(FAR struct stepper_lowerhalf_s *dev, FAR struct stepper_job_s const *param); -static int a4988_state(FAR struct stepper_lowerhalf_s *dev, - FAR struct stepper_state_s *state); +static int a4988_update_status(FAR struct stepper_lowerhalf_s *dev); static int a4988_clear(FAR struct stepper_lowerhalf_s *dev, uint8_t fault); static int a4988_idle(FAR struct stepper_lowerhalf_s *dev, uint8_t idle); static int a4988_microstepping(FAR struct stepper_lowerhalf_s *dev, @@ -69,7 +67,7 @@ static const struct stepper_ops_s g_a4988_ops = a4988_setup, /* setup */ a4988_shutdown, /* shutdown */ a4988_work, /* work */ - a4988_state, /* state */ + a4988_update_status, /* update status */ a4988_clear, /* clear */ a4988_idle, /* idle */ a4988_microstepping, /* microstepping */ @@ -114,12 +112,15 @@ static int a4988_work(FAR struct stepper_lowerhalf_s *dev, /* Compute delay between pulse */ - delay = USEC_PER_MSEC / job->speed; + delay = USEC_PER_SEC / job->speed; if (delay < 1) { delay = 1; + stpwarn("Delay is clamped to 1 us\n"); } + stpinfo("Delay is %ld us\n", delay); + /* Set direction */ if (job->steps > 0) @@ -139,6 +140,7 @@ static int a4988_work(FAR struct stepper_lowerhalf_s *dev, usleep(USEC_PER_MSEC); } + dev->status.state = STEPPER_STATE_RUN; for (int32_t i = 0; i < count; ++i) { priv->ops->step(true); @@ -147,30 +149,32 @@ static int a4988_work(FAR struct stepper_lowerhalf_s *dev, up_udelay(delay); } + dev->status.state = STEPPER_STATE_READY; + if (priv->auto_idle) { priv->ops->idle(true); } - /* Update position */ + /* Update steps done (a4988 cannot detect miss steps) */ - priv->position += job->steps; + dev->status.position += job->steps; return 0; } -static int a4988_state(FAR struct stepper_lowerhalf_s *dev, - FAR struct stepper_state_s *state) +static int a4988_update_status(FAR struct stepper_lowerhalf_s *dev) { - FAR struct a4988_dev_s *priv = (FAR struct a4988_dev_s *)dev->priv; - state->position = priv->position; + /* No state to fetch */ return 0; } static int a4988_clear(FAR struct stepper_lowerhalf_s *dev, uint8_t fault) { - return -ENOSYS; + /* No fault to clear ever */ + + return 0; } static int a4988_idle(FAR struct stepper_lowerhalf_s *dev, uint8_t idle) @@ -188,11 +192,13 @@ static int a4988_idle(FAR struct stepper_lowerhalf_s *dev, uint8_t idle) if (idle == STEPPER_ENABLE_IDLE) { priv->ops->idle(true); + dev->status.state = STEPPER_STATE_IDLE; } else { priv->ops->idle(false); usleep(USEC_PER_MSEC); + dev->status.state = STEPPER_STATE_READY; } return 0; @@ -274,7 +280,6 @@ int a4988_register(FAR const char *devpath, FAR struct a4988_ops_s *ops) } priv->ops = ops; - priv->position = 0; lower = kmm_malloc(sizeof(struct stepper_lowerhalf_s)); if (priv == NULL) @@ -285,9 +290,9 @@ int a4988_register(FAR const char *devpath, FAR struct a4988_ops_s *ops) } lower->priv = priv; - lower->state.fault = STEPPER_FAULT_CLEAR; - lower->state.state = STEPPER_STATE_INIT; - lower->state.position = 0; + lower->status.fault = STEPPER_FAULT_CLEAR; + lower->status.state = STEPPER_STATE_READY; + lower->status.position = 0; lower->ops = &g_a4988_ops; /* Initialize lower layer (only once) */ diff --git a/drivers/motor/drv8825.c b/drivers/motor/drv8825.c new file mode 100644 index 0000000000..b9245aea66 --- /dev/null +++ b/drivers/motor/drv8825.c @@ -0,0 +1,346 @@ +/**************************************************************************** + * drivers/motor/drv8825.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct drv8825_dev_s +{ + FAR struct drv8825_ops_s *ops; /* drv8825 ops */ + uint8_t auto_idle; /* If true, go in idle mode between movement */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int drv8825_setup(FAR struct stepper_lowerhalf_s *dev); +static int drv8825_shutdown(FAR struct stepper_lowerhalf_s *dev); +static int drv8825_work(FAR struct stepper_lowerhalf_s *dev, + FAR struct stepper_job_s const *param); +static int drv8825_update_status(FAR struct stepper_lowerhalf_s *dev); +static int drv8825_clear(FAR struct stepper_lowerhalf_s *dev, uint8_t fault); +static int drv8825_idle(FAR struct stepper_lowerhalf_s *dev, uint8_t idle); +static int drv8825_microstepping(FAR struct stepper_lowerhalf_s *dev, + uint16_t resolution); +static int drv8825_ioctl(FAR struct stepper_lowerhalf_s *dev, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct stepper_ops_s g_drv8825_ops = +{ + drv8825_setup, /* setup */ + drv8825_shutdown, /* shutdown */ + drv8825_work, /* work */ + drv8825_update_status, /* update status */ + drv8825_clear, /* clear */ + drv8825_idle, /* idle */ + drv8825_microstepping, /* microstepping */ + drv8825_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int drv8825_setup(FAR struct stepper_lowerhalf_s *dev) +{ + FAR struct drv8825_dev_s *priv = (FAR struct drv8825_dev_s *)dev->priv; + priv->ops->idle(false); + priv->ops->enable(true); + + return 0; +} + +static int drv8825_shutdown(FAR struct stepper_lowerhalf_s *dev) +{ + FAR struct drv8825_dev_s *priv = (FAR struct drv8825_dev_s *)dev->priv; + priv->ops->idle(true); + priv->ops->enable(false); + + return 0; +} + +static int drv8825_work(FAR struct stepper_lowerhalf_s *dev, + FAR struct stepper_job_s const *job) +{ + FAR struct drv8825_dev_s *priv = (FAR struct drv8825_dev_s *)dev->priv; + int delay; + int count; + + if (priv->ops->fault()) + { + /* In fault: do not proceed */ + + return -EIO; + } + + if (job->steps == 0) + { + /* Nothing to do */ + + return 0; + } + + /* Compute delay between pulse */ + + delay = USEC_PER_SEC / job->speed; + if (delay < 2) + { + delay = 2; + stpwarn("Delay is clamped to 2 us\n"); + } + + stpinfo("Delay is %ld us\n", delay); + + /* Set direction */ + + if (job->steps > 0) + { + priv->ops->direction(true); + count = job->steps; + } + else + { + priv->ops->direction(false); + count = -job->steps; + } + + if (priv->auto_idle) + { + priv->ops->idle(false); + usleep(USEC_PER_MSEC * 2); + } + + dev->status.state = STEPPER_STATE_RUN; + for (int32_t i = 0; i < count; ++i) + { + priv->ops->step(true); + up_udelay(2); + priv->ops->step(false); + up_udelay(delay); + } + + dev->status.state = STEPPER_STATE_READY; + + if (priv->auto_idle) + { + priv->ops->idle(true); + } + + /* Update steps done (drv8825 cannot detect miss steps) */ + + dev->status.position += job->steps; + + return 0; +} + +static int drv8825_update_status(FAR struct stepper_lowerhalf_s *dev) +{ + FAR struct drv8825_dev_s *priv = (FAR struct drv8825_dev_s *)dev->priv; + + if (priv->ops->fault()) + { + /* The same pin is used for overtemp and overcurrent fault. + * Since the current implementation is blocking and fetching + * is on demand (no interrupt), it is impossible to detect + * overcurrent. + */ + + dev->status.fault = STEPPER_FAULT_OVERTEMP; + dev->status.state = STEPPER_STATE_FAULT; + } + + return 0; +} + +static int drv8825_clear(FAR struct stepper_lowerhalf_s *dev, uint8_t fault) +{ + /* No fault to clear ever */ + + dev->status.fault &= ~fault; + if (dev->status.fault == STEPPER_FAULT_CLEAR) + { + dev->status.state = STEPPER_STATE_READY; + } + + return 0; +} + +static int drv8825_idle(FAR struct stepper_lowerhalf_s *dev, uint8_t idle) +{ + FAR struct drv8825_dev_s *priv = (FAR struct drv8825_dev_s *)dev->priv; + + if (idle == STEPPER_AUTO_IDLE) + { + priv->auto_idle = true; + return 0; + } + + priv->auto_idle = false; + + if (idle == STEPPER_ENABLE_IDLE) + { + priv->ops->idle(true); + dev->status.state = STEPPER_STATE_IDLE; + } + else + { + priv->ops->idle(false); + usleep(USEC_PER_MSEC * 2); + dev->status.state = STEPPER_STATE_READY; + } + + return 0; +} + +static int drv8825_microstepping(FAR struct stepper_lowerhalf_s *dev, + uint16_t resolution) +{ + FAR struct drv8825_dev_s *priv = (FAR struct drv8825_dev_s *)dev->priv; + + switch (resolution) + { + case 1: + { + priv->ops->microstepping(false, false, false); + } + break; + + case 2: + { + priv->ops->microstepping(true, false, false); + } + break; + + case 4: + { + priv->ops->microstepping(false, true, false); + } + break; + + case 8: + { + priv->ops->microstepping(true, true, false); + } + break; + + case 16: + { + priv->ops->microstepping(false, false, true); + } + break; + + case 32: + { + priv->ops->microstepping(true, true, true); + } + break; + + default: + { + return -EINVAL; + } + } + + return 0; +} + +static int drv8825_ioctl(FAR struct stepper_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int drv8825_register(FAR const char *devpath, FAR struct drv8825_ops_s *ops) +{ + FAR struct drv8825_dev_s *priv; + FAR struct stepper_lowerhalf_s *lower; + int ret = 0; + + /* Sanity check */ + + DEBUGASSERT(ops != NULL); + + /* Initialize the drv8825 dev structure */ + + priv = kmm_malloc(sizeof(struct drv8825_dev_s)); + if (priv == NULL) + { + stperr("Failed to allocate instance\n"); + return -ENOMEM; + } + + priv->ops = ops; + + lower = kmm_malloc(sizeof(struct stepper_lowerhalf_s)); + if (priv == NULL) + { + stperr("Failed to allocate instance\n"); + kmm_free(priv); + return -ENOMEM; + } + + lower->priv = priv; + lower->status.fault = STEPPER_FAULT_CLEAR; + lower->status.state = STEPPER_STATE_READY; + lower->status.position = 0; + lower->ops = &g_drv8825_ops; + + /* Initialize lower layer (only once) */ + + priv->ops->initialize(); + + /* Register the character driver */ + + ret = stepper_register(devpath, lower); + if (ret < 0) + { + stperr("Failed to register driver: %d\n", ret); + kmm_free(priv); + kmm_free(lower); + return ret; + } + + stpinfo("drv8825 registered at %s\n", devpath); + return ret; +} diff --git a/drivers/motor/stepper.c b/drivers/motor/stepper.c index f1e64978a7..ef6da0b97e 100644 --- a/drivers/motor/stepper.c +++ b/drivers/motor/stepper.c @@ -168,23 +168,24 @@ static ssize_t stepper_read(FAR struct file *filep, FAR char *buffer, FAR struct inode *inode = filep->f_inode; FAR struct stepper_upperhalf_s *stepper = inode->i_private; FAR struct stepper_lowerhalf_s *lower = stepper->lower; - FAR struct stepper_state_s *state; + FAR struct stepper_status_s *status; int ret; - if (buflen != sizeof(struct stepper_state_s)) + if (buflen != sizeof(struct stepper_status_s)) { return -EINVAL; } - state = (FAR struct stepper_state_s *)buffer; - ret = lower->ops->state(lower, state); + status = (FAR struct stepper_status_s *)buffer; + ret = lower->ops->update_status(lower); if (ret < 0) { - stperr("Get stepper state failed: %d\n", ret); + stperr("Update stepper state failed: %d\n", ret); return ret; } - return sizeof(struct stepper_state_s); + *status = lower->status; + return sizeof(struct stepper_status_s); } /**************************************************************************** @@ -261,6 +262,15 @@ static int stepper_ioctl(FAR struct file *filep, int cmd, unsigned long arg) } break; + case STEPIOC_SET_CURRENT_POS: + { + lower->status.position = (int32_t)arg; + ret = 0; + stpinfo("STEPIOC_SET_CURRENT_POS: new position is %ld\n", + lower->status.position); + } + break; + default: { ret = lower->ops->ioctl(lower, cmd, arg); diff --git a/include/nuttx/motor/a4988.h b/include/nuttx/motor/a4988.h index aee48c6bcc..bd4a092032 100644 --- a/include/nuttx/motor/a4988.h +++ b/include/nuttx/motor/a4988.h @@ -83,7 +83,7 @@ extern "C" * Register the a4988 character device as 'devpath' * * Input Parameters: - * devpath - The full path to the driver to register. E.g., "/dev/pwrmntr0" + * devpath - The full path to the driver to register. E.g., "/dev/stepper0" * Returned Value: * ops - operations on the concrete hardware * Zero (OK) on success; a negated errno value on failure. diff --git a/include/nuttx/motor/drv8825.h b/include/nuttx/motor/drv8825.h new file mode 100644 index 0000000000..8f696d29cc --- /dev/null +++ b/include/nuttx/motor/drv8825.h @@ -0,0 +1,105 @@ +/**************************************************************************** + * include/nuttx/motor/drv8825.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_MOTOR_DRV8825_H +#define __INCLUDE_NUTTX_MOTOR_DRV8825_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#if defined(CONFIG_STEPPER_DRV8825) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct drv8825_ops_s +{ + /* Initialize the control, called at register step */ + + CODE void (*initialize)(void); + + /* Control step output */ + + CODE void (*step)(int level); + + /* Direction */ + + CODE void (*direction)(int level); + + /* Configure microstepping */ + + CODE void (*microstepping)(int ms1, int ms2, int ms3); + + /* Enable control */ + + CODE void (*enable)(int level); + + /* Idle control */ + + CODE void (*idle)(int level); + + /* Fault fetch */ + + CODE int (*fault)(void); +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: drv8825_register + * + * Description: + * Register the drv8825 character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/stepper0" + * Returned Value: + * ops - operations on the concrete hardware + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int drv8825_register(FAR const char *devpath, FAR struct drv8825_ops_s *ops); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_STEPPER_DRV8825 */ +#endif /* __INCLUDE_NUTTX_DRIVERS_MOTOR_DRV8825_H */ diff --git a/include/nuttx/motor/stepper.h b/include/nuttx/motor/stepper.h index 1577c33e01..ef19c38767 100644 --- a/include/nuttx/motor/stepper.h +++ b/include/nuttx/motor/stepper.h @@ -61,8 +61,9 @@ enum stepper_state_e { STEPPER_STATE_INIT = 0, /* Initial state */ STEPPER_STATE_IDLE = 1, /* IDLE state */ - STEPPER_STATE_RUN = 2, /* Run state */ - STEPPER_STATE_FAULT = 3 /* Fault state */ + STEPPER_STATE_READY = 2, /* Ready to work */ + STEPPER_STATE_RUN = 3, /* Run state */ + STEPPER_STATE_FAULT = 4 /* Fault state */ }; /* Stepper driver fault type */ @@ -89,13 +90,13 @@ enum stepper_idle_e STEPPER_AUTO_IDLE = 2, /* Set automaticaly IDLE when stepper not in movement */ }; -/* Stepper driver state */ +/* Stepper driver status */ -struct stepper_state_s +struct stepper_status_s { uint8_t state; /* Stepper driver state */ uint8_t fault; /* Stepper driver faults */ - int32_t position; /* Feedback from motor - absolute position */ + int32_t position; /* Current absolute position */ }; /* Stepper parameters. */ @@ -103,7 +104,7 @@ struct stepper_state_s struct stepper_job_s { int32_t steps; /* Steps to do. Position: CW, Negative: CCW */ - uint16_t speed; /* Stepper speed in step/ms */ + uint32_t speed; /* Stepper speed in step/s */ }; /* Stepper operations used to call from the upper-half, generic stepper driver @@ -121,15 +122,14 @@ struct stepper_ops_s CODE int (*shutdown)(FAR struct stepper_lowerhalf_s *dev); - /* work */ + /* Work */ CODE int (*work)(FAR struct stepper_lowerhalf_s *dev, FAR struct stepper_job_s const *param); - /* Get motor state */ + /* Update motor/driver status */ - CODE int (*state)(FAR struct stepper_lowerhalf_s *dev, - FAR struct stepper_state_s *state); + CODE int (*update_status)(FAR struct stepper_lowerhalf_s *dev); /* Clear fault state */ @@ -158,7 +158,7 @@ struct stepper_lowerhalf_s { FAR const struct stepper_ops_s *ops; /* Arch-specific operations */ struct stepper_job_s param; /* Motor settings */ - struct stepper_state_s state; /* Motor state */ + struct stepper_status_s status; /* Motor status */ FAR void *priv; /* Private data */ }; diff --git a/include/nuttx/motor/stepper_ioctl.h b/include/nuttx/motor/stepper_ioctl.h index dc20f65866..bcb2cf8c04 100644 --- a/include/nuttx/motor/stepper_ioctl.h +++ b/include/nuttx/motor/stepper_ioctl.h @@ -33,8 +33,9 @@ * Pre-processor Definitions ****************************************************************************/ -#define STEPIOC_IDLE _STEPIOC(1) -#define STEPIOC_CLEAR_FAULT _STEPIOC(2) -#define STEPIOC_MICROSTEPPING _STEPIOC(3) +#define STEPIOC_IDLE _STEPIOC(1) +#define STEPIOC_CLEAR_FAULT _STEPIOC(2) +#define STEPIOC_MICROSTEPPING _STEPIOC(3) +#define STEPIOC_SET_CURRENT_POS _STEPIOC(4) #endif /* __INCLUDE_NUTTX_MOTOR_STEPPER_IOCTL_H */