Forum: Mikrocontroller und Digitale Elektronik Unerwartetes Rechenergebnis bei uint32_t


von cLangErr (Gast)


Lesenswert?

Ich möchte rechnen im unsigned int-Bereich von 1 bis etwa 400.000, also 
verwende ich uint32_t. Das Ergebnis einer Rechenoperation ist ebenfalls 
nur im Bereich der ganzen Zahlen für mich interessant, der Dezimalbruch 
kann einfach wegfallen.
(mit #include "type.h" )
1
unit32_t Freq = 3000;
2
3
uint32_t Ergeb = ( 10000000 / Freq );
Ergebnis ist 3333.333333...,
Ergeb ist 3333, also ausreichend.
1
unit32_t Freq = 65000;
Ergebnis ist 153,846153846,
Ergeb ist 153, nicht weiter überraschend.
1
unit32_t Freq = 66000;
Ergebnis ist 151,515151515,
Ergeb sollte sein 151, ist aber 13888.

Ab 65536 kippt der Wert, dabei dachte ich mit unint32_t sollte mich das 
nicht mehr kratzen müssen.

Casten hilft nicht, zumindest nicht so wie ich es versucht habe:
1
uint32_t Ergeb = (uint32_t) ( 10000000 / Freq );
 bzw.
1
uint32_t Ergeb = (unsigned long int) ( 10000000 / Freq );

Plattform ist LPCXpresso 1769, also Arm Cortex M3.
Compiler ist GCC, LPCXpresso v6.0.2.

1.Frage: Wieso habe ich hier Probleme mit Werten grösser 16Bit?
2.Frage: Was kann ich dagegen tun?

Danke im Voraus.

von Peter II (Gast)


Lesenswert?

cLangErr schrieb:
> Casten hilft nicht, zumindest nicht so wie ich es versucht habe:uint32_t
> Ergeb = (uint32_t) ( 10000000 / Freq );

ja weil es dort schon zu spät ist

 Ergeb = 10000000ul / Freq;

von Kuchenblech (Gast)


Lesenswert?

Kommt auf deinen Compiler an: 10000000L

von Karl H. (kbuchegg)


Lesenswert?

cLangErr schrieb:

> Casten hilft nicht, zumindest nicht so wie ich es versucht habe:
>
1
> uint32_t Ergeb = (uint32_t) ( 10000000 / Freq );
2
>
> bzw.
>
1
> uint32_t Ergeb = (unsigned long int) ( 10000000 / Freq );
2
>

Ist das dein echter Code?

Denn:
du musst unterscheiden zwischen der eigentlichen Berechnung und dem was 
mit dem Ergebnis passiert. Hier

>
1
uint32_t Ergeb = (uint32_t) ( 10000000 / Freq );

wird 10000000 durch Freq dividiert. Diese Division findet als 16 Bit 
oder 32 Bit Division statt, je nach den Datentypen der beteiligten 
Operanden. D.h. der Datentyp von 10000000 bzw. der Datentyp von Freq 
entscheiden, wie die Division durchgeführt wird!
Aus der Division kommt ein Ergebnis und erst dieses Ergebnis forsierst 
du dann zu einem uint32_t, das letzten Endes dann an Ergeb zugewiesen 
wird.

Wie eine Operation durchgeführt wird, hängt einzig und alleine von den 
Datentypen der beteiligten Operanden ab, aber niemals davon, was mit dem 
Ergebnis dann weiter passiert (an welchen Variablentyp es zugewiesen 
wird, oder ob damit weitergerechnet wird, oder ....)

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Karl Heinz (kbuchegg) (Moderator)

>wird 10000000 durch Freq dividiert. Diese Division findet als 16 Bit
>oder 32 Bit Division statt, je nach den Datentypen der beteiligten
>Operanden.

Nöö. 10000000 (10e6) passt nur in 32 Bit.

von cLangErr (Gast)


Lesenswert?

Ja, das war der echte Code, soweit runter gebrochen, dass der Fehler 
noch auftritt.

Freq ist doch aber uint32 und 10mio ist doch (offensichtlich) mehr als 
16 Bit. Dann müsste das Ergebnis doch ebenfalls 32 Bit breit bleiben.

Ich habe mal 10000000 mit uL ergänzt, so grosse konstante Zahlenwerte 
habe ich auch noch nie in Berechnungen gehabt. Das Ergebnis ist das 
selbe.

Jetzt habe ich nochmal die 10 mio als const uint32_t heraus gezogen, das 
Ergebnis ist aber trotzdem noch nicht besser.

Würden die 10000000 aber als 16 Bit-Wert aufgefasst, dann müssten die ja 
schon vorher vom Compiler klein gemacht werden, dann könnten ja die 
Rechenoperation für Freq-Werte kleiner 65535 nicht stimmen.

von Karl H. (kbuchegg)


Lesenswert?

Falk Brunner schrieb:
> @ Karl Heinz (kbuchegg) (Moderator)
>
>>wird 10000000 durch Freq dividiert. Diese Division findet als 16 Bit
>>oder 32 Bit Division statt, je nach den Datentypen der beteiligten
>>Operanden.
>
> Nöö. 10000000 (10e6) passt nur in 32 Bit.

Schon klar.
Darum hab ich auch gefragt, ob das der richtige Code ist.
Denn nur mit dem geposteten ist das Ergebnis nicht zu erklären.

von Karl H. (kbuchegg)


Lesenswert?

cLangErr schrieb:
> Ja, das war der echte Code, soweit runter gebrochen, dass der Fehler
> noch auftritt.

OK.
Dann stellt sich die Frage, wie du feststellst, dass dein Ergebnis nicht 
korrekt ist.
Du wärst nicht der Erste, der einem Fehler in der Berechnung nachläuft 
und dann draufkommt, das er bei der Anzeige des Wertes eine implizite 
Verkürzung auf 16 Bit hat.

Daher: kompletter Code! Oftmals sitzt der Fehler nicht dort, wo du ihn 
vermutest.

: Bearbeitet durch User
von GB (Gast)


Lesenswert?

cLangErr schrieb:
> unit32_t Freq = 3000;

Hast Du das aus dem Code kopiert?

Da steht unit32_t und nicht uint32_t

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


Lesenswert?

cLangErr schrieb:
> Würden die 10000000 aber als 16 Bit-Wert aufgefasst, dann müssten die ja
> schon vorher vom Compiler klein gemacht werden, dann könnten ja die
> Rechenoperation für Freq-Werte kleiner 65535 nicht stimmen.
Statt lauter "hätte", "wäre", "könnte", "müsste" und "würde" täte ich 
einfach mal den erzeugten Assemblercode ansehen. Da sieht man recht 
schnell, was der Compiler aus der C Zeile macht...

von (prx) A. K. (prx)


Lesenswert?

Lothar Miller schrieb:
> Statt lauter "hätte", "wäre", "könnte", "müsste" und "würde" täte ich
> einfach mal den erzeugten Assemblercode ansehen. Da sieht man recht
> schnell, was der Compiler aus der C Zeile macht...

Der echte C Codeausschnitt wäre auch nicht schlecht. Irgendein grob 
nachempfundener Code mit offensichtlichem Syntaxfehler ist keine grosse 
Hilfe.

Ebenso ist "kippt der Wert" keine wahrhaft präzise Problemangabe.

: Bearbeitet durch User
von cLangErr (Gast)


Lesenswert?

Ja, ich habe das aus dem Code kopiert, habe aber die Zeilen mehrfach im 
Browser-Editor bearbeitet, und da mache ich als freudsche Verschreiber 
oft unit statt uint.

Cut&Paste läuft nicht immer ganz so problemlos, die IDE ist in einer 
virtuellen Maschine.

Das ganze Programm hat ein paar tausend Zeilen Code, unter anderem kommt 
der Wert von Freq übers TCP/IP rein. Das Ergebnis der Berechnung wird 
wieder zurückgesendet, aber vor allem wandert es als Pulsperiode in die 
PWM-Unit, welche zufällig mit 10MHz läuft.

Also sehe ich nicht nur den Zahlenwert als Antwort sondern auch den 
Effekt im Oszilloskop.

Die Zeile sieht also so aus:
1
const uint32_t pwm_takt = 10000000uL;
2
3
LPC_PWM1 ->MR0 = Ergeb = (uint32_t) ( pwm_takt / Freq );
4
LPC_PWM1 ->MR1 = 10;
5
LPC_PWM1 ->LER = (1 << 0) | (1 << 1);

Ich werde mal alles raus schmeissen und wirklich nur das minimalste 
Programm posten.

von asdf (Gast)


Lesenswert?

Stelle sicher dass:

* Bei TCP die Byte Order richtig verwendet wird
* Freq nach der Zuweisung den richtigen Wert hat (Debugger)

von Frank B. (f-baer)


Lesenswert?

Ich meine, mich erinnern zu können, dass der Cortex-M generell dazu 
neigt, Rechenoperationen in 16Bit durchzuführen, wenn man keinen 
Typecast am Operanden durchführt.

Probiere es mal mit
1
LPC_PWM1 ->MR0 = Ergeb =  ( (uint32_t)pwm_takt / Freq )

Das Ergebnis zu casten bringt dir nämlich nichts, zu dem Zeitpunkt ist 
die Messe schon gelesen. Das führt dann nur noch dazu, dass die 
Zuweisung als 32-Bit-Operation durchgeführt wird.

von (prx) A. K. (prx)


Lesenswert?

Frank Bär schrieb:
> Ich meine, mich erinnern zu können, dass der Cortex-M generell dazu
> neigt, Rechenoperationen in 16Bit durchzuführen, wenn man keinen
> Typecast am Operanden durchführt.

Das wäre höchst seltsam. Zumal die Hardware das überhaupt nicht kann. 
Die hat nämlich einen Divisionbefehl, aber nur für 32 Bits.

Aus dem präsentierten Code geht leider nicht eindeutig hervor, wer 
hier dividiert. Wenn beide Seiten dem Compiler bekannt sind, dann 
dividiert der Compiler, andernfalls der Prozessor.

: Bearbeitet durch User
von cLangErr (Gast)


Lesenswert?

1
#include "stdlib.h"
2
#include "stdio.h"
3
#include "string.h"
4
5
#include <cr_section_macros.h>
6
#include <NXP/crp.h>
7
#include <math.h>
8
9
// CodeRed - added NXP LPC register definitions header
10
#include "LPC17xx.h"
11
12
// Variable to store CRP value in. Will be placed automatically
13
// by the linker when "Enable Code Read Protect" selected.
14
// See crp.h header for more information
15
__CRP const unsigned int CRP_WORD = CRP_NO_CRP ;
16
17
const uint32_t max_freq = 10000000;
18
19
20
int main (void)
21
{
22
  //PWM
23
  LPC_SC ->PCONP |= (1 << 6);              //PWM power on
24
  LPC_SC->PCLKSEL0 |= ((0<<13)|(1<<12));
25
  LPC_PINCON ->PINSEL4 &= ~3;              //reset
26
  LPC_PINCON ->PINSEL4 |= 1;              //set PWM1.1 at P2.0
27
  LPC_PWM1 ->TCR = 2;                     //counter reset
28
  LPC_PWM1 ->PR = 9;            //peripheral clk / prescaler (= PR +1)
29
  LPC_PWM1 ->MCR = (1 << 1);                //reset on MR0
30
  LPC_PWM1 ->MR0 = 500;                 //set PWM cycle 1khz
31
  LPC_PWM1 ->MR1 = 20;                  //set duty
32
  LPC_PWM1 ->LER = (1 << 0) | (1 << 1);   //latch MR0 & MR1
33
  LPC_PWM1 ->PCR = (1 << 9);             //PWM1 output enable
34
  LPC_PWM1 ->TCR = (1 << 0) | (1 << 3);   //counter enable, PWM enable
35
36
37
  const uint32_t pwm_takt = 10000000uL;
38
  uint32_t  Freq = 65000;
39
  uint32_t Ergeb = 0;
40
41
  LPC_PWM1 ->MR0 = Ergeb = ( pwm_takt / Freq );
42
  LPC_PWM1 ->MR1 = 10;
43
  LPC_PWM1 ->LER = (1 << 0) | (1 << 1);
44
45
#ifdef __LPCXPRESSO__
46
  printf ("Rechentest\n");
47
  printf ("Freq = %i , Ergeb = %i\n",Freq,Ergeb);
48
#endif
49
50
  Freq = 66000;
51
52
  LPC_PWM1 ->MR0 = Ergeb = ( pwm_takt / Freq );
53
  LPC_PWM1 ->MR1 = 10;
54
  LPC_PWM1 ->LER = (1 << 0) | (1 << 1);
55
56
#ifdef __LPCXPRESSO__
57
  printf ("Freq = %i , Ergeb = %i\n",Freq,Ergeb);
58
#endif
59
60
  while (1)                                      // repeat forever
61
  {
62
63
  }
64
}
Die Debug-Ausgabe ist jetzt (für mich überreaschend):
Rechentest
Freq = 65000 , Ergeb = 153
Freq = 66000 , Ergeb = 151

Ich habe den PWM-Teil absichtlich drin gelassen, damit ich weiter den 
PWM-Ausgang messen kann.

Tja, scheint ja nun zu gehen :/

von Karl H. (kbuchegg)


Lesenswert?

cLangErr schrieb:

> Tja, scheint ja nun zu gehen :/

Womit wir wieder am Punkt angelangt wären:
dein Problem im richtigen Programm sitzt ganz wo anders. Was du dort 
siehst, sind Symptome aber nicht Ursachen.
Darum, und das ist jetzt durchaus nicht bösartig gemeint, ist es so 
wichtig, ein Problem in seinem vollen Umfeld zu zeigen. Sprich: 
kompletter Code. Ist das zu gross, dann eben ein abgespecktes 
vollständiges Programm, bei dem man aber vorher testen sollte, ob es das 
gleiche Problem zeigt. Denn oft genug ist das dann nämlich nicht der 
Fall.

: Bearbeitet durch User
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.