Issue with M_PI and Math.h - Page 1
Forum section: electronics / general

author: Red_Micro
author_rank: Regular Contributor
timestamp: October 12, 2020, 05:36:58 pm
content: I'm using IAR Workbench IDE to program STM32 and when trying to use M_PI I get the following error: Error: identifier "M_PI" is undefined. AFAIK, M_PI is not defined in Math.h, but some sites says that yes. So this got me confused. I tried #define _USE_MATH_DEFINES #include <math.h> but it does not work either. So I'm thinking of just defining the value myself, for example: double const pi=3.1415926535897932384626433; Is this OK? Any other suggestion?

author: ataradov
author_rank: Super Contributor
timestamp: October 12, 2020, 06:51:06 pm
content: M_PI is not a part of the standard, so it is up to the implementer of the library whether it will be included or not, and if there are specific defines to enable those definitions.I don't see PI defined anywhere in IAR itself. It is defined in ARM SDP library in arm_math.h.But realistically, just define your own, it is fine.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 12, 2020, 07:51:52 pm
content: You can always safely useCode:#include <math.h>#ifndef M_PI#define M_PI 3.1415926535897932384626433#endifto provide theM_PImacro. If math.h defines it, it is used; otherwise it is defined to the shown value.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 12, 2020, 10:34:37 pm
content: Absolutely, it's just not defined in the C standard, so never guaranteed to be available on any given platform.But since you're posting in the Microcontrollers section, I'm just thinking of adding something. Using PI in C code would smell like you were tempted to use trigonometric functions... on an MCU... just wanted to make sure you're aware of the cost of those functions on a small target. Even if the MCU you're targetting has an FPU - AFAIK, FPUs on Cortex-M don't implement trig functions (please correct me if I'm wrong), so they'll all be implemented in software, and that's pretty expensive. If the MCU in question doesn't embed an FPU, it'll be obviously even slower.

author: greenpossum
author_rank: Frequent Contributor
timestamp: October 12, 2020, 11:24:57 pm
content: Maybe OP just likes going around in circles.But seriously calculations around (sorry) circles, elipses, cylinders, or even circuit time constants use pi without trig.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 13, 2020, 02:04:50 am
content: Hey, there are STM32 with floating-point units. (Do note that if usingsingle-precision floating point type, usuallyfloat, you may wish to explicitly use(float)(M_PI)or#define FLOAT_PI (float)(M_PI)and the code I showed in an earlier message.)In general, using fixed-point can yield better performance, and even better precision (but limited range) than single-precision floating point. On 32-bit ARMs, useuint32_t(unsigned or biased fixed-point) orint32_t(signed fixed point) as the base integer type.In fixed point arithmetic, integerirepresents real valuei/B, withBsome power of two (or, in cases where displaying decimal digits is more important than the speed of arithmetic operations, a power of ten). WhenB= 2n, andihasN+nbits (excluding any sign bit), the format is calledQN.n. For example, Q7.24 is 32-bit signed integer or 31-bit unsigned integer type, with 7 highest bits representing the integer part, and the 24 lowest bits representing the fractional part; so that integer value 16777216 represents arithmetic value 1.0.Using fixed point arithmetic, trigonometric functions are often implemented viaCORDIC. It is also very common to choose angular units where an integer power of two is a full turn (instead of degrees or radians); using unsigned integer arithmetic rules in C and C++, this way angular differences are trivial to calculate. Typical choices are 256 (28), 65536 (216), and 1.0 in the fixed point base you use.Current C and C++ compilers don't have good fixed-point support, so one has to implement the operations themselves.For example, a fused multiply-add of two Q7.24 multiplicands and an additive constant of type Q15.48 – useful forpreciseproducts – can be written asCode:#include <stdint.h>typedef int32_t q7p24;typedef int64_t q15p48;static inline q7p24 multiply_add(const q7p24 a, const q7p24 b, const q15p48 c){return (q7p24)( ((q15p48)a * (q15p48)b + c) >> 24 );}Because the multiplicands have both 24 fractional bits, the result has 2×24=48 fractional bits, which happens to match the format of the additive constant. To convert that back to Q7.24, we need to discard the low 24 bits.Of course, this multiply-addition can overflow. It also does not do rounding-to-nearest, only round-towards-zero. To round-to-nearest and clamp to the representable range, one can use e.g.Code:static inline q7p24 multiply_add_round(const q7p24 a, const q7p24 b, const q15p48 c){const q15p48 r = (q15p48)a * (q15p48)b + c;if (r <= (q15p48)(-0x0080000000000000LL))return (q7p24)(-0x80000000L);elseif (r < 0)return (q7p24)((r - (q15p48)0x0001000000000000LL) >> 24); /* -(q7p24)(((q15p48)0x0001000000000000LL - r) >> 24) */elseif (r <= (q15p48)(0x007FFFFFFFFFFFFFLL))return (q7p24)((r + (q15p48)0x0001000000000000LL) >> 24);elsereturn (q7p24)(0x7FFFFFFFL);}Current C and C++ compilers tend to optimize this kind of code pretty well, but not perfectly. (The reason for thestatic inlineis to tell the compiler to compile the code in-line wherever the function is called, instead of a function call to one instance; that helps with register allocation and such.)For division, you multiply the dividend prior to division; here, shift the value to be divided 24 bits left. Note that it must be promoted to a larger type (q15p48in this case) prior to division, just like in the multiplication case, so that the result has all significant bits. For rounding towards nearest integer, you can for example obtain the remainder, and compare it to half.Many compilers, like GCC, support inline assembly. GCC in particular supportsextended asm, which basically allows one to implement the low-level operation inside a static inline function body (even letting the compiler do register allocation), which produces pretty darn good results – assuming one is adept at writing those assembly routines in the first place, so that if the above C implementations do not perform sufficiently well, one can implement alternatives (with exactly the same function signature, i.e. drop-in replacements) in extended inline assembly. I usually add preprocessor directives (#ifdef ASM_OPS(assembly implementation)#else(C implementation)#endif) and write a complete test battery (to be run on the target device; up to a separate "test firmware" for my microcontroller projects) to verify they produce exact same operations.Some algebraic operations, like square root, are a bit more complicated, as they are done on the real value represented by the fixed-point number, instead of the binary representation. (In particular, the square root of a fixed point value is usually NOT equal to the square root of the binary value.) For such operations (functions), iterative approximation usingNewton's method(and other similar iterative approaches) work well.In general, we construct a functionf(y) that has a single root (f(y) = 0) when its argument matches the desired result. For square root,f(y) =y2-x, wherexis the value whose square root we want to know. Its derivative df/dy=2y, so the Newton's method iteration isynew=y- (y2-x) / (2y) = (y2+x) / (2y)with a suitable initial value forybeing half ofx, noting that only nonnegativexhave a real square root. You repeat the iteration untilynew=y, which takes only a few iterations (depends on the exact value ofx).This is well known stuff, and you can find more by searching for "numerical algorithms", "fixed-point arithmetic", and "bit twiddling" (or similar).

author: Red_Micro
author_rank: Regular Contributor
timestamp: October 13, 2020, 03:39:06 pm
content: I'm going to calculate a Hann window a0 + a1 * Cos (2*PI*i/N) . I'm ok with single precision so I may try thearm_mathlibrary. My micro does not have FPU (Cortex M0+), so I will have a look at how slow or fast this is. Fixed point is another way as other have pointed out.

author: ataradov
author_rank: Super Contributor
timestamp: October 13, 2020, 04:12:23 pm
content: On Cortex-M0+ it will be extremely slow and take up a ton of space. You need to figure out a way to avoid floating point operations.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: October 13, 2020, 04:42:02 pm
content: Least squares polynomial fit is your friend.Open up Octave and use 'polyfit' function. Then implement in fixed point math.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 13, 2020, 05:08:17 pm
content: Quote from: Red_Micro on October 13, 2020, 03:39:06 pmThis will be ultra slow. Of course whether it matters or not entirely depends on how often you need to compute this and whether it's in a critical part of the program execution-wise or not. Maybe it doesn't matter. Yours to check though. So on an M0+, I think I was definitely right to at least warn you.Another thought: unless the parameters for your Hann window can change depending on execution (a0, a1, N), an obvious better way of dealing with this is to precompute the values and just store them in a static array in your code.

author: Red_Micro
author_rank: Regular Contributor
timestamp: October 13, 2020, 05:16:02 pm
content: Quote from: SiliconWizard on October 13, 2020, 05:08:17 pmYou have a point. My Hann window is only calculated at the initialization and stored in a static array.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 13, 2020, 06:29:58 pm
content: Quote from: Red_Micro on October 13, 2020, 05:16:02 pmSo now you have everything you need to decide.* If the window doesn't take too much Flash size, just precompute it and store it as a const array.* If it does, or if you still want to be flexible and find it easier to modify coefficients in your code instead of having to re-generate the window with an external tool and paste the new values again in your code (if you ever have to modify the window), then you can still consider generating it at initialization time. Even if it's slow, if it's just done once, it may not matter. One other thing to consider if you go that route though: if your MCU doesn't have an FPU *and* you use trig functions, library code for both floating point support and the trig functions you use will end up in Flash - and may take a significant amount of code memory, possibly even more so that the table itself.Up to you. You can try both approaches and see for yourself too.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 13, 2020, 07:58:01 pm
content: Because Cortex-M0+ has only a 32×32=32 bit multiplication operation (MULS) and no hardware division, you should ensure that the product between samples and coefficients fits in 32 bits for best performance.Otherwise, you'll likely have to implement your own multiplication function for best performance. For example:Code:#include <stdint.h>typedef union {uint64_t u64;uint32_t u32;uint16_t u16;} word64;typedef union {uint32_t u32;uint16_t u16;} word32;static inline uint64_t multiply_32by16_64(const word32 a, const uint16_t b) {word64 result;/* This gives us the low 32 bits of the 48 bit result. */result.u32 = a.u32 * b;/* This gives us the high 16 bits of the 48 bit result. */result.u32 = (a.u16 * b) >> 16;return result.u64;}static inline uint64_t multiply_32by32_64(const word32 a, const word32 b){word64 result;/* This gives us all least significant 32 bits of the product. */result.u32 = a.u32 * b.u32;/* The most significant 32 bits of the product. */result.u32 = (((uint32_t)a.u16 * (uint32_t)b.u16 + (uint32_t)a.u16 * (uint32_t)b.u16) >> 16)+ ((uint32_t)a.u16 * (uint32_t)b.u16);return result.u64;}On GCC-ARM 6.3.0 (-O2 -mcpu=cortex-m0plus -mtune=cortex-m0plus), omitting thestatic inlineto compile them as functions, we getCode:.text.syntax unified.code 16.thumb_funcmultiply_32_16_64:movs r2, r1muls r2, r0lsrs r0, r0, #16muls r1, r0asrs r1, r1, #16movs r0, r2bx lrmultiply_32_32_64:movs r3, r0movs r0, r1push {r4, lr}muls r0, r3lsrs r4, r1, #16lsrs r2, r3, #16uxth r1, r1uxth r3, r3muls r3, r4muls r1, r2muls r2, r4adds r1, r3, r1lsrs r1, r1, #16adds r1, r1, r2pop {r4, pc}Note that a simple 32-bit signed or unsigned multiplication whose result fits in a 32-bit variable, is a singlemulsinstruction.Hopefully, your Cortex-M0+ is the kind that has a single-cyclemulsinstruction, and not the 32-cycle one.

author: Red_Micro
author_rank: Regular Contributor
timestamp: October 13, 2020, 08:03:08 pm
content: Quote from: Nominal Animal on October 13, 2020, 07:58:01 pmMy device is STM32G030K6. According to the datasheet it has single cycle multiplier.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 13, 2020, 09:27:27 pm
content: I believe so, too. TheProgramming manual(PDF) has useful information; and theCortex-M0+ Technical Reference Manualhas even the individual instruction timings.Even if you don't write assembly code, knowing what kind of limitations the processor has can help you write expressions easier for the compiler to optimize.For example, if you want to have 32-bit signed samples and 31-bit unsigned windowing coefficients, set the least significant bit of the coefficients zero, and use the 31 most significant bits. This is because we can write the signed-unsigned 32×32=64 bit multiply then asCode:#include <stdint.h>typedef union {uint64_t u64;int64_t s64;uint32_t u32;uint16_t u16;} word64;typedef union {uint32_t u32;int32_t s32;uint16_t u16;int16_t s16;} word32;int64_t multiply_s32_u32_s64(const int32_t a, const uint32_t b){const word32 arg1 = { .u32 = a + (1 << 31) }; /* Bias arg1 to nonnegative */const word32 arg2 = { .u32 = b };word64 result;/* This gives us all least significant 32 bits of the product. */result.u32 = arg1.u32 * arg2.u32;/* The most significant 32 bits of the product. */result.u32 = (((uint32_t)arg1.u16 * (uint32_t)arg2.u16 + (uint32_t)arg1.u16 * (uint32_t)arg2.u16) >> 16)+ ((uint32_t)arg1.u16 * (uint32_t)arg2.u16)- ((uint32_t)arg2.u32 >> 1); /* Remove the bias, assuming (b & 1) == 0. */return result.u64;}costing us only four additional cycles (LSRS, LSLS, ADDS, and SUBS instructions) per multiplication.This works, becausea×b= (a+231)×b-b231. Ifbis zero or even, the final subtraction only affects the high 32 bits of the result, and we save a bunch of carry-operations between the two words.If you have say 210= 1024 samples, it makes sense to define the coefficients with say 20 fractional bits (so that coefficients are always zero or even, and 221corresponds to 1.0). That way the signed sum cannot overflow, and you only need to shift it right to extract the most significant bits.Similarly, if the window size is not a power of two, you can still scale the coefficients so that the signed 64-bit sum has an integer number of fractional bits.

author: bson
author_rank: Unknown
timestamp: October 17, 2020, 12:18:47 am
content: Quote from: Red_Micro on October 13, 2020, 03:39:06 pmWhat are i and N? Sample index and total number of samples in window? If so you should be able to precalculate all the cosines and put them in a table.