Controlled pulse shortener

Note: posting #15 has FlexPWM Input capture code. I am playing with an updated version of it now...
Actually trying to get get FlexPWM output compare working as well.

With the Output one, I was trying to set it up to generate the up Pulse on timer from ISR callback from from the FlexPWM input on raising edge interrupt...
I was trying to delay the up pulse by some number of cycles, i.e. set by timer, and then do down by resetting the timer to pulse width desired...

Still debugging. Although right now relaxing.

I may simplify what I am debugging to have the pin High happen during the ISR for Pin going high of the monitored pin and set the time for have the timer set the pin low at the specified time.

I had to read your last sentence a few times, but now I get it, and yes, I think that's a good idea. The OP didn't say there was a specified delay from rising edge of input to rising edge of output, so you could set the output high and then just do a single output compare match to turn it off. I kinda like the idea of InputCaptureMulti as opposed to FreqMeasureMulti, i.e. just write the raw input capture values (and level) to the FIFO, and the high/low-time and period calculations could be done as data is extracted from the FIFO. For this one task, I wouldn't have any idea how to generalize the solution, though. I would just start with FreqMeasureMulti and strip everything down to the minimum and then write custom code for the ISR for each of the IC and OC channels.
 
The OP didn't say there was a specified delay from rising edge of input to rising edge of output,
No specified time, other than rising edge of output must be at least (2% * cycle time) after rising edge of input.

On a different tangent, I looked into FreqMeasureMulti a bit. From this particular page: https://github.com/PaulStoffregen/FreqMeasureMulti I found a reference to FREQMEASUREMULTI_INTERLEAVE, which looks like it might be ideal for measuring the input pulse cycle time. Might that be an option?
 
No specified time, other than rising edge of output must be at least (2% * cycle time) after rising edge of input.

On a different tangent, I looked into FreqMeasureMulti a bit. From this particular page: https://github.com/PaulStoffregen/FreqMeasureMulti I found a reference to FREQMEASUREMULTI_INTERLEAVE, which looks like it might be ideal for measuring the input pulse cycle time. Might that be an option?

FreqMeasureMulti can definitely be used to measure the period and high time of the input pulse, but unless you modify the library, there is no hook into the rising/falling edge ISRs. If you brought your input signal to 2 pins, you could configure one of those pins to measure high time with FreqMeasureMulti, and use the other way as a digital input with interrupt to set the output high and start the one-shot timer, and set the output low in the handler for the one-shot, the same as we are doing now. That might be a little better in terms of stabilizing the width of the output pulse, but it would take 2 input pins rather than 1.

What @KurtE has been looking into is modifications to FreqMeasureMulti to both measure the input signal and produce the output signal.
 
Yes - I mentioned FreqMeasureMulti back in post #13...

Here is my current WIP based off of it:

It has code for both Input capture and Output... using the timer.

Some parts I am not happy with in the output starts several FTM0_CNT units after detection of the up pulse...

I am trying to base it on receiving the uptick... If however it just needs to start at some point after start of period, could probably simply setup to automatically start at that time delta...
 

Attachments

  • t3x_ftm_pin_capture-220719a.zip
    6 KB · Views: 14
@KurtE,
I looked at your code, but this code is getting way above my level of understanding. I'm not clear what pin you are using to receive the input pulse, and what pin you are using to generate the response pulse.

BTW, thanks to both you and @joepasquariello for tackling this problem!
 
In case it helps any, the response pulse can start at any time after the input pulse. The (2% * cycle time ) parameter I used earlier was just an estimate for the soonest it could start, based on circuit timings.
 
In case it helps any, the response pulse can start at any time after the input pulse. The (2% * cycle time ) parameter I used earlier was just an estimate for the soonest it could start, based on circuit timings.

I take your sentence above to mean that the RISING edge of the output pulse can start "any time after the (rising edge of the) input pulse". Can you say something about the requirements for the output pulse width? Should the FALLING edge of the output pulse be "as close as possible" to the falling edge of the input pulse, or should it always be "at least X ns" prior to the falling edge of the input, etc.? Would it be okay to have jitter as long as every output pulse is "within" the input pulse?
 
Yes, the rising edge of the output pulse can start any time after the rising edge of the input pulse. The output pulse width should be between 2% and 50% of the period of the input pulse. This width is variable, and will be controlled by the Teensy firmware, based on external sensors.
Jitter is undesirable, since that means the output pulse width is inconsistent.
 
Yes, the rising edge of the output pulse can start any time after the rising edge of the input pulse. The output pulse width should be between 2% and 50% of the period of the input pulse. This width is variable, and will be controlled by the Teensy firmware, based on external sensors.
Jitter is undesirable, since that means the output pulse width is inconsistent.

You said previously that the output rising edge could be at the same time or soon after the input rising edge. And now you're saying the output pulse width depends on something other than the input pulse width. Does this mean that the "ideal" output would have rising edge at the same time as the rising edge of the input and would have controllable pulse width from 2% to 50% of input signal period, but not related to the input pulse width?
 
I apologize for the confusion. Maybe this waveform will help.

Our input pulse has a period determined by Tmax - To. The output pulse starts soon after the rising edge of the input pulse. Exactly how soon is not really critical; it can be in sync, if that were possible. The output pulse, illustrated by t2-t1, can me a minimum of 2% of the input pulse period. The output pulse can go all the way up to 50% of the input pulse period, illustrated by t4-t3 on the waveform. It can also be any width in between these two extremes, and this is defined in firmware.

You are correct, the output pulse is a percentage of the input pulse period, not the pulse width.
waveform.jpg
 
I apologize for the confusion. Maybe this waveform will help.

Our input pulse has a period determined by Tmax - To. The output pulse starts soon after the rising edge of the input pulse. Exactly how soon is not really critical; it can be in sync, if that were possible. The output pulse, illustrated by t2-t1, can me a minimum of 2% of the input pulse period. The output pulse can go all the way up to 50% of the input pulse period, illustrated by t4-t3 on the waveform. It can also be any width in between these two extremes, and this is defined in firmware.

You are correct, the output pulse is a percentage of the input pulse period, not the pulse width.
View attachment 28975

That helps. Tomorrow I'll take a shot at using FTM for both input (input capture on rising edge only) and output (output compare for rising/falling edges of output). We should only need the rising edge of the input because we only need the period. In the ISR for the rising edge of the input, we can:

- compute the duration of the just-finished period
- schedule the rising and falling edges of the output relative to rising edge of input

Will the input frequency always be 50 kHz?
 
Thank you, sir.
The input pulse will vary from about 40 kHz to 60 kHz depending upon which device is being tested.
But once a freq is chosen it will not change for the entire duration of a particular test session.
 
Here is a program that does what you want, as I understand it. It uses 3 pins

- pin 3 is 50 kHz PWM to simulate the 50 kHz input signal
- pin 9 is the input signal (FTM0 ch2) and does input capture on RISING edge
- pin 10 is the output signal (FTM0 ch3) and does output compare w/ toggle on match

FTM0 interrupts occur on the RISING edge of input (ch2) and on each output compare match (ch3).

- on RISING edge of INPUT, schedule the RISING edge of OUTPUT to occur in 1 us
- on RISING edge match of OUTPUT, toggle to HIGH and schedule the FALLING edge of OUTPUT to occur in 4 us
- on FALLING edge match of OUTPUT, toggle to LOW

The configuration code and ISR for the FTM0 channels is taken from FreqMeasureMulti, but is much, much simplified. I've tested on T3.2 and T3.5, and the results are as shown in the image below. There is virtually no jitter. I'm working on commenting the FTM configuration and ISR so that others can use it more easily.

t3x_ftm_ic_and_oc.png

EDIT: In my testing, the shortest delay from rising edge of input to rising edge of output, and also the minimum width of the output pulse, is a little under 1 us. Shorter delays and pulse widths don't work because the FTM interrupts are too close together, which results in "missing" the target compare match. At input frequency of 50 kHz, 1 us is 5% of the input period, so that's probably the minimum you can get with this method, at least on T3.x, which has FTM timers. With T4.x, you have the much fancier FlexPWM, which has multiple match registers per channel.

Code:
#define PWM_SIGNAL_PIN (3) // must be a pin that is NOT on FTM0

#define F_FTM (F_CPU/2) // FTM base clock frequency is F_CPU/2

#define PWM_FREQUENCY (50000) // 50000 -> 20-us period
#define PWM_DUTYCYCLE (256/2) // 50% -> 10-us high time

volatile uint16_t capture, capture_prev = 0; // use 16-bit to handle roll-over
volatile uint32_t capture_delta = 0;
volatile uint32_t capture_count = 0;
volatile uint32_t compare_count = 0;
volatile uint32_t first_capture = 1;

FASTRUN void ftm0_isr()
{
  if (FTM0_STATUS & (1<<2)) { // if channel 2
    FTM0_C2SC &= ~0x80; // clear interrupt flag (read then write 0)
    capture_count++; // increment count
    capture = FTM0_C2V; // get capture value
    if (first_capture) {
      first_capture = 0;
    }
    else {
      capture_delta = capture - capture_prev; // compute delta
      FTM0_C3V = capture + 1*(F_FTM/1'000'000); // rising edge in 1 us
    }
    capture_prev = capture;
  }
  if (FTM0_STATUS & (1<<3)) { // if channel 3
    FTM0_C3SC &= ~0x80; // clear interrupt flag (read then write 0)
    if (capture_delta > 0) {
      compare_count++; // increment count
      if ((compare_count % 2) == 1) { // if RISING edge interrupt
        FTM0_C3V += 4*(F_FTM/1'000'000); // falling edge in 4 us
      }
    }
  }
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( LED_BUILTIN, OUTPUT );

  // init the LED pin LOW
  digitalWriteFast( LED_BUILTIN, LOW );

  // configure pin 3 for 20 Hz frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, PWM_DUTYCYCLE );

  // configure pins 9,10 to be FTM0 ch2,3
  CORE_PIN9_CONFIG = PORT_PCR_MUX(4);
  CORE_PIN10_CONFIG = PORT_PCR_MUX(4);
  
  NVIC_DISABLE_IRQ(IRQ_FTM0);
  FTM0_SC = 0; // disable
  FTM0_CNT = 0; // init count = 0
  FTM0_MOD = 0xFFFF; // count to max 16-bit and roll over to 0
  FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // internal clock with prescaler=0 (div by 1)
  FTM0_MODE = FTM_MODE_WPDIS; // allow reconfiguring the CSC on the fly

  // ch2 (pin 9) input capture on RISING edges and enable interrupt
  FTM0_C2SC = FTM_CSC_CHIE | FTM_CSC_ELSA;

  // ch3 (pin 10) output compare with TOGGLE on match and enable interrupt
  FTM0_C3SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSA;
  FTM0_C3V  = 0xFFFF; // start w/ max to avoid match until ready 

  NVIC_SET_PRIORITY(IRQ_FTM0, 48); // raise interrupt level to 48
  NVIC_ENABLE_IRQ(IRQ_FTM0); // enable 
}

elapsedMillis loop_delay = 0;
uint32_t loop_count = 0;

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  if (loop_delay >= 1000) {
    loop_delay = 0;
    loop_count++;
    Serial.printf( "%10lu  %10lu  %10lu\n", loop_count, capture_count, compare_count );
    digitalWriteFast( LED_BUILTIN, (loop_count % 2) ? HIGH : LOW );    
  }
}
 
Last edited:
Updated version w/ more and better comments and elimination of a few "magic number" constants. Use variable "output_width_pct" to set output width as % of input period.

EDIT: On further reading of the FTM chapter in the reference manual, the COMBINE mode could be used, which allows using the output compare matches of 2 adjacent channels to control a single channel's output. This would allow both the rising and falling edges of the output to be scheduled in the ISR of the input rising edge, and there would be no need for interrupts on any of the output compare matches. There still would need to be delay of about 1 us from rising edge of input to rising edge of output, but the width of the output pulse could be less than 1 us, almost certainly as short as the desired 2% of 20 us, or 400 ns.

Code:
#define PWM_SIGNAL_PIN (3) // must be a pin that is NOT on FTM0

#define F_FTM (F_CPU/2) // FTM base clock frequency is F_CPU/2

#define PWM_FREQUENCY (50000) // 50000 -> 20-us period
#define PWM_DUTYCYCLE (256/2) // 50% -> 10-us high time

volatile uint16_t capture, capture_prev = 0; // use 16-bit to handle roll-over
volatile uint32_t capture_count = 0;
volatile uint32_t compare_count = 0;
volatile uint32_t first_capture = 1;

volatile uint32_t input_period = 0;
volatile uint32_t output_width_pct = 30; // modify to set output pulse width 

uint32_t ftm_clocks_per_us = F_FTM/1'000'000 + 0.5; //

// FTM0 ISR -- each FTM module has a single interrupt vector, shared by all 
// channels. ISR function ftm0_isr() is declared in cores\teensy3\kinetis.h and
// defined in cores\teensy3\mk20dx128.c, with the attribute "weak", and included
// in the interrupt vector table. The "weak" attribute allows this function to
// "override" the version in mkd20dx128.c, and we don't have to do anything but
// define the function here for it to be used. The modifier FASTRUN causes the
// ISR to be copied to RAM at run-time for fastest operation.
FASTRUN void ftm0_isr()
{
  // handle interrupts for FTM0 channel 2 (input capture on rising edge of input)
  if (FTM0_STATUS & (1<<2)) {
    // clear interrupt flag by read CnSC register, then write 0 to CHF
    FTM0_C2SC &= ~FTM_CSC_CHF;
    // read input capture value from CnV register
    capture = FTM0_C2V;
    if (!first_capture) {
      // compute input signal period and schedule output rising edge in 1 us
      input_period = capture - capture_prev;
      FTM0_C3V = capture + ftm_clocks_per_us;
    }
    else { 
      first_capture = 0; // set first_capture = false
    }
    // save capture value for next pass and increment counter for debug use
    capture_prev = capture;
    capture_count++;
  }
  
  // handle interrupts for FTM channel 3 (output compare w/ toggle on match)
  if (FTM0_STATUS & (1<<3)) {
    // clear interrupt flag by read CnSC register, then write 0 to CHF
    FTM0_C3SC &= ~FTM_CSC_CHF;
    if (input_period > 0) {
      // increment compare count
      compare_count++;       
      // if RISING edge interupt, schedule output falling edge
      if ((compare_count % 2) == 1) {
	// output width = X% of input period (with rounding)
        FTM0_C3V += (output_width_pct * input_period + 50) / 100;
      }
    }
  }
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( LED_BUILTIN, OUTPUT );

  // init the LED pin LOW
  digitalWriteFast( LED_BUILTIN, LOW );

  // configure pin 3 for 20 Hz frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, PWM_DUTYCYCLE );

  // configure pins 9,10 to be FTM0 ch2,3
  CORE_PIN9_CONFIG = PORT_PCR_MUX(4);
  CORE_PIN10_CONFIG = PORT_PCR_MUX(4);
  
  // disable FTM0 interrupt
  NVIC_DISABLE_IRQ(IRQ_FTM0);
  // init FTM0 registers
  FTM0_SC = 0; // disable
  FTM0_CNT = 0; // count = 0
  FTM0_MOD = 0xFFFF; // count to max 16-bit and roll over to 0
  FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // internal clock, prescaler=0 (div by 1)
  FTM0_MODE = FTM_MODE_WPDIS; // allow reconfiguring the CSC on the fly

  // enable ch2 (pin 9) for input capture on RISING edges and enable interrupt
  FTM0_C2SC = FTM_CSC_CHIE | FTM_CSC_ELSA;

  // set ch3 compare value for max value to delay match
  FTM0_C3V  = 0xFFFF;
  // enable ch3 (pin 10) for output compare with TOGGLE on match and enable interrupt
  FTM0_C3SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSA;

  // raise FTM0 interrupt level to 0 (highest priority)
  NVIC_SET_PRIORITY(IRQ_FTM0,0);
  // enable FTM0 interrupt
  NVIC_ENABLE_IRQ(IRQ_FTM0);
}

elapsedMillis loop_delay = 0;
uint32_t loop_count = 0;

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  if (loop_delay >= 1000) {
    loop_delay = 0;
    loop_count++;
    Serial.printf( "%10lu  %10lu  %10lu\n", loop_count, capture_count, compare_count );
    digitalWriteFast( LED_BUILTIN, (loop_count % 2) ? HIGH : LOW );    
  }
}
 
Last edited:
As a possible alternative solution you could try a hybrid hardware/software solution using an AND gate.
Look at the diagram below:
Teensy plus and gate.png
"input" is the signal you want to mimic / shorten.
When "gate" is high everything appearing on "input" appears at "output".

Start with "gate" set HIGH.
When you detect a HIGH at the Teensy pin connected to "input" start a timer.
After the pre-determined time has expired set "gate" to LOW, turning off "output".
When the "input" goes LOW, set "gate" HIGH and repeat the process.
 
Here's a bug fix for the version that uses FTM input capture and output compare. The fix is that variable "input_period" must be 16-bit unsigned to avoid error on roll-over of the 16-bit FTM counter. This version works really well, producing pulses from 5% to 50% of the input pulse period, but not being able to get to the low end target of 2% due to limitations on how close together the rising and falling edge output compare interrupts can be.

Today I tried to use the COMBINE feature of FTM to eliminate the interrupts on output compare, but it looks like that is not compatible with measuring the input period with a running FTM counter, so I think this is as good as I can do for now.

Code:
#define PWM_SIGNAL_PIN (3) // must be a pin that is NOT on FTM0

#define F_FTM (F_CPU/2) // FTM base clock frequency is F_CPU/2
#define FTM_ONE_US (F_FTM/1'000'000)

#define PWM_FREQUENCY (50000) // 50000 -> 20-us period
#define PWM_DUTYCYCLE (256/2) // 50% -> 10-us high time

volatile uint16_t capture, capture_prev = 0; // use 16-bit for roll-over
volatile uint16_t input_period = 0; // use 16-bit for correct roll-over

volatile uint32_t capture_count = 0;
volatile uint32_t compare_count = 0;
volatile uint32_t first_capture = 1;

volatile uint32_t output_width_pct = 30; // modify to set output pulse width 
volatile uint32_t ftm_clocks_per_us = F_FTM/1'000'000 + 0.5; //

// FTM0 ISR -- each FTM module has a single interrupt vector, shared by all 
// channels. ISR function ftm0_isr() is declared in cores\teensy3\kinetis.h and
// defined in cores\teensy3\mk20dx128.c, with the attribute "weak", and included
// in the interrupt vector table. The "weak" attribute allows this function to
// "override" the version in mkd20dx128.c, and we don't have to do anything but
// define the function here for it to be used. The modifier FASTRUN causes the
// ISR to be copied to RAM at run-time for fastest operation.
FASTRUN void ftm0_isr()
{
  // handle interrupts for FTM0 channel 2 (input capture on rising edge of input)
  if (FTM0_STATUS & (1<<2)) {
    // clear interrupt flag by read CnSC register, then write 0 to CHF
    FTM0_C2SC &= ~FTM_CSC_CHF;
    // read input capture value from CnV register
    capture = FTM0_C2V;
    if (!first_capture) {
      // compute input signal period and schedule output rising edge in 1 us
      input_period = capture - capture_prev;
      FTM0_C3V = capture + ftm_clocks_per_us;
    }
    else { 
      first_capture = 0; // set first_capture = false
    }
    // save capture value for next pass and increment counter for debug use
    capture_prev = capture;
    capture_count++;
  }
  
  // handle interrupts for FTM channel 3 (output compare w/ toggle on match)
  if (FTM0_STATUS & (1<<3)) {
    // clear interrupt flag by read CnSC register, then write 0 to CHF
    FTM0_C3SC &= ~FTM_CSC_CHF;
    if (input_period > 0) {
      // increment compare count
      compare_count++;       
      // if RISING edge interupt, schedule output falling edge
      if ((compare_count % 2) == 1) {
	      // output width = X% of input period (with rounding)
        FTM0_C3V += (output_width_pct * input_period + 50) / 100;
      }
    }
  }
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( LED_BUILTIN, OUTPUT );

  // init the LED pin LOW
  digitalWriteFast( LED_BUILTIN, LOW );

  // configure pin 3 for 20 Hz frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, PWM_DUTYCYCLE );

  // configure pins 9,10 to be FTM0 ch2,3
  CORE_PIN9_CONFIG = PORT_PCR_MUX(4);
  CORE_PIN10_CONFIG = PORT_PCR_MUX(4);
  
  // disable FTM0 interrupt
  NVIC_DISABLE_IRQ(IRQ_FTM0);
  // init FTM0 registers
  FTM0_SC = 0; // disable
  FTM0_CNT = 0; // count = 0
  FTM0_MOD = 0xFFFF; // count to max 16-bit and roll over to 0
  FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // internal clock, prescaler=0 (div by 1)
  FTM0_MODE = FTM_MODE_WPDIS; // allow reconfiguring the CSC on the fly

  // enable ch2 (pin 9) for input capture on RISING edges and enable interrupt
  FTM0_C2SC = FTM_CSC_CHIE | FTM_CSC_ELSA;

  // set ch3 compare value for max value to delay match
  FTM0_C3V  = 0xFFFF;
  // enable ch3 (pin 10) for output compare with TOGGLE on match and enable interrupt
  FTM0_C3SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSA;

  // raise FTM0 interrupt level to 0 (highest priority)
  NVIC_SET_PRIORITY(IRQ_FTM0,0);
  // enable FTM0 interrupt
  NVIC_ENABLE_IRQ(IRQ_FTM0);
}

elapsedMillis loop_delay = 0;
uint32_t loop_count = 0;

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  if (loop_delay >= 1000) {
    loop_delay = 0;
    loop_count++;
    Serial.printf( "%10lu  %10lu  %10lu\n", loop_count, capture_count, compare_count );
    digitalWriteFast( LED_BUILTIN, (loop_count % 2) ? HIGH : LOW );    
  }
}
 
Last edited:
@joe, I just tried your latest code (post #41) and it looks like it works really well. It does exactly what I was hoping it would. I appreciate you working on getting the range adjusted to support down to 2%.
 
@joe, I just tried your latest code (post #41) and it looks like it works really well. It does exactly what I was hoping it would. I appreciate you working on getting the range adjusted to support down to 2%.

@Biff, glad that's working for you. The method I had hoped to get down to 2% on the output was to use the COMBINE feature to couple together two FTM channels to produce both the rising and falling edges of the output without interrupts. That feature seems to be intended to produce PWM with a fixed period, a variable duty cycle, and the ability to position the high time anywhere in the PWM period. It's intended to produce PWM with a fixed period, but the need to synchronize the output with the input means that your output is not really a fixed period. I think that if we tried to "synchronize" a fixed PWM output period with the measured input period, the output would likely drift relative to the input. For now, at least, what's in post #41 is the best that I can do.

I don't know what is on the receiving end of the 2% to 50% output, but maybe you could modify that device to "re-scale" it's interpretation of the signal.
 
@joe, trying the 5% duty cycle works well at 50 kHz (see first figure below). But if I increase the frequency to 55 kHz, the duty cycle breaks, and becomes closer to 100% (see second figure below). Any idea why? :confused:

tek00012.pngtek00011.png
 

Attachments

  • tek00013.png
    tek00013.png
    47.4 KB · Views: 17
@joe, trying the 5% duty cycle works well at 50 kHz (see first figure below). But if I increase the frequency to 55 kHz, the duty cycle breaks, and becomes closer to 100% (see second figure below). Any idea why? :confused:
Yes, the minimum pulse width is about 1 us, so as you to higher frequency, 1 us is a higher percentage of input period. 4% at 40 kHz, 5% at 50, and 6% at 60 kHz all imply 1us and should be about the minimum.
 
One other key point -- the long pulses in the waveform above are not consistent. Sometimes the 5% duty cycle is displayed accurately, as shown below.
View attachment 29012

I'm guessing that's because you're really close to the boundary between OK and too short. I'm not sure exactly what variables exist from one run to the next, but I do think it's possible that the same value could fall over the line in either direction if it's right on the edge.
 
@Biff, I figured out how to use the COMBINE mode PWM on two FTM channels to generate the output signal without interrupts. The code below does that, and you can set the output width from 1% (or even less) to 99% (or even a little more), and I made output_width_pct a float, so you have better than 1% resolution. The pin assignments are:

- PWM output on pin 3 (jumper to pin 6)
- input signal on pin 6
- output signal on pin 9

It works great when measuring PWM produced by the Teensy on pin 3, but you'll have to try it with your function generator and see how it works over time. The reason I think you may run into some issues is that what the code does now is measure the period of the 1st PWM period, then "sync" to the rising edge of the 2nd PWM period, and after that it generates its output on a fixed schedule. I have tried to re-sync the output on each rising edge of the input, but so far I have not yet been able to get that to work, so you may see an issue of the output drifting with respect to the input.

Code:
/**********************************************************************
* Controlled Pulse Shortener 
* FTM input capture on pin 6 (ch4) with interrupt on rising edges
* FTM COMBINE-mode PWM on pins 9,10 (ch2,3)
* T3X pins for FTM0 channels 0-7 = 22, 23, 9, 10, 6, 20, 21, 5
***********************************************************************/
#define PWM_SIGNAL_PIN (3) // must be a pin that is NOT on FTM0

#define INPUT_PIN (6) // FTM0 ch4 for input capture
#define OUTPUT_PIN (9) // FTM0 ch2,3 COMBINE -> output on ch2

#define F_FTM (F_CPU/2) // FTM base clock frequency is F_CPU/2
#define FTM_ONE_US (F_FTM/1'000'000)

#define PWM_FREQUENCY (50000) // 50000 -> 20-us period
#define PWM_DUTYCYCLE (256/2) // 50% -> 10-us high time

volatile uint16_t capture, capture_prev = 0; // use 16-bit for roll-over
volatile uint16_t input_period = 0; // use 16-bit for correct roll-over
volatile uint16_t output_width = 0;
volatile uint32_t capture_count = 0;

volatile float output_width_pct = 2.0; // modify to set output pulse width 

// FTM0 ISR -- each FTM module has a single interrupt vector, shared by all 
// channels. ISR function ftm0_isr() is declared in cores\teensy3\kinetis.h and
// defined in cores\teensy3\mk20dx128.c, with the attribute "weak", and included
// in the interrupt vector table. The "weak" attribute allows this function to
// "override" the version in mkd20dx128.c, and we don't have to do anything but
// define the function here for it to be used. The modifier FASTRUN causes the
// ISR to be copied to RAM at run-time for fastest operation.
FASTRUN void ftm0_isr()
{
  // handle interrupts for FTM0 channel 2 (input capture on rising edge of input)
  if (FTM0_STATUS & (1<<4)) {
    // clear interrupt flag by read CnSC register, then write 0 to CHF
    FTM0_C4SC &= ~FTM_CSC_CHF;
    // read input capture value from CnV register
    capture = FTM0_C4V;
    switch (capture_count) {
      default:
      case 0: // 1st capture
        break;
      case 1: // 2nd capture
        input_period = capture - capture_prev;
        output_width = output_width_pct/100.0f * input_period + 0.5;
        break;
      case 2: // 3rd capture
        FTM0_SC = 0;
        FTM0_MOD = input_period-1;
        FTM0_CNTIN = 0;
        FTM0_CNT = 0;
        FTM0_C2V = 0;
        FTM0_C3V = output_width; // (output_width_pct * input_period + 50)/100;
        FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // internal clock, prescaler=0 (div by 1)
        break;
    }
    // save capture value for next pass and increment counter for debug use
    capture_prev = capture;
    capture_count++;
  }
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( LED_BUILTIN, OUTPUT );

  // init the LED pin LOW
  digitalWriteFast( LED_BUILTIN, LOW );

  // configure pin 3 for 20 Hz frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, PWM_DUTYCYCLE );

  // configure pin 6 to be FTM0 ch4
  CORE_PIN6_CONFIG = PORT_PCR_MUX(4);

  // configure pins 9,10 to be FTM0 ch2,3
  CORE_PIN9_CONFIG  = PORT_PCR_MUX(4);
  CORE_PIN10_CONFIG = PORT_PCR_MUX(4);
  
  // disable FTM0 interrupt
  NVIC_DISABLE_IRQ(IRQ_FTM0);
  
  // init FTM0 registers
  FTM0_SC = 0; // disable
  FTM0_MOD = 0xFFFF; // count to max 16-bit and roll over to 0
  FTM0_CNTIN = 0; // reload value
  FTM0_CNT = 0; // sets CNT = CNTIN
  FTM0_MODE = FTM_MODE_WPDIS; // allow reconfiguring the CSC on the fly

  // enable ch4 (pin 6) for input capture on RISING edges and enable interrupt
  FTM0_C4SC = FTM_CSC_CHIE | FTM_CSC_ELSA;

  // set registers for COMBINE of ch2,3
  FTM0_MODE |= FTM_MODE_FTMEN; // FTMEN = 1
  FTM0_COMBINE |= FTM_COMBINE_COMBINE1; // COMBINE ch2,3
  
  // enable ch2 (pin 9) for high-true pulses (high on C2V, low on C3V)
  FTM0_C2SC = FTM_CSC_ELSB;
  FTM0_C3SC = 0;

  // set ch2,3 compare values for max value to delay match
  FTM0_C2V = 0; // start at 0
  FTM0_C3V = 0; // start at 0

  FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // internal clock, prescaler=0 (div by 1)

  // raise FTM0 interrupt level to 0 (highest priority)
  NVIC_SET_PRIORITY(IRQ_FTM0,0);
  // enable FTM0 interrupt
  NVIC_ENABLE_IRQ(IRQ_FTM0);
}

elapsedMillis loop_delay = 0;
uint32_t loop_count = 0;

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  if (loop_delay >= 1000) {
    loop_delay = 0;
    loop_count++;
    Serial.printf( "%10lu  %10lu  %5hu\n", loop_count, capture_count, input_period );
    digitalWriteFast( LED_BUILTIN, (loop_count % 2) ? HIGH : LOW );    
  }
}
 
I tried your new code with the function generator, and unfortunately the output does indeed drift with respect to the input. If I do a series of single triggers, the output pulse (left at the default of 2%) shows up at a different place each time.
 
I tried your new code with the function generator, and unfortunately the output does indeed drift with respect to the input. If I do a series of single triggers, the output pulse (left at the default of 2%) shows up at a different place each time.

Okay. Try this one, which (I think) sync's the output pulse on each rising edge of the input. However, if you do single triggers, you will likely get output pulses in between your input pulses. This program assumes a continuous stream of input pulses in the range 40-60 kHz. The reason it matters is that the output pulse is actually a PWM signal with a period of about 1 ms and a high time of your specified percentage of input width. On each rising edge of the input, the PWM clock is being reset to 0, and the next output pulse is generated. If there are no input edges in 1 ms, the PWM clock will roll over and you'll get an output pulse each time that roll-over occurs.

Code:
/**********************************************************************
* Controlled Pulse Shortener 
* FTM input capture on pin 6 (ch4) with interrupt on rising edges
* FTM COMBINE-mode PWM on pins 9,10 (ch2,3)
* T3X pins for FTM0 channels 0-7 = 22, 23, 9, 10, 6, 20, 21, 5
***********************************************************************/
#define PWM_SIGNAL_PIN (3) // must be a pin that is NOT on FTM0

#define INPUT_PIN (6) // FTM0 ch4 for input capture
#define OUTPUT_PIN (9) // FTM0 ch2,3 COMBINE -> output on ch2

#define F_FTM (F_CPU/2) // FTM base clock frequency is F_CPU/2
#define FTM_ONE_US (F_FTM/1'000'000)

#define PWM_FREQUENCY (50000) // 50000 -> 20-us period
#define PWM_DUTYCYCLE (256/2) // 50% -> 10-us high time

volatile uint16_t capture, capture_prev = 0; // use 16-bit for roll-over
volatile uint16_t input_period = 0; // use 16-bit for correct roll-over
volatile uint16_t output_width = 0;
volatile uint32_t capture_count = 0;
volatile uint32_t ftm0_sc_save;

volatile float output_width_pct = 2.0; // modify to set output pulse width 

// FTM0 ISR -- each FTM module has a single interrupt vector, shared by all 
// channels. ISR function ftm0_isr() is declared in cores\teensy3\kinetis.h and
// defined in cores\teensy3\mk20dx128.c, with the attribute "weak", and included
// in the interrupt vector table. The "weak" attribute allows this function to
// "override" the version in mkd20dx128.c, and we don't have to do anything but
// define the function here for it to be used. The modifier FASTRUN causes the
// ISR to be copied to RAM at run-time for fastest operation.
FASTRUN void ftm0_isr()
{
  // handle interrupts for FTM0 channel 2 (input capture on rising edge of input)
  if (FTM0_STATUS & (1<<4)) {
    // clear interrupt flag by read CnSC register, then write 0 to CHF
    FTM0_C4SC &= ~FTM_CSC_CHF;
    // read input capture value from CnV register
    capture = FTM0_C4V;
    switch (capture_count) {
      case 0: // 1st capture
        break;
      case 1: // 2nd capture
        input_period = capture - capture_prev;
        output_width = output_width_pct/100.0f * input_period + 0.5;
        // disable FTM0, load new C2V/C3V, re-enable (do NOT set FTM0_CNT=0)
        ftm0_sc_save = FTM0_SC;
        FTM0_SC = 0; // disable FTM clock
        //FTM0_CNT = 0; // for some reason, setting CNT=0 here causes extra pulse 
        FTM0_C2V = 0; // rising edge time 
        FTM0_C3V = output_width; // falling edge time
        FTM0_SC = ftm0_sc_save;
        break;
      default: // subsequent captures (reset FTM0 count to sync to input edge)
        FTM0_CNT = 0;
        break;
    }
    // save capture value for next pass and increment counter for debug use
    capture_prev = capture;
    capture_count++;
  }
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( LED_BUILTIN, OUTPUT );

  // init the LED pin LOW
  digitalWriteFast( LED_BUILTIN, LOW );

  // configure pin 3 for 20 Hz frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, PWM_DUTYCYCLE );

  // configure pin 6 to be FTM0 ch4
  CORE_PIN6_CONFIG = PORT_PCR_MUX(4);

  // configure pins 9,10 to be FTM0 ch2,3
  CORE_PIN9_CONFIG  = PORT_PCR_MUX(4);
  CORE_PIN10_CONFIG = PORT_PCR_MUX(4);
  
  // disable FTM0 interrupt
  NVIC_DISABLE_IRQ(IRQ_FTM0);
  
  // init FTM0 registers
  FTM0_SC = 0; // disable
  FTM0_MOD = 0xFFFF; // count to max 16-bit and roll over to 0
  FTM0_CNTIN = 0; // reload value
  FTM0_CNT = 0; // sets CNT = CNTIN
  FTM0_MODE = FTM_MODE_WPDIS; // allow reconfiguring the CSC on the fly

  // enable ch4 (pin 6) for input capture on RISING edges and enable interrupt
  FTM0_C4SC = FTM_CSC_CHIE | FTM_CSC_ELSA;

  // set registers for COMBINE of ch2,3
  FTM0_MODE |= FTM_MODE_FTMEN; // FTMEN = 1
  FTM0_COMBINE |= FTM_COMBINE_COMBINE1; // COMBINE ch2,3
  
  // enable ch2 (pin 9) for high-true pulses (high on C2V, low on C3V)
  FTM0_C2SC = FTM_CSC_ELSB;
  FTM0_C3SC = 0;

  // set ch2,3 compare values for max value to delay match
  FTM0_C2V = 0xF000; // start at 0
  FTM0_C3V = 0xF800; // start at 0

  FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // internal clock, prescaler=0 (div by 1)

  // raise FTM0 interrupt level to 0 (highest priority)
  NVIC_SET_PRIORITY(IRQ_FTM0,0);
  // enable FTM0 interrupt
  NVIC_ENABLE_IRQ(IRQ_FTM0);
}
 
Back
Top