Senden einer sich schnell ändernden Variable über SPI

Ich versuche, den Wert eines Zählers, der in Timer1 (32 Bit) läuft, an einen anderen Chip zu senden, idealerweise über SPI. Der Timer löst alle 1 ms einen Interrupt aus, um den Zähler zu erhöhen. Insgesamt erhöht sich die Variable ständig, aber aus irgendeinem Grund geht die Variable gelegentlich rückwärts.

unsigned long t=0;
void timer1ISR(){
   timerFlagBit=0;//clear interrupt flag
   t++;//increment the counter
}
void sendValue(){
   static unsigned long tempT;
   tempT=t; //save the value to a temporary variable
   Spi_Write(tempT>>16); //most sig byte. I only want a three byte value
   Spi_Write(tempT>>8);  
   Spi_Write(tempT); //least sig byte         
}
void main(){
   init();
   startTimer();
   while(1){
      Delay_us(500);
      sendValue();       
   }
}

Irgendwelche Ratschläge, wie man einen schnell inkrementierenden Wert überträgt?

Hier sind einige Beispiele (ich habe wiederkehrende Werte herausgenommen): Es erhöht sich um einen Schritt von 0 bis 20. Der nächste Wert direkt nach 20 ist 99. Es erhöht sich dann von 99 auf 114 um einen Schritt und dann auf den nächsten Wert ist 95. Nach 95 kommen 96, 97, 98, dann 197, 198, 199, 200, 201. Nach 201 springt es zurück auf 182 und beginnt von 182 bis 196 zu inkrementieren. Nach 196 springt es auf 276.

Also springt es nur ganz am Anfang nach einem Muster, wenn ich alle Werte grafisch darstelle. Das sieht so aus: BildschirmfotoNach kurzer Zeit stabilisiert sich der Wert. Ich habe mit der Priorität mit dem Timer gespielt, aber es hat sich nichts geändert.

Dies geschieht auf einem Microchip Pic.

Können Sie einfach einen Impuls senden und den empfangenden Chip diese zählen lassen? Wie ist hier die Einstellung?
Es verursacht wahrscheinlich nicht dieses spezielle Ergebnis, aber ein Problem besteht darin, dass Sie tempT=t;es in diesem Fall wahrscheinlich als flüchtig deklarieren und Interrupts deaktivieren sollten, damit keine Chance besteht, dass der Interrupt nach der Hälfte des Lesens auftritt. Wie erfassen Sie die Daten für das Diagramm, sind Sie sicher, dass diese Seite der Dinge in Ordnung ist?
Ist es ein 32-, 16- oder 8-Bit-Mikrocontroller?

Antworten (2)

Sieht so aus, als hätten Sie eine Race-Bedingung, bei der der Interrupt zum Erhöhen des Zählers auftritt, während der Wert ausgelesen wird. Die Lösung besteht darin, die Zählervariable als voltatil zu deklarieren und dann den Timer-Interrupt zu deaktivieren, während Sie den Zählerwert in ein temporäres Register kopieren.

Ich dachte, der von mir verwendete Compiler (mikroC) deklariert implizit alle globalen Variablen als flüchtig. Jetzt ist das Problem gelöst. Danke.
@Timtianyang - Ich glaube nicht, dass ein vernünftiger Compiler alle Variablen implizit flüchtig definieren würde. Das würde die verfügbaren Optimierungsmöglichkeiten des Compilers massiv einschränken und wahrscheinlich zu erheblich langsamerem Code führen.

Jetzt wurde die Racebedingung behoben. Etwas anderes, das mit Ihrem Code verbessert werden könnte, ist der Jitter, falls dies für Ihre Anwendung von Bedeutung ist. Der wahrscheinliche Grund dafür ist, dass der Hardware-Timer auf die Hauptoszillatorfrequenz verriegelt ist, während Funktionen wie diese Delay_usnormalerweise als Befehlsschleife implementiert sind, sodass der Timer-Interrupt die Genauigkeit bis zu einem gewissen Grad beeinträchtigt. Irgendwann kommt es zu folgendem Übergang:

  • Ihr 1-ms-Timer-Interrupt wird kurz vorher ausgelöstsendValue()
  • Ihr 1-ms-Timer-Interrupt wird kurz danach ausgelöst sendValue(), und jetzt haben Sie eine zusätzliche Verzögerung von 500 uS.

Wenn Ihr anderer Code das Senden von drei SPI-Bytes aus dem Interrupt tolerieren kann (dh Sie haben keine anderen Interrupts oder Code, der für eine inakzeptable Zeit blockiert würde), können Sie den SPI-Schreibvorgang einfach innerhalb des Interrupts verschieben:

unsigned long tempT = 0;

void timer1ISR(){
   tempT++;
   Spi_Write(tempT>>16);
   Spi_Write(tempT>>8);  
   Spi_Write(tempT);
   timerFlagBit=0;
}

Da die Funktion jedoch Delay_usnur in einer Schleife sitzt, können Sie auch so etwas wie den folgenden Code verwenden, um ein Flag zu erhalten, dass ein neuer Wert verfügbar ist:

volatile unsigned long t = 0;
volatile unsigned char new_val = FALSE;

void timer1ISR(){
   t++;
   new_val = TRUE;
   timerFlagBit=0;
}

void sendValue(){
   static unsigned long tempT;
   disable_ints();
   tempT=t;
   enable_ints();
   Spi_Write(tempT>>16);
   Spi_Write(tempT>>8);  
   Spi_Write(tempT);
}

void main() {
   init();
   startTimer();
   while(1){
      if (new_val) {
         sendValue();       
         new_val = FALSE:
      }
   }
}

Letzteres ist wahrscheinlich eine gute Möglichkeit, wenn Ihre Hauptverarbeitungsschleife andere Dinge erledigt. Wenn es nichts anderes tut und das Senden mehrerer identischer Werte an Ihren SPI-Port kein Problem darstellt, können Sie natürlich auch eine kürzere Verzögerung oder gar keine verwenden.

Ich denke auch darüber nach, den Timer zu deaktivieren, aber ich mache mir Sorgen, ob das Deaktivieren des Interrupts möglicherweise zu einer Verzögerung beim Auslösen des 1-ms-Interrupts führen könnte. Ich verwende dspic33ep bei 140 MHz und die Hauptschleife enthält einige andere Timer-empfindliche Dinge. Timer1 wurde mit der höchsten Priorität konfiguriert.
Ich habe den dspic33ep noch nie verwendet, aber das Deaktivieren des Interrupts (für einen Moment) unterscheidet sich vom Stoppen des Timers auf jeder MCU, die ich verwendet habe. Der Timer zählt weiter, solange die Interrupts wieder aktiviert werden, bevor sie das nächste Mal ausgelöst werden, haben Sie kein Problem.
Guter Anruf, das werde ich ausprobieren.
Wenn Sie den Interrupt deaktivieren, passiert im Grunde, dass ein Timer-Ereignis nicht sofort mit einem Interrupt behandelt wird. Der Timer läuft weiter, es ist nur so, dass das Interrupt-Flag für ein paar Zyklen mehr als normal gesetzt bleibt. Da Sie Interrupts nur für ein paar Zyklen deaktivieren (nur für den Kopiervorgang, nicht für den SPI-Vorgang), sollte diese kleine Verzögerung bei der Verarbeitung des Interrupts keine Probleme verursachen. Wenn der ISR zusätzlich zum Inkrementieren des Zählers zeitkritische E/A ausführt, liegt möglicherweise ein Problem vor.