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
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...
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.
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.
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.
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.
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.
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
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
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.
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
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
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
voidm_licht_AN_(uint8_tAddress,uint16_tstate)
2
{
3
xSemaphoreTake(xBusInUse,portMAX_DELAY);//Blockiere Task bis ressource frei ist
4
{
5
comPaketHeader.AddrTo=Address;
6
comPaketHeader.PaketType=Light;
7
8
structhcl_licht_state*data;
9
data=(structhcl_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:
// 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.
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_tpayloadSize;
6
uint8_tpayload[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