How to Declare Port Programatically?

Hello,

I have a couple of classes that require direct port manipulation. To do so, I declared the PORT, PIN and ADDRESS the following way:

void Class::Class_init(void)
{
   #define PIN_ADDRESS PIND
   #define PORT_ADDRESS PORTD
   #define DDR_ADDRESS DDRD
}

That works well when I have only one instance of my class active

Class CS_1;

CS_1.Class_init();

But of course, it wont work if i need two instances of the same class

Class CS_1;
Class CS_2;

CS_1.Class_init();
CS_2.Class_init();

Is there a way to replace say "PIN_ADDRESS" with a variable that equals "PIND" and pass it along to the function as a variable?

Thanks.

  volatile uint8_t *portVariable;

  portVariable = &PORTD;
  *portVariable = 0b10101000;

By the way, this does nothing. It generates no code.

void Class::Class_init(void)
{
   #define PIN_ADDRESS PIND
   #define PORT_ADDRESS PORTD
   #define DDR_ADDRESS DDRD
}

Thank you for your answer!

yes, I know the code doesn't generate anything. It was just a simple example to showcase the problem.

I use the PORT, PIN and ADDRESS variables in another function of the class after declaring them. But i now need to declare another instance of the class on a different port, so I'd like to somehow instantiate the class with those variables.

void Class::Class_init(PIN_ADDRESS, PORT_ADDRESS, DDR_ADDRESS)

Of course i do have other functions inside the class that utilize those declarations.

What does the compiler see when i do a "#define PIN_ADDRESS PIND"??

How can I change it to a variable that the compiler would see the same way but I could also change on the fly?

Thank you for your answer, I now know what you were trying to explain.

Here is another simplified example of what i attempted to do:

uint8_t pin_1_ = 0x01;

volatile uint8_t *PORT_ADDRESS;

void class_init(uint8_t PIN_P_D1, uint8_t PIN_P_D5)
{
  pin_1_ = PIN_P_D1;
  pin_5_ = PIN_P_D5;

  #define PIN_1 pin_1_
  #define PIN_5 pin_5_

  PORT_ADDRESS = &PORTD;
}

static void class_sendByte()
{
  PORT_ADDRESS = PIN_1;
  _delay_us(1);

  if (data & 0x80)
    PORT_ADDRESS |= PIN_5;

  nop();

  PORT_ADDRESS &= ~PIN_1;
  _delay_us(1);
}

Unfortunately I get the following error:

invalid conversion from 'uint8_t {aka unsigned char}' to 'volatile uint8_t* {aka volatile unsigned char*}' [-fpermissive]

This:

PORT_ADDRESS = PIN_1;

You #define(d) PIN_1 to be pin_1_ which is the name of a uint8_t variable. You can't assign an uint8_t variable to a uint8_t * variable.

Why are you continuing to screw around with #define macros? They are not the answer to whatever it is you're trying to accomplish.

I copied the old code and added the modifications you mentioned. That's why there were some #define statements left there. I should erase them all, yes.

volatile uint8_t *PORT_ADDRESS;

void class_init(uint8_t PIN_P_D1, uint8_t PIN_P_D5)
{
  PIN_1 = PIN_P_D1;
  PIN_5 = PIN_P_D5;
  PINS  = (PIN_P_D1 | PIN_P_D5);

  PORT_ADDRESS = &PORTD;
}

What I intend to do is replace:

   #define PIN_ADDRESS PIND
   #define PORT_ADDRESS PORTD
   #define DDR_ADDRESS DDRD

with variables like "PORT_ADDRESS" as a drop-in replacement.

Is this not possible?

If it isn't, how would I make some of the operations I have work?

Like the following:

PORT_ADDRESS = PIN_1;

: "I" (_SFR_IO_ADDR(PIN_ADDRESS))

#define transmitMode()  do { PORT_ADDRESS |= PINS; DDR_ADDRESS |= PINS; } while(0)

#define inputMode() do { PORT_ADDRESS |= PINS; DDR_ADDRESS &= ~PINS; } while(0)

On an 8-bit AVR, those are all 8-bit registers and you manipulate them all the same way -- by using pointers:

    volatile uint8_t *portAddr;
    volatile uint8_t *ddrAddr;
    volatile uint8_t *pinAddr;

    portAddr = &PORTD;
    ddrAddr = &DDRD;
    pinAddr = &PIND;

To access the register, de-reference the pointer. For example:

  *portAddr = 0b10101000;

Thank you!

It would seem that I missed some "*" in front of some variables that didn't make the whole thing work the first time around. It is now working quite well with a few exceptions.

I cannot get some of the assembler language expressions like the following to work

: "I" (_SFR_IO_ADDR(*ddrAddr)), "r"(buf_used/2), "z"(maplebuf)

: "r1","r16","r17","r18","r19","r20","r21"

getting an "impossible constraint in asm" error

If you want to make your own PORT class to handle any port it would help to not define the addresses as those of port D.

Handle the address inits in setup().

Maybe it's just me but when I see #define in a class definition it makes me cringe a bit.

Do you need the pin numbers defined at compile time, or at run time?

Take a look at Bill Greiman's digitalIO library

It uses C++ templates to allow you to define "pins" and then access them with assignments, like:

DigitalPin<12> clockPin;
DigitalPin<13> dataPin;
//------------------------------------------------------------------------------
// Time to send one bit is ten cycles or 625 ns for 16 MHz CPU.
inline __attribute__((always_inline))
void sendBit(uint8_t bit, uint8_t data) {
  dataPin = data & (1 << bit);
  clockPin = 1;
  // may want a nop here - clock pulse is 125 ns wide
  clockPin = 0;
}

Don't the AVR shift opcodes only shift 1 bit? If so the execution time depends on which bit.

Don't the AVR shift opcodes only shift 1 bit? If so the execution time depends on which bit.

Detail, details... Yeah, I noticed that too. But it's also "always_inline", and perhaps got used in a way where the compiler is able to optimize better than that. In any case, it's just example code of using the "DigitalPin" template...

Thanks for your suggestion,

To answer your question, I only need the pin numbers defined at compile time, not at run time.

I am also still trying to get the ASM code to work

This is the original code:

: "I" (_SFR_IO_ADDR(PIND))
: "r16","r17","r18","r19") ;

Doing this works fine:

: "I" (0x09) //PIND Register on the MCU i'm using
: "r16","r17","r18","r19") ;

But if I try this, nope, asm error

volatile uint8_t PIN_BYTE = 0x09;


: "I" (PIN_BYTE)
: "r16","r17","r18","r19") ;

Again, it's just a simple example, I know the code doesn't compile to anything, it is just a small section of a larger assembler code block I have in my main code.

silviustro:
Thanks for your suggestion,

To answer your question, I only need the pin numbers defined at compile time, not at run time.

If you want a single class for any or all ports then the class itself can't be limited to 1 particular port as your example does.

Even if you have a PORT address (and PIN and DDR) compiled, the mode has to be set at runtime.
I went through this making a button class years ago, easiest and lest RAM using solution involved a .init() method to run in setup().

You can put bitmask values in an enum or array if you want faster running code or use the bit() function to save RAM at cost to speed.

volatile uint8_t PIN_BYTE = 0x09;

: "I" (PIN_BYTE)
: "r16","r17","r18","r19") ;




But if I try this, nope, asm error

The AVR IO instructions (IN, OUT) require that the Port be a CONSTANT, that is encoded into the actual instruction. The SBI and CBI instructions require that both the Port and the Bit be constants.
This means that if you had something that generated:

  SBI _SFR_IO_ADDR(PORTD), 3

And wanted to replace PORTD with something that changed at runtime, you'd have to generate something like:

  LD Rtmp, X
  SBR Rtmp, 3
  ST Ttemp, X

(and you'd have to change your inline ASM statement to pass it the address of the port in X, rather than the absolute port number:

: "x" (PIN_BYTE)

Can the class reference const addresses in an array? Data member would be the index.

Thanks for your answer westfw

thing is, i don't need it to change during runtime, I just have two devices that use separate ports but require the same ASM code to be accessed.

I could just create two functions, but that's inelegant and bloated, i'd rather just pass a variable (in this case the port) and have it set in the ASM code depending which device i'm using.

I will post the whole code as it might be of better help

#define MAPLE_BUF_SIZE  641
volatile unsigned char maplebuf[MAPLE_BUF_SIZE];
unsigned char timeout;
volatile uint8_t PIN_BYTE = 0x09;

asm volatile( 
      " push r30    \n" // 2
      " push r31    \n" // 2
      " clr %0      \n" // 1 (result=0, no timeout)
      
      " ldi r30, lo8(maplebuf)  \n"
      " ldi r31, hi8(maplebuf)  \n"

      // Loop until a change is detected. 
      " ldi r19, 10   \n"

      "wait_start_outer:  \n"
      " dec r19     \n"
      " breq timeout  \n"
      " ldi r18, 255  \n"
      " in r17, %1    \n"

      "wait_start_inner:    \n"
      " dec r18     \n"
      " breq wait_start_outer \n"
      " in r16, %1    \n"
      " cp r16, r17   \n"
      " breq wait_start_inner \n"
      " rjmp start_rx \n"

      "timeout:\n"
      " inc %0      \n" // 1 for timeout
      " sbi 0x5, 4    \n" // PB4
      " cbi 0x5, 4    \n"
      " jmp done    \n"

      "start_rx:      \n"

      "   in r16, %1\n   st z+, r16   \n" //this line repeats multiple times

      "done:\n"
      " sbi 0x5, 4    \n" // PB4
      " cbi 0x5, 4    \n"
      " pop r31     \n" // 2
      " pop r30     \n" // 2
    : "=r"(timeout)
    : "M" (PIN_BYTE)
    : "r16","r17","r18","r19") ;