Float naar byte en omgekeerd SPI

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Hoi Tweakers,

Achtergrondinfo

Ik ben zelf een quadcopter aan het maken. Als microprocessor gebruik ik de FEZ Panda 2, daar kan je C# op plaatsen met NetMF. Voor de IMU gebruik ik de Mongoose, die draait op een Arduino chip. Standaard geeft de IMU serieel heel vaak de graden voor pitch, roll, yaw, pressure en alle raw sensorwaarden door als een string. Dat is irritant, want mijn microprocessor komt nu om met events en strings die ik moet parsen en dat is duur. Ook triggert dat de garbage collector nogal vaak.

Wat ik wil doen

Ik wil de firmware van de IMU aanpassen. Nu stuurt hij de data met een noodgang over de COM poort, dat wil ik omzetten naar SPI. Ik wil het zo maken dat mijn microprocessor via SPI een aanvraag doet naar de slave en dat de slave (IMU) op dat moment de actuele waarde terug geeft.

Wat wil ik doorsturen?
Via SPI stuur je bytes door. Ik heb alleen interesse in de pitch, yaw, roll and pressure waarde. De eerste drie zijn positief of negatief en lopen van maximaal -180 tot 180. Ik heb alleen interesse in de eerste 2 decimalen achter de komma.

Ik wil geen string meer doorsturen. Ik zat te denken om de eerste byte te nemen zodat ik 8 bitjes heb die ik op true/false kan zetten voor "positief" of "negatief" getal. Gevolgd door steeds 2 bytes voor pitch, roll en yaw waarbij de eerste de gehele waarde is en de tweede de eerste 2 decimalen achter de komma. Door het eerste byte weet ik of dat dan een positieve of negatieve waarde betreft.

Ik zou zo in 7 bytes de pitch, roll en yaw waarde door kunnen geven. Momenteel kost één waarde al 10 bytes. De pressure waarde is nog even irritanter, die kan veel groter worden dan 256, dus ik wil daar 2 bytes voor misbruiken. Totaal komt dan nog steeds op 9 bytes uit, veel minder dan de bijna 160 bytes die ik nu elke keer krijg.

Wat is dan mijn probleem / vraag?
Op de Arduino zijn deze waarde allemaal float waarden. Ik zoek een manier om daar een float om te zetten in 2 bytes, één voor de gehele waarde en één voor de 2 decimalen achter de komma. En dan eigenlijk ook nog een manier om de positieve/negatieve indicatie op een bepaalde bit positie in een byte weg te schrijven.

Vervolgens krijg ik deze byte array door in C#. Daar zal ik dan op basis van 1 bitje en 2 byte waarde moeten converteren naar een double (eerste byte=geheel getal, tweede byte=eerste 2 decimalen) zonder gebruik te maken van string parsing, want dat is nogal inefficient.

Iemand enig idee hoe je zoiets zou kunnen bereiken?

[ Voor 1% gewijzigd door Verwijderd op 16-03-2012 08:43 . Reden: Foutje ]


Acties:
  • 0 Henk 'm!

  • Mijzelf
  • Registratie: September 2004
  • Niet online
Je kunt je float waarden gewoon met 100 vermenigvuldigen, dit casten naar een signed short, en die waarde naar je twee bytes schrijven.
Aan de andere kant kun je de 2 bytes weer in een signed short stoppen, en deze door 100.0 delen om een double te krijgen.
Je bereik loopt dan van -(2^15/100) to ((2^15-1)/100), oftewel van -327.68 tot 327.67
Je array met 'sign' bits zijn overbodig.

Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11-09 20:27

Matis

Rubber Rocket

Waarom niet gewoon netjes de float als 4 bytes versturen en aan de andere kant weer van die 4 bytes naar een float gaan? Zoals de IEEE 754 voorschrijft

Iets in de trant van (aan de verzendende kant)
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void put_float(uint8_t /*@out@*/*data, float val)
{
  uint8_t *tmp = (uint8_t *)&val;
#if __BYTE_ORDER == __BIG_ENDIAN
  data[0] = tmp[0];
  data[1] = tmp[1];
  data[2] = tmp[2];
  data[3] = tmp[3];
#else
  data[0] = tmp[3];
  data[1] = tmp[2];
  data[2] = tmp[1];
  data[3] = tmp[0];
#endif
}

en (ontvangende kant)
C:
1
2
3
4
5
6
7
8
9
10
11
12
float get_float(uint8_t /*@in@*/*data)
{
  float res;
#if __BYTE_ORDER == __BIG_ENDIAN
  uint8_t tmp[] = { data[0], data[1], data[2], data[3] };
  res = *((float *)tmp);
#else
  uint8_t tmp[] = { data[3], data[2], data[1], data[0] };
  res = *((float *)tmp);
#endif
  return res;
}

[ Voor 31% gewijzigd door Matis op 16-03-2012 10:01 ]

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Jee, beide bedankt voor de sneller reacties!

@Mijzelf, geniaal... ik snap het nog niet helemaal, maar zal eens kijken of het lukt. Hoe zou je bijvoorbeeld die signed short van/naar 2 bytes moeten schrijven? Ik hoop dat de signed short bij de Arduino ook kennen, dat platform ken ik al helemaal niet.

@Matis, dat kost mij 4 bytes per waarde en dat is het dubbele van de 2 bytes. De IEEE 754 kan nog zoveel voorschrijven, maar als het in 2 bytes kan dan heb ik een snellere data doorvoer (er van uit gaande dat de casting niet extra vertraging gaat geven) en leg ik advies van de IEEE 754 graag naast mij neer. Normaal zou ik die paar bytes niet erg vinden, maar in deze pid loop kan het mij niet snel genoeg gaan allemaal.

Acties:
  • 0 Henk 'm!

  • Mijzelf
  • Registratie: September 2004
  • Niet online
Verwijderd schreef op vrijdag 16 maart 2012 @ 10:07:
Hoe zou je bijvoorbeeld die signed short van/naar 2 bytes moeten schrijven?
Dezelfde truuk als Matis gebruikt:
C:
1
2
3
4
5
signed short value = (signed short)(floatvalue * 100);
char *pbytevalue = (char *)&value;

byte1 = pbytevalue[ 0 ];
byte2 = pbytevalue[ 1 ];
Ik hoop dat de signed short bij de Arduino ook kennen, dat platform ken ik al helemaal niet.
Het is standaard C. Je mag de 'signed' overigens weglaten, by mijn weten is een short standaard signed.

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 03:22
In plaats van met 100, kun je ook met 182 (of 65536/360) vermenigvuldigen zodat je maximale informatie uit je 16 bits waarde haalt.

Of anders tenminste 128, zodat je de conversie van/naar float kunt doen met een bitshift of een float vermenigvuldiging, in plaats van met een relatief kostbare deling.
Mijzelf schreef op vrijdag 16 maart 2012 @ 10:21:
Dezelfde truuk als Matis gebruikt: [..]
Of zonder type-punning:
C:
1
2
byte1 = (value >> 0)&0xff;
byte2 = (value >> 8)&0xff;

[ Voor 29% gewijzigd door Soultaker op 16-03-2012 15:21 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Verwijderd schreef op vrijdag 16 maart 2012 @ 10:07:
@Matis, dat kost mij 4 bytes per waarde en dat is het dubbele van de 2 bytes. De IEEE 754 kan nog zoveel voorschrijven, maar als het in 2 bytes kan dan heb ik een snellere data doorvoer (er van uit gaande dat de casting niet extra vertraging gaat geven) en leg ik advies van de IEEE 754 graag naast mij neer. Normaal zou ik die paar bytes niet erg vinden, maar in deze pid loop kan het mij niet snel genoeg gaan allemaal.
Als het je niet snel genoeg kan gaan waarom pak je dan een platform met garbage collector *kuch*? En waarom zou het overstappen van UART naar SPI daar enig positief invloed op hebben als je software de zwakste schakel is?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Tnx!

@Mijzelf, dit lijkt te werken, geweldig, dank voor de hulp!

@Soultaker, Het werkt inderdaad ook zonder type punning. Ik weet niet of ik het principe van het delen door 182 / 128 begrijp omdat ik die 2 decimalen wil behouden. Ik moet volgens mij dan toch altijd nog delen?

@Farlane, hij draait wel op 72Mhz, dat is een stuk sneller dan de meeste Arduino chips. Daarbij draait het C# wat ik al ken. Ik wil er later een hoop andere dingen op aansluiten.

De mongoose stuurt standaard over UART een hele lange tekst string en doet dat continu. Via SPI heb ik er nu een slave van gemaakt die alleen de benodigde bytes stuurt op aanvraag. Juist omdat ik te maken heb met een garbage collector heb ik in de overige code nog wel wat te compenseren. Je hebt wel gelijk, in plaats van SPI zou ik het waarschijnlijk net zo goed over UART aan kunnen passen maar ik dacht dat SPI sneller was met master/client.

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 03:22
Verwijderd schreef op zaterdag 17 maart 2012 @ 14:51:
@Soultaker, Het werkt inderdaad ook zonder type punning. Ik weet niet of ik het principe van het delen door 182 / 128 begrijp omdat ik die 2 decimalen wil behouden. Ik moet volgens mij dan toch altijd nog delen?
Aangenomen dat je uiteindelijk naar een float wil converteren (die binair is en niet aan "decimalen" doet) geeft 182 je meer precisie zonder dat het je echt iets kost.

12.345 wordt bijvoorbeeld (ongeveer) 12.35 als je vermenigvuldigt met 100, afrondt, en later weer deelt door 100. Maar als je 182 gebruikt in plaats van 100 kom je op ongeveer 12.346153 uit, wat een minder rond getal lijkt, maar wel dichter bij oorspronkelijke waarde ligt. (Sowieso kun je een getal als 12.35 nooit exact representeren in een binair floating point getal).

Het voordeel van 128 = 27 ten op zichte van 100 of 182 is dat je delingen/vermenigvuldigingen kunt elimineren, wat vooral nuttig is als de operatie (honderd)duizenden keren per seconde uitgevoerd moet worden op een processor zonder hardware FPU.

Bijvoorbeeld zo:
C:
1
2
3
4
int16_t value = byte1 | byte2<<8;
union fi { float f; int32_t i; } fi = { value } ;
if (value) fi.i -= 7 << 23;
printf("%f\n", fi.f);

... maar wellicht is dat momenteel wat te hoog gegrepen, en ik weet ook niet of je zó veel data uitwisselt dat deze overhead merkbaar is. (Ook als je een fixed point representatie gebruikt is een macht van twee als deler handig.)

[ Voor 14% gewijzigd door Soultaker op 17-03-2012 15:34 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Verwijderd schreef op zaterdag 17 maart 2012 @ 14:51:
@Farlane, hij draait wel op 72Mhz, dat is een stuk sneller dan de meeste Arduino chips. Daarbij draait het C# wat ik al ken. Ik wil er later een hoop andere dingen op aansluiten.

De mongoose stuurt standaard over UART een hele lange tekst string en doet dat continu. Via SPI heb ik er nu een slave van gemaakt die alleen de benodigde bytes stuurt op aanvraag. Juist omdat ik te maken heb met een garbage collector heb ik in de overige code nog wel wat te compenseren. Je hebt wel gelijk, in plaats van SPI zou ik het waarschijnlijk net zo goed over UART aan kunnen passen maar ik dacht dat SPI sneller was met master/client.
SPI is iha op de gangbare uP's sneller dan een UART (alhoewel die ook meestal naar meerdere MBits kunnen ) maar er is ook geen enkele vorm van controle op dataintegriteit oid, die zou je er eigenlijk zelf nog omheen moeten bouwen.

Maar eigenlijk bedoel ik te zeggen dat als je het systeem tot het uiterste wilt beproeven ( en je geeft zelf al aan dat dit nog lang niet het eind is ), je geen garbage collected platform moet gaan gebruiken (want dat is eigenlijk de bottleneck hier ) maar een die dichter op de machine zelf zit, wat in de meeste gevallen betekent dat je lekker C mag gaan coden.

Het zal een investering zijn om omdat je een nieuw platform moet leren, maar daarna ben je veel beter in staat om voor dit soort platformen oplossingen te maken.

Garbage collectors zijn overrated overigens.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 00:05
Afgezien van het feit dat elk zinnig ontwerp niet eens garbage collect. De correcte oplossing: hergebruik je variabelen. Desnoods maak je d'r global variabelen van.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • Killemov
  • Registratie: Januari 2000
  • Laatst online: 24-08 23:40

Killemov

Ik zoek nog een mooi icooi =)

Als je echt snel wil en je hoeft het niet zo nauw te nemen met de precisie, dan kun je ook old-skool een cirkel van 256 graden aanhouden ipv 360. >:)

