Author Topic: non-blocking Arduino stepper motor example  (Read 12997 times)

0 Members and 1 Guest are viewing this topic.

Offline FrankBussTopic starter

  • Supporter
  • ****
  • Posts: 2365
  • Country: de
    • Frank Buss
non-blocking Arduino stepper motor example
« on: March 13, 2017, 08:05:54 pm »
First time I used a stepper motor:



Unlike the Arduino stepper library, moving the stepper motor doesn't block your loop. This is useful if you want to control more than one motor at the same time. And the high interrupt rate of 10 kHz, in combination with the accumulator, allows a high resolution speed control.

Parts used:

- Pololu Stepper Motor NEMA 14 Bipolar 200 Steps/Rev 35×28mm 10V 0.5 A/Phase
- Pololu A4988 StepStick Stepper Motortreiber Carrier 1182
- Arduino Mega 2560
- 10k potentiometer
- breadboard and cables
- 10 V power supply for the motor voltage

Arduino sketch:

Code: [Select]
// pin numbers
const int analogPin = A0;
const int enablePin = 40;
const int ms1Pin = 41;
const int ms2Pin = 42;
const int ms3Pin = 43;
const int resetPin = 44;
const int sleepPin = 45;
const int stepPin = 46;
const int dirPin = 47;

// interrupt frequency
const float samplerate = 10000.0f;

void setup()
{
  // init pins
  pinMode(enablePin, OUTPUT);
  pinMode(ms1Pin, OUTPUT);
  pinMode(ms2Pin, OUTPUT);
  pinMode(ms3Pin, OUTPUT);
  pinMode(resetPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);

  digitalWrite(enablePin, LOW);
  digitalWrite(ms1Pin, LOW);
  digitalWrite(ms2Pin, LOW);
  digitalWrite(ms3Pin, LOW);
  digitalWrite(resetPin, LOW);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(stepPin, HIGH);
  digitalWrite(dirPin, HIGH);

  delay(100);
  digitalWrite(resetPin, HIGH);
 

  // initialize timer1
  noInterrupts(); // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 16000000.0f / samplerate; // compare match register for IRQ with selected samplerate
  TCCR1B |= (1 << WGM12); // CTC mode
  TCCR1B |= (1 << CS10); // no prescaler
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  interrupts(); // enable all interrupts
}

uint32_t wait = 0;
int spd = 0;
int clk = 0;

// timer 1 interrupt
ISR(TIMER1_COMPA_vect)
{
  // generate the next clock pulse on accumulator overflow
  wait += spd;
  if (wait > 0xffff) {
    if (clk) {
      digitalWrite(stepPin, HIGH);
    } else {
      digitalWrite(stepPin, LOW);
    }
    wait -= 0x10000;
    clk = !clk;
  }
}

void loop()
{
  // read potentiometer and set direction
  int poti = analogRead(analogPin);
  if (poti < 512) {
      digitalWrite(dirPin, LOW);
      poti = 512 - poti;
  } else {
      digitalWrite(dirPin, HIGH);
      poti -= 512;
  }

  // update the speed, which increments the accumulator in the interrupt
  noInterrupts();
  spd = poti * 10;
  interrupts();
}
So Long, and Thanks for All the Fish
Electronics, hiking, retro-computing, electronic music etc.: https://www.youtube.com/c/FrankBussProgrammer
 
The following users thanked this post: arduinew

Offline Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11648
  • Country: my
  • reassessing directives...
Re: non-blocking Arduino stepper motor example
« Reply #1 on: March 14, 2017, 02:35:29 am »
A4988 stepper driver has its own mcu to advance the motor and check for spikes etc. you may send pulses from arduino and then forget about it.
Nature: Evolution and the Illusion of Randomness (Stephen L. Talbott): Its now indisputable that... organisms “expertise” contextualizes its genome, and its nonsense to say that these powers are under the control of the genome being contextualized - Barbara McClintock
 

Offline FrankBussTopic starter

  • Supporter
  • ****
  • Posts: 2365
  • Country: de
    • Frank Buss
Re: non-blocking Arduino stepper motor example
« Reply #2 on: March 14, 2017, 09:18:12 am »
A4988 stepper driver has its own mcu to advance the motor and check for spikes etc. you may send pulses from arduino and then forget about it.

Yes, this is what I'm doing, it is simpler than controlling the coils individually with something like an ULN2003. But my idea can be used for both (just power the right transistors with a state machine in a sequence on each accumulator overflow in the interrupt, if you are using a ULN2003) and is better than other methods, if you don't want to block the code flow when moving a motor.
So Long, and Thanks for All the Fish
Electronics, hiking, retro-computing, electronic music etc.: https://www.youtube.com/c/FrankBussProgrammer
 

Offline obiwanjacobi

  • Frequent Contributor
  • **
  • Posts: 988
  • Country: nl
  • What's this yippee-yayoh pin you talk about!?
    • Marctronix Blog
