Rotary encoder 'virtual spring' - Page 1
Forum section: electronics / general

author: daxliniere
author_rank: Regular Contributor
timestamp: November 14, 2022, 01:59:38 pm
content: I picked up a really nice quadrature optical rotary encoder without detents and I have a project I want to use it on.The project is a controller for a DC motor and I want 2 modes. Mode 1 where rotating the encoder increases and decreases the speed. This is straight forward.Mode 2 is trickier and acts a bit like a jog command. Turning the encoder slowly will advance the motor slowly for a short time, turning it fast will advance the motor quicky, but also for a short time. It's like by turning the encoder you are fighting against gravity/friction.The idea I had for Mode 2 was to implement a virtual 'spring' which effectively fights against you by reducing the speed value (that you increased by turning the encoder), always returning it to zero.I thought this 'force' could be simulated with time using themilliscommand, but I have no idea how to go about it. Or even if there is a rotary encoder library that already offers this functionality.Any help would be greatly appreciated.Thanks in advance,Dax Liniere

author: jpanhalt
author_rank: Super Contributor
timestamp: November 14, 2022, 02:36:14 pm
content: Will the modes be selected by a switch? If not, I suspect you are expecting to train the user extensively. I hate machines that try to train me rather than the other way around.As for force, I don't think an optical encoder can exert any appreciable resistance/force. In mode 2, you might easily detect speed or acceleration to give the different types of jogs.

author: Peabody
author_rank: Super Contributor
timestamp: November 14, 2022, 04:56:42 pm
content: I think increasing the "gain" based on rotation speed is fairly commonly found in the Arduino world. I don't know if there's a library that does it, but wouldn't be surprised. You may want to search in the Arduino forum. But basically I think it's just keeping track of the time between the last two detents, and based on that, varying how many clicks you recognize. It would normally be 1:1, but could be anything. I think this is a pretty common way to handle getting from a low value to a high value, or vice versa, without having to step through each possible value in between.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 14, 2022, 05:19:48 pm
content: I guess it could be done easily.On each encoder pulse, increase a global "speed" variable.Set a timer interrupt happening every 1ms that checks and decreases this value.So the user wilo rotate the know quickly, increasing the value faster than it's being decreased by the interrupt.When no more input is provided, the value will decrease progressively until 0.Not sure if this is what you meant.

author: AVI-crak
author_rank: Regular Contributor
timestamp: November 15, 2022, 01:03:17 pm
content: A simple solution is to use a windowed filter with a fixed delay time. The difference in values ​​between the input and output of the filter is to be used as the acceleration value for the motor.The window filter cannot be given a direct value, because there is a hard transition when the counter overflows. You need to use accumulation with a soft reset for a fixed time. The encoder directly executes + or - the counter, and the system timer constantly brings the counter value to zero by one unit. Suppose the counter is set to +50, the timer will subtract one. And if there is 100, then the timer will add one.

author: mikerj
author_rank: Super Contributor
timestamp: November 15, 2022, 04:33:02 pm
content: Quote from: DavidAlfa on November 14, 2022, 05:19:48 pmThis is exactly how I'd do it, but the speed variable would be increased by a value inversely proportional to the encoder period so turning the encoder quickly ramps up the speed value faster. The timer interrupt (or main loop with timer test) would reduce the count proportionally to the currently set motor speed.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 16, 2022, 11:55:22 am
content: An useful intuitive model might be to consider the encoder as two parts: the human-manipulated knob, and a virtual motorized rotating reference scale.The knob in relation to the reference scale controls the velocity and direction.The "virtual spring" is implemented as if the reference scale rotates towards the knob zero state. The only thing that matters, is how the reference scale "decays" to zero,howthe reference scale rotates towards zero knob state, because that is exactly the way one can implement it in code also.There are two intuitive decay models: exponential, and linear.Exponential decay is typically specified as half time: the duration in which the velocity is halved. This is easy to implement when the update interval is fixed. The reference scale rotates quickly when the difference is large, but slowly when the difference is small.Linear decay is specified as the deceleration rate: the rate of velocity drop per unit time. This is easier to use when the update interval varies. The reference scale rotates at a fixed speed, except it stays at zero when it reaches zero.Let's look at the math. Within a time interval \$t\$, velocity changes from \$v_0\$ to \$v_1\$, and the state is updated by \$x\$.For exponential decay, if \$T\$ is the decay half time, and \$T_e = T/\log 2 \approx 1.44295 T\$, these are$$v_1 = v_0 e^{-\frac{t}{T_e}}, \quad x = v_0 T_e \left( 1 - e^{-\frac{t}{T_e}} \right)$$When you use a regular interrupt, \$t\$ is a constant, and this simplifies to \$v_1 = C_1 v_0\$ and \$x = C_2 v_0\$, where$$C_1 = e^{-\frac{t}{T_e}} = e^{-\frac{t \log 2}{T}}, \quad C_2 = T_e - T_e e^{-\frac{t}{T_e}} = \frac{T}{\log 2}\left(1 - e^{-\frac{t \log 2}{T}}\right)$$and \$0 \lt C_1 \lt 1\$ and \$0 \lt C_2 \lt 1\$. With integer arithmetic, we can usev_1 = (v_0 * N1) / D1;andx = (v_0 * N2) / D2;, whereD1andD2are powers of two (so that the division is optimized to a fast binary shift right), and 0 <N1<D1and 0 <N2<D2. Just make sure the intermediate product does not overflow. You can always avoid that by casting to say 64-bit integers,v_1 = (v_0 * (int_fast64_t)N1) / D1;andx = (v_0 * (int_fast64_t)N2) / D2;.For linear decay, \$a\$ is the rate of velocity drop per unit of time. We have$$v_1 = \begin{cases}v_0 - a t, & v_0 \ge a t \\v_0 + a t, & -v_0 \ge a t \\0, & \text{otherwise} \\\end{cases}, \quad x = \begin{cases}t v_0 - \frac{a}{2} t^2, & v_0 \ge a t \\t v_0 + \frac{a}{2} t^2, & -v_0 \ge a t \\\frac{v_0^2}{2 a}, & 0 \lt v_0 \lt a t \\-\frac{v_0^2}{2 a}, & 0 \lt -v_0 \lt a t \\0, & v_0 = 0 \\\end{cases}$$Using integer arithmetic, care must be taken to avoid integer overflow, but otherwise it is straightforward. The division by \$2 a\$ only occurs when the velocity decayed to zero during the last interval. This update is only needed at display frequency, so a few dozen times a second at most, so the division will not be too slow even on 8-bit microcontrollers.Let's assume you use one (or two) pin state change interrupts to track the encoder knob. Whenever turned in positive direction, you increment some volatile integer-type variable by one; and decrement in the other direction. Following the above update, you disable interrupts, read and clear that variable, and enable interrupts, to obtain the amount of change during the last interval. Multiply the variable by some suitable constant, to get \$v_\Delta\$. Add \$v_\Delta\$ to \$v_1\$, but also update the state by \$0.5 v_\Delta t\$. This causes the encoder knob change to be taken into account as if it happened *during* the last interval. The state is updated as if it happened midway during the period, and velocity as if it all happened just before the update. (Not exactly physically correct, but makes sure even small encoder changes are reflected in the state change.)This is exactly something I would recommend one experiments with, perhaps even with a simple simulator, before deciding on an implementation.I would probably use an Arduino clone and an encoder, outputting the state via USB Serial, visualized using a simple Python application that represents the state as a turning wheel or moving dot, optionally displaying the speed or RPM.I would use the dull-looking math above to calculate the real-world decay half time (in seconds) or deceleration rate (pulses per second per second), so that I would have human-scale constants to target in the code, instead of trying to port code from an Arduino to whatever else one might use, relying on things like interrupt frequency being the same, and so on. I do not particularlylikemath, but it is damn useful for deriving real-world/physical quantities that one can then implement in code, and get the same-feeling response, even if the implementation is completely different.

author: daxliniere
author_rank: Regular Contributor
timestamp: November 17, 2022, 07:24:53 pm
content: Wow, I hadn't seen these replies (no email notifications for some reason?), but WOW, thanks everyone!I'm going to have a good long read over these to digest.Speak soon!Dax.