More angle wrapping related misery in code - Page 1
Forum section: electronics / general

author: Infraviolet
author_rank: Super Contributor
timestamp: May 30, 2024, 01:15:43 am
content: I've got some more angle related troubles with wrapping and subtractions, the usual matters of modulo vs remainder vs ...I'm trying to make a move of a provided angle, "DeltaConverted", from starting at a present angular value, "angle", and get outputs which tell me not only what the (normalised wrapped) angle will be "MoveToAngle", but also how many turns are done "MoveToCount" (relative to a zero point).I've been trying in C/C++:DeltaConverted is an int16_tangle is a floatMoveToDir a uint8_t, though this isn't yet used in the code shownMoveToAngle is a floatMoveToCount is an int16_tCode:float MakeMove=1.0*DeltaConverted+angle;if(DeltaConverted==0){MoveToDir=0;}else if(DeltaConverted<0){MoveToDir=-1;}else{MoveToDir=1;}//MakeMove is the angle, non normalised, we will end up at, this far it logically works (inevitably)MoveToAngle=Clamp360(MakeMove);//this bit works tooMoveToCount=(int16_t)(fabs(MakeMove))/360;//here's where the trouble beginsif(MakeMove<0){MoveToCount=0-MoveToCount;MoveToCount=MoveToCount-1;}Note that I define:Code:int16_t Clamp360(int16_t value){value= value % 360;if(value >= 0){return value;}else{return 360 - (0 - value);}}which I know does work well.But once this code has run whilst the MoveToAngle value correctly gves where in the normalised circle we'll be when the move is done, MoveToCount does not always correctly tell us how many whole turns we've done (as versus the zero point).Namely, problems happen when the position specified by MakeMove is negative.I thought theif(MakeMove<0)part would solve thisbut I get weird effects then when getting to -2 turns.For example, for positive MakeMove values it works.MakeMove=359, gives 359 as the angle and 0 countsMakeMove=360 gives 0 as angle and 1 countsMakeMove=361 gives 1 for angle and 1 countsit keeps working for higher MakeMove values just fine.It works too whenMakeMove=1, gives angle=1, count=0MakeMove=0, gives angle=0, count=0MakeMove=-1, gives angle=359,count=-1but then for MakeMove=-360 and down things get oddMakeMove=-359 gives angle=1, -1 counts, fine so farMakeMove=-360 gives angle=0, -2 counts, trouble!MakeMove=-361 gives angle=359, -2 counts, back to behaving itselftrouble comes again at -720MakeMove=-719 gives angle=1, counts=-2, fineMakeMove=-720 gives angle=0, counts=-3, trouble!MakeMove=-721 gives angle=359, counts -3, fine againI've tried adding extra conditions around the zero and negative MakeMove cases but I can't seem to fix this, not without causing other troubles instead, like not getting MoveToCount values less than 0 ever appearing.Again, like by other angle wrapping issues thread a while back, it should be simple, but I seem to have some serious problem when angle wrapping comes up, somehow its so much harder to hold in my head than anything else so really tough to try to think through the lines the way one can with code that obeys normal non-wrapping mathematical laws.How do I fix this?I tried searching online for this, but couldn't find the right key words so was only bringing up things about how to do the Clamp360 stuff and the normalisation itself, I couldn't think of the right keywords to bring results about counting full revolutions properly.Thanks

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 30, 2024, 11:29:32 am
content: To count the number of timesanglehas wrapped, copying the sign of the direction of the wrapping, the same asCode:float angle;int16_t wraps = 0;while (angle >= 360.0f) {wraps++;angle -= 360.0f;}while (angle < 0.0f) {wraps--;angle += 360.0f;}useCode:wraps = (int16_t)floorf(angle / 360.0f);angle -= (float)wraps * 360.0f;This is becausefloorf(1.0f) == 1.0f,floorf(0.5f) == 0.0f,floorf(0.0f) == 0.0f,floorf(-0.5f) == -1.0f, andfloorf(-1.0f) == -1.0f.floor()/floorf()rounds towards negative infinity: it returns the largest integer not greater than the argument.

author: Infraviolet
author_rank: Super Contributor
timestamp: May 30, 2024, 01:51:37 pm
content: I'm getting the horrid feeling I'm having enough angle wrapping problems I'm going to need to refactor the code.Are there any web resources with examples for all possible use cases of angle operations in C (or equivalent) shown?Both dealing with wrapping, and comparing angles in the wrapped frame against angles in an unwrapped frame.I'm getting situations where I need to compare angles derived from trig in "this frame" (between 0 and 360) against angles referenced to a true zero, where I can't normalise because I need to keep the information associated with the absolute number of revolutions.I've got situations where I need to take a motor shaft with an angle recorded in an absolute frame, then position it to match with already normalised angles within whichever number of rotations it has already done. All while keeping track of the absolute frame position to return to later.I'm trying to weigh up handling the number of revolutions in a separate variable from the actual angle, but this seems to make it only more confusing to assess whether a pair of angles are say 70 degrees apart vs 430 degrees apart, vs -1370 degrees apart...If there's a good and complete guide of examples for writing heavily angle focused code, it would be really helpful to see.Thanks

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 30, 2024, 04:06:27 pm
content: If you usedbinary angular measurement, i.e. 2B= 360°, everything would be much easier.In the general case, it all boils down to understanding and leveraging the four operations:roundf()/round(): Round to nearest integer, halfway cases away from zero.truncf()/trunc(): Truncate, AKA round to nearest integer not greater than the argument in magnitude, AKA round towards zero. This is what casting to an integer type does in C.floorf()/floor(): Round to nearest integer not greater than the argument, AKA round towards negative infinity, AKA floor.ceilf()/ceil(): Round to nearest integer not smaller than the argument, AKA round towards positive infinity, AKA ceiling.Here is an illustrative table:Code:x │ -2.00 -1.75 -1.50 -1.25 -1.00 -0.75 -0.50 -0.25 0.00 0.25 0.50 0.75 1.00 1.25 1.50 1.75 2.00───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────trunc │ -2 -1 -1 -1 -1 -0 -0 -0 0 0 0 0 1 1 1 1 2round │ -2 -2 -2 -1 -1 -1 -1 -0 0 0 1 1 1 1 2 2 2floor │ -2 -2 -2 -2 -1 -1 -1 -1 0 0 0 0 1 1 1 1 2ceil │ -2 -1 -1 -1 -1 -0 -0 -0 0 1 1 1 1 2 2 2 2Note how onlyfloor(x/N)andceil(x/N)have exactlyNzeros ending/starting at zero?And how truncation gives you a double-wide zero zone? And rounding the narrowest zero zone?

author: Infraviolet
author_rank: Super Contributor
timestamp: May 30, 2024, 10:35:56 pm
content: Are there guides anywhere about properly getting the natural feel for which of the different rounding operations I need in each given scenario when doing wrapped angle calculations?Also, using the overflow of an integer variable to enforce angle wrapping with binary angular measurement still doesn't solve problems where I need to compare normalised angles against things like running totals of the full amount of angle accumulated over multiple motions over time?Given this surely crops up a lot in contexts like when people build stepper/servo systems which can rotate an infinite number of times and must not misbehave on any angle wrap, or even total count wrap, events... surely there should be some good examples online for getting the feel in full for wrapped and unwrapped angle handling code? Does anyone know of any?It's easy to know how to do the angle calculations manually in any given single situation, but devising code to handle all the possible circumstances involved in angle wrapping is such a headache. Its like why they invented atan2 as a way to avoid having to write out all the different conditions involved in atan when doing all four quadrants and with zeroes possible for x or y, but more headache-causing... why isn't there a generalised solution to many-turn angle wrapping in common use?Thanks

