www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Formel führt zu Rundungsfehler?


Autor: Andreas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

Bei dem folgenden Quellcode handelt es sich um einen Auszug aus einem 
Servocontroller. Dieser empfing früher 1 Byte und setzte es in eine 
Position um. Das ging auch soweit, nun habe ich es erweitert auf 2 Byte 
damit ich eine größere Genauigkeit erziehlen kann. Das Problem ist, dass 
das Positionsarray den richtigen wert hat (habe ich mit serieller 
Schnittstelle testweise ausgegeben), das Timing-array hat anscheinend 
aber immer den Maximalwert, sobald ich die Routine ausführe, egal was 
ich eingebe. Daher vermute ich den Fehler wohl in dieser Formel wenn ich 
nicht ganz falsch liege. Die Cast-Operatoren habe ich testweise mal 
eingefügt, hat aber auch nicht den gewünschten Erfolg gebracht, war wohl 
eher eine Verzweiflungstat ;)

Danke für eure Hilfe!
volatile uint16_t servo_timer_value[8];
uint16_t servo_position[8];
...
uint8_t servo_number = 0;
...
uint16_t data_long = 0;
...
//position dient nur zur Ausgabe auf Anfrage über serielle Schnittstelle
servo_position[servo_number] = data_long;
//Problembringende Formel:
servo_timer_value[servo_number] = 
(uint64_t)(data_long * (uint16_t)(SIG_0_7MS - SIG_2_3MS))/5535 + SIG_2_3MS;          }
...
//Timer1 sorgt nach definierter Zeit für ein Ausschalten des Ausgangssignals:
TCNT1 = servo_timer_value[servo_num];
TCCR1B = (1<<CS10);
...

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas schrieb:

> //Problembringende Formel:
> servo_timer_value[servo_number] =
> (uint64_t)(data_long * (uint16_t)(SIG_0_7MS - SIG_2_3MS))/5535 +
> SIG_2_3MS;          }
> ...

Ohne konkrete Werte für data_long bzw. der Kenntnis von SIG_0_7_MS und 
SIG_"_3MS kann man da gar nichts sagen.
Der Cast nach uint64_t kommt aber auf jeden Fall zu spät, wenn seine 
Aufgabe darin bestehen soll eventuelle Überläufe in der uint16_t 
Berechnung der Multiplikation zu verhindern und ist als solcher komplett 
sinnlos.

In welchem Zahlenraum eine Berechnung durchgeführt wird, bestimmen die 
Datentypen der beteiligten Operanden und nicht was mit dem Ergebnis 
gemacht wird.

  data_long                           ist ein uint16_t
  (uint16_t)(SIG_0_7MS - SIG_2_3MS)   ist ein uint16_t

Also wird die Multiplikation als uint16_t Multiplikation durchgeführt. 
Und wenn die mit den konkreten Zahlen überläuft, dann läuft sie über. 
Der nachfolgende Cast des Ergebnisses auf uint64_t ändert daran nichts 
mehr.
(BTW: Das Ergebnis der Multiplkation zweier 16-Bit Zahlen kann maximal 
32 Bit aufweisen. Hat man es daher mit uint16_t zu tun, ist man mit dem 
Cast eines der Operanden [oder beide] auf uint32_t immer auf der 
sicheren Seite. Weiß man allerdings, dass bedingt durch die Zahlenwerte 
die Multiplikation nicht überlaufen kann, dann muss man gar nichts 
unternehmen)

Autor: Andreas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
oh stimmt, hab ich versehentlich nicht mit angegeben:
#define  SIG_2_3MS  0xb820
#define  SIG_0_7MS  0xea20

also muss ich wohl eher wenn ich den cast nutze so schreiben?:
((uint64_t)data_long * (uint64_t)(SIG_0_7MS - SIG_2_3MS))/5535 + SIG_2_3MS;

Autor: Andreas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
schon wieder was vergessen. data_long kann Momentan Werte zwischen 0 und 
0xFFFF annehmen, da er von außerhalb kommt und zur Steuerung dient.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas schrieb:

>
> ((uint64_t)data_long * (uint64_t)(SIG_0_7MS - SIG_2_3MS))/5535 +
> SIG_2_3MS;
> 

uint32_t reicht.
16 Bit * 16 Bit  kann maximal ein 32 Bit Ergebnis liefern.

Autor: Andreas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Okay, danke für die Hilfe! Ich werde das mal testen und dann 
bescheidgeben. Nur zum Verständnis:
Wie handelt der Compiler eigentlich gänzlich ohne Casts?
Nimmt er für jeden Rechenschritt den Zahlenraum der verwendeten 
Variablen und lässt den überschüssigen Teil wegfallen?

Autor: Andreas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also wie zu erwarten war läuft es jetzt prima, DANKE! habe oben 
zusätzlich dazu noch nen Tippfehler drin, da ich anstelle durch 65535 zu 
teilen aus versehen durch 5535 teile. Das hat aber natürlich nicht zu 
der oben beschriebenen Fehlfunktion beigetragen.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas schrieb:
> Wie handelt der Compiler eigentlich gänzlich ohne Casts?

Er bohrt alle Teilausdrücke auf den größten vorkommenden Datentyp auf - 
deswegen reich ein Cast.

Wenn der größte Datentyp zu klein ist, gibts einen Überlauf, um den sich 
C nicht weiter kümmert.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Uhu Uhuhu schrieb:
> Andreas schrieb:
>> Wie handelt der Compiler eigentlich gänzlich ohne Casts?
>
> Er bohrt alle Teilausdrücke auf den größten vorkommenden Datentyp auf -
> deswegen reich ein Cast.

Wobei 'alle Teilausdrücke' besser durch 'jede einzelne Operation' 
ersetzt wird.
Je nach Datentypen kann es also sein, dass verschiedene 
Zwischenergebnisse in einem komplizierterem Ausdruck unterschiedliche 
Datentypen haben.

Und ach ja: Das Minimum ist int. Kleiner wird nicht gerechnet.

Autor: Horst Hahn (horha)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

Es wird ja der Wert von data_long auf den Werte Bereich passend zu 0.7 
bis 2.3 ms skaliert.
((uint32_t)data_long * (SIG_0_7MS - SIG_2_3MS))/65535 + SIG_2_3MS;
waere shift right um 16 Stellen, also die Division durch 65536, nicht 
einfacher und ausreichend genau, die Abweichung ist ja nur 1/65536 also 
fast 1e-5.
Ausserdem sind es schlussendlich nur Werte zwischen SIG_2_3MS und 
SIG_0_7MS,
die ja nur 0xb820..0xea20 = 0x3200 ueberstreichen.
((uint32_t)data_long * (SIG_0_7MS - SIG_2_3MS))>>16 + SIG_2_3MS;
Man koennte ja auch runden ;-)
(((uint32_t)data_long * (SIG_0_7MS - SIG_2_3MS) + 1<<15)>>16  + SIG_2_3MS;

Die Aufloesung ist also nur 12800 verschiedene Werte und nicht 65536.

Autor: Andreas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das ist natürlich ne super Idee, darauf war ich garnicht gekommen! Von 
der Abweichung ist das für meine Anwendung absolut irrelevant, ich bin 
mir nichtmal sicher, ob das Servo hochwertig genug ist, die Auflösung zu 
realisieren, kommt eben auf einen Versuch an. Ich werde das gleich mal 
umsetzen im Programm.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.