Forum: Compiler & IDEs Berechnungsfehler?


von C. S. (chappi)


Lesenswert?

Hi, ich habe 2 ADC Messungen an meinem ATmega und will nun wissen, ob 
diese nicht zu weit auseinander sind. Um eine Bedingung zu sparen 
quadriere ich erst und ziehe dann wieder die Wurzel. Habe mit einem 
Multimeter bereits nachgemessen und sie unterscheiden sich nicht mehr 
als 0.5 V.
1
temp1=ADC_read(0);
2
temp2=ADC_read(1);
3
4
if(!(sqrt((temp1-temp2)*(temp1-temp2))<0.5))
5
      ERR=1;
Jedes mal geht der Error flag auf 1. Ich bin wahrscheinlich Codeblind 
geworden, ist irgendetwas an der Rechnung falsch?

Danke

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

C. S. schrieb:
> Um eine Bedingung zu sparen
> quadriere ich erst und ziehe dann wieder die Wurzel.

Um einen Groschen zu sparen, geb' ich mal schnell 'ne Mark aus. ;-)

Ansonsten musst du schon noch die Datentypen und die Werte
nennen, die du da bekommst.  Ich tippe auf integer promotion +
sign extension Probleme.

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> Hi, ich habe 2 ADC Messungen an meinem ATmega und will nun wissen, ob
> diese nicht zu weit auseinander sind. Um eine Bedingung zu sparen
> quadriere ich erst und ziehe dann wieder die Wurzel.

Ha, ha
Tschuldige. Aber der ist gut.

Um einen simplen Vergleich zu sparen, brummst du deinem µC hunderte 
Taktzyklen auf?

> Habe mit einem
> Multimeter bereits nachgemessen und sie unterscheiden sich nicht mehr
> als 0.5 V.
>
>
1
temp1=ADC_read(0);
2
> temp2=ADC_read(1);
3
> 
4
> if(!(sqrt((temp1-temp2)*(temp1-temp2))<0.5))
5
>       ERR=1;
6
>
> Jedes mal geht der Error flag auf 1. Ich bin wahrscheinlich Codeblind
> geworden, ist irgendetwas an der Rechnung falsch?

Was genau bekommst du von ADC_read zurück?
Sind das Spannungen oder ADC Werte?



Warum schiesst du dir von hinten selbst durch die Brust ins Auge?

  if ( ! (A < B) )

Wenn du ausdrücken willst das A größer/gleich B sein muss um einen 
Fehler zu haben, dann schreib das auch so

   if( A >= B )

Man kann sich nämlich durch zu kompliziert geschriebenen Code auch 
selber austricksen :-)

von Karl H. (kbuchegg)


Lesenswert?

So kann man eine Bereichsabfrage gestalten.
Die Idee: Die Differenz zwischen den Wert (und zwar deren Absolutwert) 
muss kleiner als ein gewisses Epsilon sein (oder größer, je nachdem)
1
#include "stdlib.h"
2
3
int main()
4
{
5
  int a = 5;
6
  int b = 7;
7
  int Epsilon = 2;
8
9
  if( abs( a - b ) > Espilon )
10
    err = 1;
11
}

Welche Betragsfunktion man nimmt, hängt von den beteilgten Datentypen 
ab.
abs     int
labs    long
llabs   long long
fabs    double

von C. S. (chappi)


Lesenswert?

also beide temp variablen sind als double initialisiert. Desweitern 
führe ich in meiner ADC_read Funktion eine Berechnung durch, um von den 
10 Bit auf eine Voltzahl zu kommen, die dann auch zurückgeliefert wird.

Ich möchte wirklich prüfen, ob die Differenz beider Spanung im Bereich 
von +- 0.5V liegt.

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> also beide temp variablen sind als double initialisiert. Desweitern
> führe ich in meiner ADC_read Funktion eine Berechnung durch, um von den
> 10 Bit auf eine Voltzahl zu kommen, die dann auch zurückgeliefert wird.
>
> Ich möchte wirklich prüfen, ob die Differenz beider Spanung im Bereich
> von +- 0.5V liegt.

Diese Prüfung macht man zwar anders, aber jetzt geht es um das Problem 
in deinem Code.

Zeig doch mal mehr. Aus dem bischen kann man nichts ersehen.

Deine Berechnung stimmt?
Die Funtkionssignatur stimmt?

Hast du die Werte in temp1 und temp2 unabhängig voneinander getestet 
(ausgeben lassen).

von C. S. (chappi)


Lesenswert?

1
#include <math.h>
2
3
int main (void){
4
   [...]
5
6
   double temp1;
7
   double temp2;
8
   ADC_init();
9
10
   [...]
11
   temp1=ADC_read(0);
12
   temp2=ADC_read(1);
13
 
14
   if(!(sqrt((temp1-temp2)*(temp1-temp2))<0.5))
15
       ERR=1;
16
17
   [...]
18
19
}
20
21
void ADC_Init(void) {
22
 
23
  uint16_t result;
24
 
25
  ADMUX = (1<<REFS1) | (1<<REFS0);      // interne Referenzspannung nutzen
26
  ADCSRA |= (1<<ADEN);                  // ADC aktivieren
27
 
28
  ADCSRA |= (1<<ADSC);                  // eine ADC-Wandlung 
29
  while (ADCSRA & (1<<ADSC) );          // auf Abschluss der Konvertierung warten
30
  result = ADCW;
31
}
32
 
33
/* ADC Einzelmessung */
34
double ADC_Read( uint8_t channel )
35
{
36
  // Kanal waehlen, ohne andere Bits zu beeinflußen
37
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
38
  ADSCRA |=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
39
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
40
  while (ADCSRA & (1<<ADSC) )     // auf Abschluss der Konvertierung warten
41
    ;
42
  return (ADCW/1023.0)*5.0;                    // ADC auslesen und zurückgeben
43
}
Reicht das? Denke das ist alles Relevante zu dem Problem

von Karl H. (kbuchegg)


Lesenswert?

Wie groß sind die Spannungen, die du anlegst?

Du scheinst deine Spannungen auf 5V zurückzurechnen, hast aber die 2.56V 
Referenzspannung aktiviert.

Hast du dir schon mal die beiden Werte einzeln angesehen?


(Ich hab deinen Code mal im Simulator durchgespielt. Bei mir wird ERR 
nicht auf 1 gesetzt, wenn ich dem ADC Data Register in ADC_Read gleiche 
Werte unterjuble.

von C. S. (chappi)


Lesenswert?

Es liegt eine Spannung von 2,91 Volt am 1. Eingang und 2,89 am 2. 
Eingang an.

Was bedeutet das mit der Referenzspannung?

Das komische ist nämlich, dass die Einzelspannungen auch korrekt 
ausgelesen werden. Sprich wenn ich Beispielsweise prüfe:
1
temp1 = ADC_read(0)
2
if(!((temp1 > 2.3) & (temp1 <2.7)))
3
    ERR=1;

In diesem Fall wird die ErrorFlag nicht gesetzt.

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:

> Was bedeutet das mit der Referenzspannung?

Dein ADC liefert dir ja nicht die Spannung direkt.
Sondern er gibt dir eine Zahl zwischen 0 und 1023, die aussagt wie hoch 
die gemessene Spannung im Vergleich zur Referezspannung ist.

Im Prinzip (wenn wir mal vom Zahlenwert absehen) sagt dir dein ADC:
Die Messspannung beträgt x Prozent von der Messspannung.

> Es liegt eine Spannung von 2,91 Volt am 1. Eingang und 2,89 am 2.
> Eingang an.

Wenn du eine Referenzspannung von 2.56 Volt eingestellt hast, dann ist 
das deutlich zu hoch.

Welchen Mega verwendest du eigentlich?
Ich hab beim Mega16 nachgesehen und da ist

        (1<<REFS1) | (1<<REFS0)

die interne Referenz in Höhe von ca. 2.5 Volt

Ist deine Messspannung darüber, dann liefert der ADC ständig 1023


Eindringlicher Tip:
Ehe du weiter machst, schau dir die ADC Werte direkt an, so wie du sie 
vom ADC bekommst. Es bringt nichts, schon mit falschen Werten in eine 
Berechnungen zu gehen und sich dann zu wundern warum da nichts 
vernünftiges rauskommt.

> temp1 = ADC_read(0)
> if(!((temp1 > 2.3) & (temp1 <2.7)))
>     ERR=1;
>
> In diesem Fall wird die ErrorFlag nicht gesetzt.

Hast du keine besseren Möglichkeiten, dir die Werte anzusehen, als immer 
nur diese indirekten Schlussfolgerungen über ein ERR Flag?

Irgendwo musst du doch Zahlenwerte ausgeben können. LCD, UART oder 
dergleichen.
Wenn nicht, dann frage ich mich wozu du eigentlich die ADC Werte 
überhaupt in Spannungen umrechnest. Rechne doch gleich alles mit ADC 
Werten und rechne deine Grenzspannungen in ADC Einheiten um. Ist für den 
µC viel einfacher und schneller zu rechnen als da mit Floating Point 
rumzumachen.

von Karl H. (kbuchegg)


Lesenswert?

Setzt du eigentlich ERR jemals wieder auf 0 zurück?

von Klaus W. (mfgkw)


Lesenswert?

Dein Wandler hat 10 Bit, er liefert als einen Wert von 0 bis 1023.

Welcher dieser Werte entspricht jetzt welcher angelegten Spannung?

Der ADC-Wert 0 gehört immer zur Spannung 0 Volt.

Der höchste Wert (1023) gehört zur Referenzspannung, die restlichen
Werte dazwischen verteilen sich linear.

Hast du eine Referenzspannung von 2.56 V, dann steht 1023 für
2.56 V, 511 für 1.28 V usw..
Ist die Referenzspannung dagegen 5 V, dann steht 1023 für
5 V und 511 für 2.5 V.

PS. ok wieder zu spät...

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:

> ausgelesen werden. Sprich wenn ich Beispielsweise prüfe:
>
>
1
> temp1 = ADC_read(0)
2
> if(!((temp1 > 2.3) & (temp1 <2.7)))
3
>     ERR=1;
4
>


Mein Gott.
Du scheinst eine Vorliebe dafür zu haben, deine Abfragen möglichst 
kryptisch so zu schreiben, dass man nur ja beim ersten Hinschauen bloss 
nicht erkennen kann, was eigentlich die Absicht an dieser Stelle ist.

Ganz abgesehen davon, dass hier ein && angebrachter wäre als ein &


> Es liegt eine Spannung von 2,91 Volt am 1. Eingang und 2,89 am 2.
> Eingang an.
> ...
> ausgelesen werden. Sprich wenn ich Beispielsweise prüfe:
> temp1 = ADC_read(0)
> if(!((temp1 > 2.3) & (temp1 <2.7)))
>     ERR=1;
>
> In diesem Fall wird die ErrorFlag nicht gesetzt.

Da dein Vergleich gleichbedeutend ist mit

  if( ( temp1 <= 2.3 ) || ( temp1 >= 2.7 ) )
    ERR = 1;

sollte aber ERR auf 1 gesetzt werden, wenn deine Spannung 2.91 bzw. 2.89 
Volt beträgt. Denn die ist definitiv größer als 2.7

von Karl H. (kbuchegg)


Lesenswert?

Alles in allem tauchen mir da jetzt bei den Analysen schon zuviele 
Ungereimtheiten auf, dass ich vorschlage du postest deinen kompletten 
Code.

Mitlerweile hab ich das Vertrauen verloren, so dass ich als Fehlerquelle 
nichts mehr ausschliessen möchte. Noch nicht einmal eine falsche 
Auswertung des ERR-Flags.


Wenn dein Code zu umfangreich ist, dann produziere eine abgespeckte 
Version, die nur das notwendigste (ADC Abfrage samt Auswertung) enthält, 
teste ob sie den gleichen Fehler enthält und poste diese abgespeckte 
Version, wenn der Fehler noch da ist.

Und bitte: keine Code-Auszüge!
Ein komplettes, compilierbares Programm

von C. S. (chappi)


Lesenswert?

Also ich nutze einen ATmega 324P20PU und ich lese gerade, dass ich nur 
(1<<REFS0) brauche um AVCC als Referenzspannung einzustellen.

Karl heinz Buchegger schrieb:
> Da dein Vergleich gleichbedeutend ist mit
>
>
>
>   if( ( temp1 <= 2.3 ) || ( temp1 >= 2.7 ) )
>
>     ERR = 1;
>
>
>
> sollte aber ERR auf 1 gesetzt werden, wenn deine Spannung 2.91 bzw. 2.89
>
> Volt beträgt. Denn die ist definitiv größer als 2.7


Sry das Überprüfungsbeispiel bezog sich auf eine andere Überprüfung wo 
der Wert 2.5V beträgt.
Wobei ich glaube, dass ein logisches oder nicht die richtige lösung 
wäre. Es soll ja beides erfüllt sein. Das mit dem einfachen & habe ich 
wohl übersehen... :/

ich gebe noch bescheid, ich probiere jett erstmal aus.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

C. S. schrieb:
> Wobei ich glaube, dass ein logisches oder nicht die richtige lösung
> wäre. Es soll ja beides erfüllt sein.

Boolesche Algebra.  Die Bedingung wird negiert (es wird ja ein
Fehler-Flag in Abhängigkeit davon gesetzt), und dann wird aus UND
ein ODER.

von C. S. (chappi)


Lesenswert?

Ich dachte das Ergebnis der Bedingung wird negiert und nicht die 
Opratoren. Ich ging davon aus, dass die Logik vollzogen wird und am Ende 
das Ergenis negiert wird.

von Klaus W. (mfgkw)


Lesenswert?

Ich bin ca. 186 groß.

Damit stimmt die Aussage:
"Ich bin größer als 180 UND kleiner als 190".

Ebenfalls richtig ist:
"es stimmt nicht, daß ich kleiner als 179 bin ODER größer als 191"
1
  if( l>180 && l<190 ) { ich bins vielleicht }
2
  if( !( l<180 || l>191 ) ) { ich bins vielleicht }

von Klaus W. (mfgkw)


Lesenswert?

Daß es dir schwerfällt, die Umkehrung von && nach || nachzuvollziehen, 
ist keine Schande.
Da stutzen andere auch und fallen öfter rein.

Das zeigt um so mehr, wie wichtig es ist, im Quelltext möglichst 
einfache Formulierungen zu verwenden und nicht alles von hinten durch 
die Brust ins Auge zu zielen.

von C. S. (chappi)


Lesenswert?

habe ich so verstanden, danke für das beispiel :D.

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:

> Wobei ich glaube, dass ein logisches oder nicht die richtige lösung
> wäre.


Man kanns auch so sehen:

Du willst wissen, ob eine Spannung in einem bestimmmten Bereich ist

Dazu muss die Spannung größer als die Bereichsuntergrenze sein   UND
          die Spannung muss kleiner als die Bereichsobergrenze sein


Soweit so gut: Jetzt drehst du die Logik um.
Du willst nicht mehr wissen, ob sie im Bereich ist sondern das genaue 
Gegenteil: Du willst wissen, ob sie ausserhalb ist.

Jetzt kannst du natürlich da ein NICHT davor schreiben.

Du kannst aber auch sagen:
  Wenn meine Spannung kleiner als die Bereichsuntergrenze ist   ODER
  die Spannung größer als die Bereichsobergrenze
dann liegt sie ausserhalb des Bereichs!

> Es soll ja beides erfüllt sein.

Das geht in deinem Fall nicht.
Eine Spannung kann nicht gleichzeitig kleiner als eine Untergrenze UND 
größer als eine Obergrenze sein.

Mal dir eine Zahlengerade auf

1
       / / / / / / * * * * * / / / / /
2
      / / / / / /  * * * * */ / / / /
3
 ----+-----------+----------+---------
4
       nicht                   nicht
5
     erlaubt        erlaubt   erlaubt

dein Zahlenraum teilt sich in 3 Bereiche auf:
im * Teil liegen deine erlaubten Zahlen - das ergibt keinen Fehler
die beiden / Teile sind jeweils nicht erlaubte Bereiche - wenn deine
Spannung da drinnen liegt, dann willst du einen Fehler melden.

Deine Spannung (wenn sie ungültig ist) kann aber nur entweder im linken 
Teil ODER im rechten / Teil liegen. In beiden gleichzeitig geht nicht.

> Das mit dem einfachen & habe ich
> wohl übersehen... :/
>
> ich gebe noch bescheid, ich probiere jett erstmal aus.

Das war aber nicht das eigentliche Problem.
Dein eigentiches Problem ist immer noch unklar.

von C. S. (chappi)


Lesenswert?

juhu klappt endlich....vielen dank!!!!

von würg (Gast)


Lesenswert?

Wäre es nicht "schöner" die Differenz beider Werte zu ermitteln und mit 
der Toleranz zu vergleichen ?


Sowas wie :
1
#define toleranz 3            // Wie gross ist meine Toleranz ?
2
3
if ( a>b ) t = a - b;         // Grösserer Wert von kleinerem abziehen.
4
else       t = b - a;         // t enthält nun die (pos.) differenz a - b
5
6
if (t > toleranz) do_error_flag;
7
else              undo_error_flag;

von C. S. (chappi)


Lesenswert?

Ich persönlich finde es zwar anschaulicher, allerdings bläht das mein 
ohnehin schon langes Programm nurnoch auf. Zumal dort noch eine 
if-Abfrage hinzukommt.

von Peter (Gast)


Lesenswert?

C. S. schrieb:
> Ich persönlich finde es zwar anschaulicher, allerdings bläht das mein
> ohnehin schon langes Programm nurnoch auf. Zumal dort noch eine
> if-Abfrage hinzukommt.

du machst dir sorgen wegen der Programm größe und verwendest double und 
sqrt? Nicht die Anzahl der Zeilen machen ein Programm groß!

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> Ich persönlich finde es zwar anschaulicher, allerdings bläht das mein
> ohnehin schon langes Programm nurnoch auf. Zumal dort noch eine
> if-Abfrage hinzukommt.

Das ist .... ein richtiger Schenkelklopfer, wenn man sich die spärlichen 
Programmausschnitte so ansieht.

You made my day


(PS:
1) Funktionen sind schon lange erfunden.
2) Jede Alternative zu double bzw. sqrt ist eine spitzenmässige 
Alternative)

PS-2:
Wenn du dich dazu durchringen könntest, mal etwas größere Teile deines 
Programms zu zeigen, dann könnt man dir auch gezielt zeigen, wie du

a) die Komplexität in der Schreibweise des C-Codes runterbringen
   kannst

b) dein Programm soweit vereinfachen kannst, dass sich auch der
   µC damit leichter tut.

c) dein Programm insbesondere wegen letzterem Punkt um einen Faktor x
   beschleunigt wird.

Du könntest davon profitieren und das eine oder andere dabei lernen. 
Aber wenn du nicht willst ....

von xGast (Gast)


Lesenswert?

Ich behaupte mal 90-95% deiner Programmgröße ist auf Fließkommazahlen, 
Wurzel und Quadratfunktion zurückzuführen. D.h. du brauchst nicht 
Abfragen sparen, da diese wirklich nur minimal Platz benötigen. Schau 
lieber, dass du dein programm auf Festkomma umstellst und auf 
Wurzelfunktionen verzichtest. Dann kannst du tausende If Abfragen ein 
bauen und dein Programm wird in einem Bruchteil der Zeit ablaufen.

von würg (Gast)


Lesenswert?

Daraus lässt sich bestimmt noch ein Einzeiler machen... ;)

Mir persöhnlich ist ein aufgeblähter Sourcecode mit Kommentaren und 
gescheiter Formatierung viel lieber als den µC 1.000 takte rechnen zu 
lassen.

Mein KISS verlangt nicht nach Numerik die eine Wurzel nähert,
noch eine Multiplikation mit sich selbst.
Weiss denkst Du wieviel Schleifendurchläufe alleine für die Wurzel nötig 
sind ?
(in der auch wieder abfragen und multiplikationen stecken)

Ich kann leider kein Assembler, aber ich schätze mal :

-Werte laden
-Vergleich gröser kleiner 16 bit
-Subtraktion ausführen / Else Sprung mit Subtraktion

-Werte laden
-vergleich grösser kleiner 16bit
-Jump oder else Jump


Weisst Du wie man eine Wurzel Approximiert (jetzt musst Du eigentlich 
noch eine Fehlerabschätzung machen ;) ?
Während dessen wird obiger Code geschätzt 100 bis 1000 mal ausgeführt.
(jedenfalls nach meinem Gefühl)


Aber Hauptsache Dein Problem ist behoben.

von Karl H. (kbuchegg)


Lesenswert?

Oh. Ein PS hab ich noch vergessen.

Arithmetische Operationen kosten auch Rechen-Zeit. Die kriegst du nicht 
umsonst.
Insbesondere kostet Floating Point Rechnerei auf einem AVR eine Menge 
Zeit. Aber so richtig Zeit kosten dann "Spezialdinge" wie sqrt oder sin, 
cos, log, pow, ....

von Karl H. (kbuchegg)


Lesenswert?

Seis drum

Schreibs wenigstens so
1
#define TOLERANZ 0.5            // Wie gross ist meine Toleranz ?
2
3
uint8_t isEqual( double a, double b )
4
{
5
  double t
6
7
  if ( a > b )
8
    t = a - b;
9
  else
10
    t = b - a;
11
12
  return t < TOLERANZ;
13
}
14
15
....
16
17
int main (void){
18
   [...]
19
20
   double temp1;
21
   double temp2;
22
   ADC_init();
23
24
   [...]
25
   temp1 = ADC_read(0);
26
   temp2 = ADC_read(1);
27
 
28
   if( ! isEqual( temp1, temp2 ) )
29
       ERR = 1;
30
31
   [...]
32
33
}

schon alleine das hier wird die Abfrage um einen Faktor (grob geschätzt) 
100 bis 200 beschleunigen. Von der besseren Lesbarkeit red ich erst mal 
gar nicht.

von C. S. (chappi)


Lesenswert?

Mir ist momentan eine Optimierung im Bezug auf Rechenzeit oder 
Speicherplatz noch egal. Erstmal möchte ich mich auslassen und dann kann 
ich, falls es eng wird immer noch optimieren ;). Ich lerne ja gerade 
erst, wie ihr offenbar alle festgestellt habt ^^.

Ich werde mein Programm bald zur Verfügung stellen. Thx @ all

von Peter (Gast)


Lesenswert?

C. S. schrieb:
> allerdings bläht das mein
> ohnehin schon langes Programm nurnoch auf.

C. S. schrieb:
> Mir ist momentan eine Optimierung im Bezug auf Rechenzeit oder
> Speicherplatz noch egal.

Was nun?

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> Mir ist momentan eine Optimierung im Bezug auf Rechenzeit oder
> Speicherplatz noch egal.

Du hast es immer noch nicht.
Es geht erst in 2-ter Linie um Rechenzeit und Speicherplatz.

Du kannst deinen C-Code viel einfacher gstalten und gleichzeitig, quasi 
als Nebeneffekt, den Prozessor entlasten!

> Erstmal möchte ich mich auslassen und dann kann
> ich, falls es eng wird immer noch optimieren ;).

Was du machst, hat mit 'Optimierung beiseite lassen' nicht wirklich was 
zu tun. Was du machst ist: Ich schreibe den schlimmst möglichen Code!

Wenn es ein Gegenteil zu 'optimieren' gibt, dann betreibst du das genau 
jetzt. Und zwar ohne wirklichen Grund! Das kommt dann nämlich als 
Sahenhäubchen noch mit oben drauf.

> Ich lerne ja gerade
> erst, wie ihr offenbar alle festgestellt habt ^^.

Was denkst du wohl warum hier so viele auf dich einreden, wie auf eine 
kranke Kuh?

> Ich werde mein Programm bald zur Verfügung stellen. Thx @ all

Tus gleich!
Wenn der Rest des Programms genauso aussieht, wie das bischen was du 
gezeigt hast, dann besteht da Unmenge an Potential, wie man den im 
C-Code(!) vereinfachen kann.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Insbesondere kostet Floating Point Rechnerei auf einem AVR eine Menge
> Zeit.

Ja...jein.  Teilweise sind die recht gut optimiert.  uint32_t zum
Bleistift rechnet sehr viel länger herum, ist zwar ein paar Stellen
genauer, dafür fehlt die Dynamik.

Ich tendiere auch dazu, solche Dinge erstmal in Gleitkomma zu
implementieren, weil man dann schneller feststellt, ob der Algorithmus
auch plausibel ist und sich keine Gedanken um Zahlenbereichsüberläufe
oder derlei Dinge machen muss.  Man kann halt 2,89 V auch als 2.89 im
Programm darstellen und sich im Debugger genau so ansehen (die
nachgemessene (!) ADC-Referenzspannung ist dann oft ein #define im
Programm).

Ob ich das dann später noch in einen Festkommaalgorithmus überführe
(nachdem klar ist, dass der Algorithmus an sich "steht"), hängt
wesentlich von der Aufgabenstellung ab.  Wenn der Controller sowieso
noch nichtmal zu 50 % voll ist (ein Austausch durch den nächst
kleineren aber nicht lohnt, wer lötet schon freiwillig ein TQFP
wieder aus, wenn's nicht sein muss?) und die Rechenzeit auch keine
wirkliche Rolle spielt, dann sehe ich meist keinen Grund für
irgendwelche post-mortem-Optimierungen.

Gut, ich wäre allerdings trotzdem nicht auf die Idee gekommen, zwei
if-Anweisungen durch eine umständliche Quadratwurzel zu ersetzen. ;-)

von C. S. (chappi)


Lesenswert?

okay okay ich habs verstanden :(

was liefert deine fuktion zurück, nur 1 oder 0 richtig??

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> okay okay ich habs verstanden :(
>
> was liefert deine fuktion zurück, nur 1 oder 0 richtig??

Das was ein Vergleich eben ergibt.
Von daher: richtig

von C. S. (chappi)


Angehängte Dateien:

Lesenswert?

so, könnt ihr euch anschauen, ist nahezu das komplette Programm. Bitte 
zerpflückt mich :DD

von Karl H. (kbuchegg)


Lesenswert?

Wenn dir das noch zu kryptisch ist, kannst du es ja auch so schreiben
1
uint8_t isEqual( double a, double b )
2
{
3
  double t
4
5
  if ( a > b )
6
    t = a - b;
7
  else
8
    t = b - a;
9
10
  if( t < TOLERANZ )
11
    return 1;
12
13
  return 0;
14
}

Der springende Punkt ist aber:
Nichts und niemand hindert dich daran, für solche Dinge dir selber 
Funktionen zu schreiben. Das sieht auf den ersten Blick nach mehr Arbeit 
aus, ist es aber nicht. Alleine die Klarheit, die sich bei der 
Verwendung dadurch einstellt
1
  if( ! isEqual( temp1, temp2 ) )
2
       ERR = 1;

ist diese Funktion schon wert. Selbst wenn man nicht weiß, wie isEqual 
im Detail funktioniert, ist das hier
a) viel besser zu lesen als dein Phytagoras Ansatz. Das ist schon
   fast ein vollständiger englischer Satz, den ich nur so zu lesen
   brauche wie er da steht und ich erfasse das, was an dieser Stelle
   passiert.
b) für den µC viel einfacher auszuwerten

Man schlägt hier 2 Fliegen mit einer Klappe.
Der C Code wird einfacher
Der Code den der µC ausführen muss, wird einfacher

Und deswegen amüsieren wir uns auch über deinen Wurzel Ansatz :-)

Um deine Routenplanung für die Fahrt von Wien nach Paris nicht zu 
kompliziert werden zu lassen, fragst du nach einem guten Weg durch 
Koppenhagen durch.
Der richtige Ansatz ist es aber, gar nicht erst nach Koppenhagen zu 
fahren, sondern direkt in Wien auf die richtige Autobahn aufzufahren und 
in Paris wieder runter.

von C. S. (chappi)


Lesenswert?

