cancel
Showing results for 
Search instead for 
Did you mean: 

Quadrature Decoder Sometimes Fails to Reload When Passing Through 0 on STM32L151

JeremyWoodBCD
Associate II

I'm seeing a strange issue with the Quadrature Decoder functionality with TIM3 and TIM4 on STM32L151 (also seeing the same issue on a NUCLEO-L152RE). Sometimes when the count decrements to zero, the counter doesn't reload. Sometimes it does, picking up the reload value I set. In contrast, when the counter reaches 0xFFFF it always rolls over properly.

I'm using the Zephyr QDEC driver, which configures the timer like this:

static int qdec_stm32_initialize(const struct device *dev)
{
	const struct qdec_stm32_dev_cfg *const dev_cfg = dev->config;
	int retval;
	LL_TIM_ENCODER_InitTypeDef init_props;
	uint32_t max_counter_value;
 
	retval = pinctrl_apply_state(dev_cfg->pin_config, PINCTRL_STATE_DEFAULT);
	if (retval < 0) {
		return retval;
	}
 
	if (!device_is_ready(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE))) {
		LOG_ERR("Clock control device not ready");
		return -ENODEV;
	}
 
	retval = clock_control_on(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE),
			     (clock_control_subsys_t)&dev_cfg->pclken);
	if (retval < 0) {
		LOG_ERR("Could not initialize clock");
		return retval;
	}
 
	if (dev_cfg->counts_per_revolution < 1) {
		LOG_ERR("Invalid number of counts per revolution (%d)",
			dev_cfg->counts_per_revolution);
		return -EINVAL;
	}
 
	LL_TIM_ENCODER_StructInit(&init_props);
 
	if (dev_cfg->is_input_polarity_inverted) {
		init_props.IC1ActiveInput = LL_TIM_IC_POLARITY_FALLING;
		init_props.IC2ActiveInput = LL_TIM_IC_POLARITY_FALLING;
	}
 
	init_props.IC1Filter = dev_cfg->input_filtering_level * LL_TIM_IC_FILTER_FDIV1_N2;
	init_props.IC2Filter = dev_cfg->input_filtering_level * LL_TIM_IC_FILTER_FDIV1_N2;
 
	/* Ensure that the counter will always count up to a multiple of counts_per_revolution */
	if (IS_TIM_32B_COUNTER_INSTANCE(dev_cfg->timer_inst)) {
		max_counter_value = UINT32_MAX - (UINT32_MAX % dev_cfg->counts_per_revolution) - 1;
	} else {
		max_counter_value = UINT16_MAX - (UINT16_MAX % dev_cfg->counts_per_revolution) - 1;
	}
	LL_TIM_SetAutoReload(dev_cfg->timer_inst, max_counter_value);
 
	if (LL_TIM_ENCODER_Init(dev_cfg->timer_inst, &init_props) != SUCCESS) {
		LOG_ERR("Initalization failed");
		return -EIO;
	}
 
	LL_TIM_EnableCounter(dev_cfg->timer_inst);
 
	return 0;
}

Is there something I'm missing in how the decoder needs to be configured?

8 REPLIES 8

> Sometimes when the count decrements to zero,

Shouldn't it reload when it decrements *from* zero?

> the counter doesn't reload.

So, what happens, exactly then? How do you observe it? What's the signal source, does it have clean edges?

Read out and check/post content of TIM registers.

JW

JeremyWoodBCD
Associate II

@Community member​ Thanks for the follow-up!

> Shouldn't it reload when it decrements *from* zero?

Yes, that's it. I'll try to clarify by answering your other questions 🙂

> So, what happens, exactly then? How do you observe it?

I'm controlling a brushed DC motor from Pololu with an encoder.

I'm polling for the counter value every 1ms with LL_TIM_GetCounter() and logging the value. (I've also looked at it in the debugger).

What happens is that the counter reaches 0 and stays at zero. Here's an example log:

02-03 18:30:20.421: I: monitor: current: 136mA, pos: 3

02-03 18:30:20.422: I: monitor: current: 158mA, pos: 2

02-03 18:30:20.423: I: monitor: current: 124mA, pos: 0

02-03 18:30:20.423: I: monitor: current: 164mA, pos: 0

02-03 18:30:20.424: I: monitor: current: 122mA, pos: 0

02-03 18:30:20.425: I: monitor: current: 156mA, pos: 0

02-03 18:30:20.426: I: monitor: current: 126mA, pos: 0

> What's the signal source, does it have clean edges?

The signal from the encoder looks good, clear transitions.0693W00000aHT3rQAG.jpg 

Here are the TIM registers. First, captured while the counter was working. Then, captured in the "stuck" state:

// TIM4, Working:
CR2: 0
SMCR: 1
DIER: 0
SR: 1
EGR: 0
CCMR1: 61680
CCMR2: 0
CCER: 17
CNT: 63111
PSC: 0
ARR: 65339
RESERVED12: 0
CCR1: 0
CCR2: 0
CCR3: 0
CCR4: 0
RESERVED17: 0
DCR: 0
DMAR: 17
OR: 0
 
//TIM4 Not Working, "Stuck" at 0:
CR2: 0
SMCR: 1
DIER: 0
SR: 31
EGR: 0
CCMR1: 61680
CCMR2: 0
CCER: 17
CNT: 0
PSC: 0
ARR: 64799
RESERVED12: 0
CCR1: 0
CCR2: 0
CCR3: 0
CCR4: 0
RESERVED17: 0
DCR: 0
DMAR: 17
OR: 0

> Here are the TIM registers. 

And CR1?

Looks like you have CR1.OPM set.

JW

JeremyWoodBCD
Associate II

@Community member​

Whoops, missed CR1 when copying out of my debugger. Not sure what it is in the broken state, but in normal operation CR1 = 17.

So, CR1.OPM isn't set.

OK then it's a mystery.

At this point, I accuse the parts of your program/system which we don't see.

For example, we don't know how exactly do you read out TIM4_CNT. Can't there be the problem? Try to read out TIM4_CNT directly using debugger when the failure occurs.

Also, (inadvertent) reprogramming the two related GPIO pins would result in the same effect - i.e. read out and check relevant GPIO pins content, when the failure occurs..

Did you check the waveforms - directly on the pins - when the failure occurs?

JW

@Community member​ It's feeling like a mystery to me for sure!

Inadvertent reprogramming of those GPIOs is a possibility (although not one that makes sense from a code point of view, I'm not doing anything else with those pins), as I've seen cases where it looks like the GPIO pins are pulling the encoder signals low when faulted. However, I put a workaround in (where I manually re-load the counter if it goes below a threshold) which made the problem go away. If the cause was the pins being reconfigured, then working around it by reloading the counter wouldn't keep it working.

Thanks for looking/thinking at/on this. I'm thinking on of the next steps would be to test against a different STM32 family device. Maybe a STM32F4.

I just noticed that in the above register readouts, ARR differs. Is that normal?

JW

Oh, I hadn't noticed that. That means that these values are not from the same QDEC. My system has two QDEC instances. One on TIM3, and one on TIM4. They both exhibit this problem, and have different gearing that leads to different ARR values. I guess my working vs. broken examples aren't from the same instance.