Forum: Mikrocontroller und Digitale Elektronik Seltsamer Fehler mit Festkommaarithmetik und 7-Segment


von Philipp (Gast)


Lesenswert?

Nabend,

ich bin leider noch ziemlich unerfahren in der Mikrocontroller 
Programmierung und habe gerade mit einem Problem zu kämpfen. 
Warscheinlich ist es nur eine Kleinigkeit aber ich komm gerade einfach 
nicht drauf.

1
      if(flag.multiplex==1)
2
      {
3
        flag.multiplex=0;
4
        result = ADC;
5
        ADCSRA  |= (1<<ADSC);
6
7
        set_display_number(result*176/1024);
8
        set_dot(zehn);
9
      }

Um diesen Codeausschnitt handelt es sich, wobei result als uint32_t 
deklariert wurde. Der angezeigte Wert ist im Prinzip auch korrekt, 
ausser bei ganzen Zahlen: Wenn z.B. 10.0 angezeigt werden müsste wird 
komischerweise 00.0 angezeigt und bei 12.0 wird 11.0 angezeigt.

An der set_display_number() Methode liegt es nicht, denn wenn ich dieser 
die Werte direkt übergebe werden sie richtig angezeigt.


Über einen kleinen Denkanstoß würde ich mich sehr freuen!

Gruß,
Philipp

von Daniel H. (Firma: keine) (commander)


Lesenswert?

Philipp schrieb:
> set_display_number(result*176/1024);

Ich vermute du wirst an der Stelle einen Overflow haben, d.h. result*176 
wird größer als es uint32_t zulässt. Übrigens würde ich mir an der 
Stelle die Division sparen und einfach

> result * 0.17185

rechnen.

von Dennis H. (c-logic) Benutzerseite


Lesenswert?

oder x 11 / 64

von Philipp (Gast)


Lesenswert?

Hallo,

leider funktioniert es damit auch nicht. Der Fehler bleibt bestehen.

Die 176 kommen übrigends dadurch zustande, dass die Referenzspannung 
1,1V beträgt und die zu messende Spannung per Spannungsteiler durch 16 
geteilt wird. Dann noch ein Faktor 100 um aus den 1/000V wieder meine 
1/10V zu machen.

Also quasi: set_display_number(result*1100*16*100/1024)

Liegt der Fehler vielleicht dort?

von Philipp (Gast)


Lesenswert?

Philipp schrieb:

> Also quasi: set_display_number(result*1100*16*100/1024)

Sorry! Ich meinte natürlich /100 und nicht *100!

von Falk B. (falk)


Lesenswert?

@  Daniel H. (Firma: keine) (commander)

>> set_display_number(result*176/1024);

>Ich vermute du wirst an der Stelle einen Overflow haben, d.h. result*176
>wird größer als es uint32_t zulässt.

Kaum, denn result als ADC ergebniss ist max. 10 Bit, mal 176 (8 Bit 
macht) maximal 18 Bit. Ok, result ist wahrscheinlich 16 Bit, dann wird 
es ein Overflow. Also eher so.
1
set_display_number(result*176L/1024);

> Übrigens würde ich mir an der
>Stelle die Division sparen und einfach

>> result * 0.17185

>rechnen.

Dann wäre der Sinn von Festkommaarithmetik verfehlt.

von fonsana (Gast)


Lesenswert?

Teile die Zeile in einzelne Schritte und gib die einzelnen 
Zwischenergebnisse aus.

fonsana

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Falk Brunner schrieb:
>> Übrigens würde ich mir an der
>>Stelle die Division sparen und einfach
>
>>> result * 0.17185
>
>>rechnen.
>
> Dann wäre der Sinn von Festkommaarithmetik verfehlt.

Jo, Vorsicht. Eine einzige float in so einer Berechnung zieht die math 
library rein und bläht den Code so richtig auf. Am besten immer ein Auge 
auf die Codegrösse beim Compilieren werfen, damit das nicht passiert.

Philipp schrieb:
> Die 176 kommen übrigends dadurch zustande, dass die Referenzspannung
> 1,1V beträgt und die zu messende Spannung per Spannungsteiler durch 16
> geteilt wird.

Das klingt übrigens so, als würdest du freiwillig eine Menge Auflösung 
verschenken. Absicht?

von Bernhard S. (b_spitzer)


Lesenswert?

