Poisson-Test-Zählimpulsgenerator

Begonnen von opengeiger.de, 06. Dezember 2023, 10:27

⏪ vorheriges - nächstes ⏩

Henri

Zitat von: DG0MG am 09. Dezember 2023, 09:02Ich halte den Loop-Bereich für .. ungünstig programmiert (ohne dass ich viel Ahnung von Arduino-C habe).


Denke ich auch.

Falls möglich, besteht die ISR nur aus einem Zähler, der inkrementiert wird, und alles andere wird draußen berechnet.

Außerdem braucht es kein attach/detach interrupt in der ISR, weil das automatisch passiert.

Und dann blockiert man mit der seriellen Ausgabe ja noch timer. Die sind priorisiert, dh. sie werden dann nacheinander bearbeitet und da geht seriell ganz klar vor Impulse zählen, weil sonst die Kommunikation nicht mehr klappen würde, wenn sie ständig von einem Zählimpuls unterbrochen würde. Das ist z.B. auch der Grund, weshalb man bei schnellen Zählraten das Impulse-Knacken in der Audio-Ausgabe hört, wenn man für diese tone() verwendet. Denn tone() basiert auf PWM, verwendet hierfür einen timer, und der hat eine geringere Priorität als der zum Impulse zählen verwendete.

Bei meinem GPS Datenlogger gehen z.B. richtig viele Impulse verloren, weil jede Sekunde die Datagramme vom GPS-Modul empfangen werden müssen. Da müsste man das Zählen eigentlich auf eine zweite CPU auslagern.

Ich habe mir zu dem Thema mal vor einiger Zeit folgende Notizen gemacht:

http://gammon.com.au/interrupts


When writing an Interrupt Service Routine (ISR):


    Keep it short
    Don't use delay ()
    Don't do serial prints
    Make variables shared with the main code volatile
    Variables shared with main code may need to be protected by "critical sections" (see below)
    Don't try to turn interrupts off or on


INT0 hat Priorität vor INT1 vor INT2.
tone() verwendet INT2 und wird somit von einem INT0 unterbrochen.


When an ISR is entered, interrupts are disabled. Naturally they must have been enabled in the first place, otherwise the ISR would not be entered. However to avoid having an ISR itself be interrupted, the processor turns interrupts off.

When an ISR exits, then interrupts are enabled again. The compiler also generates code inside an ISR to save registers and status flags, so that whatever you were doing when the interrupt occurred will not be affected.


==> Also ist ein detachInterrupt(), ..., attachInterrupt() nicht notwendig.


Dauer eines External Interrupts:

I count 82 cycles there (5.125 µS in total at 16 MHz) as overhead plus whatever is actually done in the supplied interrupt routine. That is, 2.9375 µS before entering your interrupt handler, and another 2.1875 µS after it returns.

opengeiger.de

Zitat von: NuclearPhoenix am 09. Dezember 2023, 13:04Ich bin mir sicher, dass das langsamste in dem Loop das Serial.println() ist. Alles andere wird dagegen wahrscheinlich nur kleine Auswirkungen haben... am ehesten wohl noch die float Berechnungen.

Jetzt mach ich doch nochmal einen Versuch die Arduino Zählroutine zu erklären. Ich habe den Eindruck, dass das nicht recht verstanden wurde. Alles was zwischen
if (counter==MAXCNT) {
...
}
steht spielt für das Problem des Impulsverlusts keine Rolle. Der Zähler ist nur zählbereit, solange das Kriterium counter==MAXCNT nicht erfüllt ist. Solange der Zähler zählbereit ist, wird dieser Block völlig ignoriert. Die Idee ist, eine bestimmte Anzahl an Zählimpulsen abzuwarten und dafür die Zeit zu messen. Die Zählrate ist dann die vorgegebene Anzahl an Zählimpulsen dividiert durch die gemessene Zeit. Die Programmausführung kreiselt in der loop solange die vorgegebene Anzahl nicht erreicht ist. Dabei wird der if-Block aber NICHT ausgeführt. Sobald ein Zählimpuls eintrifft, springt die Programmausführung in die Interrupthandler Routine count() , dort wird die globale Variable counter hochgezählt. Wenn dann die Anzahl MAXCNT erreicht ist, wird das Interrupthandling abgeschaltet und die Auswertung der Zeitmessung gemacht. Alle Impulse die in der Zeit eintreffen werden ignoriert und das beeinflusst auch nicht das Zählergebnis, solange MAXCNT nicht gerade einstellig ist. Wenn die Berechnungen erledigt sind und die Daten ausgegeben sind, ist der if-Block fertig und das Interrupthandling wird wieder angeschaltet. Es geht also auch nicht darum dass die Interrupts in der Count() Routine disabled sind, sondern nur in dem if-Block.

