[C] Datatype met meerdere types

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • conara
  • Registratie: Februari 2010
  • Nu online
Ik ben bezig met een modbus implementatie, maar ik loop vast op het implementeren van meerdere datatypes. Met normale integers werkt het goed, maar met doubles krijg ik het nog niet helemaal lekker werkend.

Eerst wat algemene info: Ik gebruik de lwIP stack met de netconn API. Momenteel stel ik op de volgende manier het modbus bericht samen. (Een simpel voorbeeld om het goed begrijpbaar te maken):

De API functie om het bericht te verzenden:

code:
1
err_t netconn_write ( struct netconn * aNetConn, const void * aData, size_t aSize, u8_t aApiFlags );


aNetConn : the netconn object the data is written to
aData : address of beginning of the data to write
aSize : the length of the data in bytes
aApiFlags : settings

bron: http://lwip.wikia.com/wiki/Netconn_write

Ik maak nu een array aan met de grote van mijn hele modbus bericht. Een modbus bericht bestaat uit een aantal headervelden en een variabel aantal datavelden. Bijvoorbeeld:

code:
1
uint8_t response[13]; // response modbus message


uint8_t is gekozen omdat het kleinste "veld" een byte (8-bit) is. De velden kan ik dan eenvoudig vullen met data. Daarnaast kan ik bijvoorbeeld een 32 bit waarde ook eenvoudig verzenden door middel van een bitmask de juiste data in te laden. Echter gaat dit niet met een float getal omdat dit een heel ander type is.

Nu weet ik niet hoe ik dit werkend kan krijgen. Eigenlijk moet ik een soort type hebben die alle datatypes accepteert. Een soort void type oid, want aan de ontvangde kant moet het gewoon gecast worden naar het goede type (staat in de documentatie welk type het is).

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        uint32_t test = 500;


        response[0] = pcRxByte[0];  // Transaction indentifier wordt gekopieerd van het request bericht
    response[1] = pcRxByte[1];  // Transaction indentifier wordt gekopieerd van het request bericht
    response[2] = 0;                    // protocol indentifier -> modbus ( waarde 0 staat voor het modbus protocol )
    response[3] = 0;                    // protocol indentifier -> modbus ( waarde 0 staat voor het modbus protocol )
    response[4] = 0;                    // length following bytes (most significant bit)
    response[5] = 3 + 4;            // length following bytes -> 3 standaard bytes + variabel afhankelijk van de hoeveelheid data)
    response[6] = pcRxByte[6];  // unit indentifier wordt gekopieerd van het request bericht
    response[7] = pcRxByte[7];  // Function code wordt gekopieerd van het request bericht
    response[8] = 4;                            // Byte count                           
    response[9] = (test >> 24);          // most significant bit
    response[10] = (test >> 16);
    response[11] = (test >> 8);
    response[12] = (test >> 0);


Als ik dit verzend komt er keurig twee 16 bit waardes binnen via de modbus (modbus werkt namelijk met 16 bit registers, maar twee 16-bit registers kan samengevoegd worden tot een 32 bit register. Dit wil ik ook met een 32 bit float kunnen doen, maar dan gebeurd er dit:

code:
1
Error   9   invalid operands to binary >> (have 'float' and 'int')


Ik hoop dat het duidelijk is en dat iemand raad weet hoe ik dit kan aanpakken :)

Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Nee, je moet geen "type wat alle types is" hebben of zoiets geks. Je weet wat het type van alle velden is. Het probleem is dat je simpelweg de bytes uit je float moet kopieren naar de bytes in je bericht. Is je target platform big-endian net zoals modbus, dan ben je klaar. Zoniet, dan moet je de endian-ness wisselen.

Oh, `double` is geen 32 bits.

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!

  • jeroen3
  • Registratie: Mei 2010
  • Laatst online: 19:06
Je kunt unions gebruiken.
De endian is wel belangrijk. Op het platform waar je lwip draait is het waarschijnlijk little. Dat is meteen goed.
Echter op de pc werkt dit trucje niet, want die is big. Dus zul je moeten swappen (daar zijn instructies voor).

Om alle problematiek te voorkomen kun je misschien Q getallen gebruiken?
Die zijn mogelijk ook nog eens veel vriendelijker voor je platform.

[ Voor 10% gewijzigd door jeroen3 op 08-04-2015 22:23 ]


Acties:
  • 0 Henk 'm!

  • conara
  • Registratie: Februari 2010
  • Nu online
Bedankt voor de reacties.
Het probleem is dat je simpelweg de bytes uit je float moet kopieren naar de bytes in je bericht.
Eens. Hier had ik ook aangedacht, maar heb geen idee hoe ik dat kan doen. Ik moet het toch ergens tijdelijk in opslaan oid? Ik moet namelijk een pointer opgeven naar dat stuk data + de grote van het stuk data. Oftewel ik snap wat je bedoeld, maar zal niet weten hoe ik dat kan realiseren. Zou je misschien een klein voorbeeldje kunnen geven? (Op het AVR32 platform (AVR-GCC compiler) is een double 32bits (als ik het goed heb: https://gcc.gnu.org/wiki/avr-gcc)
jeroen3 schreef op woensdag 08 april 2015 @ 22:22:
Je kunt unions gebruiken.
De endian is wel belangrijk. Op het platform waar je lwip draait is het waarschijnlijk little. Dat is meteen goed.
Echter op de pc werkt dit trucje niet, want die is big. Dus zul je moeten swappen (daar zijn instructies voor).
Ik ben inderdaad bekend met unions (althans ik weet dat ze bestaan en wat het "is"). Het systeem is big endian (zie ook mijn code waar ik schuif), het is namelijk een AVR32 processor (UC3 A0512). Nu weet ik dat je met een union meerdere types kan opslaan, maar dan loop ik tegen het volgende aan:

Ik heb "stukjes header die 8 bits zijn (1 byte), maar als data ook een float, double, fixed point, integer (van 32 bits) ect. Dan heb ik deze union gemaakt:

code:
1
2
3
4
5
union {
  uint8_t byte;
  double floatingPoint;
  uint32_t integer;
}


Union is zo groot als het grootste element. Oftewel even groot als de double/uint32_t, dus 32 bits (4 bytes) (als we even aannemen dat de double ook echt 32 bits is). Als ik dan een array van die unions maak dan klopt het volgens mij niet als ik de eerste bytes ga invullen toch? Want wat gebeurd er met de ruimte die niet wordt gebruikt door de uint8_t? Wordt die gewoon kaal ingevuld?, want dan klopt de header niet meer. Dus ik probeer te zeggen dat een deel door de uint8_t in de union niet gebruikt wordt, omdat het een datatype van 1 byte is (en er dus 3 bytes leeg overblijven), maar deze zullen wel door de onderstaande functie worden verzonden:

code:
1
err_t netconn_write ( struct netconn * aNetConn, const void * aData, size_t aSize, u8_t aApiFlags );
Om alle problematiek te voorkomen kun je misschien Q getallen gebruiken?
Die zijn mogelijk ook nog eens veel vriendelijker voor je platform.
Dat zo inderdaad een goede andere mogelijkheid kunnen zijn. Als het niet gaat lukken met het double verhaal dan zal het een dergelijke oplossing worden.

EDIT:

Ik heb de union getest en inderdaad de overige (3 bytes) van de union worden ook verstuurd (in het geval van een uint8_t), waardoor het niet meer wordt herkent als modbus bericht in wireshark. (wat ook logisch is). onderstaande code is hoe ik de union gebruik:

code:
1
2
3
4
5
6
union modbusType
{
    uint8_t byte;
    double floatingPoint;
    uint32_t integer;
};



code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
union modbusType response[10] = {0}; // response modbus message


uint32_t test = 500;
response[0].byte = pcRxByte[0]; // Transaction indentifier wordt gekopieerd van het request bericht
response[1].byte = pcRxByte[1]; // Transaction indentifier wordt gekopieerd van het request bericht
response[2].byte = 0;                   // protocol indentifier -> modbus ( waarde 0 staat voor het modbus protocol )
response[3].byte = 0;                   // protocol indentifier -> modbus ( waarde 0 staat voor het modbus protocol )
response[4].byte = 0;                   // length following bytes (most significant bit)
response[5].byte = 3 + 4;           // length following bytes -> 3 standaard bytes + variabel afhankelijk van de hoeveelheid data)
response[6].byte = pcRxByte[6]; // unit indentifier wordt gekopieerd van het request bericht
response[7].byte = pcRxByte[7]; // Function code wordt gekopieerd van het request bericht
response[8].byte = 4;                           // Byte count
response[9].integer = test;          // most significant bit

[ Voor 28% gewijzigd door conara op 09-04-2015 09:40 ]


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:34

.oisyn

Moderator Devschuur®

Demotivational Speaker

conara schreef op donderdag 09 april 2015 @ 09:05:
Bedankt voor de reacties.


[...]


Eens. Hier had ik ook aangedacht, maar heb geen idee hoe ik dat kan doen. Ik moet het toch ergens tijdelijk in opslaan oid? Ik moet namelijk een pointer opgeven naar dat stuk data + de grote van het stuk data.
Het staat toch al in een variabele? Dus het is al opgeslagen. Een pointer daarnaar verkrijgen kan door simpelweg de unary &-operator te gebruiken. sizeof geeft je de grootte van het datatype in bytes.

Je zou dus gewoon memcpy() kunnen gebruiken, ware het niet dat je dan nog met het endianness probleem zit.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • conara
  • Registratie: Februari 2010
  • Nu online
Oke dus eigenlijk bedoel jij dit? De header stel je gewoon samen en verstuurd je en daarna schrijf je apart de data weg. Nu lijkt dat ideaal, had ik zelf niet aangedacht want dacht dat dat niet kon. Maar het blijkt ook niet te kunnen, want als ik dan in wireshark kijk staat alleen de header en niet de data. Ik denk dat alle data in een keer moet worden verzonden met die functie. Bedoel je dit?

code:
1
2
3
4
5
6
7
uint8_t response[9];
uint32_t test = 500;

/* Modbus Header samenstellen */
                
netconn_write( pxNetCon, response, (u16_t) 9, NETCONN_COPY );
netconn_write( pxNetCon, &test, (u16_t) 4, NETCONN_COPY );


Oftewel alle data moet wel achter elkaar staan en daarna verzonden worden met de API functie.

[ Voor 4% gewijzigd door conara op 09-04-2015 10:28 ]


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:34

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nou ik bedoelde eigenljik dit:
C:
1
memcpy(response + 9, &test, sizeof(test));

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • conara
  • Registratie: Februari 2010
  • Nu online
Ah nu begrijp ik het. Inderdaad dit is eigenlijk de oplossing die ik zocht :) Ik begrijp alleen niet wat je bedoeld met het "endianness" probleem. Modbus gebruikt de big endian "notatie" en mijn microcontroller ook. Als ik via wireshark naar mijn modbus bericht kijk staat het ook gewoon in de goede volgorde.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:34

.oisyn

Moderator Devschuur®

Demotivational Speaker

Als je nooits iets met de data wilt doen op een little-endian platform zoals de PC dan is er geen probleem idd en kun je naar hartelust data kopiëren.

Een andere mogelijkheid is overigens om geen uint8 buffer te gebruiken maar structs voor verschillende messages. Ik ken het modbus protocol verder niet dus dit voorbeeld is gedestilleerd uit jouw comments

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct ModbusHeader
{
    uint16_t transactionId;
    uint16_t protocolId;
    uint16_t length;
    uint8_t unitIdentifier;
    uint8_t functionCode;
    uint8_t byteCount;
};

struct MyModbudMessage
{
    ModbusHeader header;
    float myFloat;
};

MyModbusMessage message;
/* ... vul message ... */
netconn_write( pxNetCon, &message, sizeof(message), NETCONN_COPY );


Je moet wel even goed zorgen dat je data alignment uitzet, anders is ModbusHeader 10 bytes groot ipv 9, en zal er ook nog eens 2 bytes padding zitten tussen de header en myFloat in MyModbusMessage. Dat kan niet op een standaard-manier, dus dat is afhankelijk van je compiler (__attribute__((packed)) in GCC geloof ik)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • conara
  • Registratie: Februari 2010
  • Nu online
Dat is inderdaad een mooiere en duidelijkere manier. De MBPA Header (Modbus header) is altijd hetzelfde qua datatypes. Echter kan de data op de volgende manieren verschillen:

- Het datatype kan verschillen afhankelijk van het modbus request bericht. 16 bit integer, 32 bit integer, bool of float (in theorie is elk type mogelijk).
- De lengte van de data kan verschillen. Met het modbus protocol kan je namelijk in een keer meerdere "datavelden" (modbus noemt het registers) opvragen. Dus de ene keer kan moet je een 32 bit integer terug geven en de andere keer 10 float's.

Nu zit er (gelukkig) een maximum aan het aantal velden dat je in een keer kan opvragen, namelijk maximaal 125 "datavelden" (registers). Een dataveld is 2 bytes (16 bits). Voor de oplettende lezers inderdaad een 4 byte datatype neemt 2 "datavelden" (registers) in.

Oftewel ik zit zelf aan een dergelijke oplossing te denken:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ModbusHeader
{
    uint16_t transactionId;
    uint16_t protocolId;
    uint16_t length;
    uint8_t unitIdentifier;
    uint8_t functionCode;
    uint8_t byteCount;
};

struct MyModbudMessage
{
    ModbusHeader header;
    uint16_t  data[125];
};


Oftewel er wordt altijd genoeg ruimte gereserveerd (d.m.v. een array) en dat wordt dan ingevuld met de door jou (.oisyn) voorgestelde methode (memcpy(response + 9, &test, sizeof(test));). Ik moet dan wel zelf bijhouden hoe groot het bericht is, want kan dan natuurlijk niet sizeof gebruiken. In principe is dat geen probleem omdat ik toch die velden moet invullen en dus weet hoe groot het bericht wordt.

Een goede oplossing of zijn er betere / slimmere oplossingen waar jullie aandenken.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:34

.oisyn

Moderator Devschuur®

Demotivational Speaker

Het idee is dat je gewoon verschillende structs maakt voor verschillende data. MyModbusMessage is maar 1 specifieke message - eentje met 1 int. Je kunt er ook nog een maken met 2 floats en eentje voor een char en een short en weetiknietwat. De header zit in een aparte struct omdat die door alle structs gedeeld wordt.

[ Voor 24% gewijzigd door .oisyn op 09-04-2015 14:42 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 14-10 00:53
Let wel dat Modbus geen specificatie heeft voor 32 bits dataypes (en floats), dus afhankelijk van de gebruikte implementatie aan de andere kant kun je fout zitten met je endianess. De floats die ik ben tegengekomen waren allemaal IEEE 754, maar ook dat zou kunnen verschillen tussen implementaties.

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!

  • conara
  • Registratie: Februari 2010
  • Nu online
Bedankt voor de nuttige reacties!
Pagina: 1