cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G474RE TIM4 Rotary Encoder

Manu Abraham
Senior

Hi,

Greetings!

I have a Bourns Rotary encoder

https://www.bourns.com/docs/product-datasheets/pec11r.pdf

connected to PA11(ENCA)/TIM4_CH1 and PA12(ENCB)/TIM4_CH2.

Trying to use the encoder as a input control for user input.

This is what I am doing:

 
static void encoder_init(void)
{
	LL_GPIO_InitTypeDef		gpio;
 
	LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM4);
 
	gpio.Pin	= LL_GPIO_PIN_11 | LL_GPIO_PIN_12;	/* Enc A, Enc B */
	gpio.Mode	= LL_GPIO_MODE_ALTERNATE;		/* Alternate function */
	gpio.Speed	= LL_GPIO_SPEED_FREQ_LOW;		/* Low Speed */
	gpio.Alternate	= LL_GPIO_AF_10;			/* AF10=TIM4 */
	LL_GPIO_Init(GPIOA, &gpio);
 
	LL_TIM_SetPrescaler(TIM4, 0);
	LL_TIM_SetAutoReload(TIM4, 100);
	LL_TIM_SetCounterMode(TIM4, LL_TIM_COUNTERMODE_UP);
	LL_TIM_SetClockDivision(TIM4, LL_TIM_CLOCKDIVISION_DIV4);
 
	LL_TIM_DisableARRPreload(TIM4);
//	LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X4_TI12);
	LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_DIRECTIONALCLOCK_X1_TI12);
 
	LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);
	LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1);
	LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV2_N8);
	LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
 
	LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI);
	LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1);
	LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV2_N8);
	LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING);
 
	LL_TIM_EnableCounter(TIM4);
	LL_TIM_CC_EnableChannel(TIM4, LL_TIM_CHANNEL_CH1);
	LL_TIM_CC_EnableChannel(TIM4, LL_TIM_CHANNEL_CH2);
}
 
int main(void)
{
	uint32_t enc_cur = 0, enc_prev = 0;
 
	LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
	LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
 
	NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
	SystemClock_Config();
	gpio_init();
	uart_init();
 
	encoder_init();
	printf("\tSTM32G474 Encoder Test\n");
 
	while (1) {
		enc_cur = LL_TIM_GetCounter(TIM4);
		if (enc_cur != enc_prev) {
			printf(" ENC: %d DIR: %d\n", enc_cur, LL_TIM_GetDirection(TIM4) >> 4);
			enc_prev = enc_cur;
		}
	}
}

On RST, I get the following results:

       STM32G474 Encoder Test

 ENC: 1 DIR: 0

 ENC: 2 DIR: 0

 ENC: 3 DIR: 0

 ENC: 4 DIR: 0

When I turn the encoder CW, I get:

 ENC: 3 DIR: 1

 ENC: 2 DIR: 1

 ENC: 1 DIR: 1

 ENC: 0 DIR: 1

 ENC: 100 DIR: 1

 ENC: 99 DIR: 1

 ENC: 98 DIR: 1

 ENC: 97 DIR: 1

 ENC: 96 DIR: 1

When I turn the encoder CCW, I get:

 ENC: 95 DIR: 1

 ENC: 94 DIR: 1

 ENC: 93 DIR: 1

 ENC: 92 DIR: 1

 ENC: 91 DIR: 1

 ENC: 90 DIR: 1

 ENC: 89 DIR: 1

 ENC: 88 DIR: 1

 ENC: 87 DIR: 1

Attached screenshot:

0693W000006Gx0VQAS.png 

Despite what I do, the direction, does not seem to change.

Any thoughts, suggestions what I am doing wrong in here ?

Thanks,

Manu

1 ACCEPTED SOLUTION

Accepted Solutions

This looks much like there's no input on CH2.

JW

View solution in original post

5 REPLIES 5

I don't understand the Cube/LL gibberish, but

> LL_TIM_ENCODERMODE_DIRECTIONALCLOCK_X1_TI12

is probably Directional Clock encoder mode, which, as the manual says:

In the “directional clock�? mode on Figure 409, the clocks are provided on two lines, with a

single one at once, depending on the direction, so as to have one up-counting clock line and

one down-counting clock line.

which is not case with the usual manually turned encoder.

JW

Hi JW,

Thanks for the input. Which mode do you suggest ?

I tried both SMS=b'0011' and b'1110' both appeared to have weird results:

ie,