Daher hat Serial.println() zum Beispiel gar keinen Einfluss auf das Ergebnis, wenn es angeschaltet wird. Man kann in den if-Block daher reinschreiben was man will, auf die Messung hat das keinen Einfluss außer das man länger auf das Ergebnis warten muss. Ich hab das gerade nochmal mit einem zusätzlichen delay(1000) getestet.

#define MAXCNT 100
volatile int counter = 0;
unsigned long oldTime = 0;

void count()
{
  counter++;
}

void setup(){
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(2), count, FALLING);
}

void loop(){
  unsigned long time;
  unsigned long dt;
  float rate;
  if (counter == MAXCNT) {
    detachInterrupt(digitalPinToInterrupt(2));
    time = millis();
    dt = time-oldTime;
    rate = (float)MAXCNT*1000.0/(float)dt;
    Serial.println(round(rate));
    oldTime = millis();
    counter = 0;
    attachInterrupt(digitalPinToInterrupt(2), count, FALLING);
  }
}

opengeiger.de

Zitat von: NuclearPhoenix am 09. Dezember 2023, 14:36Also ein Pico funktioniert für sowas dementsprechend echt super, kann mich nicht beschweren. Danke nochmal für den Generator und die Ideen

Ja, das Pico Ergebnis sieht beeindruckend aus. Mit welcher IDE arbeitest Du da? Wie sah Deine Zählroutine aus?

NuclearPhoenix

Zitat von: opengeiger.de am 09. Dezember 2023, 19:03
Zitat von: NuclearPhoenix am 09. Dezember 2023, 14:36Also ein Pico funktioniert für sowas dementsprechend echt super, kann mich nicht beschweren. Danke nochmal für den Generator und die Ideen

Ja, das Pico Ergebnis sieht beeindruckend aus. Mit welcher IDE arbeitest Du da? Wie sah Deine Zählroutine aus?

Ich verwende nur die Arduino IDE und dazu das arduino-pico Package für die HW-Unterstützung (https://github.com/earlephilhower/arduino-pico). Da baut halt vieles auf der Pico C SDK auf, daher werden dort manche Sachen fundamental anders umgesetzt sein, als in den wirklichen offiziellen Arduino Urgesteinen. Möglicherweise ist auch der Code an manchen Stellen effizienter umgesetzt und daher funktioniert es so gut. Abgesehen von den Unterschieden in CPU Frequenz und Architektur zu den Arduino Dingern natürlich.

Die Zählroutine ist ganz schön riesig, zu groß um das hier reinzuposten. Da passiert auch vieles neben dem eigentlichen Zählen, die Totzeit liegt aber immer so bei um die 25µs, ist also nicht allzu wild. Du kannst gerne alles auf GitHub ganz genau nachlesen. Hier ist mal ein Link zum Anfang der ISR: https://github.com/OpenGammaProject/Open-Gamma-Detector/blob/main/software/ogd_pico/ogd_pico.ino#L1038

Für einen sauberen Test für den Generator hätte ich sowieso eigentlich einen frischen Pico nehmen sollen, der sonst nichts zu tun hat. Aber ich wollte es halt gleich in der Praxis am ganzen Detektorsystem testen. Also ich kann im Moment nicht sagen, ob die Elektronik da nicht auch noch ein bisschen was verlangsamt. Aber es sollte schon zum absoluten Großteil der Pico selbst sein :)

