Empfang von seriellen Daten

Hallo,

ich bin noch nicht sehr bewandert was die Arduino Programmierung angeht und hoffe hier auf Hilfe und auch auf Nachsicht wenn mir grundlegendes Wissen noch fehlt, ich arbeite gerade daran :slight_smile:

Nun zu meinen Problem:

Ich habe da ein Gerät, ich nenne es im weiteren BMS, das mit festen Parametern (9600 baud, 8N1) mir serielle Daten sendet. Diese Daten werden nicht angefordert, sondern vom BMS permanent gesendet.

Ich möchte nun gern diese Daten mit einem Arduino abfangen und auf ein Display mir anzeigen lassen.
Fast kein Problem eigentlich, sollte man denken, aber…

Der Mikrocontroller im Arduino hat nur ein UART für die serielle Kommunikation, und genau die wird für das Hochladen des Codes und zum ausgeben von Daten zu debugging Zwecke genutzt. Mit anderen Worten, ich kann diesen UART nicht nutzen.

Wichtig ist noch zu wissen, das mir neben den bekannten Verbindungsparametern (9600, 8N1), ich weiß, das das BMS mir immer alle 0,5s mir genau 58 Bytes sendet, ohne das diese beim BMS extra angefordert werden müssen. Das kann ich auch am Oszilloskop so nachvollziehen.

OK, dafür bietet die Arduino-Umgebung die Library SoftwareSerial an. Sie soll es ermöglichen mit einem normalen I/O-Pin die serielle Kommunikation zu bewerkstelligen.

Gut, ich habe dann ein winziges Programm geschrieben was mir die seriellen Daten vom BMS holt, sammelt und dann wieder über die eigentliche serielle Programmierschnittstelle wieder ausgibt. Leider ist es aber so das die 58 bytes eher zufällig reinrauschen und nicht so wie ich es erwarten würde immer vom Stop-Bit an beginnend.

Erkennen kann man das u.a. daran das die ersten zwei Bytes vom Wert her eigentlich immer sehr nah an dem sein müßten, was ich mit dem Oszilloskop so abfange. Ebenso gilt es für Byte Nr. 26, hier müsste immer eine 8 zu finden sein.

Mein kleines Testprogramm sieht derzeit so aus:

#include <SoftwareSerial.h>
#include <AltSoftSerial.h>
// software serial #1: TX = digital pin 10, RX = digital pin 11
SoftwareSerial portOne(8,9);
// AltSoftSerial portOne;

byte BMSbytes = 0;
byte inByte[] ={};

void setup()
{
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  portOne.begin(9600);
}

void loop()
{
  portOne.listen();
  byte BMSbytes = 0;
  Serial.println("Data from port one:");
  for (byte count = 0; count <= 57; count++){ 
    if (portOne.available()>0){
      inByte[count] = portOne.read();
      BMSbytes++;
    }
  }
Serial.print("recive bytes ");
Serial.println(BMSbytes);


  for (byte count = 0; count <= 57; count++) { 
    Serial.print("Byte: ");
    Serial.print(count);
    Serial.print(" -> ");
    Serial.println(inByte[count]);
  }

  BMSbytes = 0;
  //Serial.println();
}

Was läuft hier falsch? Wo fehlt es mir hier an Wissen oder wo mache ich mein Denkfehler?

byte inByte[] ={};

Ein Array ohne Elemente?

Ansonsten vergisst du die Geschwindigeit des Arduinos in Relation zu hereinkommenden Daten und versuchst naiverweise alle Bytes auf einen Rutsch zu lesen.

9600 Baud sind etwa 1000 Zeichen pro Sekunde, die CPU macht 16000000 Instruktionen pro Sekunde.

Sieht ja recht kompliziert aus. :slight_smile:
Welchen Arduino verwendest du? Manche haben ja meherere serielle Schnittstellen (z.B. Leonardo/Micro, Mega) in Hardware. Damit ginge es natürlich sehr komfortabel. Aber auch ein UNO sollte es mit 9600 Baud noch gut schaffen.
Mit einem UNO habe ich mit so einem einfachen Code gute Erfahrungen beim “Mitlesen und Weiterreichen” gemacht.
Variante mit SoftwareSerial:

#include <SoftwareSerial.h>
SoftwareSerial softSerial(8,9);

void setup() {
  Serial.begin(9600);
  Serial.println("SoftSerial Test Begin");
  softSerial.begin(9600);
}

void loop() {
  if (softSerial.available())  {
    Serial.write(softSerial.read());
  }    
}

Variante mit AltSoftSerial:

#include <AltSoftSerial.h>
AltSoftSerial altSerial;  // Arduino UNO: RX Pin 8, TX Pin 9

void setup() {
  Serial.begin(9600);
  Serial.println("AltSoftSerial Test Begin");
  altSerial.begin(9600);
}

void loop() {
  if (altSerial.available())  {
    Serial.write(altSerial.read());
  }    
}

Whandall: byte inByte[] ={};

Ein Array ohne Elemente?

Wie gesagt in *.ino bzw. *.c bin ich noch nicht wirklich bewandert. Ich hab dieses Konstrukt mal einfach veruscht, un der Compiler beschwert sich ja auch anscheinend nicht.

Whandall: Ansonsten vergisst du die Geschwindigeit des Arduinos in Relation zu hereinkommenden Daten und versuchst naiverweise alle Bytes auf einen Rutsch zu lesen.

9600 Baud sind etwa 1000 Zeichen pro Sekunde, die CPU macht 16000000 Instruktionen pro Sekunde.

Es sollten doch 16000 Instruktionen pro Zeichen und Sekunde reichen für diese nicht sehr hohe Geschwindigkeit. Im Netz finde ich da eher wiedrsprüchliche Angaben.

belba:
Es sollten doch 16000 Instruktionen pro Zeichen und Sekunde reichen für diese nicht sehr hohe Geschwindigkeit.

Wie kommst du dann darauf, direkt nach dem Empfang eines Zeichens das nächste lesen zu können?

  for (byte count = 0; count <= 57; count++){
    if (portOne.available()>0){
      inByte[count] = portOne.read();
      BMSbytes++;
    }
  }

Die CPU ist nicht zu langsam, sondern viel zu schnell für deine Lesemethode.

uxomm:
Sieht ja recht kompliziert aus. :slight_smile:
Welchen Arduino verwendest du? Manche haben ja meherere serielle Schnittstellen (z.B. Leonardo/Micro, Mega) in Hardware. Damit ginge es natürlich sehr komfortabel. Aber auch ein UNO sollte es mit 9600 Baud noch gut schaffen.
Mit einem UNO habe ich mit so einem einfachen Code gute Erfahrungen beim “Mitlesen und Weiterreichen” gemacht.
Variante mit SoftwareSerial:

#include <SoftwareSerial.h>

SoftwareSerial softSerial(8,9);

void setup() {
 Serial.begin(9600);
 Serial.println(“SoftSerial Test Begin”);
 softSerial.begin(9600);
}

void loop() {
 if (softSerial.available())  {
   Serial.write(softSerial.read());
 }    
}




Variante mit AltSoftSerial:


#include <AltSoftSerial.h>
AltSoftSerial altSerial;  // Arduino UNO: RX Pin 8, TX Pin 9

void setup() {
 Serial.begin(9600);
 Serial.println(“AltSoftSerial Test Begin”);
 altSerial.begin(9600);
}

void loop() {
 if (altSerial.available())  {
   Serial.write(altSerial.read());
 }    
}

Danke für deine Code-Schnipsel.

ich habe mir mal erlaub diese beiden doch sehr ähnliche Varianten in einer Zusammen zu fassen, so das ich durch auskommentieren schnell zwischen SoftwareSerial und AltSoftSerial umschalten kann.

Wichtig für mich ist jedoch, das ich die 58 Bytes in irgendeiner Form für eine interne Weiterverarbeitung (Plausibilitätsprüfung, Ausgabe zu einem Display mittels I2C) zusammen habe. Nach der internen Verarbeitung würde ich mir dann wieder das nächst beste Paket holen und es dann wieder weiter verarbeiten.

Das Ganze entwickle ich auf einen UNO möchte aber später dann, wenn möglich, ein NANO verwenden.

Whandall:
Wie kommst du dann darauf, direkt nach dem Empfang eines Zeichens das nächste lesen zu können?

  for (byte count = 0; count <= 57; count++){

if (portOne.available()>0){
      inByte[count] = portOne.read();
      BMSbytes++;
    }
  }



Die CPU ist nicht zu langsam, sondern viel zu schnell für deine Lesemethode.

