Forum: Mikrocontroller und Digitale Elektronik Protokoll Level zusammen mit Tasks, brauche hilfe


von Holger K. (holgerkraehe)


Lesenswert?

Hallo zusammen


Folgendes szenario:

Ich habe zwei Devices mit RS485 verbunden.
Eines ist Master, das andere Slave.

Nun bin ich daran, ein Protokoll zur kommunikation zu erstellen.
Dabei eröffnen sich mir so einige bisher unbekannte Probleme und 
Aufgabenstellungen. Deshalb brauche ich von euch ein paar Inputs.

Derzeit sieht das ganze so aus:

Auf dem Master läuft FreeRTOS.
Slave ist bisher nicht programmiert, ist mommentan irrelevant.

Auf dem Master wird im UART Interrupt geprüft, ob das Parity bit gesetzt 
ist. Falls ja, handelt es sich um ein Addressbyte. Wenn dieses Byte mit 
der eigenen Adresse übereinstimmt, werden die nächsten x Bytes 
empfangen.

Sobald dies erledigt ist, wird mittels einer semaphore der empfangsTask 
freigegeben. Dieser ruft eine funktion zum Parsen des Buffers auf.
Diese Funktion prüft als erstes, ob die CRC korrekt ist. Falls nein, 
sendet sie ein Paket an den absender zurück. Dazu wird ein outBuffer 
entsprechend beschrieben und mittels einer semaphore ein sendeTask 
gestartet.

Nun habe ich das Problem, dass ich nur einen sendeBuffer habe.
Wenn nun während dem vorbereiten des Sendebuffers, der Task welcher dies 
gestartet hat, unterbrochen wird und ein anderer beginnt welcher 
ebenfalls etwas senden möchte, so ist mein sendebuffer hinüber und es 
kommt ein müll raus.

Wie löst man solche Probleme üblicherweise mit RTOS?

Ein Gedanke wäre, dass in der Senderoutine dynamisch speicher alloziiert 
wird. Wenn dieser gefüllt wurde, könnte diese adresse mittels einer 
queue an den sende task übermittelt werden. Wobei dort das problem 
besteht, dass dann die eigentliche byte-weise übermittlung durch einen 
anderen task unterbrochen werden könnte und dann mit der übermittlung 
eines anderen datenpakets begonnen wird.

Wie löst man sowas?

Danke

von uwe (Gast)


Lesenswert?

mutex, bzw. critical section

von uwe (Gast)


Lesenswert?

Oder ein dritter task der als manager agiert und sich immer nur von 
einem bedienen läßt und dann andere abweist. Dafür müßte jeder Task eine 
ID haben.
Denk dir was aus...

von Holger K. (holgerkraehe)


Lesenswert?

uwe schrieb:
> Denk dir was aus...

Da hab ich eben gerade eine Blockade...
Deshalb bin ich froh um ein paar Inputs.


Ich habe funktionen wie z.B.

LichtAN()
LichtAUS()
LichtPWM()
LichtZustand()

Diese funktionen möchten alle auf den Bus Schreiben.


Wenn ich nun in meinem haupt-task die funktion LichtZustand() aufrufe
Dann bereitet diese den outBuffer entsprechend vor.
da muss dann bereits sichergestellt sein, dass nicht von irgendwoher 
sonst ein Zugriff auf diese Daten stattfindet. Somit müsste ich dann in 
jeder der obigen funktionen zuerst prüfen ob das sempahor gesetzt ist, 
wenn nein ist der bus frei, dann muss ich das semaphor setzen.

Nun stellt sich bereits die erste frage.
könnte ich dass dann auch über eine lokale variable lösen?
BusFrei = 0 oder 1. Dann würde ich zu beginn der funktion mit 
while(BusFrei==0) warten bis der bus frei wird. und danach gleich 
BusFrei = 0; Um den bus zu sperren.

Wenn nun ein anderer Task LichAN aufruft, ist die Variable auf 0 und die 
Funktion bleibt hängen.

uwe schrieb:
> Oder ein dritter task der als manager agiert und sich immer nur von
> einem bedienen läßt und dann andere abweist. Dafür müßte jeder Task eine
> ID haben.

Das klingt auch noch interessant.
Hab nur momentan echt ne Blockade mir das vorzustellen.

: Bearbeitet durch User
von Eric B. (beric)


Lesenswert?

Holger K. schrieb:
> Auf dem Master wird im UART Interrupt geprüft, ob das Parity bit gesetzt
> ist. Falls ja, handelt es sich um ein Addressbyte. Wenn dieses Byte mit
> der eigenen Adresse übereinstimmt, werden die nächsten x Bytes
> empfangen.

Dir ist bewusst, dass das parity-Bit abhängig vom gesendeten Byte 
gesetzt oder gelöscht wird und du das nicht beliebig setzen kann? Das 
heisst, dass du nicht beliebige Werte als Adresse benutzen können wirst. 
zB, 0x00 oder 0x03 schon, aber 0x01 und 0x02 nicht (oder ungekehrt, je 
nachdem wie das Parity definiert wird).

Und da bei RS485 alle Kommunikation sowieso vom Master initiert wird, 
ist diese Abfrage nicht so sinnvoll. Es würde eher auf einem Slave 
zutreffen. Aber auch da würde ich nicht auf das Parity-bit schalten.

von Holger K. (holgerkraehe)


Lesenswert?

Eric B. schrieb:
> Dir ist bewusst, dass das parity-Bit abhängig vom gesendeten Byte
> gesetzt oder gelöscht wird und du das nicht beliebig setzen kann?

Ich mache eine 9bit Übertragung.
Dabei kann ich das neunte Bit manuell setzen.

Eric B. schrieb:
> Und da bei RS485 alle Kommunikation sowieso vom Master initiert wird,
> ist diese Abfrage nicht so sinnvoll. Es würde eher auf einem Slave
> zutreffen. Aber auch da würde ich nicht auf das Parity-bit schalten.

Das Protokoll soll unabhängig vom Master wie auch vom Slave 
funktionieren.
Der Master erfragt beim Slave einen Wert und wartet, bis dieser 
Antwortet.
Dabei ist es natürlich klar, dass das nächste Paket für den Master ist.
Jedoch darf und soll auch der Slave korrekte Adresse in das Paket 
schreiben.

von Eric B. (beric)


Lesenswert?

Holger K. schrieb:
> Nun stellt sich bereits die erste frage.
> könnte ich dass dann auch über eine lokale variable lösen?
> BusFrei = 0 oder 1. Dann würde ich zu beginn der funktion mit
> while(BusFrei==0) warten bis der bus frei wird. und danach gleich
> BusFrei = 0; Um den bus zu sperren.

Nein, weil zwischen der Abfrage und das Setzen ein anderer Task 
dazwischen funken kann.

Stell, taskA und taskB führen folgende Pseudo-Code aus:
1
1: while busFree == 0
2
2:     do_nothing
3
3: busFree = 0
4
4: take_the_bus
Und am Anfang ist busFree gleich 1
1
taskA: 1: while busFree == 0  --> FALSE!
2
     --context switch --
3
4
taskB: 1: while busfree == 0  --> FALSE!
5
taskB: 3: busFree = 0
6
      --context switch --
7
8
taskA: 3: busFree = 0
9
taskA: 4: take_the_bus
10
      --context switch --
11
12
taskB: 4: take_the_bus  **CRASH!**

Die Lösung heisst mutex oder Semaphore, so wie uwe schon angedeutet hat.

von Eric B. (beric)


Lesenswert?

Holger K. schrieb:
> Eric B. schrieb:
>> Dir ist bewusst, dass das parity-Bit abhängig vom gesendeten Byte
>> gesetzt oder gelöscht wird und du das nicht beliebig setzen kann?
>
> Ich mache eine 9bit Übertragung.
> Dabei kann ich das neunte Bit manuell setzen.

Ok, dann ist das 9. Bit aber kein Paritätsbit mehr. Aber warum brauchst 
du das? Datenübertragungsfehler kannst du hiermit dann auf jedem Fall 
nicht abfangen.

> Eric B. schrieb:
>> Und da bei RS485 alle Kommunikation sowieso vom Master initiert wird,
>> ist diese Abfrage nicht so sinnvoll. Es würde eher auf einem Slave
>> zutreffen. Aber auch da würde ich nicht auf das Parity-bit schalten.
>
> Das Protokoll soll unabhängig vom Master wie auch vom Slave
> funktionieren.
> Der Master erfragt beim Slave einen Wert und wartet, bis dieser
> Antwortet.
> Dabei ist es natürlich klar, dass das nächste Paket für den Master ist.
> Jedoch darf und soll auch der Slave korrekte Adresse in das Paket
> schreiben.

So weit so gut, aber die Adresse wird doch immer das 1. Byte sein, oder?
Also braucht man das nicht extra Kennzeichnen.

von Stefan K. (stefan64)


Lesenswert?

Baue Dir einen Task, der msgs versendet und seinen Input durch eine 
Queue bekommt. In diese Queue können alle anderen Tasks ihre msgs 
einstellen. Der Sende-Task arbeitet diese dann sequentiell ab.

Holger K. schrieb:
> Sobald dies erledigt ist, wird mittels einer semaphore der empfangsTask
> freigegeben. Dieser ruft eine funktion zum Parsen des Buffers auf.
> Diese Funktion prüft als erstes, ob die CRC korrekt ist. Falls nein,
> sendet sie ein Paket an den absender zurück.

Der Empfangs-Task sollte wie alle anderen Tasks auch auf die Queue des 
Sende-Tasks schreiben. Für den Sende-Task sind dann Quittungen des 
Empfangs-Tasks msgs wie alle anderen auch.
Wenn die Rückmeldungen des Empfangs-Tasks in der Priorität über anderen 
msgs liegen sollen (ggf. sinnvoll), dann sollte der Empfangs-Task seine 
msgs mit
  xQueueSendToFront
versenden.

Auf welchem mc läuft das Ganze?

Viele Grüße, Stefan

von Holger K. (holgerkraehe)


Lesenswert?

Eric B. schrieb:
> So weit so gut, aber die Adresse wird doch immer das 1. Byte sein, oder?
> Also braucht man das nicht extra Kennzeichnen.

Ja, aber wie erkennst du das erste Byte?
Genau hier liegt der Hund begraben. Das erste Byte zu erkennen.

Dazu mache ich es mit dem neunten Byte einmalig. Das "MagicByte"

Stefan K. schrieb:
> Baue Dir einen Task, der msgs versendet und seinen Input durch eine
> Queue bekommt. In diese Queue können alle anderen Tasks ihre msgs
> einstellen. Der Sende-Task arbeitet diese dann sequentiell ab.

Gefällt mir.

Stefan K. schrieb:
> Wenn die Rückmeldungen des Empfangs-Tasks in der Priorität über anderen
> msgs liegen sollen (ggf. sinnvoll), dann sollte der Empfangs-Task seine
> msgs mit
>   xQueueSendToFront
> versenden.

Die msgs, schreibt man dann in die queue sinnvollerweise einen pointer 
zu einem strukt? Oder soll man direkt die rohdaten hineinschreiben?
Letzteres wäre ja ziemlich umständlich.

Ersteres würde aber voraussetzen, dass es sich um global verfügbare 
strukts handelt. Diese zur design zeit zu erstellen, wäre ziemlich 
ungünstig, da evtl unnötig viele strukte erzeugt werden. Dynamisch 
global verfügbare strukte zu erstellen erscheint mir ziemlich 
kompliziert.

Langsam komme ich meinem Problem näher.

Stefan K. schrieb:
> Auf welchem mc läuft das Ganze?

STM32F105

: Bearbeitet durch User
von Eric B. (beric)


Lesenswert?

Holger K. schrieb:
> Eric B. schrieb:
>> So weit so gut, aber die Adresse wird doch immer das 1. Byte sein, oder?
>> Also braucht man das nicht extra Kennzeichnen.
>
> Ja, aber wie erkennst du das erste Byte?

Kurze Antwort: das erste Byte ist halt das erste.
Wenn die Slaves sich bei laufendem Betrieb resetten können, und so quasi 
in der Mitte einer Nachricht anfangen können Daten zu empfangen, dann 
brauchst du tatsächlich irgendein mechanismus um den Anfang einer neuen 
Nachricht zu erkennen. Das aber von einem einzigen bit abhängig zu 
machen ist ziemlich Fehleranfällig.

von Eric B. (beric)


Lesenswert?

Eine robustere Mehtode der Anfang einer neuen nachrciht zu erkennen wäre 
z.B. zu fordern dass zwischen nachrichten mindestens X ms Busruhe sein 
muss, ein sogenannter Break, und jede Nachricht einen "Header" aus 1 
oder 2 bytes (z.B. 0xAA 0x55) zu verpassen.

Schau dir zur Inspiration mal das LIN Protokoll an:

http://www.cs-group.de/fileadmin/media/Documents/LIN_Specification_Package_2.2A.pdf

§1.1.5 und 2.3

von Stefan K. (stefan64)


Lesenswert?

Warum nimmst Du nicht statt dem parity-Bit das 9.Bit zur 
Adresserkennung? Der STM32F unterstützt doch 8/9-Bit Uarts.

Zu msgs in Queues:
Wie Du vorgehen willst, kommt sehr auf den Inhalt und die Größe Deiner 
Datenpakete an.

1.Möglichkeit:
Alle Daten einer msg direkt in die Queue. Das ist das einfachste 
Verfahren. Allerdings muss sich die Queue-Größe nach dem größten 
Datenpaket richten.

2.Möglichkeit:
Ein Task, der eine msg verschicken will, holt sich Speicher per malloc() 
und schreibt nur den ptr darauf in die Queue. Der Sende-Task gibt nach 
erfolgtem Versenden den Speicher per free() wieder frei. Eignet sich vor 
allem dann, wenn die msgs länger werden und eine sehr unterschiedliche 
Länge besitzen. Bichts für Leute, die sich malloc() und free() in mcs 
nicht vorstellen können. Für malloc() und free() stellt FreeRTOS 
verschiedene thread-save Varianten zur Verfügung, siehe auch freRTOS 
Memory Management:
http://www.freertos.org/a00111.html

3.Möglichkeit:
Wie 2, nur als eigener Buffer, der selbst verwaltet wird. Nicht ganz 
einfach thread-save zu implementieren und vor Allem,das auch zu testen.

Was ich bisher von Deinem Projekt gelesen habe ("Licht an/aus"), würde 
ich 1. bevorzugen, da die Länge der Msgs anscheinend überschaubar ist.

Wichtig noch:
Der Sende-Task sollte möglichst ohne spezifische Informationen über die 
einzelnen msgs auskommen. Das Einzige, was den Sende-Task interessieren 
solte , ist die Länge der msg.

Viele Grüße, Stefan

von Holger K. (holgerkraehe)


Lesenswert?

Hallo Stefan
Vielen Dank für deine Antwort.

Ich habe eben genau an Szenario 3 gedacht welches sehr komplex wird. Und 
bei Szenario 2 würde ich mir nich sicher sein, ob es denn auch wirklich 
keine probleme gibt (eben, wie testen...)

Szenario 1 erscheint mir etwas unschön, da ich dann auch noch in de 
queue erkenne müsste, wo die nächsten daten beginnen und wie viele daten 
pakete vorhanden sind. Schlussendlich würde eine queue das problem nicht 
lösen sondern stellt nur einen buffer zur verfügung.


Ich habe mir nun folgendes überlegt:
1
void m_licht_AN_(uint8_t Address, uint16_t state)
2
{
3
  xSemaphoreTake(xBusInUse,portMAX_DELAY); //Blockiere Task bis ressource frei ist
4
  {
5
    comPaketHeader.AddrTo = Address;
6
    comPaketHeader.PaketType = Light;
7
8
    struct hcl_licht_state* data;
9
    data = (struct hcl_licht_state*) &comSendData[0];
10
11
    data->state = on;
12
    data->frequency = 0;
13
14
    //Parsen und senden
15
    prot_Parse_OutBuffer();
16
17
    //warten auf transmission complete
18
    xSemaphoreTake(xBusTCflag,portMAX_DELAY);
19
  }
20
  xSemaphoreGive(xBusInUse); //Bus Freigeben
21
}


Im haupt task, wo das userprogramm läuft, ruft man m_LichtAN auf.
Wenn nun der bus bereits belegt ist durch einen früheren aufruf, wird 
der Task geblockt, bis xBusInUse wieder zurückgegeben wurde.

Innerhalb der funktion m_LichtAN, wird nun auf den geteilten 
Speicherbereich zugegriffen (comPaketHeader und comSendData etc...)

prot_Parse_OutBuffer, kopiert dann den inhalt der Strukte in einen 
eigenen sendebuffer und setzt eine semaphore welche den UART TX Task 
freigibt.

Theoretisch würde das Programm nun aus prot_Parse_OutBuffer herauskommen 
und dann die semaphore xBusInUse freigeben.

Ich weiss dann aber ja noch nicht, ob der UART Task bereits gesendet 
hat.
Theoretisch könnte dann ein anderer Task die Daten wieder ändern, bevor 
der UART gesendet hat. Deshalb habe ich noch ein TransmissionComplete 
Semaphor vorgesehen.

UART sendet nun das Paket.
Vom Slave wird nun ein ACK erwartet.
In diesem status, können nur noch daten empfangen werden, da alle Tasks 
auf xBusInUse warten und der einzige welcher senden durfte auf 
TransmissionComplete wartet.

Wenn ich nun ein Paket empfange, dann wird der empfangstask gewekt. 
Dieser sieht nun nach ob es sich um ein ACK Paket handetl und ob die CRC 
stimmt.
Wenn die CRC falsch ist, sendet dieser ein "ReSend" paket an den Slave.

Dies geht solange bis die CRC stimmt.

Hier der CodeTeil dazu:

1
    //Verarbeite den eingehenden Buffer
2
    while( prot_Parse_InBuffer((uint8_t* )rxBufferB) == 0 )
3
    {
4
      // Wir sperren den Task erneut, bis wir ein Paket mit korrekter CRC erhalten haben.
5
      // prot_Parse_InBuffer hat ein CRCFault Paket gesendet.
6
                       //Warte bis neues Datenpaket angekommen ist.
7
      xSemaphoreTake(xUSART2RXSemaphore,portMAX_DELAY);
8
    }
9
10
    //Prüfen ob wir nur ein ACK erhalten haben.
11
    //Falls ja, dann war die Transmission erfolgreich tc = 1;
12
    if(comPaketHeader.PaketType == ACK)
13
    {
14
      xSemaphoreGive(xBusTCflag);
15
    }
16
    else //Ansonsten bearbeiten wir die Daten
17
    {
18
19
    }

Wenn nun die CRC stimmt, und es sich um ein ACK Paket handelt, dann wird 
xBusTCFlag zurückgebeben (ebenfalls oben im Code ersichtlich) und somit 
geht es bei m_LichtAN weiter. Dort wird dann xBusInUser wieder 
freigegeben und es kann wieder erneut gesendet werden.

Ich denke, damit bin ich wohl auf dem richtigen weg oder?

Es gibt noch eine unschönheit. Wenn nun mein slave nicht mehr antwortet, 
weil es z.B. defekt ist oder getrennt ist, dann bleibe ich immer in 
m_LichtAN hängen, da TCflag nie zurückkommt.

Nun kann ich ja mit dem delay ein timeout setzen
1
xSemaphoreTake(xBusTCflag,portMAX_DELAY);

Dieses habe ich ja hier auf unendlich.
Mir ist bewusst, wie ich es auf einen anderen Wert setze.
Danach müsste ich jedoch prüfen, ob das timeout zu ende war oder ob ich 
das xBusTCflag erhalten habe.

Mir ist nicht bekannt, wie ich dies in FreeRTOS prüfen könnte.
Weiss jemand etwas dazu?

Danke.

von Stefan K. (stefan64)


Lesenswert?

Holger K. schrieb:
> Szenario 1 erscheint mir etwas unschön, da ich dann auch noch in de
> queue erkenne müsste, wo die nächsten daten beginnen und wie viele daten
> pakete vorhanden sind.

Ich denke, Du denkst da zu kompliziert:
Die Queue bekommt als Einträge immer komplette msgs. Immer wenn der 
Sende-Task einen Queue-Eintrag bekommt, muss er genau diese eine msg 
versenden. Deine Queue kann z.B. aus solchen Einträgen bestehen:
1
#define MAX_PAYLOAD_SIZE 20
2
3
struct
4
{
5
  size_t payloadSize;
6
  uint8_t payload[MAX_PAYLOAD_SIZE];
7
}

Der Sende-Task muss nur noch auf Queue-Werte warten und dann 
<payloadSize> Daten aus <payload[]> versenden - und wieder von vorne.


Holger K. schrieb:
> Nun kann ich ja mit dem delay ein timeout setzen
> xSemaphoreTake(xBusTCflag,portMAX_DELAY);

Holger K. schrieb:
> Danach müsste ich jedoch prüfen, ob das timeout zu ende war oder ob ich
> das xBusTCflag erhalten habe.
>
> Mir ist nicht bekannt, wie ich dies in FreeRTOS prüfen könnte.
> Weiss jemand etwas dazu?

Das liefert Dir xSemaphoreTake() als Returnwert, siehe:
http://www.freertos.org/a00122.html
  Returns:
      pdTRUE if the semaphore was obtained. pdFALSE if xTicksToWait
      expired without the semaphore becoming available.


Gruß, Stefan

: Bearbeitet durch User
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.