Hystérésis


🕮 Source : Hysteresis link sur le forum Arduino.


Généralités

L’hystérésis (nom féminin) est la propriété d’un système dont l’évolution ne suit pas le même chemin selon qu’une cause extérieure augmente ou diminue.

Son application concrète dans les sytèmes numériques consiste à minimiser l’oscillation entre les états de commutation.

Exemples :

  • Éviter que le chauffage s’allume et s’éteigne constamment lorsque la température ambiante est proche du réglage du thermostat

  • Limiter le scintillement d’un écran dont la luminosité est déterminée par la lumière ambiante et lorsque cette dernière est proche du seuil entre 2 niveaux de luminosité.

  • …​

Solution

Plusieurs solutions existent pour limiter les oscillations entre les états de commutation.

L’une d’entre elles consiste à ajouter un délai entre les changements d’état, ce qui permet d’éviter une oscillation rapide entre deux états.

Une autre solution — présentée par la suite — consiste simplement à ajouter une certaine hystérésis qui peut être vue comme une sorte “d’adhérence” à l’état actuel ou alors une “réticence” à quitter celui-ci.

Exemple d’hystérésis pour un système qui présente 5 états selon la valeur d’un potentiomètre branché sur un convertisseur analogique-numérique 10 bits (→ 1024 valeurs possibles)

hysteresis

Le diagramme montre bien que les conditions pour quitter un état existant sont différentes de celles pour y entrer.

Ainsi, une valeur d’entrée de 500 donne un niveau de sortie 2. Pour passer de l’état 2 à l’état 1, la valeur d’entrée devra diminuer jusqu’à environ 370 et devra augmenter jusqu’à environ 430 pour basculer à nouveau sur l’état 2.

Cette solution d’hystérésis peut être implémentée sous forme matérielle (→ trigger de Schmitt) ou logicielle. Celle présentée ici est une solution purement logicielle implémentée sous forme d’une classe C++.

HystFilter.h
/*
 * Class: HystFilter [Hysteresis filter].
 * Apply hysteresis to an input value and deliver a lower resolution, stabilised output value.
 *
 */


#ifndef HystFilter_h
#define HystFilter_h

#include <Arduino.h>


class HystFilter
{
  public:

    HystFilter( uint16_t inputValues, uint16_t outputValues, uint16_t margin  ) ;

    // constructor

    // inputValues:  the total number of discrete values delivered by the input.
    //               For example, a 10 bit ADC delivers 1024 values.
    //               8 bit ADC = 256, 9 bit ADC = 512, 10 bit ADC = 1024, 11 bit ADC = 2048, 12 bit ADC = 4096 etc.

    // outputValues: the number of discrete output values delivered. This governs the resolution of the function.
    //               For example a 6 bit resolution yields 64 values. This should ideally be no higher that the input resolution
    //               minus 3 bits. For example if the input resolution is 10 bits (1024), this should not exceed 7 bits (128)

    // margin:       margin sets the 'stickyness' of the hysteresis or the reluctance to leave the current state.
    //               It is measured in units of the the input level. As a general rule, this should be about 10% to 25% of the
    //               range of input values that map to 1 output value. For example, if the inputValues is 1024 and the outputValues
    //               is 128, then 8 input values map to 1 output value so the margin should be 2 (25% of 8 ).
    //               Probably a value of 2 is OK. For low resolutions or noisy environments, it can be higher. Don't make it too high
    //               or ranges overlap and some output values become unreachable.


    uint16_t getOutputLevel( uint16_t inputLevel )  ;

    // converts an input level (eg in the range 0 to 1023 to an aoutput value of 1 to 127 with hyteresis.

  private:
    uint16_t _inputValues ;
    uint16_t _outputValues ;
    uint16_t _margin ;
    uint16_t _currentOutputLevel ;
} ;

#endif
HystFilter.cpp
#include "HystFilter.h"


HystFilter::HystFilter( uint16_t inputValues, uint16_t outputValues, uint16_t margin  ) :
  _inputValues( inputValues ) ,
  _outputValues(  outputValues ) ,
  _margin(  margin ) ,
  _currentOutputLevel( 0  )
{  }


uint16_t HystFilter::getOutputLevel( uint16_t inputLevel ) {

  // get lower and upper bounds for currentOutputLevel
  uint16_t lb =   (float) ( (float) _inputValues / _outputValues ) * _currentOutputLevel  ;
  if ( _currentOutputLevel > 0 ) lb -= _margin  ;   // subtract margin

  uint16_t ub =   ( (  (float) ( (float) _inputValues / _outputValues ) * ( _currentOutputLevel + 1 )  )  - 1 )  ;
  if ( _currentOutputLevel < _outputValues ) ub +=  _margin  ;  // add margin
  // now test if input is outside the outer margins for current output value
  // If so, caclulate new output level.
  if ( inputLevel < lb || inputLevel > ub ) {
    // determine new output level
    _currentOutputLevel =   (  ( (float) inputLevel * (float) _outputValues ) /  _inputValues ) ;
  }
  return _currentOutputLevel ;
}
Exemple utilisation usr Arduino
/*

   HystFilter class [Hysteresis]

   This code example shows obtaining values 0-64 from one potentiometer wired
   as a voltage divider and 0-10 from another.

   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 17.apr.2018

   ver 0.01 03.02.2018 Original verson with table to represent range end points
   ver 0.02 13.04.2018 Original verson with calculation to determine range end points.
   ver 0.04 17.03.2018 Class implementation for multiple potentiometers (or other analog sources)

*/

#include "HystFilter.h"

HystFilter potA( 1024, 64, 3 ) ;  // 10 bit ADC = 1024, 64 discrete output values required, margin = 3 units (of 1024)
HystFilter potB( 1024, 10, 5 ) ;  // 10 bit ADC = 1024, 10 discrete output values required, margin = 5 units (of 1024)


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

void loop() {

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

  Serial.print("potB:  " ) ;
  Serial.print(  potB.getOutputLevel( analogRead(A1) ) ) ;
  Serial.print("  " ) ;
  Serial.println( analogRead(A1) ) ;
  Serial.println("  " ) ;


  delay ( 500 ) ;
}

🞄  🞄  🞄