Forum: Mikrocontroller und Digitale Elektronik Maximalwert einer Sinuskurve mit einer Software in C finden


von Olaf (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich suche eine Lösung für mein Problem und hoffentlich kann mir jemand 
auf die Sprünge helfen, der Mikrocontroller ist ein ATmega8.

Ich erwarte ein sinusförmiges Eingangssignal von 0 bis 5V, Frequenz ist 
50Hz. Der Nullpunkt dieser Sinuskurve liegt bei 2,5V.

Nun will ich mich ein wenig tiefer in die AVR-GCC-Programmierung wagen 
und möchte den höchsten Spannungswert mittels Software finden und 
speichern.

Meine Überlegungen:
Da es sich um eine Frequenz von 50Hz handelt, ist eine Periode 20ms 
lang.
Für die Abtastung einer Periodenlänge kommt daher ein Zeitfenster von 
20ms in Frage. In diesen 20ms werden (sagen wir mal) 200 ADC-Messwerte 
gelesen und überprüft, ob der aktuelle Messwert größer ist als der 
zuletzt gemessene ADC-Wert. Nur, wenn das der Fall ist, wird der neue 
Messwert als als "Höchstwert" gespeichert, der dann mit dem folgenden 
ADC-Messwert verglichen wird.
Sind die 20ms abgelaufen, wird eine neue Messwertaufnahme gestartet, und 
das insgesamt 10 Mal.
Die eingelesen Messwerte werden jeweils nach dem Ablauf des Zeitfensters 
aufaddiert. Wenn 10 Messwerte aufgenommen wurden, wird daraus der 
Mittelwert gebildet und ggfs. ausgegeben.

Kann man das so machen oder gibt es eine elegantere Lösung für dieses 
Problem?

Besten Dank,
Olaf

von MarioT (Gast)


Lesenswert?

Olaf schrieb:
> Wenn 10 Messwerte aufgenommen wurden, wird daraus der
> Mittelwert gebildet und ggfs. ausgegeben.

Warum gerade 10 Messwerte?

von Armin (Gast)


Lesenswert?

du kannst die Differenz zum letzten Messewert ausrechnen und immer dann, 
wenn die das Vorzeichen wechselt, ist entweder eich Hoch- oder ein 
Tiefpunkt.

von Olaf (Gast)


Lesenswert?

MarioT schrieb:
> Warum gerade 10 Messwerte?
Damit ich dadurch den arithmetischen Mittelwert bekomme und so ein 
Schwanken der Messwerte weitestgehende verhindern möchte.
Wenn grob gesagt alle 20ms ein Messwert ausgespuckt wird, schwankt die 
Anzeige mehr, als wenn alle 200ms ein Mittelwert aus den 10 
aufgenommenen Werten ausgegeben wird.

Armin schrieb:
> du kannst die Differenz zum letzten Messewert ausrechnen und immer dann,
> wenn die das Vorzeichen wechselt, ist entweder eich Hoch- oder ein
> Tiefpunkt.
Ich muss doch aber wissen, ob sich der Messwert in der Differenzmessung 
wirklich um einen Hochpunkt handelt und es sich nicht um ein "fallendes" 
Signal handelt. Ich stehe um diese Uhrzeit wohl aufm Schlauch und weiß 
nicht, wie ich das genau zu verstehen habe.

von Armin (Gast)


Lesenswert?

aber wenn du den Zeitpunkt kennst, bis zu dem das Signal steigt und ab 
dem das Signal fällt, dann ist das der Hochpunkt.

--> Morgen nochmal durchlesen.

von H.Joachim S. (crazyhorse)


Lesenswert?

mit der Mittelwertbildung erreichst du niemals den Spitzenwert, sondern 
immer einen Wert, der leicht darunter liegt (Abtastrate kann nicht 
unendlich hoch sein, d.h. den absoluten Spitzenwert wirst du nur selten 
treffen. Normalerweise liegst du knapp davor oder dahinter, beides 
liefert Messwerte, die tiefer liegen)
Glücklicherweiser ist die Steigung eines Sinussignals gering, so dass 
die Fehler wahrscheinlich vernachlässigbar bleiben.

von MarioT (Gast)


Lesenswert?

if Wert_neu - Wert_alt > 0 then Wert_alt = Wert_neu

Olaf schrieb:
> Damit ich dadurch den arithmetischen Mittelwert bekomme und so ein
> Schwanken der Messwerte weitestgehende verhindern möchte.

Ich meinte warum müssen es unbedingt "10" Messungen sein?
Du willst es doch auf einem µC machen. Der "denkt" aber etwas anders als 
Du. Du rechnest doch Dezimal, wenn man etwas durch 10 teilt verschiebst 
Du doch nur das Komma. Für einen µC ist teilen durch 10 aber schon ein 
größerer Akt.

von Benni N. (benninori)


Angehängte Dateien:

Lesenswert?

Oh, das Thema passt mir gerade super. Werde bald was ähnliches machen. 
Ich finde die Idee mit der Differenz der Messwerte auch gut. Nur sieht 
es bei mir so aus, dass ich die PWM an einem Drehstrommotor zuerst über 
einen Filter jage und dann die Amplitude und Frequenz messen will. Jetzt 
hat mein Sinus, je nach Drehzahl des Motors, bis zu 300 Hz. Habe jetzt 
mal in der Simulation einen Filter gebastelt. Bei hohen Frequenzen 
bekomme ich dann auch einen wunderbaren Sinus, nur unten wirds schwer. 
Der Sinus ist dort auch erkennbar, aber halt nicht ganz so sauber (siehe 
Bild). Da kann ich diese Methode schon wieder vergessen, oder?

von Karl H. (kbuchegg)


Lesenswert?

Benni Nori schrieb:

> Bild). Da kann ich diese Methode schon wieder vergessen, oder?

Ja.
Man sollte auch bedenken, dass für die Bestimmung der Differenz der 
elektronische Aufbau schon sauber sein sollte. Ansonsten passiert es 
nämlich, dass der ADC selbst bei absolut konstanter Eingangsspannung 
immer ein wenig um den wahren Wert schwankt (+/- 1 oder 2 Digit)

Wenn ich aber sowieso weiß, dass die 0-Linie meiner Spannung bei 2.5V 
liegt, dann würde ich das Maximum bzw. Minimum genau so ermitteln wie 
der TO das hatte.
Sobald die Spannung einen Wert von 2.5V + Hysterese überschreitet wird 
die nächste Maximumsuche scharf geschaltet und das letzte gefundene 
Minimum als das Minimum im letzten Abschnitt ausgewiesen. Unterschreitet 
die Spannung 2.5V - Hysterese, so wird die Minimum-Suche scharf 
geschaltet und das zuletzt gefundene Maximum ausgegeben.

von Olaf (Gast)


Lesenswert?

Hallo,

ich habe mich derweil in diesem genialen Forum weiter umgeschaut und bin 
auf einen ähnlichen Beitrag gestoßen.
Da ich eine 4 stellige 7-Segment-Anzeige verwende, habe ich den Code 
dieses Posts teilweise verwendet.

Ich kam nun zu folgendem Code (mich interessiert nur die positivste 
Spannungsspitze):
1
volatile unsigned char fertig = 0;
2
volatile unsigned int result;
3
volatile unsigned int offset = 511;
4
volatile unsigned int peak = 511;
5
6
void messung (void)
7
{
8
  {
9
    ADCSRA |= (1<<ADSC);
10
    while ( ADCSRA & (1<<ADSC) ) {
11
      ;  
12
    }
13
    result = ADCW;
14
  }
15
  fertig = 1;
16
}
17
18
int main ( void )
19
{
20
adcinit();
21
ioinit();
22
23
while (1)
24
 {
25
   if (fertig == 0)
26
   {
27
   messung();
28
   }
29
30
   if (fertig == 1 && result > peak)
31
   {
32
   peak = result;
33
   fertig = 0;
34
   }
35
36
   if (fertig == 1 && result < offset)
37
   {
38
   Errechnet = (int)(peak / 5.11);
39
   peak = offset;
40
   fertig = 0;
41
   }
42
43
   if (!Taster_gedrueckt)
44
   DisplayNumber(Errechnet);
45
   else DisplayDigit(peak);
46
 }
47
}

Leider funktioniert das nicht wirklich. Der Atmega8 macht nur eine 
Messung und läuft nicht ständig durch, obwohl ich doch das "fertig-Flag" 
zurücksetze.

Ist wohl doch nicht so einfach, wie ich mir das gedacht habe?!

Gruß,
Olaf

von Walter (Gast)


Lesenswert?

zum einen fehlt da einiges vom Code,
zum anderen kannst du dir die Variable fertig komplett sparen (ist zwar 
nicht der Fehler, macht das Programm aber übersichtlicher)

von Armin (Gast)


Lesenswert?

wofür brauchst du das "Fertig" überhaupt? das macht hier doch nur sinn, 
wenn die messung() in einem parallel thread läuft, oder? ich kann grad 
nicht folgen.

Ansonsten seh ich bloß, dass, wenn einmal genau 511 gemessen wird, das 
ganze Ding stehen bleibt (fertig=1 dauerhaft)

von dr.schmock (Gast)


Lesenswert?

Olaf schrieb:
> if (fertig == 1 && result > peak)
.
.
> if (fertig == 1 && result < offset)

wenn keine der beiden Bedingungen wahr ist, wird "fertig" nicht 
zurückgesetzt. Vielleicht ist das das Problem.

von Olaf (Gast)


Lesenswert?

Walter schrieb:
> zum einen fehlt da einiges vom Code,
> zum anderen kannst du dir die Variable fertig komplett sparen (ist zwar
> nicht der Fehler, macht das Programm aber übersichtlicher)
Der restliche Code ist nur die Ausgabe für die 7-Segment-Displays.
Mehr habe ich nicht im Code stehen, halt nur die Ansteuerung der 
Displays (das funktioniert supi) und dann die ADC-Geschichte 
(funktioniert nicht wirklich). Meine Stehlampe zieht 1,12A, das Display 
hat nur eine Nachkommastelle und zeigt 1,1A an. Soweit ist das korrekt.
Ich habe das fertig-Flag definiert, damit die if-Anweisungen ausgeführt 
werden.

Armin schrieb:
> wofür brauchst du das "Fertig" überhaupt?
> das macht hier doch nur sinn,
> wenn die messung() in einem parallel thread läuft, oder?
Antwort siehe oben... Parallel läuft es eigentlich nur mit der 
7-Segment-Ausgabe.

dr.schmock schrieb:
> Olaf schrieb:
>> if (fertig == 1 && result > peak)
> .
> .
>> if (fertig == 1 && result < offset)
>
> wenn keine der beiden Bedingungen wahr ist, wird "fertig" nicht
> zurückgesetzt. Vielleicht ist das das Problem.
Hhm, mein Offset beträgt 511, entspricht also 2,5V.
Und ich komme wirklich unter die 2,5V! Aber in diese Richtung muss ich 
mal weitersuchen.

von Olaf (Gast)


Lesenswert?

Wenn ich das "fertig-Flag" wegnehme, funktioniert die Anzeige wieder. 
:-)

Nun habe ich aber das Problem, dass bei 0A, also 2,5V (511) die Anzeige 
keine 0 mehr anzeigt, sondern beim letzten Spannungswert stehen bleibt. 
:-(

von Karl H. (kbuchegg)


Lesenswert?

Olaf schrieb:
> Wenn ich das "fertig-Flag" wegnehme, funktioniert die Anzeige wieder.
> :-)

Eben.
Dieses 'fertig' ist ein Selbstläufer. Es bringt dir nichts und ist nur 
ein Quell von Fehlern.

> Nun habe ich aber das Problem, dass bei 0A, also 2,5V (511) die Anzeige
> keine 0 mehr anzeigt, sondern beim letzten Spannungswert stehen bleibt.
> :-(

Klar. Weil deine gemessene Spannung niemals die Bedingung erfüllt

  if ( result < offset)

Mach noch einen Zähler mit rein, der jedesmal nach einer Messung um 1 
erhöht wird. Dann machst du noch

  if ( result < offset || counter > 200 )

(200 oder was dir anmgemessen erscheint) so dass du nach spätestens 200 
messungen eine Ausgabe erzwingst (und natürlich den Counter dann auch 
wieder auf 0 zurück setzt).

von Olaf (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Klar. Weil deine gemessene Spannung niemals die Bedingung erfüllt
>
>   if ( result < offset)
>
> Mach noch einen Zähler mit rein, der jedesmal nach einer Messung um 1
> erhöht wird. Dann machst du noch
>
>   if ( result < offset || counter > 200 )
>
> (200 oder was dir anmgemessen erscheint) so dass du nach spätestens 200
> messungen eine Ausgabe erzwingst (und natürlich den Counter dann auch
> wieder auf 0 zurück setzt).

Danke, das hört sich vernünftig an.

Werde aber morgen damit weitermachen, für heute reicht es mir!
Muss dann wohl in der Nacht darüber weitergrübeln. grins
Eventuell werde ich erst einmal 16 Peaks sammeln und dann den artihm. 
Mittelwert bilden, damit die Anzeige nicht zappelt.

Gute Nacht, vielen Dank für alle Antworten und bis morgen! :-)
Lieben Gruß, Olaf

von Olaf (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Klar. Weil deine gemessene Spannung niemals die Bedingung erfüllt
>
>   if ( result < offset)

Am Tage versteht man das besser und es stimmt, bei 0A (2,5V) komme ich 
wirklich nie unter offset (2,5V).
Der Zähler ist bereits eingebaut und nach 20 Zählschritten wird der Wert 
auf 0 gesetzt, wenn offset nicht mehr unterschritten wird.

Jetzt kann ich ohne Grübeln beruhigt in den Urlaub fahren!

Besten Dank, Olaf

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.