MEGA 2560 - Odd Timer Behavior first loop and intermittent thereafter

Hello, everyone! This is my first post, so first off: thank you to everyone around this community for all of the help you've unknowingly provided to me over the years :slight_smile:

Now to my issue. I'm writing a class for a stepper motor driver, and I'm using the arduino timers (Timer 3) to control the step pulses to the motor driver. The essence of what I'm going for is the following:

  • Stepper Speed Ramp Class: contains speed ramp information like max speed, acceleration, deceleration, an array for OCR values, and a couple member functions for setting max speed and calculating the speed ramp (OCR values in the array).

  • CL42T Class: this class inherits (publicly) the Stepper Speed Ramp class, and contains timer register pointers, I/O port register pointers, timer masks, and a few other member variables. This class sets the pointers to the appropriate addresses in the constructor, and has member functions for SetupDrive() (set the timer control registers, clear count, etc.), Enable/DisableDrive (set enable pin high/low), SetDirection(char) (set direction pin high or low), run/stop (start/stop timer), and Increment (set target steps, turn on timer interrupt, call RunDrive()).

  • I plan on writing more classes which will inherit CL42T and will have subassembly context member functions (i.e. Initialize, Jog, etc.)

To test this class structure, I've written a sketch to test run the stepper motor for one revolution (of the subassembly), increase the speed, and repeat. Unfortunately, I am experiencing very odd behavior out of the timer during the first loop of my sketch (as well as the fourth loop) where The timer seems to be at a much higher frequency than expected, thus running the motor at some awful speed and not the currently set "maxSpeed". Loops 2 and 3, as well as up to 10 have shown the expected behavior, but I have not gone beyond 10 to look for any deeper patterns.

I know that the init() function adjusts the timer registers before setup, so I clear the control registers for timers 1 through 5 in setup(). I want to potentially use micros() and/or millis() for this application, so I'm steering clear of messing with Timer 0. While reading through the datasheet to find potential causes to my issue, I noticed that the USART0 interrupts have a higher priority than the Timer 3 interrupts, so I suspected maybe something with the serial dumps in my test were interfering with the timer output compare interrupt. Unfortunately, with all of the serial removed from my sketch, the incorrect behavior remains. I've also tried (total shotgun move here) "synchronizing" the prescale for Timers 1,3,4,5 (GTCCR |= 1;), but I don't think I fully grasp exactly what this does, and it did not resolve the issue.

After all of my troubleshooting, I've stayed consistent with two potential root causes (although I can't seem to locate the smoking gun for either):

  1. Through the class structure, I'm setting timer control registers in an incorrect order or something of the sort. I've written many successful test sketches directly manipulating the timer control registers to get a square wave output, but I've never wrapped them in a class like this. Perhaps there is something out of order that I'm simply not seeing. Even with serial printing all of the register values (directly - not using my class structure), I cannot see anything wrong with the settings just before starting the timer/counter.

  2. Some sort of overflow or other calculation / data type error. I've looked through this, and looked up all of the C++ standard promotions, but I can't seem to find anywhere that would cause a problem in the CalculateSpeedRamp() member function. Furthermore, I've printed out the OCR array values and confirmed them against manual calculations - no issues found. While some of the values are quite large, I'm using large data types, and the resulting OCR values seem to match up to my manual calculations. Directly accessing the OCR values just prior to starting the movement also confirms the correct settings.

I had originally thought that the Timer0 interrupts might be taking excessive time and interfering with my speed ramp, but I no longer think that is the case. For one, I set and confirm the OCR value at multiple points prior to starting the movement. If the interrupt were being interfered with (blocked), then my drive would not speed up and would stay at the initial OCR value. However, since my drive is going way too fast during the "error loops", that cannot be the cause. Somehow, either the prescale bits in the timer are not being set properly, thus leading to a higher frequency count, or the OCR registers are not being written properly. Either one of these I have not been able to find as the serial printouts confirm desired settings.

Can anyone please help find where I've gone wrong? @nickgammon , parhaps?

I'm embedding the code below, and I'll attach my serial data output. Thanks in advance!

EDIT: I'm using VS Code 1.62.0 with Arduino Extension 0.4.7 and C/C++ 1.7.1

CL42TClassTest.ino:

#include "CL42T.h"

// prescale value
int PreScale = 8;

// Step Counter
volatile unsigned long TestStepperSteps = 0;
bool oneShot = true;

const char TestStepperStepPort = 'E';
const int TestStepperStepBit = 3;
const int TestStepperControlBit = 2;
const int TestStepperTimerNum = 3;
const char TestStepperTimerOutput = 'A';
const int TestStepperOneRevSteps = 27774;

// create test stepper object (global object)
CL42T TestStepperDriveObject(TestStepperStepPort,TestStepperStepBit,TestStepperControlBit,TestStepperTimerNum,TestStepperTimerOutput,PreScale);

// Macros for anscillary equipment
#define VDC24_Setup DDRF |= B00010000   // set 24VDC Control Relay pin to OUTPUT
#define VDC24_On    PORTF |= B00010000  // set 24VDC Control Relay pin HIGH
#define VDC24_Off   PORTF &= B11101111  // set 24VDC Control Relay pin LOW

// max speed variable
int MAXspeed = 60; 

void setup() {
    Serial.begin(115200); // start serial at 115200 BAUD
    while (!Serial) {
        ; // wait for serial to connect
    }
    // header
    Serial.println(F("Program to test CL42T Class."));
    // clear timer registers and print verification
    clearTimersOneToFive();
    Serial.println(F("Timers 1-5 Register Values After Reset:"));
    Serial.print(F("TCCR0A Value: "));prntByteBIN(TCCR0A);
    Serial.print(F("TCCR0B Value: "));prntByteBIN(TCCR0B);
    Serial.print(F("TCNT0 Value: "));Serial.println(TCNT0,HEX);
    Serial.print(F("OCR0A Value: "));Serial.println(OCR0A,HEX);
    Serial.print(F("TIMSK0 Value: "));prntByteBIN(TIMSK0);
    Serial.print(F("TIFR0 Value: "));prntByteBIN(TIFR0);
    Serial.print(F("TCCR1A Value: "));prntByteBIN(TCCR1A);
    Serial.print(F("TCCR1B Value: "));prntByteBIN(TCCR1B);
    Serial.print(F("TCCR1C Value: "));prntByteBIN(TCCR1C);
    Serial.print(F("TCNT1 Value: "));Serial.println(TCNT1,HEX);
    Serial.print(F("OCR1A Value: "));Serial.println(OCR1A,HEX);
    Serial.print(F("TIMSK1 Value: "));prntByteBIN(TIMSK1);
    Serial.print(F("TIFR1 Value: "));prntByteBIN(TIFR1);
    Serial.print(F("TCCR2A Value: "));prntByteBIN(TCCR2A);
    Serial.print(F("TCCR2B Value: "));prntByteBIN(TCCR2B);
    Serial.print(F("TCNT2 Value: "));Serial.println(TCNT2,HEX);
    Serial.print(F("OCR2A Value: "));Serial.println(OCR2A,HEX);
    Serial.print(F("TIMSK2 Value: "));prntByteBIN(TIMSK2);
    Serial.print(F("TIFR2 Value: "));prntByteBIN(TIFR2);
    Serial.print(F("TCCR3A Value: "));prntByteBIN(TCCR3A);
    Serial.print(F("TCCR3B Value: "));prntByteBIN(TCCR3B);
    Serial.print(F("TCCR3C Value: "));prntByteBIN(TCCR3C);
    Serial.print(F("TCNT3 Value: "));Serial.println(TCNT3,HEX);
    Serial.print(F("OCR3A Value: "));Serial.println(OCR3A,HEX);
    Serial.print(F("TIMSK3 Value: "));prntByteBIN(TIMSK3);
    Serial.print(F("TIFR3 Value: "));prntByteBIN(TIFR3);
    Serial.print(F("TCCR4A Value: "));prntByteBIN(TCCR4A);
    Serial.print(F("TCCR4B Value: "));prntByteBIN(TCCR4B);
    Serial.print(F("TCCR4C Value: "));prntByteBIN(TCCR4C);
    Serial.print(F("TCNT4 Value: "));Serial.println(TCNT4,HEX);
    Serial.print(F("OCR4A Value: "));Serial.println(OCR4A,HEX);
    Serial.print(F("TIMSK4 Value: "));prntByteBIN(TIMSK4);
    Serial.print(F("TIFR4 Value: "));prntByteBIN(TIFR4);
    Serial.print(F("TCCR5A Value: "));prntByteBIN(TCCR5A);
    Serial.print(F("TCCR5B Value: "));prntByteBIN(TCCR5B);
    Serial.print(F("TCCR5C Value: "));prntByteBIN(TCCR5C);
    Serial.print(F("TCNT5 Value: "));Serial.println(TCNT5,HEX);
    Serial.print(F("OCR5A Value: "));Serial.println(OCR5A,HEX);
    Serial.print(F("TIMSK5 Value: "));prntByteBIN(TIMSK5);
    Serial.print(F("TIFR5 Value: "));prntByteBIN(TIFR5);
    // setup 24VDC relay and ensure OFF
    Serial.print(F("Setting up 24VDC control relay..."));
    VDC24_Setup;
    VDC24_Off;
    Serial.println(F("Done."));
    // setup stepper drive object
    Serial.print(F("Setting up Test Stepper drive object..."));
    TestStepperDriveObject.SetupDrive();
    Serial.println(F("Done."));
    // Timer settings printout
    Serial.println(F("Setup Timer Settings: "));
    Serial.print(F("OCR3A Value: "));Serial.println(OCR3A);
    Serial.print(F("TCNT3 Value: "));Serial.println(TCNT3);;
    Serial.print(F("TCCR3A Value: "));prntByteBIN(TCCR3A);
    Serial.print(F("TCCR3B Value: "));prntByteBIN(TCCR3B);
    Serial.print(F("TCCR3C Value: "));prntByteBIN(TCCR3C);
    Serial.print(F("TIMSK3 Value: "));prntByteBIN(TIMSK3);
    Serial.print(F("TIFR3 Value: "));prntByteBIN(TIFR3);
    // Step pin port values printout
    Serial.println(F("Setup DDRE and PORTE: "));
    Serial.print(F("DDRE = "));prntByteBIN(DDRE);
    Serial.print(F("PORTE = "));prntByteBIN(PINE);
    // indication for main loop start
    Serial.println(F("Now starting main loop..."));
    delay(1000);
}

void loop()
{
    if (oneShot)
    {
        // Timer settings printout
        Serial.println(F("First Loop Timer Settings:"));
        Serial.print(F("TCCR0A Value: "));prntByteBIN(TCCR0A);
        Serial.print(F("TCCR0B Value: "));prntByteBIN(TCCR0B);
        Serial.print(F("TCNT0 Value: "));Serial.println(TCNT0,HEX);
        Serial.print(F("OCR0A Value: "));Serial.println(OCR0A,HEX);
        Serial.print(F("TIMSK0 Value: "));prntByteBIN(TIMSK0);
        Serial.print(F("TIFR0 Value: "));prntByteBIN(TIFR0);
        Serial.print(F("TCCR1A Value: "));prntByteBIN(TCCR1A);
        Serial.print(F("TCCR1B Value: "));prntByteBIN(TCCR1B);
        Serial.print(F("TCCR1C Value: "));prntByteBIN(TCCR1C);
        Serial.print(F("TCNT1 Value: "));Serial.println(TCNT1,HEX);
        Serial.print(F("OCR1A Value: "));Serial.println(OCR1A,HEX);
        Serial.print(F("TIMSK1 Value: "));prntByteBIN(TIMSK1);
        Serial.print(F("TIFR1 Value: "));prntByteBIN(TIFR1);
        Serial.print(F("TCCR2A Value: "));prntByteBIN(TCCR2A);
        Serial.print(F("TCCR2B Value: "));prntByteBIN(TCCR2B);
        Serial.print(F("TCNT2 Value: "));Serial.println(TCNT2,HEX);
        Serial.print(F("OCR2A Value: "));Serial.println(OCR2A,HEX);
        Serial.print(F("TIMSK2 Value: "));prntByteBIN(TIMSK2);
        Serial.print(F("TIFR2 Value: "));prntByteBIN(TIFR2);
        Serial.print(F("TCCR3A Value: "));prntByteBIN(TCCR3A);
        Serial.print(F("TCCR3B Value: "));prntByteBIN(TCCR3B);
        Serial.print(F("TCCR3C Value: "));prntByteBIN(TCCR3C);
        Serial.print(F("TCNT3 Value: "));Serial.println(TCNT3,HEX);
        Serial.print(F("OCR3A Value: "));Serial.println(OCR3A,HEX);
        Serial.print(F("TIMSK3 Value: "));prntByteBIN(TIMSK3);
        Serial.print(F("TIFR3 Value: "));prntByteBIN(TIFR3);
        Serial.print(F("TCCR4A Value: "));prntByteBIN(TCCR4A);
        Serial.print(F("TCCR4B Value: "));prntByteBIN(TCCR4B);
        Serial.print(F("TCCR4C Value: "));prntByteBIN(TCCR4C);
        Serial.print(F("TCNT4 Value: "));Serial.println(TCNT4,HEX);
        Serial.print(F("OCR4A Value: "));Serial.println(OCR4A,HEX);
        Serial.print(F("TIMSK4 Value: "));prntByteBIN(TIMSK4);
        Serial.print(F("TIFR4 Value: "));prntByteBIN(TIFR4);
        Serial.print(F("TCCR5A Value: "));prntByteBIN(TCCR5A);
        Serial.print(F("TCCR5B Value: "));prntByteBIN(TCCR5B);
        Serial.print(F("TCCR5C Value: "));prntByteBIN(TCCR5C);
        Serial.print(F("TCNT5 Value: "));Serial.println(TCNT5,HEX);
        Serial.print(F("OCR5A Value: "));Serial.println(OCR5A,HEX);
        Serial.print(F("TIMSK5 Value: "));prntByteBIN(TIMSK5);
        Serial.print(F("TIFR5 Value: "));prntByteBIN(TIFR5);
        // Step pin port values printout
        Serial.println(F("First Loop DDRE and PORTE:"));
        Serial.print(F("DDRE = "));prntByteBIN(DDRE);
        Serial.print(F("PORTE = "));prntByteBIN(PINE);
        // CL42T class settings
        Serial.println(F("C42T Class Settings: "));
        Serial.print(F("directionPORT Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.directionPORT,HEX);
        Serial.print(F("directionDDR Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.directionDDR,HEX);
        Serial.print(F("directionPIN Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.directionPIN,HEX);
        Serial.print(F("enablePORT Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.enablePORT,HEX);
        Serial.print(F("enableDDR Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.enableDDR,HEX);
        Serial.print(F("enablePIN Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.enablePIN,HEX);
        Serial.print(F("alarmPORT Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.alarmPORT,HEX);
        Serial.print(F("alarmDDR Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.alarmDDR,HEX);
        Serial.print(F("alarmPIN Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.alarmPIN,HEX);
        Serial.print(F("stepPORT Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepPORT,HEX);
        Serial.print(F("stepDDR Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepDDR,HEX);
        Serial.print(F("stepPIN Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepPIN,HEX);
        Serial.print(F("stepTCCRA Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTCCRA,HEX);
        Serial.print(F("stepTCCRA Value: "));prntByteBIN(*TestStepperDriveObject.stepTCCRA);
        Serial.print(F("stepTCCRAmask: "));prntByteBIN(TestStepperDriveObject.stepTCCRAmask);
        Serial.print(F("stepTCCRAOutput1Mask: "));prntByteBIN(TestStepperDriveObject.stepTCCRAOutput1Mask);
        Serial.print(F("stepTCCRB Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTCCRB,HEX);
        Serial.print(F("stepTCCRB Value: "));prntByteBIN(*TestStepperDriveObject.stepTCCRB);
        Serial.print(F("stepTCCRBmask: "));prntByteBIN(TestStepperDriveObject.stepTCCRBmask);
        Serial.print(F("stepTCCRBstartMask: "));prntByteBIN(TestStepperDriveObject.stepTCCRBstartMask);
        Serial.print(F("stepTCCRC Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTCCRC,HEX);
        Serial.print(F("stepTCCRC Value: "));prntByteBIN(*TestStepperDriveObject.stepTCCRC);
        Serial.print(F("stepTIMSK Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTIMSK,HEX);
        Serial.print(F("stepTIMSK Value: "));prntByteBIN(*TestStepperDriveObject.stepTIMSK);
        Serial.print(F("stepTimerOutputInterrupt1Mask: "));prntByteBIN(TestStepperDriveObject.stepTimerOutputInterrupt1Mask);
        Serial.print(F("stepTIFR Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTIFR,HEX);
        Serial.print(F("stepTIFR Value: "));prntByteBIN(*TestStepperDriveObject.stepTIFR);
        Serial.print(F("stepTCNTL Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTCNTL,HEX);
        Serial.print(F("stepTCNTL Value: 0x"));Serial.println(*TestStepperDriveObject.stepTCNTL,HEX);
        Serial.print(F("stepTCNTH Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepTCNTH,HEX);
        Serial.print(F("stepTCNTH Value: 0x"));Serial.println(*TestStepperDriveObject.stepTCNTH,HEX);
        Serial.print(F("stepOCRL Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepOCRL,HEX);
        Serial.print(F("stepOCRL Value: 0x"));Serial.println(*TestStepperDriveObject.stepOCRL,HEX);
        Serial.print(F("stepOCRH Address: 0x"));Serial.println((uint16_t)TestStepperDriveObject.stepOCRH,HEX);
        Serial.print(F("stepOCRH Value: 0x"));Serial.println(*TestStepperDriveObject.stepOCRH,HEX);
        Serial.print(F("OCRo: "));Serial.println(TestStepperDriveObject.OCRo);
        // StepperSpeedRamp class settings
        Serial.println(F("StepperSpeedRamp Class Settings:"));
        Serial.print(F("maxSpeed: "));Serial.println(TestStepperDriveObject.maxSpeed);
        Serial.print(F("Accel: "));Serial.println(TestStepperDriveObject.Accel);
        Serial.print(F("Decel: "));Serial.println(TestStepperDriveObject.Decel);
        Serial.print(F("mmPerStep: "));Serial.println(TestStepperDriveObject.mmPerStep);
        Serial.print(F("preScale: "));Serial.println(TestStepperDriveObject.preScale);
        Serial.print(F("CntFreq: "));Serial.println(TestStepperDriveObject.CntFreq);
        Serial.print(F("speedUpdateSteps: "));Serial.println(TestStepperDriveObject.speedUpdateSteps);
        Serial.print(F("maxSpeedSteps: "));Serial.println(TestStepperDriveObject.maxSpeedSteps);
        Serial.print(F("maxAccelSteps: "));Serial.println(TestStepperDriveObject.maxAccelSteps);
        Serial.print(F("accelSteps: "));Serial.println(TestStepperDriveObject.accelSteps);
        Serial.print(F("decelSteps: "));Serial.println(TestStepperDriveObject.decelSteps);
        Serial.print(F("numStepsTemp: "));Serial.println(TestStepperDriveObject.numStepsTemp);
        Serial.print(F("OCRmin: "));Serial.println(TestStepperDriveObject.OCRmin);
        Serial.print(F("OCRo: "));Serial.println(TestStepperDriveObject.OCRo);
        Serial.print(F("accelDecelIndex: "));Serial.println(TestStepperDriveObject.accelDecelIndex);
        Serial.println(F("OCR Array:"));
        for (int i = 0; i < (sizeof(TestStepperDriveObject.OCRarray)/2); i++)
        {
            Serial.print(F("OCR "));Serial.print(i);Serial.print(F(" = "));Serial.println(TestStepperDriveObject.OCRarray[i]);
        }
        oneShot = false;
    }
    // set maxSpeed of drive & calculate OCR array, then apply
    TestStepperDriveObject.SetMaxSpeed(MAXspeed);
    TestStepperDriveObject.ApplyNewSpeed();
    // turn on 24 VDC
    Serial.print(F("Turning on 24VDC Control Relay..."));
    VDC24_On;
    Serial.println(F("Done."));
    delay(1000);
    // enable drive
    Serial.print(F("Enabling Test Stepper Drive..."));
    TestStepperDriveObject.EnableDrive();
    Serial.println(F("Done."));
    delayMicroseconds(500);
    // set drive direction
    Serial.print(F("Setting Test Stepper Direction to FWD..."));
    TestStepperDriveObject.SetDirection('F');
    Serial.println(F("Done."));
    delayMicroseconds(500);
    // move stepper drive one revolution
    Serial.print(F("Starting Test Stepper at "));Serial.print(MAXspeed);Serial.println(F(" mm/s for one revolution..."));
    TestStepperDriveObject.Increment(TestStepperOneRevSteps);
    // monitor for end of movement
    while (TestStepperSteps < TestStepperDriveObject.incrementSteps)
    {
        Serial.print(F("Steps taken: "));Serial.println(TestStepperSteps);
        delay(250);
    }
    Serial.println(F("Done."));
    // clear step tracker
    TestStepperSteps = 0;
    delayMicroseconds(500);
    // disable stepper drive
    Serial.print(F("Disabling Test Stepper Drive..."));
    TestStepperDriveObject.DisableDrive();
    Serial.println(F("Done."));
    delayMicroseconds(500);
    // turn off 24VDC
    Serial.print(F("Turning off 24VDC Control Relay..."));
    VDC24_Off;
    Serial.println(F("Done."));
    // adjust max speed up 20 mm/s
    MAXspeed += 20;
    // delay to slow test progression
    delay(3000);
}

void prntByteBIN(byte b)
{
    for(int i = 7; i >= 0; i--)
    {
        Serial.print(bitRead(b,i));
    }
    Serial.println();
}

void clearTimersOneToFive()
{
    noInterrupts();
    TCCR1A = 0;
    TCCR1B = 0;
    TCCR1C = 0;
    TIMSK1 = 0;
    TCNT1H = 0;
    TCNT1L = 0;
    TIFR1 |= B00001111;
    TCCR2A = 0;
    TCCR2B = 0;
    TIMSK2 = 0;
    TCNT2 = 0;
    TIFR2 |= B00001111;
    TCCR3A = 0;
    TCCR3B = 0;
    TCCR3C = 0;
    TIMSK3 = 0;
    TCNT3H = 0;
    TCNT3L = 0;
    TIFR3 |= B00001111;
    TCCR4A = 0;
    TCCR4B = 0;
    TCCR4C = 0;
    TIMSK4 = 0;
    TCNT4H = 0;
    TCNT4L = 0;
    TIFR4 |= B00001111;
    TCCR5A = 0;
    TCCR5B = 0;
    TCCR5C = 0;
    TIMSK5 = 0;
    TCNT5H = 0;
    TCNT5L = 0;
    TIFR5 |= B00001111;
    interrupts();
}

// Test Stepper Step Interrupt
ISR(TIMER3_COMPA_vect) {
    if (((*TestStepperDriveObject.stepPIN) & (1<<TestStepperDriveObject.stepBIT))) {
        TestStepperSteps++;        
        if ((TestStepperDriveObject.incrementSteps > 0) && (TestStepperSteps >= TestStepperDriveObject.incrementSteps))
        {
            TestStepperDriveObject.StopDrive(); // position reached, stop drive
            // TestStepperSteps = 0;
        }
        if (TestStepperSteps <= TestStepperDriveObject.accelSteps)
        { // acceleration phase
            if (!(TestStepperSteps % TestStepperDriveObject.speedUpdateSteps))
            {
                TestStepperDriveObject.accelDecelIndex++;
                *TestStepperDriveObject.stepOCRH = highByte(TestStepperDriveObject.OCRarray[TestStepperDriveObject.accelDecelIndex]);
                *TestStepperDriveObject.stepOCRL = lowByte(TestStepperDriveObject.OCRarray[TestStepperDriveObject.accelDecelIndex]);
            }
        }
        if ((TestStepperDriveObject.incrementSteps > 0) && (TestStepperSteps >= (TestStepperDriveObject.incrementSteps - TestStepperDriveObject.decelSteps)))
        { // deceleration phase - position tracking only
            if (!(TestStepperSteps % TestStepperDriveObject.speedUpdateSteps))
            {
                *TestStepperDriveObject.stepOCRH = highByte(TestStepperDriveObject.OCRarray[TestStepperDriveObject.accelDecelIndex]);
                *TestStepperDriveObject.stepOCRL = lowByte(TestStepperDriveObject.OCRarray[TestStepperDriveObject.accelDecelIndex]);
                TestStepperDriveObject.accelDecelIndex--;
            }
        }
    }
}

StepperSpeedRamp.h:

#ifndef StepperSpeedRamp_h
#define StepperSpeedRamp_h

#include "Arduino.h"

// Stepper Speed Ramp Class for CL42T drives
class StepperSpeedRamp
{
public:
    // drive settings
    double maxSpeed; // mm/s
    // variables for speed ramp calculations
    double Accel; // mm/s/s
    double Decel; // mm/s/s
    double mmPerStep; // mm/step
    int preScale; // prescale selection
    long CntFreq; // counter frequency
    int speedUpdateSteps; // # steps
    long maxSpeedSteps; // # steps
    long maxAccelSteps; // # steps
    long accelSteps; // # steps
    long decelSteps; // # steps
    long numStepsTemp; // # steps
    long OCRmin;
    long OCRo;
    // speed update index (OCRarray[])
    volatile int accelDecelIndex;
    // speed ramp OCR values
    volatile uint16_t OCRarray[21];
    // member functions
    StepperSpeedRamp(int PreScale);
    void SetMaxSpeed(int MmPerSec);
    void CalculateSpeedRamp();
};
#endif

StepperSpeedRamp.cpp:

#include "Arduino.h"
#include "math.h"
#include "StepperSpeedRamp.h"

/*
* Stepper Speed Ramp Class
*/
StepperSpeedRamp::StepperSpeedRamp(int PreScale)
{
    // drive settings
    maxSpeed = 60; // mm/s - default @ 60
    // variables for speed ramp calculations
    Accel = maxSpeed*10; // mm/s/s - accelerate in 100 ms
    Decel = maxSpeed*10; // mm/s/s - decelerate in 100 ms
    mmPerStep = 0.04; // mm/step
    preScale = PreScale; // copy prescale value into object
    CntFreq = 16000000/preScale; // calculate counter frequency at PreScale
    speedUpdateSteps = 0; // # steps
    maxSpeedSteps = 0; // # steps
    maxAccelSteps = 0; // # steps
    accelSteps = 0; // # steps
    decelSteps = 0; // # steps
    numStepsTemp = 10000; // use 10,000 steps as the calculation for speed ramp
    OCRmin = 0; // minimum delay --> max speed
    OCRo = 0; // first delay --> start speed (based on acceleration)
    accelDecelIndex = 0; // speed update index (OCRarray[])
    CalculateSpeedRamp(); // calculate OCRarray
}

void StepperSpeedRamp::SetMaxSpeed(int MmPerSec)
{
    maxSpeed = MmPerSec; // set new maxSpeed value
    Accel = maxSpeed*10; // accelerate in 100 ms
    Decel = maxSpeed*10; // decelerate in 100 ms
    CalculateSpeedRamp(); // calculate new OCRarray
}

void StepperSpeedRamp::CalculateSpeedRamp()
{
    OCRmin = ((mmPerStep*(CntFreq/2))/(maxSpeed)); // calculate minimum OCR - based on max speed
    OCRo = ((CntFreq/2)*sqrt((2*mmPerStep)/Accel)); // calculate first OCR value - based on accel
    OCRarray[0] = OCRo; // load first OCR value into OCRarray
    maxSpeedSteps = ((maxSpeed*maxSpeed)/(2*mmPerStep*Accel)); // calculate steps required to reach max speed
    maxAccelSteps = ((numStepsTemp*Decel)/(Accel+Decel)); // calculate steps required to reach max deceleration phase
    if (maxSpeedSteps < maxAccelSteps)
    { // trapezoidal speed profile
        accelSteps = maxSpeedSteps; 
        decelSteps = (accelSteps*(Accel/Decel));
    }
    else if (maxSpeedSteps >= maxAccelSteps)
    { // triangular speed profile
        accelSteps = maxAccelSteps;
        decelSteps = (accelSteps*(Accel/Decel));
    }
    speedUpdateSteps = accelSteps / ((sizeof(OCRarray)/2)-2); // 0 indexed AND OCR[0] is already filled.
    for (int i = 1;i < (sizeof(OCRarray)/2);i++)
    { // populate OCR array
        OCRarray[i] = (OCRo*(sqrt((i*speedUpdateSteps)+1)-sqrt((i*speedUpdateSteps))));
    }
}

CL42T.h:

#ifndef CL42T_h
#define CL42T_h

#include "Arduino.h"
#include "StepperSpeedRamp.h"

class CL42T: public StepperSpeedRamp
{
public:
    // register pointers
    volatile uint8_t *directionPORT;
    volatile uint8_t *directionDDR;
    volatile uint8_t *directionPIN;
    volatile uint8_t *enablePORT;
    volatile uint8_t *enableDDR;
    volatile uint8_t *enablePIN;
    volatile uint8_t *alarmPORT;
    volatile uint8_t *alarmDDR;
    volatile uint8_t *alarmPIN;
    volatile uint8_t *stepPORT;
    volatile uint8_t *stepDDR;
    volatile uint8_t *stepPIN;
    volatile uint8_t *stepTCCRA;
    volatile uint8_t *stepTCCRB;
    volatile uint8_t *stepTCCRC;
    volatile uint8_t *stepTIMSK;
    volatile uint8_t *stepTIFR;
    volatile uint8_t *stepTCNTH;
    volatile uint8_t *stepTCNTL;
    volatile uint8_t *stepOCRH;
    volatile uint8_t *stepOCRL;
    volatile uint8_t *stepOCRH2;
    volatile uint8_t *stepOCRL2;
    // timer register masks
    uint8_t stepTCCRAmask;
    uint8_t stepTCCRAOutput1Mask;
    uint8_t stepTCCRAOutput2Mask;
    uint8_t stepTCCRBmask;
    uint8_t stepTCCRBstartMask;
    uint8_t stepTimerOutputInterrupt1Mask;
    // dual or single motor toggle variable
    volatile int numMOTORS;
    // step and control bits
    volatile int stepBIT;
    volatile int stepBIT2;
    volatile int controlBIT;
    volatile int controlBIT2;
    // step target value
    volatile int incrementSteps;
    // single motor constructor:
    CL42T(char StepPort, int StepBit, int ControlBit, int TimerNum, char TimerOutput, int PreScale);
    // dual motor constructor:
    CL42T(char StepPort1, int StepBit1, int ControlBit1, int TimerNum1, char TimerOutput1, int StepBit2, int ControlBit2, char TimerOutput2, int PreScale);
    void SetupDrive(); // setup pointers, port directions, etc.
    void ApplyNewSpeed(); // set OCRL/OCRH to OCRarray[0]
    void SetDirection(char DriveDir); // set direction of CL42T stepper driver
    void EnableDrive(); // enable CL42T stepper driver
    void DisableDrive(); // disable CL42T stepper driver
    void RunDrive(); // run single mmotor or both motors
    void StopDrive(); // stop single motor or both motors
    void Increment(int IncrementSteps); // run at maxSpeed for IncrementSteps
};
#endif

CL42T.cpp:

#include "Arduino.h"
#include "CL42T.h"

/*
* Single Motor Constructor:
* - set numMOTORS to 1
* - copy relevant parameters
* - set step pointers
* - set timer pointers and timer masks
*/
CL42T::CL42T(char StepPort, int StepBit, int ControlBit, int TimerNum, char TimerOutput, int PreScale)
        :StepperSpeedRamp(PreScale)
{
    // this is the single motor constructor
    numMOTORS = 1;
    // register pointers
    directionPORT = &PORTC;
    directionDDR = &DDRC;
    directionPIN = &PINC;
    enablePORT = &PORTA;
    enableDDR = &DDRA;
    enablePIN = &PINA;
    alarmPORT = &PORTK;
    alarmDDR = &DDRK;
    alarmPIN = &PINK;
    stepPORT = NULL;
    stepDDR = NULL;
    stepTCCRA = NULL;
    stepTCCRB = NULL;
    stepTCCRC = NULL;
    stepTIMSK = NULL;
    stepTIFR = NULL;
    stepOCRH = NULL;
    stepOCRL = NULL;
    stepOCRH2 = NULL;
    stepOCRL2 = NULL;
    stepTCNTH = NULL;
    stepTCNTL = NULL;
    stepPIN = NULL;
    // copy bit parameters
    stepBIT = StepBit;
    controlBIT = ControlBit;
    // determine step pointers
    switch (StepPort)
    {
    case 'B':
        stepPORT = &PORTB;
        stepDDR = &DDRB;
        stepPIN = &PINB;
        break;
    case 'E':
        stepPORT = &PORTE;
        stepDDR = &DDRE;
        stepPIN = &PINE;
        break;
    case 'H':
        stepPORT = &PORTH;
        stepDDR = &DDRH;
        stepPIN = &PINH;
        break;
    case 'L':
        stepPORT = &PORTL;
        stepDDR = &DDRL;
        stepPIN = &PINL;
        break;
    default:
        break;
    }
    // determine timer pointers and set timer masks
    switch (TimerNum)
    {
    case 1:
        stepTCCRA = &TCCR1A;
        stepTCCRB = &TCCR1B;
        stepTCCRC = &TCCR1C;
        stepTIMSK = &TIMSK1;
        stepTIFR = &TIFR1;
        stepTCNTL = &TCNT1L;
        stepTCNTH = &TCNT1H;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM12); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput)
        {
        case 'A':
            stepOCRL = &OCR1AL;
            stepOCRH = &OCR1AH;
            stepTCCRAOutput1Mask = (1<<COM1A0); // COMA - Toggle on compare match
            stepTimerOutputInterrupt1Mask = (1<<OCIE1A); // COMA interrupt enable
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS11); // prescale 8
            break;
        default:
            break;
        }
        break;
    case 2:
        stepTCCRA = &TCCR2A;
        stepTCCRB = &TCCR2B;
        stepTIMSK = &TIMSK2;
        stepTIFR = &TIFR2;
        stepTCNTL = &TCNT2;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM22); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput)
        {
        case 'A':
            stepOCRL = &OCR2A;
            stepTCCRAOutput1Mask = (1<<COM2A0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE2A);
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS21); // prescale 8
            break;
        default:
            break;
        }
        break;
    case 3:
        stepTCCRA = &TCCR3A;
        stepTCCRB = &TCCR3B;
        stepTCCRC = &TCCR3C;
        stepTIMSK = &TIMSK3;
        stepTIFR = &TIFR3;
        stepTCNTL = &TCNT3L;
        stepTCNTH = &TCNT3H;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM32); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput)
        {
        case 'A':
            stepOCRL = &OCR3AL;
            stepOCRH = &OCR3AH;
            stepTCCRAOutput1Mask = (1<<COM3A0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE3A);
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS31); // prescale 8
            break;
        default:
            break;
        }
        break;
    case 4:
        stepTCCRA = &TCCR4A;
        stepTCCRB = &TCCR4B;
        stepTCCRC = &TCCR4C;
        stepTIMSK = &TIMSK4;
        stepTIFR = &TIFR4;
        stepTCNTL = &TCNT4L;
        stepTCNTH = &TCNT4H;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM42); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput)
        {
        case 'A':
            stepOCRL = &OCR4AL;
            stepOCRH = &OCR4AH;
            stepTCCRAOutput1Mask = (1<<COM4A0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE4A);
            break;
        case 'B':
            stepOCRL = &OCR4BL;
            stepOCRH = &OCR4BH;
            stepTCCRAOutput1Mask = (1<<COM4B0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE4B);
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS41); // prescale 8
            break;
        default:
            break;
        }
        break;
    case 5:
        stepTCCRA = &TCCR5A;
        stepTCCRB = &TCCR5B;
        stepTCCRC = &TCCR5C;
        stepTIMSK = &TIMSK5;
        stepTIFR = &TIFR5;
        stepTCNTL = &TCNT5L;
        stepTCNTH = &TCNT5H;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM52); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput)
        {
        case 'A':
            stepOCRL = &OCR5AL;
            stepOCRH = &OCR5AH;
            stepTCCRAOutput1Mask = (1<<COM5A0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE5A);
            break;
        case 'B':
            stepOCRL = &OCR5BL;
            stepOCRH = &OCR5BH;
            stepTCCRAOutput1Mask = (1<<COM5B0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE5B);
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS51); // prescale 8
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}

/*
* Dual Motor Constructor:
* - set numMOTORS to 2
* - copy relevant parameters
* - set step pointers
* - set timer pointers and timer masks
*/
CL42T::CL42T(char StepPort, int StepBit1, int ControlBit1, int TimerNum, char TimerOutput1, int StepBit2, int ControlBit2, char TimerOutput2, int PreScale)
        :StepperSpeedRamp(PreScale)
{
    // this is the dual motor constructor
    numMOTORS = 2;
    // register pointers
    directionPORT = &PORTC;
    directionDDR = &DDRC;
    directionPIN = &PINC;
    enablePORT = &PORTA;
    enableDDR = &DDRA;
    enablePIN = &PINA;
    alarmPORT = &PORTK;
    alarmDDR = &DDRK;
    alarmPIN = &PINK;
    stepPORT = NULL;
    stepDDR = NULL;
    stepTCCRA = NULL;
    stepTCCRB = NULL;
    stepTCCRC = NULL;
    stepTIMSK = NULL;
    stepTIFR = NULL;
    stepOCRH = NULL;
    stepOCRL = NULL;
    stepOCRH2 = NULL;
    stepOCRL2 = NULL;
    stepTCNTH = NULL;
    stepTCNTL = NULL;
    // public pointer attributes
    stepPIN = NULL;
    // copy relevant parameters
    stepBIT = StepBit1;
    stepBIT2 = StepBit2;
    controlBIT = ControlBit1;
    controlBIT2 = ControlBit2;
    // determine step pointers
    switch (StepPort)
    {
    case 'H':
        stepPORT = &PORTH;
        stepDDR = &DDRH;
        stepPIN = &PINH;
        break;
    case 'L':
        stepPORT = &PORTL;
        stepDDR = &DDRL;
        stepPIN = &PINL;
        break;
    default:
        break;
    }
    // determine timer pointers and set timer masks
    switch (TimerNum)
    {
    case 4:
        stepTCCRA = &TCCR4A;
        stepTCCRB = &TCCR4B;
        stepTCCRC = &TCCR4C;
        stepTIMSK = &TIMSK4;
        stepTIFR = &TIFR4;
        stepTCNTL = &TCNT4L;
        stepTCNTH = &TCNT4H;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM42); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput1)
        {
        case 'A':
            stepOCRL = &OCR4AL;
            stepOCRH = &OCR4AH;
            stepTCCRAOutput1Mask = (1<<COM4A0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE4A); // COMA interrupt enable
            break;
        case 'B':
            stepOCRL = &OCR4BL;
            stepOCRH = &OCR4BH;
            stepTCCRAOutput1Mask = (1<<COM4B0); // COMB - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE4B); // COMB interrupt enable
            break;
        default:
            break;
        }
        switch (TimerOutput2)
        {
        case 'A':
            stepOCRL2 = &OCR4AL;
            stepOCRH2 = &OCR4AH;
            stepTCCRAOutput2Mask = (1<<COM4A0); // COMA - Toggle
            break;
        case 'B':
            stepOCRL2 = &OCR4BL;
            stepOCRH2 = &OCR4BH;
            stepTCCRAOutput2Mask = (1<<COM4B0); // COMB - Toggle
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS41); // prescale 8
            break;
        default:
            break;
        }
        break;
    case 5:
        stepTCCRA = &TCCR5A;
        stepTCCRB = &TCCR5B;
        stepTCCRC = &TCCR5C;
        stepTIMSK = &TIMSK5;
        stepTIFR = &TIFR5;
        stepTCNTL = &TCNT5L;
        stepTCNTH = &TCNT5H;
        stepTCCRAmask = 0; // COMA / COMB off, WGMn1:0 = 0 (CTC mode)
        stepTCCRBmask = (1<<WGM52); // WGMn2 = 1 (CTC mode), decide prescale later
        // set OCR pointers and timer output masks
        switch (TimerOutput1)
        {
        case 'A':
            stepOCRL = &OCR5AL;
            stepOCRH = &OCR5AH;
            stepTCCRAOutput1Mask = (1<<COM5A0); // COMA - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE5A); // COMA output interrupt enable
            break;
        case 'B':
            stepOCRL = &OCR5BL;
            stepOCRH = &OCR5BH;
            stepTCCRAOutput1Mask = (1<<COM5B0); // COMB - Toggle
            stepTimerOutputInterrupt1Mask = (1<<OCIE5B); // COMB output interrupt enable
            break;
        default:
            break;
        }
        switch (TimerOutput2)
        {
        case 'A':
            stepOCRL2 = &OCR5AL;
            stepOCRH2 = &OCR5AH;
            stepTCCRAOutput2Mask = (1<<COM5A0); // COMA - Toggle
            break;
        case 'B':
            stepOCRL2 = &OCR5BL;
            stepOCRH2 = &OCR5BH;
            stepTCCRAOutput2Mask = (1<<COM5B0); // COMB - Toggle
            break;
        default:
            break;
        }
        // adjust timer mask for prescale
        switch (PreScale)
        {
        case 8:
            stepTCCRBstartMask = (1<<CS51); // prescale 8
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}

/*
* Setup:
* - Set control I/O registers
* - Clear timer registers
* - Setup timer, but don't start it
*/ 
void CL42T::SetupDrive()
{
    // clear timer registers and any pending interrupt flags
    *stepTCCRB = 0;
    *stepTCCRA = 0;
    *stepTCCRC = 0;
    *stepTIMSK = 0;
    *stepTIFR |= B00001111;
    if (numMOTORS == 1)
    { // single motor drive
        // set timer registers
        *stepTCCRA = (stepTCCRAOutput1Mask);
        *stepTCCRB = (stepTCCRBmask | stepTCCRBstartMask);
        *stepTCNTH = 0;
        *stepTCNTL = 0;
        noInterrupts();
        *stepOCRH = highByte(OCRarray[0]);
        *stepOCRL = lowByte(OCRarray[0]);
        interrupts();
        // setup control I/O registers
        *stepDDR |= (1<<stepBIT); // set step pin as OUTPUT
        *stepPORT &= ~(1<<stepBIT); // pull step pin LOW
        *directionDDR |= (1<<controlBIT); // set direction pin as OUTPUT
        *directionPORT &= ~(1<<controlBIT); // set direction pin LOW
        *enableDDR |= (1<<controlBIT); // set enable pin as OUTPUT
        *enablePORT &= ~(1<<controlBIT); // set enable pin LOW
        *alarmDDR &= ~(1<<controlBIT); // set alarm pin as INPUT
        *alarmPORT |= (1<<controlBIT); // set alarm pin INPUT_PULLUP
    }
    else if (numMOTORS == 2)
    { // dual motor drive
        // set timer registers
        *stepTCCRA = (stepTCCRAOutput1Mask | stepTCCRAOutput2Mask);
        *stepTCCRB = (stepTCCRBmask | stepTCCRBstartMask);
        *stepTCNTH = 0;
        *stepTCNTL = 0;
        noInterrupts();
        *stepOCRH = highByte(OCRarray[0]);
        *stepOCRL = lowByte(OCRarray[0]);
        *stepOCRH2 = highByte(OCRarray[0]);
        *stepOCRL2 = lowByte(OCRarray[0]);
        interrupts();
        // setup control I/O registers
        *stepDDR |= ((1<<stepBIT) | (1<<stepBIT2)); // set step pins as OUTPUT
        *stepPORT &= ~((1<<stepBIT) | (1<<stepBIT2)); // pull step pin LOW
        *directionDDR |= ((1<<controlBIT) | (1<<controlBIT2)); // set direction pins as OUTPUT
        *directionPORT &= ~((1<<controlBIT) | (1<<controlBIT2)); // set direction pins LOW
        *enableDDR |= ((1<<controlBIT) | (1<<controlBIT2)); // set enable pins as OUTPUT
        *enablePORT &= ~((1<<controlBIT) | (1<<controlBIT2)); // set enable pins LOW
        *alarmDDR &= ~((1<<controlBIT) | (1<<controlBIT2)); // set alarm pins as INPUT
        *alarmPORT |= ((1<<controlBIT) | (1<<controlBIT2)); // set alarm pins INPUT_PULLUP
    }
    *stepTCCRB &= ~(stepTCCRBstartMask); // turn off timer
    *stepTCNTH = 0; // set timer value to zero
    *stepTCNTL = 0;
    *stepTIFR |= B00001111; // clear any interrupt flags generated
}

void CL42T::ApplyNewSpeed()
{ // set OCR value from OCRarray[0]
    if (numMOTORS == 1)
    {
        // set OCR
        noInterrupts();
        *stepOCRH = (highByte(OCRarray[0]));
        *stepOCRL = (lowByte(OCRarray[0]));
        interrupts();
    }
    else if (numMOTORS == 2)
    {
        // set OCR
        noInterrupts();
        *stepOCRH = (highByte(OCRarray[0]));
        *stepOCRL = (lowByte(OCRarray[0]));
        *stepOCRH2 = (highByte(OCRarray[0]));
        *stepOCRL2 = (lowByte(OCRarray[0]));
        interrupts();
    }
}

void CL42T::SetDirection(char DriveDir)
{
    if (numMOTORS == 1)
    {
        switch (DriveDir)
        {
        case 'F':
            *directionPORT |= (1<<controlBIT); // direction pin HIGH
            break;
        case 'R':
            *directionPORT &= ~(1<<controlBIT); // directon pin LOW
            break;
        default:
            break;
        }
    }
    else if (numMOTORS == 2)
    {
        switch (DriveDir)
        {
        case 'F':
            *directionPORT |= ((1<<controlBIT) | (1<<controlBIT2)); // direction pins HIGH
            break;
        case 'R':
            *directionPORT &= ~((1<<controlBIT) | (1<<controlBIT2)); // direction pins LOW
            break;
        default:
            break;
        }
    }
}

void CL42T::EnableDrive()
{
    if (numMOTORS == 1)
    {
        *enablePORT |= (1<<controlBIT); // enable pin HIGH
    }
    else if (numMOTORS == 2)
    {
        *enablePORT |= ((1<<controlBIT) | (1<<controlBIT2)); // enable pins HIGH
    }
}

void CL42T::DisableDrive()
{
    if (numMOTORS == 1)
    {
        *enablePORT &= ~(1<<controlBIT); // enable pin LOW
    }
    else if (numMOTORS == 2)
    {
        *enablePORT &= ~((1<<controlBIT) | (1<<controlBIT2)); // enable pins LOW
    }
}

void CL42T::RunDrive()
{   
    if (incrementSteps == 0)
    {
        incrementSteps = -1; // if no distance given, indicate no motion tracking to ISR
    }
    accelDecelIndex = 0;
    *stepTCNTH = 0; // clear counter value
    *stepTCNTL = 0;
    *stepTIFR |= B00001111; // clear any pending interrupt flags
    *stepTCCRB |= stepTCCRBstartMask; // turn on timer
}

void CL42T::StopDrive()
{
    *stepTCCRB &= ~(stepTCCRBstartMask); // turn off timer
    *stepTIMSK &= ~(stepTimerOutputInterrupt1Mask); // disable output compare interrupt
    incrementSteps = 0; // set incrementSteps back to zero
}

void CL42T::Increment(int IncrementSteps)
{
    incrementSteps = IncrementSteps; // copy new step target
    *stepTIMSK |= stepTimerOutputInterrupt1Mask; // enable timer output compare interrupt
    RunDrive(); // start drive
}

CL42T_ClassTest_OutputLog_20211108.zip (2.0 KB)

A timer can only produce pulses or interrupts of varying width but not of varying frequency as required by the stepper motors. I.e. one timer is sufficient to control multiple stepper motors, all derived pulses can occur only at a multiple of the timer interrupt rate.

Printing too much information about other timers etc. will not help, they only slow down the controller. Other parts of a program will use timers for other purposes, so dealing with only T3 will be sufficient.

In your case I suggest that you use a simulator or other means to test your program logic outside an Arduino. Then you have no real time restrictions, no interrupt priority problems, no processor overloading and whatever else will make testing of the program and class logic needlessly complicated.

Thanks for the response, @DrDiettrich .I disagree with this part of your response above. Do you have any specific reasons why you believe this is the case?

Per the datasheet (section 17.9), in CTC mode (mode 4 or mode 12), the timer counts until TCNT matches OCR or ICR (depending on if mode 4 or mode 12), then the timer clears and counts from zero again. In CTC mode, the OC3A/OC3B/OC3C can be toggled at compare match (COMA1 = 0, COMA0 = 1). By changing the OCR or ICR register, the duration of both the high pulse and low pulse can be affected (i.e time between last toggle and next toggle), thus affecting the frequency at which the pin is toggled.

Also, as I stated in my original post, this approach works flawlessly in a simple sketch without classes. With a simple sketch, I get only expected speeds with the motor. It also shows the exact changing frequency squarewave that I'm expecting on OC3A, confirmed with an oscilloscope.

I agree with you on the excessive serial outputs, which is why I tested it without the serial lines, but it still has the same problem :frowning:

I do have a simulator that I run, but I'm not sure of it's relative performance to other testing platforms. I have been using PICSimLab to simulate pieces of my code snippets before integrating said code into larger packages. I know that the 2560 module of PICSimLab is only released "experimentally", so I occasionally apply my test code snippet to an UNO to test the theory, then copy that format/structure to the appropriate registers in the MEGA. Thus far, PICSimLab has been very accurate on the MEGA simulations I've conducted, but I digress.

I'm unsure of how I could better see what is happening with this particular simulator that I use. I'll admit, I don't intimately know all the features of PICSimLab, so it could be that there is a function I'm not aware of to get better vision on exactly what's happening at the start of my code.

Do you have any recommendations for a simulator to use for your advised purpose?

My reasoning for printing the other timer settings was to investigate other potential interrupts which might be causing issues during startup. Also, I don't know all of the details, but I do know there are ways to make timers interact in a modulating fashion, so I turned the other timers off out of an abundance of caution, and to rule out other timer interference as a root cause. This was confirmed in testing, so I agree that I should be able to deal with timer 3 registers directly (or whatever timer is used for a particular object of my CL42T class).

It's explained in the data sheet.

How do you intend to let multiple steppers run at different step rates?

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:

  1. Step target for the object exists and has been reached (handled by ISR).
  2. 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
}

That frequency is fixed by the timer overflow frequency. There is something that you seem not to understand yet.

You can control only one stepper per timer with this method. Thats a bit ineffective. You can control several steppers with one timer if you create the pulses in the ISR.
My MobaTools library does so and can control at least 6 steppers with one timer ( timer 3 on a Mega ). And you can change any parameter ( speed, target, acceleration ) at any time from within the sketch. And you are free in defining the stepper pins.
Maybe there are some restrictions that don't allow this lib in your sketch. But you should consider creating the pulses in the ISR and control several steppers with one timer. That will be more flexible.

1 Like

I think you may be missing the detail that I'm placing the timer in CTC mode (specifically, mode 4). Here's a nice quote from page 142 of the datasheet (in the Output Compare Units section):

If I can define the period, I can define the frequency... Think of it this way: I'm counting to some value, "OCR", then the timer value resets to zero and continues counting. Each time the counter matches "OCR", the pin is toggled:

(assume step pin starts LOW, timer count is initially set to zero, and OCR set to some value greater than zero)
2) timer count reaches OCR:

  • step pin set HIGH (toggled from LOW)
  • timer count reset to zero
  1. timer count reaches OCR:
  • step pin set LOW (toggled from HIGH)
  • timer count reset to zero
  1. timer count reaches OCR:
  • step pin set HIGH (toggled from LOW)
  • timer count reset to zero

As you can see, the OCR value represents the "half period" of the period between step pulses from the output compare unit. If I vary this OCR value at some points throughout the timer operation (using the ISR on the output compare match in my CL42T test code), then I vary the frequency of the output on the OCnx pin... Again, please see my "simple example" above which demonstrates this ability.

Right, but the counter continues counting until it reaches its TOP.

Thanks for the thoughts! In my original planning for this application, I had feared that by directly toggling the pins from within the ISR, I would be making the ISRs too long and possibly too frequent. Specifically, using a timer frees the arduino from having to toggle pins during every single half step. With my OCR array approach, I update the speed every "speedUpdateSteps" instead of every half step, and I get a lot of time in between the acceleration phase and deceleration phase of the movements, which I can use for any number of the other modules used in the application (I2C LCD, I2C RTC, SPI SD module, some buttons, power monitoring, etc.).

I am getting a little hung up on one thing you stated, but I think we are simply mismatched on our choice of words:

I believe you mean I can control only one stepper per timer independently with this method - is that correct? When I set the timer to CTC mode, and set TCCRA register to enable "toggle" on both OCnA and OCnB, I can get an identical squarewave signal on both the OCnA and OCnB pins. This indicates to me that I can get up to 3 motors per 16-bit timer, assuming I want to run all motors on a single timer at the same frequency. Perhaps I was not clear about "paired" motor drives being run at the same exact step pulse frequency, hence the decision to run them on the same timer (thus only needing to manipulate one OCR register for both motors on a particular subassembly).

In timer mode 4 (CTC), TOP is defined by OCR. Also from the datasheet, section 17.9.2, page 145:

However, I'll grant you that I may need to shift to mode 12 in order to have both outputs generating the same waveform, since the datasheet specifically states "OCRnA", rather than "OCRnx". However, it will still be a CTC mode, and the TOP value (half period) can still be defined and adjusted during runtime.

Yes of course. If you want several steppers run exactly the same you can connect them in parallel to the same pin. It makes no difference if you use separate OCnx pins for this purpose. It only uses more pins.

N.B. I think you use a step/dir driver. In this case only the leading edge makes the motor do one step. Why use the 'toggle' function with OCRxn? You don't need a sqare wave. A short pulse with constant length and variable repetition frequency will do the job.
When the Counter is reset to zero the output can go HIGH, and e.g. OCR3B at a constant low count resets it to LOW. OCR3A then defines TOP which sets the repetition frequency. You get only one interrupt at every step, which should be sufficent.

1 Like

Step/dir driver is correct. To be specific, it is the Stepper Online CL42T closed loop stepper driver. The drives incorporate optical isolation of the Step/Dir/Enable inputs as well as the Alarm output. In order to mitigate any voltage drop issues by running the Arduino output pin through two opto-isolators, I've decided to place the step pin for each motor driver on its own pin. The calculations work out in that I should be able to run two drivers from the same Arduino step pin, but out of an abundance of caution... :nerd_face:

True, I could likely get away with a shorter pulse, and I contemplated using short pulses at the beginning of this project. However, the stepper drives recommend a 50% duty cycle, and there is a minimum pulse width specified (2.5us), so I figured the "output toggle" option would be perfectly applicable to this application. The "one interrupt per step" feature would be nice - currently, I'm checking the pin level every time the interrupt is triggered, and only reacting when the pin is HIGH, which leads to a lot of unnecessary interrupts.

When you mention using both OCR3B as well as OCR3A to generate the short pulses, how would the registers need to be set up in order to get this behavior? I cannot seem to wrap my head around what would be required to get the same pin to be affected by two different OCR registers. It seems that only the OCRnA register is compared when using CTC mode. Am I missing something there?

In this example only OCR3B is used to create the pulse. OCR3A is only used as TOP and should not generate any output. I think you can also use ICR3 as TOP in this case.

1 Like

If you still want to have a square wave you could set the OCRnx values always to the half of the ICRn when changing the TOP value ( ICRn ).
And you need not mangle with 8-bit registers. You can use the 16bit register adress as e.g OCR3B ( without H/L ) . The compiler handles this correctly.

That many pointer values you use makes it complicated and ineffective. You will have one interrupt per timer. Therefore the ISR knows about the timer registers, no need to handle this with pointers.

1 Like

Update: I re-wrote the architecture from the ground up, without so much flexibility, but still using the same class structure, and only targeting one motor (for now). I think I have found the smoking gun: the OCRnA value is being set too low. I caught it by printing the OCR3A value every time I print the "steps Taken" value, like so:

    // setting maxSpeed and calculating speed ramp occur above this...
    stepperTest.Increment(27774);
    Serial.print(F("OCR3A = "));Serial.println(OCR3A);
    while (stepperTest.stepsTaken < stepperTest.incrementSteps)
    {
        delay(100);
        Serial.print(F("Steps taken: "));Serial.println(stepperTest.stepsTaken);
        //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
        // this is the part I added to catch the problem:
        Serial.print(F("OCR3A = "));Serial.println(OCR3A)
    }
    Serial.println(F("Move complete."));
    stepperTest.stepsTaken = 0;
    MAXspeed += 10;
    // a delay and then this is the end of the main loop

The serial output showed that the minimum OCR value within the OCRarray is supposed to be 666 (EDIT: due to rounding and data types, it will never be the minimum value, but it should be close without going under). However, the actual OCR value was somehow being set to 144 either before or during the max speed portion of the commanded movement. Now, since the OCR register should not be affected outside of the acceleration or deceleration phase due to the comparisons in the ISR, then this incorrect OCR value is likely the last OCR assignment within the acceleration phase.

Here is a piece of the serial output which shows the smoking gun:

I suspect that somehow I am assigning a value outside of the actual array bounds to OCR3A, but I would expect the compiler to catch something like that. Perhaps it cannot catch it when the offending code is within an ISR, or perhaps it does not look that far into runtime conditions to realize an assignment would reference an index out of bounds. I've looked at the binary of the incorrect (too low) values versus the binary of the desired values, and I don't see a commonality which would imply a write-interrupt. Also, I believe a CPU write would have priority over any interrupt.

As a band-aid fix, I've wrapped the OCR assignment (within the ISR) in a check of the current array value versus the supposed minimum array value, but I'd ultimately like to manipulate my handling of the array to avoid this problem. In the band-aid, if the current array value is less than minimum, I assign the minimum to OCR3A, otherwise, I assign the current array value to OCR3A.

Does anyone have any other ideas for a root cause aside from writing OCR to a value which is outside of my array bounds? Furthermore, any ideas how my index is getting out of bounds? Since the adjustment conditions (OCRarray, array index, etc.) are all calculated in association with each other, I don't see how I could be incrementing the array index out of bounds during the acceleration phase.

Here is the full code in case anyone wants to compile and test. It is in one file this time, but still using the same class architecture (I think):

#include "math.h"

class SpeedRamp
{
public:
// private:
    long maxSpeed; // mm/s
    long Accel; // mm/s/s
    long Decel; // mm/s/s
    double mmPerStep;
    int preScale;
    long CntFreq;
    long maxSpeedSteps;
    long maxAccelSteps;
    long numStepsTemp;
    uint16_t OCRmin;
    uint16_t OCRo;
// public:
    long accelSteps;
    long decelSteps;
    int speedUpdateSteps;
    volatile int accelDecelIndex;
    volatile uint16_t OCRarray[21];
    SpeedRamp();
    void SetMaxSpeed(int MmPerSec);
    void CalculateSpeedRamp();
};

SpeedRamp::SpeedRamp()
{
    maxSpeed = 60; // default to 60 mm/s
    Accel = maxSpeed*10; // accelerate in 100 ms
    Decel = maxSpeed*10; // decelerate in 100 ms
    mmPerStep = 0.04;
    preScale = 8; 
    CntFreq = 16000000L/preScale; // calculate timer count frequency based on prescale
    numStepsTemp = 10000; // use 10,000 steps as speed ramp calculation
    CalculateSpeedRamp();
}

void SpeedRamp::SetMaxSpeed(int MmPerSec)
{
    maxSpeed = MmPerSec;
    Accel = maxSpeed*10; // accelerate in 100 ms
    Decel = maxSpeed*10; // decelerate in 100 ms
    CalculateSpeedRamp();
}

void SpeedRamp::CalculateSpeedRamp()
{
    OCRmin = ((mmPerStep*(CntFreq/2))/(maxSpeed));
    OCRo = ((CntFreq/2)*sqrt((2*mmPerStep)/Accel));
    OCRarray[0] = OCRo;
    maxSpeedSteps = ((maxSpeed*maxSpeed)/(2*mmPerStep*Accel));
    maxAccelSteps = ((numStepsTemp*Decel)/(Accel+Decel));
    if (maxSpeedSteps < maxAccelSteps)
    {
        accelSteps = maxSpeedSteps;
        decelSteps = (accelSteps*(Accel/Decel));
    }
    else
    {
        accelSteps = maxAccelSteps;
        decelSteps = (accelSteps*(Accel/Decel));
    }
    speedUpdateSteps = round(accelSteps/(sizeof(OCRarray)/2));
    for (int i = 0; i < (sizeof(OCRarray)/2); i++)
    {
        OCRarray[i] = (OCRo*(sqrt((i*speedUpdateSteps)+1)-sqrt((i*speedUpdateSteps))));
    }
}

class StepperDrive: public SpeedRamp
{
public:
    volatile uint8_t *stepPORT;
    int stepBit;
    volatile uint8_t *stepDDR;
    volatile uint8_t *stepPIN;
    volatile uint8_t *stepTCCRA;
    volatile uint8_t *stepTCCRB;
    volatile uint8_t *stepTCCRC;
    volatile uint8_t *stepTIMSK;
    volatile uint8_t *stepTIFR;
    volatile uint16_t *stepOCRA;
    volatile uint16_t *stepTCNT;
    uint8_t stepTCCRAmask;
    uint8_t stepTCCRAOutputMask;
    uint8_t stepTCCRBmask;
    uint8_t stepTCCRBStartMask;
    uint8_t stepTimerOutputInterruptMask;
    long incrementSteps;
    long stepsTaken;
    StepperDrive();
    void SetupDrive();
    void SetMaxSpeed(double MmPerSec);
    void RunDrive();
    void StopDrive();
    void Increment(long IncrementSteps);
};

StepperDrive::StepperDrive()
            :SpeedRamp()
{
    stepPORT = &PORTE;
    stepBit = 3; // PE3
    stepDDR = &DDRE;
    stepPIN = &PINE;
    stepTCCRA = &TCCR3A;
    stepTCCRB = &TCCR3B;
    stepTCCRC = &TCCR3C;
    stepTIMSK = &TIMSK3;
    stepTIFR = &TIFR3;
    stepOCRA = &OCR3A;
    stepTCNT = &TCNT3;
    stepTCCRAmask = B00000000;
    stepTCCRAOutputMask = B01000000; // COM3A0 = 1 (toggle on compare match)
    stepTCCRBmask = B00001000; // WGM32 = 1 (CTC, mode 4)
    stepTCCRBStartMask = B00000010; // CS31 = 1 (prescale 8)
    stepTimerOutputInterruptMask = B00000010; // OCIE3A = 1 (enable interrupt A)
    stepsTaken = 0;
    incrementSteps = 0;
}

void StepperDrive::SetupDrive()
{
    // clear timer registers
    *stepTCCRA = 0;
    *stepTCCRB = 0;
    *stepTCCRC = 0;
    *stepTIMSK = 0;
    *stepTCNT = 0;
}

void StepperDrive::SetMaxSpeed(double MmPerSec)
{
    SpeedRamp::SetMaxSpeed(MmPerSec); // adjust private maxSpeed variable
}

void StepperDrive::RunDrive()
{
    if (incrementSteps == 0)
    {
        incrementSteps = -1; // if no position target, give indication to ISR
    }
    accelDecelIndex = 0; // reset accelDecelIndex (OCRarray[])
    stepsTaken = 0; // reset stepsTaken
    *stepTCNT = 0; // reset timer count
    noInterrupts();
    *stepOCRA = OCRarray[0]; // set first OCR value
    interrupts();
    *stepDDR |= (1<<stepBit); // set step pin as OUTPUT
    *stepPORT &= ~(1<<stepBit); // set step pin LOW
    *stepTCCRA = (stepTCCRAOutputMask); // enable COM3A toggle on compare match
    *stepTIMSK = (stepTimerOutputInterruptMask); // enable compare match interrupt
    *stepTIFR |= B00001111; // clear any pending interrupts
    *stepTCCRB = (stepTCCRBmask | stepTCCRBStartMask); // place timer in CTC mode, start timer at prescale 8
}

void StepperDrive::StopDrive()
{
    *stepTCCRB = 0; // stop timer
    *stepTCCRA = 0; 
    *stepTIMSK = 0; // clear interrupt enable
    *stepPORT &= ~(1<<stepBit); // pull step pin LOW
}

void StepperDrive::Increment(long IncrementSteps)
{
    incrementSteps = IncrementSteps; // set position target
    RunDrive();
}

// global stepper drive object
StepperDrive stepperTest;

// global MAXspeed variable
double MAXspeed = 60;

void setup() {
    Serial.begin(115200);
    while (!Serial)
    {
        ; // wait for serial to connect
    }
    delay(1000);
    Serial.print(F("Setting up stepper object..."));
    stepperTest.SetupDrive();
    Serial.println(F("Done."));
    Serial.println(F("Now starting main loop."));
}

void loop() {
    Serial.print(F("Setting max speed..."));
    stepperTest.SetMaxSpeed(MAXspeed);
    Serial.println(F("Done."));
    Serial.print(F("maxSpeed = "));Serial.println(stepperTest.maxSpeed);
    Serial.print(F("Accel = "));Serial.println(stepperTest.Accel);
    Serial.print(F("Decel = "));Serial.println(stepperTest.Decel);
    Serial.print(F("maxSpeedSteps = "));Serial.println(stepperTest.maxSpeedSteps);
    Serial.print(F("maxAccelSteps = "));Serial.println(stepperTest.maxAccelSteps);
    Serial.print(F("accelSteps = "));Serial.println(stepperTest.accelSteps);
    Serial.print(F("decelSteps = "));Serial.println(stepperTest.decelSteps);
    Serial.print(F("OCRo = "));Serial.println(stepperTest.OCRo);
    Serial.print(F("OCRmin = "));Serial.println(stepperTest.OCRmin);
    Serial.println(F("OCR array:"));
    for (int i = 0; i < (sizeof(stepperTest.OCRarray)/2); i++)
    {
        Serial.print(F("OCR "));Serial.print(i);Serial.print(F(" = "));Serial.println(stepperTest.OCRarray[i]);
    }
    Serial.println(F("Starting movement..."));
    stepperTest.Increment(27774);
    Serial.print(F("OCR3A = "));Serial.println(OCR3A);
    while (stepperTest.stepsTaken < stepperTest.incrementSteps)
    {
        delay(100);
        Serial.print(F("Steps taken: "));Serial.println(stepperTest.stepsTaken);
        Serial.print(F("OCR3A = "));Serial.println(OCR3A);
    }
    Serial.println(F("Move complete."));
    stepperTest.stepsTaken = 0;
    MAXspeed += 10;
    delay(3000);
}

ISR(TIMER3_COMPA_vect)
{ // OC3A is on pin PE3, controlled by OCR3A
    if (PINE & (1<<3))
    { // step pin is HIGH
        stepperTest.stepsTaken++;
        if ((stepperTest.incrementSteps > 0) && (stepperTest.stepsTaken >= stepperTest.incrementSteps))
        { // if step target exists and has been reached, stop drive - targeted moves only
            stepperTest.StopDrive();
        }
        if (!(stepperTest.stepsTaken % stepperTest.speedUpdateSteps)&&(stepperTest.stepsTaken <= stepperTest.accelSteps))
        { // acceleration phase - all moves
            stepperTest.accelDecelIndex++;
            if (stepperTest.OCRarray[stepperTest.accelDecelIndex] <= stepperTest.OCRmin)
            {
                OCR3A = stepperTest.OCRmin;
            }
            else
            {
                OCR3A = stepperTest.OCRarray[stepperTest.accelDecelIndex];
            }
            // OCR3A = stepperTest.OCRarray[stepperTest.accelDecelIndex];
        }
        if ((stepperTest.incrementSteps > 0)&&(!(stepperTest.stepsTaken % stepperTest.speedUpdateSteps))&&(stepperTest.stepsTaken >= (stepperTest.incrementSteps - stepperTest.decelSteps)))
        { // deceleration phase - targeted moves only
            if (stepperTest.OCRarray[stepperTest.accelDecelIndex] <= stepperTest.OCRmin)
            {
                OCR3A = stepperTest.OCRmin;
            }
            else
            {
                OCR3A = stepperTest.OCRarray[stepperTest.accelDecelIndex];
            }
            // OCR3A = stepperTest.OCRarray[stepperTest.accelDecelIndex];
            stepperTest.accelDecelIndex--;
        }
    }
}

Ahhh yes! Haha, that's a "durrr..." moment for me :rofl:. The timer interrupts are specific to an output compare unit, so I don't need to hold that pointer within the class. Thank you for that!

The output is meaningless without further information. Also OCR is ambiguous, is it the TOP value or the compare match value? How is it related to the stepper movement?

If you need arrays with bounds checks then use one of the many C++ STL classes.

As long as you can not provide a description of your algorithm and only argue with code and registers I can not help you any further.