QDEC Peripheral with high speed encoder

Hello,

I have been testing an encoder using the QDEC peripheral.

The encoder produces 256 pulses per rotation. Testing without any load assigned to the motor, the encoder rotates at 2000-3000 rpm depending on the input.

Using the zephyr sensor api or the nrfx_qdec driver I have not been able to get correct measurements.

I tried lowering the SAMPLEPER to the lowest possible setting (128us) but it didn't work either.

However, using gpio pin interrupts I have been able to get correct measurements.

Is the QDEC peripheral suitable for such a frequency of pulses ?

Is there any other way to do this to avoid using multiple interrupts (like GPIOTE and TIMER as I read somewhere) ?

Parents
  • Hi 

    If my math is correct you will get pulses every 80us (meaning a rising or falling flank every 40us) when the encoder is rotating at 3000 RPM with 256 pulses pr rotation. This is too fast for the QDEC, even at its fastest setting. 

    It is true that you can use the GPIOTE, TIMER and PPI peripherals to measure the length of a pulse without having to rapidly process an interrupt. A simple way to do this is simply to have a free running timer that will trigger a capture and clear every time you get a pulse, and the CC register will then change depending on how long time it takes between captures. 

    The simple example included below (written for the nRF52 series originally) illustrates this concept. To run on the nRF5340 it will need some rewriting because of the difference between the PPI and DPPI peripherals. 

    #define PC_TIMER		NRF_TIMER0
    #define PC_GPIOTE_CH	0
    #define PC_PPI_CH		0
    
    static void pulse_capture_init(uint32_t pin_num)
    {
    	PC_TIMER->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
    	PC_TIMER->PRESCALER = 0;
    	NRF_GPIOTE->CONFIG[PC_GPIOTE_CH] = 	GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos |
    										GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos |
    										pin_num << GPIOTE_CONFIG_PSEL_Pos;
    	NRF_PPI->CH[PC_PPI_CH].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[PC_GPIOTE_CH];
    	NRF_PPI->CH[PC_PPI_CH].TEP = (uint32_t)&PC_TIMER->TASKS_CAPTURE[0];
    	NRF_PPI->FORK[PC_PPI_CH].TEP = (uint32_t)&PC_TIMER->TASKS_CLEAR;
    	NRF_PPI->CHENSET = (1 << PC_PPI_CH);
    
    	PC_TIMER->TASKS_START = 1;
    }
    
    static uint32_t pulse_capture_get()
    {
    	return PC_TIMER->CC[0];
    }
    

    Best regards
    Torbjørn

  • Hello Ovrebekk,

    thank you for your answer and for confirming this.

    Also thank you for sharing this sample.

    I will read more about the TIMER and GPIOTE peripherals since I have never used them before and I will try to implement this.

Reply Children
  • Hi 

    You welcome, glad I could help. 

    If you find this method to work sufficiently well I would recommend rewriting the code using the nrfx drivers. There should be an nrfx driver available for the TIMER, DPPI and GPIOTE modules. 

    I tested my code simply by calling the pulse_capture_get() function at regular intervals on a timer, but you can also enable interrupt on the GPIOTE IN event in case you want to be interrupted on every pulse. The problem with this is that you will get a very large number of interrupts, as you already discovered. 
    Potentially you can make a hybrid solution where you enable interrupts when the motor speed is low, and change to a polling solution when the motor frequency passes a certain threshold, to avoid an unpredictable and variable load on the CPU to handle all the interrupts. 

    Best regards
    Torbjørn

  • Hello,

    this is what is causing me trouble at the moment. 

    I need to calculate displacement not speed and my project requires 2 encoders in total. 

    Considering you solution I am not sure how to use it to calculate displacement.

    I think that the only way to capture displacement and understand direction will be to capture each pulse. 

  • Hi 

    That changes things. Hopefully you only need to detect displacement in one direction? 

    You could change the TIMER module to counter mode, and instead of measuring the time between two consecutive GPIOTE IN events simply count how many times each of them occur. Then you can set up a separate slower timer (using an RTC or Zephyr timer) to run a capture on the timer and read out the value at regular intervals. 

    As long as you have 2 free TIMER modules, and an additional GPIOTE and PPI channel, you should be able to serve 2 encoders. 

    Best regards
    Torbjørn

  • Hi,

    ideally I would need to be able to capture displacement in both directions. 

    I was thinking of checking this by capturing the PINB status whenever the PINA GPIOTE event occurs. 

    On the other hand, direction is something that perhaps I would be able to exclude considering that I will be controlling the motor's rotation and therefore I can try considering the actual direction equal to the command direction I set (in order to avoid the need for calculating the direction through the encoder). 

  • Hi 

    If you can exclude it it would definitely simplify the implementation, but possibly it is enough to occasionally trigger an interrupt on the PINA event and check the status of PINB. You just need some system to detect if the interrupt is delayed, since this would give you an incorrect reading. 

    Best regards
    Torbjørn

Related