Get Native 32Bit resolution for your encoder on STM32F4

For those who use STM32 F10x as the MCU for Micromouse must remember all general purpose timers are 16bits that gave you 65535 max resolution. Since the mouse with fauhalber motor generate about 20000 encoder counts per cell distance traverse, we must keep track of the encoder counts to prevent overflow. Even though there were several methods to expand encoder resolution softwarely or hardwarely to 32bit, neither are preferred for this application.

STM32F4 Series now have two 32bit general purpose timers: TIM2 and TIM5. Since the register capacity is now 32bit instead of 16bit for these 2 timers, the TIM2->CNT and TIM5->CNT registers can go beyond 65535(16bit) during in my test(obviously). If you decide to design a new mouse with stm32F4, having the 2 encoders connected to TIM2 and TIM5 will be a more no brainer choice to make your life my easier.

The configuration code for encoder are very much likely same to STM32F10x, here is the code for setting TIM2 to quadrature encoder mode on STM32F4:

void Encoder_Configration(void)

{

    NVIC_InitTypeDef NVIC_InitStructure;

    GPIO_InitTypeDef GPIO_InitStructure;

   TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

   TIM_ICInitTypeDef TIM_ICInitStructure;

   /*TIM2 clock source enable */

   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

   /* Enable GPIOA, clock */

   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);   

   /* Encoder unit connected to TIM2, quadrature mode */   

  //GPIOA Config  

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

 

    /* Enable the TIM2 Update Interrupt for left encoder*/                                                                           

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;            

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);

 

 

    /* Timer configuration in Encoder mode for left encoder*/

    TIM_TimeBaseStructure.TIM_Prescaler = 0x00;  // No prescaling

    TIM_TimeBaseStructure.TIM_Period = 0xffffffff; //max resolution

    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//devide by clock by one

    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;// count up  

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

 

    TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,  TIM_ICPolarity_Falling, TIM_ICPolarity_Falling);

    TIM_ICStructInit(&TIM_ICInitStructure);

    TIM_ICInitStructure.TIM_ICFilter = 6;//ICx_FILTER;

    TIM_ICInit(TIM2, &TIM_ICInitStructure);

    // Clear all pending interrupts

    TIM_ClearFlag(TIM2, TIM_FLAG_Update);

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    //Reset counter

    TIM2->CNT = 10000;//prevent exceeding 0 when turning wheel backwards

    TIM_Cmd(TIM2, ENABLE);//enable left encoder

}

Here are something even simpler:

void Encoder_Configration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_TIM2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_TIM2);

TIM_SetAutoreload (TIM2, 0xffffffff);
/* Configure the timer */

TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Falling);
/* TIM2 counter enable */
TIM_Cmd(TIM2, ENABLE);
}

6 thoughts on “Get Native 32Bit resolution for your encoder on STM32F4

  1. Hi There,
    Thank you very much for your tutorial.
    In the last couple of days I have been struggling with my STM32F429, trying to configure a simple and ordinary incremental encoder but without success (I am getting that feeling I’m really ….). Anyway, trying to find the reason for may failure I cross with your configuration method and then I realize that what was missing – working with interrupts. I initially thought that only by simply setting up the TIM# (on of the few available on my board) that would be enough to get the counting from the digital signal given by the encoder (EM1 from USDigital). So, my questions:
    1. Is it necessary to use Interrupts to get the counting?
    2. Once you have these interrupts do you need to create a handle routine for TIM2_IRQn?
    3. Do acquire the encoder counting using the TIM_GetCounter(TIM2) or only TIM2->CNT (16bit value)?
    4. This acquisition is done on the interrupt routine or main course of your program?
    Really appreciate for any sort of help you can provide me.
    Best regards
    Francisco
    (Portugal)
    4.

  2. 1. no need to have interrupt involved.
    2. I didn’t create handler unless you want to do something every time the count is overflowed in quadrature encoder mode.
    3. I use TIM2->CNT instead of TIM_GetCounter(TIM2) but since TIM_GetCounter(TIM2) is just calling TIM2->CNT plus some parameter check, therefore they worked same for me. The max resolution of counter is based on the auto-reload value you set. therefore TIM2->ARR = 0xffffffff will give 32 bit resolution. I wrote 65535 was kinda mistaken, it went with 16 bit resolution.
    4. Since the hardware quadrature is a module working individually, therefore it runs on neither interrupt routing nor main routine, as long as you don’t do anything in handler.

    Here is something even simpler that I am using:
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5|RCC_APB1Periph_TIM2, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_15;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM5);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM5);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_TIM2);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_TIM2);

    TIM_SetAutoreload (TIM5, 0xffffffff);
    TIM_SetAutoreload (TIM2, 0xffffffff);
    /* Configure the timer */
    TIM_EncoderInterfaceConfig(TIM5, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Falling);
    TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Falling);
    /* TIM2/5 counter enable */
    TIM_Cmd(TIM5, ENABLE);
    TIM_Cmd(TIM2, ENABLE);

  3. Hi
    Thank you very much for your assistance. It helped me a lot.
    I am working on the solutions and when it is finished I will send it to you.
    But your aid was definitely crucial
    Thanks again

  4. Hi Green Ye,

    ‘ Even though there were several methods to expand encoder resolution softwarely or hardwarely to 32bit, they are still not the preferred way for us to do.’

    You mention that a 32-bit timer and thus counter is much preferred for an quadrature encoder/decoder. I agree, but unfortunately no 32-bit counter is available at the stm32f103c8t6 I am using.

    What strategy could be used to extend the 16bit counter to 32 bit? I have a few ideas, but would appreciate your input before I go an implement one, as this may save me a lot of time (being a novice, everything still takes a lots of time.)

    fwiwi: I am (semi-succesfully) controlling the position of a dc motor using a pid-controller (pd actually) with an H-bridge (L298N) and PWM from a timer. QEI keeps count nicely, but the 16bit range of the timer is limiting.

    My ideas:
    – Set an overflow/underflow interrupt for the QEI timer, and in the isr add/substract the 32bit global variable. (Haven’t figured out yet how to do that for a timer set to encoder mode).

    – Have a timer-interrupt check the 16bit counter often enough to catch over- or underflows, but this likely is not catching all scenarios. It may work fine when the motor is spinning in one direction, but not when it’s oscillating, overshooting its target, changing direction often, quickly. I am still trying to optimize the pid tuning to minimize/eliminate that. The backlash in the gearing does not make this easy at all.

    Thanks in advance for your input!

    • unless the priority for over flow interrupt is the highest, otherwise the losing counts are inevitable.
      periodically re-condition the counts manually is what I would suggest.

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload the CAPTCHA.