[En-Nut-Discussion] Using boost::preprocessor (preprocessor metaprogramming)
duane ellis
ethernut at duaneellis.com
Wed Feb 11 13:20:27 CET 2009
thiago> [earlier] [ use of boost macros ]
thiago> It also follows the DRY principle
Hmm... I'd disagree, macro expansion on this scale is repeating your self.
I see value in the BOOST macros, *WHEN* things need to be independent
functions.
That is not the case here.
There is an old saying a fellow developer I worked with had - it is a
great and wonderful way of looking at it.
Do not show me your code.
Show me your data structures
The data structures will explain your code.
I don't know where it came from.
henrik> [earlier] Maybe some smaller examples would help ....
thaigo> GpioPinSet(SBBI0_CS0_PORT, SBBI0_CS0_BIT, hi);
thaigo> GpioPinSet(SBBI0_CS1_PORT, SBBI0_CS1_BIT, hi);
thaigo> GpioPinSet(SBBI0_CS2_PORT, SBBI0_CS2_BIT, hi);
thaigo> GpioPinSet(SBBI0_CS3_PORT, SBBI0_CS3_BIT, hi);
Why is this not table driven? or parameterized, maybe pass a pointer to
a structure with this sort of information in the structure, or something
like that?.
Instead of 5 functions: uart1_write() through write5_write(),
int
uartX_write( int who, int c )
{
UART_REGS *p_uart = uart_pointer_table[ who ];
..... code .....
}
That "uartX_write()" function is functionally identical to: fputc(), by
doing it that way, one can support any number of "open files" or in this
example, uarts, you can make legacy code work - create uart0_write(),
that calls uartX_write() passing the correct parameter for "X".
Please don't mis-understand me - having the "uart_write()" [which calls
uartX_write()] makes sense if you have a very common UART that is used
for everything. [See parameter passing code size reduction below]
In your example, instead of "boiler plate code" inside a switch
statement, why is it not parameterized into a table like this:
GPioSPi0ChipSelect( uint8_t cs, int hi )
{
if( cs < SOME_VALUE_N_PINS ){
GpioPinSet( foo[cs].pin, foo[cs].bit, hi );
GpioPinConfigSet( foo[cs].port, foo[cs].bit, GPIO_CFG_OUTPUT );
return foo[cs].gspi_reg_ptr;
} else {
errno = EIO;
return NULL;
}
}
More importantly, these are embedded devices. The above TABLE driven
approach I believe results in a smaller code foot print then the BOOST
macros (which run on huge machines that can manage bloat).
Lastly, when ever I see code that calls several functions in a row, it
*screams* refactor to me, for example the two GPIO functions *SCREAM*
for a function that perhaps looks like this:
GpioSetOutput( int PIN_ID, int VALUE )
Which - from the PIN_ID calculates the "port" and "bit" value, and sets
the direction - thus resulting in even smaller code, for example:
"PIN_ID div 8" = port number, (1 << (PIN_ID mod 8)) = bitnumber. Or is
table driven. That function would of course - both *SET* and *CONFIG*
the pin in the proper way.
I'll bet those two functions are used over and over again in pairs in
other places in other places in the code. This type of refactoring would
also reduce overall code size even more.
Another small item - by passing less parameters to a function, the
overall code size shrinks. There is less 'setup' required before each
function call. And less "messing with parameters" in the function entry
code. There is also less run time stack usage. In this case, I believe
you are porting to an avr32 platform, perhaps passing a larger parameter
(which costs nothing extra in code space) is another approach. The
underlying machines here is not 8 bit like an 8051.
Lastly: maybe there is something about the chip I don't know. Is there
a specific reason why the pin must be reconfigured every time you set it
as an output? That seems odd, and a waste of time. If true, that change
would improve overall speed/performance - yes, only slightly.
These are many little things that add up - here in the US - I'd use the
phrase: "The straw that broke the camel's back"
http://en.wikipedia.org/wiki/Last_straw
-Duane.
More information about the En-Nut-Discussion
mailing list