input: gpio_qdec: add optical encoder support
Change the gpio_qdec driver to support optical encoders. Add a property to use for defining an arbitrary number of GPIOs for the sensing devices (typically infrared LEDs, but could also be the biasing for the phototransistor), and one for adding a delay between turning those on and reading the pin status. The infrared LEDs typically consume a non negligible amount of power, so there's also a new idle-poll-time-us property that enables two possible modes of operation: - if idle-poll-time-us is zero (default) the LEDs are enabled all the time and the driver enters polling mode using the GPIO interrupt as with mechanical encoders. This is usable for mains powered devices and has the lowest overhead on the CPU. - if idle-poll-time-us is non zero, then the driver polls the encoder all the time, turning on the LEDs just before reading the state and shutting them off immediately after, but when the encoder is idle it switches the polling rate to idle-poll-time-us to save power, and only polls at sample-time-us when some movement is detected. Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
This commit is contained in:
parent
91ee5c4db2
commit
1dd2307b3f
|
@ -13,6 +13,8 @@
|
|||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/input/input.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/atomic.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(input_gpio_qdec, CONFIG_INPUT_LOG_LEVEL);
|
||||
|
@ -21,7 +23,11 @@ LOG_MODULE_REGISTER(input_gpio_qdec, CONFIG_INPUT_LOG_LEVEL);
|
|||
|
||||
struct gpio_qdec_config {
|
||||
struct gpio_dt_spec ab_gpio[GPIO_QDEC_GPIO_NUM];
|
||||
const struct gpio_dt_spec *led_gpio;
|
||||
uint8_t led_gpio_count;
|
||||
uint32_t led_pre_us;
|
||||
uint32_t sample_time_us;
|
||||
uint32_t idle_poll_time_us;
|
||||
uint32_t idle_timeout_ms;
|
||||
uint16_t axis;
|
||||
uint8_t steps_per_period;
|
||||
|
@ -35,6 +41,7 @@ struct gpio_qdec_data {
|
|||
struct k_work event_work;
|
||||
struct k_work_delayable idle_work;
|
||||
struct gpio_callback gpio_cb;
|
||||
atomic_t polling;
|
||||
};
|
||||
|
||||
/* Positive transitions */
|
||||
|
@ -49,11 +56,82 @@ struct gpio_qdec_data {
|
|||
#define QDEC_HH_LH 0x31
|
||||
#define QDEC_HL_HH 0x23
|
||||
|
||||
static void gpio_qdec_irq_setup(const struct device *dev, bool enable)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
gpio_flags_t flags = enable ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE;
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < GPIO_QDEC_GPIO_NUM; i++) {
|
||||
const struct gpio_dt_spec *gpio = &cfg->ab_gpio[i];
|
||||
|
||||
ret = gpio_pin_interrupt_configure_dt(gpio, flags);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Pin %d interrupt configuration failed: %d", i, ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool gpio_qdec_idle_polling_mode(const struct device *dev)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
|
||||
if (cfg->idle_poll_time_us > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void gpio_qdec_poll_mode(const struct device *dev)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
struct gpio_qdec_data *data = dev->data;
|
||||
|
||||
if (!gpio_qdec_idle_polling_mode(dev)) {
|
||||
gpio_qdec_irq_setup(dev, false);
|
||||
}
|
||||
|
||||
k_timer_start(&data->sample_timer, K_NO_WAIT,
|
||||
K_USEC(cfg->sample_time_us));
|
||||
|
||||
atomic_set(&data->polling, 1);
|
||||
|
||||
LOG_DBG("polling start");
|
||||
}
|
||||
|
||||
static void gpio_qdec_idle_mode(const struct device *dev)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
struct gpio_qdec_data *data = dev->data;
|
||||
|
||||
if (gpio_qdec_idle_polling_mode(dev)) {
|
||||
k_timer_start(&data->sample_timer, K_NO_WAIT,
|
||||
K_USEC(cfg->idle_poll_time_us));
|
||||
} else {
|
||||
k_timer_stop(&data->sample_timer);
|
||||
gpio_qdec_irq_setup(dev, true);
|
||||
}
|
||||
|
||||
atomic_set(&data->polling, 0);
|
||||
|
||||
LOG_DBG("polling stop");
|
||||
}
|
||||
|
||||
static uint8_t gpio_qdec_get_step(const struct device *dev)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
uint8_t step = 0x00;
|
||||
|
||||
if (gpio_qdec_idle_polling_mode(dev)) {
|
||||
for (int i = 0; i < cfg->led_gpio_count; i++) {
|
||||
gpio_pin_set_dt(&cfg->led_gpio[i], 1);
|
||||
}
|
||||
|
||||
k_busy_wait(cfg->led_pre_us);
|
||||
}
|
||||
|
||||
if (gpio_pin_get_dt(&cfg->ab_gpio[0])) {
|
||||
step |= 0x01;
|
||||
}
|
||||
|
@ -61,6 +139,12 @@ static uint8_t gpio_qdec_get_step(const struct device *dev)
|
|||
step |= 0x02;
|
||||
}
|
||||
|
||||
if (gpio_qdec_idle_polling_mode(dev)) {
|
||||
for (int i = 0; i < cfg->led_gpio_count; i++) {
|
||||
gpio_pin_set_dt(&cfg->led_gpio[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
|
@ -79,6 +163,11 @@ static void gpio_qdec_sample_timer_timeout(struct k_timer *timer)
|
|||
return;
|
||||
}
|
||||
|
||||
if (gpio_qdec_idle_polling_mode(dev) &&
|
||||
atomic_get(&data->polling) == 0) {
|
||||
gpio_qdec_poll_mode(dev);
|
||||
}
|
||||
|
||||
switch ((data->prev_step << 4U) | step) {
|
||||
case QDEC_LL_LH:
|
||||
case QDEC_LH_HH:
|
||||
|
@ -128,23 +217,6 @@ static void gpio_qdec_event_worker(struct k_work *work)
|
|||
}
|
||||
}
|
||||
|
||||
static void gpio_qdec_irq_setup(const struct device *dev, bool enable)
|
||||
{
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
gpio_flags_t flags = enable ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE;
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < GPIO_QDEC_GPIO_NUM; i++) {
|
||||
const struct gpio_dt_spec *gpio = &cfg->ab_gpio[i];
|
||||
|
||||
ret = gpio_pin_interrupt_configure_dt(gpio, flags);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Pin %d interrupt configuration failed: %d", i, ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gpio_qdec_idle_worker(struct k_work *work)
|
||||
{
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
|
@ -152,11 +224,7 @@ static void gpio_qdec_idle_worker(struct k_work *work)
|
|||
dwork, struct gpio_qdec_data, idle_work);
|
||||
const struct device *dev = data->dev;
|
||||
|
||||
k_timer_stop(&data->sample_timer);
|
||||
|
||||
gpio_qdec_irq_setup(dev, true);
|
||||
|
||||
LOG_DBG("polling stop");
|
||||
gpio_qdec_idle_mode(dev);
|
||||
}
|
||||
|
||||
static void gpio_qdec_cb(const struct device *gpio_dev, struct gpio_callback *cb,
|
||||
|
@ -165,14 +233,8 @@ static void gpio_qdec_cb(const struct device *gpio_dev, struct gpio_callback *cb
|
|||
struct gpio_qdec_data *data = CONTAINER_OF(
|
||||
cb, struct gpio_qdec_data, gpio_cb);
|
||||
const struct device *dev = data->dev;
|
||||
const struct gpio_qdec_config *cfg = dev->config;
|
||||
|
||||
gpio_qdec_irq_setup(dev, false);
|
||||
|
||||
k_timer_start(&data->sample_timer, K_NO_WAIT,
|
||||
K_USEC(cfg->sample_time_us));
|
||||
|
||||
LOG_DBG("polling start");
|
||||
gpio_qdec_poll_mode(dev);
|
||||
}
|
||||
|
||||
static int gpio_qdec_init(const struct device *dev)
|
||||
|
@ -205,6 +267,10 @@ static int gpio_qdec_init(const struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (gpio_qdec_idle_polling_mode(dev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = gpio_add_callback_dt(gpio, &data->gpio_cb);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Could not set gpio callback");
|
||||
|
@ -212,9 +278,28 @@ static int gpio_qdec_init(const struct device *dev)
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < cfg->led_gpio_count; i++) {
|
||||
const struct gpio_dt_spec *gpio = &cfg->led_gpio[i];
|
||||
gpio_flags_t mode;
|
||||
|
||||
if (!gpio_is_ready_dt(gpio)) {
|
||||
LOG_ERR("%s is not ready", gpio->port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mode = gpio_qdec_idle_polling_mode(dev) ?
|
||||
GPIO_OUTPUT_INACTIVE : GPIO_OUTPUT_ACTIVE;
|
||||
|
||||
ret = gpio_pin_configure_dt(gpio, mode);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Pin %d configuration failed: %d", i, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
data->prev_step = gpio_qdec_get_step(dev);
|
||||
|
||||
gpio_qdec_irq_setup(dev, true);
|
||||
gpio_qdec_idle_mode(dev);
|
||||
|
||||
LOG_DBG("Device %s initialized", dev->name);
|
||||
|
||||
|
@ -225,12 +310,31 @@ static int gpio_qdec_init(const struct device *dev)
|
|||
BUILD_ASSERT(DT_INST_PROP_LEN(n, gpios) == GPIO_QDEC_GPIO_NUM, \
|
||||
"input_gpio_qdec: gpios must have exactly two entries"); \
|
||||
\
|
||||
BUILD_ASSERT(!(DT_INST_NODE_HAS_PROP(n, led_gpios) && \
|
||||
DT_INST_NODE_HAS_PROP(n, idle_poll_time_us)) || \
|
||||
DT_INST_NODE_HAS_PROP(n, led_pre_us), \
|
||||
"led-pre-us must be specified when setting led-gpios and " \
|
||||
"idle-poll-time-us"); \
|
||||
\
|
||||
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, led_gpios), ( \
|
||||
static const struct gpio_dt_spec gpio_qdec_led_gpio_##n[] = { \
|
||||
DT_INST_FOREACH_PROP_ELEM_SEP(n, led_gpios, \
|
||||
GPIO_DT_SPEC_GET_BY_IDX, (,)) \
|
||||
}; \
|
||||
)) \
|
||||
\
|
||||
static const struct gpio_qdec_config gpio_qdec_cfg_##n = { \
|
||||
.ab_gpio = { \
|
||||
GPIO_DT_SPEC_INST_GET_BY_IDX(n, gpios, 0), \
|
||||
GPIO_DT_SPEC_INST_GET_BY_IDX(n, gpios, 1), \
|
||||
}, \
|
||||
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, led_gpios), ( \
|
||||
.led_gpio = gpio_qdec_led_gpio_##n, \
|
||||
.led_gpio_count = ARRAY_SIZE(gpio_qdec_led_gpio_##n), \
|
||||
.led_pre_us = DT_INST_PROP_OR(n, led_pre_us, 0), \
|
||||
)) \
|
||||
.sample_time_us = DT_INST_PROP(n, sample_time_us), \
|
||||
.idle_poll_time_us = DT_INST_PROP_OR(n, idle_poll_time_us, 0), \
|
||||
.idle_timeout_ms = DT_INST_PROP(n, idle_timeout_ms), \
|
||||
.steps_per_period = DT_INST_PROP(n, steps_per_period), \
|
||||
.axis = DT_INST_PROP(n, zephyr_axis), \
|
||||
|
|
|
@ -35,6 +35,19 @@ properties:
|
|||
description: |
|
||||
GPIO for the A and B encoder signals.
|
||||
|
||||
led-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
GPIOs for LED or other components needed for sensing the AB signals.
|
||||
|
||||
led-pre-us:
|
||||
type: int
|
||||
description: |
|
||||
Time between enabling the led-gpios output pins and reading the encoder
|
||||
state on the input pins, meant to give the state detector (such a
|
||||
phototransistor) time to settle to right state. Required if led-gpios and
|
||||
idle-poll-time-us are specified, can be explicitly set to 0 for no delay.
|
||||
|
||||
steps-per-period:
|
||||
type: int
|
||||
required: true
|
||||
|
@ -55,6 +68,15 @@ properties:
|
|||
How often to sample the A and B signal lines when tracking the encoder
|
||||
movement.
|
||||
|
||||
idle-poll-time-us:
|
||||
type: int
|
||||
description: |
|
||||
How often to sample the A and B signal while idling. If unset then the
|
||||
driver will use the GPIO interrupt to exit idle state, and any GPIO
|
||||
specified in led-gpios will be enabled all the time. If non zero, then
|
||||
the driver will poll every idle-poll-time-us microseconds while idle, and
|
||||
only activate led-gpios before sampling the encoder state.
|
||||
|
||||
idle-timeout-ms:
|
||||
type: int
|
||||
required: true
|
||||
|
|
|
@ -104,6 +104,18 @@
|
|||
idle-timeout-ms = <200>;
|
||||
};
|
||||
|
||||
qdec-gpio-polled {
|
||||
compatible = "gpio-qdec";
|
||||
gpios = <&test_gpio 0 0>, <&test_gpio 1 0>;
|
||||
led-gpios = <&test_gpio 2 0>;
|
||||
led-pre-us = <5>;
|
||||
steps-per-period = <4>;
|
||||
zephyr,axis = <0>;
|
||||
sample-time-us = <2000>;
|
||||
idle-timeout-ms = <200>;
|
||||
idle-poll-time-us = <5000>;
|
||||
};
|
||||
|
||||
analog_axis {
|
||||
compatible = "analog-axis";
|
||||
axis-x {
|
||||
|
|
Loading…
Reference in a new issue