c++ stm32

PIC, AVR, Arduino, Raspberry Pi, Basic Stamp, PLC mm.
dangraf
Inlägg: 530
Blev medlem: 9 juni 2003, 15:30:56
Ort: göteborg

c++ stm32

Inlägg av dangraf »

jag håller på och skriver lite kod i c++ för min microkontroller. Det finns vissa saker jag gärna skulle vilja göra men vet inte riktigt hur jag ska nå dit.

Det hela handlar om en meddelande pool där man har en gemensam buffer där man lägger sina meddelanden. Därefter har man "användare" som håller koll på vilket det senaste meddelandet var som man läst och kan på så sätt hålla koll på antal olästa meddelanden var för sig.

Det har blivit en hel del kod och jag tror inte att någon orkar läsa genom mina filer så jag tänkte pressentera en förenklad variant.

meddelandet ser ung ut såhär:

Kod: Markera allt

typedef struct
{
   uint8_t type;
   uint8_t size;
   uint8_t payload[8];
}msgPacketBase_t

det finns en basklass för meddelandet:

Kod: Markera allt

class msgBase
{
   void headerToString(uint8_t *outStr, uint32_t strSize);
   virtual void payloadToString(uint8_t *outStr, uint32_t strSize);   // skriver ut payload som rena hex-värden
   void convertBaseToDerived(void);
}
samt olika typer av meddelanden som t.ex ett meddelande som innehåller tiden

Kod: Markera allt

class msgTime: public msgBase
{
   void payloadToString(uint8_t *outStr, uint32_t strSize);      // skriver ut payload som en tolkning av tiden.
}
såhär långt är allt frid och fröjd, skapar jag en klass av t.ex msgTime och kallar på headerToString kallas funktionen från basklassen medan om jag kallar på payloadToString kallas den specifika funktionen för msgTime som skriver ut tiden på ett snyggt sätt.

Det jag skulle vilja göra är att kunna omvandla sin bas-klass till en specifik typ av klass.

Kod: Markera allt

void printPayload( msgPacketBase_t *ptr)
{
  msgBase myMsg(*ptr);
  myMsg.convertBaseToDerived();
  pritf("%s", myMsg.payloadToString();
}
Som jag förstått det så innehåller en klass dels informationen från de variabler som är deklarerade inom klassen + en pekare (eller fler) till vilka funktioner som klassen ska använda. Så det jag egentligen skulle vilja göra är att ändra denna pekare från basklass till i detta fall msgTime klass.

Kod: Markera allt

 this = <static_cast>(msgTime*)this;
jag har testat varianter av detta men antingen ger det kompilerings-fel eller så kallas fortfarande funktionerna från basklassen fastän man inom funktionen gjort en cast till det msgTime och returnerar pekaren som en typ av msgBase. Dynamic-cast går inte att använda, ger kompleringsfel eftersom det är en bantad variant av c++. (codesourcery för cortex m3)

går det att skapa en ny klass på samma adress som den gamla klassen?
eller finns det något smidigare sätt att ha en funktion i bas-klassen som kan omvandla sig själv till lämplig typ av meddelande?

ett annat alternativ skulle kunna vara att låta bufferten innehålla klasser istället för meddelanden. men då skulle jag vilja skapa en klass direkt i bufferten utan att behöva göra en kopiering från en variabel till bufferten. Dessutom klagar kompilatorn på att en array av klasser behöver initieras, vilket jag i detta fallet inte är intresserad av. klasserna kommer initieras när man lägger till dem i bufferten.

Misstänker att detta är väldigt enklet, har sökt en del på polymorphism och derived classes men har inte hittat något vettigt ännnu.
Användarvisningsbild
fpvkiwi
Inlägg: 54
Blev medlem: 28 december 2010, 13:50:50

Re: c++ stm32

Inlägg av fpvkiwi »

Hej dangraf
Du vill alltså att rätt version av virtuella payloadToString() anropas, beroende på vilken subklass objektet tillhör. Detta borde funka fint med polymorfism tack vare RTTI (run-time type information) som kompilatorer brukar lägga till i objekt när detta behövs. Eftersom det inte verkar funka rakt av misstänker jag att Codesourcery skalat bort stödet för polymorfism, eller så har du slagit av stödet med nån flagga.
Som jag förstått det så innehåller en klass dels informationen från de variabler som är deklarerade inom klassen + en pekare (eller fler) till vilka funktioner som klassen ska använda.
Skilj på "klass" och "objekt". Klassinformation ligger på ett statiskt ställe i programmet och innehåller bl.a. en lista på polymorfa metoder (t.ex. payloadToString). Enskilda objekt innehåller data för alla variabler och en (gömd) pekare till klassinformationen.
this = <static_cast>(msgTime*)this;
Att tilldela 'this' är ingen bra ide. convertBaseToDerived() ger inte heller något. Man kan inte påverka typinformationen. Du kan skapa en ny variabel av typen msgTime* men övningen leder inte till något logiskt vettigt ändå. Om du redan vet att du vill konvertera till msgTime, varför hanterades objektet alls som msgBase? Om du inte får igång riktig polymorfism är det bättre att uttryckligen göra jobbet manuellt.
Typ:

Kod: Markera allt

#define TYPE_MSGTIME  1
if (msgPacketBase.type == TYPE_MSGTIME)
  msgTime_Print(msgPacketBase)
else
  ...
Precis, helt utan objektorientering. Men du hade inte så mycket nytta av OO ändå, eller hur... :)
Dessutom klagar kompilatorn på att en array av klasser behöver initieras, vilket jag i detta fallet inte är intresserad av. klasserna kommer initieras när man lägger till dem i bufferten.
(Igen; det är objekt du menar, inte klasser.) Det stämmer, för att deklarera en array av objekt behöver de initieras. Du kan initiera dem till ett dummy-värde eller låta arrayen innehålla pekare till objekt (ifall du har dynamiskt minne).

För att sammanfatta; om polymorfism finns tillgängligt ska det fungera rakt av. I annat fall är en alternativ design att föredra.
dangraf
Inlägg: 530
Blev medlem: 9 juni 2003, 15:30:56
Ort: göteborg

Re: c++ stm32

Inlägg av dangraf »

Grymt! någon som har lite koll :-) jag ber om ursäkt för att jag blandar ihop namnen klass och objekt, bra synpunkt!


jag misstänker att anledningen till att man inte kan göra casten

Kod: Markera allt

this = <static_cast>(msgTime*)this;
beror på"att this redan har en platts i minnet och att inte kan vara säker på att objektet msgTime tar lika stor platts.

att sätta en define flagga fungerar inte riktigt.
mitt exempel visar bara en typ av meddelande (msgTime) men jag har givetvis många olika typer av meddelanden där payloaden betyder olika beroende på vad det är för typ.

tanken är att en (av många ) funktioner tar emot ett meddelande. därefter vill jag skapa en klass beroende på vad meddelandet är för typ. Det jag vill undvika är att ha en switch-case sats i varje funktion som tar emot meddelanden, tänkte att det borde gå att lösa på något sätt mha basklassen.

som exempel:
Det kommer in ett meddelande från CAN bussen av typen msgTime
-Can modulen lägger meddelandet i meddelande-poolen.
-display-funktionen ser att det inkommit ett meddelande och skriver ut tiden på skärmen.
-Logg-funktionen ser att det inkommit ett meddelande och skriver ut meddelande i en fil på SD-kortet
-övervaknings funktionen kollar om det gått xx sek sedan förra meddelandet, annars skickas ett annat meddelande av typen varning som berättar att ett förväntat meddelande inte tagits emot.
- varnings meddelandet skulle isf även det loggats, och ev skrivits ut på displayen.

min tanke var att om man skapar ett objekt av bas-klassen läggs denna på stacken så att objektet försvinne om man avslutar funktionen.
Om jag börjar pillra med "new" och lägger saker på heapen är jag rädd för att det lätt kan smyga sig in fel så att jag t.ex glömmer av-allokera och fyller heapen av misstag. Dvs det känns mer "fail-safe" att lägga sker på stacken. kanske har jag fel?

finns det några bra alternativ till att lösa problemet?
Användarvisningsbild
fpvkiwi
Inlägg: 54
Blev medlem: 28 december 2010, 13:50:50

Re: c++ stm32

Inlägg av fpvkiwi »

Kul att för en gång skull kunna bidra med nått här. Jag lär mig fortfarande elektronik men koda har jag gjort i många år.
därefter vill jag skapa en klass beroende på vad meddelandet är för typ
Klassen skapar du när du skriver "class" i editorn. ;) Objektet skapas i run-time. Inte meningen att vara petig, men... nåja nu råkade jag vara petig iallafall.

"this" kan inte ändra värde, alltså vara föremål för tilldelning. Det är helt enkelt oklart vad betydelsen (semantiken) skulle vara. Även om vi bestämde effekten på olika mekanismer i språket vore detta inte intuitivt med OO-tänket i C++ och skulle skapa allmän förvirring.

Din ursprungliga kod definierade en struct msgPacketBase_t med uint8_t type. Olika värden på type betyder olika saker; därför föreslog jag en #define för att deklarera betydelsen för ett av värdena. (enum funkar också) Värden för de andra typerna deklareras förstås på liknande sätt.

Notera att med en global meddelandekö använder du inte stacken. Du vill ju inte att objektet försvinner när den skapande metoden returnerar, utan när meddelandet hanterats. Det betyder inte att du behöver använda "new": en statiskt allokerad kö kräver ju inte dynamiskt minne.

Runt designfrågan;

Den väg du är inne på bygger på att du på ett abstrakt plan vill hantera alla händelser på samma sätt: Allt ska skrivas ut till skärmen, allt ska skrivas ut till SD-kortet mm. Ok så, men om händelserna är av mer skiljda slag så kanske ett enda mönster blir svårt att anpassa. En metod handle() kanske är det enda gemensamma man kan säga om händelserna? Och vad är då vinsten med en gemensam klasshierarki? OO är lockande men man kan missa att det ibland (ofta) finns bättre lösningar.

För händelser som hanteras sekventiellt och sen kastas känns det onödigt att skapa en utförlig abstraktion med klasshierarki. Troligtvis är det mer jobb att hantera klasserna än du tjänar på att kunna anropa print() oavsett objekttyp.

Nu har jag inte helhetsbilden för systemet men jag vågar ändå gissa att en bättre lösning är en cirkulär kö av msgPacketBase_t. För att hantera meddelanden, titta på type och behandla payload därefter. Till priset av en 'case'-sats sparar du in en hel klass.
Så har jag själv löst motsvarande problem. (Med utökningen att varje 'type' bara använder så många bytes av kön som den behöver.)

Jag utgår från att du verkligen *behöver* en meddelandekö och det inte räcker att utföra alla effekter direkt. För då skulle du slippa hela problemet...
Skriv svar