author: Siwastaja
author_rank: Super Contributor
timestamp: May 31, 2024, 05:47:11 am
content: Just rewrite to use binary angular measurements, like you were advised to in the previous thread:https://www.eevblog.com/forum/programming/angle-wrapping-in-code-easy-problem-need-to-double-check/You can dedicate some number of bits for "full turns". For example, if you use int64_t to store the angle, you can use 32 highest bits to denote turn number (2 billion in each direction), and lowest 32 bits to denote the angle.For easy access, you can do stuff liketypedef union{struct { uint32_t angle; int32_t full_turn_number; };int64_t both;} angle_type;

author: Infraviolet
author_rank: Super Contributor
timestamp: June 11, 2024, 09:59:18 pm
content: Ok, I've been trying to use binary angle measurements, but I've found two reasons that it doesn't work well.1. I need to add and subtract in very minor increments sometimes, these don't get included in a non-float variable. I don't need to output the angle to a better precision than 1/256th of a revolution ever, but I need to store it at much higher precision so when a fraction of a fraction of a "degree" gets added time after time, eventually it does change the integer value. I tried place shifting to use 2 bytes for the angle itself, but it became problematic.2. My system has certain properties where division of the angle by other numbers, particularly the number 7 as this is a 7 pole pair motor I'm tracking and controlling the position of, needs to be preserved appropriately across wraps. Otherwise when the angle wrapped over I could have tracked the electrical rotations properly, but not the mechanical ones. Divison by prime numbers like 7 isn't going to go well with binary integer variables.My thoughts then are that I should be designing some special class of variable where angular wrapping is automatically handled, something where rather than using normal maths functions on the variablels, everything is handled via specialised functions which respect and preserve angle wrapping. But I'm unsure how to go about this, can anyone recommend a method? I'm just so surprised there aren;t existing pieces of example source code for a problem like this which must be very common.EDIT: I've realised that floats are a bad idea again, as the precisions can change when the size of the number changes. So I'm going to try going back to binary, but I know the division by 7 and then need to have the largest scale wrapping occur in a place which preserved divisibility by 7 will be a problem. Is there a way to make a customised class variable type for this scenario? So it will wrap exactly where I want it to, and respect this wrap automatically when doing addition and subtraction?

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 12, 2024, 12:45:06 am
content: You can do this by using a mixed fixed point integer format to describe turns.The upper part describes the number of turns in normal fashion, but the fractional part is a multiple of a suitableradix.We know yourradixshould be a multiple of 7, because you want to describe values 1/7, 2/7, etc. exactly.How many full rotations of range you need, and how large integer types are you willing to use?What numbers (besides 7) does your fractional radix need to be divisible by?(We still use radix 60 for minutes and seconds, because 60 = 2·2·3·5 = 3·4·5; it is divisible by 2, 3, 4, 5, 6, 10, 12, 15, 20, and 30.)Nicest fractional radixes for you would be 63 (=7·9), 511 (=7·73), 4095 (=7·585), and 32767 (=7·4681), because these are just one less than a power of two.In general, if we have two numbersa=ai+ar/R andb=bi+br/R, R being the fractional radix, then their sum isa+b=ai+bi+ (ar+br)/Rand difference isa-b=ai-bi+ (ar-br)/Rand product isa·b=ai·bi+ (ai·br+bi·ar)/R + (ar·br)/R²and the final term, with divisor R², only affects rounding: ifar·br≥ R²/2, then the result is rounded up.Multiplying by an integer is simple,a·k=ai·k+ar·k/Ras is converting a ratio of integersa=k/massuming 0 ≤k<m:ai= quotient(k/m),ar= R·remainder(k/m)/mDividing by an integer is relatively simple,b=a/k=ai/k+ar/(k·R)which ends up being calculated asq= quotient(ai/k),r= remainder(ai/k)bi=q,br= round((ar+r·R)/k)with signs handled separately.Full division is annoying,c=a/b= (R·ai+ar) / (R·bi+br)which ends up being calculated asq= quotient((R·ai+ar) / (R·bi+br)),r= remainder((R·ai+ar) / (R·bi+br))ci=q,cr= R·r/ (R·bi+br)with signs handled separately.For the divide-by R terms, you do need to carry overflow to the integer part.For example, if you were to use 16-bit integers and a fractional radix of 4095, each such variable could represent 0 .. 16-1/4095 turns or -8 .. +8-1/4095 turns, inclusive, at a resolution of 1/4095 turn = 0.000244200244200... turn. For addition and subtraction of such rotation values you could useCode:// t16_add(t1, t2) = t1 + t2uint16_t t16_add(const uint16_t t1, const uint16_t t2) {const uint16_t t = t1 + t2;return t + ((t & 4095) == 4095);}// t16_sub(t1, t2) = t1 - t2uint16_t t16_sub(const uint16_t t1, const uint16_t t2) {const uint16_t t = t1 - t2;return t + ((t & 4095) == 4095);}// Normalize fractional partuint16_t t16_norm(const uint16_t t) {return t + ((t & 4095) == 4095);}which also work as-is for signed 16-bit integers,int16_t, if the compiler uses the normal unsigned-to-signed wrapping rules (same as signed-to-unsigned, i.e. adding or substracting 65536 from the value to be converted if necessary).In general, the normalization for M = 2ⁿ-1, R = 2ⁿ-kisreturn t + ((t & M) >= R) ?k: 0;but whenk==1, M = R, and the above simplifies toreturn t + ((t & R) == R);because((t & R) == R ? 1 : 0)is equivalent to((t & R) == R).Also, when R = 2n-1,(K*R)simplifies to((K<<n) -K), i.e. left shift by the number of bits in R, and one subtraction.

author: Infraviolet
author_rank: Super Contributor
timestamp: June 12, 2024, 02:50:16 am
content: Another point on this, when working with uint8_t variables, how do I now find an absolute difference in a wrap respecting way? If I've got a 0 to "256" (with 256 going back to 0) angular measure, and want to find how far, in either direction, away from it another uint8_t angular measure s, what's the right operation? I think it is something to do with casting, but I need to make it handle the bits in a raw fashion rather than mathematically as one would for normal numbers? And the compiler usually sets things up to work for normal numbers.When I was using floats I had expressions like:if( Clamp180(Expected - Actual) >= 90 ){what does the equivalent become for uint8_t?

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 12, 2024, 03:05:25 am
content: Quote from: Infraviolet on June 12, 2024, 02:50:16 amFromuint8_t atouint8_t b, there are two ways. First is(int8_t)(b - a)which assumes unsigned to signed conversion foruint8_ttypes uses modulo 256 arithmetic like all current hardware architectures have; the second is((a < b) ? (int8_t)(b - a) : -(int8_t)(a - b))which yields the exact same values without relying on the modulo arithmetic.Now, care to answer the two questions in boldface in#7?

author: Infraviolet
author_rank: Super Contributor
timestamp: June 12, 2024, 03:22:19 am
content: Here's my best attempt at the boldface questions:I've been thinking a bit more, I'm going to list out what I think I need from the code I have to write, maybe this would enable people to suggest what to do:1. I need some sort of representation of a circle dividable in to 256 or so (or 360) parts which can be used to actually set angles, and fed in to sine like functions. Other than for incrementing and decremtning I never need more accuracy than this.2. I need this representation to be able to hold within it "decimal" parts which can be incremented and decremented by about 1/16th of the divisble units in requirement 1. At present I'm using the bottom 4 bits of an int32_t for this purpose, the next 8 bits as the x/256 angle, and the higher bits as a revs count.3. I need the revs count, when it eventually wraps over, to do so at a multiple of 7 revolutions. And also preserivng wrapping for 252 (7*6*6). There are no other divisions which need preserving across a wraparound, just position within the 0-256 angle, how many 7s of revs and how many 252s of revs.4. When that wrap happens I need both the angle within a revolution, and the total/7 and the toal/252 parts of the revolutions count, to be continuous5. I never need to add or subtract more than 10000 (approx) from the revolutions count at once. I want all subtractions and additions to work during a crossing of the wrap-over boundary. I am not ok with solutions which put the wrap-over boundary up in the millions or billions of revolutions, but don't protect against weirdness when it occurs. I don't mind a relatively low wrapping point but I want everything to behave normally across it.6. I need a function which will tell me how great the lead, or lag, between two angles in the 0-256 range range is, and which behaves normally on wraparound, the angles being no more than 2 revolutions apart7.I need a function which lets me know if a given angle is between two others, between them on the short side, when all are being interpreted within a single revolution8.I need a function which finds and tells me the mid-way point between two angles, going the short way between them, the angles being not more than a single full revolution apart9.If one angle is behind or ahead of another by as much as 10000 revolutions I also need to be able to find how many of the "256ths" "degrees" are between the10.In some situations need to be able to store the revolutions part, and the 7 revolutions part, away safely, normalise the value in to the 0-256 range, then make multiple revolutions within this reference frame, before later restoring the reference frame back to the main one which I started from.The best I've got so far, before trying to understand radixes, is an int32_t with the bottom 4 bits as the bit that lets me accumulate small changes, the next 8 bits being >>4 ed and & ed with 0xff when I want the angle within a revolution, and the rest of the bits being a revolutions count. This, so far, has no means to deal with the preserving of 7 revolutions when wrapping. and I keep confusing myself when trying to implement the angle difference finding and averaging (I don't want to resort to trig there, it is too slow for code running fast on a weak microcontroller) to get the mid-way points.Do these requreiments suggest how I should be setting up the code? It seems every change I make to appease one requirement makes the others more tricky to achieve.I know my descriptions here aren't the best, but there's something about this problem I just don't seem to be getting, and I can't quite follow what.Thanks

author: IanB
author_rank: Super Contributor
timestamp: June 12, 2024, 03:45:48 am
content: OK, you seem to have greatly expanded your needs since your first post, but I have a feeling you are making things too complicated, and that's where you are getting into trouble.In your first post you wrote "C/C++", but this is not a good thing to say, since "C" and "C++" are different languages.Since you mentioned C++, I will choose that one and give you some simple example code below that may help.Firstly, I will use C++ to advantage, and define a new type to represent angular position.Secondly, I will avoid all use of mathematical functions, and will avoid all considerations of integer overflow or wrap-around.The example below is very bare bones, and not at all polished, but it may give you an idea of how to proceed:Code:#include <iostream>using namespace std;struct Angle{int m_revolutions; // number of complete revolutions made from zero positionint m_angle; // current angle of rotation between 0 and 359 degrees in positive direction// default constructor initializes angle to zeroAngle() : m_revolutions(0), m_angle(0) {}void Advance(int degrees){// advance position by number of degrees in positive directionm_angle += degrees;while (m_angle > 359){m_angle -= 360;++m_revolutions;}while (m_angle < 0){m_angle += 360;--m_revolutions;}}void Retard(int degrees){// retard position by number of degrees in negative directionAdvance(-degrees);}static int Difference(Angle angle1, Angle angle2){// find the difference angle1 - angle2 between two positions in degreesreturn (angle1.m_revolutions * 360 + angle1.m_angle) - (angle2.m_revolutions * 360 + angle2.m_angle);}};void PrintAngle(Angle angle){const char* plural = (angle.m_revolutions == 1 || angle.m_revolutions == -1) ? "" : "s";cout << "Current position is " << angle.m_revolutions << " complete revolution" << plural << " from zero plus " << angle.m_angle << " degrees in forward direction" << endl;}int main(){// start at zeroAngle angle;PrintAngle(angle);// advance by 205 degreesangle.Advance(205);PrintAngle(angle);// advance by 278 degreesangle.Advance(278);PrintAngle(angle);// remember this angleAngle angle2 = angle;// go back by 409 degreesangle.Retard(409);PrintAngle(angle);// go back 134 degreesangle.Retard(134);PrintAngle(angle);// find difference between angle and angle2int diff = Angle::Difference(angle2, angle);cout << "Difference between two positions is " << diff << " degrees." << endl;return 0;}Code:Current position is 0 complete revolutions from zero plus 0 degrees in forward directionCurrent position is 0 complete revolutions from zero plus 205 degrees in forward directionCurrent position is 1 complete revolution from zero plus 123 degrees in forward directionCurrent position is 0 complete revolutions from zero plus 74 degrees in forward directionCurrent position is -1 complete revolution from zero plus 300 degrees in forward directionDifference between two positions is 543 degrees.

