Comment lisser le signal des pins analogiques ?

Bonjour,

• Pour bien commencer ce sujet, je vais d'abord poser le contexte :

Je suis en train de créer une "Boîte à boutons" pour l'utiliser dans un jeu de simulation spatiale. Cette "Boîte" comporte un certain nombre de boutons et de commutateurs à bascule, dont 4 potentiomètres rotatifs de 10K Ohm. J'utilise une carte Arduino Mega 2560.

Pour le code, j'ai utilisé UnoJoy, en prenant la version MegaJoy, faite pour une Arduino Mega. Pour ceux qui ne connaissent pas, ce code permet de transformer une carte Arduino en contrôleur de jeu USB.

• Le Problème :

Là où j'aurais besoin de votre aide, c'est que le signal des pins analogiques que j'utilise pour mes potentiomètres n'est pas toujours très stable. Il peut arriver qu'en tournant mes potentiomètres rotatifs, le signal fluctue brusquement en augmentant beaucoup, avant de redescendre à sa valeur correcte.

J'ai donc cherché comment lisser le signal analogique et je suis tombé sur la bibliothèque ResponsiveAnalogRead.
Le souci, c'est que je n'ai aucune compétence en code et je n'ai donc pas réussi à faire fonctionner le code de ResponsiveAnalogRead dans mon code MegaJoy...

C'est donc là que j'aurais besoin de votre savoir en programmation des cartes Arduino pour me sortir de cette situation bloquante.

Je vous mets ci-dessous le code MegaJoy enregistré sur ma carte Arduino :

[code]

#include "MegaJoy.h"

void setup() {
  setupPins();
  setupMegaJoy();
}

void loop() {
  // Always be getting fresh data
  megaJoyControllerData_t controllerData = getControllerData();
  setControllerData(controllerData);
}

void setupPins(void) {
  // Set all the digital pins as inputs
  // with the pull-up enabled, except for the
  // two serial line pins
  for (int i = 2; i <= 54; i++) {
    pinMode(i, INPUT);
    digitalWrite(i, HIGH);
  }
}

megaJoyControllerData_t getControllerData(void) {

  // Set up a place for our controller data
  //  Use the getBlankDataForController() function, since
  //  just declaring a fresh dataForController_t tends
  //  to get you one filled with junk from other, random
  //  values that were in those memory locations before
  megaJoyControllerData_t controllerData = getBlankDataForMegaController();
  // Since our buttons are all held high and
  //  pulled low when pressed, we use the "!"
  //  operator to invert the readings from the pins
  for (int i = 2; i < 54; i++) {
    controllerData.buttonArray[(i - 2) / 8] |= (!digitalRead(i)) << ((i - 2) % 8);
  }

  // Set the analog sticks
  //  Since analogRead(pin) returns a 10 bit value,
  //  we need to perform a bit shift operation to
  //  lose the 2 least significant bits and get an
  //  8 bit number that we can use

  //controllerData.analogAxisArray[0] = analogRead(A0);
  controllerData.analogAxisArray[1] = analogRead(A1);
  controllerData.analogAxisArray[2] = analogRead(A2);
  controllerData.analogAxisArray[3] = analogRead(A3);
  controllerData.analogAxisArray[4] = analogRead(A4);
  //controllerData.analogAxisArray[5] = analogRead(A5);
  //controllerData.analogAxisArray[6] = analogRead(A6);
  //controllerData.analogAxisArray[7] = analogRead(A7);
  //controllerData.analogAxisArray[8] = analogRead(A8);
  //controllerData.analogAxisArray[9] = analogRead(A9);
  //controllerData.analogAxisArray[10] = analogRead(A10);
  //controllerData.analogAxisArray[11] = analogRead(A11);

  // And return the data!
  return controllerData;
}
[/code]

Et ici le code de ResponsiveAnalogRead pour lisser le signal analogique, que je cherche à faire fonctionner dans mon code MegaJoy :

[code]
// include the ResponsiveAnalogRead library
#include <ResponsiveAnalogRead.h>

// define the pin you want to use
const int ANALOG_PIN = A0;

// make a ResponsiveAnalogRead object, pass in the pin, and either true or false depending on if you want sleep enabled
// enabling sleep will cause values to take less time to stop changing and potentially stop changing more abruptly,
//   where as disabling sleep will cause values to ease into their correct position smoothly and more accurately
ResponsiveAnalogRead analog(ANALOG_PIN, true);

// the next optional argument is snapMultiplier, which is set to 0.01 by default
// you can pass it a value from 0 to 1 that controls the amount of easing
// increase this to lessen the amount of easing (such as 0.1) and make the responsive values more responsive
// but doing so may cause more noise to seep through if sleep is not enabled

void setup() {
  // begin serial so we can see analog read values through the serial monitor
  Serial.begin(9600);
}

void loop() {
  // update the ResponsiveAnalogRead object every loop
  analog.update();

  Serial.print(analog.getRawValue());
  Serial.print("\t");
  Serial.print(analog.getValue());

  // if the repsonsive value has change, print out 'changed'
  if (analog.hasChanged()) {
    Serial.print("\tchanged");
  }

  Serial.println("");
  delay(20);
}
[/code]

Je vous remercie d'avance pour toute aide que vous pourriez m'apporter !

Bonjour,

J'ai abandonné depuis longtemps les potentiomètres "analogiques" au profit d'encodeurs rotatifs comme le KY-040 qui plus est, permettent un appui bouton...

Exit les crachements, les adaptations de tous genres qui de toute façon de supporteront pas la durée de fonctionnement en fonction du milieu (hydrométrie, température, etc.)

A suivre...

@Stares comment est fichu le système, matériellement parlant? t'a du filtrage?
il manque des choses a ton code, faut voir les .h et .cpp si y'a, ya surement un filtrage de mis et peut-être une possibilité de le régler.

@claudius01 c'est marrant ton truc, pas mal!

Il va de soit, que l'utilisation d'un encodeur rotatif comme le KY-040 ne s'applique pas si l'on souhaite réellement acquérir un signal analogique...

Si au contraire, pour dérouler un menu, fixer une valeur et la sélectionner, etc., je préconise ces encodeurs rotatifs que l'on retrouve dans moultes interfaces IHM embarquées qui permettent de s’astreindre des contraintes d'utilisation quelles que soient les conditions (ou presque) de l'environnement extérieur :wink:

@claudius01 exactement, c'est parfait pour incrémenter un compteur par exemple (et meme plus de 8bits 12bits etc, le compteur fait la taille qu'on lui donne)

Je ne sais pas s'il y a du filtrage matériel. Ma carte est une ELEGOO Carte Mega R3

Pour le côté connectique des potentiomètres, je viens de crée un schéma pour que cela soit plus clair :

Désolé pour le code manquant, je penser que ces autres fichiers n'était pas nécessaire pour mon poste. Alors voilà pour le deuxième fichier de code dénommé MegaJoy.h qui complète celui du code MegaJoy.ino de mon message d'origine :

[code]
/*  UnoJoy.h
     Alan Chatham - 2012
      RMIT Exertion Games Lab

    This library gives you a standard way to create Arduino code that talks
     to the UnoJoy firmware in order to make native USB game controllers.
    Functions:
     setupMegaJoy()
     getBlankDataForController()
     setControllerData(megaJoyControllerData_t dataToSet)

     NOTE: You cannot use pins 0 or 1 if you use this code - they are used by the serial communication.
           Also, the setupMegaJoy() function starts the serial port at 38400, so if you're using
           the serial port to debug and it's not working, this may be your problem.

     === How to use this library ===
     If you want, you can move this file into your Arduino/Libraries folder, then use it like a normal library.
     However, since you'll need to refer to the details of the megaJoyControllerData_t struct in this file, I would suggest you use
      it by adding it to your Arduino sketch manually (in Arduino, go to Sketch->Add file...)

    To use this library to make a controller, you'll need to do 3 things:
     Call setupMegaJoy(); in the setup() block
     Create and populate a megaJoyControllerData_t type variable and fill it with your data
           The getBlankDataForController() function is good for that.
     Call setControllerData(yourData); where yourData is the variable from above,
           somewhere in your loop(), once you're ready to push your controller data to the system.
           If you forget to call sendControllerData in your loop, your controller won't ever do anything

    You can then debug the controller with the included Processing sketch, UnoJoyProcessingVisualizer

    To turn it into an actual USB video game controller, you'll reflash the
     Arduino's communication's chip using the instructions found in the 'Firmware' folder,
     then unplug and re-plug in the Arduino.

    Details about the megaJoyControllerData_t type are below, but in order to create and use it,
     you'll declare it like:

        megaJoyControllerData_t sexyControllerData;

     and then control button presses and analog stick movement with statements like:

        sexyControllerData.triangleOn = 1;   // Marks the triangle button as pressed
        sexyControllerData.squareOn = 0;     // Marks the square button as unpressed
        sexyControllerData.leftStickX = 90;  // Analog stick values can range from 0 - 255
*/

#ifndef UNOJOY_H
#define UNOJOY_H
#include <stdint.h>
#include <util/atomic.h>
#include <Arduino.h>

// This struct is the core of the library.
//  You'll create an instance of this and manipulate it,
//  then use the setControllerData function to send that data out.
//  Don't change this - the order of the fields is important for
//  the communication between the Arduino and it's communications chip.
#define BUTTON_ARRAY_SIZE 8
#define ANALOG_AXIS_ARRAY_SIZE 12

typedef struct megaJoyControllerData_t
{
  uint8_t buttonArray[BUTTON_ARRAY_SIZE];

  uint8_t dpad0LeftOn : 1;
  uint8_t dpad0UpOn : 1;
  uint8_t dpad0RightOn : 1;
  uint8_t dpad0DownOn : 1;

  uint8_t dpad1LeftOn : 1;
  uint8_t dpad1UpOn : 1;
  uint8_t dpad1RightOn : 1;
  uint8_t dpad1DownOn : 1;

  int16_t analogAxisArray[ANALOG_AXIS_ARRAY_SIZE];
} megaJoyControllerData_t;

// Call setupMegaJoy in the setup block of your program.
//  It sets up the hardware UnoJoy needs to work properly
void setupMegaJoy(void);

// This sets the controller to reflect the button and
// joystick positions you input (as a megaJoyControllerData_t).
// The controller will just send a zeroed (joysticks centered)
// signal until you tell it otherwise with this function.
void setControllerData(megaJoyControllerData_t);

// This function gives you a quick way to get a fresh
//  megaJoyControllerData_t with:
//    No buttons pressed
//    Joysticks centered
// Very useful for starting each loop with a blank controller, for instance.
// It returns a megaJoyControllerData_t, so you want to call it like:
//    myControllerData = getBlankDataForController();
megaJoyControllerData_t getBlankDataForMegaController(void);

// You can also call the setup function with an integer argument
//  declaring how often, in  milliseconds, the buffer should send its data
//  via the serial port.  Use it if you need to do a lot of processing and
//  the serial stuff is messing you up, but it'll make your controller
//  more laggy.
// IMPORTANT - you can't make this value greater than 20 or so - the code
//  on the communications chip times out on each serial read after 25ms.
//  If you need more time than 20ms, you'll have to alter the code for the
//  ATmega8u2 as well
void setupMegaJoy(int);


//----- End of the interface code you should be using -----//
//----- Below here is the actual implementation of

// This megaJoyControllerData_t is used to store
//  the controller data that you want to send
//  out to the controller.  You shouldn't mess
//  with this directly - call setControllerData instead
megaJoyControllerData_t controllerDataBuffer;

// This updates the data that the controller is sending out.
//  The system actually works as following:
//  The UnoJoy firmware on the ATmega8u2 regularly polls the
//  Arduino chip for individual bytes of a megaJoyControllerData_t.
//
void setControllerData(megaJoyControllerData_t controllerData) {
  // Probably unecessary, but this guarantees that the data
  //  gets copied to our buffer all at once.
  ATOMIC_BLOCK(ATOMIC_FORCEON) {
    controllerDataBuffer = controllerData;
  }
}

// serialCheckInterval governs how many ms between
//  checks to the serial port for data.
//  It shouldn't go above 20 or so, otherwise you might
//  get unreliable data transmission to the UnoJoy firmware,
//  since after it sends a request, it waits 25 ms for a response.
//  If you really need to make it bigger than that, you'll have to
//  adjust that timeout in the UnoJoy ATmega8u2 firmware code as well.
volatile int serialCheckInterval = 1;
// This is an internal counter variable to count ms between
//  serial check times
int serialCheckCounter = 0;

// This is the setup function - it sets up the serial communication
//  and the timer interrupt for actually sending the data back and forth.
void setupMegaJoy(void) {
  // First, let's zero out our controller data buffer (center the sticks)
  controllerDataBuffer = getBlankDataForMegaController();

  // Start the serial port at the specific, low-error rate UnoJoy uses.
  //  If you want to change the rate, you'll have to change it in the
  //  firmware for the ATmega8u2 as well.  250,000 is actually the best rate,
  //  but it's not supported on Macs, breaking the processing debugger.
  Serial.begin(38400);

  // Now set up the Timer 0 compare register A
  //  so that Timer0 (used for millis() and such)
  //  also fires an interrupt when it's equal to
  //  128, not just on overflow.
  // This will fire our timer interrupt almost
  //  every 1 ms (1024 us to be exact).
  OCR0A = 128;
  TIMSK0 |= (1 << OCIE0A);
}

// If you really need to change the serial polling
//  interval, use this function to initialize UnoJoy.
//  interval is the polling frequency, in ms.
void setupMegaJoy(int interval) {
  serialCheckInterval = interval;
  setupMegaJoy();
}

// This interrupt gets called approximately once per ms.
//  It counts how many ms between serial port polls,
//  and if it's been long enough, polls the serial
//  port to see if the UnoJoy firmware requested data.
//  If it did, it transmits the appropriate data back.
ISR(TIMER0_COMPA_vect) {
  serialCheckCounter++;
  if (serialCheckCounter >= serialCheckInterval) {
    serialCheckCounter = 0;
    // If there is incoming data stored in the Arduino serial buffer
    while (Serial.available() > 0) {
      //pinMode(13, OUTPUT);
      //digitalWrite(13, HIGH);
      // Get incoming byte from the ATmega8u2
      byte inByte = Serial.read();
      // That number tells us which byte of the megaJoyControllerData_t struct
      //  to send out.
      Serial.write(((uint8_t*)&controllerDataBuffer)[inByte]);
      //digitalWrite(13, LOW);
    }
  }
}

// Returns a zeroed out (joysticks centered)
//  megaJoyControllerData_t variable
megaJoyControllerData_t getBlankDataForMegaController(void) {
  // Create a megaJoyControllerData_t
  megaJoyControllerData_t controllerData;
  // Make the buttons zero
  for (int i = 0; i < 8; i++) {
    controllerData.buttonArray[i] = 0;
  }
  controllerData.dpad0LeftOn = 0;
  controllerData.dpad0UpOn = 0;
  controllerData.dpad0RightOn = 0;
  controllerData.dpad0DownOn = 0;

  controllerData.dpad1LeftOn = 0;
  controllerData.dpad1UpOn = 0;
  controllerData.dpad1RightOn = 0;
  controllerData.dpad1DownOn = 0;

  //Set the sticks to 512 - centered
  for (int i = 0; i < ANALOG_AXIS_ARRAY_SIZE; i++) {
    controllerData.analogAxisArray[i] = 512;
  }
  // And return the data!
  return controllerData;
}

#endif
[/code]

Pour ce qui est de la bibliothèque de code ResponsiveAnalogRead, il y a deux fichiers qui complète celui de mon message d'origine :

ResponsiveAnalogRead.cpp

/*
 * ResponsiveAnalogRead.cpp
 * Arduino library for eliminating noise in analogRead inputs without decreasing responsiveness
 *
 * Copyright (c) 2016 Damien Clarke
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <Arduino.h>
#include "ResponsiveAnalogRead.h"

void ResponsiveAnalogRead::begin(int pin, bool sleepEnable, float snapMultiplier){
    pinMode(pin, INPUT ); // ensure button pin is an input
    digitalWrite(pin, LOW ); // ensure pullup is off on button pin
    
    this->pin = pin;
    this->sleepEnable = sleepEnable;
    setSnapMultiplier(snapMultiplier);
    
    
}


void ResponsiveAnalogRead::update()
{
  rawValue = analogRead(pin);
  this->update(rawValue);
}

void ResponsiveAnalogRead::update(int rawValueRead)
{
  rawValue = rawValueRead;
  prevResponsiveValue = responsiveValue;
  responsiveValue = getResponsiveValue(rawValue);
  responsiveValueHasChanged = responsiveValue != prevResponsiveValue;
}

int ResponsiveAnalogRead::getResponsiveValue(int newValue)
{
  // if sleep and edge snap are enabled and the new value is very close to an edge, drag it a little closer to the edges
  // This'll make it easier to pull the output values right to the extremes without sleeping,
  // and it'll make movements right near the edge appear larger, making it easier to wake up
  if(sleepEnable && edgeSnapEnable) {
    if(newValue < activityThreshold) {
      newValue = (newValue * 2) - activityThreshold;
    } else if(newValue > analogResolution - activityThreshold) {
      newValue = (newValue * 2) - analogResolution + activityThreshold;
    }
  }

  // get difference between new input value and current smooth value
  unsigned int diff = abs(newValue - smoothValue);

  // measure the difference between the new value and current value
  // and use another exponential moving average to work out what
  // the current margin of error is
  errorEMA += ((newValue - smoothValue) - errorEMA) * 0.4;

  // if sleep has been enabled, sleep when the amount of error is below the activity threshold
  if(sleepEnable) {
    // recalculate sleeping status
    sleeping = abs(errorEMA) < activityThreshold;
  }

  // if we're allowed to sleep, and we're sleeping
  // then don't update responsiveValue this loop
  // just output the existing responsiveValue
  if(sleepEnable && sleeping) {
    return (int)smoothValue;
  }

  // use a 'snap curve' function, where we pass in the diff (x) and get back a number from 0-1.
  // We want small values of x to result in an output close to zero, so when the smooth value is close to the input value
  // it'll smooth out noise aggressively by responding slowly to sudden changes.
  // We want a small increase in x to result in a much higher output value, so medium and large movements are snappy and responsive,
  // and aren't made sluggish by unnecessarily filtering out noise. A hyperbola (f(x) = 1/x) curve is used.
  // First x has an offset of 1 applied, so x = 0 now results in a value of 1 from the hyperbola function.
  // High values of x tend toward 0, but we want an output that begins at 0 and tends toward 1, so 1-y flips this up the right way.
  // Finally the result is multiplied by 2 and capped at a maximum of one, which means that at a certain point all larger movements are maximally snappy

  // then multiply the input by SNAP_MULTIPLER so input values fit the snap curve better.
  float snap = snapCurve(diff * snapMultiplier);

  // when sleep is enabled, the emphasis is stopping on a responsiveValue quickly, and it's less about easing into position.
  // If sleep is enabled, add a small amount to snap so it'll tend to snap into a more accurate position before sleeping starts.
  if(sleepEnable) {
    snap *= 0.5 + 0.5;
  }

  // calculate the exponential moving average based on the snap
  smoothValue += (newValue - smoothValue) * snap;

  // ensure output is in bounds
  if(smoothValue < 0.0) {
    smoothValue = 0.0;
  } else if(smoothValue > analogResolution - 1) {
    smoothValue = analogResolution - 1;
  }

  // expected output is an integer
  return (int)smoothValue;
}

float ResponsiveAnalogRead::snapCurve(float x)
{
  float y = 1.0 / (x + 1.0);
  y = (1.0 - y) * 2.0;
  if(y > 1.0) {
    return 1.0;
  }
  return y;
}

void ResponsiveAnalogRead::setSnapMultiplier(float newMultiplier)
{
  if(newMultiplier > 1.0) {
    newMultiplier = 1.0;
  }
  if(newMultiplier < 0.0) {
    newMultiplier = 0.0;
  }
  snapMultiplier = newMultiplier;
}

ResponsiveAnalogRead.h

/*
 * ResponsiveAnalogRead.h
 * Arduino library for eliminating noise in analogRead inputs without decreasing responsiveness
 *
 * Copyright (c) 2016 Damien Clarke
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.  
 */
 
