[SOLVED] Potentiometer as a switch between functions

I am using a potentiometer to choose between four options, visualised with indicator LEDs, using switch case. This works well. However, any function (later, a motor shall run for a certain time, for example) called from a case will be triggered ad infinitum, when the potentiometer knob isn't turned. For example, here, another LED isn't flashed 23 or 37 times, but keeps flashing, because the case has not changed when the potentiometer knob hasn't been turned, or stops flashing when the potentiometer knob is turned before the function finished what it was doing. What should happen is this:

  1. Potentiometer knob not turned: Function that was called is executed exactly once
  2. Potentiometer knob turned: Function previously called shall finish. New function called is executed exactly once

What would I need to change in order to achieve this functionality?

// Variables that remain constant
const byte pinPotentiometer = A0;
const byte pinsLED[] = {3, 4, 5, 6};
const size_t numLEDs = sizeof pinsLED / sizeof pinsLED[0];

// Variables that can change
bool stateLED = LOW;

void setup()
{
  for (int i = 0; i < numLEDs; i++)
  {
    pinMode(pinsLED[i], OUTPUT);
  }
}

void loop()
{
  int readingPotentiometer = analogRead(A0);

  switch (readingPotentiometer)
  {
    case 0 ... 255:
      // functionOne(); that does someting only once when called
      turnOffIndicatorLEDs();
      digitalWrite(3, HIGH);
      break;

    case 256 ... 511:
      flashLED(250, 750, 23); // Something to illustrate the problem
      turnOffIndicatorLEDs();
      digitalWrite(4, HIGH);
      break;

    case 512 ... 767:
      // functionOne(); that does someting only once when called
      turnOffIndicatorLEDs();
      digitalWrite(5, HIGH);
      break;

    case 768 ...1023:
      flashLED(125, 125, 37); // Something to illustrate the problem
      turnOffIndicatorLEDs();
      digitalWrite(6, HIGH);
      break;
  }
}

void turnOffIndicatorLEDs()
{
  for (int i = 0; i < numLEDs; i++)
  {
    digitalWrite(pinsLED[i], LOW);
  }
}

void flashLED(int timeon, int timeoff, byte flashes)
{
  static unsigned long timeNowLED = 0;
  static unsigned long timeIntervalLED = 0;
  static byte counter = 0;

  if (millis() - timeNowLED > timeIntervalLED)
  {
    timeNowLED = millis();

    if (stateLED == LOW)
    {
      timeIntervalLED = timeon;
      stateLED = HIGH;
      digitalWrite(13, stateLED);
    }

    else
    {
      timeIntervalLED = timeoff;
      stateLED = LOW;
      counter++;
      digitalWrite(13, stateLED);
    }
  }

  if (counter >= flashes)
  {
    counter = 0;
  }
}

look up "state change detection".

if ( previousPotReading != CurrentPotReading )
{
///then do the thing.
}
1 Like

Using an analog means he'll have to maintain local flags I think, as equality comparison is going to be flaky due to noise. Look at the case ranges.
@Lagom I hope you realise that if the pot is turned to, for example, 255, you'll have enough noise to get some values 254/255/256 to flicker between your cases, which may be irrelevant, or a disaster, depending on the purpose of the functions.
C

Perhaps they can use a percentage of deviation so that exact analog readings do not need to be matched but say within 10 percent of the previous. That's what I'd do and have done in the past and will do so when needed.

That could work, but in the general case, I think a rotary selector that can be set by an inexperienced operator to flicker between functions is a poor design choice. If the pot were a circuit board pot, set once and forget, okay, but as a user knob, sooner or later... But that's up to the OP.
C

Yeah, I could leave a deadband between the four ranges, but that's not my problem at hand. The potentiometer is very reliable and has four linear valley detents.

I have the feeling that the four functions should not be called directly from a case. To run "independently/concurrently", they must be millis() timed, like the flashLED example, and thus they must be called continuously until time's up, independent of which case triggered them to run.

switch (readingPotentiometer)
  {
    case 0 ... 250:
      // functionOne(); that does someting only once when called
      turnOffIndicatorLEDs();
      digitalWrite(3, HIGH);
      break;

    case 261 ... 506:
      flashLED(250, 750, 23); // Something to illustrate the problem
      turnOffIndicatorLEDs();
      digitalWrite(4, HIGH);
      break;

    case 517 ... 762:
      // functionOne(); that does someting only once when called
      turnOffIndicatorLEDs();
      digitalWrite(5, HIGH);
      break;

    case 773 ...1023:
      flashLED(125, 125, 37); // Something to illustrate the problem
      turnOffIndicatorLEDs();
      digitalWrite(6, HIGH);
      break;
  }

won't they be repeatedly called or does the pot need to select a mode that determines what is called within a timer loop?

How do you plan to "say" that you want the same function to execute again, with none of the others in between?

Or case 3 then case 5 without accident performing case 4?

a7

They can be repeatedly called. I have four motors that need to run, each for a different time duration, after one of the four cases have been selected. To keep the code simple, I put in a LED flashing function that runs for a set time duration. As I have it now, the function is called all the time for as long as the potentiometer knob isn't turned.

