Start/End Code einer Nachricht trotz Prüfsumme/CRC richtig erkennen ?

Hallo,

habe den Fehler endlich gefunden, warum keine Antwort kommt wenn als Datenwert (empfDaten.data) eine 235 übertragen wird. Datenpaket sieht dann so: 204,3,0,235,0,0,0,53,204,238

Mein STX Startcode 204 kommt dummwerweise im 16Bit CRC (53,204) nochmal vor. Weshalb das einlesen beim Empfänger fehl schlägt.

Wie löst man das Problem am dümmsten? Egal was ich für einen Start und Ende Erkennungscode verwende, er kann irgendwann in der Prüfsumme auftauchen und versaut alles.

Baut man noch eine zusätzliche Indexabfrage ein? Oder ... ?

const uint8_t STX = '\xCC';        // Startkennzeichen 204 der Nachricht
const uint8_t ETX = '\xEE';        // Endkennzeichen 238 der Nachricht
const uint8_t MasterAddr = 0;    // # Adresse vom Master
const uint8_t myAddr = 3;        // # meine Slave Adresse


union Nachricht
{
    struct
    {
        uint8_t toAddr;
        uint8_t fromAddr;
        int32_t data;
        uint16_t crc;
    };
    uint8_t asArray[8];            // Summe aller struct Datentypen, für Zugriff über Index
} empfDaten, sendDaten;            // zwei gleiche union Buffer anlegen


bool read_Ringbuffer_0()
{   
    static uint8_t index = 0;
    static bool state_Read = false;
    static bool state_Complete = false;
    
    uint16_t c = uart0_getc();                // nächstes Zeichen vom Ringbuffer holen
    
    // UART Lib Fehlercodes    (dezimal)
    //      4096              2048                  1024                 512                   256
    // UART_FRAME_ERROR, UART_OVERRUN_ERROR, UART_PARITY_ERROR, UART_BUFFER_OVERFLOW, UART_NO_DATA
    
    if ( c & UART_NO_DATA) {    
        return false;                        // Abbruch nach 500ns, es gibt nichts zum lesen
    }
    
    if ( c > UART_NO_DATA) {                // irgendein UART Error
        state_UART_MODE = ERROR;            // Status ändern zur weiteren Fehlerbehandlung
        index = 0;
        state_Read = false;
        state_Complete = false;
        return false;                        // es gibt nichts zum lesen
    }
    
    // ------------------------------------------------------------------
    c = (uint8_t)c;                            // Low Byte zur Weiterverarbeitung
        
    if (c == ETX) {                                                // Ende-Kennung ?
        state_Read = false;
        state_Complete = true;        
    }
    if (state_Read == true && (index < sizeof(Nachricht))) {    // Bytes einsortieren
        empfDaten.asArray[index++] = c;
    }
    if (c == STX) {                                                // Start-Kennung ?
        index = 0;
        state_Read = true;
        state_Complete = false;
    }
    if (state_Complete == true && index >= (sizeof(Nachricht)) ) {        // Übertragungsende
        state_Complete = false;
        index = 0;
        if ( calc_CRC16(empfDaten.asArray, sizeof(Nachricht)) == 0) {    // Checksumme korrekt ?
            if (empfDaten.toAddr == myAddr) {                            // bin ich gemeint?
                return true;
            }
        }
    }        
    return false;                // noch nicht fertig oder Checksumme falsch
}

Verwende doch ein Fluchtsymbol.
Das hält den Parser einfach.

Hallo,

meinst du mit Fluchtsymbol ein Steuerzeichen wie \r oder \n ? Ist das am Ende nicht auch nur ein "Code" welcher auch als Dezimalzahl interpretiert werden kann und man hat dann bei einem anderen Wert das gleiche Problem.

Ich lese mir mal das Byte Stuffing durch ...

meinst du mit Fluchtsymbol ein Steuerzeichen wie \r oder \n ? Ist das am Ende nicht auch nur ein "Code" welcher auch als Dezimalzahl interpretiert werden kann und man hat dann bei einem anderen Wert das gleiche Problem.

Hat man nicht!
Das ist doch gerade der Sinn und Zwecke der Übung, dass man damit solchen Probleme aus dem Weg geht.
Keinen anderen Grund, es dafür gibt.

Wenn man z.B. \ als Escapezeichen nimmt, hat man das einzige Problem mit dem \ selber.
Das muss dann zu \ werden.

Hallo,

Escape \ entspricht [ascii] \ und [dezimal] 92, sehe ich im Datalogger, was bedeuten würde das Problem verlagert sich von Dezimalzahl 204 auf 92. So wäre das in meinen Augen mit jedem anderen Zeichen, egal welches. Die Escapezeichen funktionieren laut meiner Meinung nur wenn man Text mit char array überträgt.

Wenn ich mir das so überlege löst das Byte Stuffing auch nicht mein Problem, weil nicht davon ausgehen wird das nochmal eine vorher unbekannte Prüfsumme enthalten ist. Die könnte ja dem Overheadbyte entsprechen und damit auch alles versauen. Man dreht sich im Kreis.

Im Augenblick fällt mir nur folgendes ein. Ich verwende nur ein Startzeichen und sperre die erneute Überprüfung auf das Startzeichen solange die Anzahl der eingelesenen Zeichen nicht der Nachrichtenlänge entspricht. Allerdings kann ich dann das einlesen nicht mehr sofort auf Start bzw. index 0 setzen wenn die Übertragung mittendrin abgebrochen wurde und damit weder das Ende noch den Anfang erkennen. Dann muss ich von Hand reseten. Ohne das Problem arrangiert sich das alles wieder von alleine.

Die Escapezeichen funktionieren laut meiner Meinung nur wenn man Text mit char array überträgt.

Dann nenne es doch "Escape Byte", wenn es dir damit besser ergeht...

Klarer:
Du hast einen Startcode, und einen Endcode.
Soweit so gut...
Und jetzt tauchen diese beiden reservierten Codes auch zwischendurch mal im Datenstrom auf.
Das ist dein aktuelles Problem.
Oder?
Also müssen sie escaped werden damit du die reservierten Kontrollbytes von den gleichlautenden Datenbytes unterscheiden kannst.


Wie auch immer....
Irgend ein Protokoll wirst du einführen müssen!
Eigentlich ist es egal welches......

Hallo,

ich habe doch schon ein Protokoll.
Protokoll.png

Bis zum Oder? ist soweit alles richtig. Nur wie soll ich die nochmals escapen?

zudem es egal ist ob ich schreibe
const byte STX = '\xCC' // Start 204
const byte ETX = '\xEE' // Ende 238
oder
const byte STX = 0xCC
const byte ETX = 0xEE

Dezimal ist das immer 204 bzw. 238. Laut meinem aktuellen Gedankengängen entspricht eine Escape Sequence auch immer einem Dezimalwert der bei mir nicht vorkommen darf sonst klappt es nicht mehr. Escape hin oder her. Die beiden Steuerzeichen müssen Zeichen sein die im Datenstrom nicht vorkommen. Das geht aber nicht, weil im Datenstrom alle Byte Werte von 0 bis 255 vorkommen können. Bei Textübertragung hätte man Luft für Steuerzeichen.

Laut meinem aktuellen Gedankengängen entspricht eine Escape Sequence auch immer einem Dezimalwert der bei mir nicht vorkommen darf sonst klappt es nicht mehr.

Natürlich darf das vorkommen!
Und ebenso natürlich klappt das.
Darum sagte ich ja auch:

Wenn man z.B. \ als Escapezeichen nimmt, hat man das einzige Problem mit dem \ selber.
Das muss dann zu \ werden.

Generationen von Computermenschen handhaben das so.
Computersprachen übergreifend.
Betriebssystem übergreifend.

Ich glaube, dass du das auch so abhandeln kannst....
Da ist nur ein Groschen, welcher da erstmal fallen muss.

Dann zähl doch zum crc einfach eins dazu, wenn es dem Startzeichen 204 entspricht. Und wenn dann beim Empfang von crc 205 ein crc error kommt, ziehst du eins ab und checkst nochmal.

Hallo,

combie, du sprichts mir leider zu sehr in Rätseln, deswegen verstehe ich nicht was ich noch ändern soll. Das Problem habe ich ja schon vorher verstanden. Nur die Lösung nicht. Wie soll ich das den weiter escapen? Es bleibt doch ein Byte 0...255.

Spanier, das ist eine Idee, muss mal überlegen ob das auch praktisch funktionieren würde, wegen dem 2 Byte CRC.

Wir können das gerne ausarbeiten...
Nur nicht Heute.

Hallo,

combie, dass wäre sehr nett, bei mir drängelt nichts.

Spanier, habe mir das weiter überlegt. Wenn ich an der Prüfsumme rumfummel und daraus zwei mögliche mache, habe ich keine eindeutige Prüfsumme mehr auf die ich mich verlassen kann. Wenn ich die 2x prüfe kann auch eine dabei sein, die mir eigentlich sagen wollte das die Datenübertragung einen Fehler hatte. Dann denke ich fälschlicherweise es war alles okay und arbeite mit falschen Werten weiter. Die Idee war aber nicht schlecht, zugegeben.

Das Thema wird bestimmt noch spannend bis der Groschen fällt ... :slight_smile:

Wobei die Wahrscheinlichkeit eines Sechsers im Lotto höher wäre.

Wie genau müssen den die Daten sein? Dann toggle doch irgend ein minderwertiges Bit in der am wenigsten wichtigen Variablen, falls in der CRC das unerwünschte Byte vorkommt und berechne dann die CRC neu. Oder schick ein nixnutzbyte im Payload mit, welches du ungestraft verändern kannst.

combie, dass wäre sehr nett, bei mir drängelt nichts.

So, ich habe jetzt mal zeit gefunden, einen Encoder und einen Decoder aus meiner Wühlkiste zu suchen.
Und eine Testumgebung da drumrum zu bauen, so dass man den ganzen Klumpen auf einem Mega2560 testen kann.

Der Klumpen sieht erstmal etwas wüst aus....
Aber verteilt sich später auf 2 Arduinos und der ganze Testkram kann/muss ja auch noch weg.

Das Durcheinander wird sich also noch lichten.

Funktionsbeschreibung:

Encoder:
Der Encoder nutzt eine Struktur mit den Start, Stop und Escape Byte. Diese kannst du nach belieben wählen.
Die Methode start() beginnt einen Datenblock. Sendet also die Startkennung.
Die Methode write() versucht ein Byte zu senden. Im Fehlerfall (buffer voll) gibt es eine -1 als Rückgabe. Dann muss das Byte nochmal gesendet werden. write() ist also nicht blockierend. write() escaped die Bytes, wenn nötig.
Die Methode end() sendet die Endekennung.

Decoder:
Der Decoder bekommt ein Array zum füllen.
Die Methode read() liest von der Seriellen Schnittstelle, erkennt die Startmarkierung, DeEscaped die Daten, und erkennt die Endekennung. Sobald der Datensatz vollständig empfangen wurde, liefert read() FERTIG.
Die Methode count() sagt, wieviele Bytes ins Array geschrieben wurden
Die Methode reset() stellt die Lesebereitschaft wieder her.

Benutzte Serielle Schnittstellen:
Serial : Für den Seriellen Monitor.
Serial1 : Der Encoder versendet seine Daten darüber
Serial2 : Der Decoder empfängt seine Daten von da.

Du musst also auf dem Mega eine Brücke von TxD1 nach RxD2 schlagen.
Über die Brücke laufen die escapten Daten.
Kannste also da mitschneiden, wenn du sie sehen möchtest.

Zwei Testarrays werden angelegt.
Ein Quellarray, mit allen möglichen Bytes.
Ein Zielarray, gefüllt mit Zufall.

Beim Programmstart, werden dir beide Arrays gezeigt.
Gibst du auf der Konsole ein kleines a ein, wird das Quellarray, durch den Encoder, über die Brücke, zum Decoder gesendet.
Wenn das Array übertragen wurde, wird es dir gezeigt, und du kannst den Erfolg begutachten.

:smiling_imp: Noch Fragen? :smiling_imp:

typedef byte TestArray[256];

struct SteuerZeichen
{
  byte start;
  byte end;
  byte escape;
}s{250,251,252};

TestArray quelle; // wird mit 0 bis 255 vorbesetzt
TestArray ziel;   // wird mit random Zahlen vorbesetzt

void printArray(TestArray & a);
void sendBlock(bool start = false);

class BlockEncoder
{
  private:
  HardwareSerial & output;
  public:
  BlockEncoder(HardwareSerial & output): output(output){}
  void start()
  {
    output.write(s.start);  
    Serial.println("Encoder Start Zeichen gesendet");
  }
  void end()
  {
    output.write(s.end); 
    Serial.println("Encoder Ende Zeichen gesendet");
  }

  int write(byte data)
  {
    if(output.availableForWrite() < 2) return -1;
    if(data == s.start || data == s.end || data == s.escape)  
      output.write(s.escape);
    output.write(data);
    return 0;
  }
};

class BlockDecoder
{
  private:
  enum Status {InSync,InArbeit,IstFertig}status;
  HardwareSerial & input;
  TestArray & ziel;
  unsigned int index;
  bool escape; // merker, ob in escapesequenz

  void write2array(byte data)
  {
    if(index < sizeof(ziel)) ziel[index++] = data;
      else Serial.println("Decoder: Buffer Overflow");
  }
  
  public:
  enum Result {ARBEITE,FERTIG,SYNC,NODATA};

  
  void reset()
  {
    index  = 0;
    escape = false;
    status = InSync;
  }
  
  int read()
  { 
 
    if(!input.available()) return NODATA;
    byte inByte = input.read();
    switch(status)
    {
     
     case IstFertig: return FERTIG;

     case InSync:    if(escape)
                     {
                       escape = false;
                       return SYNC; 
                     }
                     if(inByte == s.escape)
                     {
                       escape = true;
                       return SYNC;
                     }
                     if(inByte == s.start)
                     {
                       status = InArbeit;
                       return ARBEITE; 
                     }
                     return SYNC;
                 
      case InArbeit: if(escape)
                     {
                       write2array(inByte);
                       escape = false;
                       return ARBEITE; 
                     }
                     if(inByte == s.end)
                     {
                       status = IstFertig;
                       return FERTIG;
                     }
                     if(inByte == s.escape) escape = true;
                      else write2array(inByte);
                       
    }
    return ARBEITE;
  }

  int count()
  {
      return index;  
  }
  
  BlockDecoder(HardwareSerial & input,TestArray & ziel): input(input),ziel(ziel)
  {
    reset();
  }

};

BlockEncoder encoder(Serial1);
BlockDecoder decoder(Serial2,ziel);


void setup() 
{
  Serial .begin(9600);
  Serial1.begin(9600);
  Serial2.begin(9600);
  byte i = 0;
  for(auto & q:quelle) q = i++; // vorbesetzen 
  for(auto & z:ziel)   z = random(0, 256); // vorbesetzen 

  Serial.println();
  Serial.println("-------- START ---------");

  
  Serial.println("Quelle:");
  printArray(quelle);
  Serial.println();

  Serial.println("Ziel:");
  printArray(ziel);
  Serial.println();
 
  Serial1.println("Ein paar Schrottdaten, welche (hoffendlich) vom Decoder ignoriert werden.");
 }

void loop() 
{
  if(BlockDecoder::FERTIG == decoder.read()) 
  {
     Serial.println("Fertig!");
     Serial.print("Gelesene Bytes: "); Serial.println(decoder.count());
     printArray(ziel);
     decoder.reset(); // Decoder wieder scharf stellen.
  }

  sendBlock(Serial.available() && Serial.read() == 'a');
}



void sendBlock(bool start )
{
  enum Status{Halt,Startkennung,Data,Endekennung};
  static Status status = Halt;
  static unsigned int index = 0;
    
  switch(status)
  {
    case Halt        :  index = 0;
                        if(start) status = Startkennung;
                        return; 
                        
    case Startkennung : encoder.start();
                        status = Data;
                        return; 
                        
    case Data         : if(-1 == encoder.write(quelle[index])) return;
                        if(++index>=sizeof(quelle))status = Endekennung;
                        return;
                        
    case Endekennung :  encoder.end();
                        status = Halt;
                        return; 
  }
}




void printArray(TestArray & a)
{
  int i = 0;
  for(auto zelle:a)
  {
    Serial.print(zelle); Serial.print(',');
    if(i++>=15)
    {
      Serial.println();
      i=0;
    }
  }
  Serial.println();
}

Hallo,

ich wollte gerade einen Text posten, dann kam deine Antwort dazwischen. :slight_smile: Ich schau mir das in Ruhe an, hoffentlich verstehe ich das. Danke erstmal für deine Mühen. Hast du schon jemals soviel Text geschrieben ... :grin:

Selten.

Es wäre wichtig, dass du den Encoder und Decoder verstehst.
Der Rest ist nur Beiwerk.

combie:
Es wäre wichtig, dass du den Encoder und Decoder verstehst.

genau das ist mein Problem, bin gedanklich irgendwie gefangen, ich gehe das immer wieder durch bis ich es habe, hoffentlich. Fremden Code lesen ist immer so eine Sache. :slight_smile: Eine Frage ich habe ich allerdings sofort, sonst geht gar nichts weiter. Wofür ist das Escapezeichen? Ist das wie beim Byte Stuffing das Overhead Byte mit dem kodiert und dekodiert wird?

Das ist wie bei Sonderzeichen in C. Da ist das Escapezeichen der \

\n = Zeilenvorschub, nur n bleibt n
\ = ein , den \ allein gibt es nicht
\t = ein Tabulator, nur t bleibt t

Die 3 besonderen Zeichen werden immer als 2 Zeichen übertragen, wovon das erste das Escapezeichen ist.

Gruß Tommy

Hallo,

aha, so langsam verstehe ich den Encoder. Wenn das Escapezeichen alleine vorkommt ist es kein Escapezeichen. Es ist dann ein Nutzbyte. Escape wird einem im Datenstrom erkannten Start oder Endezeichen vorangestellt, dann wird es beim decodieren besonders behandelt zum rausfiltern. Das bedeutet die Protokolllänge ist nicht konstant? Davon bin ich immer ausgegangen bis jetzt.
Ich muss es noch paar mal durchgehen bis ich es grundlegend raffe. Der Decoder ist eigentlich der Knackpunkt in meinen Gedankengängen. Danke dir für die Erklärung. Hilft ungemein.
Für heute ist erstmal Data Overload ... :slight_smile: