Electronics > Microcontrollers

Need help with AVR C integer overflow behaviour

(1/5) > >>

Lomax:
Hi all,

Although this is not my first attempt at writing C for an ATMega MCU I am stumped by the odd behaviour I see when trying to map a signed 16-bit int containing degrees of rotation (-1 to 359) to an 8-bit integer (0 to 255), using linear interpolation:

--- Code: (c) ---#include <avr/io.h>

volatile int16_t angle;
volatile uint8_t duty;

int rotate(int degrees) {
angle = (angle + degrees) % 360;
if (angle < 0) angle += 360;

return (angle * 255) / 360;
}

int main(void) {
while(1) {
duty = rotate(1);
}
}

--- End code ---

If I watch angle and duty everything seems fine until angle reaches 129, where duty suddenly jumps to 166:

As you can see, I'm forcing angle to positive before the mapping. I'm sure this is caused by something simple, and probably a fundamental misunderstanding on my part, but what?

angle multiplication overflows in the return statement. Something like this should help "return ((int32_t)angle * 255) / 360;"

Lomax:

--- Quote from: ataradov on September 17, 2023, 03:42:03 pm ---angle multiplication overflows in the return statement. Something like this should help "return ((int32_t)angle * 255) / 360;"

--- End quote ---

Doh! 129 x 255 > 32,767 :palm: Declaring angle as int32_t fixes the problem.

SiliconWizard:

--- Quote from: Lomax on September 17, 2023, 05:03:39 pm ---
--- Quote from: ataradov on September 17, 2023, 03:42:03 pm ---angle multiplication overflows in the return statement. Something like this should help "return ((int32_t)angle * 255) / 360;"

--- End quote ---

Doh! 129 x 255 > 32,767 :palm: Declaring angle as int32_t fixes the problem.

--- End quote ---

But it's wasteful as you don't need to store the angle on 32 bits. That wouldn't matter much on a 32-bit MCU, but you're dealing with a 8-bit MCU here.
So I would favor the solution ataradov suggested, with the cast just for the multiplication. C arithmetic rules are not trivial and I recommend learning them (it's a trap for many).

Infraviolet:
As you're dealing with angles, I'll throw in a little warning here. Remember that 360 wraps round to zero, it seems obvious but it gives some troublesome behaviour if you try to average or low pass filter angles which are separated across that discontinuity. You need to take sin and co first, process the, then recombine using atan2 to get the angle back.

It can cause an MCU to waste more RAM and more instructions to an operation than ideal, but if you write as

(angle * 255.5 ) / 360.0

the compiler will do this with float mathematics, then return the result as the type of integer you want.

You could try

float Tempvariable=(angle * 255.0) / 360.0;
return (uint8_t)Tempvariable;

Note that rotate() might as well return a uint8_t rather than an int, as your value is always in the 0 to 255 range.