static void encoder_init_r2(void)
{
	LL_GPIO_InitTypeDef		gpio;
 
	LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM4);
 
	gpio.Pin	= LL_GPIO_PIN_11 | LL_GPIO_PIN_12;	/* Enc A, Enc B */
	gpio.Mode	= LL_GPIO_MODE_ALTERNATE;		/* Alternate function */
	gpio.Speed	= LL_GPIO_SPEED_FREQ_LOW;		/* Low Speed */
	gpio.Alternate	= LL_GPIO_AF_10;			/* AF10=TIM4 */
	LL_GPIO_Init(GPIOA, &gpio);
 
	LL_TIM_SetPrescaler(TIM4, 0);
	LL_TIM_SetAutoReload(TIM4, 100);
	LL_TIM_SetCounterMode(TIM4, LL_TIM_COUNTERMODE_UP);
	LL_TIM_SetClockDivision(TIM4, LL_TIM_CLOCKDIVISION_DIV4);
 
	LL_TIM_DisableARRPreload(TIM4);
 
	/**
	 * Slave Mode Selection (SMS)
	 * 0000: Slave mode disabled ifCEN = ‘1 prescaler fed from internal clock.
	 * 0001: Encoder mode 1 - tim_ti1fp1 edge up/down counter depending on tim_ti2fp2 level.
	 * 0010: Encoder mode 2 - tim_ti2fp2 edge up/down counter depending on tim_ti1fp1 level.
	 * 0011: Encoder mode 3 - tim_ti1fp1, tim_ti2fp2 edge up/down counter depending on the level of the other input.
	 * 0100: Reset Mode - selected trigger input rising edge (tim_trgi) reinitializes counter and updates registers.
	 * 0101: Gated Mode - The counter clock is enabled when the trigger input (tim_trgi) is high.
	 * 0110: Trigger Mode - The counter starts at a rising edge of the trigger tim_trgi (but it is not reset). Only the start of the counter is controlled.
	 * 0111: External Clock Mode 1 - Rising edges of the selected trigger (tim_trgi) clock the counter.
	 * 1000: Combined reset + trigger mode - Rising edge of the selected trigger input (tim_trgi) reinitializes the counter, generates an update of the registers and starts the counter.
	 * 1001: Combined gated + reset mode - The counter clock is enabled when the trigger input (tim_trgi) is high. The counter stops and is reset) as soon as the trigger becomes low.
	 * 1010: Encoder mode: Clock plus direction, x2 mode.
	 * 1011: Encoder mode: Clock plus direction, x1 mode, tim_ti2fp2 edge sensitivity is set by CC2P.
	 * 1100: Encoder mode: Directional Clock, x2 mode.
	 * 1101: Encoder mode: Directional Clock, x1 mode, tim_ti1fp1 and tim_ti2fp2 edge sensitivity is set by CC1P and CC2P.
	 * 1110: Quadrature encoder mode: x1 mode, counting on tim_ti1fp1 edges only, edge sensitivity is set by CC1P.
	 * 1111: Quadrature encoder mode: x1
	 *
	 * #define TIM_SMCR_SMS_0            (0x00001UL << TIM_SMCR_SMS_Pos)              // 0x00000001
	 * #define TIM_SMCR_SMS_1            (0x00002UL << TIM_SMCR_SMS_Pos)              // 0x00000002
	 * #define TIM_SMCR_SMS_2            (0x00004UL << TIM_SMCR_SMS_Pos)              // 0x00000004
	 * #define TIM_SMCR_SMS_3            (0x10000UL << TIM_SMCR_SMS_Pos)              // 0x00010000
	 *
	 * #define LL_TIM_ENCODERMODE_X2_TI1                     TIM_SMCR_SMS_0                                                     // Quadrature encoder mode 1, x2 mode - Counter counts up/down on TI1FP1 edge depending on TI2FP2 level
	 * #define LL_TIM_ENCODERMODE_X2_TI2                     TIM_SMCR_SMS_1                                                     // Quadrature encoder mode 2, x2 mode - Counter counts up/down on TI2FP2 edge depending on TI1FP1 level
	 * #define LL_TIM_ENCODERMODE_X4_TI12                   (TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0)                                   // Quadrature encoder mode 3, x4 mode - Counter counts up/down on both TI1FP1 and TI2FP2 edges depending on the level of the other input
	 * #define LL_TIM_ENCODERMODE_CLOCKPLUSDIRECTION_X2     (TIM_SMCR_SMS_3 | TIM_SMCR_SMS_1)                                   // Encoder mode: Clock plus direction - x2 mode
	 * #define LL_TIM_ENCODERMODE_CLOCKPLUSDIRECTION_X1     (TIM_SMCR_SMS_3 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0)                  // Encoder mode: Clock plus direction, x1 mode, TI2FP2 edge sensitivity is set by CC2P
	 * #define LL_TIM_ENCODERMODE_DIRECTIONALCLOCK_X2       (TIM_SMCR_SMS_3 | TIM_SMCR_SMS_2)                                   // Encoder mode: Directional Clock, x2 mode
	 * #define LL_TIM_ENCODERMODE_DIRECTIONALCLOCK_X1_TI12  (TIM_SMCR_SMS_3 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_0)                  // Encoder mode: Directional Clock, x1 mode, TI1FP1 and TI2FP2 edge sensitivity is set by CC1P and CC2P
	 * #define LL_TIM_ENCODERMODE_X1_TI1                    (TIM_SMCR_SMS_3 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1)                  // Quadrature encoder mode: x1 mode, counting on TI1FP1 edges only, edge sensitivity is set by CC1P
	 * #define LL_TIM_ENCODERMODE_X1_TI2                    (TIM_SMCR_SMS_3 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0) // Quadrature encoder mode: x1 mode, counting on TI2FP2 edges only, edge sensitivity is set by CC1P
	 */
 
