1

I have the following problem:

My arduino takes analogRead(A0) from a potentiometer whose position I can't adjust directly.

When analogRead(AO) goes to a value X, I want pin 8 to go HIGH, this causes the circuit to open. and stop A0 getting higher . so I do an if statement:

if(analogRead(AO)> X) {

digitalWrite(pin, HIGH)  \\opens the circuit} 

else {
digitalWrite(pin, LOW)  \close the circuit
 }

The issue I have here, is when then circuit is open, I would like to change the value of the potentiometer (A0) which is controlled by a motor which is also in the circuit: but doesnt depend on the arduino board. Given I can't control A(0) then the circuit will remain on A(0) >X forever.

The system going to LOW pin or close circuit, after it reaches HIGH pin for 2 seconds would be great, but then the program goes to the beginning and realizes that still A(0)> X , and goes back to a open circuit state, therefore not allowing me to have the close circuit anymore in order to make A(0) < X and leave the circuit closed . Maybe something that cyclically turns pin on an off for t mseconds? but this is not ideal as it would cause high wear on the system

Edited the post to be more precise. I hope now its clearer
thanks


thank you for your answers, but after playing for a while with your codes, and the ideas behind it, still cannot get it to work.

for instance, Chrisl with your first:

when potentiometer is at > X, It cuts the circuit which is good, but still doesn't go off until both potentiometer goes to < X and 3 seconds lapse , but this is not going to be possible to achieve anyways because the value of the potentiometer is fixed on X

THis is what Id need-

if (PinState == HIGH)

after 3 seconds go low,

and not turn HIGH until at least these two conditions are met; analogRead has gone to < X and then analogRead > X ,and in that moment we are back at
PinState HIGH is even possible to achieve something like this?

the circuit is something like this:

schematic

simulate this circuit – Schematic created using CircuitLab

UPDATE: I have using this code, which does almost what I want: if(analogRead(A0) > X) { unsigned long currentMillis = millis();

if ((unsigned long)(currentMillis - previousMillis) >= interval) {

   pinState = !pinState; 
   digitalWrite(pin, pinState)
   // save the "current" time
   previousMillis = millis();
}}

When it gets to analogRead(A0)> X the problem of this code is that it switches the pin On and OFF continuosly,

but instead of switching it off/on after the interval time , I want it to be LOW always. Of course as long as time is more than interval,and not get to a HIGH, until It has gone back to analogRead(A0)> X and time is less than interval. And repeat the process... When time is more than interval it will go from where it was digitalWrite(pin, HIGH) to the new state digitalWrite(pin, LOW)

I made this table to be more clear

Table: analogRead(A0) > X?

1-yes -->now check if time in this state is less than interval = 3000:

1.1-YES, ---> digitalWrite(pin, HIGH)
1.2- if no ( time is more than interval ) ---> digitalWrite(pin, LOW)

2- No, analogRead(A0) < X . So --> keep digitalWrite(pin, LOW)

NEW UPDATE:

The state diagram for the new ranges should be something like this. I hope it is clear. I also removed the delays and replaced (V < X ) for (V < Y ) , it works great. Thank you Edgar.

Z in the diagram being the variable for the new range, Z << X
enter image description here

LAST UPDATE:

Question Solved by Edgar Bonet

7
  • Its difficult to understand, what exactly the code should do. Please try to explain it more clear. Timed things can be best done using millis() like in the BlinkWithoutDelay example, that comes with the Arduino IDE
    – chrisl
    Oct 9, 2019 at 12:05
  • thanks, i tried to use it, but didnt work on what was trying to achieve, i edited the post hoping its more clear now.
    – Fraunhofer
    Oct 9, 2019 at 12:21
  • So you want to physically turn the potentiometer with the motor to adjust the value to be lower than X? Then the motor must be controlled from the Arduino
    – chrisl
    Oct 9, 2019 at 12:43
  • maybe this way could work, everytime the system goes to a HIGH, it will remain there for 3 seconds , then it makes circuit close or pinLOW. on the background however the variable A0 will keep reading but not affecting pin8 or the close circuit situation , and when the system goes more than X , it does the IF..else statement again, HIGH(3 seconds) stop the IF..ELSE, reads A0 on the background and if more than X all repeats
    – Fraunhofer
    Oct 9, 2019 at 12:51
  • it's a problem to control the motor directly because it depends on another parts of the circuit.
    – Fraunhofer
    Oct 9, 2019 at 12:54

3 Answers 3

0

The description you give for the desired behavior is very unclear and hard to follow. I am going to rephrase what I think I understood in terms of a finite state machine, which is the correct way of describing this kind of systems. Please, review the description below, as chances are I did not completely understand what you really want.

Let's say the system can be in any of these three states:

  • In the V_LOW state, the input voltage is below the threshold X and the output is LOW. If the input raises above X, we set the output to HIGH and transition to the V_HIGH state.

  • In the V_HIGH state, the input is above the threshold X and the output is HIGH. If the input falls below X we go back to the V_LOW state. If we remain in V_HIGH for three seconds, we switch the output to LOW and go to the TIMEOUT state.

  • In the TIMEOUT state, the input is above the threshold, yet the output is LOW. When the input falls below X we go to the V_LOW state.

Below is a state graph for this machine. The states with a gray background have the output LOW, whereas the state with a white background has the output HIGH. Each possible transition is drawn as an arrow, labeled by the condition that triggers the transition:

state graph

Is this the behavior you have in mind? If no, try to describe the behavior you want in terms of a finite state machine, as I have done here. If yes, then I suggest you follow chrisl's advice and implement some hysteresis, by having different thresholds for going into and out of V_LOW.

Anyhow, below is a translation of the FSM above in C++. It may not be the most efficient way to write it, but I believe it is straightforward enough to not require any additional explanation:

void update_output()
{
    static enum { V_LOW, V_HIGH, TIMEOUT } state;
    static uint32_t time_entered_v_high;
    int V = analogRead(A0);

    switch (state) {
    case V_LOW:
        if (V >= X) {
            digitalWrite(pin, HIGH);
            state = V_HIGH;
            time_entered_v_high = millis();
        }
        break;
    case V_HIGH:
        if (V < X) {
            digitalWrite(pin, LOW);
            state = V_LOW;
        } else if (millis() - time_entered_v_high >= 3000) {
            digitalWrite(pin, LOW);
            state = TIMEOUT;
        }
        break;
    case TIMEOUT:
        if (V < X) {
            state = V_LOW;
        }
        break;
    }
}

Edit 1: Here is how to implement the hysteresis: you need to choose a second, lower threshold, for going back to the state V_LOW. Let's call it Y, which is smaller than X. Then, both on the diagram and the code, replace every instance of “V < X” by “V < Y”. That's it. There is absolutely nothing more to change.


Edit 2: Addressing the updated question. Now the output should go HIGH when the input is out of a defined range. I'll add hysteresis right away by using four thresholds V0 < V1 < V2 < V3:

  • the input is in range if it is within [V1, V2)

  • the input is out of range if it is not within [V0, V3)

  • the ranges [V0, V1) and [V2, V3) provide hysteresis.

Note that for consistency I like to have all the intervals closed on the same side. I will also rename the states V_LOW and V_HIGH to IN_RANGE and OUT_OF_RANGE respectively. Here is the updated code:

void update_output()
{
    static enum { IN_RANGE, OUT_OF_RANGE, TIMEOUT } state;
    static uint32_t time_exited_range;
    int V = analogRead(A0);

    switch (state) {
    case IN_RANGE:
        if (V < V0 || V >= V3) {
            digitalWrite(pin, HIGH);
            state = OUT_OF_RANGE;
            time_exited_range = millis();
        }
        break;
    case OUT_OF_RANGE:
        if (V >= V1 && V < V2) {
            digitalWrite(pin, LOW);
            state = IN_RANGE;
        } else if (millis() - time_exited_range >= 3000) {
            digitalWrite(pin, LOW);
            state = TIMEOUT;
        }
        break;
    case TIMEOUT:
        if (V >= V1 && V < V2) {
            state = IN_RANGE;
        }
        break;
    }
}
5
  • Dear Edgar, I cannot thank you enough. That is exactly what I wanted, so thankful to you. This problem has driven me crazy. I just added a few delays at the end of each case and now it works perfectly without any flickering when switching states. As per your suggestion about the hysteresis .
    – Fraunhofer
    Oct 10, 2019 at 23:38
  • Just a question, In case I wish to add a range, for example ( case V_LOW , case V_HIGH , case TIMEOUT ) going from ( ( (V >= X) , (V < X) , V < X)) to (( V >= X && V <= X ), ( V < X && V > Y ), ( V < X && V > Y )) with Y being a minimum value, How that could work? . I tried adding the ranges, more if's , more cases, but neither would work. I also tried creating a function: e.g. bool inRange(int V, int minimum, int maximum) and replacing the range ( V >= X && V <= X ), ( V < X && V > Y ), ( V < X && V > Y ) ) with inRange(V, X, Y) functions but didn't work either .
    – Fraunhofer
    Oct 10, 2019 at 23:54
  • @Fraunhofer: 1. Flickering is expected with this FSM whenever the input is noisy and close to the threshold. Hysteresis is the usual way to fix it. Delays are a stop gap solution that should better be avoided. 2. I couldn't understand your ranges thing. Could you draw your idea as a state diagram similar to the one I drew? You can then add it to the question. Oct 11, 2019 at 11:49
  • I added the state diagram . Thank you very much
    – Fraunhofer
    Oct 11, 2019 at 12:56
  • Thank you so much for all your help and explanations. Now it's perfect.
    – Fraunhofer
    Oct 11, 2019 at 15:35
0

As I wrote in my comment, you can use millis() to solve your problem. First you should ditch the else statement, since you don't want the analog signal to turn the output of.

unsigned long timestamp=0;
unsigned long interval = 3000;

void loop(){
    if(millis() - timestamp > interval){
        digitalWrite(pin, LOW);
        if(analogRead(A0) > X){
            timestamp = millis();
            digitalWrite(pin, HIGH);
        } 
    }
}

This code will check, if the time difference between now and the last activation is over 3s. If yes, it will first set the output to LOW. Then it checks, if the analog input is above X. If yes, the output will be set to HIGH and the timestamp will be set to the current time. On the next loop iteration the time difference will be lower than 3s (since we just set the timestamp to now). Thus nothing will change, keeping the output to HIGH for 3s. After that the output will be set to LOW and the code will be sensible to A0 again.

If you the output to just trigger once ever, you can use a simple flag.

unsigned long timestamp=0;
unsigned long interval = 3000;
byte flag=1;

void loop(){
    if(millis() - timestamp > interval){
        digitalWrite(pin, LOW);
        if(flag && analogRead(A0) > X){
            timestamp = millis();
            flag = 0;
            digitalWrite(pin, HIGH);
        } 
    }
}

The flag is initially set to 1, which actives the analogRead() part. Then it is set to zero, which deactivates it.

Note that this code is relatively simply and comes with some caveats, that might or might not matter to you:

  • The code will not react to changes in the first 3 seconds after startup, since the timestamp is set to zero initially.
  • After about 50 days runtime, the millis() counter will overflow. So, if the inner if statement doesn't get triggered for this long, the code will again not be able to react for 3s (until the time has run 3s after the timestamp).
0
0

Using different threshold values to switch states - a higher threshold to go HIGH and a lower value to go LOW - is called hysteresis. It makes a dead-zone between the two switching values that prevents jitter, or flickering the LED in this case, that would happen with a single switch-point and the analog value was very close to it.

To do that, you just need the second clause of your if block, now an else, to become an else if. So it would look like this (in pseudo-code):

if analog > 5,
   open the circuit;
else if analog < 4
   close the circuit;
// else must be >4 but <5, so do nothing.

for 3 seconds I make circuit close ... when the system goes more than X again, it does the IF..else statement again

Update:

So instead of hysteresis, you want close the circuit for a fixed amount of time? Then try something more like this:

if millis() - stop_time > 0 && analog > 5,
   save stop_time = (millis()+3000);
   open the circuit;
end
1
  • i did that but still not what I want. what if, I keep the if statement, and for 3 seconds I make circuit close goes to LOW, on the background however the variable A0 will keep reading but not affecting pin8 or the close circuit situation , and when the system goes more than X again, it does the IF..else statement again
    – Fraunhofer
    Oct 9, 2019 at 12:45

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.