It's very much like spinning plates (or spinning tops). You start case one (spinning plate one), move on to case two, then case three, then four. You either let them all spin, until they fall off the stick (time duration), or you decide when it's getting hairy and exciting, to then go back and spin them all up again. Because you don't know how long each motor runs (time duration can be randomises a bit), things should get out of whack quite nicely after a while.

Maybe I should better draw a graphic, a concurrency graph.

so there are start/stop times for multiple motors and the start/stop times of each motor are not synchronized with each other, meaning one can be start/stopped while another is stopped or running?

if so, the pot sets the mode or start/stop time or periods and the timer start/stops multiple motors when their start/stop times occur

Indeed. Rotating the potentiometer, motors can be started in sequence, as shown in the concurrency graph, and they are not synchronised; each one shall run on its own, and could be re-started before their running time has expired.

Each case shall trigger a motor starting (for now LED flashing) to run for a certain time. So I am thinking I cannot call the four motor functions from the case statements directly, but rather use the case statements to set "motorStart" flags.

How long each motor runs cannot be adjusted by the performer, it shall be randomised for each motor.

Now we're getting a more concise picture of the design. Ok. Something to chew on.

I still wanna know how you move from a case to not-the-next case without triggering the in-between case(s).

And how you go from N to 1 without triggering N - 1 through 2 on the way.

And how you say case X again, or do you ever need to?

None of this might matter, it just isn't clear to me and I've less caffeine in me that might help.

It does seem like it might be fun to play. I'm more likely to use columns of LEDs than to hook up motors.

Why wouldn't just constantly poking along all motors one by one by one satisfy the game rules?

a7

i think it would be better to describe how you would like things to work rather than how to do it

what exactly does the pot do? does it select some action? does that action occur once or repeatedly if the pot is in the same position?

what does each line in your drawing represent? the behavior of 4 different motors or four possible sequences? what does each colored area represent? what does the height of the colored arro represent? why does the height get larger before going to zero?

@alto777 I will trigger in between cases; have a look at the graph. There's no jumping from case 0 to case 2, as a potentiometer is continuous. It is not like four momentary switches where cases could be triggered independently. That is not desirable here, hence the use of a potentiometer.

@gcjr I thought I had it described sufficiently, adding the graph. The potentiometer, as it is rotated, triggers four motors where each shall run independently for different lengths of time, the duration randomised within limits. If you reverse the potentiometer rotation, a motor that is already running, but in the slowing down phase, will be re-triggered. The black lines going left to right represent an arbitrary timeline after start-up. Each motor is represented with a colour. When a motor is triggered, it runs at full speed and slows down logarithmically over time, until its run-time is over. Then it either sits there doing nothing or it is retriggered. It really is like spinning plates, where you either accelerate the stick's rotation every now and then, or you let it peter out, until the plate almost falls off.

so the pot has ranges that can be called positions (or tom, dick, harry). each position corresponds to a motor. a motor is started when a pot "enters" a position. the motor slows and stops automatically unless restarted. a position can't be skipped.

looks like the code needs to recognize the position of the pot and a change in position to start a motor and capture its timestamp

a timing loop can determine when a motor period has expired and stop it

That's right. And instead of all that motor code, etc. I'm using a simple flashLED function that expires after a certain time as a "stand in", to keep the posted code to a minimum.

Depending on the selected case, LED starts flashing (motor starts running) until its time is up, no matter if the potentiometer has been turned or not. The LED can only start flashing again when that case is re-entered from another case.

I suppose that I have to take a timestamp in each case statement and use a flag somewhere, so when the case does not change, the function pertaining to that case is not re-triggered.

consider

const byte PinLeds [] = { 10, 11, 12, 13 };
const int  N          = sizeof(PinLeds);

unsigned long motorMsec [N];

#define Period  1000
#define Margin  2

int  positions [N+1];
int  pos;
int  posLst;
int  val;

enum { Off = HIGH, On = LOW };

char s [60];

// -----------------------------------------------------------------------------
// check if pot is inside range of values
void
posUpdate ()
{
    int n;
    val = analogRead (A0);

    for (n = 0; n < N; n++)
        if ((positions [n] + Margin) <= val && val <= positions [n+1]-Margin)
            pos = n;
}

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    posUpdate ();

    // turn led on & capture timestamp when pos changes
    if (posLst != pos)  {
        posLst = pos;

        motorMsec [pos] = 0 == msec ? 1 : msec;       // never zero
        digitalWrite (PinLeds [pos], On);

        sprintf (s, "loop: val %6d, pos %d", val, pos);
        Serial.println (s);
    }

    // turn led off & reset timestamp when time expires
    for (unsigned n = 0; n < N; n++)  {
        if (motorMsec [n] && (msec - motorMsec [n]) >= Period)  {
            motorMsec [n] = 0;
            digitalWrite (PinLeds [pos], Off);

            sprintf (s, "loop: n %d Off", n);
            Serial.println (s);
        }
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < N; n++)  {
        digitalWrite (PinLeds [n], Off);
        pinMode      (PinLeds [n], OUTPUT);
    }

    int val = analogRead (A0);
    for (unsigned n = 0; n <= N; n++, val += 10)
        positions [n] = val;

    posLst = pos = 0;
}

Thanks, that'll take me a while to understand.