Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 16 of 16

Thread: How to Make a Pulse Train of Exactly N pulses?

  1. #1
    Member ProfHuster's Avatar
    Join Date
    Aug 2018
    Location
    Pittsburgh PA
    Posts
    23

    How to Make a Pulse Train of Exactly N pulses?

    I am programming an ultrasonic distance sensor and I need a pulse train of exactly eight 40 kHz pulses at 50% duty cycle. I have tried several approaches and can't get what I want. First I used an IntervalTimer that counted down then ended the timer. I got some double pulses.
    BUT, here is my latest. The problem is that the timer is not synced with the PWM clock so it shuts off after the right time but most of the time there is a partial pulse at the beginning and as the end, so seven full pulses, and two partial.

    What is a good way of just generating eight pulses?

    Code:
    #define PIN 10
    
    IntervalTimer startTrain; // Triggers new pulse train
    IntervalTimer stopTrain; // Turns train off
    
    void setup() {
      Serial.begin(1000000L);
      delay(1000);
      Serial.println("TestDAC");
      Serial.print("Pin "); Serial.println(PIN);
      pinMode(PIN, OUTPUT);
      analogWrite(PIN, 0);
      analogWriteFrequency(PIN, 40000);
      analogWriteResolution(8); // 0 - 255
    
      startTrain.begin(startTr, 100000);
    }
    
    void loop() {
      delay(1000);
    }
    
    #define STOP_US ((int)(1e6 * 8 / 40e3 + 0.5))
    void startTr(){
      analogWrite(PIN, 127);
      stopTrain.begin(stopTr, STOP_US);
    }
    
    void stopTr(){
      analogWrite(PIN, 0);
      stopTrain.end();
    }
    
    // EOF

  2. #2
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,663
    You'll need to develop your own variation of PWM (see teensy core sources and reference manual). The timers used for PWM can be enabled to interrupt on each pulse (compare match). Then in the ISR you can increment a counter and stop the PWM when the counter reaches 8

    which Teensy are you using?

    also see https://forum.pjrc.com/threads/55580...l=1#post202556 and post #5
    Last edited by manitou; 08-24-2021 at 08:40 PM.

  3. #3
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    Did you think of using a UART at 400kbaud, 8N1, send 8 0xF0 bytes. That's active low, but you can invert if you need.

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,421
    Quote Originally Posted by MarkT View Post
    Did you think of using a UART at 400kbaud, 8N1, send 8 0xF0 bytes. That's active low, but you can invert if you need.
    Wow, nice idea

  5. #5
    Quote Originally Posted by ProfHuster View Post
    I need a pulse train of exactly eight 40 kHz pulses at 50% duty cycle.
    The program shown below will do what you want for pin 2 of Teensy 4.x. Many of the PWM pins on T4.x use the FlexPWM modules of iMRXT1062. This program uses pin 2, which is FLEXPWM module 4, sub-module 2, channel A. On each PWM period, there is a RELOAD FLAG (RF) event. That event can be configured to generate an interrupt, but as shown here you can also poll the appropriate status register (FLEXPWM4_SM2STS) for that flag. In loop(), when the FLEXPWM_SMSTS_RF flag is set, clear the flag and increment a counter, and when the counter reaches 8+1, set the duty cycle to 0, as shown here.

    I haven't done this with T3.x, which has the FlexTimer Module (FTM) rather than FlexPWM, but I'm sure something similar is possible.

    Click image for larger version. 

Name:	pwm8Cycles.png 
Views:	22 
Size:	44.5 KB 
ID:	25649

    Code:
    const int pwmPin = 2; // pin 2 = flexpwm 4, submodule 2, channel A
    
    void setup() {
      Serial.begin( 115200 );
      while (!Serial);
      Serial.println( "Starting application" );
    
      // set PWM frequency 
      analogWriteFrequency( pwmPin, 40000 );
    
      // set PWM duty cycle
      analogWrite( pwmPin, 128 );
    }
    
    uint32_t ncycle = 0;
    uint32_t maxcycle = 8;
    
    void loop() {
      if (FLEXPWM4_SM2STS & FLEXPWM_SMSTS_RF) { // if RELOAD FLAG is set
        FLEXPWM4_SM2STS = FLEXPWM_SMSTS_RF;     //   write 1 to clear
        if (++ncycle == maxcycle+1) {
          analogWrite( pwmPin, 0 );
        }
      }
    }

  6. #6
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,365
    Quote Originally Posted by Frank B View Post
    Wow, nice idea
    As an exercise for the reader, use serial as 3 bit PWM with noise-shaping to generate high quality class D output!

  7. #7
    Member ProfHuster's Avatar
    Join Date
    Aug 2018
    Location
    Pittsburgh PA
    Posts
    23
    Quote Originally Posted by manitou View Post
    You'll need to develop your own variation of PWM (see teensy core sources and reference manual). The timers used for PWM can be enabled to interrupt on each pulse (compare match). Then in the ISR you can increment a counter and stop the PWM when the counter reaches 8

    which Teensy are you using?

    also see https://forum.pjrc.com/threads/55580...l=1#post202556 and post #5
    Teensy 3.2

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,421
    Quote Originally Posted by MarkT View Post
    As an exercise for the reader, use serial as 3 bit PWM with noise-shaping to generate high quality class D output!
    I fear the noise-shaping part is above my pay grade - I know what it is, but the details... it's more than just oversampling & filtering, right?
    And I have absolutely no idea how i.e. "moving the noise near to nyquist" would work. Something with "subtracting the errors from following samples"... eh.. big question marks.

    @German readers: Is there a good explanation in German somewhere? I'd be really interested to know details, and to see a concrete algorithm would be great,

    @Prof: Big sorry for hijacking your thread. Will not happen again
    Last edited by Frank B; 08-25-2021 at 03:24 PM.

  9. #9
    Quote Originally Posted by ProfHuster View Post
    Teensy 3.2
    Okay. Here's a screenshot for T3.2, and code that works for both T3.2 and T4.x. For T3.2, I'm using pin 3, which is FTM 1, channel 0. The CHANNEL FLAG (bit 7) of the FTM1_C0SC (status and control) register goes from 0 to 1 on each PWM period, so that's the bit that gits polled. It requires a read, then write 0 to clear. For T3.2, the pin goes high for about a second before the PWM begins. I haven't looked into why that is. Going back to @manitou's post, another way to do this would be to use the output compare (OC) capability of one of the timers. The idea there would be to set up the timer to generate an event every 12.5 us (half of a 40 kHz PWM period), and toggle the output of the timer on each event, then stop the timer after 16 events.

    Click image for larger version. 

Name:	T32pwm8Cycles.png 
Views:	17 
Size:	43.2 KB 
ID:	25658


    Code:
    #if defined (__MK20DX256__)
    const int pwmPin = 3; // T3.2 pin 3 = FTM 1, channel 0
    #elif defined (__IMXRT1062__)
    const int pwmPin = 2; // T4.x pin 2 = flexpwm 4, submodule 2, channel A
    #endif
    
    void setup() {
      Serial.begin( 115200 );
      while (!Serial);
      Serial.println( "Starting application" );
    
      delay( 100 );
    
      // set PWM frequency 
      analogWriteFrequency( pwmPin, 40000 );
    
      // set PWM duty cycle
      analogWrite( pwmPin, 128 );
    }
    
    uint32_t ncycle = 0;
    #if defined (__MK20DX256__)
    uint32_t maxcycle = 8;   // to get 8 PWM cycles on T3.2
    #elif defined (__IMXRT1062__)
    uint32_t maxcycle = 8+1; // to get 8 PWM cycles on T4.x
    #endif
    
    void loop() {
    #if defined (__MK20DX256__)
      if (FTM1_C0SC & FTM_CSC_CHF) { // if CHANNEL FLAG is set
        FTM1_C0SC &= ~FTM_CSC_CHF;   //   write 0 to clear
        ncycle++;
      }
    #elif defined (__IMXRT1062__)
      if (FLEXPWM4_SM2STS & FLEXPWM_SMSTS_RF) { // if RELOAD FLAG is set
        FLEXPWM4_SM2STS = FLEXPWM_SMSTS_RF;     //   write 1 to clear
        ncycle++
      }
    #endif
      if (ncycle == maxcycle) {
        analogWrite( pwmPin, 0 );
        Serial.println( "8 x PWM cycles complete" );   
      }
    }

  10. #10
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,663
    you could generate 8 pulses with one interval timer
    Code:
    #define PIN 10
    #define PULSE_CNT 8*2
    
    IntervalTimer tmr;
    
    volatile uint32_t pulses, polarity, done;
    
    void pulser() {
      if (pulses++ < PULSE_CNT) {
        polarity = 1 - polarity;
        digitalWrite(PIN, polarity);
      } else {
        done = 1;
        tmr.end();
      }
    }
    
    void setup() {
      Serial.begin(9600);
      while (!Serial);
      pinMode(PIN, OUTPUT);
      tmr.begin(pulser, 12.5); // 1/80khz,
    
    }
    
    void loop() {
      while (!done);
      Serial.println("done");
      tmr.end();
      done = 0;
    }
    scope shows 41.7 khz (period 24 us)

  11. #11
    Member ProfHuster's Avatar
    Join Date
    Aug 2018
    Location
    Pittsburgh PA
    Posts
    23
    Thanks Joe,
    There are other tasks going on, ADC, filtering, and serial output, so I can't use polling. I also have a Teensy 4.0 if I need to switch, but I'd like to stick with the 3.2.
    If you have time, how would I set up an ISR on the RF event for that particular module, submodule, channel?

  12. #12
    Quote Originally Posted by ProfHuster View Post
    How would I set up an ISR on the RF event for that particular module, submodule, channel?
    You can set the CHIE (channel interrupt enable) flag in the same register that contains the CHF flag, so that would be as shown below. I don't have time today to go any further than that, and I'll be away for the next couple of days. Hopefully someone else can chime in with how to attach to the interrupt for FTM 1 channel 0. If you get that far, you'll want to clear CHF in the ISR the same way that I do in the polling loop. I'll be around on Saturday or Sunday and can go further if nobody else has.

    Code:
       // to enable the interrupt, write 1 to CHIE field (channel interrupt enable) 
       FTM1_C0SC |= FTM_CSC_CHIE;
    
       // to clear the interrupt when it occurs, write 0 to CHF field (channel flag)
       FTM1_C0SC &= ~FTM_CSC_CHF;

  13. #13
    Senior Member
    Join Date
    Feb 2015
    Location
    Finland
    Posts
    239
    Using SPI, you could also use DOUT (pin 7 or pin 11) with a 16-bit write (0b0101010101010101 = 0x5555 = 21845, or 0b1010101010101010 = 0xAAAA = 43690), with the SPI clock double the desired frequency. I haven't checked how close to 80,000 the SPI clock can be set, though.

    At the desired frequency, you could also use just the SCK (pin 13 or pin 14).

  14. #14
    Senior Member
    Join Date
    Feb 2015
    Location
    Finland
    Posts
    239
    Quote Originally Posted by Frank B View Post
    it's more than just oversampling & filtering, right?
    It's simpler than it seems. The English Wikipedia Noise shaping article contains a very good Operation chapter, describing the basic idea behind noise shaping.

    Essentially, whenever you produce an output sample, and it differs from the perfect modified sample value – for example, because you have fewer output bits than input bits, or because you scaled or otherwise manipulated the sample (in either the time or frequency domain) – the difference is the quantization error in this sample. These quantization errors are filtered (often using a finite impulse filter), the result dithered (rounding errors (pseudorandomly) distributed on top of the result), and added to the output signal.

    The reason why this results in noise shaping, is closely related to FIR filtering. Quantization error forms a signal that is missing from the original. So, we add white noise at the correct amplitude – this is the contribution from dithering, and is essential! –, and filter the result. If we did it right, adding the filtered signal to the original gives us a signal which is closer to the original, with the error signal much better distributed. (As mentioned in the Wikipedia article, filtering the error-with-noise signal using the inverse of one of the Fletcher-Munson curves approximating the spectral sensitivity of human hearing, can push 44.1kHz/48kHz 16-bit samples from 96dB dynamic range to 120dB; almost as if you had 20-bit samples at the same sample rate.)

    I'm not exactly up to date on this subject, since it's been a decade since I last did some real sonification work, though. I did first encounter this in 1998 or so, when LAME, the venerable MP3 encoder, gained the first psychoacoustic model (noise shaping using the inverse of the sensitivity of human hearing), much improving its encoding quality.

    Similar effects do occur in human vision, too, and on multiple levels. You might have seen the famous picture that looks like Marilyn Monroe when viewed close up, and Einstein when viewed from afar. Our sight is different (colorless) in low light conditions (which is why pirates wore an eye-patch; that eye would be in low-light mode, and ready for use in dark!). We perceive color and shade differences than actual colors (consider the example image where an obstacle throws a shadow on a checkerboard; the light squares inside the shadow have exactly the same properties as the dark squares outside the shadow, but that's not how humans perceive them). Colors even contribute to depth perception. I myself use cell shading and other graphics art techniques, like varying the thickness of outlines to hint at depth differences, to control the information conveyed in images of large atomic systems. (The purpose is exactly the same: to shape the noise, or unnecessary or unintended information, out of human perception, so the viewer gets the correct impression: perhaps with less information in my case, but at least I can somewhat control what that information is. As you know, atoms really are nothing like marbles, and they don't actually have sticks between them, but correctly simplified (to avoid forming unwanted interpretations and connotations), that kind of ball-and-stick model can convey more than say a cross section of electron densities, especially so for large systems and molecules, like proteins and such.)

  15. #15
    Member ProfHuster's Avatar
    Join Date
    Aug 2018
    Location
    Pittsburgh PA
    Posts
    23
    Quote Originally Posted by manitou View Post
    you could generate 8 pulses with one interval timer
    Code:
    #define PIN 10
    #define PULSE_CNT 8*2
    
    IntervalTimer tmr;
    
    volatile uint32_t pulses, polarity, done;
    
    void pulser() {
      if (pulses++ < PULSE_CNT) {
        polarity = 1 - polarity;
        digitalWrite(PIN, polarity);
      } else {
        done = 1;
        tmr.end();
      }
    }
    
    void setup() {
      Serial.begin(9600);
      while (!Serial);
      pinMode(PIN, OUTPUT);
      tmr.begin(pulser, 12.5); // 1/80khz,
    
    }
    
    void loop() {
      while (!done);
      Serial.println("done");
      tmr.end();
      done = 0;
    }
    scope shows 41.7 khz (period 24 us)
    I modified the code to repeat at 10 Hz. I found that this gives very stable pulse trains with a total jitter on the length of about 50 ns using a Teensy 3.2. And the frequency is accurate. I got a period of 25.1 us.

    In contrast when I used the TeensyTimerTool and a Teesny 3.2 the length of the pulse train jittered by 2.5 us and the period was stuck at an integer number of us, so the frequency was 41.6 kHz instead of 40 kHz.
    It looks like IntervalTimer is the winner.

  16. #16
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,421
    That's a problem of delay(), not the TeensyTimerTool.. apples vs oranges.
    And please stop discussing this at different threads.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •