How big of numbers can I divide/multiply on an ATMega?

Go To Last Post
16 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all.  I'm using an ATMega328p, and am trying to convert clock ticks into microseconds to check the timing on certain events.  I was hoping to do it using this function:

 

#define F_CPU 16000000L
#define PRESCALER 8

int ticks_to_microseconds(int ticks) {
    return (ticks / (F_CPU / PRESCALER)) * 1000000; //microsecond is a millionth of  one second
}

Is it possible to divide/multiple numbers as big as 16,000,000 on this microcontroller?  Will there be a major performance penalty?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Tylerlee12 wrote:

Is it possible to divide/multiple numbers as big as 16,000,000 on this microcontroller?  Will there be a major performance penalty?

 

What does the data sheet say, about native opcodes ?

Anything larger than that needs to be done in software, and that is of course slower.  16M is close to 24bits, the most natural Type for that, is 32b.

Keep in mind you might want to do a 32b*32b=64b/32b operation

 

'major performance penalty' depends on your value for major

 

Try it, and measure the time required in the Simulator.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The C preprocessor precalculates the #defined constants and produces (ticks / ( 2000000 )) * 1000000;
but that will evaluate to 0 until you have more than 2000000 ticks.
(ticks is an int which means that integer arithmetic will be used)


Some rearranging to use an integer division produces ticks / ( F_CPU / ( PRESCALER * 1000000 ) )
which ends-up as ticks / 2

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
    int ret = (ticks / (F_CPU / PRESCALER)) * 1000000; //microsecond is a millionth of  one second
    int ret = (ticks / 2000000L) * 1000000; //microsecond is a millionth of  one second
    int ret = (0) * 1000000; //microsecond is a millionth of  one second

Integer division will always give you 0 (unless you really are calculating for 2000000 ticks.

If you know that F_CPU is in whole MHz,   you can use a more sensible expression:

 

    int ret = (ticks / (F_CPU / 1000000) / PRESCALER)); //microsecond is a millionth of  one second 

Since you know all the scaling numbers at compile time,  you might just as well pre-compute with floating point e.g.

    int ret = (ticks / ((F_CPU / 10000000.0) / PRESCALER)); //microsecond is a millionth of  one second

Untested.   I bet the Compiler will end up with a simple shift when you have easy numbers like 16MHz

And a simple f-p multiplication when you have 7.3728MHz that is automatically cast back to int.

 

David.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
you can use a more sensible expression:
david.prentice wrote:
(ticks / ...

 

Tylerlee12 wrote:
I was hoping to do it using this function:
Tylerlee12 wrote:
int ticks_to_microseconds(int ticks) {

 

The fragment posted is supposedly code for an AVR target, right?  And given the mention of F_CPU, the toolchain is GCC?  [note to OP:  Tell target CPU model as well as toolchain and related information when posting a query]
 

What I'm getting at is that "int" is going to be 16-bits, signed, on all mainstream C toolchains with an AVR8 target.

 

[why is signed being used anyway?]

 

Have you consulted standard C texts about "short" and "long" and "unsigned"?  Are you aware of the platform-agnostic type names in stdint.h ?  And how these compare to the entries in limits.h ?  And what the documentation on your toolchain might have to say?

http://www.nongnu.org/avr-libc/u...

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When you write expressions like:

(ticks / (F_CPU / PRESCALER)) * 1000000

you should always try to make life easy for the MCU and the compiler. Avoid divisions.

 

(ticks * (PRESCALER / F_CPU )) * 1000000

 

Another problem: these number are integers, not floating point. When you do

(PRESCALER / F_CPU )

the result is an integer and will be truncated to zero. Then, the whole expression will be zero. So, another change is needed:

(ticks * (PRESCALER * 1000000 / F_CPU ))

 

Now the compiler can pre-calculate

(PRESCALER * 1000000 / F_CPU )

and everything will work fine.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I said earlier. Untested.
But if you alter the parentheses, the Compiler will probably give you what you want.
As it stands. (ticks/2000000L) will be evaluated with long integer maths. Any value of ticks < 2000000L will evaluate to 0.
.
People may be horrified by f-p scaling. But in practice it does not take many cycles or Flash memory. And in your case, with proper parentheses will probably optimise the f-p to an integer shift.
.
Easy enough to test in the Simulator. I am not at a PC.
I have used similar scaling macros to time sequences with different F_CPU values e.g. baud friendly but math nasty.
.
David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
As I said earlier. Untested.

 

So you did. I should have refreshed the page cheeky

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks to you and david (and everyone else)!  To answer some questions posed to me earlier in the thread, my target is an ATMega328p and I am indeed using the GCC toolchain.  Follow up questions of my own:

 

  1. Why is division more difficult for the MCU and compiler?
  2. Testing things in a simulator has been brought up several times in this thread, and I think that's a great idea.  What simulator do you all generally use?  simavr on Github looks nice but if there's a better one I'd love to know.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As you probably remember from school, the algorithm for division is more complicated than the one for multiplication. So it's harder for a machine, the same way it's harder for a human. And in turn, for the ATMega, multiplication is harder than logic/addition/subtraction/shift by 1 bit.

 

So, for example:

(a+b) * (a-b)

is usually faster than

a*a - b*b

While you are programming, you should do these simple optimizations when possible, it takes little effort. Sometimes, the compiler will optimize for you, sometimes it won't, so I like to give it an hand.

 

Regarding a simulator, Atmel Studio comes with a good one, but it only runs on windows, so if you have a Mac or Linux system that is a problem.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ahhh...(PRESCALER * 1000000 / F_CPU ) evaluates to zero.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, any result between 0 and 1 will be truncated to zero.

So the parenthesis need to be removed.

(ticks * PRESCALER * 1000000 / F_CPU )

 

Last Edited: Mon. Aug 14, 2017 - 01:00 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Constant expressions are evaluated at compile time, therefore 8 * 1000000 / 16000000 becomes 0.5 which when converted to an integer becomes 0

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When in doubt, test.

 

#include <avr/io.h>

int main(){
	return (PORTD * 8 * 1000000 / F_CPU );
}

 

compiles to:

 

#include <avr/io.h>

int main(){
        return (PORTD * 8 * 1000000 / F_CPU );
  7a:   8b b1           in      r24, 0x0b       ; 11
  7c:   90 e0           ldi     r25, 0x00       ; 0
}
  7e:   96 95           lsr     r25
  80:   87 95           ror     r24
  82:   08 95           ret

 

Therefore, a division by 2 is being executed.

 

With parenthesis

#include <avr/io.h>

int main(){
	return (PORTD * (8 * 1000000 / F_CPU) );
}

compiles to:

 

#include <avr/io.h>

int main(){
        return (PORTD * (8 * 1000000 / F_CPU) );
  7a:   8b b1           in      r24, 0x0b       ; 11
}
  7c:   90 e0           ldi     r25, 0x00       ; 0
  7e:   80 e0           ldi     r24, 0x00       ; 0
  80:   08 95           ret

The compiler must keep the order of operations according to precedence. Multiply and division have the same precedence, so the constant expression can't be evaluated first just because. That's why the result without parenthesis isn't zero, the multiplies are executed first, so a fractional result is never formed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I stand corrected and have performed some tests.
My memory seems to have retained some old and incorrect information.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you're willing to code your own multiply and divide routines, the size of number is limited only by available memory. 

274,207,281-1 The largest known Mersenne Prime

Measure twice, cry, go back to the hardware store