author: Infraviolet
author_rank: Super Contributor
timestamp: June 13, 2024, 03:10:00 am
content: Can someone check over the following functions for me and advise whether they should be utterly reliable when angle wrapping is considered? I tested them by running some loops with all the possible input values to each, but naturally those loops gave rather more results than I could examine easily for correctness.Code:int8_t AngleDiff(uint8_t In2, uint8_t In1){int8_t Diff= ( ( In1 < In2 ) ? (int8_t)(In2 - In1) : -(int8_t)(In1-In2));return Diff;}AngleDiff(x,y) should give the shortest distance between the two angles x and y, signed to indicate which angle is "greater" ("greater" being somewhat modified to respect wrapping). The input angles are supplied in single byte binary angular measurement form, 256=360 degrees.Code:uint8_t AngleAverage(uint8_t AngleIn1, uint8_t AngleIn2){//works, but results may be jumpy when the two input angles are 180 degrees, or nearly 180, apart (128/256)int8_t Difference = AngleDiff(AngleIn1,AngleIn2);uint8_t Out=0;int8_t HalfDiff=abs(Difference >> 1);if(Difference>0){Out=AngleIn2 + HalfDiff; //to the smaller angle add half the difference}else{Out=AngleIn1 + HalfDiff; //to the smaller angle add half the difference}return (uint8_t) (Out & 0xff);}AngleAverage(x,y) should always give an output angle mid-way between the two supplied angles, again binary angular measurement is used.Code:bool CheckBetween(uint8_t testingVal, uint8_t LowerLimit, uint8_t UpperLimit){//will not work for LowerLimit==UpperLimitif(LowerLimit == UpperLimit){return 0;}if(AngleDiff(UpperLimit,LowerLimit) > 0 ){if(AngleDiff(testingVal,LowerLimit) >=0 && AngleDiff(UpperLimit,testingVal)>=0){return 1;}else{return 0;}}else{if(AngleDiff(testingVal,UpperLimit) >=0 && AngleDiff(LowerLimit,testingVal)>=0){return 1;}else{return 0;}}}CheckBetween is supposed to recognise whether a supplied angle to be tested is within the angle between the two limit angles, on the short side between them. This is not required to work accurately where both limits are close to 180 degrees apart (difference of -128, -127 or 127 or 128), all practical uses of this will have limits somewhere between 10/256 and 110/256 apart.Should the three of these work correctly with wrapping in all circumstances?....My next problem seems to be comparing binary angular measures using int32_t variables. I need to produce something similar to AngleDiff, but able to account for situations where the two supplied angles are multiple revolutions apart. I'm presently using an int32_t where the bottom 4 bits are sub-"degree" angles for incrementing/decrementing, the next 8 bits are the angle out of 256, then above that the bits count revolutions in a signed manner. But this is where wrapping over with multiples of 7 and 252 begins to matter. When I want to feed the angular parts of these in to the functions above I can just do (Int32TAngleVariable >> 4) & 0xff . (Int32TAngleVariable >> 12) doesn't work well to show the revolutions count, due to the signing bit at the start of an int32_t.Somehow I need to be able to accurately compare, for differences of multiple revolutions plus 256th parts of a single revolution, the angle I want to move to and the angle I am at. And have these both change together in the correct way if I cross a wraparound boundary, making sure also to place this boundary at a multiple of 252 revolutions. In initial testing I'm working with just a multiple of 7 revolutions, this makes early testing easier as I can get to the error conditions without having to turn for ages first, which I'll change to a multiple of 252 later on. My thoughts are though that (int32_t)25200 << 12 might make a good positive value to wrap at, and the negative equivalent of this a good bottom end of the range?Thanks

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 14, 2024, 03:11:37 am
content: If you want a cyclic integer type withBITSbits (minimum 2), set constantsHALF= 1 << (BITS - 1);MASK=HALF- 1;noting that(HALF|MASK)has all lowBITSset.Then, the signed distance fromfromtotois((to-from) &MASK) - ((to-from) &HALF)and will always evaluate to a value in -HALF....MASK, inclusive.I suggest writing it using a suitableint_fastN_ttype (N=8, 16, 32, 64, whatever can represent -HALF), and formint_fastN_t d =to-from;return (d &MASK) - (d &HALF);for the signed distance.For example, withBITS=4,HALF=8,MASK=7:Distance from 0 to 7 is +7Distance from 7 to 0 is -7Distance from 0 to 8 is -8, and distance from 8 to 0 is -8, because halfway is always negativeDistance from 15 to 0 is +1Distance from 0 to 15 is -1One can use standard operations like addition, subtraction, multiplication and division with such values usingintN_torint_fastN_ttypes, as long asN>BITS.WhenN==BITS,(intN_t)(to-from)is the signed distance exactly.The unsigned distance can be written asint_fastN_t d =to-from;return (d &HALF) ? half - (d &MASK) : (d &MASK);which will always return a nonnegative distance in 0..HALF, inclusive.The test forfrom<=to(but correctly handling the wrapping) can be written as!((to-from) &HALF)Testing whethercis betweenaandb(inclusive, correctly handling the wrapping), can be written asint_fastN_t ac = (c - a) & (MASK|HALF);int_fastN_t cb = (b - c) & (MASK|HALF);int_fastN_t ab = (b - a) &HALF;return (!ac) || (!cb) || (ab == (ac &HALF) && ab == (cb &HALF));which is equivalent to testing that the order of c,a and b,c are the same as b,a. The(!ac)tests for equality on the lower bound, and(!cb)for equality on the upper bound.Midway betweenaandbis easiest to calculate as(a+ signed_distance(a,b) / 2) & (MASK|HALF);In general, interpolating betweenaandbvian/N,N> 0, can be done via(a+ (signed_distance(a,b) *n) /N)) & (MASK|HALF);if your intermediate type has sufficient range (enough bits for the temporary product). It yieldsawhenn=0, andbwhenn=N.If you have two unsigned integer variablesfromandtoin 0..FULL-1, whereFULLis the size of the full range (and not a power of two because for those you want to use the above instead), 2·HALF=FULL, then the signed distance fromfromtotocan be calculated usingint_fastN_t d =to-from;return (d >HALF) ? d -FULL:(d < -HALF) ? d +FULL: d;The distance fromFULL-1 to 0 is +1, and from 0 toFULL-1 is -1; this too wraps correctly. Assuming inputs are within valid range, the returned value is in -HALF...+HALF, inclusive.Unsigned distance is just the absolute value of the above, for exampleint_fastN_t d =to-from;return (d >HALF) ?FULL- d :(d < -HALF) ?FULL+ d :(d < 0) ? -d : d;Forfrom<=to, you can useint_fastN_t d =to-from;return (d < -HALF) || (d >= 0 && d <=HALF));Midway betweenaandbcan be calculated asint_fastN_t d =a+ signed_distance(a,b)/2;return (d < 0) ? d +FULL: (d >=FULL) ? d -FULL: d;Linear interpolation fromn=0 ataton=Natbis similarlyint_fastN_t d =a+ (signed_distance(a,b) *n) /N;return (d < 0) ? d +FULL: (d >=FULL) ? d -FULL: d;This expression yields 0 iffrom==to, 1 iffrom<to, and 2 iffrom>to. I call itcompare(from,to):int_fastN_t d =to-from;return (d != 0) + (d >=HALF) + (d > -HALF&& d < 0);With that, it is easy to check ifcis betweenaandb, assuming they are all in 0..FULL-1:return ((compare(a,c) | compare(c,b) | compare(a,b)) != 3);Essentially, it verifies that c-a and b-c are in the same direction as b-a.It is possible to combine these two, using low bits for the binary (fractional or angle) part, and upper bits for an arbitrary cyclic range, by simply shiftingFULLandHALFleft by fractional bits for the combined arbitrary range. (That is, your arbitrary range will haveFULLandHALFwith at leastBITSleast significant bits zero.)To simplify things, one could use full 16 bits for the fractional/angle part (for use withuint16_toruint_fast16_t), and the leftover 14 bits or so for the integral angle. We need a couple of extra bits, to make sure the integral part can represent values in -FULL..2*FULL-1, inclusive. The angular precision is then 360°/65536 = 45°/8192 = 0.0054931640625°, or 10125/512 = 19.775390625 arcseconds. For sines and cosines, you need a quarter wave, which here would be split into 16384 steps.A suitable value for the integral number of turns would be 16380 = 65·252 = 2340·7. On architectures with an unsigned 32-bit by 32-bit multiplication returning the upper 32 bits, compilers can optimize "(uint32_t)(x) % 16380" and "(uint16_t)(x) % 16380" to one multiplication, some bit shifts and a couple of additions, because it is sufficiently close to a power of two (2¹⁴ = 16384). On such architectures (Cortex-M3/M4/M7, RISC-V rv32, x86, x86-64, ARM64/AARCH64), usinguint32_tas the combined type, enforcing wrapping after operations that modify the full turns part viacombined%= 1073479680;(1073479680 = 16380·65536) generates just one multiplication, some bit shifts, and a couple of additions and subtractions.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 14, 2024, 08:39:35 am
content: Quote from: Infraviolet on June 11, 2024, 09:59:18 pmWhich is exactly why youshoulduse binary angles. 64-bit (32+32) binary angle willalwayshave the range of -2 to +2 billion full turns and resolution of 0.000000083819 degrees, which I'm 99% sure is good enough for your use case. Adoubleconsumes the same amount of memory (that is, 8 bytes), and has more (excessive?) initial resolution but then after some number of full turns the resolution diminishes in an unexpected and weird way.QuoteSounds weird. I have controlled numerous motors of 2 or 3 or 7 or whatever pole pairs, and never found any reason to divide anything in run-time by the pole pair number. Maybe something to do with commutation of the motor and calculating the segment, but then again an electrical revolution could be for example 6 sectors, and as such the 7-pole pair motor would have 6*7=42, not 7 sectors.In any case, I don't understand why basic operations like division, subtraction, addition would be any different. Well of course except they don't need extra wrapping logic, which is the whole point.To ignore full turns and just divide the offset angle within the turn, just mask out the full turn bits, for example by using the helper union I showed in an earlier reply. Then just divide. The operator for integer division is /, in both C and C++.QuoteWhy do you think so? Dividing integers by integers is one of the most fundamental operations a computer does. Sure, it internally does not round, but that is made insignificant by using enough resolution to begin with, so that "off-by-half" errors do not matter.You don'tneedany classes or abstraction in any of this. Everything should fit within a few lines of code. If not, you are doing something seriously wrong.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 14, 2024, 08:23:11 pm
content: All this is relatively "basic" fixed-point, and this is IMO an essential skill to have for embedded programming.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 14, 2024, 10:51:48 pm
content: Quote from: Siwastaja on June 14, 2024, 08:39:35 amI agree, especially when using supported exact-size types (uintN_t,intN_t).Quote from: SiliconWizard on June 14, 2024, 08:23:11 pmI agree.Let me summarize.Assuming a sane C/C++ compiler that performs arithmetic bit shifts on negative values, and applies the same modulo logic to signed exact-size integer types as to unsigned ones, the one "trick" you do want to use is to use anunsignedexact-size integer to hold your cyclic values.That way, the signed distance fromuintN_tatouintN_tbis exactly(intN_t)(b-a).That cast back touintN_t, or directly calculating(uintN_t)(b-a), yields the distance in the positive direction. Its most significant bit will be set, if the positive distance is longer than the negative distance.This also means thatuintN_tcis betweenaandbif and only if the most significant bits of(uintN_t)(b-a),(uintN_t)(c-a), and(uintN_t)(b-c)are all the same, either all set or all clear. Logically it means the signed distances betweena,b,a,c, andb,chave the same sign, ie. shortest when measured in the same direction.When you use smaller than exact-size integer types, the first part ofreply #13shows how to do the sign extension correctly. While it looks like extra work, it really just boils down to the fact that in two's complement notation –– whichintN_tmust use per the C standard ––, the highest bit has value -2N-1, compared to +2N-1foruintN_t. All other bits have positive logical values, +2kat bit positionk, whenk=0 for the least significant integral bit, up tok=N-2 being the second-most significant bit.(For example, inint8_t, 33 = 32+1 = 0b00100001, and -95 = -128+32+1 = 0b10100001. To negate a value, you toggle/invert all bits ("complement") and add +1+2; this is where the"two's complement"name comes from.(Edit: It doesn't; see SiliconWizards response below.) I've found the equivalent"highest bit value is negative"more useful in practice, though.Non-power-of-two periodicity is less often needed, but boils down the same logic. There are many ways to implement it, but the second part ofreply #13shows one way. This differs from the exact-size integer stuff above in that for these, you can use either a signed or an unsigned base integer type. Unsigned%is faster (with a fixed modulus (or "range" as I call it) more easily optimizes to a multiplication and additions/subtractions), but detecting underflow (at e.g. subtraction) is easier with signed types. In practice, you should use a fixed-size integer type, signed or unsigned, and explicitly cast the sum/product to a suitable fixed-size signed or unsigned integer type as needed.In addition to angles, these are also needed to access cyclic buffers, and when accessing e.g. hash tables. When the index can only overflow, it is very easy to not realize that theif (index>=size)index-=size;applies the same modular arithmetic to the array index. When the index is only ever incremented by one, it can even be written asif (index==size)index= 0;. For hash tables, you often seeindex=hash%size, but it does require thathashis nonnegative (preferably a fixed-size unsigned integer type), andsizeto be positive (and not zero).When you aren't sure how to implement the cyclicity/modularity scheme you want, create a table. Label each row with each possiblefromvalue, and each column with a possibletovalue, and their difference in the table cell. You'll immediately see the structure, and if you have constructed such a table for each of the schemes you might need, you'll immediately recognize it. For example, signed distances and midway positionsmodulo7 areOn the left, zeros at diagonal have gray background, midway distances light gray, negative distances red, and positive distances blue.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 15, 2024, 12:11:02 am
content: Great, but allow me to just discuss the following point:Quote from: Nominal Animal on June 14, 2024, 10:51:48 pmThe two's complement is actually negating the value bitwise (flipping all bits) and then adding *one*, not two. The "two" in two's complement doesn't come from adding two to the complemented value, but from the fact that the sum of the original number and the two's complemented number is 2^N (where N is the width of the original and complemented numbers), but when only considering N bits (so modulo 2^N), the sum yields zero, which is arithmetically consistent [ x + (-x) = 0 (mod 2^N) ].Likewise, the one's complement is just the negated value (all bits flipped). The reason for the naming is that the sum of the original number and complemented number, is, in this case, all ones. So yes, those terms of one's and two's complements are more "illustrative" than coming from a strict single definition.The two's complement is actually very simple to understand - since the sum of the bitwise negated number and the number itself is always made of all ones, if you add 1 to the sum, you get zero (mod 2^N).

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 15, 2024, 12:59:54 am
content: Quote from: SiliconWizard on June 15, 2024, 12:11:02 amYou're obviously rightThanks for the correction. I over-stroke the misleading bit in my post. I do wonder where this brain-fart erupted from.Edit: It's an hour later, and I'm still blushing with shame. However, this is an excellent example of how Imustcheck and not rely on my recollection. Perhaps it is easier to others, but for myself, I need to verify and not just trust, as this error well shows. Which is why I also like and recommend 'trust, but verify' as a policy.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 15, 2024, 03:32:45 am
content: There is no shame in making a mistake.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 15, 2024, 09:53:42 am
content: Quote from: SiliconWizard on June 15, 2024, 03:32:45 amThis, and while always double-checking everything is a good strategy for designing a nuclear reactor or space station, in an informal forum discussion like this we can give Nominal Animal some slack forsometimesposting an unchecked claim, something which most other people doall the time.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 16, 2024, 03:32:48 am
content: Quote from: SiliconWizard on June 15, 2024, 03:32:45 amTrue. And as long as my own mistakes are caught and we can correct them, Iamhappy.In my own education, I've been lucky to have teachers, mentors and professors who weren't afraid of admitting they didn't know something, nor of admitting and correcting an error they've made. It has made such a difference to my life, I wish everyone had similar luck, especially in elementary school.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 16, 2024, 05:13:03 am
content: I just realized a case that well describes the issues with negative or signed distances or positions:converting a distancedmeasured in integer inches, to feetfand inchesi.(I can do the conversion to arc-minutes and arc-seconds from decimal degrees, if that would be better.)When the distance is a nonnegative integer, we can usef=d/ 12;i=d-f* 12;or equivalently,f=d/ 12;i=d% 12;Nothing new here, everyone knows this.If the distancedcan be negative, ie. we usesigned distanceto indicate both distance and direction, or it is a position with respect to origin, we have several options how such negative distances are represented. Positive distances representation is always the same.(Note that the below handle both negative and positive distances; you do not need to handle positive and negative distances separately.)If we wantf= -1,i= -2to represent negative 1' 2",d= -14, then:f=d/ 12;i=d-f*12;Basically all current C compilers also let you usei=d% 12, but the C standard is a bit confused about the sign of the remainder for negative values. This pattern is well recognized either way by current C and C++ compilers, and will optimize it to efficient code if possible.If we wantf= -2,i= +10to represent negative 1' 2",d= -14, then:f=d/ 12;i=d-f*12;if (i< 0) {f-= 1;i+= 12;}If we wantf= 1,i= 2to represent negative 1' 2",d= -14, then:temp = abs(d); //ortemp = (d< 0) ? -d:d;f=temp/ 12;i=temp-f*12; //ori=temp% 12;If you wantf= -1,i= 2to represent negative 1' 2",d= -14, then you are wrong.This representation cannot differentiate negative distances less than one feet from positive ones. For example, bothd= -5andd= +5would be represented byf= 0,i= 5.When the distance isfloatdin inches, feet isfloatf, and inches isfloati:For never negatived:f= truncf(d/ 12.0f);i=d-f* 12.0f; //ori= fmodf(d, 12.0f);If we wantf= -1,i= -2for negative 1' 2" (d= -14), then:f= truncf(d/ 12.0f);i=d-f* 12.0f; //ori= fmodf(d, 12.0f);If we wantf= -2,i= +10for negative 1' 2" (d= -14), then:f= floorf(d/ 12.0f);i=d-f* 12.0;If we wantf= 1,i= 2for negative 1' 2" (d= -14), then:temp = fabsf(d);f= truncf(temp/ 12.0f);i=temp-f* 12.0f; //ori= fmodf(d, 12.0f);If we wantf= -1,i= 2to represent negative 1' 2" (d= -14), thenf= floorf(d/ 12.0f);i= fabsf(d-f* 12.0f);but note that you cannot determine the direction by comparingdorfto zero; instead, youhave touse thesignbit()macro. Essentially, for negative directions,signbit(d)andsignbit(f)will return 1, and for positive directions 0. This works, becausefloat(anddouble) have separate representations for +0 and -0, even though they compare equal and have the same logical value.Again, note that whichever case you pick, it will convert both positive and negative distances correctly; positive distances always the same normal way in all cases.Whenfis truncated or a cast to some integer type, you have the choice of either explicit multiplication orfmodf()function. For integer divisors, I prefer the multiplication. For arbitrary divisors like Pi, I recommendfmodf()for maximum precision.A common suggestion is to replace thetruncf()call with a simple cast to some integer type, for example(int)(d/ 12.0f). This works –– C99 and later explicitly state it as rounding towards zero, truncation –– but limits the range of usefuldto 12.0f*INT_MIN.. 12.0f*INT_MAX. Becausefloathas only 24 bits of precision anyway, andints are typically at least 32-bit, that is usually okay. In many architectures the cast is faster, too, especially if immediately re-cast back tofloat.When the divisor is smaller than 1.0, such a cast can severely restrict the range even forfloats, though. So be careful, and do not assume it is strictly equivalent to truncation.The usable range restriction due to a cast tointinstead of an explicittrunc()is much more of an issue when usingdoubles, unless you use(int64_t)or(intmax_t), which have at least 64-bit range (doublehas 53 bits of precision).Such a suggestion is therefore useful iff you first consider whether it negatively impacts the range of usable floating-point numbers or not. Here, if you don't mind losing precision for very long distances,truncf()is better. If you know your distances are always withinINT_MINtoINT_MAXfeet, then the(int)cast is functionally equivalent and might be a tiny bit faster.

