Forum: Compiler & IDEs Unerklärliche Ausgabe beim Multiplexen


von Florian T. (grendal)


Angehängte Dateien:

Lesenswert?

Hallo,
mein Name ist Florian und ich beschäftige mich seit etwa 1.5 Monaten mit 
dem Programmieren von Mikrocontrollern. Auslöser hierfür war ein 
Programmierkurs an der Uni un der darauffolgende Wunsch das ganze 
praktisch anzuwenden.

Inzwischen arbeite ich an folgendem Projekt: Mit einem ATmega8, soll die 
Infrarotschnittstelle eines Stromzählers ausgelesen werden, der Zähler 
sendet etwa alle Sekunde ein Datenpaket das Informationen zu den 
Zählerständen enthält. Einige Beispielprotokolle befinden sich im 
Anhang.
Der Controller soll nun aus der Zeitdifferenz zwischen 2 Paketen und der 
Differenz der Zählerstände den aktuellen Verbrauch ausrechnen und auf 4 
7-Segmentanzeigen anzeigen.
Die Anzeige wird mit folgender ISR gelöst:
1
ISR(TIMER0_OVF_vect){
2
  PORTC=0;
3
  show(display[pos]);
4
  PORTC|=(1<<pos);
5
  if(pos==0&&einspeisen==1)
6
    PORTD&=~(1<<7);
7
  if(pos==1&&powered==1)
8
    PORTD&=~(1<<7);
9
  pos++;
10
  if(pos>3)
11
    pos=0;
12
}
Die Variable pos ist am Anfang 0, die ISR schaltet nun zuerst PORTC auf 
0, über ihn ist der + der Anzeigen mithilfe von Transistoren geschaltet.
Anschließend schaltet die Funktion show() die gewünschten Segmente für 
die anzuzeigende Zahl, welche Zahl angezeigt werden soll entnimmt sie 
einem Array.
Die show()-Funktion sieht wie folgt aus:
1
void show(int num){
2
  if(num==0)
3
    PORTD=0b11000000;
4
    else
5
      if(num==1)
6
        PORTD=0b11111001;
7
      else
8
        if(num==2)
9
          PORTD=0b10100100;
10
        else
11
          if(num==3)
12
            PORTD=0b10110000;
13
          else
14
            if(num==4)
15
              PORTD=0b10011001;
16
            else
17
              if(num==5)
18
                PORTD=0b10010010;
19
              else
20
                if(num==6)
21
                  PORTD=0b10000010;
22
                else
23
                  if(num==7)
24
                    PORTD=0b11111000;
25
                  else
26
                    if(num==8)
27
                      PORTD=0b10000000;
28
                    else
29
                      if(num==9)
30
                        PORTD=0b10010000;
31
                      else
32
                        if(num==10)
33
                          PORTD=0b00000000;
34
                        else
35
                          PORTD=0b10000110;
36
}
Überprüft also nacheinander was angezeigt werden soll. Hierbei steht 10 
für alles an, dies dient einem Displaycheck beim Starten des uC. Die 
else Anweisung nach if(num==10) schaltet das Segment auf E, dies wird 
ausgelöst wenn eine Zahl größer 10 angezeigt werden soll.
Das Array display[] wird über folgenden Code befüllt:
1
      for(i=0;i<4;i++){
2
        display[i]=(int)verkaufendiff%10;
3
        verkaufendiff/=10;
4
      }
Hierbei ist Verkaufendiff ein vorher berechneter Wert des Datentyps 
float.

Meiner Meinung nach sollte nun in jedem der 4 Einträge von display[] 
eine Zahl zwischen 0 und 9 stehen, da %10 alles bis auf die letzte 
Stelle fressen sollte.
Trotzdem zeigen die 7-Segment-Anzeigen sehr oft E an, sie sollen also 
etwas größer als 10 darstellen. Sollte eigentlich nicht möglich sein, da 
im Array ja nur Werte kleiner 10 stehen sollen.
Die ISR kann meiner Meinung nach auch nicht auf anderen Teile des Arrays 
zugreifen außer den Index Nummern 0, 1, 2 und 3, da bei einem Wert von 4 
auf 0 resettet wird.
Ich bin relativ ratlos was hier das Problem ist. Im Anhang befindet sich 
neben den Beispielprotokollen noch der Komplette Quellcode falls das 
jemandem weiterhilft.

Gruß Florian

von Thorsten S. (whitejack) (Gast)


Lesenswert?

eine Sache nebenbei:

kannst du nicht einfach die Differenz des Zählers nehmen, ist doch im 
Datenpaket enthalten:

/HAG5eHZ010C_EHZ1ZB22

1-0:0.0.0*255(10951000000xxxxx)
1-0:1.8.0*255(000001.1619)
1-0:2.8.0*255(000598.4960)
1-0:96.5.5*255(82)
0-0:96.1.255*255(00000xxxxx)
1-0:32.7.0*255(230.74*V)
1-0:52.7.0*255(231.74*V)
1-0:72.7.0*255(230.34*V)
1-0:31.7.0*255(007.76*A)
1-0:51.7.0*255(000.63*A)
1-0:71.7.0*255(004.32*A)
1-0:21.7.0*255(-01790*W)
1-0:41.7.0*255(-00134*W)
1-0:61.7.0*255(-00988*W)
1-0:96.50.0*0(FF)
1-0:96.50.0*1(07D1)
1-0:96.50.0*2(0E)
1-0:96.50.0*3(0A)
1-0:96.50.0*4(21)
1-0:96.50.0*5(10)
1-0:96.50.0*6(003D381B180A1AF13AFC2A000000xx80)
1-0:96.50.0*7(00)


oder?

TS

von Florian T. (grendal)


Lesenswert?

Leider nicht, der Zähler gibt nur die kurze Version des Protokolls aus 
und die sieht wie die angehängte EHz.txt aus.
Wenn es eine möglichkeit gibt das Protokoll auf Erweitert zu stellen 
nehm ich das natürlich gerne, hab in die Richtung aber noch nichts 
gehört.

von Karl H. (kbuchegg)


Lesenswert?

>       for(i=0;i<4;i++){
>          display[i]=(int)verkaufendiff%10;
>        verkaufendiff/=10;
>      }

wenn verkaufendiff wirklich ein float ist, dann lass dir den zuvor 
einmalig erst mal auf einen int umwandeln, ehe du dann daran gehst, den 
int zu zerlegen.

Und mach dir doch um Gottes Willen dafür eine Funktion. In deinem Code 
findet man doch nichts mehr!
1
void toDisplay( float wert )
2
{
3
  uint16_t iWert = (uint16_t)( wert + 0.5 );   // Annahme: wert kann nur positiv sein
4
  uint8_t i;
5
6
  for( i = 0; i < 4; i++ )
7
  {
8
    display[i] = iWert % 10;
9
    iWert /= 10;
10
  }
11
}

damit hast du den Teil geklärt.
Noch einfacher wird die Sache, wenn du deine ganze Show Funktion gleich 
mal über die Klinge hüpfen lässt und in den display Variablen gleich die 
Codes für die 7-Segmentanzeigen hinterlegst. Dann brauchst du nämlich in 
der ISR nicht 100 mal in der Sekunde das immer gleiche tun und immer 
wieder zur selben Zahl dieselben Codes raussuchen.

Aber bitte mit etwas Intelligenz! Die Codes kann man in einem Array 
ablegen! Da muss man nicht mit einer elends langen if-else if Kette 
arbeiten!
1
volatile uint8_t display[4] = { 0, 0, 0, 0 };
2
uint8_t pos = 0;
3
uint8_t SegCodes[10] = { 0b11000000, 0b11111001, 0b10100100, 0b10110000, 0b10011001,
4
                         0b10010010, 0b10000010, 0b11111000, 0b10000000, 0b10010000 };
5
6
7
void toDisplay( float wert )
8
{
9
  uint16_t iWert = (uint16_t)( wert + 0.5 );   // Annahme: wert kann nur positiv sein
10
  uint8_t i;
11
12
  for( i = 0; i < 4; i++ )
13
  {
14
    display[i] = SegCodes[ iWert % 10 ];
15
    iWert /= 10;
16
  }
17
}
18
19
ISR(TIMER0_OVF_vect)
20
{
21
  PORTC = 0;
22
  PORTD = display[pos];
23
  PORTC |= (1<<pos);       // das sollte man auch ersetzen. 1<<pos ist eine üble Operation
24
25
  // Bit 7 dimmen, je nachdem ob einspeisen bzw powered 1 sind ergibt das
26
  // unterschiedliche Dimm-Stufen
27
  if( pos == 0 && einspeisen == 1 )
28
    PORTD &= ~(1<<7);
29
  if( pos == 1 && powered == 1 )
30
    PORTD &= ~(1<<7);
31
32
  pos++;
33
  if( pos > 3 )
34
    pos = 0;
35
}

Und dann rufst du einfach nur die Funktion toDisplay auf, wenn du einen 
Zahlenwert hast, der auszugeben ist.
1
  ...
2
3
    if( einspeisen == 1 )
4
      toDisplay( verkaufendiff );
5
    else
6
      toDisplay( kaufendiff );
7
  ...

Weiters: Datentypen!
Es bringt nichts, wenn du da mit Datentypen großzügig rummachst. Wenn 
eine 8 Bit Variable ausreicht, dann mach die auch 8 Bit! Machst du die 
größer, dann kannst du dir damit Probleme einhandeln (und ich denke, 
genau das war eines deiner Probleme), weil ein int auf einem AVR nun mal 
nicht von alleine atomar behandelbar ist.

von Karl H. (kbuchegg)


Lesenswert?

1
  TCCR0|=(1<<0)|(1<<1);
2
  TIMSK|=(1<<0);

Benutze die Bitnamen! Die stimmen dann mit den Namen im Datenblatt 
überein! Bei 1<<0 muss man erst mal im Datenblatt bei der 
Registerbeschreibung nachsehen, wie das Bit mit der Nummer 0 heißt um 
dann im weiteren mit dem Namen im Datenblatt weiterzusuchen.
1
  TCCR0 |= (1<<CS01) | (1<<CS11);
2
  TIMSK |= (1<<TOIE0);

da steht jetzt explizit: TOIE0
-T-imer -O-verflow -I-nterrupt -E-nable -0-

Dieses Bit ist also dafür zuständig, dass der Interrupt beim Timer 
Overflow vom Timer 0 enabled (also "erlaubt") wird. Dieses ist die 
Information, die einen Leser (auch dich selbst) interessiert! Was mich 
hingegen überhaupt nicht interessiert, nicht interessieren soll, ist, 
dass dieses das Bit 0 ist! Das ist eine Information, die ich nicht 
brauche.
Die Bitnamen sind Kürzeln! Und wenn ich die kenne, bzw. den 
systematischen Aufbau dieser Kürzel kenne, dann kann man aus dem 
Bitnamen ganz leicht erkennen, was da eigentlich freigegeben wird.

Hier kann ich leicht erkennen, dass

  TIMSK |= (1<<TOIE0);

und

  ISR(TIMER0_OVF_vect)

zusammenpassen und das da kein Fehler passiert ist. In beiden Fällen ist 
übereinstimmend vom Interrupt bei einem Overflow des Timers 0 die Rede.
Bei
  TIMSK|=(1<<0);
muss ich hingegen erst mal im Datenblatt nachschlagen, ob da mit dem Bit 
0 das richtige gemeint ist, wenn ich das überprüfen will.

von Karl H. (kbuchegg)


Lesenswert?

1
    for(i=0;1;i++){
2
      log[i]=readchar();
3
      if(log[i]=='!')
4
        break;
5
    }

kann man natürlich so machen. Aber bei
1
   i = 0;
2
   do {
3
     log[i] = readchar();
4
   ] while (log[i++] != '!');

ist IMHO unmittelbarer ersichtlich, was mit dieser Schleife bezweckt 
wird und unter welchen Umständen sie beendet wird.
Wenn du das '!' in der Eingabe gar nicht brauchst, dann kannst du das 
auch so machen
1
   i = 0;
2
   log[i] = readchar();
3
   while( log[i] != '!' ) {
4
     i++;
5
     log[i] = readchar();
6
   }

oder in der C-typischen Zusammenfassung unter Ausnutzung der Tatsache, 
dass  auch eine Zuweisung ein Ergebnis hat (nämlich der Wert der 
zugewiesen wurde)
1
   i = 0;
2
   while( (log[i] = readchar()) != '!' )
3
     i++;

Man muss nicht alles auf Biegen und Brechen in eine for-Schleife 
quetschen. Vor allen Dingen dann nicht, wenn die Schleife vom Kern her 
eigentlich eine 'mache so lange wie', also eine while Schleife ist.

(Und im übrigen gehört es zum guten Ton, bei solchen Sachen 
sicherzustellen, dass man auf keinen Fall das Array überlaufen kann. 
Selbst dann nicht, wenn (aus welchem Grund auch immer) in der Eingabe 
kein '!' auftaucht.

von Florian T. (grendal)


Lesenswert?

Danke erstmal für die Kritik/Verbesserungsvorschläge, habe direkt einige 
Punkte umgesetzt, so ist das Array schreiben in eine eigene Funktion 
gewandert, einige Ints sind Chars gewichen und beim Timer Interrupt 
stehen jetzt die Bit-Namen. Außerdem habe ich entsprechende Sicherheiten 
eingebaut damit die Arraygrenzen nicht überschritten werden.

Das Schwergewicht show() habe ich bewusst gelassen, um die selben 
Ausgabeoptionen zu haben wie vorher, sprich wenn er etwas größer als 10 
ausgeben soll, stellt er ein E dar. Wenn es endlich funktioniert wird es 
natürlich ersetzt, bringt einiges an freiem Speicherplatz.

Das ganze kompiliert, auf den uC hochgeladen und ausprobiert ergibt 
allerdings weiterhin das Problem, dass ich E's angezeigt kriege, also im 
array zu große Werte stehen.

von Karl H. (kbuchegg)


Lesenswert?

Florian T. schrieb:

> Ausgabeoptionen zu haben wie vorher, sprich wenn er etwas größer als 10
> ausgeben soll, stellt er ein E dar.

In

   wert % 10

KANN nichts größeres als 9 rauskommen, wenn 'wert' ein positiver Integer 
ist.

von Marius W. (mw1987)


Lesenswert?

Florian T. schrieb:
> Die show()-Funktion sieht wie folgt aus:

Kleiner Tipp nebenbei. Es gibt sowas wie eine switch-case-Anweisung in 
C. Die solltest du dir mal anschauen...

Gruß
Marius

von Karl H. (kbuchegg)


Lesenswert?

Persönlich denke ich ja, dass die komplette Messwertaufnahme in main() 
so wie geschrieben, nicht besonders klug gemacht ist. Die ganze 
Zeiterfassung ist unsinnig (da nicht genau. _delay_us abzählen bis in 
Pin einen Pegel wechselt ist gerade bei nebenher laufenden Interrupts 
alles andere als genau) und in die Aufnahme und Verarbeitung der Daten 
vom UART hab ich auch kein Vertrauen. Du vielen float tun dann auch noch 
ihr übriges.

Daher: Erst mal nur das Multiplexen testen!

tritt deinen 'langen' Multiplexteil in die Tonne, und verwende den 
kurzen, wie ich ihn dir gezeigt habe.
Und dann ein main(), welches erst mal nur die 7-Seg Ansteuerung testet.
1
...
2
3
int main()
4
{
5
  float i;
6
7
  DDRC = 0b00001111; /*0, 1, 2, 3 als Ausgang, 5 als Eingang*/
8
  DDRD = 0b11111111;/*Komplett D auf Ausgang*/
9
  PORTD = 0b11111111;
10
11
  TCCR0 |= (1<<CS01) | (1<<CS11);
12
  TIMSK |= (1<<TOIE0);
13
14
  sei();
15
16
  while(1)
17
  {
18
    for( i = 0; i < 10000; ++i )
19
    {
20
      toDisplay( i );
21
      _delay_ms( 10 );
22
    }
23
  }
24
}

das muss erst mal eine saubere Ansteuerung der 7-Seg ergeben. In 10 
Sekunden rauschen da alle Zahlen von 0000 bis 9999 durch. Und dabei darf 
es keine Fehler geben.

Und erst dann, wenn das getestet ist, geht es mit dem Rest weiter!
Wenn sich dabei dann Fehler in der Darstellung zeigen, dann liegt der 
Fehler bei den frisch hinzugekommenen Dingen. Denn der Multiplexcode ist 
bereits getestet und für gut befunden worden. Du darfst nicht Symptome 
und Ursachen verwechseln.

von Peter D. (peda)


Lesenswert?

Da Du pro Digit ein Byte speicherst, kannst Du dort auch gleich den 
7-Segmentcode ablegen, statt der Ziffer.
Dann muß der Timerinterrupt das nicht ständig neu aufdröseln.

Und wenn keine negativen Werte benötigt werden, nimm unsigned.
Bei signed gibt es Seiteneffekte, große positive Zahlen können kleine 
negative werden und umgekehrt.
Auch werden einige Rechnungen aufwendiger, durch die zusätzliche 
Vorzeichenbehandlung.

Warum nimmst Du nicht die UART?

Schau Dir mal atof() an.
Oder sscanf().


Peter

von Florian T. (grendal)


Lesenswert?

Guten Morgen,
zuersteinmal weiterhin danke für das Feedback, so jetzt arbeiten wir mal 
die Posts ab:

Zum Thema: Funktioniert das Multiplexen/%10;
Ja tut es, habe es vorher schon einmal so ausprobiert und gerade eben 
noch einmal mit beiden Codevarianten, keinerlei Fehler.
Das in %10 keine Zahl größer 9 rauskommen kann ist mir bewusst. Deshalb 
bin ich ja hier :)

Zu UART:
Habe ich mich bis heute noch nicht wirklich mit auseinander gesetzt. Mag 
viel eleganter sein, aber das Readchar() funktioniert definitiv und 
liest auch das Protokoll richtig (getestet mit LED's die an-/ausgehen 
wenn er gleich bleibende Protokollabschnitte erkennt). Werde mich sicher 
noch damit beschäftigen, aber da dieser Teil des Codes nicht das Problem 
ist eher später.

Zu ungenaue Zeitmessung:
Ist mir voll bewusst, das soll und wird noch auf den Timer1 umgestellt, 
kann den ja soweit ich das verstanden habe über das TCNT1 Register 
auslesen.

Zu switch case:
Kenn ich, hatte ich, hab ich ersetzt um zu gucken ob da der Fehler 
liegt. Und jetzt hat mich Karl Heinz auf eine viel elegantere Lösung 
gestoßen die mir so erstmal nicht in den Kopf gekommen ist. Werde also 
am Ende diese verwenden.


Fragen:
Das (1<<pos) böse ist hab ich schon im Programmierkurs gelernt, wie 
würdet ihr das ersetzen?
Und welche Codeausschnitte kommen eurer Meinung nach noch für die 
Angezeigten E's in Frage?

Gruß Florian

von Karl H. (kbuchegg)


Lesenswert?

Florian T. schrieb:
> Guten Morgen,
> zuersteinmal weiterhin danke für das Feedback, so jetzt arbeiten wir mal
> die Posts ab:
>
> Zum Thema: Funktioniert das Multiplexen/%10;
> Ja tut es, habe es vorher schon einmal so ausprobiert und gerade eben
> noch einmal mit beiden Codevarianten, keinerlei Fehler.
> Das in %10 keine Zahl größer 9 rauskommen kann ist mir bewusst. Deshalb
> bin ich ja hier :)

Also. Was sagt dir das?
Mir sagt es, dass der Fehler nicht im Multiplexteil liegt sondern in dem 
... wie soll ich es nur nennen .... Programmteil in der Hauptschleife.

> Habe ich mich bis heute noch nicht wirklich mit auseinander gesetzt.

Dann wirds Zeit

> Mag
> viel eleganter sein, aber das Readchar() funktioniert definitiv und
> liest auch das Protokoll richtig (getestet mit LED's die an-/ausgehen
> wenn er gleich bleibende Protokollabschnitte erkennt).

Das readchar stelle ich nicht in Frage.
Aber kein Mensch sagt, dass man erst mal die kompletten Zeichen in ein 
riesen Array stopfen muss, nur damit man die Teile rauskriegt die man 
haben will. Das kann man alles auch 'on-the-fly' machen.

Ein Datensatz beginnt damit, dass ein '/' reinkommt. Das ist dein 
Startsignsal. Alles davor ist uninteressant.
Dann wird die Anzahl der '(' (und nur die!) mitgezählt.
Bei der 2.ten schaltet sich ein Modul dazu, welches das eingehende 
Zeichen auf die erste Variable aufrechnet:

Für Vorkomma
    Zahl = Zahl * 10 + ( Zeichen - '0' );
Für Nachkomma
    Zahl = Zahl + ( Zeichen - '0' ) * Zahlenbasis;
    Zahlenbasis /= 10;
und wenn die ')' daher kommt, dann schaltet sich dieser Programmteil 
wieder ab.

Dasselbe wenn die 3-te '(' eintrudelt.

Wird irgendwann von der UART ein '!' gelesen, dann werden die gelesenen 
Zahlen umgerechnet und zur Anzeige gebracht.


Kein Mensch muss dazu den kompletten String in einem 500-er Array 
zwischenspeichern.

Es reicht völlig, wenn man das jeweils nächste Zeichen holt und anhand 
der 'Umgebung' entscheidet, was damit zu tun ist.

> Das (1<<pos) böse ist hab ich schon im Programmierkurs gelernt, wie
> würdet ihr das ersetzen?

entweder:
* weiteres Array, welches das auszugebende Byte liefert, wenn man 
mittels
  pos indiziert. Das wäre die allgemeine Lösung
* Da aber bei dir die Pins sowieso durchlaufend aufsteigend angeordnet
  sind:
  Mit einer Maske, die bei jedem Aufruf um 1 Stelle geschoben wird und
  wenn pos auf 0 geht, wird auch die Maske wieder auf Ausgangsstellung
  gesetzt.
1
uint8_t multiPinMask = 0x01;
2
3
ISR(TIMER0_OVF_vect)
4
{
5
  PORTC = 0;
6
  PORTD = display[pos];
7
  PORTC |= multiPinMask;
8
9
  // Bit 7 dimmen, je nachdem ob einspeisen bzw powered 1 sind ergibt das
10
  // unterschiedliche Dimm-Stufen
11
  if( pos == 0 && einspeisen == 1 )
12
    PORTD &= ~(1<<7);
13
  if( pos == 1 && powered == 1 )
14
    PORTD &= ~(1<<7);
15
16
  pos++;
17
  multiPinMask <<= 1;
18
  if( pos > 3 )
19
  {
20
    pos = 0;
21
    multiPinMask = 0x01;
22
  }
23
}

> Ist mir voll bewusst, das soll und wird noch auf den Timer1 umgestellt,
Du hast doch schon einen Timer laufen!
Den kannst du doch benutzen. Sagt doch keiner, dass ein Timer nur eine 
Aufgabe machen darf.
Du weißt, in welchen Zeitabständen dir ISR aufgerufen wird. Wenn du dort 
in der ISR also einfach die Anzahl der Aufrufe mitzählst, dann ist das 
eine einfache Rechung, wie man eine vergangene Zeitdauer feststellt. 
Liest du von der UART ein '\', dann liest du diesen Zeit-Zähler aus und 
setzt ihn wieder auf 0.

> Und welche Codeausschnitte kommen eurer Meinung nach noch für die
> Angezeigten E's in Frage?

der ganze Rest in main().
Irgendwo bügelst du dir den Speicher nieder.

Wirf es raus und bau das nochmal neu auf.
Aber achte diesmal ein bischen mehr auf Lesbarkeit!

Und schreib nicht alles in einem Zug durch, sondern teste immer wieder 
Zwischenresultate! Mit zuviel Code auf einmal stehst du am Ende mit 
einem Programm da, welches nicht funktioniert und du hast keine Ahnung, 
wo das Problem liegt. Äh, den Zustand hast du ja jetzt auch schon.




Und die Sache mit den float solltest du dir in einer ruhigen Stunde 
nochmal durch den Kopf gehen lassen. Das ist eine nicht unwesentliche 
Belastung für den µC!
Anstelle von Gleitkomma und Euros kann man nämlich oft auch in Cent 
rechnen und dafür mit ganzen Zahlen.
Ob man €2.80 plus €3.40 mittels GLeitkomma rechnet und €6.20 
rausbekommt, oder ob man 280Cent + 340Cent rechnet und 620Cent 
rausbekommt, ist vom Ergebnis her egal. Aber der Aufwand ist ein völlig 
anderer.

von Peter D. (peda)


Lesenswert?

Die UART hat den Vorteil, daß sie bis zu 3 Byte puffert. Du kannst also 
im Hintergrund ständig einlesen.

Und den Sender kannst Du für Debugausgaben benutzen. Das ist 
komfortabler als nur die 7-Segment.
Du kannst Dir dann Texte und Zahlenwerte ausgeben lassen.


Peter

von Florian T. (grendal)


Lesenswert?

