Interrupt unregelmäßig

Servus,

Habe an meinem Arduino Due ein Can Shield und ein Ethernet Shield (wobei ich hier nur den SD-Slot verwende) montiert. zusätzlich noch ein Display 20x4 I2C,

Ich möchte die Can Nachrichten und Analogwerte regelmäßig und möglichst schnell auf die SD Karte speichern (zb alle 10ms).

Der Interrupt wird jedoch nicht alle 10ms ausgeführt, sondern manchmal auch schneller.

gibt es da Konflikte mit der SPI CLOCK?

Zuerst schreibe ich die Can-Daten in einen Array(Funktion: ReadCan) Diesen Array dann in einen String (Funktion WriteString) und diesen String dann in ein Textfile auf die SD Karte(Funktion : dataFile.println(DataALL); )

Wie kann ich es schaffen, dass er regelmäßig alle 10ms speichert?

Hier ein Teil meines Codes:

void setup()
{
  Serial.begin(115200);
  //init für alles....siehe unten 
  LCD_INIT();
  SD_INIT();
  CAN_INIT(); 
  //Funktion die einen Ordner erstellt von 1 bis 10. je nachdem was es schon gibt.bei neustart wird die datei in einen neuen ordner gespeichert. so werden keine daten verloren. 
  //das Verzeichnis auswählt, je nachdem ob schon dateien in einem ordner sind
  VerzeichnisWahl();
  
  pinMode(23,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(23), CLOSEFILE, FALLING);
  

  datalogfilezahl++;
  // Serial.println("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooop");
  datalogfile = "";//String in dem der name steht vom txt file. wird am anfang gelöscht und bei alle 10s neu gebildet
  datalogfile+= String(StartVerzeichnis);
  datalogfile += String("LOG_");
  datalogfile += String(datalogfilezahl);
  datalogfile += String(".txt");//ich muss jetz noch den string in die zeile sd open einfügen!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  //Sting kann nich länger als 9 zeichen sein, weil sd open braucht ein char und den gibts nur in 9 zeichen

  dataFile = SD.open(datalogfile, FILE_WRITE);//Sd karte wird am anfang geöffnet und alle 10s gechlossen- mehr speed
  Serial.print("opened");

  if(ED==0)//einmal am anfang wird name und einheit ins file geschrieben
  {
    DataName = "Time;AccelerationPosition;FrontWheelPressure;GearPosition;RPM;RearWheelSpeed;FrontWheelSpeed;ActualTorque;Analog0;Analog1;Analog2;Analog3;Analog4;Analog5";
    DataEinheit="[sec];[%];[BAR];[1];[U/sec];[km/h];[km/h];[Nm];[V];[V];[V];[V];[V];[V]";
    dataFile.println(DataName);    
    dataFile.println(DataEinheit); 
    ED=1;
  }
  
  Timer5.attachInterrupt(SAVING).setFrequency(100).start();


}


void loop()
{  

}

//-------------------------------------------------------------------------------------------------------------------END LOOP----------------------------------------------------------------------------------------------------------
void SAVING(void)
{
  zahler++;

 if(zahler<8)//7mal ausgeführt
  {
    ReadCAN();
    //Serial.println("READcan");
    
  }

  else if(zahler<9)
  {
      WriteString();
      //Serial.println("WriteString");
      
  }

   
  else if (zahler==10)
  {
    dataFile.println(DataALL); 
    //Serial.println("Write on SD");
    zahler=0;
  }   



}
void CLOSEFILE(void)
{
  //Timer3.stop();

  dataFile.close();
  Timer5.stop();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Verzeichnis:");
  lcd.setCursor(0,2);
  lcd.print(datalogfile); 


}
//______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________

void WriteString(void)
{
        //Daten aus feld werden in einen String geschrieben,der widerum ins txt file geschrieben wird
      DataInfo[0]=micros();//Zeit wird hier gespeichert, sie steht dann am anfang im txt file //vl auch falsche zeit!????????????????
      DataALL="";
      DataALL+= String(DataInfo[0]);
      DataALL += String(";");
      DataALL+= String(DataInfo[1]);
      DataALL += String(";");
      DataALL+= String(DataInfo[2]);
      DataALL += String(";");
      DataALL+= String(DataInfo[3]);
      DataALL += String(";");
      DataALL+= String(DataInfo[4]);
      DataALL += String(";");
      DataALL+= String(DataInfo[5]);
      DataALL += String(";");
      DataALL+= String(DataInfo[6]);
      DataALL += String(";");
      DataALL+= String(DataInfo[7]);
      DataALL += String(";");
      DataALL+= String(DataInfo[8]);
      DataALL += String(";");
      DataALL+= String(DataInfo[9]);
      DataALL += String(";");
      DataALL+= String(DataInfo[10]);
      DataALL += String(";");
      DataALL+= String(DataInfo[11]);
      DataALL += String(";");
      DataALL+= String(DataInfo[12]);
      DataALL += String(";");
      DataALL+= String(DataInfo[13]);
}

