Reputation: 480
I'm trying to write some code in C for an atmega microcontroller and I have a working macro BIT_SET
for setting a single bit:
#define BIT_MASK(b) (0x01 << (b))
#define BIT_ON(p,b) ((p) |= BIT_MASK(b))
#define BIT_OFF(p,b) ((p) &= ~BIT_MASK(b))
#define BIT_SET(p,b,v) (v ? BIT_ON(p,b) : BIT_OFF(p,b))
Now I'd like to define one-line macros representing I/O pins. Something similar to this:
#define LED B,5 // an LED diode is connected to bit 5 of port B
#define BUTTON B,4 // a button is connected to bit 4 of port B
There will be more of these macros for different peripheries in a final code, this is only a simplified example.
The problem is that I don't know how to define macros PORT
and DDR
,
so that I could use the LED
(or BUTTON
) macro like this:
BIT_SET(DDR(LED), 1); // which should expand to: BIT_SET(DDRB, 5, 1)
BIT_SET(PORT(LED), 0); // which should expand to: BIT_SET(PORTB, 5, 0)
This is my motivation:
The DDRB
line controls a direction of the pin (whether the pin is an input or an output),
the PORTB
line sets a logical value of the output pin.
Because both the lines affect the same pin, I'd like to select the pin in 1 place (#define LED ...
)
and later in a code use only symbolic names (LED
, BUTTON
) for both operations (configuring direction and setting an output value).
Macros DDRB
and PORTB
must be able to expand further (and not only once), because they're defined in an external header (not under my control).
Also I was trapped by a fact that concatenation using ##
prevents further expansion of the macro.
Upvotes: 2
Views: 1776
Reputation: 480
At the end, I decided to use inline functions instead of macros, as suggested by some comments. This is my result:
// enumeration for I/O ports
typedef enum
{
IO_B,
IO_C,
IO_D
} ioPort;
// structure representing one I/O pin
typedef struct
{
ioPort port;
uint8_t bitIx;
} ioPin;
// converts ioPort to corresponding PORTx pointer
inline volatile uint8_t* port(ioPort iop)
{
switch(iop)
{
case IO_B: return &PORTB;
case IO_C: return &PORTC;
case IO_D: return &PORTD;
default: return NULL;
}
}
// converts ioPort to corresponding DDRx pointer
inline volatile uint8_t* ddr(ioPort iop)
{
switch(iop)
{
case IO_B: return &DDRB;
case IO_C: return &DDRC;
case IO_D: return &DDRD;
default: return NULL;
}
}
// sets one bit of a port to given value
static inline void bitSet(volatile uint8_t* port, const uint8_t bitIx, const uint8_t value)
{
if(port)
{
if(value)
*port |= (1L << bitIx);
else
*port &= ~(1L << bitIx);
}
}
// configuring where peripheral devices are connected
const ioPin led = {IO_C, 5};
const ioPin button = {IO_C, 6};
// (later in a code)
// setting direction of the pin with an led connected
bitSet(ddr(led.port), led.bitIx, 1);
I must admit that, strictly speaking, this is not an answer to my original question, because I asked for a solution with macros. But I hope this could be still useful for someone.
Upvotes: 1
Reputation: 180351
You can write macros such that the preprocessor will expand
BIT_SET(DDR(LED), 1);
to
BIT_SET(DDRB, 5, 1)
as described, but that's not really what you want. BIT_SET()
is itself a macro, and you want ultimately to get the result of expanding that macro based on the arguments obtained by the other expansion. That you cannot have. The preprocessor assigns macro arguments to macro parameters before performing any expansions, and with the definition you gave for macro BIT_SET()
, the preprocessor should always reject your proposed invocation for having the wrong number of arguments.
Updated to Add:
On the other hand, the usual trick for indirect token pasting is to wrap it in a double layer of macros. For instance, the preprocessor expands this stack of macros ...
#define CONCAT2(x, y) x ## y
#define CONCAT(x, y) CONCAT2(x, y)
#define BIT_MASK(b) (0x01 << (b))
#define BIT_ON(p,b) ((p) |= BIT_MASK(b))
#define BIT_OFF(p,b) ((p) &= ~BIT_MASK(b))
#define BIT_SET(p,b,v) (v ? BIT_ON(p,b) : BIT_OFF(p,b))
#define DDRB somevar
#define LED_CODE B
#define LED_BIT 5
#define X_BIT_SET(x, y, v) BIT_SET(CONCAT(x, y ## _CODE), y ## _BIT, v)
X_BIT_SET(DDR, LED, 0)
... to
(0 ? ((somevar) |= (0x01 << (5))) : ((somevar) &= ~(0x01 << (5))))
Note that macro DDRB
obtained by concatenating LED
with _CODE
and expanding the result is itself expanded. This approach does leave you defining two separate macros for each pin (in this case LED_CODE
and LED_BIT
), with names that must adhere to a fixed pattern, but affords a form (e.g. X_BIT_SET(DDR, LED, 0)
) that is pretty easy to read where it appears in your source code.
Upvotes: 0
Reputation: 145849
I think something like this should do the trick:
Disclaimer: untested and probably buggy, but you get the idea...
// Use of PINB, PINC, ... macros and PINB0, PINB1, ... macro from avr/io.h
#define LED PORT_PIN(PINB, PINB0)
#define PORT_PIN(port, pin) (((unsigned int) (&(port) - &PINB) / (unsigned int) &PINB) \
<< 4 + (pin))
#define DDR(port_pin) *(((port_pin) >> 4) & 0xf) \
* (unsigned int) (&PINC - &PINB) + &PINB))
#define PORT(port_pin) *(((port_pin) >> 4) & 0xf) \
* (unsigned int) (&DDRC - &DDRB) + &DDRB))
#define PIN(port_pin) ((port_pin) & 0xf)
STATIC_ASSERT(&DDRC - &DDRB == &PINC - &PINB);
STATIC_ASSERT(sizeof PINB == 1 && sizeof DDRB == 1);
then you can access your macros as:
BIT_SET(DDR(LED), PIN(LED), 1);
BIT_SET(PORT(LED), PIN(LED), 0);
As a sidenote, in the same vein and depending on your compiler you could also do something like this:
typedef struct
{
uint8_t PIN_0: 1;
uint8_t PIN_1: 1;
uint8_t PIN_2: 1;
uint8_t PIN_3: 1;
uint8_t PIN_4: 1;
uint8_t PIN_5: 1;
uint8_t PIN_5: 1;
uint8_t PIN_6: 1;
uint8_t PIN_6: 1;
} REG_t;
#define MY_PINB (*(volatile REG_t *) &PINB)
#define MY_DDRB (*(volatile REG_t *) &DDRB)
and then you could access your pins like this:
#define LED (MY_PINB.PIN0)
LED = 0;
Upvotes: 1