Go Down

Topic: Arduino Due measure rare 1µs pulse with 0.2µs resolution for many channels (Read 1 time) previous topic - next topic

Rouletabille

Hi !

I have an issue, I need to measure the time of pulses of nearly 1µs with a ~0.2µs resolution to test scientific experiment which will be used in some years in an experiment, I chose an Arduino Due because I have 70 inputs to test (with 3 arduino due) and nearly no knowledge in electronics. Those pulses will be very very rare, so my idea was to put an attach interrupt on the rising edge of the pulse, launch a timer, attach on the falling edge (with a time out in case of noise) and stop it at the falling edge ^^
But, even with the 12ns clock time of the DUE and even if I can read everywhere that the first timer of the Atmel SAM3X8E ARM Cortex-M3 is Master Clock / 2, which would be perfect for me, I haven't found a function to go under the micro second or a library that can do that, and I don't know how to use directly the registers of the chip ^^""""
I found that https://github.com/ivanseidel/DueTimer and that https://github.com/antodom/tc_lib but they "only" call back function periodically, I tried to understand their code but it's not so easy (especially for the second one ...)
I can call a function periodically every 0.2µs but it will consume some clock cycles so I prefer to avoid that possibility.
So, do you know a library or a way to launch a timer with 0.2µs on the Due, or a lecture on how to do that with the registers of the Atmel SAM3X8E ARM Cortex-M3  ??? ?

Thanks !!

ard_newbie


It's a business for a Timer Capture... (See TC_CMR in capture mode in Sam3x datasheet).


bobcousins

So, do you know a library or a way to launch a timer with 0.2µs on the Due,
There is absolutely no way that will work!

But ard_newbie is right, a timer in capture mode can probably do it. There are 6 channels that can be used for input capture on the Due, so you will need 12 Dues to handle 70 signals.

There is an example here https://forum.arduino.cc/index.php?topic=158361.0
Please ask questions in the forum so everyone can benefit. PM me for paid work.

bobcousins

I was wondering how well the timer capture would work, so I adapted the sketch linked above.

Code: [Select]
#include "Arduino.h"

/*
  Input capture working for Arduino Due with input signal connected to pin 5.
  Based on the example in this discussion thread: https://forum.arduino.cc/index.php?topic=158361.0
  by Paul Dreik http://www.pauldreik.se/

  Modified by Bob Cousins to measure positive going pulse width on TIOA6.
*/

volatile uint32_t ra;
volatile uint32_t rb;
volatile uint32_t pulse_length;   // pulse length in timer counts

/*
 * Interrupt function. TC6 = timer counter TC2, channel 0
 * Note : this does not handle the overflow or other error cases
*/
void TC6_Handler()
{
 // reads the interrupt. necessary to clear the interrupt flag.
 const uint32_t status=TC_GetStatus(TC2, 0);

 // has the timer overflowed?
 const bool overflowed=status & TC_SR_COVFS;

 // input captures
 const bool inputcaptureA=status & TC_SR_LDRAS;
 const bool inputcaptureB=status & TC_SR_LDRBS;

 // loading overrun?
 const bool loadoverrun=status & TC_SR_LOVRS;

 // read LDRA and store it. If we don't read RA, we will get overflow (TC_SR_LOVRS)
 if (inputcaptureA)
 {
   ra = TC2->TC_CHANNEL[0].TC_RA;
 }

 // read LDRB and calculate pulse len. If we don't read RB, we will get overflow (TC_SR_LOVRS)
 if (inputcaptureB)
 {
   rb = TC2->TC_CHANNEL[0].TC_RB;
   pulse_length = rb-ra;
 }
}

void startTimer()
{
  Tc *tc = TC2;
  uint32_t channel = 0;
  IRQn_Type irq = TC6_IRQn;

  // We need to configure the pin to be controlled by the right peripheral.
  // The TIOxx pins available depend on the Timer unit.

  // The following is for timer unit TC6, pin TIOA6, Arduino pin 5.
  // pin 5 is port C. PIOC_PDR is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/instance/instance_pioc.h
  // and PIO_PDR_P25 is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/component/component_pio.h

  // this disables the PIO from controlling the pin. see 32.7.2
  REG_PIOC_PDR |= PIO_PDR_P25;

  // next thing is to assign the IO line to the peripheral. See 32.7.24.
  // we need to know which peripheral we should use. Read table 37-4 in section 37.5.1.
  // TIOA6 is peripheral B, so we want to set that bit to 1.
  // REG_PIOC_ABSR is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/instance/instance_pioc.h
  // PIO_ABSR_P25 is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/component/component_pio.h
  REG_PIOC_ABSR |= PIO_ABSR_P25;

  //allow configuring the clock.
  pmc_set_writeprotect(false);

  /*
  Every peripheral in the SAM3X is off by default (to save power) so must be turned on.
  */
  pmc_enable_periph_clk((uint32_t)irq);

  /*
  Configure the timer. All this is about setting TC_CMRx, see 37.7.10 in Atmel pdf.
  We use CLOCK1 at 42 MHz to get the best possible resolution.
  We want input capture on TIOA6 (pin 5). Nothing else should be necessary, BUT there is a caveat:
  As mentioned in 37.6.8, we only get the value loaded in RA if not loaded since the last trigger,
  or RB has been loaded. Since I do not want to trigger as that sets the timer value to 0, I
  instead let register B be loaded when the pulse is going low.
  */
  TC_Configure(tc, channel, TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_LDRA_RISING | TC_CMR_LDRB_FALLING);

  // Set the interrupt flags. We want:
  // interrupt on overflow
  // TIOA6 (pin 5) going low
  const uint32_t flags=TC_IER_COVFS  | TC_IER_LDRBS;
  tc->TC_CHANNEL[channel].TC_IER=flags;
  tc->TC_CHANNEL[channel].TC_IDR=~flags;

  NVIC_EnableIRQ(irq);

  //start the timer
  TC_Start(tc, channel);
}

void setup()
{
  Serial.begin(115200);
  pinMode(13,OUTPUT);

  pinMode(2, OUTPUT);
  digitalWrite (2, 0);

  startTimer();
}

void loop()
{
  float ticks_per_us = F_CPU/2.0/1e6;

  delay (2000);

  pulse_length = 0;
  ra = 0;
  rb = 0;

  // create a short pulse
  digitalWrite (2, 1);
  digitalWrite (2, 0);

  Serial.print(" ticks=");
  Serial.print(pulse_length);
  Serial.print(" t(ns)=");
  Serial.println(pulse_length*1000.0/ticks_per_us);
}


Some output :

Quote
ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=93 t(ns)=2214.29
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
 ticks=92 t(ns)=2190.48
For a pulse length of 2200 ns measured on my scope, the Due prints a value of 2190.48 ns to 2214.29 ns, which is about within 15 ns.  The tick resolution is 1/42 MHz, about 24 ns, so it's accurate to the resolution of the timer clock.

The sketch can be extended to other timer channels, you will have to dig through the data sheet to see what TIO pins are available.
Please ask questions in the forum so everyone can benefit. PM me for paid work.

Rouletabille

Hi !
Really a lot of thanks ! What you did is amazing.
I'm gonna try to adapt it to my problem and post the modifications if I manage.
Sorry to had not answer very fast but I was working on that ^^''
I found that https://github.com/adafruit/ESP8266-Arduino (ESP.getCycleCount()) and that http://asf.atmel.com/docs/latest/search.html?board=Arduino%20Due/X , I post it in the case it would be useful to someone one day ^^

Rouletabille

Hi !
So by wiring the pin 2 to the pin 5 I obtained the same results as you (that was the aim of this code !) but when I tried with a pulse generator (manual trigger) it wasn't working, except when the trigger was put in continuous mod (a pulse of 1µs every 10µs) so I replaced the delay() by using millis() but it was the same thing, all the pulses weren't caught), so I put the print code directly in the interrupt function (in the falling edge part) and so it works for every interruption (it's not really well coded but it works) !
I will put the code in case it would be useful to someone.
Now I need to make it works on the 5 other channels of the Arduino DUE.
But this code is really close to the Hardware I don't really understand it even with the datasheet of the chip (I even didn't know the "Tc" object).
Have you some documentation (lectures, videos, tutorials) on that (I mean coding close to Hardware for the due) ? Because I can't find some on the Arduino website ... !
Thanks !

Code: [Select]

#include "Arduino.h"

/*
  Input capture working for Arduino Due with input signal connected to pin 5.
  Based on the example in this discussion thread: https://forum.arduino.cc/index.php?topic=158361.0
  by Paul Dreik http://www.pauldreik.se/

  Modified by Bob Cousins to measure positive going pulse width on TIOA6.

  Modified by Florian ANDRE and Mark Istvan KOVACS to make it works with a pulse generator
  https://forum.arduino.cc/index.php?topic=490228.0
*/

volatile uint32_t ra;
volatile uint32_t rb;
volatile uint32_t pulse_length;   // pulse length in timer counts


/*
 * Interrupt function. TC6 = timer counter TC2, channel 0
 * Note : this does not handle the overflow or other error cases
*/
void TC6_Handler()
{
 // reads the interrupt. necessary to clear the interrupt flag.
 const uint32_t status=TC_GetStatus(TC2, 0);

 // has the timer overflowed?
 const bool overflowed=status & TC_SR_COVFS;

 // input captures
 const bool inputcaptureA=status & TC_SR_LDRAS;
 const bool inputcaptureB=status & TC_SR_LDRBS;

 // loading overrun?
 const bool loadoverrun=status & TC_SR_LOVRS;

 //ticks per micro seconds

 float ticks_per_us = F_CPU/2.0/1e6;



 // read LDRA and store it. If we don't read RA, we will get overflow (TC_SR_LOVRS)
 if (inputcaptureA)
 {
   ra = TC2->TC_CHANNEL[0].TC_RA;
 }

 // read LDRB and calculate pulse len. If we don't read RB, we will get overflow (TC_SR_LOVRS)
 if (inputcaptureB)
 {
   rb = TC2->TC_CHANNEL[0].TC_RB;

   //this is the falling edge so let's print the results
   pulse_length = rb-ra;
   Serial.print(" ticks=");
   Serial.print(pulse_length);
   Serial.print(" t(ns)=");
   Serial.println(pulse_length*1000.0/ticks_per_us);
 }

}

void startTimer()
{
  Tc *tc = TC2;
  uint32_t channel = 0;
  IRQn_Type irq = TC6_IRQn;

  // We need to configure the pin to be controlled by the right peripheral.
  // The TIOxx pins available depend on the Timer unit.

  // The following is for timer unit TC6, pin TIOA6, Arduino pin 5.
  // pin 5 is port C. PIOC_PDR is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/instance/instance_pioc.h
  // and PIO_PDR_P25 is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/component/component_pio.h

  // this disables the PIO from controlling the pin. see 32.7.2
  REG_PIOC_PDR |= PIO_PDR_P25;

  // next thing is to assign the IO line to the peripheral. See 32.7.24.
  // we need to know which peripheral we should use. Read table 37-4 in section 37.5.1.
  // TIOA6 is peripheral B, so we want to set that bit to 1.
  // REG_PIOC_ABSR is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/instance/instance_pioc.h
  // PIO_ABSR_P25 is defined in hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/component/component_pio.h
  REG_PIOC_ABSR |= PIO_ABSR_P25;

  //allow configuring the clock.
  pmc_set_writeprotect(false);

  /*
  Every peripheral in the SAM3X is off by default (to save power) so must be turned on.
  */
  pmc_enable_periph_clk((uint32_t)irq);

  /*
  Configure the timer. All this is about setting TC_CMRx, see 37.7.10 in Atmel pdf.
  We use CLOCK1 at 42 MHz to get the best possible resolution.
  We want input capture on TIOA6 (pin 5). Nothing else should be necessary, BUT there is a caveat:
  As mentioned in 37.6.8, we only get the value loaded in RA if not loaded since the last trigger,
  or RB has been loaded. Since I do not want to trigger as that sets the timer value to 0, I
  instead let register B be loaded when the pulse is going low.
  */
  TC_Configure(tc, channel, TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_LDRA_RISING | TC_CMR_LDRB_FALLING);

  // Set the interrupt flags. We want:
  // interrupt on overflow
  // TIOA6 (pin 5) going low
  const uint32_t flags=TC_IER_COVFS  | TC_IER_LDRBS;
  tc->TC_CHANNEL[channel].TC_IER=flags;
  tc->TC_CHANNEL[channel].TC_IDR=~flags;

  NVIC_EnableIRQ(irq);

  //start the timer
  TC_Start(tc, channel);
}

void setup()
{
  Serial.begin(115200);
  //pinMode(13,OUTPUT);

  //pinMode(2, OUTPUT);
  //digitalWrite (2, 0);

  startTimer();


}

void loop()
{

  //does nothing, just connect a pulse generator on the pin 5 to launch interruptions and print them

}

ard_newbie


Here is an other example of a Timer Capture. Although, if you need to capture pulses at relatively "high" frequencies (> 1 MHz), I suggest to use a polling mode rather than an interrupt.

See this thread, reply #3:
https://forum.arduino.cc/index.php?topic=459707.0

To capture more pulses, you will need to understand how works the Timer Counter controller, and use the Greynomad pinout diagram.

Rouletabille

Hi !
In fact I won't have interruption very often (maybe 1 in 1 week if we except noise so ...) but I need to understand this code to do it in the same time with the 6 time counters of the Arduino due (for 6 different inputs).
Day after day (by looking during hours the datasheet, the schematics and recently https://www.arduino.cc/en/uploads/Main/arduino-Due-schematic.pdf) I understand better and better how it works but I never programed a micro controller at a "deep" lever (I mean without the Arduino classic functions), for instance something like that
Code: [Select]
  TC_Configure(tc, channel, TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_LDRA_RISING | TC_CMR_LDRB_FALLING);

  // Set the interrupt flags. We want:
  // interrupt on overflow
  // TIOA6 (pin 5) going low
  const uint32_t flags=TC_IER_COVFS  | TC_IER_LDRBS;
  tc->TC_CHANNEL[channel].TC_IER=flags;
  tc->TC_CHANNEL[channel].TC_IDR=~flags;

  NVIC_EnableIRQ(irq);

  //start the timer
  TC_Start(tc, channel);
is really far from my knowledge, that's why I would need a book / lecture / video / tutorial on that please ^^''
Thanks !

ard_newbie


OK , here is an hint to begin with: Read the code I sent you and search in Sam3x Timer Counter section datasheet (section 36) the meaning of registers I use.

bobcousins

is really far from my knowledge, that's why I would need a book / lecture / video / tutorial on that please ^^''
Thanks !
Unfortunately, I don't know of any shortcuts. It's too detailed and specific for anyone to create training material. In this case, I got lucky by finding a similar example (google is your friend). But generally, I use a combination of  hardware data sheet, maunfacturer supplied framework (ASF in this case), examples supplied by the manufacturer or third parties, the schematic for the hardware. Reading the Arduino core for SAM3X is also useful as background, although they don't use timers in this mode.

It's a case of joining dots until the picture emerges, it comes with practice I guess.
Please ask questions in the forum so everyone can benefit. PM me for paid work.

Go Up