Re: non-blocking Arduino stepper motor example
« Reply #3 on: March 14, 2017, 11:52:28 am »
digitalWrite is very slow (due to a pin conversion lookup) and makes your ISR take longer than needed.
I'm sure it's not a problem in this prototype but as the project gets more crowded time pressure increases.

I think there are functions to perform the lookup once and then use the resulting MCU pin in subsequent write calls - I remember seeing that in code somewhere. I don't have the details at hand unfortunately.

Just something to keep in mind for a real project.
[2c]
Arduino Template Library | Zalt Z80 Computer
Wrong code should not compile!
 

Offline FrankBussTopic starter

  • Supporter
  • ****
  • Posts: 2365
  • Country: de
    • Frank Buss
Re: non-blocking Arduino stepper motor example
« Reply #4 on: March 14, 2017, 12:12:25 pm »
Right, you can use the port registers: https://www.arduino.cc/en/Reference/PortManipulation
So Long, and Thanks for All the Fish
Electronics, hiking, retro-computing, electronic music etc.: https://www.youtube.com/c/FrankBussProgrammer
 

Offline arduinew

  • Newbie
  • Posts: 6
  • Country: au
Re: non-blocking Arduino stepper motor example
« Reply #5 on: April 07, 2020, 07:10:12 am »
First time I used a stepper motor:



Unlike the Arduino stepper library, moving the stepper motor doesn't block your loop. This is useful if you want to control more than one motor at the same time. And the high interrupt rate of 10 kHz, in combination with the accumulator, allows a high resolution speed control.

Parts used:

- Pololu Stepper Motor NEMA 14 Bipolar 200 Steps/Rev 35×28mm 10V 0.5 A/Phase
- Pololu A4988 StepStick Stepper Motortreiber Carrier 1182
- Arduino Mega 2560
- 10k potentiometer
- breadboard and cables
- 10 V power supply for the motor voltage

Arduino sketch:

// https://www.eevblog.com/forum/microcontrollers/non-blocking-arduino-stepper-motor-example/
//https://youtu.be/Zr-NGNhVVcw
.....................................................................


My fiddling:
// pin numbers
const int analogPin = A0;
const int enablePin = 3; // was 40
const int ms1Pin = 4; //was 41
const int ms2Pin = 5;  //was 42
const int ms3Pin = 6; //was 43
const int resetPin = 7; //was 44
const int sleepPin = 8; //was 45
const int stepPin = 9; //was 46
const int dirPin = 10; // was 47

// interrupt frequency
const float samplerate = 200000.0f; /////////////////was 10,000

void setup()
{
  // init pins
  pinMode(enablePin, OUTPUT);
  pinMode(ms1Pin, OUTPUT);
  pinMode(ms2Pin, OUTPUT);
  pinMode(ms3Pin, OUTPUT);
  pinMode(resetPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);

  digitalWrite(enablePin, LOW);
  digitalWrite(ms1Pin, LOW);
  digitalWrite(ms2Pin, LOW);
  digitalWrite(ms3Pin, LOW);
  digitalWrite(resetPin, LOW);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(stepPin, HIGH);
  digitalWrite(dirPin, HIGH);

  delay(100);                         
  digitalWrite(resetPin, HIGH);


  // initialize timer1
  noInterrupts(); // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 16000000.0f / samplerate; // compare match register for IRQ with selected samplerate
  TCCR1B |= (1 << WGM12); // CTC mode
  TCCR1B |= (1 << CS10); // no prescaler
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  interrupts(); // enable all interrupts
}

uint32_t wait = 0;
int spd = 0;
int clk = 0;

// timer 1 interrupt
ISR(TIMER1_COMPA_vect)
{
  // generate the next clock pulse on accumulator overflow
  wait += spd;
  if (wait > 0xffff) {
    if (clk) {
      digitalWrite(stepPin, HIGH);
    } else {
      digitalWrite(stepPin, LOW);
    }
    wait -= 0x10000;
    clk = !clk;
  }
}

void loop()
{
  // read potentiometer and set direction
  int poti = analogRead(analogPin);
  if (poti < 512) {
    digitalWrite(dirPin, LOW);
    poti = 512 - poti;
  } else {
    digitalWrite(dirPin, HIGH);
    poti -= 512;
  }

  // update the speed, which increments the accumulator in the interrupt
  noInterrupts();
  spd = poti * 60; //////was spd = poti * 10 //// with *100 was 35 kHz, but pot 'latching' prob.
  // 60 = max, = no latch, = 22,500 Hz, NB 65 = ' pot latching'
  interrupts();
}
code tweeked as above
Sorry to bother you, FrankBuss,
i know the subject is old, but, is there any chance that the code could be updated as i want to run a 200W servomotor (with glass encoder),
so not the model servomotor type!, in conjuction with a 320X Geckodrive, and i need to push out the step rate to as fast as the Atmega 328 will allow..
Also, the centre position needs a deadzone where no pulses are sent to ensure motor is settable to stationary
- i did read about not using 'digitalwrite' command, but i dont know how to write code.. yet.
i have to admit i did succeed at altering the pin assignments, & it runs on a Nano OK! so well done for your work, much appreciated.
i have entered the tweek code as above, after fiddling with the poti*... scaling
but would be great if i could get 60 to 80 kHz.
At the mo the Gecko drive is set to multiply incoming pulses x 10 to get the rpm achieved.(1350).
The Hi res encoder is of course part of the speed issue.
One thing i do note is that the pulse O/P seems to be jittery, regardless if original code or not..
Thanks,
Arduinew
« Last Edit: April 07, 2020, 11:36:26 am by arduinew »
 

