I'm trying to emulate an existing controller and send similar signals to a Stepper driver. This driver has a Dir/Step interface like the Allegro series.
The signal I'm trying to reproduce is at 90KHz which is easy enough to do Using Timer1 in Phase-Correct PWM mode. What is troubling me is ramping up (and down) that PWM frequency from 0 to that 90KHz in about 700ms. Just calling the SetPeriod of Timer1 takes too long so I figured I could just direcly set OCR1 to 65535 and change it incrementaly to 88 (11us period with 8x Prescale).
I'm looking on any advice on how to do this while also having the slope configurable (say have it rise in 1400ms or 350ms instead of 700ms).
I tried a tight loop like this with limited results:
void Accell( long target ) //target is 88
{
for( unsigned int i=65535; i>=target; i--)
{
char oldSREG = SREG;
cli(); // Disable interrupts for 16 bit register access
ICR1 = i;
SREG = oldSREG;
sei();
delayMicroseconds(10); //This takes 712968 us for the WHOLE loop (65535 to 88)
}
}
Also, after doing a similar (but inverted loop) to bring the frequency down, even after calling stop() and disablePWM() from Timer1 I still get erratic pulses on the output pin (about 1Hz). How could I really disable the pin until the next ramp-up?
You might do better to have a fixed high frequency timer interrupt and use DDS techniques (direct digital synthesis). For instance if your stepper's max step rate is 1000 a second, you can have the timer interrupt running at say 10kHz.
For DDS you have a phase accumulator variable and a frequency variable and regularly do:
phase_acc += frequency ;
For a stepper motor you would then use the top-bit of the phase_acc variable to be the step clock, and the sign of the frequency variable as the direction pin output. As an example if your interrupt was running at 10kHz and you wanted 30 steps a second you'd use frequency = 197 (Assuming phase_inc is a 16 bit int). Then each second the phase_inc is increased by 1970000 (which would overflow the 16bit value 30 times (approx) and thus generate 30 steps if the top bit is used to drive the pin.
volatile int frequency = 0 ;
volatile int phase_inc = 0 ;
void my_isr ()
{
phase_inc += frequency ;
digitalWrite (dir_pin, frequency < 0 ? HIGH : LOW) ;
digitalWrite (step_pin, phase_inc < 0 : HIGH : LOW) ;
}
For greater resolution and accuracy long variables can be used...
Thank you, I have never heard of DDS before but it looks exactly like what I was looking for.
The thing is, I'm looking at frequencies of 90KHz for one drive (11us period) and 325KHz for another(3us period).
When I tried ISR based generation at 90KHz, I had some visible jitter on the waveform whereas it was rock solid for PWM output. That is mainly why I was looking at PWM-based generation.
Responding to an ISR in 11 (or 3!) us looks like it`s almost impossible, I can forget about digitalWrite for sure! Especially since I will be communicating with a serial port to respond to commands, that might be the opportunity to look at ARM Cortex chips( even the Maple).
In here the author measures the time needed to enter and leave ISRs ( 2.625 uS for both ). I plan on doing frequencies of 325KHz (3uS period) so that would leave me less than 500nS for the actual ISR code. I really thought that having a clock speed of 16MHz would leave me enough room but I guess not...
At 16 MHz, if you turn off interrupts, you can get as precise a timing as you want -- but then the chip can do nothing else at the same time (this includes counting time, receiving serial bytes, etc.)
Why do you need the 90 kHz? The motor probably doesn't step that often, so is this for current regulation? If so, why would a fixed, or just coarsely switched, frequency not be enough?
You could, for example, each time in loop, check the current time, and set the frequency to what you want it to be for that time. There will be slight stair-stepping in the actual frequency being output, but I doubt the motor will care much at all.
bool isRamping;
unsigned long targetTime;
unsigned long startTime;
unsigned long targetFreq;
unsigned long startFreq;
unsigned long curFreq;
void setToSpeedInTime(unsigned long toFreq, unsigned long durMs) {
startTime = millis();
targetTime = startTime + durMs;
startFreq = curFreq; // whever I targeted before
targetFreq = toFreq;
isRamping = true;
}
void maybeDoRamping() {
if (isRamping) {
unsigned long now = millis();
if ((long)(now - targetTime) >= 0) {
isRamping = false;
curFreq = targetFreq;
}
else {
curFreq = startFreq + (targetFreq - startFreq) * (now - startTime) / (targetTime - startTime);
}
set_pwm_frequency(curFreq); // your function here
}
}
void loop() {
maybeDoRamping();
if (whatever_condition()) {
setToSpeedInTime(90000, 770);
}
else if (some_other_condition()) {
setToSpeedInTime(0, 1440);
}
}
Your approach was one of the first I looked into and would be great for lower frequencies.
I need to drive the Step Controller (IMS-481) at that frequency because I am replicating part of an existing system. As we are doing this black-box style I would like to just replicate the existing signal as is. That drive btw is configured for 250 microsteps!! When I'm done, I need to scale things up to 325KHz for other drives.
I am currently playing with a Maple from LeafLabs that gives me a bit more headroom but implementing any kind of ISR-based solutions seems too slow (that rules out the DDS approach i'm afraid)
Sounds like what you really need is a high-frequency low-bitdepth DAC, and a high-bandwidth comparator, and then "play" your signal out the DAC.
With something like a 192 kHz 8-bit DAC, you should be able to feed it through SPI fast enough to drive the motor and do some control on the CPU. More than 1 byte of buffer depth would help, though... You may need to look at a higher-end CPU in the end.
I found some interesting dedicated hardware that could help, namely the AD9833 but more specifically the AD5932, which has a programmable acceleration ramp.
In the mean time I think I'll just go with a "loop-based" acceleration and deceleration function (hardcoded delayMicroseconds with varying acceleration factor) and swith to straight hardware PWM in between. That way my CPU will be available except when changing speed, which takes about 500ms in my case...
I manually do the acceleration and decelleration parts by sending pulses to the port. In the middle, I just start the timer and let the hardware do its part so I can do other stuff (like listen to the usart for commands).
Now, I just need a way of counting the number of pulses sent through the port. Remember, the timer is running at 90KHz so an ISR is out of the question. I can manually count the pulses for the acceleration ramps if need be.
Is there a way to rig up a timer to count the pulses on pin 9 (where i'm sending the PWM) and have at least one ISR for every 65535 steps or something similar?
One of the timers (Timer2, I think?) supports external clocking. That would let it serve as a counter. However, it is only an 8 bit timer, so you'd have to take an interrupt every 256 steps to keep in sync. Also, make sure nothing else is using that timer for anything else.
An alternative would be a separate counter chip. You may run out of pins if the counter is wide, so the best of worlds has something like an i2c or SPI counter chip... might as well put an outboard AVR microcontroller on there for that