Pages: [1]   Go Down
Author Topic: Static function for interrupts in a class  (Read 254 times)
0 Members and 1 Guest are viewing this topic.
New Jersey
Offline Offline
Sr. Member
****
Karma: 1
Posts: 482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I have a class that's using I2C communication via the wire.h library.  When I tried to use the Wire.onRequest(A_Function_In_My_Class) to set the function that should be called, I got a "no matching function error".  After some research I figured out I needed to make the function static in my .h file.  Here's a skeleton version of my program that compiles successfully.
Code:

// this would be in .h file   ===========================
#include <Wire.h>
const byte SLAVE_ID = 46;
class MyTestClass {
  public:
    void begin();
  private:
    static void i2cReceiveCmd(int bytesReceived);
    static void i2cSendData();
    static byte _i2cCmd;
};

// this would be in .cpp file    ===========================
byte MyTestClass::_i2cCmd;

void MyTestClass::begin() {
  Wire.begin (SLAVE_ID);
  Wire.onReceive (i2cReceiveCmd);  // interrupt handler for incoming commands
  Wire.onRequest (i2cSendData);    // interrupt handler to send data to the master when the master requests it
}

void MyTestClass::i2cSendData() {
  if (_i2cCmd == 1)
  { Wire.write(SLAVE_ID); }
}

void MyTestClass::i2cReceiveCmd(int bytesReceived)
{
  _i2cCmd = 1;
}

// this would be in .ino file  ===========================
void setup(){}
void loop(){}


My full program can be found here:
https://github.com/Scott216/Hot_Tub_Controller/tree/master/Hot_Tub_LCD/Hot_Tub_LCD

My full sketch runs just fine.  I'm new to using classes and I never used static functions before.  I was wondering if this is a good way to deal with the scenario of interrupt handlers in a class? 
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The parameter that is passed to Wire.onRequest should be the name of a ordinary C function, not a method from your class.
You can always call your method from that function.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 548
Posts: 46042
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The parameter that is passed to Wire.onRequest should be the name of a ordinary C function, not a method from your class.
Horsecrap.

There is nothing wrong with using static class methods.

OP: The only problem with doing so arises when you have multiple instances of the class. When the static function is called, it belongs to the class, not to an instance of the class. Which instance should get the data? Your class, as it is designed now, is nothing more than a collection of related functions that is not designed to be instanced more than once. As such, it really should not be a class.
Logged

New Jersey
Offline Offline
Sr. Member
****
Karma: 1
Posts: 482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Your class, as it is designed now, is nothing more than a collection of related functions that is not designed to be instanced more than once. As such, it really should not be a class.

I did it this way, using a class, so I could start familiarizing myself with classes and OOP.  I've noticed that it really helps me make my code easier to understand.  Using a bunch of small functions would do the same thing.  Any other ways to do this, other then a class or just a bunch of functions?  I did play around with using the namespace concept in another program.  It seemed like a good way to group things.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The parameter that is passed to Wire.onRequest should be the name of a ordinary C function, not a method from your class.
Horsecrap.

There is nothing wrong with using static class methods.

OP: The only problem with doing so arises when you have multiple instances of the class.
The parameter should be the name of an ordinary C function to avoid the problem you described.
Logged

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 53
Posts: 1782
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The parameter that is passed to Wire.onRequest should be the name of a ordinary C function, not a method from your class.
Horsecrap.

There is nothing wrong with using static class methods.

OP: The only problem with doing so arises when you have multiple instances of the class.
The parameter should be the name of an ordinary C function to avoid the problem you described.


No, the problem with multiple instances is how to give the static function access to each instance, pointer/reference array, hard coded, and so on.

Also there is the problem of determining which instance the interrupt/callback belongs to. You would still encounter the same situations handing out messages from one source interrupt/callback using global namespace functions.

« Last Edit: January 25, 2014, 02:08:21 am by pYro_65 » Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The parameter that is passed to Wire.onRequest should be the name of a ordinary C function, not a method from your class.
Horsecrap.

There is nothing wrong with using static class methods.

OP: The only problem with doing so arises when you have multiple instances of the class.
The parameter should be the name of an ordinary C function to avoid the problem you described.

No, the problem with multiple instances is how to give the static function access to each instance, pointer/reference array, hard coded, and so on.
The problem you describe isn't relevant to the solution that I proposed, which was simply how to get the code to compile.

It's pretty obvious that an instance should be specified when calling a method of the class that the instance belongs to.

Quote
Also there is the problem of determining which instance the interrupt/callback belongs to. You would still encounter the same situations handing out messages from one source interrupt/callback using global namespace functions.
So what solution do you propose to solve that particular problem?
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 548
Posts: 46042
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
So what solution do you propose to solve that particular problem?
That depends on what OP thinks should happen when data arrives on the I2C port. Some instance of the class cares. Maybe all of them. Without knowing that, it is not possible to define a solution.

If every instance should be notified, then each instance needs to register that interest with the class (using a static method), and the interrupt handler should then call the instance method for each registered instance.

If only one instance should be notified, based on something in the I2C data, then, again, each instance should register, and tell the class what in the I2C data is of interest. Then, the interrupt handler can call the correct instance method.

The interrupt is like a doorbell. When the switch outside the door is pressed, the noise maker inside makes a noise. It doesn't care whether anyone is interested. Registering an interrupt handler is like hiring a butler to answer the door. Only the butler needs to deal with the interrupt. Other people in the house might be expecting guests, so they would let the butler know of that interest.

That might be in the form of "Let me know whenever the doorbell rings; I'm expecting a bunch of friends and some deliveries:. It might be in the form "Let me know if UPS drops off a package for me". It might be in the form "Let me know if Joe stops by".

How to structure the class depends on what the butler needs to know in order to know how to deal with the doorbell. We really haven't been given enough information. And, maybe now, you can see that there is information missing, and why it's important, and why it affects the design of the solution. And, maybe now, you can see why a static instance CAN be used.
Logged

New Jersey
Offline Offline
Sr. Member
****
Karma: 1
Posts: 482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I brought up this topic because it applies to my hot tub controller project.  I have two Arduinos communicating over I2C.  One Arduino is for the user panel (I2C Slave) which controls an LCD display, and has inputs for some buttons and a rotary encoder.  That's the Arduino this post is about.  Then I have another Arduino (I2C Master) that controls the hot tub heater, pump, bubbler and monitors pressure, temperature and amps. 

The hot tub controller (I2C master), controls the operation of the hot tub. It's reading the sensors and deciding when to turn things on and off.   It sends the water temperature to the user panel Arduino (I2C slave), and it needs to know from the user panel the status of the push buttons and what temperature the user desires.  So over I2C the master is sending and receiving information.

In this project, I create one instance of the class to represent the hot tub in the user panel arduino.  Since there is just one hot tub, there would only be one instance.  There would only be two instances if there were two hot tubs.  But this would probably mess up the I2C since there would be two I2C masters.  So I'd have to change that around so the user panel was the I2C master. If that were the case, then I wouldn't need interrupts on the master and this static function wouldn't be needed. 


Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 548
Posts: 46042
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Since there is just one hot tub, there would only be one instance.
There is nothing in your class that forces it to be a singleton. If you're not familiar with that term, you need to become familiar with it.

In the doorbell/butler analogy, you are assuming that only one person will live in the house, so you are assuming that the butler will know to tell the one resident of the house about events involving the doorbell.

As you can imagine, those assumptions are invalid.

Now, you could have the class keep track (the constructor is a good place to do this) of the last instance of the class created, like so:

Code:
class someClass
{
    static someClass *pInst;
    static void CallbackFunction();
 
    someClass();
    void CallbackMethod();
};

someClass *someClass::pInst = NULL;

someClass::someClass()
{
   pInst = this;
}

void someClass::CallbackMethod()
{
}

void someClass::CallbackFunction();
{
   if(pInst)
      pInst->CallbackMethod();
}

Register someClass::CallbackFunction() as the function to be called when the interrupt happens.

From this, it's relatively easy to see how pInst could be replaced with an array of instances, and how, in the constructor, the new instances can be added to the array, and how, in the callback function, the callback methods for all the instances can be called.

If you are happy with assuming that the class will only be instanced once (per Arduino), then it isn't necessary to worry about multiple instances.
Logged

New Jersey
Offline Offline
Sr. Member
****
Karma: 1
Posts: 482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm not really familiar with a singleton, but I just did a little research and it seems like I should use this technique for the hot tub project. 

I like your example for multiple instances scenario. 
Logged

Pages: [1]   Go Up
Jump to: