Department of Engineering

IT Services

IDP Software

Introduction

This document describes the software part of the Integrated Design Project. The software provides the overall control for the robot and, in this project, can either run in general purpose workstations of the type used for other programming activities in the course or be downloaded into the on-board microprocessor. The former environment facilitates software development and is hence commonly used in the development phase of projects which have complex software components. The latter is more suitable for late prototype and, of course, production versions of the system. As in the other tasks in the project, a kit of parts is provided. In this case, the kit consists of a set (a software library) of datatypes and functions which can be used by a C++ program to control the robot. The core of this library is a datatype (robot_link). There are different implementations of this for the two environments. For programs running in the workstation, the library controls the sending of three byte command messages from the workstation over a wireless network to the microprocessor in the robot (see later for further details). This microprocessor interprets these instructions, produces signals to control the hardware and sends back three byte response messages to the software in the workstation. For programs running in the microprocessor, communication with the hardware is direct. This communications mechanism is fully provided by the software library and the details, including whether messages are sent over the network or not, are unimportant to the programmer (one of the benefits of having software components). This document contains, for background information, some of the additional details of the implementation of the library.

A number of initial experiments are outlined which provide useful design data, familiarity with the programming environment and the library, and which calibrate some of the robot functions needed later in the project. Following this, the software development merges with the rest of the project and the aim becomes that of solving the overall robot design problem described in the Problem Statement.

Guidance is given on Software Engineering techniques, some additional features of the C++ programming language, and on aspects of report writing specific to the software part of the project. This guidance only suggests a general framework, there is a great deal of scope for individual creativity!

Methodology for Producing Software Systems

In the last thirty years, a new branch of Engineering, Software Engineering, has grown up. This, like other branches of Engineering, has a set of methodologies for designing and building (or implementing) systems. As in other branches of Engineering, one of the central ideas is that of decomposing a large difficult problem into a set of simpler sub-problems which may then be further decomposed into yet smaller sub-problems until a level of complexity is reached at which the solution becomes relatively trivial. The sub-problem solutions are the components of the complete solution and can be assembled to form a solution to the overall problem. Some of these components may pre-exist or may be useful at a later stage as parts of some other system; for example, components of the programs written during the initial experiments in this project are likely to be reusable as low level components in the final control program.

In the context of Software Engineering, the components are program elements like subroutines and functions. Associated with these are the datatypes and data objects on which they operate. Some methodologies (functional) place the principal emphasis on the program elements, others (object oriented techniques) on the data. Often the former approach is most suitable for the top levels (i.e. first levels of decomposition); whereas for the lower levels it may be easier to think in terms of the data first, then what operations are required on it and then to contain both within an abstract datatype (e.g. a C++ class). For this project, the low-level components which are provided are C++ classes but the code to be written can follow either methodology.

Another important design issue is ensuring that the software components fit together. This typically involves specifying what data each requires as input and what output it produces. The clearest specifications arise when the input is in the form of parameters to a procedure or function and the output (if any) is the value returned by a function. C++ classes may be useful for grouping related data and operations, and for limiting the number of separate objects which need passing around. Global data should be used with care.

The production of a software system can be split into a number of phases - The Software Lifecycle:

  • Requirements Definition - define what the system is required to do.
  • System Design - design the overall system structure.
  • Component Design - design the individual components.
  • Implementation - write the software which implements the component design.
  • Integration - fit the components together and to other non-software parts of the system.
  • Testing - making sure it works reliably (including its reponse to unusual or erroneous inputs).

These phases generally overlap and may not be entirely sequential, eg: component testing before integration; and iteration back from a later phase to an earlier one. Some iteration of this kind is expected but large loops back (e.g. testing revealing mistakes in the requirements definition) are clearly to be avoided if at all possible! Throughout the process, there is a further very important activity: the production of documentation.

Proper application of these techniques should result in software that is written and documented in such a way that it can subsequently be maintained and developed, not necessarily by the original authors. Bearing this in mind should help you to decide how successful you are being in producing good software.

When designing software, you should also bear in mind other engineering techniques which you have learnt. For example, the sequence of actions performed by the robot could be modelled as a state machine (IA Digital Electronics course) and the software could then be designed around this model.

Introduction to the use of the Software Library [back to contents]

The software library contains off-the-shelf components to control the robot and to provide some support functions. The facilities provided by the library, based around the class robot_link, are discussed in detail later. Not all the listed routines are required for the project; for example the instructions to control the ultrasonic sensors are only needed if these are being used. In addition, this document provides an introduction to some aspects of the C++ language (like bit manipulation and using classes) which are not covered in the IA Computing Practicals but which are likely to be useful for this project.

A Simple Example Program

The following program shows simple use of the robot software library and illustrates some of its essential elements. Note the use of the dot operator (‘.’) to access the robot_link member functions in the same way as accessing structure data (see later).

#include <iostream>
using namespace std;
#include <robot_instr.h>
#include <robot_link.h>
#define ROBOT_NUM 33   // The id number (see below)
robot_link rlink;      // datatype for the robot link

int main ()
{
int val;                              // data from microprocessor
if (!rlink.initialise (ROBOT_NUM)) { // setup the link
  cout << "Cannot initialise link" << endl;
  rlink.print_errs("  ");
  return -1;
}
val = rlink.request (TEST_INSTRUCTION); // send test instruction
if (val == TEST_INSTRUCTION_RESULT) {   // check result
  cout << "Test passed" << endl;
  return 0;                            // all OK, finish
}
else if (val == REQUEST_ERROR) {
  cout << "Fatal errors on link:" << endl;
  rlink.print_errs();
}
else
  cout << "Test failed (bad value returned)" << endl;
return -1;                          // error, finish
}

In this program, the header files defining the robot instruction set (robot_instr.h) and the software interface for the link (robot_link.h) are included.

The program initialises the link, sends a test instruction and checks that this returns the correct test result from the robot. This program could thus be run on a workstation to verify that the link and the microprocessor in the robot are operating correctly. The ID ROBOT_NUM is the number on the network interface card plugged into your microprocessor. A version of this program to be downloaded and run on the microprocessor would not need ROBOT_NUM information since it communicates with the local hardware (see later).

The Workstation Programming Environment

For programs to be run on the workstation, this part of the project mainly uses those facilities familiar from the Part I Computing Practicals, e.g. the IDPgeany program for compiling programs. Programs written during the project can be compiled using the facilities provided by the 1BRobotgeany desktop environment which is accessed via the icon at the top of the Teaching System screen and then clicking on the "Start 1BRobot" option in the "CUED 2nd Year" section. This environment has desktop icons similar to those for the 1A and 1B Programming Classes plus, project management software and a link to the IDP Web site.

If you need help remembering how to use this environment, the online help, the Introduction to the Teaching System and C++ links may be useful.

A special shared group file space is set up for each team for the project. Within this there are folders (or directories) for each team member (named with their login identifier) and a Common folder:

  • Each team member can read and write files in their own folder and the Common folder; and read files in one another’s folders.
  • There is an icon on their desktop for a team member’s own shared folder as well as the usual icon for their private home folder (for unshared files). Double clicking on this shared folder launches the file browser providing access to the folder and to the others in the shared group filespace.
  • There is a link created in each team member’s private home folder named idp shared pointing to the team’s shared folder. This is useful for accessing this folder from programs like a terminal window which start up in the home folder.

Geany

Just drop the source files all at the same time onto the icon and click on Make. Alternatively, drop a folder onto the icon (in which case all the *.cc, *.C, and *.cpp files in the folder will be used). Here are some frequently asked questions

  1. When I use the geany icon, the compiler complains about the robot commands - make sure you're using the IDPgeany icon, not the default geany icon. The IDP icons appear when you start a session using the 1BRobotgeany option
  2. When I use the geany IDP icon, Build doesn't work - you need to use Make in the Build menu: clicking on the Brick icon isn't enough.
  3. What files do I drop into the compiler icon? - all the files that comprise the project except files that you have #included. You need to drop them in all at the same time. Alternatively, drop a folder onto the icon, but make sure the folder only contains project-related source files.
    Note that you CAN'T add a new file to the project merely by creating the file from within geany, or adding the file to an already-running geany. Shut down geany, and drop in the new set of files.
  4. I've created a file in geany, but I can't compile it - Quit from geany, then drop the source file(s) into geany
  5. I've been developing code on windows. Now I've copied it onto the CUED machines it doesn't compile - use dos2unix to convert the file's line-endings.
  6. I have several independent source files in geany. Flicking between them and using Make doesn't work. - This version of geany assumes that the files initially dropped onto it form a single program. Close geany and load just one of your files in.
  7. I have more than one geany running, using files in the same folder. Using 'Make' in one compiles the files in the other. - Shut down all the geanys and drop files into the icon again. Use one geany at a time.
  8. The Execute ("gearwheel") button does nothing - this can happen if you're running a program that's in a folder you're not allowed to create files in.

Other IDEs

Several other Integrated Development Environments are available on the system. You're welcome to use them (Code::Blocks for example is in the desktop's Applications/Programming submenu) - or just use the command line. Note that

  • Compiling needs CCSWITCHES="-I/export/teach/1BRobot" and LIBS="-L/export/teach/1BRobot". You'll need to use "-l robot"
  • Cross-compiling needs CCSWITCHES="-I/usr/arm-unknown-linux-gnueabi/include -I/export/teach/1BRobot" and LIBS="-L/usr/arm-unknown-linux-gnueabi/lib". The compiler is arm-linux-gnueabi-g++

The Microprocessor Programming Environment

In addition to running programs on the workstation, it is also possible to cross-compile them and download them to run on the microcontroller directly. The processor in the microcontroller does not use the same machine code instructions as the workstation. It uses an Intel XScale PXA270 processor that incorporates an ARM microprocessor core. Code compiled to run on a workstation will not run on the microcontroller, instead you must use an ARM cross-compiler.

Cross-compiling, Downloading and Running Programs

The following very simple example shows how to cross-compile and run a program on the micro-controller. Suppose the program is in a file hello.cc containing

#include <iostream>
using namespace std;
int main()
{
  cout << "Hello World" << endl;
}

To cross compile and link the above program, drag the hello.cc file to the IDPgeany-arm icon (rather than the normal IDPgeany icon) on the desktop, and click on the Make button. This will produce an executable file named hello.arm (.arm distinguishes it from the executable hello which IDPgeany would produce to run on the workstation). Next

  • Copy the file to the robot. To do this you need to use the unix command line to run a program called scp (SecureCoPy). Use the Terminal in the Application menu's Favourites submenu to give you a Unix Terminal window with a command line.
  • A short Unix crib is online, telling you about 3 useful unix commands: ls (to list files), pwd (to see which directory (aka folder) you're in) and cd (to change directory). It's useful to go to the directory (aka folder) where your program is. You can do this by typing
       cd directory-name
    

    but there's a short cut. Using the File Viewer, display the folder where hello.arm is (in this example it's in tpl's IDP/python2 folder). In the Terminal window, type cd followed by a space, then with the mouse drag the folder's name (in this case python2) into the Terminal window. You should get something like this.

    Press the Enter key to change directory.
  • Now use the scp command from the Terminal window, replacing 100 in the example below by the number on the WiFi card.
    scp hello.arm team@wlan-robot100.private:hello.arm
    
    • scp - scp is the program you're running. It's not in the current folder (it's a system command) so you don't need an initial ./
    • hello.arm - the name of the file you're copying over.
    • team - the name of the user on the robot. It's always team
    • wlan-robot100.private - the name of the robot you're using. Don't use 100. Use the number on the WiFi card.
    • hello.arm - the name the file will have on the robot. It needn't be the same as the original file-name
    scp might print messages about authenticity. Don't worry - just say yes. It will then prompt for the team's password: enter the password printed on the side of the microcontroller box. When you type the password nothing appears on screen.
  • Run the program. First log into the robot, replacing 100 in the example below by the number on the WiFi card.
      slogin team@wlan-robot100.private
    
    After typing the password correctly you'll be logged into the robot and you'll be given a Unix prompt . You can type ls to list the files on the robot. You should see hello.arm there. To run it, type
       ./hello.arm
    
    (the '.' means the 'current folder' - by default, the system doesn't look in the current folder for programs, so you need to explicitly make it look there)

You can either logout from the microcontroller at this point to return to work on the workstation or, perhaps more conveniently, leave this terminal window logged-in to the microcontroller and open another on the workstation to do subsequent program development, compilation and downloads.

See A short Unix crib for information on how to use the command line.

Cross-compiling robot control programs

This follows much the same method as above. In the same way that IDPgeany does for the workstation, IDPgeany-arm knows about the appropriate header files and libraries for programs to be run on the microcontroller and can handle multiple source files as well as just one. The only difference in use between IDPgeany-arm and IDPgeany is that the output executable from the former is name.arm (and will only run on the ARM chip) whereas for the latter it is just name. Apart from using IDPgeany-arm rather than IDPgeany, the only other change normally required is to the initialisation of the robot link object (see later for details). When running on the workstation, the network interface card number is required; when running on the microcontroller, none should be specified to indicate that communication is local. This can most easily be achieved by using conditional compilation and using whether or not __arm__ is defined to distinguish between the two compilers and hence select the correct version. Thus for the program above, the rlink.initialise line is replaced by

#ifdef __arm__
   if (!rlink.initialise ("127.0.0.1")) {          // setup for local hardware
#else
   if (!rlink.initialise (ROBOT_NUM)) { // setup the link
#endif

Additional C++ Language Features [back to contents]

This section provides an introduction to some aspects of the C++ language needed for this project which are either not covered at all or only very briefly in the IA Computing Practicals. The on-line help system information on C++ may also be useful, particularly the CUED C++ data book and, for revision, the C++ Tutorial Guide.

Bit Manipulation

As well as being used to represent numbers, integer (int) and related datatypes may also represent a set of bits (e.g. signal values for an input or output signal from a chip). These bits can be manipulated using the C++ bitand, bitor and xor operators (or their equivalents &, | and ^) which are analogous to the corresponding instructions in a typical microprocessor instruction set, e.g.

int param;             // a value
const int bit0 = 0x01; // ’0000 0001’ individual bits
const int bit5 = 0x20; // ’0010 0000’ expressed in hexadecimal

// ... code to setup the link etc not shown

param = bit0 bitor bit5; // ’0010 0001’ set to 21 hex (33 dec)
// send command to set bits 0 and 5 in I2C chip at address 4
rlink.command (WRITE_PORT_4, param);

// get value from I2C chip at address 1
param = rlink.request(READ_PORT_1);
if (param bitand bit5) { // check if bit 5 is set
// ... and act accordingly
}

There are also C++ integer operators for left (<<) and right (>>) shift, e.g.

bit5 = bit0<<  5;

sets bit5 to 20 hex as above. Note that these operators are equivalent to multiplying/dividing by 2N , e.g.

bit5 = bit0 * 32; // is the same as bit5 = bit0 << 5;

(since 25 = 32) but the former (<<) is usually clearer than the latter when thinking in terms of sets of bits rather than numbers. The next section addresses a similar issue of clarity of programming style.

Hexadecimal Values

In the above, the prefix 0x before a number indicates hexadecimal notation, e.g. 0x10 is 0001 0000 in binary and 16 in decimal. This is probably most useful when expressing values corresponding to one or more bits being set, for example it is clearer that two bits are set in 0x1020 than if the same value were expressed in decimal (4128) but other than making the program more readable it does not make any difference which form is used. (Note that values with a leading zero (but no following x) are octal, ie base 8. Their only real importance to this project is as a possible cause of error when expressing a decimal value (e.g. 0100 is 64 not 100!).

Classes

C++ classes are an extension of the structured datatype struct in which functions as well as data may declared within the datatype as members. These member functions may be accessed in the same way as a data member, for example object.func(param).

In addition to having member functions, the members of a class may either be public (that is part of the accessible interface to the class) or private (part of the hidden implementation of the class and accessible only to the member functions). This is important from a Software Engineering standpoint as it means that the detailed implementation of a class may be changed without affecting programs which use the class, since these rely solely on the public interface.

The class stopwatch provides a simple example of a class. robot_link is a more complex example.

class stopwatch {
public:
   stopwatch ();
   void start ();
   int read ();
   int stop ();
private:
   struct timeval base_time;
   bool running;
};

In this case, the member functions are all public but the data members are private to the implementation of the class. Providing access to the class’s data only via functions is a commonly employed technique to ensure that the data values cannot be set to inconsistent values and/or that the detailed encoding of the data does not need to be understood by the rest of the program.

Constructors and Destructors

A member function with the same name as the class acts as a Constructor which is called automatically when the object is created, ensuring that suitable initialisation of the object is performed. One with the same name preceded by a ~ is a Destructor and is called automatically when the object is destroyed. The class robot_link has both a Constructor and a Destructor; stopwatch has only a Constructor.

Overloading of Member Function Definitions

Member functions can be overloaded, that is there can be more than one function with the same name but distinguished by the type of parameters given. The different forms of initialise for robot_link are an example of this.

Global Data

Variables declared within a function body are local to that function, ie they can only be referred to by code in that function. Variables declared outside any function body are global, that is they are visible to all functions from that point on. Global variables can be very useful as a way of avoiding having to pass values between functions as parameters or returned values. However incautious use can lead to programs which are very hard to understand and maintain. A useful pragmatic guideline is to have as global data only objects whose value is set by the top level functions (e.g. main) and read by several of the lower layers. The problems arise when global data is modified by many different parts of the system.

Multiple Source Files

It is frequently convenient to split the source code for a program into more than one file, e.g. so that different people can work on different parts of the program. This also makes it possible to arrange that data or subroutines are private to the file, i.e. hidden within the module so formed, much like the private section of a class. Where the declaration of a type, a function or a variable should be visible to more than one source file, this can be achieved by putting the declarations in a header file (customarily given the suffix .h) which can then be included in each of the source files which need access to the declarations. This ensures that all the source files use consistent declarations. The example in below shows how to include various system-provided header files.

#include <iostream>
using namespace std;
#include <robot_instr.h>
#include <robot_link.h>

The syntax to include files in the current directory is similar but uses quotes ("...") rather than angle brackets (<...>) to enclose the name. E.g. consider a source file (mysource.C) containing:

#include "myheader.h"
int aglobal = 3;
// global variable initialised to 3
static int alocal = 0; // variable local to all the functions in this file
int myfunc (int arg)
{
alocal += arg;
return (alocal);
}
// a global function which manipulates alocal

and the header file (myheader.h) with the global declarations:

extern int aglobal;
int myfunc (int arg);

This header file is included in any source files which refer to these global functions and variables (simply by using their names myfunc and aglobal). The header file is also included in mysource.C which defines the function myfunc and the variable aglobal so that the compiler can check that the declarations and definitions are consistent. The definition of a function specifies what it does, the definition of a variable allocates space in memory for it and optionally sets an initial value. Definitions of functions and variables must therefore occur in only one source file, whereas the declarations can be included in many. Type declarations, e.g. the class robot_link in robot_link.h, appear in the header file but the code defining any associated methods are in a separate source file implementing the class.

If you're having trouble, experiment with some minimal files.

Below is a multi-file version of the earlier test program

// header.h
extern robot_link rlink; 
int request();
// one.cc
#include <robot_instr.h>
#include <robot_link.h>
#include "header.h"

int request() {
return rlink.request (TEST_INSTRUCTION); // send test instruction
}
// two.cc
#include <iostream>
using namespace std;
#include <robot_instr.h>
#include <robot_link.h>
#include "header.h"
#define ROBOT_NUM 33   // The id number (see below)
robot_link rlink;      // datatype for the robot link

int main ()
{
int val;                              // data from microprocessor
if (!rlink.initialise (ROBOT_NUM)) { // setup the link
  cout << "Cannot initialise link" << endl;
  rlink.print_errs("  ");
  return -1;
}
val = request();
if (val == TEST_INSTRUCTION_RESULT) {   // check result
  cout << "Test passed" << endl;
  return 0;                            // all OK, finish
}
else if (val == REQUEST_ERROR) {
  cout << "Fatal errors on link:" << endl;
  rlink.print_errs();
}
else
  cout << "Test failed (bad value returned)" << endl;
return -1;                          // error, finish
}

Dropping one.cc and two.cc together onto the IDPgeany icon creates a file called Makefile behind the scenes. Clicking on geany's Make menu item will produce a program called two (named after the file containing the main function).

Note that it's not usually a good idea to

  • Use #include to include source files
  • Create variables in header files

Conditional Compilation

It is sometimes convenient to be able to select whether certain sections of code in a source file are compiled or not. C++ provides #ifdef SYMBOL (if SYMBOL is defined) and #ifndef SYMBOL (if not defined) and #else and #endif for this purpose. A typical example would be whether or not to include code intended for debugging purposes depending on whether the symbol DEBUG is defined, e.g.

#ifdef DEBUG
cout << "Nearing junction three" << endl;
#endif

The symbol (e.g. DEBUG above) could be defined either by using #define or by a command line option to the compiler. The mechanism can also be used to test for symbols predefined by the compiler, for example the C++ cross-compiler for the microcontroller defines the symbol __arm__ whereas the normal C++ compiler for the workstations does not. Thus to select appropriate code for use on the microcontroller or the workstation, use

#ifdef __arm__
// code for microcontroller version here
#else
// code for workstation version here
#endif

This mechanism is also useful to guard against multiple inclusion of header files. If your header files have the following structure

#ifndef MY_SYMBOL_H
#define MY_SYMBOL_H
// code here
#endif

(where the MY_SYMBOL_H part - the guard - is different in each file) then you won't accidently include a file more than once in a source file

Library - Mobile Robot Link [back to contents]

This section describes the low level routines in the datatype robot_link for setting up a network connection, sending instructions over it and handling errors. There is a one-to-one correspondence between a network connection and a data object of type robot_link; that is, a single data object controls a single connection between a workstation and a robot controller. The instructions are of two types:

  • commands which are instructions sent to the robot
  • requests which obtain information from the robot

The next section details these instructions. Later, more detail of how the instructions are communicated to the robot.

The robot_link Datatype

The public part of the C++ class robot_link (minus some specialist features intended for other applications) is defined in robot_link.h as follows:

class robot_link {
public:
// Construction/Destruction
robot_link();
~robot_link();
// Initialisation
bool initialise("127.0.0.1"); //connect on local computer
bool initialise(int card); //connect to network card N
bool initialise(const char *host); //connect to specified host
bool reinitialise(); //reconnect after error
// Input output
bool command (command_instruction cmd, int param);
int request (request_instruction);
// Error handling
void clear_errs();
link_err get_err ();
link_err lookup_err (int n);
void print_errs(const char *prefix);
void print_errs();
bool any_errs ();
bool fatal_err ();
}

The initialisation and input/output functions set appropriate error values if an error occurs.

Initialisation

Object Creation

The Constructor (robot_link) and Destructor (~robot_link) provide basic initialisation of a robot_link object and orderly shutdown of the link when it is destroyed.

Link Initialisation

The member function initialise sets up the network connection between the workstation and the robot’s on-board microprocessor.

  • bool initialise("127.0.0.1") - initialises a connection locally, e.g. for when the on-board microprocessor is running the control program.
  • bool initialise(int card) - initialises a connection to the given numbered network interface card.
  • bool initialise(const char *host) - initialises a connection to the named host.

In each case a boolean value is returned indicating whether the initialisation has succeeded.

Instruction Input/Output

Input/output involves the exchange of request and command instructions between the program and the robot microprocessor. Each instruction consists of an opcode (the request or command to be performed) and a parameter (the value associated with it). The interface is provided by the member functions command and request:

  • bool command (command instruction cmd, int param) - sends the command cmd with parameter param. It returns true if no error has occurred.
  • int request (request instruction) - sends the request req and returns the value supplied by the microprocessor or the special value REQUEST_ERROR if an error occurs.

Link Resynchronisation

  • bool reinitialise() - closes and reopens the connection. It is typically used when recovering from a link error (see ErrorHandling), eg
    if (rlink.fatal_err()) { // check for errors
    rlink.print_errs(); // print them out
    rlink.reinitialise(); // flush the link
    }
    

Error Handling

Link operations (initialisation and input/output) may fail. Codes representing these errors are stored in the class robot_link and these stored values may be retrieved in various ways for subsequent processing. In addition to the error codes, counts are kept of certain types of error.

Error Codes

The type enum link_err defines the following error values:

  • LINKERR_NONE - special value returned by get_err when there is no error to report.
  • LINKERR_COMMS - a fatal (ie unrecoverable) communications error.
  • LINKERR_READ - a read error receiving data from the link.
  • LINKERR_WRITE - a write error sending data to the link.
  • LINKERR_CHKSUM - a checksum error in the instruction packet returned from the microprocessor.
  • LINKERR_NOTINIT - attempt to use the link before it is initialised.
  • LINKERR_BADCARD - the specified card number does not correspond to any in the list of known cards.
  • LINKERR_BADHOST - the specified host name does not correspond to that of any known computer.
  • LINKERR_BADPORT - invalid port number specified.
  • LINKERR_SKT1
  • LINKERR_SKT2
  • LINKERR_SKT3 - errors setting up connection to remote computer.
  • LINKERR_NOHOST - the specified host could not be contacted.
  • LINKERR_NOSERV - the network service (robot_link) for this application is not running on the specified host.
  • LINKERR_OVERFLOW - too many errors to record. If too many errors occur, this value is stored and further logging of errors is stopped.

Resetting Error Recording

  • void clear errs() - Clears all recorded errors and error counts (as at initialisation of the link).

Retrieval of Error Codes

  • bool any errs() - returns true if any errors are stored in the error record.
  • link_err get_err () - returns the next recorded error code or LINKERR_NONE if no more are recorded. This operation removes the record of this error.
  • link_err lookup_err (int n) - returns the nth recorded error or LINKERR_NONE if fewer than n are recorded (or n is less than one). This does not alter the error record.
  • bool fatal_err() - returns true if an unrecoverable error has occurred since the last time the error record was cleared (by clear_errs or print_errs).

Printing Error Descriptions

  • void print errs(prefix) - prints out all the recorded errors, one per line, prefixing each line with the character string prefix. The error record is cleared.
  • void print errs() - as above but with no prefix string.

Mobile Robot Instruction Set [back to contents]

The command and request instructions are described together, grouped according to the area of robot activity controlled. The examples assume that a robot_link object, rlink, has been de-clared.

General I2C Bus Input/Output Instructions

rlink.command (WRITE_PORT_0, v)
...
rlink.command (WRITE_PORT_7, v)

Writes the value v to the I2C PCF8574 chip with the wired address indicated by the command opcode given as the first parameter. The concept of I2C wired addresses for the PCF8574 is explained in the Electronics Design section.

v = rlink.request (READ_PORT_0)
...
v = rlink.request (READ_PORT_7)

These return the value v read from the I2C chip with the wired address specified by the command opcode. Note that the value read for a bit is low if either the external input is low or the value previously written to the bit is low - in other words, for a bit to be useful as an input it must first have a one written to it (as for bit 5 in the example earlier). See the Electronics Data Book E-03 for further details.

A to D Converter Instructions

v = rlink.request (ADC0)
...
v = rlink.request (ADC7)

These return the value v from the analogue to digital converter port (0..7) specified by the command opcode.

Motor Instructions

rlink.command(MOTOR_1_GO, speed);
rlink.command(MOTOR_2_GO, speed);
rlink.command(MOTOR_3_GO, speed);
rlink.command(MOTOR_4_GO, speed);
rlink.command(BOTH_MOTORS_GO_SAME, speed);
rlink.command(BOTH_MOTORS_GO_OPPOSITE, speed);

The parameter speed for each of these commands is of sign-magnitude form: it ranges from 0 to 127 and the highest bit, if set, specifies the reverse direction. Thus, 153=128+25 gives a slow reverse. The relationship between forward/reverse and clockwise/anticlockwise depends on the gearbox ratio. The first four commands refer to the single motor specified. The last two commands affect motors 1 and 2, in the first case rotating the wheels in the same sense for both these motors and in the second in opposite directions.

speed=rlink.request(MOTOR_1);
speed=rlink.request(MOTOR_2);
speed=rlink.request(MOTOR_3);
speed=rlink.request(MOTOR_4);

These return the current demanded speed for the relevant motor, encoded as for the motor-go commands above. This takes into account the effect of ramping (see below) but not mechanical load on the motor.

rlink.command (RAMP_TIME, t);

This sets the ramp period t for the motors (controlling how quickly the motors reach their target speed). This should be chosen according to the friction between the wheels and the surface to obtain a good compromise between wheelspin and slow response problems. t ranges from 0 (no ramping) to 254 (slow ramping), with 255 specifying the default ramp period.

Note that when the program ends the motors stop.

Status Register Instruction

stat = rlink.request (STATUS)

returns the contents of the microprocessor’s general status register. This contains the following bits, several of which correspond to the diagnostic LEDs.

Emergency Stop Instructions

The emergency stop commands allow the robot motors to be stopped automatically, at a rate specified by the ramp time, when some condition or set of conditions is met. One of the most common uses is to stop the robot, when a microswitch closes, to prevent collision damage. A combination of bits being set high (specified by Mh) and/or low (specified by Ml) on the I2C chip at address (n) may be used. Mh and Ml should be altered only while Emergency Stop Mode is disabled otherwise unpredictable behaviour may result. Both Mh and Ml are initialised to zero when the controller is powered up or reset.

  • rlink.command (STOP SELECT, n) - enables Emergency Stop Mode, selecting the I2C chip at address n to be polled continuously for the emergency stop condition. n is the I2C wired address as described in the Instruction Input Output section, except that specially a value of n with the most significant bit set (e.g. 128) disables Emergency Stop Mode. Emergency Stop Mode must, if required again, be explicitely reenabled after it has been triggered.
  • rlink.command (STOP IF HIGH, Mh) - the emergency stop condition will be triggered if any of the bits set to one in Mh is high in a value read from the I2 C chip.
  • rlink.command (STOP IF LOW, Ml) - the emergency stop condition will be triggered if any of the bits set to one in Ml is low in a value read from the I2C chip.

Thus to cause an emergency stop when either bit 0 or bit 2 becomes high or bit 4 becomes low on the inputs to the I2C chip at address 4:

rlink.command (STOP_IF_HIGH, 0x05);
rlink.command (STOP_IF_LOW, 0x10);
rlink.command (STOP_SELECT, 4);

n.b. Up to at least Oct 2013 there's a bug that means only motors 1 and 2 stop.

General Instructions

  • value = rlink.request (TEST INSTRUCTION) - This should always return the constant value TEST_INSTRUCTION_RESULT (as defined in robot_link.h). Reception of any other value indicates a communications error.
  • rlink.command (SHUTDOWN ROBOT, 0) - This command causes emergency shutdown of the robot and it is returned to near initial state, eg the motors are stopped and most settable parameters are reset to their default values. The parameter value is ignored.

Library - Support Routines [back to contents]

Input/Output to Files

Information on input/output to files is provided in the sections on Text Input and Output and Files in the C++ chapter in the Programming Databook.

The stopwatch Datatype

The public part of the C++ class stopwatch is defined in stopwatch.h as follows:

class stopwatch {
public:
   stopwatch ();
   void start ();
   int read ();
   int stop ();
};

Initialisation

The Constructor (stopwatch) performs basic initialisation of a stopwatch; it is created stopped.

#include <stopwatch.h>
...
stopwatch watch;

Starting Timing

watch.start();

Causes stopwatch watch to start (or reset and restart) timing.

Reading the Elapsed Time

etime = watch.read();

Returns the elapsed time etime in milliseconds since the watch was (re)started. If the stopwatch is not running, a value of zero is returned.

Stopping Timing

etime = watch.stop();

Causes stopwatch watch to stop timing. The elapsed time (as for watch.read) is returned.

Time Delay

The delay Function

#include <robot_delay.h>
...
delay (msecs);

delay (defined in robot_delay.h) provides a time delay of approximately msecs milliseconds.

The Diagnostic LEDs [back to contents]

The eight LEDs have the following functions:

Bit   Color Function
0   Red       Communications error (R)
1Red I2C Bus error (R)
2Yellow Emergency Stop triggered (R)
3Yellow Emergency Stop Mode enabled
4Yellow Robot Moving
5Yellow Ramp function enabled
6Green Communications active
7Green Watchdog (flashes while microprocessor is running)

The bits marked (R) are reset on read.

Sensor Simulator PCB [back to contents]

This circuit board enables software routines which interface with I2C chips to be tested before the electronics and mechanical systems have been produced. The board has a single PCF8574(A) I2C chip (see E-01 for further details) whose wired address may be set to any 3 bit value by a set of switches. The chip has 8 input/output lines and another set of switches may be used to provide input data, and LEDs to indicate the output values.

Board Layout

Using the Board

The chip’s address must be set before power is applied to the system (if the address is changed with power applied, the system may crash).

To use the chip for output, set all the data switches to 1 (the switches will otherwise override the chip’s outputs). The LEDs will then indicate the values written to the chip’s output port.

To use the chip for input, set the data switches to the required value and note that this value should also be indicated on the LEDs (on = 1). If the LEDs for bits which should be high are not on, this is likely to be because the corresponding bits in the chip’s output port have not been set high and are overriding the switch value (see the section). Port read operations will return this value.

Connect the transformer (a black plug) to the I2C box with the power cable, and connect the Sensor Simulator PCB to the I2C box with the grey ribbon cable. Then power-up and wait a couple of minutes until the WiFi card's lights briefly flash. Your I2C box might eventually use a few ports to communicate with the robot. Each port can handle 1 byte of data (8 bits; a value in the range 0-255). The Sensor Simulator PCB can be used to simulate one port at a time - don't change port while the power's on. Set the port address to a value in the range 0-7 (the yellow switch with a 4 on it isn't used). If for example you set it to 3 (by setting the brown and red switches to 1), then you'll need to use WRITE_PORT_3 when you send data, and READ_PORT_3 to get input.

  • Testing output - Set all the data switches to 1 (the switches will otherwise override the chip's outputs). In your program, put something like
      
      rlink.command(WRITE_PORT_3, 64+16+4+1);
    
    When you run the program, the LEDs corresponding to the bits set in the transmitted data will be lit. Try writing a program to set the LEDs on one at a time at half-second intervals.
  • Testing input - Set the data switches to a value of your choice. This simulates a value that your robot's sensors might be returning. Note that this value should also be indicated on the LEDs (on = 1). If the LEDs for bits which should be high are not on, this is likely to be because the corresponding bits in the chip's output port have not been set high and are overriding the switch value, so run a program with rlink.command(WRITE_PORT_3, 255); in it. If you then have
      int v=rlink.request (READ_PORT_3);
      cout << "Value="  <<v << endl;
    
    you should see the value that you set the data switches to.

Details of the robot command interface [back to contents]

This section contains background material which may be of interest but is not essential to completing the project. Communication between a workstation and the onboard microcontroller on the robot takes place via the Departmental Ethernet network. The workstations and cluster server are on one subnet and the robots on a special restricted-access wireless subnet. Traffic is routed between these subnets by the network infrastructure. Instructions (commands and requests) sent from a C++ program and responses sent by the robot each consist of a three byte message: the command identifier, its parameter and a checksum. The robot never generates unsolicited messages, that is, each message from the robot is in response to a command or request from a program. Similarly all commands and requests result in a response message from the robot.

When the control program is run directly on one of the EIETL workstations (e.g. tw901) rather than a cluster server (e.g. tw900), the link is likely to have a slightly more predictable response time since these machines would not typically be running jobs concurrently for other users or needing to compete with other processes for access to the network.

The class robot_link illustrates the way in which implementation-specific detail can be hidden. The interface to a robot_link object is the same irrespective of the communication mechanism used, for example an earlier version of the IDP robot controller used a RS232 serial line rather than a network connection but had an almost identical software component interface (the differences being other versions of initialise() and some new error codes).

robot_link also illustrates how careful checking of types can help to prevent programming errors. For example, the request member function can only operate on a request_instruction.

Tasks to be performed [back to contents]

Formulating the Software Requirements

You should start to define the functionality of your software at an early stage both to ensure that the software you write can meet the evolving system specification and so that you can start to produce components of it (eg procedures and suitable datatypes) as soon as possible. Bear in mind when designing and writing your software that the design of other parts of the system may have to change due to unforseen problems and that the software should be easily adaptable to these changes.

One aspect of the design which will need early thought is the line following algorithm since this will depend on such factors as response time and sensor placement.

Initial Tasks and Experiments

The following tasks can be performed with the basic kit of parts and the test chassis before the mechanical team have produced the real chassis, i.e. starting as soon as the initial system design phase has been completed. Some of the electronics subsystem will be needed for the later tasks so it is important to discuss the scheduling of this with the electronics sub-team. The aim of these experiments is to provide useful design data, to provide a framework for gaining familiarity with the software task and to lead on to the production of software components for the final system. As well as conducting these experiments, it is essential to bear in mind that most of the software must be written prior to integration with the completed mechanical and electronic subsystems, i.e. you should aim to have all three sub-systems completed at roughly the same time.

  1. Try the Warm-up computing exercises
  2. Verify that the example test program given above will compile and run without error on the workstation and that the "communications active" LED lights while the workstation is communicating with the robot. What happens if i) the robot is turned off and on while the program is running, or ii) the program is stopped and re-run without restarting the robot? Can you detect and recover from errors without restarting the program?
  3. Determine the time taken for the complete operation of sending a test instruction from the workstation and receiving a response from the microcontroller. Hint: Use the facilities provided by the stopwatch class and, to mimimise the effect of program startup time, use a largish number of repetitions of the robot test instruction.
  4. Familiarise yourself with cross-compilation for the microcontroller, downloading programs and running these.
  5. Familiarise yourself with the operation of the Sensor Simulator. Verify that the simulator can correctly simulate the input and output of data at the addresses chosen for the PCF8574 chip(s) in the circuitry being developed by the electronics sub-team. Pay particular attention to the initialisation required to use individual bits (i.e. signals on the chip’s input/output pins) as inputs and hence the possibility of using some pins as inputs and others as outputs. Discuss with the electronics sub-team the allocation of signals to the individual bits in these chips and hence develop the routines to read and write the chip(s).
  6. Compare the speed of various operations, e.g. reading sensor and ADC values, using a program running on the workstation and one running on the microcontroller. Bear in mind that: the workstation processor is fast but the connection to the microcontroller is limited by bandwidth and traffic; whereas the microcontroller’s ARM-based processor is much slower but commands and requests do not need to be transferred over the wireless network. What effect does output to the screen, e.g. for debugging, have on the speed in each case?
  7. Calibrate the motor speed and acceleration against the values of the parameters to the motor and ramp commands for all the motors you intend to use (they will not necessarily be identical). This information will be of help to the mechanical designers in determining a suitable wheel size. You should discuss these results with them in light of the figures they have on the effect of load on motor speed.
  8. Verify the operation and effect on the Status Register of the emergency stop commands STOP_SELECT, STOP_IF_HIGH and STOP_IF_LOW.
  9. Consider, with the Mechanics team, the effect of controller gain and line-following sensor position on the damping ratio and motion of the vehicle. What constraints on sensor position are imposed by: i) control system stability; ii) cornering and reversing; and iii) the proposed construction? Controller equations for a simple system are given in the Systems Design section D.A. The test chassis may allow you test out some of these ideas and to start to develop line-following code.
  10. Consider, with the rest of the team, the effects of motor dynamics and system inertia on stopping and starting the robot.
  11. Develop prototype line following code, and hence test out your conclusions about sensor positioning, using the test chassis and interface circuitry produced by the electronics team for the sensors.

Other Tasks

Alongside these tasks, the other sub-teams will be producing their subsystems and may need you to produce test programs to help to test these out. For example at some point you will be able to replace the test chassis by the proper one, so you'll need to re-test your line-following code. You should discuss these requirements as a team. You may find it useful to create a test suite that can be re-run whenever a significant robot modification happens.

Towards the end of the above tasks, you should be in a position to start the Software Lifecycle for the complete final program which will control the robot when it is performing the task described in the Problem Statement. See the Design Acceptance section for guidance on what the design and implementation documentation should contain and the earlier discussion of software engineering and the Software Lifecycle.

Testing should take place alongside the mechanical and electrical systems as each is refined to produce the final working robot. Initially this testing is likely to comprise component testing of particular software modules, e.g. line following, against specific electrical and mechanical subsystems before the final complete system testing is undertaken. It is important to aim to ensure that any problems are discovered as early as possible in the development process.

Design Acceptance

Before accepting your design, the demonstrator will expect to see a "Design Acceptance" document sufficient to allow a competent team of programmers to produce a working system conforming to your design

Your documentation should include:

  1. What was learnt from running the test programs - timings? ramping?
  2. a description (flowchart or pseudocode) of your overall strategy - the route you'll take, etc
  3. the overall function of the software system (e.g. using flowcharts);
  4. its structure in terms of the interaction of the component parts (e.g. datatypes/objects, modules, functions and procedures);
  5. its interface to the other subsystems (e.g. pin allocation on chips);
  6. example datatypes and low level procedures (e.g. those developed during the initial testing process)
  7. BoM - a list of function prototypes + associated function line-count
  8. A list of three sub-systems operations which will be demonstrated as part of the Basic Functionality Demonstration

Basic Functionality Demonstration

3 demonstrations are run by the software team. The details vary according to the task, but usually

  • There's a "team" task involving line-following and negociating a corner
  • There's a test to check on alignment for pick-up
  • There's a test for control of actuators (if appropriate) at the delivery point

You can only run the tests that you specified in the Design Acceptance

The tests need to be sufficiently challenging to be worthwhile, and need to have outcomes whose success can be easily and unambiguous established. For example, in L2 2014, some tests were

  • From the start box, make a turn (that you have identified to the marker beforehand) and continue for at least 10cm.
  • From a junction of your choice climb a slope, align at a delivery point and stop.
  • From the start box, align ready to collect from collection point C2 and activate an actuator (you needn't have a working mechanism attached)

The competition

N.B. A complete printed copy of the C++ code you wrote must be supplied at the end of the competition. Marks are awarded for

  • readability and maintainability of the program
  • smoothness and speed of the robot's movement especially when line following
  • provision for error recovery

Software Components [back to contents]

PartQuantity
Software Library 1
Microcontroller module 1
Power supply unit plus output lead 1
Al Chassis kit 1
Sensor simulation pcb + I2C cable     1

See Also [back to contents]