Teensy PWM impulses

Status
Not open for further replies.

sarasini

Member
Hi all,

I want to use PWM to control stepper motors. I want generate (for instance) exactly 100 impulses. How can I do it?


Thanks
:D
 
if you know how much time 100 pulses will take, you can start an interval timer that will turn off that pin after that elapsed time.
 
is this technique accurate? I need to generate exactly the amount of pulses needed, an error cannot be accepted. Now I'm doing this without pwm (just a loop with a pin on/off), but I need to free the cpu to do rs232 communication with the pc.
 
A loop is not simply accurate, it's perfect. It can do exactly the amount of pulses I need.

Code:
do {
     step();
} 
while(!isFinished());

but obviously this code is cpu blocking. So when a rs232 packet arrives, the motors go slower.

now a motor step is generated in a way like this:

Code:
digitalWriteFast(PIN_STEP_MOT, HIGH);
delayMicroseconds(1);
digitalWriteFast(PIN_STEP_MOT, LOW);
delayMicroseconds(1);

500.000 cycle/second = 500 kilohertz

my motors can do 0.072 deg/step. So at 500KHz they do 7.2 revolutions in a second.


thanks
 
I found that on TI Stellaris board forum

The best way to implement this is with a software count down timer.

1) Configure the PWM to generate pulses "forever".
2) Configure the PWM to generate an interrupt on each pulse.
3) In the interrupt, count down from 99 (100-1). When it hits zero, configure the PWM to generate only one more pulse.

Is it possibile with teensy?
 
I'm using "5 phase stepper motors". But the motors type is not important since I already have the drivers to control them. Those drivers have pwm input
 
I found that on TI Stellaris board forum

The best way to implement this is with a software count down timer.

1) Configure the PWM to generate pulses "forever".
2) Configure the PWM to generate an interrupt on each pulse.
3) In the interrupt, count down from 99 (100-1). When it hits zero, configure the PWM to generate only one more pulse.

Is it possibile with teensy?

Short answer is yes.
Long answer will depend on what other things you are trying to do at the same time. Attaching an external interrupt to the PWM pulses may cause issues with communications on Serial/SPI/I2C and such. Any more detail will have to come from someone else since its beyond my experience and knowledge.


Browsing the datasheet has given me a few more thoughts.
The Driver uses 5v logic, the Teensy 3.1 is 3.3V. A decent logic level shifter can fix this.
The spec calls for an ON pulse greater then 90% of 5v for 5uS and an OFF pulse of less then 10% of 5v for 5uS. That's 100ish Khz max.
 
1) Configure the PWM to generate pulses "forever".
2) Configure the PWM to generate an interrupt on each pulse.
3) In the interrupt, count down from 99 (100-1). When it hits zero, configure the PWM to generate only one more pulse.

Is it possibile with teensy?

You'll need to write directly to hardware registers to accomplish this.

There are 3 timers that create the 12 different PWM signals. You'll need to look at the pinouts (the schematic is the best place) and the big table in chapter 10 of the chip's reference manual to figure out which timer (FTM0, FTM1, FTM2) and which channel from that timer you're actually using.

Each channel has a pair of registers, a 16 bit value that controls the PWM duty cycle (eg, FTM0_C5V is the value for channel 5 of the FTM0 timer), and an 8 bit register for settings (FTM0_C5SC is the settings for channel 5 of FTM0). One of those 8 bit enables the interrupt. You need to set that bit, and also use NVIC_ENABLE_IRQ() to turn the interrupt on.

Use attachInterruptVector() to configure your interrupt handler for the timer interrupt. The 3 interrupts are named IRQ_FTM0, IRQ_FTM1, and IRQ_FTM2, which you'll use with NVIC_ENABLE_IRQ and attachInterruptVector.

When the PWM compare match occurs (the falling edge), you'll get the interrupt and your function will run. The first thing you'll need to do in is clear the interrupt flag in the settings (eg, FTM0_C5SC). To turn PWM off, you'll write zeros to the config bits in that register. To keep PWM on, leave those bits as they are.

For testing, you'll probably want to leave you interrupt at the default priority level, or even configure it for a lower priority (higher numerical values are lower priority, zero is the highest priority setting). Then you can use Serial.print() or Serial1.print() inside your interrupt, because the communication interrupts default to higher priority.

Obviously for final use, you want your interrupt to be at a very high priority, so you don't risk getting 1 extra pulse if some other interrupt happens to be running when that last PWM falling edge occurs. Use NVIC_SET_PRIORITY. For example, NVIC_SET_PRIORITY(IRQ_FTM0, 0). Zero is the highest priority level.

However, keep in mind that using __disable_irq() prevents any interrupts from running, even if they're priority is zero (highest). To make this work, you'll need to make sure nothing ever disables interrupts for longer than the time from the last falling edge to the beginning of a new PWM cycle. If your interrupt is ever delayed that long, your code will run too late to prevent another extra pulse.
 
Thinking about this a bit more, I believe using DMA channels would be a much better approach.

The huge challenge, with either interrupts or DMA, is the very short time between the falling edge of the last pulse and what will be the rising edge of the next pulse, if you don't respond in that time.

Interrupt response, from the hardware event to the first instruction executed, is usually 12 to 16 cycles, depending on flash caching. That's actually pretty fast, but it depends on no equal or higher priority interrupt running, and the main program not disabling interrupts. Unfortunately, lots of important code does briefly disable interrupts. If you use any such code, your interrupt could be serviced too late and you'll get another pulse.

DMA response latency is also about a dozen cycles. You'd probably have to use 2 DMA channels, so you'd suffer that 2 of those latencies in tandem. But the huge advantage of DMA is a very low likelihood of other code disabling the DMA controller.

To use DMA, you'd probably create two DMAChannel objects. Today, the only documentation for DMAChannel is the comments in DMAChannel.h.

The first DMAChannel would simply count the pulses. You'd probably create 2 dummy variables and have it copy one to the other. The main point is you'd use transferCount() to configure that channel for the number of pulses. You'd use triggerAtHardwareEvent() to make this channel actually respond to each falling edge of the timer's PWM output.

You'd configure the other DMAChannel object to copy just 1 word, from a variable you pre-load, to the FTM timer's register (eg, FTM0_C5SC). You'd set transferCount(1), so it just does that 1 transfer. You'd use triggerAtCompletionOf() to make it do the transfer the moment your first channel complete the count. DMA channels are nice like that, one can trigger another!

This ought to have a very dependable latency of approx 2 dozen cycles, which is about 0.25 us when running at 96 MHz. The latency can vary a few cycles if the DMA engine needs to wait for bus access, but that's typically only a couple cycles... nothing as unpredictable as interrupt latency if you write or use other code that needs to disable interrupts for safe access to shared data. You'd need to make sure the time from falling edge to the start of the next pulse is more than the DMA latency. My estimate might be off a little... so plan on more than 0.25 us.

DMA also has the huge advantage of not consuming any CPU time while running. You just configure the 2 DMA channels, and then the hardware will automatically do everything. You can use attachInterrupt() on the 2nd channel if you want an interrupt routine to run right after it's completed.
 
Status
Not open for further replies.
Back
Top