0

Line Robot - program's design

Program structure

Open header file C:\Users\[Your login]\Documents\Arduino\libraries\mrm-robot\src\mrm-robot-line.h. The code has rather ample documentation. Here is a general view.

There are 2 main types of classes.

  • RobotLine. As the name suggests, this class represent the robot.
  • A number of classes derived from ActionBase base class and they represent various actions.

Inheritance and polymorphism

There are different kinds of robots that can be built using ML-R parts. There can be various programs, one for each robot. This would lead to repeated code - another important thing to avoid by all means. The solution that is used here is C++ inheritance: an object-oriented concept that is beyond scope of this short introduction. Just a short example, that is used id ML-R robotic code, also in this robot. We have a general concept of robot and some more specific robots: RCJ Rescue Maze robot and RCJ Rescue Line robot. This is "a kind of" relationship. Line robot is "a kind of" robot.

Without further going into details let's see what benefits we can get from this concept. All the general code will be a part of robot's program and we will can this part "Robot class". All the code that is specific for RCJ Rescue Line will form a part called "RobotLine class".

Where will a code that a specific sensor go? It may be used by RobotLine, but some other kinds of robots may use it. We will put it into Robot class. Where will the other code for sensors and effectors (like motors) go? We will put them all into Robot class. Well, most of it. If there is a specific function for some sensor, valid just for RobotLine, this part will go into RobotLine class. CAN Bus handling and many other things will also form Robot class.

The best part is that all that Robot class code doesn't need to be visible for the programmer of the RobotLine and will not clutter its program space. He will only need to change RobotLine code, like line-following algorithm, way the data in the evacuation are stored, etc. We will have a clear separation of both data and functions, which can be developed and tested independently.

We split the code among different classes, but how we will use it? A general example: let's imagine that You have to develop a program that has to drive a robot along a given trajectory. The robots can have quite different motor groups, for example soccer robots with 4 omni wheels that are positioned in a funny way, tank-like omni-wheel robots, etc. All You know is that they all are derived from the base clase RobotBase and they all implement a common function (go()) for driving the robot along any trajectory needed in Your task. Now comes polymorphism to rescue You. If Your function takes a pointer to RobotBase as an argument, all You have to do is use go() function in Your code. C++ will compile fine and will determine, in runtime, object's (robots) actual derived class and will invoke the correct implementation! So, You will not know which robots will use Your function and how to drive them, but they will still work all fine. If we program some other functions in this way, we will be able to make a program, not knowing which kind of robot will be using it! This is very powerful concept. You cannot do that in C.

Actions as classes

A robot program loops continually. A way to implement this behaviour is to have a main loop. Therefore, Arduino has one. How to program different actions using Arduino loop? In fact, it cannot be done well. You could have a big "switch" statement in the loop, calling each action's function. However, the different functions must use some common data. Function signatures can become quite long and there will always be some common data that will be implemented as global variables - and that is a bad programming design. Not to mention that this loop will get bigger and bigger: a real monster, without a clear separation of data and actions. So, the decision we made is not to use Arduino loop.

Instead we made a loop internal to RobotLine class, which uses other classes (derived from ActionBase) as actions.

Actions serve a few purposes.

  • They encapsulate in classes actions robot has to perform. So, we have classes for robot's parts, but here also for non-material terms.
  • No global variables are used. When an information should be shared between one (but called repeatedly) or more functions, it will be stored inside the action object. For example, all the start conditions will be in the object itself.
  • You can use inheritance to indicate relationships between actions, which indeed exist. For example, a movement can be movement straight ahead or turning.
  • You can use in a consistent way actions defined for the base robot, without its code being exposed here.
  • The actions are included in menus just by including a parameter in the constructor call.
  • Buttons can be used to start actions, as well as menu commands. Menus are displayed both in the connected PC and a Bluetooth device, like a mobile phone, and any of the 2 can be used to issue commands.

Frequency of the main loop

Program flow in a robot's program consists of a loop, that is constantly run and which invokes other functions. The question is: how much time shall these functions consume before returning to the main loop? Should they return almost immediately or there is no such a pressure? In other words, should the functions contain time consuming local loops?

Well, it is definitely easier to allow them to have some. For example, if a function's task is to turn the robot by 90º, it will surely be easier to have a local loop that compares compass to the target value, before returning to the main loop. Obviously, this is a profligate local loop, depriving the main loop of its higher frequency. Without that local loop, the main loop will have to call the function many times, each time checking if the target condition is met and this is harder to do. The function will be more complicated as it should do some preparing work in the first run (like setting the target angle value), that mustn't be done in following runs. So, it has to know if it is a first run or not. There are other problems, too.

There are some disadvantages of the described local-loop strategy. The frequency of the main loop is radically decreased, below 1 Hz. That is way to low for some work that has to be done regularly, like exchanging CAN Bus messages, blinking LEDs, or checking for some other events. True, some of there actions can be done using timer interrupts, but this may be awkward and complicates programming logic and debugging, as the program flow is no more deterministic, but rather jumping from one part into another. Interrupts also burden the MCU and can, in extreme cases, choke the program.

In our opinion, it is better to have a higher main loop frequency and we will show this approach. Action classes will mitigate problematic parts as the can contain data which can be used in different parts of program.