Powered by Gentoo Linux
 
 
Page Updated: February 15 2009 17:13

Linux Stepper Control Application

Credit

My motor control utility is based heavily on Lukas Zimmermann's application.
I have added an "interactive tuning" mode, which allows for precise incremental tuning on the command line. This is particularly handy for tuning variable capacitors.

Download

The latest version of the application is available for download here.

Known Issues

  • Custom Byte-Order not available in Interactive Mode - Interactive mode has the ability to switch between full- and half-stepping. This relies on two hard coded character arrays, containing byte sequences for the different drive methods. Custom byte-sequences are still available in non-interactive mode.
  • Code written by Rank Amateur - There is probably stuff in here which will bring sorrowful tears to the eyes of decent programmers. If you spot this, please do the right thing and put me on the right track.

Issues aside - this code works well for me.

Source Code

/* stepper.c v1.65
 *
 * Original application (C) 2006, Lukas Zimmermann, Basel, Switzerland.
 * Modifications by Charles Price. Derbyshire, UK
 *
 * This program bitbangs the data lines of a PC parallel port to drive
 * stepper motor control interfaces, such as those at:
 *   http://linuxisp.co.uk/content/stepper_control
 *   http://www.pollin.de/shop/detail.php?pg=NQ==&a=NzcyOTgyOTk=
 *   
 * The program can repeat a sequence given on command line or can issue pre-
 * defined sequences for controlling stepper motors.
 *
 * Details on the Linux' Parallel Port Subsystem can be found here: 
 *   http://people.redhat.com/twaugh/parport/html/parportguide.html
 *
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Or visit http://www.gnu.org/licenses/gpl.html.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <fcntl.h>
#include <time.h>		// for nanosleep()
#include <errno.h>
#include <linux/ppdev.h>
#include <linux/parport.h>
#include <sys/ioctl.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <termios.h>

#define PARPORT_DEV		"/dev/parport0" 	// default parallel port device

#define FALSE	0
#define TRUE	1

#define END_FILE_CHARACTER 0x04  /* ctrl-d is unix-style eof input key*/

volatile sig_atomic_t keep_going = TRUE;
jmp_buf jmp_cleanup;

int ppfd;						// file descriptor for parallel port

char *progname;

long          loop_delay_us = 1000;
long          loop_count = 1;

static struct option const longopts[] =
{
  {"port", required_argument, NULL, 'p'},
  {"loop-count", required_argument, NULL, 'c'},
  {"loop-delay", required_argument, NULL, 'l'},
  {"debug", no_argument, NULL, 'd'},
  {"full-step", no_argument, NULL, 'F'},
  {"half-step", no_argument, NULL, 'H'},
  {"reverse", no_argument, NULL, 'R'},
  {"reset-on-exit", no_argument, NULL, 'r'},
  {"interactive", no_argument, NULL, 'i'},
  {NULL, 0, NULL, 0}
};

#define FULLSTEP_SEQ_SIZE 4
#define HALFSTEP_SEQ_SIZE 8

unsigned char  fullstep_seq[FULLSTEP_SEQ_SIZE] = { 0x1, 0x4, 0x2, 0x8 };
unsigned char  halfstep_seq[HALFSTEP_SEQ_SIZE] = { 0x1, 0x5, 0x4, 0x6, 0x2, 0xa, 0x8, 0x9 };

int reset_on_exit   = 0;

void
usage(int status)
{
  printf("\
Usage: %s [options] [byte-value, ...]\n\
", progname);
  fputs("\
    -h, --help                   show this help and exit\n\
    -p, --port <device>          select parallel port device\n\
    -l, --loop-delay <us delay>  set loop delay time [us].\n\
    -c, --loop-count <count>     repeat the sequence <count> times (-1 = unlimited).\n\
    -F, --full-step              use sequence to drive a stepper motor with full steps.\n\
    -H, --half-step              use sequence to drive a stepper motor with half steps.\n\
    -R, --reverse                stepper motor turns in reverse direction.\n\
    -i, --interactive            interactive fine-tune mode.\n\
    -r, --reset-on-exit          reset the data lines on exiting the program.\n\
    -d, --debug                  print debugging information.\n\
", stdout);

  exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}

int linux_getch(void)
{
  struct termios oldstuff;
  struct termios newstuff;
  int    inch;

  tcgetattr(STDIN_FILENO, &oldstuff);
  newstuff = oldstuff;                  /* save old attributes               */
  newstuff.c_lflag &= ~(ICANON | ECHO); /* reset "canonical" and "echo" flags*/
  tcsetattr(STDIN_FILENO, TCSANOW, &newstuff); /* set new attributes         */
  inch = getchar();
  tcsetattr(STDIN_FILENO, TCSANOW, &oldstuff); /* restore old attributes     */

  if (inch == END_FILE_CHARACTER) {
    inch = EOF;
  }
  return inch;
}

// handler for SIGTERM, SIGINT (interrupt the program with Ctrl +C)
void
TERM_handler (int sig)
{
  keep_going = 0;
  signal(sig, TERM_handler);
  longjmp(jmp_cleanup, -1);
}


void *
xmalloc (size_t size)
{
  register void *value = malloc (size);
  if (value == 0) {
    perror("virtual memory exhausted");
    exit(EXIT_FAILURE);
  }
  return value;
}

void
cleanup(void)
{
  if (reset_on_exit) {
    int d = 0;
    if (ioctl(ppfd, PPWDATA, &d) < 0) {
      perror("Cannot set data lines of parallel port to 0.");
    }
  }
  if (ioctl(ppfd, PPRELEASE) < 0) {
    perror("Cannot release parallel port");
    exit(EXIT_FAILURE);
  }
  close(ppfd);
}


int
init_parport(char *devname)
{
  int pp = open(devname, O_RDWR);
  if (pp) {
    int ioctl_stat = ioctl(pp, PPCLAIM);
    if (ioctl_stat != 0) {
      fprintf(stderr, "ioctl PPCLAIM error %d\n", ioctl_stat);
      pp = -1;
    }
  }

  return pp;
}

/* pp_sendchar(d) - send a character to the parallel port. */
int pp_sendchar(unsigned char *d)
{
  int ioctl_stat;
  ioctl_stat = ioctl(ppfd, PPWDATA, d);
  if (ioctl_stat != 0) {
    fprintf(stderr, "ioctl PPWDATA error %d\n", ioctl_stat);
  }
  return ioctl_stat;
}

/* main() */
int main(int argc, char *argv[])
{
  int c;
  char PpDev[63] = PARPORT_DEV;
  struct timespec t_req;
  t_req.tv_sec = 0;
  t_req.tv_nsec = 1000 * loop_delay_us;
  struct timespec t_rem;
  unsigned char *seq_bytes = NULL;
  int non_opt_cnt = 0;
  int debug = 0;
  int fullstep = 0;
  int halfstep = 0;
  int cycles = 0;
  int reverse = 0;
  int interactive = 0;

  // cut dir path away from command line
  progname = strrchr(argv[0], '/');
  if (progname == NULL)
    progname = argv[0];		// global for error messages, usage
  else
    progname++;

  char *tail;
  long l;
  unsigned char tmp;
  while ((c = getopt_long(argc, argv,
                          "h?dHFiRirp:l:c:",
                          longopts, NULL
                          )) != EOF) {
    switch(c) {
      case '?':
      case 'h': // help
        usage(EXIT_SUCCESS);
        break;
      case 'p':
        strcpy(PpDev, optarg);
        break;
      case 'd':
        debug = 1;
        break;
      case 'r':
        reset_on_exit = 1;
        break;
      case 'F':
        fullstep = 1;
        cycles = 4;
        seq_bytes = fullstep_seq;
        break;
      case 'H':
        halfstep = 1;
        cycles = 8;
        seq_bytes = halfstep_seq;
        break;
      case 'R':
        reverse = 1;
        tmp = fullstep_seq[0];
        fullstep_seq[0] = fullstep_seq[2];
        fullstep_seq[2] = tmp;
        tmp = halfstep_seq[0];
        halfstep_seq[0] = halfstep_seq[4];
        halfstep_seq[4] = tmp;
        tmp = halfstep_seq[1];
        halfstep_seq[1] = halfstep_seq[3];
        halfstep_seq[3] = tmp;
        tmp = halfstep_seq[5];
        halfstep_seq[5] = halfstep_seq[7];
        halfstep_seq[7] = tmp;
        break;
      case 'i':
	interactive = 1;
	break;
      case 'l':
        errno = 0;
        loop_delay_us = strtol(optarg, &tail, 0);
        if (errno | (tail == optarg)) {
          perror("option -l value could not be parsed.");
          exit(EXIT_FAILURE);
        }
        t_req.tv_sec = loop_delay_us / 1000000;
        t_req.tv_nsec = loop_delay_us % 1000000 * 1000;
        break;
      case 'c':
        errno = 0;
        loop_count = strtol(optarg, &tail, 0);
        if (errno | (tail == optarg)) {
          perror("option -c value could not be parsed.");
          exit(EXIT_FAILURE);
        }
        break;
      default:
        usage(EXIT_FAILURE);
    }
  }

  if (!fullstep && !halfstep) {
    non_opt_cnt = argc - optind;
    cycles = non_opt_cnt;
    if (debug) {
      fprintf(stderr, "%d non-option arguments on command line.\n", non_opt_cnt);
    }   
    seq_bytes = xmalloc(sizeof(int) * non_opt_cnt);
    int i;
    for (i = 0; i < non_opt_cnt; i++) {
      errno = 0;
      l = strtol(argv[optind + i], &tail, 0);
      if (errno | (tail == argv[optind + i]) | (l > 255) | (l < 0)) {
        fprintf(stderr, "Argument %d (=%s) out of range, must be >= 0 <= 255.\n",
               i, argv[optind + i]);
        exit(EXIT_FAILURE);
      } else {
        seq_bytes[i] = l;
      }
    }
  }

  if (debug) {
    printf("nanosleep = %ld s , %ldns\n", (long)t_req.tv_sec, (long)t_req.tv_nsec);
  }

  ppfd = init_parport(PpDev);
  if (ppfd < 0) {
    fprintf(stderr, "failed opening parallel port device %s\n", PpDev);
    exit(EXIT_FAILURE);

  } else {
    // install signal handlers for beeing able to cleanly exit
    signal(SIGINT, TERM_handler);
    signal(SIGTERM, TERM_handler);

    if (!setjmp(jmp_cleanup)) {

      // main loop
      unsigned char d;
      int ioctl_stat;
      int i;
     

      if (interactive == 1)
      {
        int kb_char;
	int seq_pos = 0;

        /* By default, we are in half-stepping mode */
        seq_bytes = halfstep_seq;

        printf("Interactive Fine-Tuning Mode.\n");
	printf("WARNING - Prolonged use of this mode may heat up your motor!\n");
	printf("Controls:\n\tF --> Half-Step Clockwise.\n\tR --> Half-Step Anti-Clockwise.\n\t");
	printf("G --> Full-Step Clockwise\n\tD --> Full-Step Anti-Clokwise\n\nPress CTRL-D to exit.\n");
        while ((kb_char = linux_getch()) != EOF)
        {
	  /* Send next character in sequence to the parallel port. */
	  d = seq_bytes[seq_pos];
          pp_sendchar(&d);
          nanosleep(&t_req, &t_rem);

          /* Has a relevant key been pressed? */
          switch(kb_char)
          {
            /* F key */
            case 0x66:
              /* Are we in half-stepping mode? If not, enable it. */
	      if (memcmp(seq_bytes,halfstep_seq,HALFSTEP_SEQ_SIZE) != 0) {
	        if (debug)
		  printf("Switching to half-step.\n");
                seq_bytes = halfstep_seq;
              }
              if (seq_pos == HALFSTEP_SEQ_SIZE-1)
                seq_pos = 0;
              else
                ++seq_pos;
              break;

            /* R key */
            case 0x72:
              /* Are we in half-stepping mode? If not, enable it. */
	      if (memcmp(seq_bytes,halfstep_seq,HALFSTEP_SEQ_SIZE) != 0) {
	        if (debug)
	          printf("Switching to half-step.\n");
                seq_bytes = halfstep_seq;
              }
              if (seq_pos == 0)
                seq_pos = HALFSTEP_SEQ_SIZE-1;
              else
                --seq_pos;
              break;

            /* G key */
	    case 0x67:
	      /* Are we in full-stepping mode? We should be. */
              if (memcmp(seq_bytes,fullstep_seq,FULLSTEP_SEQ_SIZE) != 0) {
	        if (debug)
		  printf("Switching to full-step.\n");
		seq_bytes = fullstep_seq;
              }
	      if (seq_pos == FULLSTEP_SEQ_SIZE-1)
	        seq_pos = 0;
	      else
	        ++seq_pos;
              break;

            /* D key */
	    case 0x64:
	      /* Are we in full-stepping mode? We should be. */
	      if (memcmp(seq_bytes,fullstep_seq,FULLSTEP_SEQ_SIZE) != 0) {
	        if (debug)
		  printf("Switching to full-step.\n");
		seq_bytes = fullstep_seq;
	      }
	      if (seq_pos == 0)
	        seq_pos = FULLSTEP_SEQ_SIZE-1;
	      else
	        --seq_pos;
              break;


            default:
              break;
          }
	  if (debug) {
            printf("%2x ",seq_bytes[seq_pos]);
	    //printf("%2x ",kb_char);
          }
        }
	cleanup();
	exit(EXIT_SUCCESS);
      }

      long loop_cnt = 0;
      while (keep_going && ((loop_cnt <= loop_count) || loop_count < 0)) {
        loop_cnt++;
        for (i = 0; i < cycles; i++) {
          d = seq_bytes[i];
          ioctl_stat = pp_sendchar(&d);
          nanosleep(&t_req, &t_rem);
        } // for

      } // while (keep_going)
    } // if (set_jmp())
			
    // clean up after having received termination signals
    cleanup();
		
    exit(EXIT_SUCCESS);
  }
}

Hosting from W3Z - Web Without Wires from Zycomm