0

As usual, I know how to bypass this problem with some ugly patch work, but I want to make it elegant: I would like to make a small wrapper for motors commanded by an Arduino, which would unfortunately mean writing a distinct interrupt routine for each instance of motor because it has to modify the right step counter (member variable of the motor class) to determine its rate. However those functions would obviously have the same processing... My question is: how can I determine which counter to modify in a unique interrupt routine?

Here is what I have so far, I'd like to hide interrupts from the user.

class Motor {
        volatile int counter;
        unsigned long lastUpdate; //Last timestamp update (for calculating the rate)

        static const unsigned int RESOLUTION = 1024; //Number of steps in one rev
        static const unsigned int RPMS_TO_RPM = 60000; //Convers rev/ms to rpm

    public:
        Motor() : counter(0)
        {
            lastUpdate = millis();
        }

        void encoderInput(bool pinA, bool pinB)
        {
            counter += (pinA ^ pinB)*(-1)+!(pinA ^ pinB);
        }

        int getRate() {
            int ret = float(counter)/RESOLUTION/(millis() - lastUpdate)*RPMS_TO_RPM;
            lastUpdate = millis();
            counter = 0;
            return ret;
        }
};

/* Example:
 * Motor motor1;
 *
 * void motor1_isr(void) {
 *    motor1.encoderInput(PIN_A, PIN_B);
 * }
 *
 * void setup() {
 *    attachInterrupt(PIN_I, motor1_isr, CHANGE);
 *    Serial.begin(9600);
 * }
 *
 * void loop() {
 *    Serial.println(motor1.getRate());
 *    delay(1000);
 * }
 */   

Thanks for your help, I think it would be useful to other people as well once it's done with :)

Regards, Mister Mystère

Mister Mystère
  • 952
  • 2
  • 16
  • 39
  • 1
    Typically you'd want your ISR to get information from atomically updateable volatile variables (or non-atomic mailbox ones it copies only upon seeing an atomic "updated" flag). To handle multiple motors, you either have the ISR do all of them at once, or if that means too long in the ISR, you can have it round robin service one on each successive interrupt, and increase the interrupt frequency by the number of motors. – Chris Stratton Jun 16 '13 at 16:15
  • You mean make a pointer to the member variable volatile, and call that pointer in the ISR? But the ISR can be called randomly... Would you mind to answer with an example? I can't do them all in the interrupt since a priori I don't know the number of motors (I want to make it generic). I've updated my question above with a code that normally works (I don't have Arduino this weekend), you think that's the best (simplest for the dev and the user) we can do? – Mister Mystère Jun 16 '13 at 16:29
  • 1
    Honestly, I'd recommend that you not use C++ in an ISR, especially on a system this tiny. Consider using static functions operating on compact data structures, and be very careful of atomicity and volatility concerns when changing those. Speaking generally, you could have a variable size array of structs each giving data for one motor. You can't atomicaly update a struct, so you'd probably have to go with flagging when it has been changed. – Chris Stratton Jun 16 '13 at 16:33
  • Yes I agree that working with interrupts is hard enough thinking about atomic instructions, but I figured this kind of wrapper is simple enough to be nearly equivalent to the contents of its functions after compilation. I'll keep the topic open for now and use the above code (please do not hesitate to ring if you spot something in there) and I'll switch for simpler pure C if problems show up. – Mister Mystère Jun 16 '13 at 17:09
  • These comments spawn good questions. Can you ask "what is the difference between a class and struct?" I can't wait to answer "nothing" (with a few small caveats). Another good question: "How are operations made atomic on a microcontroller. The answer is to disable/enable interrupts. – jdr5ca Jun 18 '13 at 04:49
  • Of course it has already been asked and answered: http://stackoverflow.com/questions/92859/what-are-the-differences-between-struct-and-class-in-c – jdr5ca Jun 18 '13 at 05:55

1 Answers1

1

You are facing a fundamental problem: an ISR is called with no data. In other words, the ISR function is not provided any data that indicates its source of the interrupt.

The basic approach is that the person writing the code provides the linkage between input and action by hardcoding a function. The tricky approach you are looking for is a method by which the ISR can figure out what the source was.

So you want to have N motors with 2N inputs from quadrature encoders. A single interrupt handler is attached to all the input pins on a CHANGE condition. The same handler gets called for all N motors. It can figure out which Motor to update by comparing the input pins to the values the last time it was called. If the input pins changed, then call that motor. Here is a psuedo code

Motor motor1;
Motor motor2;

onSomethingChanged() {
  static int in1aprev, in1bprev;
  static int in2aprev, in2bprev;
  int in1a, in1b;
  int in2a, in2b;
  in1a = digitalRead(....
  same for other in's

  if( (in1a!=in1alast) || (in1b!=in1blast)) {
    motor1.encoderInput(in1a,in1b);
    in1alast = in1a;
    in1blast = in1b;
  }

  if( (in2a!=in2alast) || (in2b!=in1blast)) {
    motor2.encoderInput(in2a,in2b);
    in1a2ast = in2a;
    in1b2ast = in2b;
  }

  return;
} 

Not so long ago this type of function would be handled in an entire chip (see programmable interrupt controller ). The chip implements all the logic to trigger and capture the source of the interrupt. The main CPU just gets a trigger "something happened". The handler polls the chip to ask "what happened".

Having offered this method, I'm not sure I would recommend. You cannot hide what is going on with your code. The motor must be physically wired to the correct pins. You have consumed a scarce resource -- you have to tell people.

jdr5ca
  • 2,809
  • 14
  • 25
  • You exactly understood what I was looking for, thanks for the very informative answer. Clever solution, but in order to have the behavior I am looking for (add as many motors as the user wants) I need to write a lot of code which shouldn't have its place in an interrupt lasting less than 1ms. Worth a shot, I'm still happy to have brainstormed on it with you all. – Mister Mystère Jun 20 '13 at 16:49
  • Agreed you definitely need to think about how long this might take as it directly impacts the encoder rate you can keep up with. If you are trying to run at 500 Hz encoder speed with only 50% time consumed by interrupt handler, then you have 1 millisecond. But that is 16000 cpu clock ticks... – jdr5ca Jun 21 '13 at 04:54
  • Update, it works nicely. The encoders call 2 interrupts every 1.8ms at full speed and it doesn't seem to bother it. Maybe because the content of the loop is "clocked": the maximum frequency of actions in it is 20Hz (motors rates feedback). – Mister Mystère Jul 01 '13 at 21:47