...making Linux just a little more fun!

<-- prev | next -->

# Stepper motor driver for your Linux Computer

By Sreejith N

### An introduction intended for people with no prior device driver knowledge

This article is intended for those newbie Linux users who wish to use their Linux-box for some real work. I will also share some interesting experiments that I did with my AMD machine.

### INIT

Learning new stuff is fun, but can be a bit frustrating. So, you want to write a device driver. The name itself is high-tech! You have some skills in the C programming language and want to explore the same. Also, you've written a few normal programs to run as processes in user space, and now you want to enter kernel space - where the real action takes place. Why Linux device drivers? The answer is,

- For fun
- For profit (Linux is HOT right now, especially embedded Linux)
- Because you can! The source is with you.

Although it is possible to learn device driver coding by reading some books and PDFs written by the masters, this is a complicated and time-consuming approach. We will take the quick and easy approach, which is:

- Find some pre-written, working code
- Understand how this code works
- Modify it to suit our needs

Let's make an easy start with some fundamentals.

### Stepper motor basics

Stepper motors are special direct-current (DC) motors, typically used in applications like camera zoom drive and film feed, fax machines, printers, copying machines, paper feeders/sorters, disk drives and robotics.

A DC stepper motor translates current pulses into motor rotation. A typical unipolar (single voltage) motor contains four winding coils. Applying voltage to these coils forces the motor to advance one step. In normal operation, two winding coils are activated at the same time, causing the motor to move one step clockwise. If the sequence is applied in reverse order, the motor will run counterclockwise. The speed of rotation is controlled by the frequency of the pulses.

A typical full step rotation is 1.8 degrees, or 200 steps per rotation (360 degrees). By changing the time delay between successive steps, the speed of the motor can be regulated, and by counting the number of steps, the rotation angle can be controlled.

Bit Pattern for Full Step Mode
| Green | Blue | Orange | Red | Hex Output Value
Step 0 | 1 | 0 | 1 | 0 | A
Step 1 | 1 | 0 | 0 | 1 | 9
Step 2 | 0 | 1 | 0 | 1 | 5
Step 3 | 0 | 1 | 1 | 0 | 6

### Hardware ideas

The circuit diagram for the drive is shown below.

The circuit consists of four TIP122 power transistors (T1, T2, T3 and T4), 220Ω resistors (R1, R2, R3 and R4), 3.3KΩ resistors (R5, R6, R7 and R8), 1N4148 freewheeling diodes (D1, D2, D3 and D4), and one LM7407 buffer chip (IC1). The 7407 buffer used here is a hex-type open-collector high-voltage buffer. The 3.3KΩ resistors are the pull-up resistors for the open-collector buffer. The input for this buffer comes from the parallel port. The output of the buffer is of higher current capacity than the parallel port output, which is necessary for triggering the transistor; it also isolates the circuit from the PC parallel port and hence provides extra protection against potentially dangerous feedback voltages that may occur if the circuit fails. The diode connected across the supply and the collector is used as a freewheeling diode and also to protect the transistor from the back EMF of the motor inductance. The motor used in my experiments (and documented here) was an STM 901 from Srijan Control Drives.

During normal operation, the output pattern from the PC drives the buffer, and corresponding transistors are switched on. This leads to the conduction of current through those coils of the stepper motor which are connected to the energized transistor. This makes the motor move forward one step. The next pulse will trigger a new combination of transistors, and hence a new set of coils, leading to the motor moving another step. The scheme of excitation that we have used here has already been shown above.

### How do we interface the hardware with the Linux-box?

You can use either the parallel port or the serial port for this purpose. We will be using parallel port as a digital interface between PC and the hardware (stepper motor drive). The parallel port can be considered as a register, and the I/O operations can be done simply by writing bit patterns (numbers like 0xA, 10, '1010', etc.) to this register. The base address of parallel port is 0x378. The PC parallel port is a 25 pin D-shaped female connector in the back of the computer. It is normally used for connecting computer to printer, but many other types of hardware for that port are available.

The original IBM PC's Parallel Printer Port had a total of 12 digital outputs and 5 digital inputs accessed via 3 consecutive 8-bit ports in the processor's I/O space.

1. 8 output pins accessed via the DATA Port
2. 5 input pins (one inverted) accessed via the STATUS Port
3. 4 output pins (three inverted) accessed via the CONTROL Port
4. The remaining 8 pins are grounded

To read a byte (8 bits) coming into a port, call `inb (port)`; to output a byte, call `outb (value, port)` (please note the order of the parameters). The data outputs are provided by an 74LS374 totem-pole TTL integrated circuit, which can source (+) 2.6 mA and sink (-) 24 mA. The best option for preventing damage to the port is to use optocouplers; by doing so, the port is completely electrically isolated from external hardware devices.

### What is a module?

Modules are pieces of code that can be loaded into and unloaded from a running kernel upon demand. They extend the functionality of the kernel without the need to reboot the system. Device drivers are a class of modules which allows the kernel to control hardware connected to the system. In this article, I have written a simple device driver to control a stepper motor drive; now it is time to log on to your console and start coding your very first module.

