Go Down

Topic: Hysteresis (Read 1 time) previous topic - next topic

6v6gt

Feb 04, 2018, 03:10 pm Last Edit: Apr 17, 2018, 09:17 pm by 6v6gt
Hysteresis

This exists in science and nature and is broadly defined but basically something similar to this: Hysteresis is the dependence of the state of a system on its history. Anyway, its application here in the Arduino world is very concrete, generally to minimise oscillation between switching states.


Problem Area:

You've designed a system which determines a state based on an input. For example, it could be setting the brightness of a display based on
the ambient light measured via an analog pin. It could be a potentiometer which you want to use to input numbers on the range 0-9 into an Arduino. It could be thermostat which controls a heater. You discover problems with your display when the ambient light falls very close to the boundary of two brightness levels resulting in flicker. Or in the potentiometer example, you've set it close to the boundary of two values and it wanders between them. Or the heater constantly switches on and off when the ambient temperature is close to the thermostat setting.

Solutions:

There is a number of solutions. One is to add a time delay between changes of state and this prevents rapid oscillation between two states. This would minimise display flicker (etc.) in the example above but could make the system less responsive.
Another solution, which is the theme of this article, is to simply add some hysteresis, that is 'stickiness' or reluctance to leave the current state.
This hysteresis solution can be implemented in hardware or software. However the one presented here is a pure software solution.

Diagram

The diagram below illustrates the situation where you are reading an analog value in the range 0 to 1023 (say it is derived from light sensor) and you want to convert that into 5 levels that is level 0 through to level 4 (say to control the brightness of a display using pulse width modulation).
Broadly, the diagram illustrates that the conditions for leaving an existing state are different (within a margin, tougher) than entering it. Taking a detailed look, let's say the initial state of this system is that an analog value of 500 is read from the sensor. From the graph, it is clear that this yields an output level of 2. Now let's further assume that the light level drops slowly so the analog value moves downwards. At the point where the analog value reaches 380, the output level switches from 2 to 1. If, after dropping past 380, the analog value now starts rising, it must reach 420 before the output level switches back from 1 to 2. So, it is this difference (40 analog units in this case ) between leaving and entering adjacent states which provides the protection against flicker in this example. The arrowed squares in this graph are, incidentally, similar to the classic Schmitt trigger graph.





Software implementation.

This is a simple function which is parameterised for a (linear) potentiometer across the power rails with the wiper connected to analog pin A0 yielding values in the range 0 to 9. It can easily be adapted for other purposes such as controlling display flicker by altering the number of levels and the middle transition points between levels to suit the application.


Code: [Select]


/*

   Hysteresis

   This code example shows obtaining values 0-9 from a potentiometer wired
   as a voltage divider to analog pin A0. These values remain stable even if the potentiometer
   is set on a border point between two values.

   The same principle could be used to set the brightness of a display
   dependent on the ambient light, but free from flicker or switching a heater
   on based on a thermostat etc.

   6v6gt 03.feb.2018
*/




uint16_t getOutputLevel( uint16_t inputLevel ) {
  //
  //  convert an input value into an output value with hysteresis to
  //  avoid instability at cutover points ( eliminate thrashing/flicker etc.)
  //
  //  6v6gt 03.feb.2018

  // adjust these 4 constants to suit your application

  // ========================================
  // margin sets the 'stickyness' of the hysteresis or the relucatance to leave the current state.
  // It is measured in units of the the input level. As a guide it is a few percent of the
  // difference between two end points. Don't make the margin too wide or ranges may overlap.
  const uint16_t margin = 10 ;   //  +/- 10

  // set the number of output levels. These are numbered starting from 0.
  const uint16_t numberOfLevelsOutput = 10 ;  // 0..9


  // define input to output conversion table/formula by specifying endpoints of the levels.
  // the number of end points is equal to the number of output levels plus one.
  // in the example below, output level 0 results from an input of between 0 and 112.
  // 1 results from an input of between 113 and 212 etc.
  const uint16_t endPointInput[ numberOfLevelsOutput + 1 ] = { 0, 112, 212, 312, 412, 512, 612, 712, 812, 912, 1023 } ;

  // initial output level (usually zero)
  const  uint16_t initialOutputLevel = 0 ;
  // ========================================


  // the current output level is retained for the next calculation.
  // Note: initial value of a static variable is set at compile time.
  static uint16_t currentOutputLevel = initialOutputLevel ;

  // get lower and upper bounds for currentOutputLevel
  uint16_t lb = endPointInput[ currentOutputLevel ] ;
  if ( currentOutputLevel > 0 ) lb -= margin  ;   // subtract margin

  uint16_t ub = endPointInput[ currentOutputLevel + 1 ] ;
  if ( currentOutputLevel < numberOfLevelsOutput ) ub +=  margin  ;  // add margin

  // now test if input is between the outer margins for current output value
  if ( inputLevel < lb || inputLevel > ub ) {
    // determine new output level by scanning endPointInput array
    uint16_t i;
    for ( i = 0 ; i < numberOfLevelsOutput ; i++ ) {
      if ( inputLevel >= endPointInput[ i ] && inputLevel <= endPointInput[ i + 1 ] ) break ;
    }
    currentOutputLevel = i ;
  }
  return currentOutputLevel ;
}





void setup() {
  Serial.begin( 9600 ) ;
}

void loop() {

  Serial.print( analogRead(A0) ) ;
  Serial.print("  " ) ;
  Serial.println(  getOutputLevel( analogRead(A0) ) ) ;

  delay ( 500 ) ;
}





Edit 1:
Post #25 contains an improved class implementation of this above code.

GoForSmoke

#1
Feb 22, 2018, 02:00 pm Last Edit: Feb 22, 2018, 02:49 pm by GoForSmoke
Hysteresis is like a light switch. The switch is always ON or OFF and has to cross the middle to change.

I would add burning up relays to the problems list and while reading your solutions it hit me that over the minimum time a relay needs between switching, ADC dithering might be filtered out as no-change: if change < 2 then current = prev. No dither. That's adding hysteresis of 1!

1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

lastchancename

#2
Feb 22, 2018, 02:12 pm Last Edit: Feb 23, 2018, 10:11 pm by lastchancename
@6V6GT
Nice explanation...

EDIT : So "what goes up, doesn't necessarily come back down - at the same place or time...". !
Ask the right question, which can be hard for beginners, but this doesn't mean that google is broken.
Experienced responders have a nose for laziness, (they were beginners once)... expecting the poster to contribute to the learning experience.

Robin2

#3
Feb 22, 2018, 02:42 pm Last Edit: Feb 22, 2018, 02:43 pm by Robin2
This is a very useful Topic. However I suspect many newbies will ignore the topic because they don't know what the Title means.

To get newbies interested maybe it would be useful to extend the Title - perhaps something like this

Avoid unwanted short term jumping between two states - hysteresis


And I do hope someone comes up with a better suggestion. This was my 3rd attempt - which just goes to show how useful the word hysteresis is when you know what it means

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

GoForSmoke

#4
Feb 23, 2018, 12:16 am Last Edit: Feb 23, 2018, 12:18 am by GoForSmoke
How to make input not flip-flop/dither, the magic of hysteresis.
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

6v6gt

Thanks for all the suggestions. I think in reality, it is not really a topic for the real (green behind the ears) newbie.
A newbie is happy when he has got something to roughly work. It is only when he develops a bit and goes through the process of refining his solution, does he start to consider all those bad things: flicker , thrashing wandering between states, dither etc. etc. and wonders how these could minimised.
I think the main value of this tutorial is that when such a case occurs, and the user is looking to optimise his sketch, that he can pointed in this direction with a brief hint about hysteresis.
I did, incidentally, get one PM from a (more advanced) user who wanted to use the potentiometer example to convert a number of slider potentiometers to each generate 40 discrete values. It caused me to realise that the example function I gave did not scale up well with multiple potentiometers because it contains a (potentially large) array which would have to be extracted in his case as global, but for a beginner to illustrate the principles I guess it is OK.

alto777

I PM'd 6v6gt on the matter of using this idea to make a 40 step switch.

My slide faders were delivered, according to UPS! Sadly they weren't delivered to me, so somewhere a nice shipment of parts is probably confusing someone.

But. I did modify the code for 40 positions and test it with a rotary potentiometer - it works very well. I have no doubt that I will be able to get a stable equivalent to the 40 position slide switch I seek to simulate.

I timed the function and analog read with what seemed would be worst case values, virtually twisting the knob from 0 to full - I got 90 uS average time to read and evaluate.

Then, as much as I love tables, I thought I could replace the table with a calculation. I used an function with one floating point multiply operation as a replacement to the table, that is all endPointTable[ ] became endPointTable( ), shoulda called it endPointFunction( ) I suppose.

I timed this and got 414 uS average time. Not too bad, and I prolly could call it a day - my project's main loop will be 100 Hz / 10 milliseconds and not much has to be done, so I have the time.

I cut some of the calls in the "search" loop and the time dropped to 240 uS. I did not try to eliminate all the calls to my function as it looked a bit tricky with the integer / floating math - perhaps a mixed point thing would mean we wouldn't need the table (or function) in the search loop at all.

Instead I replaced what is essentially a linear search with a binary search. That made the  time is 120 uS. It eliminates the table at the expense of what is probably a bit more code, I did not measure that.

<Later> during dinner (!) I had a brain wave and realized the mixed point math could be fairly straight ahead. Here's the modified sketch for a 40-step analog input using no floating point and no table, seems to work. I did lose the ability to have different sizes for the regions that the table allows, which might be useful at the ends of travel. Each of my steps is 1/40th of the total. I went back to the original linear search as it is necessary for how it tracks along the segments.

It makes using a different number of steps a bit easier (should # define NUMBER_OF_STEPS or pass it as a variable) and the hysteresis margin is independent of that number. By my measurements it is about 65 uS, a bit faster than the original after it was changed to 40 steps.

This math will break when we ask for more than 64 steps.

Sorry it isn't too pretty. I'm quite aware that I may have overlooked something. When I get the slide faders I'll see if they work well and report.

I am still kicking around ideas for handling at least 8 and maybe as many as 11 "switches", the big part of that problem is needing 8-11 analog to digital conversion paths.

I think hysteresis is an important fundamental idea that "everyone" should know about. Thanks 6v6gt for jump-starting this!

Code: [Select]


/*

   Hysteresis

   This code example shows obtaining values 0-9 from a potentiometer wired
   as a voltage divider to analog pin A0. These values remain stable even if the potentiometer
   is set on a border point between two values.

   The same principle could be used to set the brightness of a display
   dependent on the ambient light, but free from flicker or switching a heater
   on based on a thermostat etc.

   6v6gt 03.feb.2018

   modifications alto777 for 40-step slide fader "switch", no table.
*/

# define test 0 /* 1 = test potentiometer, 0 = time the read/adjust process */

uint16_t getOutputLevel( uint16_t inputLevel ) {
  const uint16_t margin = 400 ;   //  +/- 10
  const uint16_t numberOfLevelsOutput = 40 ;  // 0..39


  const  uint16_t initialOutputLevel = 0 ;
  static uint16_t currentOutputLevel = initialOutputLevel ;

  uint16_t lb = currentOutputLevel * 1023;
  uint16_t ub = lb;

  if ( currentOutputLevel > 0 ) lb -= margin  ;   // subtract margin
  if ( currentOutputLevel < numberOfLevelsOutput ) ub +=  margin  ;  // add margin

  // now test if input is between the outer margins for current output value
  if ( inputLevel < lb || inputLevel > ub ) {

    uint16_t i, b1;
    b1 = 1023;
    for ( i = 0 ; i < numberOfLevelsOutput ; i++ ) {

      if (inputLevel <= b1) break ;
      b1 += 1023;
    }
    currentOutputLevel = i ;
  }
  return currentOutputLevel ;
}



void setup() {
  unsigned char looper;
  long microTimer;

  Serial.begin( 9600 );

  if (!test) {    /* read but ignore A0, use worst case values */
    looper = 20;
    while (--looper) {
      microTimer = micros();
      analogRead(A0);
      getOutputLevel(0);
      analogRead(A0);
      getOutputLevel(40 * 1023);
      Serial.print((micros() - microTimer) / 2);
      Serial.println(" microseconds per ");
    }
  }
}

void loop0() {}

void loop() {
  if (test) {
    Serial.print( analogRead(A0) ) ;
    Serial.print("  " ) ;
    Serial.println(  getOutputLevel( analogRead(A0) * 40 ) ) ;

    delay ( 50 ) ;
  }
}




alto777

6v6gt

Of course the sample sketch there was only to illustrate the concept and the table in it had a (more or less ) 1 to 1 relationship with graph. If the steps are constant, your calculation method is more space efficient. Obviously, if you were attempting the same thing using a logarithmic potentiometer, you'd have difficulties without using a table.
I think the timings you have shown would certainly be quick enough for most human operators!

For multiple potentiometers, as in your case, either create a class with each potentiometer being a separate instance or increase the parameter list to make the current state an input parameter. The quick and dirty solution is to copy and paste the function for each potentiometer.

I haven't got a proper 270 degree sweep type potentiometer so I can't test this, but I am curious to know the maximum number of discreet positions you can get out of this solution, and it still be usable. Can you for instance use a potentiometer to input numbers 0 to 99. I guess you'd have to reduce the margin to say 3 or 4 and maybe create 2 dummy positions, one for each end.

alto777

Just watching the raw analog reading I see a number and at worst +/- 1 count. It seems one could smooth this out with as many as 256 buckets.

In the case of, say, a volume or brightness control all we want to achieve is to eliminate constant dithering, so this solution would work well.

In my particular case the 40 switch positions one to the next can have dramatic effects beyond a slight increase or decrease of a parameter. So I want not only to eliminate jitter but to be able to dial slide in a specific position.

It is my hope that 100mm of slider travel will allow the knob to indicate a position that can be accurately read by the program and the user. I may need to have an additional source of feedback.

With another feedback mechanism and maybe for any continuous parameter where many steps were desired I would probably choose a rotary encoder instead. Again in my case I am trying to recreate the look of an existing instrument that employs 40 position slide switches, such a switch somehow not showing up in the current offerings from the usual suspects. :-)

I have considered graphical touch screen slider switches, in fact this is what led me to this tutorial as I was thinking about how to implement dragging a virtual slider to make it hop crisply from one stable position to the next.

alto777

GoForSmoke

I think that temperature control may provide the most basic examples of use of hysteresis. The concepts of thermal inertia and time become simple and clear, the set points become design decisions, the HVAC hardware becomes better known and you don't end up with a heater and AC fighting or heater starters burning out early and higher bills to show for it.

Alto --- do you want a slider to give back 40 positions for the full movement range? You can do that easily while reading 8x default speed since you don't need great precision. The analog value will be an unsigned integer 0 to 1023 which divided by 40 is 25.75, but you say 25 per step and that 1st step is zero while the last half-step is 40 and keep the math unsigned 16-bit.

For speed you could PROGMEM a table of 1024 bytes, 1 for analog read's 0 to 1023 return range, the read is the index, it doesn't get simpler or quicker and there's plenty of flash to spare.

Nick Gammon's site has a great tutorial on analog digital conversion including ADC clock speed control with tests and results to maybe inform your project design at little. :)
http://www.gammon.com.au/adc
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

alto777

Whoever got my slide faders was nice enough to bring them over and leave them at my door, so I have had a chance to experiment with the 40 position slide fader substitute.

Short story: I will need to have another means of indicating the current setting. The quality of the slide fader ("real" ones!) and the tradeoff between margin and the physical range of travel that will definitively produce a given number does not allow setting by observation of the slider knob alone.


Longer, for those interested.


I agree with GFS that the example of binary hysteresis will probably be the most accessable for newbies. I also remember trying to get the light switch to be in an flickering state. Fortunately my father 'splained it to me, used big words and was able to adduce examples from science, engineering and politics(!). Never mind how long [ago] precisely.

I appreciate the attention to my problem. GFSs' ideas are certainly valid. In the case of adjusting brightness or volume, it might not matter if the value is not stable. I do wonder if it would be detectable, 1 step of 100 in loudness or flicker in brightness - it would still be best to use the hysteresis idea in the original code to lock in a dialed position.

In my case, however, it is essential to not only eliminate jitter but to be able to pick accurately a "switch" position. The switches are not for stepping through a continuum: one position "28" might mean "walk the dog" and "29" might be "evict your mother-in-law". Every position has the potential to be switching up a big difference to the inputs to the process.

The original slide switch was a) quite long and b) had mechanical detents and c) was supported by a nice dialplate and knob that all made setting (and reading) the value quite easy and unambiguous.

I thought I found an error in my various re-writes of 6v6gt's sketch. There may indeed be errors, but what kept me busier for longer than I should admit is that the test loop() code does not pass the same value to getOutputLevel() as it does to Serial.print(). Things got quite a bit more "explicable" when I stashed the analogRead() value in a variable and passed that variable to both functions that used it. Here I think someone could make a pithy rule - if your process depends on an external value, get that value once and use it in all subsequent steps. Given that the analogRead() might change by 1, and that that 1 might push us under or over bound +/- margin, no matter how little time is between the readings you are looking for an odd report.

The slide fader has more variation from reading to reading. Also the value can jump from minor jostling of the knob. I'd blame it on the bargain basement nature of the parts, normally, as I tend to cheap out, but these were better faders. I am sure they would be fine in most applications.

The 100mm nominal travel is reduced by some hefty "landing pads" at 0 and full throw. This is nice for being able to get those values, not so nice for leaving the active length to be divided into 40 steps. As I have said, I will need another means for indicating the setting and hysteresis will be key for making settings stable.

alto777


CrossRoads

"In my particular case the 40 switch positions one to the next can have dramatic effects beyond a slight increase or decrease of a parameter. So I want not only to eliminate jitter but to be able to dial slide in a specific position."

This sounds to me like a perfect use for a Rotary encoder.  Can move one click up or down & detect, encoders can be had with detents so you can feel when one click has been made, if you want multiple clicks you just keep turning the dial.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

alto777

True all. But the rotary encoder does not exhibit the current value!

More important is that I am hoping to replicate most of the look and feel of an existing device that has 40 pole slide switches… in a gang they make a nice display of the settings, like a graphic equalizer think of.

Right now it looks like I'll use the slide faders and have some kind of text/graphic display where all the settings can be seen exactly. The slide fader knobs will still make sorta a visual display of their own.

I may also have mentioned the idea of just using a graphic display and touch screen and go all virtual on it, but I like the tactile nature of the sliders.

A friend has chimed in very cheerfully suggesting that I could 3D print some kind of detent mechanism, leaving only the matter of calibration, or even the entire switch mechanism. Like I need another hobby!

alto777

Robin2

#13
Feb 25, 2018, 05:38 pm Last Edit: Feb 25, 2018, 05:39 pm by Robin2
Thanks for all the suggestions. I think in reality, it is not really a topic for the real (green behind the ears) newbie.

 [...]
I think the main value of this tutorial is that when such a case occurs, and the user is looking to optimise his sketch, that he can pointed in this direction with a brief hint about hysteresis.
I have been thinking some more and based on what you have said (which is perfectly reasonable) I reckon this Thread should not be in the Introductory Tutorials section - the section should be reserved for tutorials aimed at "the real green newbie".

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

BabyGeezer

This is a very useful Topic. However I suspect many newbies will ignore the topic because they don't know what the Title means.

To get newbies interested maybe it would be useful to extend the Title - perhaps something like this

Avoid unwanted short term jumping between two states - hysteresis


And I do hope someone comes up with a better suggestion. This was my 3rd attempt - which just goes to show how useful the word hysteresis is when you know what it means

...R
i would suggest
"Hysteresis - why you need to debounce"

Go Up