Pages: [1]   Go Down
Author Topic: Interrupts and stepper motor advice needed.  (Read 2299 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 2
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Dear All,

I'm trying to use my arduino duemilanove to drive some stepper motors via a geckodrive, the trouble is I'm a little confused about the timers and interrupts.

I have been happily moving the motors by pulsing a pin every 75 microseconds, using the delayMicroseconds() function and all has been working well, but the motors are running a little slow.

So I am trying to implement acceleration/deceleration for the stepper, this will require me to sending pulses of varying lengths.

My problem is I dont quite understand interrupts and was looking for some advice.  I am needing pulsing varying in length from 4000us to 50us, I have configured timer1 to tick every 0.5us (1/16000000*smiley-cool.

I was under the impression if I then set OCR1A to my required pulse time, the timer1 CTC interrupt should fire at the required time.  So if I want to send a pulse after 4000us, I set OCR1A = 8000 (due to 0.5us ticks), then the interrupt should fire and send my pulse.

Is this reasoning correct, or am I missing something.  Is there a better way to do this.

Below is the code I am doing my tests with:

Code:
ISR(TIMER1_COMPA_vect)  // Timer-1 OC1A Match interrupt handler
{
  cli();
  Serial.println(micros());
  TCNT1 = 0;
  TIMSK1 = 1<<OCIE1A;
};

void setup()
{
  Serial.begin(9600);
  delay(50);
  TCCR1A = 0;
  TCCR1B = B0100010;
  OCR1A = 8000;
  TCNT1 = 0;
  TIMSK1 = 1<<OCIE1A;
}

void loop(){}

The Serial.println(micros()); line prints out a value of between 52900 and 53100.

Thanks in advance for any help.
Logged

Waterloo, Canada
Offline Offline
Full Member
***
Karma: 1
Posts: 242
Engineer
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Looks more or less right, but by performing the serial.print inside the interrupt you're over-running the next one.

5-digits (52000) * 8 bits = 40 bits
@ 9600 bps -> T(serial) ~= 0.004s

It's likely that you're spending almost all of your processor time inside this interrupt as a result.

Are you suggesting that the micros() value output is only displayed once?  Or that it always displays the same value?

Also, you don't need to re-enable TIMSK1 inside the interrupt.  Once it is enabled, it's on until you explicitly turn it off.
I also think Timer 1 is the timer used for the micros() timer... but I can't recall with certainty right now.  So by resetting it you might be preventing micros() from rolling-over.  When you start playing with timer registers you have to be careful to rely on arduino environment functions relying on the same.
« Last Edit: March 25, 2010, 02:46:22 pm by mitch_79 » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 2
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for the reply.

Ok, the Serial.print is just there for debugging purposes, but I have changed the baud rate from 9600 to 38400 and things are looking slightly better.

The thinking behind the Serial.println(micros()) in the ISR is that everytime the interrupt fires, the Serial.println(micros()) displays the current time in micros since it was last fired, as I have set the OCR1A to 8000 and the timer is set for 0.5ms, then I would expect the difference between every two micros() readings to display 4000 each time, but this is not the case.  It varies from 4600 upto 6040.

Due to sending pulses to a motor driver I need to get this more precise, is anyone able to offer any advice?

Thanks in advance.
Logged

Waterloo, Canada
Offline Offline
Full Member
***
Karma: 1
Posts: 242
Engineer
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yep, I understand your thinking behind the serial.print.  But like I said, you are over-running the interrupts.  I'm sure of it.  Either max out the baud rate for this test, or get the serial.print out of the interrupt altogether (set a global variable and print it's contents in the main loop).
Logged

Poole
Offline Offline
Jr. Member
**
Karma: 0
Posts: 89
I'm not a complete idiot. Some bits are missing
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The main problem is that Serial.print(); really isn't up to the job, as it takes way too long to output the data and so it is missing the next 'x' interupts. What you really need to is create an output using the compare A or B register and use an oscilloscope to measure the time bewteen pulses. I understand that most people don't have oscilloscopes lying round; I was lucky enought to inherrit one from my Dad.

Try this code:
Code:

ISR(TIMER1_COMPA_vect)  // Timer-1 OC1A Match interrupt handler
{
  //No need to do anything here other than change the pause between pulses
 // by changing the value of OC1A
};

void setup()
{
  Serial.begin(9600);
  delay(50);
  TCCR1A = B0011001;    
  TCCR1B = B0100010;
  TCCR1C = B0000000;
  TIMSK1 = B0000010;  
  ITFR1  = B0000010;
  OCR1A = 4000;      //Pause between pulses in us
  OCR1B = 100;       //Pulse Length in us
  TCNT1 = 0;
  pinMode(10, OUTPUT); //this produces a pulse on pin 10;
  
}

void loop(){}

The way this differs from yours is that it uses phase and frequency correct PWM mode to generate the pulses, rather than 'normal' mode. It also uses the compare 'B' register to output a pulse on pin 10 (you can't change this, as it is hardwired).

The length of the pause between pulses is determined by the value of compare register A. In this PWM mode the counter counts both up and down, so a 'top' value of 4000 will result in a total count of 8000 between pulses, at 0.5us per 'tick' that gives you a pause bewteen pulses of 4000us.

The great thing about this mode, is that compare register A is double buffered, and updates when the timer reaches 0, making it very easy to control the length of the pulse. You can use the OC1A interrupt vector to change the value of OC1A.

The length of the pulse is determined by the value of compare register B, a value of 10 would result in a 10us pulse. If you check the datasheet for your stepper driver it shoudl tell you the minimum pulse length required to induce a step.

The 'direction' of the pulse can be changed by changing the value of TCCR1A to B0010001

I hope most (any) of that makes sense.

Chris
« Last Edit: March 30, 2010, 09:35:05 am by cjparish » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 4
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You can still use Serial.print for debugging purposes, just need to move it outside of the interrupt:
Code:
long timer=0;
ISR(TIMER1_COMPA_vect)  // Timer-1 OC1A Match interrupt handler
{
  cli();
  timer = micros();
  TCNT1 = 0;
  TIMSK1 = 1<<OCIE1A;
};

void setup()
{
  Serial.begin(9600);
  delay(50);
  TCCR1A = 0;
  TCCR1B = B0100010;
  OCR1A = 8000;
  TCNT1 = 0;
  TIMSK1 = 1<<OCIE1A;
}

void loop()
{

  delay(1000);
  Serial.println(timer);

}


this will print out the pulse width every second without burdening the interrupt handling routine.

I believe you're also missing SLI (interrupts enable command) at the end of the interrupt handler

Aleksey
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


A few corrections...

Code:
[glow]volatile[/glow] long timer=0;

ISR(TIMER1_COMPA_vect)  // Timer-1 OC1A Match interrupt handler
{
[glow]/* Remove.  This serves no purpose.  Interrupts are disabled when an ISR is called.
  cli();
*/[/glow]
  timer = micros();
  TCNT1 = 0;
  TIMSK1 = 1<<OCIE1A;
};

void loop()
{
  delay(1000);
[glow]/* Accessing timer without first disabling interrupts is not safe.
  Serial.println(timer);
*/[/glow]
}

Quote
I believe you're also missing SLI (interrupts enable command) at the end of the interrupt handler
This is not necessary.  Reenabling interrupts is automatically performed.
« Last Edit: April 05, 2010, 03:10:54 am by bcook » Logged

0
Offline Offline
Shannon Member
****
Karma: 222
Posts: 12725
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

There is no need to use interrupts as the timers can be made to directly drive pins (this is how PWM works).

However you'll need to find out more about the ATmega timers and associated registers in order to do this - worth doing some more research online to see if this has already been coded by someone.
Logged

[ I won't respond to messages, use the forum please ]

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 90
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I recently saw a post about a new stepper motor library, that looks really great.
Logged

Ross-on-Wye (UK)
Offline Offline
Newbie
*
Karma: 0
Posts: 29
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi, a better way to examine/debug the performance of a timer interrupt is to increment a counter each time it runs. Then display this variable at 1 second intervals (or any known interval... then do the math).
Also, the timer register may be reset to zero automatically using Clear Timer on Compare or CTC mode. see datasheet for MCU - setion 15.9.2 page 125.
(caution: you are setting bit5 in TCCR1B this is "reserved" and should only ever be written 1 - see datasheet) 
 
Code:
#include <avr/io.h>
volatile long counter=0;
ISR(TIMER1_COMPA_vect)  // Timer-1 OC1A Match interrupt handler
{
  counter++;  
};

void setup()
{
  .....
  TCCR1A = 0;
  // TCCR1B = B0100010;
  sbi(TCCR1B, WGM12); // CTC mode
                      // new code: TCCR1B |= _BV(WGM12);
  sbi(TCCR1B, CS11);  // prescaler 8
                      // new code: TCCR1B |= _BV(CS11);
  OCR1A = 8000;
  TCNT1 = 0;
  // TIMSK1 = 1<<OCIE1A;
  sbi(TIMSK1, OCIE 1A); // new code TIMSK1 |= _BV(OCIE1A);
}

void loop()
{

  delay(1000);
  Serial.print(" millis: ");
  Serial.print(millis());
  Serial.print(" interrupt calls: ");
  Serial.println(counter);

}

Logged

Pages: [1]   Go Up
Jump to: