Wie struct einlesen? & serielles Empfangsproblem

Hallo,

ich sende auf der seriellen ein struct

struct Nachricht {
    uint8_t adresse;
    uint8_t funktion;
    uint8_t wert;
} daten;

ich sende:
daten.adresse = 78;
daten.funktion = 79;
daten.wert = 88;

sehe im Datalogger die Übertragung
N O X \n #

und erhalte auf der seriellen µC Ausgabe NOX. Soweit noch kein Problem, ist ascii der Zeichen Darstellung.

ist mein eigenes Endeerkennungszeichen.

sende ich jedoch:
daten.adresse = 78;
daten.funktion = 179;
daten.wert = 188;

wird es verfälscht und ich sehe im Datalogger die Übertragung
N '179' '188' \n #

und erhalte auf der seriellen µC Ausgabe nur eine N. Der Rest fehlt.
Reine Stringübertragungen sind dagegen fehlerfrei.
Ich lese erstmal nur in einen Buffer ein und lasse diesen ausgeben.
Kann man echte Byte Werte nur im String übertragen? Und muss diese dann wieder herausfiltern?
Testweise habe ich die eingelesenen Zeichen mitzählen lassen, es kommt auch nur eines in den Buffer.
Wo verbleibt der Rest?

bool read_Serial_1()
{
  static byte index = 0;
  static byte counter = 0;

  if (Serial1.available() > 0)  {
    char c = Serial1.read();
    if (c >= 32 && c != '#' && (index < SERIAL_BUFFER_SIZE - 1))
    {
      serialBuffer_1[index++] = c;
      counter++;
    }
    else if ( c == '\t') {
      serialBuffer_1[index] = '\0';
      index = 0;
      serial1COMMAND = TAB;
      return true;
    }
    else if ( c == '\r') {
      serialBuffer_1[index] = '\0';
      index = 0;
      serial1COMMAND = CR;
      return true;
    }
    else if ( c == '\n') {
      serialBuffer_1[index] = '\0';
      index = 0;
      serial1COMMAND = NL;
      return true;
    }
    else if ( c == '#') {             // Übertragungsendezeichen
      serialBuffer_1[index] = '\0';
      index = 0;
      state_UART1 = FREE;
      digitalWrite(Led_UartTimeout, LOW);  // Debug LED
      Serial.print("counter ");
      Serial.println(counter);
      counter = 0;
      return true;
    }
  }
  return false;
}

Wenn du Binär einlesen willst, mache entweder eine Union aus dem struct und einem Array der gleichen Größe. Oder caste die Adresse des structs auf einen Zeiger auf Byte. In beiden Fällen kannst du dann byte-weise einlesen

Das geht auch gut mit einem anonymen struct:

union Nachricht 
{
    struct
    {
       uint8_t adresse;
       uint8_t funktion;
       uint8_t wert;
    };
    uint8_t asArray[3];
} daten;

So hat man wie bei einem reinen struct nur daten.X als Name und nicht daten.[Name des structs].[Variable]

Doc_Arduino:
sende ich jedoch:
daten.adresse = 78;
daten.funktion = 179;
daten.wert = 188;

wird es verfälscht und ich sehe im Datalogger die Übertragung
N '179' '188' \n #

Dein sendendes Programm scheint nur reines ASCII (<128) direkt als Zeichen zu schicken. Darüber hinaus wird der Dezimale Wert als Zeichenkette geschickt.

Probiere mal einen anderen Sender oder sende als HEX-Wert.

Gruß Tommy

ASCII-Buchstaben sind nur 7 bit ( also Werte 0 .. 127 )
Bytes haben 8 Bit und wie die Werte 128 .. 255 dargestellt werden, hängt von vielerlei ab.
char haben auch 8 Bit, sind aber vorzeichenbehaftet, werden also als -128 .. 127 interpretiert.

