Go Down

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

quichedood

#60
May 26, 2017, 12:52 pm Last Edit: May 26, 2017, 02:05 pm by quichedood
Damn you're right, thanks for your help !
I download last datasheet version (Microchip) and found the information.

I double checked every line in my code but can't figure it out.
I made some modifications of course :

Set clock on the TC4 timer
Code: [Select]
 // 32 bits counter (TC3 + TC4 16 bits counters / TC4 is "master")
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ;
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync


Use TC4 (TC3 will be used as a slave)
Code: [Select]
TcCount16* TC = (TcCount16*) TC4; // get timer struct

Change interrupt vector
Code: [Select]
NVIC_EnableIRQ(TC4_IRQn);

Code: [Select]
void TC4_Handler() {
  TcCount16* TC = (TcCount16*) TC4; // get timer struct
  TC->INTFLAG.bit.MC0 = 1; // writing a one clears the ovf flag of MC0 (P.624)
[...]


The value I set in CC register doesn't seem to be saved as a 32 bits integer.
Interrupts go faster when I set CC to 65535 compare to 1000000

Anyway, i keeping reading this datasheet, if you have any idea, you're welcome :)


The whole code :
Code: [Select]
// 32 bits counter (TC3 + TC4 16 bits counters / TC4 is "master")
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ; // Register CLKCTRL (Page 119) / 0x1C = TC4+TC5
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync
  //REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_ID_TCC2_TC3) ; // Register CLKCTRL (Page 119) / 0x1B = TCC2+TC3
  //while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC4; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC (to enable configuration mode)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;  // Set Timer counter Mode to 32 bits (Page 552)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as  Match Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;   // Set prescaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // CC0
  TC->CC[0].reg = 655350; // 18743 OK
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0 (P. 622)

  // Enable InterruptVector
  NVIC_EnableIRQ(TC4_IRQn); // interrupt > TC4_Handler(){}

  TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

quichedood

Think I just found, timer struct is 32 bits not 16 !
Code: [Select]
TcCount32* TC = (TcCount32*) TC4;

I confirm this in a few minutes/hours ;)

quichedood

Everything look fine, here is code :

Code: [Select]
  /*
      First timer (TC4)
      a 100ms interrupt
      Prescaler = 1, Preload = 4798000, Source clock = 48000000
      Counter on 32 bits > 2x 16 bits counters : TC3 (slave) and TC4 (master)
  */
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ; // Register CLKCTRL (Page 119)
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount32* TC = (TcCount32*) TC4; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC (to enable configuration mode)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;  // Set Timer counter Mode to 32 bits (Page 552)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as  Match Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1;   // Set prescaler (Page 551)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // CC0 Register (preload)
  TC->CC[0].reg = 4798000;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0 (P. 622)

  // Enable InterruptVector
  NVIC_EnableIRQ(TC4_IRQn); // interrupt > TC4_Handler(){}

  TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync


Code: [Select]
void TC4_Handler() {
  TcCount32* TC = (TcCount32*) TC4; // get timer struct
  TC->INTFLAG.bit.MC0 = 1; // writing a one clears the ovf flag of MC0 (P.624)
[...]


The last thing, I want a 100msec interrupt, but setting the preload to 4 800 000 makes the counter a little bit slower than real time.
Is there a way to caculate the exact preload to use in my case ?

MartinL

Hi quichedood,

The issue here is that the Zero's 48MHz clock is an integer multiple of its external 32.768kHz crystal.

Forum member Manitou48 calculated the crystal and resonator accuracy for quite a number of development boards: https://github.com/manitou48/crystals/blob/master/crystals.txt

So the frequency you're actually getting is 32768Hz * 1464 = 47972352Hz.

This means the 48MHz clock is 576┬Ás off every second.


quichedood

#64
May 26, 2017, 06:08 pm Last Edit: May 26, 2017, 06:28 pm by quichedood
Very interesting, but even with 4797235 (1464*32768/10) in CC I see a drift after 10 minutes.
I'll create a new dedicated topic to keep this thread clean ;)

EDIT : here it is http://forum.arduino.cc/index.php?topic=479806.0

borispavlov

Hi All
Thanks a lot to all contributors to this thread and mostly MartinL.

I am trying to use "STOP" command with TCC0 on Arduino Zero.
My understanding from the data sheet is that the counter should only stop counting but not cleared after the "STOP" command is issued.

I copied MartinL (thanks) code from post 34 that reads the counter and displays it.
My modification is that i use Serial instead of SerialUSB and do add the "STOP" command.

The code works and reads 3 TCC0 counter values. One is before the STOP command and 2 after the STOP command.

I am expecting to see the same value in the counter after the STOP but for some reason it is always "0" after the STOP as if the STOP command also clears the counter.

How can i read the counter after i stop it before it is cleared?

Code is attached


MartinL

Hi borispavlov,

Once you've stopped the TCC0 timer, you don't need to use READSYNC anymore.

I've just deleted the READSYNC lines in the code:

Code: [Select]
// Setup TCC0 counter and display the COUNT register continuously in the console
void setup()
{
  Serial.begin(115200);
  while(!Serial);
  
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz system clock by 1 = 48MHz
                    GCLK_GENDIV_ID(4);            // Set division on Generic Clock Generator (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 GCLK 4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                     GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  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
  
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Set prescaler to 1, 48MHz/1 = 48Mhz
                    TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop()
{
delay(5000);  //long delay to allow to open Serial port if not open already
  
  REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC;  // Trigger a read synchronization on the COUNT register
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for the CTRLB register write synchronization
  while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register read sychronization
  Serial.print("TCC0_1: ");
  Serial.println(REG_TCC0_COUNT, HEX);         // Print the result
  //REG_TCC0_COUNT = 0x5;                           // Set the COUNT register to 5
  //while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register write sychronization

delay(10);

  REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_STOP;  // Trigger a STOP on the COUNT register
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for the CTRLB register write synchronization
  //while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register read sychronization

  //REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC;  // Trigger a read synchronization on the COUNT register
  //while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for the CTRLB register write synchronization
  while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register read sychronization
  Serial.print("TCC0_2: ");
  Serial.println(REG_TCC0_COUNT, HEX);         // Print the result

delay(10);

  //REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC;  // Trigger a read synchronization on the COUNT register
  //while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for the CTRLB register write synchronization
  while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register read sychronization
  Serial.print("TCC0_3: ");
  Serial.println(REG_TCC0_COUNT, HEX);         // Print the result

  delay(6000000);       //very long delay
}

The program now outputs:

TCC0_1: 4E0DD0
TCC0_2: 4E0DD0
TCC0_3: 4E0DD0


borispavlov

Thanks MartinL for the prompt response. The code is working now but i do not understand completely.

Can you explain why all 3 TCC0 values are identical?
I would expect second and third to be identical but different from the first.

After the first value TCC0_1 is obtained, then there is "delay(10)" during which the counter should be still counting up before the STOP command. Values TCC0_2 and TCC0_3 are obtained after the STOP command.
and should be identical, but different than value TCC0_1.

 

MartinL

#68
May 27, 2017, 06:32 pm Last Edit: May 27, 2017, 06:32 pm by MartinL
Hi borispavlov,

Quote
Can you explain why all 3 TCC0 values are identical?
I've checked the SAMD21 datasheet, but unfortunately it doesn't explain the TCC0's behaviour.

As you've discovered, it appears as though it's not possible to use READSYNC to read the TCC0's COUNT register once the timer's been stopped.

The only alternative I found was to read the TCC0's COUNT register using a READSYNC request, before subsequently issuing a STOP command.


borispavlov

Hi MartinL,

I appears that READSYNC does stop the timer, but not the STOP command.

I removed the STOP command from the code and i still get 3 identical values.

What is even more confusing is that if the loop is executed again these 3 values do change.
Looks like the counter is stopped by the READSYNC command while inside the loop and starts counting after it exits the loop and then it stopped again by the "READSYNC" after re-entering the loop.

Is this possible?

Code with removed "STOP" command included.

 

MartinL

#70
May 27, 2017, 11:59 pm Last Edit: May 28, 2017, 12:23 am by MartinL
Hi borispavlov,

Quote
I appears that READSYNC does stop the timer, but not the STOP command.
Going back to post #34 of this thread. The code there uses READSYNC to sucessfully read the timer's COUNT register. So using READSYNC works when the timer is running.

The strange behaviour is occuring when the timer is stopped. I think what I said about commenting out READSYNC, in retrospect is wrong. The weird thing is if you include the READSYNC code and the timer's stopped, the COUNT register returns zero.

Nomally if you slow down the GCLK to the timer peripheral, it slows down the whole timer module, including the speed of register access and synchronization. Maybe stopping the timer switches off the GCLK to the COUNT register, but this also prevents it from being read?

Or perhaps there is a way? We just haven't found it yet.


borispavlov

Hi MartinL,

Thanks a lot for spending time on this issue.

Here is what i found regarding the STOP command:

""
static void tcc_stop_counter   (   const struct tcc_module *const    module_inst   )   
inline  static
Stops the counter.
This function will stop the counter. When the counter is stopped the value in the count register is set to 0 if the counter was counting up, or maximum or the top value if the counter was counting down.

""

This is from this website regarding SAMD21 and is at the very end of the website page:

" " "
http://asf.atmel.com/docs/latest/samr21/html/group__asfdoc__sam0__tcc__group.html#gab600907da31e721cef4eb32d2b8afccf

" " "

So the "0" values after the "STOP" command is expected and normal.





And here is what i found regarding the "READSYNC" command from the same website:


" " "
uint32_t tcc_get_count_value   (   const struct tcc_module *const    module_inst   )   
Get count value of the given TCC module.
Retrieves the current count value of a TCC module. The specified TCC module can remain running or stopped.

" " "

So a very smart Atmel engineer wrote that when using "READSYNC" the counter  can remain running or stopped without providing more information.

So the identical values after the "READSYNC" are expected but only in some so far unknown register pre-settings. 

Well "can" does not clarify this and i would like to know under what setting of what register  "READSYNC" bit will cause the counter to remain in "stopped" and under what condition it will be running.

May be you will have better better luck and figure this out.


The above info can be found on the website by searching (Ctrl-F) for "tcc_get_count" and " tcc_stop_counter".





MartinL

#72
May 29, 2017, 12:32 pm Last Edit: May 29, 2017, 12:49 pm by MartinL
Hi borispavlov,

Thanks for the link to the ASF (Atmel Software Framework) functions.

Looking at the source code for the "tcc_stop_counter" function:

Code: [Select]
/**
 * \brief Stops the counter
 *
 * This function will stop the counter. When the counter is stopped
 * the value in the count register is set to 0 if the counter was
 * counting up, or maximum or the top value if the counter was counting
 * down.
 *
 * \param[in]  module_inst   Pointer to the software module instance struct
 */
static inline void tcc_stop_counter(
    const struct tcc_module *const module_inst)
{
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    /* Get a pointer to the module's hardware instance */
    Tcc *const tcc_module = module_inst->hw;
    uint32_t last_cmd;

    /* Wait until last command is done */
    do {
        while (tcc_module->SYNCBUSY.bit.CTRLB) {
            /* Wait for sync */
        }
        last_cmd = tcc_module->CTRLBSET.reg & TCC_CTRLBSET_CMD_Msk;
        if (last_cmd == TCC_CTRLBSET_CMD_NONE) {
            break;
        } else if (last_cmd == TCC_CTRLBSET_CMD_STOP) {
            /* Command have been issued */
            return;
        } else if (last_cmd == TCC_CTRLBSET_CMD_RETRIGGER) {
            /* Cancel RETRIGGER command and issue STOP */
            tcc_module->CTRLBCLR.reg = TCC_CTRLBCLR_CMD_Msk;
        }
    } while (1);

    /* Write command to execute */
    tcc_module->CTRLBSET.reg = TCC_CTRLBSET_CMD_STOP;
}

There's nothing special going on here, other than waiting for the CMD (command) bitfield to clear to 0 before proceeding with the STOP command.

I tested this code and sure enough the TCC0's COUNT register returns 0 when the timer's stopped.

However, this behaviour contradicts the SAMD21 datasheet that states (in section TCC Timer Stop Command and Event Action):

Quote
When a stop is detected while the counter is running, the counter will maintain its current value.
The datasheet also goes on to say that (in section TCC Timer Retrigger Command and Event Action):

Quote
If the re-trigger command is detected when the counter is stopped, the counter will
resume counting operation from the value in COUNT.
I've tested retriggering after stopping the timer, but contrary to the datasheet the output shows that the timer's actually been reset.

The discrepancy doesn't appear in the datasheet's errata either.

If you need to read the stopped timer's counter, then all I can suggest is using one of the TC peripherals instead.


dlabun

Martin,

Looping back to accuracy for a moment, would using an external 24Mhz crystal oscillator improve the accuracy of the internal clocks?

MartinL

Hi dlabun,

Quote
Looping back to accuracy for a moment, would using an external 24Mhz crystal oscillator improve the accuracy of the internal clocks?
I don't know enough about the SAMD21's SYSCTRL - System Controller module that configures the crystal and the digital frequency locked loop.

I imagine it should be possible to use a crystal frequency that's factor of 48MHz, such as an 8 or 12MHz to get a more precise 48MHz. However, you'd have to change the start-up sequence code and most likely the bootloader to make it compatible with Arduino.

Also, it's not possible to simply swap out the current crystal on the Arduino Zero, as any external multipurpose crystal (not 32.768kHz) has to connect to XIN and XOUT on port pins PA14 and PA15, rather than XIN32 and XOUT32 on PA00 and PA01.

Go Up