Go Down

Topic: Arduino Zero TCC Capture (Read 14952 times) previous topic - next topic

MartinL

#15
Jul 19, 2016, 11:01 am Last Edit: Jul 19, 2016, 11:02 am by MartinL
Hi Ruch,

As AloyseTech points out, it appears as though you've reached the limit SAMD21's capabilities, as the overhead of rapidly calling the interrupt service routine (ISR) is becoming too great for the processor to manage.

You also mention that you're planning to use an OLED display and GPS in your system. The GPS will also put a high demand on the processor, especially if you're parsing NMEA strings.

Is it necessary to use this magnetometer? Could you use one that uses an I2C or SPI bus for communication? This would significantly lighten the CPU load.

Rucus

#16
Jul 20, 2016, 04:46 am Last Edit: Jul 20, 2016, 05:36 am by Rucus
Thanks, AloyseTech and MartinL.

I am actually trying to disable the interrupt by issuing a detachInterrupt() right after the program enters the TC3 ISR, so the program only tries to read the output from magnetometer once every second one period (cycle) at a time in my hope to not get repetitive ISR events. I think it is worth checking though the detachInterrupt() function if there is any significant delay added to it before the actual write to the register to disable interrupt. I think what I am trying to do makes things more complicated.

What if instead of measuring the period of the signal, I actually implement a counter to count the number of pulses coming from the sensor and do a timer interrupt (using a separate timer module in SAMD) to read the count from another timer module? Basically I implement a free-running counter that I read at constant intervals in time and derive a transfer function to kind of convert those counts into equivalent intensity? I may need to reset the counters (I need 3 of them because I need to read data from 3 magnetometers) at every sampling time. So I think in this scheme I will be needing at least 4 timer modules from the SAMD. This solution kind of integrate the output from the sensor and I may lose some precision and speed there, but I think that will not matter much from what I am trying to do.

MartinL's question is a very good point. I wish I could use one that can talk via serial protocol. The thing is that I am tied to using these magnetometers because I need the extra sensitivity and directionality offered by these fluxgate magnetometer. A MEMS type is something I like to look into in the near future.

MartinL

#17
Jul 20, 2016, 10:18 am Last Edit: Jul 21, 2016, 09:22 am by MartinL
Depending on how often you need to read the magnetometer, the simplest way might be to set up your code so that it periodically activates the TC3 handler, takes a reading and then gets the handler to automatically switch itself off. That way this function won't be consuming all of the processor's time. I'm not sure if the SAMD21 can physically measure the 150kHz signal using TC capture, but if you keep your code in the handler to a minimum it might be worth a try?

It's possible to switch off interrupts from within the TC3 handler function using the timer's interrupt enable clear register (INTENCLR):

Code: [Select]
REG_TC3_INTENCLR = TC_INTENCLR_MC0;   // Disable period interrupts
To turn the handler function back on use the timer's interrupt enable set register (INTENSET):

Code: [Select]
REG_TC3_INTENSET = TC_INTENSET_MC0;  // Enable period interrpts

Rucus

Thanks, MartinL.

I inserted the statements above to my code and I am getting an error somehow. Looks like the TC_INTENCLR_M0 and TC_INTENSET_M0 defines do not exist. I changed them to MC0 and MC1 so the statements looks like this

Code: [Select]
REG_TC3_INTENCLR = TC_INTENCLR_MC1 | TC_INTENCLR_MC0;

and

Code: [Select]
REG_TC3_INTENSET = TC_INTENSET_MC1 | TC_INTENSET_MC0;

You are right, still not fast enough to cope up with the input pulses. I want to start experimenting with timer counters and see if I get any luck on it.

MartinL

#19
Jul 21, 2016, 09:22 am Last Edit: Jul 21, 2016, 02:48 pm by MartinL
Hi Ruch,

Thanks for the correction, I've amended it.

I ran a test with the TC capture code pared-down, measuring period only and managed to get it to work up to a maximum of 80kHz.

Rucus

Thanks, MartinL!

I am wondering if you managed in the past to make the Counter work by counting the pulses from an input pin?

-Ruch

MartinL

Hi Ruch,

Quote
I am wondering if you managed in the past to make the Counter work by counting the pulses from an input pin?
Do you mean just counting the pulses, rather than timing them?

Rucus

Hi MartinL,

Yes, counting the pulses via the Timer Counter module, and reading the COUNT value at fixed intervals in time (maybe every 1ms or so).

Thanks,
Ruch

MartinL

Hi Ruch,

The example code below uses the Zero's TC4 timer in 8-bit mode, that generates an interrupt every 1ms:

Code: [Select]
void setup() {
   // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  REG_TC4_CTRLA |= TC_CTRLA_MODE_COUNT8;           // Set the counter to 8-bit mode
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  REG_TC4_COUNT8_CC0 = 0x55;                      // Set the TC4 CC0 register to some arbitary value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  REG_TC4_COUNT8_CC1 = 0xAA;                      // Set the TC4 CC1 register to some arbitary value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  REG_TC4_COUNT8_PER = 0xFF;                      // Set the PER (period) register to its maximum value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);         // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  REG_TC4_INTFLAG |= TC_INTFLAG_MC1 | TC_INTFLAG_MC0 | TC_INTFLAG_OVF;        // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_MC1 | TC_INTENSET_MC0 | TC_INTENSET_OVF;     // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_MC1 | TC_INTENCLR_MC0 | TC_INTENCLR_OVF;     // Disable TC4 interrupts
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV64 |     // Set prescaler to 64, 16MHz/64 = 256kHz
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
}

void loop() {
  // put your main code here, to run repeatedly:

}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{    
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT8.INTFLAG.bit.OVF && TC4->COUNT8.INTENSET.bit.OVF)            
  {
    // Put your timer overflow (OVF) code here:    
    // ...
  
    REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }

  // Check for match counter 0 (MC0) interrupt
  if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0)            
  {
    // Put your counter compare 0 (CC0) code here:
    // ...
  
    REG_TC4_INTFLAG = TC_INTFLAG_MC0;         // Clear the MC0 interrupt flag
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC4->COUNT8.INTFLAG.bit.MC1 && TC4->COUNT8.INTENSET.bit.MC1)          
  {
    // Put your counter compare 1 (CC1) code here:
    // ...
  
    REG_TC4_INTFLAG = TC_INTFLAG_MC1;        // Clear the MC1 interrupt flag
  }
}

Rucus

#24
Jul 24, 2016, 10:08 pm Last Edit: Jul 24, 2016, 10:14 pm by Rucus
Thanks, MartinL!

I was trying to configure the GCLK module to use it as a clock source for TC3, but instead of using the DFLL48M generator, I'd like to hook it up to the GCLK_IO[6] so I can feed the output (pulses) of my sensor to that pin (PA22). The intent is to use the output pulses from my sensor as the new clock source for TC3 where it will count up for every clock cycle. The period of clock varies as a function of entity being sensed. The COUNT register is read and reset at fixed intervals of time, so I get various COUNT values depending on the intensity of entity being sensed. Basically, at higher intensities, the sensor will output more pulses (the period for each pulse cycle will be shorter), thus, clocking the TC3 module at higher speeds (I hope). Here is what the code looks like inside the setup() function:

Code: [Select]

  PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
 PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( 6 ) | //Route input to pin PA22 via PMUX. Pin 22 is Peripheral line #6 of the upper 16 of the Port Group
 PORT_WRCONFIG_INEN |                    //Enable input for the pin selected in PINMASK, WRPINCFG and HWSEL combination
 PORT_WRCONFIG_WRPINCFG |
 PORT_WRCONFIG_PINMASK( 1u << 6 ) ; //shift left by 6 to mask other pins except PA22
 
  uint32_t temp;
  temp = (PORT->Group[0].PMUX[6].reg) & PORT_PMUX_PMUXO( 0xF ); //get old values so we can append them when we re-write to this 32-bit reg
  PORT->Group[0].PMUX[6].reg = temp | PORT_PMUX_PMUXE( PORT_PMUX_PMUXE_H_Val ); //enable peripheral function for PA22, which is an even number port. Write back new value to register. PA22 is line #6 of the upper 16 port group
  PORT->Group[0].PINCFG[22].reg |= PORT_PINCFG_PMUXEN;    //enable MUX for pin PA22
 
  Serial.println("Done PORT config");
 
  /* Set GCLK */
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |    // Divide the 48MHz system clock by x
                    GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
  Serial.println("Done GCLK GENDIV config");
 
  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
 GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                     GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to generator input pad, set to PA22 in PORT module
                     GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
  Serial.println("Done GCLK GENCTRL config");
 
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK5 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  Serial.println("Done GCLK CLKCTRL config");
                   
  REG_TC3_READREQ = TC_READREQ_RREQ |             // Enable a read request
                    TC_READREQ_ADDR(0x10);        // Offset of the COUNT register
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization
  Serial.println("Done TC READREQ config");


The code above gets stuck at this statement

Code: [Select]
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);

There are 2 things I can think of why the code hangs:
1. The PORT is not properly configured, causing the SYNCBUSY bit to "wait" infinitely for the clock where the APB should sync to.
2. The TC3 simply doesn't want to accept unstable clock source. As mentioned above, the clock period changes as a function of intensity of entity being sensed, so basically it changes over time.

I used a stable clock generator in place of the sensor, however, it seems that the code still gets stuck on the line above. So most likely the problem is on my port configuration, or a bit of both. Any idea on why my code doesn't work?

P.S. The code seem to work when I change the clock source to DFLL48M from GCLKIN. The COUNT register is updated and I can manipulate the clocking speed by changing GCLK_GENDIV_DIV(1).

-Ruch

MartinL

#25
Jul 25, 2016, 11:11 am Last Edit: Jul 25, 2016, 11:27 am by MartinL
Hi Ruch,

Regarding the port pin set-up, you don't need to use both the WRCONFIG and PINCFG registers. WRCONFIG is designed to configure a block of port pins, whereas PINCFG can be used to configure them individually.

To configure PA22, which happens to be the Arduino Zero's SDA pin, you could just use the following:

Code: [Select]
// Output the clock on the SDA pin (chosen as it happens to be one of generic clock 6's IO)
// Enable the port multiplexer (mux) on the SDA pin
PORT->Group[g_APinDescription[SDA].ulPort].PINCFG[g_APinDescription[SDA].ulPin].bit.PMUXEN = 1;

