Forum: Mikrocontroller und Digitale Elektronik Umwandlung Wert in 4 Byte Gleitkommazahl (KNX)


von Matthias (Gast)


Lesenswert?

Im KNX-Protokoll werden Leistungen als 4 Byte Gleitkommazahl übertragen. 
Mir ist allerdings nicht ganz klar, wie ich eine Zahl (z.B. Integer) in 
eine (KNX-)Gleitkommazahl mit 4 Byte umrechnen muß.

Ein Quellcode in C wäre super. Ich habe gegoogelt, aber nicht wirklich 
gute Treffer gelandet.

von Dieter H. (kyblord)


Lesenswert?

Matthias schrieb:
> Im KNX-Protokoll werden Leistungen als 4 Byte Gleitkommazahl
> übertragen.
> Mir ist allerdings nicht ganz klar, wie ich eine Zahl (z.B. Integer) in
> eine (KNX-)Gleitkommazahl mit 4 Byte umrechnen muß.
>
> Ein Quellcode in C wäre super. Ich habe gegoogelt, aber nicht wirklich
> gute Treffer gelandet.

die frage ist, wie werden gleitkommazahlen in KNX repräsentiert?
Wenn es einfach nur das IEEE754 gleitkommaformat ist, dann solltest du 
einfach mal auf wikipedia nachlesen. Du musst einen Exponenten, Mantisse 
und Vorzeichenbit setzen. Wie lange diese Felder sind, regelt der 
Standard.

https://www.inf.hs-flensburg.de/lang/informatik/ieee-format.htm

Poste mal eine Beispielkommazahl und die zugehörigen Bits. Dann kann man 
vielleicht auf das Format schließen.

sonst gibt es doch 
http://www.knx-gebaeudesysteme.de/sto_g/_All/MANUALS/ABAS_121/FR/05_function_elements/ConverterElement/index.html 
?

von Ingo Less (Gast)


Lesenswert?

Ich habe das so gelöst, habe aber keine Ahnung ob das bei dir 
funktioniert:
1
// Scale Values
2
  uint32_t CO2_Temp = (uint32_t)((((uint32_t)SensorRawData[0]) << 24) |
3
            (((uint32_t)SensorRawData[1]) << 16) |
4
            (((uint32_t)SensorRawData[3]) << 8) |
5
            ((uint32_t)SensorRawData[4]));
6
  memcpy(&SCD30_CO2_Concentration, &CO2_Temp, sizeof SCD30_CO2_Concentration);

Der Code setzt aus 4-Byte ein float zusammen.

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


Lesenswert?

Bei passender Bytereihenfolge/Endian reicht es auch, einen Pointer auf 
das erste Element zu setzen und den auf (float) zu casten:
1
char cval[4];
2
:
3
:
4
float fval = *(float*)cval;

von Matthias (Gast)


Lesenswert?

1000 Watt entspricht bei der KNX-Übertragung dem Wert 0x447A0000.

Auf https://www.binaryconvert.com/convert_float.html kann man online die 
Zahl konvertieren. Da kommt auch genau das Ergebnis raus. Jetzt muß ich 
nur noch einen Quellcode in C dazu finden.

http://www.knx-gebaeudesysteme.de/sto_g/_All/MANUALS/ABAS_121/DE/05_function_elements/ConverterElement/index.html 
hatte ich auch gefunden, aber das scheint irgend ein Modul einer 
Steuerung zu sein.

Quellcode von Ingo Less habe ich nicht kapiert.
Quellcode von Lothar M geht nicht. Bringt als Ergebnis 0x3EB raus.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Naechster Versuch:
1
#include <stdio.h>
2
#include <math.h>
3
int main() {
4
float f=1000.0;
5
printf("%02x" ,((unsigned char*)&f)[3]);
6
printf("%02x" ,((unsigned char*)&f)[2]);
7
printf("%02x" ,((unsigned char*)&f)[1]);
8
printf("%02x" ,((unsigned char*)&f)[0]);
9
10
#if 0
11
((unsigned char*)&f)[0] = 0xde;
12
((unsigned char*)&f)[1] = 0xad;
13
((unsigned char*)&f)[2] = 0xbe;
14
((unsigned char*)&f)[3] = 0xef;
15
#endif
16
printf("\n%f\n",f);
17
}
Aufm PC kommt raus:
$ ./a.exe
447a0000
1000.000000

Gruss
WK

von Martin (Gast)


Lesenswert?


von Matthias (Gast)


Lesenswert?

Printf ist soweit ich weiß, ziemlich rechenintensiv für einen 
Mikrocontroller. Ich habe das Ganze bereits für 16 Bit und es 
funktioniert:
1
unsigned int int_to_float16(int iValue)
2
{
3
unsigned int uiExponent;  // 4 Bit
4
int iMantisse;        // 11 Bit
5
unsigned char ucVorzeichen;  // 1 Bit
6
unsigned int uiReturnvalue;  // 16 Bit
7
8
uiExponent = 0;        // Exponent löschen
9
iMantisse = iValue;      // übergebener Wert wurde bereits mit 100 Miltipliziert übergeben (da in KNX mit Faktor 0,01 übergeben wird)
10
11
// prüfen, wie oft Mantisse durch zwei geteilt werden muß, damit sie in der Bereich -2048 / +2047 paßt (=> Exponent berechnen)
12
while( (iMantisse <-2048) || (iMantisse > 2047) )
13
  {
14
  iMantisse = iMantisse / 2;  // Wert halbieren
15
  uiExponent++;        // Exponent erhöhen
16
  }
17
18
19
// bei negativer Zahl ist Umrechnung Mantisse in Zweierkomplement erforderlich
20
if(iMantisse < 0)  // negative Zahl?
21
  {
22
  iMantisse = -iMantisse;            // positive Zahl draus machen
23
  iMantisse = ~iMantisse;            // Bits invertieren (Einerkomplement)
24
  iMantisse++;                // 1 dazuaddieren (Zweierkomplement)
25
  iMantisse = iMantisse & 0b0000011111111111;  // die Bits oberhalb der Mantisse ausmaskieren
26
  ucVorzeichen = KNX_VORZEICHEN_MINUS;    // Merker setzen
27
    }
28
else
29
  ucVorzeichen = KNX_VORZEICHEN_PLUS;  // Merker setzen
30
    
31
32
33
34
if(ucVorzeichen == KNX_VORZEICHEN_MINUS)  // Vorzeichen negativ?
35
  uiReturnvalue = 0b1000000000000000;  // Negativ-Bit setzen
36
else
37
  uiReturnvalue = 0b0000000000000000;  // Negativ-Bit löschen
38
    
39
uiReturnvalue = uiReturnvalue | (uiExponent << 11);  // Exponent-Bits setzen
40
uiReturnvalue = uiReturnvalue +iMantisse;      // Mantisse dazu
41
42
43
return (uiReturnvalue);
44
}

Ich benötige es nun für 32 Bit.

von W.S. (Gast)


Lesenswert?

Matthias schrieb:
> Jetzt muß ich
> nur noch einen Quellcode in C dazu finden.

Nö. Du solltest lieber das Prinzip verstehen lernen.

Also, zumeist besteht eine Gleitkommazahl aus dem Vorzeichen (1 Bit), 
dem Exponenten (8 Bit) und der Mantisse (23 Bit). Jetzt fragst du dich 
vielleicht, warum nicht 24 Bit Mantisse? Nun, das hängt an der 
Normierung der Mantisse, die solange linksverschoben wird, bis das MSB 
gesetzt ist. Der Exponent wird dabei angepaßt. Je nach Implementation 
kann die Mantisse damit
- von 0.5 bis 1 (- 1 LSB)
oder
- von 1.0 bis 2 (- 1 LSB)
gehen. Rein rechentechnisch ist das egal. Es hat nur mit dem Wert des 
Exponenten zu tun.

Da nun das MSB immer gesetzt ist, braucht man es nicht in der Zahl zu 
merken, sondern kann an dessen Stelle das Vorzeichen setzen.

W.S.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

W.S. schrieb:
> Du solltest lieber das Prinzip verstehen lernen.

Yupp. Und das Prinzip ist: Bloss nicht selber in 
Floatingpointverarbeitung rumruehren, wenns nicht unbedingt sein muss.
Das den Compiler machen lassen. Einfach aussenrum casten.
Die printfs in meinem Code sind nur dazu da, dass man "was sieht" in der 
console. Mit der eigentlichen Wandlung haben die nix zu tun.

Gruss
WK

von Matthias (Gast)


Lesenswert?

Lothar M. schrieb:
> Bei passender Bytereihenfolge/Endian reicht es auch, einen Pointer auf
> das erste Element zu setzen und den auf (float) zu casten:

Habe nun eine Funktion für das Wandeln geschrieben. Geht sehr einfach.
1
float long_to_float32(long lValue)
2
  {
3
  float uiReturnvalue;
4
5
  uiReturnvalue = lValue;
6
7
  return (uiReturnvalue);
8
  }

von Matthias (Gast)


Lesenswert?

So einfach geht es doch nicht. Meine Funktion zur KNX-Übertragung 
verwendet für die 4 Datenbyte grundsätzlich unsigned long. Daher wird 
das Format vom Compiler wieder zurück gewandelt. Anstelle der 0x447A0000 
(1000 als float32) werden 0x000003E8 (1000 als unsigned long) 
übertragen.

Wie kann ich 0x447A0000 float in 0x447A0000 unsigned long wandeln?
Habe schon versucht, den Float32 Wert in 4 einzelne Bytes zu zerlegen, 
um die dann wieder als unsigned long Wert wieder zusammen zu bauen. Aber 
der Compiler will das Zerlegen nicht machen:
1
float fTemp;
2
char element[4];
3
element[0] = (fTemp & 0x000000FF) >> 0;
4
element[1] = (fTemp & 0x0000FF00) >> 8;
5
element[2] = (fTemp & 0x0000FF00) >> 16;
6
element[3] = (fTemp & 0x0000FF00) >> 24;

Die Profi-Programmierer werden schon die Kriese bei der Frage 
bekommen...

von Uwe (Gast)


Lesenswert?

Lothar M. schrieb:
> Bei passender Bytereihenfolge/Endian reicht es auch, einen Pointer auf
> das erste Element zu setzen und den auf (float) zu casten:char cval[4];
> :
> :
> float fval = *(float*)cval;

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Matthias schrieb:
> So einfach geht es doch nicht.
Ach.
1
memcpy(element, &float, 4);
 :-)

Gruss
WK

von Matthias (Gast)


Lesenswert?

Danke. es geht nun!
1
unsigned long long_to_float32(long lValue)
2
{
3
char element[4];
4
float fTemp;
5
unsigned long ulReturnvalue;
6
7
fTemp = lValue;      // Wandlung in Float
8
9
memcpy(element, &fTemp, 4);  // die 4 Byte in Array kopieren
10
11
ulReturnvalue = 0x1000000*(unsigned long)element[3] + 0x10000*(unsigned long)element[2] + 0x100*(unsigned long)element[1] + (unsigned long)element[0];
12
13
return (ulReturnvalue);
14
}

von Jemand (Gast)


Lesenswert?

Lothar M. schrieb:
> Bei passender Bytereihenfolge/Endian reicht es auch, einen Pointer
> auf
> das erste Element zu setzen und den auf (float) zu casten:char cval[4];
> :
> :
> float fval = *(float*)cval;

Äußerst unsportlich, dabei nicht auf den Haken mit strict aliasing 
hizuweisen.

von W.S. (Gast)


Lesenswert?

Dergute W. schrieb:
> Yupp. Und das Prinzip ist: Bloss nicht selber in
> Floatingpointverarbeitung rumruehren, wenns nicht unbedingt sein muss.

Und das verstehst du als "Verstehen des Prinzips"?

Hmm..
Naja, wenn ich mir all die z.T. abstrusen Verrenkungen anschaue, die 
diverse C-Programmierer hier gepostet haben, um irgendwelche int oder 
long Zahlen nach float zu wandeln (ohne das anscheinend spezielle 
KNX-Gleitkommaformat zu kennen), dann graust es mich.

Ich hätte hier noch eine selbstgeschriebene kleine GK-Arithmetik incl. 
Ausgabekonverter für die PIC16 auf Lager - selbstverständlich in 
Assembler geschrieben, aber die habe ich bereits vor Zeiten hier mal in 
einem anderen Projekt gepostet.

W.S.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

W.S. schrieb:
> (ohne das anscheinend spezielle
> KNX-Gleitkommaformat zu kennen), dann graust es mich.

Was ist denn daran speziell? Wenn ich nach KNX und 32bit float google, 
dann springt mich von ueberall IEEE754 an...

Gruss
WK

von W.S. (Gast)


Angehängte Dateien:

Lesenswert?

Dergute W. schrieb:
> Was ist denn daran speziell?

Weiß ich nicht, hab mich dafür noch nicht interessiert.

Ansonsten ist das IEEE754 Format recht einfach zu verstehen, siehe Bild. 
Was mich damals bei den PIC16 daran gestört hatte, war die notwendige 
Schieberei mit dem Exponenten, die sich bei den PICs nicht besonders gut 
macht - und die für Konvertierungen auf dem PIC16 ungünstige 
Mantissengröße 1.0 bis 1.999usw. Von daher hatte ich das Vorzeichenbit 
direkt an die Stelle des MSB der Mantisse gesetzt und den Wertebereich 
der Mantisse auf 0.5 bis 0.999usw. gesetzt.

W.S.

von Dieter H. (kyblord)


Lesenswert?

Dergute W. schrieb:
> Moin,
>
> Matthias schrieb:
>> So einfach geht es doch nicht.
> Ach.
> memcpy(element, &float, 4);
>  :-)
>
> Gruss
> WK

tja, wenn man nur von vorne rein wüsste was der TO machen will^^

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.