MEGA 2560 - Odd Timer Behavior first loop and intermittent thereafter

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--;
        }
    }
}