views:

154

answers:

5

I'm writing firmware in C for an embedded processor. I want to have all the configuration information in one header file called config.h. This is causing problems with the ADC initialization, where simple #defines won't do the trick. Right now the code is like so:

config.h

#define NUMBER_OF_POTS  1
#define POT_1_CHANNEL  27

adc.c

#define MAKE_CSS(channel) _CSS##channel
#define CALL_MAKE_CSS(channel) MAKE_CSS(channel)

void initialize_adc() {
   CALL_MAKE_CSS(POT_1_CHANNEL);
}

What I want to do is not have to touch adc.c if I change config.h to:

#define NUMBER_OF_POTS  2
#define POT_1_CHANNEL  27
#define POT_2_CHANNEL  29

adc.c should just automatically add a second CALL_MAKE_CSS with some macro trickery.

I guess the question is: is there a trick that gives you for loop capability with a macro?

Thanks,

Steve.

+1  A: 

You don't have to rely entirely on macros. Just define your 'magic numbers' as #defines.

For example:

In config.h:

#define NUMBER_OF_POTS 2
#define POT_1_CHANNEL  27
#define POT_2_CHANNEL  29

unsigned int PotChannelList[NUMBER_OF_POTS] = {POT_1_CHANNEL, POT_2_CHANNEL};

In adc.c:

for(i = 0; i < NUMBER_OF_CHANNELS; i++)
{
  initialize_adc(PotChannelList[i]);
}

You still define the setup in config.h and don't have to change adc.c when you add a channel. You just add it to the list. The list order also defines the order of initialization.

EDIT: Sorry about the formatting mess...

cschol
I second the content of the answer, but it could need a bit of reformatting...
gimpf
There's a slight problem with this solution: any file including config.h will have the PotChannelList variable. If more than 1 compilation unit includes it, there will be a linking error because the name is defined more than once. A solution is to make it static, but then it's duplicated everywhere.
João da Silva
not to be pedantic, #defines are macros :p
hhafez
@hhafez: yes, but variables and functions should be declared in headers and defined in source files, independently of whether macros are involved. It is crucial to understand the difference between declaring a variable or function and defining it. With variables, you get tentative definitions too.
Jonathan Leffler
A: 

Have a look at boost.preprocessor. Although boost is usually for C++, the preprocessor metaprogramming lib works, well, just with the CPP, so it may do what you want. It provides a few datastructures (lists, tuples) and iteration macros.

Sorry, I can't give you any example if it really does what you want, or at least provides another way, because I seldom needed it, and it's too long ago.

Note Just saw Schroeder's answer. Not relying on the PP if it is not necessary is still the best option...

gimpf
I had a good long look at boost.preprocessor once upon a time (I was considering using it specifically to get macro looping). I don't recommend trying to make use of it. The tricks used to get 'loops' in macros are just too strange to leave in code that someone else will have to work with someday.
Michael Kohne
+3  A: 

I didn't test this:

// config.h

#define NUMBER_OF_POTS  2
extern int pots[];

// config.c

int pots[NUMBER_OF_POTS] = {
    27,
    29
};


// adc.c

void initialize_adc() {
    for (int i = 0; i < NUMBER_OF_POTS; i++) {
        CALL_MAKE_CSS(pots[i]);
    }
}
João da Silva
I'd make the pots[] array static within adc.c. Why create a global variable and a new config.c file ?
Bill Forster
You could also define "int pots[] = {..}" and make NUMBER_OF_POTS a compile-time calculation:#define NUMBER_OF_POTS (sizeof pots/sizeof(pots[1]))
Craig S
The new file may not be necessary; whether it makes sense to make the array global depends on where else it might be used, and we don't have enough code to know that. However, it is quite plausible to think that there might be other places where it would be used; equally, they might all be in adc.c.
Jonathan Leffler
Craig S: Good idea but better make that #define NUMBER_OF_POTS (sizeof pots/sizeof(pots[0/*not 1*/]))
Bill Forster
Thanks for the help guys. I'm going to put the pots[] array in config.c and put the extern in adc.c. That way I don't have to change adc.c at all for different products, but it's the only file with access to the array.
Steve
A: 

The C preprocessor cannot do loops. You'll either have to do the looping in C code, or if you really need to do something loop-like at compile time, you can write your own preprocessor (which can just be a simple shell script, e.g.) that generates the necessary code.

Adam Rosenfield
A: 

Although you can't do loops with the preprocessor, you can do unrolled loops. So if you know you're never going to have more than 4 pots you could do this;

void initialize_adc() {
  #if NUMBER_OF_POTS > 0
    CALL_MAKE_CSS(POT_1_CHANNEL);
  #endif
  #if NUMBER_OF_POTS > 1
    CALL_MAKE_CSS(POT_2_CHANNEL);
  #endif
  #if NUMBER_OF_POTS > 2
    CALL_MAKE_CSS(POT_3_CHANNEL);
  #endif
  #if NUMBER_OF_POTS > 3
    CALL_MAKE_CSS(POT_4_CHANNEL);
  #endif
}

The only benefit of this compared to other solutions here is that there is no runtime overhead at all. Extra inline code "magically" appears if and only if another channel is added, just as the questioner wanted. To extract the ugliness from within the function call (at the cost of putting it earlier in your code instead), define 4 new macros each using the same #if NUMBER_OF_POTS > x technique. Then you'd be able to go simply;

void initialize_adc() {
  INIT_CSS_1();
  INIT_CSS_2();
  INIT_CSS_3();
  INIT_CSS_4();
}
Bill Forster