Soccer A robot - program's design
Zašto ovako?
Any normal Arduino course starts with something very alike hello-world-program in which a program prints "Hello world" or does a similar, simple task. While it is possible for us to go the same way, the process would be very long before we get any useful robot actions. There is different approach, not to use Arduino development environment, but to write a program that accepts only a limited user scripts, like Lego or Fischertechnik. We decided to use a third way, to give You total control and get the results fast. The downside will be that You will immediately start working in a complex, object-oriented, Arduino program. Follow the instructions and nothing bad will happen. Later, we will explain the details of this monster. Just imagine that we started with CAN Bus messaging to sensors and motors... You would be dead-bored before the robot started doing anything.
On the other hand, be ready for a steep learning curve. You will have to grasp rather advanced concepts right in the beginning. That will make sure that You will have much less problems later. The right concept will reveal itself later, cutting development time of any advanced robot manyfold.
If You are more oriented towards smaller code examples, related to specific hardware options, and concept presented here doesn't seem right for You, use this page to see other approach to the problem. You will be able to program CAN Bus and other low-level features.
Program structure
Open header file C:\Users\[Your login]\Documents\Arduino\libraries\mrm-robot\src\mrm-robot-soccer.h. The code has rather ample documentation. Here is a general view.
There are 2 main types of classes.
- RobotSoccer. As the name suggests, this class represent the robot.
- Many 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 Soccer robot. This is "a kind of" relationship. Soccer 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 Soccer will form a part called "RobotSoccer class".
Where will a code that a specific sensor go? It may be used by RobotSoccer, 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 RobotSoccer, this part will go into RobotSoccer 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 RobotSoccer and will not clutter its program space. He will only need to change RobotSoccer code, like ball-approach algorithm, line-avoiding algorithm, 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 (like this one) 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 RobotSoccer 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.