Let's Make Robots!

ABM - A Better Mousetrap

Several months ago, I posted about what the Standard Template Library had done for development when it came out in the mid 1990s.  I remembered how it was this incredible leap forward in software.  It enabled a developer to do things in minutes that were hours, days maybe weeks before and was a huge leap forward in productivity and software quality.  It took standard algorithms and made them out of the box sorts of things that were easy to use and implement.

http://letsmakerobots.com/node/36671

It seems this sort of library doesn't really exist yet in robotics.  There are fragments spread across a number of projects that bring you in the ball park.  I think there are some cool things people are doing such as

http://myrobotlab.org/ - this is an exciting project!  I will be involved in this when I start to actually know something and can be useful.
www.willowgarage.com - robot operating system - this looks very cool!  much closer to what I envision, but is mostly Linux.  When I can dig into a Raspberry pie, I will start learning this.

 I have been working on this over the past several months in my spare time.  The zip file included is a proof of concept on a simple autonoumous robot that has edge detectors, an H Bridge on a motor shield (I model it as an HBridge) and current sensors. 

 I started out calling it the Standard Robotics Library, but that is really a very stuffy name for something that does not do much.  I have been working on some docs which I include below.  I think calling it ABM, A Better Mousetrap, is probably a better name right now, but someday, it might be a Standard Robotics Library.  The zip file I include allows one to compile in Visual C++ 2010 and then run the Arduino environment on the same code.  Visual Studio has some great debugging tools which really speed development.  I do have a fake "Arduino.h" file in the project which has to have its name changed before compiling for the Arduino.  Since writing these docs, I have also created an edge detector in the Ar_LDREdgeDetector.cpp file.  This encapsulates Fritz's edge detector he documents on


http://letsmakerobots.com/node/1833

and is setup and used in the MyAutonomousRobot.cpp and I feel truly shows the power of what this object oriented approach can do.  I look forward to comments and suggestions. 

Standard Robotics Library (SRL) or ABM (A Better Mousetrap)

What is it?

It is an object oriented C++ library that attempts to standardize how microcontrollers interface to hardware and the algorithms that drive a robot’s behaviors.  It is a cooperative multitaskling library meaning that when there is nothing to do, it yields to allow CPU time for other tasks.  For now, this library works only with Arduino, but may be extended to other processors since it is possible that this library could be cross platform.  I also welcome other developers who wish to add to this library to do so and look forward to any and all comments on it.
 
Why?

I noticed in robotics there are a finite set of standard problems every type of robot must solve.  People write the same code over and over and rarely are able to reuse anything without extensive rework. I looked around for a library that does what the SRL does and found nothing so decided to do my own. 

For instance, many people start with a robot that moves around, but when it runs into an obstacle, it will back up and try to turn away from the obstacle.  Solutions might be an ultrasonic sensor on a rotating servo, bumper switches, infrared sensors, etc. but no matter what, the desired robot behavior is to back up and turn away from the obstacle.  Using C, each of these sensor configurations has a different output which triggers the behavior.   SRL is a standard interface to hardware and behavior.  By following the “rules”, these pieces can plug into each other in different combinations and work smoothly together since they all agree on the same rules or interfaces.  Just as you can use alligator clips to connect to bare wires coming out of the wall to power your dishwasher or blender, it sure is a lot more convenient if you have a standardized plug on the wall and on the power cord.  The SRL library strives to be the easy connection between hardware and behavior by creating programming abstractions for both.

For instance, let’s say a robot starts with continuous rotation servos to move the robot.  After a while, the developer may realize it would be nice to use an HBridge with geared drive motors for better performance.   From a programming perspective, all that is needed is to unplug the servos and plug in an HBridge.  Since a continuous rotation servo interface and HBridge object interface to the robot in the same way (obey the same rules), the propulsion or locomotion mechanism is invisible. 

It is also fast.  I have measured scan times as low as 6-10 microseconds but depends on specifics of application on Arduino.

 Why Not?

All engineering decisions have tradeoffs.  The SRL uses more program and stack/heap memory than writing the same solution in C.  

C++ uses more memory than straight C.  A base class declaration with a derived class and no methods on it is 300 bytes of program memory(note:  not each instantiation of an object, just the declaration of the class.  Every time an object is created, it pulls this from the stack/heap memory not program memory and doesn't use that much memory.)   The largest of SRL classes has an overhead of 1500-2000 bytes which is pretty big when you only have 32,768 to begin with.  The Arduino has 2k bytes of stack/heap space which is really around 1300 bytes after the Arduino is up and running.   Stack/heap space is more likely to run out than programming memory, and the SRL uses more of both.

Depending on your situation and complexity of the application, the SRL may not be the solution, especially if you are pushing the limits on memory.  I found on my simple autonomous robot that my solution in C used about 7K and left 638 bytes on the stack/heap.  The same implementation using the SRL architecture used 17K and left 638 bytes on the stack/heap. 

If I started to run out of memory, I would seriously consider using a Mega.  I feel the separation of concerns, multi threaded approach well worth the pain of several extra dollars.

Basic Design

All hardware and algorithms are abstracted to discreet types.    Basic interface classes start with capital “I” and are pure virtual classes.  These then have intermediary classes that start with Base and then the hardware specific classes start with Ar_.

So, to represent an analog input on the Arduino

IHardware – pure virtual class – no implementation here

BaseAnalogInput- all logic in here – all implementation methods except ones needed to access the microcontroller.

Ar_AnalogInput – implementation methods that access the microcontroller (Arduino in this case).  Could be a plethora of other classes in the future.

I decided to not include the intermediary classes on the algorithms.  I did this mostly because there could be many different types and having an intermediary class adds to overhead with no discernible usability added except to make this potentially cross platform.  If this gets ported to another hardware platform besides Arduino, we would need to address this issue.

IAlgorithm

ASRBackUpandTurnAwayRotate3MinsDirection

Below is a simple example of what would be written in the .ino file for a basic robot that had two bumper switches, and an HBridge that drove two geared motors.

MyAuthonomousRobot robot;

void setup()

{

                Robot.Initialize() ;
  Robot.GoForward() ;
  Robot.DisableMessaging(250) ; // turns off messaging for 250 ms so has a chance to setup hardware before reading...

}

void loop()

{

                Robot.Run() ;

}

Inside the MyAutonomousRobot.cpp file would be:

MyAutonomousRobot :: MyAutonomousRobot ()

{

                InitializeHardware() ;

                InitializeAlgorithms() ;

}

void MyAutonomousRobot::InitializeHardware()

{

                _bumperSwitchLeft  = new Ar_Switch(1); // number is the actual pin number

  _bumperSwitchLeft  ->Initialize();

  AddHardware (_bumperSwitchLeft  );

                _bumperSwitchRight = new Ar_Switch(2);

  _bumperSwitchRight ->Initialize();

  AddHardware (_bumperSwitchLeft  );

  _bridge = new Ar_HBridge(3,4,5,180, 6,7,8,220) ;  // 3,4,5 are pins for the half bridge 0- 180 is pwm value

  //6,7,8 are pins for the other side of the h bridge – 220 is the pwm value for the voltage

  _LeftMotor = _bridge->LeftMotor() ;

  _RightMotor = _bridge->RightMotor() ;                                                                                               

//LeftMotor and RightMotor implement Stop(), GoForward(), GoBackward() – any objects that implement ISimpleLocomotion could be used here…

}

void MyAutonomousRobot::InitializeAlgorithm()

{

                AddAlgorithm(new ASRBackUpandTurnAwayRotate3MinsDirection( this, _bumperSwitchLeft , _bumperSwitchRight  ));

                // this algorithm tests each bumper and if either is true will back up and turn away.  Every 3 minutes, it changes which direction it asks the robot to go…

// can have multiple algorithms each very complex or not complex at all

}

//this method is called with each iteration of the loop

void MyAutonomousRobot::Run ()

{

                RunHardware() ; 

                ManageLocomotion() ;

                RunAlgorithms() ;           

}

RunHardware() method calls the Run () method on all IHardware objects that are added to its list via the BaseRobot::AddHardware(IHardware * ).  Most IHardware derived classes at this point have empty Run () methods since no logic needed.  Ideally, there should be a IRunnableHardware class which derives from IHardware, but the overhead of having an additional method consumes program space which is not as much of a premium of stack/heap space.  One can reduce overhead very slightly by not adding all hardware via BaseRobot::AddHardware(…) unless they need to.   That said, all Ar_AnalogInput objects must be added via BaseRobot::AddHardware(…) since the class implements the Run() method.  It has a timer it checks and only gets the analog value every 100 milliseconds.  This is written this way since the Arduino method analogRead(…) can take as much as 100 ms to retrieve

The ManageLocomotion() method encapsulates a message pump.  It runs commands in order and when the command is completed, it removes the message from the queue.  If it is a timed message, the message will remain in the pump until the time is done, and then it is removed.  Presently, these actions are simple, but could be more complex to represent a flying robot etc.

RunAlgorithms() method calls the Run () method on all IAlgorithm derived objects that are added to its list via the BaseRobot::AddAlgorithm(IAlgorithm * ).

One of the main problems encountered while designing the SRL is how to model hardware into an abstraction.  If the abstraction is too exact, it will only fit one situation.  If it is too broad, it will fit all situations but require so much code for every permutation that the library will be bloated.  The SRL answer is small but limited scope components and an architecture to tie them easily into the infrastructure. 

For instance, I use a motor driver for my HBridge.

http://www.canakit.com/dual-motor-l298-h-bridge-control-ck1122-uk1122.html

The Ar_HBridge class models all the standard HBridge behaviors of the L298 or any HBridge chip with two HalfHBridge components that can only go forward, backwards or stop.  The driver board also has two current sensing outputs which I use to sense if the robot has run into a wall or gotten stuck.  I could have created a CanakitHBridgeKit1122 class and modeled it to include those extra outputs.   If they were never used though, that is extra overhead to carry around which can be hardly afforded with the constraints of an Arduino.  Also, in a few years, Canakit could change their hardware, and the class has  to be reworked to support changes.

My additions to the code to support this are fairly minimal.  I add this code to the InitializeHardware().

BaseAnalogInput * leftOverAmperage = new Ar_AnalogInput(A0) ;

leftOverAmperage->Initialize() ;

AddHardware (leftOverAmperage);

BaseAnalogInput * rightOverAmperage = new Ar_AnalogInput(A1) ;

rightOverAmperage->Initialize() ;

AddHardware (rightOverAmperage);

I add this code to the InitializeAlgorithm();

AddAlgorithm(new IfOverAmpBackupAndTurnAwayAlgorithm(leftOverAmperage, rightOverAmperage, this, 220 )); 

/* if either AnalogInput has a reading over 220, it will back up and then turn right or left depending on which had the higher reading.*/

This approach decouples the algorithm from the hardware and fosters reusable components to be matched with desired behaviors.  If I decide to use two current sensing boards on a different project, I can reuse all of the code.

Debugging and Code Profiling

Debugging and profiling code in an embedded environment is sometimes challenging.   The Debugging class is optionally available to all classes that begin with Ar_.  It sends messages through the USB port that can give a user an idea of what the microcontroller is doing. 

#ifdef DEBUGGING

class Debugging

{

Public:

                void EnableDebugging( bool enable) ;

void EnableDebugging( bool enable, int msFrequency) ;

                // can start and stop profiling

// if enabling, returns 0, if disabling, returns ms since enabled…

                int EnableProfiling( bool enable) ; 

                int EnableProfiling( bool enable, int msFrequency) ; 

                int GetStackSize() ;

                void SetName( char * name) ;

                void SetMethod( char * method) ;

protected:

                bool IsDebugging () ;

                bool IsDebugTimerDone() ;

                void SendMessage() ;

                void SendMessage( char * message) ;

private:

                bool  _debugging ;

                BaseTimer * _timer ;

                char * _name;

                char * _method ;

} ;

#endif

The Debugger class is built into the inheritance tree and uses a preprocessor directive (#define DEBUGGING  around all debug code) which doesn’t affect actual performance or size of final deployed program.  The debugging module is designed to allow you to enable debugging for objects one at a time and put real names to hardware objects so that what you get back from the USB port is readable.  Also, debug module has built in methods to determine memory footprint etc. 

In the robot’s Initialize method –

BaseAnalogInput * leftMotorOverAmp = new Ar_AnalogInput(A0) ;

#ifdef DEBUGGING

                leftMotorOverAmp->SetName(“leftMotorOverAmp”);

                leftMotorOverAmp->SetMethod(“inside GetValue”);

EnableDebugging( true,1000) ; // this way it will send a message out the port every second with current value

#endif

Inside Ar_AnalogInput.cpp:

Int  Ar_AnalogInput::GetValue()

{

#ifdef DEBUG

                if ( IsDebugging() == true && IsDebugTimerDone() == true)

                {             

                                SendMessage() ;

                }             

#endif                  

 

}

The SRL also has a NOARDUINO preprocessor which allows the code to run within Microsoft Visual C++ which cuts out all Arduino specific calls.  This way, a developer can use a more sophisticated development environment with better debug tools to create an algorithm and ensure its success before deploying to the Arduino microcontroller.  I use Microsoft Visual C++ to prove my concepts and algorithms, move my code to the Arduino sketch directory with a batch file and recompile using the gcc compiler with the Arduino IDE. If the program is experiencing a slowdown, I can more easily ascertain who is the culprit by using the code profiling.

Deploying the final cut to the Arduino can also be easier.  Since each piece is broken out, in the loop() method in the main Arduino file, each algorithm and its hardware can be tested out independently to ensure correct pins, appropriate algorithm etc.  When testing, I accidently had used the same pin in two different places.  I tested each of the algorithms one by one with its hardware configuration, and then when I put them together, I noticed something was broken.  Since I knew they worked alone, I knew it was putting them together that made them fail.  I removed the algorithm one by one from the configuration and was able to quickly figure out which was the culprit.  If I had written this in C, that would have been a much more difficult debug sequence.

Conclusion

I am very surprised no one else has done anything similar to this.  I feel good about the design and think that if folks take the time to understand it, will find it useful and maybe even invigorating enough to add their own algorithms and/or hardware to.

What I have presented is truly a small piece of what needs to be done. 

At the very least, it will make my embedded development on the Arduino easier and possibly better.  The debugging and profiling are huge helps to any developer as well as the separation of behavior from hardware.  I look forward to comments and suggestions.

 

AttachmentSize
SRLTester.zip851.8 KB

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

So far I have only skimmed through your description, but it looks pretty good.  I will try to download and read through the code later.

Please do keep in mind that this is just a proof of concept.  I really want to make this better so any and all input is appreciated

Regards,

 

Bill