Agreed - it is explained in great detail in the datasheet. The way I understand the information in the datasheet, the OCR and ICR registers can be manipulated via interrupts, and (EDIT: bold) placing the timer in CTC output toggle mode will cause changes to the OCR or ICR value to affect the frequency of the rising edge of the pulses.
More specifically, Chapter 17, section 9, explains the modes of the timers. "CTC" is the "clear timer on compare match" mode (table 17-2 on page 145, and section 17.9.2 starting on page 145), wherein the timer/counter increments until TCNT matches OCR or ICR, sets an output compare match flag (and calls the associated interrupt if enabled in TIMSK), then resets TCNT from zero. Furthermore, in "Compare Output Mode, non-PWM", the output compare pins (in my particular test case, OC3A) can be toggled on compare match by setting the appropriate COMnx bits in the TCCRA register. The OCnx behaviors for CTC mode are detailed in table 17-3 on page 155 of the datasheet.
In this application, each stepper motor has it's own dedicated timer output pin. The final application will have a total of seven stepper drives, using the following timer outputs:
- OC1A
- OC2A
- OC3A
- OC4A/B (dual motor subassembly)
- OC5A/B (dual motor subassembly)
The dual motor subassemblies are strategically placed on the same timer as the motors need to be synchronized while running, and I can toggle both output compare pins on a single timer at the same frequency, given the appropriate bits are set in the TCCRA register. The reason I want to use timers for the step pulse is to free up the mega for other tasks in between speed ramps and counting steps (hierarchical state machine).
Based on my "simple sketch" trials (prior to writing the CL42T class), I concluded that I can set up the timers and an OCR array for the desired movement, start the movement, and then simply monitor for the end of the domino line via one of two stop conditions:
- Step target for the object exists and has been reached (handled by ISR).
- StopDrive() member function has been called on the object from somewhere else in the code (based on hierarchical State Machine conditions).
Here is an example sketch that can be used to reproduce the "ramp frequency" of the stepper motor, by varying the OCR value from within the output compare ISR (again using timer 3 in this sketch):
#include "math.h"
#define StartTimer3 TCCR3B |= (1<<CS31) // prescale 8
#define StopTimer3 TCCR3B &= ~(1<<CS31) // turn off timer/counter
#define Enable3ATimerInterrupt TIMSK3 |= (1<<OCIE3A) // enable COM3A interrupt
#define Disable3ATimerInterrupt TIMSK3 &=~(1<<OCIE3A) // disable COM3A interrupt
const double CntFreq = 2000000; // Hz
const double MaxSpeed = 800; // mm/s
const double Accel = (MaxSpeed*10); // mm/s/s
const double Decel = (MaxSpeed*10); // mm/s/s
const double mmPerStep = 0.04; // mm/step
volatile long stepsTaken = 0;
long numSteps = 27774;
int speedIncreaseStepInterval = 0;
// variables for speed ramp calculation
long maxAccelSteps = 0;
long maxSpeedSteps = 0;
long accelSteps = 0;
long decelValue = 0;
long decelSteps = 0;
long numStepsTemp = 0;
long OCRmin = 0;
long OCRo = 0;
long OCR_next = 0;
long OCR = 0;
long OCRtemp = 0;
int OCR_array[21];
volatile int accelDecelIndex = 0;
void setup()
{
Serial.begin(115200);
while (!Serial)
{
; // wait for serial to connect
}
}
void loop()
{
// clear timer registers
TCCR3A = 0;
TCCR3B = 0;
TCCR3C = 0;
TIMSK3 = 0;
TIFR3 = 0;
TCNT3H = 0;
TCNT3L = 0;
OCR3AH = 0;
OCR3AL = 0;
// setup timer registers, don't turn on timer
TCCR3A |= (1<<COM3A0); // COM3A output enable
TCCR3B |= (1<<WGM32); // WGMn2 = 1 (CTC mode)
// set pin mode and initial state for Step pin
DDRE |= B00001000; // set pin mode for OCR3A to output
PORTE &= B11110111; // set step pin LOW
// enable timer 3 compare match interrupt
Enable3ATimerInterrupt;
// calculate OCR array for desired movement
CalcForMove(numSteps);
// set OCR registers
noInterrupts();
OCR3AH = highByte((uint16_t)OCR_array[0]);
OCR3AL = lowByte((uint16_t)OCR_array[0]);
interrupts();
// confirm OCR register values
Serial.print(F("OCR3AL: "));Serial.println(OCR3AL,HEX);
Serial.print(F("OCR3AH: "));Serial.println(OCR3AH,HEX);
// turn on 24VDC
Serial.print(F("Turning on 24VDC control relay..."));
DDRF |= B00010000;
PORTF |= B00010000;
Serial.println(F("Done."));
delay(1000);
// set stepper drive enable pin HIGH
Serial.print(F("Enabling Element Conveyor Drive..."));
DDRA |= B00000100;
PORTA |= B00000100;
Serial.println(F("Done."));
delayMicroseconds(5);
// set stepper drive direction pin
Serial.print(F("Setting direction to FWD..."));
DDRC |= B00000100;
PORTC |= B00000100;
Serial.println(F("Done."));
// start timer to start the move
Serial.println(F("Starting movememt."));
delay(2500);
StartTimer3;
// monitor for movement complete
while (stepsTaken < numSteps)
{
delay(250); // wait until move ends
}
delay(2500);
// set stepper drive enable pin LOW
Serial.print(F("Disabling Element Conveyor Drive..."));
PORTA &= ~(B00000100);
Serial.println(F("Done."));
delay(2500);
// turn off 24VDC
Serial.print(F("Turning off 24VDC Control Relay..."));
PORTF &= ~(B00010000);
Serial.println(F("Done."));
delay(5000);
}
ISR (TIMER3_COMPA_vect)
{
if ((PINE & B00001000) != 0)
{
stepsTaken++;
if (stepsTaken >= numSteps)
{
StopTimer3;
Disable3ATimerInterrupt;
accelDecelIndex = 0;
}
if (stepsTaken <= accelSteps)
{ // acceleration phase
if (stepsTaken % speedIncreaseStepInterval == 0)
{
accelDecelIndex++;
OCR = OCR_array[accelDecelIndex];
}
}
else if (stepsTaken >= (numSteps - decelSteps))
{ // deceleration phase
if (stepsTaken % speedIncreaseStepInterval == 0)
{
OCR = OCR_array[accelDecelIndex];
accelDecelIndex--;
}
}
OCR3AH = highByte((uint16_t)OCR);
OCR3AL = lowByte((uint16_t)OCR);
}
}
void CalcForMove(long Steps)
{
OCRmin = ((mmPerStep*(CntFreq/2))/MaxSpeed); // calculate minimum OCR value based on maxSpeed
OCRo = ((CntFreq/2)*sqrt((2*mmPerStep)/Accel)); // calculate first OCR value based on acceleration
maxSpeedSteps = ((MaxSpeed*MaxSpeed)/(2*mmPerStep*Accel)); // steps to reach maxSpeed
maxAccelSteps = ((Steps*Decel)/(Accel+Decel)); // steps before deceleration phase
if (maxSpeedSteps < maxAccelSteps)
{ // trapezoid speed profile
decelSteps = (maxSpeedSteps*(Accel/Decel));
accelSteps = maxSpeedSteps;
}
else
{ // triangular speed profile
accelSteps = maxAccelSteps;
decelSteps = (Steps - accelSteps);
}
if ((Steps-decelSteps) < accelSteps)
{ // rounding correction
numStepsTemp = accelSteps;
accelSteps = decelSteps;
decelSteps = (Steps-numStepsTemp);
numStepsTemp = 0;
}
speedIncreaseStepInterval = accelSteps / ((sizeof(OCR_array)/2)-1); // figure out speed update interval based on OCR array size
OCR_array[0] = OCRo; // set first OCR value in OCR array
for (int i=1;i<(sizeof(OCR_array)/2);i++)
{ // calculate and populate OCR array
OCR_array[i] = (OCRo*(sqrt((i*speedIncreaseStepInterval)+1)-sqrt((i*speedIncreaseStepInterval))));
}
stepsTaken = 0; // initialize steps taken to zero
}