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)
goldilocks
  • 58,859
  • 17
  • 112
  • 227
ceremcem
  • 287
  • 7
  • 16

2 Answers2

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

joan
  • 71,024
  • 5
  • 73
  • 106
  • 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 Dec 23 '14 at 08: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 Dec 23 '14 at 09: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 Dec 23 '14 at 09: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 Dec 23 '14 at 09:21
  • something like above - in the edit part - ? – ceremcem Dec 23 '14 at 09:49
  • Yes, bear with me I'm trying to code an example. – joan Dec 23 '14 at 09:57
  • @ceremcem Example added to post. – joan Dec 23 '14 at 10:22
  • I'm examining the code – ceremcem Dec 23 '14 at 11:05
  • Okay man! Your example made the code is at least usable! I need 30 kHz but this code can not operate above 15 kHz, BUT, there is something interesting. I stopped the execution right after time.sleep(float(offset)/1000000.0) line, the motor moved and this movement was very short. I run the code fully at 30 kHz, the motor moved much longer and then stucked. I think this means: This code make the motor operate at 30 kHz for a second or less and then, something happens and motor gets stucked. Working on it. – ceremcem Dec 23 '14 at 11:39
  • @ceremcem How long is your ramp up in microseconds? 15 kHz or 30 kHz should make no difference to the code (apart from the number of pulses you need to generate). Theoretically it should do 500 kHz. If your stepper is exceptionally picky you may need to add the line disable_pvt=1 to /boot/config.txt to stop a few microseconds perturbation twice a second. – joan Dec 23 '14 at 11:57
  • I examined the output with a logic analyzer. I think the switching phase is just a little bit problematic. Here is the screenshots: overall wave, switching phase. Do you think we may achieve the goal by using python or should I dive into C? I have a day now. – ceremcem Dec 23 '14 at 20:02
  • I can't think of a way to do what you want from Python. From C (I think) it would be quite easy. You could just create the ramp ending on the final speed and then patch the last control block to point to a couple before rather than the start. When I get time I'll write and post some C which duplicates the Python. – joan Dec 23 '14 at 20:37
  • @ceremcem C example added. – joan Dec 23 '14 at 22:42
  • After setting the correct parameters, I got a square wave of 25 kHz and as far as I could test with a logic analyzer again, the switches were very smooth. Today I had a physical test with the same type of stepper motor and it worked just great. But, the problem is, the python code works as well... So, I'll apply this solution in the field and we will see what happens on the real environment. – ceremcem Dec 24 '14 at 14:37
  • In real environment conditions, the python code didn't work (the motor stucked while transition) but the C code worked just fine at 40 kHz. Actually you saved me! Thank you! – ceremcem Dec 25 '14 at 14:26
  • is it wise to control this piece of "code" with signals from python or do you suggest any other ways? – ceremcem Dec 27 '14 at 22:24
  • The only point of pigpiod is as a convenience to start the C library and leave it running (to provide socket and pipe access). Don't use pigpiod. Leave your C program which generates the waveform running (e.g. after generating the waveform do while(1) {sleep (1)}. You can then access the constructed waveform from Python by using its waveform Id. – joan Dec 27 '14 at 22:47
  • I mean, like the C code I just edited in my question. – ceremcem Dec 28 '14 at 00:49
  • The code you have in your post should work as written. To check try pigs t from the command line. It should show the current tick. If that's the case a pigpio Python script will automatically connect to the library started by your C program. – joan Dec 28 '14 at 09:38
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

juanmf
  • 101
  • 2