-4

I'm trying to use a pointer in a function argument to read a variable from a class, like this:

void Function(int (*Argument)) {
    Variable = Object*Argument.ClassVariable;
}

Note: the reason I want to do this is so that I can set the value of a rotary encoder position equal to the value a class variable was previously at, prior to allowing the user to change that class variable value. This way, when the user cycles through the class variables to set them, the starting position for each variable is the saved value.

Class objects are named Object1, Object2, etc. The variable I want to read from each object is named ClassVariable. When I call the function, i will useFunction(ClassNumber);

If ClassNumber is 13 at the time the function is called, I want Variable to be made equal to Object13.ClassVariable

Is this possible?

Further Info:

The entire class is public.

Constructor for class objects (note, this is arduino, byte is 8-bit unsigned):

  Modes(byte hue, byte sat, byte val, int del, bool hROal, bool hset, bool sset, bool vset, bool dset) {
    hueVal = hue;
    satVal = sat;
    valVal = val;
    delVal = del;
    hueROallow = hROal;
    hSet = hset;
    sSet = sset;
    vSet = vset;
    dSet = dset;
  }

Each class object is a separate 'mode' that the user can select. The Argument i intend using in the function is the 'mode number' currently selected.

FULL CODE (very much a work in progress)

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>

const byte Button2 = 8;

const byte EncApin = 3;           // EncApin **MUST BE interrupt**
const byte EncBpin = 4;           // These are the three pins the encoder A/B & button are connected to
const byte EncButt = 2;           //

static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};   // array to set encoder H/L sequence
byte encVal;          // initialises reading from encoder

Adafruit_SH1106 display(0);   // Initialise display? (On SPI OLED screens this sets the MOSI/CLK/DC/RST/CS pins)


int displayVal; // to store the current output value for display
String displayMode;
byte mode = 1;              // set mode 0 at beginning
byte qtyModes = 3;          // set number of modes that may be selected here

byte setMode = 1;
byte qtySetModes = 4;       // set number of sub-modes for setting values

///// FUNCTION PROTOTYPES ///////// must be declared here to allow the class to use them
void EncReading(); 
void EncReadingRO();

////////////////////////////////////////////////////////
////////////// ******** CLASSES ********* //////////////
////////////////////////////////////////////////////////
class Modes {

  public:   // everything in the class below this will be available to the program outside of the class

  // Class Member Variables
  // These are initialized at startup
  byte hueVal;
  byte satVal;
  byte valVal;
  int delVal;
  bool hueROallow;
  bool hSet;
  bool sSet;
  bool vSet;
  bool dSet;

  byte CLdisplayVal;
  String displayDesc;

  // These maintain the current state
  unsigned long prevMillis;   // will store last time LED was updated

  // Constructor - creates a Mode & initializes the member variables and state
  // additional options for whether rollover of values allowed needed
  Modes(byte hue, byte sat, byte val, int del, bool hROal, bool hset, bool sset, bool vset, bool dset) {
    hueVal = hue;
    satVal = sat;
    valVal = val;
    delVal = del;
    hueROallow = hROal;
    hSet = hset;
    sSet = sset;
    vSet = vset;
    dSet = dset;
  }

  void Update() {
    switch (setMode) {
      case 1:           // case 1 for hue update
        if (hSet == 0) { 
          displayDesc = F(" HUE (FIXED)");
          CLdisplayVal = hueVal;
          break;
          }
        if (hueROallow == 1) EncReadingRO();
        else EncReading();
        hueVal = encVal;
        CLdisplayVal = encVal;
        displayDesc = F(" HUE");
        break;

      case 2:           // saturation update
        if (sSet == 0) { 
          displayDesc = F(" SATURATION (FIXED)");
          CLdisplayVal = satVal;
          break;
          }
        EncReading();
        satVal = encVal;
        CLdisplayVal = encVal;
        displayDesc = F(" SATURATION");
        break;

      case 3:           // value update
        if (vSet == 0) { 
          displayDesc = F(" BRIGHTNESS (FIXED)");
          CLdisplayVal = valVal;
          break;
          }
        EncReading();
        valVal = encVal;
        CLdisplayVal = encVal;
        displayDesc = F(" BRIGHTNESS");
        break;

      case 4:           // delay update
        if (dSet == 0) { 
          displayDesc = F(" TIMING (FIXED)");
          CLdisplayVal = delVal;
          break;
          }
        EncReading();
        delVal = encVal;
        CLdisplayVal = encVal;
        displayDesc = F(" TIMING");
        break;
      }
    displayReading();
  }

  void displayReading() {
    unsigned long currMillis = millis();        // These four lines are to
    static unsigned long prevMillis;                     // act as a delay, except
    if (currMillis - prevMillis >= 100) {   // without holding the execution          // note: encoder reading sensitive to delal changes, 100 not bad
      prevMillis = currMillis;                  // of other code
      display.fillRect(39, 30, 54, 24, BLACK);
      display.fillRect(0, 0, 128, 18, WHITE);
      display.setTextSize(1);
      display.setTextColor(BLACK);
      display.setCursor(1,1);
      display.println(displayMode);
      display.setCursor(1,10);
      display.println(displayDesc);
      display.setTextSize(3);
      display.setTextColor(WHITE);
      display.setCursor(39,30);
      display.println(CLdisplayVal);
      display.display();
    }
  }
};


////////// Construct objects - this sets up the objects we want of the class 'Modes'
// Modes modex(Hue0-255, Sat0-255, Val0-255, Del0-255, Hue rollover0/1, Hue settable0/1, Sset0/1, Vset0/1, Dse0/1
Modes mode1(50, 100, 150, 100, 1, 1, 1, 1, 1);   // object 'mode1', initializing with H50, S100, V150, D100, hue rollover & settable options ON
Modes mode2(55, 105, 155, 105, 0, 1, 1, 1, 1);
Modes mode3(63, 73, 83, 93, 0, 1, 1, 1, 1);

////////////////////////////////////////////////////////
////////// ******** SETUP / LOOP ********* /////////////
////////////////////////////////////////////////////////

void setup() {
  Serial.begin(115200);
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
  display.clearDisplay();
  display.display();
  pinMode(EncApin, INPUT_PULLUP);  // Turn on internal pullup resistors for encoder pins & buttons
  pinMode(EncBpin, INPUT_PULLUP);
  pinMode(Button2, INPUT_PULLUP);
  pinMode(EncButt, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(EncApin), read_encoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(EncButt), encButtPress, FALLING);
  pciSetup(Button2);    // set up encApin for pin change interrupt
  encVal = mode1.hueVal;           // initialize the encoder to the mode1 setmode1 value
}

void loop() {
  switch(mode) {
    case 1:
      displayMode = F("MODE:Call it anything");
      mode1.Update();
      // add call to the mode 1 LED display function here, using mode1.xyz variables
      break;

    case 2:
      displayMode = F("MODE:Second");
      mode2.Update();
      // add call to the mode 2 LED display function here, using mode2.xyz variables
      break;

    case 3:
      displayMode = F("MODE:Third");
      mode3.Update();
      // add call to the mode 2 LED display function here, using mode2.xyz variables
      break;

  }

Serial.print(F("Enc: "));
Serial.print(encVal);
Serial.print(F("    M1 H: "));
Serial.print(mode1.hueVal);
Serial.print(F("  S: "));
Serial.print(mode1.satVal);
Serial.print(F("  V: "));
Serial.print(mode1.valVal);
Serial.print(F("  D: "));
Serial.print(mode1.delVal);
Serial.print(F("    M2 H: "));
Serial.print(mode2.hueVal);
Serial.print(F("  S: "));
Serial.print(mode2.satVal);
Serial.print(F("  V: "));
Serial.print(mode2.valVal);
Serial.print(F("  D: "));
Serial.println(mode2.delVal);

//  Serial.print(F("freeMemory()="));
//  Serial.println(freeMemory());

}

////////////////////////////////////////////////////////
//////////// ******** FUNCTIONS ********* //////////////
////////////////////////////////////////////////////////

///// Function to set encVal, 0-255, NO rollover
void EncReading() {
  int8_t encoderdata;
  encoderdata = read_encoder();                           // returns the +- value from the read_encoder function
  if (encoderdata) {                                      // if not equal to zero
    if (encVal+encoderdata>255 || encVal+encoderdata<0);    // these if/else statements clamp encVal to prevent byte rollover
    else {
      encVal += encoderdata;
    } // end else
  }
}
///// Function to set encVal, 0-255, WITH rollover
void EncReadingRO() {
  int8_t encoderdata;
  encoderdata = read_encoder();                           // returns the +- value from the read_encoder function
  if (encoderdata) {                                      // if not equal to zero
    encVal += encoderdata;
  }
}

////////////////////////////////////////////////////////
//////////// ******** INTERRUPTS ********* /////////////
////////////////////////////////////////////////////////

/////// Pin Change Interrupt Setup Function /////////// called in void setup to set the selected pin as a PCI
void pciSetup(byte pin)
{
    *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
    PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
    PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}

//////////// PCI Interrupt Sub Routines////////
// ISR (PCINT0_vect) is ISR for D8-13 // ISR (PCINT2_vect) is ISR for A0-5 // ISR (PCINT2_vect) is ISR for D0-7 //
ISR (PCINT0_vect) // handle pin change interrupt for D8 to D13 here, depending on which are set in void setup
 {    
  if(digitalRead(Button2) == LOW)
     buttonPress();
 }


//////// Func for ENCODER button press ISR ****** CHANGE 'setMode' ***** ///////////////
void encButtPress() {
  static unsigned long prevInterrTime = 0;
  unsigned long interrTime = millis();
  if (interrTime - prevInterrTime > 200) {
    setMode++;
    // *** ADD HERE *** need to set encVal to whatever the existing value is in the setMode being switched to
    if (setMode > qtySetModes) setMode = 1;
    prevInterrTime = interrTime;
    setPrevEncVal(mode);
  }
}

void setPrevEncVal(byte (*modeNum)) {
  switch (setMode) {
    case 1: encVal = mode&modeNum.hueVal; break;
    case 2: encVal = mode1.satVal; break;
    case 3: encVal = mode1.valVal; break;
    case 4: encVal = mode1.delVal; break;
  }
}

//////// Func for button press ISR ****** CHANGE 'mode' ***** ///////////////
void buttonPress() {
  static unsigned long prevInterrTime = 0;
  unsigned long interrTime = millis();
  if (interrTime - prevInterrTime > 200) {
    mode++;           // increment 'mode'
    setMode = 1;      // reset 'setMode'
  }
  if (mode > qtyModes) mode = 1;
  prevInterrTime = interrTime;
}

