Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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