Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
38 changes: 38 additions & 0 deletions drivers/inc_encoder/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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_MAX_RPM
int "Maximum RPM"
default 210
help
Defines the maximum RPM the encoder is expected to handle.

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.

config INC_ENCODER_HARDWARE_PERIOD_MS
int "RPM Calculation Period (in ms)"
depends on USEMODULE_INC_ENCODER_HARDWARE
default 200
help
Time period in milliseconds for RPM calculation.

endmenu # Incremental Encoder Driver
13 changes: 13 additions & 0 deletions drivers/inc_encoder/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
SUBMODULES := 1

# Since we have submodules we need to manually handle saul instead of
# including driver_with_saul.mk
MODULE ?= $(shell basename $(CURDIR))
SAUL_INTERFACE ?= $(MODULE)_saul.c

# only include <module>_saul.c if saul module is used
ifneq (,$(filter saul,$(USEMODULE)))
SRC += $(SAUL_INTERFACE)
endif

include $(RIOTBASE)/Makefile.base
10 changes: 10 additions & 0 deletions drivers/inc_encoder/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ifneq (,$(filter inc_encoder_hardware,$(USEMODULE)))
FEATURES_REQUIRED += periph_qdec
USEMODULE += ztimer_msec
endif

ifneq (,$(filter inc_encoder_software,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_gpio_irq
USEMODULE += ztimer_usec
endif
10 changes: 10 additions & 0 deletions drivers/inc_encoder/Makefile.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PSEUDOMODULES += inc_encoder_hardware
PSEUDOMODULES += inc_encoder_software

ifneq (1,$(words $(filter inc_encoder_hardware inc_encoder_software,$(USEMODULE))))
$(error "Please specify exactly one inc_encoder backend: \
inc_encoder_hardware or inc_encoder_software!")
endif

USEMODULE_INCLUDES_inc_encoder := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_inc_encoder)
135 changes: 135 additions & 0 deletions drivers/inc_encoder/hardware.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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 "inc_encoder_constants.h"

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

/* The maximum delta_count that does not cause an overflow in the RPM calculation */
#define DELTA_COUNT_MAX (INT32_MAX / (SEC_PER_MIN * MS_PER_SEC * GEAR_RED_RATIO_SCALE))

/* Maximum RPM before we accumulate more than DELTA_COUNT_MAX pulses per calculation period,
* which would cause an overflow. */
#define MAX_RPM ((DELTA_COUNT_MAX * SEC_PER_MIN * MS_PER_SEC * GEAR_RED_RATIO_SCALE) \
/ (CONFIG_INC_ENCODER_PPR \
* CONFIG_INC_ENCODER_GEAR_RED_RATIO \
* CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS \
* 4))

#if (MAX_RPM < CONFIG_INC_ENCODER_MAX_RPM)
# error With the current configuration the RPM calculation can overflow. \
Please reduce the period, pulses per revolution, gear reduction ratio, or the max RPM.
#endif

/* Prototypes */
static bool _rpm_calc_timer_cb(void *arg);
static void _acc_overflow_cb(void *args);

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

if (qdec_init(dev->params.qdec_dev, QDEC_X4, _acc_overflow_cb, (void *) dev)) {
LOG_ERROR("[inc_encoder] Qdec mode not supported!\n");
return -EINVAL;
}

dev->extended_count = 0;
dev->prev_count = 0;
dev->leftover_count = 0;
dev->last_rpm = 0;

/* Task to periodically calculate RPM */
ztimer_periodic_init(ZTIMER_MSEC, &dev->rpm_timer, _rpm_calc_timer_cb, (void *) dev,
CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS);

ztimer_periodic_start(&dev->rpm_timer);

return 0;
}

int inc_encoder_read_rpm(inc_encoder_t *dev, int32_t *rpm)
{
int irq_state = irq_disable();
*rpm = dev->last_rpm;
irq_restore(irq_state);
return 0;
}

int inc_encoder_read_reset_milli_revs(inc_encoder_t *dev, int32_t *rev_counter)
{
int32_t total_count;
int32_t delta_count;

int irq_state = irq_disable();
total_count = qdec_read_and_reset(dev->params.qdec_dev);
total_count += dev->extended_count;
delta_count = total_count - dev->prev_count;

/* We reset the counter but we need to keep the number
* of pulses since last read for the RPM calculation */
dev->leftover_count = delta_count;
dev->extended_count = 0;
dev->prev_count = 0;
irq_restore(irq_state);

/* The 4X mode counts all falling and rising edges */
*rev_counter = (int32_t) total_count / 4;

*rev_counter *= UNIT_SCALE * GEAR_RED_RATIO_SCALE;
*rev_counter /= CONFIG_INC_ENCODER_PPR;
*rev_counter /= CONFIG_INC_ENCODER_GEAR_RED_RATIO;
return 0;
}

/* Private API */
static bool _rpm_calc_timer_cb(void *arg)
{
inc_encoder_t *dev = (inc_encoder_t *) arg;
int32_t delta_count;
int32_t rpm;
int32_t total_count;

total_count = dev->extended_count + qdec_read(dev->params.qdec_dev);
delta_count = total_count - dev->prev_count;
if (dev->leftover_count != 0) {
/* Add leftover count from last reset */
delta_count += dev->leftover_count;
dev->leftover_count = 0;
}
dev->prev_count = total_count;

rpm = (int32_t)(SEC_PER_MIN * MS_PER_SEC * GEAR_RED_RATIO_SCALE * delta_count) /
(int32_t)(CONFIG_INC_ENCODER_PPR * CONFIG_INC_ENCODER_GEAR_RED_RATIO
* CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS * 4); /* 4X mode counts all edges */

dev->last_rpm = rpm;
return true;
}

static void _acc_overflow_cb(void *args)
{
inc_encoder_t *dev = (inc_encoder_t *) args;

dev->extended_count += qdec_read_and_reset(dev->params.qdec_dev);
}
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_rev_counter(const void *dev, phydat_t *res)
{
inc_encoder_t *d = (inc_encoder_t *)dev;
int32_t rev_counter;
if (inc_encoder_read_reset_milli_revs(d, &rev_counter)) {
/* Read failure */
return -ECANCELED;
}
res->val[0] = (int16_t) rev_counter;
res->unit = UNIT_CTS;
res->scale = -3; /* millirevolutions */
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_rev_count_saul_driver = {
.read = read_reset_rev_counter,
.write = saul_write_notsup,
.type = SAUL_SENSE_COUNT,
};
35 changes: 35 additions & 0 deletions drivers/inc_encoder/include/inc_encoder_constants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2025 TU Dresden
* SPDX-License-Identifier: LGPL-2.1-only
*/

#pragma once

/**
* @ingroup drivers_inc_encoder
* @{
*
* @file
* @brief Constants used in the incremental rotary encoder driver
*
* @author Leonard Herbst <[email protected]>
*/

#ifdef __cplusplus
extern "C" {
#endif

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

/**
* @brief Scaling factor to convert revolutions per minute to millirevolutions per minute.
*/
#define UNIT_SCALE 1000

#ifdef __cplusplus
}
#endif
/** @} */
Loading
Loading