Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C: Pass two comma separated values with a single #define

The Problem

Programming re-usable modules for microcontrollers (AVR in my case) requires flexibility in general IO pins. Each pin defined by a letter (A-G) and a number (0-7). It is, however, controlled by one bit at the same positon in three registers. Thus the configuration file needs to contain four entries (3 pointer to the registers + 1 position), which is not that elegant.

The easy fix is to simply accept this, but since it is such a common problem it deserves at least a little attention.


The Idea

It would be nice to let the precompiler do the repetitive work like this:

//CONFIGURATION
#define IO_NAME B5

//MACRO
#define PORT_(ID)
#define PIN_(ID)
#define DDR_(ID)
#define BIT_(ID)

The results should look like this

PORT_(IO_NAME)   =>  PORTB
PIN_(IO_NAME)    =>  PINB
DDR_(IO_NAME)    =>  DDRB
BIT_(IO_NAME)    =>  5

The resulting expressions are defined in AVR Studio.


What I've Tried

I couldn't figure out how to ignore either the letter not the number, so I tried concatention instead:

#define PORT_(REG, BIT)             PORT_2(REG, BIT)
#define PIN_(REG, BIT)              PIN_2(REG, BIT)
#define DDR_(REG, BIT)              DDR_2(REG, BIT)

#define PORT_2(REG, BIT)            (PORT ## REG)
#define PIN_2(REG, BIT)             (PIN ## REG)
#define DDR_2(REG, BIT)             (DDR ## REG)

#define BIT(REG, BIT)               (BIT)

The extra layer is required to use any #defined value as either REG or BIT.

The following code works as intended:

#define IO_NAME_REG B
#define IO_NAME_BIT 5

PORT_(B, 5)                           => PORTB
PORT_(IO_NAME_REG, IO_NAME_BIT)       => PORTB

When I however try

#define IO_NAME B, 5
PORT_(IO_NAME)

it results in the Error:

macro "PORT_" requires 2 arguments, but only 1 given

As far as I figured out the comma is interpreted as the comma operator and thus the left value is ignored. I concluded this from this experiment:

#define IO_NAME B, 5
PORT_(IO_NAME,)                      => PORT_5

Replacing the comma with a define:

#define comma ,
#define IO_NAME B comma 5
PORT_(IO_NAME,)                      => PORT_5

leads to the same result


Workaround

Of course it is possible to seperate the 'B' and '5' into two seperate defines. While this is an improvment over four defines it's still not that comfortable.


The Question

... is not stricly limited to the title. Any solution which is shorter or simpler than

#define IO_NAME_REG B
#define IO_NAME_BIT 5
PORT_(IO_NAME_REG, IO_NAME_BIT)       => PORTB

is welcome to me.

If this is simply not possible I'd like to know the reasons why.

like image 700
nqtronix Avatar asked Apr 17 '16 14:04

nqtronix


1 Answers

This should do what you want:

#include <stdio.h>

#define IO_NAME B, 5

#define PORT_(arg)  PORT_2(arg)
#define PIN_(arg)   PIN_2(arg)
#define DDR_(arg)   DDR_2(arg)
#define BIT_(arg)   BIT_2(arg)

#define PORT_2(reg, bit)    (PORT ## reg)
#define PIN_2(reg, bit)     (PIN ## reg)
#define DDR_2(reg, bit)     (DDR ## reg)
#define BIT_2(reg, bit)     (bit)

#define PORTB   1
#define PINB    2
#define DDRB    3

int main()
{
    printf("%d\n", PORT_(IO_NAME));
    printf("%d\n", PIN_(IO_NAME));
    printf("%d\n", DDR_(IO_NAME));
    printf("%d\n", BIT_(IO_NAME));

    return 0;
}

This produces the following output:

1
2
3
5

The ## in the macro bodies causes the preprocessor to create new tokens by concatenating the operands.

This is almost what you had. The difference is your top-level macros need to take a single argument, while the secondary macros take two.

like image 196
Tom Karzes Avatar answered Nov 03 '22 17:11

Tom Karzes