Forum: Mikrocontroller und Digitale Elektronik ADXL345 und ESP8266: Echtzeitdatenergassung mit konstanter Abtastrate und Interrupts


von Christian (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

kurz zu mir: Mein Name ist Christian. Ich bin Maschinenbauer, bastle 
aber seit einiger Zeit mit Mikrocontrollern und bin begeistert, was man 
da alles so damit machen kann. Ich konnte schon einige Projekte 
erfolgreich umsetzten. Aktuell hänge ich aber seit einiger Zeit an einer 
Stelle. Deshalb habe ich mich entschlossen, mich an Euch zu wenden.

Meine Applikation ist folgende: Ich möchte über einen ESP8266 einen 
ADXL345 Beschleunigungssensor auslesen, diese Daten über UDP an ein 
externes Programm am PC senden und dort eine FFT machen. Ich nutze zur 
Programmierung die Arduino IDE. Wie so oft bei den Applikationen 
funktionierte eine erste Inbetriebahme einwandfrei. Ich kann meine Daten 
schicken, die Daten kommen an und ich kann die FFT berechnen. Leider kam 
dann ein -für mich bisher unlösbares- Problem auf. Es zeigte sich, dass 
meine FFT-Ergebnisse nicht stimmen. Basis ist ein FFT-Algorithmus, den 
ich schon mehrfach eingesetzt habe und bei Referenzschwingungen korrekte 
Ergebnisse zeigte. Das Problem liegt vielmehr an einer falschen, nicht 
korrekten Zeitbasis. Leider widmet sich keines der Ergebnisse zu dem 
Sensor einer Datenerfassung in "Echtzeit".

Meine zentrale Frage ist: Wie bekomme ich die Daten korrekt äquidistant 
vom Sensor abgetastet?

Ich habe noch nie mit digitalen Sensoren gearbeitet und deshalb wären 
ein paar Hinweise von Euch sehr hilfreich. Vielleicht hat der eine oder 
andere Erfahrungen mit dem Sensor ADXL345. Wichtig wäre mir, ob der Eine 
oder andere mich in meinem Kurz "bestätigen" oder diesen "anpassen" 
könnte.

Zu meinen bisherigen Versuchen:

Meine Zielgröße ist eine Abtastrate von über 1500 Hz, deshalb nutze die 
SPI-Schnittstelle, um überhaupt so hohe Raten zu erreichen. Zitat aus 
dem Datenblatt:

    "Use of the 3200 Hz and 1600 Hz output data rates is only 
recommended with SPI communication rates greater than or equal to 2 
MHz."

Der ADXL345-Sensor hat zwei Pins mit Namen INT1 und INT2. Diese Pins 
können anscheinend so programmiert werden, dass sie mir das 
Vorhandensein von neuen Daten anzeigen (digital, HIGH oder LOW). 
Vermutlich muss ich dabei einen DATA_READY-Interrupt definieren. Hier 
weiss ich leider nicht weiter, wie ich das in Code umsetzen kann.

Wenn ich richtig liege, muss ich diese Pins mit einem Eingang meines ESP 
verbinden und könnte so  erkennen ob neue Daten vorliegen. Für diese 
Information wird dann über ein zusätzliches Kabel neben den schon 
vorhandenen SPI-Kabeln übertragen?

Nun meine Frage: Meint Ihr dieser Ansatz wäre zielführend?

Wenn dem so wäre, wie würde ich das im Code umsetzen? Vor allem würde 
mich interessieren, wie Ihr bei der Gesamtarchitektur vorgehen würdet.

Wären Interrupts ein Ansatz? Ich stelle mir das so vor: Zunächst muss 
ich heraubekommen, welche Register ich im Sensor für die Definition des 
korrekten Verhalten beschreiben muss. Ich definiere anschließend einen 
normalen Interrupt in meinem Code. Wenn der Interrupt (im Takt der 
Samplerate) kommt, schreibe ich in diesem meine Daten via UDP weg. Hier 
habe ich schon die nächste Frage: Ich meine aber einmal gelesen zu 
haben, dass in einem Interrupt keine Einleseroutinen, z.B. Serial.read 
beinhalten sollten, da diese sehr lange brauchen. Könnte das bei meiner 
UDP-Kommunikation auch zu einem Problem werden?

Zusammengefasst: Ich denke, meine Lösung geht nur über die korrekte 
Einstellung des ADXL435.

Es wäre sehr nett, wenn Sie jemand einmal meine Problemstellung 
anschauen und mir ein paar Hinweise geben könnte, wie ich das umgesetzt 
bekomme.

Vielen Dank und vielleicht bis später!

Grüße

Christian

von void (Gast)


Lesenswert?

Christian schrieb:
> Meine zentrale Frage ist:
> Wie bekomme ich die Daten korrekt äquidistant vom Sensor abgetastet?

Das macht der ADXL435 Sensor schon von selbst. Der Sensor tastet in 
äquidistanten Abständen ab, welche du als 'output data rate' im Register 
BW_RATE (bits D3-D0 Rate) festlegen kannst.


> Der ADXL345-Sensor hat zwei Pins mit Namen INT1 und INT2. Diese Pins
> können anscheinend so programmiert werden, (...)

Dazu musst du lediglich, wie schon von dir vermutet, die Register 
INT_ENABLE und INT_MAP im Sensor setzten. Im INT_SOURCE Register kann 
zusätzlich der aktuelle Status der anstehenden Interrupts abgefragt 
werden.
Beispiel:
SPI_ADXL345_Write_Reg(INT_ENABLE, 0x81)  // enable DATA_READY + Overrun
SPI_ADXL345_Write_Reg(INT_MAP,    0x01) // DATA_READY -> INT1 pin / 
Overrun -> INT2 pin

So wird für jeden neuen Sensor-Wert der INT1 pin = high gesetzt.
Der INT2 pin = high zeigt dir gleichermassen, wenn du die Sensor-Werte 
aus dem FIFO nicht schnell genug abgeholt hast und es zu einem Überlauf 
gekommen ist, du also Sensor-Werte verloren hast. Womit wir bei deinem 
Problem sind.

Vermutlich gehen bei dir Sensor-Werte verloren, weil
1) die SPI Kommunikation zum ADXL435 zu langsam ist (zu niedrige 
Baudrate, Software-SPI, ...)
2) die UDP Kommunikation zum PC hin zu lange dauert (und z.B. in der 
Zeit die SPI Kommunikation blockiert)


> Wenn der Interrupt (im Takt der Samplerate) kommt, schreibe ich in diesem meine 
Daten via UDP weg.
> (...) dass in einem Interrupt keine Einleseroutinen, z.B. Serial.read
> beinhalten sollten, da diese sehr lange brauchen. Könnte das bei meiner
> UDP-Kommunikation auch zu einem Problem werden?

Ja, UDP Kommunikation solltest du auf keinen Fall in dem 
Empfangsinterrupt implementieren.

Du solltest nicht versuchen für jeden Sensor-Wert (6 byte) ein UDP Paket 
zu verschicken. Der Header des UDP Paket (8 byte) wäre damit schon 
länger als deine Nutzdaten. Das Erstellen jedes UDP Paketes braucht eine 
gewisse Zeit.
Sinnvoller ist es eine Anzahl von Sensor-Messwerten zu sammeln und 
zusammen in einem UDP Paket zu verschicken. Als Hausnummer würde ich 100 
Sensor-Messwerte (=600 byte) für ein UDP Paket ansetzten. Damit bist du 
bei 32 UDP Paketen pro Sekunde und benötigst für doppelt gebufferte 
Sensor-Messwerte im Controller nur 2x600 byte RAM.

von Christian B. (bastlerbire)


Lesenswert?

Guten Abend void,

vielen Dank für Deine ausführliche Antwort. Ich werde Deine Hinweise 
gleich morgen früh ausprobieren. Spontan habe ich jedoch gleich ein paar 
Rückfragen:

Die Array-Thematik, die Du ansprichst, hatte ich auch schon ausprobiert, 
jedoch wieder verworfen, da ich meine, dass es schnell genug ist, meine 
UDP-Werte einzeln zu verschicken. Ich hatte einmal -wenn ich das richtig 
gemacht habe- die Zeiten für das Zusammenstellen und Versenden eines 
UDP-Paketes mit folgendem Code:
1
Udp.beginPacket(ip, recPort);
2
Udp.write(packetBuffer,UDP_PACKET_SIZE);     
3
Udp.endPacket();

