Forum: Mikrocontroller und Digitale Elektronik Multi Drop Bus und USART Rx Buffer


von Martin Z. (mzahedi)


Lesenswert?

Hallo liebe Community,

ich habe eine Frage an euch: ich implementiere gerade einen MDB-Sniffer. 
Ich darf zitieren: "MDB is more or less a simple serial protocol. It 
works at 9600, 9, N, 1. That means 9600 Baud, 9 data bits, no parity 
bit, and one stopbit." [1]. Also es handelt sich einfach um ein UART 
Protokoll.

Nun, nachdem es sich um ein Bussystem handelt, werden die Slaves vom 
Master (Vending Machine Controller, VMC) genannt gepollt. Wie hier sehr 
gut zu sehen ist: 
http://blog.bouni.de/blog/2012/07/11/the-mdb-protocol-part-3/ sendet der 
VMC einen Datenblock der max. 36 Byte lang sein kann / die Adressierung 
folgt über das erste Byte des Datenblocks (siehe auch den ersten Link).

Ich möchte die Kommunikation zw. dem VMC und den Slaves mit sniffen und 
komme nun schon langsam zu der Frage:

Ich möchte diese Bytes Event based (ISR) auslesen. Nachdem die Länge des 
Datenblocks variabel sein kann, kann ich nicht mit Hilfe eines UART 
Ringbuffers mit einer fixen Länge arbeiten (so hätte ich nämlich mit der 
SW überprüfen können, wenn voll dann gib mir den Block quasi :) ) .. 
also ich möchte komplette Blöcke erkennen. Der VMC setzt beim ersten 
Byte ein Modebit, der letzte Byte ist die CHK. Dh hier kann ich leider 
nicht mit Hilfe des Modebits das Ende des Datenblocks effizient erkennen 
(und bis zum nächsten Datenblock kann ich ebenfalls nicht warten, da ich 
in bestimmten Fällen sofort antworten muss - ich gebe mich quasi als 
Slave aus).

Nun um das Ende des Blocks zu erkennen fallen mir zwei Optionen ein:
* lt. spec haben wir einen interbyte timeout von 1ms (Timeouts zwischen 
der jeweiligen Bytes des Blocks) .. ich kann in meiner Schleife also die 
Logik implementieren "ist das erste byte da (modebit!), warte ~2 ms (1ms 
timeout + 11 Bits bei 9600 Baud ~= 1,14ms), ist uart rx available, lese 
rx buffer .. wenn nicht datenblock beendet, vmc in "receiving state". 
Die 2ms sind jez schnell daher gesagt. Ein höherer Wert wäre eventuell 
besser...

* ich könnt ebenfalls jedes byte rauslesen und ab dem zweiten byte eine 
"isThisTheLastByte()" Funktion schreiben die mein Array quasi interiert 
und die checksum berechnet und dann mitm letzten byte des arrays 
vergleicht. sprich das ist die checksume, und juhu Datenblock beendet.

Erste Frage ist, welche Lösung ist die bessere / saubere Variante? Bzw 
noch wichtigere Frage, kann ich dieses Problem nicht mit Hilfe meiner 
ISR viel leichter lösen? :-x Dass die HW erkennt, ok Sendepause -> hier 
mein Buffer :)

Ich hoffe das ist irgendwie verständlich für euch :) Freue mich auf eure 
Antowrten :)

Danke und Liebe Grüße aus Wien

[1] http://blog.bouni.de/blog/2012/05/06/the-mdb-protocol-part-1/

von Martin Z. (mzahedi)


Lesenswert?

Wichtiger Nachsatz: nachdem sich nicht viele Entwickler an Regeln/Specs 
usw halten, sehe ich das mit dem Interbyte Timeout ein bisschen 
problematischer :)

Add: und klar ist die zweite Variante rechenaufwendiger, jedoch bestehen 
mehr 85% aller Blöcke aus zwei Bytes da der VMC die Devices einfach 
pollt und ACK zurückbekommt - sprich, es passiert gerade nichts :)

Add: ich arbeite mit einem ATxmega32

: Bearbeitet durch User
von Georg (Gast)


Lesenswert?

Martin Z. schrieb:
> haben wir einen interbyte timeout von 1ms (Timeouts zwischen
> der jeweiligen Bytes des Blocks)

1. Im RxD-Interrupt wird das empfangene Byte in den Buffer geschrieben 
und ein Timer mit 2 ms gestartet bzw. neu gestartet.

2. Im Time-Interrupt (d.h. es sind 2 ms seit dem letzten Empfang 
vergangen) wird ein Flag gesetzt, dass die VMC-Message komplett ist, und 
diese in den Message-Buffer kopiert. Der Timer wird gestoppt, falls er 
sonst seinen Zyklus wiederholt.

CNC-Prüfung und Auswertung erfolgt dann im Hauptprogramm aufgrund des 
Flags.

Georg

von Martin Z. (mzahedi)


Angehängte Dateien:

Lesenswert?

Hi,

danke für die Antwort. Denke auch dass es die beste Variante ist. Somit 
habe ich doppelte Überprüfung. Wie du im Anhang sehen kannst, verzichtet 
der Controller auf das Interbyte Timeout :) und die 
Byte-Übertragungsdauer ist ~1.1ms.
Werde den Timer auf 1.25ms stellen. Bei einer max. Datenblocklänge von 
36 Byte habe ich dann eine Abweichung von ~3.6ms .. um dies zu umgehen, 
werde ich mitm USART buffer arbeiten. Was sagst du? :)

LG

: Bearbeitet durch User
von Georg (Gast)


Lesenswert?

Martin Z. schrieb:
> Bei einer max. Datenblocklänge von
> 36 Byte habe ich dann eine Abweichung von ~3.6ms

Nein, wieso? So wie ich geschrieben habe, wird der Timer bei jedem 
empfangenen Byte neu mit x ms gestartet, da addiert sich nichts auf.

Martin Z. schrieb:
> Werde den Timer auf 1.25ms stellen

Warum? Die Pause zwischen den kompletten messages ist doch deutlich 
grösser, oder nicht?

Georg

von Martin Z. (mzahedi)


Lesenswert?

Natürlich hast recht. Jez habe ich verstanden - denke ich - wie du es 
vorschlägst. Hier ein Pseudo-Code:

1
bool timer_running = 0;
2
bool timer_flag = 0;
3
4
UART_Rx_ISR() { 
5
     if (timer_running) {
6
          Timer_Stop();
7
          if (timer_flag) {
8
               timer_flag = 0;
9
          }
10
     }
11
     USART_RXComplete(); // Stores received data in RX software buffer
12
     
13
     Timer_Set_Counter(0);
14
     Timer_Start();
15
     timer_running = 1;
16
}
17
18
TC_ISR() {
19
     timer_flag = 1;
20
}
21
22
/*******************************/
23
/**  some where in a function **/
24
/*******************************/
25
26
mdb_sniffer() {
27
     while(!timer_flag) {
28
          if(USART_RXBufferData_Available()) {
29
               rec_byte = USART_RXBuffer_GetByte()
30
          }
31
     }
32
     if (recv_len > 0) {
33
          // VMC Data block complete
34
          // do something
35
          // recv_len = 0;
36
     }
37
}

Habe ich das richtig verstanden? :) Timer-Wert setze ich auf 2,5ms oder 
3ms.

> Warum? Die Pause zwischen den kompletten messages ist doch deutlich
grösser, oder nicht?

Es gehts ja nicht um die gesamten Blöcke sondern um die einzelnen Bytes.

Greetz

: Bearbeitet durch User
von Georg (Gast)


Lesenswert?

Martin Z. schrieb:
> Jez habe ich verstanden - denke ich

Nein, nicht wirklich.

Prinzip A: Zeichen werden per RxD-Interrupt empfangen. Immer.

Prinzip B: Eine empfangene message ist vollständig, wenn eine längere 
Pause eintritt. Dann läuft ein Timer ab und in der Timer ISR wird ein 
Flag gesetzt, dass die message jetzt fertig empfangen ist und bearbeitet 
werden kann.

Prinzip C: Dazu wird der Timer bei jedem empfangenen Zeichen neu 
gestartet mit 2 ms, das geschieht im RxD-Interrupt, zusätzlich zum 
Abspeichern des Zeichens. Folglich läuft der Timer nie ab, solange 
zeichen an Zeichen gesendet wird, erst wenn nichts mehr kommt (B).

Prinzip D: Damit danach nichts weiter passiert, wenn ein Zeichen einer 
neuen message empfangen wird, wird der Timer in der Timer ISR nach B 
ganz abgeschaltet.

Prinzip E: Der ganze Zyklus startet neu, wenn das erste Zeichen einer 
message empfangen wird.

Die Implementierung ist furchtbar einfach:

RxD Interrupt:
speichere Zeichen.
Starte 2 ms-Timer
fertig

Time-Interrupt:
Stop Timer
Setze Flag für die Verarbeitung
optional: kopiere die message in einen weiteren Buffer für die 
Verarbeitung
und lösche den Empfangsbuffer
fertig

Verarbeitung:
Setze Flag zurück
tu was nötig ist.

Martin Z. schrieb:
> Es gehts ja nicht um die gesamten Blöcke sondern um die einzelnen Bytes.

Ganz ganz falsch. Was willst du denn mit einem einzelnen Byte?

Georg

von Martin Z. (mzahedi)


Lesenswert?

Sei nicht streng, wir reden eh vom selben :-P

hab deine Logik eh verstanden, nur ein wenig verkompliziert mit meinen 
flags (sollte ein pseudo-code sein ;) )

1
ISR(USART_SLV_RXC_vect) {
2
  USART_NineBits_RXComplete(&USART_SLV);
3
  TCC0_RESTART();
4
}
5
6
ISR(TCC0_OVF_vect) {
7
  TCC0_STOP();
8
  TCC0_flag = 1;
9
}
10
11
void mdb_slave_poll() {
12
  if (TCC0_flag) {
13
    uint16_t recv_byte;
14
    while (mdb_receive_9bit(&USART_SLV, &recv_byte)) {
15
      uint8_t modebit = recv_byte >> 8;
16
      uint8_t payload = recv_byte && 0xFF;
17
18
      if (modebit) {
19
        mdb_slave.slv_addr = payload & MDB_BYTE_FORMAT_ADDR_MASK;
20
      }
21
22
      if (mdb_slave.slv_rx_len < MDB_DATA_MAX_BLOCKSIZE) {
23
        mdb_slave.slv_rx_buf[mdb_slave.slv_rx_len++] = payload;
24
      } else {
25
        LOGGER_INFO("VMC data block exceeds the allowed maximum size\n");
26
      }
27
    }
28
    /* VMC data block complete */
29
    if (mdb_slave.slv_rx_len > 0) {
30
      if (mdb_validate_chk(mdb_slave.slv_rx_buf, &mdb_slave.slv_rx_len)) {
31
        /* TODO: report data and so on*/
32
      }
33
      /* reset the payload length counter */
34
      mdb_slave.slv_rx_len = 0;
35
    }
36
    TCC0_flag = 0;
37
  }
38
}

> Ganz ganz falsch. Was willst du denn mit einem einzelnen Byte?
Auch hier reden wir vom selben :) Natürlich will ich den gesamten Block 
herausfischen. Um dies zu tun, brauche ich ja eben diese pseudo ~2ms um 
Sendepause - sprich "das ist kein Byte mehr gekommen" - zu erkennen :) 
Also wie gesagt, ich denke ich habs :) Danke dir

: 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.