Wenn du kein Problem mit strings hast, sende doch mal Serial.println("N\xB3\xBC").
Das sind dieselben 3 Zeichen wie bei dir.

Wo verbleibt der Rest?

Zeichen > 127 werden von dir als negativ interpretiert und per[b] if (c >=32)[/b] ausgefiltert :wink:

@Doc_Arduino
Ich verstehe dich nicht!

  char c = Serial1.read()

Wenn du uint8_t senden und empfangen willst, dann solltest du das auch tun.
char ist allerdings ein Zeichen.
Und darum macht dir print() auch einen Buchstaben draus.

Ja, klarer Fall von Strings und Binär durcheinander gebracht. Das sind zwei verschiedene Sachen die man nicht einfach vermischen kann.

Das mit dem c >= 32 z.B. ist um ASCII-Steuerzeichen herauszufiltern. Wenn man Binärwerte übertragt braucht man das natürlich nicht

Serenifly:
Das mit dem c >= 32 z.B. ist um ASCII-Steuerzeichen herauszufiltern. Wenn man Binärwerte übertragt braucht man das natürlich nicht

Außerdem ist da eine böse Falle versteckt, wenn man mal auf ein größeres Board wechselt. Auf ARM ist char nämlich aus irgendwelchen Gründen ein vorzeichenloser Typ. Die Zeile würde also auf Zero, Due usw. was ganz anderes tun.

Hallo,

das mit char c >= 32 ist natürlich eine Falle. Ich habe bisher nur Zeichenketten hin und her geschoben und war happy.
Mit byte c ist es jedoch nicht getan. Ich erhalte damit nun erstmal mehr wie ein Zeichen. ⸮N⸮⸮

if (Serial1.available() > 0)  {
    byte c = Serial1.read();
    if (c != '#' && (index < SERIAL_BUFFER_SIZE - 1))
    ...

Mit obiger union von Serenifly erhalte ich
expected ';' at end of member declaration
für die Zeile
uint8_t asArray[3];

das wird fehlerfrei kompiliert, aber dann ohne array. Was tun?

union Nachricht{
    struct
    {
       uint8_t adresse;
       uint8_t funktion;
       uint8_t wert;
    };
} daten;

Den Sender habe ich umgestellt, der sendet nur noch die 3 Bytewerte und das # Endezeichen. Also ohne '\n'
Der Sender sendet bytes. Datentyp unsigned char. Sonst würde ich im Datalogger schon Müll sehen. Im Datalogger passt jedoch alles.

void uart0_putc(unsigned char data)
{

Mal was grundlegendes. Ich kann nach dem Umbau keine Strings mehr empfangen? Richtig? Nur noch reine Bytewerte?
Und brauche eine Funktion die mir die reinkommendes Bytes in die struktur bzw. union reinschiebt? Worauf ich dann wieder normal zugreifen kann?

Das mit dem Array passt schon. Der Fehler war nur der fehlende Strichtpunkt nach dem struct. Wie kommst du auf die Idee dann das Array wegzulassen?

Den Sender habe ich umgestellt, der sendet nur noch die 3 Bytewerte und das # Endezeichen.

Wenn du Binär sendest kannst du nicht wirklich mit einem Endzeichen arbeiten. Du kannst als erstes Byte die Länge des Daten-Telegramms senden wenn du eine Kontrolle willst

Wenn du mit Strings arbeiten willst, sende die Werte mit Komma getrennt und zerlege den String danach mit strtok() + atoi(). Oder frage ob ob ein Komma da ist und mache dann gleich atoi()

Hallo,

das Array hatte ich testweise weggelassen zur Fehlersuche, komme mit dem vermischten Syntax von union und struct noch nicht klar. Einzeln schon. Jetzt funktioniert es.

union Nachricht
{
    struct
    {
       uint8_t adresse;
       uint8_t funktion;
       uint8_t wert;
    };
    uint8_t asArray[3];
} daten;

Wenn ich in beiden Fällen weiterhin mit
daten.adresse
daten.funktion
daten.wert
arbeiten kann, was ist dann der genauer Unterschied zu struct alleine nochmal genau?
Wofür dient das asArray[3] ? Das ich mit Zeigern Byte reinschreiben kann?

struct Nachricht
{   
    uint8_t adresse;
    uint8_t funktion;
    uint8_t wert;
} daten;

und nun muss ich mir eine Funktion basteln die mir die ankommenden Einzelbytes in das union-struct schiebt?

Bei einer Union teilen sich alle Variablen den gleichen Speicherplatz. Das ist vor allem sinnvoll wenn das struct Multi-Byte Datentypen enthält. Man kann also Byte für Byte einlesen und z.B. 2 * int und 1 * float direkt beschreiben. Ohne irgendwas zu konvertieren.

Das ich mit Zeigern Byte reinschreiben kann?

Du brauchst dabei keine Zeiger. Direkt in das Array schreiben und fertig. Senden geht genauso. Man kann mit Serial.write() das Array auf einmal senden

Wenn du Binär sendest kannst du nicht wirklich mit einem Endzeichen arbeiten. Du kannst als erstes Byte die Länge des Daten-Telegramms senden wenn du eine Kontrolle willst

Da haben sich schon viele Leute viele Ideen ausgedacht.

Erstmal musst du sicher sein, dass Sender und Empfänger synchron sind.

  • Bidirektional: Halbduplex, Handshake austauschen
  • nach einer Pause fängt auf jeden Fall eine neue Nachricht ohne größere Pausen im Datenstrom an.

Dann kennst du in deinem Fall die NachrichtenLänge: sizeof(Nachricht)

  • Ansonsten einen Header drumherum senden: Anfangszeichen, Typ, Länge, Nutzdaten, Checksum

  • Kannst auch alternativ ein Spezialzeichen definieren: z.B. \xFF Wenn das ein Datenbyte sein soll, folgt ein weiteres \xFF, sonst was anderes, das dann als Steuerzeichen (z.B. \x00 = Ende) interpretiert wird.

Da kannst du dich richtig austoben :wink:


Um deine Bytes in eine passende Struct zu füllen, kannst du statt union auch einen Pointer umcasten:
(Keine Angst vor Zeigern :wink: )

struct Nachricht recData;  // hier wird das empfangene abgelegt
byte * target = (byte*) &recData; // Byte-Zeiger initialisieren

while ( Serial.available() )
  *target++ = Serial.read(); // empfangenes Zeichen ablegen und Zeiger erhöhen

Hallo,

okay, ich glaube ich habe es verstanden. Erstmal sacken lassen und dann umsetzen.
Etwas rumgespielt habe ich schon. Das struct im union mit Array ist ja richtig multifunktional. :slight_smile:
Danke Serenifly.

union Nachricht
{
    struct
    {
       uint8_t adresse;
       uint8_t funktion;
       uint8_t wert;
    };
    uint8_t asArray[3];
} daten;

void setup()  {
  Serial.begin(500000);
  
  daten.adresse = 78;
  daten.funktion = 179;
  daten.wert = 188;

  Serial.println(daten.adresse);
  Serial.println(daten.funktion);
  Serial.println(daten.wert);

  Serial.println(daten.asArray[0]);
  Serial.println(daten.asArray[1]);
  Serial.println(daten.asArray[2]);
}

void loop() {

}

Ja das Übertragungsprotokoll muss ich anpassen bzw. umbauen. Da muss ich mir was neues ausdenken. Mit Strings verlasse ich mich bisher auf das Endezeichen und einen eingebauten Timeout. Der Umbau soll alles vereinfachen. :wink:
Bisher war das alles ein einziger String zum Slave. Jetzt möchte ich ein einheitliches Datenformat möglichst ohne String. Wenn sich ein Slave mittels Adresse angesprochen fühlt, sendet dieser die gewünschten Daten zurück.

Vor Zeigern habe ich noch scheu ... sonst dreht er selbst am Zeiger. :slight_smile:

Einfach so:

Serial.write(daten.asArray, sizeof(daten));

Eine andere Stelle wo das extrem wichtig ist ist I2C. Weil du da im Receive Event Handler nur einmal write() machen kannst. So kann man auf die Art bequem mehrere Variablen als Antwort senden

Hallo,

jetzt gehts ans Eingemachte?

Serial.write funktioniert so leider nicht. Habe mal die Sendefunktion vom Slave hergenommen und statt *uart0_putc > Serial.print *eingesetzt.

union Nachricht
{
    struct
    {
       uint8_t adresse;
       uint8_t funktion;
       uint8_t wert;
    };
    uint8_t asArray[3];
} daten;

void setup()  {
  Serial.begin(500000);
  
  daten.adresse = 78;
  daten.funktion = 179;
  daten.wert = 188;

  Serial.println(sizeof(daten));
  Serial.println(sizeof(Nachricht));
  Serial.println();
  Serial.println(daten.adresse);
  Serial.println(daten.funktion);
  Serial.println(daten.wert);
  Serial.println();
  Serial.println(daten.asArray[0]);
  Serial.println(daten.asArray[1]);
  Serial.println(daten.asArray[2]);
  Serial.println();
  
  Serial.write(daten.asArray, sizeof(daten));

  Serial.println();
  
  sendStruct((uint8_t*)&daten, sizeof(daten));
  
}


void loop() {

} 


void sendStruct(uint8_t *structPtr, uint8_t length)
{   
  while(length-- > 0)
  {
    Serial.print(*structPtr);     // Bytes rausschieben
    structPtr++;
  }
}

3
3

78
179
188

78
179
188

N⸮⸮
78179188

Hallo,

ach ne, dass mit Serial.write ist ja nur eine Frage der Formatierung. Bin da wieder durcheinander. Sorry.

Hallo,

das mit der Start und Endekennung ist noch nicht perfekt, wenn sich das mit den Daten-Byte deckt gibts noch ein Problem, aber es geht in die richtige Richtung. Es läuft erstmal.

Danke an alle Helfer!

Eine Frage noch an Micha.
Hat die Schreibweise \xFF und \x00 einen bestimmten Grund? Sind doch auch nur 255 und 0. 'grübel' :wink:

reiner Testcode:

  Arduino Mega2560

  13.09.2017
*/
                                
typedef enum {FREE, WAIT, BUSY, FEHLER} sUART1;    // Steuerzustände
sUART1 state_UART1 = FREE;

unsigned long countErrorsUART1;        // uart1 Fehlerzähler

unsigned long last_millis;
unsigned long UartTimeoutStart;
unsigned long UartTimeoutEnde;

const byte Led_UartTimeout = 30;

byte addr_ATtinySlave = 78;
// definieren der Nachricht
union Nachricht
{
    struct
    {
       uint8_t adresse;
       uint8_t funktion;
       uint8_t wert;
    };
    uint8_t asArray[3];
}; 
Nachricht sendDaten;
Nachricht empfDaten;

void setup()  {
  Serial.begin(500000);
  Serial1.begin(250000);
  Serial2.begin(250000);

  pinMode(Led_UartTimeout, OUTPUT);
  
  sendDaten.adresse = addr_ATtinySlave;
  sendDaten.funktion = 0;
  sendDaten.wert = 145;  
}


void loop() {

  read_Serial_1_to_Serial_0();

  check_Uart_Receive_Timeout();

  if (millis() - last_millis > 999) {
    last_millis = millis();
    
    if (state_UART1 == FREE) {   
      sendDaten.adresse++;
      sendDaten.funktion++;
      sendDaten.wert++;                 
      Serial.println("Serial2 sendet");  
      Serial2.print('&');     
      Serial2.write(sendDaten.adresse);
      Serial2.write(sendDaten.funktion);
      Serial2.write(sendDaten.wert);
      Serial2.print('#'); 
      Serial2.flush();
      state_UART1 = WAIT;                        
    }
  }
  
    
  
} // loop Ende


// ****** Funktionen ******* //
bool read_Serial_1()
{
  static byte index = 0;
  static byte counter = 0;
  static bool state = false;

  if (Serial1.available() > 0)  {
    
    byte c = Serial1.read();
        
    if (c == '#') {               // Ende-Kennung
      state = false;  
    }
        
    if (state == true && (index < 3)) {
      empfDaten.asArray[index] = c;
      index++;
      counter++;
    }

    if (c == '&') {               // Start-Kennung
      state = true;  
    }
    
    if (state == false && index >= 2) {         // Übertragungsende      
      index = 0;
      state = false;
      Serial.print("counter ");
      Serial.println(counter);
      counter = 0;
      
      digitalWrite(Led_UartTimeout, LOW);  // Debug LED
      state_UART1 = FREE;
      return true;
    }
  }
  return false;
}


void read_Serial_1_to_Serial_0 ()
{
  if ( read_Serial_1() == true)  {
    Serial.println(empfDaten.adresse);
    Serial.println(empfDaten.funktion);
    Serial.println(empfDaten.wert);
    Serial.println();
  }  
}


void check_Uart_Receive_Timeout ()
{ 
  static unsigned long timeout = 0;
  
  if (state_UART1 == WAIT) {
    state_UART1 = BUSY;
    UartTimeoutStart = millis(); 
    digitalWrite(Led_UartTimeout, HIGH);  // Debug LED
  }

  if (state_UART1 == BUSY) {
    UartTimeoutEnde = millis(); 
  }
    
  timeout = UartTimeoutEnde - UartTimeoutStart;
  if (timeout > 5) {
    state_UART1 = FEHLER;
  } 
  
  
  if (state_UART1 == FREE) {  
    //Serial.print("Debug ms");   Serial.print('\t'); 
    //Serial.println(timeout);
  }  

  if (state_UART1 == FEHLER) { 
    countErrorsUART1++; 
    Serial.print("UART TIMEOUT");   Serial.print('\t');  Serial.println(timeout);
    Serial.print("UART1 Errors");   Serial.print('\t');  Serial.println(countErrorsUART1);
    state_UART1 = FREE;
  }  
}
index < 3

Sowas solltest du allgemein machen und wenigstens sizeof(empfDaten) oder sizeof(Nachricht) verwenden

Hallo,

ja das ist sinnvoll. :wink: Das war erstmal reiner Testcode um zusehen ob und wie das überhaupt funktioniert.
Das Protokol wird auch noch erweitert.

Hallo,

habe den Testcode erweitert mit allgemeiner Längenermittlung :wink: und einer einfachen Checksummenberechnung.
Ich hoffe das kann man allgemein so stehen lassen.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.13
  Arduino Mega2560

  14.09.2017
*/
                                
typedef enum {FREE, WAIT, BUSY, FEHLER} sUART1;    // Steuerzustände
sUART1 state_UART1 = FREE;

unsigned long countErrorsUART1;        // uart1 Fehlerzähler

unsigned long last_millis;
unsigned long UartTimeoutStart;
unsigned long UartTimeoutEnde;

const byte Led_UartTimeout = 30;

const byte SNT = 255;   // Startkennzeichen der Nachricht
const byte ENT = 0;     // Endkennzeichen der Nachricht
  
byte addr_ATtinySlave = 31;
// definieren der Nachricht
union Nachricht
{
    struct
    {
       byte adresse;
       byte funktion;
       byte wert;
       byte checksumme;
    };
    byte asArray[4];
}; 
Nachricht sendDaten;
Nachricht empfDaten;

void setup()  {
  Serial.begin(500000);
  Serial1.begin(250000);    // Serial1 gekreuzt mit Serial2 verbunden
  Serial2.begin(250000);

  pinMode(Led_UartTimeout, OUTPUT);

  sendDaten.adresse = addr_ATtinySlave;
  sendDaten.funktion = 26;
  sendDaten.wert = 22;  
}


void loop() {

  read_Serial_1_to_Serial_0();

  check_Uart_Receive_Timeout();

  if (millis() - last_millis > 999) {
    last_millis = millis();
    
    if (state_UART1 == FREE) {  
      send_Nachricht();    
      sendDaten.adresse++;
      sendDaten.funktion++;
      sendDaten.wert++;                          
    }
  }
  
    
  
} // loop Ende


// ****** Funktionen ******* //
bool read_Serial_1()
{
  static byte index = 0;
  static bool state = false;
  
  if (Serial1.available() > 0)  {
    
    byte c = Serial1.read();
     
    if (c == ENT && index > (sizeof(Nachricht)-1) ) {     // Ende-Kennung ?
      state = false;    
    }
        
    if (state == true && (index < sizeof(Nachricht))) {
      empfDaten.asArray[index++] = c;
    }

    if (c == SNT && state == false) {                     // Start-Kennung ?
      state = true;     
    }
    
    if (state == false) {                                 // Übertragungsende      
      index = 0;
      state = false;                                      
      // Checksumme korrekt ?
      if ( empfDaten.checksumme == calc_Checksumme(empfDaten.asArray, sizeof(Nachricht)) ) {
        digitalWrite(Led_UartTimeout, LOW);                 // Debug LED
        state_UART1 = FREE;
        return true;
      }
    }
  }
  return false;
}


void read_Serial_1_to_Serial_0 ()
{
  if ( read_Serial_1() == true)  {
    Serial.println(F("Serial1 hat empfangen:"));  
    Serial.println(empfDaten.adresse);
    Serial.println(empfDaten.funktion);
    Serial.println(empfDaten.wert);
    Serial.println(empfDaten.checksumme);
  }  
}


void check_Uart_Receive_Timeout ()
{ 
  static unsigned long timeout = 0;
  
  if (state_UART1 == WAIT) {
    state_UART1 = BUSY;
    UartTimeoutStart = millis(); 
    digitalWrite(Led_UartTimeout, HIGH);  // Debug LED
  }

  if (state_UART1 == BUSY) {
    UartTimeoutEnde = millis(); 
  }
    
  timeout = UartTimeoutEnde - UartTimeoutStart;
  if (timeout > 5) {
    state_UART1 = FEHLER;
  } 
  
  if (state_UART1 == FREE) {  
    //Serial.print("Debug ms"); Serial.print('\t'); Serial.println(timeout);
  }  

  if (state_UART1 == FEHLER) { 
    countErrorsUART1++; 
    Serial.print(F("UART TIMEOUT"));   Serial.print('\t');  Serial.println(timeout);
    Serial.print(F("UART1 Errors"));   Serial.print('\t');  Serial.println(countErrorsUART1);
    state_UART1 = FREE;
  }  
}


void send_Nachricht ()
{       
      sendDaten.checksumme = calc_Checksumme(sendDaten.asArray, sizeof(Nachricht)) ;        
      Serial.println(F("Serial2 sendet ..."));  
      Serial2.write(SNT);    
      for (byte i=0; i < sizeof(Nachricht); i++) {
        Serial2.write(sendDaten.asArray[i]);
      }
      Serial2.write(ENT); 
      Serial2.flush();
      state_UART1 = WAIT;
}

      
byte calc_Checksumme (byte feld[], byte length)        
{
  byte Checksumme = 0;
      
  for (byte i=0; i<length-1; i++) {                 // nur Nutzdaten aufsummieren
    Checksumme = Checksumme + feld[i];              // ohne Rücksicht auf Überlauf
  }
   
  return Checksumme;                                  
}