PMT pulse counting using Teensy 3.2

Status
Not open for further replies.

janjos

New member
Hello,

For the last 20 years I've been programming embedded controllers, 8051 family, Zilog, Rabbit, Atmel, using Basic and C languages and sometimes assembler for time critical pieces of code.
Since more or less 1 year I'm using Arduino (Uno, Mega) boards through the Arduino IDE and AVR Studio.

For a new project of the chemistry department of the University I work for, I need to count TTL level pulses coming from the amplifier of a Photo Multiplier tube. I tried this with the mentioned Arduino boards and arrived to the limit of the power processing of the controllers.
What I did was feed the TTL pulse to an external interrupt pin. In the ISR (INT0_vect) I increment a long unsigned. I can count no more than 250 K pulses per second.
I would like to have a counting error of no more than 0,01%.

I have no experience with ARM processors so the last days I have been reading a lot of information on this forum. I did not find yet what I'm looking for.
My question is, if I use the same principle (external interrupt) using a Teensy 3.2 running at 72 MHz, will I be able to count up to 5 million pulses per second ?
And in the meanwhile send the count value to a remote location using serial communication ?

Thanks for your comments.
Janjos
 
The interrupt solution is most probably not the most reliable and quickest way to go. I’d rather suggest that you look at the source code of the Teensy FreqCount library which uses an internal timer/counter directly without the interrupt overhead and an at least go up to more than 10 MHz.
 
No, it definitely will not count to 5 MHz using interrupts.

I did a quick test just now.

Teensy 3.2 running at 72 MHz can handle up to 1.05 MHz.

Teensy 3.6 running at 180 MHz can handle up to 2.5 MHz.

The key to getting this performance is NVIC_SET_PRIORITY to raise the priority level of the interrupt. Here's the code I tested on both boards.

Code:
volatile unsigned long count=0;
unsigned long prior_count=0;

void pulse() {
  count = count + 1;
}

void setup() {
  pinMode(3, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(3), pulse, RISING);
  NVIC_SET_PRIORITY(IRQ_PORTA, 0);
}

void loop() {
  unsigned long new_count = count;
  if (new_count != prior_count) {
    Serial.println(new_count);
    prior_count = new_count;
    delay(10); // print at reasonable speed
  }
}
 
@Theremingenieur
I did look at the library's source code, I'm still "digesting" it, still trying to understand how the timer/counter registers get set. Thank you for the tip.

@Paul
Thank you for testing this on both Teensy 3.2 and 3.6. The method I was using, the pulse generating an external interrupt on Arduino UNO pin 5, improves with the processing power and speed of the used processor but does not work for higher frequencies.
Thank you also for explaining and testing your method using your library (https://github.com/PaulStoffregen/FreqCount).

I ordered a Teensy 3.2 from your Belgium distributor Velleman but I'm still waiting for it.
In the meanwhile, I did a test using your library in the UNO and it is working up to 6 million pulses per second.

For all others that might have a similar project, I can confirm that what Paul did on the second half of the video, is the way to go.

Janjos
 
For anyone finding this page later, here's a copy of the code used in the 2nd part of the video.

Code:
#include <FreqCount.h>

unsigned long count = 0;
unsigned long prior_count=0;

void setup() {
  FreqCount.begin(100);
}

void loop() {
  if (FreqCount.available()) {
    count = count + FreqCount.read();
    if (count != prior_count) {
      Serial.println(count);
      prior_count = count;
    }
  }
}
 
One might consider this tangentially related, but I see PMTs and pulse counting and get excited. In addition to pulse frequency information, the elapsed time between pulses is useful for certain types of experiments.

Under sufficient reverse bias, a select few LEDs behave like Single Photon Avalanche Detectors (SPADs), albeit with horrible quantum efficiency. I use these LEDs in a didactic environment though, where this specification matters not. The Teensy makes a great frequency/time-interval counting instrument for conditioned SPAD pulses. A "discriminator" circuit (just an LM311 comparator) is constructed to set the detected pulse threshold voltage and the output is sent to Teensy pin 13. The Teensy enumerates as a keyboard and depending on the button pressed, it will either output frequency data directly to a spreadsheet ad infinitum or will collect and store up to 20k time interval samples in RAM then spit them out to a spreadsheet for data analysis. The code below uses elapsedMicros for timing, but I imagine there is a way to deploy one of the hardware counters to count clock ticks for finer time-interval resolution...as I assume the PulsePosition library is able to achieve 20ns resolution. It seems this clock cycle counting method is employed in the FreqMeasure library, but I was initially scared of trying it because of the stated frequency "limits". Someday I will test this and report back.

In any case, I would love to get any feedback on my code!

SPADLabCircuit.jpg

Code:
#include <FreqCount.h>
#include <Bounce.h>

const int sensePin = 13;
const int intervalPin = 1;
const int freqPin = 12;
const int intervalLED = 23;
const int freqLED = 14;
const unsigned long senseLength = 2000;

volatile unsigned int bangTime = 0;
volatile int dataSent;

Bounce intervalButton = Bounce(intervalPin, 25);
Bounce freqButton = Bounce(freqPin, 25);

void setup() {
  pinMode(intervalPin, INPUT_PULLUP);
  pinMode(freqPin, INPUT_PULLUP);

  pinMode(intervalLED, OUTPUT);
  pinMode(freqLED, OUTPUT);
  digitalWrite(intervalLED, LOW);
  digitalWrite(freqLED, LOW);

  Serial.begin(115200);
}

int16_t countMode = 0;     //mode = 0: nothing happens; mode = 1: frequency counter operational; mode = 2: Interval Timer
int16_t intervalCount = 0;
int16_t intervalData[20000];

elapsedMillis elapsedTime;
elapsedMicros senseTest;

void loop() {

  if (freqButton.update()) {
    if (freqButton.fallingEdge() && countMode == 0) {
      FreqCount.begin(1000);
      elapsedTime = 0;
      Keyboard.print("time [ms]");
      Keyboard.set_key1(KEY_TAB);
      Keyboard.send_now();
      Keyboard.set_key1(0);
      Keyboard.send_now();
      Keyboard.println("Frequency [Hz]");
      digitalWrite(freqLED, HIGH);
      countMode = 1;
    }
    else if (freqButton.fallingEdge() && countMode == 1) {
      FreqCount.end();
      digitalWrite(freqLED, LOW);
      countMode = 0;
      Keyboard.set_modifier(MODIFIERKEY_CTRL);
      Keyboard.set_key1(KEY_HOME);
      Keyboard.send_now();
      Keyboard.set_modifier(0);
      Keyboard.set_key1(0);
      Keyboard.send_now();

    }
  }

  if (intervalButton.update()) {
    if (intervalButton.fallingEdge() && countMode == 0) {
      digitalWrite(intervalLED, HIGH);
      countMode = 2;

      Keyboard.print("integration time [ms]: ");
      Keyboard.println(senseLength);
      Keyboard.println("interval time [us]");

      dataSent = 1;
      elapsedTime = 0;
      senseTest = 0;
      intervalCount = 0;
      attachInterrupt(digitalPinToInterrupt(sensePin), isr, RISING);

    }
  }

  if (countMode == 1 && FreqCount.available()) {
    unsigned long freq = FreqCount.read();
    Keyboard.print(elapsedTime);
    Keyboard.set_key1(KEY_TAB);
    Keyboard.send_now();
    Keyboard.set_key1(0);
    Keyboard.send_now();
    Keyboard.println(freq);
    Serial.println(freq);
  }

  if (countMode == 2 && dataSent == 0) {
    intervalData[intervalCount] = bangTime;
    //Serial.print(bangTime); Serial.print(" - "); Serial.print("intervalData["); Serial.print(intervalCount); Serial.print("] = ");Serial.println(intervalData[intervalCount]);
    intervalCount++;
    dataSent = 1;
  }

  if (countMode == 2 && (elapsedTime > senseLength)) {
    detachInterrupt(digitalPinToInterrupt(sensePin));
    digitalWrite(intervalLED, LOW);
    countMode = 0;

    for (int i = 1; i < intervalCount; i++) {
      Keyboard.println(intervalData[i]);
      Serial.println(intervalData[i]);
    }
    
    Keyboard.set_modifier(MODIFIERKEY_CTRL);
    Keyboard.set_key1(KEY_HOME);
    Keyboard.send_now();
    Keyboard.set_modifier(0);
    Keyboard.set_key1(0);
    Keyboard.send_now();

  }

}

void isr() {
  bangTime = senseTest;
  senseTest = 0;
  dataSent = 0;
}
 
hi Paul
I saw your discussion and testing in video
i'm very interesting for using teensy to count the PMT pulse
my PMT is:
https://www.hamamatsu.com/jp/en/product/type/H10682-110/index.html
my pulse-pair resolution need 20 ns and my output pulse width is about 10 ns
i'm using Oscilloscope to detect now (result as the following)
https://imgur.com/iCZ4S9L
https://imgur.com/FvnMEyl
but it is not really convenient so i want to change it to teensy
is that teensy Hardware allow the specification in my case?
and is that the code fine or i have to add something for my case?(ex:setting gate time)
Looking forward to your reply!
 
Dear all,

I was wondering if somebody has implemented the Photon Counter and could report real values...

Also, I was wondering if the Teensy3.6 could be set up similar as the National Instruments boards counters work, see attached figure, and at which speed.
My objective is also to count photons, but I just need to count the arrival respect some zero time that is reset with a trigger signal.

I am not asking that somebody does the work for me, but if some experienced user could tell me which type of performance can I realistically expect from Teensy3.6 (the fastest Arduino-like that I know of), it really help me to decide if yes or not is worth spending the significant amount of time that such project will take.

Thanks in advanceBuffered_Edge_Counting.png
 
You may use the method previously described by Paul Stoffregen to count SOURCE pulse up to 30 MHz (on Teensy 3.6), and enable INT on rising edge of Sample Clock to actually fill the buffer with the current Counter value.
This is OK if your Sample Clock is up to, say, 2-2.5 MHz (maybe 3 if you overclock the Teensy to 240 MHz).

I don't pretend this to be the best way to do it: just what *I* would do, as a basic-level Teensy programmer. :p
 
Thanks XFER.

The idea is to fill a buffer with the number of clock ticks since a trigger signal happens (SOURCE in the figure) each time that a rising edge occurs ("Sample Clock" in the figure, this is coming from the photon multiplier). Because a clock tick is associated to a time, it is possible to convert to time if needed.

Above, Paul Stoffregen wrote: "If using the LPTRM hardware to count the pulses, up to 30 MHz should be possible." If it is possible to achieve 30MHz time resolution and rate, then we are in business! (my avalanche photo diode has a dead time of 45ns, so 30MHz is enough to not miss a count!!!)

So, I am not sure how to interpret your answer: which value is the good one? 30MHz or 2MHz?

All the best,
 
What are the values to count? Photon detection instances AND clock ticks after the 'arming of the counter' - will that follow the 45 ns dead time? How many photons appear at a time - and will it be a single burst or across multiple clocks for some duration?

Are clock ticks regular? Do they need to be counted or just kept in sync? The Teensy has a counter for it's clock 240 MHz or 256 MHz are possible 'cycle counters'.

If that started with the 'arming' signal then the counter value could be placed as measured CPU clock cycles without actually catching each clock tick.

The T_3.6 can run OC'd up to 256 MHz - but there is a Teensy 4 Beta unit under test - details on that Beta thread - expected for production this 'summer' that runs at 600 MHz. So if you started with a T_3.6 the T_4 might arrive in time to improve upon it - depending on the code and features in use, support for existing libraries/features is the goal - and the same Arduino environment.
 
So, I am not sure how to interpret your answer: which value is the good one? 30MHz or 2MHz?

The 30 MHz figure is only for counting pulses of your "SOURCE"-labelled signal, which would only increment a counter.
The 2 MHz figure is for interrupt-triggering by your "Sample Clock"-labelled signal: slower, because it has to trigger an Interrupt Service Routine to add the counter value to the buffer

But as I said, I don't pretend this to be the best solution: it's just that *I* can't come out with a better one. :)
You'd better follow advices by the real masters, like Defragster and of course Paul Stoffregen himself (The Man Who Designed Teensy ;) )
 
OK. I see that my explanation of what I have in mind is not clear enough... I try again.

I have done a new diagram.

edge_counter.png

The idea is to write down in a "Buffer" the number of "Clock Ticks" since the "Reset/Trigger Signal" when a "TTL from Photo Multiplier" signal arrives.

So, two physical inputs into the board: the "reset/trigger Signal" and the "TTL from Photo Multiplier"

I am assuming that the increment of "Clock Ticks" is done internally and does not need to be done in software.

I am also assuming that is possible to reset the clock to zero.

No maths involved, no averaging, no adding, nothing: just write an uint to a buffer.

The photons arrival can be at any moment, there is no particular pattern. However, given the limitation of my current detector, they will not be arriving faster than every 45ns. I would like to point out that if it is possible to know when the photon arrives with better time resolution, then this is even better!!!

Now, Defragster seem to indicate a time resolution of 256MHz (3.9ns) with Teensy3.6 and 600MHz (1.66ns) with Teensy4.0. If those values are true, this could be a small revolution in my community as funding for fundamental science is getting harder to obtain, and to have a photon counter with this specifications at such cost would be absolutely fantastic!

Another tricky part, as my experiments with Arduino have show me, is getting the data to the computer, without loosing counts. There is something called DMA that is supposed to help with this, but I have not time to look into the details (OK, now is when you all guys are laughing right? I am not an electrical engineering, just a experimental physicists who got started with Arduino a few years ago and is always trying to push the limits of the equipment I have)

By the way, thanks Defragster and XFer for taking time to reply!!!
 
I have done a new diagram.

Much clearer, now. :)