Wie sieht den der Funktionsprototyp von set_display_number() aus? Hat 
der auch uint32 oder nur uint16? Wenn uint16, dann versuche mal einen 
Type-Cast mit
1
set_display_number((uint16t)((result*11)/64));
Die Klammern habe ich gesetzt, damit der Compiler auch sicher nicht 
zuerst 11/16 rechnen will. Dabei kommt nämlich 1 raus...

tschuessle
Bernhard

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Bernhard Spitzer schrieb:
> damit der Compiler auch sicher nicht zuerst 11/16 rechnen will.
Will er sicher nicht, denn da steht 64...
> zuerst 11/16 rechnen will. Dabei kommt nämlich 1 raus...
Bei mir kommt mit 11/16 nur 0 raus...


Man könnte da mal eine Zwischenvariable einführen, und die genauer 
anschauen. So würde ich das machen...

Philipp schrieb:
> Wenn z.B. 10.0 angezeigt werden müsste wird
> komischerweise 00.0 angezeigt und bei 12.0 wird 11.0 angezeigt.
Evtl. passt da auch einfach was mit dem Multiplexen nicht zusammen, und 
es wird die falsche Ziffer an der falschen Stelle angezeigt...

von Karl H. (kbuchegg)


Lesenswert?

Bernhard Spitzer schrieb:
> Wie sieht den der Funktionsprototyp von set_display_number() aus? Hat
> der auch uint32 oder nur uint16? Wenn uint16, dann versuche mal einen
> Type-Cast mit
>
1
set_display_number((uint16t)((result*11)/64));
> Die Klammern habe ich gesetzt, damit der Compiler auch sicher nicht
> zuerst 11/16 rechnen will.

Das darf er sowieso nicht.
Neben Precedence gibt es ja auch noch die Assoziativität, die das klar 
regelt.

von Peter D. (peda)


Lesenswert?

Philipp schrieb:
> Um diesen Codeausschnitt handelt es sich

Das ist ungefähr so, als würde von Dir jemand verlangen, ein Buch durchs 
Schlüsselloch zu lesen.

Code-Pfitzelchen bringen rein garnichts!
Da muß man ewig rumraten, wie alle Funktionen, Macros und Variablen 
deklariert sind und was sie vermutlich bedeuten könnten.

Zeig einen compilierbaren relevanten Code als Anhang.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Peter Dannegger schrieb:
> Philipp schrieb:
>> Um diesen Codeausschnitt handelt es sich
>
> Das ist ungefähr so, als würde von Dir jemand verlangen, ein Buch durchs
> Schlüsselloch zu lesen.

Den muss ich mir merken. Der ist gut.

von MWS (Gast)


Lesenswert?

> set_display_number(result*176/1024);

Da mangels ausführlichen Codes sowieso Ratestunde ist, tippe ich mal 
darauf dass es egal ist, wie result hier deklariert ist, falls es erst 
dem Funktionsparameter übergeben und dann darauf die Rechnung ausgeführt 
wird.

Leicht feststellbar, indem man den Parameter auf 32Bit ändert. Oder sich 
lss ansieht.

von Philipp (Gast)


Lesenswert?

Hallo,
Danke erstmal für die vielen antworten. Ich reiche den vollständigen 
Code heute Abend noch nach. Ich komme dem Fehler nicht auf die Spur.

Als ich mir gestern zum testen mal den adc-Wert aus result 
(übrigends32bit) ausgeben lassen habe ist mir aufgefallen, dass der Wert 
manchmal nicht korrekt ist und "springt". Z.b. Kommt nach dem Wert 696 
nicht 697 sondern 607 und danach kommt 698.

Meine set number Methode zeigt aber alle Zahlen von 0-999 korrekt an(mit 
for getestet).

Bis später,
Philipp

von Stefan W. (dl6dx)


Lesenswert?

Philipp schrieb:
> Ich reiche den vollständigen Code heute Abend noch nach.

Hallo Philipp,

wichtig ist auch die Information, welchen Compiler du nutzt und auf 
welchem Prozessor das Ganze läuft.

Grüße

Stefan

von Falk B. (falk)


Lesenswert?

@  Philipp (Gast)

>Meine set number Methode zeigt aber alle Zahlen von 0-999 korrekt an(mit
>for getestet).

Wie? So hier?
1
      if(flag.multiplex==1)
2
      {
3
        flag.multiplex=0;
4
        //result = ADC;
5
        // test
6
        result = 1023;
7
        ADCSRA  |= (1<<ADSC);
8
9
        set_display_number(result*176/1024);
10
        set_dot(zehn);
11
      }

von Philipp (Gast)


Lesenswert?

Hallo,

hier ist der Programmcode. Ich habe das übrigends in Eclipse (mit 
AVR-Plugin) geschrieben und mit AVR-Studio4 auf meinen Atmega168PA 
gebrannt. Leider gibt es in Eclipse den Atmega168PA nicht zur Auswahl 
und bei dem Atmega168P lassen sich die Register nicht ansprechen, 
weshalb ich den Atmega168 ausgewählt habe(Ich hoffe es liegt nicht 
daran?).

Falk Brunner schrieb:
> @  Philipp (Gast)
>
>>Meine set number Methode zeigt aber alle Zahlen von 0-999 korrekt an(mit
>>for getestet).
>
> Wie? So hier?
>       if(flag.multiplex==1)
>       {
>         flag.multiplex=0;
>         //result = ADC;
>         // test
>         result = 1023;
>         ADCSRA  |= (1<<ADSC);
>
>         set_display_number(result*176/1024);
>         set_dot(zehn);
>       }

Es wird 175 Ausgegeben. Wenn ich anstelle von 1023 result per 
for-schleife durchzähle funktioniert die Ausgabe bei allen Zahlen 
Fehlerfrei. Ich schätze es muss irgendwie mit dem ADC-Wert 
zusammenhängen, da dort manchmal schon völlig falsche Werte ausgespuckt 
werden.(?)

1
#define taster_channel PC1
2
#define taster_mode PC2
3
#define hundert 2
4
#define zehn 1
5
#define eins 0
6
7
#include <avr/io.h>
8
#include <avr/sleep.h>
9
#include <avr/interrupt.h>
10
#include <avr/eeprom.h>
11
#include <util/delay.h>
12
13
#include "debounce.h"
14
#include "7segment.h"
15
16
typedef struct {      // Flag Register
17
  unsigned display:1;
18
  unsigned dot:1;
19
  unsigned multiplex:1;
20
21
} flagbyte;
22
23
volatile flagbyte flag;
24
volatile uint16_t count=0;
25
26
ISR(TIMER0_OVF_vect)
27
{
28
  flag.multiplex=1;
29
  if(flag.display)
30
  {
31
    if((PORTC&0b00111000)==0b00011000)
32
    {
33
      PORTD=0;
34
      PORTC=0b101000;
35
      PORTD=led_data[1];
36
    }
37
    else if((PORTC&0b00111000)==0b00101000)
38
    {
39
      PORTD=0;
40
      PORTC=0b110000;
41
      PORTD=led_data[0];
42
    }
43
    else if((PORTC&0b00111000)==0b00110000)
44
    {
45
      PORTD=0;
46
      PORTC=0b011000;
47
      PORTD=led_data[2];
48
    }
49
  }
50
  else
51
  {
52
      PORTD=0xFF;
53
      PORTC=0b00011000;
54
  }
55
}
56
57
int main(void)
58
{
59
  DDRD=0xFF;
60
  PORTD=0xFF;
61
62
    DDRC=0b00111000;
63
    PORTC=0b00011000;
64
65
    TIMSK0 = (1<<TOIE0);
66
    TCCR0B = (1<<CS02);
67
68
    TIMSK1 = (1<<TOIE1);
69
    TCCR1B = (1<<CS11);
70
71
    uint32_t result;
72
  DIDR0=1;
73
  ADMUX   = (1<<REFS1) | (1<<REFS0);
74
  ADCSRA  = (1<<ADEN) | (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);
75
  ADCSRA  |= (1<<ADSC);
76
  while (ADCSRA & (1<<ADSC) ) {}
77
  result = ADC;
78
79
    flag.display=1;
80
81
    sei();
82
83
    while (1)
84
    {
85
      if(debounce (PINC, taster_channel))
86
      {
87
        toggle_dot(zehn);
88
      }
89
      if(debounce (PINC, taster_mode))
90
      {
91
        flag.display^=1;
92
      }
93
      if(flag.multiplex==1)
94
      {
95
        flag.multiplex=0;
96
        result = ADC;
97
        ADCSRA  |= (1<<ADSC);
98
        set_display_number(result*176/1024);
99
        //set_display_number(result);
100
      }
101
    }
102
    return 1;
103
}

