Saturday, October 28, 2017

Library for A4988 stepper motor driver using timer interrupt

Update 4, October, 2023: I have made a new library for controlling stepper motor drivers that supports multiple motors and has a better acceleration algorithm. It can also coordinate xyz steppers.

This library is designed for AVR ATmega328 microcontroller, but with few adjustments can work with any AVR microcontroller with at least 3-4 Kb of flash program memory.
At the moment only one motor is supported.

Features

  • the interrupt can be triggered by Timer0 or Timer1
  • automatic microstepping mode selection
  • can work with constant and very low speeds
  • accelerated speed mode, with separate acceleration and deceleration settings
  • can work with only one pin of the microcontroller if the rest are hardwired

Nema 17 Bipolar Stepper Motor

Characteristics:


Features
Program Memory
Data Memory (SRAM)
All enabled
3552 bytes
40 bytes
Acceleration, no microstepping
3088 bytes
40 bytes
Microstepping, no acceleration
2104 bytes
14 bytes
No microstepping, no acceleration
1724 bytes
12 bytes

Compiled using avr-gcc (WinAVR 20100110) 4.3.3 and -Os optimization level and MATH_LIB = -lm.
If MATH_LIB = -lm is commented out in Make file, the memory with all functions enabled is 6520 bytes program and 304 bytes data memory.So allways uncomment MATH_LIB = -lm.





You can find more about the A4988 module here How to use A4988 stepper motor driver module tutorial.


How to use the A4988-stepperDriver library

The stepper motor library uses a timer which triggers an interrupt every 10us. If the delay between steps is more than 10us the ISR will take only a few nano seconds so the flow of the user's program will not be affected.
Based on the motor speed the microstepping will be selected automatically - the lower the speed the higher the microstepping. That way the motor will be more silent and won't vibrate. Also the movement will be more fluid.

How to use the code 

  • Setting some things up

First open the header file and setup a few things.
I/O
In the section SETUP FOR USER modify the DDR, PORT and PIN defines for each output pin you want to use, according to your setup. Excluding the STEP pin all the rest are optional.

/*************************************************************
 SETUP FOR USER
**************************************************************/
///////////////////    DIGITAL CONTROL PINS SETUP
/* Step Pin */
#define STEP_DDR    DDRD
#define STEP_PORT    PORTD
#define STEP_PIN    PD0

/* Direction Pin (Optional) */
#define USE_DIRECTION_PIN          1
#define DIR_DDR     DDRD
#define DIR_PORT    PORTD
#define DIR_PIN     PD1

/* Enable Pin (Optional) */
#define USE_ENABLE_PIN           1
#define ENABLE_DDR    DDRD
#define ENABLE_PORT    PORTD
#define ENABLE_PIN    PD6

/* Sleep Pin (Optional) */
#define USE_SLEEP_PIN           1
#define SLEEP_DDR    DDRB
#define SLEEP_PORT    PORTB
#define SLEEP_PIN    PB2

/* Reset Pin (Optional) */
#define USE_RESET_PIN           1
#define RESET_DDR    DDRC
#define RESET_PORT    PORTC
#define RESET_PIN    PC5


STEPPER_DEGREES - stepper motor type. How many degrees the motor turns in one full step. Usually 1.8 for 200 steps per rotation or 0.9 for 400 steps per rotation.


USE_TIMER0 and USE_TIMER1 - put 1 to the one you want to use and 0 to the rest.

If you use Arduino to compile this code, choose Timer1 like so: USE_TIMER1  1 because Arduino is using Timer0 for millis and other functions and could interfere with this code.


USE_MICROSTEPPING - if you don't need microstepping set this to 0. Default 1.
MICROSTEPPING_DIVIDE_TIME - if total duration of spinning doesn't matter, set this to 0. This will allow using smaller delays between steps at a higher microstepping resolution. Default 1.

USE_stepperRotateToDegree - the function is descibed bellow. Use 0 if not needed.
IMPROVE_TIME_PRECISION -  sometimes the delay between steps (the motor speed) will result in a fraction. If this is set to 1 it will be corrected. If you only care about the number of steps and not the time taken, set this to 0 to save space.
MICROSTEPPING TIME THRESHOLD - since the code selects the step resolution based on the delay between steps you can tweak the threshold values in microseconds.
- FULL_STEP_THRESHOLD     200 - speeds under 200 microseconds will use full step
- HALF_STEP_THRESHOLD    500 - under 500 us 1/2 step will be used
- QUARTER_STEP_THRESHOLD   800
- EIGHTH_STEP_THRESHOLD       10000
- SIXTEENTH_STEP_THRESHOLD 1000000
USE_ACCELERATION - linear acceleration. If you don't need acceleration, deceleration set this to 0 to save space and ISR time execution.
ACCELERATION and DECELERATION -  in percents from 0 to 100. Both must add up to 100. Lets say you want for the motor to make 1000 steps. By setting these to constants to 20, the first 200 steps will be used to accelerate to the desired speed. Same for the deceleration. Useful when you have a heavy load or you want to reach a certain high speed. The motor is like a car - you can't accelerate from 0 to 120 Km/h in an instant nor decelerate that fast without problems.
ACCELERATION_STARTING_SPEED - in microseconds. The greater the value the slower the starting speed when the acceleration is used.
PERPETUAL_MOTION - if set to 1 the motor will spin forever or until the function A4988_stepperStop is called, or until the battery runs out, or... i'll stop now.

  • The functions

void A4988_stepperSetup(void); 
Run this before any other functions. This will set up the output pins and the timer, but will not start the timer.

void A4988_stepMilliseconds(uint16_t nr_of_steps, uint32_t delay_between_steps_ms);
Make a desired number of steps.
nr_of_steps - how many steps. Maximum value is 65535
delay_between_steps_ms - the delay between each step in miliseconds. This will dictate the speed of rotation 

void A4988_stepMicroseconds(uint16_t nr_of_steps, uint16_t delay_between_steps_us);
Same as the function above but the delay is in microseconds.
For both function the delay will be divided by 2 because each step needs a HIGH and a LOW pulse.

void A4988_stepperDirection(uint8_t direction);
Direction of rotation. The argument can be TURN_RIGHT or TURN_LEFT.

void A4988_isBusy(void);
Since every function that spins the motor will start a timer and trigger the ISR, the next function will be run by the CPU until the ISR triggers and that will mess things up. So after each function that spins the motor, use this function. The function uses a while loop, so use this only if the order of rotations matter.

uint8_t A4988_isNotBusy(void);
To execute other code while the rotation takes place, use this function. It will return 1 if the rotation is complete or 0 if not. See the example bellow on how to use it.




Other functions you might need:

void A4988_stepperRotateNTimes(uint16_t nr_of_rotations, uint16_t total_duration_ms);
Rotate the motor a certain times - full rotation not just a step.
nr_of_rotations - how many rotations to make
total_duration_ms - the total duration time in which the rotations will be made

void A4988_stepperRotateToDegree(float degree, uint16_t delay_between_steps_us);
Rotate the motor to a certain degree. See the video for more details. I don't know where this can be used but i made it just for fun.

void A4988_stepperStop(void); 
When PERPETUAL_MOTION is activated use this function to stop the motor.

void A4988_stepperIncreaseSpeed(uint8_t speed);
void A4988_stepperDecreaseSpeed(uint8_t speed);
 

When PERPETUAL_MOTION is activated use this functions to increase speed or decrease speed. The value can be 1 or greater. One value represent 10us because this is the resolution at which the ISR is triggered. You can have two buttons and on every button press you can execute one of the functions passing 1 as a argument and the speed will increase or decrease with 10us.

Enable function
Enable or disable the motor output. See the pins description above. 
A4988_stepperEnable()
A4988_stepperDisable()

Sleep mode
Enter in sleep mode or exit the sleep mode. Saves more power than disable function.A4988_stepperSleep()
A4988_stepperWakeUp()


  • Example

/*************************************************************
 INCLUDES
**************************************************************/
#include <avr/io.h>
#include "A4988-stepperDriver.h"
#include <util/delay.h>



/*************************************************************
 SETUP
**************************************************************/



/*************************************************************
 MAIN FUNCTION
**************************************************************/
int main(void){
 // A4988 setup
 A4988_stepperSetup();
 
 // Turn left
 A4988_stepperDirection(TURN_LEFT);
 
 // Make 100 steps with 800 microseconds delay between each step
 A4988_stepMicroseconds(100, 800);
 
 // Wait until the rotation ends
 A4988_isBusy();
 
 // Add a small delay when changing the direction of rotation to prevent
 // mechanical shock. The value can be changed depending on the speed
 _delay_ms(100);
 
 // A delay just to see when a rotation stops and the other begins
 _delay_ms(2000);
 
 // Turn right
 A4988_stepperDirection(TURN_RIGHT);  
 
 // Make 50 steps with 800 microseconds delay between each step
 A4988_stepMicroseconds(50, 800);
 
 // Wait until the rotation ends
 A4988_isBusy();
 
 // A delay just to see when a rotation stops and the other begins
 _delay_ms(2000);
 
 while(1){
  // If the rotation is complete (not busy) the function will return 1,
  // else will return 0 and the code inside can run again
  if(A4988_isNotBusy()){
   // Clock mode. Make a full rotation in 60,000 milliseconds = 1 minute
   A4988_stepperRotateNTimes(1, 60000);
  }
  
  // Do other stuff
 }
}

 

