Author Topic: Need help with AVR C integer overflow behaviour  (Read 2646 times)

0 Members and 1 Guest are viewing this topic.

Offline LomaxTopic starter

  • Frequent Contributor
  • **
  • Posts: 565
  • Country: eu
  • Minimalist
Need help with AVR C integer overflow behaviour
« on: September 17, 2023, 03:20:37 pm »
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) [Select]
#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);
}
}

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?

« Last Edit: September 17, 2023, 03:32:04 pm by Lomax »
 

Offline ataradov

  • Super Contributor
  • ***
  • Posts: 11261
  • Country: us
    • Personal site
Re: Need help with AVR C integer overflow behaviour
« Reply #1 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;"
Alex
 
The following users thanked this post: Lomax

Offline LomaxTopic starter

  • Frequent Contributor
  • **
  • Posts: 565
  • Country: eu
  • Minimalist
Re: Need help with AVR C integer overflow behaviour
« Reply #2 on: September 17, 2023, 05:03:39 pm »
angle multiplication overflows in the return statement. Something like this should help "return ((int32_t)angle * 255) / 360;"

Doh! 129 x 255 > 32,767 :palm: Declaring angle as int32_t fixes the problem.
« Last Edit: September 17, 2023, 05:06:20 pm by Lomax »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14481
  • Country: fr
Re: Need help with AVR C integer overflow behaviour
« Reply #3 on: September 17, 2023, 08:15:25 pm »
angle multiplication overflows in the return statement. Something like this should help "return ((int32_t)angle * 255) / 360;"

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

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).
 
The following users thanked this post: Lomax

Offline Infraviolet

  • Super Contributor
  • ***
  • Posts: 1017
  • Country: gb
Re: Need help with AVR C integer overflow behaviour
« Reply #4 on: September 17, 2023, 10:08:09 pm »
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.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4039
  • Country: nz
Re: Need help with AVR C integer overflow behaviour
« Reply #5 on: September 17, 2023, 10:46:36 pm »
360 * 255 = 91800, so your multiplication result can exceed what an "int" can store by a factor of 2.8.

You could use 32 bit arithmetic but simply dividing both 255 and 360 by 5 would give the same effect and be a lot faster.

(angle * 51) / 72

Or, for that matter...

(angle * 17) / 24

 
The following users thanked this post: Squantor, Lomax, SiliconWizard

Online T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21688
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: Need help with AVR C integer overflow behaviour
« Reply #6 on: September 17, 2023, 11:41:27 pm »
It's not clear what you're doing, if this applies, but notice an unsigned byte 2s-complement is modular in 256, or a short out of 65k, so can be used to represent angles without any additional logic.

