Channel: credentiality
Viewing all articles
Browse latest Browse all 94

Raspberry Pi GPIO pins to stepper controller

I picked up a 3-axis beefy NEMA-23 stepper kit from ebay that sell for around $240.  It comes with a Longs Motor DM542A stepper controller, which seems to be more or less identical to a lot of other brands.

The kit came with a board that takes commands from a PC parallel port (driven by eg., linuxcnc), and apparently the timing is such that USB-parallel adapters don't work well; you need a hardware parallel port.

I wasn't particularly excited to track down a machine old enough to have a parallel port and lug it around with my project, so I looked into whether the GPIO pins on my Raspberry Pi would be suitable to drive the DIR (direction) and PUL (pulse / step) pins directly.  I found some general discussion, but not much that was concrete.

I think I originally picked up NOOBS to put on the SD card, but it's been a while and I don't remember exactly.  But it came up with a menu and I picked the recommended option of Raspbian wheezy, which it downloaded and installed.  Installation was a breeze, and the machine has been working great for me.  (I'm writing this post on it, even, although it's pretty laggy if I try to open multiple tabs in the browser).

Most of the skepticism I saw about using the GPIO pins centered around the need for an RTOS to ensure that the process toggling the pulse line can do so at the required intervals without getting held up for extra milliseconds.  When I ran "uname -a", I noticed "PREEMPT", which means that it already has the kernel patches for hard timing.  Awesome!

Voltages were another concern: the stepper driver wants 5v logic inputs, but the raspi runs at 3.3v.  Fortunately, this doesn't seem to be a problem either; the driver board seems perfectly happy with 3.3v input.

At first I though my driver wasn't working right when I wired it up -- I got no power light.  But it turns out the ENBL line needs to be pulled down rather than up for the board to work.

I also found that the board doesn't seem to have the - side of each input tied together, so I had to wire up the ENBL-, DIR- and PUL- inputs all to ground.

I looked over the pinout for P1 on the raspi to figure out which GPIO lines to use, and didn't find any major reasons to use any particular ones; seems like almost all of them have alternate uses that I mostly don't think I'll need.  So I used pins 1, 3, 5, 7 and 9, corresponding to +3.3v, GPIO-0, GPIO-1, GPIO-4 and GND on my rev. 1 board.

My first stab at controlling the stepper used the python example code.  Note that this code uses board pin numbers rather than the GPIO numbering:


import RPi.GPIO as GPIO
import time

# use P1 header pin numbering convention

# Set up the GPIO channels - one input and one output
GPIO.setup(3, GPIO.OUT)
GPIO.setup(5, GPIO.OUT)
GPIO.setup(7, GPIO.OUT)

GPIO.output(3, GPIO.HIGH)

while True:
  GPIO.output(5, GPIO.HIGH)
  GPIO.output(5, GPIO.LOW)

This worked just fine, although as expected, it'd glitch every few seconds, audible as a stuttering sound in the stepper.  Glitching got worse when I loaded down the system by doing things like loading web pages.

Next I tried the C example code to see if it'd do better.  It might have been somewhat better, but still had noticeable glitching.  

I found excellent wiki page with example code for using the RTOS patches, and mashed it up with the GPIO example code, and it works great!

So it looks like the principle is sound.  The remaining task is to get it integrated with linuxcnc, at which point we'll have a $35 CNC controller with HDMI output that can drive step/direction pins on stepper controllers directly, without having to use parallel ports and adapter boards.

// Compile with:
// gcc -o rt_gpio rt_gpio.c -lrt

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sched.h>
#include <sys/mman.h>

#include <string.h>

// defines for using the realtime kernel
#define MY_PRIORITY (49) /* we use 49 as the PRREMPT_RT use 50
                            as the priority of kernel tasklets
                            and interrupt handler by default */

#define MAX_SAFE_STACK (8*1024) /* The maximum stack size which is
                                   guaranteed safe to access without
                                   faulting */

#define NSEC_PER_SEC    (1000000000) /* The number of nsecs per sec. */

// defines for talking to the gpio pins

// Access from ARM Running Linux

#define BCM2708_PERI_BASE        0x20000000
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controll
er */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

int  mem_fd;
void *gpio_map;

// I/O access
volatile unsigned *gpio;

// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))

#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0

void setup_io();

void stack_prefault(void) {

        unsigned char dummy[MAX_SAFE_STACK];

        memset(dummy, 0, MAX_SAFE_STACK);

int main(int argc, char* argv[])
        struct timespec t;
        struct sched_param param;
        int interval = 1000000; // ns

        /* Declare ourself as a real time task */

        param.sched_priority = MY_PRIORITY;
        if(sched_setscheduler(0, SCHED_FIFO, &amp;param) == -1) {
                perror("sched_setscheduler failed");

        /* Lock memory */

        if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
                perror("mlockall failed");

        /* Pre-fault our stack */


        clock_gettime(CLOCK_MONOTONIC ,&amp;t);
        /* start after one second */

  int g,rep;

  // Set up gpi pointer for direct register access

  * You are about to change the GPIO settings of your computer.          *
  * Mess this up and it will stop working!                               *
  * It might be a good idea to 'sync' before running this program        *
  * so at least you still have your code changes written to the SD-card! *

#define DIR (0)
#define STEP (1)

  INP_GPIO(DIR); // must use INP_GPIO before we can use OUT_GPIO

  GPIO_SET = 1 << DIR;

  int state = 0;
        while(1) {
                /* wait until next shot */
                clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &amp;t, NULL);

                /* do the stuff */
    if (state) {
      GPIO_SET = 1 << STEP;
    } else {
      GPIO_CLR = 1 << STEP;
    state = !state;

                /* calculate next shot */
                t.tv_nsec += interval;

                while (t.tv_nsec >= NSEC_PER_SEC) {
                       t.tv_nsec -= NSEC_PER_SEC;

// Set up a memory regions to access GPIO
void setup_io()
   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");

   /* mmap GPIO */
   gpio_map = mmap(
      NULL,             //Any adddress in our space will do
      BLOCK_SIZE,       //Map length
      PROT_READ|PROT_WRITE,// Enable reading &amp; writting to mapped memory
      MAP_SHARED,       //Shared with other processes
      mem_fd,           //File to map
      GPIO_BASE         //Offset to GPIO peripheral

   close(mem_fd); //No need to keep mem_fd open after mmap

   if (gpio_map == MAP_FAILED) {
      printf("mmap error %d\n", (int)gpio_map);//errno also set!

   // Always use volatile pointer!
   gpio = (volatile unsigned *)gpio_map;

} // setup_io

Viewing all articles
Browse latest Browse all 94

Latest Images

Trending Articles

Latest Images