Tips:

- If MATH_LIB = -lm is commented out in Make file, the memory with all functions enabled is 6520 bytes program and 304 bytes data memory, compared to 3552 bytes and 40 bytes data memory. So always uncomment MATH_LIB = -lm.

- Disconnecting the stepper motor while power is on, may lead to the A4988 board destruction.

- If you hear squeaky high pitched noises coming from the stepper motor, first check if the motor voltage is not to low in case of using a battery.

- If the motor was working on a certain high speed and now works only on lower speeds, again check the motor voltage if is not low.


Download

A4988-stepperDriver v1.0

21 comments:

  1. Hi, is it possible to use this library to run more than one motor independently?

    ReplyDelete
    Replies
    1. Unfortunately not. When i have the time, i will make a new one that does that.

      Delete
  2. Give me full code, please.

    ReplyDelete
  3. You can download the code from the download section at the bottom.

    ReplyDelete
  4. Seems to be great solution for multitasking with stepper motor. What to change to use with Arduino Mega 2560? How should I make setup if I have DIR_PIN 4, STEP_PIN 3 and MS1+MS2+MS3 connected to Pin 2 (for 1/16 step). What to put here:
    #define STEP_DDR DDRD
    #define STEP_PORT PORTD
    #define STEP_PIN PD0
    #define USE_DIRECTION_PIN 1
    #define DIR_DDR DDRD
    #define DIR_PORT PORTD
    #define DIR_PIN PD1
    I'm working on developing processor for photograpy. It must turn the tank, check temperature, turn on/off heater, use display, button. It does not work with normal multitasking sketch. It looks like this: https://drive.google.com/file/d/1Bz9r5vWO6RxxMcCDi0NXGn5CBByLq4OR/view

    ReplyDelete
    Replies
    1. Cool project. According to pinout diagram that I found on this site http://www.circuitstoday.com/wp-content/uploads/2018/02/Arduino-Mega-Pin-Configuration.jpg pins 2 and 3 are on port E (pins 4 and 5) and pin 4 is on port G (pin 5). So the setup should look like this:

      // PIN 3 on arduino is on port E pin 5 on ATmega 2560
      #define STEP_DDR DDRE
      #define STEP_PORT PORTE
      #define STEP_PIN PD5

      // This is 1 or 0 (true or false)
      #define USE_DIRECTION_PIN 1

      // PIN 4 on arduino is on port G pin 5 on ATmega 2560
      #define DIR_DDR DDRG
      #define DIR_PORT PORTG
      #define DIR_PIN PD5

      Delete
  5. Thank you, great!!! It almost work. There is probably something to change but I'm beginner, I don't know what to check. It works like this now your setup and your standard main program:
    https://drive.google.com/open?id=1bTyVqYg8nxIjs28_2LxHtsCpVzqNV4f5

    ReplyDelete
    Replies
    1. You can be an expert and still don't know how other people's code works, because even I can't remember how my code works. You need to try a few things and hopefully it will work.

      - make sure the CPU frequency is set correctly
      - it could be that the ISR fires to often. Try setting this like so:
      #define USE_ACCELERATION 0 // set this to 0
      #define IMPROVE_TIME_PRECISION 0 // set this to 0
      #define MICROSTEPPING_DIVIDE_TIME 0 // set this to 0
      - make sure MS1+MS2+MS3 are indeed high
      - after all this, try different delays between steps in this function A4988_stepMicroseconds(100, 800). Instead of 800 use 1100 and see if is better or worse. If it is not better try 700 or 500 and see the difference.
      Let me know if any of these solved the problem.

      Delete
  6. It is much better now. Should it work like this? https://drive.google.com/open?id=1oNe1bLQZPZjwvjKccFqfvmB5SDQZFEDG
    But it does not react for the delay, I can put 800, 80 or 8000 it always turns with the same speed. I think the second move is faster it has different ton. And in second move it seems there is the ramp despite it is set to 0?
    I don't know how to check the CPU frequency but I never touch this in this board. I put the changes like you wrote and set the MS pins to HIGH in normal way (the 3 MS pins are connected to pin 2):
    #define MS_PIN 2
    void setup() {
    pinMode(MS_PIN, OUTPUT);
    digitalWrite(MS_PIN,HIGH);
    }

    ReplyDelete
    Replies
    1. The first turn looked okay but the tone was kinda low.

      I don't know how the CPU frequency is set in Arduino because I don't use Arduino. Check if it's 16MHz.

      Can I see the full code?

      Delete
  7. I did something wrong, probably changed the values in wrong places. Now I changed it in #define lines ant the stepper does not move at all

    ReplyDelete
    Replies
    1. In the code you've posted the pins are set to the default values. They should be changed like before like so

      // PIN 3 on arduino is on port E pin 5 on ATmega 2560
      #define STEP_DDR DDRE
      #define STEP_PORT PORTE
      #define STEP_PIN PD5

      // This is 1 or 0 (true or false)
      #define USE_DIRECTION_PIN 1

      // PIN 4 on arduino is on port G pin 5 on ATmega 2560
      #define DIR_DDR DDRG
      #define DIR_PORT PORTG
      #define DIR_PIN PD5

      Delete
    2. Also the while loop should look like this:

      while(1){
      // Turn left
      A4988_stepperDirection(TURN_LEFT);

      // Make 100 steps with 800 microseconds delay between each step
      A4988_stepMicroseconds(100, 800);

      // Wait until the rotation ends
      A4988_isBusy();

      // Add a small delay when changing the direction of rotation to prevent
      // mechanical shock. The value can be changed depending on the speed
      _delay_ms(100);

      // A delay just to see when a rotation stops and the other begins
      _delay_ms(2000);

      // Turn right
      A4988_stepperDirection(TURN_RIGHT);

      // Make 50 steps with 800 microseconds delay between each step
      A4988_stepMicroseconds(50, 800);

      // Wait until the rotation ends
      A4988_isBusy();

      // A delay just to see when a rotation stops and the other begins
      _delay_ms(2000);
      }

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
  9. https://pastebin.com/2yrpHCW7
    This is the full code. When I corrected errors the stepper runs the same way without changes like here
    https://drive.google.com/file/d/1bTyVqYg8nxIjs28_2LxHtsCpVzqNV4f5/view

    ReplyDelete
    Replies
    1. In the function A4988_stepMicroseconds(100, 800) 100 are the number of steps to make and the 800 is how many microseconds to wait between each step. This dictates the speed of rotation. The function sets the microstepping by setting the pins that controlls the MS1, MS2 and MS3. I believe the problem is that you use only one pin for microstepping.
      First remove this function from the while loop A4988_stepperRotateNTimes because is not needed.
      Then try and increase the number of steps to make like so:
      A4988_stepMicroseconds(10000, 800)
      I would also try to alocate two more pins so the code can select the step division

      Delete
    2. Hi, I checked CPU speed, it is 16MHz. I connected MS1 MS2 and MS3 separately and set it to HIGH. The code is here: https://pastebin.com/4LBzuZ8Y. I found some parameters when ity started to work, but not perfect.
      It is like this with
      #define USE_ACCELERATION may be 0 or 1
      #define IMPROVE_TIME_PRECISION must be 0
      #define MICROSTEPPING_DIVIDE_TIME may be 0 or 1
      Here is how it works
      https://drive.google.com/open?id=1SR6ahclA184VBvjVkl3okMWUCoDzGbkW
      Changing ACCELERATION/DECELERATION impacts maximum speed at the same delay in A4988_stepMicroseconds(). It looks like the stepper goes forward and back for the while when changes direction. Most strange is that changing MS1...MS3 pins HIGH to LOW and combinations does not change anything. So I guess it is not set properly yet. The motor is noisy, shouldn't it be more quiet? I couldn't make short ramp because decreasing ACCELERATION to 10 or 5 caused decreasing the maximum speed regardless of microseconds delay and big noise.
      BTW I found here nice algorithm for ramps, that I used in my first version: https://www.youtube.com/watch?v=fHAO7SW-SZI

      Delete
    3. Yes, I know about the algorithms, but I tried to avoid square root and too many divisions because they take many CPU cycles. But do you really need acceleration since the mass to be moved is not great?

      The motor behavior is strange but I admit is a great tune musically wise. The code looks fine. Maybe is because of the Arduino platform and it compiles differently. When I tested the code it worked very well.

      You don't need to set the MS pins manually. The function is setting the microstepping ratio depending on the speed. At the time I thought if the speed needs to be higher and precision is not required the MS should be 1, then 1/2, 1/4 and so on when depending on the speed. But from what i recall I thought the user should choose the microstepping ratio also by setting USE_MICROSTEPPING to 0 but I see you already did that.

      Try again with the optional functions disabled because that could activate other pins. So make all these to 0:

      #define USE_RESET_PIN 0
      #define USE_SLEEP_PIN 0
      #define USE_ENABLE_PIN 0

      Delete
    4. I tried this but the move is very similar not smooth and loud. I will do it another way. I am going to use another Arduino nano just to run the stepper and the Mega will do rest of work. Not elegant but cheap and easy. Thank you for your help. Now I'm going to make PID control of the heater :-)

      Delete
    5. Sorry it didn't work.
      I've never programmed a PID controller but I did some research on it because I wanted to build a solder reflow oven but I didn't had the time to do it.

      Delete
  10. This comment has been removed by the author.

    ReplyDelete