(Note also that short, int, etc. sizes are minimums, and their actual size is platform dependent; use uint16_t, etc. where this matters. Or add otherwise-redundant masking (unsigned short foo = (expression) & 0xffff will be optimized out on most platforms anyway) to guarantee the desired result.  Even byte is minimum IIRC, though there are very few platforms indeed where the byte/word size isn't 8 bits, aside from very old platforms.)

Most things you'd use angles for, you can tweak to use fixed point fractions of a circle.  Polynomial approximation of sin/cos for example.  Or use the bits directly in a CORDIC, etc..

Also, avoid using % and /, they're very slow, particularly for variable arguments (that can't be streamlined by the compiler, but IIRC integer division isn't optimized anyway?, and modulo not at all*).  Not that this necessarily matters for whatever you're doing right now -- but keep an eye out for low-hanging optimizations like this when it does matter.

*You can craft a multiplication method for division (with remainder) by a constant, but I don't think GCC uses it.  Div/mod by powers of 2 generally are optimized to the respective logical operations.

Tim
« Last Edit: September 17, 2023, 11:43:05 pm by T3sl4co1l »
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Need help with AVR C integer overflow behaviour
« Reply #7 on: September 18, 2023, 12:44:27 am »
((int32_t)angle * 255) / 360 is wrong, because it maps 0..359 to 0..254.

The correct formula is (value * out_range) / in_range, i.e. 256/360 = 32/45, when rounding towards zero.

Rounding halfway away from zero, you can use (value * out_range + half) / in_range, where half is +in_range/2 if value is positive, and -in_range/2 if value is negative.  (Either one is fine when value is zero.)

If you want the same results as ((int)(roundf(angle*32.0f/45.0f)))&255, try
Code: [Select]
uint8_t degrees_to_byte(int16_t deg)
{
    uint32_t  pdeg = (deg < 0) ? (uint32_t)((int32_t)deg + 33120) : deg;
    return ((pdeg * 32 + 22) / 45) & 255;
}
which works correctly for all angles between -32768 and +32767 degrees, the full range an int16_t in C can represent.  For negative angles, we add the smallest multiple of 360 not smaller than 32768, to make sure the angle is positive: 33120.  Then, we cast it to unsigned 32-bit number.  (Because of C integer promotion rules, the casts in the pdeg initialization are not necessary, and (deg < 0) ? deg + 33120 : deg; would do the exact same thing.)
Thus, pdeg can range from 0 to 33119, inclusive.

Since 256/360 = 32/45, we multiply pdeg by 32, add half of 45 for rounding (+22), and do an integer division by 45.  Note that the value being divided is always between 22 and 1059830, inclusive, ie. a 21-bit positive integer.

As 256/360 = 32/45 = 0.7111..., we could open-code the multiplication and division into a fixed-point multiplication by 32/45, since division is quite slow on AVRs.  Because (32×2¹⁹)/45 = 372827.0222... –– 19 being the smallest power of two that yields exactly correct values here ––, this yields us the dense but efficient form
Code: [Select]
uint8_t degrees_to_byte(int16_t deg)
{
    return ((((uint32_t)((int32_t)deg * 372827) >> 16) + 4) >> 3) & 255;
}
For negative deg, the multiplication will overflow, but we're not interested in the high bits anyway (part of full turns in the angle), so we can simply cast the result to unsigned 32-bit integer so we retain all significant bits of the result (and do not actually care about the most significant bit, whether it is correct result bit or copied from the sign bit).  Again, we rely here on the fact that with these integer types, negative values use two's complement format, which allows us to do this "yeah, we don't care about those highest bits at all, really" trick.  On AVR, this is a simple int-int multiplication, implemented by the __mulsi3() avr-libc function or equivalent, so this multiplication is quite fast.
After the multiplication, we drop the least significant 16 bits, because on AVR this is just two register moves.  Since we have a division by 8 left, we add the +4 for rounding (with half away from zero), and do the three bit shifts, and finally use only the 8 least significant bits left.

Both above forms return the exact same value as ((int)(roundf((float)deg*32.0f/45.0f))) & 255 for all possible int16_t values of deg, -32768 to +32767, inclusive.  I verified this with a test program, using x86-64 double-precision floating point (i.e. ((int)(round(deg*32.0/45.0)))&255 to test all possible values of deg).
« Last Edit: September 18, 2023, 02:12:13 am by Nominal Animal »
 
The following users thanked this post: T3sl4co1l

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Need help with AVR C integer overflow behaviour
« Reply #8 on: September 18, 2023, 02:08:45 am »
If OP maps 0..360 to 0..255, 360 will correspond to 255 and not 256, and 180 will correspond to 127.5 and not 128 as one would expect for cyclical 8-bit angle units.

When linearly mapping in_min ≤ input ≤ in_max to out_min ≤ output ≤ out_max using the standard formula,
    output = out_min + (input - in_min) * (out_max - out_min) / (in_max - in_min)
the input and output ranges are split into (min(in_max - in_min + 1, out_max - out_min + 1)) equal-width bins, and in_min and out_min correspond to the minimum values in the first bin.

The error many programmers make is believing that in_max and out_max correspond to the maximum values in the last bin.
This is not so; they too correspond to the minimum values in the last bin!

If we use the rounding variant,
    output = out_min + ( (input - in_min) * (out_max - out_min) + in_half ) / (in_max - in_min)
where in_half = (in_max - in_min) / 2, then in_min and out_min correspond to the center value in the first bin, and out_min and out_max correspond to the center value in the last bin.  Thus, first and last bins have technically half the width of the rest of the bins.



If we use in_first and out_first for the first values in the first bin, and in_last and out_last for the last values in the last bin, then we need to use
    output = out_first + (input - in_first) * (out_last - out_first + 1) / (in_last - in_first + 1)
because we are mapping (in_last - in_first + 1) different input values to (out_last - out_first + 1) different output values.

For cyclical values, we want the first and last bins to have just half the width of the other bins (so that the "zero" bin is not double-wide when we consider values that wrap around our cyclical range), and thus need to use the rounding variant,
    output = out_first + ( (input - in_first) * (out_last - out_first + 1) + in_half) / (in_last - in_first + 1)
where in_half = (in_last - in_first + 1) / 2.  This way in_first and out_first correspond to the middle values in the first bin, and in_last and out_last to the middle values in the last bin.

This works, and you can use the above for both integer and floating-point mapping.  Just make sure the multiplicands either have sufficient range, or at least one of them is cast to a type having sufficient range, for the product.

When implemented using integer arithmetic, the ones with in_half above round halfway towards last/max.  C round(), roundf(), and roundl() should use the same logic, but some implementations round exact halfway towards odd or even.
 

Offline LomaxTopic starter

  • Frequent Contributor
  • **
  • Posts: 565
  • Country: eu
  • Minimalist
Re: Need help with AVR C integer overflow behaviour
« Reply #9 on: September 18, 2023, 08:43:02 am »
Crikey, that opened a can of worms :D I'm sure you all know what it's like when you're staring at something that should be obvious but your brain is stuck at the wrong angle. The AVR and Atmel C is just one of many tools in my kit and not one I reach for very often, so please don't be too hard on me. I understand well the inefficiency in using unnecessarily large integers, performing floating point operations, etc, but efficiency is not important at this stage. I'm trying to come up with a way to convert a numerical angle (0-359) into analogue sine/cosine voltages (by means of R/C filtering), and the snippet I posted was just a stepping stone on that path - the 0-359 to 0-255 mapping was a temporary test to get a linear PWM duty cycle representing the angle and it's long gone. The rotate function now looks like this, and angle is defined as an int16_t:

Code: [Select]
void rotate(int degrees) {
angle = (angle + degrees) % 360;
if (angle < 0) angle += 360;
radian = angle * (M_PI / 180.0);
sine   = (sin(radian) + 1) * 127;
cosine = (cos(radian) + 1) * 127;
OCR1A = sine;
OCR1B = cosine;
}

I'm sure there are many more n00b errors in this - my trig is even worse than my C - but it seems to do what I want and loads the AVR ~15% when running at 2 MHz. It would be interesting to hear what my errors are if/how the code could be further optimised.

Edit: My current test simulation looks like this:



The input angle will eventually come over the serial port, so the rotation and associated modulo operation will not be used. It's only there so I can do proof-of concept testing using the encoder input. Final code might look something like this:

Code: [Select]
void output(int angle) {
float radian = angle * (M_PI / 180.0);
uint8_t sine = (sin(radian) + 1) * 127;
uint8_t cosine = (cos(radian) + 1) * 127;
OCR1A = sine;
OCR1B = cosine;
}
« Last Edit: September 18, 2023, 09:09:40 am by Lomax »
 

Offline ledtester

  • Super Contributor
  • ***
  • Posts: 3036
  • Country: us
Re: Need help with AVR C integer overflow behaviour
« Reply #10 on: September 18, 2023, 09:21:37 am »
It would be interesting to hear what my errors are if/how the code could be further optimised.
...
Code: [Select]
void output(int angle) {
float radian = angle * (M_PI / 180.0);
uint8_t sine = (sin(radian) + 1) * 127;
uint8_t cosine = (cos(radian) + 1) * 127;
OCR1A = sine;
OCR1B = cosine;
}

A lookup table for the sine and cosine values would be a lot faster than calling sin() and cos().

This project write-up shows how to store such a lookup table in flash memory on an Arduino Uno so it doesn't take up space in RAM:

https://home.csulb.edu/~hill/ee470/Lab%202d%20-%20Sine_Wave_Generator.pdf

The key points are:

- include <avr/pgmspace.h>
- use the PROGMEM directive, e.g.: const uint8_t sinewave[] PROGMEM= { ... }
- use the pgm_read_byte() function to access the array, e.g.: OCR1A=pgm_read_byte(&sinewave[angle]);

The cosine lookup can also use the sine table by just shifting the input by 90 degrees:

OCR1B=pgm_read_byte(&sinewave[(450-angle)%360])

(Note angle should be between 0 and 359.)
« Last Edit: September 18, 2023, 09:27:49 am by ledtester »
 
The following users thanked this post: Lomax

Online Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 3364
  • Country: nl
Re: Need help with AVR C integer overflow behaviour
« Reply #11 on: September 18, 2023, 11:48:58 am »
If OP maps 0..360 to 0..255, 360 will correspond to 255 and not 256, and 180 will correspond to 127.5 and not 128 as one would expect for cyclical 8-bit angle units.

If you want to map a circle into a byte, then you should not map 360 to 255.
For a circle, 360 degrees is a full turn, so it maps back to zero.
 

Offline LomaxTopic starter

  • Frequent Contributor
  • **
  • Posts: 565
  • Country: eu
  • Minimalist
Re: Need help with AVR C integer overflow behaviour
« Reply #12 on: September 18, 2023, 12:03:24 pm »
If OP maps 0..360 to 0..255, 360 will correspond to 255 and not 256, and 180 will correspond to 127.5 and not 128 as one would expect for cyclical 8-bit angle units.

If you want to map a circle into a byte, then you should not map 360 to 255.
For a circle, 360 degrees is a full turn, so it maps back to zero.

the 0-359 to 0-255 mapping
 

Online Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 3364
  • Country: nl
Re: Need help with AVR C integer overflow behaviour
« Reply #13 on: September 18, 2023, 04:08:10 pm »
If OP maps 0..360 to 0..255, 360 will correspond to 255 and not 256, and 180 will correspond to 127.5 and not 128 as one would expect for cyclical 8-bit angle units.

If you want to map a circle into a byte, then you should not map 360 to 255.
For a circle, 360 degrees is a full turn, so it maps back to zero.

the 0-359 to 0-255 mapping

That would make the overflow from 255 to 0 the same as an overflow from 359 to zero. The difference is small, and depending on the allowed rounding errors it may or may not be acceptable.

The correct way is to map 360 to 256. Note that on the circumference of a circle there are 360 equal distances when divided in degrees, and 256 distances when divided over a byte.

 

Offline Infraviolet

  • Super Contributor
  • ***
  • Posts: 1017
  • Country: gb
Re: Need help with AVR C integer overflow behaviour
« Reply #14 on: September 19, 2023, 02:51:34 pm »
"convert a numerical angle (0-359) into analogue sine/cosine voltages"
Why?

In a lot of circumstances it would be easier to measure the angle, hold it as a digital value then pass it over serial or something, as digital numerical data, to whatever requires the angle reading. Calculating sine and cos components is pretty simple, but properly generating analog voltages from an atmega328p is HARD, if you try it with PWM and RC filtering your output levels will fluctuate whenever a timing tick or other interrupt fires and makes a PWM pulse come late or last a little longer than normal. If you really need to generate voltages you'd likely do better to have a proper DAC as an I2C slave to the 328p. Is there an analogue thing you are trying to control from cos and sine voltages, explain what and a simpler way might be possible.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: gb
  • Doing electronics since the 1960s...
Re: Need help with AVR C integer overflow behaviour
« Reply #15 on: September 19, 2023, 05:15:32 pm »
I know of some avionics (my hobby etc) products which map 0-359 to 0-255 and the result is horrible! You get various weird effects, not to mention accumulating errors of 1-2 degrees which create havoc down the line.

But if you are just feeding the voltage into an 8 bit DAC to generate a sinewave (the X/Y/400Hz "synchro" system used in avionics) fair enough. It just won't be quite accurate and people will notice that. Same if you are doing LVDT emulation (done that too, but with 12 bit DACs).

PWM is OK if it runs at a proper high speed so the lowpass filtering doesn't cause problems.

Honestly, in 2023, I would not waste my time with these cheap chips unless the product is ultra price sensitive. I am using a 32F417 which runs at 168MHz and you can just do the whole lot with floats. It does a float mult in 1 cycle (7ns). An "int" is 32 bits and of course everything also takes 7ns. The chip costs 10 quid in low qtys, 5 quid 1k+. You can do waveform synthesis in hardware on it using timers, dma, dacs, so no software actually running to make it all work, after the set-up.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21688
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: Need help with AVR C integer overflow behaviour
« Reply #16 on: September 19, 2023, 11:19:12 pm »
Or even if you're tied to the AVR ecosystem for...reasons, the newer e.g. AVR-DA and etc. have a number of compelling features that keep them... if not competitive, then generally useful at least.  There are significant differences in hardware, of course.

Working in C, there's not really a huge deal going between mainstream MCUs, at least if you write in a reasonably portable manner (this does take some experience, granted).  STM32s and ATSAMs are quite popular, competitive, and very powerful in comparison to the old ATMEGAs.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Need help with AVR C integer overflow behaviour
« Reply #17 on: September 20, 2023, 02:49:54 am »
Never underestimate the power of vector algebra.

For example, if you have a 2D unit vector \$(x, y)\$ (unit meaning length is 1, \$x^2 + y^2 = 1^2 = 1\$), then \$x = \cos \varphi\$ and \$y = \sin \varphi\$ where \$\varphi\$ is the angle, positive counter-clockwise, the vector makes with the positive \$x\$ axis.

To rotate any 2D vector \$(u, v)\$ by angle \$\varphi\$ represented by unit vector \$(x, y)\$, you do
$$\begin{aligned}
u^\prime &= u x - v y \\
v^\prime &= v x + u y \\
\end{aligned}$$

If a vector \$(x^\prime, y^\prime)\$ should be an unit vector –– rounding errors tend to creep in, causing a bit of shrinking/expansion ––, it can always be normalized to an unit vector \$(x, y)\$ via
$$\begin{aligned}
L^\prime &= \sqrt{ {x^\prime}^2 + {y^\prime}^2 } \\
x &= \frac{x^\prime}{L^\prime} \\
y &= \frac{y^\prime}{L^\prime} \\
\end{aligned}$$
Since division tends to be much slower than multiplication, often the division by square root is replaced by multiplication with the reciprocal square root, \$1 / \sqrt{{x^\prime}^2 + {y^\prime}^2}\$.  This may sound like a slow operation, but there are tricks that one can use to speed it up significantly, even when using say fixed-point numbers with no hardware square root or division operations.  One is to first calculate \${L^\prime}^2\$.  If it is less than one half, multiply it by four, and double both \$x^\prime\$ and \$y^\prime\$; repeat until it it is at least one half.  If it is two or greater, divide it by four, and halve both \$x^\prime\$ and \$y^\prime\$.  This simplifies to finding the reciprocal of square root of \$z\$, with \$1/2 \le z \lt 2\$.  As \$1/\sqrt{z} = \sqrt{1/z}\$ is a nice smooth monotonically decreasing curve, one can use a binary search, a polynomial approximation, or even Newton-Raphson iteration via \$r \gets r ( 3 - z r^2 ) / 2\$.  Binary search will give one additional bit of precision per iteration but requires fewer elementary operations per Newton-Raphson iteration, but Newton-Raphson converges faster requiring fewer iterations (after the first one or two) to reach the same precision.

If you use a polynomial approximation for the first sine octant, you can construct an unit vector given only the angle.  The above normalization will be useful then too, to minimise the approximation error; then, the approximation error shows up as "noise" in the angle parameter \$\varphi\$, but not in the unit vector length per se.  That is, the sine-cosine pari constructed thus will have \$\sin^2 \varphi + \cos^2 \varphi = 1\$.

With this unit vector approach, if you don't have hardware floating point or division operations, sooner or later you will end up with CORDIC: an algorithm to calculate trigonometric functions with bit shifts, additions, multiplications, and some look-up tables.

With the above, and using quaternions or bivectors for representing 3D orientation and rotations, even an 8-bit AVR has easily enough computational power to do real-time 3D position and orientation tracking with sensor fusion from accelerometers and magnetic field sensors using standard software floating-point operations; much more so if using fixed-point arithmetic with 8, 16, 24, or 32 fractional bits (and 7 integral bits, if using unit vectors and unit quaternions/bivectors) and extended inline assembly implementations for their efficient multiplication.
« Last Edit: September 20, 2023, 02:52:18 am by Nominal Animal »
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8173
  • Country: fi
Re: Need help with AVR C integer overflow behaviour
« Reply #18 on: September 20, 2023, 05:34:09 am »
I know of some avionics (my hobby etc) products which map 0-359 to 0-255 and the result is horrible! You get various weird effects, not to mention accumulating errors of 1-2 degrees which create havoc down the line.

Well, quite obviously, if 8 bits of angular resolution is not enough, then it's not enough. The idea of using self-wrapping numbers to represent angles is sound, though, not only because of the performance, but also not having to write wrapping code (which can go wrong). If 8 bits is not enough, and especially if one is worried about accumulating errors, why not use 32 bits to represent the full circle. You can easily mask any number of bits (e.g., if you decide to use a 1024-element sine LUT, then just use the 10 highest bit of the angle), and you actually have significantly more resolution than a 32-bit float, with a huge performance gain over floating point implementation (which also has to range-check and wrap everywhere).
 

Offline LomaxTopic starter

  • Frequent Contributor
  • **
  • Posts: 565
  • Country: eu
  • Minimalist
Re: Need help with AVR C integer overflow behaviour
« Reply #19 on: September 23, 2023, 11:04:41 am »
"convert a numerical angle (0-359) into analogue sine/cosine voltages"
Why?

Because I want to drive an analogue dial instrument that shows wind direction. The instrument has sin/cos inputs. My wind direction sensor only has a resolution of 16 steps, which I read over a 1-Wire bus, so required accuracy is not demanding; I just want a rough indication of the direction the wind is blowing. Appreciate the advanced discussion though!
 

Offline mikerj

  • Super Contributor
  • ***
  • Posts: 3240
  • Country: gb
Re: Need help with AVR C integer overflow behaviour
« Reply #20 on: September 23, 2023, 11:59:09 am »
For only 16 positions a table to lookup the sin and cos values would be practical (and very fast).
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Need help with AVR C integer overflow behaviour
« Reply #21 on: September 23, 2023, 12:12:20 pm »
Because I want to drive an analogue dial instrument that shows wind direction. The instrument has sin/cos inputs. My wind direction sensor only has a resolution of 16 steps, which I read over a 1-Wire bus, so required accuracy is not demanding; I just want a rough indication of the direction the wind is blowing.
The point is you are complicating your task by unnecessarily using angular degrees.

If you used 0-255 instead – with say 0 East, 64 North, 128 West, 192 South – you would simplify your code, because (direction & 255) correctly maps 256 to 1, -1 to 255, -128 to 128, -64 to 192, and so on.

To convert from 0..15 or -8..7 wind direction, just multiply by 16 and bitwise-and with 255, or use a look-up table for the 16 wind directions.

As to generating the sin/cos outputs for the indicator, the exact indicator inputs matter a lot: are the inputs voltage or current?  What voltage or current range they use?

Most microcontrollers only support I/O voltages between 0 and Vcc.  With four control pins – two differential pairs – you could use four output pins, as two pairs, with one of each pair always being ground, and the other pin used for either full output, or PWM'd and low-pass filtered for a lower voltage.
If the current draw of the indicator is higher than say 10mA, then you might use a dual full bridge (with optional low-pass filters).
For easier control of the indicator, you might use a four-channel DAC that can source and sink sufficient current.
The simplest direction indicator might be just a magnetic needle with four coils, wired in north-south and east-west pairs, inside a magnetically shielded enclosure (so that external magnetic fields do not affect the indicator reading).
 

Offline LomaxTopic starter

  • Frequent Contributor
  • **
  • Posts: 565
  • Country: eu
  • Minimalist
Re: Need help with AVR C integer overflow behaviour
« Reply #22 on: September 23, 2023, 01:12:29 pm »
My original question, which I know was pretty stupid, has been answered. I did not see the need to provide every other detail about what I'm trying to do, but since many point out the performance downside of floating point math calculation of the sin/cos values I guess I'll have to clarify things a little.

First of all, I know and understand how a lookup table would work, and why it would be a lot less CPU intensive to use one, particularly with only 16 cardinal directions. But I have no need for this performance boost, because the update frequency will only be 1 Hz. The AVR will only have to perform the sin/cos calculations once a second. I would gain nothing by switching to a lookup table.

Secondly, just because my current wind sensor only has 16 directions that does not exclude the possibility that a future replacement may provide far higher resolution. I do not want to restrict myself to any arbitrary resolution - though 255 steps per revolution seems plenty, particularly when the internal damping algorithm of the instrument is taken into account. Should this prove unsatisfactory in practice it ought to be trivial to switch to a 16-bit resolution for the PWM. But I am pretty sure this will not be needed.

I am basically trying to convert an old analogue wind instrument to a (slightly less outdated) NMEA 0183 wind instrument. Because I prefer its appearance over modern digital instruments. Specifically, the AVR will be listening to the MWV NMEA 0183 sentence, transmitted to it once per second over RS422/385:


       1 2 3 4   5 6
       | | | |   | |
$IIMWV,x.x,a,x.x,a*hh<CR><LF>


  • Wind Angle, 0 to 359 degrees
  • Reference, R = Relative, T = True
  • Wind Speed
  • Wind Speed Units, K/M/N
  • Status, A = Data Valid, V = Invalid
  • Checksum

For example: $IIMWV,214.8,R,5.1,K,A*33

When a message arrives (once per second) and triggers the USART interrupt the AVR should parse the NMEA sentence and update the sin/cos voltages representing wind direction, as well as the frequency of a square wave representing wind speed (~0-4 kHZ IIRC) with the received values. I think this should be well within the capabilities of an ATMega328, even clocked at 2 MHz. In fact, some quick checking reveals the following number of clock cycles spent on each of the calculations:


radian = angle * (M_PI / 180.0);

227

sine   = (sin(radian) + 1) * 127;

2086

cosine = (cos(radian) + 1) * 127;

2005


That's a total of 4318 clock cycles spent calculating the sin/cos values. While this might seem excessive, at 2 MHz the AVR could perform these calculations 463 times per second (provided it did nothing else). Or in other words, with a 1 Hz update rate these calculations use less than 0.25% of the available time on a 2 MHz AVR.
« Last Edit: September 23, 2023, 01:48:30 pm by Lomax »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf