0

I am working on a code for the Atmel Microcontroller and using ATMEL Studio.

You can check the toolchain and studio version from here.

*\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\lib\gcc\avr\5.4.0*

I have a code in my two programs.

CASE_1:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)BAUD_RATE)) + 0.5)
USART1.BAUD = (uint16_t)USART_BAUD_RATE(300);

CASE_2:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)BAUD_RATE)) + 0.5)
/* uint32_t my_BaudRate = 300; //I set this value in the program, normally it's 115200, 2400, but sometimes it can be 300*/
USART1.BAUD = (uint16_t)USART_BAUD_RATE(my_BaudRate );

Sample Example:

#include <stdio.h>
#include <stdint.h>

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)(BAUD_RATE))) + 0.5)

uint16_t getValue_1() {
    return (uint16_t)USART_BAUD_RATE(300);
}

uint16_t getValue_2(uint32_t inVal) {
    return (uint16_t)USART_BAUD_RATE(inVal);
}

int main()
{
    printf("Macro with Constant argument: %d\r\n", getValue_1());
    printf("Macro with variable argument: %d\r\n", getValue_2(300));
    return 0;
}

// Output
/* 
Macro with Constant argument: 65535
Macro with variable argument: 1131
*/

I found out that the USART.BAUD has different values in both programs although it should be the same if I set the my_BaudRate to 300.

In Program 1: USART1.BAUD has the value 0xFFFF, although it should have been overflown and had 0x046B. I tried changing the BAUD_RATE value to 280 or less, it always sets it to 0xFFFF. Only after I set the value to 306, it sets the actual value less than 0xFFFF.

In Program 2: it works as expected, USART1.BAUD has the value 0x046B.

I think it has something to do with preprocessor or compiler optimization, but I am not sure if this is a known behavior or if I need to be careful about these kinds of macros.

I Would be grateful for any insights.

Best Regards.

9
  • Macros are simple find-replace things. USART_BAUD_RATE(300) will be replaced by (float)(5000000 * 64 / (16 * (float)300)) + 0.5). And similarly USART_BAUD_RATE(my_BaudRate) will be replaced by (float)(5000000 * 64 / (16 * (float)my_BaudRate)) + 0.5) Commented Jun 6, 2024 at 11:26
  • 1
    What likely happens here is not an overflow, but simply a cut-off. The result of (5000000 * 64 / (16 * 300)) + 0.5 is 66667,166666667. That is then converted to the int value 66667, and the explicit conversion to uint16_t will discard the top bits and leave you with 65535. Which is indeed 0xffff. If you split up the expression into smaller parts, and using temporary variables to store the intermediate results, it might be easier to see. Commented Jun 6, 2024 at 11:32
  • That the second example, with the variable, works could be due to many different reasons which we can not guess unless you show us a proper minimal reproducible example. Commented Jun 6, 2024 at 11:34
  • @BetaEngineer – The definition of my_BaudRate is missing in your question. Commented Jun 6, 2024 at 11:35
  • USART1.BAUD = (uint16_t)(uint32_t)USART_BAUD_RATE(300); should give you the wrapped value 0x46B, avoiding undefined behavior. (Replace uint32_t with uint_fast32_t, uint_least32_t or unsigned long if uint32_t is not implemented.) Commented Jun 6, 2024 at 12:27

3 Answers 3

5

… it should have overflown and should have had 0x046B.

Given the BAUD_RATE argument is is 300, ((float)(5000000 * 64 / (16 * (float)BAUD_RATE)) + 0.5) evaluates to 66,667.166…

Then (uint16_t)USART_BAUD_RATE(300) attempts to convert 66,667.166… to uint16_t. Since 66,667.166… in hexadecimal is 1046B.2AA…16, you apparently expect this conversion to wrap modulo 216 and to truncate, thus producing 46B16.

However, C 2018 6.3.1.4 1 says:

When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.

Since the integral part of 1046B.2AA…16 is larger than FFFF16, it cannot be represented in uint16_t, and therefore the behavior is not defined by the C standard. It is not uncommon for a compiler to generate different results for undefined expressions using constants and undefined expressions using variables, because the compiler may evaluate expressions using constants at compile time using its own built-in arithmetic but generate instructions for expressions using variables that use different arithmetic.

