Arduino Forum

Products => Arduino Zero => Topic started by: ronin101 on Mar 27, 2020, 09:26 am

Title: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Mar 27, 2020, 09:26 am
Hello forum,
long time reader first writer as they say ;-)

I need to generate a very low duty cycle PWM ( like between 1 and 5 % ) on a Samd21 M0 and be able to phase shift it dynamically

(https://i.ibb.co/GpGNxKv/Screenshot-2020-03-26-at-13-57-15.png) (https://imgbb.com/)

I've tried every type of PWM generation using timer counters. I though the dual-slope critical pulse width modulation was the solution but after tinkering with it I realized that since both CCx control must be on each side of the TOP of the counter, I can't move a very thin voltage peak to the side, it only works with 50% duty cycles.

For example on the figure below, I can't set CC0 to 1 and CC2 to 2 and have a signal peak between le orange 1 and the orange 2, the peak will be between orange 1 and red 2. So I can't shift the signal peak to the left nor the right of the center.

(https://i.ibb.co/ZYhVZ2Y/Screenshot-2020-03-26-at-14-05-47.png) (https://imgbb.com/)

I'm pretty new at direct register manipulation but I have read the PWM part of the Samd21 extensively as well as this forum and I'm now stuck. I've read about circular buffer, dead time and RAMP alternatives but I don't understand those features enough to know if this the right direction.

Any info to put me in the right direction would be appreciated.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Mar 27, 2020, 11:29 am
Hi ronin101,

Unfortunately, there's no straightforward way to achieve this. The solution depends upon your application, but changing the waveform's phase requires changing it's period, using either the SAMD21's period PER or buffered period PERB registers. The buffered PERB register only takes effect on the next timer update, to prevent glitches from appearing on the waveform. Changes to the PER register take effect immediately.

Separation between the pulses can be achieved by adjusting the period in the timer's overflow (OVF) interrupt service routine. Alternatively, the RAMP2 functionality can be employed to interleave two PWM channels, one to generate the pulse the other the distance between them. If you're using a repeating waveform pattern then it's possible to use the Direct Memory Access Controller (DMAC) with a circular descriptor in conjuction with the timer. Examples of RAMP2 and DMAC operation are described here: https://forum.arduino.cc/index.php?topic=659141.0 (https://forum.arduino.cc/index.php?topic=659141.0).
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Mar 27, 2020, 03:14 pm
Thank you for your answer MartinL,

My application need is to synchronize from an input signal and be able to change
the phase between this input and my generated signal.

I'm already able to smoothly change the period using PERB so that my output signal
match the input signal period. Do you suggest changing the phase by updating
the period for only one loop, then returning to the input signal period ? This would
induce positive or negative delay from the input signal and induce shifting.

I didn't know the OVF interrupt feature, I'll check what it can do in the documentation.

Also thank you for confirming that RAMP2 may be a solution, I didn't want to start studying it
without any confirmation that it may be useful because it seems a bit daunting. I'll check the link
you provided.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Mar 27, 2020, 08:09 pm
Quote
My application need is to synchronize from an input signal and be able to change
the phase between this input and my generated signal.
Do the pulse widths of you input and generated signal need to match exactly?

Is the generated output waveform just a delayed version of the input?
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Mar 28, 2020, 11:29 am
Between input and output signals, the frequency must match but the pulse widths are totaly unrelated.
I must be able to set the width needed on the output but the input width is fixed and does not change.

Quote
Is the generated output waveform just a delayed version of the input?
Only in the sense that they have the same frequency and they are both basic PWM signals.
The output pulse width and phase have to be dynamicaly settable

I could however reingeneer my project for the output to be a delayed ( postivily or negativily )
version of the input but with a different pulse width. So if you have an idea in mind about that
I would be glad to hear it.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Mar 28, 2020, 04:07 pm
Hi ronin101,

It's possible to use the SAMD21's External Interrupt Controller (EIC) and Event System to route the input pulses to an internal TCC/TC timer. The edge of the pulses can be used to trigger the timer. This timer can then measure the required delay and in turn trigger another second timer on the Event System to output a PWM pulse.

The event system is just a 12-channel peripheral to peripheral highway that allows them to communicate with each other without CPU intervention.

Would this idea meet your project's requirements?
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Mar 29, 2020, 07:17 pm
Hi ronin101,

Here's an example of using the External Interrupt Controller (EIC), Event System and TC/TCC timers to generate a delay on the output.

The code uses a PWM test output of a 1ms pulse repeated every 4ms, or in other words a 4ms (250Hz) period with a 25% duty-cycle. This signal is generated on digital pin D8 with timer TCC1.

Connecting D8 to the input on digital pin D12, routes this signal through the EIC and on to timer TC4 (working in conjuction with TC5 in 32-bit mode) via the event system.

This signal retriggers TC4 from zero and sets it counting at 48MHz. When this timer reaches the value in it's Counter Compare CC0 register, TC4 also generates an event that's picked up by timer TCC0. Upon receiving TC4's event, TCC0 retriggers and outputs a single oneshot 1ms pulse on digital pin D3.

The delay is set by value of TC4's CC0 register, where:

Delay = CC0 / 48000000

Note that the timer peripherals don't require any CPU intervention during operation. The loop() function is empty.

Here the scope displays the input pulses (yellow) and the delayed output by 1ms (light blue):

(https://forum.arduino.cc/index.php?action=dlattach;topic=673283.0;attach=353929)

Here's the code:

Code: [Select]
// Setup timer TC4/TC5 to trigger on input event and set time delay for TCC0 output pulse, (TCC1 test input)
// Test output: D8, Input: D12, PWM Output: D3
void setup()
{
  // Generic Clocks (GCLK) //////////////////////////////////////////////////////////////////////////////
 
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK0 |    // Select the 48MHz GCLK0
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  // Set GCLK0 as a clock source for TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK0 |    // Select the 48MHz GCLK0
                      GCLK_CLKCTRL_ID_TC4_TC5;    // Set GCLK0 as a clock source for TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Port Multiplexing //////////////////////////////////////////////////////////////////////////////////
 
  // Enable the port multiplexer D3, D8 and D12
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[8].ulPort].PINCFG[g_APinDescription[8].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
 
  // Set-up the pin multiplexers
  PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;
  PORT->Group[g_APinDescription[8].ulPort].PMUX[g_APinDescription[8].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;

  // External Interrupt Controller (EIC) (Input) ///////////////////////////////////////////////////////////
 
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;         // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;    // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3;        // Clear the interrupt flag on channel 3
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                // Wait for synchronization

  // Event System /////////////////////////////////////////////////////////////////////////////////////////
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4 event
 
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(2) |                                // Attach the event user (receiver) to channel 1 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);              // Set the event user (receiver) as timer TCC0, event 1

  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_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 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_TC4_MCX_0) |       // Set event generator (sender) as TC4 Match Compare channel 0
                       EVSYS_CHANNEL_CHANNEL(1);                           // Attach the generator (sender) to channel 1

  TC4->COUNT32.EVCTRL.reg |= TC_EVCTRL_MCEO0 |               // Output event on Match Compare channel 0
                             TC_EVCTRL_TCEI |                // Enable the TC event input
                             //TC_EVCTRL_TCINV |             // Invert the event input
                             TC_EVCTRL_EVACT_RETRIGGER;      // Set event to RETRIGGER timer TC4
 
  TCC0->EVCTRL.reg |= //TCC_EVCTRL_TCEI1 |                     // Enable the TCC event 1 input
                      TCC_EVCTRL_TCEI0 |                     // Enable the TCC event 0 input
                      //TCC_EVCTRL_TCINV1 |                    // Invert the event 1 input
                      //TCC_EVCTRL_TCINV0 |                   // Invert the event 0 input                     
                      TCC_EVCTRL_EVACT0_RETRIGGER;           // Set event 0 to count the incoming events

  // Timer TCC1 (Test Output at 250Hz, 25% duty-cycle) /////////////////////////////////////////////////////////

  TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;             // Set the TCC1 timer counter to normal PWM mode (NPWM)
  while (TCC1->SYNCBUSY.bit.WAVE);                    // Wait for synchronization

  TCC1->CC[0].reg = 48000;                            // Set duty cycle 25% with 1ms pulse
  while (TCC1->SYNCBUSY.bit.CC0);                     // Wait for synchronization

  TCC1->PER.reg = 191999;                             // Set period to 4ms
  while (TCC1->SYNCBUSY.bit.PER);                     // Wait for synchronization

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

  // Timer TC4/TC5 (Delay Timer) /////////////////////////////////////////////////////////////////////////////

  TC4->COUNT32.CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ |   // Set TC4 to normal frequency mode (NFRQ)
                            TC_CTRLA_MODE_COUNT32;    // Enable 32-bit timer mode (in conjuction with TC5)

  TC4->COUNT32.CC[0].reg = 47999;                     // Set the delay to 1ms
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);           // Wait for synchronization
 
  TC4->COUNT32.CTRLBSET.reg = TC_CTRLBSET_ONESHOT;    // Enable oneshot operation
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);           // Wait for synchronization
 
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;                  // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);           // Wait for synchronization

  // Timer TCC0 (PWM Output) /////////////////////////////////////////////////////////////////////////////////

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;             // Set the TCC0 timer counter to normal PWM mode (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE);                    // Wait for synchronization

  TCC0->CC[1].reg = 47999;                            // Set duty cycle 100% with 1ms pulse on channel 1
  while (TCC0->SYNCBUSY.bit.CC1);                     // Wait for synchronization

  TCC0->PER.reg = 47999;                              // Set period to 1ms
  while (TCC0->SYNCBUSY.bit.PER);                     // 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_NRE1;              // Set the non-recoverable state output to 0 when inactive
                                                                                     
  TCC0->CTRLA.bit.ENABLE = 1;                         // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                  // Wait for synchronization
}

void loop() {}
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Mar 29, 2020, 08:07 pm
Propagation delay using this method (with TC4's CC0 register set to 0) is around 190ns.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Mar 30, 2020, 11:11 am
The propagation delay of 190ns accounts for 9 processor clock cycles at 20.83ns (1/48MHz).

Looking at the SAMD21 datasheet the interrupt using level detection without filtering takes 3 clock cycles. I can only assume that asynchronous retriggering the TC and TCC0 timers on the event system also takes 3 clock cycles each.

To account for the propagation delay:

Delay = (CC0 + 9) / 48000000
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Mar 30, 2020, 12:06 pm
Wow ! Thanks A LOT for this very detailled explanation and example. Let me test it and adapt it to my project and I'll get but to you to confirm this works for my needs. This is very promising. :D
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 01, 2020, 11:30 am
Just to update on my progress on the subject, I've managed to do what I wanted via a workaround using
the one shot mode of the output PWM.

I enable the oneshot mode with:

Code: [Select]
REG_TCC0_CTRLBSET |= TC_CTRLBSET_ONESHOT;
        while(TCC0->SYNCBUSY.bit.CTRLB);

On the other hand I have a frequency timer counter that measures the period of the input signal. Each time the frequency counter detects the end of the period I restart the output PWM manually like this:

Code: [Select]
// Check if the previous pulse is complete
if (TCC0->STATUS.bit.STOP)                                   
          {
              LOGD("retrigger for period: %d tic %d ms\n",
FreqMeasurementTimer::periodInTic, tic2us(FreqMeasurementTimer::periodInTic)/1000l);
// Retrigger the timer's One/Multi-Shot pulse
              TCC0->CTRLBSET.reg |= TCC_CTRLBSET_CMD_RETRIGGER;
 // Wait for synchronization
              while (TCC0->SYNCBUSY.bit.CTRLB);                       
          }

By using the one shot mode I no longer need to match the period of the output signal on the period of the input signal because technically a one shot pulse has no period. Si I just adjust PER and CC0 to get a pulse where I want in relation to the input pulse. If PER and CC0 are the same this gives a pulse with no phase, on each input pulse. If PER > CC0 I get positive phase.

I've attached a screenshot of the result.

I still have problem when using a negative phase and a pulse duration that overlaps the input pulse.

Anyway; I'm well aware of Martin's solution being well superior has it uses internal event system whereas my solution is a hack mixing timer counters and C code. But maybe it can help someone else for a related but slightly different problem.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 01, 2020, 03:35 pm
Hello Martin, now that I have and alpha version more or less working with my hack explained in the previous post I'm studying your solution in detail. Unfortunalty I didn't notice you used the one shot mode too ..  :smiley-sad-blue:

So I guess, despite being cleaner and more optimised your solution does not allow to have a negative phase and an overlap of the pulses either ?

(https://i.ibb.co/gZXmWt1/IMG-5924.png) (https://ibb.co/TMnY0PX)

On this image the yellow signal is the input, the pink is the output.
Is this situation possible ? Where pink pulses have negative phases but overlap
the whole yellow peak duration ?


Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Apr 02, 2020, 11:40 am
Hi ronin101,

Although the rising edge of timer TCC0's output pulse (light blue) must start within the input signal's (yellow) period, the output pulse itself is able to continue beyond and into the input signal's next period:

(https://forum.arduino.cc/index.php?action=dlattach;topic=673283.0;attach=354915)

In the example code (above) I changed:

TCC1 CC0 = 12000
TC4 CC0 = 173999
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 02, 2020, 06:03 pm
Brilliant ! Thank you.
I'was not able to successfuly run your example yet. I only get the input signal but I don't see
any output signal. Maybe again a difference between the zero and the m0 pro though I noticed
you didn't use D2 or D4. I'm working in on it.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Apr 03, 2020, 09:59 am
Other than the fact that D2 and D4 are swapped and that the M0 Pro lacks the additional ATN pin, hardware-wise the M0 Pro and Arduino Zero should be identical.

Have you plugged the test output signal on D8 into input D12?
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 03, 2020, 10:27 am
:smiley-eek-blue:  :smiley-roll-blue:  :smiley-roll-blue:

Silly me !!!! I though this was done by code via the Event Sytem which I did not understand.
Well at least it forced me to study the code in depth to understand what was happening.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 04, 2020, 04:40 pm
Ahhh !!. Register coding is soooo frustrating. I tried to mix the code Martin posted with some code I had that measure the input signal frequency and duty cycle.

The original PWM code with phasing control was working this way:

TCC1 -> output D8 ---> input D12 -> int3 -> TC4 ---> delay ----> TCCO ---> output D2
( originaly it was D3 but I changed it to D2 for my project need)

I repurposed TC4 as my frequency counter timer, so I used TC5 as the new delay counter
( note that I don't need 32bits accuracy so I can use both of them in 16 bits)
My original idea was to retrigger TC5 manually in the TC4_Handler() when each period is done.
It dit not work ...

I also tried to channel int3 both to TC4 and TC5 so TC5 will trigger directly via the Event System.
It did not work either ...

I tried a LOT of combinations, it almost worked but never exactly has intended.

I'm posting the code that I have, in case somebody is courageous enough to look at it. This is really refrustrating as I feel I'm very close but never got the exact right parameters to make it work. Unless I'm missing something important and what I want to do is just not possible ...

Code is too long for the forum so I post it here:

https://pastebin.com/fSdSyDUu (https://pastebin.com/fSdSyDUu)
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 04, 2020, 08:39 pm
I'm starting to think I used too low values for PER and CC0 which makes my code fail in limit cases for the microprocessor. I used a 20 us tic and PER/CC0 value of 1/1 so it was easy to check on the oscilloscope but starting again from scratch I'm noticing better results with bigger values.
I suspect the values I chose may have been too short for the event system.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 05, 2020, 12:11 pm
I GOT IT WORKING !!  :'(  :'(  :D  :D

Several little mistakes but the major issue was the use of a prescaler on TCC0
Code: [Select]
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV8 | TCC_CTRLA_ENABLE;
I have absolutely no idea why this causes the PWM to fail but if I don't use any prescaler
and compensate for the lack of it by multiplying the number of tics needed it works.

This works:
Code: [Select]
TCC0->CC[0].reg = 8;                 
while (TCC0->SYNCBUSY.bit.CC0);

TCC0->PER.reg = 8;                     
while (TCC0->SYNCBUSY.bit.PER);
                                       
TCC0->CTRLA.bit.ENABLE = 1;
while (TCC0->SYNCBUSY.bit.ENABLE);
This does not:
Code: [Select]
TCC0->CC[0].reg = 1;                 
while (TCC0->SYNCBUSY.bit.CC0);

TCC0->PER.reg = 1;                     
while (TCC0->SYNCBUSY.bit.PER);
                                     
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV8 |
                  TCC_CTRLA_ENABLE;
while (TCC1->SYNCBUSY.bit.ENABLE);

Here is the final working code:

https://pastebin.com/AQhdJQMF (https://pastebin.com/AQhdJQMF)
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Apr 06, 2020, 10:48 am
Hi ronin101,

Glad to hear that you got it working.

The issue with the prescaler might be to do with the "Prescaler and Counter Synchronization" or PRESCSYNC bitfield in the TCC and TC timers' CTRLA registers. This configures the whether the next wraparound should reset or retrigger the timer: on the next generic clock (GCLK), prescaler clock (PRESC), or the next generic clock while resetting the prescaler (RESYNC).

In your case, it might be worth setting the PRESCSYNC bitfield to RESYNC, to reset the prescaler when the timers are retriggered, for example:

Code: [Select]
TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16
                         TC_CTRLA_PRESCSYNC_RESYNC |    // Set to retrigger timer on GCLK with prescaler reset
                         TC_CTRLA_MODE_COUNT32;         // Set the counter to 32-bit mode

Also, I noticed in your code that you're dividing down generic clock 5 (GCLK5 by 120), in order to generate 400kHz. While this will clock the timers at the desired frequency, it will also clock the timers' registers at the same rate, leading to large synchronization delays for every register access.

Register synchronization delay is given by the forumla:

5 * Period(GCLK) + 2 * Period(APB) < D < 6 * Period(GCLK) + 3 * Period(APB)

where:
Period(GCLK) = period of the Generic Clock
Period(APB) = period of the Advanced Peripheral Bus (APB) at 48MHz
D = synchronization delay

Therefore with the GCLK set to 400kHz, the worst case synchronization delay is:

D = 6 * 1/400kHz + 3 * 1/48MHz = 15ms (for every synchronized TCC or TC register access)

This is OK if your pulse generation application is the microcontroller's only task, but might start to cause problems if it's required to perform other duties as well. For this reason, it's usually preferable to try use timer prescaler (if possible), rather than dividing down the generic clock.

Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: ronin101 on Apr 11, 2020, 12:16 pm
Hi Martin,

I did'nt have time to test all this during this week yet but I'll look into it.

Very interesting insight about the use of the prescaler vs the generic clock,
it could have cause me a lot of time figuring out the potential side effects of
this technical choice.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: WonS on Jun 26, 2020, 11:19 am
Hello Martin,

I must thank you first for your tremendous contribution to the popularization of arduino programming by helping many including me for the register-level coding which could be daunting many times.  I learned a large part of how to code bare-metal from reading your posts.   

As I need some advice on TCC PWM pulse outputs, I thought this thread is relevant. After trying many different attempts without success, I finally post a question! 
I read most of the relevant topics in the forum and I tried to apply them to my application using TCC ISR which worked until I needed some more features.

As in the attached image, there is an external pulse-inputs (from TCC1) that triggers synchronized output pulses with configurable widths.  There are 4 output pulses generated per external spike in ONE-SHOT mode, only 2 pulses of interest shown in the image.

The goal is to generate alternating pulses that differ in timing (or phase) in one of the two outputs (WO[4] in this case). In other words, the two diferent pulses profiles should repeat every other cycle.   My code can runs fine if there is no alternative requirement.    As turned out, this was not straight forward.

I attempted to change the count direction in TCC0_Handler with 2-stage state machine, at each state change the count direction so that single slope PWM but for unknown reason the count direction did not change as desired at every cycle.   Here is one of my attempts:

(https://i.postimg.cc/XJtkL0H0/TCC-alternating-pulses.jpg)


Code: [Select]


void TCC0_Handler()           // ISR TCC0 overflow callback function
{
  // Check for overflow (OVF) interrupt
  if (TCC0->INTFLAG.bit.OVF)// && TCC0->INTENSET.bit.OVF)
  {
    // NOTE: this ISR update of CCx doesn't work in TCC1_Handler.
    switch (period_state) {
    case 'A':
      period_state = 'B';
     
      TCC1->PERB.reg = tcc1_per ;
      while (TCC1->SYNCBUSY.bit.PERB);
      TCC0->PERB.reg = h2_width<<2;
      while (TCC0->SYNCBUSY.bit.PERB);

      //// switch polarity of wave output. (result not what expected, at least the speed at which the update happens seems to be too slow   
      //TCC0->WAVE.reg |= TCC_WAVE_POL(0x1);      // Single slope PWM operation     
      //while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization

      while (TCC0->SYNCBUSY.bit.CTRLB) {
        /* Wait for sync */
      }
      SerialUSB.println("Upcounting");

      if (TCC0->CTRLBSET.bit.DIR) {
        TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_DIR; //
      }   
      //TCC0->CTRLBSET.reg = TCC_CTRLBSET_DIR_Pos;  //  // count down. (not working here)
      else {
        TCC0->CTRLBSET.reg = TCC_CTRLBSET_DIR;
      }

      TCC0->CCB[0].reg = h1_width;
      while (TCC0->SYNCBUSY.bit.CCB0);
      TCC0->CCB[1].reg = h2_width;
      while (TCC0->SYNCBUSY.bit.CCB1);

      TCC0->INTFLAG.bit.OVF = 1;         // Clear the OVF interrupt flag
      break;
    case 'B':
      period_state = 'A';

      TCC1->PERB.reg = tcc1_per;
      while (TCC1->SYNCBUSY.bit.PERB);

      TCC0->PERB.reg = h2_width<<2;
      while (TCC0->SYNCBUSY.bit.PERB);

      //// switch polarity of wave output. ( result not what expected, at least the speed at which the update happens seems to be too slow 
      //TCC0->WAVE.reg |= TCC_WAVE_POL(0x0);// | TCC_WAVE_POL1_Pos;          // Single slope PWM operation     
      //while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization


      while (TCC0->SYNCBUSY.bit.CTRLB) {
        /* Wait for sync */
      }       
      SerialUSB.println("Down counting");
     
      if (TCC0->CTRLBSET.bit.DIR) {
        TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_DIR; //
      }   
      //TCC0->CTRLBSET.reg = TCC_CTRLBSET_DIR_Pos;  //  // count down. (not working here)
      else {
        TCC0->CTRLBSET.reg = TCC_CTRLBSET_DIR;
      }
 
      TCC0->CCB[0].reg = h1_width;
      while (TCC0->SYNCBUSY.bit.CCB0);
      TCC0->CCB[1].reg = h2_width;
      while (TCC0->SYNCBUSY.bit.CCB1);
      break;
    default:
      SerialUSB.println("Default");
    }
    TCC0->INTFLAG.bit.OVF = 1;         // Clear the OVF interrupt flag
  }
}





In setup, I used attachIntterupt for external trigger:


Code: [Select]


pinMode(PIN_PUSLEIN, INPUT_PULLUP); // C2_PULSEIN pin (3) 
attachInterrupt(PIN_PUSLEIN, interrupt_pulsegen_pulsein, RISING);



And the following is what the attachInterrupt calls...


Code: [Select]


// One shot pulse triggered by external spike
void interrupt_pulsegen_pulsein()
{

  if (millis() - ignited_time <= tcc1_duration) {

    if (TCC0->STATUS.bit.STOP)                                 // Check if the previous pulse is complete
    {
      // count restarts...
      TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer's One/Multi-Shot pulse
      while (TCC0->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization
    }

    if (TCC2->STATUS.bit.STOP)                                 // Check if the previous pulse is complete
    {
      TCC2->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer's One/Multi-Shot pulse
      while (TCC2->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization
    }

  }
}





I tried to only use TCC0_Handler to change the count direction but maybe I am fundamentally doing it wrong?
As I was refraining from using the "event systems" because I wasn't sure if I am overcomplicating the task. However, after reading your prev posts,  I learned that using event systems for generating interruptions from the same ISR may free-up the CPU intervention.  Could it be the case that I must use event systems to toggle count direction at each cycle? 

I would really appreciate if you could help me to achieve the desired output pulse shape by upgrading my code.

Thanks a lot.

Eric
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Jun 26, 2020, 05:06 pm
Hi Eric,

It very much depends on the exact timing of your PWM pulses, including duty-cycle (phase), period (frequency) and resolution.

One possible technique is to decrease the timer's period into smaller time slices and reconstruct the pulses with a predetermined sequence of duty-cycles. This can be achieved using the SAMD21's DMAC, or if the PWM frequency is low, using the timer's interrupt service routine.

Another option, if the lag on output 1 isn't too large, is to use dead-time insertion, but this can only insert a small number of GCLK cycles to delay the pulse.

If you need to generate 4 pulses then it's probably easier to use the timer in continous PWM mode and switch the duty-cycle to 0% at the end of the sequence, rather than using oneshot mode.

Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: WonS on Jun 27, 2020, 11:32 am
Thanks for your advices Martin. I have been testing your ideas since, especially the first one with small time sliced periods with ISR.   Basically trying to segment a cycle into 3 slices (state machine),  first two slices for pulses and the third for long silence period. However, there are some problems that I face as shown in the oscilloscope which leaves me with not much idea.  :o


(https://i.postimg.cc/XvgX7fMB/WIN-20200627-02-06-48-Pro.jpg)

Zoomed out in time scale:
(https://i.postimg.cc/wvTMt1Hf/WIN-20200627-02-07-41-Pro.jpg)
 

My fear at this point is that this may not be a small grammatical mistake but a design problem in more higher level.  If I don't have to alternate the pulse sequence like in the pulse diagram the simple single slope PWM seems to work.   Would you be kind to take a look?   To run, the only wire need to be connected is between the PIN_C2_PULSEOUT (PA07) and PIN_PULSEIN (PA02), which is the path for trigger pulses.  I need to have the output pulse per incoming short pulse as a trigger.

My application requires low frequency of <400Hz (period=2.5ms), duty cycle  of 5 ~ 20% and no stringent requirement on the resolution.    I am wondering if the interrupt service routine with TCC0_handler is not adequate for this and indeed needs to use DMA?  Any help really appreciated.

Eric


Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Jun 29, 2020, 11:30 am
Hi Eric,

I've managed to produce the waveform you require.

The following code sets up port pin PA19 as an input that routes pulses through to the event system, (a 12-channel peripheral-to-peripheral highway), via the External Interrupt Controller (EIC). After a 2us progagation delay, these pulses (or events) are received by timers TCC0 and TCC1. The fact they these timers are triggered by the same event effectively synchronizes them.

Upon receiving an event both timers retrigger, TCC0 generating a 2500us pulse on channel 1 and an alternating 1250us and 2500us pulse on channel 0. The alternating pulse widths is achieved by enabling circular CC0/CCB0 buffering in the TCC0's WAVE register.

Timer TCC1 meanwhile calls its interrupt service routine. Once at the beginning of a cycle, to alternately switch on and off pattern generation and once at the end of the pulse, to turn pattern generation off.

When the pulse on channel 0 is 1250us, pattern generation is switched off for the entire cycle and the pulse is output as normal. However, when the pulse on channel 0 is 2500us, pattern generation is switched on for the first 1250us, forcing the output low. After 1250us, pattern generation is switched off allowing the pulse to return high again for its remaining 1250us.

A 5us pulse interval is generated from timer TCC2 on port pin PA16. Connecting PA16 to PA19 feeds the pulses through to timers TCC0 and TCC1.

Here's the resulting output, 5us interval pulses (yellow), alternate phase shifted pulse on TCC0 channel 0 (light blue) and standard pulse on TCC0 channel 1 (purple):

(https://forum.arduino.cc/index.php?action=dlattach;topic=673283.0;attach=370835)

Please find the code attached:
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: WonS on Jun 29, 2020, 08:34 pm
Hi Martin, thank you very much for your kind help in tackling this task. :)  The wave screenshot looks awesome. I will study the code tonight and seeks to apply it to my project and come back if I have another question. Thanks! 
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: WonS on Jun 30, 2020, 09:59 pm
Hi Martin, 

Your solution enlightens me to a new world of pattern generator. 
Very elegant way! I would never knew how to use it by myself reading the data sheet. Thank you for letting me study this.

Some follow up question...
1) I can't wrap my head around how the PATT.bit.PGE4 in TCC1_Handler is able to generate the alternating pattern.    When it is set to 0 at MC0  (@ count=1250), the TCC0 ch1 is zeroed,  but I don't get what the PATT.bit.PGE4=1 or 0 at and at count=4000 (@OVF) does.  Do you mind to explain a bit more?  By reading the data sheet the use of pattern generator it is very unclear...

2) When I disconnect the EIC pin, TCC0 still outputs some patterns even though it is supposed to be a one-shot mode, meaning one pulse input maps to one pulse output per channel. Is there a way to correct this?

3) Just conceptually, what would be the advantage of using DMA here?  I am in my early stage in understanding DMAC and wondering if in the future it might be beneficial to use it. For example, in addition to my current setting , I plan to have TCC0 or TCC2 to trigger three ADC reading per cycle and would like to hear your perspective.  THanks!


Eric



 
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: MartinL on Jul 01, 2020, 10:13 pm
Hi Eric

1)In the PATT register, setting the pattern generator's bit PGE4 (Pattern Generator Enable Channel 4) simply overrides the channels output, setting the output high or low depending on the value defined by the bit PGV4 (Pattern Generator Output Value Channel 4).

The TCC0 alternates cycles on channel 0, both aligned to the rising edges of the input signal. On one cycle TCC0 generates standard a 1250us pulse, on the other it generates a 2500us pulse, but the TCC1 timer actives the TCC0's pattern generator to set the first 1250us of the pulse low. This gives the output of a 1250us pulse with a phase delay.

2) I'm not seeing this behaviour on my board. If I disconnect the PA19 input both PA22 and PA23 outputs go low.

There is an issue that if the input pulses are stopped, the TCC1's ISR and the TCC0's circular buffer can get out of sync with one another.

3) Unfortunately, in this instance the DAMC won't work, as it doesn't have the same flexibility as an interrupt service routine.

The problem is that the PGE4 bit in the PATT register needs to be set every other cycle by the TCC1 overflow (OVF) and cleared by the match compare 0 (MC0). This requires two DMAC channels, one to trigger on TCC1 overflow and the other on match compare 0, however the two channels cannot simultaneously share the single destination PATT register.

The TCCx timers can generate events on the event system, to trigger the ADC. The DMAC can used to automatically copy the results from the ADC into memory.
Title: Re: PWM generation for a very low duty cycle and phase shifting capabiliy on SAMD21
Post by: WonS on Jul 05, 2020, 06:40 am
Thank you so much for your explanation Martin.  I am indebted to your teaching.  Have a good day.