Laden...

SerialPort Problem mit DataReceived event

Erstellt von robmir vor 16 Jahren Letzter Beitrag vor 16 Jahren 14.093 Views
R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 16 Jahren
SerialPort Problem mit DataReceived event

Hallo zusammen,

ich programmiere einen Tester, der über den SerialPort mit einem Steuergerät kommunizieren soll. Da ich zur Zeit kein Steuergerät habe, benutze ich zwei Rechner (Desktop und altes Laptop) mit dem gleichen Programm. Die Einstellungen im Geräte-Manager von COM1 sind identisch. Und der Code schaut so aus:


public event DataReceivedEventHandler DataReceivedEvent;
private SerialPort serialPort = null;

public SerialPortAdapter()
{
	try
	{
		serialPort = new SerialPort();

		serialPort.DtrEnable = true;
		serialPort.ReadTimeout = 500;
		serialPort.RtsEnable = true;
		serialPort.WriteTimeout = 500;
		serialPort.Encoding = Encoding.ASCII;
		serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
		serialPort.ErrorReceived += new SerialErrorReceivedEventHandler(serialPort_ErrorReceived);
		serialPort.BaudRate = 115200;
		serialPort.DataBits = 8;
		serialPort.PortName = "COM1";
		serialPort.DiscardNull = true;
		
		serialPort.Open();
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}
		
void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
	byte[] dataBuffer = new byte[serialPort.ReadBufferSize];

	int countOfBytes = serialPort.Read(dataBuffer, 0, serialPort.ReadBufferSize);
	
	// hier wird dataBuffer an höhere Schicht per event weiter gegeben und dort wird das Protokoll z.b. kwp2000 
	// weiter verarbeitet. 
	if (DataReceivedEvent != null)
	{
		DataReceivedEvent(this, dataBuffer);
		
	}
}

public void Send(byte[] frame)
{
	try
	{
		serialPort.Write(frame, 0, frame.Length);
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

Wenn ich von Desktop-Rechner an Laptop eine Nachricht sende, kommt sie als ein Frame an. Aber wenn ich von Laptop an Desktop-Rechner die gleiche Nachricht sende (z.B "0123456789") kommt sie fragmentiert an (d.h. in 2 Paketen). Und hier habe ich Probleme, ich versuche in der höherer Schicht (kwp2000-Protokoll) die länge der Nachricht von Header auslesen und so lange zu puffern bis die ganze Nachricht angekommen ist. Nur da komme ich irgendwie durcheinander. Jetzt ist die Frage, was ich da falsch mache?
Ich vermute, es hat etwas mit threads zu tun 🙁

Grüße

robmir

S
8.746 Beiträge seit 2005
vor 16 Jahren

Seit wann gibts denn Steuergeräte mit rs232? KWP läuft doch üblicherweise auf CAN.

Fragmentierung ist völlig normal. Damit musst du umgehen. Immer schön mit ReadExisting() lesen.

Alles erstmal in einen Puffer lesen, Längenbyte checken (1 Byte?). Prüfen ob Puffer schon komplett, wenn nicht, Puffer stehen lassen und nix tun. Wenn der Puffer voll ist, an die KWP-Schicht senden und Puffer leeren.

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 16 Jahren

Original von svenson
Seit wann gibts denn Steuergeräte mit rs232? KWP läuft doch üblicherweise auf CAN.

Fragmentierung ist völlig normal. Damit musst du umgehen. Immer schön mit ReadExisting() lesen.

Alles erstmal in einen Puffer lesen, Längenbyte checken (1 Byte?). Prüfen ob Puffer schon komplett, wenn nicht, Puffer stehen lassen und nix tun. Wenn der Puffer voll ist, an die KWP-Schicht senden und Puffer leeren.

ich dachte, dass keiner mehr antworten wird 😉
Ja, das wunder mich auch mit dem rs232 aber so will der Kunde haben.
Das Problem ist, dass ich das Längenbyte im SerialPortAdapter checken muss, und das gehört in die KWP-Schicht wegen Teilung (KWP-Schicht weiss doch wie Header aufgebaut ist) 🙁 und die Analyse von Header soll KWP-Schicht übernehmen. Was meinst Du, soll ich das (Header Analyse) doch im SerialPortAdapter machen?

Zweitens mit serialPort.Read() bekomme ich gleich byte[] und von ReadExisting() string. Ok, string kann ich zu byte[] konvertieren.

Grüße

robmir

S
8.746 Beiträge seit 2005
vor 16 Jahren

Ich verstehe dein Schichtenproblem, aber das liegt nunmal auch in der (ungünstigen) Kombination Rs232 und KWP begründet. Wo du die Paketerkennung machst ist eigentlich Wurscht. Optimal ist kein Fall. Wenn die Daten vom CAN kommen, hast du ein Paket. Da dient die Länge nur noch zum Check.

Deine KWP-Nutzdaten sind und bleiben erstmal Bytes. Erst die Dienstschicht (DTC, Ecu-ID, etc.) bastelt dann ggf. (ASCII) Strings.

Und ein Tipp: Baue haufenweise Fehlertoleranz ein... leider hat man oft kein korrektes KWP vor sich. Praktisch jedes Fahrzeug hat mindestens ein Steuergerät, welches auf die 3-4 Standard Diagnose- und Info-Dienste fehlerhaft antwortet.

Vor allem Master-Slave-Geschichten sind extrem fehlerbehaftet.

Ist aber auch kein Wunder, bei diesem Hersteller-Wildwuchs, gepaart mit völlig konfusen Norm-Dokumenten.

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 16 Jahren

Erstmals vielen Dank für die Antwort 🙂

Original von svenson
Praktisch jedes Fahrzeug hat mindestens ein Steuergerät, welches auf die 3-4 Standard Diagnose- und Info-Dienste fehlerhaft antwortet.

Ich habe das Glück, dass es sich nur um einen Mikrocontroller handelt, der auch bei uns entwickelt wird. D.h. über Rs232 und KWP2000 soll der Mikrocontroller konfiguriert werden.
Die Standard Core für Mikrocontroller bekomme ich von Kundenseite, da weiß ich nocht nicht wie der Kunde das KWP-Protokoll implementiert hat.
Ich möchte auch das ganze etwas unabhängig von der Implementierung halten, so dass ich die PC-Applikation wiederverwenden kann 🙂 tja, schaut so aus, dass ich doch im SerialPortAdapter die Erkennung durchführen muss, das wird mich aber nicht zufrieden stellen 😉

Wie meinst Du das mit Fehlertoleranz? eigentlich bin ich neu im Embedded-Bereich

Grüße

robmir

P
90 Beiträge seit 2005
vor 16 Jahren

ich habe an dieser stelle zum datenempfangen über die serielle schnittstelle eine frage,

Gibts einen Pin in der schnittstelle den ich mit 5 v belegen kann und sobald die spannung abbricht das eine funktion ausführe die mir eine messagebox anzeigt "spannung abgebrochen"

danke im vorraus


Rolf

S
8.746 Beiträge seit 2005
vor 16 Jahren

Mit 5V kommst du an der seriellen nicht weiter. Die hat andere Spannungspegel und ist bipolar (+/- V). Du brauchst einen TTL<->RSR232-Umsetzer.

Mit dem Parallel-Port sollte es aber gehen.

P
90 Beiträge seit 2005
vor 16 Jahren

wo bekommt man solch einen umsetzer was kostet dieser und wie funktioniert es ?


Rolf

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 16 Jahren

ok, ich will noch zu meiner Frage gehen.
Ich muss den Puffer via

number = serialPort.Read(dataBuffer, 0, serialPort.ReadBufferSize);

auslesen.
Grund: Mit ReadExisting werden die Bytes, die den Wert 127 überschreiten, auf 63 zurückgesetzt. Ich habe schon verschiedene Encoder probiert aber es hat nicht funktioniert. Whatever.

Problem ist, dass während die Daten ausgelesen werden, können Neue Bytes in Buffer eintreffen und da werde ich nicht benachrichtigt. Erst dann, wenn neue Nachricht kommt stelle ich fest, dass im Puffer noch bytes liegen. Acha, und die Frames, die ankommen sind immer variabel von der Länge (zwischen 4 bytes und 255 bytes).

Ist das normal?? wie kann ich sowas vermeiden?

hier noch code:


public event DataReceivedEventHandler DataReceivedEvent;
private List<byte> byteList = new List<byte>();
private Queue<byte[]> queue = new Queue<byte[]>();

void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
	lock (serialPortAdapter)
	{
		int number = 0;

		byte[] dataBuffer = new byte[serialPort.ReadBufferSize];

		// Reads the serialPort buffer
		number = serialPort.Read(dataBuffer, 0, serialPort.ReadBufferSize);

		if (number > 0)
		{
			BytesReceived(dataBuffer, number);
		}
	}
}

private int BytesReceived(byte[] dataBuffer, int number)
{
	int result = 0;

	AddsDataBufferBytesToList(dataBuffer, number);

	// Gets the frame length
	if (length == 0)
	{
		length = CheckLength(dataBuffer);
	}

	// If the whole fram is in buffer then adds the string to a queue.
	if (length == byteList.Count)
	{
		queue.Enqueue(byteList.ToArray());
		isAllDataReaded = false;
		byteList.Clear();
		length = 0;
		//serialPort.ReceivedBytesThreshold = 1;
		result = 1;
	}
	// In the buffer are more than one frame. The buffer must be fragmented and removed whole frames from
	// buffer and added to the queue.
	else if (length < byteList.Count)
	{
		//queue.Enqueue(CreateByteArray(byteList, length));
		//byteList.RemoveRange(0, length);
		//length = CheckLength(byteList.ToArray());

		while(byteList.Count >= length)
		{
			queue.Enqueue(CreateByteArray(byteList, length));
			byteList.RemoveRange(0, length);
			if (byteList.Count > 1)
				length = CheckLength(byteList.ToArray());
		}

		length = 0;
	}
	else if (length > byteList.Count)
	{
		//serialPort.ReceivedBytesThreshold = length;
	}

	// Invokes observers.
	if (queue.Count > 0)
	{
		for (int i = 0; i < queue.Count; i++)
		{
			if (DataReceivedEvent != null)
			{
				DataReceivedEvent(this, queue.Dequeue());
			}
		}
	}

	return result;
}

private byte[] CreateByteArray(List<byte> byteList, int length)
{
	byte[] frame = new byte[length];

	for (int i = 0; i < length; i++)
	{
		frame[i] = byteList[i];
	}

	return frame;
}


private void AddsDataBufferBytesToList(byte[] dataBuffer, int number)
{
	for (int i = 0; i < number; i++)
	{
		byteList.Add(dataBuffer[i]);
	}
}


private int CheckLength(byte[] bytes)
{
	int i = 0;

	if (!isAllDataReaded)
	{
		i = KWP2000.GetFrameLength(bytes);
	}

	return i;
}

S
8.746 Beiträge seit 2005
vor 16 Jahren

Original von robmir
Problem ist, dass während die Daten ausgelesen werden, können Neue Bytes in Buffer eintreffen und da werde ich nicht benachrichtigt.

Genau deswegen musst du auch ReadExisting() verwenden....

Das _Zurücksetzen _von Byte-Werten oberhalb von 127 liegt am Default-ASCII-Encoding. Einfach auf ANSI umstellen, dann funzt es (siehe SerialPort.Encoding-Property).

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 16 Jahren

Original von svenson
Genau deswegen musst du auch ReadExisting() verwenden....
Das _Zurücksetzen _von Byte-Werten oberhalb von 127 liegt am Default-ASCII-Encoding. Einfach auf ANSI umstellen, dann funzt es (siehe SerialPort.Encoding-Property).

Hei,

auf ANSI umstellen, aber wie?? ich habe alle Encodings ausprobiert, ohne Erfolg.

Grüße

robmir

S
8.746 Beiträge seit 2005
vor 16 Jahren

ANSI bekommst du mit Encoding.Default.

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 16 Jahren

Original von svenson
ANSI bekommst du mit Encoding.Default.

Das habe ich auch schon probiert, wie gesagt ohne Erfolg d.h. Zahl größer 127 wird auf 68 gesetzt.

Grüße

Robmir

379 Beiträge seit 2004
vor 16 Jahren

@robmir

Sag mal hast du damals noch eine Antwort gefunden? Ich stehe momentan auch vor dem Problem das ich Daten versenden möchte, die größer als 127 sind.

In der MSDN steht lediglich:

Standardmäßig verwendet SerialPort ASCIIEncoding zum Codieren der Zeichen. ASCIIEncoding codiert alle Zeichen, die größer als 127 sind, z. B. (char)63 oder '? '. Um zusätzliche Zeichen in diesem Bereich zu unterstützen, legen Sie Encoding auf UTF8Encoding, auf UTF32Encoding oder auf UnicodeEncoding fest.

Ich habe dem SerialPort alle Encodings einmal zugewiesen, aber ich erhalte immer nur 0x3F (63) für einen byte größer als 127.

D.h, ich habe beispielsweise vor dem öffnen des Ports:

this.serialPort.Encoding = System.Text.Encoding.UTF8;

zugewiesen...

Ich lese die Daten mit ReadExisting() aus... Muss ich das Encoding vielleicht noch an einer anderen Stelle setzen?

Meine Einstellungen für den SerialPort sind:
Portname: COM1
BaudRate: 9600
DiscardNull: False
DataBits: 8
DtrEnable: False
RtsEnable: False
StopBits: One
Hanshake: None
Parity: None

Ich arbeite schon lange mit C#, aber die Arbeit mit dem SerialPort sind für mich Neuland 🤔

ciao Anke

D
171 Beiträge seit 2008
vor 16 Jahren

Verpack die Daten doch als Byte,schau mal hier
Hab an sowas auch etwa ein bis zwei Wochen immer mal wieder rum gedoktort.

379 Beiträge seit 2004
vor 16 Jahren

Danke, habe die Lösung gerade eben selber gefunden 😉

Und zwar war nicht das Senden fehlerhaft, sondern das Auslesen...

Ich lese das ganze jetzt folgendermaßen aus:

int bytesToRead = this.serialPort.BytesToRead;

byte[] b = new byte[bytesToRead];

this.serialPort.Read(b, 0, bytesToRead);

und das klappt prima 😉 vorher hatte ich serialPort.ReadExisting() verwendet, weil ich gelesen habe, das man dies machen sollte, um genau diesem Problem entgegen zu wirken 8o

Naja - trotzdem danke

schreibt Anke 8)