Repository navigation
fix(foc utils): restore the FLT_MIN guard in _atan2() - #583
stijncarelsbergh wants to merge 2 commits into
Conversation
The comment says 'inject FLT_MIN in denominator to avoid division by zero' but the term is missing (it was dropped from the ODrive original), so _atan2(0,0) is 0/0 and returns NaN instead of 0. Restores the original guard, which only affects the degenerate (0,0) case.
|
Won't this still return a very large number? It seems more logical to return 0 for that case. I've been using this code for a long time, and meant to include it last time I updated LinearHall (the primary user of atan2), but forgot to do the PR for it due to being in different repositories. It should also be slightly faster, doing a single branch chain instead of min and max, and no extra addition. |
…ep |x| == |y| correct @dekutree64 pointed out in simplefoc#583 that the comparison chain is cheaper than min/max plus an epsilon in the denominator, and that is right: it drops one float add and one min/max pair, and on AVR every float compare and division is a library call. His snippet as written was `else return 0; // Avoid division by zero`, but that branch also catches |x| == |y| != 0 (where a == 1 and atan2 is +-PI/4). Measured in single precision: atan2(1,1) would become 0 instead of 0.7854, i.e. a 45 degree error, and up to 135 degrees over a full sweep, while the chain below keeps the 0.0116 degree accuracy of the polynomial. So: chain form, with the equal case split into (0,0) -> 0 and otherwise a = 1. This also drops the FLT_MIN include from the previous commit.
|
Thanks @dekutree64 - you are right that the epsilon is the wrong tool here, and I have The epsilon version does not return a large number for Your snippet has one case that isn't
Over a full 360 deg sweep the worst error becomes 135 deg, against 0.0116 deg for the float a;
if (abs_x < abs_y) a = abs_x / abs_y;
else if (abs_x > abs_y) a = abs_y / abs_x;
else if (abs_x == 0.0f) return 0.0f; // atan2(0,0) == 0
else a = 1.0f; // |x| == |y| != 0 -> a == 1That returns 0 for Pushed as If I misread your snippet (e.g. you expect callers to never hit |
|
Wow, I'm glad I forgot the PR then :) I suspect it's been silently failing for me, causing imperceptible twitches, whereas the div by 0 permanently wrecks the PID so it's really obvious when it happens. I would probably implement it like this, but I don't think it really matters since in the vast majority of cases with yours, one of the first two if statements should fire and skip past the zero check anyway. EDIT: On second thought, yours is better since the < and > results can be extracted from a single compare operation (at least on ARM with FPU), whereas mine would do a separate compare with 0 in half of the common cases. |
fix(foc utils): restore the FLT_MIN guard in _atan2()
The comment says 'inject FLT_MIN in denominator to avoid division by zero' but the
term is missing (it was dropped from the ODrive original), so _atan2(0,0) is 0/0
and returns NaN instead of 0. Restores the original guard, which only affects the
degenerate (0,0) case.
Split out of #571 at your request: one fix per PR, against
dev. The branch containsnothing else, so it can be reviewed, amended or dropped on its own.
The CI board matrix runs automatically; I did not run any hardware test, so the behavioural
claims are from reading the code plus the compiler. Happy to adjust the wording, split it
differently or drop it - no attachment to this one.