//	LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X4_TI12);	/* Enc MODE3: Up/Down on TI1FP1, TI2FP2 edges */
	LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X1_TI1);		/* Enc x1 MODE: Up/Down on TI1FP1 edge */
 
	LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);
	LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1);
	LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV2_N8);
	LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
 
	LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI);
	LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1);
	LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV2_N8);
	LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING);
 
	LL_TIM_EnableCounter(TIM4);
	LL_TIM_CC_EnableChannel(TIM4, LL_TIM_CHANNEL_CH1);
	LL_TIM_CC_EnableChannel(TIM4, LL_TIM_CHANNEL_CH2);
}

which results in

#1. With

   LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X4_TI12);   /* Enc MODE3: Up/Down on TI1FP1, TI2FP2 edges */

On RST

      STM32G474 Encoder Test

ENC: 1 DIR: 0

ENC: 2 DIR: 0

ENC: 1 DIR: 1

ENC: 2 DIR: 0

ENC: 1 DIR: 1

ENC: 2 DIR: 0

ENC: 1 DIR: 1

ENC: 2 DIR: 0

ENC: 1 DIR: 1

ENC: 2 DIR: 0

On Single CW rotation:

ENC: 3 DIR: 0

ENC: 2 DIR: 1

On Single CCW rotation

ENC: 3 DIR: 0

ENC: 2 DIR: 1

4x CW rotations

ENC: 3 DIR: 0

ENC: 2 DIR: 1

ENC: 3 DIR: 0

ENC: 2 DIR: 1

ENC: 3 DIR: 0

ENC: 2 DIR: 1

ENC: 3 DIR: 0

ENC: 2 DIR: 1

4x CCW rotations

ENC: 3 DIR: 0

ENC: 2 DIR: 1

ENC: 3 DIR: 0

ENC: 2 DIR: 1

ENC: 3 DIR: 0

ENC: 2 DIR: 1

ENC: 3 DIR: 0

ENC: 2 DIR: 1

#2. With

   LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X1_TI1);      /* Enc x1 MODE: Up/Down on TI1FP1 edge */

On RST

       STM32G474 Encoder Test

On Single CW rotation:

 ENC: 1 DIR: 0

 ENC: 0 DIR: 1

On Single CCW rotation:

 ENC: 1 DIR: 0

 ENC: 0 DIR: 1

Both dont look quite sane. Or am I still doing something wrong ?

Thanks,

Manu

This looks much like there's no input on CH2.

JW

Hi JW,

I was getting pretty frustrated -- Connected a scope to ENC_A and _B, things looked very well indeed.

Pulled a few hairs not knowing what to do.

Checked CH1 , CH2 waveforms on PA11, 12.

Well, as you said: one channel was dead .. The connector on CH2 was not making contact.

Changed the connector, things look quite good indeed.

Thanks a bunch! I would have lost quite a lot of hair, if not for your observation. 😉

Something really bad happened over here. After I did my last post; We had an electrical issue here, complete blackout, UPS tripped. :\

Resuming after fixing the problem, the filesystem has a previous version of main.c, ie, it was missing the newer

encoder_init_r2() function.

Had to copy back the function from the forum. 🙂

It looks quite well.

One issue though, it starts with the set auto reload value (ie at max, in this case 100)

Any thoughts to get it start with, say 0 ?

       STM32G474 Encoder Test

 ENC: 100 DIR: 1

 ENC: 0 DIR: 0

 ENC: 1 DIR: 0

 ENC: 2 DIR: 0

 ENC: 3 DIR: 0

 ENC: 4 DIR: 0

 ENC: 5 DIR: 0

 ENC: 6 DIR: 0

 ENC: 7 DIR: 0

 ENC: 6 DIR: 1

 ENC: 5 DIR: 1

 ENC: 4 DIR: 1

 ENC: 3 DIR: 1

 ENC: 2 DIR: 1

 ENC: 1 DIR: 1

 ENC: 0 DIR: 1

 ENC: 100 DIR: 1

 ENC: 99 DIR: 1

 ENC: 98 DIR: 1

 ENC: 97 DIR: 1

Thanks again,

Manu

The quick hack, that I did was to set the counter to 1.

Wonder, whether that's a good thing to do ..

Thanks,

Manu