author: Infraviolet
author_rank: Super Contributor
timestamp: June 17, 2024, 02:23:07 am
content: Ok, I think I'm now in a position where I might be able to solve my problems as soon as I have a way to do the following:Take the difference between two numbers(Angle1 - Angle2), these numbers being wrapping numbers which wrap at an arbitrary value (exact value to be selected later, but definitely not a convenient power of 2 or anything like that, that wrapping will happen for MaxRevs and -MaxRevs, not for a 0 to MaxRevs range. So it wraps more like a -180 to 180 than a 0 to 360).Subtraction must be able to happen across the wrap point, if Angle1 is not far above -MaxRevs and Angle2 is not far below +MaxRevs I'd expect the subtraction to happen across the wrap pointBut the sign of the output of the subtraction must be such that it will, including when subtracting across the wrap point, give a clear indication of whether Angle1 or Angle2 is "bigger". Bigger not meaning here an actual size, but rather working n the way that for a 0 to 360 circle you might say 30 is "bigger" than 350 because you go upward from 350 to reach 30 by the shorter route.--------------I've got this to work easily where binary numbers are used as the limit, because subtracting them works across the limit of, for example, a uint8_t's wrap point, then going back to zero. But I'm a bit more confused when using numbers with an arbitrary wrap point, and where that wrap point goes between +MaxRevs and -MaxRevs (or +Maxrevs and -Maxrevs+1 I think more accurately, so that -MaxRevs and +MaxRevs are not duplicate values for the same point*).I'm doing this with signed integer numbers, not floats, so I don't need to worry about decimal parts. But I need to know how to write a generic version which can be easily rewrriten for whichever size of signed integer I choose to use once I know exactly what my MaxRevs value will be.Can someone advise on the logic for this please?*clamping being done with the below, though part of me wonders whether it would be wiser to have a >= for the positive limit and a < for the lower limit. The slowness of this for values of many*MaxRevs is ok, because I can't foresee of any way a value >2*MaxRevs or < -2*MaxRevs could ever produce itself, any given operation would do a single addition or subtraction that could go from within range to that far outside the range.while(count > MaxRevs){count=count-2*MaxRevs;}while(count <= -MaxRevs){count=count+2*MaxRevs;}Thanks

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 17, 2024, 02:35:55 am
content: As far as FP goes with GCC or (LLVM, which has a very similar behavior for "compatibility" reasons), there's one compilation flag that can make *a lot* of difference: -ffast-mathassuming, of course, that you know what it is doing and if that's acceptable in your case. The default behavior (without this option enabled) aims for correctness, and can yield pretty slow code. If you can accept the degraded correctness, enabling -ffast-math will make your FP code much faster.Some pointers here:https://gcc.gnu.org/wiki/FloatingPointMathRegarding trig functions (which have been also discussed lately), note that glibc's implementation is also very slow (but "surprisingly" accurate). I think Newlib's implementation is pretty close to that of glibc.If you can accept some loss of accuracy, implementing your own trig functions (discussed already) *will* make a lot of difference if performance is a bottleneck.

author: IanB
author_rank: Super Contributor
timestamp: June 17, 2024, 05:23:41 am
content: Quote from: Infraviolet on June 17, 2024, 02:23:07 amSince you are dealing with a circle, you need to use words like "clockwise" and "counterclockwise" instead of "bigger" and "smaller".Let's suppose angle 1 is 30 degrees, and angle 2 is 350 degrees. You want to know the shortest path from angle 1 to angle 2. This would be 40 degrees counterclockwise, or −40 degrees. The way you do this, is you compute (angle2 − angle1), or (350 − 30), which is 320. Now, if this result is greater than 180, you subtract 360 from it. 320 − 360 = −40, which is the expected result.The converse might be if angle 1 is 350 degrees, and angle 2 is 30 degrees. In this case, the shortest path from angle 1 to angle 2 is 40 degrees clockwise, or +40 degrees. Once more, you compute (angle2 − angle1), or (30 − 350), which is −320. Now, if this result is less than −180, you add 360 to it. −320 + 360 = +40, which is the expected result.On the other hand, if the result of the subtraction lies between −180 and +180, then you just use the result as given, and no need to add or subtract 360.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 17, 2024, 07:53:34 am
content: Quote from: IanB on June 17, 2024, 05:23:41 am... or, you just interpret the bits as signed integer, and get the -180..+180 deg range automagically, without any if statements. This is what binary angles offer you, choice of 0..360 vs. -180..180 depending on if you cast to signed type or not.If you use a type with "full rounds" part, taking a difference with the full data width directly gives you the shortest distance, and direction thereof.Quote from: Infraviolet on June 17, 2024, 02:23:07 amWhat even is this wrap point, and why do you think you need it?If, for some weird reason, you need to represent angles to the user e.g. between 5...364 degrees instead of 0...359 or -180..+179 (which would be theusualrepresentations), then just add an offset as a very first (in parsing) or last (in displaying) step. Internally, use the sensible format, that is, binary angle representations without any funny "arbitrary wrap points".

author: IanB
author_rank: Super Contributor
timestamp: June 17, 2024, 05:22:24 pm
content: Quote from: Siwastaja on June 17, 2024, 07:53:34 amThis is a very helpful and useful result, but we also need to consider the human understanding part. The way I described it with decimal integers, someone can read it and follow along. Which means later on if the code is implemented this way, then someone reading the code will also be able to follow it. The additional cost of tests and adding or subtracting 360 will likely be insignificant, computationally.On the other hand, if the work is done with binary arithmetic, then the result occurs as if by magic, and will need extensive comments in the code to explain to someone unfamiliar with such calculations.I found it helpful to look at an example to see this this binary angle approach works.Let's work with 8 bits, so that angles are measured clockwise from 0 to 255 (0 being 12 o'clock, and 255 being 11:59).Let angle one be 9, and let angle two be 252. The shortest distance from 9 to 252 is 13 steps clockwise, while the shortest distance from 252 to 9 is 13 steps counterclockwise, or −13.So, in the first case, we compute 252 − 9 = 243. Now we treat 243 as a signed integer, and it becomes −13, which is the desired result.In the second case, we compute 9 − 252 = 13 due to wraparound. As a signed integer this is directly the expected result.This could well be described as "cunning logic", but it could also be described as "obfuscated code". Care is needed.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 18, 2024, 09:01:58 am
content: Quote from: Infraviolet on June 17, 2024, 02:23:07 amIanB suggested "clockwise" and "counterclockwise", but I prefer "positive" and "negative".AngleTo - AngleFrom should be positive if you need fewer iterations of incrementing AngleFrom than decrementing to get to Angle To.AngleTo - AngleFrom should be negative if you need fewer iterations of decrementing AngleFrom than incrementing to get to Angle To.AngleTo - AngleFrom should be zero if the two are equal.The same logic is used in e.g. Cqsort():compar(a,b)needs to return negative ifa<b, 0 ifa==b, and positive ifa>b.Quote from: Infraviolet on June 17, 2024, 02:23:07 amStandard representable range of values is-MaxRevs..+MaxRevs-1, i.e. including-MaxRevsbut excluding+MaxRevs, based on two's complement integer range.LetRevsRange = 2*MaxRevs(== (MaxRevs) - (-MaxRevs)), and must be representable in the type you use forrevs. Then:while (revs>= MaxRevs)revs-= RevsRange;while (revs< -MaxRevs)revs+= RevsRange;For an arbitrary valuerevs, that might be many timesMaxRevs, you can userevs-= RevsRange * (revs/ RevsRange);if (revs>= MaxRevs)revs-= RevsRange;if (revs< -MaxRevs)revs+= RevsRange;or, equivalently, giving the compiler more opportunities for optimization,revs%= RevsRange; // Result is between -RevsRange+1 and RevsRange-1, inclusiveif (revs>= MaxRevs)revs-= RevsRange;if (revs< -MaxRevs)revs+= RevsRange;On architectures where conditionals are slow, multiplication/modulo is fast, but the compiler cannot eliminate them for above, you can userevs= (revs+ MaxRevs) % RevsRange;revs+= (revs< 0) ? MaxRevs : -MaxRevs;which the compiler should know how to optimize. After the first line,revscan be-RevsRange+1..RevsRange-1, but we will need to substractMaxRevsto compensate for the initial addition. On the second line, ifrevs< 0, we need to addRevsRange - MaxRevs = MaxRevs; ifrevs>= 0, we only need to subtractMaxRev.For arbitraryNegRevs..PosRevs-1range,NegRevs < 0,PosRevs > 0,RevsRange = PosRevs - NegRevs, you can userevs= (revs- NegRevs) % RevsRange;revs+= (revs< 0) ? -NegRevs : NegRevs;Quote from: Infraviolet on June 17, 2024, 02:23:07 amYes, that will limitcountto range-MaxRevs+1 .. MaxRevs. The limit with the "or equal" check will be excluded. If both have the "or equal" check, then the later one dominates.I do recommend you use-MaxRevs..MaxRevs-1, instead, so your limits match the "standard" ones, e.g. two's complement behaviour (and thusintN_ttypes' wrapping rules).

author: Infraviolet
author_rank: Super Contributor
timestamp: June 28, 2024, 11:08:29 pm
content: I worked out some solutions which seem to work. For the benefit of others facing these sort of problems, I'll share them here at some point, once I've made the comments in my code tidier and such.

author: Infraviolet
author_rank: Super Contributor
timestamp: July 03, 2024, 07:06:42 pm
content: These are the type dfinition and functions I've found to work. The last few functions just work with angles contained as a single byte, other functions use the AngleType stored angles. The use of an int16_t for the number of revolutions before wrapping ensures that when combining with the angle within a given revolution (as done inside some functions) to make an int32_t, we never get a value there so big as to overflow the int32_t. During wrapping this code goes from +MaxRevs to -MaxRevs+1, or from -MaxRevs+1 to +MaxRevs.Code:const int16_t MaxRevs=25200;//maximum number of electrical revolutions before we wrap, a multiple of 252 chosen here as the gearbox gives 1 mechanical roation at the output for 252 electric rotations within the motor//so long as we never give a single move command for a very large angle change, then the motor will never move the wrong way to reach itstruct AngleType{int16_t m_revolutionsPart; //number of electrical revolutions completed, relative to an arbitrary zerouint16_t m_anglePart; //stored with bottom 4 bits being for incrementing, next 8 bits being the angle out of 256, uppermost 4 bits unused//default constructor initialises it at zeroAngleType() : m_revolutionsPart(0), m_anglePart(0) {}void AddToAngle(int16_t BinaryDelta){int32_t IntAngle=m_anglePart;IntAngle=(int32_t)IntAngle+BinaryDelta;while(IntAngle>=4096L){//(256<<4)){IntAngle=IntAngle - 4096L;//(256<<4);m_revolutionsPart=m_revolutionsPart+1L;}while(IntAngle<0L){IntAngle=IntAngle + 4096L;//(256<<4);m_revolutionsPart=m_revolutionsPart-1L;}while(m_revolutionsPart > (int32_t)MaxRevs){m_revolutionsPart=m_revolutionsPart-2L*MaxRevs;}while(m_revolutionsPart <= (int32_t)(0L-MaxRevs) ){m_revolutionsPart=m_revolutionsPart+2L*MaxRevs;}m_anglePart=(uint16_t) ( (int32_t)IntAngle & (int32_t)0xfff );//or m_anglePart=(uint16_t) ( (int32_t)IntAngle)); //???}void AddRevs(int16_t RevsDelta){m_revolutionsPart=m_revolutionsPart+RevsDelta;while(m_revolutionsPart > MaxRevs){m_revolutionsPart=m_revolutionsPart-2*MaxRevs;}while(m_revolutionsPart <= (0-MaxRevs) ){m_revolutionsPart=m_revolutionsPart+2*MaxRevs;}}void SubtractFromAngle(int16_t BinaryDelta){AddToAngle(-BinaryDelta);}void SubtractRevs(int16_t RevsDelta){AddRevs(-RevsDelta);}void UpdateAnglePart(uint16_t BinaryNewAnglePart){//give a new absolute value for the angle part, and this will automatically add to or decrement the revs, for situations where you measure the angle itself (at fast enough intervals not to have the motor having rotated >360 elec degrees between measurements) from an absolute "encoder" rather than constantly monitoring an incremental "encoder" type systemuint16_t m_anglePartOld=m_anglePart & 0x0fff;m_anglePart=BinaryNewAnglePart & 0x0fff;if(m_anglePart < (uint16_t)85<<4 && m_anglePartOld > (uint16_t)171<<4){m_revolutionsPart=m_revolutionsPart+1;}else if(m_anglePartOld < (uint16_t)85<<4 && m_anglePart > (uint16_t)171<<4){m_revolutionsPart=m_revolutionsPart-1;}while(m_revolutionsPart > MaxRevs){m_revolutionsPart=m_revolutionsPart-2*MaxRevs;}while(m_revolutionsPart <= (0-MaxRevs) ){m_revolutionsPart=m_revolutionsPart+2*MaxRevs;}}};int16_t MultiTurnDiffOfAngles(AngleType Angle1, AngleType Angle2){//returns a difference, or a very large or very small number if multiple revs apart//output of this is in actual 256ths, the 4 bits of incrementers have been abandonedint32_t Angle1Big=(int32_t)((Angle1.m_anglePart>>4) & 0xff)+(int32_t)Angle1.m_revolutionsPart * (int32_t)256L;int32_t Angle2Big=(int32_t)((Angle2.m_anglePart>>4) & 0xff)+(int32_t)Angle2.m_revolutionsPart * (int32_t)256L;//because m_revolutionsPart is between 32767 and -32768 , AngleXBig will never get near the limits of an int32_tint32_t DiffBig=(int32_t)Angle1Big-(int32_t)Angle2Big;if(DiffBig > -(MaxRevs*256L) && DiffBig <=(MaxRevs*256L)){//within 1*MaxRevs of each other, 256s because DiffBig is in 256ths rather than in revs itself//return (int16_t)DiffBig;}else{while(DiffBig > (MaxRevs*256L)){DiffBig=DiffBig-2L*(MaxRevs*256L);}while(DiffBig <= (0L-(MaxRevs*256L)) ){DiffBig=DiffBig+2L*(MaxRevs*256L);}//return (int16_t)DiffBig;}//now we clamp the value of DiffBig to +/-256, then return it as an int16_tif(DiffBig>256L){DiffBig=256;}if(DiffBig< -256L){DiffBig=-256;}int16_t DiffSmall=DiffBig;return DiffSmall;}void printAngleType(AngleType AngleToPrint){Serial.print(F(" Revs: "));Serial.print(AngleToPrint.m_revolutionsPart);Serial.print(F(" Angle: "));Serial.print((AngleToPrint.m_anglePart>>4));Serial.print(F("/256 plus "));Serial.print(AngleToPrint.m_anglePart & 0xf);Serial.print(" increments ");}int8_t AngleDiff(uint8_t In2, uint8_t In1){//expects angle in 256ths, with the 4 bits of incrementing already removedint8_t Diff= ( ( In1 < In2 ) ? (int8_t)(In2 - In1) : -(int8_t)(In1-In2));return Diff; //returns in 256ths, no 4 bits of incrementing are present}const uint8_t Sine_table256[] PROGMEM = {generate as needed};uint8_t FasterSine(int32_t AngleIn){//expects angle in 256ths, with the 4 bits of incrementing already removeduint8_t AngleInSmall=AngleIn & 0xff;uint8_t ValueOut=pgm_read_byte_near(Sine_table256 + AngleInSmall);return ValueOut;}uint8_t AngleAverage(uint8_t AngleIn1, uint8_t AngleIn2){//works, but results may be jumpy when the two input anles are "180 degrees", or nearly 180, apart (128/256)int8_t Difference = AngleDiff(AngleIn1,AngleIn2);uint8_t Out=0;int8_t HalfDiff=abs(Difference >> 1);if(Difference>0){Out=AngleIn2 + HalfDiff; //to the smaller angle add half the difference}else{Out=AngleIn1 + HalfDiff; //to the smaller angle add half the difference}return (uint8_t) (Out & 0xff);}bool CheckBetween(uint8_t testingVal, uint8_t LowerLimit, uint8_t UpperLimit){//will not work for LowerLimit==UpperLimitif(LowerLimit == UpperLimit){return 0;}if(AngleDiff(UpperLimit,LowerLimit) > 0 ){//LowerLimit < UpperLimit){if(AngleDiff(testingVal,LowerLimit) >=0 && AngleDiff(UpperLimit,testingVal)>=0){return 1;}else{return 0;}}else{if(AngleDiff(testingVal,UpperLimit) >=0 && AngleDiff(LowerLimit,testingVal)>=0){return 1;}else{return 0;}}}Hopefully these can help someone in future.