Let's have an unusual start! You have written so many "Hello, World" programs. So this time something different - "No gates, No windows, it's open". You will get these words printed on your console when you insert your first module.

    /*mymodule.c - The simplest kernel module.*/
    #include <linux/module.h> /* Needed by all modules */
    #include <linux/kernel.h> /* Needed for KERN_ALERT */

    int init_module(void)
    {
            printk("<1>No gates, No windows, it's open\n");
            return 0;/* A non-0 return means init_module failed; module can't be loaded.*/
    }

    void cleanup_module(void)
    {
            printk("Goodbye\n");
    }

The "start" function in a kernel module is init_module () which is called when the module is insmoded into the kernel, and the "end" (cleanup) function cleanup_module() is called just before it is rmmoded.

### Compiling kernel modules

Kernel modules need to be compiled with certain GCC options to make them work. In addition, they also need to be compiled with certain symbols defined. This is because kernel header files need to behave differently, depending on whether we're compiling a kernel module or executable.

1. -C: A kernel module is not an independent executable, but an object file which will be linked into the kernel during runtime using insmod.
2. -O2: The kernel makes extensive use of inline functions, so modules must be compiled with the optimization flag turned on. Without optimization, some of the assembler macro calls will fail. This will cause loading the module to fail, since insmod won't find those functions in the kernel.
3. -D_KERNEL_: Defining this symbol tells the header files that the code will be run in kernel mode, not as a user process.
4. -W -Wall : Enable All Warnings.

### Makefile (2.4.24 kernel)

As an example, let's take a look at the options we're going to use in compiling the "stepper" module that we'll see a little later in this article:

    TARGET := stepper
    WARN   := -W -Wall
    INCLUDE:= /usr/src/linux-2.4/include
    #INCLUDE:= -isystem /usr/src/`uname -r`/include
    CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} -I${INCLUDE}
    all : stepper.o
    #${TARGET}.o : ${TARGET}.c
    clean:
            rm -rf *.o

You can learn more about `make` utility by reading "man make".

### Anatomy of device drivers

There is a lot of documentation on the Web, including PDFs and ebooks, on device drivers; also, you can download some useful guides from The Linux Documentation Project website. For the time being, just read these points carefully; later, you can move on to some detailed references.

- A device driver has two sides. One side talks to the rest of the kernel and to the hardware, and the other side talks to the user.
- To talk to the kernel, the driver registers with subsystems to respond to events. Such an event might be the opening of a file, writing some useful data, the plugging in of a pen-drive (USB device), etc.
- Since Linux is a type of UNIX, and in UNIX everything is a file, users talk with device drivers through device files, which are like a digital representation of a hardware device.
- A stepper is a character device, and thus the user talks to it through a character device file. The other common type of device file is block. We will only discuss character device files in this article.
- The user controls the stepper motor through the /dev/stepper device file
- When the user opens /dev/stepper, the kernel calls stepper's open routine
- When the user closes /dev/stepper, the kernel calls stepper's release routine
- When the user reads or writes from or to /dev/stepper - I think you got the idea...

Now let's examine our code.

    #define MODULE

    #include <linux/module.h>
    #include <asm/uaccess.h>
    #include <sys/io.h>
    #include <linux/fs.h>

    #define LPT_BASE 0x378
    #define DEVICE_NAME "stepper"

    static int Major,i,j,k;
    static int Device_Open = 0;

    //static int pattern[2][8][8] = {
    //        {{0xA,0x9,0x5,0x6},{0xA,0x8,0x9,0x1,0x5,0x4,0x6,0x2}},
    //        {{0x6,0x5,0x9,0xA},{0x2,0x6,0x4,0x5,0x1,0x9,0x8,0xA}}
    //};

    static int pattern[2][8][8] = {
            {{0xA,0x9,0x5,0x6,0xA,0x9,0x5,0x6},{0xA,0x8,0x9,0x1,0x5,0x4,0x6,0x2}},
            {{0x6,0x5,0x9,0xA,0x6,0x5,0x9,0xA},{0x2,0x6,0x4,0x5,0x1,0x9,0x8,0xA}}
    };

    int step()
    {
            if(k<8) {
    //              if(pattern[i][j][k]==0) {
    //                      k=0;
    //                      printk("%d\n",pattern[i][j][k]);
    //                      k++;
    //              }
    //              else {
                            printk("%d\n",pattern[i][j][k]);
                            k++;
    //              }
            }
            else  {
                    k=0;
                    printk("%d\n",pattern[i][j][k]); /*#####*/
                    k++; /*#####*/
            }
            return 0;
    }

    static int stepper_open(struct inode *inode,struct file *filp)
    {
            static int counter = 0;
            if(Device_Open) return -EBUSY;
            printk("Opening in WR mode...\n");
            Device_Open++;
            MOD_INC_USE_COUNT;
            return 0;
    }

    static int stepper_release(struct inode *inode,struct file *filp)
    {
            printk("Clossing...\n");
            Device_Open --;
            MOD_DEC_USE_COUNT;
            return 0;
    }
    static int stepper_write(struct file *file, const char *buffer, size_t len,
    loff_t *offset)
    {
            char *data;
            char cmd;
            get_user(data,buffer);
            switch (cmd=data) {
                    case 'H':
                            printk("Reffer README file\n");
                            break;
                    case 'h':
                            printk("Half-Step mode initialized\n");
                            j=0;
                            break;
                    case 'f':
                            printk("Full-Step mode initialized\n");
                            j=1;
                            break;
                    case 'F':
                            i=0;
                            step();
                            break;
                    case 'R':
                            i=1;
                            step();
                            break;
    //              default:
    //                      printk("Give 'H' for Help\n");
    //                      break;
            }
            return 1;
    }

    static struct file_operations fops={
            open:stepper_open,
            write:stepper_write,
            release:stepper_release,
    };

    int init_module(void)
    {
            Major = register_chrdev(0, DEVICE_NAME, &fops);
            if (Major < 0) {
                    printk("<1>Registering the character device failed with %d
    \n",Major);
                    return Major;
            }
            printk("<1>Registered, got Major no= %d\n",Major);
            return 0;
    }

    void cleanup_module(void)
    {
            printk("<1>Unregistered\n");
            unregister_chrdev(Major,DEVICE_NAME);
    }

Follow this link to download the code.

### Driver initialization

1. The init_module() function is called on the driver's initialization
2. The cleanup_module () function is called when the driver is removed from the system
3. The init function will register hooks that will get the driver's code called when the appropriate event happens
4. There are various hooks that can be registered: file operations, PCI operations, USB operations, network operations. Ours is a file operation.
5. The driver registers a character device tied to a given major number and a user can create access points corresponding to this major number.The following command will do it for you:

    mknod /dev/stepper c 254 0

### How stuff works

A user space program can write commands to the device file to rotate the stepper motor through a desired angle at desired speed. The speed of rotation depends upon the delay given in the user program.

The built in commands for controlling the stepper motor is given below.

- H ----------- Help
- h ----------- Half-step mode
- f ----------- Full-step mode
- F ----------- Rotate one step clockwise
- R ----------- Rotate one step anti-clockwise

### File operations

The driver makes use of the following device file operations:

1. open for allocating resources
2. release for releasing resources
3. write the required pattern to the parallel port.
4. there is no reading in our program, but if you want, you can read the current pattern at the parallel port.

If you write 'F' once to "/dev/stepper", the motor will rotate through its minimum step-angle. If you keep on writing 'F' to "/dev/stepper", it will rotate continuously. The "write" system call will do this for you.

    #include "header.h"

    main ()
    {
            char t,buf[6] = {'h','f','F','R','H','q'};
            int fd,rt,i,j;
            size_t count;
            printf("Select Mode \n(1) [Half-step clockwise]\n(2) [Half-step
    anti-clockwise]\n(3) [Full-step clockwise]\n(4) [Full-step anti-clockwise]  :
    ");
            t=getchar();
            if(t=='1') {i=0; j=2;}
            else if(t=='2') {i=0; j=3;}
            else if(t=='3') {i=1; j=2;}
            else {i=1; j=3;}
            fd=open("stepper",O_WRONLY);
            rt=write(fd,&amp;buf[i],count);
            for(i=0;i<1000;i++) {
                    rt=write(fd,&buf[j],count);
                    usleep (100000);
            }
            close(fd);
    }

Also, if you are familiar with shell scripting, you can do the same by writing a simple shell script. Now you can start talking to the device /dev/stepper. It will be really interesting if you talk in Linux's language - I mean a shell script. Just use simple echo commands as given below:

    echo H > /dev/stepper

Do you think that Morpheus is talking to you? Your kernel is replying to your commands. How's that! Now, you too can feel like you are The One.

### Conclusion

I hope I have given you some basics of device driver coding and perhaps a "small step toward Robotics". Here is a detailed schematic of a stepper-controlled robotic arm; feel free to try it out. One can connect three stepper motors simultaneously to the PC parallel port and can achieve step-wise mobility; this allows anyone to start thinking of complex innovative mobility with multi-threaded programming in Linux. You can also add C functions to our module to enhance its functionality... the possibilities are endless!

I am a Linux enthusiast living in India. I enjoy the freedom and power that Linux offers. I must thank my mentor Mr. Pramode C. E. for introducing me to the wonderful world of Linux.

I completed my B-Tech in Electrical and Electronics Engineering from Govt. Engineering College, Thrissur (Kerala, India) (2001 - 2005). Presently I am working in inDSP Audio Technologies Pvt. Ltd, Trivandrum, India as an Embedded Systems Engineer.

I spend my free time reading books on Linux and exploring the same. My other areas of interest include device drivers, embedded systems, robotics and process control.

Copyright © 2006, Sreejith N. Released under the Open Publication license unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 122 of Linux Gazette, January 2006

<-- prev | next -->