hier komme ich auf etwa 58 Microsekunden (gemessen mit der 
micros()-Funktion) für das Versenden eines Pakets aus x-, y-, und z-Wert 
(=12 Bytes). Meine "Denke" war, folgende:
Die 0,000058 sec entsprechen 17.241 Hz. Das heißt für mich doch, dass 
ich theoretisch Frequenzen bis zu dieser Frequenz erfassen kann und die 
Werte einzeln wegschicken könnte, wenn ich alle anderen Einflüsse 
vernachlässige, oder? Wäre super, wenn Du das  bestätigen oder 
widerlegen könntet. Deshalb zusammengefasst: Diese Zeit sollte doch 
ausreichend kurz sein, um Werte mit 3200 Hz zu erfassen, weshalb ich den 
Array-Ansatz verworfen hatte. Liege ich damit richtig?

Bei dem Array-Ansatz kann ich höchstens 44 Werte wegschicken, da dann 
mein Puffer in meinem externen Programm (Labview) höchstens 548 Bytes 
(12 Bytes * 44 Werte = 528 Bytes). Hier war mir nicht ganz klar, wie ich 
das dann mit dem Interrupt im Code verbinde (da hier das Wegschicken in 
jedem Fall länger dauert, wie Du ja oben ausgeführt hast), bei einem 
Wert ist mir die "Architektur" klar: In meiner Interrupt-Funktion habe 
ich in diesem Fall die oben genannten Codezeilen zur UDP-Kommunikation 
drin, hier dürfte -wenn ich richtig liege- nichts "aus dem Tritt 
kommen". Wo bzw. wie würde ich das dann mit einem Array korrekt 
umsetzen? Vielleicht hast Du mir da noch einen Tipp.

Dir in jedem Fall einmal vielen, vielen Dank dass Du mich da 
grundsätzlich auf den "rechten Weg" gebracht hast. Ich probiere das 
morgen früh gleich einmal aus und werde hier wieder berichten und 
schicke Dir den Code.

DANKE und Grüße

Christian

von RP6conrad (Gast)


Lesenswert?

Bei den ESP wird auch noch zeit benotigt u die ganse Wifi Geschichte ab 
zu handelen. Leider hasst du das nicht selbst in griff : irgendwen wird 
ihre Arduino sketch abgebrochen, und lauft dan Wifi. Mess mal ihre min 
und max "durchlaufzeit", dan haben sie ein idee wiefiel Zeit verpasst 
wird mit Wifi.

von void (Gast)


Lesenswert?

Dir auch einen schönen Abend Christian.


> (...) die Zeiten für das Zusammenstellen und Versenden eines
> UDP-Paketes mit folgendem Code:
>
> Udp.beginPacket(ip, recPort);
> Udp.write(packetBuffer,UDP_PACKET_SIZE);
> Udp.endPacket();
>
> hier komme ich auf etwa 58 Microsekunden

Der Code scheint wirklich das UDP-Paket zu erstellen und auch zu 
verschicken bis die Daten wirklich aus dem TX-Buffer des Ethernet-Stacks 
raus sind. Falls jemand mitliest der ESP8266Wifi/lwIP besser kennt, 
bitte korrigiert mich.
Udp.endPacket() // Blockiert bis Paket wirklich gesendet ist, liefert 
return Wert 1 wenn erfolgreich.
https://www.arduino.cc/en/Reference/WiFiUDPEndPacket
Ruft dazu auf: -> _ctx->send() [1]WiFiUdp.cpp -> udp_sendto() 
[1]/include/UdpContext.h
upd_sendto() [2] ist aus dem lwIP stack und macht blockierendes senden.

[1] 
https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/src/
[2] 
http://www.nongnu.org/lwip/2_0_x/group__udp__raw.html#gaa0e135a5958f1f0cc83cbeb609e18743


Weil aber das senden gerade über WLAN von der Signal-Qualität abhängig 
ist halte ich es für unwahrscheinlich das deine gemessenen 58 
Microsekunden konstant über alle Sende-Vorgänge sind. Im Gegenteil ist 
anzunehmen, dass es eine stark schwankende Sende-Latenz gibt. Annahme: 
99% deiner UDP Pakete haben <=60 µs Sende-Latenz, 1% haben <=10 ms.

Wenn dein Sende-Funktion einmalig für 10ms steht,
erzeugt der ADXL435 Sensor 32 neue Sensor-Werte und spätestens dann 
läuft sein (32 fach) FIFO über.

Dazu kommt noch der von RP6conrad angesprochene Effekt das vermutlich 
deine main loop zyklisch für Wifi Management tasks (oder anderes) 
unterbrochen wird. Bzw. Arduino like, eben dies vor dem periodischen 
Aufruf von loop() gemacht wird.

Diese beiden Effekte sollten sich messbar bemerkbar machen.
Das würde ich beides mal mit einem Timer ausmessen ("micros()" 
Funktion).


> Die 0,000058 sec entsprechen 17241 Hz. (...)
> Deshalb zusammengefasst: Diese Zeit sollte doch
> ausreichend kurz sein, um Werte mit 3200 Hz zu erfassen, weshalb ich den
> Array-Ansatz verworfen hatte. Liege ich damit richtig?

Ja, deine Rechnung ist richtig. Aber deine Messung vermutlich nicht 
(immer).


> Wo bzw. wie würde ich das dann mit einem Array korrekt
> umsetzen? Vielleicht hast Du mir da noch einen Tipp.

Ein Array mit 88 Einträgen zu je 12 byte (1056 byte = 2*528 byte = 
2*44*12 byte).
Ein Zähler (oder pointer) der auf den gerade aktuell zu füllenden 
Eintrag im Array verweist.
Immer wenn ein Sensor Messwert vorliegt (INT1 = high, kann auch einfach 
als Pin am Controller ausgewertet werden) SPI Transfer auslösen und 
Messwerte in Array eintragen an die Stelle an die der Zähler verweist 
und Zähler auf nächstes freien Eintrag zeigen lassen.
Falls Zähler = Array Eintrag 45 ist  UPD Paket mit Array Einträgen 1-44 
verschicken
Falls Zähler = Array Eintrag 89 ist  Zähler auf 0 zurück setzten und UPD 
Paket mit Array Einträgen 45-88 verschicken


Deine Debugging-Möglichkeiten:
- Overrun am INT2 pin überwachen. Falls das auftritt, hast du 
Sensor-Messwerte verloren (FIFO Überlauf).
- Sende und main loop() Zeiten messen.
- Mit Wireshark die Anzahl der angekommenen UDP Pakete am PC auf 
Konsistenz mit Anzahl der Messwerte prüfen.

von Christian B. (bastlerbire)


Angehängte Dateien:

Lesenswert?

Guten Abend void(),

Dir noch einmal vielen, vielen Dank für Deine ausführlichen Erklärungen. 
Diese sind auch für mich als Fachfremden nachvollziehbar (einiges davon 
glaube ich nun zumindest verstanden zu haben). Mein Ziel ist nämlich, 
das alles nachzuvollziehen und zu verstehen. Danke ich weiß das zu 
schätzen, wir haben alle wenig Zeit.

Ich habe auf Basis Deiner Erläuterungen einmal versucht, das in Code 
umzusetzen. Das wird nicht funktionieren, da ich heute Abend keinen 
Arduino zum testen habe. Das ist erst mal ein Versuch, Deine 
grundlegende Architektur nachzubilden, void. Morgen früh werde ich das 
gleich austesten und auch mal wie von Dir vorgeschlagen, die Zeiten 
"ausmessen". Mit Wireshark hatte ich mir schon bei meinen ersten 
Versuchen gearbeitet. Das schaue ich mir morgen auch noch mal an.

Es wäre klasse, wenn Du mir zu meinen Versuchen noch ein paar Hinweise 
geben könntest. Wichtig: Wie oben beschrieben, geht es mir nicht darum, 
dass Du mir meine Code schreibst. Mir wäre es lieber, wenn Du mich auf 
meine Fehler hinweisen und mir Lösungshinweise geben könntest. Natürlich 
nur wenn Du Zeit und Lust (und noch Geduld mit mir) hast.

Folgende Punkte sind mir in meinem Code noch unklar (wie geschrieben, da 
werden noch unzählige Fehler drin sein, aber mir geht es erst mal darum, 
ob ich die Gesamtarchitektur verstanden habe):

Im Code in der jetzigen Form ln?ese ich ständig die Register in der 
main-loop(), quasi "volle Pulle". Würdest Du das so auch machen, da Du 
ja schreibst:

void schrieb:
> Immer wenn ein Sensor Messwert vorliegt (INT1 = high, kann auch einfach
> als Pin am Controller ausgewertet werden) SPI Transfer auslösen und
> Messwerte in Array eintragen an die Stelle an die der Zähler verweist
> und Zähler auf nächstes freien Eintrag zeigen lassen.

Meine Interrupt-Routine nutze ich nur, um in der Hauptschleife zu 
erkennen, indem ich einen Zähler inkrementiere und dann detektiere, wann 
die 45 Werte vorhanden sind und ich dann mein Array via UDp wegschicken 
kann. Somit habe ich in meinem zeitkritischen Interrupt keine 
"bremsenden" Kommunikationsaufrufe und er kann "schön" weiter mein Array 
füllen. Passt das aus Eurer Sicht? So hatte ich Dich void zumindest 
verstanden.

Was vermutlich auch noch nicht korrekt sein wird, ist meine Leseroutine 
für die Register (readRegister). Hier bin ich mir nicht sicher, ob das 
der von Dir angesprochene SPI-Transfer ist. Da muss ich morgen nochmal 
ran.

Ich würde mich sehr darüber freuen, wenn Du mit mir weiter die Lösung 
entwicklen könntest - bitte entschuldige meine vielleicht manchmal 
dummen Nachfragen - ich gebe mir aber wirklich Mühe und versuche Dir zu 
folgen

Viele Grüße und nochmal Danke

Christian

von Christian B. (bastlerbire)


Angehängte Dateien:

Lesenswert?

Guten Abend zusammen,

ich hatte Euch ja versprochen, dass ich mich heute noch einmal an den 
Code setze und diesen unter Realbedingungen ausprobiere. Leider bin ich 
nicht so weit gekommen wie ich mir gewünscht hatte, aber anbei findet 
Ihr meinen -hoffentlich finalen- Code.

Was soweit funktioniert ist der "Hardware-Interrupt", getriggert vom 
Sensor. Das habe ich durch ein paar Oszillopskop-Aufnahmen verifiziert. 
Die eingestellten Sollfrequenzen 50, 800, 1.600 und 3.200 Hz passen mit 
den Impulsfrequenzen zusammen: 49,41 / 782,4 / 1.603 und 3.145 Hz (siehe 
ZIP-Ordner). Ich denke, diese Abweichungen liegen in der Toleranz, oder?

Auch der Code scheint erst einmal zu funktionieren. In der 
Interrupt-Routine wird die von Dir void vorgeschlagene Laufvariable 
(loopi) hochgezählt: Jedes mal, wenn der Interrupt kommt (also mit 
meiner konstanten Abtastfrequenz) wird diese hochgezählt. Wenn mein 
Maximum (45) erreicht ist, schicke ich mein UDP-Packet weg. Bitte 
beachtet: In dieser Version sind die ganzen UDP-und Wifi-Funktionen noch 
nicht berücksichtigt. Ich habe jetzt nur mal den UDP-Block 
(auskommentiert) drin gelassen (siehe auch der letzte Beitrag).
1
Udp.beginPacket(ip, recPort);
2
Udp.write(packetBuffer,UDP_PACKET_SIZE);     
3
Udp.endPacket();

Jetzt eine Frage an Euch: Meint Ihr, dass ich am Ziel bin? Rein vom Code 
her gesehen? Vielleicht seht Ihr ja Optimierungspotenzial in Bezug auf 
das Timing. Wichtigste Frage ist aus meiner Sicht doch, ob das o.g. 
UDP-Paket korrekt im Code platziert ist... Ich werde morgen die Tests 
machen, um dann eventuelle Buffer-Überläufe zu detektieren und mit 
micros()/millis() Zeiten messen. Ich werde auch die Analysen machen, die 
Du void empfohlen hast:

void schrieb:
> Deine Debugging-Möglichkeiten:
> - Overrun am INT2 pin überwachen. Falls das auftritt, hast du
> Sensor-Messwerte verloren (FIFO Überlauf).
> - Sende und main loop() Zeiten messen.
> - Mit Wireshark die Anzahl der angekommenen UDP Pakete am PC auf
> Konsistenz mit Anzahl der Messwerte prüfen.

Gleich mal die Frage: Was habe ich denn aber für Möglichkeiten, wenn 
hier noch Verzögerungen auftauchen sollten? Mein globales Ziel ist es ja 
für meine FFT absolut äquidistant abgetastete Werte zu erhalten - sonst 
stimmt das alles nicht...

Ich würde mich freuen, wenn Ihr mal eine Abschätzung abgeben könntet, ob 
ich mit dem Code auf dem richtigen Weg bin und ob Ihr 
Verbesserungsvorschläge habt.

Ich bin gespannt auf Euer Feedback, Eure Anmerkungen und Kritik!

Vielen Dank an alle schon einmal und Grüße

Christian

von void (Gast)


Lesenswert?

Christian B. schrieb:
> Was soweit funktioniert ist der "Hardware-Interrupt", getriggert vom
> Sensor. Das habe ich durch ein paar Oszillopskop-Aufnahmen verifiziert. (...)
> Ich denke, diese Abweichungen liegen in der Toleranz, oder?

49,41 Hz =   50 Hz -1.18%
782,4 Hz =  800 Hz -2.20%
 1603 Hz = 1600 Hz +0.19%
 3145 Hz = 3200 Hz -1.72%

Erstmal diese Toleranz ist erwartet. Der ADXL345 arbeitet ohne ext. 
Quarz und erzeugt damit seine Frequenz durch einen internen 
(Ring-)Oszillator. Eine Abweichung von +/-2..3% ist da durchaus zu 
erwarten. Eine max. spezifierte Abweichung der Frequenz von der 
eingestellten ODR habe ich aber leider nicht im Datenblatt gesehen.

Anzunehmen wäre aber eine ähnliche Abweichung aller deiner 4 Messungen, 
weil du unter gleichen Temperatur- und 
Spannungsversorgungs-Verhältnissen gemessen hast.
Die Frequenz-Anzeige deine Messungen 'Messung_1600Hz.jpg' und 
'Messung_3600Hz.jpg' wird vermutlich besser/korrekter wenn du für die 
Messungen noch folgendes machst:
- Tastkopf Masse anschließen (bzw. CH1 von AC auf DC umschalten)
- Trigger Level in die Mitte des Signals legen
- Trigger mode: normal (nicht auto)
- Finger weg von autoset, welcher das alles durcheinander würfelt. ;-)


> Mein Ziel ist nämlich,
> das alles nachzuvollziehen und zu verstehen.

Auch wenn du nicht Fachfremd wärst, finde ich du schlägst dich bei der 
Aufgabe ganz gut. Und deine Vorgehensweise finde ich methodisch und gut. 
Von daher macht es Spass dir mit Feedback zu helfen.

Den Code schaue ich mir morgen nochmal an. Heute Nacht wird das nichts 
mehr...

von Christian B. (bastlerbire)


Lesenswert?

Guten Morgen void,

Dir erstmal vielen, vielen Dank für Dein Feedback. Ich freue mich 
wirklich sehr, dass sich ein Profi mit meinem Problem auseinander setzt 
und mit mir eine solche Geduld hat. Noch mal ein Lob: Du erklärst 
wirklich super. Das habe ich in Diskussionen zwischen Elektronikern / 
Elektrotechnikern & Maschinenbauern meist anders erlebt.
Ich habe jetzt schon unglaublich viel von Deinen Ausführungen gelernt 
(und das ist mir das Wichtigste). Deshalb kann ich das nur zurückgeben: 
Es macht echt Spaß! VIELEN DANK!
Ich hoffe, ich habe Deine Hinweise korrekt umgesetzt. Ich bin gespannt 
auf Dein Feedback und werde heute im Laufe des Tages noch die gestern 
geschriebenen "Analysen" machen und die Ergebnisse hier wieder 
einstellen.

Nochmal: Danke, ich weiß das wirklich zu schätzen - wir haben alle wenig 
Zeit.

Ich freue mich auf heute Abend, wünsche Dir einen erfolgreichen Tag und 
sende Dir

viele Grüße

Christian

von Stefan F. (Gast)


Lesenswert?

> Was habe ich denn aber für Möglichkeiten, wenn
> hier noch Verzögerungen auftauchen sollten?

Ich denke, dass du so nicht auf einen grünen Zweig kommen wirst. Bei 
meinen Experimenten habe ich ebenfalls festgestellt, dass meine eigenen 
Programmteile (also die loop() Funktion) in unregelmäßigen Abständen für 
ungewisse Zeit (teilweise einige hundert Millisekunden) nicht mehr 
ausgeführt wird.

Ich denke, dass du daran nichts ändern kannst. Man müsste eine neue 
Firmware für den Chip schreiben, die ganz anders funktioniert.

Wenn man die WLAN Schnittstelle deaktiviert, bekommt man ein besser 
vorhersagbares Zeitverhalten. Aber auch dann treten Verzögerungen auf 
(wegen dem Nachladen von Code aus dem Flash). Außerdem ist das Ein-/Aus 
Schalten der WLAN Schnittstelle Zeitaufwändig und mit Verlust aller 
Daten im RAM Verbunden, weil man den Chip dazu jedesmal neu booten muss.

Ich würde die Suche nach einer eleganten Lösung an dieser Stelle 
abbrechen und lieber einen zweiten Mikrocontroller daneben setzen, der 
die Zeitkritischen Aufgaben erledigt. Ich habe gelesen, dass der ESP32 
zwei Mikrocontroller enthalten soll. Vielleicht kannst du das mit dem 
Teil besser umsetzen.

von Christian B. (bastlerbire)


Lesenswert?

Hallo Stefanus,

vielen Dank für Deine Antwort. Auch wenn Sie mich ehrlich gesagt sehr 
frustriert hat...Ich werde jetzt mal die Antwort von void heute Abend 
abwarten und mich dann noch einmal melden.

Zwei Rückfragen habe ich aber gleich:

1. Zum neuen Setup
Ich hatte ganz zu Beginn einmal mit dem Pretzelboard/NanoESP 
experimentiert (https://iot.fkainka.de/board). Auf diesem Board 
kommuniziert ein ESP8266 (kein 32er) mit einem Arduino Nano über die 
(glaube ich sehr langsame) SoftwareSerial-Schnittstelle. Jetzt könnte 
ich hier doch meine zeitkritische SPI-Datenerfassung des ADXL345 auf den 
Nano "umbauen", um dann -nicht mehr zeitkritisch- die Daten "gemütlich" 
wegzuschicken, wenn das Paket voll/da ist. Habe ich Dich da richtig 
verstanden? Grundsätzlich bekomme ich das bestimmt hin, aber...Die Daten 
sollten quasi in "Echtzeit" in meiner GUI in Labview dargestellt werden 
(das heißt, es sollten keine Verzögerungen auftauchen und das Event 
direkt angezeiggt werden). Schließt sich das nicht aus (schnelle Anzeige 
und langsame Schnittstelle)? Ich hoffe, Du verstehst was ich meine...

2. zum alten Setup
Ich hatte einmal etwas von einem SPIFFS-File-System gehört. Wäre das 
noch eine Idee, um mein derzeitiges Setup zu retten? Ich denke 
folgendes: Wenn ich die (ja jetzt offensichtlich äquidistant 
abgetasteten) Daten korrekt erfasse, speichere ich mein Array wiederum 
irgendwie in einen SPIFFS-Puffer. Wenn dann in der loop() Zeit ist 
(wobei ich mich dann gleich wieder frage, wie ich das detektiere...) 
schicke ich die Daten via UDP weg.

Wie gesagt, das erst einmal als erste (frustrierte) Rückfrage :-(

Dir in jedem Fall vielen Dank für Deine Einschätzung

Ich melde mich

Grüße

Christian

von Stefan F. (Gast)


Lesenswert?

Wegen dem Pretzel Board: Ja, damit dürfte die Aufgabe ohne größere 
Probleme lösbar sein. Das Board ist aber ziemlich teuer. Wenn du lieber 
eine eigene ähnliche Schaltung verwenden willst, schau mal von meinem 
projekt ab: http://stefanfrings.de/wlan_io/index.html

Diese Schaltung + Software wurde wochenlang erfolgreich auf Stabilität 
geprüft. Ist dem Pretzel-Board ganz ähnlich bis auf den wesentlichen 
Unterschied, dass die ESP Kommunikation mit den Standardmäßigen 115200 
Baud abläuft. Somit ist es nicht nötig, den ESP vor dem Einbauen auf 
9600 Baud umzukonfigurieren.

Deine Forderung nach "keiner Verzögerung" ist technisch nicht umsetzbar. 
Wenn es gut läuft, hast allein im WLAN netz schon um 10ms Verzögerung. 
Wenn es schlecht läuft, können es einige hundert ms sein. Da hast du mit 
WLAN die denkbar schlechteste Wahl getroffen.

Andererseits kann das Menschliche Auge ohnehin nur wenige Bilder pro 
Sekunde wahrnehmen. Deswegen würden mich die Verzögerungen durch das 
WLAN nicht stören. Man muss das Ganze nur so programmieren, dass 
unregelmäßige Verzögerungen nicht zu Fehlfunktionen führen. Jeder Audio- 
und Video-Streaming Player demonstriert, dass das machbar ist. Und zwar 
mit etwas Pufferspeicher und notfalls dem Verwerfen von Frames.

> Schließt sich das nicht aus (schnelle Anzeige und langsame Schnittstelle)?

Dein WLAN und der ESP sind zumindest im Mittel sicher erheblich 
schneller, als dein Auge. Und wenn es mal hakelt, friert die Anzeige 
halt mal kurz ein, lässt ein paar Updates aus und fängt sich dann 
wieder.

Dein Hauptproblem ist, dass der ESP zeitweise aufhört, zu messen. Dan 
kannst du mit einem zweiten Mikrocontroller leicht lösen. Der muss 
nämlich nicht stehen bleiben, während der ESP mit sich selbst oder dem 
Netz beschäftigt ist.

> Ich hatte einmal etwas von einem SPIFFS-File-System gehört. Wäre das
> noch eine Idee, um mein derzeitiges Setup zu retten?

Da wird der Flash Speicher zum Speichern von Daten verwendet. Der 
verschleißt aber schnell. Wenn du vor hast, die Speicherplätze mehr als 
100 mal zu überschreiben, informiere dich vorher, ob dieses SPIFFS 
Filesystem Wear-Levelling beinhaltet. Denn wenn es nicht dabei ist, dann 
vergiss es gleich wieder.

Mir ist allerdings unklar, wie du damit dein Setup retten willst.

Ich würde einen zweiten µC zum Messen und Puffern benutzen. Der ESP 
dient dann nur noch als Netzwerk-Schnittstelle. Egal ob er läuft oder 
nicht, die Messung findet trotzdem fortlaufend statt und füllt den 
Puffer. Ein zweiter Thread überträgt den aktuellen Stand aus dem Puffer 
in regelmäßigen Intervallen zum PC. Wenn das dort genau so regelmäßig 
ankommt ist alles gut. Wenn nicht, hat das keinen Einfluss auf das 
Timing der Messung.

von RP6conrad (Gast)


Lesenswert?

Auch SPIFFS auf der ESP hat das gleiche problem : irgendwo lauft die 
WLAN Geschichte und blokkiert dan die loop(). Ich verwende das bei eine 
serial datalogger, aber ab und zu werden Daten verloren gehen. Ich 
sollte eine andere Schnittstelle versuchen, mit WLAN hasst du immer 
diese Verzogerungen. Auch eine andere µcontroller scheint mir besser : 
STM32 hat sehr billige und gute boards. Teilweise sind die auch mit die 
Arduino Umgebung zu programmieren. Aber auch eine Umstieg nach GCC und 
standard C scheint mir sinnfol. Arduino hat relatif fiel "overhead", und 
fur Zeitkritische Sachen ist das nicht ideal.

von Christian B. (bastlerbire)


Angehängte Dateien:

Lesenswert?

Guten Abend,

ich kann Euch etwas Positives berichten. Es funktioniert...! Ich habe 
mal ein paar "Messergebnisse" beigefügt. Für unsere Zwecke ist das 
völlig ausreichend. Die Frequenzen stimmen sehr gut! Das seht Ihr anbei. 
Die Dateinamen entsprechen jeweils den eingestellten Frequenzen. Das 
passt alles soweit, nur...

...sehen meine Signale leider sehr schlecht aus...Die FFT stimmt zwar 
(da der Sensor auf die Schwingungen reagiert), aber nur mit 
Impulsfolgen, nicht mit einem "schönen" Sinus, wie es sein sollte. Ihr 
seht das auf den Abbildungen (die rechte Spalte sind jeweils die 
verrauschten Zeitsignale, links die FFT - bitte nicht von den 
Achsbeschriftungen beeirren lassen, die stimmen noch nicht).

ch habe etwas experimentiert und ich bin mir mittlerweile fast sicher, 
dass es mit den Datentypen zusammenhängt (Der aktuelle Sketch liegt 
bei). Dieser hat sich nur teilweise geändert). Am "besten" wird das 
Signal, wenn ich meine Daten folgendermaßen übergebe (Datei 
Spikes_Sinus.jpg):
1
  xVal = (int16_t)((((int)values[1]) << 8) | values[0]);
2
     yVal = (int16_t)((((int)values[3]) << 8) | values[2]);
3
     zVal = (int16_t)((((int)values[5]) << 8) | values[4]);

Die schlechten Signale rühren daher, dass alle drei Werte in allen 
Achsen 0 sind - besonders gut zu erkennen in der Datei Spikes_3.jpg - 
alle drei Achsen zeigen plötzlich einen Werte von 0...?

Mache ich das mit dem Einlesen der "Rohwerte" richtig? Ürsprünglich 
hatte ich diese Werte, direkt meinem Array zugewiesen (seht Ihr im Code, 
im auskommentierten Teil), da war das Signal aber noch 
verrauschter...Kann das daran liegen, dass ich die Werte über ein Array 
mittels UDP übertrage:
1
 Udp.write((byte *) &accels,sizeof(accels));

Witzigerweise reichen diese Impulsfolgen aus, um die korrekte Frequenzen 
zu berechnen. Zu Erregung habe ich den Sensor mit einer vorgegebenen 
Frequenz beaufschlagt.

Nun meine Frage: Seht Ihr hier eine Fehler? Dazu müsst Ihr vielleicht 
noch wissen, wie die Daten bei mir im Labview ankommen: Als Sting, den 
ich in ein vorzeichenloses Array konvertieren muss. Dieses besteht für 
jeden Messwert aus 4 Werten, z.B. 128 0 0 0, das ganze für x,-y- und 
z-Achse macht dann 12 Werte (12 Bytes) für jeden Messwert (aus allen 
drei Achsen). Dieses Array muss ich dann in Labview aufwendig wieder in 
die einzelnen korrekten Werte zerlegen. Ich habe bis jetzt gerade mein 
Labview-Programm nachvollzogen und bin zu dem Schluss gekommen, dass die 
Werte schon aus dem Controller so rübergeschrieben werden. Das komische 
ist jedoch, dass hier keine "Meldung" kommt:
1
      if (xVal==0 && zVal==0 && zVal == 0){
2
          Serial.print(xVal); Serial.print("\t");
3
          Serial.print(yVal,DEC); Serial.print("\t");
4
          Serial.print(zVal,DEC); Serial.print("\t");
5
          Serial.println("+++++++++++++++++++++++++++++++++++++");      
6
         }

Das hat mich wiederum stutzig gemacht...Mein Gefühl sagt mir, dass das 
"irgendwie" mit den besagten Datentypen (des Structure-Array?) 
zusammenhängt.

Es wäre super, wenn Ihr mir noch einmal einen Hinweis geben könntet. Ich 
bin nahe dran, denke ich...Mit Euer Hilfe kann ich das vielleicht doch 
noch schaffen.

Bitte entschuldigt, wenn ich diesen Sachverhalt nicht fachliche korrekt 
und sauber erklären kann. Ich hoffe, die Bilder und der Code erklären 
alles.

Ich würde mich über ein Feedback sehr freuen. Vielen Dank noch einmal an 
alle, die hier mitdiskutiert haben (RP6conrad & Stefanus) - ohne Euch 
hätte ich es nicht bis hierher geschafft! Das gilt vor allem für Dich 
void!!!

Danke schon einmal, viele Grüße und vielleicht bis später

Christian

von void (Gast)


Lesenswert?

Erstmal vielen Dank Christian, ich hatte einen erfolgreichen Tag. ;-)
Und auch Dank an Stefanus der die praktische Erfahrung mit dem ESP8266 
bzw. dem ESP8266Wifi Ethernet-Stack mitbringt, die mir fehlt. Ich habe 
auf diesem µC bisher nur die Tasmota firmware mit MQTT verwendet. Daher 
auch mein Interesse an dem Thema.

Stefanus F. schrieb:
> Bei meinen Experimenten habe ich ebenfalls festgestellt, dass meine
> eigenen Programmteile (also die loop() Funktion) in unregelmäßigen
> Abständen für ungewisse Zeit (teilweise einige hundert Millisekunden)
> nicht mehr ausgeführt wird.

Das ist genau der angesprochene Effekt. Bei Verzögerungen von teils 
mehreren 100ms ist es nicht realistisch den ADXL435 Sensor direkt vom 
ESP8266 auszulesen. Schon für 100ms würde ein 10x so großer FIFO im 
ADXL435 benötigt. Von daher ist der Vorschlag von Stefanus absolut 
richtig.

Stefanus F. schrieb:
> Dein Hauptproblem ist, dass der ESP zeitweise aufhört, zu messen. Dann
> kannst du mit einem zweiten Mikrocontroller leicht lösen. Der muss
> nämlich nicht stehen bleiben, während der ESP mit sich selbst oder dem
> Netz beschäftigt ist.

Praktisch bedeutet der Vorschlag, dass der zweite µC für die Messung in 
Echtzeit und als vergrößerter FIFO fungiert. Bei der Datenrate des 
Sensors von 38,4 kB/s (=12 Byte * 3200 Werte/s) wirst du mit dem von 
Stefanus vorgeschlagenen Board mit ATmega644 (4kB RAM) ca. 100ms lang 
Daten im RAM zwischenspeichern können. Sprich du wirst bei >100ms 
blockieren des ESP8266, dann auch wieder Ausfälle bekommen, aber das 
kommt dann vermutlich nur selten vor und ist akzeptabel.

Wahlweise kannst du einen zweiten µC mit mehr RAM nehmen. Klassisches 
Optimierungsproblem.


Noch zwei Worte zum Datenformat.
Falls die Anzahl der Messwerte für die FFT welche du am PC machen 
möchtest nicht zu hoch ist, solltest du versuchen die Messwerte als ein 
Paket (oder eine durchnummerierte Anzahl von Paketen) zu versenden und 
alle Daten zu verwerfen wenn es während des Messens der Sensor-Werte für 
das Paket zu einem Overflow kam. Das garantiert, dass die Messdaten 
innerhalb eines Paketes immer äquidistant abgetastete Werte sind. Eine 
FFT über ein Paket ist damit immer korrekt. Über die Grenzen von Paketen 
aber nicht gesichert (wenn Pakete nicht angekommen sind/verworfen 
wurden).

Die Darstellung der x/y/z Werte würde ich erst am PC (falls notwendig) 
umwandeln.
Dein Code unten dazu ist zwar korrekt, aber wandelt im besten Fall "3x 
2x 8bit" über "3x 16bit" nach "3x 2x 8bit". Also 6 byte in 6 byte evtl. 
mit anderer Endianess.
P.S.: Irgendwie bin ich bis hierhin von 12byte pro x/y/z Messwerte-Tupel 
ausgegangen. Es sind aber nur 6byte (DATAX0, DATAX1, DATAY0, DATAY1, 
DATAZ0, DATAZ1) oder habe ich was übersehen?
1
struct dirs{
2
  int x;
3
  int y;
4
  int z;
5
};
6
//(...)
7
struct dirs accels[44];  // UDP-Buffer in Labview is 548 Bytes/ 12 Bytes = 44 (per sent packet):
8
// x | y | z --> 0 0 0 0 | 0 0 0 0 | 0 0 0 0
9
     accels[loopi].x = ((int)values[1]<<8)|(int)values[0];     //The X value is stored in values[0] and values[1].
10
     accels[loopi].y = ((int)values[3]<<8)|(int)values[2];     //The Y value is stored in values[2] and values[3]. 
11
     accels[loopi].z = ((int)values[5]<<8)|(int)values[4];     //The Z value is stored in values[4] and values[5].
12
//(...)
13
Udp.write((byte *) &accels,sizeof(accels));


Zu deinem Code.
Der INT1 (DATA_READY) vom ADXL435 Sensor ist so wie ich das lese solange 
high, wie noch Mess-Daten im FIFO liegen. Das spricht dafür dieses 
Signal gar nicht als Interrupt auszuwerten, sondern per Pollen des Pins. 
Aber als (Level) Interrupt geht natürlich auch.
Pseudocode:
1
int NointerruptPin=4; // INT1 at GPIO4
2
3
loop()
4
{
5
    while ((HIGH == digitalRead(NointerruptPin)) && (loopi < 45))
6
    {
7
        readSPIRegister(DATAX0, 6, values);
8
        accels[loopi] = values;  // copy values to accels array
9
        loopi++;
10
    }
11
12
    if (loopi == 45)
13
    {
14
        SendPacket(accels[0], size(44*accels[0])); //sende packete 1-44
15
        loopi = 0;
16
    }
17
}

Mein Vorschlag, welchen du nicht im Code abgebildet hast, war dass du 
den Buffer (das Array) zweiteilst.
Eine Hälfte wird jeweils gerade befüllt, die andere als ein Paket 
verschickt. Weil du jetzt die Messung in einen zweiten µC verlegst, wird 
es darin vermutlich Sinnvoller sein ein Ringbuffer zu implementieren.


Oh, etwas spät. Das überlappt sich jetzt mit deiner Erfolgsmeldung.

von void (Gast)


Lesenswert?

> ich bin mir mittlerweile fast sicher, dass es mit den Datentypen zusammenhängt

Für DATA_FORMAT = 0xB liefert der Sensor
 rechts-alignte, vorzeichenbehaftete 13bit Werte welche auf 16bit sign 
extended werden.

Die landen dann in integer (int) Variablen. Int ist bei ESP8266 ein 
signed 32bit Datentyp. [1]
Besser wäre dediziert den int16_t Typ zu verwenden, wenn du signed 16bit 
brauchst.
Bzw. bei deiner Schieberei bei der du die zwei bytes in denen der 16bit 
sign extended Wert drin steht auch als uint8_t behandelst. Sonst 
passieren noch lustige Umwandelungen, die ich mir nach Mitternacht 
gerade nicht mehr vorstellen kann.


uint8_t values[6];

struct dirs{
  int16_t x, y, z;
};

int16_t xVal, yVal, zVal = 0;

xVal = (int16_t)((((uint16_t)values[1]) << 8) | (uint8_t)values[0]);


[1] 
https://github.com/esp8266/Arduino/blob/master/tools/sdk/include/c_types.h

von Stefan F. (Gast)


Lesenswert?

Was treibt einen eigentlich dazu, komprimierte JPG Dateien mit ZIP 
nochmal zu "komprimieren"? Die werden nicht noch kleiner!

von void (Gast)


Angehängte Dateien:

Lesenswert?

Um dem Vorsprung von Stefanus an Praxiserfahrung einhalt zu gebieten ;-) 
und selber was zu lernen, habe ich heute Abend zum Test einfach mal das 
Setup von Christian nachgestellt.

hw: Wemos D1 mini(ESP8266) -> 2.4GHz WiFi AccessPoint(TL-WR802N) -> 
Switch(100MBitEth) -> Linux-PC
sw: SPI_Interrupt_UDP_Beta.ino, modifiziert mit loopi++ in jeder loop(), 
weil keine hw die INT1 erzeugt vorhanden ist.
settings: arduino 1.8.5 / esp8266 for arduino v2.4.1 / lwIP v2 
LowerMemory

Ergebnis:
Sendet UDP Paket mit 528 Byte payload im Mittel alle ~5ms, also etwa 825 
kbps (~6 MB/min) Datendurchsatz.
Innerhalb von einer Minute (12000 Pakete) benötigten 52 Pakete (~0.5%) 
mehr als 10ms.
Die Verteilung der Paket-Empfangsdauer ist Anhang gezeigt.

Für Echtzeitmessungen mit harten Anforderungen wird das so also nichts.
Aber wenn man hier und da mal ein paar Daten verlieren kann das durchaus 
okay sein.

Ich denke mal Christian kommt jetzt von selber weiter. Und ich wende 
mich dann mit dem Gelernten wieder der Anwendung als Haussteuerung mit 
MQTT zu.

von Christian B. (bastlerbire)



Lesenswert?

Guten Abend zusammen,

ich möchte Euch berichten, wie bei mir derzeit der Stand ist. Zunächst 
nocheinmal Danke an alle, die mich bis hierher geführt haben. Ich hatte 
ja in meinem letzten Post von einem Erfolg berichtet. Leider trifft dies 
nur auf die FFT zu...Ich hätte mir wirklich gewünscht, void hätte 
vollkommen recht:

void schrieb:
> Ich denke mal Christian kommt jetzt von selber weiter.

Vieleicht kurz vorab als Feedback zu Euren Anmerkungen:

void: Dass da nicht alle Werte ankommen, habe ich registriert und 
"akzepiert". Für unsere Anwendung ist das aber absolut ausreichen. Das 
sind keine Echtzeitanforderungen. Hauptsache, die FFT stimmt. Danke für 
Deine anschauliche Erklärung Deiner Messung!

Stefauns:
Die Sache mit dem Pretzelboard und Deinem Ansatz ist mir nicht mehr aus 
dem Kopf. Deshalb habe ich den nächsten Sensor - einen Dehnmessstreifen 
mit Verstärkerplatine von Sparkfun 
https://www.sparkfun.com/products/13879 über dieses Konzept 
angeschlossen.

Zusätzlich zu dem Beschleunigungssensor muss ich eine Kraftmessung 
mittels DMS implementieren. Dazu habe ich mal den von Dir Stefanus 
beschriebenen Ansatz angegangen: Ich habe einen zusätzlichen Controller 
(Arduino Nano), den ich über die SoftwareSerial()-Schnittstelle anbinde. 
Das funktioniert auch soweit. Ich bekomme die Daten der Dehmessstreifen 
ausgelesen. Der Arduino ist dabei mein Sender und sendet die Daten an 
den ESP (Der Sender ist: softwareSerial_HX711_Arduino_final.ino) Ich 
glaube jedoch, dass mein Code nicht optimal ist. Dazu mal meine 
Gedanken:

1. Die SoftwareSerial-Schnittstelle ist nicht gerade schnell, oder? Wäre 
es hier besser auf die Hardware-Serielle-Schnittstelle zu gehen? Das war 
urspünglich mein Ansinnen, ich bin da aber an der Pinbelegung meines 
NodeMCU gescheitert.

2. Ich lese jetzt gerade ständig von der seriellen Schnittstelle in 
meiner Hauptschleife mittel des folgendes Codes:
1
 recvWithStartEndMarkers();
2
3
    if (newData == true) {
4
        //Serial.print("This just in ... ");
5
        //Serial.println(receivedChars);
6
        DMS_value = atoi(receivedChars);
7
        //Serial.println(receivedChars);
8
        //Serial.println(DMS_value);
9
        newData = false;
10
    }

Meint Ihr, das ist ok (von den Geschwindigkeiten/Verzögerungen her)? 
Oder soll ich besser noch eine if-Abfrage einbauen und wirklich nur zu 
bestimmten Zeitpuntken lesen (z.B. nach 5 Sekunden), da die DMS ja nicht 
zeitkritisch ist...? Ich hatte auch das ursprünglich so geplant, aber 
habe es irgendwie nicht hinbekommen, wenn Ihr nun sagt, dass das besser 
ist, muss ich das noch einmal versuchen...

3. Ich habe das Gefühl, dass mein Code durch die Implementierung des 
zusätzlichen Controllers nicht mehr so stabil ist. Das äußert sich 
dadurch, dass die Verbindung manchmal abreißt, was mir davor nie 
passiert ist. Meint Ihr, das liegt an der SoftwareSerial-Schnittstelle, 
oder an Unzulänglichkeiten meines Codes?

4. Wie würdet Ihr das denn grundsätzlich angehen? Meine Dehnmessstreifen 
kann ich ja prinzipiell irgendwann wenn Zeit ist abfragen. Mit 
grundsätzlich meine ich: Wie frägt man in einer mehr oder minder 
zeitkritischen Applikation (damit meine ich meine bisherige 
Beschleunigungssmessung) "nebenher" einen unzeitkritischen Wert ab, bzw. 
allgemeiner, wie stellt man sicher, dass diese Abfrage nicht die höher 
priorisierte Applikation ausbremst...?

Es wäre wunderbar, wenn Ihr mir noch den einen oder anderen Denkanstoß 
geben könntet, damit ich auch diese Hürde packen kann. Wie Ihr merkt, 
gebe ich wirklich alles, um Eure Hinweise aufzunehmen und umzusetzen.

Ich würde mich sehr über die Weiterführung der Diskussion freuen.

Vielleicht bis später!

Euch einen schönen, warmen Abend

Grüße

Christian

von Stefan F. (Gast)


Lesenswert?

> Die SoftwareSerial-Schnittstelle ist nicht gerade schnell, oder?

Deswegen nutzt mein Konzept Software-Serial nur zum Senden und das geht 
durchaus ganz problemlos mit 115200 Baud. Für den Empfang teilen sich 
USB Port und ESP eine gemeinsame Leitung.

> Ich habe das Gefühl, dass mein Code durch die Implementierung
> des zusätzlichen Controllers nicht mehr so stabil ist

Wildes Raten ist hier nicht hilfreich. Was wir auf Basis von so wenig 
Informationen Meinen, ist irrelevant.

> Wie würdet Ihr das denn grundsätzlich angehen?

Soweit möglich mit State Machines (Endlicher Automat, Zustandsautomat).
Dazu Interruptroutinen nur wenn nötig und nur so klein wie nötig. 
Interruptroutinen sollen Ereignisse erfassen und Daten puffern. Die 
Verarbeitung der Daten sollte außerhalb der Interruptroutinen erfolgen.

Es gibt immer Ausnahmen, dennoch empfehle ich das als Grundregel.

Unerwartete Verzögerungen ermittelst du am einfachsten mit Hilfe eines 
oder mehrerer I/O Pins, die du an einen Logic Analyzer anschließt. Mit 
diesem Gerät kannst du über längeren Zeitraum aufzeichnen, wann welcher 
Ausgang seinen Pegel geändert hat.

Serielle Ausgaben sind auch ganz nett, vor allem zusammen mit dem Wert 
eines fortlaufenden Millisekunden-Zählers. Allerdings kostet die 
Umwandlung in Text und die Übertragung viel Zeit, was in deinem Fall 
wohl die ganze Messung zunichte machen würde.

von Christian B. (bastlerbire)


Lesenswert?

Hallo Stefanus,

vielen Dank für Deine erneuten Hinweise. Ich habe mir mittlerweile auch 
einmal Deine Homepage angeschaut. Wirklich genial, was Du alles kannst - 
da bin ich wirklich beeindruckt...

Dürfte ich Dich noch einmal behelligen, bzw. ein paar Rückfragen 
stellen?

Du nutzt grundsätzlich die Basis-AT-Befehle und nicht die 
UDP-Bibliotheken wie ich, um die Werte zu versenden, oder? Basis für die 
UDP-Übertragung ist dann die SoftwareSerial-Schnittstelle. Wenn ich 
Deinen Ansatz richtig verstehe, nutzt Du die serielle Schnittstelle 
tatsächlich nur um die Werte zu senden, oder? Dein Ansatz bedeutet 
demnach, dass Du die Daten "bequem" in einem System (auf einem Board) 
erfasst und diese dann bei Gelegenheit wegschickst. In meinem Fall würde 
das bedeuten, dass ich -sobald mein Array voll ist- über die AT-Befehle 
mein Array wegschicke, meinst Du folgender Pseudo-Code passt im Groben?
1
#include <SoftwareSerial.h>    
2
3
SoftwareSerial esp8266(11, 12); // RX, TX
4
5
void setup() {
6
7
  esp8266.begin(115200);
8
9
  sendCom("AT+CIPMODE=0", "OK");
10
  sendCom("AT+CIPMUX=0", "OK");
11
  sendCom("AT+CIPSTART=\"UDP\",\"192.168.4.2\",90", "OK"); //UDP-Server
12
13
}
14
15
void loop() {   // läuft mit maximaler Geschwindigkeit
16
17
  Erfasse DMS(); // Hole DMS-Wert ab
18
  int DMS_Wert == xx;
19
 
20
  Erfasse Beschleunigungssensor(); // hole Beschleunigungswert ab
21
  int x = xx;
22
  int y = xx;
23
  int z = xx;
24
  
25
  Messarray[loopi] = (DMS_Wert, x, x, z);  // Äquidistant abgetastete Arraywerte 
26
  
27
  if loopi == 44{ //(wenn der 44. Interrupt angekommen ist)
28
    if (Serial.available())
29
    
30
    sendCom("<x;y;z;DMS>");  // Schreibe das Array-Paket auf die UDP
31
32
    loopi = 0; // Reset löscht das Array 
33
    
34
  }
35
36
}
37
38
String sendCom(String command)   // Hier übergebe ich mein Array 
39
{
40
  esp8266.println(command);
41
}

Dier nutze ich doch dann die langsame SoftwareSerial-Schnittstelle nur 
zu bestimmten Zeiten, was für mich doch wichtig wäre, oder? Meine 
Hauptschleife (zur Erfassung der Messwerte), läuft mit maximaler 
Geschinwdigkeit. Prinzipielö bin ich dann doch bei meinem gestrigen 
Ansatz, nur dass ich keine zusätzlich Kommunikation zur 
Messwerterfassung (der DMS-Werte) benötige, sondern "nur" eine zum 
Versenden über Wifi, bin ich richtig?

Es wäre super nett, wenn Du mir o.g Ansatz einmal bestätigen oder 
"widerlegen" könntest, dann würde ich das tatsächlich einmal mit einem 
Pretzelboard angehen (so eines habe ich hier zum testen) --hoffentlich 
dann mein finaler Ansatz!

Ich bedanke mich vielmals für Deine ausführlichen Erklärungen!

Viele Grüße

Christian

von Stefan F. (Gast)


Lesenswert?

> Du nutzt grundsätzlich die Basis-AT-Befehle

Nein, ich nutze in genau einem Projekt die AT Befehle. Alle anderen habe 
ich mit Arduino Firmware gemacht. Ich bin der AT Firmware sehr gut 
zurecht gekommen. Mein I/O Modul habe ich wochenlang auf Stabilität 
geprüft, da ich es in Kombination mit einer Alarmanlage nutzen wollte. 
Von der Stabilität der Firmware bin ich (inzwischen) überzeugt.

> Wenn ich Deinen Ansatz richtig verstehe, nutzt Du die serielle
> Schnittstelle tatsächlich nur um die Werte zu senden, oder?
> Dein Ansatz bedeutet demnach, dass Du die Daten "bequem" in
> einem System (auf einem Board) erfasst und diese dann bei
> Gelegenheit wegschickst.

Vermutlich meinst du meine I/O Firmware. Ich nutze dort eine Tx Leitung, 
um per Software-Serial Kommandos an das ESP Modul zu senden. Empfangen 
tue ich jedoch mit dem echten seriellen Port, der wechselweise auch für 
die USB-UART Schnittstelle genutzt wird.

Der Mikrocontroller kann abwechselnd seriell über das ESP Modul mit dem 
PC Kommunizieren und in den Pausen dazwischen andere Aufgaben 
wahrnehmen. In meine I/O Firmware hat der PC die Rolle das Masters. Er 
sende Kommandos an den µC, die dieser beantwortet.

Bei so einem Datensammler wie deinem scheinen mir UDP Pakete, die der µC 
unaufgefordert an den PC sendet allerdings besser geeignet. Also so wie 
du das oben skizziert hast. Da oben Fehlt ein bisschen Code, der den 
nötigen AT Befehl erzeugt und die Zahlen darin einfügt. Das müsste mit 
sprintf() relativ einfach gehen.

Du solltest dein Programm so gestalten, dass die serielle Kommunikation 
nicht das Timing deiner Messungen beeinflusst. Das ist bei Softserial 
ziemlich Tricky, denn SoftSerial muss zeitweise alle Interrupts sperren 
(damit ihr Timing stimmt).

Ich denke, du wirst am Besten klar kommen, indem du die CPU Zeit 
Scheibchenweise aufteilst. Es wird immer abwechselnd Zeit zum Messen und 
Zeit zum Senden haben.

Ganz grob so:
1
unsigned long time;
2
3
void setup()
4
{
5
    time=millis();
6
}
7
8
void loop() 
9
{
10
    if (millis()-time>=10)
11
    {
12
        time+=10;
13
        messen();
14
        senden();
15
    }
16
}

Die Funktion messen() liest den Sensor aus. Die Funktion senden() sendet 
gemessene Daten, falls genügend Messungen angesammelt wurden. Beides 
passiert in diesem Fall im 10ms Intervall. Damit das klappt, dürfen die 
beiden Funktionen zusammen nicht länger als 10ms dauern. Ich denke, das 
kann man schaffen.

Mit folgendem Code kannst du soft-serial bis zu 115200 Baud senden, das 
ist in deinem Fall besser als die lahme Soft-Serial von Arduino:
1
#include <util/delay.h> 
2
#include <avr/pgmspace.h>
3
#include <avr/interrupt.h> 
4
5
#define F_CPU 16000000
6
#define SERIAL_BITRATE 115200
7
8
// Calculate the time per bit minus 5 CPU cycles
9
#define US_PER_BIT 1000000/SERIAL_BITRATE-5/F_CPU
10
11
#if F_CPU/SERIAL_BITRATE<100
12
    #warning SERIAL_BITRATE is to high for this CPU clock frequency
13
#endif
14
15
// Makros to control the soft TxD Pin
16
// Don't forget to configre the PIN as output.
17
#define SOFTSERIAL_TXD_HIGH   { PORTD |= (1<<PD2); }
18
#define SOFTSERIAL_TXD_LOW    { PORTD &= ~(1<<PD2); }
19
 
20
// Send a single character or byte.
21
void softSerial_sendChar(char c)
22
{
23
    cli();
24
    // Send start bit
25
    SOFTSERIAL_TXD_LOW;
26
    _delay_us(US_PER_BIT);
27
    // Send 8 data bits
28
    for (uint8_t i=0; i<8; i++) 
29
    {
30
        if (c & 1)
31
        {
32
            SOFTSERIAL_TXD_HIGH;
33
        }
34
        else
35
        {
36
            SOFTSERIAL_TXD_LOW;
37
        }
38
        _delay_us(US_PER_BIT);
39
        c=c>>1;
40
    }
41
    SOFTSERIAL_TXD_HIGH;  
42
    sei();
43
    _delay_us(2*US_PER_BIT);
44
}
45
46
// Send a char array from RAM.
47
void softSerial_sendStr(char* text)
48
{
49
    char c;
50
    while ((c=*text))
51
    {
52
        softSerial_sendChar(c);
53
        text++;
54
    }
55
}
56
57
// Send a char array from program memory, use with PSTR().
58
void softSerial_sendStr_P(PGM_P text)
59
{
60
    char c;
61
    while ((c=pgm_read_byte(text)))
62
    {
63
        softSerial_sendChar(c);
64
        text++;
65
    }
66
}

Empfangen kannst du damit nichts. Zum Empfangen musst du einen echten 
seriellen Port benutzen. Ich würde in deinem Fall jedoch einfach gar 
nichts empfangen. Eine simple unidirektionale Kommunikation vom µC zum 
ESP Modul nach dem Fire&Forget Prinzip sollte prima funktionieren.

Bei 115200 Baud kannst innerhalb von 10ms jeweils einen AT+CIPSEND 
Befehl plus etwa 80 Byte Nutzdaten senden. Du hast aber etwas weniger 
Zeit, wegen dem Auslesen des Sensors. Kannst ja mal ausrechnen, ob das 
deiner Anforderung genügt.

Ansonsten gibt es natürlich auch µC mit zwei echten seriellen Ports, zum 
Beispiel den ATmega328PB (mit B!). Ich bin nicht sicher, ob der von 
Arduino unterstützt wird, aber wenn ja, dann würde dies die 
Programmierung drastisch vereinfachen. Denn echte serielle Ports können 
bidirektional mit Puffer kommunizieren. Der µC kann daher andere 
Programmteile ausführen, WÄHREND er gleichzeitig mit dem ESP 
kommuniziert. Bei meinem obigen Ansatz mit Softserial geht das nur 
abwechselnd.

Egal ob mit SoftSerial oder einem echten seriellen Port: Wenn du nicht 
auf die Antwort des ESP Modul wartest, kann dein Programm sinnvolle 
Dinge tun. Egal ob die WLAN Verbindung steh, hakelt oder der ESP aus 
sonstigen Gründen mal gerade eine Denkpause einlegt. Wenn er länger als 
10ms hängt (bzw weclhes Intervall auch immer du programmierst), gehen 
allerdings einzelne UDP Pakete verloren.

von Christian B. (bastlerbire)


Lesenswert?

Hallo Stefanus,

sorry, ich bin es nochmal: Ich schaue gerade nach dem 
"Pretzelboard-Ansatz" und bin wieder auf ein Problem gestoßen: Mein 
Sensor benötigt -wenn ich mit 1600 Hz abtasten möchte- eine 
SPI-Kommunikation. Jetzt habe ich mir mal das Pinout-Diagramm des Boards 
angeschaut und glaube, dass ich das dann gar nicht kann, da die Pin 12 
und 11 vom ESP belegt sind...:-( sehe ich das richtig?

https://iot.fkainka.de/pinout-pretzel-board-nanoesp

Ich denke, wenn das so ist, dass ich mich nun entscheiden muss, ob ich 
mir einen alternativen Sensor aussuche, oder das derzeitige Setup so 
nehme wie es ist...Mit dem neuen Sensor müsste ich ja wieder zurück an 
den Anfang :-(

Sorry, dass ich Dich noch einmal nerve - das Thema betrifft ja nur am 
Rande den Betreff, bzw. das Thema über das wir gerade diskutieren. Ich 
bin wirklich am Verzweifeln...

Vielen Dank Dir und Grüße

Christian

von Stefan F. (Gast)


Lesenswert?

> dass ich das dann gar nicht kann, da die Pin 12
> und 11 vom ESP belegt sind...:-( sehe ich das richtig?

Sieht so aus, schade. Muss es denn wirklich ein fertiges Board mitsamt 
ESP sein? Du kannst doch auch einfach ein x-beliebiges Board mit dem 
besser geeigneten ATmega328PB nehmen und ein ESP-01 Modul extern dran 
hängen.

Wie wäre es damit:
https://eckstein-shop.de/Pololu-A-Star-328PB-Micro-33V-8MHz-Programmable-Module-ATmega328PB-AVR
https://eckstein-shop.de/AI-Thinker-ESP-8266-Serial-WIFI-Wireless-Remote-Control-Module-ESP-01S

Laut Pololu unterstützt Arduino den zweiten seriellen Port: 
https://www.pololu.com/docs/0J74/4.4

Das wäre doch eine wesentlich elegantere Lösung als mit SoftSerial.

von Christian B. (bastlerbire)


Lesenswert?

Hallo Stefanus,

Du bis klasse! Und gibst mir wieder Mut!

Ich schaue mir das jetzt mal an und melde mich wieder!

Viele Grüße

Christian

von Stefan F. (Gast)


Lesenswert?

Falls du dieses ATMgea328PB Board bestellst, denke daran, dass du es 
irgendwie mit dem PC verbinden willst. Du brauchst wohl auch noch einen 
USB-UART dazu und eventuell einen ISP Programmer (falls es keinen 
Bootloader enthält).

von Stefan F. (Gast)


Lesenswert?

Mir ist noch ein Produkt eingefallen.

Arduino Pro Micro in der 3,3V Version. Der hat nur einen seriellen Port, 
was ausreicht weil dort die USB Schnittstelle im AVR Mikrocontroller 
integriert ist.

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.