Poisson-Test-Zählimpulsgenerator

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

⏪ vorheriges - nächstes ⏩

opengeiger.de

Klar, über ein Gefährdungspotential sagt die Impulsrate so gut wie nichts, solange man nicht weiß, was die Ursache ist. Umgekehrt, wenn ich nichts über die Quelle weiß, und mein Zähler kreischt mich so an, dann zuck ich doch zusammen und geh lieber mal auf Abstand.

Ja, ich werd mal versuchen mit nem neueren, schnelleren uC noch etwas genauer zu werden (Totzeit), und mit der Rate noch etwas höher zu kommen. Gerade wenn man ne Lösung zum Testen sucht, sollte man ja ne Größenordnung besser sein als das Testobjekt selbst.

etalon

Zitat von: opengeiger.de am 08. Dezember 2023, 15:48Klar, über ein Gefährdungspotential sagt die Impulsrate so gut wie nichts, solange man nicht weiß, was die Ursache ist.
...

Die Ursache kennt man ja idR vorher nie (außer man weiß genau, was man sucht). Man muss also ein geeignetes Messgerät vorhalten, um auch ein Gefährdungspotential ableiten zu können.

Zitat von: opengeiger.de am 08. Dezember 2023, 15:48...
Umgekehrt, wenn ich nichts über die Quelle weiß, und mein Zähler kreischt mich so an, dann zuck ich doch zusammen und geh lieber mal auf Abstand.
...

Da hast du nen Punkt!  :good3:
Wenn man weder über sein Messgerät etwas weiß, noch über die Quelle, dann muss man erst mal Vorsicht walten lassen und Abstand halten, wenn es an einem Ort mehr tickert als an allen anderen Orten (natürlich mit dem selben Messgerät gemessen). Das ist dann allerdings der denkbar schlechteste Fall...  :D

opengeiger.de

#17
Kleiner Update für den Arduino Due (Atmel AT91SAM3X8E 84 MHz Cortex®-M3 ARM-Core):
Der Code läuft problemlos, nur die IOs haben jetzt 3.3V und das Board kostet das Doppelte gegenüber dem Uno.

Ich habe den Code ein klein wenig verändert, weil der Due nun 2^31 als maximale long integer zulässt, die man nach double casten kann. Damit wird die Auflösung der Delays bei hohen Impulsraten ein wenig besser. Man kommt mit dem Due runter auf eine Totzeit von 130us (7.692kHz) und damit kann man auch noch 10kcps geradeso hinbekommen. Ich habs mal gemacht und das Histogramm aus etwa 400k Delaywerten erzeugt. Ja, passt noch, aber man sieht leichte Artefakte. Man müsste nun also wirklich einen eigene delayNanoseconds() Routine schreiben, die dann eine feinere Quantisierung der Delaywerte zulässt.



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

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


Aber jetzt muss ich erstmal schauen, ob ich nen Zähler programmiert bekomme, der eine Impulsrate mit 10kcps über ne Interruptroutine korrekt ausgezählt bekommt. Ich denk mal da wird auch STM32 Prozessor, wie er im Radiacode verbaut ist, etwas ins Schwitzen geraten. Der Atomfast gibt sogar 10kcps als max countrate explizit an.

Edited: delayMicroseconds(negEx); hinzugefügt, damit's klarer ist

NuclearPhoenix

Zitat von: opengeiger.de am 08. Dezember 2023, 18:56Man müsste nun also wirklich einen eigene delayNanoseconds() Routine schreiben, die dann eine feinere Quantisierung der Delaywerte zulässt.
Hast du zufällig einen Pi Pico da? Da weiß ich nämlich, dass du bei dem die Anzahl der Prozessorzyklen als Zeitwert hernehmen kannst -- das erlaubt grob 10ns Zeitauflösung. Natürlich schneller, wenn du den übertaktest (bis zu 250 MHz, also ca 4ns, meiner Erfahrung nach kein Problem). Aber so genau habe ich mit dem Feature auch noch nicht herumgespielt.

Mit welchem Sketch genau generierst du denn die Plots? Unkommentierst du einfach die paar kommentierten Stellen? Sorry, falls du das eh erklärt hast und ich was überlesen habe :blush:

opengeiger.de

Nee, mit dem Pico hab ich bisher noch nichts gemacht, hab nur ein paar von den größeren Raspi's. Aber ich dnek das geht, wenn man ein oder zwei Dummy Instructionen in ner Schleife nutzt und mit dem Schleifenparameter dann das Delay einstellt. Man muss das halt mal rauskalibrieren, das ist etwas Mühe, aber wenn mal es mal hat, dann hat man's. Ich seh die CHallenge aber eher auf der andern Seite. Ich schätze das Interrupthandling beim Zählen kostet auch ganz schön Zeit und das könnte in so einem Zweierpaar Generator-Zähler schneller der limitierende Faktor sein. Das werde ich mir demnächst mal anschauen.

Und ja, wenn ich ne Instruktion ein- oder ausschalten will, dann kommentier ich die Zeile halt aus.

Die Auswertung für die Statistik hab ich unter Matlab geschrieben, das läuft aber auch unter der Freeware Octave genauso. Das ist allerdings ein schneller Hack und sieht für die 10kcps Impulsrate so aus:

clear;
close('all');
infid = fopen('arduOutput6.txt');
lambda = 0.01;
x = fscanf(infid,'%f')';
nSamp=length(x);

nClass=100;
[Hx, xout] = hist(x,nClass);
dx=xout(2)-xout(1);
plot(xout, Hx/dx/nSamp, '-');
hold on;
x=0:1:1000;
plot(x, lambda*exp(-lambda*x),'r--');
hold off;
title('histgram negEx');
xlabel('negEx value');
ylabel('probability'); 

Ich benutze also die histogramm Funktion hist() in blau und plotte dann in rot gestrichelt die theoretische Verteilungsdichtefunktion drüber. Wenn da dann was nicht stimmt, dann läuft das ziemlich schnell aus dem Ruder. Ich hab den Generator nochmal ne Weile laufen lassen, das waren dann 445181 Impulse, dafür habe ich diese Auswertung nochmal gemacht, jetzt sieht das etwas besser aus. Ich denke die Statistik der Impulsabstände ist recht gut so. Allerdings muss man eben sehen, dass da immer noch die Totzeit von 130us draufkommt auf diese Abstände. Ein Zählrohr hat zwar auch immer so 100-200us Totzeit, aber schön wärs halt wenn man die Totzeit eben auch mal kleiner stellen könnte zu Testzwecken, denn das erzeugt ja den Hauptstress für den Controller. Aber wie schon gesagt, jetzt schau mer mal was so ne uC-Zählroutine mit Interrupt überhaupt auf nem Standard-uC so best case hinbekommt. Da über 1kcps zu kommen ist vermutlich schon sportlich, wenn man nicht gerade ne Menge Geld für so nen Cortex M7 Prozessor ausgeben will. Weiss jemand, welchen STM32 der Radiacode genau drin hat?

Ich hänge auchmal die 400k Impulsabstände (arduOutput6.zip) an, falls jemand das Octave-Script ausprobieren möchte.


opengeiger.de

Da ham wir schon den Dreck!  >:(

Ich hab nun mal nen Minimal Impulszähler auf den Arduino Due geladen, in der Hoffnung der 84MHz ARM Core ist schnell genug, und hab ihn mit nem Poisson-Generator auf nem Arduino MKR WAN (Cortex M0 mit 16MHz, Totzeit 426us) mit verschiedenen Zählraten befeuert und was is? Auch der Due verschluckt ab 500cps sichtbar Pulse  :o .

Hier das Ergebnis:
Sie dürfen in diesem Board keine Dateianhänge sehen.

Tja, doch besser mal nachprüfen, besonders wenn man Szintillationszähler baut!

Hier die Arduino Codes:

Arduino MKR (Generator)

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

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

Arduino Due (Impulszähler)

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

Hat jemand ne Idee, wie man so nem Arduino noch etwas die Sporen geben könnte? Es sieht mir ja nicht so arg danach aus, dass es am Prozessor liegt, nachdem der einfache 16MHz Uno in etwa dasselbe Verhalten zeigt. Ich könnt mir eher denken, dass die Arduino IDE da was nicht so ganz Optimales tut.  :(

DG0MG

Zitat von: opengeiger.de am 09. Dezember 2023, 07:35Hat jemand ne Idee, wie man so nem Arduino noch etwas die Sporen geben könnte? Es sieht mir ja nicht so arg danach aus, dass es am Prozessor liegt

Ich halte den Loop-Bereich für .. ungünstig programmiert (ohne dass ich viel Ahnung von Arduino-C habe).

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


  • Wenn Du für einen Zeitraum X (egal, wie klein der ist) Interrupterzeugung komplett abschaltest, ist es nur logisch, dass Impulse verloren gehen müssen, mit umso größerer Wahrscheinlichkeit, je höher die Impulsdichte ist. Interrupts verbietet man ja nur so kurz wie möglich, schaltet sie aber nicht komplett ab. Die Interruptroutine wird dann nachgeholt, sobald sie wieder erlaubt werden. Evtl. ist das die Lösung: https://www.arduino.cc/reference/de/language/functions/interrupts/nointerrupts/
    Beachte auch, dass Du die serielle Ausgabe (die dauert!!) und Fließkomma-Operationen innerhalb der abgeschaltenen Interrupts machst, das ist gänzlich böse.
  • Statt if (counter == MAXCNT) würde ich etwas wie if (counter >= MAXCNT) schreiben, für den Fall, dass ausgerechnet beim Wert 100 die Schleife ein Problem hat. Demzufolge könnte man dann auch statt counter = 0; eher counter = counter-100; schreiben, das ist aber eine zusätzliche Rechenoperation, zugegeben.
  • Da Du den Zeitzähler mit time = millis(); schonmal in eine Variable überführt hast, würde ich das nicht ein paar Programmzeilen später nochmals tun (oldTime = millis();), weil sich der Milliwert inzwischen geändert haben kann. Stattdessen einfach: oldTime = time;

Letztendlich also etwa so, ohne dass ich weiß, ob die Syntax richtig ist und ob das geht:

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



"Bling!": Irgendjemand Egales hat irgendetwas Egales getan! Schnell hingucken!

opengeiger.de

Zum Ab-und Anschalten des Interrupthandlings:
Es ist ja so, dass das Verschlucken von Zählpulsen nur dann entscheidend ist, wenn tatsächlich auch gezählt wird. Da ich hier wegen der Konstanz der Statistik mit einer Impulsvorwahl arbeite (im Gegensatz zur Zeitvorwahl) dann bedeutet ja das Kriterium if (counter == MAXCNT) dass nun eine Messung fertig ist, das heisst auch, dass dann nicht weitergezählt werden muss, sondern eine Auffrischung der Anzeige stattfindet. In der Zeit ist es schon sinnvoll das Interrupthandling abzuschalten, denn sonst häufen sich ja die anstehenden Interrupts in der Queue und erzeugen einen Fehler in der nächsten Zählung. Wenn dagegen das Kriterium if (counter == MAXCNT) nicht erreicht  ist, dann wird ja alles in diesem Block nicht durchlaufen und spielt keinerlei Rolle für die Ausführungsdauer der Schleife. Das einzige was Zeit kostet ist die Zählerstandsabfrage im if statement.

Ob if (counter >= MAXCNT) schneller ist, das kann ich leicht austesten, das kommt natürlich auf den Compiler an.

oldTime = time könnte in der Tat etwas schneller sein als oldTime = millis(), da es ja kein Funktionsaufruf ist, das test ich auch!

Danke in jedem Fall für die Ideen! :good2:


Xodor

Probiere doch mal einen ESP32, der sollte deutlich mehr INTs mit seinen 240MHz pro Sekunde schaffen als ein AVR.

NuclearPhoenix

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

Xodor

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.

Vielleicht die Schnittstelle mal mit 115200 initialisieren anstatt mit 9600?

NuclearPhoenix

Zitat von: Xodor am 09. Dezember 2023, 13:25Vielleicht die Schnittstelle mal mit 115200 initialisieren anstatt mit 9600?
Ja das hilft vielleicht ein wenig. Oft ändert der Wert aber nix, wenn man den USB-Serial Output am Mikrocontroller verwendet und nicht irgendwelche HW UART Pins. Aber Arduino Serial ist immer recht langsam, selbst bei hohen baud rates, damit muss man einfach umgehen. Und bei zeitkritischen Sachen am besten gar kein Serial benutzen.

Xodor

Oder vielleicht anstatt das Ergebnis per UART sofort auszugeben, es erst in einem Array speichern und je nach Größe des RAMs vom Controller erst nach 100-200 Datensätzen das Ergebnis über den UART ausgeben.

NuclearPhoenix

Soooo, ich habe jetzt mal den letzten Sketch hier auf einen Pico (mit -O3) hochgeladen und den noch dazu auf 250MHz übertaktet. Der Sketch schaut so aus:

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

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

Das habe ich dann bei verschiedenen Zählraten in meinen Detektor reingeschossen und so sehen meine Ergebnisse aus:
Sie dürfen in diesem Board keine Dateianhänge sehen.

Ich habe ebenfalls einen Pico am Detektor, der läuft aber auf den standardmäßigen 133MHz. Die zwei verschiedenen Modi die ich getestet habe sind einerseits der normale Spektrometerbetriebsmodus in dem auch die Energie gemessen wird (also der ADC benutzt wird) und ein "Geiger" Modus der nur Pulse zählt. Der Geigermodus hat natürlich eine deutlich kürzere Totzeit als der Energiemodus. Leider hat es sich herausgestellt, dass es am Ende gar nicht so viel Unterschied in den erreichbaren Zählraten macht wie ich es gerne gehabt hätte.

Im Energiemodus hat aber die einfache Totzeitkorrektur für das Display super funktioniert. Selbst bei 50kcps hat er mir so um die 55kcps am Display angezeigt. Die Ergebnisse im Plot oben sind nicht totzeitkorrigiert, also das macht schon einen riesen Unterschied, auch wenn es nicht super genau ist. Ich habe dann noch 100kcps probiert, aber ich glaube da macht der Generator dann auch schon echt einen Abgang. Totzeitkorrigiert konnte ich da ca. 85kcps am Detektor ablesen, der auch schon ein wenig am Ende war ;D

Also ein Pico funktioniert für sowas dementsprechend echt super, kann mich nicht beschweren. Danke nochmal für den Generator und die Ideen ;)

NuclearPhoenix

Zitat von: Xodor am 09. Dezember 2023, 14:30Oder vielleicht anstatt das Ergebnis per UART sofort auszugeben, es erst in einem Array speichern und je nach Größe des RAMs vom Controller erst nach 100-200 Datensätzen das Ergebnis über den UART ausgeben.
Ja genau, so mach ich das immer. Vor allem wenn man zwei Kerne hat kann der andere Kern dann einfach in einem Loop hin und wieder mal das Array ausgeben. Ich glaube das ist der beste Weg für sowas. :)