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


von Andreas (Gast)


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!
1
volatile uint16_t servo_timer_value[8];
2
uint16_t servo_position[8];
3
...
4
uint8_t servo_number = 0;
5
...
6
uint16_t data_long = 0;
7
...
8
//position dient nur zur Ausgabe auf Anfrage über serielle Schnittstelle
9
servo_position[servo_number] = data_long;
10
//Problembringende Formel:
11
servo_timer_value[servo_number] = 
12
(uint64_t)(data_long * (uint16_t)(SIG_0_7MS - SIG_2_3MS))/5535 + SIG_2_3MS;          }
13
...
14
//Timer1 sorgt nach definierter Zeit für ein Ausschalten des Ausgangssignals:
15
TCNT1 = servo_timer_value[servo_num];
16
TCCR1B = (1<<CS10);
17
...

von Karl H. (kbuchegg)


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)

von Andreas (Gast)


Lesenswert?

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

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

von Andreas (Gast)


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.

von Karl H. (kbuchegg)


Lesenswert?

Andreas schrieb:

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

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

von Andreas (Gast)


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?

von Andreas (Gast)


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.

von Uhu U. (uhu)


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.

von Karl H. (kbuchegg)


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.

von Horst H. (horha)


Lesenswert?

Hallo,

Es wird ja der Wert von data_long auf den Werte Bereich passend zu 0.7 
bis 2.3 ms skaliert.
1
((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.
1
((uint32_t)data_long * (SIG_0_7MS - SIG_2_3MS))>>16 + SIG_2_3MS;
Man koennte ja auch runden ;-)
1
(((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.

von Andreas (Gast)


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.

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.