Variable Frequency PWM

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Variable Frequency PWM

4,027 Views
weblar
Contributor V

Hi,

I'm wanting to generate continuous sequences of pulses, each of which sequence may have a different frequency.

For example, I need to generate the following:

10 pulses, 4800Hz

8 pulses, 4200Hz

4 pulses, 3200Hz

2 pulses, 1000Hz

1 pulse, 100Hz

2 pulses, 800Hz,

6 pulses, 1800Hz

Using the above as an example, I can't decide whether my best option is a software PWM system rather than using a FlexTimer - the latter, I'm not entirely sure will help me as I don't think I can generate the broad spectrum of frequencies necessary.

The range of frequencies can be anywhere in the range of 10Hz up to 20kHz.

The key thing to the above is that there cannot be a break in the pulse generation - the transition between frequencies must be seamless.

Any suggestions would be greatly received.

10 Replies

2,347 Views
egoodii
Senior Contributor III

You don't say 'how many simultaneous ongoing sequences' you will need, but if I can assume the quantity will fit in one FTM instantiation (8 channels on your chip?), then 'timer banged output' is the way to go --- you can set up ANY set of sequences that you can calculate in firmware and set up the 'time to next edge' (subject to discrete-width-limitations of the fundamental FTM clock on 16-bit counters) after each previous event expires.  See:

Can we Change FTM Mod value Simultaneously 

2,347 Views
weblar
Contributor V

Just single-channel output is needed - multi channel operation is not needed. What I almost want to do is to load up the FTM with a table of frequencies and steps then say go.

There could potentially be a lot of sequences - like ten's of thousands. I guess it all depends on whether the FTM can generate the required variety of frequencies, if not, then some kind of software pulse generation is probably the way to go, driven by a PIT base timer maybe?

0 Kudos

2,347 Views
egoodii
Senior Contributor III

You might be able to get FTM to 'pull' the next-event-time into the channel-compare register via DMA, I don't know --- there is certainly a 'per channel feed' into the DMA mux.   I haven't looked for that much 'hands off' interaction.  I find the interrupt-overhead to get that 'next event edge' calculated to be very small.  So with this 'timer banged' approach the FTM isn't exactly 'generating' the frequencies (you let it free-run the full 64K counts), it is merely creating EXACT time-points to be the next output-signal event, leaving firmware time to 'get around to' the task of coming up with where to put the next exact edge (i.e., any time before that next time passes!) --- presumably all while trying to get 'other work done'!  I don't think you can get a PIT-process  to create that exact output control --- there would be unavoidable jitter.

That all being said, if you are just generating ONE output, then you CAN interrupt on FTM overflow (MOD-reload) and update the MOD value (and presumably the channel PWM-count) for the next period you want.  That process will take some understanding of the synchronization mechanisms, but might be less overhead for you.

2,347 Views
mjbcswitzerland
Specialist V

Hi

There is a method in the uTasker project which allows stepper motor sequences to be generated using the FlexTimer/TPM and 2 DMA channels so that ramps for accurate acceleration/displacement/deceleration can be defined and executed very accurately and without any CPU intervention (limit of 64k pulses between at each frequency given by the 16 bit timers).

If I use a KL26 and the reference values in this thread (plus I add 20 pulses at 20kHz at the end to show that 20kHz is also not problem) it looks like this:

pastedImage_1.png
                                                    pastedImage_2.png

To do this, one TPM is used to generate a PWM output. The output is then connected to the TPM_CLKIN pin so that it can be used by a second TPM as clock source. This second TPM counts the output pulses of the first and generates a DMA trigger when it overflows (the number of pulses required at the start). This DMA triggers loading the next number of pulses to be counted and also the next output frequency on the first TPM.

A restriction is that although the frequency of the output can be accurately controlled, it is not always 50% mark-space because the TPM generates a PWM output and not a square wave. For most such controls a pulse edge is what is important (with the accurate period between each edge) but if a 50% mark space ration were needed this could be achieved by using a third DMA channel to program the 50% point (as well as the period). Since 20kHz is the highest frequency I have set 50% MSR for that.

Using the uTasker PWM interface it is easy to configure, as shown below:

First of all a table with the frequencies and pulses at each frequency is defined:

static const unsigned short rampFrequencyValue[] = {
    PWM_FREQUENCY(4800, 16),                                             // 4800Hz
    (PWM_FREQUENCY(4200, 16) - 1),                                       // 4200Hz
    (PWM_FREQUENCY(3200, 16) - 1),                                       // 3200Hz
    (PWM_FREQUENCY(1000, 16) - 1),                                       // 1000Hz
    (PWM_FREQUENCY(100, 16) - 1),                                        // 100Hz
    (PWM_FREQUENCY(800, 16) - 1),                                        // 800Hz
    (PWM_FREQUENCY(1800, 16) - 1),                                       // 1800Hz
    (PWM_FREQUENCY(20000, 16) - 1),                                      // 20kHz
};

static const unsigned short rampCountValue[] = {
    10,                                                                  // 10 pulses at first frequency
    (8 - 1),                                                             // 8 pulses at next frequency
    (4 - 1),
    (2 - 1), 
    (1 - 1),
    (2 - 1),
    (6 - 1),
    (20 - 1), 
    0,                                                                   // end (dummy due to buffer delay)
};

The frequencies are specified here using a pre-scaler of 16 and the -1 is due to the PWM comparison operation.

Then the two TPMs are configured to control the sequence via DMA and to start it.

    PWM_INTERRUPT_SETUP pwm_setup;
    pwm_setup.int_type = PWM_INTERRUPT;
    // Configure DMA trigger generator (PTB16 is clock input)
    //
    pwm_setup.pwm_mode = (PWM_EXTERNAL_CLK | PWM_PRESCALER_0 | PWM_NO_OUTPUT | PWM_FULL_BUFFER_DMA | PWM_DMA_PERIOD_ENABLE | PWM_DMA_CONTROL_FREQUENCY); // clock PWM timer from external clock
    pwm_setup.pwm_reference = (_TIMER_1 | 0);                            // timer module 1, channel 0
    pwm_setup.pwm_frequency = rampCountValue[0];                         // set the input pulse count value until overflow
    pwm_setup.pwm_value = pwm_setup.pwm_frequency;
    pwm_setup.ucDmaChannel = 2;                                          // use DMA channel 2
    pwm_setup.dma_int_priority = 0;
    pwm_setup.ucDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_TPM1_OVERFLOW;   // load next value on own timer overflow
    pwm_setup.ptrPWM_Buffer = (unsigned short *)&rampCountValue[2];      // buffer controlling the DMA trigger points
    pwm_setup.ulPWM_buffer_length = (sizeof(rampCountValue) - sizeof(unsigned short));
    pwm_setup.dma_int_handler = fnEndOfRamp;                             // call back on DMA termination
    fnConfigureInterrupt((void *)&pwm_setup);                            // enter configuration for DMA trigger generation
    FTM1_MOD = rampCountValue[1];
    // Configure PWM output signal (FRDM_KL26 PTE29 which is fed back to PTB16)
    //
    pwm_setup.pwm_mode = (PWM_SYS_CLK | PWM_PRESCALER_16 | PWM_FULL_BUFFER_DMA | PWM_DMA_PERIOD_ENABLE | PWM_DMA_CONTROL_FREQUENCY);
    pwm_setup.pwm_reference = (_TIMER_0 | 2);                            // timer module 0, channel 2 (red LED in RGB LED)
    pwm_setup.pwm_frequency = rampFrequencyValue[0];                     // set start frequency
    pwm_setup.pwm_value = _PWM_PERCENT(50, PWM_FREQUENCY(20000, 16));    // 50% PWM (high/low) when the frequency is 20kHz
    pwm_setup.ucDmaChannel = 1;                                          // use DMA channel 1
    pwm_setup.ptrPWM_Buffer = (unsigned short *)&rampFrequencyValue[1];  // buffer controlling the frequencies
    pwm_setup.ulPWM_buffer_length = (sizeof(rampFrequencyValue) - sizeof(unsigned short));
    pwm_setup.dma_int_handler = 0;                                       // no callback on DMA termination
    fnConfigureInterrupt((void *)&pwm_setup);                            // configure and start the PWM output

As can be seen, TPM1-CH0 is used to control the DMA triggers, using DMA channel 2 for its count value updates. TPM0-CH2 is used to generate an output signal, using DMA channel 1 for its frequency updates. Both DMA channels are triggered by the overflow of TPM1 (the pulse count match).

When the sequence has completed there is an (optional) interrupt called back from the DMA driver which can be used to start further such sequences or to stop the PWM output. For example:

static void fnEndOfRamp(void)
{
    FTM0_SC = 0;                                                         // stop the PWM output when the sequence has completed
}

Unfortunately this is not 100% compatible with devices with FlexTimer rather than TPM. The reason being that that they don't offer the capability of triggering the DMA on the timer overflow. Instead, the trigger has to be performed on an individual channel match instead, which means that ramp counts value are accumulated, and so looks like this instead.

static const unsigned short rampCountValue[] = {
    10,                                                                  // 10 pulses at first frequency
    (18 - 1),                                                            // 8 pulses at next frequency
    (22 - 1),
    (24 - 1),
    (25 - 1),
    (27 - 1),
    (33 - 1),
    (53 - 1),
    0,                                                                   // end (dummy due to buffer delay)
};

Rather than specifying the overflow interrupt with PWM_DMA_PERIOD_ENABLE, PWM_DMA_CHANNEL_ENABLE needs to be used.
And rather than pwm_setup.ucDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_TPM1_OVERFLOW; pwm_setup.ucDmaTriggerSource = DMAMUX_CHCFG_SOURCE_FTM0_C0 would be used....

Regards

Mark


Professional support for Kinetis: http://www.utasker.com/index.html
Remote desktop one-on-one coaching: http://www.utasker.com/services.html
Getting started to expert videos: https://www.youtube.com/results?search_query=utasker+shorts

2,347 Views
weblar
Contributor V

Hi Mark,

Your answer looks perfect for my needs.

