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]


    Mark III OOPic oUserClass (for the V5.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 class 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 class for the Mark III MiniSumo using all Virtual Circuits. I called it PBot_MkIII_VC.  This page describes the OOPic V5.0 version of this user class. 

    Note: If you are using the V6.0 compiler, you should check out my [updated 6.0 page].

    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 setup 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, turn and spin the 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_VC 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 started out with the diagnostics, because this would let me debug things as I coded them.  I figured I could add some LED's to the Bot and use 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).

    Here are the three areas of code that defined and set up these LED objects. 

    // Object constants, used for predefined values
    Const DEBUG_LED_1 = 24;
    Const DEBUG_LED_2 = 25;
    Const DEBUG_LED_3 = 26;
    Const DEBUG_LED_4 = 27;
    Const DEBUG_LED_5 = 28;
    Const DEBUG_LED_6 = 29;

    // Object Devices
    oDIO1 LeftEyeOn        = New oDIO1;
    oDIO1 LeftLineOn       = New oDIO1;
    oDIO1 CenterLineOn     = New oDIO1;
    oDIO1 RightLineOn      = New oDIO1;
    oDIO1 RightEyeOn       = New oDIO1;
    oDIO1 BothEyesOn       = New oDIO1;

    // Setup the Status LEDs
    LeftLineOn.IOLine      = DEBUG_LED_1 ;
    LeftLineOn.Direction   = cvOutput ;
    LeftEyeOn.IOLine       = DEBUG_LED_2 ;
    LeftEyeOn.Direction    = cvOutput ;
    CenterLineOn.IOLine    = DEBUG_LED_3 ;
    CenterLineOn.Direction = cvOutput ;
    RightEyeOn.IOLine      = DEBUG_LED_4 ;
    RightEyeOn.Direction   = cvOutput ;
    RightLineOn.IOLine     = DEBUG_LED_5 ;
    RightLineOn.Direction  = cvOutput ;
    BothEyesOn.IOLine      = DEBUG_LED_6 ;
    BothEyesOn.Direction   = cvOutput ;


    Some things to notice here:

    All of the other Mark III I/O devices were defined and initialized in a similar way.

    After defining all the Inputs and outputs, I needed to connect them together with Virtual Circuits so that the "On" flags were automatically updated. To illustrate this I'll start out with an easy example: The line sensors.

    Accept for a moment that there is a oByte object called LineLevel that holds the light/dark threshold value for the line sensors.  If a sensor's value is less than this threshold, then the Line sensor is considered "On", otherwise it's off.  So what we need is a Virtual Circuit that reads a Line Sensor's oA2D value, compares it with the LineLevel  and sets the appropriate LineOn flag.

    Here's the code for just the Left Line sensor:

    // I/O and storage Objects
    oByte LineLevel        = New oByte;
    oA2D  LeftLine         = New oA2D;

    // Virtual Circuit objects
    oCompare LeftLineComp  = New oCompare;
    oWire    LeftEyeWire   = New oWire;

    // --------------------------------
    // setup the line sensors
    // --------------------------------

    // Start with a default Line Level for threshold.
    // This is changed by CalibrateLine() function
    LineLevel              = LINE_LEVEL;

    // Configure I/O channels and enable inputs
    LeftLine.IOLine        = LEFT_LINE_IO;
    LeftLine.Operate       = cvTrue;

    // --------------------------------
    // setup the Line Virtual Circuits
    // --------------------------------

    // Compare Line level to threshold
    LeftLineComp.Input.Link(LeftLine);
    LeftLineComp.ReferenceIn.Link(LineLevel);
    LeftLineComp.Fuzziness = 0;

    // Wire Low Level to Line On.
    LeftLineWire.Input.Link(LeftLineComp.Below);
    LeftLineWire.Output.Link(LeftLineOn);

    LeftLineComp.Operate   = cvTrue;

    You can see that I use an oCompare object that is linked to the LeftLine oA2D object and the LineLevel oByte object.  This object has three output flags, above, below and between.  I use Below to indicate that the sensor is below the threshold.  I then use an oWire object to link the Below output to the LeftLineOn oBit object.  This will light the LED whenever the Mark III's Left Line sensor is on the white line.  This code is repeated for the other two line sensors.

    The Eye sensors are just a bit trickier.

    The left and right eye could be handled in a similar way, just comparing them with a threshold, but they are really a bit more complicated than that.  In order to turn towards an opponent, you really need to discern one idle state and three different active states:  1) No contact, 2) Robot towards Left, 3) Robot towards Right & 4) Robot straight ahead.  To do this we compare the relative values of the left and right sensors.  If one is significantly higher than the other, we can say that the opponent is in that direction.  If the eyes are not greatly different, but they are high in value, then the opponent must be straight ahead.  Otherwise, we can't see the opponent at all.

    When I first started creating virtual circuits to compare the left and right eye, I just fed the normal A2D values into an oCompare object and set the fuzziness to the signal difference that represented an off-center opponent.  This didn't work because the oCompare object couldn't handle the negative values that occurred when one of the eye sensors was close to zero.  To get around this, I changed the eye sensors to oA2DX objects, which enabled me to offset the values up above the "fuzziness" level, so all the comparisons were done as positive numbers.  The code below produces eye values that range from 28 to 156.  With a fuzziness of 24, any two oA2DX values can be compared and the numbers always stay positive.

    The oCompare function generates three flag conditions.  Above and Below are used to indicate left or right side opponents, and Between means either: no-opponent, or opponent dead ahead.  More code is added later to discern between these two conditions.

    Here's the code that detects the left eye or right eye dominance.

    // Object constants, used for predefined values
    Const EYE_OFFSET      = -100 ; // Bias the Eye level up by 28
    Const EYE_DIFFERENCE  = 24; // Set diff less than Eye Bias
    Const EYE_LEVEL       = 52; // Level for "See Him", includes Bias

    Const LEFT_EYE_IO     = 4;
    Const RIGHT_EYE_IO    = 3;

    // Object Devices
    oA2DX LeftEye         = New oA2DX;
    oA2DX RightEye        = New oA2DX;

    // Virtual Circuit objects
    oCompare  EyesComp    = New oCompare;
    oCompare0 EyeComp     = New oCompare0;
    oGate     BothEyes    = New oGate(2);  // Two inputs
    oDIO1     LeftEyeOn   = New oDIO1;
    oDIO1     RightEyeOn  = New oDIO1;
    oDIO1     BothEyesOn  = New oDIO1;

    oWire     LeftEyeWire  = New oWire;
    oWire     RightEyeWire = New oWire;

    // --------------------------------
    // setup the Eye sensors
    // --------------------------------

    // Configure I/O channels and enable inputs
    // We need to use oA2DX so we can bias the values up to permit
    // the oCompare object to apply the "fuzzyness" factor without going negative
    LeftEye.IOLine       = LEFT_EYE_IO;
    LeftEye.Center       = EYE_OFFSET; // Bias the level up
    LeftEye.Unsigned     = cvTrue; // Use whole 0-255 span
    LeftEye.Limit        = cvFalse; // Do not limit range to 127
    LeftEye.Operate      = cvTrue;

    RightEye.IOLine      = RIGHT_EYE_IO;
    RightEye.Center      = EYE_OFFSET; // Bias the level up
    RightEye.Unsigned    = cvTrue; // Use whole 0-255 span
    RightEye.Limit       = cvFalse; // Do not limit range to 127
    RightEye.Operate     = cvTrue;

    // --------------------------------
    // setup the Eye Virtual Circuits
    // --------------------------------

    // Compare left and right eyes.
    EyesComp.Input.Link(LeftEye);
    EyesComp.ReferenceIn.Link(RightEye);
    EyesComp.Fuzziness  = EYE_DIFFERENCE;
    EyesComp.Operate    = cvTrue;

    // Wire Left Eye High to Left Eye On
    LeftEyeWire.Input.Link(EyesComp.Above);
    LeftEyeWire.Output.Link(LeftEyeOn);
    LeftEyeWire.Operate = cvTrue;

    // Wire Right Eye High to Right Eye On
    RightEyeWire.Input.Link(EyesComp.Below);
    RightEyeWire.Output.Link(RightEyeOn);
    RightEyeWire.Operate = cvTrue;

    The next thing to do is to determine when both eyes see the opponent.  To do this I combine several factors.  If the Between state of the EyesComp object is true, then there is a chance that both eyes are seeing the opponent because the two eye values are simmilar.  So to make sure, I just want to check that the eyes are also reading high numbers as well.  I really should check both eyes, but checking one will suffice.  If one eye is high, AND the two eyes are similar, then my reasoning says both must be high.

    So to turn this into a virtual circuit, I add another oCompare object to check the level of the Right Eye, and then add a oGate object to do a logical AND with the Between state to detect when both conditions are true.  The only remaining trick is that despite the Icon used for the oGate object, it does a logical OR operation, not an AND.  But as all digital logic nerds know, to use an OR gate to do AND operations, you just invert all the inputs and outputs.

    Here's the added code to detect the BothEyesOn condition.

    // Compare Righ Eye with threshold
    EyeComp.Input.Link(RightEye);
    EyeComp.Fuzziness     = EYE_LEVEL;
    EyeComp.Operate       = cvTrue;

    // Look for both eyes active:
    // AND "no eye dominance" with "high levels"
    BothEyes.Input1.Link(EyesComp.Between);
    BothEyes.Input2.Link(EyeComp.Above);
    BothEyes.Output.Link(BothEyesOn);
    BothEyes.InvertIn1    = cvTrue;
    BothEyes.InvertIn2    = cvTrue;
    BothEyes.InvertOut    = cvTrue;
    BothEyes.Operate      = cvTrue; 

    The remaining code does not use any virtual circuits, so it's pretty self explanatory. 

    There's the CalibrateLine( ) function for reading the three line sensors and calculating a new light/dark threshold based on the current values.  This assumes that the Sumo is down on the black section of the ring when it's called.  There's also a set of functions for driving the wheels to perform several typical Sumo motions.  The Forward( ) function applies the same drive to both wheels to go forward or back.  The Spin( ) function drives the wheels in opposite directions, and the Turn( ) function just drives one wheel.  Finally Drive( ) combines the Forward( ) and Turn( ) functions into a single function to perform arc motions.

    All that's left to do is show how to use the PBot_MkIII_VC.osc oUserClass in a real Sumo program. 
    Sample Sumo programs are available on my [download] page, and I explain how they work on my [MkIII Sumo] page. 

     

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