How would I go about converting a atmega interrupts for teensys?

Status
Not open for further replies.

joonaskali

New member
Hi,

I'm trying to convert this guys sketch for teensy 4.0. He does say to read the doccumentation of the controllers for proper interrupt flags but I'm not smart enough for that.

I tried changing the interrupt flags to some other flags but I could not get it to work. How can I get this to work with teensys?

Arduino: 1.8.15 (Windows 10), TD: 1.54, Board: "Teensy 4.0, Serial, 600 MHz, Faster, US English"

Here is the compile error I am getting.
Code:
Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:297: error: expected constructor, destructor, or type conversion before '(' token

 ISR(TIMER1_COMPA_vect)

     ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void xStep()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:26: error: 'PORTF' was not declared in this scope

 #define X_STEP_HIGH             PORTF |=  0b00000001;

                                 ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:76:3: note: in expansion of macro 'X_STEP_HIGH'

   X_STEP_HIGH

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void yStep()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:29: error: 'PORTF' was not declared in this scope

 #define Y_STEP_HIGH             PORTF |=  0b01000000;

                                 ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:84:3: note: in expansion of macro 'Y_STEP_HIGH'

   Y_STEP_HIGH

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void zStep()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:32: error: 'PORTL' was not declared in this scope

 #define Z_STEP_HIGH             PORTL |=  0b00001000;

                                 ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:92:3: note: in expansion of macro 'Z_STEP_HIGH'

   Z_STEP_HIGH

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void aStep()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:35: error: 'PORTA' was not declared in this scope

 #define A_STEP_HIGH             PORTA |=  0b00010000;

                                 ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:100:3: note: in expansion of macro 'A_STEP_HIGH'

   A_STEP_HIGH

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void cStep()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:41: error: 'PORTL' was not declared in this scope

 #define C_STEP_HIGH             PORTL |=  0b00000100;

                                 ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:116:3: note: in expansion of macro 'C_STEP_HIGH'

   C_STEP_HIGH

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void setup()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:173: error: 'TCCR1A' was not declared in this scope

   TCCR1A = 0;

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:174: error: 'TCCR1B' was not declared in this scope

   TCCR1B = 0;

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:175: error: 'TCNT1' was not declared in this scope

   TCNT1  = 0;

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:177: error: 'OCR1A' was not declared in this scope

   OCR1A = 1000;                             // compare value

   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:178: error: 'WGM12' was not declared in this scope

   TCCR1B |= (1 << WGM12);                   // CTC mode

                   ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:179: error: 'CS11' was not declared in this scope

   TCCR1B |= ((1 << CS11) | (1 << CS10));    // 64 prescaler

                    ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:179: error: 'CS10' was not declared in this scope

   TCCR1B |= ((1 << CS11) | (1 << CS10));    // 64 prescaler

                                  ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated: In function 'void setNextInterruptInterval()':

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:45: error: 'TIMSK1' was not declared in this scope

 #define TIMER1_INTERRUPTS_OFF   TIMSK1 &= ~(1 << OCIE1A);

                                 ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:290:5: note: in expansion of macro 'TIMER1_INTERRUPTS_OFF'

     TIMER1_INTERRUPTS_OFF

     ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:45: error: 'OCIE1A' was not declared in this scope

 #define TIMER1_INTERRUPTS_OFF   TIMSK1 &= ~(1 << OCIE1A);

                                                  ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino:290:5: note: in expansion of macro 'TIMER1_INTERRUPTS_OFF'

     TIMER1_INTERRUPTS_OFF

     ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:291: error: 'OCR1A' was not declared in this scope

     OCR1A = 65500;

     ^

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:294: error: 'OCR1A' was not declared in this scope

   OCR1A = mind;

   ^

C:\Users\Joonas\Desktop\stepperControl_iforce2d\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated\Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino: At global scope:

Stepper7_linearSpeedInterruptMultipleMotorsCoordinated:297: error: expected constructor, destructor, or type conversion before '(' token

 ISR(TIMER1_COMPA_vect)

    ^

expected constructor, destructor, or type conversion before '(' token



This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.


Thank you!

View attachment Stepper7_linearSpeedInterruptMultipleMotorsCoordinated.ino
 
There is no single easy answer to this question. Every case requires carefully studying the original source code to learn how it works.

However, Teensyduino does provide some convenient features for the very common usages, which I believe probably apply here. But I only looked at the code for a couple minutes.

First, the easy part. These are doing the same work as digitalWrite(), but accessing the registers directly because 8 bit AVR is too slow.

Code:
#define X_STEP_HIGH             PORTF |=  0b00000001;
#define X_STEP_LOW              PORTF &= ~0b00000001;

You can probably replace all of these with digitalWrite() or digitalWriteFast() on Teensy. But be careful with digitalWriteFast(). It will probably be much too fast. You'll probably also need to add delayNanoseconds(60) or maybe even 120ns delay to get pulses similar to the highly optimized AVR code.


Second is the more complicated part, the timer. The AVR timers have several features like automatically changing a pin according to changes in the timer. But the good news is this code doesn't seem to be using any of that stuff. My impression from a quick look at the code is it's just using the timer to run the TIMER1_COMPA_vect function at a precise regular time interval.

Teensyduino has a feature called IntervalTimer to do this. Here's the documentation:

https://www.pjrc.com/teensy/td_timing_IntervalTimer.html

