Forum: Mikrocontroller und Digitale Elektronik Asynchrone Kommunikation programmieren


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Hallo,

ich versuche mich gerade daran eine Tool für die Kommunikation mit dem 
seriellen Endgerät. Auch um zu lernen und für spätere Anwendungen die 
ggf. mit CAN oder ähnlichem arbeiten realisieren zu können.

Ich empfange die Daten von dem UART über einen Interrupt, lese sie in 
einen Messagebuffer und werte hier das LEN-Byte aus um zu wissen wann 
ich einen ganzen Frame empfangen habe. Am Ende prüfe ich darin auch noch 
das CRC. Das funktioniert alles asynchron, Byteweise.

Über einen beim ersten Byte gestarteten Timer handle ich dabei den 
Timeout. Der wird bei jeder Iteration zurückgesetzt. Kommt längere Zeit 
(hab jetzt mal 100ms genommen) nichts, wird er ausgelöst. Wir ein Frame 
vollständig empfangen wird der Timer angehalten.

Habe ich ein Frame empfangen schiebe ich es, so wie es ist, in eine 
Mailbox, welche ich mit einem globalen Array realisiert habe. Immer 
schön mit Push oben drupp.

Etwas ähnliches mache ich zum senden. Also fertigen Frame in die 
Out-Mailbox. Ein Timer prüft diese regelmäßig (alle 10ms) auf Inhalt ab. 
Findet er was darin, sendet er die Bytes in einem Schwung los. 
Währenddessen halte ich den Sendetimer an um Doppelauslösungen zu 
verhindern.

Wie aber bediene ich die beiden Mailboxen nun im Sinne einer 
Challenge/Response Kommunikation? Ich muss ja einen bestimmten Ablauf 
durchziehen, starte mit dem senden oder empfangen einer Botschaft und 
dann geht es immer hin und her. Auch hier braucht man ja einen Timeout 
wenn der Partner nicht antwortet.

Ich muss ja in meiner Sequenz immer was senden, dann warten bis der 
Empfänger antwortet, die Antwort auswerten und ggf. davon abhängig eine 
neue Frage zusammenbauen oder auch abbrechen. Dabei wäre es wohl kein 
guter Plan in der Sequenz blockierende waits einzubauen.

Zumal unabhängig von meiner Anfrage auch Daten spontan vom Empfänger 
gesendet werden können. Sende- und Empfangsroutine müssen also in der 
Lage sein unabhängig voneinander zu arbeiten, auch was die Sitzungen 
angeht. D.H. wenn ich eine Kommunikation starte und auf den Empfang 
einer Nachricht warte, kann es sein das der Empfänger selbst eine 
Anfrage an mich stellt und diese darf dann natürlich nicht als Antwort 
auf meine Frage gewertet werden.

Die Grafik zeigt nur ein sehr einfaches Beispiel. Andere Frames 
enthalten 12 oder mehr Datenbytes.

Wie geht man da vor? Lädt man sich die zu sendenden Pakete in ein Array? 
Oder arbeitet man da eher mit abstrakten Symbolen? Mir fehlt da grad 
jede Idee...

(Programmiersprache ist Javascript, sollte aber fürs Konzept keine Rolle 
spielen?)

Vielen Dank!

: Bearbeitet durch User
von Klaus S. (kseege)


Lesenswert?

Olli Z. schrieb:
> Wie geht man da vor?

Typischerweise überlegt man zuerst mal, welche Randbedingungen erfüllt 
werden sollen.
Ein Minimalist fragt sich, was das System mindestens leisten muß 
(wieviel Teilnehmer, wieviel Messages je Sekunde).
Ein Maximalist fragt sich zuerst, was das System in Zukunft leisten 
soll.
Erst danach überlegt man, wie man das wohl realisiert.

Gruß Klaus (der soundsovielte)

von Max M. (jens2001)


Lesenswert?

Olli Z. schrieb:
> Programmiersprache ist Javascript

Auf einem Mikrcontroller?

von Daniel D. (danielduese)


Lesenswert?

Max M. schrieb:
>> Programmiersprache ist Javascript
>
> Auf einem Mikrcontroller?