void ReadCAN(void)
{
   if(CAN_MSGAVAIL == CAN.checkReceive())            // check if data coming
   {
    CAN.readMsgBuf(&len, buf);    // read data,  len: data length, buf: data buf
    int canId = CAN.getCanId();
        
    //GEARPOSITION*******************************************************************************************
    if(canId==0x540 && buf[0]==2)
    {    
      GearPosition = buf[3] & 0x007;
      DataInfo[3]=GearPosition;

      //ENGINE-RPM**********************************************************************************************
      
      RPM = buf[1]<<8 | buf[2];
      DataInfo[4]=RPM;
     }
     //ACCELERATIONPOSITION*************************************************************************************
     else if(canId==0x120)
     {
      AccelerationPosition=buf[2];
      DataInfo[1]=AccelerationPosition;
     }
     //FRONTWHEELPREASSURE*************************************************************************************
     else if(canId==0x290)
     {  
      unsigned int FWPRead=(((buf[0]<<8 | buf[1])) & 0xFFF0)>>4;
      double FWP=FWPRead*0.0625; //!!komma zahl 
     DataInfo[2]=FWP;
     }
      
     //ENG-ACTUAL-TORC*****************************************************************************************************
     else if(canId==0x121)
     {  
      unsigned int TorcRead=((buf[0]<<8 | buf[1]));
      double Torc=TorcRead*0.2; //!!komma zahl
     DataInfo[7]=Torc;
     }

     //REARWHEELSPEED*****************************************************************************************************
     else if(canId==0x12B)
     {  
      unsigned int RWSpeedRead=((buf[2]<<8 | buf[3]));
      double RWSpeed=RWSpeedRead*0.05625; //!!komma zahl
      unsigned int FWSpeedRead=((buf[0]<<8 | buf[1]));
      double FWSpeed=FWSpeedRead*0.05625; //!!komma zahl
      DataInfo[5]=RWSpeed;
      
      //FRONTWHEELSPEED*****************************************************************************************************

      DataInfo[6]=FWSpeed;
    
        
      }
      //reads analog value and savve it on sd//ANALOG*******************************************************************

    } 
    
    int i=0;
    for (int analogPin = 0; analogPin < 6; analogPin++) 
    {
      int sensor = analogRead(analogPin);      
      DataInfo[i+8]=sensor;
      i++;
    }
    /*
    Analog0 = analogRead(0);
    DataInfo[8]=Analog0;
    Analog1 = analogRead(1);
    DataInfo[9]=Analog1;
    Analog2 = analogRead(2);
    DataInfo[10]=Analog2;
    Analog3 = analogRead(3);
    DataInfo[11]=Analog3;
    Analog4 = analogRead(4);
    DataInfo[12]=Analog4;
    Analog5 = analogRead(5);
    DataInfo[13]=Analog5;*/
}

Code.txt (14.8 KB)

Welchen Interrupt meinst Du, wodurch wird er ausgelöst?

Ausgaben in einer ISR sind böse, auch ein Close. Für möglichst schnelles Schreiben sollte auch auf String Objekte verzichet werden.

Hallo,

also erst noch mal zum Verständnis:

  • Du hast da ein CAN-Shield am SPI anstatt des eingebauten CAN-Interface vom Due.

  • Mit dem CAN-Shield liest du an einem Bus mit 500000 Bit/s Daten, anscheinend von einem Auto, jedenfalls etwas was viele Daten produziert

  • Am selben (?) SPI hängt auch noch die SD-Karte auf die geschrieben werden soll

  • Dieses Schreiben wird über einen Timer-Interrupt alle 10 ms ausgelöst ?

Wenn das so stimmt, habe ich meine Zweifel, ob das so funktioniert:

Der MCP2515 auf nem CAN-Shield kann maximal 2 CAN-Messages puffern, damit da bei dem schnellen Datenverkehr nichts verloren geht, wird der SPI-Bus weitestgehend mit dem CAN-Transfer beschäftigt sein.

Das man da parallel drüber auch noch in dem Takt auf eine SD-Karte schreiben kann, halte ich für sehr fraglich. Eventuell arbeiten beide Libs auch noch mit unterschiedlichen SPI-Einstellungen und müssen den Bus jedesmal umkonfigurieren.

Wahrscheinlich kommt der Interrupt gar nicht schneller, sondern das ganze stolpert so vor sich hin und versucht irgendwie den Anschluss zu finden.

Schau dir vielleicht erst mal den SPI-Verkehr und den CAN-Verkehr parallel auf einem Logikanalysator an und schau erst mal, was da wirklich abgeht.

Also ich meine den Interrupt der die Funktion SAVING aufruft.

Ich habe das Can shild am due anstatt des eingebauten can interface.

Ja ich lese ein Motorrad aus mit 500kBaud

Auf dem selben SPI Bus hängt auch die SD Karte.

Das schreiben wird über den Timer interrupt ausgelöst.

Aber ich dachte man kann auf den SPI bus mehrere Slaves anhängen. Das mach ich ja mit dem CS Pin von den Bausteinen.

Logicanalyser habe ich leider keinen.

Wenn ich im Interrupt eine Zahl hochlaufen lasse und diese ausgebe, ist die Zeitdifferenz konstant. sobald ich die zeile: dataFile.println(DataALL); einfüge gehts nicht mehr. Aber ich muss das zyklisch gleichmäßig auf die Sd karte schreiben. Was hätte ich für eine Alternative?

Gruß

Natürlich kann man an den SPI-Bus mehrere Slaves hängen. Aber nur mit einem davon gleichzeitig reden.

Schreib mal einen Sketch ohne CAN, der nur eine Zeile Text formatiert und auf die SD-Karte schreibt. Und messe mit millis() oder micros() wie lange das dauert.

Welchen Grund sollte es geben, dass man zyklisch alle 10 ms auf die SD-Karte schreiben "muss". Wer die später liest, dem ist es völlig egal zu welcher Zeit die geschrieben wurde.

Das Shield wird einen Interrupt Pin haben, der anzeigt das neue Daten da sind. Das ist der einzige Interrupt den man haben muss, wenn man mit dem Ding arbeitet. Wenn das Shield neue CAN-Daten anzeigt, dann muss man die sofort abholen. Wenn das gerade nicht geht, weil man gerade "müssen muss", dann sind die CAN-Daten dann eben weg.

Die Lib für den internen CAN des Due legt wahrscheinlich einen Puffer im Arbeitsspeicher an, wo sie die Messages aufhebt, bis das Programm Zeit hat die zu bearbeiten. Damit hat man die Probleme schon gar nicht, die man sich mit dem Shield schafft.

Normalerweise würde man so ein Programm einfach im loop vom Arduino laufen lassen, keine Interrupts, keine Timer, alles über millis steuern. Bei vielen CAN-Interfaces, beim Due weiß ich gerade nicht, haben die CAN-Messages ja Zeitstempel, daran sieht man ja später wann sie gekommen sind, gibt also gar keinen Grund sie zu einem bestimmten Zeitpunkt auf die Karte zu schreiben.

arduinouser099:
Also ich meine den Interrupt der die Funktion SAVING aufruft.

Und wo ist diese Funktion bzw Interruptaufruf in deinem Sketch?
Grüße Uwe

Timer5.attachInterrupt(SAVING).setFrequency(100).start();

hier wird der interrupt 100mal in der sekunde aufgerufen

ich muss das zyklisch gleichmäßig auf die Sd karte schreiben

Nein. Musst du nicht und tust du auch nicht.
Schreiben auf die SD Karte geht "sektor" - weise. Immer und erst wenn ein 512 Byte Block komplett ist, wird der tatsächlich geschrieben. (Bei flush() oder close() natürlich auch) Und das kann echte Zeit dauern.
Dass das überhaupt innerhalb einer ISR geht, ist erstaunlicher als deine Beobachtung unregelmäßiger Zeiten.

Im Prinzip versucht das Programm etwas zu machen, was man mit dem internen CAN vom Due einfach so machen würde.

void loop(){
  CAN_FRAME incoming;

  if (Can0.available() > 0) {
    Can0.read(incoming);

    // incomming formatieren

    // Text auf Karte schreiben

  }

Ich hab gerade nachgeschaut, standardmässig hat die DueCAN Lib einen Speicher für 32 Messages. Solange das Formatieren und Schreiben im Mittel schnell genug ist, so dass der Puffer nicht überläuft, ist es kein Problem wenn mal ein einzelnes Schreiben länger dauert.

War vielleicht schlecht formuliert, aber ich brauche eine fixe Abtastfrequenz vom Can Bus.

Zeitstempel hab ich leider keinen.

Ich öffne das File ganz am Anfang, schreibe dann jede Zeile extra hinein. und am schluss schließe ich das File.

Den internen Can controller kann ich derzeit nicht ausprobieren, da ich noch keinen Transceiver habe damit ich Can high und low einlesen kann..

Das format vom ausgegebenen File sollte dann so sein:

Time;AccelerationPosition;FrontWheelPressure;GearPosition;RPM;RearWheelSpeed;FrontWheelSpeed;ActualTorque;Analog0;Analog1;Analog2;Analog3;Analog4;Analog5
[sec];[%];[BAR];[1];[U/sec];[km/h];[km/h];[Nm];[V];[V];[V];[V];[V];[V]
9524799.00;0.00;0.00;0.00;0.00;0.00;0.00;0.00;522.00;491.00;519.00;537.00;527.00;451.00

Am anfang einmal den Namen, in der 2. Zeile dann die einheit und dann kommen die daten in einer zeile. (und nicht untereinander;)

Die Werte sollen alle unter der gleichen Zeit gespeichert werden. so wie oben. Und die Zeit soll gleichmäßig hinaufgezählt werden.

CAN arbeitet vom Prinzip her ereignisorientiert (Eintreffen einer gültigen Nachricht), da wird nix abgetastet.

Und was soll es helfen mit einem Timer auf die SD-Karte zu schreiben, wenn man sich gleichzeitig nicht um eintreffende CAN-Messages kümmert ? Wie gesagt, das Shield speichert maximal zwei, wenn dreißig gekommen sind, ist available immer noch true und du kriegst beim Lesen die letzten zwei ...

void loop()
{  

  ReadCAN();
  
if(ED1==0)
{  
  Timenow=micros();
  ED1=1;
}
  if(micros()-Timenow>=100000)
  {
    WriteString();
    dataFile.println(DataALL); 
    ED1=0;
  }
}

hab den code nochmal geändert. mache jetz alles im loop.

(Zeitstempel alle 100ms)

jedoch dauert das File.println immer unterschiedlich lang. wie kann ich das ändern?
gruß

jedoch dauert das File.println immer unterschiedlich lang. wie kann ich das ändern?

Gar nicht.

Du könntest aber das Schreiben regelmäßiger triggern:

unsigned long TimeNow; 
void setup() {
...
TimeNow = micros();
}
void loop() { 
  ReadCAN();
  if(micros()-Timenow>=100000) {
    WriteString();
    dataFile.println(DataALL);
    TimeNow += 100000;
  }
}

Ok vielen dank.
mit 100ms funktioniert es jetz. siehe bild 1

jedoch wenn ich es auf 10ms ändere , da ich es ja schneller benötige
kommt wieder müll raus. siehe bild 2

mein code. (habe nur von 100 auf 10 geändert:

void setup() {
...
TimeNow = micros();
}


void loop()
{  
 
  ReadCAN();
  if(micros()-Timenow>=10000)
  {
    WriteString();
    dataFile.println(DataALL); 
    Timenow += 10000;
  }
}

hier die bilder

Was wir da sehen sind Zeiten ?
Wo im Programm werden die erzeugt ?

Wenn man schon mit micros arbeitet, warum weist man damit nicht den empfangen Frames möglichst zeitgenau beim Lesen einen Zeitstempel zu, wenn das Shield keinen eigenen hat ?

Was dieses alle 10 ms mit der SD-Karte müssen müssen zu bedeuten hat bleibt unerklärlich. Ich schau mir das jetzt erst mal nur passiv an und antworte nicht mehr.

Die Zeit wird in der Funktion WriteString ganz am Anfang geschrieben. Nachher schreibe ich die Daten auf die SD Karte. Die Zeitdifferenz zwischen den einzelnen Zeilen stelle ich im Diagramm dar (also die Zeit zwischen den Nachrichten)

Wenn man schon mit micros arbeitet, warum weist man damit nicht den empfangen Frames möglichst zeitgenau beim Lesen einen Zeitstempel zu, wenn das Shield keinen eigenen hat ?

Mache ich. Genau bevor ich die daten auf die sd karte schiebe gebe ich die zeit dem string.

void WriteString(void)
{
        //Daten aus feld werden in einen String geschrieben,der widerum ins txt file geschrieben wird
      DataInfo[0]=micros();//Zeit wird hier gespeichert, sie steht dann am anfang im txt file //vl auch falsche zeit!????????????????
      DataALL="";
      DataALL+= String(DataInfo[0]);
      DataALL += String(";");
...
}

Die 10 ms muss ich nicht speichern, sondern abtasten. Aber nachdem ich die Daten nicht zwischenspeichern kann, muss ich sie gleich speichern. (und das ist dann halt auch alle 10 ms...)

Die 10 ms müssen deshalb sein, da ich das für das format brauche:

Ich schreibe zu jeder Zeit einen ganzen Datensatz:

3807136.00;0.00;0.00;0.00;0.00;0.00;0.00;0.00;151.00;151.00;152.00;151.00;151.00;151.00
Zeit;...Daten.....

Gruß

Hi

Mit 100ms hat Alles geklappt?
Mit 10ms nicht mehr?

Wenn ich mit dem Auto eine Kurve mit 90 'schaffe', versuche ich Das beim nächsten Mal gleich mit 200 - wenn's geklappt hat, hat man sich die ganzen Zwischenversuche gespart.

Bis zu welchem Intervall funktioniert Dein regelmäßiges Speichern?
Schon Mal drüber nachgedacht, daß der ganze Kram, Den Du Da machst, einfach diese Zeit braucht?

Wenn's mit 11ms zu 90% klappt, kann man sich Gedanken machen, wo man optimieren kann, ob man wirklich Alles erst in zig Variablen speichern muß und mittels Funktionen in einen String zusammen zu verwurschteln.

Erkenne erst Mal, wo Du wie viel Zeit verlierst, bevor Du mit Mach3 in den nächsten Brückenpfeiler rein willst.

MfG

Seufz, also noch ein Versuch.

Genau bevor ich die daten auf die sd karte schiebe gebe ich die zeit dem string.

Das ist die falsche Stelle, das hat mit “abtasten” nichts zu tun.

So sieht das mit der Zeit beim CAN-Interface des Due aus

typedef struct
{
  uint32_t id;		// EID if ide set, SID otherwise
  uint32_t fid;		// family ID
  uint8_t rtr;		// Remote Transmission Request
  uint8_t priority;	// Priority but only important for TX frames and then only for special uses.
  uint8_t extended;	// Extended ID flag
  uint16_t time;      // CAN timer value when mailbox message was received. <---------------------- Zeit
  uint8_t length;		// Number of data bytes
  BytesUnion data;	// 64 bits - lots of ways to access it.
} CAN_FRAME;

Da setzt die Hardware die Zeit ein, wo die Message auf dem Bus war.

Wenn die Lib vom Shield das nicht selber macht, kann man sie erweitern und da wo der Struct erzeugt wird ein Feld hinzufügen, wo man die micros dieses Zeitpunkts einsetzt.

Aber nachdem ich die Daten nicht zwischenspeichern kann

Warum sollte man sie nicht zwischenspeichern können. Die CAN-Lib vom Due speichert solche structs, wie oben schon erwähnt auch zwischen.

Die 10 ms müssen deshalb sein, da ich das für das format brauche:

Nö, man fasst beim Speichern einfach die Messages zusammen, deren Empfangstimestamp maximal 10 ms auseinanderliegt.

Wie viel man zwischenspeichert hängt hauptsächlich vom freien Arbeitsspeicher ab, eventuell ist es effizienter in größeren Blöcken zu speichern. Das kann auch von der SD-Lib und der verwendeten Karte abhängen. Da muss man ggf. das richtige Verhältnis zwischen Zwischenspeicher und Blockgröße beim Schreiben experimentell herausfinden. Auf jeden Fall sollte man die CAN-Rohdaten zwischenspeichern, nicht irgendwelche Strings.

Die Arduino Strings sind wahrscheinlich zur Ausgabe inneffizient, die müssen sicher auch noch dynamischen Speicher auf dem Heap anlegen und aufräumen. Das könnte auch die Zeitabweichungen erklären.

Falls sich das gewünschte Dateiformat im laufenden Betrieb nicht schreiben lässt, muss man halt ein effizienteres verwenden und später auf dem Zielrechner (?) konvertieren.

dynamischen Speicher auf dem Heap anlegen und aufräumen. Das könnte auch die Zeitabweichungen erklären.

Generell hast du zwar Recht mit deinen Effizienz-Überlegungen.
Auch kann es natürlich von der SD Karte selbst abhängen, allerdings ist SPI generell um Größenordungen langsamer als was die Karten heutzutage an Geschwindigkeit beim Lesen/Schreiben versprechen.
Da ziemlich viel passiert, bis die Karte einen neuen Sektor an eine bestehende Datei angehängt hat, kann das Schreiben eines Sektors unterschiedlich lang dauern. Und das reine Speichern im SD - Library Puffer auf dem Arduino, wenn ein print gar keine Sektor-Grenze überschreitet, dauert im Vergleich dazu gar keine Zeit, egal ob mit unnötigen String Objekten auf dem Heap oder effizienter.

Seufz

Auch da hast du Recht :wink: