Asynchroner ADC

Hallo zusammen,

ich bin seit einiger Zeit dabei die alte Marlin-Version (reprap Firmware für einen Arduino Due) von bobc auf einen aktuellen Stand zu bringen. Und da wollte ich ein neues Feature einbringen.

Mein kleines Problem sieht wie folgt aus.

Ich messe in einem Interrupt mehrere ADC-Eingänge. Dazu starte ich den Messvorgang pro Durchgang für einen ADC und lese ihn im nächsten aus. Das funktioniert soweit bestens.

Bissl pseudocode:

Read_adc(channel) {
  value = adc_get_channel_value(ADC, channel);
  adc_disable_channel(ADC, channel);
  return value
}

Start_adc(channel) {
  adc_enable_channel(ADC, channel);
  adc_start(ADC);
}

ISR_adc() {
switch(status)
  case start_a:
    Start_adc(a);
    status = read_a;
  case read_a:
    Read_adc(a);
    status = start_b;
etc...
}

Jetzt würde ich gerne in einem zweiten Interrupt einen anderen ADC deutlich schneller auslesen. Der erste läuft bei 2kHz, bzw. 2kHz/12 da ich 6 ADCs am laufen habe. Der andere Interrupt läuft zwischen 10kHz und 90kHz und soll nur einen einzigen ADC abfragen. Am liebsten direkt in dem Interrupt.

Wie könnte mein Ansatz sein?

Hoffentlich hast Du die break's in Deinen switch schon eingebaut.

Wo ist das Problem mit einem weiteren (externen?) ADC? Zum Due kann ich allerdings garnichts sagen :-(

Ja, breaks sind da :)

Vielleicht hätte ich mich auch besser ausdrücken können. Ich will keinen weiteren ADC sondern einen weiteren Channel auslesen. Dieser sollte allerdings viel häufiger abgefragt werden.

ISR neu
adc neu
ISR 1
adc a
ISR neu
adc neu
ISR neu
adc neu
ISR 1
adc b
ISR neu
adc neu
...

Irgendwie in diese Richtung soll das gehen.

Die beiden Interrupts stehen dabei in keinem Zusammenhang und laufen jeweils eigenständig.

Ein zweiter Interrupt muß irgendwie mit dem anderen synchronisiert werden, damit er das langsamere Auslesen nicht stört.

Ich würde einen schnellen Interrupt nehmen, und im Handler einmal den schnellen Kanal abfragen, und zwischendrin auch die langsameren Kanäle. Das könnten dann 4 Zustände werden: start-schnell, read-schnell, start-langsam, read-langsam. Mit einer Interrupt-Frequenz f würde dann der schnelle Kanal mit f/4 abgetastet werden, die langsamen mit f/4/n (n=Anzahl langsame Kanäle). Vorausgesetzt f/2 bleibt unter der maximalen Abtastrate des ADC.

Etwas einfacher könnte es werden, wenn der ADC selbst einen weiteren Interrupt auslöst, wenn eine Konvertierung beendet ist und Daten abgeholt werden können.

Das mit den Zuständen wird leider so nicht klappen. Der ‘alte’ Interrupt läuft halt auf 2kHz und der ‘neue’ ist variabel.

Wie schaut es aus wenn ich das ganze im Freerun laufen lassen? Kann man im Freerun nur einen Kanal abfragen und der wird dann auch wieder neu besetzt, oder müssen immer beide Kanäle abgefragt werden?

Quelle

void setup() {
  ADC->ADC_MR |= 0x80; //Freerun adc 6 und 7 (pin A0 und A1)
  ADC->ADC_CR = 2;
  ADC->ADC_CHER=0xC0; // 1<<7|1<<6
}

ISR_1() { // 2kHz timer
  if((ADC->ADC_ISR & 0x80) == 0x80) { // 7 fertig?
    a0 = ADC->ADC_CDR[7];
  }
}

ISR_2() { //variabler timer
  if((ADC-ADC_ISR & 0x40) == 0x40) { //  6 fertig?
    a1 = ADC->ADC_CDR[6];
  }
}

Oder ist das totaler Murks?

Sorry, mit dem Due und seinen Freerun Features kenne ich mich nicht aus. Deine Frage wäre ja besser im Due Forum aufgehoben, aber um Englisch kommt man da wohl nicht herum.

Danke trotzdem. Ich werde mal drüben fragen.

