Fast counting with arduino and compatibles

For stand-alone microprocessors

Fast counting with arduino and compatibles

Postby Doug Coulter » Tue Sep 10, 2019 3:08 pm

Like the hardware flip-flop metastability problem, taking a snapshot of a counter while it's counting without errors from partial carries, missing any counts and so on - is considered an impossible problem. I think this is correct. And I don't think I've solved it here, but golly, I've gotten the error rate WAY down, don't lose counts and so on. This could miss one overflow of the 16 bit counter due to turning off interrupts - YMMV, if you have issues, try the other way and leave them on.
This really is a hard one. The meta-stability issue is handled as well as possible in the arduino hardware with some sync flip flops. Ditto the capture when you read it. If you stop the counter to read it, you'd miss counts. If you don't stop interrupts, you might see an overflow between when your read the counter hardware and get the total - right in the middle of a line of code. To quote some obscure Joe Jackson tune "everything gives you cancer, there's no cure, there's no answer". This used to take a good solid page of schiz for old TTL to get close to this good, actually.

Here's a thread on the newer double speed clone hardware. viewtopic.php?f=6&t=1149&start=0


This uses counter 1 in an uno or compatible (including the newer lg8t) to do the initial count capture at rates up to whatever the arduino will synchronize (half the system cpu clock?) - at any rate, fast for what I use these for - geiger and scintillation counting.

When T1 overflows, it interrupts the cpu and there's an interrupt service routine to update the upper half of an unsigned long representing the total count.
That's not all, though. There are a couple of other unsigned longs I use here - one to save the previous value, and one to report the difference with, so you get "counts since I last checked" as an output if you like - often more useful - but you can output whichever of the variables you want (and skip some computation if you're not going to use the result).

This uses some rarely documented registers in the hardware, so here's at least an example I adapted from ??? - it's been awhile.
There are a few variables to declare, and set to zero from setup (I use a subroutine called init_ram() here), a counter setup routine, and a counter read routine you can call whenever you want "counts since last time" updated.

So here it is. Variables first:
You can either trust the arduino runtime to zero these or do it at setup() - I do it manually as I also have a software reset in many sketches...
Code: Select all
volatile unsigned long c1counts;    // total counts from (hardware) counter 1, d5 pin
// top half incremented by timer overflow
volatile unsigned long c1z; // previous count for subtraction
volatile unsigned long curcount; // what we'll report, this - last total


The trick to making this all workable is the counter setup. I honestly forget where I found this (or I'd give credit) back in the early days, but here's what it became:
Code: Select all
////////////////////////////////////////////////
void t1set() // setup timer1 as a counter that interrupts on overflow
{
  TCCR1A = 0; // turn off all the weird compare/capture modes etc
  TCCR1B = 6; // clock on falling edge (7 for rising)
  TIFR1 = _BV(TOV1); // clear initial overflow - shouldn't need to, but do.
  TCNT1 = 0; //zero out timer
  TIMSK1 = _BV(TOIE1);// sets the timer overflow interrupt enable bit
  }

The above is run once by calling it from setup()

Now to read the counter - and this is potentially flawed as there's not even a theoretical way to be perfect here (this IS right on average if you skip that one crazy case, or just use totals till overflow occurs - the code in theory handles those overflows as it is...tradeoffs).

Code: Select all
////////////////////////////////////////////////
void readCounts()
{ // just fast hardware counter (d5 pin), interrupts us only every 65k counts
/* Disable interrupts */
noInterrupts();
/* Read TCNT1 into c1counts low part */
c1counts &= 0xffff0000;
c1counts |= TCNT1; // from the hardware register @@@ there's a better capture register
curcount = c1counts - c1z; // just the new ones for this time interval
c1z = c1counts; // for next time
/* Restore global interrupt flag */
interrupts();
}


And to count above 65k total - you'll need to put in this ISR. It's automagic, just put this code in the sketch somewhere. All it does is add 1 to the upper half of the unsigned long when counter 1 overflows.

Code: Select all
////////////////////////////////////////////////
//////// manual isr setup, you bet, saves a whole library
// actual isr for hardware counter 1
ISR(TIMER1_OVF_vect)
{
c1counts += 0x00010000; // add one to top half of longword
}
//////////////////////////////////



curcount is what I report at intervals in my much more complex code (my own opsys, scheduler etc) - this is abstracted so you can use it in more simple cases.

I'm testing this now on the new nano double speed clone, I've only tried up to 1.11 mhz but it's fine there (this overflows counter1 on every 100ms reporting interval).
Posting as just me, not as the forum owner. Everything I say is "in my opinion" and YMMV -- which should go for everyone without saying.
User avatar
Doug Coulter
 
Posts: 3515
Joined: Wed Jul 14, 2010 7:05 pm
Location: Floyd county, VA, USA

Re: Fast counting with arduino and compatibles

Postby Doug Coulter » Wed Sep 11, 2019 11:48 am

I've now tested counting speed with a signal generator making good 3.3v logic levels with a square wave. There begin to be errors or missed pulses at 14 MHz or so, it losing around 10% of them.
This is pretty much as expected, most likely limited by the input signal synchronization clock available in the chip, which should be twice the speed of the original nano.

This is "not shabby" and given that even fairly slow counts from various detectors can happen very close to one another in time - random is random, limited only by detector response - pretty good for expected count rates much below this. Some scintillators for example have response times in the few nanoseconds and on a scope you can see many closely spaced pulses during say, a hit from a cosmic ray shower as various of the particles produced in a collision overhead get there at nearly but not the same, time. This kind of thing wouldn't be an issue with geiger counters or proportional detectors that have far longer "dead" times. But hey...it's good to design test equipment to be a lot better than the anticipated need - the old rule used to be 10x.

I need to test this at 5v as well, but right now I'm blinking a led using MsTimer2 code - and in this design the led has no series resistor, so to test that I'll have to remove the led or the software that blinks it first.

Higher logic levels and A/D full scale are generally better in a noisy environment, as well as do a better job driving fets to run higher power stuff.
Posting as just me, not as the forum owner. Everything I say is "in my opinion" and YMMV -- which should go for everyone without saying.
User avatar
Doug Coulter
 
Posts: 3515
Joined: Wed Jul 14, 2010 7:05 pm
Location: Floyd county, VA, USA


Return to Embedded software

Who is online

Users browsing this forum: No registered users and 3 guests

cron