Go Down

Topic: Arduino Due counter (Read 3452 times) previous topic - next topic

rajivpersonal

I need to measure time between two events, say START and STOP, with a resolution of not worse than 20 nanoseconds. I have thought of building a 50/100 MHz oscillator to drive the input of an Arduino Due counter.

1. How fast can the Arduino Due counter count? 50MHz? 100MHz?

2. Is there a library that will allow me to start and stop a counter and obtain the count of pulses being fed to it externally? Paul Stoffregen has written a library FreqCount.h which even though it does not support the Due yet, has a FreqCount.begin(gateTimeIn_ms), FreqCount.end() and FreqCount.read(), to count the pulses within a gate time specified in milliseconds. However I am not looking for a gate time. I want to capture the pulses counted between two events, say START and STOP. Knowing the number of pulses counted between the two events and the period of one pulse, I can get a nanosecond period timer. Say a 100MHz clock were to be connected to the counter input. Starting the count at the START event and stopping it at the STOP event, will give me a count of 10 ns pulses. The time between the START and STOP events will be (count * 10) ns.

Can someone help me with this problem?

MorganS

The Due works at 84MHz. How would you expect it to count 100MHz? It can't.

What kind of duration are you looking at? The onboard crystal on the Due is not especially accurate. If you're trying to time seconds, then the accuracy of the crystal is worse than your timing specification.

Maybe a dedicated timer module will be needed to meet the accuracy specification.
"The problem is in the code you didn't post."

rajivpersonal

AFAIK counters are not synchronous with the uC clock. In fact, counters do not require a clock to count, only an edge transition. And can count at much higher rates than the uC that hosts them.

Magician

AFAIK counters are not synchronous with the uC clock. In fact, counters do not require a clock to count, only an edge transition. And can count at much higher rates than the uC that hosts them.
In general yes, but not with SAM3X arduino DUE has. Synchronization logic makes it possible to count at 42MHz max.
Code: [Select]
#define   SMP_RATE              1UL           // 1 Hz ( 1 sec. gate)
#define   CLK_MAIN       84000000UL
#define   TMR_CNTR       CLK_MAIN / (2 *SMP_RATE)

// Name - Port - Pin(DUE board)
// TCLK1 = PA4 = AD5
// TIOA1 = PA2 = AD7
// TIOB1 = PA3 = AD6

volatile uint32_t freq = 0;  
volatile uint32_t flag = 0;  

         int debug_osm = 0; // debug over serial monitor
         int debug_cnt = 0; // debug variable duty period

void setup()
{
  Serial.begin (115200);

  Atmr_setup();        
  Btmr_setup();        

  pio_TIOA0();  // Gate OUT, Dig. 2 conect Dig-2 to AN-7.
  pio_TCLK1();  // Counter IN, AN - 5 (max 3.3V -> keep safe !!!)
  pio_TIOA1();  // Gate IN, AN - 7.
  pinMode( 7, OUTPUT); // OUT, Digital 7 -> connect to AN-5 for Testing
}

void loop()
{
  char in_Byte;
  
  if( Serial.available() > 0 ) {
    in_Byte = Serial.read();
    if( in_Byte == 'd' ) { //debug          
      debug_osm = 1 - debug_osm;
      if (debug_osm) Serial.println(F("\n\tDebug activated"));
      else           Serial.println(F("\n\tDebug de-activ."));
      }
    }

  if( flag && debug_osm ) {
       Serial.print("\n\tfreq: ");
       Serial.print(freq,  DEC);  
       flag = 0;
    }

  debug_cnt += 10;
  if( debug_cnt > 255 ) debug_cnt = 0;
  analogWrite( 7, debug_cnt);
}


void TC1_Handler(void)
{
if ((TC_GetStatus(TC0, 1) & TC_SR_LDRBS) == TC_SR_LDRBS) {
  uint32_t dummy = TC0->TC_CHANNEL[1].TC_RA;
           freq  = TC0->TC_CHANNEL[1].TC_RB;
           flag  = 1;
  }
}