Hey ... maar dan heb je ook wat!


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Matis schreef op vrijdag 16 maart 2012 @ 09:56:
en (ontvangende kant)
C:
1
2
3
4
5
6
7
8
9
10
11
12
float get_float(uint8_t /*@in@*/*data)
{
  float res;
#if __BYTE_ORDER == __BIG_ENDIAN
  uint8_t tmp[] = { data[0], data[1], data[2], data[3] };
  res = *((float *)tmp);
#else
  uint8_t tmp[] = { data[3], data[2], data[1], data[0] };
  res = *((float *)tmp);
#endif
  return res;
}
Volgens mij gaat deze code zorgen voor een HardFault op de ARM9 aangezien deze vereist dat types op multiples van hun eigen grootte gealigned zijn.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 03:22
Grote kans dat de tmp-array dat toevallig ook is, maar je hebt gelijk, daar kun je niet zomaar vanuit gaan.

Je kunt beter memcpy() of een union gebruiken, hoewel je dan nog wel met je endianness zit (die ik in dit geval gewoon zou negeren als beide platforms dezelfde endianness hebben).

edit:
Weet je trouwens zeker dat je een "fault" hoort te krijgen? De ARM architectuur waar ik bekend mee ben roteert unaligned words (wat net zo goed niet het gewenste resultaat geeft, natuurlijk).

[ Voor 61% gewijzigd door Soultaker op 19-03-2012 22:22 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Soultaker schreef op maandag 19 maart 2012 @ 22:05:
edit:
Weet je trouwens zeker dat je een "fault" hoort te krijgen? De ARM architectuur waar ik bekend mee ben roteert unaligned words (wat net zo goed niet het gewenste resultaat geeft, natuurlijk).
Je hebt gelijk volgens mij; blijkbaar is het (optioneel?) genereren van een fault vanaf ARMv6

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11-09 20:27

Matis

Rubber Rocket

farlane schreef op maandag 19 maart 2012 @ 21:52:
Volgens mij gaat deze code zorgen voor een HardFault op de ARM9 aangezien deze vereist dat types op multiples van hun eigen grootte gealigned zijn.
Dat zou goed kunnen, wij draaien dit op een STM32 Cortex-M3 (ARMv7-M); Daar heb ik dit probleem nog nooit gezien.
Ik snap overigens niet echt goed wat je bedoelt met die opmerking, zowel data als tmp zijn uint8_t's en de float is toch 32 bits breed?

@soultaker, endianness is in dit geval wel een probleem. Immers is de uart (waar de float-waardes vandaan komen) little endian, waardoor je wel degelijk afhankelijk bent van je compiler / architectuur om de float juist te converteren.

[ Voor 19% gewijzigd door Matis op 20-03-2012 13:39 ]

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-09 12:01
Matis schreef op dinsdag 20 maart 2012 @ 13:37:
Dat zou goed kunnen, wij draaien dit op een STM32 Cortex-M3 (ARMv7-M); Daar heb ik dit probleem nog nooit gezien.
Ik snap overigens niet echt goed wat je bedoelt met die opmerking, zowel data als tmp zijn uint8_t's en de float is toch 32 bits breed?
Ook de ARMv7-M heeft deze restricties, alhoewel ze wel wat relaxed zijn tov oudere generaties meen ik. Het probleem met dit soort dingen is echter dat je ze wel of niet tegenkomt, afhankelijk van waar de linker je variabelen plaatst.

Btw, het probleem ontstaat als je de float adresseert adhv een variabele die 8 bits breed is : het is niet gegarandeerd (in deze vorm) dat dat byte op een 32 bits grens is geplaatst.

Bv:
C:
1
2
3
4
5
struct t
{
    uint8_t p1;
    uint32_t p2;
} __attribute__((packed));

Adresseren van p2 kan problemen veroorzaken, als de compiler/linker niet speciale maatregelen treffen: in dit geval zal adresseren van de uint32_t waarschijnlijk worden vertaald als een aantal 8bits loads en shifts ipv een 32bits load.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 03:22
Matis schreef op dinsdag 20 maart 2012 @ 13:37:
Immers is de uart (waar de float-waardes vandaan komen) little endian, waardoor je wel degelijk afhankelijk bent van je compiler / architectuur om de float juist te converteren.
Dat is mijn punt: bijna alle architecturen zijn tegenwoordig little endian, dus als de verzender en ontvanger dezelfde endianness gebruiken en je toch maar wat aan het hobbyen bent, kun je pragmatisch zijn en er gewoon vanuit gaan dat het floating point formaat aan bijde kanten hetzelfde is (jij was notabene de eerste die voorstelde om dat te doen ;)).

Maar zoals gezegd heb je geen aparte code nodig afhankelijk van de endianness van je platform; als je de conversie doet zonder type punning of andere dubieuze hacks kun je gewoon gebruik maken van het feit dat endianness van ints en floats doorgaans hetzelfde is:
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void float_to_bytes(float f, uint8_t b[4])
{
    union fu { float f; uint32_t u; } fu = { f };
    b[0] = fu.u >> 24;
    b[1] = fu.u >> 16;
    b[2] = fu.u >>  8;
    b[3] = fu.u >>  0;
}

void bytes_to_float(const uint8_t b[4], float *f)
{
    union uf { uint32_t u; float f; } uf = { b[0] << 24
                                           | b[1] << 16
                                           | b[2] <<  8
                                           | b[3] <<  0 };
    *f = uf.f;
}

Dit werkt net zo goed op big-endian en little-endian en PDP-endian platforms (die hopelijk helemaal niet meer bestaan) en slaat de waarden altijd als big-endian op in de byte array.

Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Die ARM7 heeft toch helemaal geen FPU, dus waarom niet heel de zooi meteen naar fixed-point omzetten en zo versturen? Als alle cycles tellen moet je niet in software floats gaan zitten rekenen.

Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 11-09 20:27

Matis

Rubber Rocket

PrisonerOfPain schreef op dinsdag 20 maart 2012 @ 20:08:
Die ARM7 heeft toch helemaal geen FPU, dus waarom niet heel de zooi meteen naar fixed-point omzetten en zo versturen? Als alle cycles tellen moet je niet in software floats gaan zitten rekenen.
De ARM7 heeft inderdaad geen FPU, maar dat staat los van het probleem van de TS. Het was meer een side-branch van het wel (dan wel niet) type punnen van floats naar bytes in een big / little endian conversie.

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Matis schreef op dinsdag 20 maart 2012 @ 21:32:
[...]

De ARM7 heeft inderdaad geen FPU, maar dat staat los van het probleem van de TS. Het was meer een side-branch van het wel (dan wel niet) type punnen van floats naar bytes in een big / little endian conversie.
De TS vraagt praktisch letterlijk om fixed point (iets waarbij geen type punning bij komt kijken, hoogstens een BE/LE conversie op het moment van versturen). TS vroeg namelijk om, efficienter en kleiner. Die graden kun je makkelijk in een uint16_t krijgen met nog genoeg precisie over voor de decimals die 'ie ook wil hebben.

C:
1
2
3
4
5
6
float degrees = 188.f; 
float fp_scale = 1 << 7; // 9:7 fixed point format 
uint16_t idegrees = (uint16_t)(degrees * fp_scale); 
uint16_t indegrees = htons(idegrees); 

send(sock, &indegrees, sizeof(uint16_t), 0);


Client:
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint16_t indegrees;
recv(sock, &indegrees, sizeof(uint16_t), 0);


// misschien naar 32 bit omzetten omdat je ARM7 toch een 32 bit alu heeft 
// en je dan meer ruimte hebt om te overflowen tijdens je fixed-point operaties 
idegrees = ntohs(indegrees); 

// increment by 0.5 degrees 
idegrees += 1 << 6; 

// increment by 1 deg 
idegrees += 1 << 7; 

// divide by 2 
idegrees >>= 1; 

// convert to float for display  
const float inv_fp_scale = 1.f / (1 << 7); 
degrees = idegrees * inv_fp_scale;
Pagina: 1