SumoORE C Tutorial

Overview
There are many different sumo strategies that can be attempted. The default simplesumo function that comes with the software libraries shows one way : roam the ring, avoid the edge and hope that the other robot happens to be spotted. This can be a good strategy as it keeps the momentum of your robot up but your robot is exposed to attack from the side and back and is particularly vulnerable when near the edge of the ring.

Another common minisumo strategy is to spin around continually searching for the other robot. When that robot is spotted, charge it. Because your robot is continually changing direction, it is less likely that the other robot will be able to attack it from the side. You are also able to search a greater area of the ring more quickly. The downside of this approach is that you essentially are starting from 0 speed when you see the other robot so you may not have as much momentum and force when you contact the other robot to start pushing it.

Adding a new function to the code
Lets go ahead and implement this strategy to see how it does in the ring.

First we start with the function itself. Open up the usercode.c file and add the following text:

'spinsumo' is the name of our function. The 'void' before the function names means that our function will not pass any numbers back from it with a 'return' statement when it ends. In fact our function will never end. The '(void)' after our function name tells the compiler that we will not be passing any numbers to this function. You can think of the '{' and '}' charcters like a BEGIN and END. Everything inside these characters will be part of our function. The C language uses these characters quite a bit. They are simply a container where our code will go.

Displaying Text on the LCD
The first thing I like to do is to have the function display something on the LCD to let us know that our code is running and the robot is doing what we expect.

The lcd_print function will display something on the LCD for us. The '\f' sends a special character to the LCD that will clear it and place our cursor at the upper left location. Everything after that will start from that location. The '\n' character tells the LCD that everything after it will be placed on the next line. In this case, that is the second line.

So 'lcd_print("\fSpinSumo\n")' will clear the screen, print SpinSumo on the first line and then move the cursor to the second line.

I also like to display the current battery voltage at the start of the program just to make sure that we don't accidently run the battery down too much while doing our testing. 'lcd_print("B:");' will print B: to the second line. 'display_batt;' will then print the current battery voltage right after the B:.

Comments in C begin with a //. Its a good idea to have lots of comments in your code to make it easier to understand later. Its easy to forget what your intent was with the bits of code so a little hint here and there can be incredible useful.

Adding a 5 second pause
Ok, time to get into the meat of the program. The first thing we will do is count to 5 seconds. Minisumo robots start to battle on the count of 5. That gives the robot operators a chance to move away from the robots so that they don't accidently see things outside of the ring that may interfere with with.

We've added code at the start of our function and at the end. At the start, we needed to declare a variable that we could use to do the count down with. Variables which are declared inside a function are local to this function. That means that we only can use them here, not anywhere else in the program. That's fine for us here, as we aren't planning to leave this function and this is just a simple counter anyhow.

Lets take a closer look at the variable we created : 'unsigned char i=0'. This makes a variable called 'i' which starts with a value of 0. The variable will be an unsigned char. What does that mean? Well, unsigned means that we will only have positive values held here, not negative values. char means that it is 8 bits in length. In other words, this variable can be anything from 0 to 255. Why did we name it 'i'? No reason other that it is short to type and it doesn't hold anything important to us, so we don't bother giving it a more descriptive name. i,j and k are commonly used for counter names in C. We could have called it anything though.

There are lots of other types of variables, such as int (16 bits) and long (32 bits) that we could have used instead. We also could have declared it as signed, meaning that it could be negative. If we had made it a signed char, it would only be able to hold values from -128 to 127. This still fits the range that we need, given that we are going to just count from 0 to 5, but there is a reason for picking the unsigned char variety. The PIC processor that we are using is an 8-bit processor. The most efficient variable type is an unsigned char and results in the fastest and smallest code in this case. Because we didn't need a bigger variable, we stuck with the littlest one.

Now lets move the second bit of code that we added. Ok, here is our first real introduction to the strange syntax of C. Loops (where the code does something more than once) are written using the for(..;..;..){} syntax. If you are familiar with basic, this is the equivalent of the standard for-next loop.

In this case we want to count down from 5 to 0. When the count reaches 0, we want the robot to start to battle. The C statement for this is 'for(i=5;i>0;i--)'. The first 'i=5;' means that the count will start from 5. This is only executed once, at the start of the loop. This is our start condition. The next 'i>0;' is our end condition. We are telling the robot that we want it to keep going through the loop as long as our counter (i in this case) is greater than 0. And finally, the 'i--' is executed each time through the loop. In C, we actually have to tell the compiler how to adjust our counter. So what does 'i--' actually mean? Well, it is shorthand for i=i-1. We want the value i to decrease every time the loop is run.

The first time through the loop, i will be equal to 5 (or start value). This is greater than 0 (or stop condition), so the code inside the loop will execute. In this case, we position the cursor on the LCD at the last position of the second line with 'lcd_gotoxy(2,8);' and then print out the current value of i in this location with 'lcd_print("%u",i);' This means that the number 5 will be printed on the LCD. Lets describe the lcd_print a little more. We already know that '\f' clears a screen and that '\n' moves the cursor. Now we see a way to print variables to the LCD using %u. '%u' inside the "" marks is used for printing out integers. This is sometimes called a format placeholder, meaning the we are creating a placeholder for a variable that we want to be displayed in our string here. The ',i' after the "" is which variable we want to print out here. There are lots of formatting commands that can be used here. %i or %d for signed integers, %x for hex values, %c for printing a character and %% for actually printing a percentage character. lcd_print accepts the same formatting codes as regular C printf, so you can read up on printf if you are interested in finding out more.

Ok, We print 5 and then 'delay_ms(1000);' is executed. This tells our robot to sit and do nothing for 1000 milliseconds which is 1 second. When it is finished with waiting, it will jump back to the for loop, execute the 'i--' for us so that i is now 4. 4 is still greater than our stop condition of 0 so it executes the lcd_gotoxy and lcd_print commands again, waits.... this continues until i = 1. When i = 1, after the delay the code will jump back to the start, subtract 1 from i. Now i = 0 which matches our stop condition. The loop will not execute this time and the program will now start running from after the end of the loop statement (marked with the } bracket which matches the { bracket at the start of the for loop}). In this case, clear the LCD and print out 'FIGHT!'.

Start up the motors
Ok, time for our robot to actually move. Lets start by moving the left motor forward and the right motor backwards so that our robot turns in a clockwise spin.

When our robot first starts up, the motors are disabled so it doesn't run off wild. So the first thing we need to do is enable the motors. We do this with the 'flags.servoEnabled=TRUE;' Setting this to FALSE would stop the motors from moving.

Our motors here are actually RC servos that have been modified to rotate continuously. The set_servo command starts sending a pulse to the servos which will make them turn one way or the other at different speeds. We can refer to the servo that we want to control with LEFT or RIGHT or BOTH, the direction with FWD or BWD and then the speed with a number between 0 (stopped) and 100 (max). In this case we are using a speed of 20 as a starting point. The robot will be turning, but not at full speed.

Scan for the Opponent
Ok, the robot is moving. Now we need to start scanning the sensors so we can actually spot the enemy!

Ok, we've added 2 more variables called left and right. We will use these variables to store the IR readings from our 2 front enemy sensors. We've declared them as unsigned char again as we aren't going to be dealing with numbers bigger than about 100 here.

We've created a new loop with the 'for{}' statement. Because we've given the compiler no start or stop conditions, this loop will never end. The robot will just keep running the code inside this loop over and over until you either turn off the robot or the batteries run dry. You can think of this as a repeat...forever command.

Inside the loop we do a small delay of 10 milliseconds and then read the left and right sensors using the read_range command. We place the readings from this command into our two new variables left and right. Now that we have some readings, we need to decide what to do based on that. If there is no enemy detected, the left and right variables will contain a 0 value. If there was some IR reflection detected, these variables will hold something other than 0.

For this demo, we will use 60 as a threshold value here for IR detection. If the left or right variables hold a number greater than 60, that means that an enemy was detected. If they hold less than 60, no enemy was detected and we can keep on turning and searching. 60 is a good place to start as it ensures and nice strong reflection for our enemy before we start charging. You can experiment with this value later.

Attack the Opponent
Lets add some code to make the robot do some things when it sees the other robot during its spin.

Wow... ok, we added a bunch of stuff. Lets work through what we've done here.

There are 4 cases of what the robot will be able to see with the front sensors: 1) both left and right sensors detect an enemy 2) only the left sensor detects an enemy 3) only the right sensor detects an enemy 4) no sensors are detecting and enemy

For the 1st case, we need to check the left sensor AND the right sensor. In C, you use the && symbol instead of typing the word AND. So the line 'if ((left>60)&&(right>60))' really means 'if left is greater than 60 AND right is greater than 60 then do whatever is between the {} brackets'. In this case, we set both servo motors to the forward direction at full speed (100).

For the 2nd case, we only need to check the left sensor. If the first case wasn't true, then the line '} else if (left>60){' will check to see if left by itself is greater than 60. If it is, code within the brackets here will run. If we are detecting a robot in front of us but to the left, we need to turn towards that robot before charging it. What we will do here is turn the right motor to full speed ahead and the left motor to stopped (speed=0). This will rotate the robot towards the left.

The 3rd case is very similar but we are just checking the right sensor this time with the line '} else if (right>60){'. If this is true, we do the same as case 2 but this time the left motor is full speed ahead and the right motor is stopped. This will rotate the robot towards the right where the enemy robot is.

And finally we have our 4th case. If the sensors are not detecting anything at all, we continue spinning the robot clockwise at a speed of 20. In this case we don't need an 'if' statement. If the first 3 cases were not true, we simply run this code.

So our robot will spin around in circles until it sees the other robot and then it will charge that robot. Perfect. What happens if we start charging the robot and it slips away from us somehow and we are no longer seeing it? We've actually already taken care of this case! When our robot stops seeing the other robot while charging, it will automatically go back to spinning in circles. Excellent!

Calling our new Function
We've finally got some code that you can compile and run to try out. But wait! How does our robot know to run this code? We need to make a modification to the main.c function. Open up that file and scroll to near the bottom. There is a section of code like this:

''THIS IS NO LONGER VALID AS OF SOFTWARE REV 1.2... SPINSUMO IS INCLUDED AS PART OF THE DEFAULT SOFTWARE SO THERE IS NO NEED TO ADD IT TO THE MAIN.C

This code checks that status of button 2 (BTN2) when the robot first starts up. If BTN2 is being held (BTN2=0), the program will start the test_menu code. If the button is not held, it will call the simplesumo code. Instead of calling that code, lets call our code instead. Change the 'simplesumo;' line to 'spinsumo;'

You can compile the code and use the bootloader to get it onto your robot now if you'd like to test it. The robot will now spin after the 5 second count down and then charge anything that it sees in the ring.

Avoiding the ring edges
You'll notice that it charges right off the edge of the ring though, following the opponent to its doom. This is fine in most cases, but a false detection in the IR enemy detector and your robot will end up losing the match simply by driving itself off the ring! It would be better possibly if the robot would detect the edge of the ring and back away.

Lets make the final modification to our code to add this feature:

Ok, we added another check at the start of our for{ever} loop. The first thing we do is check the line sensors. LINE_L2 is a variable that holds the current A/D (analog to digital) reading from our left line sensors. LINE_R2 is a variable that holds the same thing but for the right sensor. LINE_THRESHOLD is a preselected value that we will use to determine if something is a line or not. If the current A/D reading for one of the line sensors is less than LINE_THRESHOLD, we are detecting a white line. If it is greater, we see no line, just nice black sumo ring. LINE_THRESHOLD is set to 0x40 hex (64 decimal). You can experiment with this value by changing it in the main.h file if you like.

For checking the line, we make no distinction between both line sensors detecting at the same time or just one or the other sensor detecting. If either of the sensors detects, we will do the same thing : reverse the robot away from the line.

In C, an OR statement is down with the character '||'. So the statement 'if ((LINE_L2<LINE_THRESHOLD)||(LINE_R2<LINE_THRESHOLD)){' means 'if left line sensor is less than the line threshold OR the right line sensor is less than the line threshold THEN do whatever is the in {} brackets'. We set both motors to reverse (BWD) at full speed (100). We then wait a small amount of time, somewhere between 400 and 655 milliseconds. 'random' is a global variable that is constantly changing to a value of something between 0 and 255. It allows us to add a little randomness into the robot behaviour so that the robot never gets stuck into a loop doing something over and over and over. This is not strictly required in this case, but it certainly doesn't hurt.

Ok! You can compile and program again. The robot will spin and scan. When it find the opponent, it will push it off the edge of the ring. If our robot detects the edge of the ring while pushing the opponent off, it will reverse briefly and then go back to scan mode.

Conclusion
There are of course many more modifications that can be made to the robot at this point. One thing you'll notice is that the robot may not detect the opponent robot when they are on opposite sides of the ring. As long as the other robot is moving, this isn't a problem. It becomes a problem though if both robots are spinning and never see each other, or if the other robot is stuck on the edge of the ring and just needs a small tap from your robot to be out. What can you do here?

How about only spin searching for 5 or 10 seconds before changing to a roam the ring behaviour like we had in simplesumo? After it does that for a few (or more) seconds, it could convert back to scan mode.

That, of course, is left as an exercise for you to implement. Good luck!

More Software Libraries
You can find out more about the software libraries that are provided to control the SumoORE robot, bootloader and libraries here: SumoORE Software