-
Notifications
You must be signed in to change notification settings - Fork 2.1k
drivers/hall_effect: support for generic hall effect sensors #21801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
LeonardHerbst
wants to merge
14
commits into
RIOT-OS:master
Choose a base branch
from
LeonardHerbst:hall_effect
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+918
−0
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
de1ebb1
drivers: added support for a generic hall effect driver
LeonardHerbst c6a8839
Clean up and documentation.
LeonardHerbst 4a1a9cf
tailing whitespace
LeonardHerbst 64d76eb
Introduced a macro to avoid using integer literals
LeonardHerbst 96291be
Fixup
LeonardHerbst 41dfe5a
Fixup
LeonardHerbst 0fe0558
Fixup
LeonardHerbst 4f869b0
Renamed the driver.
LeonardHerbst 40f4bdc
split a long line
LeonardHerbst 9df6e4a
Fixed typo
LeonardHerbst 4f4fd33
Added as an optional backend.
LeonardHerbst 62ff937
Fixed typos
LeonardHerbst 5749d85
Removed external board since RIOT already has other boards that suppo…
LeonardHerbst a1bd6f7
Added a inc_encoder backend to the saul driver test
LeonardHerbst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| ifneq (,$(filter inc_encoder_hardware,$(USEMODULE))) | ||
| DIRS += backends/hardware | ||
| endif | ||
|
|
||
| ifneq (,$(filter inc_encoder_software,$(USEMODULE))) | ||
| DIRS += backends/software | ||
| endif | ||
|
|
||
| include $(RIOTMAKE)/driver_with_saul.mk |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| ifneq (,$(filter inc_encoder_hardware,$(USEMODULE))) | ||
| FEATURES_REQUIRED += periph_qdec | ||
| endif | ||
|
|
||
| ifneq (,$(filter inc_encoder_software,$(USEMODULE))) | ||
| FEATURES_REQUIRED += periph_gpio | ||
| FEATURES_REQUIRED += periph_gpio_irq | ||
| endif | ||
|
|
||
| USEMODULE += ztimer_usec |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ifneq (1,$(words $(filter inc_encoder_%,$(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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| MODULE := inc_encoder_hardware | ||
| BASE_MODULE := inc_encoder | ||
|
|
||
| SRC := inc_encoder_hardware.c | ||
|
|
||
| # enable submodules | ||
| SUBMODULES := 1 | ||
|
|
||
| include $(RIOTBASE)/Makefile.base |
135 changes: 135 additions & 0 deletions
135
drivers/inc_encoder/backends/hardware/inc_encoder_hardware.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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_USEC, &dev->rpm_timer, _rpm_calc_timer_cb, (void *) dev, | ||
| CONFIG_INC_ENCODER_HARDWARE_PERIOD_MS * US_PER_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_ceti_revs(inc_encoder_t *dev, int32_t *pulse_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 */ | ||
| *pulse_counter = (int32_t) total_count / 4; | ||
|
|
||
| *pulse_counter *= UNIT_SCALE * GEAR_RED_RATIO_SCALE; | ||
| *pulse_counter /= CONFIG_INC_ENCODER_PPR; | ||
| *pulse_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); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| MODULE := inc_encoder_software | ||
| BASE_MODULE := inc_encoder | ||
|
|
||
| SRC := inc_encoder_software.c | ||
|
|
||
| # enable submodules | ||
| SUBMODULES := 1 | ||
|
|
||
| include $(RIOTBASE)/Makefile.base |
160 changes: 160 additions & 0 deletions
160
drivers/inc_encoder/backends/software/inc_encoder_software.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| /* | ||
| * 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 "time_units.h" | ||
|
|
||
| /* If delta_t exceeds this threshold, the calculated RPM will be less than one | ||
| * and will be truncated to zero. | ||
| * When delta_t is larger than this threshold, we directly return zero | ||
| * and prevent potential overflows in the RPM calculation. | ||
| * | ||
| * An overflow would occur when delta_t > INT32_MAX / (PPR * GEAR_RED_RATIO), | ||
| * but our threshold is always lower than that because: | ||
| * | ||
| * INT32_MAX > SEC_PER_MIN * US_PER_SEC * GEAR_RED_RATIO_SCALE | ||
| */ | ||
| #define DELTA_T_THRESHOLD ((SEC_PER_MIN * US_PER_SEC * GEAR_RED_RATIO_SCALE) \ | ||
| / (CONFIG_INC_ENCODER_PPR * CONFIG_INC_ENCODER_GEAR_RED_RATIO)) | ||
|
|
||
| /* Prototypes */ | ||
| static void _pulse_cb(void *arg); | ||
| static bool _read_delta_t_direction(inc_encoder_t *dev, uint32_t *delta_t, bool *cw); | ||
|
|
||
| /* 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->pulse_counter = 0; | ||
| dev->cw = false; | ||
| dev->stale = true; | ||
| dev->last_read_time = ztimer_now(ZTIMER_USEC); | ||
|
|
||
| if (gpio_init_int(dev->params.interrupt, GPIO_IN, GPIO_RISING, _pulse_cb, (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 cw; | ||
| if (!_read_delta_t_direction(dev, &delta_t, &cw) || (delta_t >= DELTA_T_THRESHOLD)) { | ||
| *rpm = 0; | ||
| return 0; | ||
| } | ||
|
|
||
| /* delta_t represents the number of microseconds since the last pulse. | ||
| * Invert and divide by the number of microseconds 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 (!cw) { | ||
| *rpm *= -1; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int inc_encoder_read_reset_ceti_revs(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); | ||
|
|
||
| *pulse_counter *= UNIT_SCALE * 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_cb(void *arg) | ||
| { | ||
| inc_encoder_t *dev = (inc_encoder_t *) arg; | ||
| uint32_t now = ztimer_now(ZTIMER_USEC); | ||
|
|
||
| /* Reading the shifted phase: high -> cw, low -> ccw */ | ||
| dev->cw = gpio_read(dev->params.direction); | ||
|
|
||
| if (now < dev->last_read_time) { | ||
| 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->cw ? 1 : -1; | ||
| /* data is no longer stale */ | ||
| dev->stale= false; | ||
| } | ||
|
|
||
| static bool _read_delta_t_direction(inc_encoder_t *dev, uint32_t *delta_t, bool *cw) | ||
| { | ||
| uint32_t now; | ||
| uint32_t pulse_age; | ||
| int irq_state = irq_disable(); | ||
|
|
||
| if (dev->stale) { | ||
| /* Rotation stopped */ | ||
| irq_restore(irq_state); | ||
| return false; | ||
| } | ||
| now = ztimer_now(ZTIMER_USEC); | ||
|
|
||
| /* Handle potential overflows */ | ||
| if (now < dev->last_read_time) { | ||
| 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; | ||
| } | ||
| *cw = dev->cw; | ||
|
|
||
| irq_restore(irq_state); | ||
| return true; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it actually necessary to use the microsecond timer here? It prevents deeper sleep modes on most microcontrollers 🤔