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:


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),
(PWM_FREQUENCY(4200, 16) - 1),
(PWM_FREQUENCY(3200, 16) - 1),
(PWM_FREQUENCY(1000, 16) - 1),
(PWM_FREQUENCY(100, 16) - 1),
(PWM_FREQUENCY(800, 16) - 1),
(PWM_FREQUENCY(1800, 16) - 1),
(PWM_FREQUENCY(20000, 16) - 1),
};
static const unsigned short rampCountValue[] = {
10,
(8 - 1),
(4 - 1),
(2 - 1),
(1 - 1),
(2 - 1),
(6 - 1),
(20 - 1),
0,
};
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;
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);
pwm_setup.pwm_reference = (_TIMER_1 | 0);
pwm_setup.pwm_frequency = rampCountValue[0];
pwm_setup.pwm_value = pwm_setup.pwm_frequency;
pwm_setup.ucDmaChannel = 2;
pwm_setup.dma_int_priority = 0;
pwm_setup.ucDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_TPM1_OVERFLOW;
pwm_setup.ptrPWM_Buffer = (unsigned short *)&rampCountValue[2];
pwm_setup.ulPWM_buffer_length = (sizeof(rampCountValue) - sizeof(unsigned short));
pwm_setup.dma_int_handler = fnEndOfRamp;
fnConfigureInterrupt((void *)&pwm_setup);
FTM1_MOD = rampCountValue[1];
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);
pwm_setup.pwm_frequency = rampFrequencyValue[0];
pwm_setup.pwm_value = _PWM_PERCENT(50, PWM_FREQUENCY(20000, 16));
pwm_setup.ucDmaChannel = 1;
pwm_setup.ptrPWM_Buffer = (unsigned short *)&rampFrequencyValue[1];
pwm_setup.ulPWM_buffer_length = (sizeof(rampFrequencyValue) - sizeof(unsigned short));
pwm_setup.dma_int_handler = 0;
fnConfigureInterrupt((void *)&pwm_setup);
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;
}
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,
(18 - 1),
(22 - 1),
(24 - 1),
(25 - 1),
(27 - 1),
(33 - 1),
(53 - 1),
0,
};
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