OneShot timer on pin rising edge

Hi,

For the Arduino Zero, I would like to generate a pulse signal on D20between 10us - 50us depended on certain calculated value in software. The pulse should start on pin D10 rising edge. What is the best way to achieve this as much as possible within the hardware timers?

Edit: the oneshot timer will be repeated frequently

I think I can use this code as basis: one shot timer with external trigger - Arduino Zero - Arduino Forum

  • How can I change the IO's to the one I would like to use?
  • Is there a way to improve the triggering by seperate pin ISR

Thanks!

I have a oneshot pulse triggered repeatedly by interrupt here

Hi LeCrAm,

When you say digital pin D20 on the Arduino Zero, do you mean port pin PA22, assigned as SDA?

Hi MartinL, sorry I forgot to mention, I'm actually using the SparkFun SAMD21 Mini Breakout: SparkFun SAMD21 Mini Breakout - DEV-13664 - SparkFun Electronics

Hi Juraj, thanks I will look into this! Do you think I can meet the performance as described above? Because Triac is mostly used around 50 or 60 Hz?

Hi LeCrAm,

Might I ask what port pin (for example PA08, PA09, etc...) pin D20 is on?

The SparkFun SAMD21 Mini Breakout's pin assignments are different from the Arduino Zero's.

Ok, thats pin PA22

Edit:
Or even better detailed info:

Hi LeCrAm,

The following code uses the event sytem via the External Interrupt Controller (EIC), to route a trigger source signal from PA18 (D10) to timer TCC0. Upon receiving the event, the TCC0 timer retriggers and outputs a 10us oneshot pulse on PA22 (SDA).

The code code also generates a 100us (10kHz) test output using timer TCC2 on PA16 (D11). To test just plug this pin into PA18 (D10).

Employing the event system is a more efficient way of routing the trigger signal to timer TCC0 than an interrupt service routine. In this instance, the event system generates a progagation delay of around 120ns.

Here's the code:

// Generate 10us Oneshot pusle on PA22 upon receiving rising edge trigger on PA18
// 100us (10kHz) trigger source generated on PA16
void setup()
{
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
 
  ////////////////////////////////////////////////////////////////////////////////////////
  // Genric Clock Initialisation 
  ////////////////////////////////////////////////////////////////////////////////////////
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK0 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK0 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  ////////////////////////////////////////////////////////////////////////////////////////
  // TCC2 Initialisation - generate pulse every 100us (10kHz) on port pin PA16
  ////////////////////////////////////////////////////////////////////////////////////////

  // Enable the port multiplexer on port pin PA16
  PORT->Group[PORTA].PINCFG[16].bit.PMUXEN = 1;
  // Set-up the pin as a TCC2/WO[0] peripheral on PA16
  PORT->Group[PORTA].PMUX[16 >> 1].reg |= PORT_PMUX_PMUXE_E;

  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set the TCC2 timer to normal PWM mode (NFRQ)
  while(TCC2->SYNCBUSY.bit.WAVE);                    // Wait for synchronization
 
  TCC2->PER.reg = 4799;                              // Set the period (PER) register for a PWM frequency of 10kHz (100us)
  while(TCC2->SYNCBUSY.bit.PER);                     // Wait for synchronization

  TCC2->CC[0].reg = 2400;                            // Set the counter compare 0 (CC0) register for a PWM duty-cycle of 50%
  while(TCC2->SYNCBUSY.bit.CC0);                     // Wait for synchronization

  TCC2->CTRLA.bit.ENABLE = 1;                        // Enable TCC2
  while (TCC2->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization

  ////////////////////////////////////////////////////////////////////////////////////////
  // TCC0 Initialisation - output 10us oneshot pulse upon receiving an event on PA22
  ////////////////////////////////////////////////////////////////////////////////////////
  
  // Enable the port multiplexer on port pin PA22 
  PORT->Group[PORTA].PINCFG[22].bit.PMUXEN = 1;
  // Set-up the pin as TCC0/WO[4] peripheral on PA22
  PORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set the TCC0 timer to normal PWM mode (NPWM)
  while(TCC0->SYNCBUSY.bit.WAVE);                    // Wait for synchronization           
  
  TCC0->PER.reg = 959;                              // Set the period (PER) register for oneshot pulse width
  while(TCC0->SYNCBUSY.bit.PER);                     // Wait for synchronization

  TCC0->CC[0].reg = 480;                             // Set the counter compare 0 (CC0) register for a pulse width of 10us
  while(TCC0->SYNCBUSY.bit.CC0);                     // Wait for synchronization
  
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Enable oneshot operation
  while(TCC0->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization

  TCC0->DRVCTRL.reg |= TCC_DRVCTRL_NRE4;   // Activate the Non Recoverable Fault Enable to drive pin low after oneshot
  
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Event System Initialisation - set up to allow TCC0 to receive incoming pulses on PA18
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  // Enable the port multiplexer on port pin PA18
  PORT->Group[PORTA].PINCFG[18].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on PA18
  PORT->Group[PORTA].PMUX[18 >> 1].reg |= PORT_PMUX_PMUXE_A;
  
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO2;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE2_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT2;                                // Disable interrupts on external interrupt 3
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
  
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);              // Set the event user (receiver) as timer TCC0, event 0
                    
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous                     
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_2) |    // Set event generator (sender) as external interrupt 2
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TCC0->EVCTRL.reg |= TCC_EVCTRL_TCEI0 |             // Enable TCC0 event 0 inputs                               
                      TCC_EVCTRL_EVACT0_RETRIGGER;   // Retrigger timer TCC0 on receiving event 
                                 
  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization 
}

void loop(){}

LeCrAm:
Hi Juraj, thanks I will look into this! Do you think I can meet the performance as described above? Because Triac is mostly used around 50 or 60 Hz?

it uses the event system as Martin's so there is no problem with frequency

Thanks both of you! I can confirm it works. I checked by scope. In the past with the 8-bit AVR I could dream setting the registers for counters, I'm having some more difficulty with the samd21.

Employing the event system is a more efficient way of routing the trigger signal to timer TCC0 than an interrupt service routine.

Thanks, I was also looking for best solution to prevent too much prop. delay.

This means that I'm able to control the pulse-width by changing the value:

TCC0->CC[0].reg = 480;
while(TCC0->SYNCBUSY.bit.CC0);

If I would like to regulate between 10us and 50us it means that I have to change TCC0->PER.reg to at least 480*5=2400, right?

Thanks again!

Hi LeCrAm,

If I would like to regulate between 10us and 50us it means that I have to change TCC0->PER.reg to at least 480*5=2400, right?

Just change the CC[0] register to 2400.

if, at a certain point, I would like to disable the "oneshot feature" temporally only for the pin D10 event, but still have the option to fire the oneshot timer manually by:

TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;                // Retrigger the timer's One/Multi-Shot pulse
while (TCC0->SYNCBUSY.bit.CTRLB);                               // Wait for synchronization

Which register I have to set?

I would like to disable the "oneshot feature" temporally only for the pin D10 event, but still have the option to fire the oneshot timer manually

In this case, just turn off the timer's event 0 action:

TCC0->EVCTRL.reg &= ~TCC_EVCTRL_EVACT0_Msk;  // Turn off the action on event 0

...and to turn it back on again:

TCC0->EVCTRL.reg |= TCC_EVCTRL_EVACT0_RETRIGGER;  // Turn on the retrigger action on event 0

Thanks for your support!

Above code doesn't seem to work, however if I use this, it does:

EIC->EVCTRL.reg &= ~EIC_EVCTRL_EXTINTEO2;

At the moment my code works quite ok using 2 timers, but I'm realizing that maybe there is a smarter way to achieve what I want:

I would like to output a 10KHz PWM signal on D20 with fixed small duty-cycle, until I get a variable fequency signal from D10 that will always be higher frequency than the 10KHz signal, lets assume 30KHz. I would like D20 to follow the falling edges of the 30KHz signal. I would like to change the duty-cycle in software.

Can you perhaps point me in the right direction?

Hi LeCrAm,

The line of code:

TCC0->EVCTRL.reg &= ~TCC_EVCTRL_EVACT0_Msk;  // Turn off the action on event 0

does turn off the events to the timer. It's just that I made a mistake in the initial post, corrected it, but perhaps this was too late.

In any case, as you mention the line:

EIC->EVCTRL.reg &= ~EIC_EVCTRL_EXTINTEO2;

does the same thing, but further upstream, as it blocks the event passing through the External Interrupt Controller (EIC) instead.

I would like to output a 10KHz PWM signal on D20 with fixed small duty-cycle, until I get a variable fequency signal from D10 that will always be higher frequency than the 10KHz signal, lets assume 30KHz. I would like D20 to follow the falling edges of the 30KHz signal. I would like to change the duty-cycle in software.

The issue is that a 10kHz continuous PWM signal doesn't know when the next 30kHz input pulse is coming. If it arrives too late, the 10kHz signal could have finished its timer cycle and started on the next one.

Using oneshot gets around this problem, since once the oneshot pulse has ended, the timer cycle is effectively over until it's retriggered again.

Ok clear.

I'm using a seperate 10KHz ISR routine to manually trigger the Oneshot timer.

Then I would like to reset (clear) the seperate actual counter value each time I get a trigger on D10. If the frequency of D10 is higher, this will prevent the 10KHz ISR routine fires. I have everything in place, my 10KHz timer code:

void init_start_timer()
{
// Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    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_COUNT16_CC0 = 0x12C0;                   // Set the TC4 CC0 register as the TOP value in match frequency mode
  while (TC4->COUNT16.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_OVF;              // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_OVF;             // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_OVF;          // Disable TC4 interrupts
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1024 = 46.875kHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency (MFRQ) mode
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization
  
}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{     
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT16.INTFLAG.bit.OVF && TC4->COUNT16.INTENSET.bit.OVF)             
  {
      // Manual triggering     
      if (TCC0->STATUS.bit.STOP)                                 // Check if the previous pulse is complete
      {
        TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer's One/Multi-Shot pulse
        while (TCC0->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization
      }
      
    REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }
}

Which register I have to control to reset the counter value to achieve the ISR routine not to fire?

Hi LeCrAm,

The easiest way is probably to set the TCC0's duty-cycle to 0, using the counter compare (CC0) register. The timer will retrigger as usual, but no pulse will be output:

TCC0->CC[0].reg = 0;             // Set the duty-cycle to 0
while(TCC0->SYNCBUSY.bit.CC0);   // Wait for synchronization

Hi Martin, maybe I didn't explained well, The only thing I would like to achieve is to prevent that TC4_Handler will be fired by resetting/clearing/restarting the counter with higher frequency signal, this means if this higher frequency signal is dissapears, the counter will not be resetted anymore and TC4 will fire with a frequency of 10KHz:

void init_start_timer()
{
// Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    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_COUNT16_CC0 = 0x12C0;                   // Set the TC4 CC0 register as the TOP value in match frequency mode
  while (TC4->COUNT16.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_OVF;              // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_OVF;             // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_OVF;          // Disable TC4 interrupts
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1024 = 46.875kHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency (MFRQ) mode
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization
 
}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{     
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT16.INTFLAG.bit.OVF && TC4->COUNT16.INTENSET.bit.OVF)             
  {
      // Manual triggering     
      if (TCC0->STATUS.bit.STOP)                                 // Check if the previous pulse is complete
      {
        TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer's One/Multi-Shot pulse
        while (TCC0->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization
      }
     
    REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }
}

I figured out in the meantime, by disabling/enabling again, the counter resets:

  // Reset 10KHz timer counter
  TC4->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; // disable
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                             // Wait for synchronization
  TC4->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; // enable
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                             // Wait for synchronization

I figured out in the meantime, by disabling/enabling again, the counter resets:

But disabling the timer will also float (or tri-state) its outputs.

It's possible to turn off the TC4_Handler function by clearing the overflow bit in the INTENCLR register:

TC4->COUNT16.INTENCLR.reg = TC_INTENCLR_OVF;  // Disable overflow (OVF) interrupts

Martin,

Thanks for your support!

An additional question, I'm using TCC0 for the oneshot functionality, is it also possible to use an additional/independent oneshot timer on TCC1? This oneshot timer on TCC1 should be triggered by software only, without external trigger by digital input.

The oneshot output should be D8 (PA06).