ifraaank
ifraaank

Reputation: 122

Detect if magnetic encoder passes 360 or 0 and in which direction (roll-over/wraparound direction?) - C

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?

Upvotes: 2

Views: 1639

Answers (3)

chqrlie
chqrlie

Reputation: 145277

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.

Upvotes: 2

Frankie_C
Frankie_C

Reputation: 4877

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;
}

Upvotes: 1

Sander De Dycker
Sander De Dycker

Reputation: 16243

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

Upvotes: 3

Related Questions