cancel
Showing results for 
Search instead for 
Did you mean: 

TIM3 Quadrature Encoder Mode Strangeness

jerry2
Senior

I'm got a Nucleo-F746ZG connected to a DC motor, which I'm driving via PWM using TIM1.

I'm using TIM3 in quadrature encoder mode to read the encoder on the motor. The problem I'm encountering is the TIM3 CNT register is not incrementing fast enough. The encoder on the motor generates 500 counts per revolution of the motor, and I've verified this using an oscilloscope to look at the two encoder outputs and comparing the expected counts with the motor's RPM as measured by a tachometer connected to the output shaft.

At a measured speed of 3108 RPM, I see encoder edges every 10 microseconds, which is exactly what I expect. TIM3 is set up to count on every rising and falling transition of the two encoder outputs, so I'd expect TIM3's CNT to increase at a pretty fast rate, but I'm seeing it increase at only around 20-30 counts every 100 milliseconds, which is way too low. Occasionally the count even decreases. I'm having a hard time understanding why this is happening. The motor is spinning in only one direction, and looking at the encoder output on a scope the signals look rock solid with no glitches.

Is there any reason why TIM3 is behaving this way? On this board TIM3 is clocked at 54 MHz, so that should be fast enough to keep up with a 500 count per revolution quad encoder on a motor spinning at 3000 RPM (that's only 25,000 counts per second). This weirdness happens even when the motor is spinning much slower, say 400 RPM.

1 ACCEPTED SOLUTION

Accepted Solutions
jerry2
Senior

I found the issue (and fixed it).

When I created this code for reading a quadrature encoder I copied code that I had written earlier for reading a Hall encoder. The Hall encoder code worked fine, so I thought that would be a good place to start. I made appropriate changes to the code and ended up in the position I described in my first post in this thread.

The issue was that I forgot to turn off the TI1S bit in the CR2 register for TIM3. This bit needs to be set for a Hall encoder to XOR the CH1, CH2, and CH3 inputs to TI1, but it's definitely not needed for a quadrature encoder!

Turning off the TI1S bit in CR2 fixed the problem and now I'm getting encoder counts that increment at the appropriate rate, and don't run backwards!

Thanks to everyone who responded to my query.

View solution in original post

10 REPLIES 10
TDK
Guru

> I'm seeing it increase at only around 20-30 counts every 100 milliseconds, which is way too low.

How are you determining this, exactly?

Timer should have no issues keeping up with 25000 edges/sec.

Can you look at encoder signal on a scope to verify the signal is clean and as expected?

If you feel a post has answered your question, please click "Accept as Solution".
jerry2
Senior

As I said in my original post, “The motor is spinning in only one direction, and looking at the encoder output on a scope the signals look rock solid with no glitches.�?

Which pins? Aren't there some on-board connections to these pins?

Also, read out and check/post TIM3 registers content.

JW

jerry2
Senior

Below is the code used to set up TIM3 to read the quadrature encoder. I'm using PA6 and PC7 as the pins for the encoder's A and B outputs. According to the Nucleo-144 manual, these pins are used for SPI1 and I2S, but there's no SPI or I2S devices on this board, or connected to it. STM32CubeMX didn't complain when I assigned these pins.

Another piece of data: I connected this motor to an Infineon XMC4700 Relax board and it reads the encoder without issue and the position and RPM readings agree to within +/- 2 RPM of the external tachometer I use to verify actual RPM on the motor.

#include    <stm32f7xx.h>
#include    <stdint.h>
#include    "gpio.h"
 
 
static TIM_TypeDef   *pwmRegs[5] = {TIM1_BASE, TIM2_BASE, TIM3_BASE, TIM4_BASE, TIM5_BASE};
 
int32_t
QEInit(void)
 
{
 
    /*
     * Initialize the GPIO ports used by the motor quadrature encoder outputs.
     */
    GPIOInitPin(GPIO_PORTA, GPIO_PIN6, GPIO_DIGITAL, 1, GPIO_AF2, 0, GPIO_HIGHSPEED, GPIO_NOPULL, GPIO_PUSHPULL);
    GPIOInitPin(GPIO_PORTC, GPIO_PIN7, GPIO_DIGITAL, 1, GPIO_AF2, 0, GPIO_HIGHSPEED, GPIO_NOPULL, GPIO_PUSHPULL);
 
 
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;                 // enable TIM3 clock
    RCC->APB1RSTR |= RCC_APB1RSTR_TIM3RST;              // reset TIM3
    RCC->APB1RSTR &= ~RCC_APB1RSTR_TIM3RST;
 
    /*
     * Initialize TIM3 for quadrature encoder duty. TIM3 is connected to the APB1 bus and runs at 54 MHz.
     */
    pwmRegs[2]->CR1 = 0;                                // set edge-aligned mode, upcounting, and disable the timer
    pwmRegs[2]->CR2 = TIM_CR2_TI1S;                     // clear control register 2 to defaults
    pwmRegs[2]->SMCR = (0b011 << TIM_SMCR_SMS_Pos); 	// set slave mode to Encoder mode 3
    pwmRegs[2]->DIER = 0;                               // turn off DMA and disable interrupts
    pwmRegs[2]->CCMR1 = (0b01 << TIM_CCMR1_CC1S_Pos) | (0b01 << TIM_CCMR1_CC2S_Pos); // CC1 configured as input and mapped to TI1; CC2 configured as input and mapped to TI2
 
    pwmRegs[2]->CCER = (0 << TIM_CCER_CC1P_Pos) | (0 << TIM_CCER_CC2P_Pos) |
                       TIM_CCER_CC1E | TIM_CCER_CC2E;   // enable captures on rising and falling edges of encoder inputs
    pwmRegs[2]->PSC =  0;                               // set pre-scaler to 1
    pwmRegs[2]->ARR =  500;                             // set counts per shaft rotation
    pwmRegs[2]->CCR1 = 0;                               // clear capture register
    pwmRegs[2]->CNT = 0;                                // clear counter
 
    pwmRegs[2]->CR1 = 1;                                // start TIM3
 
}
 
 
uint32_t
QEReadEncoder(void)
 
{
 
  return(pwmRegs[2]->CNT);
 
}

Source looks OK, but there are reasons I asked for content of registers. The processor does not work out of the source but out of the registers.

Also,

> pwmRegs[2]->ARR = 500;

Why? How exactly do you observe CNT? Don't you simply miss whole revolutions? Simply leave ARR at its default, which is its natural max.

JW

jerry2
Senior

I'll grab a dump of the TIM3 registers and post them.

500 is the number of counts the encoder generates per shaft rotation. I've also tried setting ARR to the max (0xffff), but it didn't make any difference.

I'm observing CNT by periodically reading it out in an ISR generated by another timer. This timer's period is much shorter than the time it takes for one shaft rotation, so I'm not missing whole revolutions. Could this issue be caused by reading CNT too often or at the wrong time? I don't remember reading anything in the reference manual that says anything about CNT being sensitive to this.

To get an idea of the history of CNT, in the timer interrupt where I'm reading it, I save the value to an array. When I look at the array after running the motor long enough to fill up the array, I see the value of CNT changing much more slowly than it should be, and I see stretches where it changes direction and decreases instead of increasing for several reads of the counter. This is the part that that's most puzzling. The encoder signals are very clean on the scope and I see no glitches that the QE decoder could interpret as a change in motor direction. And, as I've mentioned in an earlier post, when this same motor and encoder are connected to an Infineon XMC4700 development board the QE counts are rock solid, at the correct rate, and exhibit no weirdness (like the count decreasing occasionally).

Did you check that system/AHB clock runs as expected? Generate PWMs on the TIM3 CH1 and CH2 pins and observe.

Did you check that the "sampling" interrupt is regular as expected? Toggle a pin in the "sampling" interrupt and observe.

Did you measure the input signals directly on the mcu pins?

Review grounds arrangement.

Read out and check/post content of TIM and relevant GPIO registers content.

JW

jerry2
Senior

Here's the answers to your questions:

  1. I haven't verified APB bus timing using TIM3, but TIM3 and USART3 are on the same APB and my baud rate calculations are correct for an APB1 bus speed of 54 MHz, so I'd assume that TIM3 is also being clocked at 54 MHz.
  2. Yes, I checked the sampling interrupt interval in exactly the manner you described--toggling a GPIO in the ISR and measuring the interval using a scope.
  3. Yes, I probed the encoder signals directly on the MCU pins. Clean as a whistle.
  4. This is a Nucleo board, so grounding on the board itself is out of my control. The encoder outputs from the motor are on a ribbon cable, and I'm plugging the A/B outputs and ground directly into the Arduino connectors on the Nucleo. The cable from the encoder is about 10cm long.
  5. I'll post TIM and GPIO register content as soon as I reconfigure the components (the motor is currently connected to the Infineon board).

BTW, I firmly believe this issue is caused by something I'm doing wrong. I strongly doubt the encoder support in the STM32 timers is at fault.

jerry2
Senior

I found the issue (and fixed it).

When I created this code for reading a quadrature encoder I copied code that I had written earlier for reading a Hall encoder. The Hall encoder code worked fine, so I thought that would be a good place to start. I made appropriate changes to the code and ended up in the position I described in my first post in this thread.

The issue was that I forgot to turn off the TI1S bit in the CR2 register for TIM3. This bit needs to be set for a Hall encoder to XOR the CH1, CH2, and CH3 inputs to TI1, but it's definitely not needed for a quadrature encoder!

Turning off the TI1S bit in CR2 fixed the problem and now I'm getting encoder counts that increment at the appropriate rate, and don't run backwards!

Thanks to everyone who responded to my query.