Adaptive microstepping

Does polulu make a microcontroller that has adaptive microstepping? What do I mean by this? A controller that will use microsteps (1/16) at slow speeds moving up to full steps for high speeds. The benefits is smooth (less vibration) operation in the low speed range and ability to still run at higher speeds. I’m not sure how well this would work as I am just typing this as I am thinking.

I have an application that I need 300RPM from the stepper. At full steps this is not a problem. I am driving it using accelstepper on an arduino pro mini. I can run up to 2000 steps/sec but anything above that is not reliable. According to the author of accelstepper anything above 1000 steps/sec is unreliable. The problem is with full steps at low rpm, say 20rpm, the stepper vibrates and makes siginifcant noise. If I run it at 1/16th steps it is very smooth but I can’t come close to the required 16000 steps/sec to hit 300 rpm.

In order to remedy this I was thinking of using the arduino to set the MS1, MS2, and MS3 to control the microstep size. I would also use the arduino to reset the polulu after changing the microstep size. This would let me setup microstepping for low rpm ranges and full steps for high rpm ranges.

Has anyone else done this? Anyone see an issue using this approach?

thanks,
Brendin

The step resolution is largely just a function of sending data from the CPU to the controller. Not being able to send more than 2,000 steps per second seems to be a problem with whatever library you’re using. With an AVR microcontroller, if all you do is generate step wave functions, you should be able to do > 50,000 steps a second. (There’s 16,000,000 instructions per second, after all.)

If you only need to control one motor, you could try using timer1, which has high resolution. Set it to fast PWM mode with TOP == ICR1 (mode 14,) and clock it out at fCPU / 8 (2 MHz) This means that the minimum step rate is 30 steps/second, and the max is 1,000,000 – but you’ll never want to get that high :slight_smile:
Then, set the PWM output compare mode to output a “1” below the timer value 2 (for a 1 us step pulse) and set the timer target register to 2000000/steprate. So, if you want a step rate of 10,000 steps a second, set the target register to 200.

Then, you can turn the timer off if you don’t want to step at all, or turn it on to start generating steps. You may want to use the timer overflow interrupt to count the number of steps taken.

There is more information in the Atmega328p data sheet about this, in chapter 15 (“16-bit timer/counter 1 with PWM.”)

Hello, Brendin

Please keep in mind that the step rate does not solely depend on your control signalling. Drivers have timing limits (i.e. page 6 of the A4988 datasheet), and the motors have physical limitations on how many pulses per second they can respond to. Sometimes you can spin your motor faster if you ramp up the speed. I am not sure what driver you are using, but the A4988 does not need to be reset to change microstepping modes. Page 7 of the datasheet says, “When changing the step mode the change does not take effect until the next STEP rising edge.”

- Ryan

I’d guess the root of your problem is the (horrible) inefficiency of Arduino. Do some googling, there is good info out there like jeelabs.org/2010/01/06/pin-io-performance/ showing Arduino can be 29 times slower for simple pin access. I recently found my main loop on an Arduino project was running at about 1700 Hz, snails pace for a micro. In fact I’ve got windows (labview) programs that run faster. I also found the serial comms was blocking program flow, especially at lower baud rates. I assumed the micro should dump text to the uart and continue at full speed, but apparently not. Its the price you pay for ease of use, I guess.

I haven’t done it, but I can’t see any problem, should work fine, except you may find the transition between step modes isn’t smooth, nor is it trivial to program.

Thanks for all the replies. I did get the stepping modes working properly on the arduino and it is much smoother. It kind of works like an automatic transmition. It runs through 1/16 to 1 step depending on the rpm range. The transition is pretty smooth between ranges. I am adjusting with a pot to set stepper rpm.

I think though that AVR is really the way to go. I am going to start breadboarding an AVR based solution now. I also what to try the drv8825 but I am a moron and didn’t order them a few days back when they were apparently on sale. I didn’t realize that all the drivers were on sale for almost 1/2 price. DOH!

Nice.
Maybe you could post your code for the benefit of all ?

And +1 on the “D’oh” - I put my Black Friday order in , and then went to the specials page and saw all the stepper drivers half price. No point raising another order, shipping is a killer for me (to Australia).

The UART has no buffer. The Arduino library implements a 64-byte buffer, as can be seen in the source code:

github.com/arduino/Arduino/blob … Serial.cpp

However, each byte sent will cause another interrupt, and each interrupt is a minimum of 3 microseconds (and likely more because of the somewhat inefficient implementation of the buffer.) Take 3 microseconds 10,000 times a second (once per byte at 115 kbps) and 30% of your CPU is gone right there!

Also, if you write more than can be drained by the serial port, you will block at some point, no matter how big you make the buffer.

I am testing it on an atmega168 right now using avr c instead of using an arduino. I can drive a stepper smoothly using 1/16th stepping throughout my target rpm range of 0 to 300 rpm. I am using the following code and I hard code the value of the delay.

// 31.25/31.25us = 300 rpm
// 3rpm 3125us
while(1)
   {
     STEP= 0x01;
     _delay_us(31.25); // I believe the docs for the a4988 specify 1us delay but starting at 10
     STEP= 0x00;
     _delay_us(31.25); 
     
   }

Now I want to add a pot on the adc. I can’t add the read code into the while loop as it screws up the timing. So I was thinking of using your idea of a timer to drive the stepper. I would like to use the pot to change the frequency that the timer runs.

The timer would scale up to a frequency of 300 RPM which is equal to 5 rps (rotations per sec). At 1/16 stepping the freq = 16 2005=16000hz. This means I really need to run at 32000hz as I am pulsing one high and one low to make one step.

low range (zero) would just turn the timer off
The frequency of the timer would scale with the pot

0 would start the timer
<300

Any thoughts on this approach? Maybe there is a better one.

This means I really need to run at 32000hz as I am pulsing one high and one low to make one step.

If the timer is in fast PWM or CTC mode, then it will pulse both high and low during a single cycle. Also, it will automatically generate the pulses – you don’t need to do it in your code at all. All you do is read the ADC and change the value of ICR1 to make the timer cycle faster or slower.

The code would look something like this:

#include <avr/power.h>
#include <avr/io.h>

unsigned short speed = 0xffff; // slowest
unsigned char channel = 0; // for A0 pin
int main() {
  // set up ADC to read input
  power_adc_enable();
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1); // check data sheet
  // check data sheet for "ref" to use -- typically AVCC
  ADMUX = (ref << REFS0) | (1 << ADLAR) | channel;
  DIDR0 = (1 << channel);
  DDRC = 0;
  PORTC = 0;

  // set up timer to generate PWM
  power_timer1_enable();
  OCR1A = 2; // how long a pulse to generate
  TCCR1A = (1 << COM1A1) | (1 << WGM0); // check data sheet
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); // CTC mode, count at fCPU rate
  TCCR1C = 0;
  DDRB = (1 << 1) | (1 << 2); // timer 1 PWM outputs

  while (true) {
    ICR1 = speed;
    // start sampling
    ADCSRA |= (1 << ADIF) | (1 << ADSC);
    while (ADCSRA & (1 << ADSC)) {
      // ADC busy
    }
    speed = ADC;
  }
}

This is assuming avr-gcc + avr-libc, rather than Arduino.

I was able to get it working using timer2 and the code below (excerpt). Unfortunately timer 2 does not have enough resolution being 8 bit. With the scaling most of the pot adjusts a very small range in rpm. I tried a 16bit timer1 version but I couldn’t get it working. I checked the output on the scope and it wasn’t outputting. I’ll take a look at your code for the timer1 setup above and do some further debugging on mine.

void setupTimer(int speed)
{
    if (speed <6) { speed=6; }

    OCR2A = speed;

    TCCR2A |= (1 << WGM21);
    // Set to CTC Mode

    TIMSK2 |= (1 << OCIE2A);
    //Set interrupt on compare match

    TCCR2B |= (1 << CS21) | (1 << CS20);
    // set prescaler to 32 and starts PWM

    sei();
    // enable interrupts


    // enable interrupts
}

ISR (TIMER2_COMPA_vect)
{
    // action to be done every 250 usec
		 STEP ^= (1<<0);
}

int main()
{

uint16_t raw;
uint16_t lastraw;

 raw = ReadADC(0);
lastraw = ReadADC(0);

setupTimer(raw/4);

  while(1)
   {
     raw = ReadADC(0);
	 if ((raw> (lastraw+5)) || (raw< (lastraw-5) ))
	 { 
	   setupTimer(raw/4); 
	   lastraw=raw;
	  }


     _delay_us(31.25); //

     
   }


}

Here is how I coded the adaptive microstepping using the arduino and the accelstepper library. There is nothing special about this code it was written quickly just as a proof of concept for my project.

#include <AccelStepper.h>

int potPIN = A0;  // select the input pin for the potentiometer
int potVAL = 0;  // variable to store the value coming from the potentiometer

int polM1 = 7;
int polM2 = 6;
int polM3 = 8;

// Define some steppers and the pins the will use
AccelStepper stepper1(1, 5, 3); // Defaults to 4 pins on 2, 3, 4, 5

void setup()
{  
 // Serial.begin(9600);      // set the baud rate for the serial window
    pinMode(potPIN, INPUT); 
    
    pinMode(polM1, OUTPUT); 
    pinMode(polM2, OUTPUT); 
    pinMode(polM3, OUTPUT); 
    
    stepper1.setMaxSpeed(2000.0);
    stepper1.setAcceleration(1000.0);
 
    stepper1.setSpeed(0);
 
}

void loop()
{
   potVAL = analogRead(potPIN); // read the value from the potentiometer
  
   if (potVAL >10) {  
  
     calculateSteps(potVAL);
     stepper1.runSpeed();
  

   } 
}

void calculateSteps(int pVal) {
 
  int range=0;
  
  if ( (pVal>=0) && (pVal<=200) ) {
    // 1/16 stepping
    digitalWrite(polM1,HIGH);
    digitalWrite(polM2,HIGH);
    digitalWrite(polM3,HIGH);
    range=0;    
  }
  if ( (pVal>200) && (pVal<=400) ) {
    // 1/8 stepping
    digitalWrite(polM1,HIGH);
    digitalWrite(polM2,HIGH);
    digitalWrite(polM3,LOW);
    
    range=1;  
  }
  if ( (pVal>400) && (pVal<=600) ) {
    // 1/4 stepping
    digitalWrite(polM1,LOW);
    digitalWrite(polM2,HIGH);
    digitalWrite(polM3,LOW);

    range=2;   
  }
  if ( (pVal>600) && (pVal<=800) ) {
    // 1/2 stepping
    digitalWrite(polM1,HIGH);
    digitalWrite(polM2,LOW);
    digitalWrite(polM3,LOW);
    
    range=3;  
  }  
  if ( (pVal>800) && (pVal<=1100) ) {
        // 1 stepping
    digitalWrite(polM1,LOW);
    digitalWrite(polM2,LOW);
    digitalWrite(polM3,LOW);

    range=4;  
  }  
  
     stepper1.setSpeed( ( (pVal-(range*200)) *2.5)+500 );

}