Manchmal sollte man es einfach mal ausprobieren.

Das ganze geht quasi genau so. Und ist sogar einfacher als gedacht.

  • Freerun aktivieren
  • Channels aktivieren die man benötigt
  • Einfach den jeweiligen Channel abfragen

Erst wenn der Channel abgefragt wird, wird ein neuer Wert vom ADC eingelesen.

Aktuell habe ich nur ein etwas merkwürdiges verhalten.

Ich initiallisiere meinen ADC. Das läuft alles rund. Wenn ich die Werte abfrage passt auch alles.

Jetzt habe ich eine externe Variable. Die soll meinen Vergleichswert speicher.

Dazu habe ich in einer Header-Datei:

extern int fsr_basis;

in meiner Initialisierung:

fsr_basis = 0; // reset
for (int i=0; i<16 ;i++) {
  while (!((ADC->ADC_ISR & 0x80) == 0x80)); // warten bis der ADC fertig ist.
  int adc_value = ADC->ADC_CDR[7];
  serial.print(adc_value); // Syntax ist jetzt aus dem Kopf, zeigt aber den entsprechenden ADC richtig an (in meinem Setup um die 2500 ADC läuft auf 12bit).
  fsr_basis += adc_value;
  serial.print(fsr_basis); // und genau hier spukt es. Er zeigt mir hier Wert im 7-stelligen Bereich an. Irgendwas mit >5000000
}
fsr_basis >>= 4;

Was mach ich hier verkehrt? Kann da nen Interrupt das Ergebnis unbrauchbar machen? Muss ich die ggf. in der Schleife unterbinden?

Kann da nen Interrupt das Ergebnis unbrauchbar machen?

Ja…

Die Variable muss “volatile” gemacht werden.
Das auslesen der Variablen muss “ATOMIC” werden.
Siehe:

ah, extern volatile. Ich dachte immer das man das bei extern nicht benötigt. Werde ich gleich mal umsetzen :slight_smile:

Wieder was dazu gelernt. Merci!

wie passt Freerun und Interrupt zusammen?
und while Schleifen mit mehreren Kanälen (und Interrupts) ?

wird dein int fsr_basis von irgendwelchen ISR ( gar mehreren ) verwendet ?

Die while-Schleife ist nur für die Initialisierung und nur für einen ADC-Channel.

Ich habe an meinem Drucker einen FSR installiert. Der unbelastete Wert allerdings wird sich immer wieder ein wenig verändern. Je nach Temperatur etc. Von daher möchte ich bevor mein Drucker die Null-Position anfährt einmal austesten wo der aktuelle Wert steht, um diesen dann später vergleichen zu können. Das ganze läuft außerhalb eines ISR.

Freerun deshalb, weil ich in einem ISR die Temperatur auslese. Das ist relativ langsam (10Hz) oder so, sodass ich dort theoretisch gar nix testen muss. Die while-Schleife ist mehr ein Art Versicherung.

Beim eigentlichen Homing-Prozess wird der fsr_basis dann mit den neu gemessenen Werten verglichen.

Grob so:

if (abs(fsr_basis - adc_aktuell) >= toleranzbereich)) homing_z_fertig();

Hallo,

wenn Du schon einen ISR dafür nutzt, dann würde ich den nur dafür verwenden um die AD Messung anzustoßen. Den Rest dann in der loop. Im einfachsten Fall setzte nur eine Statusvariable auf true im ISR Handler und das wars. Dann weis die Funktion in der loop es darf los gehen. Ich würde nicht den ISR Handler "zu müllen".

Doc_Arduino: wenn Du schon einen ISR dafür nutzt, dann würde ich den nur dafür verwenden um die AD Messung anzustoßen.

Das ja das schöne am Freerunning Mode. Die Messung wird immer selbst neu gestartet. Und ein Interrupt wird ausgelöst wenn die Messung fertig ist. Jedenfalls auf dem AVR. Sollte aber beim ARM eigentlich auch so sein.

Eine andere Option um ADC Messungen ohne Code zu triggern ist über ein Timer.

Hallo,

na dann eben anders herum, Status Variable im ISR zum auslesen.

Danke für die ganzen Anregungen. Ich habe das ganze jetzt komplett aus dem ISR geschmissen und nur noch den letzten Vergleich rein genommen. Das passt schonmal ganz gut. Jetzt fehlt nur noch das böse feintuning...