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.
:
Bearbeitet durch User
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
:
Bearbeitet durch User
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 | 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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.