Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ rsource "ads101x/Kconfig"
rsource "bmx055/Kconfig"
rsource "fxos8700/Kconfig"
rsource "gp2y10xx/Kconfig"
rsource "inc_encoder/Kconfig"
rsource "hdc1000/Kconfig"
rsource "hm330x/Kconfig"
rsource "hsc/Kconfig"
Expand Down
25 changes: 25 additions & 0 deletions drivers/inc_encoder/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: 2025 TU Dresden
# SPDX-License-Identifier: LGPL-2.1-only

config MODULE_INC_ENCODER
bool "Incremental Rotary Encoder"
depends on TEST_KCONFIG


menu "Incremental Rotary Encoder Driver"
depends on USEMODULE_INC_ENCODER

config INC_ENCODER_GEAR_RED_RATIO
int "Gear Reduction Ratio (in tenths)"
default 204
help
Defines the gear reduction ratio. For example a gear reduction ratio
of 1:20.4 would result in a value of 204.

config INC_ENCODER_PPR
int "Pulses per Revolution"
default 13
help
Number of Rising Edges per Revolution.

endmenu # Incremental Encoder Driver
1 change: 1 addition & 0 deletions drivers/inc_encoder/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTMAKE)/driver_with_saul.mk
3 changes: 3 additions & 0 deletions drivers/inc_encoder/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_gpio_irq
USEMODULE += ztimer_usec
2 changes: 2 additions & 0 deletions drivers/inc_encoder/Makefile.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
USEMODULE_INCLUDES_inc_encoder := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_inc_encoder)
156 changes: 156 additions & 0 deletions drivers/inc_encoder/inc_encoder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* SPDX-FileCopyrightText: 2025 TU Dresden
* SPDX-License-Identifier: LGPL-2.1-only
*/

/**
* @ingroup drivers_inc_encoder
* @{
*
* @file
* @brief Device driver implementation for a generic incremental rotary encoder
*
* @author Leonard Herbst <[email protected]>
*
* @}
*/

#include "inc_encoder.h"
#include "inc_encoder_params.h"

#include <errno.h>
#include "log.h"
#include "ztimer.h"
#include "time_units.h"

/**
* @brief Scaling factor to apply to adjust for the gear reduction ratio being in tenths.
*/
#define GEAR_RED_RATIO_SCALE 10

/* Prototypes */

static void _pulse_callback(void *arg);
static void _read_reset_pulse_counter(inc_encoder_t *dev, int32_t *pulse_counter);
static bool _read_delta_t_direction(inc_encoder_t *dev, uint32_t *delta_t, bool *ccw);

/* Public API */

int inc_encoder_init(inc_encoder_t *dev, const inc_encoder_params_t *params)
{
dev->params = *params;

if (gpio_init(dev->params.direction, GPIO_IN)) {
LOG_ERROR("[inc_encoder] Failed configuring the direction pin as an input!\n");
return -EIO;
}

dev->delta_t = 0;
dev->ccw = false;
dev->stale = true;
dev->pulse_counter = 0;
dev->last_read_time = ztimer_now(ZTIMER_USEC);

if (gpio_init_int(dev->params.interrupt, GPIO_IN, GPIO_RISING, _pulse_callback, (void *) dev)) {
LOG_ERROR("[inc_encoder] Failed configuring the interrupt pin!\n");
return -EIO;
}

return 0;
}

int inc_encoder_read_rpm(inc_encoder_t *dev, int32_t *rpm)
{
uint32_t delta_t;
bool ccw;
if (!_read_delta_t_direction(dev, &delta_t, &ccw)) {
*rpm = 0;
return 0;
}

/* delta_t represents the number of microseconds since the last pulse.
* Invert and divide by the number of micro seconds per minute
* to obtain the RPM. Apply scaling factors like gear reduction
* or pulses per revolution.
*/
*rpm = SEC_PER_MIN * US_PER_SEC * GEAR_RED_RATIO_SCALE
/ (delta_t * CONFIG_INC_ENCODER_PPR * CONFIG_INC_ENCODER_GEAR_RED_RATIO);
if (ccw) {
*rpm *= -1;
}
return 0;
}

int inc_encoder_read_reset_ceti_revs(inc_encoder_t *dev, int32_t *pulse_counter)
{
_read_reset_pulse_counter(dev, pulse_counter);
*pulse_counter *= 100 * GEAR_RED_RATIO_SCALE;
*pulse_counter /= CONFIG_INC_ENCODER_PPR;
*pulse_counter /= CONFIG_INC_ENCODER_GEAR_RED_RATIO;
return 0;
}

/* Private API */

/* Triggered on the rising edge of a pulse */
static void _pulse_callback(void *arg)
{
inc_encoder_t *dev = (inc_encoder_t *) arg;

uint32_t now = ztimer_now(ZTIMER_USEC);

/* Reading the shifted phase: low -> cw, high -> ccw */
dev->ccw = gpio_read(dev->params.direction);
if (now < dev->last_read_time) {
/* timer had an overflow */
dev->delta_t = UINT32_MAX - dev->last_read_time + now + 1;
}
else {
dev->delta_t = now - dev->last_read_time;
}
dev->last_read_time = now;
dev->pulse_counter += dev->ccw ? -1 : 1;
/* data is no longer stale */
dev->stale= false;
}

static void _read_reset_pulse_counter(inc_encoder_t *dev, int32_t *pulse_counter)
{
int irq_state = irq_disable();
*pulse_counter = dev->pulse_counter;
dev->pulse_counter = 0;
irq_restore(irq_state);
}

static bool _read_delta_t_direction(inc_encoder_t *dev, uint32_t *delta_t, bool *ccw)
{
int irq_state = irq_disable();
/* There have been no pulses for a while -> rotation probably stopped. */
if (dev->stale) {
irq_restore(irq_state);
return false;
}
uint32_t now = ztimer_now(ZTIMER_USEC);
uint32_t pulse_age;

if (now < dev->last_read_time) {
/* the timer had an overflow */
pulse_age = UINT32_MAX - dev->last_read_time + now + 1;
}
else {
pulse_age = now - dev->last_read_time;
}
if (pulse_age >= dev->delta_t) {
/* Data is stale if the time elapsed since the last pulse
* is longer than delta_t */
*delta_t = pulse_age;
dev->stale= true;
}
else {
*delta_t = dev->delta_t;
}
*ccw = dev->ccw;

irq_restore(irq_state);
return true;
}
62 changes: 62 additions & 0 deletions drivers/inc_encoder/inc_encoder_saul.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2025 TU Dresden
* SPDX-License-Identifier: LGPL-2.1-only
*/

/**
* @ingroup drivers_inc_encoder
* @{
*
* @file
* @brief Generic incremental rotary encoder adaption to the RIOT actuator/sensor interface
*
* @author Leonard Herbst <[email protected]>
*
* @}
*/

#include <string.h>
#include <stdio.h>

#include "saul.h"
#include "inc_encoder.h"

static int read_rpm(const void *dev, phydat_t *res)
{
inc_encoder_t *d = (inc_encoder_t *) dev;
int32_t rpm;
if (inc_encoder_read_rpm(d, &rpm)) {
/* Read failure */
return -ECANCELED;
}
res->val[0] = (uint16_t) rpm;
res->unit = UNIT_RPM;
res->scale = 0;
return 1;
}

static int read_reset_pulse_counter(const void *dev, phydat_t *res)
{
inc_encoder_t *d = (inc_encoder_t *)dev;
int32_t counter;
if (inc_encoder_read_reset_ceti_revs(d, &counter)) {
/* Read failure */
return -ECANCELED;
}
res->val[0] = (int16_t) counter;
res->unit = UNIT_CTS;
res->scale = -2;
return 1;
}

const saul_driver_t inc_encoder_rpm_saul_driver = {
.read = read_rpm,
.write = saul_write_notsup,
.type = SAUL_SENSE_SPEED,
};

const saul_driver_t inc_encoder_pulse_count_saul_driver = {
.read = read_reset_pulse_counter,
.write = saul_write_notsup,
.type = SAUL_SENSE_COUNT,
};
95 changes: 95 additions & 0 deletions drivers/inc_encoder/include/inc_encoder_params.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2025 TU Dresden
* SPDX-License-Identifier: LGPL-2.1-only
*/

#pragma once

/**
* @ingroup drivers_inc_encoder
*
* @{
* @file
* @brief Default configuration for a generic incremental rotary encoder.
*
* @author Leonard Herbst <[email protected]>
*/

#include "board.h"
#include "inc_encoder.h"
#include "saul_reg.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Default gear reduction ratio
*/
#ifndef CONFIG_INC_ENCODER_GEAR_RED_RATIO
# define CONFIG_INC_ENCODER_GEAR_RED_RATIO 204
#endif

/**
* @brief Default number of pulses per revolution
*/
#ifndef CONFIG_INC_ENCODER_PPR
# define CONFIG_INC_ENCODER_PPR 13
#endif

/**
* @name default configuration parameters for a generic incremental rotary encoder
* @{
*/
/**
* @brief Default pin of the first phase used to trigger the interrupt
*/
#ifndef INC_ENCODER_INTERRUPT
# define INC_ENCODER_INTERRUPT GPIO_PIN(1, 1)
#endif

/**
* @brief Default pin of the second (shifted) phase used to determine the direction
*/
#ifndef INC_ENCODER_DIRECTION
# define INC_ENCODER_DIRECTION GPIO_PIN(1, 2)
#endif

/**
* @brief Default parameters
*/
#ifndef INC_ENCODER_PARAMS
# define INC_ENCODER_PARAMS { .interrupt = INC_ENCODER_INTERRUPT, \
.direction = INC_ENCODER_DIRECTION }
#endif
/**@}*/

/**
* @brief SAUL info for the RPM and pulse count driver
*/
#ifndef INC_ENCODER_SAUL_INFO
# define INC_ENCODER_SAUL_INFO { { .name = "Incremental Rotary Encoder RPM Sensor" }, \
{ .name = "Incremental Rotary Encoder Pulse Count Sensor" } }
#endif

/**
* @brief Incremental rotary encoder configuration
*/
static const inc_encoder_params_t inc_encoder_params[] =
{
INC_ENCODER_PARAMS
};

/**
* @brief Additional meta information to keep in the SAUL registry
*/
static const saul_reg_info_t inc_encoder_saul_info[][2] =
{
INC_ENCODER_SAUL_INFO
};

#ifdef __cplusplus
}
#endif

/** @} */
Loading
Loading