void Atmr_setup() // Gate 1 sec., conect Dig-2 to AN-7.
{
  pmc_enable_periph_clk (TC_INTERFACE_ID + 0 *3 + 0);
  TcChannel * t = &(TC0->TC_CHANNEL)[0];
  t->TC_CCR = TC_CCR_CLKDIS;
  t->TC_IDR = 0xFFFFFFFF;  
  t->TC_SR;                
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |
              TC_CMR_WAVE |                
              TC_CMR_WAVSEL_UP_RC |        
              TC_CMR_EEVT_XC0 |    
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR;
  t->TC_RC = TMR_CNTR;
  t->TC_RA = TMR_CNTR /2;
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET;
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
}

void Btmr_setup()  // Counter
{
  pmc_enable_periph_clk (TC_INTERFACE_ID + 0 *3 + 1);

  uint32_t reg_val = TC_BMR_TC1XC1S_TCLK1; // Input Capture Pin, AN-5.
  TC0->TC_BMR |= reg_val;

  TcChannel * t = &(TC0->TC_CHANNEL)[1];
  t->TC_CCR = TC_CCR_CLKDIS;
  t->TC_IDR = 0xFFFFFFFF;  
  t->TC_SR;  
  t->TC_CMR = TC_CMR_TCCLKS_XC1  
             | TC_CMR_LDRA_RISING    
             | TC_CMR_LDRB_FALLING    
             | TC_CMR_ABETRG          
             | TC_CMR_ETRGEDG_FALLING;
            
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;

  t->TC_IER =  TC_IER_LDRBS;
  t->TC_IDR = ~TC_IER_LDRBS;

  NVIC_DisableIRQ(TC1_IRQn);
  NVIC_ClearPendingIRQ(TC1_IRQn);
  NVIC_SetPriority(TC1_IRQn, 0);
  NVIC_EnableIRQ(TC1_IRQn);
}

void pio_TIOA0()
{
  PIOB->PIO_PDR   = PIO_PB25B_TIOA0;  
  PIOB->PIO_IDR   = PIO_PB25B_TIOA0;  
  PIOB->PIO_ABSR |= PIO_PB25B_TIOA0;
}

void pio_TCLK1()
{
  PIOA->PIO_PDR = PIO_PA4A_TCLK1;  
  PIOA->PIO_IDR = PIO_PA4A_TCLK1;
}

void pio_TIOA1()
{
  PIOA->PIO_PDR = PIO_PA2A_TIOA1;  
  PIOA->PIO_IDR = PIO_PA2A_TIOA1;
}

rajivpersonal

Oh wow! That must've taken you some time! Thank you... :)

I do not need a gate period. I need to start the counter on receipt of one interrupt and stop the counter on the receipt of a second interrupt. And retrieve the count.

(count * period_of_one_count) will give me the time between the two interrupts. 42 MHz will work fine for me - each count will signify 23.81 ns

What needs to change to effect the above functionality?

Thanks again... <3

Rajiv






Magician

Ok,  timer has to be driven by internal clock, and gated by external events, what I may see as a problem is using an interrupt in classic meaning. To go into interrupt subroutine arduino could spend a lot of time, and it is not specified. Better to do all in hardware, so gating again. I have another sketch, that measure period, but to make it work with two events you have to add an logic IC that 'd combine two lines into one, or modified sketch. To help you start with.
Code: [Select]
/*
 Test Sketch for an Arduino DUE board,
 Period/Duty Cycle measurements on PWM signals.
 Created by Anatoly Kuzmenko, August 2015
 Released into the public domain.
 k_anatoly@hotmail.com
*/

// Name - Port - Pin(DUE board)
// TIOA1 = PA2 = AD7

volatile uint32_t tmr_RA = 0; 
volatile uint32_t tmr_RB = 0; 
volatile uint32_t read_s = 0; 

         int debug_osm = 0; // debug over serial monitor
         int debug_cnt = 0; // debug variable duty period

void setup()
{
  Serial.begin (115200);
  Btmr_setup();         
  pio_TIOA1();  // IN, AN - 7 (max 3.3V -> keep safe !!!).
  pinMode( 7, OUTPUT); // OUT, Digital 7 -> connect to AN-7 for Testing
}

void loop()
{
  char in_Byte;
 
  if( Serial.available() > 0 ) {
    in_Byte = Serial.read();
    if( in_Byte == 'd' ) { //debug           
      debug_osm = 1 - debug_osm;
      if (debug_osm) Serial.println(F("\n\tDebug activated"));
      else           Serial.println(F("\n\tDebug de-activ."));
      }
    }

  if( read_s && debug_osm ) {
    uint32_t period = tmr_RB / 42;
    uint32_t duty_c = (100 * (tmr_RB - tmr_RA)) / tmr_RB;
   
    uint32_t freq = 42000000UL/ tmr_RB;
   
      Serial.print("\n\tPeriod: ");
      Serial.print(period, DEC);
      Serial.print("\tmicroseconds");
      Serial.print("\n\tDuty Cycle: ");
      Serial.print(duty_c, DEC);
      Serial.print("\t%");
      Serial.print("\n\tFrequency: ");
      Serial.print(freq, DEC);
      read_s = 0;
    delay(1000);
    }

  debug_cnt += 10;
  if( debug_cnt > 255 ) debug_cnt = 0;
  analogWrite( 7, debug_cnt);
}

void TC1_Handler(void)
{
if ((TC_GetStatus(TC0, 1) & TC_SR_LDRBS) == TC_SR_LDRBS) {

  tmr_RA = TC0->TC_CHANNEL[1].TC_RA;
  tmr_RB = TC0->TC_CHANNEL[1].TC_RB;
  read_s = 1;
  }
}

void Btmr_setup()  // Counter
{
  pmc_enable_periph_clk (TC_INTERFACE_ID + 0 *3 + 1);

  TcChannel * t = &(TC0->TC_CHANNEL)[1];
  t->TC_CCR = TC_CCR_CLKDIS;
  t->TC_IDR = 0xFFFFFFFF;   
  t->TC_SR;   

  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1

             | TC_CMR_LDRA_RISING     
             | TC_CMR_LDRB_FALLING   
             | TC_CMR_ABETRG         
             | TC_CMR_ETRGEDG_FALLING;
             
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;

  t->TC_IER =  TC_IER_LDRBS;
  t->TC_IDR = ~TC_IER_LDRBS;

  NVIC_DisableIRQ(TC1_IRQn);
  NVIC_ClearPendingIRQ(TC1_IRQn);
  NVIC_SetPriority(TC1_IRQn, 0);
  NVIC_EnableIRQ(TC1_IRQn);
}

void pio_TIOA1()
{
  PIOA->PIO_PDR = PIO_PA2A_TIOA1; 
  PIOA->PIO_IDR = PIO_PA2A_TIOA1;
}

jpedrego

Dear rajivpersonal and Magician,

I am trying to do something very similar: to use the Due as a Photon Counter

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.

I have done a diagram, see attached figure.

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!!!

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 )

Regards

MorganS

Have a look at timer/counter mode for the hardware timers.
"The problem is in the code you didn't post."

ard_newbie


Something is missing in your description: the Clock frequency.

jpedrego

The Clock is given by the DUE internal clock.

I am reading right now the section 36 Timer Counter of the ATMEL documentation. It seems promising...

ard_newbie


If you mean SysTick counter for the "DUE internal clock", this counter counts down from 84000 to 0 in 1 ms.

antodom

Hi there @rajivpersonal,

@HermannSW has already done some testing to know the limits of DUE'sATSAM3X8E for measuring digital signals. Using in this case, library tc_lib, which is just a wrapper around the TC modules available in the raw microcontroller. In this post you have a summary:

https://forum.arduino.cc/index.php?topic=393842.msg3086694#msg3086694

You can find tc_lib at: https://github.com/antodom/tc_lib

I think they can help you to have a better idea about DUE's ATSAM3X8E capacities, they report measurements of signals with frequencies greater than 20Mhz.


I hope it helps.
------------
antodom

jpedrego

Hi all.

I have modified the code from Magician. If my understanding of what is going on is correct, the code below should put into the variable "CaptureCountA", the value of the clock (MCK/2 = 42MHz?) on the "rising edge of the trigger input", to quote the documentation.

Using a wavefunction generator, I am generating a square waveform of 0V-3V, with a 100us period. Such signal is send to the AnalogIn7 of the Arduino Due

When using the code below, I would have expected to get values spaced of 4200 (100us * 42MHz), however, I get non-sense (see the attached file, copied from the serial monitor).

Therefore, I am missing something... Any idea?



Code: [Select]

volatile uint32_t CaptureCountA, TimerCount;
volatile boolean CaptureFlag;
const int max_i = 64;
int i;

void setup() {
  Serial.begin(115200);

  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // Timer Counter 0 channel 1 IS TC1
  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKDIS ;             // disable internal clocking while setup regs
  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1   // capture mode, MCK/2, clk on rising edge                           
                              | TC_CMR_ABETRG              // TIOA is used as the external trigger
                              | TC_CMR_LDRA_RISING ;       // load RA on rising edge of trigger input

  TC0->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS;               // Trigger interruption on Load RA and load RB
  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN ;// Reset TC counter and enable

  NVIC_EnableIRQ(TC1_IRQn);                             // Enable TC1 interrupts
}

void loop() {
    if (i < max_i){
        i+=1; 
        if (CaptureFlag) {
        CaptureFlag = 0;
        printf("\r %d , %d , %d \n", CaptureCountA);
        }
        else {
        Serial.println(TC0->TC_CHANNEL[1].TC_CV);
        }
    }
}
void TC1_Handler() {
  uint32_t status = TC0->TC_CHANNEL[1].TC_SR;       // Read & Save satus register -->Clear status register
  if ((status & TC_SR_LOVRS) == TC_SR_LOVRS) abort();
  if ((status & TC_SR_LDRAS) == TC_SR_LDRAS) {  // If ISR is fired by LDRAS then ....
    CaptureCountA = TC0->TC_CHANNEL[1].TC_RA;        // get data from capture register A for TC0 channel 1
  }
  CaptureFlag = 1;                      // set flag indicating a new capture value is present
}

jpedrego

I forgot to attach the file!

ard_newbie

#14
May 23, 2019, 07:31 am Last Edit: May 23, 2019, 07:51 am by ard_newbie
In order to produce a minimum number of interrupts, I would use a code close to the one used for an Quadrature decoder. If you program TC_SMMR to produce a Phase A square wave at a 42 MHz frequency, and use your Reset/Trigger Signal as an Index to reset TC_CV counter.  

Use the Photo Multiplier Signal to trigger an interrupt (finally the only one), connect this signal to a PIO, log Clock Ticks counter value in the PIO Handler.

Same explanation with more details:


The Clock Ticks counter can be incremented at 42 MHz using the Quadrature Decoder sketch with e.g. QDEC1. TC_CV is incremented via pulses received thru TIOA6. TC_SMMR Phase A will be useful  to output a pulse for TIOA6.

QDEC1:
Timer Counter 2 channel 0 / TIOA and TIOB of channel 0
PHA  TIOA6 = pin 5
PHB  TIOB6 = pin 4
Timer Counter 2 channel 1 / TIOB of channel 1
INDEX  TIOB7 = pin 10

Each time a pulse(INDEX)  is received thru TIOB7, TC_CV is reset. This reset  pulse is generated by your Reset/Trigger signal.

See this thread, reply 87:
https://forum.arduino.cc/index.php?topic=140205.75

Now you have to leverage the signal received from the Photo Multiplier. This signal received on a GPIO will trigger an interrupt. attachinterrupt() is a bit slow, so you will modify Winterrupts.h so that you can write your own PIOx_Handler() code, much faster to detect a pulse on a PIOx pin. First off, disable all interrupts from a PIO, except the one you want with PIO_IDR.

See this thread, reply 1:
https://forum.arduino.cc/index.php?topic=602555.0

Once an interrupt fires, the code enters  in the PIOx_Handler() after 8 clock cycles (from 84 MHz), therefore you will copy TC_CV value in a buffer  at that moment minus 4 (from 42 MHz).


Edit: Phase A of TC_SMMR maximum frequency will be 42 MHz/4, so use directly the output of a Timer Counter with the maximum frequency: 42 MHz



Go Up