8.b. Working with Multiple C Files in Atmel Studio

The C source code for an example line maze solver is available in the folder examples\atmegaxx8\3pi-mazesolver.

Note: An Arduino-compatible version of this sample program can be downloaded as part of the Pololu Arduino Libraries (see Section 5.g) The Arduino sample sketch is all contained within a single file.

This program is much more complicated than the examples you have seen so far, so we have split it up into multiple files. Using multiple files makes it easier for you to keep track of your code. For example, the file turn.c contains only a single function, used to make turns at the intersections:

The first line of the file, like any C file that you will be writing for the 3pi, contains an include command that gives you access to the functions in the Pololu AVR Library. Within turn(), we then use the library functions delay_ms() and set_motors() to perform left turns, right turns, and U-turns. Straight “turns” are also handled by this function, though they don’t require us to take any action. The motor speeds and the timings for the turns are parameters that needed to be adjusted for the 3pi; as you work on making your maze solver faster, these are some of the numbers that you might need to adjust.

To access this function from other C files, we need a “header file”, which is called turn.h. The header file just contains a single line:

void turn(char dir);

This line declares the turn() function without actually including a copy of its code. To access the declaration, each C file that needs to call turn() adds the following line:

#include "turn.h"

Note the double-quotes being used instead of angle brackets. This signifies to the C compiler that the header file is in the project directory, rather than being a system header file like 3pi.h. Always remember to put the code for your functions in the C file instead of the header file! If you do it the other way, you will be making a separate copy of the code in each file that includes the header.

The file follow-segment.c also contains a single function, follow_segment(), which will drive 3pi straight along a line segment until it reaches an intersection or the end of the line. This is almost the same as the line following code discussed in Section 7, but with extra checks for intersections and the ends of lines. Here is the function:

void follow_segment()
{
int last_proportional = 0;
long integral=0;
while(1)
{
// Normally, we will be following a line. The code below is
// similar to the 3pi-linefollower-pid example, but the maximum
// speed is turned down to 60 for reliability.
// Get the position of the line.
unsigned int sensors[5];
unsigned int position = read_line(sensors,IR_EMITTERS_ON);
// The "proportional" term should be 0 when we are on the line.
int proportional = ((int)position) - 2000;
// Compute the derivative (change) and integral (sum) of the
// position.
int derivative = proportional - last_proportional;
integral += proportional;
// Remember the last position.
last_proportional = proportional;
// Compute the difference between the two motor power settings,
// m1 - m2. If this is a positive number the robot will turn
// to the left. If it is a negative number, the robot will
// turn to the right, and the magnitude of the number determines
// the sharpness of the turn.
int power_difference = proportional/20 + integral/10000 + derivative*3/2;
// Compute the actual motor settings. We never set either motor
// to a negative value.
const int max = 60; // the maximum speed
if(power_difference > max)
power_difference = max;
if(power_difference < -max)
power_difference = -max;
if(power_difference < 0)
set_motors(max+power_difference,max);
else
set_motors(max,max-power_difference);
// We use the inner three sensors (1, 2, and 3) for
// determining whether there is a line straight ahead, and the
// sensors 0 and 4 for detecting lines going to the left and
// right.
if(sensors[1] < 100 && sensors[2] < 100 && sensors[3] < 100)
{
// There is no line visible ahead, and we didn't see any
// intersection. Must be a dead end.
return;
}
else if(sensors[0] > 200 || sensors[4] > 200)
{
// Found an intersection.
return;
}
}
}

Between the PID code and the intersection detection, there are now about six more parameters that could be adjusted. We’ve picked values here that allow 3pi to solve the maze at a safe, controlled speed; try increasing the speed and you will quickly run in to lots of problems that you’ll have to handle with more complicated code.

Putting the C files and header files into your project is easy with Atmel Studio. On the right side of your screen, in the “Solution Explorer” pane, you should see a list of files in your project. Right click on the name of your project and you will have the option to add files to the list. When you build your project, Atmel Studio will automatically compile all C files in the project together to produce a single hex file.