Skip to content

Commit

Permalink
Merge #18392 #19292
Browse files Browse the repository at this point in the history
18392: drivers/servo: reimplement with high level interface r=benpicco a=maribu

### Contribution description

The previous servo driver didn't provide any benefit over using PWM directly, as users controlled the servo in terms of PWM duty cycles. This changes the interface to provide a high level interface that abstracts the gory PWM details.

In addition, a SAUL layer and auto-initialization is provided.

### Testing procedure

The test application provides access to the servo driver via the `saul` shell command.

```
> saul
2022-08-02 22:12:31,826 # saul
2022-08-02 22:12:31,827 # ID	Class		Name
2022-08-02 22:12:31,830 # #0	ACT_SWITCH	LD1(green)
2022-08-02 22:12:31,832 # #1	ACT_SWITCH	LD2(blue)
2022-08-02 22:12:31,834 # #2	ACT_SWITCH	LD3(red)
2022-08-02 22:12:31,837 # #3	SENSE_BTN	B1(User button)
2022-08-02 22:12:31,838 # #4	ACT_SERVO	servo
> saul write 4 0
2022-08-02 22:12:41,443 # saul write 4 0
2022-08-02 22:12:41,445 # Writing to device #4 - servo
2022-08-02 22:12:41,447 # Data:	             0 
2022-08-02 22:12:41,450 # [servo] setting 0 to 2949 (0 / 255)
2022-08-02 22:12:41,453 # data successfully written to device #4
> saul write 4 256
2022-08-02 22:12:45,343 # saul write 4 256
2022-08-02 22:12:45,346 # Writing to device #4 - servo
2022-08-02 22:12:45,347 # Data:	           256 
2022-08-02 22:12:45,351 # [servo] setting 0 to 6865 (255 / 255)
2022-08-02 22:12:45,354 # data successfully written to device #4
```

Each write resulted in the MG90S servo that I connected to move to the corresponding position.

### Issues/PRs references

19292: sys/phydat: Fix unit confusion r=benpicco a=maribu

### Contribution description

Previously, `UNIT_G` was used for g-force with the correct symbol `g`, `UNIT_GR` for gram (as in kilogram) with the incorrect symbol `G` (which would be correct for Gauss), and `UNIT_GS` for Gauss with symbol `Gs` (which is an alternative correct symbol).

To avoid confusion between G-Force, Gauss, and Gram the units have been renamed to `UNIT_G_FORCE`, `UNIT_GRAM`, and `UNIT_GAUSS`. In addition, gram now uses the correct symbol `g`; which sadly is the same as for g-force. But usually there is enough context to tell them apart.

### Testing procedure

Green CI

### Issues/PRs references

None

Co-authored-by: Marian Buschsieweke <[email protected]>
  • Loading branch information
bors[bot] and maribu authored Feb 21, 2023
3 parents 37b6491 + 463e2e7 + 65a9fa2 commit eea95c1
Show file tree
Hide file tree
Showing 38 changed files with 1,014 additions and 254 deletions.
2 changes: 1 addition & 1 deletion drivers/adxl345/adxl345_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ static int read_acc(const void *dev, phydat_t *res)
{
adxl345_read((const adxl345_t *)dev, (adxl345_data_t *)res->val);

res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;
res->scale = -3;
return 3;
}
Expand Down
4 changes: 2 additions & 2 deletions drivers/bmx055/bmx055_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static int read_mag(const void *dev, phydat_t *res)
if (bmx055_mag_read(mydev, res->val) != BMX055_OK) {
return 0;
}
res->unit = UNIT_GS;
res->unit = UNIT_GAUSS;
res->scale = 0;
return 3;
}
Expand All @@ -41,7 +41,7 @@ static int read_acc(const void *dev, phydat_t *res)
if (bmx055_acc_read(mydev, res->val) != BMX055_OK) {
return 0;
}
res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;
res->scale = -3;
return 3;
}
Expand Down
4 changes: 2 additions & 2 deletions drivers/fxos8700/fxos8700_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ static int read_mag(const void *dev, phydat_t *res)
/* Read failure */
return -ECANCELED;
}
res->unit = UNIT_GS;
res->unit = UNIT_GAUSS;
res->scale = -3;
return 3;
}
Expand All @@ -48,7 +48,7 @@ static int read_acc(const void *dev, phydat_t *res)
res->scale = 0;
}
else {
res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;
if (((fxos8700_t *)dev)->p.acc_range == FXOS8700_REG_XYZ_DATA_CFG_FS__2G) {
res->scale = -4;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/hmc5883l/hmc5883l_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static int read(const void *dev, phydat_t *res)
res->val[0] = data.x;
res->val[1] = data.y;
res->val[2] = data.z;
res->unit = UNIT_GS;
res->unit = UNIT_GAUSS;
res->scale = -3;
return 3;
}
Expand Down
230 changes: 185 additions & 45 deletions drivers/include/servo.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Freie Universität Berlin
* Copyright (C) 2015 Eistec AB
* Copyright (C) 2022 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
Expand All @@ -11,82 +12,221 @@
* @defgroup drivers_servo Servo Motor Driver
* @ingroup drivers_actuators
* @brief High-level driver for servo motors
*
* Usage
* =====
*
* Select a flavor of the driver, e.g. `USEMODULE += servo_pwm` for
* @ref drivers_servo_pwm or `USEMODULE += servo_timer` for
* @ref drivers_servo_timer to use. Typically, the PWM implementation is the
* preferred one, but some MCU (e.g. nRF52xxx) cannot configure the PWM
* peripheral to run anywhere close to the 50 Hz to 100 Hz required.
*
* In addition, you many need to extend or adapt @ref servo_params and,
* depending on the selected implementation, @ref servo_pwm_params or
* @ref servo_timer_params to match your hardware configuration.
*
* The test application in `tests/driver_servo` can serve as starting point for
* users.
*
* @{
*
* @file
* @brief High-level driver for easy handling of servo motors
*
* @author Hauke Petersen <[email protected]>
* @author Joakim Nohlgård <[email protected]>
* @author Marian Buschsieweke <[email protected]>
*/

#ifndef SERVO_H
#define SERVO_H

#include <stddef.h>
#include <stdint.h>

#include "periph/pwm.h"
#include "periph/timer.h"
#include "saul.h"
#include "saul_reg.h"
#include "time_units.h"

#ifndef SERVO_TIMER_MAX_CHAN
/**
* @brief In case the `servo_timer` backend is used to driver the servo,
* this is the highest channel number usable by the driver
*
* @note To drive *n* servos, *n* + 1 timer channels are required. Hence,
* this must be at least 2
*
* Trimming this down safes a small bit of RAM: Storage for one pointer is
* wasted on every servo that could be controlled by a timer but is not
* actually used.
*/
#define SERVO_TIMER_MAX_CHAN 4
#endif

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Descriptor struct for a servo
* @brief The SAUL adaption driver for servos
*/
extern const saul_driver_t servo_saul_driver;

/**
* @brief PWM configuration parameters for a servos
*
* Only used with
*/
typedef struct {
pwm_t device; /**< the PWM device driving the servo */
int channel; /**< the channel the servo is connected to */
unsigned int min; /**< minimum pulse width, in us */
unsigned int max; /**< maximum pulse width, in us */
unsigned int scale_nom; /**< timing scale factor, to adjust for an inexact PWM frequency, nominator */
unsigned int scale_den; /**< timing scale factor, to adjust for an inexact PWM frequency, denominator */
} servo_t;
uint16_t res; /**< PWM resolution to use */
uint16_t freq; /**< PWM frequency to use */
pwm_t pwm; /**< PWM dev the servo is connected to */
} servo_pwm_params_t;

/**
* @brief Initialize a servo motor by assigning it a PWM device and channel
*
* Digital servos are controlled by regular pulses sent to them. The width
* of a pulse determines the position of the servo. A pulse width of 1.5ms
* puts the servo in the center position, a pulse width of about 1.0ms and
* about 2.0ms put the servo to the maximum angles. These values can however
* differ slightly from servo to servo, so the min and max values are
* parameterized in the init function.
*
* The servo is initialized with default PWM values:
* - frequency: 100Hz (10ms interval)
* - resolution: 10000 (1000 steps per ms)
*
* These default values can be changed by setting SERVO_RESOLUTION and
* SERVO_FREQUENCY macros.
* Caution: When initializing a servo, the PWM device will be reconfigured to
* new frequency/resolution values. It is however fine to use multiple servos
* with the same PWM device, just on different channels.
*
* @param[out] dev struct describing the servo
* @param[in] pwm the PWM device the servo is connected to
* @param[in] pwm_channel the PWM channel the servo is connected to
* @param[in] min minimum pulse width (in the resolution range)
* @param[in] max maximum pulse width (in the resolution range)
*
* @return 0 on success
* @return <0 on error
* @brief Servo device state
*/
int servo_init(servo_t *dev, pwm_t pwm, int pwm_channel, unsigned int min, unsigned int max);
typedef struct servo servo_t;

/**
* @brief Set the servo motor to a specified position
* @brief Memory needed for book keeping when using @ref drivers_servo_timer
*/
typedef struct {
/**
* @brief Look up table to get from channel
*
* @note Since timer channel 0 is used to set all servo pins, we use
* `chan - 1` as idx rather than `chan` to not waste one entry.
*/
servo_t *servo_map[SERVO_TIMER_MAX_CHAN];
} servo_timer_ctx_t;

/**
* @brief Timer configuration parameters for a servos
*/
typedef struct {
tim_t timer; /**< Timer to use */
uint32_t timer_freq; /**< Timer frequency to use */
uint16_t servo_freq; /**< Servo frequency (typically 50 Hz or 100 Hz) */
servo_timer_ctx_t *ctx; /**< Per-timer state needed for book keeping */
} servo_timer_params_t;

/**
* @brief Configuration parameters for a servo
*/
typedef struct {
#if defined(MODULE_SERVO_PWM) || defined(DOXYGEN)
/**
* @brief Specification of the PWM device the servo is connected to
*
* @note Only available when @ref drivers_servo_pwm is used
*/
const servo_pwm_params_t *pwm;
#endif
#if defined(MODULE_SERVO_TIMER) || defined(DOXYGEN)
/**
* @brief Specification of the timer to use
*
* @note Only available when @ref drivers_servo_timer is used
*/
const servo_timer_params_t *timer;
/**
* @brief GPIO pin the servo is connected to
*
* @note Only available when @ref drivers_servo_timer is used
*/
gpio_t servo_pin;
#endif
uint16_t min_us; /**< Duration of high phase (in µs) for min extension */
uint16_t max_us; /**< Duration of high phase (in µs) for max extension */
#ifdef MODULE_SERVO_PWM
/**
* @brief PWM channel to use
*
* @note Only available when @ref drivers_servo_pwm is used
*/
uint8_t pwm_chan;
#endif
#ifdef MODULE_SERVO_TIMER
/**
* @brief Timer channel to use
*
* @pre `(timer_chan > 0) && (timer_chan <= SERVO_TIMER_MAX_CHAN)`
*
* @note Only available when @ref drivers_servo_timer is used
*
* The timer channel 0 is used to set the GPIO pin of all servos
* driver by the timer, the other channels are used to clean the GPIO pin
* of the corresponding servo according to the current duty cycle.
*/
uint8_t timer_chan;
#endif
} servo_params_t;

/**
* @brief Servo device state
*/
struct servo {
const servo_params_t *params; /**< Parameters of this servo */
/**
* @brief Minimum PWM duty cycle / timer target matching
* @ref servo_params_t::min_us
*
* Note that the actual PWM frequency can be significantly different from
* the requested one, depending on what the hardware can generate using the
* clock source and clock dividers available.
*/
uint16_t min;
/**
* @brief Maximum PWM duty cycle / timer target matching
* @ref servo_params_t::min_us
*
* Note that the actual PWM frequency can be significantly different from
* the requested one, depending on what the hardware can generate using the
* clock source and clock dividers available.
*/
uint16_t max;
#ifdef MODULE_SERVO_TIMER
uint16_t current; /**< Current timer target */
#endif
};

#if defined(MODULE_SERVO_TIMER) || DOXYGEN
/**
* @brief Default timer context
*/
extern servo_timer_ctx_t servo_timer_default_ctx;
#endif

/**
* @brief Initialize servo
*
* The position of the servo is specified in the pulse width that
* controls the servo. With default configurations, a value of 1500
* means a pulse width of 1.5 ms, which is the center position on
* most servos.
* @param[out] dev Device handle to initialize
* @param[in] params Parameters defining the PWM configuration
*
* In case pos is larger/smaller then the max/min values, pos will be set to
* these values.
* @retval 0 Success
* @retval <0 Failure (as negative errno code to indicate cause)
*/
int servo_init(servo_t *dev, const servo_params_t *params);

/**
* @brief Set the servo motor to a specified position
*
* The position of the servo is specified in the fraction of maximum extension,
* with 0 being the lowest extension (e.g. on an 180° servo it would be at -90°)
* and `UINT8_MAX` being the highest extension (e.g. +90° on that 180° servo).
*
* @param[in] dev the servo to set
* @param[in] pos the position to set the servo (in the resolution range)
* @param[in] pos the extension to set
*
* Note: 8 bit of resolution may seem low, but is indeed more than high enough
* for any practical PWM based servo. For higher precision, stepper motors would
* be required.
*/
void servo_set(const servo_t *dev, unsigned int pos);
void servo_set(servo_t *dev, uint8_t pos);

#ifdef __cplusplus
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/lis2dh12/lis2dh12_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ static int read_accelerometer(const void *dev, phydat_t *res)
if (lis2dh12_read(dev, (lis2dh12_fifo_data_t*)res->val) != LIS2DH12_OK) {
return 0;
}
res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;
res->scale = -3;
return 3;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/lis3dh/lis3dh_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static int read_acc(const void *dev, phydat_t *res)
res->val[2] = xyz.acc_z;
/* unit: milli-G */
res->scale = -3;
res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;

return 3;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/lis3mdl/lis3mdl_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static int read_mag(const void *dev, phydat_t *res)

lis3mdl_read_mag(d, (lis3mdl_3d_data_t *)res);

res->unit = UNIT_GS;
res->unit = UNIT_GAUSS;
res->scale = -3;
return 3;
}
Expand Down
4 changes: 2 additions & 2 deletions drivers/lsm303dlhc/lsm303dlhc_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static int read_acc(const void *dev, phydat_t *res)
res->val[i] *= fac;
}

res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;
res->scale = -3;
return 3;
}
Expand Down Expand Up @@ -63,7 +63,7 @@ static int read_mag(const void *dev, phydat_t *res)
res->val[i] = (int16_t)tmp;
}

res->unit = UNIT_GS;
res->unit = UNIT_GAUSS;
res->scale = -3;
return 3;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/lsm6dsl/lsm6dsl_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ static int read_acc(const void *dev, phydat_t *res)
}

res->scale = -3;
res->unit = UNIT_G;
res->unit = UNIT_G_FORCE;

return 3;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/mag3110/mag3110_saul.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static int read_mag(const void *dev, phydat_t *res)
{
mag3110_read((const mag3110_t *)dev, (mag3110_data_t *)res->val);

res->unit = UNIT_GS;
res->unit = UNIT_GAUSS;
res->scale = 2;

return 3;
Expand Down
Loading

0 comments on commit eea95c1

Please sign in to comment.