Gibts sogar für dem RP Pico

: Bearbeitet durch User
von Motopick (motopick)


Lesenswert?

Wenn einem nichts besseres einfaellt, schreibt ein, zwei, ... N
Statemachines, und das Problem ist geloest.

von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Mir geht es um den theoretischen Ansatz, aber wenn es hilft können wir 
das gern am praktischen Beispiel machen.

------------------------------------------------

Das serielle Bauteile über das ich hier spreche ist ein Funkempfänger. 
Dieser nutzt eine 1-Draht Schnittstelle welche am normalerweise daran 
angeschlossenen Steuergerät mittels eines LIN-Transceivers angeschlossen 
ist. Es handelt sich beim Übertragungsprotokoll (Layer 1) aber nicht um 
LIN, sondern um einfachen UART mit 9600 Baud 8E1. Ich möchte mit diesem 
Empfänger arbeiten und daher die Funktion des Steuergeräts emulieren. 
Zunächst an einem PC mit ScriptCommunicator als Framework.

Im Original hängen mehrere Teilnehmer am gleichen 1-Draht Bus, es gibt 
aber keine direkte Adressierung. Vielmehr reagieren bestimmte Teilnehmer 
nur auf bestimmte Kommandos.

Die Datagramm-Frequenz habe ich noch nicht gemessen, ist aber 
überschaubar da anders als bei CAN nur ereignisbehaftet gesendet wird.

Der Empfänger kann auf bestimmte Sender "programmiert" werden. Jeder 
Sender hat eine 4 Byte ID. Sendet ein solches drahtlosgerät, sendet der 
Empfänger selbst, spontan (also eben nicht wie bei I2C) eine Botschaft 
mit dem Inhalt.

Die Datagramme haben immer diesen Aufbau:
1
DIR LEN CMD [DATA..] CRC
DATA ist optional und in meinen Messungen bis zu 15 Bytes lang.

Anders als in "klassischen" UART Anwendungen gibt es keinen Handshake 
und auch kein EOL Zeichen. Man muss also am Datenstrom erkennen wie 
viele Bytes zu einem Frame gehören. Das LEN Byte gibt dabei die Anzahl 
der nach ihm folgenden Bytes an.

Das CMD Byte gibt Aufschluss darüber ob ein Datagramm per Funk empfangen 
wurde, oder man was vom Empfängerbaustein will (z.B. eine ID 
programmieren).

CRC ist einfach ein XOR aller Bytes des Frame.

------------------------------------------------

Als Schnittstelle habe ich einen FTDI-USB UART von dem RX und TX über 
einen LIN-Transceiver am 1-Draht Bus des Empfängers hängt.

Aufgrund des 1-Draht Busses ist obligatorisch das die Busteilnehmer die 
selbst ausgesandten Daten auch wieder empfangen.

Da es keine Bus-Arbitierung, Master/Slave oder ähnliches gibt, ist es 
wichtig das der Sender prüft ob gerade Daten auf dem Bus geschickt 
werden oder nicht um eine laufende Kommunikation nicht zu stören. TX und 
RX müssen sich also intern gegenseitig verriegeln damit sich Sendungen 
von A->B nicht mit dem eigenen Echo von B->A vermischen, etc.

------------------------------------------------

Das Protokoll ist mir nicht 100% bekannt, man erkennt aber einen paar 
Flows. Zum einen wird ein vom Steuergerät gesendetes Kommando vom 
Empfänger quittiert indem er es zurücksendet und nach dem CMD Byte ein 
ACK-Byte (0x01) oder NAK-Byte (0x00) voranstellt.

Darüber hinaus gibt es Sequenzen die mehrere Datenpakete beinhalten, wie 
z.B. hier am Beispiel der Sender-ID Programmierung:
- Steuergerät sendet "Wakeup" (wird nicht quittiert)
- Steuergerät sendet "Start der Programmierung" (wird quittiert)
- Steuergerät sendet "Sender-ID 1" (wird quittiert)
- Steuergerät sendet weitere Sender-IDs...
- Steuergerät sendet eine Ende-Sequenz (wird quittiert)