Ja du hast absolut recht. Ich dachte halt nur, erst ne Funktion 
schreiben, dann noch 3 double Variablen in der Funktion erzeugen, das 
würde das gnaze komplizierter machen. Aber ich bin hier der Noob und ihr 
die Profs. also habt ihr recht :D

von Karl H. (kbuchegg)


Lesenswert?

Ich werd aus deinem Code nicht wirklich schlau, was das werden soll.

Aber am Anfang der Schleife ist zb der Teil hier
1
      PORTC |= S0|S1|S2;  // H | H | H = Y7
2
      _delay_ms(50);
3
      if (!(PINB & (1<<D_IN1)))
4
        ERR=+1;
5
        PORTC = 0x00;     //reset MUX 
6
      _delay_ms(50);
immer gleich. Nur die Zahlenwerte ändern sich. -> Kandidat füe eine 
Funktion
1
void Pulse( uint8_t Pins, uint8_t InputMask )
2
{
3
  PORTC |= Pins;
4
  _delay_ms(50);
5
6
  if (! (PINB & IputMask) )
7
    ERR = +1;
8
9
  PORTC = 0x00;     //reset MUX 
10
  _delay_ms(50);
11
}

weil ich es gerade sehe: Ist es Absicht, dass du da

    ERR = +1;

geschrieben hast, oder solltest du eigentlich

    ERR += 1;

schreiben? So wie du deine Schreibweise hast, ohne Leerzeichen 
dazwischen kann so ein Fehler schon einmal unbemerkt bleiben. Mit 
Leerzeichen ist aber klar, dass

    ERR =+ 1;

eher ein Tippfehler ist.

Wie auch immer. Mit der Funktion vereinfacht sich dann der Anfang deiner 
Hauptschleife enorm
1
  while(1)
2
  {
3
    if (debounce(PINB,PB0)) //PINB & (1<<D_IN0))//Falls Taster an PIN PB0 gedrueckt...
4
    {
5
/*************************** 12V Zuleitung (auf 16V eingestellt) ***************************/
6
7
      temp1=ADC_read(3);
8
      _delay_ms(50);
9
      if ((temp1 < 3.35 ) || (temp1 > 3.45))
10
        ERR+=1;
11
12
13
/*************************** Durchgangsprüfung ***************************/
14
15
      PORTD |= (1<<D_OUT1);  //relay: Z
16
17
/************* D_IN1: 12V_L to 0V_L *************/
18
      Pulse( S0|S1|S2, 1<<D_IN1 );  // H | H | H = Y7
19
20
/************* D_IN2: 12V_L to 0V_L *************/
21
      Pulse( S1|S2, 1<<D_IN2 );    // L | H | H = Y6
22
23
/************* D_IN3: 12V_L to 0V_L *************/
24
      Pulse( S0|S2, 1<<D_IN3 );    // H | L | H = Y5
25
26
/************* D_IN4: 12V_L to 0V_L *************/
27
      Pulse( S2, 1<<D_IN4 );       // L | L | H = Y4
28
29
/************* D_IN5: 12V_L to 0V_L *************/
30
      if (!(PINB & (1<<D_IN5)))
31
        ERR+=1;
32
33
....

und schon hast du wieder einen ordentlichen Komplexitätsbrocken aus der 
Hauptschleife raus. Ob der Name 'Pulse' für die Funktion vernünftig ist 
oder nicht, kann ich nicht sagen, weil ich nicht durschaut habe, was du 
da eigentlich machst. Im Zweifel musst du dir einen anderen Namen dafür 
einfallen lassen.
Auch würde ich das 0-Setzen von ERR auf jeden Fall machen, ehe da 
irgendwas an ERR rumfummelt und nicht hinten nach, wo man es gerne 
übersieht.
1
[C]
2
  while(1)
3
  {
4
    if (debounce(PINB,PB0)) //PINB & (1<<D_IN0))//Falls Taster an PIN PB0 gedrueckt...
5
    {
6
      ERR = 0;    // kein Fehler soweit
7
8
      if( ! isADCwithinBorder( 3, 3.35, 3.45) )    // 12V Zuleitung (auf 16V eingestellt)
9
        ERR += 1;
10
11
      PORTD |= (1<<D_OUT1);                        // Durchgangsprüfung : relay: Z
12
13
      Pulse( S0 | S1 | S2, 1<<D_IN1 );             // D_IN1: 12V_L to 0V_L;  H | H | H = Y7
14
      Pulse(      S1 | S2, 1<<D_IN2 );             // D_IN2: 12V_L to 0V_L;  L | H | H = Y6
15
      Pulse(      S0 | S2, 1<<D_IN3 );             // D_IN3: 12V_L to 0V_L;  H | L | H = Y5
16
      Pulse(           S2, 1<<D_IN4 );             // D_IN4: 12V_L to 0V_L;  L | L | H = Y4
17
18
/************* D_IN5: 12V_L to 0V_L *************/
19
      if (!(PINB & (1<<D_IN5)))
20
        ERR+=1;

und schon hat sich über eine Seite C-Code auf ein paar wenige Zeilen 
eingedampft. Und zwar ohne das es unübersichtlich wird. Ganz im 
Gegenteil (und 1 Fehler, die Behandlung von ERR in einem Fall, ist so 
nebenbei auch noch korrigiert worden).

von C. S. (chappi)


Lesenswert?

Vielen Dank. Ich habe mich an das Thema mit den Funktionen nicht 
rangetraut, weil ich nicht wusste, welche Werte ich übergeben muss. Ich 
wusste garnicht, dass man
1
S0|S1|S2

einfach in eine uint8_t Variable übergeben kann.

>Auch würde ich das 0-Setzen von ERR auf jeden Fall machen, ehe da
>irgendwas an ERR rumfummelt und nicht hinten nach, wo man es gerne
>übersieht.

Habe ich im Else Zweig wenn der Taster nicht gedrückt wird. Oder reicht 
das nicht?

von Klaus W. (mfgkw)


Lesenswert?

Karl heinz Buchegger schrieb:
> Ob der Name 'Pulse' für die Funktion vernünftig ist
> oder nicht, kann ich nicht sagen, weil ich nicht durschaut habe, was du
> da eigentlich machst.

Damit ist der Name offenbar nicht vernünftig.
QED

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:

>>Auch würde ich das 0-Setzen von ERR auf jeden Fall machen, ehe da
>>irgendwas an ERR rumfummelt und nicht hinten nach, wo man es gerne
>>übersieht.
>
> Habe ich im Else Zweig wenn der Taster nicht gedrückt wird. Oder reicht
> das nicht?


Doch.
Man muss aber danach suchen.

Zieh ich es vor, dann ist klar, dass die Logik lautet
1
    nimm an das es keinen Fehler gibt  (ERR = 0)
2
3
    dann überprüf alle möglichen Dinge die fehlerhaft sein könnten  (ERR += 1)
4
5
    im Abschluss werte aus, ob irgendetwas fehlerhaft war  (if( ERR > 0 ))

das ist eine logische Abfolge dessen, was mit der Variablen ERR 
passieren kann und wie sie benutzt wird. Warum willst du das 0-setzen 
von ERR irgendwo anders verstecken, wo man es erst einmal suchen muss 
und sich die if-Hierarchie genau ansehen muss, um draufzukommen, wann 
ERR eigentlich auf 0 gesetzt wird?

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> Vielen Dank. Ich habe mich an das Thema mit den Funktionen nicht
> rangetraut, weil ich nicht wusste, welche Werte ich übergeben muss. Ich
> wusste garnicht, dass man
>
>
1
> S0|S1|S2
2
>
>
> einfach in eine uint8_t Variable übergeben kann.


Warum denn nicht. Wenn du mehere LED einschaltest, machst du ja auch

  PORTC =  ( 1<<LED1 ) | (1<<LED2);

du veroderst einzelne Ausdrücke um einen kompletten uint8_t Wert zu 
erhalten, der genau die beiden Bits gesetzt hat. Der Ausdruck

( 1<<LED1 ) | (1<<LED2)

ergibt genau diesen uint8_t, mit dem man dann als Ganzes etwas macht: An 
einen Port zuweisen oder aber auch an eine Funktion übergeben.

du hast ja auch keine Skrupel

  j = irgendeine_Funktion( 2 + 3 );

aufzurufen und beim Aufruf einen Ausdruck anzugeben. Du erwartest hier 
ja auch, dass die Funktion mit dem Wert 5 aufgerufen wird, das heisst 
der Ausdruck vor dem Funktionsaufruf ausgewertet wird.
| ist auch nur eine 'arithmetische' Operation, so wie + - * / % es ebenfalls sind. 
Er macht halt nur etwas anderes. Nämlich die Bits miteinander verodern, anstelle 
von addieren. Aber ansonsten ist das doch konzeptionell völlig dasselbe.

von C. S. (chappi)


Lesenswert?

Ja das ergibt alles schon Sinn nur man traut sich halt nicht :D. Vielen 
Dank für das alles hier!

von Karl H. (kbuchegg)


Lesenswert?

C. S. schrieb:
> Ja das ergibt alles schon Sinn nur man traut sich halt nicht :D.

Genau deshalb lautet auch die am öftesten hier gepostete Antwort:
Kauf dir ein C-Buch und übe ein bischen auf dem PC zumindest die ersten 
paar Kapitel durch. :-)

Funktion, Funktionsargumente, Ausdrücke etc, also die einfachen Sachen, 
die im C-Buch in den ersten paar Kapitel behandelt werden ... all diese 
Dinge sollten schon so einigermassen sitzen, ehe man sein erstes 
richtiges Projekt angeht.

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.