Controlled pulse shortener

Biff

Active member
What seems like a simple Teensy 3.2 task is proving to be more perplexing than I thought. :D

I've got a device that's generating pulses (in the 50 kHz range). I need to detect the rising edge, and drive a response pulse that begins as soon as possible (maybe a 2-5% delay?), and lasts up to 50% of the width of the source pulse. The width of this source pulse would be measured in advance. The response pulses would be dynamically adjusted as the width of the source pulse changes.

I was hoping to use the Arduino PDB function to accomplish this, but unfortunately, the PDB 'pulse out' feature cannot be driven to a Teensy pin. I also thought about the FTM function, but providing an input trigger seems challenging.

Any suggestions or ideas? I think I might be overlooking something obvious....
 
When you say "the width of the source pulse would be measured in advance", do you mean the Teensy 3.2 does not need to measure it, and it will be a constant for a given build? If so, you could use this simple approach:

- configure the input pin as a digital input with interrupt on rising edge
- in the ISR, set the output high and start a one-shot timer with the desired high time
- in the timer callback, set the output low

The code below does this for Teensy 3.2. It uses the OneShotTimer from the TeensyTimerTool library, so you need that library. The pin assignments are:

- pin 3 is a PWM signal that simulates the signal from your device
- pin 8 is the input signal pin (jumper pin 3 to pin 8)
- pin 13 is the output signal (I'm using the LED for visual confirmation. You can use, say, pin 7)

I'm using a low frequency of just 10 Hz so that you can see that the LED (output pin) is blinking, but you can set this to 50 kHz and it will work, though I haven't looked at the output with a scope. Note that PULSE_WIDTH passed to the OneShotTimer::trigger() function is in microseconds. The T3.2 is fast enough to generate a 50-kHz signal this way, and I think the output pulse width will be within your 2-5% accuracy spec.

Code:
#include "TeensyTimerTool.h"

#define PWM_SIGNAL_PIN (3) // must use a pin that has PWM capability
#define INPUT_PIN (8) // all pins can be digital input with interrupt
#define OUTPUT_PIN (LED_BUILTIN) // use built-in LED for easy visual confirmation 

#define PWM_FREQUENCY (10) // 50000 -> 200-us period, 100-us high time
#define OUTPUT_PULSE_WIDTH (100) // 100 us high time

TeensyTimerTool::OneShotTimer timer1; // chooses timer from a pool

volatile uint32_t interruptCount = 0; // count of rising edge interrupts

void inputPinInterrupt()
{
  // set output pin HIGH and start one-shot timer
  digitalWriteFast( OUTPUT_PIN, HIGH );
  timer1.trigger( OUTPUT_PULSE_WIDTH );
  interruptCount++;  
}

void timerCallback()
{
  // set output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );
}

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

  // interrupt on RISING edge of input
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputPinInterrupt, RISING);

  // init the output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );

  // init one-shot timer (call timer1.trigger() to start the timer)
  timer1.begin( timerCallback );

  // configure PWM frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, 256/2 );
}

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  delay( 1000 );
  Serial.println( interruptCount );
}
 
Thanks Joe. Teensy would need to measure the width of the source pulse, and adjust the output pulse. This measurement could take place once every second or so. I'm hoping the OUTPUT_PULSE_WIDTH can become a dynamic value in your code?
 
Relatively simple way might involve connecting the input signal to 2 pins and use attachInterrupt RISING on the 1st pin and attachInterrupt FALLING on the 2nd pin. Then inside each interrupt, record the current time with either micros() or ARM_DWT_CYCCNT. Subtract them to get the pulse width, maybe apply low pass filtering or averaging over 1 second, then occasionally scale it 8 bit integer range and call analogWrite to update the output duty cycle.
 
Here's an update to the previous program that uses Paul's suggested method, but instead of using 2 pins, one for rising and one for falling edges, this one interrupts on CHANGE (both rising and falling edges). The input signal high time is computed by reading ARM_DWT_CYCCNT on all transitions and subtracting the time when signal goes high from when it goes low. This code has no filtering. Each output pulse has a high time of 1/2 of the high time of the previous input pulse. The output pulse width has to be less than input pulse width by some amount.

Code:
#include "TeensyTimerTool.h"

#define PWM_SIGNAL_PIN (3) // must use a pin that has PWM capability
#define INPUT_PIN (8) // all pins can be digital input with interrupt
#define OUTPUT_PIN (LED_BUILTIN) // use built-in LED for easy visual confirmation 

#define PWM_FREQUENCY (50000) // 50000 -> 200-us period, 100-us high time

#define ARM_CYCLES_TO_NS(cycles)  ((cycles)*(1E9/F_CPU))

TeensyTimerTool::OneShotTimer timer1; // chooses timer from a pool

volatile uint32_t highInterruptCount = 0; // count of rising edge interrupts
volatile uint32_t lowInterruptCount = 0; // count of falling edge interrupts
volatile uint32_t high_time_start = 0;
volatile uint32_t low_time_start = 0;
volatile uint32_t high_time_arm_cycles = 0;
volatile uint32_t high_time_us = 0;

void inputPinInterrupt()
{
  // set output pin HIGH and start one-shot timer
  if (digitalReadFast( INPUT_PIN ) == HIGH) {
    high_time_start = ARM_DWT_CYCCNT;
    digitalWriteFast( OUTPUT_PIN, HIGH );
    timer1.trigger( high_time_us/2 );
    highInterruptCount++;    
  }
  else {
    low_time_start = ARM_DWT_CYCCNT;
    high_time_arm_cycles = low_time_start - high_time_start;
    high_time_us = ARM_CYCLES_TO_NS( high_time_arm_cycles ) / 1000; 
    lowInterruptCount++; 
  } 
}

