Saturday, 25 September 2010

Arduino Stepper Driver v2

Here is a quick post of the Arduino code (interrupt service routine) I used to trial run the stepper motor. It's quick and dirty, so beware! It uses timer 3 to create the step signal. The main loop is responsible for acceleration. It does so by monitoring time and updating speed. Obviously, this is not perfect, but it's a start.


// Quick and Dirty Stepper Interrupt Service Routine
// Lets your Arduino Mega drive a stepper motor
// via a timer.
//
//
//
// Copyright (C) 2010 cncorbust.com
// This program is free software: you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at
// your option) any later version.
// This program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE.  See the GNU General Public
// License for more details.
// You should have received a copy of the GNU General Public
// License along with this program.  If not, see www.gnu.org.
//
//
// Uses timer 3
//
// Pin 5 outputs step signal
// Pin 11 outputs direction signal
//
const int directionPin = 11;
const int stepPin = 5; // tied to timer 3; don't change this.

volatile long pos3;
volatile long delay3;
volatile int dir3; // -1 0 1

// Keep track of position
ISR(TIMER3_COMPA_vect)
{
  pos3+=dir3;
}

// call this to query current position
long getMotorPosition()
{
  cli();
  long r = pos3;
  sei();
  return r;
}

// Call to get started
void initMotor()
{
  digitalWrite(directionPin,0);
  pinMode(directionPin,OUTPUT);

  cli();
  pos3 = 0;
  dir3 = 0;
 
  TCCR3A = B01000001;
  TCCR3B = B00010000;  // no clock source, so stopped
  TIMSK3 = B00000010;
  OCR3AH = 255;  // just in case someone starts it without setting speed
  OCR3AL = 255;
  TCNT3H = 0;  // reset timer to trigger update of TOP from OCR3A
  TCNT3L = 0;
  pinMode(stepPin,OUTPUT);
  sei();
}

// set to 0 to stop motor
// set to negative value to move backwards
// be careful not to exceed max values (which depend on prescale factor)
void setMotorStepDelay(long microsecondsPerStep)
{
  if(microsecondsPerStep==0)
  {
    cli();
    dir3 = 0;
    delay3 = 0L;
    TCCR3B = B00010000;  // no clock source, so timer is stopped
    sei();
    return;
  }

  // 1/64 prescale:  x/1m*16m/4/64 (/4 because of timer mode)
  // long clockTicksPerStep = microsecondsPerStep / 16;  

  // 1/8 prescale: x/1m*16m/4/8
  long clockTicksPerStep = microsecondsPerStep / 2;

  // Just in case. Still many ways to break this...
  if(clockTicksPerStep>65535)
    clockTicksPerStep = 65535;
  else if(clockTicksPerStep<-65535)
    clockTicksPerStep = -65535;
 
  // horribly long time to block interrupts below...
  // Which part of "quick and dirty" did you not understand?

  cli();

  // TCCR3B = B00010011;  // 1/64 prescale
  TCCR3B = B00010010;  // 1/8 prescale
 
  if(clockTicksPerStep<0)
  {
    clockTicksPerStep = -clockTicksPerStep;
    dir3 = -1;
    digitalWrite(directionPin,1);  // 1 is backward on my setup
  }
  else
  {
    dir3 = 1;
    digitalWrite(directionPin,0);
  }

  OCR3AH = clockTicksPerStep >> 8;   // set delay, high byte first
  OCR3AL = clockTicksPerStep;
  if(delay3==0)
  {
    // we're starting from a stopped state, so kill the previous timer step...
    // reset timer to trigger update of TOP from OCR3A
    TCNT3H = 0;
    TCNT3L = 0;
  }
 
  delay3 = microsecondsPerStep;
 
  sei();
}


//------------------------------------------------------------------
//------------------------------------------------------------------
//------------------------------------------------------------------

// Your program here....


void setup()
{
  pinMode(13,OUTPUT); // led pin
  initMotor();
}

// Helper method to move motor slowly
void adjustPosition()
{
  digitalWrite(13,HIGH);  // Led indicator
  setMotorStepDelay(500); // 2000 micro-steps per second
  while(true);
}

// Main routine. Moves stepper back and forward
void doMainRun()
{
  int direction = -1;
  while(true)
  {
    // A 1 second stand-still, to engage/disengage motor by hand
    digitalWrite(13,HIGH);
    delay(1000);
    digitalWrite(13,LOW);
 
    long acceleration = 120000L; // steps per second per second
    long maxSpeed = 10L * 200L * 25L; // steps per second
    long travelDistance = 200L * 10L * 40;  // how far at full speed (roughly only!)
 
    long fullSpeedDuration = travelDistance * 1000L / maxSpeed; // duration in ms
    long baseTime;
    long dt;
    long speed;
 
    // speed up
    baseTime = millis();
    do
    {
    dt = millis() - baseTime;
    speed = dt * acceleration / 1000L;
    if(speed>maxSpeed) speed = maxSpeed;
    if(speed==0)
      setMotorStepDelay(0);
    else
      setMotorStepDelay(1000000L/speed*direction);
    } while(speed<maxSpeed);

    // go at full throttle
    delay(fullSpeedDuration); // let it run for a while

    // slow down
    baseTime = millis();
    do
    {
    dt = millis() - baseTime;
    speed = maxSpeed - dt * acceleration / 1000L;
    if(speed>maxSpeed) speed = maxSpeed;
    if(speed<=0)
      setMotorStepDelay(0);
    else
      setMotorStepDelay(1000000L/speed*direction);
    } while(speed>0);

    // Now go in reverse and do it all again.
    // It's like the share market...
    direction = -direction;
  }
}

void loop()
{
  doMainRun();
//  adjustPosition();
}

Thursday, 23 September 2010

First Speed Test

Last night I had a free moment and took the z-axis for a little test drive. I threw together a quick and dirty interrupt service routine for the Arduino to see how things would work out.

The good news: I'm able to take the motor up to 50,000 microsteps per second (5,000 full steps/s, or 25 revolutions/s, or 1,500 rpm), and that's with a very rough acceleration algorithm. At a threaded rod pitch of 1.75mm, this yields 44mm/s or 2.6m/min. Acceleration was comfortable at 120,000 microsteps per second per second, though I didn't try to push this further due to lack of time. All up, this is a (so far) pretty good result. It comes close to what I would hope to achieve with an Arduino. Things might change under greater load.

 
First z-axis test run. It won't compete with Hollywood,
but it's my first YouTube upload.

The not so good news...

First, there is something wrong with my nut mount. I think the clamp isn't tight enough so it moves a bit. This also shows up as a slight modulation in the actual sledge movement velocity. I will have to investigate this on the weekend. I believe the culprit is the depth of the groove in which the nut sits. If it's too deep, the clamp won't have enough bite.

Second, the threaded rod seems to have a mild "bend". I'm not sure if this is just the rod, or if it is a sign of mis-alignment of the nut and the two bearings. Again, the weekend will tell me more.

Third, there are several distinct levels of resonant frequencies. Probably speeds I will want to avoid once the system is in action. Note to self: Avoid resonant frequencies/speeds.

To close the post with some good news: The stepper and the entire construction feel quite sturdy. It basically doesn't notice me at all when I try to apply reasonable force against the movement (more force than I would apply using a power drill, anyhow). Very subjective, but encouraging.

Wednesday, 22 September 2010

Up and Down: z-axis pictures

Yesterday, pieces of MDF, some rods, and a bunch of bolts turned themselves into a working z-axis just by magic (and me, spending a day in the garage). Together with the Arduino, stepper motor, and driver, I am proud to say, my one-dimensional cnc router is almost complete...


Z-axis operational. The Logitech speaker at top right
is a marketing department requirement
and otherwise has no engineering benefit.

I only have little background in woodworking (some cupboards and shelves), so what completely scared me was to meet the required accuracy with drilling holes to keep things parallel/perpendicular. We're talking linear bearings, not cupboard doors. I guess I re-invented a few woodworking tricks on the way to make it happen. I quadruple-checked everything before cutting and drilling, so it took some time.

It is difficult to convey the feeling you get when, after drilling 24 holes, you insert the shafts into the linear bearings and the end-mounts, and things are actually parallel and work and slide nicely. Phew ! First time lucky.

Effectively, there was only one deviation from the engineering plan posted previously. The hose clamps I use to connect the motor axis to the threaded rod have a screw stick out and I had to widen the hole of the stepper motor mounting plate. I couldn't just cut all the way through, though, because I would get too close to the stepper mounting holes, so I had to cut a ring 5mm deep into the 16mm MDF around the already existing main hole. A new challenge, but it worked. I wish I had a router at that time. Addendum: I'll have to do the same for the small square plate on top so it can move up all the way towards the stepper motor.


Hose clamps are little trouble makers.

The stepper motor mounting holes (the ones on the motor, not the MDF) presented their own challenge: They are so close to the stepper motor body, it is all but impossible to hold nuts in a position that allows passing a bolt through them. In fact, I can't even put the bolt through from the stepper motor's side as the bold head is too large. This made for some interesting solution for the two bolts that hold the stepper in place at the bottom end as they are at the level of the base plate.

Using metal clips to hold down the ball bearings seems to work fine (so far). They resist a quite reasonable manual force to pry them out. We'll see what that means in the real world...


Feels sturdy. I could cut a groove below
the bearing should it become a problem.


The M12 nut is hidden below the square MDF board. It feels stable and it sits in a small groove in the MDF board, so it isn't moving at all. The biggest problem, though, is that the nut has a fair bit of free play along the threaded rod. This is probably the biggest disappointment at this stage. It might not matter too much for the z-axis, as the weight will hold things down, but for x and y this might not be great. My friend Caliper says it's about 0.65mm.

A tightened nut.

I also tried some "new and stylish" ways to use bolts. I know, old hat for you, but new to meee...

It's almost like IKEA. Except, this time all parts came in the box.

Finally, last night I had a little time to quickly setup my Arduino, stuck a temporary cooling "device" to the G251 and typed out a quick stepper routine. Gingerly I plugged in the power supply, brazing myself for any sorts of mechanical or (heaven forbid!) coding errors. But no, things worked, and the stepper pulled the sledge calmly (at half a rotation per second, ie, 1,000 micro-steps per second) along its intended path.


Arduino in (micro-)control.
The red wire on the breadboard is my
poor-man's E-Stop. I did hold onto it tightly for a while.



The latest in cooling technology. I believe Intel
will contact me shortly to negotiate rights.
(Yes, there is cooling paste. I'm a professional!)


Now it's time for some Arduino Stepper Motor Interrupt Service Routine programming to "push the speed envelope".

The need for speed - limited only by a 1.75mm pitch threaded rod...

Tuesday, 21 September 2010

Subscribe by Email

A blog-related update. Not everyone is comfortable with subscribing to blogs with RSS, so I looked around for a solution to turn RSS feeds into email notifications. A number of sites offer this service, but in my view, the probably most trustworthy is Google's FeedBurner.

You can now subscribe to this blog by email. Look at the sidebar and click the "subscribe" button. You will get a notification when new blog posts are made, but at most once a day, though I doubt I'll write this much - I'm not twittering ("6:23pm Mounting MDF board now -- 6:25pm Drilling a hole -- 6:26pm Dang. Wrong size!").

Obviously, you can unsubscribe at any time.

Enjoy...

Monday, 20 September 2010

Plan to Action

After extensive "planning", I'm starting to turn theory into reality. I've spent quite some time on playing around (ahem - I mean designing) with an engineering CAD software package to design the z-axis of the cnc router. It's fun, but also awfully slow when you are just only starting to learn the software. At some point, I spent around 3 hours to "design" a board with 6 holes in it. With pen and paper, that might have taken slighty less time. But then, it's all for fun and not for profit...

So, here the the overview engineering drawing of the z-axis I have begun to built this afternoon. I am choosing a design where z-axis stepper motor moves up and down with the drill, so, the drill will be mounted on the large back plate. The small square front plate will be the connection point to the y-axis.

z-axis design intent.
Like all good plans, this one may need changing too...


There are a number of concerns, but maybe with medication I might be able to sleep tonight...
  • I'm choosing a fairly long z-axis (shafts 400mm; movement 250mm) as I aim to do some artistic 3D-shape cutting and maybe plastic extrusion. However, this makes the z-axis more prone to bending/errors.
  • I lack experience with linear bearings, so I have no idea if a 150x150mm square arrangement of 4 bearings will be robust and stable.
  • My stepper motor to threaded rod - connection will be via a clamped plastic hose. The stepper will have quite a bit of force at such a small clamp-radius, so I don't know if that might tear up the hose.
  • The best I can cut by hand is to about 1mm. That will not be enough to keep precision linear bearings parallel and happy. I'm oversizing the screw-holes to have some adjustment capability. But will that be enough (or too much) ?
  • I haven't solved the problem of mounting the actual drill to the board yet.
  • I'll be mounting the ball-bearing near the stepper motor to the mdf board with a metal clip and a couple of screws. That bearing will carry the entire weight of the drill, stepper motor, cutting forces, etc. Not sure how that will fare.
  • The entire approach of using a threaded rod and a single nut (M12) to move the system is a bit scary. Will it get too hot and lock up, or will it have too much friction ? Well, it's experimental.

Hopefully I'll have some free time tomorrow to complete it and then we'll get some answers...

Sunday, 12 September 2010

y-z Sledge

I'm planning to mount the linear bearings for the y-axis and z-axis of the cnc router onto a single MDF board, z-axis bearings on the front, and y-axis bearings on the back. This will make the entire construction more compact, but will require a bit of experimenting.

Here's the plan...

y-z-sledge: The larger blocks are y-axis linear bearings
and the smaller blocks are z-axis linear bearings.

I'll try to compensate for my current lack of precision drilling (I wish I had a cnc router. Hmmm? ... Duh !) by oversizing the holes and using bolts so I can adjust and match position after drilling.

Saturday, 11 September 2010

Imperial Anger

I spent quite some time today getting angry while shopping for fasteners, nuts, washers, etc. It turns out that Australia isn't all that metric as she claims, despite metrication in the 1970s. Grrrrrr!

So, it turns out that our good friend and supposed-to-be market leader Bunnings (no link here due to simmering anger) sells all sorts of 8/32" 16/598" 12/17" 385/7791" and whatelsewhocares, but I couldn't find anything zinc & metric.


Metric Please !

After searching for some time, I was approached by a staff member who, upon me stating what I was looking for, gave me a rather long speech about his own disappointment with Bunnings as the policy is "if it's metric, it's galvanized" (and still no great selection). Arrrrr.

Well, at least he suggested a nearby shop that would carry zinc-metric bolts. That was 11 minutes before that shop's closing time. So, I made it just in time to Cost Less Bolts Pty Ltd and bought myself a small bagfull of fasteners etc. Their prices are substantially better than Bunnings', but you need to know what you want. There's no browsing and maybe a bit of impatience towards the undecided.

The good news: I now own an M12 1000mm threaded rod that I intend to cut down and use as the z-axis drive, and unlike the galvanized ones at Bunnings, this one is actually straight.

Back home, I started out building the z-axis, but - naturally -, realised that I needed different fasteners because the thread inside the linear bearings only goes half-way through (Arrrrr!). I paid a visit to Pen's hardware, but, unfortunately they didn't have the right lengths (Is M5 30mm so unusual??), so, progress will have to wait until Monday.

Wednesday, 1 September 2010

Arduino Interruptus

Here is my first shot at a basic two-channel interrupt-driven routine for an Arduino Mega. The example toggles LEDs on and off, but it can be used for stepper motors on a telescope etc. It's just a start. No acceleration yet. More soon...

Arduino - twice interrupted.


// Basic Arduino Two Channel Interrupt Routine
//
// Copyright (C) 2010 www.cncorbust.com
// This program is free software: you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at
// your option) any later version.
// This program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the 
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE.  See the GNU General Public
// License for more details.
// You should have received a copy of the GNU General Public
// License along with this program.  If not, see www.gnu.org.
//
//
// To make this work: Use an Arduino Mega
// connect pin 11  - Resistor (eg 1kOhm) - LED - GND (shows signal for timer 1)
// connect pin 5 - Resistor (eg 1kOhm) - LED - GND (shows signal for timer 3)
//

void setup()
{
  // We do this the long way to be very clear about what happens...
  
  // Define how many clock cycles you want to wait for a full on-off cycle
  // There are limits to these numbers: ticks/1024/4 must be smaller 65356 and greater 1
  
  unsigned long ticksForTimer1 = 16000000L * 1; // 1 second for one off-on-off cycle
  unsigned long ticksForTimer3 = 16000000L * 2.333;
  
  // we use the timer's 1/1024 pre-scaler for this example,
  // so adjust ticks by dividing by 1024
  ticksForTimer1 /= 1024;
  ticksForTimer3 /= 1024;
  
  // we set the timers to work "phase and frequency correct"
  // this means the timer counts up, then down, ie twice the distance
  // also, at TOP, the counter toggles the output.
  // This means it counts 4 times the range for a full off-on cycle.
  // Adjust ticks to take account for this...
  
  ticksForTimer1 /= 4;
  ticksForTimer3 /= 4;
  
  // Now, set up timer 1
  //
  // We basically set three things: Timer mode (wave form), pin output mode, and pre-scaler
  // For timer mode we set "9", which means:
  //       Timer counts up to TOP, then down to zero
  //       Timer takes its TOP value from OCR1A
  //       Setting OCR1A is double-buffered
  // For pin output mode we choose "01", which means the output pin toggles on a compare match.
  // For pre-scaler we choose to divide by 1024 to make things visible
  //  
  
  // TCCR1A is a timer control register with these bits:
  //   COM1A1   - 0  output mode
  //   COM1A0   - 1  output mode
  //   COM1B1   - 0  ignore
  //   COM1B0   - 0  ignore
  //   COM1C1   - 0  ignore
  //   COM1C0   - 0  ignore
  //   WGM11    - 0  timer mode
  //   WGM10    - 1  timer mode
  //
  // TCCR1B is a timer control register with these bits:
  //   ICNC1    - 0  ignore
  //   ICES1    - 0  ignore
  //   reserved - 0  ignore
  //   WGM13    - 1  timer mode
  //   WGM12    - 0  timer mode
  //   CS12     - 1  clock select (pre-scaler division)
  //   CS11     - 0  clock select (pre-scaler division)
  //   CS10     - 1  clock select (pre-scaler division)
  //   
  // TIMSK1 contains the interrupt enable register
  //
  
  cli(); // disable interrupts
  TCCR1A = B01000001;
  TCCR1B = B00010101;
  OCR1AH = ticksForTimer1 >> 8;       // set delay, high byte first
  OCR1AL = ticksForTimer1;
  TCNT1H = 0;  // reset timer to trigger update of TOP from OCR1A
  TCNT1L = 0;
  // Timer 1 writes to pin 11
  pinMode(11,OUTPUT);
  sei(); // enable interrupts

  // Needless to explain, the same goes for timer 3...

  cli(); // disable interrupts
  TCCR3A = B01000001;
  TCCR3B = B00010101;
  TIMSK3 = B00000010;
  OCR3AH = ticksForTimer3 >> 8;   // set delay, high byte first
  OCR3AL = ticksForTimer3;
  TCNT3H = 0;  // reset timer to trigger update of TOP from OCR3A
  TCNT3L = 0;
  // Timer 3 writes to pin 5  
  pinMode(5,OUTPUT);
  sei(); // enable interrupts
  
  // Unrelated to interrupts. Just for the demo...
  Serial.begin(9600);
}

// A counter to keep track of position 3
volatile long pos3;

// Use this to read pos3 so the interrupt handler does not give you garbage
long getPos3()
{
  cli(); // no interrupts
  long r = pos3;
  sei(); // allow interrupts
  return r;
}

ISR(TIMER3_COMPA_vect)
{
  pos3++; // update position (timer3 only)
}

// In the main loop, simply send changes in position to the serial port
long lastP = -1;
void loop()
{
  // indicate that we are active...
   digitalWrite(13,HIGH);
   long p = getPos3();
   if(p!=lastP)
   {
     Serial.println(p);
     lastP = p;
   }
}