Stepper Motor Control

The next motor I worked on was the stepper motor which works by turning on multiple coils in a sequence to turn the motor. The code I started with used bit banging to rotate the stepper in one direction at one speed. This code can be found here. First thing I did with this code was to have it react to a sensor, in this case a light dependent resistor(LDR), the LDR used was the NORP12 RS. Using two LDRs and the analog digital converter(ADC) to have the stepper to point in the direction of a light source, the LDRs would be mounted on the stepper so if one LDR was reading more light the stepper would move in that direction until the two LDRs were were reading the same amount of light. The code is shown below:

//
// dsPIC30F4011 example - The stepper motor points in
// the direction of the strongest light on two LDRs
// Written by Ronan Byrne, Adapted from code written by Ted Burke
// Last updated 17-10-2015
//

#include <xc.h>
#include <stdio.h>
#include <libpic30.h>

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
_FWDT(WDT_OFF);                  // Watchdog timer off
_FBORPOR(MCLR_DIS);              // Disable reset pin

unsigned int read_analog_channel(int n);
int main(void)
{

    // Configure analog inputs
    TRISB = 0x01FF;      // Port B all inputs
    ADPCFG = 0xFF00;     // Lowest 8 PORTB pins are analog inputs
    ADCON1 = 0;          // Manually clear SAMP to end sampling, start conversion
    ADCON2 = 0;          // Voltage reference from AVDD and AVSS
    ADCON3 = 0x0005;     // Manual Sample, ADCS=5 -> Tad = 3*Tcy = 0.1us
    ADCON1bits.ADON = 1; // Turn ADC ON

    // Setup UART
    U1BRG = 48;            // 38400 baud @ 30 MIPS
    U1MODEbits.UARTEN = 1; // Enable UART

    // Make RD0-3 digital outputs
    TRISD = 0b0000;
    // Declare variables for LDR values
    int v1, v2, V3;

    // Check values of LDRs and turn stepper in diretion of higher value
    while(1)
    {
        // Read the analog channels. The result is an
        // integer between 0 and 1023 inclusive.
        v1 = read_analog_channel(0);
        v2 = read_analog_channel(1);
        V3 = v1 - v2; // Differance between the LDR values
        // Print values
        printf("V1 = %d, V2 = %d, V3 = %d\n", v1,v2, V3);

        // If the right LDR is reading higher turn right
        if( V3 > 50)
        {
            LATD = 0b0001; __delay32(67500);
            LATD = 0b0010; __delay32(67500);
            LATD = 0b0100; __delay32(67500);
            LATD = 0b1000; __delay32(67500);
        }
        // If the left LDR is reading higher turn left
        else if ( V3 < -50)
        {
            LATD = 0b1000; __delay32(67500);
            LATD = 0b0100; __delay32(67500);
            LATD = 0b0010; __delay32(67500);
            LATD = 0b0001; __delay32(67500);
        }
        // Else stay put
        else
        {
            LATD = 0b0000;
        }
    }
    return 0;
}

// This function reads a single sample from the specified
// analog input. It should take less than 2.5us if the chip
// is running at about 30 MIPS.
unsigned int read_analog_channel(int channel)
{
    ADCHS = channel;          // Select the requested channel
    ADCON1bits.SAMP = 1;      // start sampling
    __delay32(30);            // 1us delay @ 30 MIPS
    ADCON1bits.SAMP = 0;      // start Converting
    while (!ADCON1bits.DONE); // Should take 12 * Tad = 1.2us
    return ADCBUF0;
}

Unfortunately I forgot to get a video of this working before moving onto the next part.

When I got that working I decided to try use the LDRs to pin point the direction of the light and get the stepper to point in that direction, to do this I got two more LDRs and had them point in four directions. From reading the values from the LDRs I can get the angle of the light and then tell the stepper to move to this angle. First I needed to pick the right resistor value to get the most range and most accuracy over a range of lux values of the LDRs, to do this I plotted lux values against the voltage divider voltage using a few different values of resistance, the graph is shown below:
Voltage Divider Voltages
From the graph it can be seen that the 1000 ohm resistor has the largest range and most linearity over the range of lux values shown, so I used this 1000 ohms for my voltage divider. The final circuit can be seen below:

ldr stepper.png

To find the direction of the light I used trigonometry. When reading the LDR values I would pick the LDR with the higher value for each axis and then find the angle from this. An example of this is shown below:
LDR Trig Example

To turn the stepper to a certain angle I would need to find how many steps it takes for one full rotation, I found this to be 512 steps. So from this I found the angle per step to be 1.42steps/°, one step is when all the coils have been energized once, so for more accuracy I would increase the steps variable by 0.25 every time a coil was energized. By multiplying the steps/° by the angle of the light, the stepper would move to that angle. The final code is shown below:

//
// dsPIC30F4011 example - The stepper motor points in
// the direction of the strongest light.
// The strongest light is determined by four LDRs.
// Written by Ronan Byrne, Adapted from code written by Ted Burke
// Last updated 05-11-2015
//

#include <xc.h>
#include <stdio.h>
#include <libpic30.h>
#include <math.h>

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
_FWDT(WDT_OFF);                  // Watchdog timer off
_FBORPOR(MCLR_DIS);              // Disable reset pin

unsigned int read_analog_channel(int n);
float rotate_left(float steps), rotate_right(float steps);

int main(void)
{

    // Configure analog inputs
    TRISB = 0x01FF;      // Port B all inputs
    ADPCFG = 0xFF00;     // Lowest 8 PORTB pins are analog inputs
    ADCON1 = 0;          // Manually clear SAMP to end sampling, start conversion
    ADCON2 = 0;          // Voltage reference from AVDD and AVSS
    ADCON3 = 0x0005;     // Manual Sample, ADCS=5 -> Tad = 3*Tcy = 0.1us
    ADCON1bits.ADON = 1; // Turn ADC ON

    // Setup UART
    U1BRG = 48;            // 38400 baud @ 30 MIPS
    U1MODEbits.UARTEN = 1; // Enable UART

    // Make RD0-3 digital outputs
    TRISD = 0b0000;
    // Declare variables
    int v1, v2, v3, v4; // LDR voltages
    // Variables used for the maths
    float delta_angle, angle1, step_angle, step_circle, x, y;
    float pi = 3.14159265359;
    float angle0 = 0.0;
    float steps = 0.0;
    // Calculate Step per degree
    step_circle = 512.0/360.0;

    // Check the LDR values and point stepper in
    // direction of strongest source of light
    while(1)
    {
        // Read the analog channels. The result is an
        // integer between 0 and 1023 inclusive.
        v1 = read_analog_channel(0);
        v2 = read_analog_channel(1);
        v3 = -1*read_analog_channel(2);
        v4 = -1*read_analog_channel(3);
        // Print values of the LDR values

        // Check which Y value is larger
        if((v2 + v4) > 0) y = v2 * 1.0;
        else if ((v2 + v4) == 0) y = 0.0;
        else y = v4 * 1.0;
        // Check which X value is larger
        if((v1 + v3) > 0) x = v1 * 1.0;
        else if ((v1 + v3) == 0) x = 0.0;
        else x = v3 * 1.0;

        // Calculate the angle of the light
        angle1 = (atan2(y,x) * 180.0)/(pi);
        // Calculate differance in position
        delta_angle = angle1 - angle0;
        if( delta_angle < 1.25 && delta_angle >-1.25)
        {
            delta_angle = 0.0;
        }

        // Set new angle as new position
        angle0 = angle1;
        // Calculate number of steps for angle
        step_angle = step_circle * delta_angle;
        // Print calculated values
        printf("x = %f, y = %f, Angle = %f\n\n",x,y,angle1);
        __delay32(3000000);

        steps = 0.0;

        // Turn right until stepper is pointing at light angle
        while(step_angle <  (steps-2.5))
        {
            steps = rotate_right(steps);
        }
        // Turn left until stepper is pointing at light angle
        while(step_angle > (steps+2.5))
        {
            steps = rotate_left(steps);
        }
        LATD = 0b0000;
    }
    return 0;
}

// Rotate the stepper right and decrement the steps count with each coil energized
float rotate_right(float steps)
{
    LATD = 0b0001; __delay32(67500);
    steps = steps - 0.25;
    LATD = 0b0010; __delay32(67500);
    steps = steps - 0.25;
    LATD = 0b0100; __delay32(67500);
    steps = steps - 0.25;
    LATD = 0b1000; __delay32(67500);
    steps = steps - 0.25;
    return steps;
}
// Rotate the stepper left and increment the steps count with each coil energized
float rotate_left(float steps)
{
    LATD = 0b1000; __delay32(67500);
    steps = steps + 0.25;
    LATD = 0b0100; __delay32(67500);
    steps = steps + 0.25;
    LATD = 0b0010; __delay32(67500);
    steps = steps + 0.25;
    LATD = 0b0001; __delay32(67500);
    steps = steps + 0.25;
    return steps;
}
// This function reads a single sample from the specified
// analog input. It should take less than 2.5us if the chip
// is running at about 30 MIPS.
unsigned int read_analog_channel(int channel)
{
    ADCHS = channel;          // Select the requested channel
    ADCON1bits.SAMP = 1;      // start sampling
    __delay32(30);            // 1us delay @ 30 MIPS
    ADCON1bits.SAMP = 0;      // start Converting
    while (!ADCON1bits.DONE); // Should take 12 * Tad = 1.2us
    return ADCBUF0;
}

The stepper continuously searches for the source of light and whenever a new angle of light is seen it will move to that angle, this can cause problems when there is a false reading causing the stepper to move to this angle but it should return to the right angle afterwards. Also because the LDRs will rarely read 0, the stepper rarely ever moves to 0, 90, 180 or 270 degrees even if the light comes from these angles but this only adds a small inaccuracy which is fine for this project. A video below shows the circuit in action:

This could be used for solar panels, tracking the sun to increase their efficiency.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s