Conver absolute encoder raw values to modulo 360

martinplc

Member
Join Date
Oct 2023
Location
skopje
Posts
5
Hello. An absolute encoder on a rotary axis gives values in range -2147483648 to 2147483648. When value is > 2147483648 , the encoder value is overflows to -2147483648. When value is <-2147483648, the encoder value overflows to 2147483648 I need to create a function in C that converts the encoder value into a modulo 360 rotary axis value. The initial encoder value is 0 and it changes in positive when encoder rotates in positive direction and it changes in negative when encoder rotates in negative direction. One rotation is equal to 3600 encoder value units.

For positive input encoderValue it is ok. But for negative input of
encoder value i do not have correct results

Can someone help?

Here is my code:

#include <stdio.h>
#include <inttypes.h>

int mapEncoderToRotaryAxis(int64_t encoderValue) {


// Define the number of encoder value units in one rotation
int encoderUnitsPerRotation = 3600;

// Calculate the mapped output value in modulo 360 degrees
int64_t deltaEncoder = encoderValue;

while (deltaEncoder < 0) {
deltaEncoder += encoderUnitsPerRotation;
}

int mappedValue = (int)(deltaEncoder % encoderUnitsPerRotation);

return mappedValue;
}

int main() {
int64_t encoderValue;
printf("Enter an encoder value between -2147483648 and 2147483648: ");
scanf("%" PRId64, &encoderValue);

int rotaryAxisValue = mapEncoderToRotaryAxis(encoderValue);
printf("Mapped value in modulo 360 rotary axis: %d degrees\n", rotaryAxisValue);

return 0;
}
 
Is the problem that when it rolls over or rolls under, that the number is wrong by some offset i.e. by ±848 (= 231 modulo 3600)?
 
Last edited:
when i input -2147483627 i should get 869. But mu function returns 2773.
It depends how the encoder got to -2147483627.

  • If it got there by rising from 0 and rolling over the max limit (+2G), then that 869 is the correct answer.
  • If it got there by dropping from 0 and has not yet rolled under the min limit (-2G), then C's 2773 is the right answer.
TL;DR

How to handle moduli of negative numbers has at least two interpretations.

The closest multiple of 3600 immediately below -2147483627 is -2147486400, and -2147483627 - -2147486400 - 2773. This is where C gets its number.

With a rotary encoder, I would expect the truncated values rising through a multiple of 3600 to be [3599, 0, 1] and dropping to be [1, 0, 3599].

If [-1 MODULO 3600] is -1, that will not handle the value passing through zero correctly. So the way C does it is correct.

To handle rollover, consider that when the encoder is at 2147483547 (= 231-1 = 0x7FFFFFFF), the modulus is 847. If the encoder increases by 1, it would be 2147483648 (= 231-1 = 0x80000000) and the modulus would be 848, but that becomes -2147483648 because bit 31 is the sign bit, and C evaluates [-2147483648 % 3600] as 2752 (note that 2752 + 848 = 3600). What happened? Rolling over the upper limit resulted in the modulus calculation having an offset of 1904 (=2752 - 848).

How to handle that issue is going to be where you spend all the coding. One solution is to maintain an offset that starts at 0 and is adjusted when rollovers and "rollunders" are detected.

  • Rollover: add 1696 (= 3600 - 1904) to the offset and them %= 3600 the offset to remove any excess
  • Rollunder: add 1904 to the offset and then %= 3600 the offset
  • For each raw value
    • X = raw % 3600;//// Evaluate the modulus
    • X = X + offset;/////// Add the current offset to X
    • X = X % 3600;///////// Eliminate remaining excess
Apply this to the example of -2154483627 after a single rollover:

  • // offset is 1696, from a single rollover
  • 2773 <= -2147483627 % 3600;
  • 4469 <= 2773 + 1696;
  • 869 <= 4469 % 3600;
 
Is the problem that when it rolls over or rolls under, that the number is wrong by some offset i.e. by ±848 (= 231 modulo 3600)?
So the answer to this query was, and is, yes.


And the key points are that

  • 4294967296 = 232
  • 4294967296 MODULO 3600 = 1696.
 
Thank you so much for your detailed response. I will try your suggestions first thing in the morning and i will notify you about the outcome.
 
Comments are left as an exercise.
% cat modulo.c
#include <stdint.h>

const int32_t divisor = 3600;
const int32_t masksignum = 0x80000000;
const int32_t masklow31 = ~masksignum;
const int32_t rollunder = ((masksignum % divisor) << 1) % divisor;
const int32_t rollover = divisor - rollunder;

int32_t
m3600(int32_t raw
,int32_t *poffs
,int32_t *plastraw
)
{
int32_t rawneg = raw & masksignum;
int32_t lastrawneg = *plastraw & masksignum;
int32_t result = raw % divisor;
if (rawneg != lastrawneg)
{
int32_t r31 = raw & masklow31;
int32_t l31 = *plastraw & masklow31;
if (rawneg && !lastrawneg)
{
if (r31 < l31) { *poffs = (*poffs + rollover) % divisor; }
}
else if (!rawneg && lastrawneg)
{
if (r31 > l31) { *poffs = (*poffs + rollunder) % divisor; }
}
}
*plastraw = raw;
return ((raw % divisor) + *poffs) % divisor;
}