//////// Func for +1/-1 encoder reading ISR ///////
/* returns change in encoder state (-1,0,1) */
int8_t read_encoder() {
  static uint8_t old_AB = 0;
  old_AB <<= 2;
  old_AB |= ((digitalRead(EncBpin))?(1<<1):0) | ((digitalRead(EncApin))?(1<<0):0);
  return ( enc_states[( old_AB & 0x0f )]);
}
Cammack
  • 27
  • 6
  • 1
    Store your objects in an array, then it'll just be `Objects[Argument].ClassVariable` – Igor Tandetnik Apr 18 '18 at 22:46
  • 5
    Don't invent some syntax, post some real code that illustrates how you want to call the function. –  Apr 18 '18 at 22:46
  • No invented syntax - in my code, the function: `void Function() { Variable = Object1.ClassVariable;}` returns the desired result, setting the value of `Variable` equal to the value of `Object1.ClassVariable`. I only want to know if I can use a function argument to set which object `ClassVariable` is to be read from. – Cammack Apr 18 '18 at 23:21
  • @Igor, I might end up with 15 objects, each of which has 9 member variables defined. Is there an easy way to set up an array of objects as you suggest? Trying to read the variables from the class objects in order to create such an array seems to need the same solution as the initial problem? – Cammack Apr 18 '18 at 23:44
  • Can you show the definitions of those objects and variables? It's not clear what you are trying to achieve. – Igor Tandetnik Apr 18 '18 at 23:58
  • The code you have doesn't work. I guess that's why Neil mentioned that much. You can't create a function that creates an object name at run time, because C++ is not an interpreted language (that is, you compile it, and your object names become just a reference in memory. In other words, they become meaningless to the execution). There are better ways to return attributes (possibly using interfaces). But I want to understand what is what you're trying to do. The piece of code you show doesn't tell the whole story – JACH Apr 19 '18 at 00:14
  • Furthermore: having classes (or objects) without meaningful names is a bad idea. And believe me: object1, object2, object3 are not meaningful at all (and the path to hell, if you ask me) – JACH Apr 19 '18 at 00:16
  • @JACH I'll post the full code, I just thought it more likely to obfuscate than help. The function in question is setPrevEncVal (line 291). Line 293 is where I'm trying to get a pointer to work The class is `Modes`, the true object names being used are `mode1`, `mode2` etc. – Cammack Apr 19 '18 at 00:32
  • I posted a suggestion. It will require a learning curve, but it will be worth it. Also, don't use magic numbers https://en.wikipedia.org/wiki/Magic_number_(programming) . At the very minimum, use an enum or defines. Just to be clear, in my answer I used "MODE_1"... that's also a "magic number". Use meaningful descriptions instead – JACH Apr 19 '18 at 00:37
  • As added above, the reason I want to do what I'm asking is so that a previously stored value (the parameters for other functions) can be written to the variable being used to store a rotary encoder's position, so that when the user cycles through the settings, the previous value is returned before turning the encoder dial changes it to a new value. – Cammack Apr 19 '18 at 00:39
  • Thanks @JACH! Learning curve is good, all of this is new to me anyway, I've never used classes before. – Cammack Apr 19 '18 at 00:42

1 Answers1

1

To answer the question:

You can't, because C++ is a compiled language, and as such, the names of the functions, objects and variables become meaningless (instead, they become just addresses)

For example, you may see in your code

 int myVariable = 6;

but the compiler sees:

 mov [6], [0x6874]

Read Compiled vs. Interpreted Languages for more reference.

[For your particular case]

You'll be better off if you use interfaces and a factory.

class Mode {
    virtual int getAttribute() = 0;
}

class AMeaningfulName : public Mode {
    int getAttribute()  { return 1; }
}

class SelectAnotherMeaningfulNamePlease : public Mode {
    int getAttribute() { return 2; }
}

class ModeFactory {
    Mode getMode(int userSelectedMode) {

        if (userSelectedMode == MODE_1) {
            return new AMeaningfulMode();
        } else if (userSelectedMode == MODE_2) {
            return new SelectAnotherMeaningulNamePlease();
        } else {
            //send an exception so the user knows he selected something invalid
        }
    }
}


///now, in your main or elsewhere
ModeFactory factory = new ModeFactory();
Mode mode = factory.getMode(userSelectedValue);
int theAtttributeIWanted = mode.getAttribute();

Take a look at virtual functions https://en.wikipedia.org/wiki/Virtual_function

And also at factory pattern https://www.tutorialspoint.com/design_pattern/factory_pattern.htm

JACH
  • 996
  • 11
  • 20
  • Let's see if I'm following your example... Each mode is defined as a separate class, `AMeaningfulName` etc, this is where the settings for each mode will be stored. Another class `ModeFactory` creates objects of these separate mode classes, depending on the mode selected by the user. The separate mode classes are called by the virtual function in `Mode`, this is where the code to change each mode class variable is. The main code will use `ModeFactory` to create mode objects, will tell `Mode` which separate mode class it should be using, and will send the outputs to the display etc. Am I close? – Cammack Apr 19 '18 at 11:12
  • 1
    Yes. That's the idea. In this case, Mode is the interface that your child classes implement. Then you add the specifics to each class. Then your main code doesn't care if you are executing an object of class1 or class2, as long as it has the methods in your interface. It's a very common and very useful practice. Take a look at this explanation, which also made me laugh https://stackoverflow.com/a/14244705/773334 – JACH Apr 19 '18 at 14:17