opengeiger.de

#34
@NuclearPhoenix :Vielen Dank für die Info's! Ja, das Arduino Urgestein ist vielleicht nicht mehr so das Beste! ;)

Ich hab jetzt auch mal einen Shootout gemacht, DG0MGs Vorschlag gegen meine Version. DG0MGs Vorschlag für die Loop war der:

void loop(){
  unsigned long time;
  unsigned long dt;
  float rate;
  if (counter >= MAXCNT) {
    noInterrupts();
    time = millis();
    counter = 0;
    interrupts();

    dt = time-oldTime;
    oldTime = time;
    rate = (float)MAXCNT*1000.0/(float)dt;
    Serial.println(round(rate));
  }
}

DG0MGs Code lässt sich fehlerfrei übersetzen und das Ergebnis ist: DG0MG hat knapp "gewonnen" mit etwa 1cps "Vorsprung" (bei 1000cps Sollrate)!!!  :D

Leider bringt das die Kuh noch nicht vom Eis. Ich denke, ich werd mich da eher mal mit dem Pico befassen! Ja und ich denke, mit billigen uCs könnte der kommerzielle Ansatz auch eine Zählratenkalibrierung mit anschliessender Kompensation in der Anwendung sein. Die sichtbare Nichtlinearität durchs Verschlucken der Zählpulse ist smooth genug, das könnte man auch rechnerisch korrigieren, wenn man eine entsprechende Kalibriermessung gemacht hat.

Henri

Zitat von: opengeiger.de am 09. Dezember 2023, 18:54Jetzt mach ich doch nochmal einen Versuch die Arduino Zählroutine zu erklären. Ich habe den Eindruck, dass das nicht recht verstanden wurde.

Stimmt, Du nimmst im Prinzip immer nur Stichproben mit definierter statistischer Genauigkeit und wertest nicht kontinuierlich aus. Zwischen den Stichproben machst Du dann die Berechnungen und sendest das Ergebnis über Seriell.

Ich habe das bislang so gemacht, dass ich den Inhalt der Variable "counter" in eine andere Variable geschrieben und counter sowie MAXCNT zurückgesetzt und dann mit der anderen Variable gerechnet habe. Somit ändert sich der Variableninhalt für das abgelaufene Messintervall während der Berechnung dann auch nicht mehr, während counter schon wieder Impulse sammeln kann.


Das == ist aber etwas riskant, denn wenn count sich zufällig so schnell inkrementiert, dass die genaue Schwelle übersprungen wird, ohne dass in dieser Zeit loop den Zählerstand auswertet, dann bleibt Dein Programm hängen. Da ist das >= von DG0MG sicherer.

Viele Grüße!

Henri

opengeiger.de

So nun hab ich mal ein Arduino-Flagschiff aufgetrieben, einen Portenta H7 Lite. Das Board hat nen ST STM32H747XI Dual Core Prozessor drauf, ein Core davon ist ein Arm Cortex-M7 der mit 480MHz taktet, der andere Core ist ein Arm Cortex-M4 mit 240MHz. Wie der Compiler damit umgeht, weiß ich allerdings nicht. Den Code den ich als Poisson-Generator compiliert habe ist der hier:

void setup() {
  Serial.begin(9600);
  pinMode(5,OUTPUT);
  digitalWrite(5,LOW);
}

void loop() {
  double lambda = 0.001; //delayMicroseconds --> 10000cps
  double rnd = random(2147483648)/2147483647.00;
  int negEx = round(-log(1-rnd)/lambda);
  delayMicroseconds(negEx);
  delayMicroseconds(10);
  digitalWrite(5,HIGH);
  digitalWrite(5,LOW);
}

Ohne das delayMicroseconds(10) Statement sehe ich keinerlei Totzeit mehr, allerdings hat der Output High Puls auf Pin 5 keine konstante Dauer und die fallende Flanke weist gegenüber der steigenden Flanke bei dieser Zeitauflösung dann einen deutlichen Jitter auf. Der kleinste Impulsabstand, den ich gemessen habe, waren 8us. Wenn das delayMicroseconds(10) eingebaut ist, entsteht aber eine Totzeit von nur 11us, digitalWrite() wird also irgendwie parallelisiert. Damit kommt man jetzt aber deutlich über 10kcps. Damit bin ich nun zufrieden  :yahoo: .

Das nächste wäre nun ein Impulszähler mit dem Portenta H7. Da muss ich jetzt nur noch klären, wie man den Interrupt aufsetzen muss bei diesem Dual Core Prozessor, das scheint ein größerer Akt zu sein. Die Doku ist noch ziemlich schlecht. Arduino haut ein Board nach dem anderen raus aber dokumentiert wird nur minimal oder man überlässt es den Usern :(  Hat hier jemand Erfahrung mit dem Portenta? Oder ich steig halt doch um auf den Raspi Pico  ;)

Xodor

Kann man nicht sofern die MCUs so etwas haben einen Hardware Zähler benutzen? Die sind viel schneller als IRQs.

Der ESP32 hat zum Beispiel ein PCNT Zähler als Hardware eingebaut mit dem sich Frequenzen bis ca. 40 MHz zählen lassen. 

NuclearPhoenix

Zitat von: opengeiger.de am 11. Dezember 2023, 14:11So nun hab ich mal ein Arduino-Flagschiff aufgetrieben, einen Portenta H7 Lite. Das Board hat nen ST STM32H747XI Dual Core Prozessor drauf, ein Core davon ist ein Arm Cortex-M7 der mit 480MHz taktet, der andere Core ist ein Arm Cortex-M4 mit 240MHz.
Noch nie was davon gehört, das klingt echt schon ziemlich exotisch, vor allem mit dieser krassen Asymmetrie der beiden Kerne. Aber ich hab auf die Schnelle ein Tutorial für den Dual-Core gefunden. Anscheinend programmiert man die beiden Kerne getrennt voneinenander :unknw: https://docs.arduino.cc/tutorials/portenta-h7-lite/dual-core-processing
Komische Herangehensweise, aber trotzdem irgendwie interessant...

Zitat von: Xodor am 11. Dezember 2023, 14:30Kann man nicht sofern die MCUs so etwas haben einen Hardware Zähler benutzen? Die sind viel schneller als IRQs.

Der ESP32 hat zum Beispiel ein PCNT Zähler als Hardware eingebaut mit dem sich Frequenzen bis ca. 40 MHz zählen lassen. 
Ich habe keine Ahnung was die Verzögerung für den Start eines IRQs ist, aber die sind schon echt schnell. Oft ist das doch deutlich einfacher umzusetzen als irgendwelche HW Zähler anzusprechen. Wie ist das denn beim ESP32?

Xodor

ZitatIch habe keine Ahnung was die Verzögerung für den Start eines IRQs ist, aber die sind schon echt schnell. Oft ist das doch deutlich einfacher umzusetzen als irgendwelche HW Zähler anzusprechen. Wie ist das denn beim ESP32?

Bei extrem vielen IRQs bremst es ja deinen Main-Loop aus. Da hat der PCNT den Vorteil, dass hier nichts gebremst wird.

Es gibt ein Projekt, wo jemand mit einem ESP32 einen Frequenzzähler gebaut hat. Dort kann man sich ansehen wie der PCNT verwendet wird.

https://blog.eletrogate.com/esp32-frequencimetro-de-precisao/


NuclearPhoenix

Zitat von: Xodor am 11. Dezember 2023, 15:43Bei extrem vielen IRQs bremst es ja deinen Main-Loop aus. Da hat der PCNT den Vorteil, dass hier nichts gebremst wird.
Das stimmt natürlich, aber bei einem dual-core Prozessor ist das ja eigentlich völlig egal. Zumindest, wenn man damit leben kann einen ganzen Prozessorkern dafür herzugeben :)

Sieht auf jeden Fall sehr kompliziert aus den PCNT zu nutzen ehrlich gesagt. Außerdem sehe ich doch da eine "pcnt_intr_handler" Funktion, d.h. das läuft doch erst wieder am Prozessor? Sobald ich sowieso eine ISR brauche, ist es doch egal wie die ausgelöst wird. Da mach ich mir doch nicht den Aufwand und setze den ganzen PCNT Interrupt in Gang, wenn ich das mit einem ganz einfachen PinChange Interrupt auch haben kann. Vielleicht hat der ESP32 sonst keine Interrupts, die schnell genug wären und deshalb macht man das so? Keine Ahnung, bitte korrigiere mich, wenn ich was nicht verstanden habe...

DL3HRT

Zitat von: NuclearPhoenix am 11. Dezember 2023, 16:33Sieht auf jeden Fall sehr kompliziert aus den PCNT zu nutzen ehrlich gesagt. Außerdem sehe ich doch da eine "pcnt_intr_handler" Funktion, d.h. das läuft doch erst wieder am Prozessor? Sobald ich sowieso eine ISR brauche, ist es doch egal wie die ausgelöst wird.
Natürlich wird ein ISR ausgelöst, aber nicht bei jedem Impuls. Wenn man den PCNT nutzt so legt man fest wie hoch der Hardwarezähler zählen darf, bis ein Interrupt ausgelöst wird. Man kann den Hardwarezähler dann z.B. auf 10000 festlegen und bekommt den Interrupt erst dann, wenn 10000 Impulse gezählt wurden. Oder man koppelt das mit eiem Timer und liest den HW-Zähler beim Auslösen des Timer-Interrupts aus.

PCNT ist nicht kompliziert. Es gibt halt einige Parameter, die man einstellen muss.

Xodor

Richtig, beim PCNT der mit einem Timer von 1000mS gekoppelt ist, bekommst du alle 1000mS einen INT, in dem du einfach den Zählerstand auslesen kannst. Somit bekommst du bei 10000 CPS nur einen INT pro Sekunde anstatt 10000 INTs. Dadurch hat deine MCU alle Zeit der Welt sich mit anderen Dingen wie Berechnungen oder der Ausgabe auf dem Display zu beschäftigen.

NuclearPhoenix

Zitat von: DL3HRT am 11. Dezember 2023, 16:43Natürlich wird ein ISR ausgelöst, aber nicht bei jedem Impuls. Wenn man den PCNT nutzt so legt man fest wie hoch der Hardwarezähler zählen darf, bis ein Interrupt ausgelöst wird. Man kann den Hardwarezähler dann z.B. auf 10000 festlegen und bekommt den Interrupt erst dann, wenn 10000 Impulse gezählt wurden. Oder man koppelt das mit eiem Timer und liest den HW-Zähler beim Auslösen des Timer-Interrupts aus.
Ah okay, ja das macht dann gleich auch viel mehr Sinn :)

opengeiger.de

Zitat von: Xodor am 11. Dezember 2023, 15:43Es gibt ein Projekt, wo jemand mit einem ESP32 einen Frequenzzähler gebaut hat. Dort kann man sich ansehen wie der PCNT verwendet wird.

https://blog.eletrogate.com/esp32-frequencimetro-de-precisao/


Definitiv eine schöne Seite und sehr gut erklärt. Klingt auch überzeugend, könnte man wirklich mal angehen. Man lernt sogar portugiesisch dabei  :) .
Hab noch nie was mit dem ESP gemacht, aber das schreib ich mir jetzt mal auf die Liste! Wenn das am Ende läuft, dann ist wieder Luft für die Scintillationsdetektoren. Mit 40Mcps wird keiner so schnell ankommen  :)) !

 :good2: