Forum: Mikrocontroller und Digitale Elektronik High- und Lowbyte Übertragung zw. µC und PC


von Lukas W. (lucanics)


Lesenswert?

Hallo!

Ich habe einen Mikrocontroller (Arduino Nano) und einen 
Beschleunigungssensor (LIS331), der mit einer Frequenz von 1000Hz 
digitale 3D-Beschleunigungsdaten zum µC sendet. Der µC ist mittels RS232 
Schnittstelle mit dem PC verbunden. Dort kommen die Beschleunigungsdaten 
nicht mit 1000Hz an, sondern nur mit ca. 305Hz (wenn alle drei Achsen 
weitergeleitet werden).

Wir vermuten, dass das daran liegt, weil wir die Daten nicht mittels 
high- und lowbytes zum PC senden, sondern in "Strings". Es liegen somit 
große Datenmengen vor, die auf dieser Schnittstelle nicht mit 1000Hz 
übertragen werden können (bei zwei Achsen beträgt die Aufnahmefrequenz 
466Hz und bei einer übertragenen Achse 901Hz).

Wir schaffen es leider nicht, die Daten in diese Bytes umzuwandeln. Bzw. 
vl. sind wir hier auf dem Holzweg und es gibt ganz andere Vorschläge, 
wie wir diese Aufnahmefrequenz steigern könnten?

Ausschnitte aus unserem Code seht ihr unten. Habt ihr 
Verbesserungsvorschläge? Wir sind für alle Anregungen dankbar!! Auch 
Links und Literturhinweise würden uns schon sehr freuen! :)


----------------------------------------------------------------------

Unser Code (der Library in C) für das Einlesen der X-Achse sieht 
folgendermaßen aus:

int LIS331::getXValue(){
  byte high;
  byte low;
  if (!readReg(LR_OUT_X_L, &low)){
    return false;
  }
  if (!readReg(LR_OUT_X_H, &high)){
    return false;
  }
  return (low|(high << 8));

}


Und unser Arduino Code (die relevanten Stellen) zum Einlesen der Daten 
besteht aus:
void setup()
{
Serial.begin(1000000); % Baudrate = 1 000 000
LIS331 lis; % Erstellung des Objekts lis
}

void loop()
{
lis.begin();
int val = lis.getXValue();
Serial.print(val);
}

------------------------------------------------------------------------


Durch den Befehl Serial.print geben wir die Daten unseres Wissens nach 
in Strings aus... das machst das ganze sehr langsam.


Vielen Dank im Voraus und alles Gute,
Lukas

von spess53 (Gast)


Lesenswert?

Hi

>  byte alle;
>  ...
>alle = (low|(high << 88));

Autsch.

MfG Spess

von Dennis K. (scarfaceno1)


Lesenswert?

die Baudrate muss bei Arduino nicht in Hz angegeben werden, sondern in 
Baud.

Manual zu serial.begin nochmal lesen.

Eine Übertragung mit 1 000 000 Baud schafft die Com Schnittstelle auch 
nicht.

von Lukas W. (lucanics)


Lesenswert?

spess53 schrieb:
> Hi
>
>>  byte alle;
>>  ...
>>alle = (low|(high << 88));
>
> Autsch.
>
> MfG Spess


sorry, das war ein Tippfehler.

von Lukas W. (lucanics)


Lesenswert?

Dennis K. schrieb:
> die Baudrate muss bei Arduino nicht in Hz angegeben werden, sondern in
> Baud.
>
> Manual zu serial.begin nochmal lesen.
>
> Eine Übertragung mit 1 000 000 Baud schafft die Com Schnittstelle auch
> nicht.


Ich habe nicht behauptet, dass es sich bei der Baudrate um Hz handelt. 
Die Com Schnittstellt schafft angeblich bis zu 2Millionen Baud.


Danke für den Hinweis, ich denke jedoch nicht, dass das Problem hier 
liegt, weil durch das Erhöhen der Baudrate die Übertragung nicht 
schneller wird.

Habt ihr weitere Ideen?

von spess53 (Gast)


Lesenswert?

Hi

>sorry, das war ein Tippfehler.

Auch

alle = (low|(high << 8));

passt nicht in ein Byte.

MfG Spess

von Oliver R. (orb)


Lesenswert?

Lukas Wiedemann schrieb:
> Die Com Schnittstellt schafft angeblich bis zu 2Millionen Baud.

Warum nutzt Du nicht die Standardbaudraten?

von Lukas W. (lucanics)


Lesenswert?

spess53 schrieb:
> Hi
>
>>sorry, das war ein Tippfehler.
>
> Auch
>
> alle = (low|(high << 8));
>
> passt nicht in ein Byte.
>
> MfG Spess


mein Fehler... so solls heißen:

int LIS331::getXValue(){
  byte high;
  byte low;
  if (!readReg(LR_OUT_X_L, &low)){
    return false;
  }
  if (!readReg(LR_OUT_X_H, &high)){
    return false;
  }
  return (low|(high << 8));

}

von Lukas W. (lucanics)


Lesenswert?

Oliver R. schrieb:
> Lukas Wiedemann schrieb:
>> Die Com Schnittstellt schafft angeblich bis zu 2Millionen Baud.
>
> Warum nutzt Du nicht die Standardbaudraten?


der Serial Monitor in Arduino schafft 115200. Doch das war zu wenig für 
uns... deshalb 1M

von Konrad S. (maybee)


Lesenswert?

(Datensätze pro Sekunde) × (Elemente pro Datensatz) × (Elementgröße)
1000 × 3 × 2 = 6000 Bytes/s

Das geht als Binärdaten locker bei 115200 Baud.
1
Serial.write( val & 0xff );
2
Serial.write( val >> 8 );

Als Strings geht es bei 115200 Baud nicht, das ist richtig. Aber 
andererseits kannst du mit dem Arduino 1 MBaud nicht bedienen, da ist 
der einfach zu langsam.

von Rudolph (Gast)


Lesenswert?

Konrad S. schrieb:
> (Datensätze pro Sekunde) × (Elemente pro Datensatz) × (Elementgröße)
> 1000 × 3 × 2 = 6000 Bytes/s
>
> Das geht als Binärdaten locker bei 115200 Baud.
> Serial.write( val & 0xff );
> Serial.write( val >> 8 );

Da muss man sich dann nur noch ein bisschen Protokoll drum herum 
überlegen.
Zum Beispiel einmal pro ms sechs Bytes senden, das dauert 521µs, die 
479µs Pause dienen der Erkennung der Datenrahmen.

Aber weil das vielleicht über ser->usb->virtuell com mit dem Timing so 
eine Sache sein könnte, pro ms sind das 11 Byte bei 115200 Baud, da kann 
man sich noch nen Header überlegen:

0xff 0xff 0x55 0xaa hX lX hY lY hZ lZ

Oder so halt. :-)

von Rudolph (Gast)


Lesenswert?

Hab noch die Absicherung vergessen, eine Checksumme mitzuschicken
damit der Empfänger was zum überprüfen hat schadet sicher auch nicht.

0xff 0xff hX lX hY lY hZ lZ crc8

Ein passendes Start-Token zu finden ist eventuell trickreich.

von Konrad S. (maybee)


Lesenswert?

Rudolph schrieb:
> 0xff 0xff hX lX hY lY hZ lZ crc8

Sowas wie 0x7f7e als Kenner dürfte ausreichend sein, da der Wert 
eigentlich nicht auftauchen kann (12-Bit Zweierkomplement). Der Kenner 
muss auch nicht in jedem Datenpaket enthalten sein, einmal pro Sekunde 
einstreuen sollte reichen.

Irgendeine Prüfsumme kann nicht schaden.

Die Werte würde ich in Little-Endian-Reihenfolge übertragen, da muss 
sich der PC nicht so abquälen damit.

von Lukas W. (lucanics)


Lesenswert?