The AVR timer code isn't as simple to replace as the direct pin control, because it's scattered throughout the program. The timer is started in one place (which is where you'll start IntervalTimer). Inside TIMER1_COMPA_vect the timer is manipulated, because it isn't fully automatic to just run the function again. But IntervalTimer is fully automatic like that. You just configure it once and the function interrupts your program automatically at the configured timing. So unless that code is doing something tricky which I don't fully understand (it could be) you probably can just delete those parts and let IntervalTimer take care of everything.

The TIMER1_COMPA_vect function is surrounded by an ISR() macro, because AVR requires special stuff like that for its interrupts. Just delete it. On ARM chips like Teensy 4, functions for interrupts are created the same way as any other function. You can leave the name TIMER1_COMPA_vect or change it to any name you like. Special names aren't needed on Teensy, since you'll give the name you choose to the IntervalTimer begin() function.


Again, there is no complete answer for this sort of question. Every case involves studying the original code to understand its function. I looked only for a couple minutes. It is doing something special with its setNextInterruptInterval() function which I didn't decipher. If it's changing the timing, you might need to use IntervalTimer's update() function. The IntervalTimer functions all take input as microseconds (and you can use float variables if you want more precision than 1 microsecond) so you'll have to figure out what actual amount of time this AVR code computes in terms of the speed of the AVR timer and convert it to actual microseconds.
 
Hi paul, thanks for the reply. Thanks for steering me into the right direction! Sadly I don't have a Teensy 3.x. :(

I've converted the code to now compile but another problem arises! The motors aren't moving!
I think I've gotten the correct timer delays but I'm not sure about the OCR1A and TCNT1 values.

Maybe OCR1A is timer tick between function repetions but what is TCNT1 then?

Code:
#include <Arduino.h>
#include "variables.h" //Contains most of the variables
#include "Wire.h"
#include "SPI.h"
#include "SerialTransfer.h"
#include "IntervalTimer.h"


#define X_DIR_PIN          16
#define X_STEP_PIN         17

#define Y_DIR_PIN          14
#define Y_STEP_PIN         15

#define Z_DIR_PIN          12
#define Z_STEP_PIN         11


IntervalTimer myTimer;

volatile int interruptseconds = 1000;

SerialTransfer myTransfer; // remember to use myTransfer.tick(); to get callback

struct stepperInfo
{
	// externally defined parameters
	float acceleration;
	volatile unsigned long minStepInterval; // ie. max speed, smaller is faster
	void (*dirFunc)(int);
	void (*stepFunc)();

	// derived parameters
	unsigned int c0;   // step interval for first step, determines acceleration
	long stepPosition; // current position of stepper (total of all movements taken so far)

	// per movement variables (only changed once per movement)
	volatile int dir;						// current direction of movement, used to keep track of position
	volatile unsigned int totalSteps;		// number of steps requested for current movement
	volatile bool movementDone = false;		// true if the current movement has been completed (used by main program to wait for completion)
	volatile unsigned int rampUpStepCount;	// number of steps taken to reach either max speed, or half-way to the goal (will be zero until this number is known)
	volatile unsigned long estStepsToSpeed; // estimated steps required to reach max speed
	volatile unsigned long estTimeForMove;	// estimated time (interrupt ticks) required to complete movement
	volatile unsigned long rampUpStepTime;
	volatile float speedScale; // used to slow down this motor to make coordinated movement with other motors

	// per iteration variables (potentially changed every interrupt)
	volatile unsigned int n;		 // index in acceleration curve, used to calculate next interval
	volatile float d;				 // current interval length
	volatile unsigned long di;		 // above variable truncated
	volatile unsigned int stepCount; // number of steps completed in current movement
};

void xStep()
{
	digitalWrite(X_STEP_PIN, HIGH);
	digitalWrite(X_STEP_PIN, LOW);
}
void xDir(int dir)
{
	digitalWrite(X_DIR_PIN, dir);
}

void yStep()
{
	digitalWrite(Y_STEP_PIN, HIGH);
	digitalWrite(Y_STEP_PIN, LOW);
}
void yDir(int dir)
{
	digitalWrite(Y_DIR_PIN, dir);
}

void zStep()
{
	digitalWrite(Z_STEP_PIN, HIGH);
	digitalWrite(Z_STEP_PIN, LOW);
}
void zDir(int dir)
{
	digitalWrite(Z_DIR_PIN, dir);
}

#define NUM_STEPPERS 3

volatile stepperInfo steppers[NUM_STEPPERS];

void resetStepperInfo(stepperInfo &si)
{
	si.n = 0;
	si.d = 0;
	si.di = 0;
	si.stepCount = 0;
	si.rampUpStepCount = 0;
	si.rampUpStepTime = 0;
	si.totalSteps = 0;
	si.stepPosition = 0;
	si.movementDone = false;
}

void zeroPositions()
{
	for (int i = 0; i < maxPositions; i++)
	{
		speedpositions[i] = 0;
		for (int j = 0; j < 3; j++)
		{
			positions[i][j] = 0;
		}
	}
}

void callbackController()
{
	//Serial.println("callback");
	uint16_t recSize = 0;
	recSize = myTransfer.rxObj(inputArray, recSize);

	if (inputArray[xbLBA] < inputArray[xbRBA])
	{
		//Right trigger code
		speedmodifier = map(inputArray[xbRBA], 0, 1000, 0, 100);
		speed = speed + speedmodifier;
		//Checks if speed is over max
		if (speed > MaxTotalSpeed)
		{
			speed = MaxTotalSpeed;
		}
	}
	else
	{
		//Left trigger code
		speedmodifier = map(inputArray[xbLBA], 0, 1000, 0, 100);
		speed = speed - speedmodifier;
		//If speed is negative
		if (speed < 0)
		{
			speed = 0;
		}
	}
	//Serial.println(speed);
	for (int i = 0; i < 4; i++)
	{
		inputArray[i] = map(inputArray[i], 0, 1000, 0, speed);
	}
}

// supplied as a reference - persistent allocation required
const functionPtr callbackArr[] = {callbackController};

void setup()
{
	pinMode(X_STEP_PIN, OUTPUT);
	pinMode(X_DIR_PIN, OUTPUT);

	pinMode(Y_STEP_PIN, OUTPUT);
	pinMode(Y_DIR_PIN, OUTPUT);

	pinMode(Z_STEP_PIN, OUTPUT);
	pinMode(Z_DIR_PIN, OUTPUT);

	Serial1.begin(115200);
	Serial.begin(115200);
	Serial.println("Start");

	///////////////////////////////////////////////////////////////////
	configST myConfig;
	myConfig.debug = true;
	myConfig.callbacks = callbackArr;
	myConfig.callbacksLen = sizeof(callbackArr) / sizeof(functionPtr);
	///////////////////////////////////////////////////////////////////

	myTransfer.begin(Serial1, myConfig);

	zeroPositions();

	steppers[0].dirFunc = xDir;
	steppers[0].stepFunc = xStep;
	steppers[0].acceleration = 1000;
	steppers[0].minStepInterval = 50;

	steppers[1].dirFunc = yDir;
	steppers[1].stepFunc = yStep;
	steppers[1].acceleration = 1000;
	steppers[1].minStepInterval = 50;

	steppers[2].dirFunc = zDir;
	steppers[2].stepFunc = zStep;
	steppers[2].acceleration = 1000;
	steppers[2].minStepInterval = 50;
}

void resetStepper(volatile stepperInfo &si)
{
	si.c0 = si.acceleration;
	si.d = si.c0;
	si.di = si.d;
	si.stepCount = 0;
	si.n = 0;
	si.rampUpStepCount = 0;
	si.movementDone = false;
	si.speedScale = 1;

	float a = si.minStepInterval / (float)si.c0;
	a *= 0.676;

	float m = ((a * a - 1) / (-2 * a));
	float n = m * m;

	si.estStepsToSpeed = n;
}

volatile byte remainingSteppersFlag = 0;

float getDurationOfAcceleration(volatile stepperInfo &s, unsigned int numSteps)
{
	float d = s.c0;
	float totalDuration = 0;
	for (unsigned int n = 1; n < numSteps; n++)
	{
		d = d - (2 * d) / (4 * n + 1);
		totalDuration += d;
	}
	return totalDuration;
}

void prepareMovement(int whichMotor, long steps)
{
	volatile stepperInfo &si = steppers[whichMotor];
	si.dirFunc(steps < 0 ? HIGH : LOW);
	si.dir = steps > 0 ? 1 : -1;
	si.totalSteps = abs(steps);
	resetStepper(si);

	remainingSteppersFlag |= (1 << whichMotor);

	unsigned long stepsAbs = abs(steps);

	if ((2 * si.estStepsToSpeed) < stepsAbs)
	{
		// there will be a period of time at full speed
		unsigned long stepsAtFullSpeed = stepsAbs - 2 * si.estStepsToSpeed;
		float accelDecelTime = getDurationOfAcceleration(si, si.estStepsToSpeed);
		si.estTimeForMove = 2 * accelDecelTime + stepsAtFullSpeed * si.minStepInterval;
	}
	else
	{
		// will not reach full speed before needing to slow down again
		float accelDecelTime = getDurationOfAcceleration(si, stepsAbs / 2);
		si.estTimeForMove = 2 * accelDecelTime;
	}
}

volatile byte nextStepperFlag = 0;

void setNextInterruptInterval()
{

	bool movementComplete = true;

	unsigned long mind = 999999;
	for (int i = 0; i < NUM_STEPPERS; i++)
	{
		if (((1 << i) & remainingSteppersFlag) && steppers[i].di < mind)
		{
			mind = steppers[i].di;
		}
	}

	nextStepperFlag = 0;
	for (int i = 0; i < NUM_STEPPERS; i++)
	{
		if (!steppers[i].movementDone)
			movementComplete = false;
		if (((1 << i) & remainingSteppersFlag) && steppers[i].di == mind)
			nextStepperFlag |= (1 << i);
	}

	if (remainingSteppersFlag == 0)
	{
		//TIMER1_INTERRUPTS_OFF
		//OCR1A = 65500;
		myTimer.end();
		interruptseconds = 65500;
	}

	myTimer.update(mind);
}

//SR(TIMER1_COMPA_vect)
void function()
{
	//unsigned int tmpCtr = OCR1A;
	unsigned int tmpCtr = interruptseconds;

	//OCR1A = 65500;
	interruptseconds = 65500;

	for (int i = 0; i < NUM_STEPPERS; i++)
	{

		if (!((1 << i) & remainingSteppersFlag))
			continue;

		if (!(nextStepperFlag & (1 << i)))
		{
			steppers[i].di -= tmpCtr;
			continue;
		}

		volatile stepperInfo &s = steppers[i];

		if (s.stepCount < s.totalSteps)
		{
			s.stepFunc();
			s.stepCount++;
			s.stepPosition += s.dir;
			if (s.stepCount >= s.totalSteps)
			{
				s.movementDone = true;
				remainingSteppersFlag &= ~(1 << i);
			}
		}

		if (s.rampUpStepCount == 0)
		{
			s.n++;
			s.d = s.d - (2 * s.d) / (4 * s.n + 1);
			if (s.d <= s.minStepInterval)
			{
				s.d = s.minStepInterval;
				s.rampUpStepCount = s.stepCount;
			}
			if (s.stepCount >= s.totalSteps / 2)
			{
				s.rampUpStepCount = s.stepCount;
			}
			s.rampUpStepTime += s.d;
		}
		else if (s.stepCount >= s.totalSteps - s.rampUpStepCount)
		{
			s.d = (s.d * (4 * s.n + 1)) / (4 * s.n + 1 - 2);
			s.n--;
		}

		s.di = s.d * s.speedScale; // integer
	}

	setNextInterruptInterval();

	//TCNT1  = 0;
}

void adjustSpeedScales()
{
	float maxTime = 0;

	for (int i = 0; i < NUM_STEPPERS; i++)
	{
		if (!((1 << i) & remainingSteppersFlag))
			continue;
		if (steppers[i].estTimeForMove > maxTime)
			maxTime = steppers[i].estTimeForMove;
	}

	if (maxTime != 0)
	{
		for (int i = 0; i < NUM_STEPPERS; i++)
		{
			if (!((1 << i) & remainingSteppersFlag))
				continue;
			steppers[i].speedScale = maxTime / steppers[i].estTimeForMove;
		}
	}
}


void runAndWait()
{
	adjustSpeedScales();
	setNextInterruptInterval();
	myTimer.begin(function, interruptseconds);

	while (remainingSteppersFlag);
	
	remainingSteppersFlag = 0;
	nextStepperFlag = 0;

	myTimer.end();
}


void loop()
{
	for (int i = 0; i < NUM_STEPPERS; i++)
	{
		prepareMovement(i, 800);
		runAndWait();
	}

	prepareMovement(0, 8000);
	prepareMovement(1, 800);
	prepareMovement(2, 2400);
	runAndWait();

	delay(1000);

	prepareMovement(0, -8000);
	prepareMovement(1, 1600);
	prepareMovement(2, -2400);
	runAndWait();
}
 
Last edited:
it looks like the 2560 timer is configured to run at 16mhz/64 (250KHz). So the tick duration is 4 us. The teensy IntervalTimer wants time in microseconds, so you need to multiply your duration values for 2560 by 4, e.g., myTimer.begin(function, 4 * interruptseconds); For OCR1A = 1000, a pulse is generated every 4000 us. The pulse width for the digitalWrite HIGH/LOW is 5.6 us for the mega2560, but only 40 ns for faster Teensy 4.

you might also want to use myTimer.begin(function,4*mind); in place of myTimer.update(mind); to restart the timer.

(TCNT1 is current value of the 2560 timer1. setting TCNT1 to zero effectively restarts the mega timer)
 
Last edited:
Status
Not open for further replies.
Back
Top