Forum: PC-Programmierung 1kHz HEX Datenstream lesen mit RS232 und VB2005


von Paul A. (kangaroo)


Lesenswert?

Hallo Leute,

ich bin hier zur Zeit in Australien und arbeite an einem Projekt, bei 
dem wir eine 1kHz Datenstream per VB auslesen sollen. Ein kleiner Sensor 
liefert einen 9-Bit Hex Datenstrom, den es gilt per VB aufzunehmen und 
weiterzuverarbeiten.

Soweit ist das ja kein Problem, Daten per RS232 zu empfangen, jedoch 
scheiter ich nun daran, das der Sensor permanent Daten im Format 
FFFFFFFF+CRlf liefert und mir beim Einlesen in eine Variable diese bis 
oben hin vollballert und mit Daten überschüttet. Bisher habe ich mit 
serialPort.ReadExisting experimentiert und auch die Daten erhalten.
Ich bräuchte aber eine Funktion, die am Datenstrom lauscht und mir jede 
HEX Zahl in die variable schreibt. Kommt dann die neue HEX Zahl soll der 
alte Wert in der Variablen überschrieben werden, usw...

Ja, da hänge ich im Moment ein wenig, hab mich aber auch erst in die 
Materie der RS232 eingearbeitet. Evtl. ist die Lösung ja ganz einfach 
und ReadExisting ist einfach nicht die richtige Methode. Evtl. ist aber 
auch VB nicht die richtige Sprache, aber das ist das was sie hier bisher 
nutzen und die Sensorabfrage darin integrieren möchten.

Danke für eure Ideen dazu.

Viele Grüße aus dem sommerlichen Brisbane bei 38º
Paul

PS: wundert euch nicht, das ich Nachts schreibe, es ist hellichter Tag!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

9-Bit-Daten kann die serielle Schnittstelle des PCs nicht verarbeiten, 
das geht nur mit üblen Tricks (Umschalten zwischen 8o1 und 8e1 je nach 
berechneter Parität des zu sendenden Bytes, Auswerten der 
Paritätsfehlerinformation bei jedem empfangenen Byte und Abgleich mit 
der momentan aktiven Übertragungsart) - und das wird aus einem 
Usermode-Programm heraus sehr langsam.

Was magst Du mit einem "1kHz-Datenstrom" meinen? Serielle Schnittstellen 
arbeiten mit gewissen standardisierten Baudraten, die (bei der 
klassischen seriellen Schnittstelle) ganzzahlige Teiler von 115200 sind, 
also 57600, 38400, 28800, 19200, 14400, 9600 ...
Und das ist die Bruttobitrate, bei einer Datenlänge von 9 Bit kommen 
noch zwei Bits "Verpackung" dazu, so daß die Nutzdatenrate in 
Datenwörtern/Sekunde ein elftel der Baudrate beträgt.

Andere Werte lassen sich allenfalls mit USB-RS232-Wandlern oder 
Steckkarten mit eigener UART-Hardware erzielen, diese bieten zusätzlich 
zu obiger Baudratenreihe noch andere, vornehmlich höhere Werte an.

von Albrecht H. (alieninside)


Lesenswert?

Paul A. schrieb:
> Hallo Leute,
>
> ich bin hier zur Zeit in Australien und arbeite an einem Projekt, bei
> dem wir eine 1kHz Datenstream per VB auslesen sollen. Ein kleiner Sensor
> liefert einen 9-Bit Hex Datenstrom, den es gilt per VB aufzunehmen und
> weiterzuverarbeiten.
>

Also das mit den 9-Bit wird wohl so sein wie Rufus es schreibt, es sei 
denn du meinst mit dem 9ten Bit das Stopbit das wäre dann etwas anderes. 
Sind es wirklich 9 Bit wirds aufwendiger, da bietet es sich an einen 
Mikrocontroller zwischen Sensor und PC zu schalten, der entweder A) die 
9 Bit als Bitstream verpackt (in 8-Bit Paketen) oder B) der Einfachheit 
halber immer 9-Bits in 2 Bytes verpackt, also 8 Bit im ersten Byte + 1 
Bit im zweiten Byte.


> Soweit ist das ja kein Problem, Daten per RS232 zu empfangen, jedoch
> scheiter ich nun daran, das der Sensor permanent Daten im Format
> FFFFFFFF+CRlf liefert und mir beim Einlesen in eine Variable diese bis
> oben hin vollballert und mit Daten überschüttet. Bisher habe ich mit
> serialPort.ReadExisting experimentiert und auch die Daten erhalten.

serialPort.ReadExisting sagt mir jetzt gerade nichts, aber ich habe 
bereits unter VB6 mit dem seriellen Port gearbeitet, da gabs das "MSComm 
Control" und das hatte selbstverständlich auch Events, also irgendwas 
mit ... On Comm ... oder so, sprich es wurde ein Event ausgelöst sobald 
ein neues Datenbyte am seriellen Port anlag, das machte die Sache 
natürlich relativ einfach, weil man dadurch den seriellen Port nur dann 
kurz auslesen musste wenn tatsächlich ein neues Byte eingetroffen war 
und nicht permanent, würde mich wundern, wenn das unter VB.Net nicht 
auch so geht.


> Ich bräuchte aber eine Funktion, die am Datenstrom lauscht und mir jede
> HEX Zahl in die variable schreibt. Kommt dann die neue HEX Zahl soll der
> alte Wert in der Variablen überschrieben werden, usw...
>
> Ja, da hänge ich im Moment ein wenig, hab mich aber auch erst in die
> Materie der RS232 eingearbeitet. Evtl. ist die Lösung ja ganz einfach
> und ReadExisting ist einfach nicht die richtige Methode. Evtl. ist aber
> auch VB nicht die richtige Sprache, aber das ist das was sie hier bisher
> nutzen und die Sensorabfrage darin integrieren möchten.

Also wenn das Comm Control unter .Net wenigstens das selbe kann wie 
unter VB6 ist das überhaupt kein Problem, du musst dann aber auch auf 
jeden Fall die Events des Comm Controls verwenden sonst verlierst du 
permanent irgendwelche Bytes und dein Programm wird ziemlich 
prozessorlastig.

>
> Danke für eure Ideen dazu.
>
> Viele Grüße aus dem sommerlichen Brisbane bei 38º
> Paul
>
> PS: wundert euch nicht, das ich Nachts schreibe, es ist hellichter Tag!

von Paul A. (kangaroo)


Lesenswert?

arrrrgggh, da hab ich mich wohl missverständlich ausgedrückt:

Wir nennen ihn nur den 9-Bit Sensor. Er ist über eine RS422 
Schnittstelle über ein USB-Interface am VCP COM3 angeschlossen. Wenn man 
ihn mit '1' triggert, dann liefert er einen dauernden Datenstream im 
Format FFFFFF+CRlf, bis er die '0' gesendet bekommt, also z.B.

ffffff
000000
000001
000002
...
0001fa
usw

und das im 1kHz Takt.

Es handelt sich um einen Drehgeber, der in einer Maschine eingaut ist 
und nun sollen je nach Drehwinkel bestimmte Funktionen ausgelößt werden. 
Wenn sich die Maschine nicht dreht bekommt man trotzdem Daten, eben dann 
ständig den gleichen Wert. Er feuert quasi ununterbrochen und gibt den 
aktuellen Stand des Drehwinkels durch.

Weiße ich disem Datenstrom nun eine Variable zu, so ist diese gefüllt 
mit haufenweiße Werten  und sieht dann so aus:

000004
000004
000005
000006
000007
...

usw.

Was ich nun erreichen möchte ist folgendes: es sollte immer nur ein Wert 
der Variablen zugeordnet werden, als nur bis zum CRlf. Kommt der nächste 
Wert soll die Variable mit diesem neuen Wert überschrieben werden. Es 
soll also nicht der ganze Stream (oder ein Teil davon) in der Variablen 
stehen, sondern nur

000004

kommt der nächste Wert soll eben

000005

darin stehen, usw. Mein Problem ist also, diesen andauernden Datenstrom 
zur Laufzeit hin aufzuspalten und jeden einzelnen Wert der Variablen X 
zuzuordnen.

