Efficient nrfx-based Quadrature Decoder for High-Speed Encoders on NRF5340

Hello,

I'm developing a project based on the NRF5340 and have encountered some challenges. As I'm relatively new to C, I'm trying to implement a quadrature (incremental) encoder decoder that accurately captures all four states of the signal. For example, with an encoder offering 1024 increments on two outputs, I should be able to obtain 4096 distinct positions.

I initially planned to use the Zephyr QDEC driver; however, as noted in this thread (https://devzone.nordicsemi.com/f/nordic-q-a/91398/qdec-peripheral-with-high-speed-encoder/384446), it seems too slow for my application. In addition to decoding the signal, I also need to reliably determine the direction of rotation.

Could anyone point me to a sample or provide guidance on using the nrfx-specific functions for this purpose—ideally in a way that minimizes CPU load?

  • Thanks for sharing the sample! I tried it using GPIOTE, and it seems to be working for my use case.

    For anyone who might need it in the future, here’s my sample code:

    #include <zephyr/device.h>
    #include <zephyr/kernel.h>
    #include <nrfx_gpiote.h>
    #include <nrfx_dppi.h>
    #include <nrfx_timer.h>
    #include <nrfx_log.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/irq.h>
    #include <stdatomic.h>
    #include <stdio.h>
    
    #define INPUT_PIN_A	(32 + 4)
    #define INPUT_PIN_B	(32 + 5)
    
    #define GPIOTE_INST	0 //DT_ALIAS(gpiote0) //NRF_DT_GPIOTE_INST(DT_ALIAS(gpiote0), gpios)
    #define GPIOTE_NODE	DT_NODELABEL(gpiote0)
    
    static bool A = false;
    static bool B = false;
    
    const nrfx_gpiote_t inst = NRFX_GPIOTE_INSTANCE(GPIOTE_INST);
    
    static _Atomic int32_t counter = ATOMIC_INIT(0);
    
    void gpiote_event_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t trigger, void *p_context)
    {
        if (trigger == NRFX_GPIOTE_TRIGGER_LOTOHI)
        {
            //printk("Pin %d transitioned from LOW to HIGH!\n", pin);
        }
        else if (trigger == NRFX_GPIOTE_TRIGGER_HITOLO)
        {
            //printk("Pin %d transitioned from HIGH to LOW!\n", pin);
        } else {
            //printk("Pin %d transitioned\n", pin);
            if (pin != INPUT_PIN_A && pin != INPUT_PIN_B) return;
    
            bool dir = false;
            if (pin == INPUT_PIN_A)
            {
                if (A == false && B == false)
                {
                    A = true;
                    dir = true;
                }
                else if (A == true && B == false)
                {
                    A = false;
                    dir = false;
                }
                else if (A == false && B == true)
                {
                    A = true;
                    dir = false;
                }
                else if (A == true && B == true)
                {
                    A = false;
                    dir = true;
                }
            }
            else if (pin == INPUT_PIN_B)
            {
                if (A == false && B == false)
                {
                    B = true;
                    dir = false;
                }
                else if (A == true && B == false)
                {
                    B = true;
                    dir = true;
                }
                else if (A == false && B == true)
                {
                    B = false;
                    dir = true;
                }
                else if (A == true && B == true)
                {
                    B = false;
                    dir = false;
                }
            }
    
            // Update counter
            
            if (dir) {
                if (atomic_get(&counter) >= 20000) atomic_set(&counter, 1);
                else atomic_inc(&counter);
            } else {
                if (atomic_get(&counter) <= -20000) atomic_set(&counter, -1);
                else atomic_dec(&counter);
            }
        }
    }
    void gpiote_init(void)
    {
        IRQ_CONNECT(DT_IRQN(GPIOTE_NODE), DT_IRQ(GPIOTE_NODE, priority), nrfx_isr,
                    NRFX_CONCAT(nrfx_gpiote_, GPIOTE_INST, _irq_handler), 0);
    
        if (!nrfx_gpiote_init_check(&inst))
        {
            printk("Initializing GPIOTE Instance\n");
            nrfx_err_t err = nrfx_gpiote_init(&inst, 0);
            if (err != NRFX_SUCCESS) {
                printk("Failed to init, error: 0x%08X\n", err);
                return;
            }
            nrfx_gpiote_latency_set(&inst, NRF_GPIOTE_LATENCY_LOWLATENCY);
        }
    
        nrfx_gpiote_input_pin_config_t pin_config_A_LH = {
            .p_pull_config = &(nrf_gpio_pin_pull_t){NRF_GPIO_PIN_NOPULL},
            .p_trigger_config = &(nrfx_gpiote_trigger_config_t){NRFX_GPIOTE_TRIGGER_TOGGLE, NULL},
            .p_handler_config = &(nrfx_gpiote_handler_config_t){gpiote_event_handler, NULL}
        };
    
        nrfx_gpiote_input_pin_config_t pin_config_B_LH = {
            .p_pull_config = &(nrf_gpio_pin_pull_t){NRF_GPIO_PIN_NOPULL},
            .p_trigger_config = &(nrfx_gpiote_trigger_config_t){NRFX_GPIOTE_TRIGGER_TOGGLE, NULL},
            .p_handler_config = &(nrfx_gpiote_handler_config_t){gpiote_event_handler, NULL}
        };
    
        printk("Initializing GPIOTE_PIN_A\n");
        if (nrfx_gpiote_input_configure(&inst, INPUT_PIN_A, &pin_config_A_LH) != NRFX_SUCCESS)
        {
            printk("Failed to configure INPUT_PIN_A!\n");
        }
    
        printk("Initializing GPIOTE_PIN_B\n");
        if (nrfx_gpiote_input_configure(&inst, INPUT_PIN_B, &pin_config_B_LH) != NRFX_SUCCESS)
        {
            printk("Failed to configure INPUT_PIN_B!\n");
        }
    
        nrfx_gpiote_trigger_enable(&inst, INPUT_PIN_A, true);
        nrfx_gpiote_trigger_enable(&inst, INPUT_PIN_B, true);
    
        printk("GPIOTE Initialization Done\n");
    
        A = nrf_gpio_pin_read(INPUT_PIN_A);
        B = nrf_gpio_pin_read(INPUT_PIN_B);
        printk("Initial state is (%d,%d) \n",A,B);
    }
    
    int main(void)
    {
        printk("Initializing GPIOTE...\n");
        gpiote_init();
        printk("Waiting for Event\n");
        while (1)
        {
            k_sleep(K_MSEC(1000));
            printk("Counter is at %d \n",counter);
        }
    }

    Best regards,

    Philipp

Related