Electrical – Set DDR and PORT registers based on pin number (AVR)

avrc

I'm writing an initialization function on an ATTiny441 to set the data direction register (DDR) for Port A or B based on the physical pin number.

enter image description here

So far my implementation seems a bit clunky. Below is the initialization function in nrf.c that I'll call in main.c:

/* nrf.c */
#import <avr/io.h>
#import "t441.h"

bool ce_port_a;
uint8_t ce_bit;


void nrf_init(uint8_t ce_pin,
              uint8_t csn_pin)
{
    /* Map CE pin */
    t441_map_pins(ce_pin, &ce_bit, &ce_port_a);

    /* Set CE to output */
    if(ce_port_a) DDRA |= BIT(ce_bit);
    else DDRB |= BIT(ce_bit);

    /* Clear CE */
    _nrf_toggle_ce(0);

    /* ... */
}

In the same nrf.c, here's the function used to toggle the CE pin:

/* nrf.c */
void _nrf_toggle_ce(uint8_t direction)
{
    /* Set CE */
    if(direction) ce_port_a ? SET_BIT(PORTA, ce_bit) : SET_BIT(PORTB, ce_bit);
    /* Clear CE */
    else ce_port_a ? CLEAR_BIT(PORTA, ce_bit) : CLEAR_BIT(PORTB, ce_bit);
}

(Note the SET_BIT() and CLEAR_BIT() functions are defined elsewhere, but I assume that they're descriptive enough.)

And, finally, here's the function in t441.c that maps the physical pin number to a Port and bit number:

/* t441.c */
uint8_t t441_port_b_map[4] = {0, 1, 3, 2};

void t441_map_pins(uint8_t pin_num, uint8_t *port_pin_num, bool *port_a)
{
    if(pin_num < 6)
    {
        /* Pin is on Port B */
        *port_a = false;
        *port_pin_num = t441_port_b_map[pin_num - 2];
    }
    else
    {
        /* Pin is on Port A */
        *port_a = true;
        *port_pin_num = 13 - pin_num;
    }
}

Question: Is there a simpler or more elegant way to approach this? I'm somewhat concerned with program memory usage but mainly with code re-usability — e.g. having to significantly change things if using a microcontroller with more than two IO ports.

(I'll move this to Stack Overflow if appropriate.)

Best Answer

I'm probably not the most qualified to answer, but I'll compile what folks have said thus far.

From Chris Stratton:

Generally speaking, that's a bad idea. Use the logical port assignments, and if you want, #define them by radio function. If you really want to see what a user pin abstraction introduces in terms of complication, look at Arduino core code where they do that.

From Harry Svensson:

There's a lot of code happening during runtime rather than compile time. Runtime code will consume precious EEPROM memory. You definitely need to make use of #ifdef together with #define if you want to unclunky-fy your code. Since they are only evaluated during compile time.

And from Lundin:

There is no reason to set it "based on the physical pin number". You need to ask yourself why you need the pin numbers to be variable in relation to the ports. Do you expect Atmel to suddenly remap the ports? Do you need the code to be portable to another package at the same time as you need operations based on pin number? If not, then you are just inventing artificial requirements and doing meta programming. Just hardcode the port setup or #define all the constants. Toggling DDR in runtime, rather than just setting it up once, is quite a rare case too, do you actually need to do that?

Seems like the best way to approach it is with preprocessor commands, e.g. #ifdef, #define, and so on. Since I'm only using a single IC there's no need to waste memory on code portability.