Arduino chip as Stepper Controller

Less than 48 hours and I should have some chips in my hands...

Thinking of direct port output to speed up the output to the driver transistors. Something to study...

Okay - Now I've done it :D...

Got looking for Direct Port I/O and found a part of the Arduino documentation that doesn't seem to have a direct link.
Here's the link - Arduino Reference - Arduino Reference
It explains some of the problems and some of the uses. In a general Arduino project you might not want to use this approach as there are a few pitfalls that the Arduino IDE hides from you, and that is not always a bad thing. As i intend to use this in a dedicated chip it should cause me no problems.

Port B on the Arduino/ATMege328 is pins 8, 9, 10, 11, 12, 13, with the 2 remaining pins used by the crystal/resonator
DDRB is the Data Direction Register. Assigning 1 to a bit makes it an output, 0 makes it an input.
PORTB is the predefined variable that lets you read or write port B.
PORTB = B0001 turns on pin 8 and turns off pin 9, 10 & 11.

I also used pre-initialized arrays to set up the step patterns.

Down to 1232 bytes.

Here's the last version of the code -

/*
Step & Direction Stepper Driver
 Pins 8, 9, 10, 11 are tied to transistors 
 for each of the motor phases.
 Pins 2 & 3 are used as interrupts,
 Pin 2 as Step and 3 as Direction.
 */
// the following 3 arrays contain the bit patterns to drive the transistors to in turn drive each of the phases of the stepper
int patSimple[] = {B0011,B0010, B0100, B1000, B0001, B0010, B0100, B1000};
int patWave[] = {B0011, B0110, B1100, B1001, B0011, B0110, B1100, B1001};
int patHalf[] = {B0001, B0011, B0010, B0110, B0100, B1100, B1000, B1001};
int pinDir = 3;
volatile int ctr;
volatile int dir;
void setup()
{
  DDRB = B1111 ;  // this enables Port B Bits 0 - 3, Arduino I/O 8, 9, 10, 11 as outputs
  pinMode(pinDir, INPUT);
  ctr=0;
  dir = 0;
  attachInterrupt(0, Step, RISING);  // Depending on the application FALLING might be a better choice.
  attachInterrupt(1, Direction, CHANGE);
}

void loop()
{
// Nothing to see here...  
}

void Step()
{
  if (dir) 
  {
    ctr-- ;
  }
  else 
  {
    ctr++ ;
  }
  ctr = ctr & 7;
// 2 of the following 3 lines must be commented.
//  PORTB = patsimple[ctr];  // Simple Stepping
//  PORTB = patWave[ctr];    // Wave Stepping
     PORTB = patHalf[ctr];      // Half Stepping
}  

void Direction()
{
    dir = -digitalRead(pinDir);    // Direction is 0 (zero) or -1 (minus one)
}

Got my chips and parts on Monday and Tuesday night I put together a board for programming ATMega328 and ATTiny2313 chips. I didn't order any 120Ohm resistors so I had to wait til yesterday to test it out.

Used AVRDude to program the chips and had to add a section to the avrdude.conf file for the ATMega328 as it only had the ATMEga328P. I could have added the -F argument to the command string, but it was just as easy to add a section for the 328. Copied the whole 328P section and made 3 changes - in the 2 locations where 328P appeared I erased the P and I changed the System Descriptor bytes (avrdude reports what it finds if it doesn't agree) from 1E 95 0F to 1E 95 14 and everything worked. Took the chip out of the programming socket and swapped it for the chip in my Arduino board and loaded it with Blink.

I also put one of my ATTiny2313 chips in the and used the Arduino IDE to load the code to the 2313 and it seems to have loaded just fine. Have to hook up a circuit and test out the chip. Will probably be Sunday night before I get the chance as I have too much in my schedule between now and then.

I will post a picture of my programmer board and a schematic the first of the week.

Just so you know, you are on the right track. I drive a unipolar stepper using 1/16 step microstepping driving 4 TIP110s's from PORTB and C of my ATMEGA168 using 8-bit fast PWM. You can either store the sine wave values in an array or in a table/switch statement.

IMPORTANT: this works for me because 1. I use ~5kHz PWM, not the standard arduino 500Hz PWM and 2. I only need to rotate slowly.

To microstep using PWM you have to use a PWM frequency several times higher than the fastest microstepping rate you anticipate. This means that at the standard Arduino PWM rate of ~500Hz you will only be able to meaningfully microstep at about 100Hz--even that is only 5 PWM cycles per microstep--which at full step for a 1.8* motor is only 30RPM and for 1/8 microstepping (1600steps/rev) you will only be able to meaningfully microstep at 1/4RPM. Which might be fine if you are making a rotisserie; I only need 10RPM myself.

To be able to output small microsteps at high microstepping rates, you have to crank up the PWM frequency; this is basic sampling theory. There is no problem in principle; commercial microstepping drivers run their PWM at about 30kHz, but I assume we would run into trouble with our discrete TIPs and have to write very tight code (maybe assembly) to use PWM rates that high. I don't know, but I do know that my 5kHz is going fine.

Well it works. (almost didn't, hooked the chip up upside down and generated a little heat...) plugged the chip in and hooked up to my led drivers and it works.

Got a few things to take care of before I can play with it too much so ... The first of the week i should have a stepper driver working.

So - It is really rather easy to hook up an ATTiny2313 to an Arduino programmed as an ISP and load the code into the 2313. I did have to use version 0.22 of the Arduino Environment to have teh ArduinoISP work properly, but I stumbled across that little piece of info somewhere along the way.

Worked on the board for my first driver. Got it mostly soldered up and got to thinking that I would have to change the bleeder resistor as 56Ohms wold draw too much current from the 328, so I will have to pull them and get some resistors in the 120 to 220 range. Oh well, minor setback.

Got to playing with assembly language - took my code size from 1200 bytes to 86 bytes.

Out of necesity, Arduino adds a lot of overhead to interrupt routines. Saves all the registers and such, but it would take a lot more power to analyze the code and reduce that overhead. Writing in assembler means I had to do the optimizing and I am responsible for what can go wrong...

I am using AVRStudio 4 to write and test the code (has a simulator built in)(even has a disassembler that will load the hex files and list them )
I recommend AVR Studio 4 over 5 as 5 is just to S-L-O-W...

Here's what the code looks like in assembler.

.NoList
.INCLUDE "tn2313Adef.inc" ; Header for ATTINY2313A
.List
; ============================================
;   R E G I S T E R   D E F I N I T I O N S
; ============================================
.DEF Conf = R16 ; Multipurpose register
.DEF StepCt = R17
.DEF StepMask = R18
.DEF Direction = R19
.DEF PortBHold = R20
.DEF PortDHold = R21
.DEF PortDMask = R22
.DEF SeqBase = R23
; ============================================
;   R E S E T   A N D   I N T   V E C T O R S
; ============================================
.CSEG
.ORG $0000
	rjmp Main ; Reset vector
	rjmp Step ; Int vector 1
	rjmp Dir ; Int vector 2
	reti ; Int vector 3
	reti ; Int vector 4
	reti ; Int vector 5
	reti ; Int vector 6
	reti ; Int vector 7
; ============================================
;     I N T E R R U P T   S E R V I C E S
; ============================================
Step: add StepCt,Direction
      and StepCt,StepMask
      mov ZL,SeqBase
	  add ZL,StepCt
	  Lpm PortBHold,Z
	  out PORTB,PortBHold
	  reti
Dir:  ldi PortDMask,8
      in  PortDHold,PIND
      and PortDHold,PortDMask
      breq SinNeg
	  ldi Direction ,1
      rjmp DirEx
SinNeg:	  ldi Direction ,-1
DirEx:reti
; ============================================
;     M A I N    P R O G R A M    I N I T
; ============================================
Main:
; Init stack
	ldi Conf, LOW(RAMEND) ; Init LSB stack
	out SPL,Conf
; Init Port A
	ldi Conf,0 ; Direction Port A
	out DDRA,Conf
; Init Port B
	ldi Conf,(1<<DDB0)|(1<<DDB1)|(1<<DDB2)|(1<<DDB3) ; Direction Port B
	out DDRB,Conf
; [Add all other init routines here]
	ldi Conf,(1<<SE)|(0<<ISC00)|(1<<ISC01)|(1<<ISC10)|(0<<ISC11) ; enable sleep
	out MCUCR,Conf
	ldi StepCt,0
	ldi StepMask,7
	ldi Direction,1
	ldi SeqBase, StepSeq
	add SeqBase,SeqBase
 	sei
; ============================================
;         P R O G R A M    L O O P
; ============================================
Loop:
	rjmp loop ; go back to loop

StepSeq: .DB 1,3,2,6,4,12,8,9,0,0
; End of source code

Finally can say I have an ATMel chip - the ATtiny2313 functioning as a stepper driver.
I got it working in Arduino, the assembler version still has some strange behavious (and the assembler version of the code is hareder to troubleshoot in hardware. In the Simulator it works just fine, but in hardware.....

Here's the Arduino version.
By my count it will run in excess of 140RPM running in Half Step mode when driven by a simple Arduino program.

/*
Step & Direction Stepper Driver
 Pins 8, 9, 10, 11 are tied to transistors 
 for each of the motor phases.
 Pins 2 is used as interruptfor the steps
 and 3 is the Direction.
 */
// the following 3 arrays contain the bit patterns to drive the transistors
// to in turn drive each of the phases of the stepper
int patSimple[] = {
  B0011,B0010, B0100, B1000, B0001, B0010, B0100, B1000};
int patWave[]   = {
  B0011, B0110, B1100, B1001, B0011, B0110, B1100, B1001};
int patHalf[]   = {
  B0001, B0011, B0010, B0110, B0100, B1100, B1000, B1001};
int pinDir = 5;
volatile int ctr;
volatile int dir;

void setup()
{
  DDRB = B1111 ;  // this enables Port B Bits 0 - 3, Arduino I/O 8, 9, 10, 11 as outputs
  pinMode(pinDir, INPUT);
  ctr=0;
  dir = 0;
  attachInterrupt(0, Step, FALLING);  // Depending on the application FALLING might be a better choice.

}

void loop()
{
  // Nothing to see here...  
}

void Step()
{
  if (digitalRead(5)) 
  {
    ctr++ ;
  }
  else 
  {
    ctr-- ;
  }
  ctr = ctr & 7;
  // 2 of the following 3 lines must be commented.
  // PORTB = patsimple[ctr];  // Simple Stepping
  // PORTB = patWave[ctr];    // Wave Stepping
  PORTB = patHalf[ctr];      // Half Stepping
}

And I was testing it with code that looks like this in my Arduino-

int steps;

void setup() {                
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  pinMode(2, OUTPUT);     
  pinMode(3, OUTPUT);   
  digitalWrite(3,HIGH);  
}

void loop() {
  for (steps=0;steps<400;steps++)
  {
    digitalWrite(2, HIGH);   // set the LED on
    digitalWrite(2, LOW);    // set the LED off
    delay(1);              // wait for a second
  }
}

Still want to crack the assembly code...

Hi, I am experimenting with the code you posted ( Reply #15 on: January 15, 2012, 05:19:57 PM ) with 4 IRF530's, an Arduino Uno, a small unipolar stepper and solder-less board and It works really well with EMC2 linux. My goal is to drive a homebrew CNC router table with the above components. I have very little (NO) experience with port manipulation and am somewhat apprehensive about adding another axis to the code, as I recall reading that I could bugger up the TX RX ports? does the UNO have enough ports available to control 3 axes? I would still be happy with 2! In any case, great work on this elegant code so far!

I am using the ATtiny2313 for this application. It has a few extra pins, It turns out I need 1 interrupt pin, 1 input and 4 outputs. I would also like to add 1 more input as an enable and possibly a Ready output. The problem with trying to run more than one stepper in this application is the Interrupt inputs. Because of priority of interrupts the second interrupt would get missed if it occurred at the time as the first.

Look into some ATtiny2313 chips and use the Arduino as the programmer. That circuit is abut as inexpensive and simple as an Arduino circuit can be. If yuwant more info to use that approach I can point you to the few pieces you need to put together to do that.

That would be great. I was wondering about the ability to program with the arduino and swap the IC out to a PCB, so that sounds like it could work for me. Any resources you can point me to would be greatly appreciated.

To program a chip I use Arduino-0.22 to load the ArduinoISP sketch on my Arduino Uno board. I made a small board that has 2 sockets, a 20 pin that could be used to program 20 and 8 pin chips and a 28 pin socket that I use to program 328's (like the Uno). Search for Arduino as ISP as there is a lot of info out there that exlains the process and the wiring. My little board has 6 wires between the boards to program the chip and a 120 Ohm resistor between the Reset and +5 on the Arduino Uno. To program an Arduino sketch using the ArduinoISP there is a selection on the ArduinoIDE File menu to write the sketch usin Arduino as ISP.

I use the Arduino-0.22 IDE as there was a change in something in Arduino-1.0 and the ArduinoISP only works at 9600 baud instead of 19200 which causes a problem.

Here's a picture (poor) of my ISP board to use with the Uno.

I got it working -

Figured out what was lacking in my assembly code and now it works.

.NoList
.INCLUDE "tn2313Adef.inc" ; Header for ATTINY2313A
.List
; ============================================
;   R E G I S T E R   D E F I N I T I O N S
; ============================================
.DEF Conf = R16 ; Multipurpose register
.DEF StepCt = R17
.DEF StepMask = R18
.DEF Direction = R19
.DEF PortBHold = R20
.DEF PortDHold = R21
.DEF PortDMask8 = R22
.DEF SeqBase = R24
; ============================================
;       S R A M   D E F I N I T I O N S
; ============================================
.DSEG
.ORG  0X0060
; ============================================
;   R E S E T   A N D   I N T   V E C T O R S
; ============================================
.CSEG
.ORG $0000
	rjmp Main ; Reset vector
	rjmp Step ; Int vector 1
	reti ; Int vector 2
	reti ; Int vector 3
	reti ; Int vector 4
	reti ; Int vector 5
	reti ; Int vector 6
	reti ; Int vector 7
; ============================================
;     I N T E R R U P T   S E R V I C E S
; ============================================
Step:   in  PortDHold,PIND
; look at only PD#, the Direction input
        and PortDHold,PortDMask8
; if PD3 High, Add Direct to StepCt, if PD3 Low Subtract Direction
        breq SinNeg
        add StepCt,Direction
        rjmp Next
SinNeg: sub StepCt,Direction
; limit StepCt to range from 0 to 7 (-1 becomes 7)
Next:   and StepCt,StepMask
; use Z as a pointer to the StepSequence table
        mov ZL,SeqBase
; StepCt is teh offset into the table
	add ZL,StepCt
; transfer the next bit pattern from ProgMem to PortBHold, R20 
	Lpm PortBHold,Z
	out PortB,PortBHold
	reti
; ============================================
;     M A I N    P R O G R A M    I N I T
; ============================================
Main:
; Init stack
	ldi Conf, LOW(RAMEND) ; Init LSB stack
	out SPL,Conf
; Init Port A
	ldi Conf,0 ; Direction Port A
	out DDRA,Conf
; Init Port B
	ldi Conf,(1<<DDB0)|(1<<DDB1)|(1<<DDB2)|(1<<DDB3) ; Direction Port B
	out DDRB,Conf
; Set ISC00 to 0 and ISC01 to 1, Falling Edge INT0 Interrupt
	ldi Conf,(0<<ISC00)|(1<<ISC01) 
	out MCUCR,Conf
; Set INTO , Bit 6 of GIMSK to Enable INT0
	ldi Conf,(1<<int0)
	out GIMSK, Conf
; Initialize StepCt, R17
	ldi StepCt,0
; Initailize StepMask, R18
	ldi StepMask,7
;Initialize Direction, R19
	ldi Direction,1
; Initialize SeqBase to point to 8 bit address of StepSeq
	ldi SeqBase, StepSeq
	add SeqBase,SeqBase
; Initialize PortDMask8, R22, to mask only PD3 of port D
        ldi PortDMask8,8
        ldi ZH,0    
 	sei
; ============================================
;         P R O G R A M    L O O P
; ============================================
Loop:  rjmp loop ; go back to loop
; bit patterns for output to the Stepper Drive Bits.
StepSeq: .DB 1,3,2,6,4,12,8,9,0,0
; End of source code

This was one of the best reads on Arduino stepper logic thata I have seen. You are to be commended and I thank you very much.

It's a pity that it seemed to drop so abruptly,

Do you have any final specs on the speed of your assembly code version?

Got into a few other projects so I have not paid much attention to it for a while. I was able to get hold of a Hardinge UM milling machine and that took some time to get cleaned and moved into my shop. I have a Harbor Freight Mini Mill that I want to convert to CNC along with a 10:1 gearbox that I want to use as an indexer on that mill. Living down here in South Texas my year is backwards from folks that live farther north. Here it get too hot to work in an un-airconditioned garage so most of my projects out there take place from September to May whereas when I lived up in Indiana the projects took place from April to October when it was warm enough to work out in the shop.

I have mounted 2 2313 on a perf board along with the 8 MOSFETs and actually had it running - ~140 rpm using my Arduino as the step generator. As that was written in the Arduino IDE I suspect that I can get quite a bit more speed using MACH3 or GRBL.

What's the timing for 12 instructions and an interrupt on an ATtiny running at 8Mhz? According to how I interpret the info from AVRStudio the interrupt shold process in ~2.25mSec.

gharryh:
Now i am confused. Do you use the dedicated chip to drive the stepper or the Arduino.
If you use the PC to send Direction and Step commands what does the Arduino do?

yeah very interesting...

I used the Arduino to test my idea and then migrated the code over to the ATtiny2313. A stepper driver takes the problem of controlling the motor away from your main processor - be it an Arduino or a PC running Mach3. The chip is programmed so it has 2 inputs - Step & Direction, and 4 outputs - Pahses 1 - 4. Those outputs drive 4 MOSFETs which are then connected to the 4 phases of the stepper. Very similar to some other stepper controllers that are available - the PIC Stepper is one that I chose to emulate. Instead of the PICStepper I have the AVRStepper...

And by using a stepper driver my Arduino can control 4 steppers instead of just 2. Or I can hook those steppers to my PC and control them using Mach3, or to some other device.

4 is impressive! With my code I can control 3 at once.

Distributed processing. And by using the 2313's for controllers they can be placed away from the main arduino meaning things can be mounted more conveniently.

Sort of a bump to the thread.

I have some perforated circuit board material coming - Vectorboard #169P44WEC1 which is copper clad on 1 side with .042 holes spaced every 0.1". Saves having to drill my sample boards. I have generated a board layout using AutoCAD and will be spending the next couple days drawing the resist pattern on the board and then etching it with Radio Shack Ferric Chloride etchant. Have spent that last couple weeks going over the design and that helped me spot an error in the schematic. I sort of had a schematic, but this was a project where the schematic followed the breadboard build and I found out I had drawn it with the signals to the wrong pins... Oh well, slide them over and the schematic matches the working parts...

Salvaged some terminals and now I just have to get the materials in hand to try it out.