#ifndef RESPONSIVE_ANALOG_READ_H
#define RESPONSIVE_ANALOG_READ_H

#include <Arduino.h>

class ResponsiveAnalogRead
{
  public:

    // pin - the pin to read
    // sleepEnable - enabling sleep will cause values to take less time to stop changing and potentially stop changing more abruptly,
    //   where as disabling sleep will cause values to ease into their correct position smoothly
    // snapMultiplier - a value from 0 to 1 that controls the amount of easing
    //   increase this to lessen the amount of easing (such as 0.1) and make the responsive values more responsive
    //   but doing so may cause more noise to seep through if sleep is not enabled
    
    ResponsiveAnalogRead(){};  //default constructor must be followed by call to begin function
    ResponsiveAnalogRead(int pin, bool sleepEnable, float snapMultiplier = 0.01){
        begin(pin, sleepEnable, snapMultiplier);
    };

    void begin(int pin, bool sleepEnable, float snapMultiplier = 0.01);  // use with default constructor to initialize 
    
    inline int getValue() { return responsiveValue; } // get the responsive value from last update
    inline int getRawValue() { return rawValue; } // get the raw analogRead() value from last update
    inline bool hasChanged() { return responsiveValueHasChanged; } // returns true if the responsive value has changed during the last update
    inline bool isSleeping() { return sleeping; } // returns true if the algorithm is currently in sleeping mode
    void update(); // updates the value by performing an analogRead() and calculating a responsive value based off it
    void update(int rawValueRead); // updates the value accepting a value and calculating a responsive value based off it

    void setSnapMultiplier(float newMultiplier);
    inline void enableSleep() { sleepEnable = true; }
    inline void disableSleep() { sleepEnable = false; }
    inline void enableEdgeSnap() { edgeSnapEnable = true; }
    // edge snap ensures that values at the edges of the spectrum (0 and 1023) can be easily reached when sleep is enabled
    inline void disableEdgeSnap() { edgeSnapEnable = false; }
    inline void setActivityThreshold(float newThreshold) { activityThreshold = newThreshold; }
    // the amount of movement that must take place to register as activity and start moving the output value. Defaults to 4.0
    inline void setAnalogResolution(int resolution) { analogResolution = resolution; }
    // if your ADC is something other than 10bit (1024), set that here

  private:
    int pin;
    int analogResolution = 1024;
    float snapMultiplier;
    bool sleepEnable;
    float activityThreshold = 4.0;
    bool edgeSnapEnable = true;

    float smoothValue;
    unsigned long lastActivityMS;
    float errorEMA = 0.0;
    bool sleeping = false;

    int rawValue;
    int responsiveValue;
    int prevResponsiveValue;
    bool responsiveValueHasChanged;

    int getResponsiveValue(int newValue);
    float snapCurve(float x);
};

#endif

Voilà, j'espère que cela sera suffisant.

@Stares je ne suis pas pro du codage je trouve que là c'est compliqué pour pas grand chose de mon point de vue "d'amateur", mais certains pourront mieux t'en parler, au moins, il y a tout :slightly_smiling_face:

par contre sur ton schéma on vois très clairement qu'il n'y a aucun filtrage matériel. pas bon surtout pour un contrôleur de jeu (logiquement ça se veux précis...) là tu peux ajouter un condo, par exemple 0.22uF, relié au signal et a GND sur chaque A, et une résistance, 470R par exemple, en série sur chaque A. tu aura forcement un mieux

D'accord, je vais m'informer sur le filtrage matériel avec condensateur + résistance, car c'est quelque chose dont je n'ai aucune connaissances.

Je vais attendre aussi d'autres réponses s'il y en a, pour savoir comment lisser le signal de mes potentiomètres analogique, du côté code.

Merci pour ton aide en tout cas !

Y aurait-il d'autres personnes pour m'aider à lisser, côté code, le signal analogique de mes potentiomètres afin d'éviter les fluctuations ?

Si l'intégration de la bibliothèque ResponsiveAnalogRead dans mon code pour lisser le signal s'avère trop compliquée, je suis ouvert à l'utilisation d'autres méthodes, tant que cela permet d'obtenir un signal analogique stable.

Je ne suis pas en mesure de le faire seul, car je n'ai aucune connaissance en programmation et je suis donc complètement perdu. Je n'ai pas non plus trouvé de solution sur internet suffisamment claire pour moi :cold_sweat:

A mon avis la solution ne sera pas principalement dans la programmation et ce n'est pas une chasse aux bibliothèques "magiques" qui te donnera la solution.

La première des actions est de comprendre comment fonctionne un convertisseur analogique digital.
C'est bien expliqué dans la datasheet de l'atmega 2560. → le nom de la carte et de son fabricant Arduino est secondaire, ce qui compte, c'est la référence du microcontrôleur et c'est chez son fabricant Microchip qu'il faut aller chercher la documentation.

Dans ces microcontrôleurs (je ne connais que l'atmega328, mais les cœurs sont les mêmes) il n'y a pas autant de convertisseurs qu'il y a d'entrées dites "analogiques".
Les entrées, dites "analogiques" sont connectées aux entrées d'un multiplexeur, la sortie de ce dernier est reliée au convertisseur.

Dans la datasheet, il est bien précisé que quand un cycle de conversion est engagé, il ne faut rien changer. Il ne faut surtout pas changer de canal dans le multiplexeur..
Si tu changes d'entrée Ax pendant un cycle de conversion, le résultat sera un mélange des deux entrées, donc résultat sera faux.

C'est écrit noir sur blanc dans la datasheet : dès qu'il y a une modification, quelle qu'elle soit, il faut systématiquement rejeter la première mesure.

Ça, c'est le premier point.

Le deuxième point est qu'un microcontroleur ne sera jamais aussi précis qu'un appareil de mesure appelé voltmètre.
Le microcontroleur a une activité, il envoie, ou reçoit des signaux numériques d'amplitude 5V. Cette activité pollue les pistes par conduction ou rayonnement, sur la carte en circuit imprimé, mais aussi sur la puce, c'est inévitable.
Heureusement, elle pollue aléatoirement, l'activité du microcontrôleur étant non prévisible.
La pollution aléatoire, au contraire de la systématique, peut s'éliminer avec un simple moyennage sur X valeurs consécutives.
→ Notes que c'est ce que font les appareils de mesures appelés "voltmètre".

Troisième point : Le signal qui provient des potentiomètres peut aussi être pollué.
Tu peux utiliser des fils blindés :

  1. fil signal

  2. fil masse → reliée sur la carte arduino à la masse la plus proche de l'entrée analogique utilisée -> c'est important !
    Ce qui est encore plus important c'est que chaque module relié à une carte microcontroleur doit avoir son propre fil de masse. Tous les fils étant reliés ensemble juste avant la carte micro. Les guirlandes de fils de masse qui vont de composant en composant sont décoratives, mais ne sont pas une bonne idée.
    De préférence, les fils signal et masse seront torsadés.

  3. blindage relié en un seul point à la masse coté carte 2560.

Le choix de la masse sur ton dessin de câblage est parfait.
Question : as-tu câblé un condensateur sur les potentiomètres entre le +V et la masse ?
C'est nécessaire : ce condensateur ne donnera pas la solution à lui seul, mais les petits ruisseaux font les grandes rivières.
Dis-nous quelles sont les valeurs de condensateurs que tu possèdes, on choisira.

Il est important de préciser si la phase transitoire se produit quand tu touches le bouton du potentiomètre ou aussi quand tu laisses les doigts sur le bouton.
Si c'est juste quand tu touches le bouton, cela signifie que c'est toi qui pollues : mise à la masse obligatoire du bouton.

Notre corps est une antenne de réception.
Je me garderais bien de dire que c'est la cause, mais en présence de masse un peu déficiente cela peut participer.

À ce sujet, pour relier les masses de différents composants entre eux, j'utilise de la tresse à dessouder en cuivre en largeur 4 à 5 mm.
Ça se soude, cela peut être fixé par vis et rondelle, cela a une bonne section donc peu résistif, c'est pratique.

Point important à comprendre : que ce soit un traitement numérique (moyennage ou traitement logiciel) ou un traitement matériel (filtrage avec résistance et condensateur), le traitement apportera toujours un retard qui augmentera avec le niveau de pollution du signal.

Tu m'as compris le câblage n'est pas un point secondaire, moins il y aura à corriger plus ce sera facile.

Idée de manipulation :
Sans modifier le câblage :

  1. Tu rejettes systématiquement la première valeur.

  2. Tu fais une moyenne glissante sur X valeurs → X étant à ajuster en fonction des impératifs de ton projet.

  3. Promène les mains dans ton montage sans toucher : une sensibilité quand tu approches les mains n'est pas bon signe, essaye de renforcer les masses ou d'éloigner des câbles les uns des autres.
    Il n'existe aucune théorie sur cette méthode, c'est délicat au début, demande de l'apprentissage, mais est d'une efficacité redoutable pour détecter des instabilités.

Note importante : La configuration de l'ADC des micros est paramétrable, mais la configuration choisie par Arduino est déjà très prudente, c'est ceinture plus bretelles. Il n'y a pas beaucoup d'espoir de ce côté.

Si ce n'est pas suffisant, modifie le câblage.

Ah j'oubliais : la référence Aref !
C'est plus des conseils, ce que je vais dire n'est pas en relation avec ce que tu nous présentes : tension qui s'élève puis qui se stabilise à une valeur plus basse quand on tourne un bouton de potentiomètre.

Par défaut, la référence de tension pour le convertisseur analogique est la tension Vcc.
Cette tension n'est pas connue précisément, en soi ce n'est pas gênant et on peut toujours la mesurer avec un voltmètre.
Plus gênant, elle peut être bruitée par l'activité du micro. Si elle est bruitée, la mesure sera aussi bruitée.

Il y a la possibilité d'utiliser une référence interne, mais elle est peu précise : 1,1 V ± 0,1 V.
Là aussi on peut la mesurer mais ce n'est pas clair si elle est stable en température.
Il a aussi la possibilité d'appliquer une tension stable sur la pin appellée Aref.

Justement sur les cartes Arduino il y a une tension stable : celle qui est donnée par le régulateur de tension 3,3 V qui est rarement utilisé et donc qui n'est pas "salis".

Je donne l'info pour d'autres applications, tu en feras ce que tu voudras.
Tu n'es pas obligé de faire comme je fais ni tout ce que j'ai dis.
Je transmets mon expérience d'électronicien qui n'est certainement pas celle d'un autre électronicien.

Si j'étais devant la maquette je proposerais probablement d'autres idées, mais à distance je ne peux pas faire plus.

Déjà merci beaucoup pour ce pavé d'informations, bien que compliqué sur certains points par rapport à mon niveau (très) amateur (c'est la première fois que je touche à de l'électrotechnique, en me lançant dans ce projet).

Alors non c'est vraiment comme sur mon schéma mis dans un message plus haut. Et je n'ai pas de condensateur chez moi.
Et comme j'y connais rien, je ne sais pas quel condensateur acheté s'il en faut un (son "type", sa valeur ?). En faudrait-il un seul ou plusieurs ?

Une ou plusieurs résistances serait-elle nécessaire également ?

En général, la valeur de mes potentiomètres est très stable et précise je trouve, mais parfois, lorsque je tourne la molette et que la valeur change donc, il peut arriver que la valeur "saute" soudainement et de beaucoup (de 30% à 80% par exemple) durant un laps de temps court, avant de revenir à ça valeur normal.

Mais je n'y trouve pas de logique particulière. Je peux tourner la molette du potentiomètre pendant un certain temps sans que cela ce produise, en laissant mes doigts dessus donc. Puis ça arrive soudainement sans prévenir.

Je ne serais pas faire cela dans le code. Je ne comprends absolument pas de quoi il s'agit :sweat_smile:

Vraiment à la base, je suis partie dans ce projet car je me suis informé sur la création d'une boite à boutons et j'ai découvert que l'on pouvait créé cela facilement avec la bibliothèque UnoJoy. Une sorte de code "plug and play", sans avoir à toucher au code.

Et tout c'est très bien passé car tout marche très bien, à part juste ce petit soucis de potentiomètres qui peut fluctuer de temps en temps. C'est vraiment le seul obstacle qu'il me reste à résoudre.

Donc voilà ne m'en veux pas trop si je suis trop ignorant dans ce domaine :sweat_smile:

ça ressemble vraiment a une bête interférence, @68tjs a expliqué mieux que moi le filtrage matériel, je pense que simplement ce point va au moins bien t'effacer le probleme. blindage + condo :wink:

Excuses moi mais cela ne veut rien dire pour moi.

La valeur saute, ok de 30 à 80 % : toujours Ok

Mais dans quel sens ?
Toujours le même, ou aléatoire ?
Ce ne serait pas des faux contacts ?

Quand on est à distance il faut être précis.

Condensateurs :
Je mettrais :
Valeur : 1µ Farad
Tension de service : 10 V
Technologie : Tantale ou céramique.
Attention le tantale est polarisé, il faut faire attention de mettre le signe + du coté du +5 V, le céramique ne l'est pas.
Boitier à sortie droites (composant à piquer), évite ceux à sorties axiales qui font des longueurs de fil.

En attendant de recevoir les condensateurs, tu peux continuer tes investigations.

Câblage : soudé court, pas de longueur de fil inutile.
Les longueurs de fil sur un condensateur diminuent son efficacité.

On t'a vendu du rêve, mais soit sans crainte, tu verras qu'avec de l'aide tu t'y mettras facilement.

Pour l'aide pour le code, ce n'est pas moi qui s'y mettra, d'autres connaissent bien mieux que moi, je ne suis qu'un pauvre électronicien qui s'essaye à la programmation.

vous pourriez commencer par tester la qualité du montage histoire qu'on sache de quoi on parle

conservez votre système comme cela:

et effectuez une lecture en limitant les valeurs échantillonnées entre 0 et 255 (on divise par 4 la valeur lue)

const byte potPins[] = {A1, A2, A3, A4};
const byte nbPots = sizeof potPins / sizeof * potPins;
uint8_t valeursPot[nbPots];

uint8_t lirePot(byte potIndex) {
  analogRead(potPins[potIndex]);
  return analogRead(potPins[potIndex]) >> 2; // on se limite à 256 valeurs, double lecture pour être tranquille
}

void setup() {
  Serial.begin(115200);
  Serial.println("A1\tA2\tA3\tA4");
}

void loop() {
  for (byte i = 0; i < nbPots; i++) {
    valeursPot[i] = lirePot(i);
    Serial.print(valeursPot[i]);
    if (i == nbPots-1) Serial.println();
    else Serial.print("\t");
  }
}

ouvrez le traceur graphique à 115200 bauds (au lieu du moniteur série habituel) et faites bouger les potentiomètres.

Montrez nous la tête du graphique et dites nous si les variations sont conformes à ce que vous faisiez lors de la manipulation des potentiomètres.

Alors, je viens de tester cela et il ne semble pas y avoir de "sauts" de valeurs soudaines.

Voici le graphique en tournant un de mes 4 potentiomètres, qui suit bien mes mouvements :

Par contre, à un certain endroit de la course de mon potentiomètre, en arrêtant de le tourner, je pouvais voir des "micro-fluctuations" dans le graphique :

J'ai regarder sur internet pour les condensateurs, mais je n'en trouve pas à la fois en 1µ Farad et en 10V. Ils sont souvent en 50V. Et les frais de port sont bien plus chères que les condensateurs eux-mêmes ! :neutral_face: Sinon sur Amazon (pour avoir les fdp gratuit) il y a un coffret de condensateur céramique allant de 100 à 10000 nF 50V. Cela serait-t-il suffisant ?

Ça a l’air pas trop mal. La petite fluctuation que vous voyez pourrait être gommée en logiciel (il semble n’y avoir qu’un point de différence) et si vous mettez un condensateur ça va amortir cela aussi.

Si vous approchez les mains des autres potentiomètres ou si vous les bougez tous en même temps ça se comporte bien ?

À la suite des explications que tu donnes, je pense de plus en plus à des potentiomètres de mauvaise qualité.

Personnellement, j'ai du mal à trouver une explication liée au convertisseur analogique digital.
Ce que tu nous dis dans ton dernier message :

me conforte de plus en plus dans l'idée que le défaut est probablement dû à un mauvais contact entre le curseur mobile et la piste résistive.

Pour les condensateurs, les valeurs que j'ai données sont les valeurs minimales.
Du moment que cela fait au moins 1 µF et que cela supporte au moins 10 V (puisque Vcc = 5 V → il faut toujours une marge de sécurité), c'est bon.

Kit ou pas kit : ???????
Les valeurs usuelles dans le domaine des activités de ce forum sont :
Céramique 100 nF et 1 ou 2 µF
Tantale 1µF (parce que c'est un petit boîtier) sinon 10 ou 22 µF et éventuellement 100 µF
Les autres valeurs sont pour faire de l'électronique analogique, elles resteront dans la boîte :unamused:.

Il y a aussi la technologie appelée "Chimique" ou "Aluminium", c'est un peu moins cher que le tantale, c'est plus gros que le tantale, c'est moins performant que le tantale.
Mon "Pavlov" personnel dit : Tantale :grinning:

J'insiste : ce n'est pas facile de se faire comprendre sur l'électronique.
Je travaillais dans l'électronique professionnelle, j'ai acquis des réflexes du type Pavlovien.

@J-M-L lui a le réflexe de la zone critique avec les interruptions, heureusement qu'il fait régulièrement des rappels sur ce forum parce que je ne l'ai pas et j'oublie de faire ce qu'il faut.

Ce qui veut dire que quand je sens un problème potentiel et que cela ne coute pas grand-chose d'ajouter un condensateur, je n'hésite pas je le fais.

Mais en aucun cas je peux affirmer que ces condensateurs sont absolument indispensables : c'est ça la difficulté en électronique, il y a tellement de variables qui interviennent que l'on est obligé de faire des suppositions qui sont influencées par son expérience passée.

Quand je vois les prix pratiqués par Amazon, je n'y vais jamais.
Je fais, comme la plupart ici, je vais sur Aliexpress qui depuis quelque temps a bien réduit les temps de livraison.

PS : profites-en pour trouver d'autres potentiomètres, cela peut servir :grinning:

Oui, les différents signaux des potentiomètres ne semblent pas "entrer" en conflit entre eux lorsque je les bouge tous ensemble. Cela se comporte bien.

Oui si c'était quel que soit le potentiomètre ou quelle que soit la position du curseur.
On sait que l'implantation des cartes Arduino n'est pas une merveille d'immunité aux bruits.

Ce qui me gène c'est que cela dépendrait de la position du curseur.

Si c'est vraiment vrai mon impression est que la qualité des potentiomètres est un peu légère et je "sniffe" une panne potentielle dans 10 j ou 1 mois ou 6 mois ?

On va dire que @Stares fait ses premières armes et ce n'est pas facile et pas de précipitation.

@Stares Ce à quoi pense @J-M-L est un filtre RC
Il faudrait une résistance d'environ 1 kohms câblé entre la sortie curseur du potentiomètre et l'entrée analogique et un condensateur entre l'entrée analogique et la masse.

On en reparle demain, j'ai mon chien qui réclame sa balade du soir.