Seltsamer Fehler mit Festkommaarithmetik und 7-Segment

Gast #2731954
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
Gast #2731974
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?
#2731977
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.
(Firma: matzetronics) #2732043
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?
#2732206
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
Moderator (Firma: Titel) Persönliche Seite #2732232
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...
#2732281
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.
#2732304
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
Gast #2732382
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.
Gast #2732684
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
#2732760
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
      }
Gast #2733151
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)
#2733196
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)
Gast #2733261
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.
#2733269
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
#2733282
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
#2733300
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
}
Gast #2736032
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren