Skip to content

Commit b4f4cfa

Browse files
committed
drivers: generic incremental rotary encoder driver
1 parent a8f0171 commit b4f4cfa

File tree

23 files changed

+908
-0
lines changed

23 files changed

+908
-0
lines changed

drivers/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ rsource "ads101x/Kconfig"
3333
rsource "bmx055/Kconfig"
3434
rsource "fxos8700/Kconfig"
3535
rsource "gp2y10xx/Kconfig"
36+
rsource "inc_encoder/Kconfig"
3637
rsource "hdc1000/Kconfig"
3738
rsource "hm330x/Kconfig"
3839
rsource "hsc/Kconfig"

drivers/inc_encoder/Kconfig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# SPDX-FileCopyrightText: 2025 TU Dresden
2+
# SPDX-License-Identifier: LGPL-2.1-only
3+
4+
config MODULE_INC_ENCODER
5+
bool "Incremental Rotary Encoder"
6+
depends on TEST_KCONFIG
7+
8+
9+
menu "Incremental Rotary Encoder Driver"
10+
depends on USEMODULE_INC_ENCODER
11+
12+
config INC_ENCODER_MAX_RPM
13+
int "Maximum RPM"
14+
default 210
15+
help
16+
Defines the maximum RPM the encoder is expected to handle.
17+
18+
config INC_ENCODER_GEAR_RED_RATIO
19+
int "Gear Reduction Ratio (in tenths)"
20+
default 204
21+
help
22+
Defines the gear reduction ratio. For example a gear reduction ratio
23+
of 1:20.4 would result in a value of 204.
24+
25+
config INC_ENCODER_PPR
26+
int "Pulses per Revolution"
27+
default 13
28+
help
29+
Number of Rising Edges per Revolution.
30+
31+
config INC_ENCODER_HARDWARE_PERIOD_MS
32+
int "RPM Calculation Period (in ms)"
33+
depends on USEMODULE_INC_ENCODER_HARDWARE
34+
default 200
35+
help
36+
Time period in milliseconds for RPM calculation.
37+
38+
endmenu # Incremental Encoder Driver

drivers/inc_encoder/Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
SUBMODULES := 1
2+
3+
# Since we have submodules we need to manually handle saul instead of
4+
# including driver_with_saul.mk
5+
MODULE ?= $(shell basename $(CURDIR))
6+
SAUL_INTERFACE ?= $(MODULE)_saul.c
7+
8+
# only include <module>_saul.c if saul module is used
9+
ifneq (,$(filter saul,$(USEMODULE)))
10+
SRC += $(SAUL_INTERFACE)
11+
endif
12+
13+
include $(RIOTBASE)/Makefile.base

drivers/inc_encoder/Makefile.dep

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ifneq (,$(filter inc_encoder_hardware,$(USEMODULE)))
2+
FEATURES_REQUIRED += periph_qdec
3+
USEMODULE += ztimer_msec
4+
endif
5+
6+
ifneq (,$(filter inc_encoder_software,$(USEMODULE)))
7+
FEATURES_REQUIRED += periph_gpio
8+
FEATURES_REQUIRED += periph_gpio_irq
9+
USEMODULE += ztimer_usec
10+
endif
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
PSEUDOMODULES += inc_encoder_hardware
2+
PSEUDOMODULES += inc_encoder_software
3+
4+
ifneq (1,$(words $(filter inc_encoder_hardware inc_encoder_software,$(USEMODULE))))
5+
$(error "Please specify exactly one inc_encoder backend: \
6+
inc_encoder_hardware or inc_encoder_software!")
7+
endif
8+
9+
USEMODULE_INCLUDES_inc_encoder := $(LAST_MAKEFILEDIR)/include
10+
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_inc_encoder)

drivers/inc_encoder/hardware.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 TU Dresden
3+
* SPDX-License-Identifier: LGPL-2.1-only
4+
*/
5+
6+
/**
7+
* @ingroup drivers_inc_encoder
8+
* @{
9+
*
10+
* @file
11+
* @brief Device driver implementation for a generic incremental rotary encoder
12+
*
13+
* @author Leonard Herbst <[email protected]>
14+
*
15+
* @}
16+
*/
17+
18+
#include "inc_encoder.h"
19+
#include "inc_encoder_params.h"
20+
#include "inc_encoder_constants.h"
21+
22+
#include <errno.h>
23+
#include "log.h"
24+
#include "ztimer.h"
25+
#include "time_units.h"
26+
27+
/* The maximum delta_count that does not cause an overflow in the RPM calculation */
28+
#define DELTA_COUNT_MAX (INT32_MAX / (SEC_PER_MIN * MS_PER_SEC * GEAR_RED_RATIO_SCALE))
29+
30+
/* Maximum RPM before we accumulate more than DELTA_COUNT_MAX pulses per calculation period,
31+
* which would cause an overflow. */
32+
#define MAX_RPM ((DELTA_COUNT_MAX * SEC_PER_MIN * MS_PER_SEC * GEAR_RED_RATIO_SCALE) \
33+
/ (CONFIG_INC_ENCODER_PPR \
34+
* CONFIG_INC_ENCODER_GEAR_RED_RATIO \
35+
* CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS \
36+
* 4))
37+
38+
#if (MAX_RPM < CONFIG_INC_ENCODER_MAX_RPM)
39+
# error With the current configuration the RPM calculation can overflow. \
40+
Please reduce the period, pulses per revolution, gear reduction ratio, or the max RPM.
41+
#endif
42+
43+
/* Prototypes */
44+
static bool _rpm_calc_timer_cb(void *arg);
45+
static void _acc_overflow_cb(void *args);
46+
47+
/* Public API */
48+
int inc_encoder_init(inc_encoder_t *dev, const inc_encoder_params_t *params)
49+
{
50+
dev->params = *params;
51+
52+
if (qdec_init(dev->params.qdec_dev, QDEC_X4, _acc_overflow_cb, (void *) dev)) {
53+
LOG_ERROR("[inc_encoder] Qdec mode not supported!\n");
54+
return -EINVAL;
55+
}
56+
57+
dev->extended_count = 0;
58+
dev->prev_count = 0;
59+
dev->leftover_count = 0;
60+
dev->last_rpm = 0;
61+
62+
/* Task to periodically calculate RPM */
63+
ztimer_periodic_init(ZTIMER_MSEC, &dev->rpm_timer, _rpm_calc_timer_cb, (void *) dev,
64+
CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS);
65+
66+
ztimer_periodic_start(&dev->rpm_timer);
67+
68+
return 0;
69+
}
70+
71+
int inc_encoder_read_rpm(inc_encoder_t *dev, int32_t *rpm)
72+
{
73+
int irq_state = irq_disable();
74+
*rpm = dev->last_rpm;
75+
irq_restore(irq_state);
76+
return 0;
77+
}
78+
79+
int inc_encoder_read_reset_milli_revs(inc_encoder_t *dev, int32_t *rev_counter)
80+
{
81+
int32_t total_count;
82+
int32_t delta_count;
83+
84+
int irq_state = irq_disable();
85+
total_count = qdec_read_and_reset(dev->params.qdec_dev);
86+
total_count += dev->extended_count;
87+
delta_count = total_count - dev->prev_count;
88+
89+
/* We reset the counter but we need to keep the number
90+
* of pulses since last read for the RPM calculation */
91+
dev->leftover_count = delta_count;
92+
dev->extended_count = 0;
93+
dev->prev_count = 0;
94+
irq_restore(irq_state);
95+
96+
/* The 4X mode counts all falling and rising edges */
97+
*rev_counter = (int32_t) total_count / 4;
98+
99+
*rev_counter *= UNIT_SCALE * GEAR_RED_RATIO_SCALE;
100+
*rev_counter /= CONFIG_INC_ENCODER_PPR;
101+
*rev_counter /= CONFIG_INC_ENCODER_GEAR_RED_RATIO;
102+
return 0;
103+
}
104+
105+
/* Private API */
106+
static bool _rpm_calc_timer_cb(void *arg)
107+
{
108+
inc_encoder_t *dev = (inc_encoder_t *) arg;
109+
int32_t delta_count;
110+
int32_t rpm;
111+
int32_t total_count;
112+
113+
total_count = dev->extended_count + qdec_read(dev->params.qdec_dev);
114+
delta_count = total_count - dev->prev_count;
115+
if (dev->leftover_count != 0) {
116+
/* Add leftover count from last reset */
117+
delta_count += dev->leftover_count;
118+
dev->leftover_count = 0;
119+
}
120+
dev->prev_count = total_count;
121+
122+
rpm = (int32_t)(SEC_PER_MIN * MS_PER_SEC * GEAR_RED_RATIO_SCALE * delta_count) /
123+
(int32_t)(CONFIG_INC_ENCODER_PPR * CONFIG_INC_ENCODER_GEAR_RED_RATIO
124+
* CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS * 4); /* 4X mode counts all edges */
125+
126+
dev->last_rpm = rpm;
127+
return true;
128+
}
129+
130+
static void _acc_overflow_cb(void *args)
131+
{
132+
inc_encoder_t *dev = (inc_encoder_t *) args;
133+
134+
dev->extended_count += qdec_read_and_reset(dev->params.qdec_dev);
135+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 TU Dresden
3+
* SPDX-License-Identifier: LGPL-2.1-only
4+
*/
5+
6+
/**
7+
* @ingroup drivers_inc_encoder
8+
* @{
9+
*
10+
* @file
11+
* @brief Generic incremental rotary encoder adaption to the RIOT actuator/sensor interface
12+
*
13+
* @author Leonard Herbst <[email protected]>
14+
*
15+
* @}
16+
*/
17+
18+
#include <string.h>
19+
#include <stdio.h>
20+
21+
#include "saul.h"
22+
#include "inc_encoder.h"
23+
24+
static int read_rpm(const void *dev, phydat_t *res)
25+
{
26+
inc_encoder_t *d = (inc_encoder_t *) dev;
27+
int32_t rpm;
28+
if (inc_encoder_read_rpm(d, &rpm)) {
29+
/* Read failure */
30+
return -ECANCELED;
31+
}
32+
res->val[0] = (uint16_t) rpm;
33+
res->unit = UNIT_RPM;
34+
res->scale = 0;
35+
return 1;
36+
}
37+
38+
static int read_reset_rev_counter(const void *dev, phydat_t *res)
39+
{
40+
inc_encoder_t *d = (inc_encoder_t *)dev;
41+
int32_t rev_counter;
42+
if (inc_encoder_read_reset_milli_revs(d, &rev_counter)) {
43+
/* Read failure */
44+
return -ECANCELED;
45+
}
46+
res->val[0] = (int16_t) rev_counter;
47+
res->unit = UNIT_CTS;
48+
res->scale = -3; /* millirevolutions */
49+
return 1;
50+
}
51+
52+
const saul_driver_t inc_encoder_rpm_saul_driver = {
53+
.read = read_rpm,
54+
.write = saul_write_notsup,
55+
.type = SAUL_SENSE_SPEED,
56+
};
57+
58+
const saul_driver_t inc_encoder_rev_count_saul_driver = {
59+
.read = read_reset_rev_counter,
60+
.write = saul_write_notsup,
61+
.type = SAUL_SENSE_COUNT,
62+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 TU Dresden
3+
* SPDX-License-Identifier: LGPL-2.1-only
4+
*/
5+
6+
#pragma once
7+
8+
/**
9+
* @ingroup drivers_inc_encoder
10+
* @{
11+
*
12+
* @file
13+
* @brief Constants used in the incremental rotary encoder driver
14+
*
15+
* @author Leonard Herbst <[email protected]>
16+
*/
17+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
22+
/**
23+
* @brief Scaling factor to apply to adjust for the gear reduction ratio being in tenths.
24+
*/
25+
#define GEAR_RED_RATIO_SCALE 10
26+
27+
/**
28+
* @brief Scaling factor to convert revolutions per minute to millirevolutions per minute.
29+
*/
30+
#define UNIT_SCALE 1000
31+
32+
#ifdef __cplusplus
33+
}
34+
#endif
35+
/** @} */

0 commit comments

Comments
 (0)