2

I use a magnetic encoder, which reacts on hand inputs. When turning, it calculates the angle, and that part works perfectly. That means that if I turn the encoder 360 degrees, the angle is reset. (therefore it's 0 again). However, I would like to detect if the encoder has passed 360 and goes back to 0 or if it goes from 0 back up to 360. I need to know which direction the encoder is turned, in 30 degree steps.

So: How do I know if the encoder is turned in the clockwise direction (going fx. from 0 -> 360 -> 0 -> 360) or if it is turned in the counter-clockwise direction (going fx. from 360 -> 0 -> 360 -> 0)

Right now it sees the step from 360 to 0 as a counter-clockwise turn, where it actually is a clockwise turn...

Any suggestions?

ifraaank
  • 122
  • 2
  • 13
  • can it be turned more than 180 degrees at a time ? – Sander De Dycker Jun 13 '18 at 08:56
  • Well, yes, but since it's human input, it is very unlikely, and you'd have to be very fast. – ifraaank Jun 13 '18 at 08:58
  • then it's technically impossible to know which direction it was turned, because eg. a 90 degrees clockwise turn could also have been a 270 degrees counter-clockwise turn. If your magnetic encoder doesn't indicate the direction of the rotation, the best you can do is guess (eg. shortest distance - ie. 90 degrees clockwise is more likely than 270 degrees counter-clockwise), but then you might sometimes get it wrong – Sander De Dycker Jun 13 '18 at 09:03
  • You must be sure that the samples read from encoder are faster than a turn. Avoiding to miss a full turn the direction can be simply obtained examining the sign of the difference between actual count and last one. You can also understand when the turn is complete and count the turns. – Frankie_C Jun 13 '18 at 09:05
  • @SanderDeDycker Yes, I figured. But since it 99% of the time will only be turned in 30 degree steps (the encoder has dents in these intervals) it should be possible to get it right most of the time. I just can't figure out the logic – ifraaank Jun 13 '18 at 09:07
  • @Frankie_C Yes, i read it fairly often, so that shouldn't be a problem. – ifraaank Jun 13 '18 at 09:08
  • The point is to read as often to keep track if the angle is increasing or decreasing, and consequently act on passing through the 0. Of course as @SanderDeDycker also said if the encoder doesn't provide a direction can happen that you read wrong values. But if this is a manual tracker on a device generally is not a big deal. The user will correct automatically. – Frankie_C Jun 13 '18 at 09:12
  • @Frankie_C Yeah, I read the angle as often as possible, and the turns are human and very slow/precise. When printing the angle, it's very easy to see which direction it goes. Programmatically I just find it hard to detect the point where it resets. – ifraaank Jun 13 '18 at 09:28
  • The encoder gives you just a pulse? – Frankie_C Jun 13 '18 at 09:45
  • Nope, it gives me the absolute angle via I2C. – ifraaank Jun 13 '18 at 10:16
  • There is a reason that quadrature encoders were invented. – Martin James Jun 13 '18 at 10:26

3 Answers3

3

Since the magnetic encoder can be turned more than 180 degrees at a time (albeit unlikely), it's technically impossible to know which direction it was turned, because eg. a 90 degrees clockwise turn could also have been a 270 degrees counter-clockwise turn.

If your magnetic encoder doesn't indicate the direction of the rotation, the best you can do is guess (eg. shortest distance - ie. 90 degrees clockwise is more likely than 270 degrees counter-clockwise), but then you might sometimes get it wrong.

If that's acceptable, it's relatively easy to do (assuming integer angles in degrees between 0 and 360) :

int rotation_angle(int old_angle, int new_angle) {
  int result = (360 + new_angle - old_angle) % 360;
  return (result > 180) ? result - 360 : result;
}

Then :

printf("%d\n", rotation_angle(0, 330)); // prints : -30
printf("%d\n", rotation_angle(330, 0)); // prints : 30
Sander De Dycker
  • 16,053
  • 1
  • 35
  • 40
1

If you can guarantee that the polling happen more frequently of the fastest rotation of a half turn (180°), the following considerations should be true:

  1. the absolute difference between current reading and the last one could not exceed half turn=180°
  2. If absolute difference is >= 180° we have crossed the . The count of degree we moved is calculated by adding or subtracting a full turn (360°) depending on current sense of rotation (cw add, ccw subtract).
  3. If absolute difference is < 180° and the difference sign is positive we have moved clockwise (increment angle)
  4. If absolute difference is < 180° and the difference sign is negative we have moved counter clockwise (decrement angle)
  5. If the difference == 0 then no move have happened.

In code:

int LastAngle = GetAngle();    // Init angle reading
bool bClockWise = true;
...
// polling or interrupt handler
int CurrAngle = GetAngle();
int diff = CurrAngle - LastAngle;
if (diff==0)
{
    //No move
    ...
}
else if (abs(diff) < 180)   //Angle changed from last read
{
    //if we are here diff is not 0
    //we update rotation accordingly with the sign
    if (diff > 0)
        bClockWise = true;     //we were rotating clockwise
    else
        bClockWise = false;    //we were rotating counterclockwise
}
//If absolute difference was > 180° we are wrapping around the 0
//in this case we simply ignore the diff sign and leave the rotation
//to the last known verse.
...

If you want count the turns you can code:

int Turns = 0;
if ((diff != 0) && (abs(diff) > 180))
{
    if (bClockWise)
        Turns++;     //Increase turns count
    else
        Turns--;     //Decrease turns count
}

The following macros can be used to check for motion and rotation sense:

#define IsMoving    (diff)        //Give a value !=0 if there is a movement
#define IsCw        (bClockWise)  //Give true if clockwise rotation
#define IsWrap      (abs(diff) >= 180)  //Give true if knob wrapped

P.S. Please note that the diff variable is functional for rotational sense detection and movement, is not the absolute difference in degrees between movements.

If you want compute the real movement you should take into account the wraparound:

int Angle = 0;    //the real angle tracked from code start
if (diff != 0)
{
    if (abs(diff) >= 180)
    {
        if (bClockWise)
            Angle += diff + 360;     //Adjust for positive rollover
        else
            Angle += diff - 360;     //Adjust for negative rollover
    }
    else
        Angle += diff;
}
Frankie_C
  • 4,764
  • 1
  • 13
  • 30
  • This does not work: going from `10` to `350` gives a difference of `340` which is positive eventhough the move was negative. – chqrlie Jun 14 '18 at 14:06
  • @chqrlie It should work. With the sampling fast enough to guarantee that we never read in excess of 180°, we can assume that each difference greater that 180° is considered a wraparound and the sense of rotation (cw or ccw) is not changed. It should be clear from code, I update `bClockWise` only if the **absolute value of the difference** is less than 180°. Else we are wrapping around the 0° from cw or ccw, and in this case is valid the last rotational sense. – Frankie_C Jun 14 '18 at 14:38
  • @chqrlie See updated answer for real angle tracking. This answer supply: -rotating/still flag, -rotational sens flag, -wraparound flag and angle tracking. There should be enough for a simple track-knob. – Frankie_C Jun 14 '18 at 14:53
  • Here is a much simpler test: `diff = (540 + CurrAngle - LastAngle) % 360 - 180; bClockWise = (diff >= 0);` – chqrlie Jun 14 '18 at 15:12
  • @chqrlie of course the algorithm can be simplified. But I used a linear approach that made easy to understand the principles of the logic that should be the scope of SO answers. A too squeezed formula resembles black magic and make difficult to explain how it works. – Frankie_C Jun 14 '18 at 19:29
1

Here is a very simple solution:

int rotation_angle(int new_reading, int old_reading) {
    /* angle readings are in [0..360] range */
    /* compute the difference modulo 360 and shift it in range [-180..179] */
    return (360 + 180 + new_reading - old_reading) % 360 - 180;
}

rotation_angle() return the signed angle difference.

Notes:

  • since new_reading and old_reading are assumed to be in range [0..360], the range for their difference is [-360..360], adding 360 ensures that the modulo operation returns a positive value between 0 and 359.
  • adding 180 before the modulo and subracting 180 from the result shifts the range of output values to [-180..180].
  • 0 means no change in angle
  • a negative value means a turn toward smaller angle values.
  • a positive value means a turn toward large angle values
  • if the angle change can exceed 180 degrees, the interpretation of the return value is most likely incorrect. Angle sampling must be performed sufficiently quickly to prevent such conditions.
chqrlie
  • 131,814
  • 10
  • 121
  • 189