Hilft euch das weiter?

Herzlichen Dank und viele Grüße aus Brisbane
Paul

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Die Werte sind offensichtlich durch Zeilenvorschübe getrennt. Speicher 
die empfangenen Bytes in einer (zusätzlichen) Stringvariablen zwischen, 
die Du auswertest, wenn der Zeilenvorschub empfangen wird. Den Wert 
weist Du erst in diesem Moment der eigentlichen Variablen zu, und Du 
setzt den Inhalt der Stringvariablen zurück, so daß das nächste 
empfangene Zeichen wieder an ihrem Anfang eingetragen wird.

von Paul A. (kangaroo)


Lesenswert?

Rufus t. Firefly schrieb:
> Speicher
> die empfangenen Bytes in einer (zusätzlichen) Stringvariablen zwischen

Alles klar, nur das diese empfangenen Werte niemals enden. Es ist ein 
andauernder Datenstrom, der kommt. Wann mache also einen Schnitt?

wenn ich

a = serialPort1.ReadExisting

mache, dann ist mein Variable a voller Werte (mit Zeilenvorschub 
getrennt), die dem Datenstrom gleichen. Weiß nicht genau wieviel Werte 
man in eine Variable reinschreiben kann, aber auch dies wird endlich 
sein. Spätestens, wenn diese überläuft, fehlen mir die Daten, die danach 
gesendet werden.

Gibt es nicht eine Möglichkeit/Methode ReadExisting zu lauschen und die 
Daten in Echtzeit auzusplitten und weiter zu verarbeiten. Etwa so

-> lies den Datenstrom und wenn ein CRlf kommt speichere den bisher 
erhaltenen Datenstrom in einer anderen Variablen.
-> dann überschreibe den alten Wert mit dem neuen Wert der gleich nach 
dem letzten CRlf kommt...

Idealerweiße hätte a immer nur den aktuellen Wert

Danke und viele Grüße
Paul

von Albrecht H. (alieninside)


Lesenswert?

Das Stichwort heißt nach wie vor "Events"!

Hab das gerade mal unter VS2008 überflogen.
Da gibt es das "DataReceived"-Event und die "BytesToRead"-Property, das 
sollte genügen um das Problem zu lösen.

von Gerry E. (micky01)


Lesenswert?

Ist es möglich, dass das neunte Bit ein simples Paritätsbit ist?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

> wenn ich
>
> a = serialPort1.ReadExisting

Dann mach das doch nicht, sondern lies Zeichen für Zeichen. Dann kannst 
Du problemlos CR/CRLF auswerten.

von Philipp B. (philipp_burch)


Lesenswert?

Für sowas verwende ich gerne einen Ringpuffer (FIFO), dann kann man das 
auch schön asynchron auswerten. Das .net-FX ist mir nicht mehr so 
geläufig, aber ich meine, der StringBuilder kann sowas auch. Du müsstest 
also im DataReceived-Event jeweils alle Daten vom SerialPort lesen und 
an den Puffer anhängen. In einem anderen Thread, also beispielsweise in 
einem Timer-Event liest du jetzt einfach alle Zeichen bis zum ersten 
CrLf aus dem Puffer und entfernst sie daraus. Das wiederholst du so 
lange, bis du beim letzten CrLf angekommen bist. Jetzt kannst du die als 
letztes empfangenen Daten parsen (Integer.Parse() kommt soweit ich weiss 
auch mit Hex klar) und deiner Variable zuweisen. Wenn du das jetzt z.B. 
alle 100ms ausführst, dann hast du schön vorhersehbare Updates deiner 
Variable und die Prozessorlast hält sich in Grenzen.

In VB6 könnte man es (ohne auf die Effizienz zu achten) auch irgendwie 
so lösen:
1
Private buffer As String
2
Private value As Long
3
4
Private Sub mscomm1_OnComm(...)
5
  'Das ist dann auch sowas wie ein FIFO
6
  buffer = mscomm1.Input & buffer
7
End Sub
8
9
Private Sub Timer1_Tick()
10
  Dim nums() As String
11
  'Auftrennen bei CrLf
12
  nums = Split(buffer, vbCrLf)
13
  If UBound(nums) > 0 Then
14
    'Zweitletzte Zahl nehmen (Der letzte Eintrag ist möglw. nicht vollständig
15
    value = Val(nums(UBound(nums) - 1))
16
  End If
17
  'Unbehandelten Rest wieder in den Puffer schreiben
18
  buffer = nums(UBound(nums))
19
End Sub

von Paul A. (kangaroo)


Lesenswert?

Hallo Leute,

danke für eure Tipps. Leider war ich bishher nicht erfolgreich und 
konnte die Daten nicht auslesen.

Im Datareceived event erhalte ich um die 440 Werte/s, die in den Puffer 
geschrieben werden. Das event läuft also in einer Art Schleife, solange 
Daten vom Port empfangen werden, richtig? Ist das dann schnell genug, um 
die Daten weiterzuverarbeiten?

Das Aufteilen mit Split hat auch nicht so recht funktioniert und die 
Daten sind dann nicht vollständig vorhanden, d.h. es kommen nicht alle 8 
digits des Hexstreams an, sondern manchmal nur 2, 3 oder 7. Nur ab und 
an ist der Wert vollständig.

Bisher konnte ich keinen Beispielcode finden um den Puffer vernünftig 
auszulesen, so dass keine Daten verloren gehen...
Es funktioniert einwandfrei, wenn die Daten endlich sind und keine neuen 
mehr nachkommen. Dieses Interface sendet aber ständig Daten, also ein 
Stream der nicht beendet wird.

Kann mir jemand mal einen Beispielcode geben, der die Daten parallel zum 
Stream auslesen und aufteilen kann (als beim vbCr trennen)?
Ich brauche nur eine Variable mit dem aktuellen Wert aus dem Stream, die 
alle 8digits des Hex beinhaltet und sich sofort mit ändert, wenn die 
Daten im Stream sich ändern.

Hat jemand sowas schon mal gemacht und kann ein Beispiel posten?

Danke euch nochmal
Paul

von Albrecht H. (alieninside)


Lesenswert?

Paul A. schrieb:
> Hallo Leute,
>
> danke für eure Tipps. Leider war ich bishher nicht erfolgreich und
> konnte die Daten nicht auslesen.
>
> Im Datareceived event erhalte ich um die 440 Werte/s, die in den Puffer
> geschrieben werden. Das event läuft also in einer Art Schleife, solange
> Daten vom Port empfangen werden, richtig?

Jain, das zum "Datareceived event" zugehörige "Unterprogramm" wird nur 
jedesmal aufgerufen, wenn die die serielle Schnittstelle des PCs neue 
Daten empfangen hat, das kann im besten Fall, (wenn man es so 
voreingestellt hat mit "Me.SerialPort.ReceivedBytesThreshold = 1", (in 
deinem Fall je nach Geschwindigkeit aber nicht zwangsläufig sinnvoll)), 
bereits nach einem Byte passieren, muss aber nicht, es können auch 
mehrere Bytes sein. Der Trick besteht nun darin, jedesmal, wenn das 
Programm im "Datareceived event" ist, alle bis dahin angekommenen Daten 
auszulesen, (das geht dann z.B. auch mit .ReadExisting), und 
weiterzuverarbeiten, (das sind ja dann hoffentlich immer nicht 
allzuviele). Die Daten in DataReceived direkt auszuwerten macht aber bei 
dir u.U. nicht so viel Sinn, da du ja nicht davon ausgehen kannst, dass 
immer ein kompletter Datensatz in Form von "FFFFFFFF+CRlf" ankommt, mal 
kann es "CRlf+FFFFF..." sein, mal "FFFF" ein anderes mal nur "CR" oder 
auch einfach "lf+fffff" je nachdem zu welchen Zeitpunkt dein Programm 
startet oder ob es z.B. Übertragungsfehler oder Verzögerungen beim 
Auslösen des DataReceived-Events gibt.
Du solltest also evt. aus dem DataReceived-Event heraus, die 
angekommenen Daten ersteinmal zwischenspeichern, also in einem String 
(oder einem 1-dimensionalen ByteArray), aneinanderhängen, bis du 
genügend Daten zum auswerten zusammenhast, (also irgendwas in der Form 
"FFFF+CRlf+FFFFFFFF+CRlf+FFFFFF"). "Ringbuffer" ist hier schon mal ein 
gutes Stichwort. Also ein String der nicht unendlich größer wird weil du 
permanent weitere Daten dranhängst, sondern einer mit einer festen 
Länge, der von Anfang bis Ende beschrieben wird und wenn das Ende 
erreicht ist wieder am Anfang beschrieben wird. Diesen Ringbuffer kannst 
dann mit einer zeitgesteuerten Routine, (Timer), regelmäßig abarbeiten. 
Das Konzept des Ringbuffers lebt davon, dass du die Daten im Ringbuffer 
immer etwas schneller abarbeitest als sie vom DataReceived-Event 
nachgeliefert werden. Natürlich musst du dann auch noch ein paar 
Variablen einführen, die dir sagen, bis zu welcher Stelle der Ringbuffer 
zuletzt beschrieben wurde und bis zu welcher Stelle du ihn zuletzt 
abgearbeitet hast, (also erkennen, dass z.B. gerade gar nichts 
abgearbeitet werden muss weil keine oder nicht genügend neue Daten 
vorliegen).