Und hier noch der Inhalt aus der 7-Segment.h Datei:
1
volatile uint8_t led_data[3];
2
3
volatile uint8_t segmente[]={
4
  0b10000001,    //0
5
  0b11010111,    //1
6
  0b10100100,    //2
7
  0b10010100,    //3
8
  0b11010010,    //4
9
  0b10011000,    //5
10
  0b10001000,    //6
11
  0b11010101,    //7
12
  0b10000000,    //8
13
  0b10010000,    //9
14
  0b11000000,    //A =10
15
  0b10101001,    //C =11
16
  0b10101000,    //E =12
17
  0b11000010,    //H =13
18
  0b11111110,    //- =14
19
  0b11110000    //° =15
20
};
21
22
void set_display_number(uint16_t number)
23
{
24
  if(number>=999)
25
  {
26
    led_data[hundert]=segmente[9];
27
    led_data[zehn]=segmente[9];
28
    led_data[eins]=segmente[9];
29
  }
30
  else
31
  {
32
    uint8_t counter=0;
33
    while(number>99)
34
    {
35
      counter++;
36
      number -= 100;
37
    }
38
    led_data[hundert]=segmente[counter];
39
    counter=0;
40
    while(number>9)
41
    {
42
      counter++;
43
      number -= 10;
44
    }
45
    led_data[zehn]=segmente[counter];
46
    counter=0;
47
    led_data[eins]=segmente[number];
48
  }
49
}
50
51
void set_dot(uint8_t ziffer)
52
{
53
  led_data[ziffer]&=~(0b10000000);
54
}
55
56
void delete_dot(uint8_t ziffer)
57
{
58
  led_data[ziffer]|=(0b10000000);
59
}
60
61
void toggle_dot(uint8_t ziffer)
62
{
63
  led_data[ziffer]^=(0b10000000);
64
}

(Ich hoffe das ist jetzt nicht zu viel Code für einen Beitrag)

von Karl H. (kbuchegg)


Lesenswert?

Was macht der Timer 1?


>    TIMSK1 = (1<<TOIE1);
>    TCCR1B = (1<<CS11);

Du gibst da einen Interrupt frei, für den ich die ISR nicht sehen kann. 
Nicht gut.


> Ich schätze es muss irgendwie mit dem ADC-Wert
> zusammenhängen, da dort manchmal schon völlig falsche
> Werte ausgespuckt werden.(?)

Das lässt sich ja überprüfen. Du hast eine Ausgabe zur Verfügung (wenn 
auch nur 3-stellig. Lässt du dir halt einfach immer die Hälfte vom ADC 
Wert anzeigen, dann passt der da rein. Dein Problem hängt ja nicht damit 
zusammen, dass der ADC Wert um +- 1 schwankt)

von Karl H. (kbuchegg)


Lesenswert?

Wie ist die Aussenbeschaltung vom ADC?
ARef, AVcc?

von Philipp (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Du gibst da einen Interrupt frei, für den ich die ISR nicht sehen kann.
> Nicht gut.

Oh Sorry, das ist noch ein Überbleibsel als ich ausprobiert habe was 
passiert wenn ich den ADC in einem seperaten langsam laufenden 
Timer-Interrupt auslese.

Karl Heinz Buchegger schrieb:
> Das lässt sich ja überprüfen. Du hast eine Ausgabe zur Verfügung (wenn
> auch nur 3-stellig. Lässt du dir halt einfach immer die Hälfte vom ADC
> Wert anzeigen, dann passt der da rein. Dein Problem hängt ja nicht damit
> zusammen, dass der ADC Wert um +- 1 schwankt)

Habe jetzt mal folgendes ausgeben lassen:
1
      if(flag.multiplex==1)
2
      {
3
        flag.multiplex=0;
4
        //result = ADC;
5
        set_display_number(ADC/2);
6
        ADCSRA  |= (1<<ADSC);
7
8
        //set_display_number(ADC/2);
9
        //set_display_number(result);
10
      }

Dabei kommt das hier heraus:
http://www.youtube.com/watch?v=gZnhV_pHSr8

Also wieder total komische Werte (von 199 -> 190 -> 101 -> 102 -> 200 -> 
201). Und zwar nicht zufällig sondern diese Sprünge sind immer an der 
gleichen Stelle im Wertebereich. Ist meine 7-Segment Routine so OK oder 
könnte es vielleicht doch irgendwie daran liegen? Ich weiß nicht was ich 
noch testen könnte.

Kann mein Atmega vielleicht auch eine Macke haben?

Karl Heinz Buchegger schrieb:
> Wie ist die Aussenbeschaltung vom ADC?
> ARef, AVcc?

Aref ist mit 100nF nach GND entkoppelt und AVCC mit 22µH + 100µF nach 
VCC.

von Stefan W. (dl6dx)


Lesenswert?

Philipp schrieb:
> void set_display_number(uint16_t number)

Das dürfte die entscheidende Information sein.

set_display_number(result*176/1024) enthält also einen impliziten cast 
des Ausdrucks result*176/1024 auf uint16_t.

Wenn ich den Abschnitt "Conversions" (C89, identisch ISO/IEC 9899:1999) 
richtig verstehe, sollte dieser cast aber erst nach der Auswertung des 
rvalue greifen. result*176/1024 müsste also noch uint32_t sein.

Ok, schauen wir mal:
ADC 10 bit. *176 gibt 28 bit. /1024 ist ein Rechtshift um 10 bit, 
bleiben also 18 bit übrig. -> Das passt nicht in einen uint16_t.

Du hast aber nur 3 signifikante Ziffern, der Wert ist also eh zu weit 
aufgelöst. Ist der Umrechnungsfaktor wirklich passend?

Grüße

Stefan

von Karl H. (kbuchegg)


Lesenswert?

Stefan Wagner schrieb:

> Ok, schauen wir mal:
> ADC 10 bit. *176 gibt 28 bit. /1024 ist ein Rechtshift um 10 bit,
> bleiben also 18 bit übrig. -> Das passt nicht in einen uint16_t.

Irgendwo musst du dich da verrechnet haben.
Das Ergebnis kann nur Werte im Bereich 0 bis 176 sein.

von Stefan W. (dl6dx)


Lesenswert?

Ach ja, was mir noch auffiel:

Philipp schrieb:
> volatile uint8_t segmente[]={

Wenn ich das richtig sehe, ist das doch die Tabelle mit den Werten für 
die Ansteuerung der Segmente.

Warum dann die Deklaration als volatile? Diese Werte sind doch konstant 
und sollen es auch bleiben. Einer Optimierung durch den Compiler steht 
doch nicht im Wege, selbst wenn auch aus einer ISR auf das Array 
zugegriffen wird. (Die Deklaration const uint8_t segmente[] hielte ich 
für wesentlich sinnvoller.)

Grüße

Stefan

von Karl H. (kbuchegg)


Lesenswert?

Philipp schrieb:

> Also wieder total komische Werte (von 199 -> 190 -> 101 -> 102 -> 200 ->
> 201).

Das sieht so aus, als ob die Segmenttabelle um 1 verschoben wäre.
eine angezeigte 9 ist eigentlich 8
                0 ist eigentlich 9
                1 ist eigentlich 0
usw.

> Und zwar nicht zufällig sondern diese Sprünge sind immer an der
> gleichen Stelle im Wertebereich. Ist meine 7-Segment Routine so OK

Ich hätt jetzt gesagt: ja

Schreibs mal so
1
void set_display_number(uint16_t number)
2
{
3
  if(number >= 999)
4
  {
5
    led_data[hundert] = segmente[9];
6
    led_data[zehn]    = segmente[9];
7
    led_data[eins]    = segmente[9];
8
  }
9
  else
10
  {
11
    led_data[eins] = segmente[ number % 10 ];
12
    number = number / 10;
13
14
    led_data[zehn] = segmente[ number % 10 ];
15
    number = number / 10;
16
17
    led_data[hundert] = segmente[ number ];
18
  }
19
}

von Stefan W. (dl6dx)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Irgendwo musst du dich da verrechnet haben.
> Das Ergebnis kann nur Werte im Bereich 0 bis 176 sein.

Ups. Ja. Und das hätte mir beim genauen Hinsehen auch auffallen müssen. 
(1023/1024 ist doch recht nahe an 1.) Ich war wohl doch schon zu müde.

Grüße

Stefan

von Philipp (Gast)


Lesenswert?

Danke nochmal an euch! Mittlerweile funktioniert es sogar, obwohl ich 
nicht sagen kann an was das Problem jetzt genau lag. Ich lese den ADC 
jetzt einfach doch in einem extra Interrupt aus.

Gruß,
Philipp

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.