web.archive.org

The Basics - Robot Software

Encoder Front Page

Overview

One of the least discussed topics in hobby level robotics is software. You can almost always find a hardware description, schematic, drawing, and tons of help on the electro-mechanical side of your project. However, the software, which admittedly is one of the hard parts, eludes many people. In this article, I would like to demonstrate how to structure your software so your robot will function properly.

I would also like to point out a great technique for getting started. Many software engineers make heavy use of something called 'Pseudo Code'. This is a technique where you write your program in half programming language and half human language (English, Spanish, Driud, etc). Pseudo code is very useful for getting the layout of your program ready, to get basic algorithms worked out, and to be able to read your 'code' quickly. I will demonstrate this technique a little later.

As for programming languages, it doesn't matter which language you choose to use. I personally use the 'C' programming language the most. BASIC, Assembler, or FORTH are all fine choices. It turns out that the language is a small part of the structure of your program. The software has a job to do, and the key is to get it done! I am going to write this article in SBASIC, but the techniques are the same no matter which language you choose. SBASIC is available from Karl Lunts website at http://www.seanet.com/~karllunt/tips.htm

Basic Structure

Most robot programs have a similar structure. It consists of 4 major parts.

  1. Declarations and Variables
  2. Subroutines and Functions
  3. Initialization
  4. The Main Loop

Each of these sections have common attributes that I will discuss along the way.

Declarations and Variables

The first part in most programs is used to include files that you need, to declare variables that you are going to use, and to define constants for use in your program. You should also put a comment at the top of the file that indicates what the program does. You will find that lots of robot programs look alike, so having comments is an important way to tell them apart. In SBASIC, comments are done using the tick mark. Everything from the start of the ' mark to the end of the line is a comment, and will be ignored by the compiler.

A typical declaration section looks something like this:

'***************************************************************
' Declaration and Variables Section
'***************************************************************
include "regs11.lib"
declare SensorInput	' Variable that holds input sensor data
declare BehaveState	' Variable that holds the previous behavior
declare TurnDelayCount	' Variable for timing turns
'
' I like to name constants relative to their function. For example
' the following constants are used with the SensorInput variable,
' so I put the variable name in the constant name. This makes it
' easier to find functions that are affecting and affected by
' the value or the sensor. I also relate the function of the
' value in the name, as well as the group. For example, you
' can see which bits belong to the sensor variable, which
' are bumper related, and what their functions are. This is a good
' way to keep track of things.
const  Sensor_Port			= portc
const  Sensor_Port_DDR			= ddrc
const  Sensor_Bumper_Left		= %00000001
const  Sensor_Bumper_Right 		= %00000010
const  Sensor_Bumper_Back 		= %00000100
const  Sensor_Bumper_Mask		= %00000111
const  Sensor_Mask			= %00000111
const  Sensor_DDRC_Value		= Sensor_Mask XOR $FF
const  Motor_Port	= portb
const  Motors_Forward  	= %10100000
const  Motors_Backward 	= %01010000
const  Motors_Left	= %10010000
const  Motors_Right	= %01100000
const  Motors_Stop	= %00000000
const  Behave_Forward		= 1
const  Behave_Back_Left		= 2
const  Behave_Back_Right	= 3
const  Behave_Turn_Left		= 4
const  Behave_Turn_Right	= 5
const  Behave_Stop		= 6
const  Turn_Delay_Counter_Init	= 300

There are a few things to point out in the file above. The "include" statement causes the contents of another file to be inserted at that point. This is how you can reuse parts of code from other program files.

Then three variables are declared. In SBASIC, variables are 16 bits long by default. Variables can be used by name in other parts of the program.

The next part defines a set of constants. Constant values are used to assign symbolic names to values. Rather than having to remember that the left bumper sensor is connected to pin 1 on PORTC, we define a name called Sensor_Bumper_Left which holds the bit value.

I find that defining constants works best when you incorporate their use or associated variable name into the name of the constant. Otherwise, you can generate bugs in your code by using a similar sounding name or value on your variable.

Subroutines and Functions

Writing a single long line of code can be tedious, and can also increase your chances of coding in a bug. It is often best to create subroutines for handling blocks of code. If you code things up properly, you can also reuse code quite easily. The bulk of your software is going to exist in subroutines and functions. Most subroutines are very short, and consist of a well modularized bit of code.

The most common way to read through these subroutines is to find the Main entry point (next section), and to read through what code gets executed.

'***************************************************************
' Subroutines and Functions section
'***************************************************************
'
' Move_Forward sets both drive motors into forward motion
' Again, I like to categorize functions into groups, and
' make them explicit in the name.
'
Motors_Move_Forward:
        pokeb Motor_Port, Motors_Forward
        return
Motors_Move_Backward:
        pokeb Motor_Port, Motors_Backward
        return
Motors_Turn_Left:
        pokeb Motor_Port, Motors_Left
        return
Motors_Turn_Right:
        pokeb Motor_Port, Motors_Right
        return
Motors_Move_Stop:
        pokeb Motor_Port, Motors_Stop
        return
StartTurnDelay:
	TurnDelayCount = Turn_Delay_Counter_Init
	return
StopTurnDelay:
	TurnDelayCount = 0;
	return
UpdateTurnDelay:
	if TurnDelayCount <> 0
		TurnDelayCount = TurnDelayCount - 1
        endif
	return
'
' Initialize_Robot is called once when the robot starts. It will
' put the robot into a known state.
'
Initialize_Robot:
      '
      ' Setup the serial port for 9600 baud
      '
      pokeb   baud, $30
      pokeb   sccr2, $0c
      ' Set the data direction register for the sensor inputs
      pokeb   Sensor_Port_DDR, Sensor_DDRC_Value
      ' Set the motors to the off position
      gosub Motors_Move_Stop
      TurnDelayCount = 0
      BehaveState = Behave_Forward
      SensorInput = 0
      return
'
' Read sensors and put the values into a variable.
' This routine includes any 'processing' that needs
' to be done to the sensor data
'
Sensor_Read:
	' Read the sensor values
	SensorInput = peekb(Sensor_Port)
	' Mask off any output pins
	SensorInput = SensorInput AND Sensor_Mask
	' Input switches are active low. Flip the sense of
	' the bits so they are active high in the variable
	' This allows them to be tested correctly in the
	' rest of the program
	SensorInput = SensorInput XOR Sensor_Bumper_Mask
	return
'
' Behave is the routine that determines the robots behaviour. This
' routine will evaluate the sensors, determine the correct action,
' and make the robot do what it is supposed to
'
Behave:
    if (SensorInput AND Sensor_Bumper_Left) <> 0
	' We are hitting something on the front left.
	' Back off of it
	gosub Motors_Move_Backward
        gosub StopTurnDelay
        BehaveState = Behave_Back_Left
    elseif (SensorInput AND Sensor_Bumper_Right) <> 0
        ' We are hitting something on the front right.
        ' Back off of it
        gosub Motors_Move_Backward
        gosub StopTurnDelay
        BehaveState = Behave_Back_Right
    elseif (SensorInput AND Sensor_Bumper_Back) <> 0
        ' We just backed into something, go forward
        gosub Motors_Move_Forward
        gosub StopTurnDelay
        BehaveState = Behave_Forward
    else
        ' The bumper switches are clear. Therefore, we
        ' set our state based on the behaviour information
        select (BehaveState)
            case Behave_Back_Left
		' We were backing because of something on left
		' Turn to the right
		gosub Motors_Turn_Right
		BehaveState = Behave_Turn_Right
		gosub StartTurnDelay
            endcase
            case Behave_Back_Right
		' We were backing because of something on right
		' Turn to the left
		gosub Motors_Turn_Left
		BehaveState = Behave_Turn_Left
		gosub StartTurnDelay
	    endcase
	    '
	    ' The default action for the case statement.
	    '
	    ' If the TurnDelayCount is Zero, then we can
	    ' go forward. Otherwise, we will continue to
	    ' turn in the TurnDelayCount direction.
	    if TurnDelayCount = 0
    		gosub Motors_Move_Forward
		BehaveState = Behave_Forward
	    endif
	endselect
    endif
    return

Initialization

All programs have a starting point. In SBASIC, the name of the subroutine that is called initially is Main:

The Main routine is called when the controller resets or is powered up. Its job is to initialize variables, setup ports, and get the robot into a known state. I usually like to get the initialization done, then print a message to either the serial port or to an LCD display if one exists. This lets you know that the program has started, and also lets you see if the CPU resets for some reason.

'***************************************************************
'
' Main is the starting point for the program. It will handle calling
' the appropriate initialization routine, print out an indication that
' it is alive, then go into the Infinite Loop
'
'***************************************************************
Main:
      ' Initialize the robot first thing
      gosub Initialize_Robot
      ' Give some indication that the robot is starting.
      print "Robot program has started"
      '
      ' Being the infinite loop. Notice there is no way out of this loop
      '
      do
	    gosub Sensor_Read
	    gosub Behave
	    '
	    ' Do any housekeeping chores
	    '
	    gosub UpdateTurnDelay
      loop
      '
      ' Code after the final loop should never get executed, and therefore
      ' shouldn't exist. If you are paranoid, then you should put in some
      ' code to stop the robot in its tracks, and shutdown.
      '
end

The Main Loop

All computers systems end up with some sort of Main Loop. On your PC, there is a routine in the operating system called the Idle loop. This idle loop sits and waits for timers, disks, user input, and a host of other things. Robots also have a Main Loop. In the code sample above, you can see it labeled as the infinite loop. Its called that because it never ends. It calls Sensor_Read, Behave, UpdateTurnDelay, then goes back to Sensor_Read.

If you write a program on your PC and run it, the Main Loop (aka Idle Loop) will call your program to let it run. Your program will often have an 'end', 'exit', or 'return' statement at the end. This allows your program to return control to the Idle Loop.

On your robot, there is no Idle Loop to return control to. Therefore, you shouldn't have a return statement in your Main: routine.

Psuedo Code

A great trick used by professional software engineers is to start writing code by writing the comments first. This helps you get your program layout done before you invest too much time in getting all of the nitty-gritty details down. In Psuedo Code, you end up doing a layout for all of the routines, put in lots of comments as notes to yourself about what your program is about to do, and sometimes even add in little snippets of code that might explain your intent better than words. When you are done, you will have a skeleton outline for your program.

Quite often, you will find that you have overlooked some important point on your first stab. Thats OK. Since you haven't invested a bunch of time in writing the actual code, whacking out a large chunk of this Psuedo code is not a big deal.

Here, as example, is the Psuedo Code I did for the code in this article.

'
' A basic little robot program.
'
'***************************************************************
' Declaration and Variables Section
'***************************************************************
include "regs11.lib"
declare SensorInput	' Variable that holds input sensor data
declare BehaveState	' Variable that holds the previous behavior
declare TurnDelayCount	' Variable for timing turns
'***************************************************************
' Subroutines and Functions section
'***************************************************************
'
' Move_Forward sets both drive motors into forward motion
' Again, I like to categorize functions into groups, and
' make them explicit in the name.
'
Motors_Move_Forward:
        return
Motors_Move_Backward:
        return
Motors_Turn_Left:
        return
Motors_Turn_Right:
        return
Motors_Move_Stop:
        return
StartTurnDelay:
	return
StopTurnDelay:
	return
UpdateTurnDelay:
	' If the turncountdelay is not zero, then decrement it
	' by one.
	return
'
' Initialize_Robot is called once when the robot starts. It will
' put the robot into a known state.
'
Initialize_Robot:
      '
      ' Setup the serial port for 9600 baud
      '
      ' Set the data direction register for the sensor inputs
      ' Set the motors to the off position
      gosub Motors_Move_Stop
      ' Initialize all global variables
      return
'
' Read sensors and put the values into a variable.
' This routine includes any 'processing' that needs
' to be done to the sensor data
'
Sensor_Read:
	' Read the sensor values
	' Mask off any output pins
	' Input switches are active low. Flip the sense of
	' the bits so they are active high in the variable
	' This allows them to be tested correctly in the
	' rest of the program
	return
'
' Behave is the routine that determines the robots behaviour. This
' routine will evaluate the sensors, determine the correct action,
' and make the robot do what it is supposed to
'
Behave:
    ' if Sensors detect hit on front left, back off and turn right
    ' else if sensors detect hit front right, back and turn left
    ' else if sensors detect hit on back, go forward
    ' Otherwise, we have no current sensor input to deal with.
    ' The behaviour variables are evaluated. If last time through
    ' we were backing up, then we must be clear to make our turn.
    '
    ' Based on the behave state, turn as appropriate. If a turn
    ' is initiated, set a counter variable to keep track of how
    ' long we have been turning (TurnDelayCount)
    ' Otherwise, if we are currently turning, check to see if
    ' we have turned far enough by checking the TurnDelayCount
    '
    '
    ' If the TurnDelayCount is Zero, then we can
    ' go forward. Otherwise, we will continue to
    ' turn in the TurnDelayCount direction.
     if TurnDelayCount = 0
  		gosub Motors_Move_Forward
		BehaveState = Behave_Forward
     return
'***************************************************************
'
' Main is the starting point for the program. It will handle calling
' the appropriate initialization routine, print out an indication that
' it is alive, then go into the Infinite Loop
'
'***************************************************************
Main:
      ' Initialize the robot first thing
      gosub Initialize_Robot
      ' Give some indication that the robot is starting.
      print "Robot program has started"
      '
      ' Being the infinite loop. Notice there is no way out of this loop
      '
      do
	    ' Read Sensors
	    gosub Sensor_Read
	    ' Do behaviour
	    gosub Behave
	    '
	    ' Do any housekeeping chores
	    '
	    gosub UpdateTurnDelay
      loop
      '
      ' Code after the final loop should never get executed, and therefore
      ' shouldn't exist.
      '
end

Tricks of the trade

Here is a little random collection of notes:

1) Always read your sensory data into variables in memory, THEN do ALL of your processing using these stored values. If you continually read raw sensor data, you may find your program doing random things based on electrical glitches, or subtle changes in the state of your program.

2) Try to keep some sort of state variable about what your robot thinks it is doing at the moment. It will make debugging much easier. You can print out this state variable in your Main Loop to help track what the program has decided to do on each iteration.

3) Be generous with your internal comments. Make ample notes to yourself why you are doing certain things. This helps you in two ways. By explaining it in comments, it helps clairify in your mind what you are doing. They can be a great design/debug aid as you are forced to explain in the third person what the program is supposed to do. Second, when you need to come back to look at your code, sometimes these notes are invaluable.

Summary

I hope you found this article to be useful. The key points I hope you see are that programs have structure, that subroutines are used to keep from having a single long mass of code, and that the controller program should have a Main Loop. You also should keep in mind the tip about reading all of your sensor data in one chunk, and using memory based variables to do your processing.

A text file with the complete BASIC code is in robot.bas


What else do you want to know? Send me mail and ask away. Kevin Ross