Go Down

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

jpedrego

Update ( I thought of sharing it with you as I go along):

There was some silly mistakes in the code above. Now I am getting something that make sense with the following code:
Code: [Select]

volatile uint32_t CaptureCountA, CaptureCountB;
volatile boolean CaptureFlag
const int max_i = 64;
int i, Buff1[max_i];
void setup() {
  Serial.begin(115200);                                   // initilize serial port to 250000 baud

  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
                              | TC_CMR_LDRB_FALLING;       // load RB on falling edge of trigger input

  TC0->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // 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_DisableIRQ(TC1_IRQn);
  //NVIC_ClearPendingIRQ(TC1_IRQn);
  NVIC_SetPriority(TC1_IRQn, 0);                      // Give TC1 interrupt the highest urgency
  NVIC_EnableIRQ(TC1_IRQn);                             // Enable TC1 interrupts

  // print the startup header
  Serial.println("Timer Capture");
  i = 0;
}

void loop() {
  if (i < max_i){
    if (CaptureFlag) {
      i+=1;
      CaptureFlag = 0;
      Buff1[i] = CaptureCountA;
    }
  }
  else if(i==max_i){
    i+=1;
    Serial.println("The measurements are:");
    for (int j = 0; j <= max_i; j++) {
      printf("\r %d \n", Buff1[j]);
    }
  }
}

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
  }
  else if ((status & TC_SR_LDRBS) == TC_SR_LDRBS) { // If ISR is fired by LDRBS then ....
    CaptureCountB = TC0->TC_CHANNEL[1].TC_RB;         // get data from caputre register B for TC0 channel 1
  }
}


The above works up to 200KHz square wave. At 300KHz, I get twice the correct number most of the time, so it is missing one period.

Comments/Questions:
- why the code is missing periods? My guess is that the interrupts are eating time. I imagine that this is linked to the point made by ard_newbie in  reply 1 of:
https://forum.arduino.cc/index.php?topic=602555.0

- Another thing I do not understand is that, if I comment the:
Code: [Select]
  else if ((status & TC_SR_LDRBS) == TC_SR_LDRBS) {
    CaptureCountB = TC0->TC_CHANNEL[1].TC_RB;
  }


then the code stops. I tried to comment, in addition,  | TC_CMR_LDRB_FALLING;
the same, the code stops.

Is there a way so the code only go to interrupts on the rising edge? It will halve the number of interrupts, and it will also imply less logic on the code...

Another option, again suggested by ard_newbie on the reply 1 of:
https://forum.arduino.cc/index.php?topic=602555.0

is to forget about the interrupts and do code blocking. However, mot sure how to do this in this context, not even sure if it is possible: the Atmel documentation call it "programmable event"... not sure how to interpret it in this context...


In any case, I still need to digest all the information provided by ard_newbie in his post and links... I really find my limited knowledge of C (I am a Fortran90 lover) as a barrier as sometimes it is just the code itself which confuse me...

In any case, work in progress...

ard_newbie

#16
May 23, 2019, 10:38 am Last Edit: May 23, 2019, 10:45 am by ard_newbie
The path I suggested in reply #14 is not fast enough. In fact, since the minimum timelapse between 2 consecutive Photo_Multiplier signals is 45 ns (~ 3 clock cycles at 84 MHz), and the maximum is ? (we don't know), it should be very complex to get a relevent measure.

The only path I can think of now is to leverage the CPU timer counter SysTick (SysTick increments at each and every clock cycle), and as far as possible avoid interrupts.

SysTick counts down from a Load value to 0 and so on. The default Load value upon a DUE is 84000, corresponding with an interrupt fired by SysTick_Handler() every 1 ms. To reduce the number of interrupts fired by SysTick (since I guess we don't need delay(), millis() and micros()), you can Load SysTick with a maximum value of 2 Pow 24. In fact I will choose a bit less (16716000 for precisely 199 ms).

You will have to modify Winterrupts.h to be able to write your own PIO_Handler code because attachinterrupts is too slow. I have explained how to do that in a previous reply#14.

The Reset/Trigger signal will trigger an interrupt, inside this interrupt there is a Reset of SysTick.

Use a blocking code to detect a Rising edge of the Photo_Multiplier pin and log the number of clock cycles since the last Reset/Trigger Signal.

Connect pull down resistors to the 2 input signals.

Code: [Select]

// Reset/Trigger signal connected to PC1
// Photo Multiplier connected to PD1
/*************************************************************************************
  void SysTick Handler ( void) {
  i f ( sysTickHook ( ) ) return ;
  . . .
  }
*************************************************************************************/
//#pragma GCC optimize ("-O3")
#define INT_MASK_TS   (PIO_PC1)
#define INT_MASK_PHM  (PIO_PD1)

volatile uint32_t msCounter;
// sysTickHook will be triggered by sysTick_Handler once per 199 ms
extern "C" {
  int sysTickHook(void) {
    msCounter++;
    return 0;
  }
}
void setup() {
  Serial.begin(250000);
  pinMode(26, INPUT);  // PD1
  SysTick->LOAD = 16716000; // For 199 ms
  pioc_setup();
}

void loop() {
  
  const uint32_t Offset = 1; // Adjust this value
  const uint32_t BufferSize = 100;
  static uint32_t Index;
  uint32_t Buffer[BufferSize];

  // Todo: print Buffer values after measurement
  while (true)
  {
    while (!(PIOD->PIO_PDSR & INT_MASK_PHM)); // Wait until Photo Multiplier is high
    Buffer[Index++]  = (msCounter * 16716000 ) + (16716000 - SysTick->VAL) - Offset;
  }
}
/*******************************************************************/
void pioc_setup()
{
  PMC->PMC_PCER0 = PMC_PCER0_PID13;   // PIOC power ON
  
  PIOC->PIO_IDR = ~(0ul);             // disable all PIOC interrupts
  PIOC->PIO_PER = INT_MASK_TS;        // enable paralel input - output
  PIOC->PIO_IFER = INT_MASK_TS;       // enable glitch filter (1/2 clock cycle glitches discarted)
  PIOC->PIO_AIMER =   INT_MASK_TS;    // The interrupt source is described in PIO_ELSR
  PIOC->PIO_FRLHSR = INT_MASK_TS;     // Interrupt source on a Rising edge detection
  PIOC->PIO_IER = INT_MASK_TS;        // enable interrupt trigger from INT_MASK_TS pin
  NVIC_EnableIRQ(PIOC_IRQn);
}

// Reset/Trigger signal connected to PC1
void PIOC_Handler()
{
  PIOC->PIO_ISR;
 // if (PIOC->PIO_PDSR & INT_MASK_TS)  // useless because the only one
 // {
    SysTick->VAL = 0; // Reset SysTick
    msCounter = 0;
 // }
}


jpedrego

I just came across this:
https://github.com/manitou48/DUEZoo/blob/master/isrperf.txt

It is a bit old, but probably still valid. I imagine that what he/she means by "modified kernel ISR" is the same modification of the "winterrupts.c" as proposed by ard_newbie

Assuming that my measurement can only be done by interrupts, then I will never be at a 42MHz rate! At least I can aim for a 42MHz time resolution with much lower counting rates... but this kind that misses the point of what I was trying to achieve...

By the way, I only had to modify the line:
TC0->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // Trigger interruption on Load RA and load RB

To trigger interruption on Load RA

I still not know how to implement it without interrupts...


In any case, I feel have learn a fair amount about the DUE and its potentials in the last two days ;-)


ard_newbie

#18
May 23, 2019, 11:35 am Last Edit: May 23, 2019, 11:38 am by ard_newbie
Since you can't have 2 blocking codes and you need the best possible granularity (a 84 MHz counter = Systick Counter), at least one interrupt is necessary. The number of clock cycles to enter a PIO_Handler() is between 8 and 9 clock cycles, therefore the "delay" is more or less known.

A refinement for the sketch provided in reply #16 would be to copy continuously PIOD->PIO_PDSR into a status variable with a DMA and a linked list item, test this variable upon INT_MASK_PHM and maybe:

while (!(status & INT_MASK_PHM));

would be 2 or 3 clock cycles shorter.

jpedrego

#19
May 24, 2019, 11:34 am Last Edit: May 24, 2019, 11:36 am by jpedrego
Dear all,

I do not much time now, but a I though a quick update was in order.

The code using the code blocking (I did copy-paste) is given me non-sense: it is triggering far too often (see attached figure, 'counter_code_blocking.png'). (I did not implement the pull-down resistors as suggested, but I would be surprised if this is the reason...)
Any idea why? Do you really think that the pull-down resistor could help? In any case, it will have to wait until Monday now...


However, I have modified the version using interrupts and reduced the logic to a minimum and storing the SysTick to have a better time resolution. I have also introduced a while(True) to avoid the main Arduino Loop (it really made a difference!). With the code below, I was able to acquire with 12ns resolution a 1MHz square wave. There were some points that did not have the right counting, but overall is quite good (see attached figure 'counter_1MHz_12ns.png').

I have not introduced yet the reset of the counter by an external signal.


The code used is:

Code: [Select]

volatile uint32_t CaptureCountA;
const int max_i = 64;
int i, Buff[max_i];

void setup() {
  Serial.begin(250000);                                    // initilize serial port to 250000 baud

  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
                              | TC_CMR_LDRB_FALLING;       // load RB on falling 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_SetPriority(TC1_IRQn, 0);                           // Give TC1 interrupt the highest urgency
  NVIC_EnableIRQ(TC1_IRQn);                                // Enable TC1 interrupts

  Serial.println("Timer Capture");
  i = 0;
  SysTick->LOAD = 16716000; // For 199 ms
}

void loop() {
while (true){
  if (i < max_i){
    if (CaptureCountA!=0) {
      Buff[i] = CaptureCountA;
      CaptureCountA = 0;
      i+=1;
    }
  }
  else if(i==max_i){
    i+=1;
    Serial.println("And...");
    for (int j = 0; j <= max_i; j++) {
      printf("\r %d \n", Buff[j]);
    }
  }
}
}
void TC1_Handler() {
    TC0->TC_CHANNEL[1].TC_SR;   // Read & Save satus register -->Clear status register
    TC0->TC_CHANNEL[1].TC_RA;   // get data from capture register A for TC0 channel 1
    CaptureCountA = SysTick->VAL;
}


Have a nice weekend!

Go Up