Preprocesser computation

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

So this is probably a really basic C question, but it's been a few decades since my last C class.

 

I have some values used in my code that are the results of formulas.  All of the variables are known at compile time, so I'd like them to be evaluated by the preprocessor, to avoid having them in the code (for both size and speed reasons).  For example, I have a bit of code that uses a timer in CTC mode. Therefore, I need to set the OCR2A register to the CTC value.  The formula is (1/freq)/(1/F_CPU/prescaler)-1.  Now, in my particular case, it's easy enough to compute it and just say #define CTCVAL=25.  Works fine, but not very portable across different chips.

 

I'd like to do something like

#define CTCVAL = (1/FREQ) / (1/F_CPU) / PRESCALER) -1

but that doesn't compile.  Doing something like:

uint8_t ctcval= (1/FREQ) / (1/F_CPU) / PRESCALER) -1;

OCR2A=ctcval;
would work (with some more casts), but that means evaluating the expensive divisions at run time, and as this code is run often (I'm changing the CTC values of the fly based on another time), it seems very inefficient.

The other solution is something like

#if F_CPU = 80000000

#define CTCVAL 25

#ELSEIF F_CPU=16000000

#define  CTCVAL 13

etc.

 

but that's not easy to maintain.

 

So is there a way to do the math in the preprocessor?  Ideally without ending up linking in the floating point libraries?  Not just for this computation, either, but more generally.

 

Thanks.

This topic has a solution.
Last Edited: Tue. Jan 23, 2018 - 02:39 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgalak wrote:
it's been a few decades since my last C class.

But plenty of 'C' references are still available - online and in book form

 

All of the variables are known at compile time

Remember that the preprocessor knows nothing about variables.

 

Also remember that the preprocessor only does integer maths.

 

#define CTCVAL = (1/FREQ) / (1/F_CPU) / PRESCALER) -1

but that doesn't compile. 

What do you mean, "doesn't compile"?

 

If you're getting an error message, then you need to post that message!

 

 

Doing something like:

uint8_t ctcval= (1/FREQ) / (1/F_CPU) / PRESCALER) -1;

OCR2A=ctcval;

would work (with some more casts), but that means evaluating the expensive divisions at run time

Conventionally, things with ALL UPPERCASE names are #defined constants - so that expression should be computable at compile time.

 

But note that the divisions will be done with integer maths - so are probably going to be zero

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Calculation with known values is something done by the C compiler (optimiser) rather than the pre-pro. So if you write:

int main(void) {
    dest = 13 * 57;
}

then the multiplication will be done at compile time so this simply sets dest to be 741. Sure you can dress this up as:

#define SOME_VAL 13
#define OTHER_VAL 57

int main(void) {
    dest = SOME_VAL * OTHER_VAL;
}

if you like but the preprocessor does nothing but substitution here so what the C compiler sees is still:




int main(void) {
    dest = 13 * 57;
}

and once again the C compiler precalculates this for you. What it cannot do of course is:

int main(void) {
    dest = 13 * *(uint8_t *)0x57;
}

in this case this involves a read from memory location 57 at run time so the multiplication by 13 has to happen in code at run time too. But as long as values are known to the compiler at compile time it will perform the operation. Even complex stuff like:

dest = 13 * sin(0.707);

All that is known at compile time so it can all be precalculated.

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

Try:

#define CTCVAL  ((1/FREQ) / (1/F_CPU) / PRESCALER) -1)

(Assuming FREQ, F_CPU and PRESCALER are defined)

 

#defines do not need the =

 

Then just use CTCVAL where needed...

 

Edit:

In my code, I sometimes do (for 8-bit timer0 in Fast PWM mode):

#if ((((F_CPU / TIMER0_PRESCALE) / TIMER0_TICKS_PER_SECOND) - 1) > 255)
#error Invalid Timer0 Count
#endif

And then do in the timer0 setup:

OCR0A = (((F_CPU / TIMER0_PRESCALE) / TIMER0_TICKS_PER_SECOND) - 1) ;

The error message is displayed if I have chosen values that are out of bounds for a particular timer...

David (aka frog_jr)

Last Edited: Tue. Jan 23, 2018 - 02:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Calculation with known values is something done by the C compiler (optimiser) rather than the pre-pro. So if you write:

int main(void) {
    dest = 13 * 57;
}

 

Aha!  That's the bit I was missing.  I was focusing on the preprocessor, and forgot about the compiler optimization.  Yes, I believe that'll work.

 

Just to be clear, even if floating point math is used, it'll still not link in the floating point libraries if all the floating point math is done at compile time?

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

And yes, having poked into the disassembled view of the code, it looks like it does just that. 

 

I've used the following line:

 

	OCR2A = (uint8_t) ((1.0/BAUDRATE) / (1.0/(F_CPU/PRESCALER)) - 1

and got the following disassembled code:

 

00000143  LDI R24,0x19		Load immediate 
00000144  MOVW R30,R20		Copy register pair 
00000145  STD Z+0,R24		Store indirect with displacement 

So the result of the computation, 0x19, is clearly "hardcoded" into the assembly code.

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

awneil wrote:
the preprocessor only does integer maths

While this is true, as the others have pointed out, it is not relevant here!

 

blush

 

The only place the preprocessor does arithmetic is in evaluating conditions; eg,

#define ONE 1
#define TWO 2
#define THREE 3

#if (ONE + TWO) == THREE

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What I do not see is a definition for PRESCALER. Where does that come from? 

 

And, as previously noted, equal signs are not used as part of a # define statement except for conditionals, as awneil pointed out, immediately above.

 

Integer arithmetic IS an issue, here, because the original statement was written

 

#define CTCVAL = (1/FREQ) / (1/F_CPU) / PRESCALER) -1

What are (1/FREQ) and (1/F_CPU) when evaluated using integer arithmetic? Of course, the equation can be rewritten to avoid that construction, but, as written, it will not give useful results.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

ka7ehk wrote:
What are (1/FREQ) and (1/F_CPU) when evaluated using integer arithmetic?
Curiously in #6 these mysteriously became calculations such as:

OCR2A = (uint8_t) ((1.0/BAUDRATE) / (1.0/(F_CPU/PRESCALER)) - 1

I guess we now know why 1/N's became 1.0/N's

 

Of course (as long as N is known at compile time and optimisation if enabled) you can do this without "cost" as Constant Folding means the compiler will just gobble this all up in its stride.

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

ka7ehk wrote:
as written, it will not give useful results.

See #2 - last line

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#define CTCVAL  ((1/FREQ) / (1/F_CPU) / PRESCALER) -1)

should become, avoiding divides by zero (and is as easy to read)

#define CTCVAL  ((F_CPU/FREQ)) / PRESCALER) -1)
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, I know I don't need an equal sign in the #define. Was working from memory on that one. I make that typo all the time.

PRESCALER is #defined elsewhere, I didn't show that line.

And yes, I realized that I needed to do 1.0 to force floating point arithmetic. Didn't have it in the initial version as I knew that the preprocessor didn't do floating point math.

The version in comment #6 has now been tested and works just fine. Thanks for the help, everyone.

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

jgalak wrote:
Yes, I know I don't need an equal sign in the #define. Was working from memory on that one. I make that typo all the time.
???  If that is well known to you, then why did you say
jgalak wrote:
but that doesn't compile.
referring to the #define immediately preceding with the assignment operator in it?

 

Never mind, I guess; you have moved on.

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

It wasn't the equal sign that wasn't compiling - like I said, I was entering the post from memory, the actual code didn't have it.  It was something else - something I wrote triggered a float operation in a #define.  I don't have that line of code in front of me to get the exact error.

 

Regardless, as you've said, I've moved on.  I now understand how to accomplish what I wanted, and the code is, in fact, working great.

 

Thanks, everyone.

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

But a good example of why one should always copy & paste code - rather than manually re-type into a post ...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

True

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#define CTCVAL = (1/FREQ) / (1/F_CPU) / PRESCALER) -1

There are a variety of issues with this code:

1) The C preprocessor works in integer, so 1/most anything will cause problems.

2) The code has unbalanced parenthesis.

3) FREQ, F_CPU and PRESCALER are not defined  Assuming that they are positive, the result would be a fraction and round down to zero.

4) Possible sign extension problems.

5) There is no assignment '=' as past of the 'C' preprocessor syntax.

 

The unix C preprocessor 'cpp' works with arbitrary size and complexity.

Presumably, the compiler you are using also works with arbitrarily complex equations.

 

Consider keeping the math in the positive integer domain.

#define F_CLK 32000000

#define TARGET_FREQ 400000

#define PRESCALER 4

#define X (F_CLK/TARGET_FREQ/PRESCALER)

X should evaluate to the number of clocks per TARGET_FREQ.
 

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

Have a look at <util/delay.h>

It has some fairly extensive calculations for delay loops based on the #define'd value of F_CPU.

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com