Loop braucht manchmal 7ms und manchmal auch 14ms

Hallo liebe Community!

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.

struct bno055_t myBNO;
imu::Quaternion quat;
imu::Vector<3> acc;
imu::Vector<3> gyr;

Das hier meine ich.

Meine Frage ist: Weiß jemand warum es manchmal zu diesen Verzögerungen kommt? Ich würde gerne durchgehend 100Hz haben.

#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();

    counter = counter + 1;
    storeData();
  }
}

void storeData() {

  stopWatch(); //starts stopwatch
  /*___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());

  stopWatch(); //stopps stopwatch

  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;
  }
}

Beste Grüße und einen schönen Tag
xeonus

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.

  • Wenn mal ein Sektor geschrieben wird.
  • Wenn die FAT manipuliert wird.
  • Für das “wear leveling”

Hier ist eine Abbildung vom Serial Monitor.

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?

Ich tippe auch auf SD, ohne die anderen Libraries, oder den MKR Zero zu kennen.
Ersetze mal zum Test die file.print durch dummies…

@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.

Danke und LG
clemens

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.

1 Like

Danke für die raschen Antworten!

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.

Danke euch allen! Top Hilfe :slight_smile:

wenn die Spikes stören, eventuell mit flush dafür sorgen, dass wirklich immer geschrieben wird.

@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.

LG

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)

Hallo,

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.

Das close beinhaltet doch quasi ein flush.

Gruß Tommy

Hallo,

stimmt, dass wird darin mit aufgerufen.

Hat schon mal jemand an das Beispiel NonBlockingWrite gedacht?

Hallo,

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.

1 Like

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.

LG
xeonus

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

uint8_t system, gyro, accel, mag = 0;
  bno.getCalibration(&system, &gyro, &accel, &mag);

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");

Steht im Void Setup

Vielen Dank schon mal :slight_smile:

Hast du die Warnungen aktiviert?
Nein!
Dann machen, denn das Statement tut sicher nicht das, was du willst.

Ich weiß leider nicht was Warnungen sind. Compilen geht normal.