------------------------------------------------

von Klaus S. (kseege)


Lesenswert?

Olli Z. schrieb:
> Die Datagramm-Frequenz habe ich noch nicht gemessen, ist aber
> überschaubar da anders als bei CAN nur ereignisbehaftet gesendet wird.

"Überschaubar" ist ein Gummibegriff und CAN wurde extra für 
prioritätsgesteuerte ereignisbasierte Peer-to-Peer-Kommunikation 
erfunden (im Gegensatz zu zyklischen Protokollen wie ASI oder Interbus 
und Token-basierenden Round-Robin-Sytemen wie ArcNet).

Gruß Klaus (der soundsovielte)

von Steve van de Grens (roehrmond)


Lesenswert?

Versuche nicht, eine Eier-legende Wollmilchsau zu schaffen. Programmiere 
nur das, was du wirklich brauchst. Nichts verschwindet schneller wieder 
in der Versenkung, als Software.

von Olli Z. (z80freak)


Lesenswert?

Mein Ansatz bislang war folgender:

Empfang der Daten über ISR
--------------------------
Die vom UART empfangenen Bytes lösen einen Interrupt aus. Die ISR 
schiebt die Daten einfach in einen RX-Buffer damit nichts verloren geht. 
Im Javascript löse ich das mit einem Array auf das die Bytes gepushed 
werden:
1
var rxBuffer = [];
2
3
function rxISR()
4
{
5
  rxBuffer.push(serialPort.readAll());
6
}

Auswertung der Daten über Protokoll-Dekoder
-------------------------------------------
Seine Aufgabe ist es in den Empfangenen Daten einen Frame zu entdecken 
und als Frames in die Mailbox zu werfen.

Aufgerufen wird der Dekoder über einen Single-Shot Timer. Nach 
Beendigung seiner Funktion aktiviert er diesen wieder. Die Auslösezeit 
kann abhängig vom Zustand der Routine sein: ist grad nichts im Buffer 
wartet er länger.
1
const FRAME_LEN_MIN = 2;
2
const FRAME_LEN_MAX = 0x1F;
3
4
var rxDeframerMode = 1;
5
var rxDeframerLen = 0;
6
var rxDeframerFrame = [];
7
8
function rxDeframer()
9
{
10
  var dirBytes = [ 0x12, 0x21 ];
11
  var nextPoll = 100;
12
  
13
  // find next DIR (start of frame)
14
  if (rxDeframerMode == 1 && rxBuffer.length > 0)
15
  {
16
    while (var dir = rxBuffer.shift())
17
    {
18
    if (dirBytes.indexOf(dir) > -1)
19
    {
20
      rxDeframerFrame = [ dir ];
21
      rxDeframerMode = 2;
22
          rxDeframerLen = 0;
23
      break;
24
    }
25
    }
26
  }
27
28
  // read LEN byte
29
  if (rxDeframerMode == 2 && rxBuffer.length > 0)
30
  {
31
    // LEN sanity check
32
      if (rxBuffer[0] < FRAME_LEN_MIN || rxBuffer[0] > FRAME_LEN_MAX)
33
    {
34
    rxDeframerMode = 1;
35
    }
36
    else
37
    {
38
      rxDeframerLen = rxBuffer.shift();
39
      rxDeframerFrame.push(rxDeframerLen);
40
      rxDeframerMode = 3;
41
    }
42
  }
43
44
  // read DATA bytes (wait until all needed bytes in buffer)
45
  if (rxDeframerMode == 3 && rxBuffer.length >= rxDeframerLen)
46
  {
47
    // verify CRC
48
    var crcRead = rxBuffer[rxDeframerLen-1];
49
    var crcCalc = calcChecksum(rxDeframerFrame.concat(rxBuffer.slice(0, rxDeframerLen-1)));
50
    if (crcCalc == crcRead)
51
    {
52
    // valid frame found
53
    rxDeframerFrame.push(rxBuffer.splice(0, rxDeframerLen));
54
    rxMailbox.push(rxDeframerFrame);
55
    rxDeframerMode = 1;
56
    }
57
    else
58
    {
59
    // invalid frame, restart tokenizer
60
    rxDeframerMode = 1;
61
    }
62
  }
63
  
64
  // if unhandled bytes left in buffer, do short-wait
65
  if (rxBuffer.length > 0) {
66
    nextPoll = 10;
67
  }
68
  
69
  rxDeframerTimer.start(nextPoll); // restart for next poll
70
}