Mit

    if (portOne.available()>0){

wollte ich abwarten, bis das nächte Zeichen zur verfügung steht. Aber wenn ich mir das so ansehe, dann sollte ich doch meine ganze Schleifenkonstruktion mal überdenken…

belba: ich habe mir mal erlaub diese beiden doch sehr ähnliche Varianten in einer Zusammen zu fassen, so das ich durch auskommentieren schnell zwischen SoftwareSerial und AltSoftSerial umschalten kann.

Warum postest du das dann nicht?

belba: Wichtig für mich ist jedoch, das ich die 58 Bytes in irgendeiner Form für eine interne Weiterverarbeitung (Plausibilitätsprüfung, Ausgabe zu einem Display mittels I2C) zusammen habe. Nach der internen Verarbeitung würde ich mir dann wieder das nächst beste Paket holen und es dann wieder weiter verarbeiten.

Dann wäre wohl ein Puffer geeigneter Größe eine gute Idee, in dem du die Zeichen ablegst, wenn welche ankommen.

Woran erkennst du Anfang und Ende einer Nachricht?

belba: Mit

    if (portOne.available()>0){

wollte ich abwarten, bis das nächte Zeichen zur verfügung steht.

Seit wann wartet ein if?

belba: wenn ich mir das so ansehe, dann sollte ich doch meine ganze Schleifenkonstruktion mal überdenken...

Das for ist völlig ungeeignet, aber es wurde dir ja schon gezeigt wie loop aussehen sollte.

Die aktuelle frickler Version sieht wie folgt aus:

//#include <SoftwareSerial.h>
//SoftwareSerial BMSSerial(8,9);

#include <AltSoftSerial.h>
AltSoftSerial BMSSerial;  

void setup() {
  Serial.begin(9600);
  Serial.println("Test Begin");
  BMSSerial.begin(9600);
}

void loop() {
  byte BMSbytes = 0;
  byte inByte[] = {};
  while (BMSSerial.available())  {
    inByte[BMSbytes] = BMSSerial.read();
    Serial.write(inByte[BMSbytes]);
    BMSbytes++;
  }
  Serial.print("received bytes: ");
  Serial.println(BMSbytes);   
}

Als Ergebnis auf der seriellen Konsole erhalte aber scheinbar nur (höchstens) 53 Bytes:

Test Begin
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
⸮O⸮⸮O⸮⸮⸮⸮⸮⸮⸮}⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮n⸮⸮⸮⸮⸮⸮⸮⸮⸮7⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮_received bytes: 53
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
⸮O⸮⸮O⸮⸮⸮⸮⸮⸮⸮}⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮n⸮⸮⸮⸮⸮⸮⸮⸮⸮7⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮_|received bytes: 53
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
received bytes: 0
⸮O⸮⸮O⸮⸮⸮⸮⸮⸮⸮}⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮n⸮⸮⸮⸮⸮⸮⸮⸮⸮7⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮_|received bytes: 53
received bytes: 0
received bytes: 0
received bytes: 0

Mit dem Array wollte ich soetwas wie ein Puffer bauen, weiß aber noch nicht ob so etwas geignet ist. Ich bin erstmal froh wenn meine 58 Bytes ankommen.

Der Anfang und das Ende einer Nachricht werden einzig und alleine durch die Start- und Stop-Bits der seriellen Kommunikation gekennzeichnet. In der höheren Protokollebene gibt es dazu keine Kennzeichen.

Nur zur Sicherheit: Das Gerät sendet TTL-seriell und nicht RS232? Bei RS232 braucht man noch Pegelwandler zu TTL, ansonsten empfängt der Arduino Datenmüll und es besteht wegen der anderen Pegel sogar Beschädigungsgefahr.

  byte inByte[] = {};

Beratungsresistent?

  while (BMSSerial.available())  {

Noch ein Hinweis auf Beratungsresitenz, wo kam in dem vorgeschlagenen Kode ein while vor?

belba: Der Anfang und das Ende einer Nachricht werden einzig und alleine durch die Start- und Stop-Bits der seriellen Kommunikation gekennzeichnet.

Nö, das nichts mit der Nachricht zu tun, nur mit den einzelnen Bytes der Nachricht.

Wie bereits erwähnt wurde: Es ist wichtig, dass du herausfindest, wie du Anfang und/oder Ende eines "Datensatzes" erkennen kannst. Möglichkeiten wären zum Beispiel: "Makrierung" durch spezielle Zeichen, die sonst nirgends in den Daten vorkommen (dürfen). Oder durch eine (längere) "Sendepause" zwischen den "Datensätzen".

Hast du eine Idee wie die "Datensätze" aufgebaut sind? Handelt es sich um "menschenlesbare" Daten oder (nur) um Binärdaten? In einem ersten Schritt würde ich mal "reverse engineering" machen und versuchen herauszufinden, was da überhaupt gesendet wird. Weitere Verarbeitung nach diesem Schritt.

Hi

Wenn der Datenstrom alle halbe Sekunde rein kommt - warum nicht die zeitliche Lücke dafür nutzen? Deine loop() wartet so lange auf Daten, bis 100ms keine neuen Daten mehr kamen - so hast Du noch etwas Zeit, bis der neue Datenstrom anfängt und kannst die Daten verarbeiten. Wenn die Verarbeitung ect.pp. abgeschlossen ist, wird auf den Start der neuen Übertragung gewartet (da sonst wohl öfter die 100ms überschritten werden und Du öfter NULL-Bytes Datenstrom 'überprüfen' würdest. - fehlt hier aber ;)

long lastbyte=millis();
boolean fertig=false;
byte datenarray[60]={};   //Array mit 60 Platzhaltern vorbereiten
byte bytenr=0;

loop()
{
   //so lange empfangen, bis wir fertig sind
   while (fertig==false){
      if (serial.available()){
         datenarray[bytenr]=aerial.read();
         bytenr++;
         lastbyte=millis();
      }
      if (millis()-lastbyte>100){
         fertig=true;
      }
   }
   if (bytenr==57){
      // nur ausführen, wenn ein kompletter Satz Daten empfangen wurde - Zahl anpassen
      //AUSWERTUNG der empfangenen Bytes - die Anzahl der Bytes steht jetzt in Bytenr
   }
   bytenr=0;
   fertig=false;
}

Ähnlich versuche ich derzeit an die Daten meines Stromzähler zu kommen (E21 Schnittstelle, lässt sich mit einem CNY70 'abfragen' - also die Empfänger-Diode passt hier ganz gut). Also, neue Daten (Beschreiben des Puffer von Null an) nach einer gewissen Pause - bei mir kommen die Daten alle zwei Sekunden - in unbekannter Menge und mit relativ unbekanntem Inhalt. Noch kommen aber nicht die Daten, Die ich erwarte - da fehlt auch noch etwas reverse-ebgineering ...

MfG

Ich würde für lastbyte ja unsigned long nehmen, denn millis() ist ebenfalls unsigned long. Es könnte sonst nach einiger Zeit (etwa 25 Tagen) zu "unerwünschten Nebenwirkungen" kommen. :)

Hallo, lasse Dir doch erst einmal die gesendeten Daten des BMS über ein Terminal-Programm auf dem PC ausgeben. Nur so weißt Du sicher, was- wann gesendet wird.

Dann- kannst Du auch mit dem Arduino sicher terminieren. Gruß und Spaß Andreas

Genau meine Rede :)

Vielen Dank für die vielen Hinweise und Tipps. Leider kann ich nicht auf jeden Beitrag einzeln eingehen, ich möchte statt dessen mal ein generelles Update machen und auf die Teils wiederkehrende Fragen eingehen.

Ein großer Fehler den ich gemacht habe, war tatsächlich nicht auf die Pegel zu achten. Das BMS sendet mit einem 3,3V Pegel, mein Arduino jedoch erwartet 5V. Um so erstaunlicher das der Arduino dennoch Daten empfangen hat, und diese waren dann mit unter durchaus plausibel, aber eben jedoch nicht vollständig. Ich habe nun ein Optokoppler eingeschleift der mir direkt zwei Dienste erweist: 1. Pegeltrennung und 2. galvanische Trennung!

Danach hatte ich ad hock meine erwarteten 58 Bytes bekommen und konnte mich daran machen, ein Programm zu schreiben welches die Daten einsammelt, die Checksumme prüft und diese zumindest vorläufig über die serielle Konsole in menschenlesbar ausgibt.

Ein Start- oder Stop-Merkmal hat der Hersteller des BMS in seinem Protokoll nicht vorgesehen, leider :frowning: So bleibt mir nur das übliche Start-/Stop-Bit im seriellen Protokoll und der Verlass darauf das SoftwareSerial damit sauber umgeht.

Und genau hier hakt es wieder… ab einer bestimmten Anzahl der Parameter die ich vom BMS erhalte im Arduino verarbeite, kommt es wieder zu Fehlern. Dabei war meine Idee ja das mit

…
  BMSSerial.begin(9600);
  while (BMSbytes <= 57)  {
    if (BMSSerial.available()){
      BMSMsg[BMSbytes] = BMSSerial.read();
      BMSbytes++;
    }
  }
...

die vom BMS kommenden Daten zuerst in ein Array wegschreibe, um dann beliebig viel Zeit für die Verarbeitung zu erhalten. Wenn dann alles erledigt ist, sollte dann die Hauptschleife wieder mit obrigen Code sich frische Daten holen…wo ist da mein Denkfehler? Gegenwärtig funktioniert es nur mit dem großen auskommentierten Block am Ende gut. Entferne ich in diesem Block die Kommentierung, so werden nur ein bis zwei mal die Daten Korrekt ausgegeben, danach kommen Checksummenfehler.
:frowning:

Ich füge das aktuelle Frickelprogramm mal diesen Post an und bitte seht es mir nach wenn da noch reichlich ungeschickte Konstrukte drin stehen. Es ist ewig her das ich mit PHP gearbeitet habe und C bzw. diese vereinfachte Variante davon ist für mich komplettes #Neuland :wink:

Für lehrreiche Hinweise bin ich natürlich sehr Dankbar und wenn mal aus dem Frickelzeugs hier mal was solides vernünftiges herauskommt, könnte ich mir vorstellen das auch bei Git einzustellen. Ich kann mir gut vorstellen das es andere Anwender des BMS gibt die das nützlich finden würden!

Vielen Dank und eine gute Nacht!

serial-test5.ino (8.86 KB)

Hi

Ich habe mir die letzte .ino jetzt nicht angesehen anmerk

Meine Gedanken gehen in die Richtung, daß Du Daten verpasst.
Die Serial-Funktion hat (meines Wissen) einen Puffer von 64 Byte, die Anzahl der gerade jetzt im Puffer belegten (und damit auslesbaren) Bytes bekommst Du mit Serial.available().
Einzelne Bytes liest Du mit Serial.read() aus, was jedes Mal wieder ein Byte Platz schaffen dürfte.
Wenn der Puffer aber voll ist, gehen entweder Daten verloren, oder werden überschrieben.
In beiden Fällen stimmt dann die Prüfsumme nicht (dafür ist Diese ja auch da).

Hatte hier einen ähnlichen Fall - nur anders herum :slight_smile:
Ich habe meinen Puffer (ein Array) mit den Daten gefüllt, bis zu einer Stop-Bedingung.
Danach hing das Programm … eigentlich auch klar, da ich, sobald ‘available’ true zurück gibt, in die Auslese-Routiene gesprungen bin, dort aber, da der Puffer voll war, kein Serial.read() auslöste - somit wurde der Puffer auch nicht leerer und mein Programm sah immer noch Daten, Die bearbeitet werden sollen.
Ich habe diese Daten dann einfach weggeschmissen (byte nonsens=Serial.read(); ) - später sollte der Puffer größer sein, als die zu erwarteten Bytes - leider brachte mir mein Code trotzdem immer noch nicht die Daten, Die ich sehen will (laut INet).

MfG

Das BMS sendet mit einem 3,3V Pegel, mein Arduino jedoch erwartet 5V. Um so erstaunlicher das der Arduino dennoch Daten empfangen hat

Daran ist nichts erstaunlich. Schau im Datenblatt gegen Ende unter "DC characteristics". 3,3V wird noch als High erkannt

So bleibt mir nur das übliche Start-/Stop-Bit im seriellen Protokoll

Das Start/Stop-Bit macht nicht was du denkst. Das ist eine Ebene tiefer um zu erkennen wo ein Byte anfängt. Mit deinem Übertragungsprotokoll hat das rein gar nichts zu tun

Bei 9600 Baud dauert ein Zeichen ca. 1ms ( 1 / 9600 * 10 Sekunden). Du kannst das daher nicht in einem Rutsch einlesen! Das wurde dir schon mehrmals gesagt. Man braucht hier keine while-Schleife. Du hast schon eine Schleife mit loop() selbst. Verabschiede dich von dem Gedanken das alles in einem Durchlauf von loop() machen zu müssen

Dein Empfangs-Array lokal in loop() zu deklarieren wo es bei jedem Durchlauf der Funktion neu angelegt wird ist da auch falsch. Das sollte global sein

Generell ist es ein Problem sich bei sowas mit dem Sender zu synchronisieren. Du kannst natürlich zählen und eine bestimmte Anzahl von Bytes einlesen. Aber dass die genau ein Telegramm darstellen ist unwahrscheinlich. Eher steigst du da irgendwo mittendrin ein. Man könnte was mit der Zeit machen wenn du sagst dass das nur alle halbe Sekunde geschieht. Also erst mal überprüfen dass z.B. 200ms nichts empfangen wurde. Und dann weiß man dass das man ab dem nächsten Byte einlesen muss. Die Daten die vorher angekommen sind musst man natürlich verwerfen! Das wurde auch schon abgesprochen

Hallo, gebe doch bitte einmal einen aussagekräftigen Link zu Deinem "BMS". Auch einen Link zum Datenblatt oder ECHTNAME wäre nicht schlecht. Das ist doch alles Rätselraten hier... Gruß und Dank Andreas