/* 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);
  }
}


