I am using a Kinetis MK22FN512 and controlling some stepper motor drivers that take pulse inputs. I'm currently using the FTMs to generate 50% duty cycle pulse trains. It works perfectly.
Now I'm trying to implement an acceleration profile. This requires modulation of the PWM period while sending the pulse series (duty cycle fixed at 50%). The frequency of the signal will increase in a linear fashion, plateau and then decrease.
At first glance, it looks like this may be possible to do using the "Combine Mode" of the FTM. I was also thinking that DMA could be employed so that the processor doesn't need to be constantly servicing a routine.
Has anyone implemented something like this? Is it even possible? Any suggestions?
Thanks!
Hi Andrew
The uTasker project has a Stepper Motor interface which allows DMA based FlexTimer operation to generate ramps and such. For optimal use of stepper motor torque exponential ramps can be programmed.
The interface is:
Prepare ramp tables (frequencies and the number of cycles each frequency is present for in the ramp):
static const unsigned short rampFrequencyValue[] = {
PWM_FREQUENCY(4800, 16), // 4800Hz
PWM_FREQUENCY(4200, 16), // 4200Hz
PWM_FREQUENCY(3200, 16), // 3200Hz
PWM_FREQUENCY(1000, 16), // 1000Hz
PWM_FREQUENCY(100, 16), // 100Hz
PWM_FREQUENCY(800, 16), // 800Hz
PWM_FREQUENCY(1800, 16), // 1800Hz
PWM_FREQUENCY(20000, 16), // 20kHz
};
static const unsigned short rampCountValue[] = { // accumulated
10, // 10 pulses at first frequency
18, // 8 pulses at next frequency
22,
24,
25,
27,
33,
53,
0, // end (dummy due to buffer delay)
};
Configure two FlexTimers - one generating a frequency and the other using this as clock input (eg. CLKIN0) to count the cycles (with no output).
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_CHANNEL_ENABLE | PWM_DMA_CONTROL_PWM); // clock PWM timer from external clock (use channel DMA)
pwm_setup.int_handler = 0; // no user interrupt call-back on PWM cycle
pwm_setup.pwm_reference = (_TIMER_1 | 0); // timer module 1, channel 0
pwm_setup.pwm_frequency = 0xffff; // cause maximum timer count to be used
pwm_setup.pwm_value = rampCountValue[0]; // first match value
pwm_setup.ucDmaChannel = 2; // use DMA channel 2
pwm_setup.dma_int_priority = 0;
pwm_setup.usDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_FTM1_C0; // load next value on own timer match
pwm_setup.ptrPWM_Buffer = (unsigned short *)&rampCountValue[1]; // 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
pwm_setup.pwm_mode = (PWM_SYS_CLK | PWM_PRESCALER_16 | PWM_FULL_BUFFER_DMA | PWM_DMA_CHANNEL_ENABLE | PWM_DMA_CONTROL_FREQUENCY);
pwm_setup.pwm_reference = (_TIMER_0 | 2); // timer module 0, channel 2
pwm_setup.ucDmaChannel = 1; // use DMA channel 1
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.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 buffer transfer termination
fnConfigureInterrupt((void *)&pwm_setup); // configure and start the PWM output
FTM1, channel 0 is used as DMA trigger each time the required cycles have been detected.
FTM0, channel 2 is used to generate an output frequency (50% MSR at the maximum frequency of 20kHz)
On each DMA trigger (after each cycle count match) the next frequency is set and the next cycle count value incremented.
At the end of the ramp the interrupt call back fnEndoOfRamp() is called, which can prepare the next job or stop the operation. Eg.
static void __callback_interrupt fnEndOfRamp(void)
{
FTM0_SC = 0; // stop the PWM output when the sequence has completed
}
The stepper motor pulse is a fixed width one (not 50% duty cycle throughout) and the maximum ramp cycle count is 64k due to the 16 bit timer.
Regards
Mark
Complete Kinetis solutions for professional needs, training and support:http://www.utasker.com/kinetis.html
Kinetis K22:
- http://www.utasker.com/kinetis/FRDM-K22F.html
- http://www.utasker.com/kinetis/TWR-K22F120M.html
- http://www.utasker.com/kinetis/BLAZE_K22.html
- http://www.utasker.com/kinetis/tinyK22.html
uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market
Request Free emergency remote desk-top consulting at http://www.utasker.com/services.html
Thanks!
This definitely gives me something to start with. Unfortunately, all 4 of my FTMs are all being used (2 motors, 2 encoders). So I wouldn't be able to employ this particular solution if I wanted to move two motors at once. But this gets me closer to a solution.
I've only been using Kinetis and ARM for a few months. So, I'm still learning the ins and out of the architecture.
Perhaps the PIT module could be used instead of a second FTM to perform the counting/DMA-triggering...Any apparent reasons why that would not be possible?
Andrew
The PITs have neither an input nor an output so can't be used for counting PWM output cycles or generating output signals.
If you don't have high speeds and long ramping sequences you could use a single PWM output to update its next frequency on 'every' cycle (that is, when it needs to generate 10 cycles at a certain frequency it would have the same frequency value 10x in the table and then not need an input to count its output cycles.
It would however be less inefficient, especially for generating a controlled distance movement (say 1000 steps before generating a distance-moved interrupt) because it would need a 1000 entry table with the same "speed" value at each location - although DMA based operations are very efficient it still does load the bus slightly - eg. a 1MHz DMA trigger/transfer rate can become noticeable since it begins to starve the CPU's bus access.
Regards
Mark