The photons arrival can be at any moment, there is no particular pattern. However, given the limitation of my current detector, they will not be arriving faster than every 45ns. I would like to point out that if it is possible to know when the photon arrives with better time resolution, then this is even better!!!

Now, Defragster seem to indicate a time resolution of 256MHz (3.9ns) with Teensy3.6 and 600MHz (1.66ns) with Teensy4.0

That's the internal clock period of the CPU, and so its the max frequency of the internal counter (your "clock ticks").
But, a potential period as short as 45 ns for the photon detector (your "TTL" signal) is indeed very, very fast.
With my suggested interrupt-driven approach ("TTL" to a pin with interrupt triggered by rising edge), just entering the Interrupt Service Routine will require about 66us on a Teensy 3.6 overclocked to 240 MHz (extrapoled from https://forum.pjrc.com/threads/45413-ISR-latency-Teensy-3-1-and-3-6?p=148851&viewfull=1#post148851).

So I'd say disregard my suggestion: simple, but too slow.
Let's see what the Masters suggest. :)
 
Mmmm... alternative approach: polling on "TTL". Since you don't have much work to do, polling in a tight loop could be feasible.
You would wait for a low->high pattern and then pushing the internal tick counter value into the buffer.
The "digitalReadFast()" function should be really fast (don't know the exact figure) on a T3.6, quite faster than a ISR.

Still, let's wait for others to chime in!
 
The Teensy Cycle counter is just a running uint32_t that rolls over every (2^32/F_CPU) ticks of the clock. Except for rolling over every 17+ seconds or so - they act just like comparing millis() or micros() - but is about as fast as a simple memory read to get current value.

It seems an interrupt on the reset / trigger signal could record the current CycCnt as '0' and then any TTL interrupt after that would record the current CycCnt and the diff would be the # of F_CPU ticks between readings.

IIRC The TTL from P.M. should stay high a min of 5-10 ns to be detectable, and if it just toggled/changed it would be better. The same would apply to the reset / trigger signal.

Any change on a pin on the port wakes the CPU to see what pin and what change and if there is an established interrupt func() to call based on that.

Paul has made notes on 'jitter reduction' I found it in past 2? days and reposted a link to it - that keeps the variability of the response more consistent and minimal.

Simple pseudo sketch might be:
Code:
// global volatile
volatile uint32_t dataStream[1024]; // one CycCnt per TTL
volatile uint32_t nextData = 0;
volatile uint32_t samplePeriods = 0;
volatile uint32_t periodStream[20]; // two entries per Trigger/Reset

setup() {
  attachInt( RESET_PIN, TestStart_isr(), RISING );  // see jitter reduction post - set these higher/highest priority as needed to allow triggering to override output or other
  attachInt( TTL_PM, PhotonNow_isr(), RISING );
  
// T4 has cycle counter running before setup(), T_3.6 currently needs it started
}

TestStart_isr() {
  periodStream[ samplePeriods++ ] = CycCnt;
  periodStream[ samplePeriods++ ] = nextData;
}
PhotonNow_isr() {
  dataStream[ nextData++ ] = CycCnt;
}

loop() {
  // watch for change in samplePeriods - it will go 0,2,4,6,...
  // if it has changed by 2 a test period started and may be ongoing
  // when changed by 4 a test period has completed and Stream values for 'prior' period can be read as complete, or monitor nextData and print on arrival … YMMV 
  // for continuous use another incoming signal could be used to reset nextData and samplePeriods after the data has been sent. 
  // > Perhaps 2D array - but that could add time to data storage in _isr() - perhaps a uint32_t *dataStreamNext rather than dataStream[]

  // [B]your code here to parse stream and calc and report CycCnt diffs for photon arrival offsets from test start time[/B]
}

Just made that up based on my understanding of the details - seems complete ...

That should run pretty quickly depending on photon rate give a good record of arrival for each with granularity of (1/F_CPU)
With T_3.6 at 256 MHz and in cores find 'caveats' and set F_BUS to 128 with comment change for faster I/O detection

Those times will be 'relative' to "current" cycle rate of the CPU clock in a given sample period +/- some PPM over time. If you had a GPS or PPS signal for absolute clock reference the number of F_CPU ticks/ sec could be normalized so results are more directly comparable from week to week if that is meaningful.

ADD: Alternatively the clock ticks could be sampled to tie to actual hardware timing - is it noted how fast the clock ticks are presented? - but doing that at the same time would add overhead/jitter to photon detection. Perhaps if the Trigger were followed by some second(s) delay [or alternate trigger] before first Photon - it could sample the clock for a second in a similar fashion - then transition to waiting for Photons? This would possibly be better where all samples are relative to test hardware clock at hand - rather than some Atomic clock's ticks?

This is one thing I've done a couple different ways in prior year or so less-pseudo examples are possible
 
Last edited:
How long does a sample period run with photon firings? How many are done in succession? And prior post wondered what the rate was on 'Clock Ticks'?

Are the signals and definitions of the input signals fixed and defined?

If you can define/alter sensible added inputs it might allow the Teensy to be more productive and focused and efficient at the task. Not sure what that would be - but one thing that comes to mind would be to use the FTM (?) freq counter to count the Clock Pulses for the duration of a test period. AFAIK that hardware is just an independent 'counter' that wouldn't slow or alter interrupt timing - or it could be done immediatley before and after each test run if those points were known. Then for a given test period a number of clock_counts could be mapped onto the number of F_CPU ticks in that period without added overhead except synchronizing them so that mapping math is meaningful.

As noted if the Trigger/Reset and the TTL_PM signals did a CHANGE only for indication rather than Rise/Fall ( two changes ) that would reduce the chance of distracting the Teensy checking pin state changes.

Based on temp and environment the F_CPU ticks can change perhaps a few from second to second and few hundred from hour to hour for a Teensy 'sitting in a window', and they will be different from Teensy to Teensy.
 
Don't know if will work, but I'd look into using the input capture capability of a T3.6 Flexible Timer Module (FTM).

First though, your "Requirements Document" needs to be fleshed out quite a bit:

* Does it need to detect photons and transmit data to the PC continuously and simultaneously? Or, can you have an acquire mode to gather samples followed by a data transmission mode to send the data?

* If you can have two modes as described above, how much data do you need to acquire before switching over and sending it to the PC? Is it based on time or number of "hits" detected?

* How deep do the buffers need to be?

* Is there a lower bound on the time between rising edges of your "Sample Clock"?

* What is the minimum "High" time of your "Sample Clock"? Minimum "Low" time?


Those are the questions I can think of right now.



I recommend using an FTM and asked several pertinent questions in @jofre's nearly identical thread on the same topic. So far, no reply to my questions.
 
Last edited by a moderator:
Since the dual post reactivated on this thread first - where FreqCount was the topic that thread is a dupe - could be closed if you wanted to bring that post over here.

FTM makes sense for the clock if it is measurable and meaningful, beyond what elapsed cycle counts can show - but the counts themselves - by info so far seem to be limited and sporadic - so interrupts might catch them.
 
@gfvalvo - pulled your post from other thread and edited it into your post … and closed it … that is the frustration caused by duplicate posts/threads …
 
It seems an interrupt on the reset / trigger signal could record the current CycCnt as '0' and then any TTL interrupt after that would record the current CycCnt and the diff would be the # of F_CPU ticks between readings.

As jofro explained, photons may arrive with a 45ns minimum period: shorter than the latency time of a ISR with maximum priority on an overclocked T3.6.
The ISR can't cope: while you are inside the ISR, another photon may arrive and you would miss it. Just saying.
 
As jofro explained, photons may arrive with a 45ns minimum period: shorter than the latency time of a ISR with maximum priority on an overclocked T3.6.
The ISR can't cope: while you are inside the ISR, another photon may arrive and you would miss it. Just saying.

Opps ... Miscue on the magnitude - that'd be just over 22.222 million hits per second … just for the start of the sample ...
 
I was suggesting something along the lines of...

Code:
#include <stdint.h>

#define PHOTON_TTL_PIN [...]

volatile uint32_t countsBuffer[8];	// 64 byte: just the size of a USB packet
volatile uint32_t index = 0;


void yield()
{	// We override yield() to disable serial events check, gaining a bit of CPU cycles. Maybe
}

setup()
{
[...]
}

loop()
{	
	while(!digitalReadFast(PHOTON_TTL_PIN));	// wait for signal to go high
	// Now we are on the rising edge: a photon arrived
	countsBuffer[index++] = CycCnt;				// CycCnt is the internal tick counter
	if (index == 8)	// Time to send the buffer to the attached PC
	{
		index = 0;	// Reset the buffer
		Send_64_byte_packet_over_USB_using_DMA();	// Can't help here, sorry. Don't even know if possible without missing photons
	}
	while (digitalReadFast(PHOTON_TTL_PIN));	// wait for the signal to go low again (probably already did)
}

Note: all the math, like converting ticks to whatever time unit, checking if CycCnt did reset to 0 between photons, etc., it's better left to the attached PC in my opinion.

I don't pretend this to work, less it to be the best solution, mind you: just a humble suggestion.
 
Status
Not open for further replies.
Back
Top