void timerCallback()
{
  // set output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( INPUT_PIN, INPUT );
  pinMode( OUTPUT_PIN, OUTPUT );
  
  // The following 2 lines are only necessary for T3.0, T3.1 and T3.2
  ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter

  // interrupt on both RISING and FALLING edges of input
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputPinInterrupt, CHANGE);

  // init the output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );

  // init one-shot timer (call timer1.trigger() to start the timer)
  timer1.begin( timerCallback );

  // configure PWM frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, 256/2 );
}

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  delay( 1000 );
  Serial.printf( "%10lu  %10lu  %10lu\n", highInterruptCount, lowInterruptCount, high_time_us );
}
 
Here's another update with an important change. In the previous version, "high_time_us" was an integer, and in this version it's a float. The measurement of the high time of the input pulse is in ARM clock cycles and then converted to nanoseconds, both being integers. The argument to OneShotTimer::trigger() is the timer period in microseconds, but it's type float, so we can get better than 1-us resolution if we compute float high_time_us = high_time_ns / 1000.0.

Code:
#include "TeensyTimerTool.h"

#define PWM_SIGNAL_PIN (3) // must use a pin that has PWM capability
#define INPUT_PIN (8) // all pins can be digital input with interrupt
#define OUTPUT_PIN (LED_BUILTIN) // use built-in LED for easy visual confirmation 

#define PWM_FREQUENCY (20) // 50000 -> 200-us period, 100-us high time

#define ARM_CYCLES_TO_NS(cycles)  ((cycles)*(1E9/F_CPU))

TeensyTimerTool::OneShotTimer timer1; // chooses timer from a pool

volatile uint32_t highInterruptCount = 0; // count of rising edge interrupts
volatile uint32_t lowInterruptCount = 0; // count of falling edge interrupts
volatile uint32_t high_time_start = 0;
volatile uint32_t low_time_start = 0;
volatile uint32_t high_time_arm_cycles = 0;
volatile uint32_t high_time_ns = 0;
volatile float    high_time_us = 0;

void inputPinInterrupt()
{
  // set output pin HIGH and start one-shot timer
  if (digitalReadFast( INPUT_PIN ) == HIGH) {
    high_time_start = ARM_DWT_CYCCNT;
    digitalWriteFast( OUTPUT_PIN, HIGH );
    timer1.trigger( high_time_us/2.0 ); // timer period is type float
    highInterruptCount++;    
  }
  else {
    low_time_start = ARM_DWT_CYCCNT;
    high_time_arm_cycles = low_time_start - high_time_start;
    high_time_ns = ARM_CYCLES_TO_NS( high_time_arm_cycles ); 
    high_time_us = high_time_ns / 1000.0;
    lowInterruptCount++; 
  } 
}

void timerCallback()
{
  // set output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( INPUT_PIN, INPUT );
  pinMode( OUTPUT_PIN, OUTPUT );
  
  // The following 2 lines are only necessary for TLC and T3.x
  #if defined (KINETISL) || defined(KINETISK) 
  ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
  #endif
  
  // interrupt on both RISING and FALLING edges of input
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputPinInterrupt, CHANGE);

  // init the output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );

  // init one-shot timer (call timer1.trigger() to start the timer)
  timer1.begin( timerCallback );

  // configure PWM frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, 256/2 );
}

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  delay( 1000 );
  Serial.printf( "%10lu  %10lu  %10.3f\n", highInterruptCount, lowInterruptCount, high_time_us );
}
 
I tried the latest code posted above, and a single pass shows exactly what I was hoping for.
singlepass.png

Light blue is incoming, dark blue is outgoing.

But, if I enable display persistence, the outgoing signal goes extremely unstable. I expect some minor jitter here, but this puppy is out of control. Outgoing signal is connected to scope and nothing else.
persistence.png

Some things to note: I changed the incoming signal source to a function generator so I could easily adjust the period. I also changed the frequency to 50 kHz from Joe's original 10 Hz.
 
If I enable display persistence, the outgoing signal goes extremely unstable. I expect some minor jitter here, but this puppy is out of control. Outgoing signal is connected to scope and nothing else. Some things to note: I changed the incoming signal source to a function generator so I could easily adjust the period. I also changed the frequency to 50 kHz from Joe's original 10 Hz.

There is going to be some jitter, and it's going to look much worse at 50 kHz than it will at 10 Hz. I'm guessing, but did you change the relationship output_width = input_width/2 ? You will run into trouble as output_width gets closer to input_width.

Try it with my output pulse width of "high_time_us/2", first at 1 kHz, then maybe at 10, 20, 30, 40, 50 kHz and see where you begin to have trouble. Also, if you do what Paul suggested, which is bring the input signal to two pins, configure one for RISING edge interrupts and one for FALLING edge interrupts, you would avoid the call to digitalReadFast() in my program, which could be a signficant part of the delay between the input rising edge and the output rising edge. With the input signal on two pins, you would split the existing ISR logic into two separate ISRs, one for RISING edges and one for FALLING edges.
 
Last edited:
A few ideas to reduce jitter.

Use FASTRUN on interrupt functions, so you don't suffer flash memory's slower and variable performance depending on caching.

Increase priority of critical interrupts, so they aren't blocked by others.

Don't use Serial.print() or other libraries which might use interrupts and DMA. Or if you must use it, try to do so at a time when its usage of on-chip buses would have least impact on your timing critical functions.
 
Thanks for those reminders, Paul. Here's an update with FASTRUN for the interrupt routines and the Serial.printf() statements commented out. I left the interrupts at their default priorities for now, since they should never occur at the same time.

@Biff, could you please run this and see if it reduces the jitter and is okay at 50 kHz?

Code:
#include "TeensyTimerTool.h"

#define PWM_SIGNAL_PIN (3) // must use a pin that has PWM capability
#define INPUT_PIN (8) // all pins can be digital input with interrupt
#define OUTPUT_PIN (LED_BUILTIN) // use built-in LED for easy visual confirmation 

#define PWM_FREQUENCY (50000) // 50000 -> 200-us period, 100-us high time

#define ARM_CYCLES_TO_NS(cycles)  ((cycles)*(1E9/F_CPU))

TeensyTimerTool::OneShotTimer timer1; // chooses timer from a pool

volatile uint32_t highInterruptCount = 0; // count of rising edge interrupts
volatile uint32_t lowInterruptCount = 0; // count of falling edge interrupts
volatile uint32_t high_time_start = 0;
volatile uint32_t low_time_start = 0;
volatile uint32_t high_time_arm_cycles = 0;
volatile uint32_t high_time_ns = 0;
volatile float    high_time_us = 0;

FASTRUN void inputPinInterrupt()
{
  // set output pin HIGH and start one-shot timer
  if (digitalReadFast( INPUT_PIN ) == HIGH) {
    high_time_start = ARM_DWT_CYCCNT;
    digitalWriteFast( OUTPUT_PIN, HIGH );
    timer1.trigger( high_time_us/2.0 ); // timer period is type float
    highInterruptCount++;    
  }
  else {
    low_time_start = ARM_DWT_CYCCNT;
    high_time_arm_cycles = low_time_start - high_time_start;
    high_time_ns = ARM_CYCLES_TO_NS( high_time_arm_cycles ); 
    high_time_us = high_time_ns / 1000.0;
    lowInterruptCount++; 
  } 
}

FASTRUN void timerCallback()
{
  // set output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( INPUT_PIN, INPUT );
  pinMode( OUTPUT_PIN, OUTPUT );
  
  // The following 2 lines are only necessary for TLC and T3.x
  #if defined (KINETISL) || defined(KINETISK) 
  ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
  #endif
  
  // interrupt on both RISING and FALLING edges of input
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputPinInterrupt, CHANGE);

  // init the output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );

  // init one-shot timer (call timer1.trigger() to start the timer)
  timer1.begin( timerCallback );

  // configure PWM frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, 256/2 );
}

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  delay( 1000 );
  //Serial.printf( "%10lu  %10lu  %10.3f\n", highInterruptCount, lowInterruptCount, high_time_us );
}
 
I left the interrupts at their default priorities for now, since they should never occur at the same time.

ARM systick defaults to priority 32. Most others default to 128. Lower numbers have higher priority, so you would need to lower systick below 128 or raise the pin interrupts to 0 or 16 to prevent systick from adding latency.
 
ARM systick defaults to priority 32. Most others default to 128. Lower numbers have higher priority, so you would need to lower systick below 128 or raise the pin interrupts to 0 or 16 to prevent systick from adding latency.

Oh, yes, good point. The digital input and timer interrupts shouldn't coincide, but I forgot about systick. I know how to raise the priority of the digital input interrupt, but I'll have to look into TeensyTimerTool and how to raise the priority of the OneShotTimer interrupt. I'll let the OP try the latest code and provide feedback, as I'm not clear on what are his actual requirements for "shortening" the pulse.
 
Sorry I know I am late to the table ;) And I am about to throw out here a few random thoughts. And I am a bit rusty on my T3.2 inner workings so could be completely off base.

So feel free to disregard.

With this talk here about interrupt latency and priorities, I wonder if we are missing a possibility to use the hardware timer to do this work for you.

That is if you are using the FlexTime Module (FTM), can you use the "Dual Edge Capture Mode" to do this for you?
I thought I played with this at one point, although don't remember which processor it was.

I am not seeing the code now, but it was probably similar to the code that is in Paul's library: FreqMeasureMulti:
Code:
FREQMEASUREMULTI_MARK_ONLY -> A new value becomes available at each falling ramp and contains the time since the previous rising ramp. Thus, only the "high time" of the signal period is returned

My guess is it could be done with single capture mode as well, where in the interrupt you grab the time from the appropriate register and if necessary change the register for edge...

As for the output pulses, keep wondering if again you could simply use PWM to produce the pulses, where the input code might adjust the frequency and duty...
And/Or if you can setup timer to simple one shot change the state of an IO pin, like PWM, where it drives both the start and stop pulse such that interrupt latency is not
a factor in the generated pulse. Sorry not sure if I am clear here.

Again sorry if these are just some random thoughts, but thought I would throw them out there in case it could help.
 
I believe this was for Teensy 3.2, right?