Rudolph schrieb:
> Konrad S. schrieb:
>> (Datensätze pro Sekunde) × (Elemente pro Datensatz) × (Elementgröße)
>> 1000 × 3 × 2 = 6000 Bytes/s
>>
>> Das geht als Binärdaten locker bei 115200 Baud.
>> Serial.write( val & 0xff );
>> Serial.write( val >> 8 );
>
> Da muss man sich dann nur noch ein bisschen Protokoll drum herum
> überlegen.
> Zum Beispiel einmal pro ms sechs Bytes senden, das dauert 521µs, die
> 479µs Pause dienen der Erkennung der Datenrahmen.
>
> Aber weil das vielleicht über ser->usb->virtuell com mit dem Timing so
> eine Sache sein könnte, pro ms sind das 11 Byte bei 115200 Baud, da kann
> man sich noch nen Header überlegen:
>
> 0xff 0xff 0x55 0xaa hX lX hY lY hZ lZ
>
> Oder so halt. :-)


Super, genau sowas in die Richtung habe ich gesucht! Danke erstmal! 
Kannst du mir genau diesen Punkt näher erläutern? Also wieso benötige 
ich diesen Header und wofür steht der? 0xff steht ja z.B. für 255 usw., 
aber wozu genau benötige ich das?

Und mit dem Code
>> Serial.write( val & 0xff );
>> Serial.write( val >> 8 );

... kommt bei mir nun folgende Beispiels-"werte" raus, die jeweils durch 
Semikolons getrennt sind:
` ;P ;@
` ; @;@
  ;@ ;
@ ;p ;

usw.

was bedeutet das dann genau, bzw. wie kann ich von diesen Zeichen wieder 
auf Zahlenwerte schließen?

Danke für die schnellen und hilfreichen Antworten!

von Gerald G. (gerald_g)


Lesenswert?

Bei 1kHz lohnt es sich eventuell auch einfach nur die Änderung des 
vorherigen Wertes zu schicken. Sollten deutlich kleinere Werte sein und 
entsprechend schneller zu senden sein. Das muss nur am PC Programm 
beachtet werden, dass man quasi die Ableitung des Signales schickt.

von Konrad S. (maybee)


Lesenswert?

Lukas Wiedemann schrieb:
> Super, genau sowas in die Richtung habe ich gesucht! Danke erstmal!
> Kannst du mir genau diesen Punkt näher erläutern? Also wieso benötige
> ich diesen Header und wofür steht der? 0xff steht ja z.B. für 255 usw.,
> aber wozu genau benötige ich das?

> Und mit dem Code
>>> Serial.write( val & 0xff );
>>> Serial.write( val >> 8 );
>
> ... kommt bei mir nun folgende Beispiels-"werte" raus, die jeweils durch
> Semikolons getrennt sind:

> was bedeutet das dann genau, bzw. wie kann ich von diesen Zeichen wieder
> auf Zahlenwerte schließen?

Mit dem Serial-Monitor bekommst du nur "Text" angezeigt, damit geht es 
nicht.
Was du überträgst sind aber Binärdaten. Dafür brauchst du ein Programm, 
das Bytes einliest bis der Header erkannt wird. Ab da muss es immer zwei 
Bytes als "short int" einlesen und irgendetwas damit machen oder, wenn 
es wieder ein Header ist, wegwerfen. Der Header sorgt für 
Synchronisation: Du musst den Anfang eines Datensatzes erkennen können.

von Karl H. (kbuchegg)


Lesenswert?

Lukas Wiedemann schrieb:

> Super, genau sowas in die Richtung habe ich gesucht! Danke erstmal!
> Kannst du mir genau diesen Punkt näher erläutern? Also wieso benötige
> ich diesen Header und wofür steht der? 0xff steht ja z.B. für 255 usw.,
> aber wozu genau benötige ich das?


Setell dir einfach mal vor, auf dem PC holst du von der Seriellen 
Schnittstelle die Bytes ab, wobei das PC Programm gerade gestartet ist 
und der Arduino schon seit 2 Stunden seine Daten rausbläst

die Bytes sind
1
   0x45 0x78 0x12 0x57 0x48 0x78 ...

welches Byte ist denn jetzt was? Wo fängt ein Datensatz an und und wo 
hört er auf?
Wie gesagt: der Arduino soll angenommenerweise schon die längste Zeit 
senden. Du kannst daher nicht davon ausgehen, dass das erste Byte, das 
du am PC zu Gesicht bekommst auch das erste Byte aus der 3-er Sequenz 
von Messwerten ist.

Das ganze ist ein bischen wie einer Italienerin zuzuhören, wenn er 
schnell spricht. Wenn der nicht an einem Satzanfang Luft holen würde, 
hättest du kaum eine Chance auch nur zu wissen wo ein Wort anfängt und 
wo es aufhört. Da prasseln einfach nur die Silben unaufhörlich auf dich 
ein und du kannst nichts irgendwie gesichert zuordnen.

>
> Und mit dem Code
>>> Serial.write( val & 0xff );
>>> Serial.write( val >> 8 );
>
> ... kommt bei mir nun folgende Beispiels-"werte" raus, die jeweils durch
> Semikolons getrennt sind:
> ` ;P ;@
> ` ; @;@
>   ;@ ;
> @ ;p ;

Wenn du die Messwerte binär rausgibst, ist es recht sinnfrei sie sich 
als ASCII Repräsentierung anzusehen. Du musst deinem Anzeigeinstrument 
(welches auch immer du benutzt) schon auch mitteilen, dass es dir die 
Byte gefälligst als Hex-Werte anzeigen soll.

> was bedeutet das dann genau, bzw. wie kann ich von diesen Zeichen wieder
> auf Zahlenwerte schließen?

Du schaust dir auf dem PC die Daten in der falschen Repräsentierung an. 
Die empfangenen Bytes als ASCII Zeichen anzusehen ist nicht sinnvoll.

von Lukas W. (lucanics)


Angehängte Dateien:

Lesenswert?

Danke! Das ist schon mal eine riesen Hilfe. Kann die Daten mittels 
"hterm" einlesen (siehe Grafik im Anhang - grün = Bytes; rot = 
Dezimalzahlen). 1000Hz werden jedoch (nach 'Augenmaß') nicht erreicht.

Der Code in Arduino sieht nun folgendermaßen aus:


----------------------------------------
// Header
Serial.write(0xff);
Serial.write(0xff);
Serial.write(0x55);
Serial.write(0xaa);

// Daten einlesen
  int val = lis.getXValue();
  Serial.write(val & 0xff );
  Serial.write(val >> 8 );
  //Serial.print(";");
  int val = lis.getYValue();
  Serial.write(val & 0xff );
  Serial.write(val >> 8 );
  //Serial.print(";");
  int val = lis.getZValue();
  Serial.write(val & 0xff );
  Serial.write(val >> 8 );

----------------------------------------

Passt das eurer Meinung nach soweit?

von Karl H. (kbuchegg)


Lesenswert?

Lukas Wiedemann schrieb:
> Danke! Das ist schon mal eine riesen Hilfe. Kann die Daten mittels
> "hterm" einlesen (siehe Grafik im Anhang - grün = Bytes; rot =
> Dezimalzahlen).

Nur so als Tip:
Das kann man doch sicher auch als Hex-Werte anzeigen lassen.
Auf dieser Ebene hat man mit Dezimalzahlen eigentlich so gut wie nie 
auch nur irgendwas angefangen.

> 1000Hz werden jedoch (nach 'Augenmaß') nicht erreicht.

Tja. Komfort kostet eben.

> Passt das eurer Meinung nach soweit?

Was heißt unserer Meinung? Kommt am PC das richtig an oder kommt nicht 
das richtige an?

von Konrad S. (maybee)


Lesenswert?

Lukas Wiedemann schrieb:
> Passt das eurer Meinung nach soweit?

Die Daten könnten korrekt sein.

Der Header ist schlecht gewählt. Nimm den von mir vorgeschlagenen Wert, 
der sollte eindeutig sein.

von Lukas W. (lucanics)


Angehängte Dateien:

Lesenswert?

Rudolph schrieb:
> Konrad S. schrieb:
> Da muss man sich dann nur noch ein bisschen Protokoll drum herum
> überlegen.
> Zum Beispiel einmal pro ms sechs Bytes senden, das dauert 521µs, die
> 479µs Pause dienen der Erkennung der Datenrahmen.

Wieso dauert das 512µs... wie lautet hier die Rechnung dazu? Damit ich 
mir das in Zukunft selbst überlegen kann.


Rudolph schrieb:
> Hab noch die Absicherung vergessen, eine Checksumme mitzuschicken
> damit der Empfänger was zum überprüfen hat schadet sicher auch nicht.
>
> 0xff 0xff hX lX hY lY hZ lZ crc8
>
> Ein passendes Start-Token zu finden ist eventuell trickreich.

Wie wird diese Checksumme (ich schätze mal dafür steht 'crc8') erstellt?

Karl Heinz schrieb:
>> 1000Hz werden jedoch (nach 'Augenmaß') nicht erreicht.
>
> Tja. Komfort kostet eben.

Wie meinst du das mit dem Komfort? Ich benötige die 1000Hz.

Konrad S. schrieb:
> Lukas Wiedemann schrieb:
>> Passt das eurer Meinung nach soweit?
>
> Die Daten könnten korrekt sein.
>
> Der Header ist schlecht gewählt. Nimm den von mir vorgeschlagenen Wert,
> der sollte eindeutig sein.


Hab ich ausprobiert... die Daten sehen nun wie folgt aus (siehe Anhang, 
blau = hex, rot = dez, grün = bin).


Mfg,
Lukas

von Konrad S. (maybee)


Lesenswert?

Konrad S. schrieb:
> Sowas wie 0x7f7e als Kenner dürfte ausreichend sein, da der Wert
> eigentlich nicht auftauchen kann (12-Bit Zweierkomplement).

Du brauchst zwei Byte als Header, also nimm die Sequenz x7f 0x7e. Diese 
Sequenz kann in den Daten nicht auftauchen.

Und ja, es macht Sinn, wenn das Fenster so breit wie ein Datensatz ist. 
;-)

von Rudolph (Gast)


Lesenswert?

Lukas Wiedemann schrieb:
>> Zum Beispiel einmal pro ms sechs Bytes senden, das dauert 521µs, die
>> 479µs Pause dienen der Erkennung der Datenrahmen.
>
> Wieso dauert das 512µs... wie lautet hier die Rechnung dazu? Damit ich
> mir das in Zukunft selbst überlegen kann.

Wenn ich mich da nicht vertan habe (ja, seriell COM ist ne Weile her) 
sind es 10 Bit pro Byte Übertragung.
115200 Baud -> 8,68µs/Bit
-> 86,81µs/Byte
-> 520,83µs für sechs Bytes
-> 868,06µs für zehn Bytes, 16-Bit Header, 3*16 Bit Daten, 16 Bit 
Checksumme

> Rudolph schrieb:
>> Hab noch die Absicherung vergessen, eine Checksumme mitzuschicken
>> damit der Empfänger was zum überprüfen hat schadet sicher auch nicht.
>>
>> 0xff 0xff hX lX hY lY hZ lZ crc8
>
> Wie wird diese Checksumme (ich schätze mal dafür steht 'crc8') erstellt?

Da gibt es diverse Algorithmen zu, such Dir was aus. :-)
Man könnte zum Beispiel einfach die drei Sensor-Werte addieren und als 
vierten 16-Bit Wert mitschicken.
Wobei die Checksumme auch wieder nicht den Wert vom Header annehmen 
können darf.

Ich bin jetzt auch nicht davon ausgegangen, dass so schnell wie möglich 
gesendet wird, sondern eben schon mit 1000Hz, die Übertragung also auch 
nur einmal pro ms gestartet wird.

von LIS331 (Gast)


Lesenswert?

Hat schon mal jemand darüber nachgedacht, dass auch das Auslesen des 
Sensors das bremsende Element sein könnte?

Wie ist der angebunden? I²C?

von Rudolph (Gast)


Lesenswert?

Rudolph schrieb:
> 115200 Baud -> 8,68µs/Bit

Naja, was mir gerade durch den Kopf geht, das berücksichtigt noch nicht 
den Overhead durch die Software, das ist wie lange die Daten auf der 
Leitung brauchen.

Ein "Serial.write(val >> 8 );" könnte länger benötigen, wie auch immer 
das implementiert ist.
Und das bedeutet auch, es bleiben 132µs die Daten einzulesen, klingt 
erstmal viel, könnte für den Arduino aber zu knapp sein.

Ich weiss auch nicht, ob die Funktion blockierend arbeitet oder per IRQ 
oder wie auch immer.
Direkt in C würde ich einen Sende-IRQ benutzen und ein Array senden.

Wie ist überhaupt der Sensor angebunden? SPI oder I2C?

Wobei, warum überhaupt auf 16 Bit umrechnen und dann zurück?
SPI und I2C liefern sowieso 8 Bit an, vom Sensor in ein 8-Bit Array 
schieben und das Array auf die Serielle werfen.

von Lukas W. (lucanics)


Lesenswert?

Rudolph schrieb:
> Wie ist überhaupt der Sensor angebunden? SPI oder I2C?

Der Sensor ist mittels I2C angebunden.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Hmm, dann müsst Ihr sicherlich noch das Auslesen des Sensors und das 
Übertragen zum PC gleichzeitig durchführen. Ansonsten wird das ganze 
zeitlich schon ziemlich knapp, da I2C einen recht hohen Overhead 
besitzt.

Der ganze I2C-Zugriff mittel getXValue() und darin enthaltenen separaten 
Registerzugriffen mittels readReg() ist hochgradig ineffizient. Und 
dabei hat ST doch extra die entsprechenden Register sinnvoll angeordnet, 
so dass man alle zusammen in einem einzigen Zugriff mit Autoinkrement 
der Subadressierung auslesen kann. Das ganze ist im Datenblatt haarklein 
und sehr anschaulich beschrieben. Oder habt Ihr es nicht nötig, die 
Datenblätter der relevanten Bauteile intensivst zu studieren?

Bei Eurem Zugriffsverfahren müssen insgesamt ca. 234 Bit übertragen 
werden. Bei einem I2C-Takt von 125 kHz schafft man überhaupt nicht 1000 
Übertragungen pro Sekunde, sondern gerade einmal die Hälfte. Und das 
auch nur dann, wenn man an keiner anderen Stelle Zeit verliert.

Ein optimierter Zugriff erfordert aber nur 84 Bit, d.h. damit wäre Eure 
Anforderung locker zu erfüllen.

Dass natürlich, wie schon oben erwähnt, dass Auslesen der Sensordaten 
(Sample n+1) und Verschicken (Sample n) simultan geschehen muss, ist 
dabei auch völlig klar.

von Rudolph (Gast)


Lesenswert?

Mal prüfen, ob der nicht per SPI angebunden werden kann?
Der Sensor selbst kann ja beides.

von Lukas W. (lucanics)


Lesenswert?

Rudolph schrieb:
> Mal prüfen, ob der nicht per SPI angebunden werden kann?
> Der Sensor selbst kann ja beides.

Jap, funktioniert mittels SPI auch, das haben wir jedoch nicht 
geschafft. Ankommende Daten hatten stets den Wert "0". Wo der Fehler lag 
wissen wir leider nicht.



Andreas Schweigstill schrieb:
> Der ganze I2C-Zugriff mittel getXValue() und darin enthaltenen separaten
> Registerzugriffen mittels readReg() ist hochgradig ineffizient. Und
> dabei hat ST doch extra die entsprechenden Register sinnvoll angeordnet,
> so dass man alle zusammen in einem einzigen Zugriff mit Autoinkrement
> der Subadressierung auslesen kann.

Vielen Dank für den Hinweis. Das müssen wir uns nochmal anschauen. Die 
Umsetzung ist denk ich mal schwieriger als sie scheint...

Andreas Schweigstill schrieb:
> Dass natürlich, wie schon oben erwähnt, dass Auslesen der Sensordaten
> (Sample n+1) und Verschicken (Sample n) simultan geschehen muss, ist
> dabei auch völlig klar.

Geht das mittels Arduino-Code?

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.