0
\$\begingroup\$

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.)

\$\endgroup\$
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – Chris Stratton Oct 9 '17 at 19:30
  • \$\begingroup\$ @ChrisStratton Ah, good to know. I used that early on but questioned myself when I had #define CE_PORT, CE_DDR, CE_PIN, and so on, plus #define functions. \$\endgroup\$ – calcium3000 Oct 9 '17 at 19:39
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – Harry Svensson Oct 9 '17 at 19:54
  • \$\begingroup\$ @ChristStratton Would you like to submit an answer so I can accept it? \$\endgroup\$ – calcium3000 Oct 10 '17 at 20:31
  • \$\begingroup\$ @HarrySvensson Would you like to submit an answer so I can accept it? \$\endgroup\$ – calcium3000 Oct 10 '17 at 20:31
0
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.