Introducing Robotics 3.1…

Robotics 3.1 is an optional module available to final-year students of the level 7 Electrical & Control Engineering programme (DT009) in the Dublin Institute of Technology. The learning outcomes of the module are:

  • Explain, carry out analysis and solve problems associated with fundamental electrical and mechanical engineering science,
  • List, outline and discuss comparative features of robot technology,
  • Explain and compare the operation, characteristics, and application of robot actuators,
  • Design, build and test electric robot actuator control subsystems,
  • Explain and compare the operation, application and characteristics of robot environment sensors,
  • Design, build and test robot environment sensing subsystems,
  • Design, build and test computer and microcontroller controlled robot subsystems.

The module is very practically focused and involves implementing various robot subsystems. Each student also undertakes a mini-project in which he or she designs, builds and tests a small robotic system.

The revision material presented here is divided into the following sections:

  1. Introduction to robotic technology
  2. Fundamental electrical, mechanical and control principles
  3. The dsPIC microcontroller
  4. Actuators and power electronics

To assist in exam preparation, the structure of each section combines revision notes with additional content in question and answer format.

Standard Hardware Kit for Robotics 3.1

  • 1 ziploc bag
  • 1 geared dc motor
  • 1 stepper motor
  • 1 servo motor
  • 1 breadboard
  • 1 PICkit2
  • 1 USB to mini USB cable
  • 1 dsPIC30F4011 microcontroller
  • 1 SN754410NE quad half H-bridge IC
  • 2 BC237 NPN transistors (or similar)
  • 1 6-pin header
  • A few LEDs
  • A few resistors
  • 1 potentiometers
  • 1 4xAA battery holder
Posted in Uncategorized | Leave a comment

Great video about the 28BYJ-48 stepper motor

I just came across this excellent video about the 28BYJ-48 stepper motor by Bret Stateham. It does a really outstanding job of explaining what’s going on inside that metal case! Thanks to Cormac McConigley for bringing this to my attention.

Posted in Uncategorized | Leave a comment

Timer Interrupt examples for dsPIC30F4011

This post features two timer interrupt examples for the dsPIC30F4011 microcontroller.

The first example just illustrates the bare essentials of setting up a Timer 1 interrupt and using it to increment a millisecond counter variable. The interrupt service routine (ISR), _T1Interrupt(), runs every 1ms. It simply resets the interrupt flag bit and then increments a global variable, ms_time. When ms_time reaches 1000 it is reset to 0. The main function switches an LED on or off depending on the value of ms_time. The net result is that the LED flashes on for 500ms and then off for 500ms over and over again.

//
// dsPIC30F4011 simple timer interrupt example
// Written by Ted Burke, Last updated 30-11-2015
//
 
#include <xc.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

int ms_time = 0; // used to count milliseconds

// Timer 1 interrupt service routine (ISR)
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
{
    // Clear Timer 1 interrupt flag
    IFS0bits.T1IF = 0;
 
    // Increment the millisecond count
    ms_time = ms_time + 1;
    if (ms_time == 1000) ms_time = 0;
}

int main(void)
{
    _TRISC14 = 0;   // RC14 (pin 16) controls an LED
    
    // Configure Timer 1
    // Set PR1 and TCKPS to cause interrupt every 1ms
    PR1 = 30000;          // Set the Timer 1 period to 1ms
    T1CONbits.TCKPS = 0;  // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
    IEC0bits.T1IE = 1;    // Enable Timer 1 interrupt
    T1CONbits.TON = 1;    // Turn on Timer 1

    // Now just use ms_time value to make an LED flash
    while(1)
    {
        if (ms_time < 500) _LATD14 = 0; // LED off
        else _LATD14 = 1;               // LED on
    }
}

This second example is more complicated. In this case, the timer ISR not only increments the millisecond counter, ms_time, but it also looks after stepping the stepper motor backwards or forwards until it reaches a target angle determined by the global variable stepper_target. The timer ISR runs every 1ms, which is more frequent than the maximum stepping frequency of the stepper, so I used a little counter trick in the ISR to only step the motor every 3rd time an interrupt occurs. Of course, the stepper only steps when its current position is different from the target angle.

The actual sequence of stepper positions is determined by a simple state machine with four states. There’s nothing particularly significant about the angles I chose here – I just wanted to illustrate the principle of controlling stepper angle just by assigning a value to a global step counter variable and then leaving the timer ISR to do the job of moving the stepper to that angle one step at a time.

Here’s the full C code:

//
// dsPIC30F4011 stepper motor timer interrupt example
// Written by Ted Burke, Last updated 30-11-2015
//
 
#include <xc.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

int state = 1;            // State variable for state machine
int ms_time = 0;          // Counts milliseconds in current state

int stepper_target = 0;   // Assign a value to this to set stepper angle
int stepper_position = 0; // Used to count steps towards target value
int count = 0;            // Counts timer interrupts - step every 4th time

void change_state(int);   // Updates state variable and resets ms_time
void step_forward();      // Steps forward and increments stepper_position
void step_back();         // Steps backwards and decrements stepper_position
void led(int);            // Switch LED on or off

int main(void)
{
    // Digital outputs
    _TRISC14 = 0;   // RC14 (pin 16) controls an LED
    TRISD = 0b0000; // RD0-3 control the stepper motor
    
    // Configure Timer 1
    // Set PR1 and TCKPS to cause interrupt every 1ms
    PR1 = 30000;          // Set the Timer 1 period to 1ms
    T1CONbits.TCKPS = 0;  // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
    IEC0bits.T1IE = 1;    // Enable Timer 1 interrupt
    T1CONbits.TON = 1;    // Turn on Timer 1
    
    // This simple state machine moves the stepper
    // through a repeating sequence of 4 positions.
    // The state changes every 5 seconds.
    while(1)
    {
        if (state == 1)
        {
            led(1);                // LED on
            stepper_target = 0;    // stepper at 0 degrees
            if (ms_time >= 5000) change_state(2);
        }
        else if (state == 2)
        {
            led(0);                // LED off
            stepper_target = 1024; // stepper at 180 degrees
            if (ms_time >= 5000) change_state(3);
        }
        else if (state == 3)
        {
            led(1);                // LED on
            stepper_target = 1536; // stepper at 270 degrees
            if (ms_time >= 5000) change_state(4);
        }
        else if (state == 4)
        {
            led(0);                // LED off
            stepper_target = 512;  // stepper at 90 degrees
            if (ms_time >= 5000) change_state(1);
        }
    }
}

// Timer 1 interrupt service routine (ISR)
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
{
    // Clear Timer 1 interrupt flag
    IFS0bits.T1IF = 0;
 
    // Increment the millisecond count
    ms_time = ms_time + 1;
    
    // Stepping every 1ms would be too often for the stepper,
    // so only step once every 3 interrupts.
    count = count + 1;
    if (count == 3)
    {
        // This only happens every 3rd interrupt - i.e. once every 3ms
        count = 0;
        if (stepper_position < stepper_target) step_forward();
        if (stepper_position > stepper_target) step_back();
    }
}

// This function updates the global state variable and resets
// the millisecond counter.
void change_state(int n)
{
    state = n;
    ms_time = 0;
}

// Move stepper motor forward one full step
void step_forward()
{
    if      (LATD == 0b0001) LATD = 0b0010;
    else if (LATD == 0b0010) LATD = 0b0100;
    else if (LATD == 0b0100) LATD = 0b1000;
    else if (LATD == 0b1000) LATD = 0b0001;
    else LATD = 0b0001;
    stepper_position = stepper_position + 1;
}

// Move stepper motor back one full step
void step_back()
{
    if      (LATD == 0b0001) LATD = 0b1000;
    else if (LATD == 0b0010) LATD = 0b0001;
    else if (LATD == 0b0100) LATD = 0b0010;
    else if (LATD == 0b1000) LATD = 0b0100;
    else LATD = 0b0001;
    stepper_position = stepper_position - 1;
}

void led(int on)
{
    if (on) _LATC14 = 1;
    else _LATC14 = 0;
}

dsPIC

Posted in Uncategorized | 1 Comment

Stepper robot example from today’s class

//
// stepperbot.c - Stepper robot example from class
// Left motor is on RD-3, right motor is on RE0-3
//
// Written by Ted Burke - last updated 17-11-2015
//

#include <xc.h>
#include <libpic30.h>
 
// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF);                  // Watchdog timer off
_FBORPOR(MCLR_DIS);              // Disable reset pin

void forward(float);
void turn_left(float);
void turn_right(float);
void left_step_forward();
void left_step_back();
void right_step_forward();
void right_step_back();

const float mm_per_step = 0.2356;
const float degrees_per_step = 0.225;

int main()
{
    // Set up which pins are which
    TRISD = 0b1111111111110000;
    TRISE = 0b1111111111110000;
    
    while(1)
    {
        // Forward around half octagon
        forward(480);
        turn_right(45);
        forward(480);
        turn_right(45);
        forward(480);
        turn_right(45);
        forward(480);
        turn_right(180); // U turn
        
        // Back to beginning
        forward(480);
        turn_left(45);
        forward(480);
        turn_left(45);
        forward(480);
        turn_left(45);
        forward(480);
        turn_left(180); // U turn
    }

    return 0;
}

void forward(float millimetres)
{
    int steps = millimetres / mm_per_step;
    while(steps > 0)
    {
        left_step_forward();
        right_step_forward();
        __delay32(60000);
        steps = steps - 1;
    }
}

void turn_left(float degrees)
{
    int steps = degrees / degrees_per_step;
    while(steps > 0)
    {
        left_step_back();
        right_step_forward();
        __delay32(60000);
        steps = steps - 1;
    }
}

void turn_right(float degrees)
{
    int steps = degrees / degrees_per_step;
    while(steps > 0)
    {
        left_step_forward();
        right_step_back();
        __delay32(60000);
        steps = steps - 1;
    }
}

void left_step_forward()
{
    if      (LATD == 0b0001) LATD = 0b0010;
    else if (LATD == 0b0010) LATD = 0b0100;
    else if (LATD == 0b0100) LATD = 0b1000;
    else if (LATD == 0b1000) LATD = 0b0001;
    else LATD = 0b0001;
}

void left_step_back()
{
    if      (LATD == 0b0001) LATD = 0b1000;
    else if (LATD == 0b0010) LATD = 0b0001;
    else if (LATD == 0b0100) LATD = 0b0010;
    else if (LATD == 0b1000) LATD = 0b0100;
    else LATD = 0b0001;
}

void right_step_forward()
{
    if      (LATE == 0b0001) LATE = 0b0010;
    else if (LATE == 0b0010) LATE = 0b0100;
    else if (LATE == 0b0100) LATE = 0b1000;
    else if (LATE == 0b1000) LATE = 0b0001;
    else LATE = 0b0001;
}
void right_step_back()
{
    if      (LATE == 0b0001) LATE = 0b1000;
    else if (LATE == 0b0010) LATE = 0b0001;
    else if (LATE == 0b0100) LATE = 0b0010;
    else if (LATE == 0b1000) LATE = 0b0100;
    else LATE = 0b0001;
}
Posted in Uncategorized | Leave a comment

State Machine Challenge

20151103_173655

robot_track

Image | Posted on by | Leave a comment

State Machine example code from class (two versions)

These are the two versions of the (incomplete) state machine example we looked at in today’s class. The first uses am if-else-if structure within the main state machine loop, whereas the second uses a separate while loop for each state, all enclosed within the main state machine while loop.

We’ll continue adding to this example in tomorrow’s class.

Firstly, here’s the if-else-if version:

//
// State machine example
//

// Function prototypes
void setup();
void set_motor_directions(int lmd, int rmd); // left and right motor directions
int read_analog_channel(int channel); // read colour sensor (FLS:0, FRS:1, BLS:2, BRS:3)

int main()
{
    setup(); // Configure pins etc.
    
    int fls, frs, bls, brs;
    int state = 1;
    
    // State machine loop
    while(1)
    {
        // Read all sensors into variables
        fls = read_analog_channel(0);
        frs = read_analog_channel(1);
        bls = read_analog_channel(2);
        brs = read_analog_channel(3);
        
        // Implement current state
        if (state == 1)
        {
            // Set motors
            set_motor_directions(1, 1);
            
            // Change state?
            if (fls > 450 && fls < 760) state = 2; // FLS is green if (frs > 450 && frs < 760) state = 3; // FRS is green if (fls > 450 && fls < 760 && frs > 450 && frs < 760) state = 8; if (frs > 760) state = 4; // FRS is white
            if (fls > 760) state = 5; // FLS is white
        }
        else if (state == 2)
        {
            // Set motors
            set_motor_directions(1, 0);
            
            // Change state?
            if (fls < 450 || fls > 760 ) state = 1; // FLS not green
        }
        else if (state == 3)
        {
            // Set motors
            set_motor_directions(0, 1);
            
            // Change state?
            if (frs < 450 || frs > 760 ) state = 1; // FLS not green
        }
        // etc etc
    }
    
    return 0;
}

Secondly, here’s the while-while-while version:

//
// State machine example
//

// Function prototypes
void setup();
void set_motor_directions(int lmd, int rmd); // left and right motor directions
int read_analog_channel(int channel); // read colour sensor (FLS:0, FRS:1, BLS:2, BRS:3)

int main()
{
    setup(); // Configure pins etc.
    
    int fls, frs, bls, brs;
    int state = 1;
    
    // State machine loop
    while(1)
    {
        // Implement current state
        while (state == 1)
        {
            // Set motors
            set_motor_directions(1, 1);
            
            // Change state?
            fls = read_analog_channel(0);
            frs = read_analog_channel(1);
            if (fls > 450 && fls < 760) state = 2; // FLS is green if (frs > 450 && frs < 760) state = 3; // FRS is green if (fls > 450 && fls < 760 && frs > 450 && frs < 760) state = 8; if (frs > 760) state = 4; // FRS is white
            if (fls > 760) state = 5; // FLS is white
        }
        
        while (state == 2)
        {
            // Set motors
            set_motor_directions(1, 0);
            
            // Change state?
            fls = read_analog_channel(0);
            if (fls < 450 || fls > 760 ) state = 1; // FLS not green
        }
        
        while (state == 3)
        {
            // Set motors
            set_motor_directions(0, 1);
            
            // Change state?
            frs = read_analog_channel(1);
            if (frs < 450 || frs > 760 ) state = 1; // FLS not green
        }
        
        // etc etc
    }
    
    return 0;
}
Posted in Uncategorized | 1 Comment

Example: control servo angle with a potentiometer

//
// Example for dsPIC30F4011 - control servo angle with potentiometer
// PWM output is on PWM1H (pin 37)
// Potentiometer input (0-5V) is on AN0 (pin 2)
// written by Ted Burke - last updated 27-10-2015
//

#include <xc.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 PWM
    // PWM period = PTPER * prescale * Tcy = 9470 * 64 * 33.33ns = 20ms
    _PMOD1 = 0;   // PWM channel 1 mode: 0 for complementary, 1 for independent
    _PEN1H = 1;   // PWM1H pin enable: 1 to enable, 0 to disable
    _PTCKPS = 3;  // PWM prescaler setting: 0=1:1, 1=1:4, 2=1:16, 3=1:64
    PTPER = 9470; // Set PWM time base period to 20ms (15-bit value)
    _PTEN = 1;    // Enable PWM time base to start generating pulses

    // Configure AN0-AN8 as analog inputs
    ADCON3bits.ADCS = 15;  // Tad = 266ns, conversion time is 12*Tad
    ADCON1bits.ADON = 1;   // Turn ADC ON
    
    // Move through fixed sequence
    int v;
    while(1)
    {
        // v is between 0 and 1023
        v = read_analog_channel(0);
        
        // PWM pulse width is between 1ms (PDC1 = 947) and 2ms (PDC1 = 1894)
        PDC1 = 947 + 947*(v/1023.0);
    }
}

// This function reads a single sample from the specified
// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
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 = 3.2us
    return ADCBUF0;
}
Posted in Uncategorized | 1 Comment

SCARA fixed sequence example code

//
// SCARA robot manipulator fixed sequence example for dsPIC30F4011
// written by Ted Burke - last updated 19-10-2015
//

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

#define PI 3.1415926535897

// 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

double L1 = 0.25;             // 25 cm
double L2 = 0.20;             // 20 cm
double pdc_0_degrees = 1.0;   // 1.0 ms gives 0 degrees
double pdc_180_degrees = 2.0; // 2.0 ms gives 180 degrees

// Define a fixed sequence of target coordinates
double T_step = 1.0; // moves point every 1 second
int N = 4; // number of points
double x[] = {10.0, 12.0, 15.0, 19.0};
double y[] = {12.0, 8.0, 13.0, 13.0};

// function prototypes
void move_to(double x, double y);
void setup();

int main(void)
{
    setup();
    
    // Move through fixed sequence
    int n = 0;
    while(1)
    {
        move_to(x[n], y[n]);
        __delay32(T_step * 30000000L);
        n = n + 1;
        if (n >= 4) n = 0;
    }
}

void move_to(double x, double y)
{
    double S, E;
    
    // inverse kinematic equations
    E = acos( (x*x + y*y - L1*L1 - L2*L2) / (2*L1*L2) );
    S = atan2(y, x) - acos ( (x*x + y*y + L1*L1 - L2*L2) / (2*L1*sqrt(x*x+y*y)) );
    
    // Set servo angles
    PDC1 = pdc_0_degrees + (pdc_180_degrees - pdc_0_degrees) * S / PI;
    PDC2 = pdc_0_degrees + (pdc_180_degrees - pdc_0_degrees) * E / PI;
}

void setup()
{
    // Configure PWM
    // PWM period = PTPER * prescale * Tcy = 9470 * 64 * 33.33ns = 20ms
    _PMOD1 = 0;   // PWM channel 1 mode: 0 for complementary, 1 for independent
    _PMOD2 = 0;   // PWM channel 2 mode: 0 for complementary, 1 for independent
    _PEN1H = 1;   // PWM1H pin enable: 1 to enable, 0 to disable
    _PEN2H = 1;   // PWM2H pin enable: 1 to enable, 0 to disable
    _PTCKPS = 3;  // PWM prescaler setting: 0=1:1, 1=1:4, 2=1:16, 3=1:64
    PTPER = 9470; // Set PWM time base period to 20ms (15-bit value)
    _PTEN = 1;    // Enable PWM time base to start generating pulses
}
Posted in Uncategorized | Leave a comment