A Robotics Project Page
Google
 
Web    www.PhilBot.com
My other sites
  • OurCoolHouse
  • Phil's Resume
  • Web Portfolio

  • [Home] [Projects] [Hardware] [Software] [Books] [Links] [Downloads]


    Sample Programs

    Mark III OOPic oUserClass (For the V6.0 Compiler)

    The [Mark III] MiniSumo robot is a great platform for getting your feet wet in the robotics world.  Check out my [Mark III Project] page to learn more about this cool little Sumo Bot.

    I particularly like the [OOPic] version of the Mark III, that lets me use the C programming language with a suite of predefined "Objects" to interface to the robot.  In accordance with my standard philosophy of building modular, reusable code, I decided that I would design a stand-alone OOPic object class to encapsulate all of the I/O devices and capabilities on the Mark III. 

    The OOPic compiler is pretty cool: in addition to all the predefined Objects, it lets you, the programmer, create your own Objects.  This is done by putting all the code and data for a new object in one file, and then referencing that file when you create an oUserClass object in the calling program.  For example, I decided to create a PhilBot user class for the Mark III MiniSumo using all Virtual Circuits. I called it PBot_MkIII_VC.

    I have recently re-written my code for the new Rev 6.0 OOPic compiler.  This new version is called: [PBot_MkIII_1.0_VC6], and that's what I'm describing here.

    What I wanted was a generic UserClass that I could use as the basis for all my future Mark III projects.  So I sat down and figured out my requirements:

    1. I wanted all the Mark III inputs and outputs to be set-up automatically by the User Class.
    2. I wanted the various analog inputs to be monitored automatically and provide simple digital flags indicating the various conditions important to a Mini-Sumo.
    3. I wanted a simple way to calibrate all the analog inputs for normal Sumo operation
    4. I wanted an easy way to drive Bot.
    5. I wanted a way to "see" why the bot was acting the way it was.  This meant some sort of real-time diagnostics.

    I've included the full [PBot_MkIII_1.0_VC6] Class source code on my [downloads] page, so it would probably help if you downloaded and printed it out for reference. 

    MArk III MiniSumo Robot with LEDs mounted to prototyping board.I figured I'd need some help debugging my programs, so I added some LED's to the Bot and used these to display the flags being set by the UserClass.  I purchased the Mark III Prototyping board, and mounted a bank of 8 LED to the board.  I wired these LEDs to the bank "D" digital I/O port, (I/O 24 to 31) and tied them to the 0V rail via 200 ohm resistors (See pic at right. Click pic for full view).

    The most important task with any robot is defining the I/O allocations.  I always do this FIRST, at the top of my program as constants or defines. Here's the code I used for the new MarkIII UserClass.

    //-----------------------------------------------------------------
    // System Constants
    //-----------------------------------------------------------------
    // I/O Definitions
    Const       IO_EYE_LEFT     = 4;
    Const       IO_EYE_RIGHT    = 3;
    Const       IO_LINE_LEFT    = 7;
    Const       IO_LINE_CENTER  = 6;
    Const       IO_LINE_RIGHT   = 5;

    Const       IO_WHEEL_LEFT   = 10;
    Const       IO_WHEEL_RIGHT  = 9;

    Const       IO_DEBUG_LED_1  = 24;
    Const       IO_DEBUG_LED_2  = 25;
    Const       IO_DEBUG_LED_3  = 26;
    Const       IO_DEBUG_LED_4  = 27;
    Const       IO_DEBUG_LED_5  = 28;

    Notice the standard naming convention that I use here and in other programs:  The constants are all named as IO_ + Function_ + Position.  
    Tip: If you create simple rules for variable names, it's always easier to read & understand your code later on.

    After defining the I/O Assignments, then I declare the various Objects.  I'm going to use oA2DX for all of the analog inputs because these will let me set an offset that splits the +/- range at the threshold that I'm trying to detect.  Then the yes/no level of the sensor can use the Negative property as a boolean (single bit) flag.  I'll use these flags to change each status LED so that I can see the sensors working.  I'll also declare an oCountdown object to use for system timing.  Many programs can use the OOPic.Delay property for pausing the program, but by using the oCountdown object I can do other things while I'm waiting for the timer to elapse.  Here are the objects that I declare:

    //-----------------------------------------------------------------
    // System Objects: Total Object Memory Used:  55 Bytes
    //-----------------------------------------------------------------
    // Device Objects
    oCountDownO ActionTimer     = New oCountDownO;  // User Timer   

    oDIO1       LeftEyeOn       = New oDIO1;        // Status Bits/LEDs
    oDIO1       LeftLineWhite   = New oDIO1;
    oDIO1       CenterLineWhite = New oDIO1;
    oDIO1       RightLineWhite  = New oDIO1;
    oDIO1       RightEyeOn      = New oDIO1;

    oA2DX       LeftEye         = New oA2DX;        // Sharp I/R Range
    oA2DX       RightEye        = New oA2DX;
    oA2D        LeftLine        = New oA2DX;        // Fairchild I/R Reflective
    oA2D        CenterLine      = New oA2DX;
    oA2D        RightLine       = New oA2DX;

    oServo      LeftWheel       = New oServo;       // Cont. Rot. Servos
    oServo      RightWheel      = New oServo;

    // Virtual Circuit objects
    oWire       LeftEyeWire     = New oWire;        // VC Connectors to Drive LEDs
    oWire       LeftLineWire    = New oWire;
    oWire       CenterLineWire  = New oWire;
    oWire       RightLineWire   = New oWire;
    oWire       RightEyeWire    = New oWire;

    Once the objects have been declared I define the various functions to configure the objects and virtual circuits. 
    Tip: I find that a consistent program structure can make my coding and debugging life easier, so I always define two functions:  SetupIO( ) and SetupVC( ).  SetupIO( ) just configures the objects that are used to interface to the various sensors and actuators.  SetupVC( ) is used to make any Virtual Circuit connections.

    Here's the SetupIO( ) function:

    //-----------------------------------------------------------------
    //  SetupIO()
    //  Configure all the IO devices
    //-----------------------------------------------------------------
    Void    SetupIO(Void)
    {
        // --------------------------------
        // setup the ActionTimer Timer for 1/10th Second ticks 
        // Preload counter with 5 second delay
        // --------------------------------
        ActionTimer.PreScale    = CLOCK_PRESCALE;
        ActionTimer.ClockIn.Link(ooPIC.Hz60);
        ActionTimer             = FIVE_SECONDS;
        ActionTimer.Operate     = cvTrue;
        
        // --------------------------------
        // setup the Eye sensors
        // We use oA2DX so we can bias the values up to permit single bit checking
        // --------------------------------
        LeftEye.IOLine          = IO_EYE_LEFT;
        LeftEye.Center          = ZERO_OFFSET - EYE_OFFSET; // Bias the level up
        LeftEye.Operate         = cvTrue;

        RightEye.IOLine         = IO_EYE_RIGHT;
        RightEye.Center         = ZERO_OFFSET - EYE_OFFSET; // Bias the level up
        RightEye.Operate        = cvTrue;

        // --------------------------------
        // setup the line sensors
        // We use oA2DX so we can bias the values up to permit single bit checking
        // --------------------------------
        LeftLine.IOLine         = IO_LINE_LEFT;
        LeftLine.Center         = ZERO_OFFSET;
        LeftLine.Operate        = cvTrue;

        CenterLine.IOLine       = IO_LINE_CENTER;
        CenterLine.Center       = ZERO_OFFSET;
        CenterLine.Operate      = cvTrue;

        RightLine.IOLine        = IO_LINE_RIGHT;
        RightLine.Center        = ZERO_OFFSET;
        RightLine.Operate       = cvTrue;

        // --------------------------------
        // Setup the Servos 
        // Note: The StartDrive() function must be called 
        // before the servos will be activated.
        // --------------------------------
        
        // Set the servo Idle Position
        LeftWheel.IOLine        = IO_WHEEL_LEFT;
        LeftWheel.Center        = WHEEL_OFFSET;
        LeftWheel.Value         = WHEEL_STOP;
        LeftWheel.Refresh       = cvTrue;
        LeftWheel.InvertOut     = 0;

        RightWheel.IOLine       = IO_WHEEL_RIGHT;
        RightWheel.Center       = WHEEL_OFFSET;
        RightWheel.Value        = WHEEL_STOP;
        RightWheel.Refresh      = cvTrue;
        RightWheel.InvertOut    = 1;

        // --------------------------------
        // Setup the Status LEDs (but disable them) 
        // --------------------------------
        LeftEyeOn.IOLine        = IO_DEBUG_LED_1 ;
        LeftLineWhite.IOLine    = IO_DEBUG_LED_2 ;
        CenterLineWhite.IOLine  = IO_DEBUG_LED_3 ;
        RightLineWhite.IOLine   = IO_DEBUG_LED_4 ;
        RightEyeOn.IOLine       = IO_DEBUG_LED_5 ;

        LeftEyeOn.Direction       = cvInput ;
        LeftLineWhite.Direction   = cvInput ;
        CenterLineWhite.Direction = cvInput ;
        RightLineWhite.Direction  = cvInput ;
        RightEyeOn.Direction      = cvInput ;
    }

    Some things to note:

    Next I configure the few remaining VC Objects.  All I need here are some oWire objects to transfer the oA2DX outputs to their associated LEDs.  I can also invert some bits so that the required condition is indicated with a cvTrue level.  Here's the SetupVC( ) code:

    //-----------------------------------------------------------------
    //  SetupVC()
    //  Configure all the Virtual Circuits
    //-----------------------------------------------------------------
    Void    SetupVC(Void)
    {
        // --------------------------------
        // setup the Eye Virtual Circuits
        // --------------------------------
        // Wire High Level to Line On.
        LeftEyeWire.Input.Link(LeftEye.Negative);
        LeftEyeWire.Output.Link(LeftEyeOn);
        LeftEyeWire.InvertIn    = cvTrue;   // Transfer inverted value
        LeftEyeWire.Operate     = cvTrue;

        // Wire High Level to Line On.
        RightEyeWire.Input.Link(RightEye.Negative);
        RightEyeWire.Output.Link(RightEyeOn);
        RightEyeWire.InvertIn   = cvTrue;   // Transfer inverted value
        RightEyeWire.Operate    = cvTrue;
        
        // ---------------------
        // setup the Line Virtual Circuits
        // ---------------------
        // Wire Low Level to White
        LeftLineWire.Input.Link(LeftLine.Negative);
        LeftLineWire.Output.Link(LeftLineWhite);
        LeftLineWire.Operate    = cvTrue;

        // Wire Low Level to White
        CenterLineWire.Input.Link(CenterLine.Negative);
        CenterLineWire.Output.Link(CenterLineWhite);
        CenterLineWire.Operate  = cvTrue;

        // Wire Low Level to White
        RightLineWire.Input.Link(RightLine.Negative);
        RightLineWire.Output.Link(RightLineWhite);
        RightLineWire.Operate   = cvTrue;
    }

    The next thing I need to do is provide a way to calibrate the three line sensors.  I provide two calibration routines, one for Sumo and one for LineFollowing.  I need two different routines since the starting conditions are different for the two different competitions.  Sumo assumes that the robot is starting out on an all-black section of the ring, so SumoCalibrate( ) takes the average of all three sensors, and then sets a threshold equal to 3/4 of this average.  Line following assumes that the robot starts out straddling the white/black line, so FollowerCalibrate( ) takes the average of the two outer sensors, and then picks a threshold half way between this value and the central sensor.  In both cases the routine then sets all the oA2DX .Offset values to "127 - threshold".  So, any input below the threshold will read a negative value.  Here's the two calibrate functions:

    //-----------------------------------------------------------------
    //  FollowerCalibrate()
    //  This function is used to measure the current reflected IR from
    //  a White or Black Line by the Line sensors.
    //  The threshold is set halfway between the inner and outer sensors
    //  This call assumes the Bot is centered on the line.
    //-----------------------------------------------------------------
    Void FollowerCalibrate(Void)
    {
        // Set all the lines back to zero offset
        LeftLine.Center         = ZERO_OFFSET;
        CenterLine.Center       = ZERO_OFFSET;
        RightLine.Center        = ZERO_OFFSET;

        // Read the two outer sensors & take average
        TempWord = (RightLine.Value + LeftLine.Value) >> 1 ;
        
        // Read the inner sensors & take average with outer average
        TempWord = (CenterLine.Value + TempWord) >> 1 ;
        
        // Set a new offset to make A "white line" negative.
        LeftLine.Center         = ZERO_OFFSET - TempWord;
        CenterLine.Center       = ZERO_OFFSET - TempWord;
        RightLine.Center        = ZERO_OFFSET - TempWord;
    }

    //-----------------------------------------------------------------
    //  SumoCalibrate()
    //  This function is used to measure the current reflected IR from
    //  the SUMO ring by the Line sensors and determines a "White Line"
    //  threshold by taking 3/4 of the current level.
    //  This call assumes the Bot is NOT on a white line at the time 
    //-----------------------------------------------------------------
    Void SumoCalibrate(Void)
    {
        // Set all the lines back to zero offset
        LeftLine.Center         = ZERO_OFFSET;
        CenterLine.Center       = ZERO_OFFSET;
        RightLine.Center        = ZERO_OFFSET;

        // Read each of the three line sensors and calculate a line level
        // Equal to 3/4 of the initial level
        TempWord = (RightLine.Value + CenterLine.Value + LeftLine.Value) >> 2; ;
        
        // Set a new offset to make A "white line" negative.
        LeftLine.Center         = ZERO_OFFSET - TempWord;
        CenterLine.Center       = ZERO_OFFSET - TempWord;
        RightLine.Center        = ZERO_OFFSET - TempWord;
    }

    Tip: Notice that before taking sensor readings, it was important to set the Offset values back to 127.  Although this was done when the program started, there's nothing stopping someone calling these functions more than once, so it's important to expect the unexpected and make sure the program can deal with it.

    The last function that was need is an easy way to set the wheel speeds.  In many cases the robot wants to move at a certain speed for a specific amount of time, so I also included this capability in the Drive( ) function. I also made a speed request of zero disable the servo drive.  This eliminates any creep from a miss-calibrated servo.  Remember when using the Drive( ) function that to turn RIGHT you have several choices, but all of them involve moving the LEFT wheel faster then the RIGHT.  Here's the Drive( ) code:

    //-----------------------------------------------------------------
    //  Drive(LeftSpeed, RightSpeed, DriveTime)
    //  This function drives both wheels at the indicated speeds
    //  The motion is held for "DriveTime" tenths of a second
    //  No time limit is placed if Time = 0
    //
    //  Positive values are forward, Negative are backwards.
    //  Max range is +/- 18
    //
    //-----------------------------------------------------------------
    Void Drive(Byte LeftSpeed, Byte RightSpeed, Byte DriveTime)

        // Set left wheel speed (Disable if zero)
        If (LeftSpeed == 0)
        {
            LeftWheel.Value     = WHEEL_STOP ;
            LeftWheel.Operate   = cvFalse;
        }
        Else
        {
            LeftWheel.Operate   = cvTrue;
            LeftWheel.Value     = WHEEL_STOP + LeftSpeed ;
        }

        // Set right wheel speed (Disable if zero)
        If (RightSpeed == 0)
        {
            RightWheel.Value    = WHEEL_STOP ;
            RightWheel.Operate  = cvFalse;
        }
        Else
        {
            RightWheel.Operate  = cvTrue;
            RightWheel.Value    = WHEEL_STOP + RightSpeed ;
        }
        
        // Continue action for requested time period in tenths.
        If (DriveTime > 0)
        {
            ActionTimer = DriveTime;
            While(ActionTimer.NonZero);
        }
    }

    Now that you know all about PBot_MkIII_1.1_VC6 you can forget about it, because the whole point is that you don't ever need to change it to write lots of different Mark III programs, you just need to reference it in your program.  Your Sumo and Line follower programs can just concentrate on the STRATEGY.  To see what I mean, I've created several sample programs.  Use these as a starting point to create your own Uber clever Sumo bots.

    All the OOPic 6.0 sample programs are available on my [download] page, and I explain how they work on my 6.0 [MkIII Software] page. 

     

    Web content is copyright © PhilBot.com 2005, Deep Creek Lake, MD.
    Contact: Phil Malone 301.387.2331, webmaster@PhilBot.com