Go Down

Topic: Macro Versions of Arduino Functions (Read 1 time) previous topic - next topic

zephyr

I have noticed that the majority of Arduino sketches tend to use statically defined values for arduino pins.  In these cases it would be possible to define the port and bit mapping at compile time using macros in place of actual c code that executes at runtime.  This could result in code size and execution time savings.

mellis

Sounds like it could be useful.  Do you have an idea of how it would be implemented?  Would you need a different syntax or could have you a "smart" macro that could tell if it had a compile-time constant or not?  

In any case, I think we're considering adding support for (documenting really) the PORTx, DDRx, and PINx registers, which would provide fast i/o with macros and no overhead for looking up pin numbers in an array at runtime.  Would that be a good solution for your uses?

zephyr

If the compiler could figure out if you were using a variable or a define, then it could either expand the macro or add the call to the existing function.  Otherwise you'd need another set of names for the macro versions.

My usual practice is to just bypass pinmode, digitialread and digitalwrite and go directly to the registers.

westfw

#3
Aug 07, 2007, 01:53 am Last Edit: Aug 07, 2007, 02:08 am by westfw Reason: 1
Which functions did you have in mind?  I looked slightly at making the
"core" ardino functions smaller, but got hung up on the relatively
large amount of functionality in those functions.  digitalWrite(),
for instance, automatically checks whether the pin is currently
in PWM mode (and fixes it if so.)  Merely specializing code for
constant pin numbers wouldn't get very far.

Elsewhere, someone points out that gcc implements
  __builtin_constant_p()
that allows code to tell whether its argument is a compile-time
constant or not.  So in theory you can do things like:
Code: [Select]
#define set_bit(port,bit) \
 ( (__builtin_constant_p (port) && __builtin_constant_p (bit))  \
    ? sbi(port,bit) : set_bit_func(port,bit) )

And the compiler will evaluate the compile-time constant
expressions and omit the unused code.

(of course, by the time you implement a "new" set of functions
with different capabilities, you might as well use the lower-level
features built into avr-gcc to start with.  There's a lot of value
to using the standard arduino/wiring functions despite their
relatively large size (and the closer your program gets to filling
the full 8/16k in the chip, the less relevant the space used by
non-macro versions of simple functions is, anyway.))

mellis

That __builtin_constant_p macro is pretty cool, though you're right, I'm not sure it makes sense in this case.

zephyr

My question was really motivated from a philosophical standpoint.  In the interest of making the wiring code as user friendly as possible (not needing to know port mappings) while at the same time trying to minimize the speed and code size penalties for using the wiring code.  As far as deciding whether to disable pwm, this small overhead (i.e. inline assembler code) should be negligible and could be kept in both macro and runtime versions.

A lofty goal, (for someone who has a lot of free time) would be to write all of the core code in inline assembler, much savings in speed and size.  But I guess the ultimate question there, why not just code in assembly language to begin with?

westfw

Quote
A lofty goal, would be to write all of the core code in inline assembler, much savings in speed and size.


I think you underestimate the compiler.  You might get smaller, but I have my doubts about "MUCH" smaller.  At least, not if you want to keep equivalent functionality.   I looked at some of the "core code" (ie object code from led_blink), and first of all, there isn't all that much of it.  Second, the parts that "annoyed" me from an efficiency point of view had more to do with the fact that (for example)  delay() takes a LONG as it's argument than overly bloated code produced by the compiler (and then there was the harsh reminder that the AVR instruction set isn't all so wonderful as it looks at first glance; sbi/cbi can only be done in a single instruction on SOME ports.  And similar.  Sigh.)  Third, that overhead mostly occurs only once, and the optimization done in 0008 got rid of a bunch of the obvious "extra" stuff.  75% or more of the code memory is available for user programs, which is pretty good.  And the mega168 is in the wings to more than double the available code space.  I'd like the bootloader to be smaller, but I don't think the chances are great that it would fit in 512 bytes (which is the next "quantum" size a bootloader could be.)  All the serial bootloaders I  found in a quick glance around the net were happy to fit in 1024 bytes...
Quote
But I guess the ultimate question there, why not just code in assembly language to begin with?

Not just that, but there's also the intermediate step of avoiding the "arduino core" and sticking to lower-level C functions (or macros) instead.  I think the ardurino "core" functions do a pretty good job of being powerful enough to provide useful simplification to the users, while being small and efficient enough not to be a burden.

kg4wsv

Don't forget that function calls actually reduce code space if the function is called very much (actual value of "very much" is a function of the size of the code in the function).

I recently thought I'd save some space by inlining a function that was nothing more that two other function calls.  This function was called several times throughout my program, and the end result was I went from a couple of dozen bytes under 7k (ATmega8) with the function call to several dozen bytes over 7k with it inlined.

-j

westfw

Heh.  It follows that heavy users of "delay()" should write a version that uses shorter arguments AROUND the standard delay() function.  For example, the following modification of led_blink() compiles to less code when it uses the delaysec() subroutine rather than the delaysec() macro, and it's only got six calls to delay!
Code: [Select]
#if 1
void delaysec (unsigned char seconds)
{
   delay(1000*seconds);
}
#else
#define delaysec(s) delay(s*1000)
#endif

int ledPin = 13;                 // LED connected to digital pin 13

void setup()
{
 pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}

void loop()
{
 digitalWrite(ledPin, HIGH);   // sets the LED on
 delaysec(1);                  // waits for a second
 digitalWrite(ledPin, LOW);    // sets the LED off
 delaysec(1);                  // waits for a second
 digitalWrite(ledPin, LOW);    // sets the LED off
 delaysec(2);                  // waits for a second
 digitalWrite(ledPin, LOW);    // sets the LED off
 delaysec(2);                  // waits for a second
 digitalWrite(ledPin, LOW);    // sets the LED off
 delaysec(3);                  // waits for a second
 digitalWrite(ledPin, LOW);    // sets the LED off
 delaysec(3);                  // waits for a second
}

Go Up