STM32 gotchas
52.TIM - in Encoder mode, prescaler must be 0

Many (although not all) of the STM32 timers have an Encoder Mode option in the Slave-mode Controller. When set, the timer's counter counts according to edges on CH1 and CH21 up and down, changing direction (as indicated by the TIMx_CR1.DIR bit) according to the order of edges, so that it allows connecting a quadrature encoder.

There are essentially two modes available2. In one, edges of only one channel contribute to counting - this mode is often called "x2", as there are two counts per the quadrature cycle. In the other mode, edges of both channels are counted, this is "x4" mode.

Sometimes, users wish to use the "x1" mode (or would like to have counted even less edges, for whatever reason), where only one edge is counted per quadrature cycle (e.g. only upgoing in one direction of movement and only downgoing in the other direction, only on one channel). This is often required for encoders used in manually turned "knobs" with detents, as their mechanism usually generates one full quadrature cycle per detent. It appears, that using prescaler (set to 1, i.e. dividing by 2) would solve the problem.

However, after setting prescaler3 to nonzero, the counter starts to behave strangely. While the encoder is turned/moved only to one direction, it works quite OK, but as soon as direction changes, counts may start to be missing, or behave "strange" and "unpredictible".

The root of the problem is in the fact, that the prescaler counter counts only in one direction. While the Encored-mode logic decodes the order of edges and changes the TIMx_CR1.DIR bit accordingly, this applies only to the "main" counter (CNT), but not the prescaler counter. The following diagram illustrates this effect for TIMx_SMCR.SMS=0b001 (Encoder mode 1 - Counter counts up/down on TI1FP1 edge depending on TI2FP2 level) - counter increments/decrements according to indicated direction, when prescaler counter transitions from 1 (= value set in TIMx_PSC) to 0:

CH2      _________________________..._|
               ___     ___     ___...___     ___     ___
CH1      _____|   |___|   |___|         |___|   |___|   |___

UP/DOWN       U   D   U   D   U         U   D   U   D   U

PRESC         0   1_  0   1_  0         1_  0   1_  0   1
                    \_+     \_+           \_-     \_-
CNT           5   5   6   6   7         7   6   6   5   5

Note, that at the beginning the encoder was moving only one edge up and down, yet it produced an ever increasing value in CNT; and after moving one more edge in the middle of the diagram, again moving up and down produces an ever decreasing value.

There is only one remedy to this problem: if "x1" or other "divided" mode is desired, prescaler can't be used, rather, the resulting count has to be post-processed (divided) after reading it out from TIMx_CNT, in software.

This problem has been discussed in a local conference.

1. Only CH1+CH2 are available for Encoder mode.

2. Three, but two of them are only "mirrored" versions of the same mode, changing roles of the two channels. In newer STM32 Timers, there are also other modes available for different models of rotary/linear movement encoders, e.g. Direction/Pulse. For details, resort to the given model's RM.

3. ... and considering that prescaler is preloaded, e.g. followed the setup by a forced update by setting TIMx_EGR.UG...

4. Encoders in manually turned control "knobs" often output "noisy" or "bouncy" output, where there are several transitions instead of one clean edge, even if turned in one direction. This is equivalent to encoder being moved one edge up and down repeatedly, i.e. the output count of "normal" decoder would increment and decrement one count repeatedly, rapidly, until the "bouncing" stops and levels stabilize in the new state.
This is normally not a problem, as mechanical detents stop the movement in between edges; but with the prescaler set to nonzero, the above described effect may be seen due to "bouncing" even if the encoder is turned in one direction i.e. without explicit mechanical reversal. In the STM32 timer, filters can be switched on input channels (see TIMx_CCMRx.ICxF), somewhat mitigating this problem; but with prescaler set to 0, there's no real need to use them, as the effect of "bouncing" on the final count is always effectively cancelled (by the up-down-increment-decrement logic) at the moment the encoder comes stationary between edges.