Search code examples
cavravr-gcc

AVR clean pin aliasing solution - enumerating I/O bits


I'm working in C on an Arduino device, where the pins are labeled differently. I'm using PLAIN C though, not the Arduino "language".

Each pin is defined by it's port (eg. PORTB) and pin (bit) in the port (eg. PB0).

I'd like to concisely alias pins, so I could make macros or functions somewhat like what the Arduino uses:

pin_direction(D2, 1); // D2 as output
set_pin(D2, 0); // write zero to D2
pin_direction(D3, 0); // D3 as input
enable_pullup(D3, 1); // enable D3 pullup

Instead (atm) I have to use something ugly like this:

#define D0 0
#define D1 1
#define D2 2
#define D3 3
...
#define D10 2
#define D11 3

#define PORT_D0 PORTD
#define PORT_D1 PORTD
#define PORT_D2 PORTD
#define PORT_D3 PORTD
...
#define PORT_D10 PORTB
#define PORT_D11 PORTB

// the same for PIN_xx and DDR_xx

And then I can use macros to do the work:

#define sbi(port, bit) (port) |= _BV(bit)
#define cbi(port, bit) (port) &= ~ _BV(bit)

sbi(DDR_D2, D2); // D2 to output
cbi(PORT_D2, D2); // D2 to output
sbi(DDR_D3, D3); // D3 as input
sbi(PORT_D3, D3); // D3 pullup enable

Now this works, but it's very messy. Any idea how to - without a monster overhead of something like huge switch - do this better - more like my first example? Somehow enumerate all bits and then resolve the right register on the fly?

I'm using avr-gcc with avr-libc.


Solution

  • Here's the solution I use.

    In my util.h (common to all my AVR projects):

    #define DDR_REG(port) DDR ## port
    #define PORT_REG(port) PORT ## port
    #define PIN_REG(port) PIN ## port
    
    #define SET_BIT(port, bit) do { (port) |= (1 << (bit)); } while(0)
    #define CLR_BIT(port, bit) do { (port) &= ~(1 << (bit)); } while(0)
    #define BIT_IS_SET(port, bit) ((((uint8_t)(port)) >> ((uint8_t)(bit))) & 0x1)
    
    #define IO_SET_INPUT_AUX(port, bit) CLR_BIT(DDR_REG(port), bit)
    #define IO_SET_AS_INPUT(io) IO_SET_INPUT_AUX(io)
    
    #define IO_SET_OUTPUT_AUX(port, bit) SET_BIT(DDR_REG(port), bit)
    #define IO_SET_AS_OUTPUT(io) IO_SET_OUTPUT_AUX(io)
    
    #define IO_OUTPUT_0_AUX(port, bit) CLR_BIT(PORT_REG(port), bit)
    #define IO_OUTPUT_0(io) IO_OUTPUT_0_AUX(io)
    
    #define IO_OUTPUT_1_AUX(port, bit) SET_BIT(PORT_REG(port), bit)
    #define IO_OUTPUT_1(io) IO_OUTPUT_1_AUX(io)
    
    #define IO_GET_INPUT_AUX(port, bit) BIT_IS_SET(PIN_REG(port), bit)
    #define IO_GET_INPUT(io) IO_GET_INPUT_AUX(io)
    

    In my pin mappings file:

    #define UPBTN_IO B,7
    #define DOWNBTN_IO D,0
    #define ENTERBTN_IO D,1
    (etc)
    

    In code:

    IO_SET_AS_INPUT(UPBTN_IO);
    

    This relies on some fun preprocessor bits, like there only being one round of macro expansion on macro parameters.