2
\$\begingroup\$

I am a newbie in Embedded C (solo learner). I am using it to drive around a robot (using the ATmega2560 with 14.7456 MHz). I am experiencing some unexpected behaviour with the following code:

int j = 2;

PORTJ = pow(2, j) - 1;

Now I expect 3 to be seen at port J. However, I see 2. Moreover, if I do this:

PORTJ = pow(2, 2) - 1;

then it works fine (that is, I see 3 at port J). The reason I want to do this using a variable is because my actual code is this:

int j = 0;

while(1)
{   
    j = (j + 1) % 9;

    PORTJ = pow(2, j) - 1;

    _delay_ms(500);
}

This works for all values of J except for j = 2. What am I doing wrong here? I have recently started so I don't know much about this. Any help is appreciated.

\$\endgroup\$
16
  • 1
    \$\begingroup\$ pow is a function returning a floating point type. PORTx is an integer type. So you are getting a rounding error. \$\endgroup\$
    – Eugene Sh.
    Commented Sep 23, 2016 at 18:42
  • 3
    \$\begingroup\$ @supercat Well, this is something every programmer should know. Moreover, even without any calculations some floating point values just cannot be represented. Even if they are non fractional. \$\endgroup\$
    – Eugene Sh.
    Commented Sep 23, 2016 at 18:47
  • 2
    \$\begingroup\$ @RohanSaxena: When you use a constant, the calculation is being performed on the PC rather than the target hardware. The PC has hardware to efficiently perform calculations more precisely than would be possible on a typical embedded CPU. \$\endgroup\$
    – supercat
    Commented Sep 23, 2016 at 18:49
  • 5
    \$\begingroup\$ In addition to the rounding issues, you generally want to avoid floating point math on MCUs like these. Such microcontrollers lack floating point hardware, so your C compiler will emit a lot of code to do the same calculations using the small integer hardware on the MCU. The upshot is that a "simple" floating point operation may end up costing you hundreds of bytes of program memory and may take in the order of a thousand cycles to execute. Bad if you have performance requirements. And often (like in this case), floating point is completely unnecessary. \$\endgroup\$
    – marcelm
    Commented Sep 23, 2016 at 18:54
  • 2
    \$\begingroup\$ @luchador: Compilers are allowed to, at their leisure, replace calls to standard library functions with code which will behave in a fashion consistent with how the function would be required to behave. Some implementations guarantee that floating-point computations performed at runtime will yield the same results as computations at compile time, but the Standard does not require that implementations guarantee such behavior. \$\endgroup\$
    – supercat
    Commented Sep 23, 2016 at 21:25

3 Answers 3

15
\$\begingroup\$

While this is a programming question, it is one that can turn up quite often in embedded settings, so it's not really off-topic here.

The most likely issue is indeed caused by truncation (not rounding - that does not happen).

A far better approach would be to use a bit shift operator - this will not only be more true to your intent, but faster too, as in basic circumstances it is a primitive operation directly implemented in many ALUs (though not, as pipe points out, the AVR, which can only shift one bit position at a time)

int j = 2;

PORTJ = (1 << j) - 1

(EDIT - thanks to ilkkachu for the correction)

You will find such operations all over the place in I/O macros for many microcontrollers and hardware or bitfield related code on larger systems.

(Incidentally, should you every try to use this to shift or produce a value larger than an int, look up the type rules which apply - they are not necessarily intuitive!)

\$\endgroup\$
6
  • 4
    \$\begingroup\$ "The most likely issue is indeed caused by truncation (not rounding - that does not happen)." - Rounding does happen; truncation is merely a specific way of rounding, known as rounding towards zero. \$\endgroup\$
    – marcelm
    Commented Sep 23, 2016 at 19:03
  • 3
    \$\begingroup\$ this is also much, much faster than pow(). Bit-shifting should take 1 cycle, whereas pow() requires floating point conversion, overhead from a function call, and because it needs to deal with any power the algorithm is much more complex too \$\endgroup\$
    – Jezzamon
    Commented Sep 24, 2016 at 0:55
  • 5
    \$\begingroup\$ I think what you want is PORTJ = (1 << j) - 1 \$\endgroup\$
    – ilkkachu
    Commented Sep 24, 2016 at 7:27
  • \$\begingroup\$ Ditto. The answer as currently edited does not compute the correct value for the port as compared to what the OP indicated. \$\endgroup\$ Commented Sep 24, 2016 at 15:46
  • \$\begingroup\$ The actual problem isn't related to rounding/truncating at all. 2 times 2 is obviously always 4 - an even number. So the reason why it doesn't work is floating point inaccuracy. \$\endgroup\$
    – Lundin
    Commented Sep 26, 2016 at 11:33
1
\$\begingroup\$

The problem is that the pow function is unfortunately only specified for floating point numbers. And floating point numbers have inaccuracy, see this.

So when you do 2^2 you might not get exactly 4, but perhaps 3.9999. That's the actual bug. Then 3.9999 - 1 is 2.9999. When you convert this back to an integer type to print it on the port, it will get truncated to 2.

Now as it happens you shouldn't use floating point to begin with, as using floating point numbers in embedded systems is almost always bad design. Only programs that use floating point calculations extensively should need them. For example if you do lots of trigonometry calculations, signal processing, fuzzy logic, AI or similar special-case applications.

In which case you picked the wrong MCU entirely, as an 8 bit MCU with no FPU on chip will be extremely ineffective at processing such calculations! It isn't a PC.

Just forget about float numbers. It is trivial to write an integer version of pow() yourself:

uint32_t intpow (uint32_t base, uint32_t exp)
{
  uint32_t sum;

  if(exp == 0)
  {
    sum = 1;
  }
  else
  {
    sum = base;
    for(uint32_t i=1; i<exp; i++)
    {
      sum *= base;
    }
  }

  return sum;
}

In case you don't need large numbers, you can optimize the above quite a bit by going down to uint16_t or uint8_t, which your 8-bitter MCU will enjoy far more. And as noted in other answers, anything 2^x could be solved with bit shifts.

Also, as mentioned in another answer, consider using look-up tables with pre-calculated values instead, if that's an option.

\$\endgroup\$
0
\$\begingroup\$

For this simple example I would make a table (array) of int values that match the bit width of your PORTJ. This table would have ten values indexed from 0 to 9.

Inside the loop the assignment to the port simply becomes:

PORTJ = array[j];

This will be fast and be very small code.

If the entries in the table (array) become true complex calculations do them in a spreadsheet and export the results to your C code file.

\$\endgroup\$
5
  • \$\begingroup\$ An indexed memory operation like this will actually be quite a bit slower than a bit shift in the ALU. But if the calculation were instead a different one more costly compute, this could be a good idea. \$\endgroup\$ Commented Sep 24, 2016 at 3:28
  • \$\begingroup\$ @ChrisStratton AFAIK, there's no barrel shifter in the ATmega2560, so when the index is unknown at compile-time, an indexed lookup is likely faster. \$\endgroup\$
    – pipe
    Commented Sep 24, 2016 at 4:06
  • 2
    \$\begingroup\$ Good point (And another reason not to chose an AVR). In the code in question though, the shift count is iteratively increasing so the actual fastest would be to use the single bit shift instruction once per cycle of the loop. \$\endgroup\$ Commented Sep 24, 2016 at 4:11
  • \$\begingroup\$ @ChrisStratton - The AVRs have very good index registers. When using the Data Indirect addressing mode the index registers X, Y and Z can be used to access the array. A decent compiler (IAR for one) can optimize a simple loop to make the loop very efficient. My main point was not to argue about whether the bit shift was better or not but to point out the not desirable inclusion of code heavy math computation routines inside a program on a relatively simple processor. \$\endgroup\$ Commented Sep 24, 2016 at 15:44
  • \$\begingroup\$ @ChrisStratton In view of the OP's "actual code" posted, would it be even faster to use an if instead of the modulo function, in conjunction with a single bit shift per loop? \$\endgroup\$ Commented Sep 24, 2016 at 16:29

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.