tags:

views:

194

answers:

7

in PHP, I have something like

function doStuff($in, $value)  
{  
   $var = "V_" . $in;  
   $$var = $value;
}

Is there a way to do something similar in C?

Basically I'm trying to figure out how to make a sort of library to make working with IO pins on an AVR easier. So for example, there would be a function to set a particular pin to an OUTPUT. That pin in the AVR is part of PORTB. Setting it to an output and giving it a value requires me to reference DDRB and PORTB constants and set their values. Rather than going through all of that, I'd like to be able to call a function such as SetMode(Pin #, Mode);. I just can't figure out how to do that.

+2  A: 

in short, no, there aren't variable variables in C. what you could do is make some type of hashmap of the variables, with the names as the key, and use that.

GSto
A hashmap wouldn't work as I am trying to access defined constants, not retrieving a particular value.
Bocochoco
Make a constant array and initialize it to contain those constants. There aren't very many after all.
Ben Voigt
A: 

When you say pin # you are referring to the actual pin number on the physical chip right ?

if it is. You could do this.

1- create a map function that takes in the pin number and returns and corresponding PORT and PIN

ex.

You want to access pin #1 on the chip

SetMode( int pinNumber, char mode ) {

    typedef struct  {
        int pin;
        int port;
    }pinValues;


    pinValues pinStruct;
    mapPin( &pinStruct, pinNumber );  // this resolves the pin # on the chip to a port            
    // and pin.
    GPIO_init( pinStruct, mode ); // this initializes the pin;
}

the mapPin function should be pretty simple just create one array containing the pin numbers

ex.

say the chip has only 4 pins

const char GPIO_pin[5] = { 1,2,3,4 };

and create a struct for the port and pin corresponding to each pin #

ex

typedef struct {
  int pin;
  int port;
}pinPort;


pinPort pinPortStruct[5] = { (PORTA,0), (PORTA,1), (PORTB,1), (PORTB,1) };

so pin # 1 corresponds to PORTA 0

so what you do is simply search though GPIO_pin and then return the struct that corresponds to that index

for( int i = 0;i <4; i++)
{
 if( pin == GPIO_pin[i] )
     return pinPortStruct[i];
} 

I hope this is what you need.

jramirez
+1  A: 

All AVR registers have addresses. You can use the addresses for implementing generic functions.

Peter G.
+2  A: 

Preprocessor definitions or macros are typical ways of accomplishing your desired goal in C.

Domingo Galdos
A: 

Depending on how many pins/ports you are talking about, it might be easiest to use a case statement:

void SetMode(int pin, int mode) {
    switch (pin) {
        case PIN_A:
            DDRA = mode;
            PORTA = mode;
            break;

        case PIN_B:
            DDRB = mode;
            PORTB = mode;
            break;
        ...
    }
}

The constants PIN_A, PIN_B, etc can be defined through #define macros or an enum. One advantage to this approach is that you can refer to all of your ports/pins using similar notation even if you have to treat some of them differently than others (each case can be different). If you have a large number of pins/ports to deal with, then this might not be the optimal approach.

bta
+4  A: 

Your question is still a little unclear (as indicated by the assortment of interpretations in the answers). I'm assuming that you want to refer to pins by physical pin number. If this is not correct, please clarify your question so we can provide better answers.

Here's roughly how I would do it if someone held a gun to my head:

DISCLAIMER: I have not tested this nor been particularly careful about checking documentation. The code is written for avr-gcc/avr-libc on Linux, though it may work elsewhere.

// Map from physical pin number to associated direction register.
volatile uint8_t *ddr_map[] = {
    NULL, // Vcc, GND, or some other non-IO pin.
    &DDRB,
    &DDRB,
    &DDRC,
    // etc...  Values will vary for different target chips.
};

// Map from physical pin number to port mask.
uint8_t mask_map[] = {
    0x00,
    _BV(0),
    _BV(1),
    _BV(0),
    // etc...  Values will vary for different target chips.
}

typedef enum {
    IN,
    OUT
} PinDir;

void setMode(int pin, PinDir dir) {
    if(dir == OUT) {
        *ddr_map[pin] |= mask_map[pin];
    } else {
        *ddr_map[pin] &= ~mask_map[pin];
    }
}

See http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_port_pass

And here's why it's not a good idea:

  1. It doesn't abstract away any meaningful behavior (it actually removes abstraction -- the physical pin number is lower level than the logical port/pin). Moreover, the physical pin number is not necessarily the same for different package formats. The pins of PORTB may not be assigned to the same physical pin numbers on a QFP package as a PDIP package. So this code is actually more confusing.

  2. It adds overhead. You have an extra function call (which costs cycles and stack) and two (or more) arrays used for lookups (which cost flash and RAM on the AVR unless you take special measures, in which case they cost extra cycles and flash or EEPROM) not to mention all the indirections (array lookups, pointer dereferencing) and the extra compare and branch. In desktop & web development you would be right to laugh at my concern over such small costs, but on AVR that waste has considerably more impact. (NOTE: You might be able to convince the compiler to optimize some of this out, but if you are using -Os it will be difficult. And now you're worrying about even lower level details than before...)

  3. The provided means of manipulating pins is not so complicated as to be worth hiding in this way. You should get comfortable with converting between hexadecimal and binary in your head (it's not hard). Even if you don't want to mess with hex, the _BV() macro makes pin manipulations pretty easy (or just use (1 << x) which is more portable and will be recognized by more programmers).

By the way, PORTB, DDRB, etc. are not constants. They are variables that are tied to specific addresses or registers. Trying to modify a constant with something like CONST_THINGY |= 0x03 would produce a compiler error.

Variable variables

C does not have the feature you described. It is a low level language (it is sometimes described as "high-level assembly") that doesn't provide many fancy features (by today's standards). This is why it is the language of choice for AVR -- you want to be close to the hardware, and you don't want lots of extra overhead.

What C does have is pointers. Based on your question and comments I would guess that you aren't very familiar with them, so here's a quick explanation:

  • The & operator returns a pointer to a variable, and is used like this: pointer = &variable;
  • * actually has a couple of uses.
    • The first is declaring a pointer variable (i.e. a variable that holds a pointer instead of an int, char, or float): int *pointer; Notice that you have to specify what type of variable it will point at.
    • The second use is what is called dereferencing a pointer. Basically, this means accessing a variable through the pointer. If pointer points at variable, *pointer = 42; will set variable equal to 42, and other_var = *pointer will set other_var to the value of variable.
  • There is also pointer arithmetic, but that's beyond the scope of this answer.

The point of all this is that you can effectively treat variables themselves like values, storing them and passing them around. You can't really modify them in any meaningful way other than manipulating their value, but you don't need to either.

Steve S
Outstanding!tl;dr: C is not PHP, not by a long shot, even if they both have curly brackets. To use C effectively, learn C.
Domingo Galdos
A: 

For the generic case, pointers are as close as you get. C does not necessarily have any concept of names at runtime, particularly on a microcontroller (some names typically exist in an OS with dynamic linking, but even there it's not required).

For the pin number scenario, lookup tables to figure out the port, bit in port, and so on for any given number can work. That is the technique employed by Arduino, which attempts to abstract away C++ programming on the AVR. They like renaming things, for instance calling PWM signals "analogWrite", C++ "wiring" and programs "sketches", and all I/O pins are numbered after their position on the development board. The downsides are the massive confusion as soon as you're programming something other than that first board, and having to figure out the side effects buried in their library when you want to do something low level.

Yann Vernier