Ich arbeite aktuell an einem Projekt, bei dem ich die Bewegung von einem Skateboard aufzeichnen mag.
Ich verwende einen Arduino MKR Zero und die IMU BNO055 von Bosch, als MKR IMU Shield.
Der Sensor hat eine Abtastfrequenz von 100 Hz. Diese würde ich auch gerne komplett ausnutzen.
Bei meinem aktuellen Sketch werden die Daten eingelesen und alle X Sekunden auf die SD Karte geflushed. Funktioniert ansich alles fein und die Loop dauert kürzer als 10ms (Zeit zwischen samples bei 100Hz). Doch manchmal, aus mir unerklärlichen Gründen, braucht die Loop statt 7 Sekunden 14 oder so und dadurch sinkt die Frequenz kurzfristig. Dies passiert etwa bei jedem 10 sample, also alle 100ms.
Ich habe den Code auf Englisch kommentiert und ihn versucht übersichtlich zu halten. Ich verwende zum SPeichern auf der SD-Karte die SDFat library (ich hoffe ich verwende diese richtig).
Die Structs kommen direkt von der Sensor library, durch sie kann ich direkt die Sensor Daten abfragen.
Offensichtlich misst du mit deiner Stoppuhr nicht die loop() Zeit, sondern die Zeit für die SD Karte. Der gezeigte Code passt also irgendwie nicht richtig zum Threadtitel.
Leider zeigst du deine Ergebnisse nicht.
Und ja, die SD Karte (und ihre Lib) braucht manchmal Zeit für sich.
Ich habe gerade bemerkt, dass jeder 8. Wert immer den Spike hat. Jeder 8. Wert braucht etwa doppelt so lange.
@combie Ja du hast recht ich habe nicht direkt in der Loop gemessen, aber da der Void den ich messe, immer wieder aufgerufen wird, wird so auch indirekt die Loop gemessen, oder?
Das Flushen ansich ist auf jeden Fall daran nicht Schuld, da es aktuell nur alle 10 Sekunden aufgerufen wird.
Hier wäre der Code mit der Stoppuhr im Loop direkt:
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include <SPI.h>
#include <SD.h>
#include <avr/dtostrf.h>
#include "BNO055_support.h"
#include <SdFat.h>
#include "SdFat.h"
// Check I2C device address and correct line below (by default address is 0x29 or 0x28)
// id, address
Adafruit_BNO055 bno = Adafruit_BNO055(55, 0x28);
//creating a File
File file;
// declaring global variables
const int chipSelect = 28; // CS Pin for SD
int last_data_save = 0; // variable for the last time data was read
int counter = 0; //counts the columns with values (samples)
//sensor data global
struct bno055_t myBNO;
imu::Quaternion quat;
imu::Vector<3> acc;
imu::Vector<3> gyr;
// Name of the filename__________________________________
const char dataFileName[14] = "test5.txt";
//How often should be flushed?
int flushtimer = 10000; //milliseconds
//managing sample timing
int last_data_read = 0; // variable for the last time data was read
const int SAMPLE_RATE = 100; //100 Hz
const int SAMPLE_TIME = 1000 / SAMPLE_RATE; // 1000 = 1 sec divided by the Sample Rate = Time between samples
//_________________________________________________________________________________________
// ------------------ initializeSD&Sensor ----------------------------------
void setup(void)
{
//Initialize I2C communication
Wire.begin();
//Initialization of the BNO055
BNO_Init(&myBNO); //Assigning the structure to hold information about the device
//Configuration to NDoF mode
bno055_set_operation_mode(OPERATION_MODE_NDOF);
delay(1);
Serial.begin(19200);
//wait for the serial monitor
//delete line below for real measurements
while (!Serial) {}
// SD-Card Initialise
Serial.print("Initializing SD card...");
if (!SD.begin(28)) {
Serial.println("initialization failed!");
while (1);
}
file = SD.open(dataFileName, O_CREAT | O_WRITE); //Opens file - SDFAT library
}
void loop(void)
{
if (millis() - last_data_read >= SAMPLE_TIME) {
last_data_read = millis();
stopWatch(); //starts stopwatch
counter = counter + 1;
storeData();
stopWatch(); //stopps stopwatch
}
}
void storeData() {
/*___Sample Counter___*/
file.print(counter);
file.print(";");
/*__Tracking time/millis__*/
file.print(millis());
file.print(";");
/*______Quaternion_____*/
quat = bno.getQuat();
file.print(quat.w());
file.print(";");
file.print(quat.y());
file.print(";");
file.print(quat.x());
file.print(";");
file.print(quat.z());
file.print(";");
/*______ACCELEROMETER_____*/
acc = bno.getVector(Adafruit_BNO055::VECTOR_ACCELEROMETER);
file.print(acc.x());
file.print(";");
file.print(acc.y());
file.print(";");
file.print(acc.z());
file.print(";");
/*______GYROSCOPE_____*/
gyr = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE);
file.print(gyr.x());
file.print(";");
file.print(gyr.y());
file.print(";");
file.println(gyr.z());
if (millis() - last_data_save >= flushtimer) {
last_data_save = millis();
saveData();
}
//delay(1000);
}
void saveData() {
file.flush(); //writing data to SD
Serial.println("Writing to SD");
}
// ------------------ Stoppuhr ----------------------------------
boolean stopWatch_started = false; // function to measure time
int stopWatch_time;
void stopWatch() {
if (stopWatch_started == false) {
stopWatch_time = millis();
stopWatch_started = true;
}
else {
int duration = millis() - stopWatch_time;
Serial.print("Duartion: ");
Serial.print(duration);
Serial.println(" ms");
stopWatch_started = false;
}
}
Hättest du eine Idee wie ich verhindern kann, dass es zu diesem Spike kommt?
@michael_x mit dummies meinst du, statt der Daten vom IMU einfach einen String z.B zu speichern?
Falls es an der SD-Karte liegt, würde hier eine schnellere SD-Karte Abhilfe schaffen?
Aktuell verwende ich eine 2GB Karte der Klasse 6 mit ~6MB/S. Also nicht die langsamste und auf jeden Fall nicht die schnellste.
Ich meinte, einfach mal gar nichts speichern. Dann siehst du, was die übrigen Libraries brauchen und dass der Spike weg ist. (Der entsteht vermutlich alle 512 byte, wenn tatsächlich die SD-Karte physikalisch dran kommt.)
Schnelle SD Karten sind üblicherweise auch nicht schnell im SPI - Modus (den es aus historischen Gründen und zum Glück für Arduinos gibt).
Alternativ kannst du auch für das Gegenteil einen Test schreiben (Permanent jede ms ein paar Byte schreiben und deine Stopwatch auf micros() umstellen).
Ja, nee...
Ich finde den Ort schon ok, wo du gemessen hast.
Nur eben die Information, war inkonsistent.
void, heißt unbestimmt, oder nicht vorhanden.
Damit ist der Rückgabe Datentype der Funktion gemeint.
Was du meinst, ist also kein void, kein Leerraum, sondern eine Funktion.
Ich möchte fast dafür garantieren, dass die zugehörige Datenmenge in etwa einer Sektor Größe entspricht.
So und jetzt zum schwierigen Teil....
Die Pausen stören dich.....
Allerdings gehören sie zur SD Karte im SPI Modus, wie das Atmen zum Menschen.
Eigentlich können alle modernen SD Karten einen 4 Bit breiten Streaming Modus.
Dieser würde ein ruckelfreies Schreiben ermöglichen. Wird z.B. in Video Kameras genutzt.
Nur eben weder die SD Lib, noch dein Board ist dafür vorbereitet.
Ich würde mal sagen, dass dein Problem keine einfache Lösung kennt.
Die Bereiche Sensoren lesen und Daten schreiben müssen zeitlich entkoppelt werden.
Evtl. ist es ja möglich, das Sensor lesen in einen Interrupt zu stopfen.
Aber das "Wie?" kann ich nicht sagen, denn dafür kenne ich mich mit SAMD nicht gut genug aus.
habe jetzt alle file.print zu Serial.print geändert. Es funktioniert alles vll 1-3 ms schneller.
@combie Ah okay, verstehe jetzt was du meinst.
Ich habe auch noch gelesen, dass binär Speichern ein wenig was bringen könnte. Hab jedoch nicht gleich damit angefangen, in der Hoffnung, dass es vll etwas anderes gibt.
Werde es dann vll noch binär probieren oder einfach damit leben.
@noiasca meinst du damit, dass ich statt file.print immer gleich flushen soll?
Das Problem dabei wäre, dass das, dass das trotzdem nicht schneller geht, da das flushen ~6ms dauert und dann dazu noch die Zeit für die Loop kommt.
In der Abbildung oben flushe ich alle 100 ms, also alle 10 samples. Doch intsgesamt ist die Aufnahmefrequenz leider ein wenig schlechter weil nun 1 Spike alle 10 samples ist und einer alle 8.
Vor allem die "Spikes" sind weg, nehme ich mal an.
Aus deiner Beobachtung ("alle 8 Zyklen") schätze ich mal, dass du jedesmal ca. 64 byte schreibst, was etwa auch die Größe des Seriellen Puffers darstellt, also eventuell auch eine unnötige Bremse...
Sieh zu, dass du da in jedem Zyklus weniger als den Puffer vollschreibst, und die Baudrate natürlich größer als die Zykluszeit machst (z.B. 50 char / 10 ms braucht mindestens 57600)
das flush wirkt blockierend. Du müßtest bei Bedarf anderweitig feststellen das alle Daten geschrieben wurden. Das flush benötigt man in der Regel nur wenn man die Datei schließen möchte.
wie Micha schon schrieb, erhöhe die Baudrate zum auf mindestens 250k, sonst verfälschst du dir die Messung durch serielle Ausgaben. Noch besser wäre es du hättest einen Datalogger, wenn ja, schalte einen Pin ein/aus über die zu messenden Code Bereiche und schaue im Datalogger wie lang diese dauern.
Wenn du permanent ohne Pause messen und speichern möchtest, dann musst du erstmal wissen wie lange das Schreiben mit flush/close überhaupt dauert. Dir hilft es wenig nur in den Buffer zu schreiben ohne zu wissen ob der voll wird oder nicht. Wenn voll kommt es zu unerwünschten Blockaden. Ich meine egal wie groß der Buffer ist, irgendwann ist jeder Buffer voll wenn die Daten nicht schnell genug rausgehen oder man ihnen keine Zeit gibt. Die Größe des Buffers verzögert nur die Probleme.
Was auch interessant wäre wie lange das auslesen des Sensors dauert. Ich meine das er aller 10ms neue Daten liefern kann ist das Eine. Seine Auslesezeit geht einem aber schon für andere Dinge verloren. Die Differenz wäre dann die Zeit die man selbst noch verplempern kann.
Ich würde dann im schlimmsten Fall die Samplefrequenz an die gesicherte Schreibdauer anpassen.
Danke für alle Antworten.
Ich glaube ich werde mit dem Spike der alle 8 samples auftritt leben. Mit dem flush alle 10 Sekunden funktioniert das ganz gut mit annähernd den 100hz. Der Sensor dürfte schon mit 100 Hz liefern, da sich die Werte immer verändern.
Ich hätte noch kurz eine andere Frage.
Und zwar bin ich mir nicht sicher welchen Operator ich verwenden soll.
Der Sensor muss immer Kalibriert werden. Dafür gibt es
Mich interessiert die "System" Kalibrierung. Diese kann zwischen 0-3 liegen.
Ich hätte gerne, dass im Setup darauf gewartet wird, dass system einmal 3 erreicht, LED blinkt und dann erst mit dem Rest weiter macht.
Hab daweil das:
uint8_t system, gyro, accel, mag = 0;
bno.getCalibration(&system, &gyro, &accel, &mag);
if (system = 3) {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000);
Serial.print("calibrated");