Es sind aber auch Lösungen denkbar in denen du alles im 
DataReceived-Event erledigst, z.B. mit zwei kurzen Stringvariablen (so 
lange wie ein kompletter Datensatz), eine Stringvariable wird bei jedem 
DataReceived-Event sukzessive mit Daten gefüllt, (Startsignal ist ein 
ausgelesenes CRlf), ist die Variable "voll" wird sie in die zweite 
kopiert, das ist dann der aktuelle Wert. Hört sich jetzt einfacher an 
als es wohl in der praktischen Umsetzung sein dürfte, denn auch hier 
brauchst du wieder ein paar Variablen für Pointer und Flags die dir 
sagen wie der Zustand vor dem letzten Aufruf des letzten 
DataReceived-Events war und an denen das restliche Programm sehen kann, 
dass jetzt ein gültiger Wert vorliegt.

Oder du kannst natürlich auch, wenn es die Anwendung erlaubt, 
"bescheißen". Der Sensor liefert permanent Daten, wenn du jetzt 
"Me.SerialPort.ReceivedBytesThreshold" z.B. auf "20" stellst, hast du 
bei jedem Aufruf des DataReceived-Events auf jedenfall genügend Daten um 
wenigstens einen Datensatz komplett auszuwerten. Also mit .ReadExisting 
in einen String lesen, String zerteilen anhand von CRlf, jeder 
Teilstring mit der richtigen Länge ist nun ein gültiger Datensatz, in 
eine globale Variable kopieren fertig ...


Noch allgemein zum seriellen Port unter VB2005/2008 in diesem Thread:
Beitrag "Re: SerialPort abfragen (VB2008)"
Das activevb-Beispiel fand ich ganz gut.

> ...
> Ist das dann schnell genug, um
> die Daten weiterzuverarbeiten?

Das kann ich aus dem Stehgreif so leider auch nicht beantworten, werde 
so was aber bestimmt mal im Laufe des Jahres ausprobieren ...
Wenn der Rechner schnell genug ist vermutlich schon.

>
> ...

von Paul A. (kangaroo)


Lesenswert?

Hallo Albrecht,

Danke für Deine ausführliche Mail und die Infos bezüglich des 
DataReceived Events. Da hatte ich wohl was falsch verstanden. Jetzt 
scheint es für mich klarer zu sein und ich werde deine aufgezeigten 
Möglichkeiten versuchen umzusetzen.

Und du hast recht, die Daten kommen im DataReceived Event nicht komplett 
an, wenn ich sie dort direkt auswerte und nur manchmal ist ein Datensatz 
vollständug abrufbar .Bisher war mir nicht klar warum. Nach deinen 
Erläuterungen weiß ich nun warum.

Danke nochmal und viele Grüße aus Brisbane
Paul

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.