4

Okay so you aren't able to pass a variable into in ISR. This is causing problems for me. I'm using a rotary encoder, and I need it to be connected to an interrupt pin and running a ISR. When using this method, no pulses are ever skipped and the knob works great.

Currently I have the ISR setup like this:

void vhISR()
{
  // this checks the PINE register for a 1 or 0 on the 5th bit (or 6th)
  // I'm bit shifting the result so that variable is either true or false for comparison, not 16/0 or 32/0
  rotCurrentA = (PINE & B00010000) >> 4; 
  rotB = (PINE & B00100000) >> 5;

  if (rotCurrentA != rotLastA)
    {
      if (rotCurrentA != rotB) // cw
        {
          voltageHigh += knobResolution;
          rotLastA = rotCurrentA;
        }
      else // ccw
        {
          voltageHigh -= knobResolution;
          rotLastA = rotCurrentA;
        }
    }
}

The code simply varies voltageHigh by 0.1V increments. The problem is this: I need to do this same 0.1 variations on several other variables, depending on which one I select. I need to increase/decrease a voltageLow, a timer, and a resistance, all that need +/- 0.1 increments (but on different ranges. For instance, voltLow is somewhere between 0.1 & 5, and VH is somewhere between 6 & 10). Normally what I would do is just pass the variable I needed by reference into the function and have the function increment/decrement that variable....but an ISR is not a normal function and you cannot pass a variable into it.

How do I get around this? I'm not sure if that's enough information to go off of let me know.

4
  • why not use the Encoder library?
    – Juraj
    Jun 27 at 18:07
  • Not an option. This is working perfectly, aside from this small issue. Jun 27 at 18:10
  • 1
    a switch statement based on a global variable with the current selected setting?
    – Juraj
    Jun 27 at 18:13
  • 2
    Don't change the variable in the ISR. Just keep track of the number of clicks and add them to the appropriate variable in code. You'd need to be turning off interrupts to use those variables anyway, so just replace that code with the read and modify.
    – Delta_G
    Jun 27 at 18:23

2 Answers 2

8

I would follow Delta_G's advice, and write an ISR that counts the steps and does nothing more. As a general rule, you want to do as little as possible within an ISR. I would even avoid floating point and just count the raw number of steps:

// Count of rotational steps updated in the ISR. Must be unsigned in
// order to avoid signed overflow, which is undefined behavior.
volatile uint16_t rotation_count;

void vhISR()
{
    static uint8_t rotLastA;
    uint8_t rotCurrentA = (PINE >> 4) & 1;
    uint8_t rotB = (PINE >> 5) & 1;

    if (rotCurrentA != rotLastA)
        return;
    if (rotCurrentA != rotB) // cw
        rotation_count++;
    else // ccw
        rotation_count--;
    rotLastA = rotCurrentA;
}

// Return the number of rotational steps since the last call. Should be
// called often enough to avoid overflowing an int16_t.
int16_t get_rotation()
{
    static uint16_t last_count;

    // Avoid a data race: rotation_count should not be modified while we
    // are reading it.
    noInterrupts();
    uint16_t count_copy = rotation_count;
    interrupts();
    int16_t delta = count_copy - last_count;  // this is signed!
    last_count = count_copy;
    return delta;
}

Note that the raw count is an unsigned number which can roll over modulo 216. The value returned by get_rotation(), however, is a signed number which, owing to the rules of modular arithmetic, is immune to the rollover of the raw count. As long as you call get_rotation() often enough (more often than every 32767 steps), the returned value will always be correct.

Then, in your main program, you can use this to update whatever variable you want:

const float knobResolution = 0.1;
float voltageHigh, voltageLow, resistance;

// The variable that the user has selected for changing.
enum {VOLTAGE_HIGH, VOLTAGE_LOW, RESISTANCE} selection;

void loop()
{
    // Set a pointer to the selected variable.
    float *selected_var;
    switch (selection) {
    case VOLTAGE_HIGH:
        selected_var = &voltageHigh;
        break;
    case VOLTAGE_LOW:
        selected_var = &voltageLow;
        break;
    case RESISTANCE:
        selected_var = &resistance;
        break;
    }

    // Update the selected variable.
    *selected_var += get_rotation() * knobResolution;
}

Note here that the selection enum is probably redundant with selected_var. You could use the latter to keep track of the user selection, then you would not need the switch/case.

1
  • This is awesome! Thank you for typing it all out for me. I'll have to mull this over soon and implement once I understand everything. Thanks! Jun 29 at 14:25
2

Okay so you aren't able to pass a variable into in ISR ...

Not as such, because the ISR is triggered by hardware, and therefore you don't pass arguments to it.

However there is nothing stopping you having a global variable which the ISR can access (preferably declared volatile) so the ISR can change its behaviour depending on what the global variable holds.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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