Pololu 3pi Robot User's Guide

Revision: New - 9/9/08

Ported by Permission

1. Introduction
2. Contacting Pololu
3. Important Safety Warning and Handling Precautions
4. Getting Started with Your 3pi Robot
4.a. What You Will Need
4.b. Powering Up Your 3pi
4.c. Using the Preloaded Demo Program
4.d. Included Accessories
5. Programming Your 3pi
5.a. Downloading and Installing the C/C++ Library
5.b. Compiling a Simple Program
6. Example Project #1: Line Following
6.a. About Line Following
6.b. A Simple Line-Following Algorithm for 3pi
6.c. Advanced Line Following with 3pi: PID Control
7. Example Project #2: Maze Solving
7.a. Solving a Line Maze
7.b. Working with Multiple C Files in AVR Studio
7.c. Left Hand on the Wall
7.d. The Main Loop(s)
7.e. Simplifying the Solution
7.f. Improving the Maze Solving Code
8. Pin Assignment Tables
9. Expansion Information
10. Related Resources

1. Introduction

The Pololu 3pi robot is a small, high-performance, autonomous robot designed to excel in line-following and line-maze-solving competitions. Powered by four AAA batteries (not included) and a unique power system that runs the motors at a regulated 9.25 V, 3pi is capable of speeds up to 100 cm/second while making precise turns and spins that don't vary with the battery voltage. This results in highly consistent and repeatable performance of well-tuned code even as the batteries run low. The robot comes fully assembled with two micro metal gearmotors, five reflectance sensors, an 8×2 character LCD, a buzzer, three user pushbuttons, and more, all connected to a user-programmable AVR microcontroller. The 3pi measures approximately 3.7 inches (9.5 cm) in diameter and weighs 2.9 oz (83 g) without batteries.

The 3pi is based on an Atmel ATmega168 microcontroller running at 20 MHz with 16KB of flash program memory and 1KB data memory. The use of the ATmega168 microcontroller makes the 3pi compatible with the popular Arduino development platform. Free C and C++ development tools are also available, and an extensive set of libraries make it a breeze to interface with all of the integrated hardware. Sample programs are available to show how to use the various 3pi components, as well as how to perform more complex behaviors such as line following and maze solving.

This document is still under construction, but we are releasing it in its current form since some customers are beginning to receive their 3pi robots.

2. Contacting Pololu

You can check the 3pi product page for additional information, including pictures, videos, example code, and other resources.

We would be delighted to hear from you about any of your projects and about your experience with the 3pi robot. You can contact us directly or post on our forum. Tell us what we did well, what we could improve, what you would like to see in the future, or share your code with other 3pi users.

3. Important Safety Warning and Handling Precautions

The 3pi robot is not intended for young children! Younger users should use this product only under adult supervision. By using this product, you agree not to hold Pololu liable for any injury or damage related to the use or to the performance of this product. This product is not designed for, and should not be used in, applications where the malfunction of the product could cause injury or damage. Please take note of these additional precautions:

- The 3pi robot contains lead, so follow appropriate handling procedures, such as not licking the robot and washing hands after handling. The 3pi robot is intended for use indoors on relatively flat, smooth surfaces. Avoid running your 3pi on surfaces that might scrape or damage the underside of your robot's PCB as it drives around.

- Avoid placing the robot so that the underside of the PCB makes contact with conductive materials (e.g. do not place the 3pi in a bin filled with metal parts). This could inadvertently short out the batteries and damage your robot, even with the 3pi turned off. Shorting various pads or components together could also damage your 3pi.

- Since the PCB and its components are exposed, take standard precautions to protect your 3pi robot from ESD (electrostatic discharge), which could damage the on-board electronics. When picking up the 3pi, you should first touch a safe part of the robot such as the wheels, motors, batteries, or the edges of the PCB. If you first touch components on the PCB, you risk discharging through them. When handing the 3pi to another person, first touch their hand with your hand to equalize any charge imbalance between you so that you don't discharge through the 3pi as the exchange is made.

- If you remove the LCD, take care to replace it in the right orientation such that it is over the rear battery back. It is possible to put the LCD in backwards or offset; doing so could damage the LCD or the 3pi.

4. Getting Started with Your 3pi Robot
Getting started with your 3pi can be as simple as taking it out of the box, adding batteries, and turning it on. The 3pi ships with a demo program that will give you a brief tour of its features.

General features of the Pololu 3pi robot, top view.

Labeled bottom view of the Pololu 3pi robot.

The following subsections will give you all the information you need to get your 3pi up and running!

4.a. What You Will Need
The following materials are necessary for getting started with your 3pi:

- 4 AAA batteries. Any AAA cells will work, but we recommend NiMH batteries, which are rechargeable and can be purchased from Pololu or at a local store. If you use rechargeable batteries, you will also need a battery charger. Battery chargers designed to connect to external series battery packs may be used with the 3pi's battery charger port.

- AVR ISP programmer with 6-pin connector. The 3pi features an ATmega168 microcontroller, which requires an external programmer such as the Pololu Orangutan USB programmer or Atmel's AVRISP series. The 3pi has a standard 6-pin programming connector, so your programmer will need to have a 6-pin ISP cable for connecting to the target device. (You will also need whatever cable your programmer requires to connect to a computer. Our Orangutan USB programmer requires a USB cable that is not included with the programmer alone but that is included as part of the 3pi+programmer combo deal).

- A desktop or laptop computer. You will need a personal computer for developing your code and loading it onto the 3pi. The 3pi can be programmed on Windows, Mac, and Linux operating systems, but Pololu support for Macs is limited.

You might find the following materials useful in creating an environment for your robot to explore:

- Several large sheets of white posterboard (available at crafts or office supply stores) or dry-erase whiteboard stock (commonly available at home/construction supply stores).

- Light-colored masking tape for joining multiple sheets together.

- 3/4" black electrical tape to create lines for your robot to follow.

4.b. Powering Up Your 3pi

The first step in using your new 3pi robot is to insert four AAA batteries into the battery holders. To do this you will need to remove the LCD. Pay attention to the LCD's orientation as you will want to plug it back in this way when you are done. With the LCD removed your 3pi should look like the picture to the right.

Once the batteries are in place, you should return the LCD to its position over the rear battery holder. Make sure each male LCD header pin goes into a corresponding female socket.

Next, push the power button (located on the left side of the rear battery pack) to turn on your 3pi. You should see the two blue power LEDs on the underside of the 3pi light, and the 3pi should begin running its preloaded demo program. You can simply push the power button again to turn the 3pi off, and you can push the reset button (located just below the power button) to reset the program the robot is running.

4.c. Using the Preloaded Demo Program

Your 3pi comes preloaded with a program that demonstrates most of its features and allows you to test that it is working correctly. When you first turn on your 3pi, you will hear a beep and see the words "Pololu 3pi Robot", then "Demo Program" appear, indicating that you are running the demo program. If you hear a beep but do not see any text on the LCD, you may need to adjust the contrast potentiometer on the underside of the board. When the program has started successfully, press the B button to proceed to the main menu. Press C or A to scroll forward or backward through the menu, and press B to make a selection or to exit one of the demos. There are seven demos accessible from the menu:

1. Battery: This demo displays the battery voltage in millivolts, which should be above 5000 (5.0 Volts) for a fully-charged set of batteries. Removing the jumper marked ADC6 will separate the battery voltage measurement circuit from the analog input, causing the number displayed to drop to some low value.

2. LEDs: Blinks the red and green user LEDs on the underside of the board. If you have soldered in the optional user LEDs, they will also blink.

3. Trimpot: Displays the position of the user trimmer potentiometer, which is located on the underside of the board, as a number between 0 and 1023. While displaying the value, this demo also blinks the LEDs and plays a note whose frequency is a function of the current reading. It is easiest to turn the trimpot using a 2mm flat-head screwdriver.

4. Sensors: Show the current readings of the IR sensors using a bar graph. Bigger bars mean lower reflectance. Placing a reflective object such as your finger under one of the sensors will cause the corresponding reading to drop visibly on the graph. This demo also displays "C" to indicate that button C has an effect -press C and the IR emitters will be turned off. In indoor lighting conditions away from bright incandescent or halogen lights, all of the sensors should return entirely black readings with IR off. Removing the jumper marked PC5 disables control of the emitters, causing them to always be on.

5. Motors: Hold down A or C to run the motor on the corresponding side, or hold down both buttons to run both motors simultaneously. The motors will gradually ramp up to speed; in your own programs, you can switch them on much more suddenly. Tap A or C to switch the corresponding motor to reverse (the button letter becomes lowercase if pressing it will drive the corresponding motor in reverse).

6. Music: Plays an adaptation of J. S. Bach's Fugue in D Minor for microcontroller and piezo, while scrolling a text display. This demonstrates the ability of the 3pi to play music in the background.

7. Timer: A simple stopwatch. Press C to start or stop the stopwatch and A to reset. The stopwatch continues to count while you are exploring the other demos.
The source code for the demo program is included with the Pololu AVR C/C++ Library described in Section 5. After downloading and unpacking the library zip file, the demo program can be found in the folder examples\3pi-demo-program.

4.d. Included Accessories

The 3pi robot ships with two through-hole red LEDs and two through-hole green LEDs. There are connection points for three optional LEDs on your 3pi: one next to the power button to indicate when the 3pi is on and two user-controllable LED ports near the front edge of the robot. Using these LEDs is completely optional as the 3pi will function just fine without them. You can customize your 3pi by choosing your desired combination of red and green LEDs, or you can even use your own LEDs if you want more color/brightness options.

Note that you should only add LEDs if you are comfortable soldering, and you should take care to avoid desoldering any of the components near the through-hole LED pads. LEDs are polarized, so be sure to solder them such that the longer lead connects to the pad marked with the+. Before you solder them in you can press-fit them in place and check to make sure they light as expected. Once soldered in place, carefully trim off the excess portion of the LED leads.

Your 3pi also ships with three shorting blocks of each color: blue, red, yellow, black. This means you can customize your 3pi by selecting the shorting block color you most prefer, or you can use a mixture of colors!

5. Programming Your 3pi

To do more with your 3pi than explore the demo program, you will need to program it, which requires an external AVR ISP programmer such as our Orangutan USB programmer. Your first step should be to set up your programmer by following its installation instructions. If you are using the Orangutan USB programmer, please see its user's guide.
Next you will need software that can compile your programs and transfer them to your 3pi via your programmer. We recommend you download two software packages for this:

1. WinAVR, a free, open-source suite of development tools for the AVR family of microcontrollers, including the GNU GCC compiler for C/C++.

2. AVR Studio, Atmel's free integrated development environment (IDE) that natively works with WinAVR's free GCC C/C++ compiler. AVR Studio includes AVR ISP software that will let you upload your programs to the 3pi.

Note: You can also program your 3pi using the Arduino IDE and an external ICSP programmer, such as our Orangutan USB programmer. For instructions on this approach, please see our guide: Programming Orangutans and the 3pi Robot from the Arduino Environment. The rest of this guide will be based on AVR Studio.

For more general instructions on using the Pololu C/C++ libraries with our AVR-based robot boards, including Linux installation instructions, see thePololu AVR C/C++ Library User's Guide.

5.a. Downloading and Installing the C/C++ Library

The Pololu C/C++ AVR Library makes it easy for you to use the advanced features of your 3pi; the library is used for all of the examples in the following sections. For your convenience, the source code for these examples and the demo program is included with the library. To begin the installation process for the Pololu AVR C/C++ Library, you will need to download one of the following zip files:

Precompiled binary distribution (219k zip)
Source distribution (262k zip)

We recommend the precompiled version, which will be easier to install for most people. If you have trouble installing the precompiled version, or you are interested in taking a closer look at the source code, please download the source distribution and see Section 4 of the Pololu AVR C/C++ Library User's Guide for compilation instructions.

Open the .zip file and click "Extract all" to extract the Pololu AVR Library files. A directory called "libpololu-avr" will be created.

Determine the location of your avr-gcc files. In Windows, they will usually be in a folder such as: C:\WinAVR-20080610\avr. In Linux, the avr-gcc files are probably located in /usr/avr. If you currently have an older version of the Pololu AVR Library, your first step should be to delete all of the old include files and the libpololu.a file that you installed previously.

Next, copylibpololu.a into thelib subdirectory of your avr directory (e.g. C:\WinAVR-20080610\avr\lib.)

Finally, copy the entire pololu subfolder into the include subfolder. (e.g. The Pololu include files should now be located in C:\WinAVR-20080610\avr\include\pololu.)

The Pololu AVR Library header files, installed correctly.

You are now ready to use the Pololu AVR library with your 3pi.
5.b. Compiling a Simple Program

A very simple demo program for the 3pi is available in the folder examples\simple-test-3pi, using a few basic commands from the Pololu AVR Library. Here is a copy of the source code:

01. #include <pololu/3pi.h>
02.
03. int main()
04. {
05.     print("Hello!");
06.
07.     play("L16 ceg>c");
08.
09.     while(1)
10.     {
11.         red_led(0);
12.         green_led(1);
13.
14.         delay_ms(100);
15.
16.         red_led(1);
17.         green_led(0);
18.
19.         delay_ms(100);
20.     }
21.
22.     return 0;
23. }

Navigate to thesimple-test-3pi folder, double-click on the file test.aps, and the project should open automatically in AVR Studio, showing the C file.

AVR Studio showing the 3pi sample program.

To compile this program, select Build > Build or press F7. Look for warnings and errors (indicated by yellow and red dots) in the output displayed below. If the program compiles successfully, the message "Build succeeded with 0 Warnings..." will appear at the end of the output, and a file test.hex will have been created in theexamples\simple-test-3pi\default folder.

AVR Studio build window, compiling the example project.

Connect your programmer to your computer and to the ISP port of your 3pi, and turn on the 3pi's power by pressing the button labeled POWER. If you are using the Pololu Orangutan Programmer, the green status LED close to the USB connector should be on, while the other two LEDs should be off, indicating that the programmer is ready.
Your programmer must be installed correctly before you use it. If you are using the Orangutan USB programmer, please see its user's guide for installation instructions.

Pololu 3pi robot with an Orangutan USB programmer connected to its ISP port.

SelectTools > Program AVR > Connect to connect to the programmer. For the Orangutan Programmer, the default options of "STK500 or AVRISP" and "Auto" should be fine, so click Connect and the AVRISP programming window should appear.
You will use AVRISP to load test.hex into the flash memory of your 3pi. To do this, click "..." in the Flash section and select filetest.hex that was compiled earlier. Note that you have to first navigate to your project directory! Now click "Program" in the Flash section, and the test code should be loaded onto your 3pi.

Programming the 3pi from AVR Studio.

If your 3pi was successfully programmed, you should hear a short tune, see the message "Hello!" on the LCD, and the LEDs on the board should blink. If you hear the tune and see the lights flashing, but nothing appears on the LCD, make sure that the LCD is correctly plugged in to the 3pi, and try adjusting the contrast using the small potentiometer on the underside of the 3pi, closest to the ball caster.

6. Example Project #1: Line Following
6.a. About Line Following

Now that you have learned how to compile a simple program for the 3pi, it's time to teach your robot do something more complicated. In this example project, we'll show you how to make your 3pi follow a black line on a white background, by coordinating its sensors and motors. Line following is a great introduction to robot programming, and it makes a great contest: it's easy to build a line-following course, the rules are simple to understand, and it's not hard to program your 3pi to follow a line. Optimizing your program to make your 3pi zoom down the line at the highest speed possible, however, is a challenge that can introduce you to some advanced programming concepts.

A great looking line following course can be constructed for a few dollars in a couple of hours at home. For information on building your own course, see our tutorial on Building Line Following and Line Maze Courses.

Pololu 3pi robot on a 3/4" black line.

6.b. A Simple Line-Following Algorithm for 3pi

A simple line following program for the 3pi is available in the folder examples\3pi-linefollower.
Note: An Arduino-compatible version of this sample program can be downloaded as part of the Pololu Arduino Libraries (see Section 5.g).

The source code demonstrates a variety of different features of the 3pi, including the line sensors, motors, LCD, battery voltage monitor, and buzzer. The program has two phases.

The first phase of the program is the initialization and calibration phase, which is handled by the function intitialize(). This function is called once, at the beginning of the main() function, before anything else happens, and it takes care of the following steps:

1. Calling pololu_3pi_init(2000) to set up the 3pi, with the sensor timeout set to 2000×0.4 us = 800 us. This means that the sensor values will vary from 0 (completely white) to 2000 (completely black), where a value of 2000 indicates that the sensor's capacitor took at least 800 us to discharge.

2. Displaying the battery voltage returned by the read_battery_millivolts() function. It is important to monitor battery voltage so that your robot does not surprisingly run out of batteries and shut down during the middle of a competition. For more information, see Section 3 of the command reference.

3. Calibrating the sensors. This is accomplished by turning the 3pi to the right and left on the line while calling the calibrate_line_sensors() function. The minimum and maximum values read during this time are stored in RAM. This allows the read_line_sensors_calibrated() function to return values that are adjusted to range from 0 to 1000 for each sensor, even if some of your sensors respond differently than the others. The read_line() function used later in the code also depends on having calibrated values. For more information, see Section 11 of the command reference.

4. Displaying the calibrated line sensor values in a bar graph. This demonstrates the use of the lcd_load_custom_character() function together with print_character() to make it easy to see whether the line sensors are working properly before starting the robot. For more information on this and other LCD commands, see Section 5 of the command reference.

5. Waiting for the user to press a button. It's very important for your robot not to start driving until you want it to start, or it could unexpectedly drive off of a table or out of your hands when you are trying to program it. We use the button_is_pressed() function to wait for you to press the B button while displaying the battery voltage or sensor readings. For more information on button commands, see Section 8 of the command reference.

In the second phase of the program, your 3pi will take a sensor reading and set the motor speed appropriately based on the reading. The general idea is that if the robot is off on either side, it should turn to get back on, but if it's on the line, it should try to drive straight ahead. The following steps occur inside of a while(1) loop, which will continue repeating over and over until the robot is turned off or reset.

1. The function read_line() is called. This takes a sensor reading and returns an estimate of the robot's position with respect to the line, as a number between 0 and 4000. A value of 0 means that the line is to the left of sensor 0, value of 1000 means that the line is directly under sensor 1, 2000 means that the line is directly under sensor 2, and so on.

2. The value returned byread_line() is divided into three possible cases:

0-1000: the robot is far to the right of the line. In this case, to turn sharply left, we set the right motor speed to 100 and the left motor speed to 0. Note that the maximum speed of the motors is 255, so we are driving the right motor at only about 40% power here.

- 1000-3000: the robot is approximately centered on the line. In this case, we set both motors to speed 100, to drive straight ahead.

- 3000-4000: the robot is far to the left of the line. In this case, we turn sharply to the right by setting the right motor speed to 0 and the left motor speed to 100.

3. Depending on which motors are activated, the corresponding LEDs are turned on for a more interesting display. This can also help with debugging.

To open the program in AVR studio, you may go to examples\3pi-linefollower and simply double-click on test.aps. Compile the program, load it onto your 3pi, and try it out. You should find that your robot is able to follow the curves of your line course without ever completely losing the line. However, its motors are moving at a speed of at most 100 out of the maximum possible of 255, and the algorithm causes a lot of unnecessary shaking on the curves. At this point, you might want to work on trying to adjust and improve this algorithm, before moving on to the next section. Some ideas for improvement are:

- Increase the maximum possible speed.

- Add more intermediate cases, with intermediate speed settings, to make the motion less jerky.

- Give your robot a memory: have its maximum speed increase after it has been on the line consistently for a few cycles.

You might also want to:

- Measure the speed of your loop, using timing functions from Section 2 of the command reference to time a few thousand cycles or by blinking the LEDs on and off every 1000 cycles.

- Display sensor readings on the LCD. Since writing to the LCD takes a significant amount of time, you should do this at most few times per second.

- Incorporate the buzzer into your program. You might want your 3pi to play music while it is driving or make informational beeps that depend on what it is doing. See Section 4 of the command reference for more information on using the buzzer; for music, you'll want to use the PLAY_CHECK option to avoid disrupting your sensor readings.

The entire source code to this simple line following program is presented below, for your reference.

01. /*
02.  * 3pilinefollower  demo code for the Pololu 3pi Robot
03.  *
04.  * This code will follow a black line on a white background, using a
05.  * very simple algorithm.  It demonstrates autocalibration and use of
06.  * the 3pi IR sensors, motor control, bar graphs using custom
07.  * characters, and music playback, making it a good starting point for
08.  * developing your own more competitive line follower.
09.  *
10.  * http://www.pololu.com/docs/0J21
11.  * http://www.pololu.com
12.  * http://forum.pololu.com
13.  *
14.  */
15.
16. // The 3pi include file must be at the beginning of any program that
17. // uses the Pololu AVR library and 3pi.
18. #include <pololu/3pi.h>
19.
20. // This include file allows data to be stored in program space.  The
21. // ATmega168 has 16k of program space compared to 1k of RAM, so large
22. // pieces of static data should be stored in program space.
23. #include <avr/pgmspace.h>
24.
25. // Introductory messages.  The "PROGMEM" identifier causes the data to
26. // go into program space.
27. const char welcome_line1[] PROGMEM = " Pololu";
28. const char welcome_line2[] PROGMEM = "3\xf7 Robot";
29. const char demo_name_line1[] PROGMEM = "Line";
30. const char demo_name_line2[] PROGMEM = "follower";
31.
32. // A couple of simple tunes, stored in program space.
33. const char welcome[] PROGMEM = ">g32>>c32";
34. const char go[] PROGMEM = "L16 cdegreg4";
35.
36. // Data for generating the characters used in load_custom_characters
37. // and display_readings.  By reading levels[] starting at various
38. // offsets, we can generate all of the 7 extra characters needed for a
39. // bargraph.  This is also stored in program space.
40. const char levels[] PROGMEM = {
41.     0b00000,
42.     0b00000,
43.     0b00000,
44.     0b00000,
45.     0b00000,
46.     0b00000,
47.     0b00000,
48.     0b11111,
49.     0b11111,
50.     0b11111,
51.     0b11111,
52.     0b11111,
53.     0b11111,
54.     0b11111
55. };
56.
57. // This function loads custom characters into the LCD.  Up to 8
58. // characters can be loaded; we use them for 7 levels of a bar graph.
59. void load_custom_characters()
60. {
61.     lcd_load_custom_character(levels+0,0); // no offset, e.g. one bar
62.     lcd_load_custom_character(levels+1,1); // two bars
63.     lcd_load_custom_character(levels+2,2); // etc...
64.     lcd_load_custom_character(levels+3,3);
65.     lcd_load_custom_character(levels+4,4);
66.     lcd_load_custom_character(levels+5,5);
67.     lcd_load_custom_character(levels+6,6);
68.     clear(); // the LCD must be cleared for the characters to take effect
69. }

6.c. Advanced Line Following with 3pi: PID Control

A more advanced line following program for the 3pi is available in the folder examples\3pi-linefollower-pid.
Note: An Arduino-compatible version of this sample program can be downloaded as part of the Pololu Arduino Libraries (see Section 5.g).
The technique used in this example program, known as PID control, addresses some of the problems that you might have noticed with the previous example, and it should allow you to greatly increase your robot's line following speed. Most importantly, PID control uses continuous functions to compute the motor speeds, so that the jerkiness of the previous example can be replaced by a smooth response. PID stands for Proportional, Integral, Derivative; these are the three input values used in a simple formula to compute the speed that your robot should turn left or right.

- The proportional value is approximately proportional to your robot's position with respect to the line. That is, if your robot is precisely centered on the line, we expect a proportional value of exactly 0. If it is to the left of the line, the proportional term will be a positive number, and to the right of the line, it will be negative. This is computed from the result returned byread_line() simply by subtracting 2000.

- The integral value records the history of your robot's motion: it is a sum of all of the values of the proportional term that were recorded since the robot started running.

- The derivative is the rate of change of the proportional value. We compute it in this example as the difference of the last two proportional values.

Here is the section of code that computes the PID input values:

01. // Get the position of the line.  Note that we *must* provide
02. // the "sensors" argument to read_line() here, even though we
03. // are not interested in the individual sensor readings.
04. unsigned int position = read_line(sensors,IR_EMITTERS_ON);
05.
06. // The "proportional" term should be 0 when we are on the line.
07. int proportional = ((int)position)  2000;
08.
09. // Compute the derivative (change) and integral (sum) of the
10. // position.
11. int derivative = proportional  last_proportional;
12. integral += proportional;
13.
14. // Remember the last position.
15. last_proportional = proportional;

Note that we cast the variable position to anint type in the formula for proportional. An unsigned int can only store positive values, so the expression position-2000, without casting, would lead to a negative overflow. In this particular case, it actually wouldn't affect the results, but it is always a good idea to use casting to avoid unexpected behavior.
Each of these input values provides a different kind of information. The next step is a simple formula that combines all of the values into one variable, which is then used to determine the motor speeds:

01. // Compute the difference between the two motor power settings,
02. // m1  m2.  If this is a positive number the robot will turn
03. // to the right.  If it is a negative number, the robot will
04. // turn to the left, and the magnitude of the number determines
05. // the sharpness of the turn.
06. int power_difference = proportional/20 + integral/10000 + derivative*3/2;
07.
08. // Compute the actual motor settings.  We never set either motor
09. // to a negative value.
10. const int max = 60;
11. if(power_difference > max)
12.     power_difference = max;
13. if(power_difference < max)
14.     power_difference = max;
15.
16. if(power_difference < 0)
17.     set_motors(max+power_difference, max);
18. else
19.     set_motors(max, maxpower_difference);

The values 1/20, 1/10000, and 3/2 are adjustable parameters that determine how your 3pi will react to the line. In general, increasing these PID parameters will make power_difference larger, causing stronger reactions, while decreasing them will make the reactions weaker. It's up to you to think about the different values and experiment with your robot to determine what effect each parameter has. This example gives the motors a maximum speed of 100, which is a safe initial value. Once you have adjusted the parameters to work well at a speed of 100, try increasing the speed. You'll probably need to readjust the parameters as the maximum speed increases. By gradually increasing the maximum speed and tuning the parameters, see if you can get your 3pi to run as fast as possible!

7. Example Project #2: Maze Solving
7.a. Solving a Line Maze

The next step up from simple line following is to teach your 3pi to navigate paths with sharp turns, dead ends, and intersections. Make a complicated network of intersecting black lines, add a circle to represent the goal, and you have a line maze, which is a challenging environment for a robot to explore. In a line maze contest, robots travel as quickly as possible along the lines from a designated start to the goal, keeping track of the intersections that they pass along the way. Robots are given several chances to run the maze, so that they can follow the fastest possible path after learning about all of the dead ends.

The mazes that we will teach you to solve in this tutorial have one special feature: they have no loops. That is, there is no way to re-visit any point on the maze without retracing your steps. Solving this type of maze is much easier than solving a looped maze, since a simple strategy allows you to explore the entire maze. We'll talk about that strategy in the next section.

We also usually construct our mazes using only straight lines drawn on a regular grid, but this is mostly just to make the course easy to reproduce - the maze-solving strategy described in this tutorial does not require these features.

For information on building your own course, see our tutorial on Building Line Following and Line Maze Courses.

7.b. Working with Multiple C Files in AVRStudio
The C source code for an example line maze solver is available in the folder examples\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:

01. #include <pololu/3pi.h>
02.
03. // Turns according to the parameter dir, which should be 'L', 'R', 'S'
04. // (straight), or 'B' (back).
05. void turn(char dir)
06. {
07.     switch(dir)
08.     {
09.     case 'L':
10.         // Turn left.
11.         set_motors(80,80);
12.         delay_ms(200);
13.         break;
14.     case 'R':
15.         // Turn right.
16.         set_motors(80,80);
17.         delay_ms(200);
18.         break;
19.     case 'B':
20.         // Turn around.
21.         set_motors(80,80);
22.         delay_ms(400);
23.         break;
24.     case 'S':
25.         // Don't do anything!
26.         break;
27.     }
28. }

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 calledturn.h. The header file just contains a single line:

01. 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:

01. #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 like3pi.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 copies 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 6, but with extra checks for intersections and the ends of lines. Here is the function:

01. void follow_segment()
02. {
03.     int last_proportional = 0;
04.     long integral=0;
05.
06.     while(1)
07.     {
08.         // Normally, we will be following a line.  The code below is
09.         // similar to the 3pilinefollowerpid example, but the maximum
10.         // speed is turned down to 60 for reliability.
11.
12.         // Get the position of the line.
13.         unsigned int sensors[5];
14.         unsigned int position = read_line(sensors,IR_EMITTERS_ON);
15.
16.         // The "proportional" term should be 0 when we are on the line.
17.         int proportional = ((int)position)  2000;
18.
19.         // Compute the derivative (change) and integral (sum) of the
20.         // position.
21.         int derivative = proportional  last_proportional;
22.         integral += proportional;
23.
24.         // Remember the last position.
25.         last_proportional = proportional;
26.
27.         // Compute the difference between the two motor power settings,
28.         // m1  m2.  If this is a positive number the robot will turn
29.         // to the left.  If it is a negative number, the robot will
30.         // turn to the right, and the magnitude of the number determines
31.         // the sharpness of the turn.
32.         int power_difference = proportional/20 + integral/10000 + derivative*3/2;
33.
34.         // Compute the actual motor settings.  We never set either motor
35.         // to a negative value.
36.         const int max = 60; // the maximum speed
37.         if(power_difference > max)
38.             power_difference = max;
39.         if(power_difference < max)
40.             power_difference = max;
41.
42.         if(power_difference < 0)
43.             set_motors(max+power_difference,max);
44.         else
45.             set_motors(max,maxpower_difference);
46.
47.         // We use the inner three sensors (1, 2, and 3) for
48.         // determining whether there is a line straight ahead, and the
49.         // sensors 0 and 4 for detecting lines going to the left and
50.         // right.
51.
52.         if(sensors[1] < 100 && sensors[2] < 100 && sensors[3] < 100)
53.         {
54.             // There is no line visible ahead, and we didn't see any
55.             // intersection.  Must be a dead end.
56.             return;
57.         }
58.         else if(sensors[0] > 200 || sensors[4] > 200)
59.         {
60.             // Found an intersection.
61.             return;
62.         }
63.
64.     }
65. }

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 AVR Studio. In the left column of your screen, you should see options for "Source Files" and "Header Files". Right click on either one and you will have the option to add or remove files from the list. When you build your project, AVR Studio will automatically compile all C files in the project together to produce a single hex file.
7.c. Left Hand on the Wall

The basic strategy for solving a non-looped maze is called "left hand on the wall". Imagine walking through a real labyrinth - a human-sized maze built with stone walls - while keeping your left hand on the wall at all times. You'll turn left whenever possible and only turn right at an intersection if there is no other exit. Sometimes, when you reach a dead end, you'll turn 180 degrees to the right and start walking back the way you came. Eventually, as long as there are no loops, your hand will travel along each length of wall in the entire labyrinth exactly once, and you'll find your way back to the entrance. If there is a room somewhere in the labyrinth with a monster or some treasure, you'll find that on the way, since you'll travel down every hallway exactly twice. We use this simple, reliable strategy in our 3pi maze solving example:

01.// This function decides which way to turn during the learning phase of
02.// maze solving.  It uses the variables found_left, found_straight, and
03.// found_right, which indicate whether there is an exit in each of the
04.// three directions, applying the "left hand on the wall" strategy.
05.char select_turn(unsigned char found_left, unsigned char found_straight, unsigned char found_right)
06. {
07.     // Make a decision about how to turn.  The following code
08.     // implements a lefthandonthewall strategy, where we always
09.     // turn as far to the left as possible.
10.     if(found_left)
11.         return 'L';
12.     else if(found_straight)
13.         return 'S';
14.     else if(found_right)
15.         return 'R';
16.     else
17.         return 'B';
18. }

The values returned byselect_turn() correspond to the values used byturn(), so these functions will work nicely together in our main loop.

7.d. The Main Loop(s)

The strategy of our program is expressed in the file maze-solve.c. Most importantly, we want to keep track of the path that we have followed, so we define an array storing up to 100; these will be the same characters used in the turn() function. We also need to keep track of the current path length so that we know where to put the characters in the array.

01. char path[100] = "";
02. unsigned char path_length = 0; // the length of the path

Our "main loop" is found in the function maze_solve(), which is called after calibration, from main.c. This function actually includes two main loops - a first one that handles solving the maze, and a second that replays the solution for the fastest possible time. In fact, the second loop is actually a loop within a loop, since we want to be able to replay the solution many times. Here's an outline of the code:

01. // This function is called once, from main.c.
02. void maze_solve()
03. {
04.     while(1)
05.     {
06.         // FIRST MAIN LOOP BODY
07.         // (when we find the goal, we use break; to get out of this)
08.     }
09.
10.      // Now enter an infinite loop  we can rerun the maze as many
11.      // times as we want to.
12.     while(1)
13.     {
14.         // Beep to show that we finished the maze.
15.         // Wait for the user to press a button...
16.
17.         int i;
18.         for(i=0;i<path_length;i++)
19.         {
20.             // SECOND MAIN LOOP BODY
21.         }
22.
23.         // Follow the last segment up to the finish.
24.         follow_segment();
25.
26.         // Now we should be at the finish!  Restart the loop.
27.     }
28. }

The first main loop needs to drive down a segment of the course, decide how to turn, and record the turn in the path variable. To pass the correct arguments to select_turn(), we need to carefully examine the intersection as we cross it. Note that there is a special exception for finding the end of the maze. The following code works pretty well, at least at the slow speeds that we're using:

01. // FIRST MAIN LOOP BODY
02. follow_segment();
03.
04. // Drive straight a bit.  This helps us in case we entered the
05. // intersection at an angle.
06. // Note that we are slowing down  this prevents the robot
07. // from tipping forward too much.
08. set_motors(50,50);
09. delay_ms(50);
10.
11. // These variables record whether the robot has seen a line to the
12. // left, straight ahead, and right, whil examining the current
13. // intersection.
14. unsigned char found_left=0;
15. unsigned char found_straight=0;
16. unsigned char found_right=0;
17.
18. // Now read the sensors and check the intersection type.
19. unsigned int sensors[5];
20. read_line(sensors,IR_EMITTERS_ON);
21.
22. // Check for left and right exits.
23. if(sensors[0] > 100)
24.     found_left = 1;
25. if(sensors[4] > 100)
26.     found_right = 1;
27.
28. // Drive straight a bit more  this is enough to line up our
29. // wheels with the intersection.
30. set_motors(40,40);
31. delay_ms(200);
32.
33. // Check for a straight exit.
34. read_line(sensors,IR_EMITTERS_ON);
35.if(sensors[1] > 200 || sensors[2] > 200 || sensors[3] > 200)
36.     found_straight = 1;
37.
38. // Check for the ending spot.
39. // If all three middle sensors are on dark black, we have
40. // solved the maze.
41.if(sensors[1] > 600 && sensors[2] > 600 && sensors[3] > 600)
42.     break;
43.
44. // Intersection identification is complete.
45. // If the maze has been solved, we can follow the existing
46. // path.  Otherwise, we need to learn the solution.
47. unsigned char dir = select_turn(found_left, found_straight, found_right);
48.
49. // Make the turn indicated by the path.
50. turn(dir);
51.
52. // Store the intersection in the path variable.
53. path[path_length] = dir;
54. path_length ++;
55.
56. // You should check to make sure that the path_length does not
57. // exceed the bounds of the array.  We'll ignore that in this
58. // example.
59.
60. // Simplify the learned path.
61. simplify_path();
62.
63. // Display the path on the LCD.
64. display_path();

We'll discuss the call to simplify_path() in the next section. Before that, let's take a look at the second main loop, which is very simple. All we do is drive to the next intersection and turn according to our records. After doing the last recorded turn, the robot will be one segment away from the finish, which explains the final follow_segment() call in the outline of maze_solve() above.

01. // SECOND MAIN LOOP BODY
02. follow_segment();
03.
04. // Drive straight while slowing down, as before.
05. set_motors(50,50);
06. delay_ms(50);
07. set_motors(40,40);
08. delay_ms(200);
09.
10. // Make a turn according to the instruction stored in
11. // path[i].
12. turn(path[i]);

7.e. Simplifying the Solution

After every turn, the length of the recorded path increases by 1. If your maze, for example, has a long zigzag passageway with no side exits, you'll see a sequence like 'RLRLRLRL' appear on the 3pi's LCD. There's no shortcut that would get you through this section of the path faster than just following the left hand on the wall strategy. However, whenever we encounter a dead end, we can simplify the path to something shorter. Consider the sequence 'LBL', where 'B' stands for "back" and is the action taken when a dead end is encountered. This is what happens if there is a left turn on the side of a straight path that leads immediately to a dead end. After turning 90° left, 180° right, and 90° left again, the net effect is that the robot is heading in its original direction again. The path can be simplified to a 0° turn: a single 'S'.

Another example is a T-intersection with a dead end on the left: 'LBS'. The turns are 90° left, 180°, and 0°, for a total of 90° right. The sequence should be replaced with a single 'R'. In fact, whenever we have a sequence like 'xBx', we can replace all three turns with a turn corresponding to the total angle, eliminating the U-turn and speeding up our solution. Here's the code to handle this:

01. // Path simplification.  The strategy is that whenever we encounter a
02. // sequence xBx, we can simplify it by cutting out the dead end.  For
03. // example, LBL > S, because a single S bypasses the dead end
04. // represented by LBL.
05. void simplify_path()
06. {
07. // only simplify the path if the secondtolast turn was a 'B'
08.     if(path_length < 3 || path[path_length2] != 'B')
09.         return;
10.
11.     int total_angle = 0;
12.     int i;
13.     for(i=1;i<=3;i++)
14.     {
15.         switch(path[path_lengthi])
16.         {
17.         case 'R':
18.             total_angle += 90;
19.             break;
20.         case 'L':
21.             total_angle += 270;
22.             break;
23.         case 'B':
24.             total_angle += 180;
25.             break;
26.         }
27.     }
28.
29.     // Get the angle as a number between 0 and 360 degrees.
30.     total_angle = total_angle % 360;
31.
32.     // Replace all of those turns with a single one.
33.     switch(total_angle)
34.     {
35.     case 0:
36.         path[path_length  3] = 'S';
37.         break;
38.     case 90:
39.         path[path_length  3] = 'R';
40.         break;
41.     case 180:
42.         path[path_length  3] = 'B';
43.         break;
44.     case 270:
45.         path[path_length  3] = 'L';
46.         break;
47.     }
48.
49.     // The path is now two steps shorter.
50.     path_length = 2;
51. }

One interesting point about this code is that there are some sequences that should never be encountered by a left-turning robot, like 'RBR', which would be replaced by 'S' according to this code. In a more advanced program, you might want to keep track of inconsistencies like this, since they indicate some kind of a problem that could cause the robot to get lost.

7.f. Improving the Maze Solving Code

We have gone over the most important parts of the code; the other bits and pieces (like the function display_path(), the start-up sequence and calibration, etc.) can be found with everything else in the folder examples\3pi-mazesolver. After you have the code working and you understand it well, you should try to improve your robot to be as fast as possible. There are many things you can do to try to make it better:

- Increasing the line-following speed.
- Improving the line-following PIDconstants.
- Increasing turning speed.
- Identifying situations where the robot has gotten lost.
- Adjusting the speed based on what is coming up; e.g. driving straight through an 'S' at full speed.

8. Pin Assignment Tables

General features of the Pololu 3pi robot, top view.

Labeled bottom view of the Pololu 3pi robot.

Specific features of the Pololu 3pi robot, top view.

Pin Assignment Table Sorted by Function
Free digital I/Os (x3) (remove PC5 jumper to free digital pin 19)
Arduino Pin - digital pins 0, 1, 19
ATmega168 Pin - PD0, PD1, PC5

Free analog inputs (if you remove jumpers, x3)
Arduino Pin - analog inputs 5 - 7
ATmega168 Pin - ADC6, ADC7

Motor 1 (left motor) control (A and B)
Arduino Pin - digital pins 5 and 6
ATmega168 Pin - PD5 and PD6

Motor 2 (right motor) control (A and B)
Arduino Pin - digital pins 3 and 11
ATmega168 Pin - PD3 and PB3

QTR-RC reflectance sensors (left to right, x5)
Arduino Pin - digital pins 14 - 18
ATmega168 Pin - PC0 and PC4

Red (left) user LED
Arduino Pin - digital pin 1
ATmega168 Pin - PD1

Green (right) user LED
Arduino Pin - digital pin 7
ATmega168 Pin - PD7

User pushbuttons (left to right, x3)
Arduino Pin - digital pins 9, 12, and 13
ATmega168 Pin - PB1, PB4, and PB5

Buzzer
Arduino Pin - digital pin 10
ATmega168 Pin - PB2

LCD control (RS, R/W, E)
Arduino Pin - digital pins 9, 12, 13, and 7
ATmega168 Pin - PB1, PB4, PB5, and PD7

LCD data (4-bit: DB4 - DB7)
Arduino Pin - digital pins 9, 12, 13, and 7
ATmega168 Pin - PB1, PB4, PB5, and PD7

Reflectance sensor IR LED control drive low to turn IR LEDs off)
Arduino Pin - digital pin 19 (through jumper)
ATmega168 Pin - PC5

User trimmer potentiometer
Arduino Pin - analog input 7 (through jumper)
ATmega168 Pin - ADC7

2/3rds of battery voltage
Arduino Pin - analog input 6 (through jumper)
ATmega168 Pin - ADC6

ICSP programming lines (x3)
Arduino Pin - analog input 6 (through jumper)
ATmega168 Pin - ADC6

Reset pushbutton
Arduino Pin - Reset
ATmega168 Pin - PC6

UART (RX and TX)
Arduino Pin - digital pins 0 and 1
ATmega168 Pin - PD0 and PD1

I2C/TWI inaccessable to user

SPI inaccessable to user

Pin Assignment Table Sorted by Pin
ATmega168 Pin Orangutan Function

PD0 free digital I/O
USART input pin (RXD)

PD1 free digital I/O
connected to red user LED (high turns LED on)
USART output pin (TXD)

PD2 LCD control line RS
external interrupt 0 (INT0)

PD3 M2 control line
Timer2 PWM output B (OC2B)

PD4 LCD control line E
USART external clock input/output (XCK)
Timer0 external counter (T0)

PD5 M1 control line
Timer0 PWM output B (OC0B)

PD6 M1 control line
Timer0 PWM output A (OC0A)

PD7 LCD data line DB7
connected to green user LED (high turns LED on)

PB0 LCD control line R/W
Timer1 input capture (ICP1)
divided system clock output (CLK0)

PB1 LCD data line DB4
user pushbutton (pressing pulls pin low)
Timer1 PWM output A (OC1A)

PB2 buzzer
Timer1 PWM output B (OC1B)

PB3 M2 control line
Timer2 PWM output A (OC2A)
ISP programming line

PB4 LCD data line DB5
user pushbutton (pressing pulls pin low)
Caution: also an ISP programming line

PB5 LCD data line DB6
user pushbutton (pressing pulls pin low)
Caution: also an ISP programming line

PC0 QTR-RC reflectance sensor
(drive high for 10 us, then wait for line input to go low) sensor labeled PC0 (leftmost sensor)

PC1 QTR-RC reflectance sensor
(drive high for 10 us, then wait for line input to go low) sensor labeled PC1

PC2 QTR-RC reflectance sensor
(drive high for 10 us, then wait for line input to go low) sensor labeled PC2 (center sensor)

PC3 QTR-RC reflectance sensor
(drive high for 10 us, then wait for line input to go low) sensor labeled PC3

PC4 QTR-RC reflectance sensor
(drive high for 10 us, then wait for line input to go low) sensor labeled PC4 (rightmost sensor)

PC5 analog input and digital I/O
jumpered to sensors' IR LEDs (driving low turns off emitters)
ADC input channel 5 (ADC5)

ADC6 dedicated analog input
jumpered to 2/3rds of battery voltage ADC input channel 6 (ADC6)

ADC7 dedicated analog input
jumpered to user trimmer potentiometer ADC input channel 7 (ADC7)

Reset reset pushbutton
internally pulled high; active low digital I/O disabled by default

9. Expansion Information
The 3pi Robot is designed to be expandable, and an expansion board is under development. This section will be updated soon with detailed information on using our expansion board or designing your own.

10. Related Resources
To learn more about using the Pololu 3pi Robot, see the following list of resources:
WinAVR AVR Studio
Pololu AVR Library Command Reference: detailed information about every function in the library.
Programming the 3pi Robot from the Arduino Environment: a guide to programming the 3pi using the Arduino IDE in place of AVR Studio.
AVR Libc Home Page
ATmega168 documentation
Tutorial: AVR Programming on the Mac
Finally, we would like to hear your comments and questions over at the 3pi Robot Group on the Pololu Robotics Forum!

User Guide Ported by:
Paul Garcia (md10bldr)