If Teensy 4.0 or 4.1 were used, an interesting possibility to use input capture, output compare (maybe 2 sync'd FlexPWM timers) together with AOI and XBAR might exist. Maybe...

Theoretically, the original pulse could route to both FlexPWM input capture and also through XBAR to an AOI where it gets a hardware logical AND with a FlexPWM output. The AOI output would route through XBAR to a GPIO pin. So the output pin would go high immediately (or with only a hardware logic gate delay) with the input. It would trigger the input capture, so the precise moment (within resolution of the 150 MHz peripheral clock) it went high could be known to the input capture interrupt. That interrupt could program an output compare to go low, causing the output pulse to end at a precise amount of time after the input capture event. Then another input capture for the falling edge of the input signal could record the exact length (again within resolution of 150 MHz peripheral clock) of the input pulse, and configure the output compare to soon go to logic high again, so the AOI could be again ready to cause the output to change immediately when the next input pulse begins. At least I believe this ought to be possible... if all the XBAR connections can be made. Looking at the diagram on page 3303, looks like it should be able to work.
 
I believe this was for Teensy 3.2, right?
Yes - but thought I would play a little more with the FTM timer...
More or less the code is borrowed from the FREQMEASUREMULTI library.

I just extracted it off to allow me see if I could measure the pulse widths with input capture...
Still WIP:
But current sketch:
Code:
void setup() {
  while (!Serial && millis() < 4000)
    ;
  Serial.begin(9600);
  pinMode(A0, INPUT_DISABLE);
  pinMode(A1, INPUT_DISABLE);
  a0_prev = analogRead(A0);
  //a1_prev = analogRead(A1);
  duty_prev = map(a0_prev, 0, 1024, 32, 224);

  analogWriteFrequency(PWM_SIGNAL_PIN, PWM_FREQUENCY);
  analogWrite(PWM_SIGNAL_PIN, duty_prev);
  //fmm.begin(5, FREQMEASUREMULTI_MARK_ONLY);
  pic.begin(5, &pic_cb);

#if 0
  Serial.printf("FTM0_SC: %08x\n", FTM0_SC);
  Serial.printf("FTM0_CNT: %08x\n", FTM0_CNT);
  Serial.printf("FTM0_MOD: %08x\n", FTM0_MOD);
  Serial.printf("FTM0_C0 SC V: %08x %08x\n", FTM0_C0SC, FTM0_C0V);
  Serial.printf("FTM0_C1 SC V: %08x %08x\n", FTM0_C1SC, FTM0_C1V);
  Serial.printf("FTM0_C2 SC V: %08x %08x\n", FTM0_C2SC, FTM0_C2V);
  Serial.printf("FTM0_C3 SC V: %08x %08x\n", FTM0_C3SC, FTM0_C3V);
  Serial.printf("FTM0_C4 SC V: %08x %08x\n", FTM0_C4SC, FTM0_C4V);
  Serial.printf("FTM0_C5 SC V: %08x %08x\n", FTM0_C5SC, FTM0_C5V);
  Serial.printf("FTM0_C6 SC V: %08x %08x\n", FTM0_C6SC, FTM0_C6V);
  Serial.printf("FTM0_C7 SC V: %08x %08x\n", FTM0_C7SC, FTM0_C7V);
  Serial.printf("FTM0_CNTIN: %08x\n", FTM0_CNTIN);
  Serial.printf("FTM0_STATUS: %08x\n", FTM0_STATUS);
  Serial.printf("FTM0_MODE: %08x\n", FTM0_MODE);
  Serial.printf("FTM0_SYNC: %08x\n", FTM0_SYNC);
  Serial.printf("FTM0_OUTINIT: %08x\n", FTM0_OUTINIT);
  Serial.printf("FTM0_OUTMASK: %08x\n", FTM0_OUTMASK);
  Serial.printf("FTM0_COMBINE: %08x\n", FTM0_COMBINE);
  Serial.printf("FTM0_DEADTIME: %08x\n", FTM0_DEADTIME);
  Serial.printf("FTM0_EXTTRIG: %08x\n", FTM0_EXTTRIG);
  Serial.printf("FTM0_POL: %08x\n", FTM0_POL);
  Serial.printf("FTM0_FMS: %08x\n", FTM0_FMS);
  Serial.printf("FTM0_FILTER: %08x\n", FTM0_FILTER);
  Serial.printf("FTM0_FLTCTRL: %08x\n", FTM0_FLTCTRL);
  Serial.printf("FTM0_QDCTRL: %08x\n", FTM0_QDCTRL);
  Serial.printf("FTM0_CONF: %08x\n", FTM0_CONF);
  Serial.printf("FTM0_FLTPOL: %08x\n", FTM0_FLTPOL);
  Serial.printf("FTM0_SYNCONF: %08x\n", FTM0_SYNCONF);
  Serial.printf("FTM0_INVCTRL: %08x\n", FTM0_INVCTRL);
  Serial.printf("FTM0_SWOCTRL: %08x\n", FTM0_SWOCTRL);
  Serial.printf("FTM0_PWMLOAD: %08x\n", FTM0_PWMLOAD);
#endif  
}
void loop() {
  int a0 = analogRead(A0);
  //int a1 = analogRead(A1);
  int duty = map(a0, 0, 1024, 32, 224);
  //uint32_t pic_value = fmm.read();
  if ((duty != duty_prev) || (pic_value != pic_prev)) {
    Serial.printf("%d\t%u(%f.2)\n", duty, pic_value, PinInputCapture::countToNanoseconds(pic_value)/1000.);
    analogWrite(PWM_SIGNAL_PIN, duty);
    duty_prev = duty;
    pic_prev = pic_value;
  }
  delay(10);
The Header file that I put the other code into... Again WIP\
Code:
class PinInputCapture;

typedef void (*PWCB)(PinInputCapture *pic, bool pin_high);

class PinInputCapture {
  public:
  bool begin(uint8_t pin, PWCB callback = nullptr);
  void end();

  inline uint32_t pulseWidth() {return _pulse_width; }
  inline uint32_t period() {return _period;}

	static float countToNanoseconds(uint32_t count);
  static uint32_t countToMicroseconds(uint32_t count);
  inline uint8_t Pin() {return _pin;}

// sort of protected
//  static void _isr();
  void isr(bool inc);
  PWCB _callback;
  uint8_t _pin;
  uint8_t _channel;
	bool _next_is_falling;

	uint32_t _raiscap_previous;
	uint32_t _fallcap_previous;
  uint32_t _pulse_width;
  uint32_t _period;

  static PinInputCapture *s_list[8];
  static uint8_t s_channelmask;
  static uint16_t s_capture_msw;
};

#define FTM_CSC_RAISING (FTM_CSC_CHIE | FTM_CSC_ELSA)
#define FTM_CSC_FALLING (FTM_CSC_CHIE | FTM_CSC_ELSB)

PinInputCapture *PinInputCapture::s_list[8] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
uint8_t PinInputCapture::s_channelmask = 0;
uint16_t PinInputCapture::s_capture_msw = 0;

bool PinInputCapture::begin(uint8_t pin, PWCB callback) {
  switch (pin) {
    case 22:
      _channel = 0;
      CORE_PIN22_CONFIG = PORT_PCR_MUX(4);
      break;
    case 23:
      _channel = 1;
      CORE_PIN23_CONFIG = PORT_PCR_MUX(4);
      break;
    case 9:
      _channel = 2;
      CORE_PIN9_CONFIG = PORT_PCR_MUX(4);
      break;
    case 10:
      _channel = 3;
      CORE_PIN10_CONFIG = PORT_PCR_MUX(4);
      break;
    case 6:
      _channel = 4;
      CORE_PIN6_CONFIG = PORT_PCR_MUX(4);
      break;
    case 20:
      _channel = 5;
      CORE_PIN20_CONFIG = PORT_PCR_MUX(4);
      break;
#if defined(KINETISK)
    case 21:
      _channel = 6;
      CORE_PIN21_CONFIG = PORT_PCR_MUX(4);
      break;
    case 5:
      _channel = 7;
      CORE_PIN5_CONFIG = PORT_PCR_MUX(4);
      break;
#endif
    default:
      return false;
  }
  _pin = pin;
  _callback = callback;

	NVIC_DISABLE_IRQ(IRQ_FTM0);
  #define FTM_SC_VALUE (FTM_SC_TOIE | FTM_SC_CLKS(1) | FTM_SC_PS(0))
	if (FTM0_MOD != 0xFFFF || (FTM0_SC & 0x7F) != FTM_SC_VALUE) {
		FTM0_SC = 0;
		FTM0_CNT = 0;
		FTM0_MOD = 0xFFFF;
		FTM0_SC = FTM_SC_VALUE;
		#ifdef KINETISK
		FTM0_MODE = FTM_MODE_WPDIS; //allow reconfiguring the CSC on the fly
		#endif
	}

	volatile uint32_t *csc = &FTM0_C0SC + _channel * 2;
	#if defined(KINETISL)
	*csc = 0;
	delayMicroseconds(1);
	#endif
	*csc = FTM_CSC_RAISING; // first capture is always rising

  _next_is_falling = false;
  s_list[_channel] = this;
	s_channelmask |= (1 << _channel);

	NVIC_SET_PRIORITY(IRQ_FTM0, 48);
	NVIC_ENABLE_IRQ(IRQ_FTM0);
  return true;
}

void PinInputCapture::end(void)
{
	switch (_channel) {
		case 0: CORE_PIN22_CONFIG = 0; break;
		case 1: CORE_PIN23_CONFIG = 0; break;
		case 2: CORE_PIN9_CONFIG  = 0; break;
		case 3: CORE_PIN10_CONFIG = 0; break;
		case 4: CORE_PIN6_CONFIG  = 0; break;
		case 5: CORE_PIN20_CONFIG = 0; break;
#if defined(KINETISK)
		case 6: CORE_PIN21_CONFIG = 0; break;
		case 7: CORE_PIN5_CONFIG  = 0; break;
#endif
		default: return;
	}
	s_channelmask &= ~(1 << _channel);
	volatile uint32_t *csc = &FTM0_C0SC + _channel * 2;
	*csc = 0;
}

void ftm0_isr(void)
{
	bool inc = false;
	if (FTM0_SC & FTM_SC_TOF) {
		#if defined(KINETISK)
		FTM0_SC = FTM_SC_VALUE;
		#elif defined(KINETISL)
		FTM0_SC = FTM_SC_VALUE | FTM_SC_TOF;
		#endif
		PinInputCapture::s_capture_msw++;
		inc = true;
	}

	uint8_t mask = FTM0_STATUS & PinInputCapture::s_channelmask;
	if ((mask & 0x01)) PinInputCapture::s_list[0]->isr(inc);
	if ((mask & 0x02)) PinInputCapture::s_list[1]->isr(inc);
	if ((mask & 0x04)) PinInputCapture::s_list[2]->isr(inc);
	if ((mask & 0x08)) PinInputCapture::s_list[3]->isr(inc);
	if ((mask & 0x10)) PinInputCapture::s_list[4]->isr(inc);
	if ((mask & 0x20)) PinInputCapture::s_list[5]->isr(inc);
	#if defined(KINETISK)
	if ((mask & 0x40)) PinInputCapture::s_list[6]->isr(inc);
	if ((mask & 0x80)) PinInputCapture::s_list[7]->isr(inc);
	#endif
}

void PinInputCapture::isr(bool inc)
{
	volatile uint32_t *csc = &FTM0_C0SC + _channel * 2;
	uint32_t capture = csc[1];
	_next_is_falling = !_next_is_falling; // toggle capture mode
	#if defined(KINETISK)
	csc[0] = (_next_is_falling ? FTM_CSC_FALLING : FTM_CSC_RAISING);
	#elif defined(KINETISL)
	csc[0] = 0; // disable
	asm volatile ("nop");
	asm volatile ("nop");
	csc[0] = (_next_is_falling ? FTM_CSC_FALLING : FTM_CSC_RAISING) | FTM_CSC_CHF;
	#endif
	if (capture <= 0xE000 || !inc) {
		capture |= (s_capture_msw << 16);
	} else {
		capture |= ((s_capture_msw - 1) << 16);
	}
	// compute the waveform period
	if (_next_is_falling) {
    _period = capture - _raiscap_previous;
		_raiscap_previous = capture;
	} else  {
    _pulse_width = capture - _raiscap_previous;
		_fallcap_previous = capture;
	}
  if (_callback) (*_callback)(this, _next_is_falling);
}

float PinInputCapture::countToNanoseconds(uint32_t count)
{
#if defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK)
	return (float)count * (1000000000.0f / (float)F_BUS);
#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL)
	return (float)count * (2000000000.0f / (float)F_PLL);
#else
	return 0.0;
#endif
}
uint32_t PinInputCapture::countToMicroseconds(uint32_t count)
{
#if defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK)
	return (uint32_t)((float)count * (1000000.0f / (float)F_BUS));
#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL)
	return (uint32_t)((float)count * (2000000.0f / (float)F_PLL));
#else
	return 0;
#endif
}

I have T3.2 with a pot_thumb 25K hooked up, that I use to change the PWM output of one pin (3) and I put a jumper to pin 5 which is a valid FTM0 pin...
And then have the code try to measure the pulse width;

screenshot.jpg
I show a little of Logic Analyzer output, and at bottom is debug outptu showing stuff, the interesting is the 2nd column which shows the delta clocks for pulse and then converted to Microseconds. Which is pretty close to what the analyzer is saying...

May play a little more with this, in curious to try now FTM output compare which should set the IO pin state.
 
UPDATE:
My goal here is to sample a pulse and produce an immediate response pulse with a high level of between 2% and 50% of the period of the sampled pulse.
I tried the latest code posted by @Joe on Friday. Unfortunately, I still see a significant amount of jitter, about 700nS in the capture below.
tek00000.png

Note: still using a function gen to provide 50 kHz input pulse, to Teensy pin 15. Response pulse measured at Teensy pin 13. I have adjusted the timer1.trigger to (high_time_us/6.0) in this example.
 
UPDATE:
My goal here is to sample a pulse and produce an immediate response pulse with a high level of between 2% and 50% of the period of the sampled pulse.
I tried the latest code posted by @Joe on Friday. Unfortunately, I still see a significant amount of jitter, about 700nS in the capture below.

Note: still using a function gen to provide 50 kHz input pulse, to Teensy pin 15. Response pulse measured at Teensy pin 13. I have adjusted the timer1.trigger to (high_time_us/6.0) in this example.

That is much better, though. There is only a little jitter in the start of the output pulse relative to the input pulse, and more jitter (variation) in the length of the output pulse. I think this is because the length of the input pulse is measured in software (via ARM_DWT_CYCCNT) rather than in hardware. That jitter should be reduced further if we raise the priority of the digital input and timer interrupts to be higher than systick. Even better would be to use an input capture to measure the width of the input pulse and and output compare to control the width of the output pulse, as discussed by Paul and Kurt. That would require more custom development, though. The solution so far is done entirely with existing libraries. I'm still not clear on your requirement, though. Do you mean that the width of the output pulse can be anywhere in the range of 2-50% of width of input pulse?

EDIT: averaging the measured input pulse width would also help, as Paul originally suggested.
 
Last edited:
The output pulse can be anywhere from 2% to 50% of the input PERIOD, not pulse width. How is the priority of the digital input and timer interrupts increased?
 
Here's an update with additional code in setup() to increase the priority of the digital input interrupt to 0 (highest). This should help reduce variability in the measurement of the input pulse width, and is probably the most important one to increase. I'm trying to figure out how to increase the priority of the TeensyTimerTool::OneShotTimer.

Code:
#include "TeensyTimerTool.h"

#define PWM_SIGNAL_PIN (3) // must use a pin that has PWM capability
#define INPUT_PIN (8) // all pins can be digital input with interrupt
#define OUTPUT_PIN (LED_BUILTIN) // use built-in LED for easy visual confirmation 

#define PWM_FREQUENCY (20) // 50000 -> 200-us period, 100-us high time

#define ARM_CYCLES_TO_NS(cycles)  ((cycles)*(1E9/F_CPU))

TeensyTimerTool::OneShotTimer timer1; // chooses timer from a pool

volatile uint32_t highInterruptCount = 0; // count of rising edge interrupts
volatile uint32_t lowInterruptCount = 0; // count of falling edge interrupts
volatile uint32_t high_time_start = 0;
volatile uint32_t low_time_start = 0;
volatile uint32_t high_time_arm_cycles = 0;
volatile uint32_t high_time_ns = 0;
volatile float    high_time_us = 0;

FASTRUN void inputPinInterrupt()
{
  // set output pin HIGH and start one-shot timer
  if (digitalReadFast( INPUT_PIN ) == HIGH) {
    high_time_start = ARM_DWT_CYCCNT;
    digitalWriteFast( OUTPUT_PIN, HIGH );
    timer1.trigger( high_time_us/2.0 ); // timer period is type float
    highInterruptCount++;    
  }
  else {
    low_time_start = ARM_DWT_CYCCNT;
    high_time_arm_cycles = low_time_start - high_time_start;
    high_time_ns = ARM_CYCLES_TO_NS( high_time_arm_cycles ); 
    high_time_us = high_time_ns / 1000.0;
    lowInterruptCount++; 
  } 
}

FASTRUN void timerCallback()
{
  // set output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( INPUT_PIN, INPUT );
  pinMode( OUTPUT_PIN, OUTPUT );
  
  // The following 2 lines are only necessary for TLC and T3.x
  #if defined (KINETISL) || defined(KINETISK) 
  ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
  #endif
  
  // interrupt on both RISING and FALLING edges of input
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputPinInterrupt, CHANGE);

  // set priority of digital input interrupt to 0 (highest) so it's higher than systick
  int irq_number = digitalPinToInterrupt(INPUT_PIN); 
  int irq_priority = NVIC_GET_PRIORITY( irq_number );
  Serial.printf( "DigInput Interrupt Number/Priority = %1d\/%1d\n", irq_number, irq_priority );
  NVIC_SET_PRIORITY( irq_number, 0 );
  irq_priority = NVIC_GET_PRIORITY( irq_number );
  Serial.printf( "DigInput Interrupt Number/Priority = %1d\/%1d\n", irq_number, irq_priority );

  // init the output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );

  // init one-shot timer (call timer1.trigger() to start the timer)
  timer1.begin( timerCallback );

  // configure PWM frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, 256/2 );
}

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  delay( 1000 );
  //Serial.printf( "%10lu  %10lu  %10.3f\n", highInterruptCount, lowInterruptCount, high_time_us );
}
 
And here's one more update that (I think) sets the priority of the OneShotTimer to 0, which should further reduce the jitter.

Code:
#include "TeensyTimerTool.h"

#define PWM_SIGNAL_PIN (3) // must use a pin that has PWM capability
#define INPUT_PIN (8) // all pins can be digital input with interrupt
#define OUTPUT_PIN (LED_BUILTIN) // use built-in LED for easy visual confirmation 

#define PWM_FREQUENCY (20) // 50000 -> 200-us period, 100-us high time

#define ARM_CYCLES_TO_NS(cycles)  ((cycles)*(1E9/F_CPU))

using namespace TeensyTimerTool; // need this to use symbol "FTM0"
OneShotTimer timer1(FTM0); // use first channel of FTM0

volatile uint32_t highInterruptCount = 0; // count of rising edge interrupts
volatile uint32_t lowInterruptCount = 0; // count of falling edge interrupts
volatile uint32_t high_time_start = 0;
volatile uint32_t low_time_start = 0;
volatile uint32_t high_time_arm_cycles = 0;
volatile uint32_t high_time_ns = 0;
volatile float    high_time_us = 0;

FASTRUN void inputPinInterrupt()
{
  // set output pin HIGH and start one-shot timer
  if (digitalReadFast( INPUT_PIN ) == HIGH) {
    high_time_start = ARM_DWT_CYCCNT;
    digitalWriteFast( OUTPUT_PIN, HIGH );
    timer1.trigger( high_time_us/2.0 ); // timer period is type float
    highInterruptCount++;    
  }
  else {
    low_time_start = ARM_DWT_CYCCNT;
    high_time_arm_cycles = low_time_start - high_time_start;
    high_time_ns = ARM_CYCLES_TO_NS( high_time_arm_cycles ); 
    high_time_us = high_time_ns / 1000.0;
    lowInterruptCount++; 
  } 
}

FASTRUN void timerCallback()
{
  // set output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );
}

void setup()
{
  // init USB serial and wait for ready
  Serial.begin(9600);
  while (!Serial) {}
  
  // configure pins
  pinMode( PWM_SIGNAL_PIN, OUTPUT );
  pinMode( INPUT_PIN, INPUT );
  pinMode( OUTPUT_PIN, OUTPUT );
  
  // The following 2 lines are only necessary for TLC and T3.x
  #if defined (KINETISL) || defined(KINETISK) 
  ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
  #endif
  
  // interrupt on both RISING and FALLING edges of input
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputPinInterrupt, CHANGE);

  // set priority of digital input interrupt to 0 (highest) so it's higher than systick
  int irq_number = digitalPinToInterrupt(INPUT_PIN); 
  int irq_priority = NVIC_GET_PRIORITY( irq_number );
  Serial.printf( "DigInput Interrupt Number/Priority = %1d/%1d\n", irq_number, irq_priority );
  NVIC_SET_PRIORITY( irq_number, 0 );
  irq_priority = NVIC_GET_PRIORITY( irq_number );
  Serial.printf( "DigInput Interrupt Number/Priority = %1d/%1d\n", irq_number, irq_priority );

  // init the output pin LOW
  digitalWriteFast( OUTPUT_PIN, LOW );

  // init one-shot timer (call timer1.trigger() to start the timer)
  timer1.begin( timerCallback );

  // set priority of OneShotTimer (on FTM0) to 0
  irq_number = IRQ_FTM0;
  irq_priority = NVIC_GET_PRIORITY( irq_number );
  Serial.printf( "OneShot Interrupt Number/Priority = %1d/%1d\n", irq_number, irq_priority );
  NVIC_SET_PRIORITY( irq_number, 0 );
  irq_priority = NVIC_GET_PRIORITY( irq_number );
  Serial.printf( "OneShot Interrupt Number/Priority = %1d/%1d\n", irq_number, irq_priority );

  // configure PWM frequency and 50% duty cycle
  analogWriteFrequency( PWM_SIGNAL_PIN, PWM_FREQUENCY );
  analogWrite( PWM_SIGNAL_PIN, 256/2 );
}

void loop()
{
  // wait 1 second and print interrupt count, just to confirm frequency 
  delay( 1000 );
  //Serial.printf( "%10lu  %10lu  %10.3f\n", highInterruptCount, lowInterruptCount, high_time_us );
}
 
Sorry, I don't know the full requirements here, for your T3.2 and the like...

But if this is the only thing you wish for the Teensy to do, you can keep it simple and don't use timers, or interrupt or... And do it brute force.

Example:

Code:
int a0_prev;
int duty_prev;
#define PWM_SIGNAL_PIN 3  // must use a pin that has PWM capability
#define READ_PIN 5
#define ECHO_PIN 6
#define PWM_FREQUENCY (50000)  // 50000 -> 200-us period, 100-us high time

uint32_t last_start_time;
uint32_t start_time;
uint32_t stop_time;
uint32_t pulse_width;
uint32_t cycle_time;

uint32_t echo_width = 10;  // first one...

void setup() {
  while (!Serial && millis() < 4000)
    ;
  pinMode(A0, INPUT_DISABLE);
  a0_prev = analogRead(A0);
  duty_prev = map(a0_prev, 0, 1024, 32, 224);

  analogWriteFrequency(PWM_SIGNAL_PIN, PWM_FREQUENCY);
  analogWrite(PWM_SIGNAL_PIN, duty_prev);

  pinMode(READ_PIN, INPUT);
  pinMode(ECHO_PIN, OUTPUT);
  pinMode(23, OUTPUT);  // debug

  // maybe calibrate for first/last
  while (digitalReadFast(READ_PIN) == 0)
    ;
  start_time = micros();
  while (digitalReadFast(READ_PIN) != 0)
    ;
  stop_time = micros();
  pulse_width = stop_time - start_time;
  echo_width = pulse_width / 2;
}

void loop() {
  while (1) {
    last_start_time = start_time;
    // try two reads just to make sure...
    while (digitalReadFast(READ_PIN) == 0) ;
    start_time = micros();
    digitalWriteFast(ECHO_PIN, HIGH);
    digitalWriteFast(23, HIGH);
    bool end_pulse_detected = false;
    uint32_t time_stop_echo = start_time + echo_width;
    while (micros() < time_stop_echo) {
      if (!end_pulse_detected) {
        if (digitalReadFast(READ_PIN) == 0) {
          stop_time = micros();
          digitalWriteFast(23, LOW);
          end_pulse_detected = true;
        }

      }
    }
    digitalWriteFast(ECHO_PIN, LOW);
    if (!end_pulse_detected) {
      while (digitalReadFast(READ_PIN) != 0) ;
      digitalWriteFast(23, LOW);
      stop_time = micros();
    }
    pulse_width = stop_time - start_time;
    cycle_time = start_time - last_start_time;

    // maybe adjust echo_width
    echo_width = pulse_width / 2;


    // some way to adjust on same board.
    // check analog changes... as I am driving signal on same board.
#if 1
    int a0 = analogRead(A0);
    int duty = map(a0, 0, 1024, 32, 224);
    if (fudge(duty, duty_prev, 3)) {
      Serial.println(duty);
      analogWrite(PWM_SIGNAL_PIN, duty);
      duty_prev = duty;
    }
#endif    
  }
}
inline bool fudge(int x1, int x2, int amount) {
  x1 -= x2;
  if ((x1 > amount) || (x1 < -amount)) return true;
  return false;
}

Again unclear on how you wish to control the actual width of the response. Right now I have it with half the size of the previous pulse...

I also have a thumb control on breadboard where I am changing the duty.
I also have another pin, that shows me when I detect the pulse went high and low...

I am still playing around with using timer capture and trying to output, but forgot how much slower T3.2 is versus T4...

Not sure if this sketch helps or not, but thought I would throw it out there...

Obviously you could change from using micros() to arm cycles... if micros does not have the resolution you need.
 
Hi Kurt. Just a note that my comment in the line that defines PWM_FREQUENCY is incorrect. 50 kHz signal has a period of only 20 us, not 200 us. According to the OP's spec, the output pulse must have high time less than 50% of period, so that would be < 10 us.
 
@KurtE, I tried the code you suggested, and am seeing nearly the same thing. But note that the rising edge of the echo signal also has some jitter this time.
tek00002.png

@joe, I tried your latest code that modifies priority of the OneShot, but unfortunately I did not see any improvement. Jitter was still around 700nS.

Maybe we're barking up the wrong tree here ?
 
Last edited:
@joe, I tried your latest code that modifies priority of the OneShot, but unfortunately I did not see any improvement. Jitter was still around 700nS.

Maybe we're barking up the wrong tree here ?

The method we've been trying is the best I can think of that doesn't involve developing any low-level code. If it needs to be better, I would want to get away from the digital input interrupt and use a flex timer channel for input capture instead. If you started from the library FreqMeasureMulti, you could copy/paste code that sets up an FTM0 channel for input capture on both edges of the input signal. You would modify the ISR to (a) compute the high time on each falling edge, and (b) start the OneShotTimer for the output on each rising edge. That should be better, but if it's still not good enough, you could eliminate the OneShotTimer by choosing another channel on FTM0 and use its output compare feature to generate the rising and falling edges of the output signal. Since all channels of a given flex timer module share the same clock and counter, you could define the output edges to occur at specific delays from the rising edge of the input pulse, and there would be close to no jitter. The high time of the input signal would be computed by subtracting the input capture of the rising edge of input from falling edge of input, which you can see in the FreqMeasureMulti source code. Once you have the high time, you could use the output compare feature of the second channel to trigger the rising and falling edges of the output relative to the rising edge of the input. If your input pulses are very regular, so will be the output pulses, with close to 0 jitter.
 
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.

But again reasonably more complex code.
 
Back
Top