#ifdef THE_MAIN
#include <stdlib.h>
#include <stdio.h>
int
THE_MAIN(int argc, char** argv)
{
int offs = 0;
int raw = 0;
int lastraw = 0;
int count = argc < 2 ? 0 : atoi(argv[1]);
if (count < 1) { count = 32; }
while (count--)
{
raw += 900000001;
printf("%d,%d,%d,%d,%d"
"=m3600(raw),raw,lastraw,offs,count"
"\n"
,m3600(raw,&offs,&lastraw),raw,lastraw,offs,count
);
}
return 0;
}
#endif//THE_MAIN


%
%
% gcc -DTHE_MAIN=main modulo.c -o modulo
%
%
% ./modulo
1,900000001,0,0,31=m3600(raw),raw,lastraw,offs,count
2,1800000002,900000001,0,30=m3600(raw),raw,lastraw,offs,count
3,-1594967293,1800000002,0,29=m3600(raw),raw,lastraw,offs,count
4,-694967292,-1594967293,1696,28=m3600(raw),raw,lastraw,offs,count
5,205032709,-694967292,1696,27=m3600(raw),raw,lastraw,offs,count
6,1105032710,205032709,1696,26=m3600(raw),raw,lastraw,offs,count
7,2005032711,1105032710,1696,25=m3600(raw),raw,lastraw,offs,count
8,-1389934584,2005032711,1696,24=m3600(raw),raw,lastraw,offs,count
9,-489934583,-1389934584,3392,23=m3600(raw),raw,lastraw,offs,count
10,410065418,-489934583,3392,22=m3600(raw),raw,lastraw,offs,count
11,1310065419,410065418,3392,21=m3600(raw),raw,lastraw,offs,count
12,-2084901876,1310065419,3392,20=m3600(raw),raw,lastraw,offs,count
13,-1184901875,-2084901876,1488,19=m3600(raw),raw,lastraw,offs,count
14,-284901874,-1184901875,1488,18=m3600(raw),raw,lastraw,offs,count
15,615098127,-284901874,1488,17=m3600(raw),raw,lastraw,offs,count
16,1515098128,615098127,1488,16=m3600(raw),raw,lastraw,offs,count
17,-1879869167,1515098128,1488,15=m3600(raw),raw,lastraw,offs,count
18,-979869166,-1879869167,3184,14=m3600(raw),raw,lastraw,offs,count
19,-79869165,-979869166,3184,13=m3600(raw),raw,lastraw,offs,count
20,820130836,-79869165,3184,12=m3600(raw),raw,lastraw,offs,count
21,1720130837,820130836,3184,11=m3600(raw),raw,lastraw,offs,count
22,-1674836458,1720130837,3184,10=m3600(raw),raw,lastraw,offs,count
23,-774836457,-1674836458,1280,9=m3600(raw),raw,lastraw,offs,count
24,125163544,-774836457,1280,8=m3600(raw),raw,lastraw,offs,count
25,1025163545,125163544,1280,7=m3600(raw),raw,lastraw,offs,count
26,1925163546,1025163545,1280,6=m3600(raw),raw,lastraw,offs,count
27,-1469803749,1925163546,1280,5=m3600(raw),raw,lastraw,offs,count
28,-569803748,-1469803749,2976,4=m3600(raw),raw,lastraw,offs,count
29,330196253,-569803748,2976,3=m3600(raw),raw,lastraw,offs,count
30,1230196254,330196253,2976,2=m3600(raw),raw,lastraw,offs,count
31,2130196255,1230196254,2976,1=m3600(raw),raw,lastraw,offs,count
32,-1264771040,2130196255,2976,0=m3600(raw),raw,lastraw,offs,count
%

 
You could try converting that signed value to an unsigned one by bitwise operation like this:

uint64_t usignedencodervalue = 0 | encodervalue;

then

unsigned int rotationposition = usignedencodervalue % encoderUnitsPerRotation;
 
You could try converting that signed value to an unsigned one by bitwise operation like this:

uint64_t usignedencodervalue = 0 | encodervalue;

then

unsigned int rotationposition = usignedencodervalue % encoderUnitsPerRotation;


There is still the same problem with rollover and rollunder, and I am pretty sure my code works as-is if all the int64_t declarations are changed to uint64_t, and you swap the bangs (!s; NOTs) in the (rawneg && !lastrawneg) and (!rawneg && lastrawneg) if expressions, because the rollover is at 232<=>0 instead of at 231<=>-231
 
This depends on how the encoder is designed, if it is designed so that in each turn at 0 degrees rotation the value is an exact multiple of 3600, then I think converting to unsigned should work
 
This depends on how the encoder is designed, if it is designed so that in each turn at 0 degrees rotation the value is an exact multiple of 3600, then I think converting to unsigned should work


The magic question is, what does it do just before and as it reaches 1,193,046 complete forward rotations, when the count should be 4,294,965,599: does it roll over to 0 instead of 4,294,965,600, or does it continue to 4,294,967,295 (= 231 - 1), at which point the [0:3600) rotation value will be 1695, and then roll over to 0? Because if the latter, that [0 MODULO 3600] will be off by 169.6°.
 
Time for more pop corn

It looks like the OP is trying to use an absolute encoder like an incremental encoder. Also, where did he get a 32 bit absolute encoder? Is it SSI or does it really have 32 output bits.
Normally absolute encoders have 8-16 bits per turn and the rest of the bits keep track of the number of turns. That doesn't appear to be the case here. The OP is keeping this info a secret.



Hint, you are doing it wrong.
 
Hi guys, I have formulated my quesion the way u did, just to simplify the problem. I am using Delta Electronics AS228P-A plc to drive ASD B3A-0721-M servo driver. The motor is ECM-B3M-CA0604RS1 with an absolute encoder.
I acces the actual motor feedback position via Canopen communication. The servo drive gives me the actual feedback motor position in PUU (position user units). So the servo has its own PUU register counter, where it counts encoder pulses and scales/adapts them to PUU. I need a rotary modulo representation of that puu. I have configured theservo driver so that one physical motor rotation generates 3600 PUU. Here is the explanation about how the servo drive gives me the actual motor position in attachment.

puu.PNG
 
Since 3600 is not a power of 2, in case of overflow or underflow a phase shift occurs.

What would happen if instead of 3600 pulses per rotation you configure 4096 pulses per rotation ?
 
What would happen if instead of 3600 pulses per rotation you configure 4096 pulses per rotation ?
Now that we know that the pulses per rotation parameter is user-configurable, this is the correct solution. Then the entire operation becomes a one-line bit-wise AND with a scale:
angle, ° = (rawint & 0x0fff) * (360.0 / 0x1000);
"But," some will say, "then the conversion from pulses to ° wouldn't be a power of 10!" Awww, too bad. Try breaking free of that Système-International-addled mindset and thinking like a proper engineer instead.

P.S. On review, I am not sure my other C code will work with uint32_t.

Neither will this one, but this is simpler:

% cat modulo.c
#include <stdint.h>

typedef struct MOD3600str
{
int32_t mod3600;
int32_t last;
} MOD3600, *pMOD3600;

float m3600(int32_t newraw, pMOD3600 this)
{
int32_t diff = newraw - this->last;
this->last = newraw;
this->mod3600 += diff;
this->mod3600 %= (int32_t)3600;
if (this->mod3600 < 0) { this->mod3600 += (int32_t)3600; }
return ((float)this->mod3600) / 10.0;
}

#ifdef THE_MAIN
#include <stdlib.h>
#include <stdio.h>

int THE_MAIN(int argc, char** argv)
{

int32_t rawincr = 0;
int32_t rawdecr = 0;
MOD3600 m3600incr = { rawincr % 3600, rawincr };
MOD3600 m3600decr = { rawdecr % 3600, rawdecr };
int count = argc < 2 ? 0 : atoi(argv[1]);
if (count < 1) { count = 32; }
while (count--)
{
rawincr += (int32_t)90000001;
rawdecr -= (int32_t)90000001;
printf("%7.2f,%7.2f"
"\n"
,m3600(rawincr,&m3600incr) ,m3600(rawdecr,&m3600decr)
);
}
return 0;
}
#endif//THE_MAIN


%
% gcc -DTHE_MAIN=main modulo.c -o modulo
%
%
% ./modulo
0.10, 359.90
0.20, 359.80
0.30, 359.70
0.40, 359.60
0.50, 359.50
0.60, 359.40
0.70, 359.30
0.80, 359.20
0.90, 359.10
1.00, 359.00
1.10, 358.90
1.20, 358.80
1.30, 358.70
1.40, 358.60
1.50, 358.50
1.60, 358.40
1.70, 358.30
1.80, 358.20
1.90, 358.10
2.00, 358.00
2.10, 357.90
2.20, 357.80
2.30, 357.70
2.40, 357.60
2.50, 357.50
2.60, 357.40
2.70, 357.30
2.80, 357.20
2.90, 357.10
3.00, 357.00
3.10, 356.90
3.20, 356.80
%


 

Similar Topics

hi dear colleagues and friends We use ABB procontrol p14 DCS system for our power plant we want to revamp monitoring section to new one The...
Replies
3
Views
2,294
conver file.mer from ftview 7.0 to file.med ftview 5.1. who knowl help me ????????????????????????????
Replies
2
Views
2,936
hi, people who know help me with, I don't know conver RMS from SLC5 to rslogix5000 .[B][/R] help me.......:bawling::bawling::bawling: thank you...
Replies
5
Views
2,217
What is the Conversion factor for this? 1. In refrence to a Perfect Vacuum 2. Relative to atmospheric Pressure. Thanks
Replies
12
Views
11,044
I have 2 Absolute Encoders 8192 steps per Rev. (Model TR Electronic CEV65m-11003) These Encoders communicate to the PLC5-60 via Parallel push/pull...
Replies
3
Views
1,519
Back
Top Bottom