EC11 Rotary Encoder with Sensor API

I have an EC11 rotary encoder wired to my nRF52840 and I am trying to get readings from it using the sensor API. However, I'm having issues with (seemingly) garbage data. When I rotate one "click" I get a variety of readings. Sometimes no readings whatsoever, sometimes multiple readings. I referenced the nrf_desktop wheel implementation for my configuration.

I should note that I am relying on software pullups for my configuration, hence the specific gpio definition for the encoder pins.

It is important to me that I do not poll the encoder in order to save power. Am I missing something obvious?

Here's my overlay file:

/ {
    aliases {
        sw0 = &button0;
        sw1 = &button1;
        sw2 = &button2;
        qdec0 = &qdec0;
        qenca = &encoder_a;
        qencb = &encoder_b;
    };
    
    encoder_a: encoder_a_pin {
        gpios = <&gpio1 13 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
        label = "Rotary interrupt A";
    };
    encoder_b: encoder_b_pin {
        gpios = <&gpio1 15 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
        label = "Rotary interrupt B";
    };
};


&pinctrl {
    qdec_pinctrl: qdec_pinctrl {
        group1 {
            psels = <NRF_PSEL(QDEC_A, 1, 13)>,
                    <NRF_PSEL(QDEC_B, 1, 15)>;
        };
    };
};

&qdec0 {
    status = "okay";
    pinctrl-0 = <&qdec_pinctrl>;
    pinctrl-names = "default";
    steps = <80>;
    led-pre = <0>;
};

My interface code:

const struct device *const encoder_device = DEVICE_DT_GET(DT_NODELABEL(qdec0));

static void sample_encoder()
{
    struct sensor_value val;
    int rc;
    rc = sensor_sample_fetch(encoder_device);
    if (rc != 0)
    {
        LOG_INF("Failed to fetch sample (%d)\n", rc);
        return 0;
    }

    rc = sensor_channel_get(encoder_device, SENSOR_CHAN_ROTATION, &val);
    if (rc != 0)
    {
        LOG_INF("Failed to get data (%d)\n", rc);
        return 0;
    }

    if (val.val1 != position)
    {
        position = val.val1;
        LOG_INF("Position = %d degrees\n", position);
    }
}

void init_encoder(){
    if (!device_is_ready(encoder_device))
    {
        LOG_ERR("Cannot get QDEC device");
        return -ENXIO;
    }

    static const struct sensor_trigger qdec_trig = {
        .type = SENSOR_TRIG_DATA_READY,
        .chan = SENSOR_CHAN_ROTATION,
    };

    err = sensor_trigger_set(encoder_device, (struct sensor_trigger *)&qdec_trig,
                             sample_encoder);
 }

Here is an example log of one "click" clockwise:

[00:11:40.740,112] <inf> main_logging: Position = 4 degrees

[00:11:40.822,021] <inf> main_logging: Position = 13 degrees

And here is the logs from three "clicks" counterclockwise:

[00:12:14.900,543] <inf> main_logging: Position = -9 degrees

[00:12:17.931,579] <inf> main_logging: Position = -18 degrees

[00:12:19.979,553] <inf> main_logging: Position = -4 degrees

[00:12:20.061,492] <inf> main_logging: Position = -13 degrees

Parents
  • Hello, 

    What version of the nRF Connect SDK are you using? I've tested with a similar rotary encoder here but with no luck. Saw similar output and put aside. Let's look into this together. Your overlay looks correct. What about the signals on the encoder, have you tried looking at these with a logic analyzer or oscilloscope? The issue is most like in the SDK. 

    The Zephyr Community on Discord might be the best chance of figuring out this issue. There are relevant discussions there e.g. https://github.com/zephyrproject-rtos/zephyr/issues/73529

    Kind regards,
    Øyvind

  • Hi  have you had any luck identifying the issue with your encoder? I still have not been able to find an answer.

  • Hello, 

    No, I'm afraid I've had no luck so far. 

    Did you have any luck in the Zephyr channel?

    -Øyvind

  • I'm afraid that there is not much help from my side with Zephyr drivers, as we do not have much rotary encoders in our products. 

    Looking at zephyr/dts/bindings/input/gpio-qdec.yaml please make sure that you have configured the rotary encoder with the following

      qdec {
              compatible = "gpio-qdec";
              gpios = <&gpio0 14 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>,
                      <&gpio0 13 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>;
              steps-per-period = <4>;
              zephyr,axis = <INPUT_REL_WHEEL>;
              sample-time-us = <2000>;
              idle-timeout-ms = <200>;
      };

    This is used in zephyr/samples/subsys/display/lvgl/boards/native_posix.overlay

    Does this help with your rotary encoder output? Next step would be to debug using e.g. logic analyzer to ensure the signals are correct

    Looking at the zephyr/samples/subsys/display/lvgl/src/main.c the following snippet should provide some help, and perhaps help with debugging i.e. setting break points

    #ifdef CONFIG_LV_Z_ENCODER_INPUT
    	lv_obj_t *arc;
    	lv_group_t *arc_group;
    
    	arc = lv_arc_create(lv_scr_act());
    	lv_obj_align(arc, LV_ALIGN_CENTER, 0, -15);
    	lv_obj_set_size(arc, 150, 150);
    
    	arc_group = lv_group_create();
    	lv_group_add_obj(arc_group, arc);
    	lv_indev_set_group(lvgl_input_get_indev(lvgl_encoder), arc_group);
    #endif /* CONFIG_LV_Z_ENCODER_INPUT */

    zephyr/modules/lvgl/input/lvgl_encoder_input.c

    And I assume you have been looking at the QDEC sameple (zephyr/samples/sensor/qdec/src/main.c)

  • This is what I needed!

    I got the encoder working correctly using the inputs library.

    Here's my new .overlay file:

    / {
        gnd_pins {
            compatible = "gpio-leds";
            qdec_gnd: qdec_gnd_pin {
                gpios = <&gpio1 14 (GPIO_ACTIVE_HIGH)>;
                label = "Rotary GND";
            };
            qdec_btn_gnd: qdec_btn_gnd_pin {
                gpios = <&gpio0 5 (GPIO_ACTIVE_HIGH)>;
                label = "Rotary btn GND";
            };
        };
        buttons: buttons {
            compatible = "gpio-keys";
            debounce-interval-ms = <50>;
            qdec_btn: btn_pin {
                gpios = <&gpio0 29 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
                label = "Encoder switch interrupt";
                zephyr,code = <INPUT_BTN_0>;
            };
        };
        qdec_btn {
            input = <&buttons>;
            compatible = "zephyr,input-longpress";
            input-codes = <INPUT_BTN_0>;
            short-codes = <INPUT_BTN_1>;
            long-codes = <INPUT_BTN_2>;
            long-delay-ms = <1000>;
        };
        qdec {
            compatible = "gpio-qdec";
            status = "okay";
            gpios = <&gpio1 13 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>,
                    <&gpio1 15 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>;
            steps-per-period = <4>;
            zephyr,axis = <INPUT_REL_WHEEL>;
            sample-time-us = <2000>;
            idle-timeout-ms = <200>;
        };
    };

    My test main.c file:

    #include <zephyr/kernel.h>
    #include <zephyr/pm/pm.h>
    #include <zephyr/pm/policy.h>
    #include <zephyr/pm/state.h>
    #include <zephyr/pm/device.h>
    #include <zephyr/input/input.h>
    #include <zephyr/drivers/gpio.h>
    
    static const struct gpio_dt_spec qdec_gnd = GPIO_DT_SPEC_GET(DT_NODELABEL(qdec_gnd), gpios);
    static const struct gpio_dt_spec qdec_btn_gnd = GPIO_DT_SPEC_GET(DT_NODELABEL(qdec_btn_gnd), gpios);
    
    static void test_cb(struct input_event *evt)
    {
            if (evt->code == INPUT_REL_WHEEL)
            {
                    printk("z event %d\n", evt->value);
            }
            if (evt->code == INPUT_BTN_1 && evt->value == 1)
            {
                    printk("short press %d\n", evt->value);
            }
            if (evt->code == INPUT_BTN_2 && evt->value == 1)
            {
                    printk("long press %d\n", evt->value);
            }
    }
    
    INPUT_CALLBACK_DEFINE(NULL, test_cb);
    
    int main(void)
    {
            if (!gpio_is_ready_dt(&qdec_gnd) || !gpio_is_ready_dt(&qdec_btn_gnd))
            {
                    printk("Device not ready\n");
            }
    
            gpio_pin_configure_dt(&qdec_gnd, GPIO_OUTPUT_LOW);
            gpio_pin_configure_dt(&qdec_btn_gnd, GPIO_OUTPUT_LOW);
    
            while (1)
            {
                    k_sleep(K_FOREVER);
            }
    }
    

    And prj.conf file:

    # Config encoder
    CONFIG_INPUT=y
    CONFIG_INPUT_GPIO_QDEC=y

    For anyone wondering about the ground pins, here's how I have my encoder connected:

    Full sample project also included.

    xiao-pp.zip

Reply
  • This is what I needed!

    I got the encoder working correctly using the inputs library.

    Here's my new .overlay file:

    / {
        gnd_pins {
            compatible = "gpio-leds";
            qdec_gnd: qdec_gnd_pin {
                gpios = <&gpio1 14 (GPIO_ACTIVE_HIGH)>;
                label = "Rotary GND";
            };
            qdec_btn_gnd: qdec_btn_gnd_pin {
                gpios = <&gpio0 5 (GPIO_ACTIVE_HIGH)>;
                label = "Rotary btn GND";
            };
        };
        buttons: buttons {
            compatible = "gpio-keys";
            debounce-interval-ms = <50>;
            qdec_btn: btn_pin {
                gpios = <&gpio0 29 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
                label = "Encoder switch interrupt";
                zephyr,code = <INPUT_BTN_0>;
            };
        };
        qdec_btn {
            input = <&buttons>;
            compatible = "zephyr,input-longpress";
            input-codes = <INPUT_BTN_0>;
            short-codes = <INPUT_BTN_1>;
            long-codes = <INPUT_BTN_2>;
            long-delay-ms = <1000>;
        };
        qdec {
            compatible = "gpio-qdec";
            status = "okay";
            gpios = <&gpio1 13 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>,
                    <&gpio1 15 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>;
            steps-per-period = <4>;
            zephyr,axis = <INPUT_REL_WHEEL>;
            sample-time-us = <2000>;
            idle-timeout-ms = <200>;
        };
    };

    My test main.c file:

    #include <zephyr/kernel.h>
    #include <zephyr/pm/pm.h>
    #include <zephyr/pm/policy.h>
    #include <zephyr/pm/state.h>
    #include <zephyr/pm/device.h>
    #include <zephyr/input/input.h>
    #include <zephyr/drivers/gpio.h>
    
    static const struct gpio_dt_spec qdec_gnd = GPIO_DT_SPEC_GET(DT_NODELABEL(qdec_gnd), gpios);
    static const struct gpio_dt_spec qdec_btn_gnd = GPIO_DT_SPEC_GET(DT_NODELABEL(qdec_btn_gnd), gpios);
    
    static void test_cb(struct input_event *evt)
    {
            if (evt->code == INPUT_REL_WHEEL)
            {
                    printk("z event %d\n", evt->value);
            }
            if (evt->code == INPUT_BTN_1 && evt->value == 1)
            {
                    printk("short press %d\n", evt->value);
            }
            if (evt->code == INPUT_BTN_2 && evt->value == 1)
            {
                    printk("long press %d\n", evt->value);
            }
    }
    
    INPUT_CALLBACK_DEFINE(NULL, test_cb);
    
    int main(void)
    {
            if (!gpio_is_ready_dt(&qdec_gnd) || !gpio_is_ready_dt(&qdec_btn_gnd))
            {
                    printk("Device not ready\n");
            }
    
            gpio_pin_configure_dt(&qdec_gnd, GPIO_OUTPUT_LOW);
            gpio_pin_configure_dt(&qdec_btn_gnd, GPIO_OUTPUT_LOW);
    
            while (1)
            {
                    k_sleep(K_FOREVER);
            }
    }
    

    And prj.conf file:

    # Config encoder
    CONFIG_INPUT=y
    CONFIG_INPUT_GPIO_QDEC=y

    For anyone wondering about the ground pins, here's how I have my encoder connected:

    Full sample project also included.

    xiao-pp.zip

Children
No Data
Related