The only fly in the ointment is that I only have a FTM peripheral not a TPM but maybe I can knock up a board which has the required hardware (or use a Teensy, for example).

I'm not too concerned by the MSR, around 50% is fine but it is pulse-edge driven so the MSR is not critical here.

As I'm going to have such a large table of frequencies/pulse counts, I'd considered using two lists which are swapped during the `fnEndOfRamp` function, with the old list being updated with a new set of values while the second list is being executed. The beauty of this solution being DMA driven is that I can then use USB or a UART to receive the new values without any worry about delays being incurred due to comms traffic.

Thanks,

Kevin

0 Kudos

2,347 Views
mjbcswitzerland
Specialist V

Kevin

Which Kinetis device are you using in your case?
As mentioned, the code is not fully compatible between parts with TPM and FlexTimer due to the fact that the FlexTimer doesn't generate a timer overflow DMA request (which is the logical one to use) and instead needs to use a counter match one. If you tell me the chip I'll check that the behavior is identical with the alternative configuration.

In my example I use a table in Flash but these things can also be calculated at run time in RAM instead. The complete interrupt can be used to inform the application SW that it can continue with other things since the sequence has terminated and of course to start a new sequence if desired. Using the time during the first sequence to calculate subsequent tables is the way to go!

Note that the final frequency in the sequence will continue to run at the end unless it is stopped. This is good for stepper-motor control (for example) where the motor is being accelerated (eg.) with a ramp shape to achieve max. torque and once it reaches the final speed it then free-runs at that final speed afterwards.

Regards

Mark

0 Kudos

2,347 Views
weblar
Contributor V

Hi Mark,

I'm planning on using a Teensy 3.5 or 3.6, which I believe are K65/K66 MCUs with both the TPM and FTM peripherals.

What I should have done to begin with was explain my application - I am wanting to drive a microstepper to follow a waveform (be it a sine wave or other pattern).

This waveform may contain data for 30 minutes or more so I think splitting it into chunks and having a PC application squirt subsequent chunks is the way to go. I don't think there is any way the Kinetis could hold all the information in RAM although I'm yet to determine how much information there is for a 30min sine waveform - I would imagine it's a lot though.

I'm very much liking the DMA feature here, where the MCU can be just left to its own devices and all I need to care about is filling up the tables.

0 Kudos

2,347 Views
mjbcswitzerland
Specialist V

Hi


Teensy3.5 and 3.6 are K64FX512VMD12 and K66FX1M0VMD18 respectively:
http://www.utasker.com/kinetis/TEENSY_3.5.html
http://www.utasker.com/kinetis/TEENSY_3.6.html


Regards

Mark

0 Kudos

2,347 Views
mjbcswitzerland
Specialist V

Hi

I checked on a FRDM-K66F board and can confirm that the operation is the same as long as I use a TPM as DMA trigger source. Also I tried generating a square wave rather than fixed MSR PWM and this looks also good:

pastedImage_1.png

The only thing is that I need to double the frequencies in the table when I use this because the output is "toggled" on each timer overflow rather than a "period" being generated (effectively halves the frequency of the output).

The only changes to the code are:

pwm_setup.pwm_reference = (_TPM_TIMER_2 | 0);                        // TPM module 2, channel 0

This is to use a TPM instead of FTM to monitor the frequency output so that a DMA trigger can be used on its timer overflow.

pwm_setup.ucDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_TPM2_OVERFLOW;   // load next value on own timer overflow

This is the new DMA trigger (from different TPM).

I still used a FTM for the actual output generation (although a TPM can be used by just specifying it rather than a TPM)

There is one thing that worries though and that is the fact that the user's manuals state that no two DMA MUX should be configured to have the same DMA trigger source. Here I use the same for two DMA channels and the operation is OK.

To avoid risks I would recommend that a second TPU (if still free) be set up exactly the same way as the first so that it also generates a DMA trigger at the same time as the first.
One of the triggers could then use the second TPM as its trigger rather than sharing the trigger with the other TPM. This would be:

pwm_setup.ucDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_TPM1_OVERFLOW;

Unfortunately I couldn't confirm the operation with purely Flex Timers - the difficulty with these is the missing overflow DMA trigger source and so a CV register has to be modified (by DMA transfer from the count table) rather than a MOD register. The MOD register works well because it is updated (synchronously) each time it overflows. The writes to the CV register take place but they are only applied/synchonised when the MOD overflows as well. I was under the impression that in the output toggle mode the new value would be loaded on the next clock but this didn't prove to be the case and the delay for it to take place is not acceptable for such an application. As long as there are two (maybe 1 if the advise in the user's manual is ignored and doesn't turn out to be a real issue) free TPMs available I see no problem with using the technique to generate such patterns without CPU overhead (or interrupt jitter, etc.) on parts with TPM, or FTM + TPM (like the K66).

Regards

Mark

0 Kudos

2,347 Views
weblar
Contributor V

Hi Mark,

Thank you for the extra information.

I'll give this a try when my hardware arrives. I think this definitely answers my question though and looks like a very good solution indeed. I will look to use the TPM/DMA solution and take into account the DMA trigger source note you've highlighted.

Kind regards,

Kevin

0 Kudos