Vermelde Erfolg, lag weder an den floats noch an irgend einem anderen 
Codeausschnitt sondern am verwendeten Datentyp für das Display-Array.
Bei der von dir toDisplay genannten Funktion kommt es beim aufteilen der 
Zahl bei verwendung von unsigned 16 Bit Integern zum Overflow, die 
Verwendung von unsigned long Variablen also 32 Bit Integern hat das 
Problem gelöst, keine E's mehr. Lohnt also ab und zu doch mit Datentypen 
großzügig rumzumachen ;)

Es wurden also tatsächlich zu große Werte durch %10 in das Array 
geschrieben, von char auf unsigned int zu gehen hat einfach nicht 
gereicht.
Rausbekommen hab ich es letztendlich indem ich den ganzen Code auf 
Standard-C umgeschrieben habe, sodass ich mit Eclipse anständig debuggen 
konnte und siehe da, je nach verwendetem Datentyp steht in dem Array 
totaler Schwachfug.

Ist zwar ziemlich blöd, dass ich jetzt um Zahlen von 0-9 zu speichern n 
32 Bit Integer verwende, aber es funktioniert jetzt :)
Denke man kann das array auch wieder auf char umstellen, dafür musste 
man in der toDisplay Funktion dann erst auf 32 Bit gehen und dann wenn 
man die einzelne Zahl hat auf den Char/unsigned Char.

Fürs nächste mal hier posten merk ich mir dann noch:
genauer sagen was ich schon probiert hab, viele der angesprochenen 
Sachen habe ich nämlich schon probiert/gehabt, dann aber zur Fehlersuche 
ersetzt.
Der Code wurde auch nicht am Stück geschrieben sondern in kleinen 
Häppchen, zuerst das Readchar(), was erstmal gar nicht funktionierte, 
also musste n Quarz her, danach kam dann die Auswertung dazu und zum 
Schluss das Problemkind/die Anzeige.

Nochmal danke an alle Beteiligten, auch wenn ich hier eher 
Codeverbesserungen als Problemlösungen mitgenommen hab, sowas kann man 
ja aber auch immer brauchen und verhindert hoffentlich Fehler in der 
Richtung für spätere Projekte.

Gruß Florian


Edit: Hallo Peter,
ich habe deinen Post grade erst gesehen, hab mich eben etwas in den UART 
eingelesen, ist schon ne feine Sache. Wenn ich das allerdings richtig 
sehe frisst der PINs D0 und D1, weshalb ich dann meinen gesamten 
Ausgabeteil übern Haufen werfen müsste, da ich die Segment Anzeigen auf 
mehrere Ports verteilen müsste. Die Ausgabe wäre dann wohl nur noch 
häßlich zu implementieren. Für weitere Projekte werd ich das wohl 
nutzen, für das aktuelle wird mein readchar() herhalten.

von Karl H. (kbuchegg)


Lesenswert?

> Zahl bei verwendung von unsigned 16 Bit Integern zum Overflow,
> die Verwendung von unsigned long Variablen also 32 Bit Integern hat
> das Problem gelöst, keine E's mehr. Lohnt also ab und zu doch mit
> Datentypen großzügig rumzumachen ;)

Das klingt nach einer völlig falschen Diagnose.

Selbst wenn dein float zu gross ist, kann es nicht passieren, dass bei 
irgendeinem uint16_t bei der Rechnung  wert%10 etwas ausserhalb des 
Bereichs 0 bis 9 rauskommt.

Egal was bei der Konvertierung
 uint16_t iWert = (uint16_t)( wert + 0.5 );   // Annahme: wert kann nur 
positiv sein

tatsächlich für ein Wert rauskommt, wird es immer so sein, dass du die 4 
niederwertigsten Stellen dezimal mittels

  for( i = 0; i < 4; i++ )
  {
    display[i] = SegCodes[ iWert % 10 ];
    iWert /= 10;
  }

absplitten kannst und kein einziges der Digits wird ausserhalb des 
Bereichs 0 bis 9 sein.
Ob 4 Digits für die Darstellung der Zahl ausreichen ist eine andere 
Geschichte. Aber die Digits sind auf jeden Fall im Wertebereich korrekt.

Es sei denn du lebst in einem anderen Universum, in dem eine andere 
Mathematik gilt.

Zeig doch mal deinen Code, wie er jetzt aussieht. Die Vermutung steht, 
dass du den Fehler nur kaschiert hast, aber nicht behoben.

von Florian T. (grendal)


Lesenswert?

Habe folgendes in C geschrieben aufm Computer kompiliert und ausgeführt:
1
#include <stdio.h>
2
3
unsigned char display[4];
4
5
void toarray(float input){
6
  unsigned char i;
7
  unsigned wert=(unsigned)(input+0.5);
8
  for(i=0;i<4;i++){
9
    display[i]=(char)wert%10;
10
    wert/=10;
11
  }
12
}
13
14
15
int main(){
16
  float wert=1234.56789;
17
  char i;
18
  toarray(wert);
19
  for(i=4;i>0;i--)
20
    printf("%i",display[i-1]);
21
  return 0;
22
}
Output sollte sein: 1235(gerundet), Ich bekomme aber: 123251

Nutze ich 32 bit Integer (in meinem Fall einfaches int, benutze Cygwin)
1
#include <stdio.h>
2
3
unsigned int display[4];
4
5
void toarray(float input){
6
  unsigned char i;
7
  unsigned wert=(unsigned)(input+0.5);
8
  for(i=0;i<4;i++){
9
    display[i]=(int)wert%10;
10
    wert/=10;
11
  }
12
}
13
14
15
int main(){
16
  float wert=1234.56789;
17
  unsigned char i;
18
  toarray(wert);
19
  for(i=4;i>0;i--)
20
    printf("%i",display[i-1]);
21
  return 0;
22
}
erhalte ich die gewünschte Ausgabe von 1235.
Hieraus schließe ich, dass der Modulo Operator bei einem Overflow nicht 
korrekt funktioniert

von Karl H. (kbuchegg)


Lesenswert?

>     display[i]=(char)wert%10;

Das castet zuerst den Wert nach char und bestimmt erst dann den Rest.

> Ich bekomme aber: 123251

Wie kannst du eine 6-stellige Ausgabe bekommen, wenn du nur 4 Digits 
hast?

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:
>>     display[i]=(char)wert%10;
>
> Das castet zuerst den Wert nach char und bestimmt erst dann den Rest.

Lass den Cast weg!
Du brauchst ihn nicht und er ist dein Problem!

In Zukunft:

 plain vanilla char verwendest du ausschliesslich nur dann, wenn du
 Textverarbeitung machst!

 Ansonsten benutzt du IMMER entweder
   signed char         wenn du einen kleinen Integer mit Vorzeichen
                       brauchst
   unsigned char       wenn du einen kleinen Integer ohne Vorzeichen
                       brauchst.

Und zwar IMMER!

Und am besten gewöhnst du dich daran, die Datentypen aus stdint.h zu 
benutzen:

  char     für alles was Textverarbeitung ist
  int8_t   für einen kleinen Integer mit Vorzeichen
  uint8_t  für einen kleinen Integer ohne Vorzeichen

von Uwe (de0508)


Lesenswert?

Hallo,

Das Array |display| ist von Datentype 16-Bit "lang" und er gibt mit
1
printf("%i",display[i-1]);
 auch jeweils 16-Bit aus.

_

von Karl H. (kbuchegg)


Lesenswert?

> Hieraus schließe ich, dass der Modulo Operator bei einem Overflow
> nicht korrekt funktioniert

Deine Schlussfolgerung ist falsch.

Die richtige Schlussfolgerung ist:
  auf deinem System ist ein char ein signed char
  die Konvertierung von Wert in einen signed char ergab ein
  negatives Ergebnis. Die Operation i%10 ergibt bei einem negativen
  i ein negatives Ergebnis.

Dein vorsorglicher Cast war das Problem.
Du willst niemals Casten, wenn du nicht musst. Casts sind Waffen!



Warum hast du die Funktion nicht einfach so übernommen, wie ich sie dir 
geschrieben habe? Buchstabe für Buchstabe?
Ich hab mir schon was dabei gedacht, als ich sie geschrieben habe.

von Peter D. (peda)


Lesenswert?

Florian T. schrieb:
> Nutze ich 32 bit Integer (in meinem Fall einfaches int, benutze Cygwin)

Beim AVR ist int nicht 32bit, sondern signed 16bit!
Und wie gesagt, signed hat Seiteneffekte, also nur dort einsetzten, wo 
nötig!

Besonders lustig: (signed_var % 10) kann auch negativ werden.


Peter

von Florian T. (grendal)


Angehängte Dateien:

Lesenswert?

Indem einige Einträge des Arrays nach der %10-Operation größer 10 sind.
Die einzelnen Array Einträge sind: 1, 2, 3, 251.
Bei der Behandlung der Einer-Stelle versagt also der Modulo Operator. 
Wahlweise leben natürlich auch die CPU's meines PC's und Laptop's in 
Welten mit anderer Mathematik.

Nehme ich als Startwert 6543.16789, erhalte ich als Ausgabe für die 
einzelnen Stellen folgende Ausgabewerte: 6, 5, 252, 253.

Für 9500.16789 erhalte ich: 9, 5, 250, 6

Jeweils auf 2 verschiedenen Rechnern.

Im Anhang der aktuelle uC Code (einzige wirkliche Änderung die 
Problemmäßig etwas geändert hat ist der Wechsel auf unsigned long für 
display[])

von Karl H. (kbuchegg)


Lesenswert?

Florian T. schrieb:

> Bei der Behandlung der Einer-Stelle versagt also der Modulo Operator.

Vielleicht hast du es nur noch nicht gelesen.
Er versagt eben nicht. Er macht genau das, was er machen soll.
Nur deine Verwendung der Datentypen bzw. unnötiger Casts hat das Problem 
hervorgerufen.

> Im Anhang der aktuelle uC Code (einzige wirkliche Änderung die
> Problemmäßig etwas geändert hat ist der Wechsel auf unsigned long für
> display[])

Hat sich ja mitlerweile geklärt.

Du hast bei der Übernahme der Funktion rumgepfuscht und dir eine Fehler 
durch den Austausch der Datentypen eingehandelt.

-> Datentypen eben doch nicht auf die leichte Schulter nehmen, wie du 
das momentan machst.

von Peter D. (peda)


Lesenswert?

Florian T. schrieb:
> Die einzelnen Array Einträge sind: 1, 2, 3, 251.
> Bei der Behandlung der Einer-Stelle versagt also der Modulo Operator.

Nö.
Als char ist es nicht 251, sondern -5, der Modulo Operator arbeitet 
völlig korrekt.
Dein Problem ist, char zu nehmen, was per default signed ist.


Peter

von Florian T. (grendal)


Lesenswert?

Okay Fehler IST der Typecast, war mir nicht bewusst, dass char und 
unsigned char was anderes ist, hatte im Hinterkopf, dass beides von 0 
bis 255 geht, ist ein Fehler der mir unter Garantie nicht nochmal 
passieren wird.

Code gerade angepasst runter in Keller gerannt und ausprobiert: Keine 
E's mehr. Aber zu große Zahlen, da bin ich mir aber sicher woher es 
kommt: ungenaue Zeitmessung, teilen durch Wert kleiner 1, dadurch werden 
die Zahlen größer.

von Karl H. (kbuchegg)


Lesenswert?

Florian T. schrieb:
> Okay Fehler IST der Typecast, war mir nicht bewusst, dass char und
> unsigned char was anderes ist

Auch das stimmt so nicht.

  Ob ein "char" als "signed char" oder als "unsigned char" angesehen
  wird, entscheidet der Compiler bzw. der Compilerbauer!
  Und oft genug kann man beim Aufruf des Compilers das auch noch
  überstimmen.

Daher: Du kannst dich bei char weder darauf verlassen, dass er ein 
Vorzeichen hat, noch kannst du dich darauf verlassen, dass er keines 
hat.

Daher: "char" ausschliesslich und immer nur für Textverarbeitung 
einsetzen! Also in Fällen, wo es keine Rolle spielt.

In allen anderen Fällen IMMER entweder "signed char" oder "unsigned 
char". Oder eben int8_t bzw. uint8_t (welches konsequent benutzt die 
bessere Alternative ist)

> bis 255 geht, ist ein Fehler der mir unter Garantie nicht nochmal
> passieren wird.

So soll es sein.
Mach dir nichts draus. Das wird nicht der letzte Fehler bleiben und ja, 
das gehört genauso zum Lernprozess dazu. Wichtig ist, dass der Fehler 
korrekt diagnostiziert wird und du auch das 'Problem' verstehst. Denn 
mit einer falschen Diagnose und einem dubiosen 'der rechnet falsch' ist 
niemandem geholfen.

von Florian T. (grendal)


Lesenswert?

Lernprozess oh ja... saß 3 Stunden dran um rauszukriegen warum er die 
Daten nicht anständig erkennt,
Grund war, dass ich diese if-Zeile haben wollte
1
if(log[i]=='('||log[i]==')')

aber diese hatte
1
if(log[i]=='('||')')
wodurch er den Klammern Counter jedes mal erhöht hat egal was in Log[i] 
drinstand.

Frustresistenz ist genug vorhanden^^ Meistens hat man dann das Verlangen 
sich ein großes Buch vor den Kopf zu schlagen weil es ein so primitiver 
Fehler war.

Ich mach mich jetzt ans Verbessern der Zeitmessung und danach wird noch 
eingebaut, dass der Ausgabewert über mehrere Protokolle gemittelt werden 
soll.

von Karl H. (kbuchegg)


Lesenswert?

Florian T. schrieb:
> Lernprozess oh ja... saß 3 Stunden dran um rauszukriegen warum er die
> Daten nicht anständig erkennt,
> Grund war, dass ich diese if-Zeile haben wollte
>
1
> if(log[i]=='('||log[i]==')')
2
>
>
> aber diese hatte
>
1
> if(log[i]=='('||')')
2
>
> wodurch er den Klammern Counter jedes mal erhöht hat egal was in Log[i]
> drinstand.

Tipp:

Seit du 6 Jahre alt bist, hast du dein Gehirn darauf trainiert, dass 
beim Lesen zwischen Wörtern ein Leerraum steht. Dein Gehirn ist darauf 
konditioniert und findet diese Leerräume mitlerweile problemlos, so dass 
du beim Lesen eben nicht ständig die Buchstaben absuchen musst, wo denn 
ein Wort aufhört und wo das nächste anfängt. D.h. du konzentrierst dich 
komplett und ausschliesslich auf die Bedeutung der Wörter. Das Zerlegen 
der Wörter geschieht für dich völlig unbewusst im Hintergrund. Und 
probiers doch mal aus, 
umwievielschwererdieserSatzzulesenist,wennmanneinfachalleZwischenräumewe 
glässt.

Es ist daher unklug, in einer Programmiersprache diesen Automatismus 
dadurch lahmzulegen, indem man Zeichen auf Zeichen, knirsch an knirsch 
schreibt. Selbst dann, wenn es dir die Programmiersprache erlaubt!
1
  if( log[i] == '(' || ')' )

Und hier fällt viel eher auf, dass da eben nicht 2 Vergleiche stehen, 
die mit einem logischen Oder verknüpft sind.

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.