Ein Frame beginnt immer mit einem DIR-Byte. Der Einfachheit halber 
nehmen wir an das dieses nur 0x12 oder 0x21 sein kann.
Ein solches Byte kann aber auch aus einem anderen Fragment einer 
Übertragung aufgeschnappt worden sein. Wenn also am Anfang des Buffers 
kein DIR-Byte steht, müssen die Bytes verworfen werden bis ein solches 
gefunden wurde.

Wurde ein Byte als DIR-Byte qualifiziert wird als nächstes ein LEN-Byte 
erwartet. Das kann man nun auf Plausibilität prüfen (mind. 2 max. X 
Zeichen) und ggf. wieder von vorn anfangen wenn es nicht passt.

Sobald die angegebene LEN Anzahl Bytes im rxBuffer liegen, überprüfe ich 
das CRC. Stimmt das nicht, ist entweder die Übertragung gestört gewesen 
oder das erste und zweite Byte waren Daten und kein DIR LEN. In diesem 
Fall verwerfe ich DIR und LEN und starte neu.

Habe ich ein gültiges Frame, lege ich das in die Mailbox.

von Klaus S. (kseege)


Lesenswert?

Olli Z. schrieb:
> Im Original hängen mehrere Teilnehmer am gleichen 1-Draht Bus, es gibt
> aber keine direkte Adressierung.

> Der Empfänger kann auf bestimmte Sender "programmiert" werden. Jeder
> Sender hat eine 4 Byte ID.

Ich empfinde Deine Beschreibung als in sich widersprüchlich. Manchmal 
ist eine Zeichnung (selbst von Hand) aussgekräftiger als viele Wörter.

Gruß Klaus (der soundsovielte)

von Olli Z. (z80freak)


Lesenswert?

Klaus S. schrieb:
> Olli Z. schrieb:
>> Im Original hängen mehrere Teilnehmer am gleichen 1-Draht Bus, es gibt
>> aber keine direkte Adressierung.
>
>> Der Empfänger kann auf bestimmte Sender "programmiert" werden. Jeder
>> Sender hat eine 4 Byte ID.
>
> Ich empfinde Deine Beschreibung als in sich widersprüchlich. Manchmal
> ist eine Zeichnung (selbst von Hand) aussgekräftiger als viele Wörter.

Hm, habe ich doch angefügt...

Aber die o.g. ID ist Teil der Nutzdaten und hat nichts mit 
Empfängerbaustein und Steuergerät zu tun, sondern ist eine Kennung der 
Funksender welche vom Empfängerbaustein empfangen werden.

: Bearbeitet durch User
von Olli Z. (z80freak)


Lesenswert?

Steve van de Grens schrieb:
> Versuche nicht, eine Eier-legende Wollmilchsau zu schaffen. Programmiere
> nur das, was du wirklich brauchst. Nichts verschwindet schneller wieder
> in der Versenkung, als Software.

Tue ich nicht, maximal erfinde ich das Rad neu ;-) Aber ich will das für 
mich verstehen, nicht unbedingt um ein laufendes Programm zu haben, 
sondern wie man sowas lösen kann.

Ich denke das der modulare Ansatz sogar sehr hilfreich ist, denn es 
kümmern sich unabhängige Module (threads) um verschiedene Dinge. Sprich, 
in der Hauptroutine werde ich nur mit den Mailboxen arbeiten. Ich 
schiebe eine Nachricht in die txMailbox rein und erwarte innerhalb einer 
gewissen Zeit eine Antwort in der rxMailbox. Dabei brauche ich mich 
nicht drum zu kümmern das diese Nachricht störungsfrei auf den Bus geht 
und wie die Antwort herein kommt, das machen alles die darunter 
liegenden Schichten, sicher und performant.

von Steve van de Grens (roehrmond)


Lesenswert?

Olli Z. schrieb:
> Aber ich will das für mich verstehen, nicht unbedingt um ein
> laufendes Programm zu haben, sondern wie man sowas lösen kann.

Dann überlege dir, wie dein Konzept mit Daten funktioniert, die nicht 
ins RAM passen und wo die finale Länge erst während der Übertragung 
ermittelt wird. Das ist ja (je nach Anwendung) auch ein durchaus 
realistisches Szenario.

von Klaus S. (kseege)


Lesenswert?

Olli Z. schrieb:
> Ich denke das der modulare Ansatz sogar sehr hilfreich ist, denn es
> kümmern sich unabhängige Module (threads) um verschiedene Dinge. Sprich,
> in der Hauptroutine werde ich nur mit den Mailboxen arbeiten. Ich
> schiebe eine Nachricht in die txMailbox rein und erwarte innerhalb einer
> gewissen Zeit eine Antwort in der rxMailbox. Dabei brauche ich mich
> nicht drum zu kümmern das diese Nachricht störungsfrei auf den Bus geht
> und wie die Antwort herein kommt, das machen alles die darunter
> liegenden Schichten, sicher und performant.

Ja, genau so. Und jetzt fehlt nur noch ein zweites Programm, das die 
Datagramme aus der Mailbox holt, sie interpretiert und die angeforderten 
Aktionen ausführt. Motopick hat bereits geschrieben, wie es am 
bequemsten geht. Was daran hast Du nicht verstanden?

Gruß Klaus (der soundsovielte)

von Peter D. (peda)


Lesenswert?

Ich finde Protokolle mit Timeout immer gruselig. Will man solche 
Protokolle über andere Schnittstellen tunneln, geht dabei die 
Zeitbeziehung verloren.

Ich lese einfach alle Bytes in einen FIFO, egal wie lange es dauert. Es 
kostet ja keine CPU-Zeit.
Sobald ein Byte im FIFO ist, wird es in den Parsepuffer geschoben. 
Dieser prüft auf das Endezeichen und erst dann parst er das ganze Paket. 
Bei einem Überlauf wird der Puffer nicht weitergezählt, d.h. das neue 
Byte überschreibt das letzte. Kann das Endezeichen auch im Datenfeld 
auftreten, wird dort ein Escapezeichen eingefügt. Damit weiß der Parser, 
daß es nicht das Ende ist.
Damit ich mich beim Debuggen nicht mit Binärdaten abquälen muß, 
übertrage ich bei neuen Geräten die Daten nur noch als Klartext.

von Achim H. (pluto25)


Lesenswert?

Olli Z. schrieb:
> - Steuergerät sendet "Wakeup" (wird nicht quittiert)

Das würde ich nochmal überdenken. Wenn er das schlecht verstanden hat 
(Crc Fehler) wacht er nicht auf.
Wenn alle jederzeit drauf los plappern dürfen sollten sie immer zuhören. 
Auswerten ob sie angesprochen wurden oder die Daten für sie interessant 
sind. Wenn eine Frage/Anweisung an einen anderen gerichtet ist, dessen 
Antwort abwarten um dann sofort die eigene Frage/Anweisung zu senden. 
Der Master hat dann die Antwort auf seine Frage und gleich die Frage 
eines anderen um dann darauf zu reagieren.
Wenn viele Slaves regelmäßig eigenständig Plappern wollen brauchts noch 
eine weitere Regel z.B. er darf nur dann seine Frage anhängen wenn sein 
Vorgänger (der mit der nächst niedrigeren ID) geantwortet hat.
Oder (wie im einem Wechselrichterprotokoll) nach einer zufälligen Zeit 
loslegen. Spricht dann schon jemand wird eben gewachtet auf die nächste 
Frage.
Eine weitere zufällige Zeit nachdem der ausgeredet hat würde auch gehen.
Hier fragt nur einer wenn der Master schläft (5 Minuten nichts mehr 
gefragt hat.) Anderenfalls braucht er auch nicht fragen, da die 
gewünschten Daten in den Antworten zum Master vorhanden sind.

von Olli Z. (z80freak)


Lesenswert?

Peter D. schrieb:
> Ich finde Protokolle mit Timeout immer gruselig. Will man solche
Das soll ja nur der Betriebssicherheit dienen, sodass Störungen etc. 
nicht zu einem Deadlock führen.

> Ich lese einfach alle Bytes in einen FIFO, egal wie lange es dauert. Es
Ja, genauso mache ich es auch.

> Sobald ein Byte im FIFO ist, wird es in den Parsepuffer geschoben.
> Dieser prüft auf das Endezeichen und erst dann parst er das ganze Paket.
Auch das ist mein Ansatz. So eine Art Tokenizer für Frames. Die 
Herausforderung ist das Ende-Zeichen. Das ergibt sich bei mir nur durch 
die Kombination LEN + gültige CRC. Es könnte nämlich immer sein das ich 
etwas gelesen habe was ich für ein DIR und LEN halte, es aber nur Daten 
waren. Daher muss ich warten bis ich genug Bytes im Puffer habe um das 
zu entscheiden.

> Bei einem Überlauf wird der Puffer nicht weitergezählt, d.h. das neue
Was wäre bei mir ein Überlauf? Hm.. also ich empfange und empfange und 
mein Tokenizer erkennt keine gültigen Frames. Da habe ich den Ansatz 
gewählt das ich bei einem Musterfehler (DIR und LEN scheinten zu 
stimmen, aber die CRC passt dann nicht) so viele Bytes vom Anfang des 
Puffers wegwerfe bis ich wieder eine gutaussehende DIR/LEN Kombination 
finde. Danach beginnt das Spiel von neuem. So kann mir der Puffer 
eigentlich nicht überlaufen.

> Byte überschreibt das letzte. Kann das Endezeichen auch im Datenfeld
> auftreten, wird dort ein Escapezeichen eingefügt. Damit weiß der Parser,
> daß es nicht das Ende ist.
Das muss er aber wissen das dies Daten sind. Klingt ein wenig nach 
Henne/Ei Problem für mich.

> Damit ich mich beim Debuggen nicht mit Binärdaten abquälen muß,
> übertrage ich bei neuen Geräten die Daten nur noch als Klartext.
Ich sehe das Konzept, aber dieses Protokoll ist nicht von mir, daher 
habe ich diesbezüglich keine Optionen.

von Olli Z. (z80freak)


Lesenswert?

Achim H. schrieb:
> Olli Z. schrieb:
>> - Steuergerät sendet "Wakeup" (wird nicht quittiert)
> Das würde ich nochmal überdenken. Wenn er das schlecht verstanden hat
> (Crc Fehler) wacht er nicht auf.

Genau, sowas kann passieren. Ich habe das sogar bei meinen Sniffs 
zwischen Original-Steuergerät und Empfänger beobachtet beim Bootstrap. 
Da sendet das Steuergerät mehrere Befehle ohne Antwort vom Empfänger. 
Dann hält es kurz inne und beginnt wieder mit einem Wakeup. Beim zweiten 
Mal klappte es dann immer. Klemme ich den Empfänger ab, macht das 
Steuergerät das bis zum Sanktnimmerleinstag. Daran finde ich jetzt 
nichts falsches.

Und genau für sowas brauche ich Timeout-Timer, oder es muss ins 
Protokoll der Kommunikation eingebaut werden. Quasi 3x denselben Befehl 
senden und wenn dann keine Antwort kommt => Neustart.

> Wenn alle jederzeit drauf los plappern dürfen sollten sie immer zuhören.
Ganz so schlimm ist es in der Praxis wohl nicht. Es scheint so zu sein 
das wenn ich als Steuergerät dieses Wakeup sende, hält der Empfänger 
erstmal den Mund, die anderen auch. Damit habe ich etwas Zeit weitere 
Daten zu senden, selbst wenn der Empfänger ein gültiges Datagramm von 
einem Funksender erhält welches er auf den Bus legen könnte.

Der Rest Deiner Antwort leuchtet mir ein. Ich habe zunächst vor nur mit 
einem Empfänger zu arbeiten, das wird die Sache enorm vereinfachen. Ggf. 
nehme ich für einen weiteren Empfänger einfach eine weitere 
Schnittstelle.

Ich glaube das ich mich ein Stück darauf verlassen kann das nach einem 
Frame vom Steuergerät an den Empfänger, dieser nur auf diese Nachricht 
antwortet. Da Sende-/Empfangs-Frames keine Session-ID enthalten wäre es 
sonst auch unmöglich zu wissen ob die Antwort die gerade kommt zum 
letzten Kommando oder einem davor gehören würde.

von Olli Z. (z80freak)


Lesenswert?

Nochmal zum Ablauf. Die Daten werden praktisch immer asynchron und 
non-blocking im Hintergrund empfangen, geparsed und in einer rxMailbox 
gepuffert. Ebenso Sendedaten.

Den Code dazu habe ich noch etwas verfeinert und er scheint so auch gut 
zu laufen.

Ich bediene mit meiner Sequenz also nur die Mailboxen. Die Sequenz 
enthält ja erstmal nur die gewünschte Ausführung, müsste aber auch auf 
Probleme eingehen können. Also doch eine State-Machine?

Wenn ich als Steuergerät sende, wird mir der Empfänger nicht mit 
Funkdatagrammen dazwischenfunken. Diese Annahme KANN falsch sein, das 
muss ich noch näher untersuchen. Wenn es falsch ist, dann müsste ich ja 
die rxMailbox nach einer zum zuletzt gesendeten Befehl passenden Antwort 
durchsuchen.

Genau hier drehe ich mich grad im Kreis. Ist es eine Routine die die 
rxMailbox Frame für Frame abarbeitet und daraus weitere Aktionen 
auslöst, oder ist es meine Sequenz die sendet und auf die Antwort 
wartet?

Bei letzterem bräuchte man keine Statemachine, hätte aber im Code 
Warteschleifen auf Ereignisse. Diese ließen sich aber sehr einfach mit 
einem Timeout ummanteln.

Mache ich das Frame-Gesteuert dann muss der rxMailbox-Handler wissen was 
er mit einer Nachricht zu tun hat. Wie programmiert man sowas?

: Bearbeitet durch User
von Achim H. (pluto25)


Lesenswert?

Olli Z. schrieb:
> Klemme ich den Empfänger ab, macht das Steuergerät das bis
> zum Sanktnimmerleinstag.
Das sollte er natürlich nicht. Wenn einer mehrfach nicht antwortet hat 
er z.B. einfach keinen Strom oder einen Schaden? Das muß der Master 
wissen um dann eine entsprechende Meldung ab zu geben. Er hat ja auch 
noch andere Jobs die nicht liegen bleiben sollten/dürfen.

> wäre es sonst auch unmöglich zu wissen ob die Antwort die gerade kommt
> zum letzten Kommando oder einem davor gehören würde
Ist das denn wichtig? Oder welche Routine gefragt hat? Jede Antwort 
enthält Daten die irgendwo benötigt werden. Diese einfach ablegen und 
die nächste bearbeiten. Spätestens wenn alle Boxen leer sind fällt auf 
das die Antwort fehlt. Dann nochmal fragen, evt Fehlerzähler erhöhen und 
wenn nichts kommt den Slave als "Problemkind" melden.

> Wenn ich als Steuergerät sende, wird mir der Empfänger nicht mit
> Funkdatagrammen dazwischenfunken.

Dazwischenfunken sollte natürlich niemand, da können Regeln festgelegt 
werden (Busüberwachung / Timeouts). Andererseits solange der Bus das 
Überlebt gibts nur defekte Daten. Die sorgen nur für eine Verzögerung 
ohne weitere Probleme. (Solange nicht immer alle gleichzeitig Quatschen 
;-)

> dann muss der rxMailbox-Handler wissen was
> er mit einer Nachricht zu tun hat

Er kann unterscheiden ob es die eigene Frage war = Verwerfen, evt 
"Hardware OK " Flag setzen.
Dann ob es eine fremde Frage war - auch verwerfen, evt ein "Slave xy 
lebt" Flag setzen und "Antwortwartezeit" resetten.
Oder ungefragte Antworten - verwerfen, ggf die enthaltenen Daten 
einsortieren zum Anzeigen, auswerten, loggen falls sie für ihn 
interessant sind.
Und Antworten auf eigene Fragen haben ja einen Zweck/eine Routine die 
die Daten braucht. Einsortieren und "Antwort da" Flag setzen. Oder 
gleich die passende Routine auslösen?

von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Beim einlesen in die Erstellung von State-Machines mit JavaScript habe 
ich diesen netten Dienst hier entdeckt: 
https://musing-rosalind-2ce8e7.netlify.app/
Leider kann man sein Werk darin nicht laden und sichern, daher hier mal 
den Code den ich erstellt habe:
1
{
2
  "initial": "idle",
3
  "states": {
4
    "idle": {
5
      "on": {
6
        "btnProgKeysClicked": {
7
          "startProgKeySession": {}
8
        }
9
      }
10
    },
11
    "startProgKeySession": {
12
      "on": {
13
        "cmdProgKeyAccepted": {
14
          "progKeyId1": {}
15
        },
16
        "cmdProgKeyDenied": {
17
          "abort": {}
18
        },
19
        "cmdProgKeyTimeout": {
20
          "cmdProgKeyTimeout": {}
21
        }
22
      }
23
    },
24
    "cmdProgKeyTimeout": {
25
      "on": {
26
        "retryWaitElapsed": {
27
          "startProgKeySession": {}
28
        }
29
      }
30
    },
31
    "progKeyId1": {
32
      "on": {
33
        "cmdProgKeyId1Accepted": {
34
          "progKeyId2": {}
35
        },
36
        "cmdProgKeyId1Denied": {
37
          "abort": {}
38
        },
39
        "cmdProgKeyId1Timeout": {
40
          "abort": {}
41
        }
42
      }
43
    },
44
    "progKeyId2": {
45
      "on": {
46
        "cmdProgKeyId2Accepted": {
47
          "progKeyDone": {}
48
        },
49
        "cmdProgKeyId2Denied": {
50
          "abort": {}
51
        },
52
        "cmdProgKeyId2Timeout": {
53
          "abort": {}
54
        }
55
      }
56
    },
57
    "progKeyDone": {
58
      "type": "final"
59
    },
60
    "abort": {
61
      "type": "final"
62
    }
63
  }
64
}

So könnte das doch schon hinkommen?

von Peter D. (peda)


Lesenswert?

Olli Z. schrieb:
> Das muss er aber wissen das dies Daten sind. Klingt ein wenig nach
> Henne/Ei Problem für mich.

Nö, das ist sehr einfach zu parsen. Wird ESC erkannt, dann wird ein Flag 
gesetzt "das nächste Byte ist immer ein Datenbyte und wird nicht als EOT 
gewertet". Man weiß also spätestens nach 2 Bytes, ob ein Paket zu ende 
ist oder nicht.

Olli Z. schrieb:
> Es könnte nämlich immer sein das ich
> etwas gelesen habe was ich für ein DIR und LEN halte

Eben deshalb habe ich lieber Gewißheit, als nur etwas vermuten zu 
müssen.

Olli Z. schrieb:
> Was wäre bei mir ein Überlauf?

Die Überlaufabsicherung ist nur fehlertolerantes Programmieren. Der 
Sendepointer könnte z.B. in den Wald zeigen und irgendwelche Daten 
senden, die das Protokoll verletzen. Oder es ist eine Funkstrecke 
dazwischen, die einige Bytes verstümmelt. Dann soll der Empfänger nicht 
auch noch abstürzen.
Oder man hat versehentlich einen zu kleinen Puffer gewählt oder 
nachträglich das Protokoll geändert, ohne den Puffer anzupassen.
Ich nehme daher für den Überlauftest immer sizeof() und keine magische 
Nummer. Dann macht der Compiler den Test immer richtig.

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.