char Puffer in eine Struktur casten



  • Hallo,

    ich habe folgendes Problem:

    ich lese Daten via serielle Schnittstelle ein. Das Paket besitzt eine konstante Länge mit 3 Sync Bytes am Anfang. Nur wenn diese übereinstimmen möchte ich den Datenstrom in eine Struktur mit verschiedenen Variablen casten. Nach ein paar Umrechnungen soll u.a. die Erdbeschleunigung herauskommen.
    Allerdings scheint es so, als ob es beim casten Probleme gibt, da in den Puffer auch Nullen eingelesen werden. Teilweise kommen utopisch große Werte heraus, -30000, + 25000 usw.
    Wenn ich diesen Puffer z.b. nach CString caste, schneidet er schön an ersten Null ab.

    Kann es sein, dass es so auch beim casten in die Struktur geschieht?
    Puffer ist deklariert als unsigned char...

    Grüße


  • Mod

    Wie sieht Deine Struktur aus. Weißt Du dass ein structure alignment gibt?

    Zeig uns Deine Struktur und evtl. musst Du pragma pack verwenden.



  • Das hab ich auch gerade gelesen, wusste ich vorher nicht. In C# funktioniert die Struktur, wohl wegen des "managed Codes"...

    typedef struct Struktur_Raw
    {
    	unsigned char SyncByte1;	
    	unsigned char SyncByte2;	
    	unsigned char SyncByte3;	
    	unsigned char MsgLength;
    	unsigned short MsgID;				
    	long Stat;					
    	long acc0;				
    	long acc1;			
    	long acc2;					
    }Struktur_RawDat;
    

    Edit:

    #pragma pack(1)
    typedef struct Struktur_Raw
    {
        unsigned char SyncByte1;   
        unsigned char SyncByte2;   
        unsigned char SyncByte3;   
        unsigned char MsgLength;
        unsigned short MsgID;              
        long Stat;                 
        long acc0;             
        long acc1;         
        long acc2;                 
    }Struktur_RawDat;
    

    mit dem #pragma pack(1) funktioniert es bei mir nicht, auch nicht mit __attribute__ ((packed))



  • Jemand noch eine Idee?



  • Zeig' uns mal deinen Cast.



  • hier:

    tsRead->m_Wnd->ComRead(szBuffer,22);		
    memcpy(tmpMsg,szBuffer,22);
    Struktur_RawDat *Dat;
    Dat = (Struktur_RawDat*)tmpMsg;
    


  • Und im szBuffer stehen die richtigen Werte, aber nach dem Cast in der Struktur nicht mehr? Sind denn wenigstens die ersten fünf Werte korrekt?
    Bedenke, daß in C# long immer 64bit groß ist - ist das bei deinem C++ long auch so?
    Ansonsten zeig mal die C#-Struktur.



  • Ich muss mich korrigieren. Es war keine C#-Struktur. Die Funktion zum Daten-Casten war in eine dll ausgelagert (C++). Das Programmvon dem es ausgerufen wird, ist in C# geschrieben.

    Und im szBuffer stehen die richtigen Werte, aber nach dem Cast in der Struktur nicht mehr?

    Ja, das ist definitiv so, warum auch immer. Manchmal klappts, dann kommt ca. 9,81 m/s² raus, meistens aber nicht... bin allmählich wirklich ratlos, zumal der Code in der dll auch meinem entspricht.



  • Wenn es manchmal klappt und manchmal nicht würde ich eher darauf tippen dass die Daten doch in einem anderen Format vorliegen als du erwartest und die die Message falsch interpretierst. 😉
    Oder sie kommt nicht komplett an, oder sowas in der Richtung.



  • Die Daten kommen vollständig an, und die Struktur ist korrekt... 😞

    Wie sieht es denn mit Nullen im Puffer aus? Ich weiß das eine Null eine Stringende-Kennung ist. Geht da vielleicht, je nach dem, ob eine Null drin steht oder nicht, was verloren?



  • Du baust irgendwo Mist, zeigst uns aber keinen Code. Deine Beschreibungen sind schwammig. Irgendwas passt irgendwo nicht, manchmal geht's manchmal kommen unsinnige Werte raus.
    Ich fürchte wir können dir nicht helfen.



  • Stutzpeter schrieb:

    Die Daten kommen vollständig an, und die Struktur ist korrekt... 😞

    Ja schon klar, aber nur weil sie vollständig sind heißt das noch lange nicht dass es auch die Daten sind die du erwartest. Wie gesagt, du hast irgendwo nicht deterministisches Verhalten. Und das würde ich auf den Input schieben, Computerprogramme sind immer deterministisch. Wenn du kein Multithreading und keine Zufallsgenratoren nutzt wird der Input auch immer gleich verarbeitet werden.
    Also ist entweder der Input anders als erwartet, oder du hast irgendwo undefinierted Verhalten in deinem Code. Zweiteres ließe sich recht einfach ausschließen.

    Stutzpeter schrieb:

    Wie sieht es denn mit Nullen im Puffer aus? Ich weiß das eine Null eine Stringende-Kennung ist. Geht da vielleicht, je nach dem, ob eine Null drin steht oder nicht, was verloren?



  • Also der Datenstrom, der ankommt, ist korrekt. Es gibt ein Referenztool, welches das Protokoll in die entsprechenden Variablen castet. Dort gibt es keine Sprünge oder ähnliches. Die Struktur wurde daraus 1:1 kopiert und es ist immer noch das selbe Gerät.

    Kurze Erläuterung zu meinem Code:
    1. Die vollständige Struktur des Imu-Protokolls + Variablen:

    #pragma pack(1)
    typedef struct Struktur_RawImuDaten
    {
    	unsigned char SyncByte1;	// 1 
    	unsigned char SyncByte2;	// 2
    	unsigned char SyncByte3;	// 3  
    	unsigned char MsgLength;	// 4  
    	unsigned short MsgID;		// 6  
    	unsigned short WeekNr;		// 8  
    	long Milliseconds;          // 12  
    	long GPSWeek;				// 16  
    	double SecondsWeek;			// 24  
    	long Stat;					// 28  
    	long acc0;					// 32  
    	long acc1;					// 36
    	long acc2;					// 40
    	long phi0;					// 44
    	long phi1;					// 48
    	long phi2;					// 52
    	long CRC;					// 56
    
    }Struktur_RawImuDaten;
    int phi0 = 7;       
    int phi1 = 7;       
    int phi2 = 7;       
    int acc0 = 7;       
    int acc1 = 7;       
    int acc2 = 7;       
    int OdoSpeedL = 7;  
    int OdoSpeedH = 7;  
    int OdoCounter = 7; 
    int Stat = 7;      
    int Error = 7;     
    int AgePhi = 7;      
    int AgeAcc = 7;
    

    2. Hier die Funktion zum Öffnen des Ports:

    int CserialtestDlg::ComOpen(CString csPort,int Baud,int Parity,int Stopbits,int Databits)
    {
    	static const int	iPMode[]={NOPARITY,EVENPARITY,ODDPARITY,SPACEPARITY,MARKPARITY};
    	static const int	iSMode[]={ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS,ONESTOPBIT};
    
    	COMMTIMEOUTS		sTo;
    	DCB					sDcb;
    
    	hCOMPORT= CreateFile(csPort,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
    	if(hCOMPORT==INVALID_HANDLE_VALUE)
    	{
    		hCOMPORT=0;
    		return 0;
    	}
    
    	memset(&sDcb,0,sizeof(sDcb));
    	sDcb.DCBlength=sizeof(sDcb);
    
    	sDcb.BaudRate     	= Baud;
    	sDcb.Parity       	= NOPARITY;
    	sDcb.StopBits     	= ONESTOPBIT;
    	sDcb.ByteSize     	= 8;
    
    	if(!SetCommState(hCOMPORT,&sDcb))
    	{
    		CloseHandle(hCOMPORT);
    		return 0;
    	}
    
    	sTo.ReadIntervalTimeout		   = MAXDWORD; 		
    	sTo.ReadTotalTimeoutMultiplier = 0;
    	sTo.ReadTotalTimeoutConstant   = 0;
    	sTo.WriteTotalTimeoutMultiplier= 0;
    	sTo.WriteTotalTimeoutConstant  = sTo.WriteTotalTimeoutMultiplier+1;
    
    	if(!SetCommTimeouts((HANDLE)hCOMPORT,&sTo))
    	{
    		CloseHandle(hCOMPORT);
    		return 0;
    	}
    
    	return 1;
    }
    

    3. Und letztlich verbinden und Thread starten:

    void CserialtestDlg::OnBnClickedOk()
    {
    	THREADSTRUCT *_param = new THREADSTRUCT;
    	_param->m_Wnd = this;
    
    	int	iLen,iKey;	
    
    	CString csBuffer;
    
    	if(!ComOpen(_T("COM3"), 230400,P_NONE,S_1BIT,D_8BIT))
    	{
    		MessageBox(_T("\nKann die Schnittstelle nich oeffnen !\n\n"), _T("Error"), MB_ICONERROR);	
    	}	
    	else
    	{
    		MessageBox(_T("Verbindung ok!"), _T("Juhu"), MB_ICONINFORMATION);	
    		bPortOpen = TRUE;
    
    	}
    	AfxBeginThread(Thread_Read, _param);
    }
    
    UINT CserialtestDlg::Thread_Read(LPVOID param)
    {
    	THREADSTRUCT* tsRead = (THREADSTRUCT*)param;
    
    	unsigned char Buffer[56];
    	memset(Buffer, 0, sizeof(Buffer));
    
    	unsigned char tmpMsg[56];
    	memset(tmpMsg, 0, sizeof(tmpMsg));
    
    	float fAcc0 = .0;
    	float m_ScaleFactorACC = (float)0.0000186264;
    	float m_ScaleFactorOMG = (float)720 / 2147483648 * 200;
    
    	CString csAcc0, csOut;
    
    	tsRead->m_Wnd->m_Edit_Out.SetWindowTextW(_T("Acc0\r\n"));
    
    	while(tsRead->m_Wnd->bPortOpen)
    	{	
    		tsRead->m_Wnd->ComRead(Buffer,56);
    
    		memcpy(tmpMsg,Buffer,56);
    		Struktur_RawImuDaten *ImuDat;
    		ImuDat = (Struktur_RawImuDaten*)tmpMsg;
    
    		if((ImuDat->SyncByte1 == 0xAA) && (ImuDat->SyncByte2 == 0x44) && (ImuDat->SyncByte3 == 0x13))
    		{
    			fAcc0 = ImuDat->acc0 * m_ScaleFactorACC;
    			csAcc0.Format(_T("%f"), fAcc0);
    
    			tsRead->m_Wnd->m_Edit_Out.GetWindowTextW(csOut);
    			csOut = csOut + csAcc0 + _T("\r\n");
    
    			tsRead->m_Wnd->m_Edit_Out.SetWindowTextW(csOut);
    
    			Sleep(50);
    		}			
    	}	
    	return 0;
    }
    


  • Blockiert ComRead(Buffer,56) so lange bis wirklich 56 Byte gelesen wurden?
    Wenn ja, wozu ist dann das Sleep(50) da?
    Wenn nein: wie kannst du erwarten dass dein Programm überhaupt jemals funktionieren kann?

    Und wo ist jetzt der erwähnte C# Teil?
    Oder ist das der Code der funktioniert?
    Oder ruft der C# Teil nur das C++ Programm als .exe auf, ohne Daten vom C++ Programm zurückzubekommen?

    Wir können halt nicht hellsehen.

    BTW: So wie du auf die Struktur zugreifst ist das UB (=undefined behavior=undefiniertes Varhalten=ein Programmierfehler). Das Kopieren von einem char Array in ein zweites char Array dient auch keinem erkennbaren Zweck.
    Korrekt wäre hier statt eines Zeigers auf Struktur_RawImuDaten eine Struktur_RawImuDaten Variable zu verwenden und die Daten dann mit memcpy in diese Variable reinzukopieren.



  • Blockiert ComRead(Buffer,56) so lange bis wirklich 56 Byte gelesen wurden?

    Ich weiß es nicht:

    int CserialtestDlg::ComRead(void *Buffer,int Max)
    {
    	DWORD	dwCount;
    
    	ReadFile(hCOMPORT, Buffer, Max, &dwCount, 0);
    
    	return dwCount;
    }
    

    Und wo ist jetzt der erwähnte C# Teil?
    Oder ist das der Code der funktioniert?
    Oder ruft der C# Teil nur das C++ Programm als .exe auf, ohne Daten vom C++ Programm zurückzubekommen?

    Der C#-Code öffnet nur die serielle Schnittstelle, die Funktion zur Verarbeitung des Datenstroms und die Struktur sind in einer C++-DLL implementiert, da hab ich die Struktur ja auch her.

    So wie du auf die Struktur zugreifst ist das UB (=undefined behavior=undefiniertes Varhalten=ein Programmierfehler)

    Und wie kann ich aus dem undefinierten Verhalten ein definiertes Verhalten machen?

    Korrekt wäre hier statt eines Zeigers auf Struktur_RawImuDaten eine Struktur_RawImuDaten Variable zu verwenden und die Daten dann mit memcpy in diese Variable reinzukopieren

    Wenn ich versuche:

    memcpy(ImuDat, Buffer, 56);
    

    bekomme ich die Fehlermeldung "no suitable conversion function from "Struktur_RawImuDaten" to "void *" exists"



  • Stutzpeter schrieb:

    Blockiert ComRead(Buffer,56) so lange bis wirklich 56 Byte gelesen wurden?

    Ich weiß es nicht:

    int CserialtestDlg::ComRead(void *Buffer,int Max)
    {
    	DWORD	dwCount;
    
    	ReadFile(hCOMPORT, Buffer, Max, &dwCount, 0);
    
    	return dwCount;
    }
    

    Gut, dann lies hier mit den Werten aus deinem Programm nach:
    https://msdn.microsoft.com/en-us/library/windows/desktop/aa363190(v=vs.85).aspx

    Stutzpeter schrieb:

    Korrekt wäre hier statt eines Zeigers auf Struktur_RawImuDaten eine Struktur_RawImuDaten Variable zu verwenden und die Daten dann mit memcpy in diese Variable reinzukopieren

    Wenn ich versuche:

    memcpy(ImuDat, Buffer, 56);
    

    bekomme ich die Fehlermeldung "no suitable conversion function from "Struktur_RawImuDaten" to "void *" exists"

    No dann tät ich mal memcpy(&ImuDat, Buffer, 56); probieren.

    Der Grund warum dein Programm nicht funktioniert ist aber ein anderer. Bzw. eigentlich zwei.

    1. Dein Programm stellt nicht sicher dass es überhaupt ausreichend viele Bytes empfangen hat. Das kannst du fixen indem du den Returnwert von ComRead verarbeitest.
    2. Dein Programm "scannt" nicht richtig nach der "Anfang-des-Pakets" Sequenz bzw. hat auch sonst keinen erkennbaren Code der sicherstellen könnte dass irgendwann mal der Anfang eines Pakets gefunden wird.
      Ohne weitere Informationen über das angeschlossene Gerät wäre es etwas aufwändig das zu implementieren - zumindest wenn man es 100% korrekt hinbekommen will.

    U.u. würde es aber auch schon reichen die Timeouts anders einzustellen. Viele Geräte garantieren dass Bytes innerhalb einer Nachricht max. so-und-so weit zeitlich auseinanderliegen. Und dass zwischen zwei Nachrichten mindestens so-und-so-lange Pause ist. Das kann man dann oft ausnutzen um mit ganz wenig Code sicherzustellen dass immer irgendwann der Anfang eines Pakets gefunden wird.



  • Gut, dann lies hier mit den Werten aus deinem Programm nach:
    https://msdn.microsoft.com/en-us/library/windows/desktop/aa363190(v=vs.85).aspx

    Ich lese es, aber ich verstehe nicht genau wie ich das umsetzen soll. Das Gerät schickt 56-Bytes große Nachrichten mit 200Hz und einer Baudrate von 230400 raus. Ich komm aber nicht drauf wie ich das die Timeouts bestimmen bzw. den korrekten Anfang bekomme.

    Das mit dem Rückgabewert von ComRead hab ich eingebaut, ändert aber vorerst auch noch nichts an meinem Problem.



  • Danke für den Hinweis hustbaer, ich hab ReadTotalTimeoutConstant auf 5 gesetzt, für 200Hz. Jetzt stimmen auch die gecasteten Werte plötzlich, wie kann denn das sein? Es ist nur noch alle paar Hundert Werte ein Ausreisser drin (z.b. 24003.03944 statt ~9.81). Hat jemand eine Erklärung dafür? Bzw. wie bekomme ich es hin, dass auch kleinste Aussreisser nicht mehr vorkommen?

    _Edit:
    Je länger ich messe desto kürze werden die Intervalle zwischen den "falschen" Werten... hmmm 😕



  • Ich kann dir nur sagen was ich machen würde.
    Ich würde vermutlich Code schreiben der
    a) Die empfangenen Bytes alle in eine Queue steckt und dann
    b) An allen möglichen Start-Positionen in der Queue prüft ob eine Message vollständig geparsed werden kann.
    Und zwar unter berücksichtigung aller zur Verfügung stehenden Felder.
    Also z.B. würde ich das MsgLength und CRC Feld prüfen -- und natürlich die Sync-Bytes.
    Wenn dann eine "gültig aussehende" Message erkannt worden ist würde ich diese anhand des MsgID Felds auswerten (vielleicht schickt das Gerät ja noch andere Messages mit einem anderen Aufbau und anderen Nutzdaten -- diese darf man dann natürlich nicht gleich verarbeiten wie die von dir gewünschten Messages), und dann die entsprechenden Bytes aus der Queue löschen.

    Bzw. wenn sich zu viele Bytes in der Queue ansammeln (z.B. mehr als 255, wenn man davon ausgeht dass das MsgLength Feld sich immer auf die Gesamtlänge der Message bezieht, also vom 1. Sync-Byte bis zum letzten CRC Byte), dann einfach die letzten (ältesten) Bytes verwerfen.

    Weiters würde ich das Sleep(50) entfernen, denn das ist bloss kontraproduktiv. Das führt nämlich unweigerlich dazu dass der serielle Empfangspuffer irgendwann überläuft, und Daten verworfen werden.
    (Die Messages kommen ja mit 200 Hz daher, es wird nach jeder Message aber 50ms gewartet, was die Rate mit der gelesen wird auf 20Hz limitiert. Da nicht mehr als eine Message auf einmal gelesen wird führt das notwendigerweise dazu dass sich die Daten im Puffer ansammeln.)

    Ersatzlos kann das Sleep(50) natürlich auch nicht gestrichen werden, der Ersatz wäre dann aber einfach ein kleines Timeout beim Lesen, wie z.B. die von dir verwendeten 5 ms.



  • Ich hab in den letzten Tagen ein bisschen rumprobiert. Zunächst gingen durch den alten Einlesemechanismus viel zu viele Pakete verloren, auch mit dem Timeouts konnte das nicht kompensiert werden.

    Nun lese ich byteweise ein, und bekomme so 200 Pakete pro Sekunde, bei 200Hz kann man nicht mehr erwarten.
    Ich weiß nicht ob es ein MFC-Effekt ist, aber die falschen Werte hatten nichts mit der Struktur und auch nichts mit dem Casten zu tun.

    Jetzt für die MFC-Profis:
    Mit dem neuen Empfangsmechanismus hab ich einfach mal gemessen und die Werte permanent in der GUI ausgeben lassen, mit dem Effekt: je länger ich messe, desto mehr Murks kommt gegen Ende dabei raus.
    Der Code dazu sah ja so aus:

    tsRead->m_Wnd->m_Edit_Out.GetWindowTextW(csOut);
    csOut = csOut + csAcc0 + _T("\r\n");
    
    tsRead->m_Wnd->m_Edit_Out.SetWindowTextW(csOut);
    

    Dann dachte ich mir, ich gebe nicht mehr alle Messwerte in der GUI aus, sondern nur noch den Aktuellen, mit dem Effekt: KEIN falscher Wert mehr!

    Inzwischen gebe ich gar nichts mehr in der GUI aus, sondern schreibe alles in eine Textdatei, mit dem Effekt: die 2. Zeile in der Textdatei ist immer Murks, alle anderen sind ok, egal wie lange ich messe. Sie ist sowohl in der Textdatei Murks, als auch wenn ich sie mir in der GUI ausgeben lasse... 😕

    Hat jemand irgendeine Erklärung für diese ganze Geschichte?

    Der neue Empfangsthread sieht so aus:

    UINT CserialtestDlg::Thread_Read(LPVOID param)
    {
        THREADSTRUCT* tsRead = (THREADSTRUCT*)param;
    
        float fAcc0 = .0;
    	float fAcc1 = .0;
    	float fAcc2 = .0;
    	float fOmg0 = .0;
    	float fOmg1 = .0;
    	float fOmg2 = .0;
    
        float m_ScaleFactorACC = (float)0.0000186264;
        float m_ScaleFactorOMG = (float)720 / 2147483648 * 200;
    
    	int iCount = 0;
    	int iTempCount = 0;
    
    	unsigned char Buffer[1];
    	unsigned char Temp[56];
    
        CString csAcc0, csAcc1, csAcc2, csOmg0, csOmg1, csOmg2, csCRC, csMsgLength, csOut, csCount, csData;
    
    	CStdioFile csFile;
    
    	csFile.Open(_T("..\\test.txt"), CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite);
    
        while(tsRead->m_Wnd->bPortOpen)
        {  	
    		memset(Buffer, 0, sizeof(Buffer));	
    		memset(Temp, 0, sizeof(Temp));
    
    		// Lesemechanismus, das einlesen erfolgt byteweise
    		if(tsRead->m_Wnd->ComRead(Buffer, 1) == 1)
    		{
    			if((Buffer[0] == 0xAA))		// pruefe syncbyte 1
    			{
    				iTempCount = 0;
    
    				Temp[iTempCount] = Buffer[0];
    				iTempCount++;
    
    				if(tsRead->m_Wnd->ComRead(Buffer, 1) == 1)
    				{
    					if(Buffer[0] == 0x44)	// pruefe syncbyte 2
    					{
    						Temp[iTempCount] = Buffer[0];
    						iTempCount++;
    
    						if(tsRead->m_Wnd->ComRead(Buffer, 1) == 1)
    						{
    							if(Buffer[0] == 0x13)	// pruefe syncbyte 3
    							{
    								Temp[iTempCount] = Buffer[0];
    								iTempCount++;
    
    								for(int i = 0; i < 53; i++)		// wenn syncbytes ok, restlichen 53 bytes einlesen
    								{
    									if(tsRead->m_Wnd->ComRead(Buffer, 1) == 1)
    									{
    										Temp[iTempCount] = Buffer[0];
    										iTempCount++;					
    									}
    								}
    
    								Struktur_RawImuDaten *ImuDat = (Struktur_RawImuDaten*)Temp;
    
    								fAcc0 = ImuDat->acc0 * m_ScaleFactorACC;
    								fAcc1 = ImuDat->acc1 * m_ScaleFactorACC;
    								fAcc2 = ImuDat->acc2 * m_ScaleFactorACC;
    								fOmg0 = ImuDat->phi0 * m_ScaleFactorOMG;
    								fOmg1 = ImuDat->phi1 * m_ScaleFactorOMG;
    								fOmg2 = ImuDat->phi2 * m_ScaleFactorOMG;
    
    								csAcc0.Format(_T("%f"), fAcc0);
    								csAcc1.Format(_T("%f"), fAcc1);
    								csAcc2.Format(_T("%f"), fAcc2);
    								csOmg0.Format(_T("%f"), fOmg0);
    								csOmg1.Format(_T("%f"), fOmg1);
    								csOmg2.Format(_T("%f"), fOmg2);
    
    								csOut = csAcc0 + _T("; ") + csAcc1 + _T("; ") + csAcc2 + _T("; ") + csOmg0 + _T("; ") + csOmg1 + _T("; ") + csOmg2 + _T("\r\n");
    
    								csFile.WriteString(csOut);
    							}
    						}
    					}
    				}
    			}  	
    		}   
    
    	}  
    	//bFileOpen = csFile.Open(_T("..\\test.txt"), CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite);
    	//csFile.WriteString(csData);
        return 0;
    }
    

Log in to reply