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