If you want this conversion to wrap, you must implement that yourself by testing the value while it is still in the float type and subtracting as necessary. If you want it not to overflow or wrap, you must use a wider integer type or redesign the code to use different units so the value will fit in a uint16_t.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the reference to the C standard. In my testing, using (uint16_t)USART_BAUD_RATE(300) always results in 0xFFFF and (uint16_t)USART_BAUD_RATE(var), here var is uint32 and has value 300, always results in 0x046B. That is why I thought maybe it has something to do with preprocessor or compiler optimization. But, now I'll fix it by making sure my value is correct.
0

I'm pretty sure the difference between the two cases is that the invocation

USART_BAUD_RATE(300)

Will be compiled by the optimiser into load a constant value which is computed at compile time (potentially more accurately and better controlled than at runtime). This contrasts with the second example passing a variable when the runtime system is obliged to do all the computation. It would be worth taking a look at the assembler code listing to see if this is the case.

It is worth commenting on the lost art of efficient integer arithmetic for embedded systems. Casting to float is unnecessary (and can bring in a lot of extra baggage on embedded systems that lack hardware FP support).

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)(BAUD_RATE))) + 0.5)

The first float probably wants to be inside the numerator too (on some systems it may overflow).

This can be rewritten in a pure integer form without much difficulty.

#define USART_BAUD_RATE(B) (( 5000000 * 64 + 8*(B)) / (16 * (B))

Or if you can be sure the denominator multiplier will always be at least a multiple of 8 it can be simplified to

#define USART_BAUD_RATE(B) (( 5000000 * 8 + (B)) / (2 * (B))

If you want the answer modulo 0xffff then do a bitwise & to get the result into the right range and if you want saturation then min(x,0xffff). Assigning an out of range value to a smaller type is undefined behaviour (although in many cases it may appear to do what you expect - until one day it doesn't). I'm assuming some of these constants are clock frequency and hardware divider ratios so that the multiplies are all needed,

Comments

0

I would probably improve your USART_BAUD_RATE macro via several transformations. Starting with this:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)BAUD_RATE)) + 0.5)

, I would first combine the integer constants 64 in the numerator and 16 in the denominator. The division is exact, including in floating point, and this not only simplifies the expression but reduces opportunities for integer overflow. That gives us this:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 4 / ((float)BAUD_RATE)) + 0.5)

I see no particular advantage to 5000000 * 4 over 20000000, so I would combine to yield the latter:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(20000000 / ((float)BAUD_RATE)) + 0.5)

I would then furthermore observe that the first cast is unnecessary. Its operand already has type float on account of the operands in the expression being evaluated being a mixture of floats and integers. Removing that yields:

#define USART_BAUD_RATE(BAUD_RATE) ((20000000 / ((float)BAUD_RATE)) + 0.5)

However,

  1. The objective of the remaining cast seems to be to ensure FP division instead of integer division, and that would be better achieved by using an FP constant in the numerator than by casting the denominator; and

  2. Macro arguments generally should be enclosed in (their own) parentheses where they appear in the macro's replacement text.

Both of those suggest a conversion to

#define USART_BAUD_RATE(BAUD_RATE) ((20000000f / (BAUD_RATE)) + 0.5)

. Note well that 2000000f is a constant of type float, so floating-point division is performed and the quotient also has type float. But why float? The constant 0.5 has type double, so you're converting the float result to double and producing a final result of type double. On most C implementations, the only advantage float has over double is storage size, and you're not storing the value anyway (in the macro), so I would do this, instead:

#define USART_BAUD_RATE(BAUD_RATE) ((20000000.0 / (BAUD_RATE)) + 0.5)

As to your actual problem, however, you seem to need this as an unsigned integer, not a double or float, (see @Eric's answer) so I would probably go ahead and put that into the macro. If you're willing to assume that BAUD_RATE will never be less than 1, then this will do, and is largely independent of implementation-dependent behavior:

#define USART_BAUD_RATE(BAUD_RATE) ((uint_least32_t)((20000000.0 / (BAUD_RATE)) + 0.5))

As for the usage of this macro, however, if you mean to extract the least-significant 16 bits, then it would be clearer to do that explicitly, by masking. With the final version of the macro (above) expanding to an expression of integer type, that's no particular problem:

USART1.BAUD = USART_BAUD_RATE(my_BaudRate) & 0xffff;

// or

USART1.BAUD = USART_BAUD_RATE(300) & 0xffff;

1 Comment

As for your suggestion regarding MACRO definition, I will consider that in my project, as it simplifies the calculation and also less chances for overflow. The only hurdle is that (In my complete project) in the formula, 5000000 and 16 are also Macro defined, but they can have different values (although always constant), so for different projects, I can make calculations myself and put the simplified version of formulas as you suggested.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.