I'm having a problem with a proof of concept code snippet which, when functional, will form the basis of a bigger project. I am using Timer1 on the Mega 2560 to produce a stream of pulses which drive a stepper motor. I want to gradually increase the pulse rate to some maximum value and then gradually decrease it again. I am using the following code to achieve this:
int LEDPIN=13;
void setup()
{
pinMode(13, OUTPUT);
// initialize Timer1
cli(); // disable global interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCCR1B |= (1 << WGM12); // turn on CTC mode
OCR1A = 1000; // set compare match register to some large(ish) value
// Set prescaler:
TCCR1B |= (1 << CS10);
TCCR1B |= (1 << CS11);
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
sei(); // enable global interrupts
}
ISR(TIMER1_COMPA_vect)
{
digitalWrite(LEDPIN,HIGH);
digitalWrite(LEDPIN,LOW);
}
//Smoothly ramp up and down pulse speed in the main loop
void loop(){
int d=5000; //Delay in microseconds between each update of OCR1A
int MX=2000; //Maximum value of OCR1A
int MN=50; //Minimum value of OCR1A
for (int ii=MX; ii>MN; ii--){
OCR1A=ii;
delayMicroseconds(d);
}
for (int ii=MN; ii<MX; ii++){
OCR1A=ii;
delayMicroseconds(d);
}
}
This almost works. The problem I have is that the signal becomes choppy at times. I can see the choppiness both in the motion of the the stepper motor and examining the pulse train with a scope. I suspect the problem lies in the timer's counter sometimes wrapping around when the value of OCR1A changes. I'm not sure what would be the way to fix this or whether there is a better solution to achieve the above effect.
I can't use tone() for a couple of reasons. I'll likely need pulse rates below 30 Hz and also I need to put a counter in the ISR to keep track of the position of the stepper motor. I'll be using up to 3 or 4 steppers at the same time and they will be turning at different rates.
EDIT: I'll take a look at the source code for tone. Perhaps it'll be possible to modify it in a fairly straightforward manner to do what I want.
I'll be using up to 3 or 4 steppers at the same time and they will be turning at different rates.
I'd need maybe 60 to 80 kHz at most.
1/80000*16000000 = 200. If your sketch is not doing much else that is very likely enough clock cycles to do what you want using blink-without-delay. With that frequency range you will have to use unsigned long.
If your project is doing more I suspect putting the motor control on a separate Uno would actually simplify the overall project.
Cheers, yes, blink without delay is how I started but that worked badly because the sketch is relatively complicated (it reads from USB host shield, updates an LCD display, etc). This is a project I finished a while ago with accelstepper but I want higher frequencies than that library provides so I'm looking to roll my solution with one timer per stepper. That's possible on the Mega.
I looked over the tone() code and I think I now have a solution that works. The solution to getting a smooth response is to wait until TCNT1 is zero before changing the value of OCR1A. This becomes more critical at larger prescaler values and when accelerating at higher speeds. The code sample is:
int LEDPIN=13;
void setup()
{
pinMode(13, OUTPUT);
// initialize Timer1
cli(); // disable global interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCCR1B |= (1 << WGM12); // turn on CTC mode
OCR1A = 1000; //Set compare match register to some value. Not important, it gets changed later
bitWrite(TCCR1B, CS10, 1); //Set prescaler (it's over-written later anyway)
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt:
sei(); // enable global interrupts
}
void loop(){
int d=10; //Delay in ms between each update of OCR1A
int MX=25000; //Maximum frequency (hz)
int MN=3; //Minimum frequency (hz)
int delta=40; //By how much to change frequency on each pass through for loop
for (int ii=MN; ii<MX; ii+=delta){ playTone(ii); delay(d); } //ramp up
for (int ii=MX; ii>MN; ii-=delta){ playTone(ii); delay(d); } //ramp down
}
//Heavily butchered code from tone() library
void playTone(uint16_t frequency)
{
uint8_t prescalarbits = 0b001;//default to no prescaler
uint32_t ocr = ocr = F_CPU / frequency / 2 - 1;
if (ocr > 65535) //If it over-flows go to 64 prescaler
{
ocr = F_CPU / frequency / 2 / 64 - 1;
prescalarbits = 0b011;
}
//Only proceed when counter register is at zero signal choppy without this line
while(TCNT1>0){
}
TCCR1B = (TCCR1B & 0b11111000) | prescalarbits; //set prescaler
OCR1A = ocr; // Set the OCR
}
ISR(TIMER1_COMPA_vect)
{
digitalWrite(LEDPIN,!digitalRead(LEDPIN));
//OR: With direct pin manipulations it's only 75 ns to switch the pin
// but then must add a monostable 555 to lengthen the pulse for stepper driver board
//PORTB &= ~bit(7);//switch off pin 13
// PORTB |= bit(7);//switch on pin 13
}