Mittelwertbildung am analogen Eingang (Potentiometer)

Hallo,

ich habe einen 10k Potti an 5 Volt und Erde hängen und lese das, was am mittleren Pin anliegt im Loop aus:

int PottiAmp1;              // cable color: orange
int PottiAmp2;              // cable color: orange
int PottiAmp3;              // cable color: orange
int PottiGit1;              // cable color: pink
int PottiGit2;              // cable color: green
int PottiGit3;              // cable color: blue


// Smooth Function
long int allPottis[20][6];
int average = 0;



void setup() {

  Serial.begin(9600);
      

}

void loop() {

// Analog States

  int PottiGit3 = analogRead(A5);   //blue

  Serial.print("\t 6: ");
  Serial.print(PottiGit3);

// smooth Analog signals
  PottiGit3 = smooth(PottiGit3, 6);

  Serial.print("\t 6: ");
  Serial.println(PottiGit3);
  

int smooth(int newValue, int Potti){

  //Werte eins hoch rutschen, hinten anfangen
  for (int i=0; i<=19; i++){
    allPottis[19-i][Potti] = allPottis[18-i][Potti];
  }
  
  //neuen Wert an den Anfang setzen
  //if (newValue == allPottis[1][Potti]){
    allPottis[0][Potti] = newValue;
  //}
  

  //calculate sum
  int sum = 0;
  for (int i = 0; i<=19; i++){
    sum = sum + allPottis[i][Potti];
  }

  //calculate average and cast to int
  double average = sum/21;
  average = (int) average;
  return (average);
}

Ich hoffe das ist nicht zu viel Code. Habe schon den Großteil weggelöscht. Das Problem ist nun folgendes, wenn ich den neuen Wert am Potti und den aktuellen Durchschnittswert über den seriellen Monitor ausgeben lasse:

6: 99 6: 99
6: 101 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 99 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 100
6: 99 6: 99
6: 100 6: 99
6: 99 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 112
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 100 6: 99
6: 99 6: 99
6: 99 6: 99
6: 100 6: 99
6: 99 6: 99
6: 100 6: 112

Es haut mir in unregelmäßigen Absständen einen viel zu hohen Wert rein, den ich mir nicht erklären kann. Hat jemand eine Idee?

Array-Bereichsüberschreitung:

long int allPottis[20][6];  // [0..19][0..5]
...
    PottiGit3 = smooth(PottiGit3, 6); // 6 ist knapp daneben
  //calculate average and cast to int

double average = sum/21;
 average = (int) average;
 return (average);

Das sieht ja voll witzig aus....

Kann man ersetzen durch:

return sum/21;

Versuchs doch mal mit einem gleitendem Mittelwert....

   template<typename DatenType> 
    class GleitenderMittelwert
    {
 private:
 const double factor;
 double mittelwert;
 
 public:
 GleitenderMittelwert(const double factor):factor(factor),mittelwert(0){}
 
 void setInitial(double value)
 {
 mittelwert = value;
 }
 
   operator DatenType() const
 {
 return mittelwert;
 } 
 
 DatenType doValue(DatenType value) // neuen Wert verarbeiten
 {
 mittelwert *= 1.0 - factor;
 mittelwert += factor * value;
 return  mittelwert;
 }
 
 DatenType operator= (DatenType value)
 {
   return doValue(value); 
 }
 
 DatenType operator() (DatenType value)
 {
   return doValue(value); 
 }
 
 DatenType operator() () const
 {
   return mittelwert; 
 }
 };



GleitenderMittelwert<int> gmw(0.01);


void setup()
{
  Serial.begin(9600);
  gmw.setInitial(analogRead(A5));
}

void loop()
{
  Serial.println(gmw=analogRead(A5));
  delay(200);
}

noch witziger wäre, zu sehen was rauskommt, wenn er 20 Werte aufsummiert, dann durch 21 teilt und abschneidet.

Was hat es für einen Sinn die arrayvariable als long zu definieren, deren summe aber als int? ok wir sind noch im INT Bereich aber ein solche Codekonstruktion läßt genaue Überlegungen bezweifeln.

Grüße Uwe

Vielen Dank michael_x, das hat geholfen.

Welchen Vorteil hat denn diese Mittelwertbildung combie?

Das lustig aussehende wird geändert combie... war mir nicht sicher ob das so kurz auch geht :wink:

Welchen Vorteil hat denn diese Mittelwertbildung combie?

  1. Durch das Template ist es recht flexibel (weitgehend Type neutral)
  2. Es verhält sich, in der geposteten Variante, (fast)wie eine Mittelwertbildung mit 100 Array Elementen(ohne diese Menge Speicher zu fressen).

Und ganz nebenbei: Es funktioniert.

war mir nicht sicher ob das so kurz auch geht

Warum nicht?
Da kann ich dir nur ein C++ Buch empfehlen.

uwefed:
Was hat es für einen Sinn die arrayvariable als long zu definieren, deren summe aber als int? ok wir sind noch im INT Bereich aber ein solche Codekonstruktion läßt genaue Überlegungen bezweifeln.

Grüße Uwe

Es war ursprünglich int. Dachte dann, dass der Fehler daran liegen könnte, dass das array schon "voll" ist und hab es auf long int gestellt. Danke fürs aufmerskame lesen.

ganz nebenbei: Es funktioniert

Hauptsächlich lässt es dich erstmal am anderen Tip ( Dem mit dem C++ Buch ) verzweifeln, bis du zum Thema Operatoren kommst:

combie ändert nämlich die Bedeutung des = Zeichens, damit wir was zum Staunen haben.
(Ist jetzt keine Zuweisung mehr, sondern die eigentliche Tiefpass-Funktion ( gleitender Mittelwert )

Ersetze:

Serial.println(gmw=analogRead(A5));

Durch:

Serial.println(gmw(analogRead(A5)));

Oder:

Serial.println(gmw.doValue(analogRead(A5)));

Alles das gleiche.
Freie Auswahl.
Für jeden Geschmack was dabei.

Hallo,
das ist das Elend bei der MittelWertBildung- die unkontrollierten Ausreißer.
Das Zauberwort heißt Median berechnen.
Nutze ich schon seid dem ohne Probleme.
Gruß und Spaß
Andreas

Hier waren wir inzwischen bei einem anderen Problem, wie man ohne Array von alten Werten eine Filter-Funktion hinkriegt. Dein Median braucht erstens ein Array und muss das auch noch sortieren.
Wenn das nicht stört, und man Probleme mit Ausreißern hat (hier nicht), ist das sicher überlegenswert.

Hier als "Ausreißer" war ein Programmierfehler zu sehen. Da ist Median sicher keine empfehlenswerte Strategie, zumindest wenn man den Fehler auch richtig entfernen kann.

SkobyMobil:
Das Zauberwort heißt Median berechnen.

Eine gute Glättung kann man auch -ganz ohne Array- mit einer einzigen Code-Zeile realisieren:

A0smooth = A0smooth + (A0 - A0smooth ) / n;

A0 ist das Rohsignal
A0smooth das geglättenes
n das Glättungsfaktor z.B. etwas zwischen 3 und 30.

Alles vorzugsweise Float.
Wenn mit unsigned Integer gearbeitet wird, bietet sich an A0 zuerst mit Faktor 64 zu multiplizieren. Das Ergebnis ist dann im Bereich 0...65540.

Das bildet ein Low-Pass filter ab, was die Elektroniker mit einem Widerstand-Kondensator-Glied zum Entstören machten.

P.S. Wenn A0 sich nur langsam ändert, bekommt man damit auch noch 1-2 Bits mehr Auflösung geschenkt.

mit einer einzigen Code-Zeile realisieren

Ist die gleiche Lösung wie combies Anfangs-Antwort. (#2)
Der allerdings versucht, diese Zeile möglichst tief zu vergraben. :slight_smile:

Wenn mit unsigned Integer gearbeitet wird, bietet sich an A0 zuerst mit Faktor 64 zu multiplizieren. Das Ergebnis ist dann im Bereich 0...65540.

Da bietet sich auch ein n = 16 oder 32 an, wobei der Compiler automatisch Division (und Multiplikation mit 64) zu shift-Operationen optimiert.

OK, macht jetzt im Zusammenhang mit Arduinos analogRead (mit ca. 100 µs delay) nicht wirklich etwas aus, aber es gibt ja auch nicht blockierendes ADC, bei dem man diese 100 µs für anderes nutzen könnte.

michael_x:
Ist die gleiche Lösung wie combies Anfangs-Antwort. (#2)

Ich glaube nicht.
Es ist kein gleitender Mittelwert, sondern ein Tiefpassfilter.

Im Ergebnis ist die Funktion eine andere.
Der gleitender Mittelwert ergibt nach n Messungen ein mathematisch richtigen Ergebnis.

Der Tiefpassfilter bildet eine Asymptote. Er braucht -mathemathisch gesehen- unendlich lange, um ein vollendeter Mittelwert anzuzeigen.

In der Praxis ist jedoch das Ergebnis vom Tiefpassfilter oft ausreichend.

float factor = 0.1; 
float mittelwert;

mittelwert *= 1.0 - factor;
mittelwert += factor * value;

Das nennt combie "gleitender Mittelwert". Es ist ein Tiefpassfilter erster Ordnung (wenn dies in einem regelmäßigen Zyklus gemacht wird), der sich asymptotisch einer sprungförmigeen Änderung von value annähern würde.

Sein factor und dein 1/n sind sogar dasselbe.

Es ist ein Tiefpassfilter erster Ordnung,

So ist es wohl.

michael_x:
Sein factor und dein 1/n sind sogar dasselbe.

Ok, habe mich von der Beschreibung "GleitenderMittelwert" irren lassen.

Jetzt noch ein Schmankerl, wenn man "n" sehr groß wählt, z.B. um ein Langzeitdurchschnitt abzubilden:

if(millis < 10000) {
n=10 }else{
n=5000 }

Damit pendelt sich der Tiefpassfilter in den ersten 10Sekunden* schneller auf dem Durchschnitt.

  • auch noch kurz aller 50 Tage, aber das merkt man nicht.

warning: ISO C++ forbids comparison between pointer and integer [-fpermissive]

:slight_smile:

Auch hübsch wäre, wenn man die Anzahl der bisherigen Zyklen prüft und am Anfang einen echten Mittelwert bildet. Da kann man digital deutlich mehr machen als Tiefpässe zu simulieren.

byte n = 1;
const byte TIEFPASS = 32;
float mittel = 0.0;

void loop() {
   int v = analogRead(0);
   if (n < TIEFPASS) { mittel = (mittel * (n-1) + v ) / n; n++; }
   else mittel = (mittel*TIEFPASS - (mittel-v)) / TIEFPASS;

   Serial.print(mittel,1);
   delay (100);  
}

Fängt nicht bei 0.0 an !