4

I want to drive a stepper motor driver hardware by its pulse and direction inputs. I need to use a ramp function in order not to make stepper motor stuck. I need to increase the pulse frequency smoothly up to the needed frequency. For that purpose, I use the following code:

import pigpio
import time

try:
  pi = pigpio.pi()
  GPIO_pin=4

  pi.set_mode(GPIO_pin, pigpio.OUTPUT)
  pi = pigpio.pi() # connect to local Pi

  freq = 30000 # Hz 

  period = 1.0 / freq * 10**6

  print "period: %f" % period


  ramp_time = 1 # sec

  start_date = time.time()

  for i in range(1000): 

    time_diff =  time.time() - start_date 

    ramp_loc = time_diff / ramp_time
    #c = (i % 2) + 1
    if ramp_loc >= 1.0: 
      break

    print "ramp location: ", ramp_loc

    if ramp_loc <= .001: 
      ramp_loc = .001

    c = ramp_loc

    square = []
    #                          ON       OFF    MICROS
    square.append(pigpio.pulse(1<<GPIO_pin, 0,       period/2/c))
    square.append(pigpio.pulse(0,       1<<GPIO_pin, period/2/c))



    #pi.wave_clear()
    pi.wave_add_generic(square)

    wid = pi.wave_create()

    if wid >= 0:
      pi.wave_send_repeat(wid)

  time.sleep(5)

finally: 
  pi.wave_clear()
  pi.wave_tx_stop() # <- important!
  pi.stop()

Unfortunately there is some kind of jitter while increasing the frequency. That makes stepper motor stuck in somewhere in the acceleration period.

Edit

This is the fully working C code:

/* original code from Joan
 * modified by Cerem Cem ASLAN
 * 28.12.2014
 * License: Do whatever you want to do
 */

#define GPIO 4

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> 

#include <pigpio.h>

/*
gcc -o swave swave.c -lpigpio -lrt -lpthread
sudo ./swave
*/

/* generates a simple stepper ramp. */
int ramp(
   unsigned start_delay,
   unsigned final_delay,
   //unsigned step,
   unsigned count, 
   unsigned rise_time
        )
{
   unsigned step; 
   int i, j, p, npulses, np, wid=-1, each_step_width, step_pulse_count;
   rawWaveInfo_t waveInfo;
   rawCbs_t *cbp1, *cbp2;
   gpioPulse_t *pulses;

   step = (start_delay - final_delay) / count; 

   each_step_width = (rise_time * 1000) / count ; 
   printf("each step width: %d\n microseconds", each_step_width); 

   //npulses = (((start_delay-final_delay) / step) + 1 ) * count * 2;

   npulses = 10; 
    for (i=start_delay; i>=final_delay; i-=step)
    {
        step_pulse_count = each_step_width / i; 
        for (j=0; j<step_pulse_count; j++)
        {
          npulses += 2; 
        }
    }

    printf("number of pulses: %d", npulses); 


    //npulses += 10;


   pulses = (gpioPulse_t*) malloc(npulses * sizeof(gpioPulse_t));


   if (pulses)
   {
      p = 0;


      for (i=start_delay; i>=final_delay; i-=step)
      {
         step_pulse_count = each_step_width / i; 
         for (j=0; j<step_pulse_count; j++)
         {
            pulses[p].gpioOn = (1<<GPIO);
            pulses[p].gpioOff = 0;
            pulses[p].usDelay = i/2;
            p++;

            pulses[p].gpioOn = 0;
            pulses[p].gpioOff = (1<<GPIO);
            pulses[p].usDelay = i/2;
            p++;
         }
      }

       /* dummy last pulse, will never be executed */

      pulses[p].gpioOn = (1<<GPIO);
      pulses[p].gpioOff = 0;
      pulses[p].usDelay = i;
      p++;

      np = gpioWaveAddGeneric(p, pulses);

      wid = gpioWaveCreate();

      if (wid >= 0)
      {
         waveInfo = rawWaveInfo(wid);
         /*
         -7 gpio off         next-> -6
         -6 delay final step next-> -5
         -5 gpio on          next-> -4
         -4 delay final step next-> -3
         -3 gpio off         next-> -2
         -2 delay final step next-> -1
         -1 dummy gpio on    next->  0
          0 dummy delay      next-> first CB
         */
         /* patch -2 to point back to -5 */
         cbp1 = rawWaveCBAdr(waveInfo.topCB-2);
         cbp2 = rawWaveCBAdr(waveInfo.topCB-6);
         cbp1->next = cbp2->next;
      }
      free(pulses);
   }
   return wid;
}

#define START_DELAY 100 //microseconds
#define FINAL_DELAY 25   // microseconds
#define STEP_COUNT 50
#define RISE_TIME 100 // milliseconds

void start_sig_handler(int signo)
{
  while (signo == SIGCONT)
  {
    printf("received start signal\n");
    int arg, pos = 0, np, wid, steps;

    gpioWaveTxStop();
    gpioWaveClear(); 

    wid = ramp(START_DELAY, FINAL_DELAY, STEP_COUNT, RISE_TIME);

    if (wid >= 0)
    {
        gpioWaveTxSend(wid, PI_WAVE_MODE_ONE_SHOT);
    }
    break; 
  }
}


void stop_sig_handler(int signo)
{
  if (signo == SIGUSR2)
  {  
    printf("received stop signal\n");
    gpioWaveTxStop();
    gpioWaveClear(); 
  }

}


uint32_t HB_TICK; 

void heartbeat_sig_handler(int signo)
{
  if (signo == SIGUSR1)
  {  
    //printf("received heartbeat\n");
    HB_TICK = gpioTick(); 
  }
}

int pigpio_watchdog()
{
  static uint32_t timeout = 500000; // microseconds
  if (gpioTick() > HB_TICK + timeout)
  {
    printf("watchdog timed out. HB_TICK: %d, gpioTick: %d ||| ", HB_TICK, gpioTick()); 
    return 1;  
  }
  return 0; 
}


int main(int argc, char *argv[])
{

  printf("starting swave..."); 
  if (gpioInitialise() < 0) 
  {
    printf("can not initialize gpio library"); 
    return 1;
  }
  else
  {
    printf("started swave");

  }

  gpioSetSignalFunc(SIGCONT, start_sig_handler); 
  gpioSetSignalFunc(SIGUSR1, heartbeat_sig_handler); 
  gpioSetSignalFunc(SIGUSR2, stop_sig_handler); 

  // prevent shutdowns by unimportant signals
  gpioSetSignalFunc(28, heartbeat_sig_handler); 

  gpioSetMode(GPIO, PI_OUTPUT);

  printf("getting into loop...");
  HB_TICK = gpioTick();
  while(1)
  {
    //printf("looping...");
    if (pigpio_watchdog() > 0)
    {
      // stop the output in order not to 
      // physically damage anything without intention
      gpioWaveTxStop();
      gpioWaveClear(); 
      //gpioTerminate();
      //break; 
    }
    time_sleep(0.01); 
  }

}

Usage

In order to use the smooth square wave, first start swave and let it run as a separate process:

$ sudo ./swave

Start sending heartbeats to swave, else it will clear its output in 0.5 secs:

$ while [[ true ]]; do sudo kill -SIGUSR1 $(pidof swave); sleep .01; done

To start the square wave, send SIGCONT signal to swave:

$ sudo kill -SIGCONT $(pidof swave)

To stop the wave, send SIGUSR2 signal to swave:

$ sudo kill -SIGUSR2 $(pidof swave)
0

2 Answers 2

5

You seem to be creating then sending 1000 waveforms. There will be jitter between each waveform.

Try generating one big waveform and sending that.

Basically de-indent the lines

#pi.wave_clear()
pi.wave_add_generic(square)

wid = pi.wave_create()

if wid >= 0:
  pi.wave_send_once(wid)

so that they are executed after the for loop (just the once rather than repeatedly).


EDITED TO ADD

This code is intended to demonstrate what we talked about in the comments.

#!/usr/bin/env python

import time

import pigpio

START_DELAY=5000
FINAL_DELAY=100
STEP=100

GPIO=4

pi = pigpio.pi()

pi.set_mode(GPIO, pigpio.OUTPUT)

pi.wave_clear()

# short waveform to repeat final speed

wf=[]

wf.append(pigpio.pulse(1<<GPIO, 0,       FINAL_DELAY))
wf.append(pigpio.pulse(0,       1<<GPIO, FINAL_DELAY))

pi.wave_add_generic(wf)

wid0 = pi.wave_create()

# build initial ramp

wf=[]

for delay in range(START_DELAY, FINAL_DELAY, -STEP):
   wf.append(pigpio.pulse(1<<GPIO, 0,       delay))
   wf.append(pigpio.pulse(0,       1<<GPIO, delay))

pi.wave_add_generic(wf)

# add lots of pulses at final rate to give timing lee-way

wf=[]

# add after existing pulses

offset = pi.wave_get_micros()

print("ramp is {} micros".format(offset))

wf.append(pigpio.pulse(0, 0, offset))

for i in range(2000):
   wf.append(pigpio.pulse(1<<GPIO, 0,       FINAL_DELAY))
   wf.append(pigpio.pulse(0,       1<<GPIO, FINAL_DELAY))

pi.wave_add_generic(wf)

wid1 = pi.wave_create()

# send ramp, stop when final rate reached

pi.wave_send_once(wid1)

time.sleep(float(offset)/1000000.0) # make sure it's a float

pi.wave_send_repeat(wid0)

time.sleep(1)

pi.wave_tx_stop()

pi.stop()

EDITED TO ADD C EXAMPLE

This shows the patching of the last few DMA control blocks to repeat the final step.

#define GPIO 4

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <pigpio.h>

/*
gcc -o swave swave.c -lpigpio -lrt -lpthread
sudo ./swave
*/

/* generates a simple stepper ramp. */
int ramp(
   unsigned start_delay,
   unsigned final_delay,
   unsigned step,
   unsigned count)
{
   int i, j, p, npulses, np, wid=-1;
   rawWaveInfo_t waveInfo;
   rawCbs_t *cbp1, *cbp2;
   gpioPulse_t *pulses;

   npulses = (((start_delay-final_delay) / step) + 1 ) * count * 2;
   npulses += 10;

   pulses = (gpioPulse_t*) malloc(npulses*sizeof(gpioPulse_t));

   if (pulses)
   {
      p = 0;

      for (i=start_delay; i>=final_delay; i-=step)
      {
         for (j=0; j<count; j++)
         {
            pulses[p].gpioOn = (1<<GPIO);
            pulses[p].gpioOff = 0;
            pulses[p].usDelay = i;
            p++;

            pulses[p].gpioOn = 0;
            pulses[p].gpioOff = (1<<GPIO);
            pulses[p].usDelay = i;
            p++;
         }
      }

       /* dummy last pulse, will never be executed */

      pulses[p].gpioOn = (1<<GPIO);
      pulses[p].gpioOff = 0;
      pulses[p].usDelay = i;
      p++;

      np = gpioWaveAddGeneric(p, pulses);

      wid = gpioWaveCreate();

      if (wid >= 0)
      {
         waveInfo = rawWaveInfo(wid);
         /*
         -7 gpio off         next-> -6
         -6 delay final step next-> -5
         -5 gpio on          next-> -4
         -4 delay final step next-> -3
         -3 gpio off         next-> -2
         -2 delay final step next-> -1
         -1 dummy gpio on    next->  0
          0 dummy delay      next-> first CB
         */
         /* patch -2 to point back to -5 */
         cbp1 = rawWaveCBAdr(waveInfo.topCB-2);
         cbp2 = rawWaveCBAdr(waveInfo.topCB-6);
         cbp1->next = cbp2->next;
      }
      free(pulses);
   }
   return wid;
}

#define START_DELAY 5000
#define FINAL_DELAY 100
#define STEP_DELAY  100
#define STEP_COUNT 5

int main(int argc, char *argv[])
{
   int arg, pos = 0, np, wid, steps;

   if (gpioInitialise() < 0) return 1;

   printf("start piscope\n");

   getchar();

   gpioSetMode(GPIO, PI_OUTPUT);

   wid = ramp(START_DELAY, FINAL_DELAY, STEP_DELAY, STEP_COUNT);

   if (wid >= 0)
   {
      gpioWaveTxSend(wid, PI_WAVE_MODE_ONE_SHOT);

      time_sleep(1.0);
   }

   printf("stop piscope\n");

   getchar();

   gpioTerminate();

}

Waveform overview Waveform overview

Waveform transition detail Waveform transition detail

19
  • I tried to do so and it seems that it will work with some extra help. Now the motor accelerates smoothly but after acceleration it should keep turning at the last speed. That's why I'm trying to queue the repetative wave but there is no success yet.
    – ceremcem
    Commented Dec 23, 2014 at 8:57
  • The best I can suggest from Python is to generate two waveforms. The first a short (on/off) pulse at the final rate. The second like you do at the moment but with several additional pulses at the final rate. Create each waveform to get ids 0 (the first) and 1 (the large second). Send the second (once) for a timed amount of time (just shorter than its expected duration) and then send the first in repeat mode. Does that make sense? C has the hooks to allow you to patch the waveform on the fly, but that is definitely advanced usage territory.
    – joan
    Commented Dec 23, 2014 at 9:07
  • Yes, I was absolutely trying to do the same thing. If I have some time more than couple of hours, I would dive into the C code, but I haven't... It also makes sense...
    – ceremcem
    Commented Dec 23, 2014 at 9:11
  • Slight modification. In one Python wave_add_generic add the large waveform but don't create it. Then add a couple of thousand pulses at the final rate and call wave_add_generic on that. Then create the waveform. That will get over the Python limit of something like 6000 pulses per individual socket message. That should give plenty of lee-way in the timing to start the second waveform repeatedly.
    – joan
    Commented Dec 23, 2014 at 9:21
  • something like above - in the edit part - ?
    – ceremcem
    Commented Dec 23, 2014 at 9:49
0

Stepper drivers do bot need 50% duty cycle waves. A short >10uS HIGH will do the job. I wrote a strategy of several ramp algorithms (Linear, Exponential, custom, interactive) in this stepper lib for RPi. https://github.com/juanmf/StepperMotors

Let me know, should you try it, if the doc is lacking.

Best, Juan

Demo https://youtu.be/3OWGrZM90M8?si=ZGPBlZOpHeYMuQJV

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.