// Switch port mux to peripheral function H - generic clock I/O
// Set the port mux mask for even port pin number, SDA = PA22 = 22 is an even number, PMUXE = PMUX Even
PORT->Group[g_APinDescription[SDA].ulPort].PMUX[g_APinDescription[SDA].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;

The g_APinDescription array maps from Arduino to port pins. The PMUX "ulPin" value is shifted right by 1 to divide the SDA value (port pin 22) by 2 (in this case the result is 11). This is because there are 32 pins, but only 16 PMUX registers (containing odd and even pin pairs) per port.

The next point is that as you're using port pin PA22 (SDA), you'll need to use generic clock (GCLK) 6, as GCLK_IO[6] refers to this GCLK, rather than 5.

Rucus

Hello MartinL,

Thanks for your response. Thanks also for pointing out the usage of WRCONFIG and PINCFG, something I did not realize at first. The code you attached works perfectly! The code is able to count the pulses fed to the input pin (PA22).

Out of curiosity, I played around WRCONFIG register just for the sake of trying to make it work. I finally got it to work using the code below:

Code: [Select]
PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( 7 ) | //PMUX set to H alternate function
PORT_WRCONFIG_INEN |
PORT_WRCONFIG_WRPINCFG |
PORT_WRCONFIG_PINMASK( 0x40 ) ; //Mask other pins except PA22


Thanks for your help, MartinL! I greatly appreciate it.

-Ruch

Rucus

Hi,

I am now trying to configure the TCC modules to count up using external clock fed through one of the pins. Basically, what I am trying to do is to count using the TCC module instead of TC module.

Below is what my initialization code looks like:

Code: [Select]
PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( PORT_PMUX_PMUXE_H_Val ) | //PMUX set to H alternate function
PORT_WRCONFIG_INEN |
PORT_WRCONFIG_WRPINCFG |
PORT_WRCONFIG_PINMASK( 0x10 ) ; //Mask other pins except PA20
Serial.println("Done PORT config");

/* Set GCLK */
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(10) |    // Divide the input clock by (x)
GCLK_GENDIV_ID(4);      // Set division on Generic Clock Generator (GCLK) 6
while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
Serial.println("Done GCLK GENDIV config");

REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN |         // Enable GCLK 4
GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to generator input pad, set to PA20 in PORT module
GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
Serial.println("Done GCLK GENCTRL config");

REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK4 |     // ....on GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1;    // Feed the GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
Serial.println("Done GCLK CLKCTRL config");

REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR;                     // Set register for counter to count up
while (TCC0->SYNCBUSY.bit.CTRLB);       // Wait for (write) synchronization
Serial.println("Done TCC CTRLB config");

REG_TCC0_COUNT = 0x0000;               // Clear timer's COUNT value
while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
Serial.println("Done TCC COUNT cleared");

REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |      // Set timer prescaler
TCC_CTRLA_ENABLE;               // Enable TC6
while (TCC0->SYNCBUSY.bit.ENABLE);       // Wait for synchronization
Serial.println("Done TCC CTRLA config");
Serial.println("Done configuring counters");


My code gets stuck at this line:

Code: [Select]
while (TCC0->SYNCBUSY.bit.CTRLB);       // Wait for (write) synchronization

Comparing between TC and TCC modules, I have noticed a few things. With TC module, I have to implicitly "tell" TC through the READREQ register on what register I want to synchronize to. This register seem non-existent in the TCC module. I am wondering if is there a need to tell TCC module that I want to synchronize on one of its registers? If so, what register should I manipulate?

The second thing I noticed is that with TC module, you can just poll the SYNCBUSY bit from the STATUS register and wait until the sync is done for any writes to registers. With TCC, there is a SYNCBUSY register that contains individual sync flags for each fields that needs synchronization. I am wondering if I am polling the sync bits correctly in my code above?

Thanks,
Ruch

MartinL

#28
Jul 28, 2016, 10:00 am Last Edit: Jul 28, 2016, 10:14 am by MartinL
Hi Ruch,

The issue might be to do with the line:

Code: [Select]
REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR;                     // Set register for counter to count up
The TCC CTRLB has two registers CTRLBSET and CTRLBCLR. The purpose of having a SET and CLR register, is that it avoids your code having to do a read-modify-write operation each time you access it. This operation is instead done by the processor in hardware, thereby saving time. It makes having to logically OR the register with the bitmask to set, or logically AND the register with the inverse bitmask to clear unnecessary.

To modify the CTRLB register to count up, (clear the DIR bit), without altering any other bits in the register, you just need to remove the logical OR:

Code: [Select]
REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR;                     // Clear DIR bit for counter to count up
Quote
With TC module, I have to implicitly "tell" TC through the READREQ register on what register I want to synchronize to. This register seem non-existent in the TCC module. I am wondering if is there a need to tell TCC module that I want to synchronize on one of its registers? If so, what register should I manipulate?
Luckily with the exception of reading the COUNT register, there's no need to do any read synchronization with the TCC timer registers.

Quote
I am wondering if I am polling the sync bits correctly in my code above?
Your polling of the sync bits looks correct.

Quote
Comparing between TC and TCC modules, I have noticed a few things.
I also find the differences between the TCC and TC modules a bit strange. I looks as though they were designed by two separate teams of engineers.

Rucus

Thanks, MartinL!

Your suggestion solved a part of the problem. I found out that no clock actually reaches the TCC0 module. I don't know what happened, looks like some problem again with my PORT configuration. When I changed the clock source to something working in the past (GLCK_6), the code is now able to continue which to me is an indication that the TCC0 module is now being clocked. However, another issue came about. It seems that COUNT register only contains zero. My code calls the function below every 1 s (called from the loop()).

Code: [Select]
void ReadCountValTCC()
{
if(TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)
{
Serial.println("OVF on TCC0!");
}
else
{
Serial.print("TCC0: ");
Serial.println(TCC0->COUNT.reg, DEC);
TCC0->COUNT.reg = 0x0000; //clear count reg
while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
}
}


TCC0 always read as '0'.

TCC0: 0
TCC0: 0
TCC0: 0
....


I read from the specs that the counter will count up to the TOP value before resetting to ZERO value. From what I understand, the TOP value can be set from the PER, but PER default value is already 0xFFFFFF, in which case the counter should be able to count up to 2^24. Did I miss to set a register to make the TCC0 module count up?

Thanks,
Ruch

Go Up