Offline FrankBussTopic starter

  • Supporter
  • ****
  • Posts: 2365
  • Country: de
    • Frank Buss
Re: non-blocking Arduino stepper motor example
« Reply #6 on: April 10, 2020, 10:43:13 am »
If you need higher frequencies, but don't need to count the individual steps, you could try the Arduino tone function, or programming the ATmega timer registers directly to avoid the gaps between consecutive tone calls. This will also result in no jitter, which is caused by the interrupt latency and the software phase accumulator implementation. But the resolution wouldn't be that fine anymore (the higher the frequency, the larger the Hz steps, because it divides the main clock by an integer).

An alternative would be the AD9833, if you don't need to count the steps. It can generate a clock with up to 12.5 MHz, adjustable in 0.1 Hz steps. Internally it uses the same phase accumulator concept, but with a much higher clock rate, so jitter would be very low, especially at frequencies far away from the max frequency. The datasheet has all the details. There are cheap boards at eBay which you can just connect to an Arduino, and there are lots of demo projects for it on the internet.

If you want to count the steps as well, it gets complicated. Maybe a faster microcontroller could do it, like a STM32, which runs with more than 100 MHz depending on the type. Or a DSP. But in this case I would probably use a FPGA. I've done a few projects with FPGA devboards, e.g. this 4 channel ADC with 8 bit and 10 MHz samplerate. You could implement a custom frequency generator with step count etc. with VHDL or Verilog, and combine it with a soft CPU or hard CPU on the FPGA for the firmware, depending on the FPGA.
So Long, and Thanks for All the Fish
Electronics, hiking, retro-computing, electronic music etc.: https://www.youtube.com/c/FrankBussProgrammer
 

Offline glatocha

  • Regular Contributor
  • *
  • Posts: 114
Re: non-blocking Arduino stepper motor example
« Reply #7 on: April 14, 2020, 02:34:03 am »
What about using DMA to push the preprogrammed sequence to the outputs? In that case you should only need to update the DMA speed when you change speed. In theory this can work totally in the background.
I read a little about DMA in MSP430 and PICs, brief googling is saying the Arduino Due should have DMA controller.
 

Online Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 3361
  • Country: nl
Re: non-blocking Arduino stepper motor example
« Reply #8 on: April 23, 2020, 12:11:39 pm »
If you want to go further then "arduino", then have a look at grbl.
Grbl can be used in an "arduino" environment if you wish, but it's a "normal" C program, independent of "arduino" libs and it controlls 3 syncrhonized stepper motors.
It's used in3D printers and small CNC machines.

At the moment I'm experimenting with a 6 axis verion on an STM32 "Blue-Pill".
 

Offline Johannsen

  • Regular Contributor
  • *
  • Posts: 50
  • Country: 00
Re: non-blocking Arduino stepper motor example
« Reply #9 on: February 07, 2021, 10:22:20 pm »
How can the sketch improved with port manipulation? Is it possible to change the steps/rotation?
I would like to set the steps in the loop eg move 360° or move 20turns at a speed of 50RPM
 

Offline phil from seattle

  • Super Contributor
  • ***
  • Posts: 1029
  • Country: us
Re: non-blocking Arduino stepper motor example
« Reply #10 on: February 08, 2021, 01:18:18 am »
If you want to go further then "arduino", then have a look at grbl.
Grbl can be used in an "arduino" environment if you wish, but it's a "normal" C program, independent of "arduino" libs and it controlls 3 syncrhonized stepper motors.
It's used in3D printers and small CNC machines.

At the moment I'm experimenting with a 6 axis verion on an STM32 "Blue-Pill".
I second that. It has very high quality synchronization between multiple steppers. grbl has been tuned to the Atmel 386p.  Supports up to a 30K step rate. If you want to use an ARM chip, I recommend grblHAL which is a 32 bit version grbl and is easily portable to most 32-bit processors.  Currently runs on 13 different processors including most of the popular ones. STM32F1xx (Blue Pill), STM32F3xx, STM32F4xx (Black Pill), ESP32, LPC, iMXRT106x (teensy 4.x), Pico (in progress), and others.  The Teensy port has been tested to a 400kHz step rate and can be built in the Arduino environment.
« Last Edit: February 08, 2021, 01:20:35 am by phil from seattle »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf