Pololu 3pi Robot User's Guide © 2001-2011 Pololu Corporation
Pololu 3pi Robot User's Guide © 2001-2011 Pololu Corporation
http://www.pololu.com/docs/0J21
Page 1 of 68
Pololu 3pi Robot User's Guide 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. How Your 3pi Works . . . . . . . . . . . . . . . . . . 5.a. Batteries . . . . . . . . . . . . . . . . . . . . . 5.b. Power management . . . . . . . . . . . . . . . 5.c. Motors and Gearboxes . . . . . . . . . . . . . 5.d. Digital inputs and sensors . . . . . . . . . . . . 5.e. 3pi Simplified Schematic Diagram . . . . . . . 6. Programming Your 3pi . . . . . . . . . . . . . . . . . 6.a. Downloading and Installing the C/C++ Library 6.b. Compiling a Simple Program . . . . . . . . . . 7. Example Project #1: Line Following . . . . . . . . . . 7.a. About Line Following . . . . . . . . . . . . . . 7.b. A Simple Line-Following Algorithm for 3pi . . 7.c. Advanced Line Following with 3pi: PID Control 8. Example Project #2: Maze Solving . . . . . . . . . . . 8.a. Solving a Line Maze . . . . . . . . . . . . . . 8.b. Working with Multiple C Files in AVR Studio . 8.c. Left Hand on the Wall . . . . . . . . . . . . . . 8.d. The Main Loop(s) . . . . . . . . . . . . . . . . 8.e. Simplifying the Solution . . . . . . . . . . . . 8.f. Improving the Maze-Solving Code . . . . . . . 9. Pin Assignment Tables . . . . . . . . . . . . . . . . . 10. Expansion Information . . . . . . . . . . . . . . . . 10.a. Serial slave program . . . . . . . . . . . . . . 10.b. Serial master program . . . . . . . . . . . . . 10.c. Available I/O on the 3pis ATmegaxx8 . . . . 11. Related Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Page 2 of 68
1. Introduction
Note: Starting with serial number 0J5840, 3pi robots are shipping with the newer ATmega328P microcontroller instead of the ATmega168. The serial number is located on a white bar code sticker on the bottom of the 3pi PCB. The ATmega328 is essentially a drop-in replacement for the ATmega168 with twice the memory (32 KB flash, 2 KB RAM, and 1 KB of EEPROM), so the 3pi code written for the ATmega168 should work with minimal modification on the ATmega328 (the Pololu AVR Library [http://www.pololu.com/docs/0J20] now supports the ATmega328P). The Pololu 3pi robot is a small, high-performance, autonomous robot designed to excel in line-following and line-mazesolving 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 dont 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 82 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 or ATmega328 microcontroller, henceforth referred to as the ATmegaxx8, running at 20 MHz. ATmega168-based 3pi robots feature 16 KB of flash program memory and 1 KB RAM, and 512 bytes of persistent EEPROM memory; ATmega328-based 3pi robots feature 32 KB of flash program memory, 2 KB RAM, and 1 KB of persistent EEPROM memory. The use of the ATmegaxx8 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. Please note that an external AVR ISP programmer, such as our USB AVR Programmer product/1300] is required to program the 3pi robot.
[http://www.pololu.com/catalog/
For a Spanish version of this document, please see Pololu 3pi Robot Guia Usuario [http://www.pololu.com/file/download/Pololu3piRobotGuiaDeUsuario.pdf?file_id=0J137] (3MB pdf) (provided by customer Jaume B.).
1. Introduction
Page 3 of 68
2. Contacting Pololu
You can check the 3pi product page [http://www.pololu.com/catalog/product/975] 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 [http://www.pololu.com/contact] directly or post on our forum [http://forum.pololu.com/]. 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.
2. Contacting Pololu
Page 4 of 68
Page 5 of 68
Page 6 of 68
The following subsections will give you all the information you need to get your 3pi up and running!
Page 7 of 68
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.
Page 8 of 68
6. Music: Plays an adaptation of J. S. Bachs 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. Note: If the 3pi receives any serial data while the demo program is waiting for a button press from the user, it will switch into serial slave mode. See Section 10.a for more information. The source code for the demo program is included with the Pololu AVR C/C++ Library described in Section 6, in the folder examples\3pi-demo-program.
Page 9 of 68
The power put out by a battery is measured by multiplying the volts by the amps, giving a measurement in watts (W). For example, at the point marked in the graph, we have a voltage of 0.9 V and a current of 0.6 A, this means that the power output is 0.54 W. If you want more power, you need to add more batteries, and there are two ways to do it: parallel and series configurations. When batteries are connected in parallel, with all of their positive terminals tied together and all of their negative terminals tied together, the voltage stays the same, but the maximum current output is multiplied by the number of batteries. When they are connected in series, with the positive terminal of one connected to the negative terminal of the next, the maximum current stays the same while the voltage multiplies. Either way, the maximum power 5. How Your 3pi Works Page 10 of 68
output will be multiplied by the number of batteries. Think about two people using two buckets to lift water from a lake to higher ground. If they stand next to each other (working in parallel), they will be able to lift the water to the same height as before, while delivering twice the amount of water. If one of them stands uphill from the other, they can work together (in series) to lift the water twice as high, but at the same rate as a single person. In practice, we only connect batteries in series. This is because different batteries will always have slightly different voltages, and if they are connected in parallel, the stronger battery will deliver current to the weaker battery, wasting power even when there is nothing else in the circuit. If we want more current, we can use bigger batteries: AAA, AA, C, and D batteries of the same type all have the same voltage, but they can put out very different amounts of current. The total amount of energy in any battery is limited by the chemical reaction: once the chemicals are exhausted, the battery will stop producing power. This happens gradually: the voltage and current produced by a battery will steadily drop until the energy runs out, as shown in the graph below:
A rough measure of the amount of energy stored in a battery is given by its milliamp-hour (mAH) rating, which specifies how long the battery will last at a given discharge rate. The mAH rating is the discharge rate multiplied by how long the battery lasts: if you draw current at a rate of 200 mA (0.2 A), and the battery lasts for 3 hours, you would call it a 600 mAH battery. If you discharge the same battery at 600 mA, you would get about an hour of operation (however, battery capacity tends to decline with faster discharge rates, so you might only get 50 minutes). Note: If you have purchased rechargeable batteries for the 3pi, you should fully charge them before you first use them. You should never attempt to program your 3pi if its batteries are drained or uncharged. Losing power during programming could permanently disable your 3pi.
Page 11 of 68
approach makes linear regulators poor choices for applications that have a large difference between the input and output voltages, or for applications that require a lot of current. For example, 15 V batteries regulated down to 5 V with a linear regulator will lose two-thirds of their energy in the linear regulator. This energy becomes heat, so linear regulators often need large heat sinks, and they generally dont work well with high-power applications. Switching regulators turn power on and off at a high frequency, filtering the output to produce a stable supply at the desired voltage. By carefully redirecting the flow of electricity, switching regulators can be much more efficient than linear regulators, especially for high-current applications and large changes in voltage. Also, switching regulators can convert low voltages into higher voltages! A key component of a switching regulator is the inductor, which stores energy and smooths out current; on the 3pi, the inductor is the gray block near the ball caster labeled 100. A desktop computer power supply also uses switching regulators: peek through the vent in the back of your computer and look for a donut-shaped piece with a coil of thick copper wire wrapped around it thats the inductor. The power management subsystem built into the 3pi is shown in this block diagram:
The voltage of 4 x AAA cells can vary between 3.5 5.5 V (and even to 6 V if alkalines are used). This means its not possible simply to regulate the voltage up or down to get 5 V. Instead, in the 3pi, a switching regulator first boosts the battery voltage up to 9.25 V (Vboost), and a linear regulator regulates Vboost back down to 5 V (VCC). Vboost powers the motors and the IR LEDs in the line sensors, while VCC is used for the microcontroller and all digital signals. Using Vboost for the motors and sensors gives the 3pi three unique performance advantages over typical robots, which use battery power directly: First, a higher voltage means more power for the motors, without requiring more current and a larger motor driver. Second, since the voltage is regulated, the motors will run the same speed as the batteries drop from 5.5 down to 3.5 V. You can take advantage of this when programming your 3pi, for example by calibrating a 90 turn based on the amount of time that it takes. Third, at 9.25 V, all five of the IR LEDs can be powered in series so that they consume the lowest possible amount of power. (Note that you can switch the LEDs on and off to save even more power.) One other interesting thing about this power system is that instead of gradually running out of power like most robots, the 3pi will operate at maximum performance until it suddenly shuts off. This can take you by surprise, so you might want your 3pi to monitor its battery voltage.
Page 12 of 68
A simple circuit for monitoring battery voltage is built in to the 3pi. Three resistors, shown in the circuit at right, comprise a voltage divider that outputs a voltage equal to two-thirds of the battery voltage, which will always be safely below the main microcontrollers maximum analog input voltage of 5 V. For example, at a battery voltage of 4.8 V, the battery voltage monitor port ADC6 will be at a level of 3.2 V. Using 10-bit analog-to-digital conversion, where 5 V is read as a value of 1023, 3.2 V is read as a value of 655. To convert it back to the actual battery voltage, multiply this number by 5000 mV3/2 and divide by 1023. This is handled conveniently by the read_battery_millivolts_3pi() function (provided in the Pololu AVR Library; see Section 6.a for more information), which averages ten samples and returns the battery voltage in mV:
unsigned int read_battery_millivolts_3pi() { return readAverage(6,10)*5000L*3/2/1023; }
Page 13 of 68
The free-running speed of a small DC motor is usually many thousands of rotations per minute (rpm), much higher than the speed we want the wheels of a robot to turn. A gearbox is a system of gears that converts the high-speed, lowtorque output of the motor into a lower-speed, higher-torque output that is a much better suited for driving a robot. The gear ratio used on the 3pi is 30:1, which means that for every 30 turns of the motor shaft, the output shaft turns once. This reduces the speed by a factor of 30, and (ideally) increases the torque by a factor of 30. The resulting parameters of the 3pi motors are summarized in this table: Gear ratio: Free-running speed: Free-running current: Stall torque: Stall current: 30:1 700 rpm 60 mA 6 ozin 540 mA
The 30:1 gearmotor used on the 3pi.
The two wheels of the 3pi each have a radius of 0.67 in, which means that the maximum force it can produce with two motors when driving forward is 26/0.67 = 18 oz. The 3pi weighs about 7 oz with batteries, so the motors are strong enough to lift the 3pi up a vertical slope or accelerate it at 2 g (twice the acceleration of gravity). The actual performance is limited by the friction of the tires: on a steep enough slope, the wheels will slip before they stall in practice, this happens when the slope is around 30-40.
Page 14 of 68
If switches 1 and 4 are closed (the center picture), current flows through the motor from left to right, and the motor spins forward. Closing switches 2 and 3 causes the current to reverse direction and the motor to spin backward. An H-bridge can be constructed with mechanical switches, but most robots, including the 3pi, use transistors to switch the current electronically. The H-bridges for both motors on the 3pi are all built into a single motor driver chip, the TB6612FNG, and output ports of the main microcontroller operate the switches through this chip. Here is a table showing how output ports PD5 and PD6 on the microcontroller control the transistors of motor M1: PD5 0 0 1 1 PD6 0 1 0 1 1 off off on off 2 off on off off 3 off on off on 4 off off on on M1 off (coast) forward reverse off (brake)
Motor M2 is controlled through the same logic by ports PD3 and PB3: PD3 0 0 1 1 PB3 0 1 0 1 1 off off on off 2 off on off off 3 off on off on 4 off off on on M2 off (coast) forward reverse off (brake)
Page 15 of 68
Pololu 3pi Robot User's Guide Speed control is achieved by rapidly switching the motor between two states in the table. Suppose we keep PD6 high (at 5 V, also called a logical 1) and have PD5 alternate quickly between low (0 V or 0) and high. The motor driver will switch between the forward and brake states, causing M1 to turn forward at a reduced speed. For example, if PD6 is high two thirds of the time (a 67% duty cycle), then M1 will turn at approximately 67% of its full speed. Since the motor voltage is a series of pulses of varying width, this method of speed control is called pulse-width modulation (PWM). An example series of PWM pulses is shown in the graph at right: as the size of the pulses decreases from 100% duty cycle down to 0%, the motor speed decreases from full speed down to a stop.
In the 3pi, speed control is accomplished using special PWM speed control, showing gradual deceleration. PWM outputs of the main microcontroller that are linked to the internal timers Timer0 and Timer2. This means that you can set the PWM duty cycle of the two motors once, and the hardware will continue to produce the PWM signal, in the background, without any further attention. The set_motors() function in the Pololu AVR Library (see Section 6.a for more information) lets you set the duty cycle, and it uses 8-bit precision: a value of 255 corresponds to 100% duty cycle. For example, to get 67% on M1 and 33% on M2, you would call
set_motors(171,84);
To get a slowly decreasing PWM sequence like the one shown in the graph, you would need to write a loop that gradually decreases the motor speed over time.
Page 16 of 68
Normally, the pull-up resistor R (20-50 k) brings the voltage on the input pin to 5 V, so it reads as a 1, but pressing the button connects the input to ground (0 V) through a 1 k resistor, which is much lower than the value of R. This brings the input voltage very close to 0 V, so the pin reads as a 0. Without the pull-up resistor, the input would be floating when the button is not pressed, and the value read could be affected by residual voltage on the line, interference from nearby electrical signals, or even distant lightning. Dont leave an input floating unless you have a good reason. Since the pull-up resistors are important, they are included within the AVR the resistor R in the picture represents this internal pull-up, not a discrete part on the 3pi circuit board. A more complicated use for the digital inputs is in the reflectance sensors. Here is the circuit for the 3pis leftmost reflectance sensor, which is connected to pin PC0:
Page 17 of 68
The sensing element of the reflectance sensor is the phototransistor shown in the left half of U4, which is connected in series with capacitor C21. A separate connection leads through resistor R12 to pin PC0. This circuit takes advantage of the fact the digital inputs of the AVR can be reconfigured as digital outputs on the fly. A digital output presents a voltage of 5 V or 0 V, depending on whether it is set to a 1 or a 0 by your program. The way it works is that the pin is alternately set to a 5 V output and then a digital input. The capacitor stores charge temporarily, so that the input reads as a 1 until most of the stored charge has flowed through the phototransistor. Here is an oscilloscope trace showing the voltage on the capacitor (yellow) dropping as its charge flows through the phototransistor, and the resulting digital input value of pin PC0 (blue):
The rate of current flow through the phototransistor depends on the light level, so that when the robot is over a bright white surface, the value returns to 0 much more quickly than when it is over a black surface. The trace shown above was taken when the sensor was on the edge between a black surface and a white one this is what it looks like on pure white:
The length of time that the digital input stays at 1 is very short when over white, and very long when over black. The function read_line_sensors() in the Pololu AVR Library switches the port as described above and returns the time for each of the five sensors. Here is a simplified version of the code that reads the sensors:
Page 18 of 68
// figure out which pins changed for (i = 0; i < _numSensors; i++) { if (sensor_values[i] == 0 && !(*_register[i] & _bitmask[i])) sensor_values[i] = time; }
This piece of code is found in the file src\PololuQTRSensors\PololuQTRSensors.cpp. The code makes use of timer TCNT2, which is a special register in the AVR that we have configured to count up continuously, incrementing every 0.4 s. Basically, the code waits until one of the sensors changes value, counting up the elapsed time in the variable time. (It is important to use a separate variable for the elapsed time since the timer TCNT2 periodically overflows, dropping back to zero.) Upon detecting a transition from a 1 to a 0 on one of the sensors (by measuring a change in the input port PINC), the code determines which sensor changed and records the time in the array sensor_values[i]. After the time limit _maxValue is reached (this is set to 2000 by default on the 3pi, corresponding to 800 s), the loop ends, and the time values are returned.
Page 19 of 68
Page 20 of 68
You can download a pdf version of the schematic here [http://www.pololu.com/file/download/3pi_schematic.pdf?file_id=0J119] (40k pdf).
Page 21 of 68
development environment (IDE) that natively works with WinAVRs free GCC C/C++ compiler. AVR Studio includes AVR ISP software that will let you upload your programs to the 3pi. 3. The Pololu AVR C/C++ Library features of your 3pi.
[http://www.pololu.com/docs/0J20],
4. The drivers and software for the Pololu USB AVR Programmer. If you are using the Pololu USB AVR Programmer, the development bundle should be enough to get you started, though we still recommend you take a moment to look through the programmer users guide [http://www.pololu.com/docs/0J36] and get more familiar with it and its bonus features. If you are using a different programmer, you will need to set up your programmer by following its installation instructions. Note: The rest of this guide will assume you are programming the 3pi using AVR Studio 4 in Windows, but it is possible to program the 3pi on other platforms. For more general instructions on AVR programming, including Linux and Mac OS X installation instructions, see the Pololu USB AVR Programmer Users Guide [http://www.pololu.com/docs/0J36] and the Pololu AVR C/C++ Library Users Guide [http://www.pololu.com/docs/0J20]. We provide limited support for 3pi programming on the Mac. You can also program your 3pi using the Arduino IDE and an external ICSP programmer, such as our USB AVR Programmer. For instructions on this approach, please see our guide: Programming Orangutans and the 3pi Robot from the Arduino Environment [http://www.pololu.com/docs/0J17].
Warning: Do not attempt to program your 3pi if its batteries are drained or uncharged (make sure you charge any new rechargeable batteries fully before you first use them). Losing power during programming could permanently disable your 3pi.
Page 22 of 68
Note: To learn more about the functions in the Pololu AVR Library and how to use them, please see the Pololu AVR Library Command Reference [http://www.pololu.com/docs/0J18].
} }
delay_ms(100);
return 0;
Navigate to the simple-test folder, double-click on the file simple-test.aps, and the project should open automatically in AVR Studio, showing the C file. Make sure you select the version that is appropriate for the microcontroller on your 3pi: atmega328p if your serial number is 0J5840 or greater, else atmega168.
Page 23 of 68
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 the examples\atmegaxx8\simple-test\default folder.
Page 24 of 68
Connect your programmer to your computer and to the ISP port of your 3pi, and turn on the 3pis power by pressing the button labeled POWER. If you are using the Pololu USB AVR Programmer, the green status LED close to the USB connector should be on and the yellow status LED should be blinking, indicating that the programmer is ready.
Warning: Do not attempt to program your 3pi if its batteries are drained or uncharged (make sure you charge any new rechargeable batteries fully before you first use them). Losing power during programming could permanently disable your 3pi.
Note: Your programmer must be installed correctly before you use it. If you are using the Pololu USB AVR programmer, please see its users guide [http://www.pololu.com/docs/0J36] for installation instructions.
Page 25 of 68
Pololu 3pi robot with an Orangutan USB programmer connected to its ISP port.
Select Tools > Program AVR > Connect to connect to the programmer. For the Pololu USB AVR programmer or Orangutan USB 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 the file test.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.
Page 26 of 68
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.
Page 27 of 68
Page 28 of 68
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 [http://www.pololu.com/docs/0J18]. 5. Waiting for the user to press a button. Its 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 9 of the command reference [http://www.pololu.com/docs/0J18]. 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 its 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 robots 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 by read_line() is divided into three possible cases: 01000: 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. 10003000: the robot is approximately centered on the line. In this case, we set both motors to speed 100, to drive straight ahead. 30004000: 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\atmegaxx8\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 17 of the command reference [http://www.pololu.com/docs/0J18] 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.
Page 29 of 68
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 3 of the command reference [http://www.pololu.com/docs/0J18] for more information on using the buzzer; for music, youll 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.
/* * 3pi-linefollower - demo code for the Pololu 3pi Robot * * This code will follow a black line on a white background, using a * very simple algorithm. It demonstrates auto-calibration and use of * the 3pi IR sensors, motor control, bar graphs using custom * characters, and music playback, making it a good starting point for * developing your own more competitive line follower. * * http://www.pololu.com/docs/0J21 * http://www.pololu.com * http://forum.pololu.com * */ // The 3pi include file must be at the beginning of any program that // uses the Pololu AVR library and 3pi. #include <pololu/3pi.h> // This include file allows data to be stored in program space. // ATmegaxx8 has 16x more program space than RAM, so large // pieces of static data should be stored in program space. #include <avr/pgmspace.h> The
// Introductory messages. The "PROGMEM" identifier causes the data to // go into program space. const char welcome_line1[] PROGMEM = " Pololu"; const char welcome_line2[] PROGMEM = "3\xf7 Robot"; const char demo_name_line1[] PROGMEM = "Line"; const char demo_name_line2[] PROGMEM = "follower"; // A couple of simple tunes, stored in program space. const char welcome[] PROGMEM = ">g32>>c32"; const char go[] PROGMEM = "L16 cdegreg4"; // Data for generating the characters used in load_custom_characters // and display_readings. By reading levels[] starting at various // offsets, we can generate all of the 7 extra characters needed for a // bargraph. This is also stored in program space. const char levels[] PROGMEM = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111 }; // This function loads custom characters into the LCD. Up to 8 // characters can be loaded; we use them for 7 levels of a bar graph. void load_custom_characters() { lcd_load_custom_character(levels+0,0); // no offset, e.g. one bar lcd_load_custom_character(levels+1,1); // two bars lcd_load_custom_character(levels+2,2); // etc... lcd_load_custom_character(levels+3,3); lcd_load_custom_character(levels+4,4); lcd_load_custom_character(levels+5,5); lcd_load_custom_character(levels+6,6);
Page 30 of 68
clear(); // the LCD must be cleared for the characters to take effect
// This function displays the sensor readings using a bar graph. void display_readings(const unsigned int *calibrated_values) { unsigned char i; for(i=0;i<5;i++) { // Initialize the array of characters that we will use for the // graph. Using the space, an extra copy of the one-bar // character, and character 255 (a full black box), we get 10 // characters in the array. const char display_characters[10] = {' ',0,0,1,2,3,4,5,6,255}; // The variable c will have values from 0 to 9, since // calibrated values are in the range of 0 to 1000, and // 1000/101 is 9 with integer math. char c = display_characters[calibrated_values[i]/101]; // Display the bar graph character. print_character(c);
// Initializes the 3pi, displays a welcome message, calibrates, and // plays the initial music. void initialize() { unsigned int counter; // used as a simple timer unsigned int sensors[5]; // an array to hold sensor values // This must be called at the beginning of 3pi code, to set up the // sensors. We use a value of 2000 for the timeout, which // corresponds to 2000*0.4 us = 0.8 ms on our 20 MHz processor. pololu_3pi_init(2000); load_custom_characters(); // load the custom characters // Play welcome music and display a message print_from_program_space(welcome_line1); lcd_goto_xy(0,1); print_from_program_space(welcome_line2); play_from_program_space(welcome); delay_ms(1000); clear(); print_from_program_space(demo_name_line1); lcd_goto_xy(0,1); print_from_program_space(demo_name_line2); delay_ms(1000); // Display battery voltage and wait for button press while(!button_is_pressed(BUTTON_B)) { int bat = read_battery_millivolts(); clear(); print_long(bat); print("mV"); lcd_goto_xy(0,1); print("Press B"); } delay_ms(100);
// Always wait for the button to be released so that 3pi doesn't // start moving until your hand is away from it. wait_for_button_release(BUTTON_B); delay_ms(1000); // Auto-calibration: turn right and left while calibrating the // sensors. for(counter=0;counter<80;counter++) { if(counter < 20 || counter >= 60) set_motors(40,-40); else
Page 31 of 68
// This function records a set of sensor readings and keeps // track of the minimum and maximum values encountered. The // IR_EMITTERS_ON argument means that the IR LEDs will be // turned on during the reading, which is usually what you // want. calibrate_line_sensors(IR_EMITTERS_ON); // Since our counter runs to 80, the total delay will be // 80*20 = 1600 ms. delay_ms(20);
} set_motors(0,0);
// Display calibrated values as a bar graph. while(!button_is_pressed(BUTTON_B)) { // Read the sensor values and get the position measurement. unsigned int position = read_line(sensors,IR_EMITTERS_ON); // Display the position measurement, which will go from 0 // (when the leftmost sensor is over the line) to 4000 (when // the rightmost sensor is over the line) on the 3pi, along // with a bar graph of the sensor readings. This allows you // to make sure the robot is ready to go. clear(); print_long(position); lcd_goto_xy(0,1); display_readings(sensors); delay_ms(100); } wait_for_button_release(BUTTON_B); clear(); print("Go!"); // Play music and wait for it to finish before we start driving. play_from_program_space(go); while(is_playing());
// This is the main function, where the code starts. All C programs // must have a main() function defined somewhere. int main() { unsigned int sensors[5]; // an array to hold sensor values // set up the 3pi initialize(); // This is the "main loop" - it will run forever. while(1) { // Get the position of the line. Note that we *must* provide // the "sensors" argument to read_line() here, even though we // are not interested in the individual sensor readings. unsigned int position = read_line(sensors,IR_EMITTERS_ON); if(position < 1000) { // We are far to the right of the line: turn left. // Set the right motor to 100 and the left motor to zero, // to do a sharp turn to the left. Note that the maximum // value of either motor speed is 255, so we are driving // it at just about 40% of the max. set_motors(0,100); // Just for fun, indicate the direction we are turning on // the LEDs. left_led(1); right_led(0);
Page 32 of 68
} else {
// We are somewhat close to being centered on the line: // drive straight. set_motors(100,100); left_led(1); right_led(1);
} // // // // // // //
// We are far to the left of the line: turn right. set_motors(100,0); left_led(0); right_led(1);
This part of the code is never reached. A robot should never reach the end of its program, or unpredictable behavior will result as random code starts getting executed. If you really want to stop all actions at some point, set your motors to 0,0 and run the following command to loop forever: while(1);
Page 33 of 68
Note that we cast the variable position to an int 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 wouldnt 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:
// Compute the difference between the two motor power settings, // m1 - m2. If this is a positive number the robot will turn // to the right. If it is a negative number, the robot will // turn to the left, 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. // to a negative value. const int max = 60; if(power_difference > max) power_difference = max; if(power_difference < -max) power_difference = -max; We never set either motor
The values 1/20, 1/10000, and 3/2 represent adjustable parameters that determine how your 3pi will react to the line. The particular values chosen for this example were somewhat arbitrarily picked, and while they work sufficiently for typical line following, there is plenty of room to improve them. In general, increasing these PID parameters will make power_difference larger, causing stronger reactions, while decreasing them will make the reactions weaker. Its 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. Youll 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! We have been able to run 3pis with a maximum speed of 255 on courses with 6"-radius curves, all by finding the right PID parameters. Please see Section 2 of the 3pi robot videos [http://www.pololu.com/docs/0J32] gallery for videos of 3pi line followers using tuned PID and higher maximum speeds.
Page 34 of 68
Page 35 of 68
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 dont 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 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 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);
Page 36 of 68
// 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. Weve 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 youll 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.
Page 37 of 68
way, since youll travel down every hallway exactly twice. We use this simple, reliable strategy in our 3pi maze solving example:
// This function decides which way to turn during the learning phase of // maze solving. It uses the variables found_left, found_straight, and // found_right, which indicate whether there is an exit in each of the // three directions, applying the "left hand on the wall" strategy. char select_turn(unsigned char found_left, unsigned char found_straight, unsigned char found_right) { // Make a decision about how to turn. The following code // implements a left-hand-on-the-wall strategy, where we always // turn as far to the left as possible. if(found_left) return 'L'; else if(found_straight) return 'S'; else if(found_right) return 'R'; else return 'B'; }
The values returned by select_turn() correspond to the values used by turn(), so these functions will work nicely together in our main loop.
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. Heres an outline of the code:
// This function is called once, from main.c. void maze_solve() { while(1) { // FIRST MAIN LOOP BODY // (when we find the goal, we use break; to get out of this) } // Now enter an infinite loop - we can re-run the maze as many // times as we want to. while(1) { // Beep to show that we finished the maze. // Wait for the user to press a button... int i; for(i=0;i<path_length;i++) { // SECOND MAIN LOOP BODY } // Follow the last segment up to the finish. follow_segment(); } // Now we should be at the finish! Restart the loop.
Page 38 of 68
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 were using:
// FIRST MAIN LOOP BODY follow_segment(); // Drive straight a bit. This helps us in case we entered the // intersection at an angle. // Note that we are slowing down - this prevents the robot // from tipping forward too much. set_motors(50,50); delay_ms(50); // These variables record whether the robot has seen a line to the // left, straight ahead, and right, whil examining the current // intersection. unsigned char found_left=0; unsigned char found_straight=0; unsigned char found_right=0; // Now read the sensors and check the intersection type. unsigned int sensors[5]; read_line(sensors,IR_EMITTERS_ON); // Check for left and right exits. if(sensors[0] > 100) found_left = 1; if(sensors[4] > 100) found_right = 1; // Drive straight a bit more - this is enough to line up our // wheels with the intersection. set_motors(40,40); delay_ms(200); // Check for a straight exit. read_line(sensors,IR_EMITTERS_ON); if(sensors[1] > 200 || sensors[2] > 200 || sensors[3] > 200) found_straight = 1; // Check for the ending spot. // If all three middle sensors are on dark black, we have // solved the maze. if(sensors[1] > 600 && sensors[2] > 600 && sensors[3] > 600) break; // Intersection identification is complete. // If the maze has been solved, we can follow the existing // path. Otherwise, we need to learn the solution. unsigned char dir = select_turn(found_left, found_straight, found_right); // Make the turn indicated by the path. turn(dir); // Store the intersection in the path variable. path[path_length] = dir; path_length ++; // You should check to make sure that the path_length does not // exceed the bounds of the array. We'll ignore that in this // example. // Simplify the learned path. simplify_path(); // Display the path on the LCD. display_path();
Well discuss the call to simplify_path() in the next section. Before that, lets 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
Page 39 of 68
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.
// SECOND MAIN LOOP BODY follow_segment(); // Drive straight while slowing down, as before. set_motors(50,50); delay_ms(50); set_motors(40,40); delay_ms(200); // Make a turn according to the instruction stored in // path[i]. turn(path[i]);
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. Heres the code to handle this:
// Path simplification. The strategy is that whenever we encounter a // sequence xBx, we can simplify it by cutting out the dead end. For
Page 40 of 68
// example, LBL -> S, because a single S bypasses the dead end // represented by LBL. void simplify_path() { // only simplify the path if the second-to-last turn was a 'B' if(path_length < 3 || path[path_length-2] != 'B') return; int total_angle = 0; int i; for(i=1;i<=3;i++) { switch(path[path_length-i]) { case 'R': total_angle += 90; break; case 'L': total_angle += 270; break; case 'B': total_angle += 180; break; } } // Get the angle as a number between 0 and 360 degrees. total_angle = total_angle % 360; // Replace all of those turns switch(total_angle) { case 0: path[path_length - 3] break; case 90: path[path_length - 3] break; case 180: path[path_length - 3] break; case 270: path[path_length - 3] break; } with a single one.
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. Now lets step through a slightly more complicated maze, showing how we can simplify the path as we explore it: Fully explore the maze using a left-hand-on-the-wall strategy.
Page 41 of 68
The above list of actions is a record of all the steps we took to fully explore the maze while looking for the end, which is marked by the large black circle. Our goal is to now reduce this list to represent the shortest path from start to finish by weeding out all of the dead ends. One option is to perform this pruning when we finish the maze, but the better approach is to perform the pruning as we go to keep our list from growing excessively large and taking up more memory than we have available. Prune out the first dead end as we identify it.
Page 42 of 68
When we encounter the first intersection after our first back action, we know we have reached a dead end that can be removed from our list of actions. In this case, the most recent actions in our list is the sequence SBL, and the diagram shows that this sequence can be simplified into a single right turn R. Prune out the rest of this dead-end branch as we back-track.
Page 43 of 68
We next end up with the sequence RBL, which reduces to a single back B, and this combines with the next action to produce the sequence LBL, which reduces to a single straight S. Prune out the final dead-end branch to leave us with the shortest path from start to finish.
Page 44 of 68
The last dead end gives us the sequence SBL, which reduces to a sigle right turn R. Our action list is now just R and represents the shortest path from start to finish. As we drove the maze, our action list would have looked like the following: 1. L 2. LS 3. LSB 4. LSBL => LR (pruning occurs here) 5. LRB 6. LRBL => LB (pruning occurs here) 7. LBL => S (pruning occurs here) 8. SB 9. SBL => R (pruning occurs here)
Page 45 of 68
Pololu 3pi Robot User's Guide Increasing the line-following speed. Improving the line-following PID constants. 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. The following video shows a 3pi prototypeit only has one blue power LED, but it is otherwise functionally identical to the final versionthat we programmed to compete in LVBots Challenge 4.0. The code is more advanced (and complicated) than the sample maze-solving code we have just provided. Improvements over the sample program include a higher base running speed with better-tuned line-following PID constants, faster and smoother turns, and increased speed on long straight segments. When we were trying to improve the 3pis maze performance, our first step was to improve its line-following ability by better tuning the PID constants as we slowly increased the robots maximum speed, and our second step was to improve the turns to be faster and smoother. Very quickly, however, we noticed that further speed improvement was being limited by the intersections. If the robot was moving too quickly when it hit them, it would invariably screw up somewhere. Going slowly enough to survive the intersections led to unnecessarily slow driving on long straight segments, however. Our solution was to time the length of every segment the robot encountered during the learning phase. The code would reset the timer at an intersection and then stop it when the 3pi hit the following intersection. As the program stored an array of visited intersections, it also stored the segment times in a parallel array, producing something like:
{ L, S, S, R, L, ... } { 3, 3, 6, 5, 8, ... }
The top array gives the action performed at each visited intersection (L = turned left, S = went straight, R = turned right), and the bottom array gives the amount of time spent driving along the segment that directly led to that intersection. The units of the segment times were chosen to provide numbers that can allow the robot to meaningfully differentiate between longer and shorter segments but that never exceed 255 for any segment in the maze. This second restriction means that the values can be stored in an array of unsigned chars (i.e. each segments time takes up just one byte of memory), which helps keep memory usage down. The ATmega168 has just 1024 bytes of RAM, so its important that applications like this store data in an efficient way that leaves enough room for the stack, which is also stored in RAM. A good rule of thumb is to leave 300 400 bytes of RAM available for the stack and data used by the Pololu AVR library (or more if you have some deeply nested functions or functions with a lot of local variables). Note that the ATmega328 has 2048 bytes of RAM, which gives you a bit more room for your data. Once the 3pi has learned the maze, the maze-driving algorigthm is essentially: 1. If the robot is going straight at the next intersection, drive the current segment at high speed; dont even worry about slowing down until we know we have an intersection coming up that will require a turn. 2. Otherwise, drive the current segment at high speed until time T has elapsed, at which point slow back down to normal speed until the next intersection is reached. The value T is computed from a function that uses the previously measured segment length. For short segments, T is negative and the 3pi just drives the entire segment at normal speed. For longer segments, T is positive and causes the 3pi to drive most of the segment at high speed before slowing down just in time to handle the intersection safely. We came up with a function for T on paper and then ran a series of tests to get the various constants right.
Page 46 of 68
Typically, one might use encoders to measure the lengths of the segments. We were able to just use timing on the 3pi, however, because of the 3pis power system, which uses a regulated voltage for the motors and produces highly repeatable results. With a more traditional power system, motor speed would decrease as the batteries discharge, and a timing approach like this would potentially produce unreliable results. For example, if you were to use a robot with a more traditional power system, the function you come up with for T when the batteries are freshly charged might work poorly when they are nearly drained. Tip: Once you start significantly increasing your maze-solving speed, performance becomes dependent on the traction of the tires. Unfortunately, traction decreases over time as the tires pick up dust and dirt from the course. Our fast maze solver needs to have its tires cleaned every few runs or else it starts fishtailing on the turns, which slows it down and can even cause it to mess up. You can see the effects of this on the second (solution) run of the video (the tires hadnt been cleaned recently). You can easily clean the tires by wiping them with a little rubbing alcohol on a paper towel.
Page 47 of 68
Page 48 of 68
Page 49 of 68
Page 50 of 68
PB1, PB4, PB5, and PD7 digital pins 9, 12, 13, and 7 digital pin 19 analog input 7 analog input 6 digital pins 11, 12, and 13 reset digital pins 0 and 1
reflectance sensor IR LED control (drive low to turn IR LEDs off) PC5 (through jumper) user trimmer potentiometer 2/3rds of battery voltage ICSP programming lines (x3) reset pushbutton UART (RX and TX) I2C/TWI SPI ADC7 (through jumper) ADC6 (through jumper) PB3, PB4, PB5 PC6 PD0 and PD1 inaccessable to user inaccessable to user
Page 51 of 68
Page 52 of 68
The following two pictures show the black version with cutouts mounted on a 3pi robot:
We also offer a more advanced expansion kit [http://www.pololu.com/catalog/product/2152] that lets you turn your 3pi robot into an m3pi robot. The m3pi expansion kit has sockets for additional electronics, making it simple to significantly increase the capabilities of your 3pi. One socket lets you use a powerful mbed development board [http://www.pololu.com/catalog/product/2150] as a high-level robot controller (by issuing serial commands to the 3pi base while it is running its serial slave program), and another socket can be used for a the easy addition of a wireless serial module
Page 53 of 68
(XBee, Wixel [http://www.pololu.com/catalog/product/1337], Bluetooth, etc). Please note that the m3pi robot is also available fully assembled. Please see the m3pi robot product page [http://www.pololu.com/catalog/product/2151] for more information.
Complete documentation of the serial functions used here can be found in Section 10 of the Pololu AVR Library Command Reference [http://www.pololu.com/docs/0J18]. This slave program receives serial data on port PD0 (RX) of the 3pi and transmits responses (when necessary) on port PD1 (TX), using a 115.2 kbaud, TTL-level serial protocol. In this example, there are no parity bits, 8 data bits, and one stop bit (N81). The commands implemented here each consist of a single command byte followed by zero or more data bytes. To make it easy to differentiate the command bytes from the data bytes, the command bytes are all in the range 0x80-0xff, while the data bytes are in the range 0x00-0x7f. That is, the command bytes have their most significant bits set, while the data bytes have that bit unset. Some commands result in the 3pi sending data back out to the controlling device. For commands where integers are sent back, the least significant byte is sent first (little endian). If bad commands or data bytes are detected, the slave program beeps and displays an error message on the LCD. This means that if you are using the expansion kit without cutouts [http://www.pololu.com/catalog/product/978], you should probably remove the LCD-related commands before loading the program onto your 3pi. The following commands are recognized by the slave program:
Page 54 of 68
Pololu 3pi Robot User's Guide Command byte 0x81 Command signature Data Response bytes bytes 0 6
20012011 Pololu Corporation Description Sends the slave name and code version, e.g. 3pi1.0. This command also sets motor speeds to 0 and stops PID line following, if active, so it is useful as an initialization command. Reads all five IR sensors and sends the raw values as a sequence of two-byte ints, in the range 0-2000 Reads all five IR sensors and sends calibrated values as a sequence of two-byte ints, in the range 0-1000 Sends the voltage output of the trimpot as a two-byte int, in the range 0-1023 Sends the battery voltage of the 3pi in mV, as a two-byte int Plays a tune specified by a string of musical commands. The first data byte specifies the length of the following string (max length 100), so that the slave program knows how many more data bytes to read. See the play() command in Section 3 of the Pololu AVR Library Command Reference for a description of the musical command format. Performs one round of calibration on the sensors, reads all five IR sensors, and sends calibrated values as a sequence of twobyte ints, in the range 0-1000. This should be called multiple times, as the robot moves over a range from white to black. Resets the calibration. This should always be used when connecting to a slave, in case the master reset without a slave reset, for example in case of a power glitch. Reads all five IR sensors using calibrated values and estimates the position of a black line under the robot. The value, which is sent back as a two-byte integer, is 0 when the line is under sensor PC0 or farther to the left, 1000 when the line is directly under sensor PC1, up to 4000 when it is under sensor PC4 or farther to the right. See Section 19 of of the Pololu AVR Library Command Reference for the formula used to estimate position. Clears the LCD screen on the 3pi. Prints 1-8 characters to the LCD. The first byte is the length of the following string of characters, as with the play command above. Moves the LCD cursor to x-y coordinates given by the next two bytes. Turns the robot left and right while calibrating. For use when the robot it positioned over a line. Returns the character c when complete.
raw sensors
10 10 2 2
0xB3
play music
2-101 0
0xB4
calibrate
10
0xB5
reset calibration
0xB6
line position
0xB7 0xB8
0 2-9
0 0
0xB9
LCD goto xy
0xBA
autocalibrate
Page 55 of 68
20012011 Pololu Corporation Sets up PID parameters and begins line following. The first data byte sets the maximum motor speed. The next four bytes, a, b, c, and d, represent the PID parameters. Specifically, the difference in the motor speeds will be set to (L-2000)a/b + Dc/d, where L is the position of the line as described above, and D is the derivative of L. The integral term is not implemented in this program. See Section 7.c for more information on PID line following. Stops PID line following, setting motor speeds to 0. Sets motor M1 turning forward with a speed of 0 (off) up to 127 (full speed). Sets motor M1 turning backward with a speed of 0 (off) up to 127 (full reverse). Sets motor M2 turning forward with a speed of 0 (off) up to 127 (full speed). Sets motor M2 turning backward with a speed of 0 (off) up to 127 (full reverse).
0xBB
start PID
0 1 1 1 1
0 0 0 0 0
Source code
#include <pololu/3pi.h> /* * 3pi-serial-slave - An example serial slave program for the Pololu * 3pi Robot. See the following pages for more information: * * http://www.pololu.com/docs/0J21 * http://www.pololu.com/docs/0J20 * http://www.poolu.com/ * */ // PID constants unsigned int pid_enabled = 0; unsigned char max_speed = 255; unsigned char p_num = 0; unsigned char p_den = 0; unsigned char d_num = 0; unsigned char d_den = 0; unsigned int last_proportional = 0; unsigned int sensors[5]; // This routine will be called repeatedly to keep the PID algorithm running void pid_check() { if(!pid_enabled) return; // Do nothing if the denominator of any constant is zero. if(p_den == 0 || d_den == 0) { set_motors(0,0); return; } // Read the line position, with serial interrupts running in the background. serial_set_mode(SERIAL_AUTOMATIC); unsigned int position = read_line(sensors, IR_EMITTERS_ON); serial_set_mode(SERIAL_CHECK); // The "proportional" term should be 0 when we are on the line. int proportional = ((int)position) - 2000;
Page 56 of 68
// Compute the derivative (change) of the position. int derivative = proportional - last_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 right. If it is a negative number, the robot will // turn to the left, and the magnitude of the number determines // the sharpness of the turn. int power_difference = proportional*p_num/p_den + derivative*p_num/p_den; // Compute the actual motor settings. We never set either motor // to a negative value. if(power_difference > max_speed) power_difference = max_speed; if(power_difference < -max_speed) power_difference = -max_speed; if(power_difference < 0) set_motors(max_speed+power_difference, max_speed); else set_motors(max_speed, max_speed-power_difference);
// A global ring buffer for data coming in. This is used by the // read_next_byte() and previous_byte() functions, below. char buffer[100]; // A pointer to where we are reading from. unsigned char read_index = 0; // Waits for the next byte and returns it. Runs play_check to keep // the music playing and serial_check to keep receiving bytes. // Calls pid_check() to keep following the line. char read_next_byte() { while(serial_get_received_bytes() == read_index) { serial_check(); play_check(); // pid_check takes some time; only run it if we don't have more bytes to process if(serial_get_received_bytes() == read_index) pid_check(); } char ret = buffer[read_index]; read_index ++; if(read_index >= 100) read_index = 0; return ret;
// Backs up by one byte in the ring buffer. void previous_byte() { read_index --; if(read_index == 255) read_index = 99; } // Returns true if and only if the byte is a command byte (>= 0x80). char is_command(char byte) { if (byte < 0) return 1; return 0; } // Returns true if and only if the byte is a data byte (< 0x80). char is_data(char byte) { if (byte < 0) return 0;
Page 57 of 68
// If it's not a data byte, beeps, backs up one, and returns true. char check_data_byte(char byte) { if(is_data(byte)) return 0; play("o3c"); clear(); print("Bad data"); lcd_goto_xy(0,1); print_hex_byte(byte); previous_byte(); return 1;
///////////////////////////////////////////////////////////////////// // COMMAND FUNCTIONS // // Each function in this section corresponds to a single serial // command. The functions are expected to do their own argument // handling using read_next_byte() and check_data_byte(). // Sends the version of the slave code that is running. // This function also shuts down the motors and disables PID, so it is // useful as an initial command. void send_signature() { serial_send_blocking("3pi1.0", 6); set_motors(0,0); pid_enabled = 0; } // Reads the line sensors and sends their values. This function can // do either calibrated or uncalibrated readings. When doing calibrated readings, // it only performs a new reading if we are not in PID mode. Otherwise, it sends // the most recent result immediately. void send_sensor_values(char calibrated) { if(calibrated) { if(!pid_enabled) read_line_sensors_calibrated(sensors, IR_EMITTERS_ON); } else read_line_sensors(sensors, IR_EMITTERS_ON); serial_send_blocking((char *)sensors, 10); } // Sends the raw (uncalibrated) sensor values. void send_raw_sensor_values() { send_sensor_values(0); } // Sends the calibated sensor values. void send_calibrated_sensor_values() { send_sensor_values(1); } // Computes the position of a black line using the read_line() // function, and sends the value. // Returns the last value computed if PID is running. void send_line_position() { int message[1]; unsigned int tmp_sensors[5]; int line_position; if(pid_enabled) line_position = last_proportional+2000; else line_position = read_line(tmp_sensors, IR_EMITTERS_ON);
Page 58 of 68
// Sends the trimpot value, 0-1023. void send_trimpot() { int message[1]; message[0] = read_trimpot(); serial_send_blocking((char *)message, 2); } // Sends the batter voltage in millivolts void send_battery_millivolts() { int message[1]; message[0] = read_battery_millivolts(); serial_send_blocking((char *)message, 2); } // Drives m1 forward. void m1_forward() { char byte = read_next_byte(); if(check_data_byte(byte)) return; } set_m1_speed(byte == 127 ? 255 : byte*2);
// Drives m2 forward. void m2_forward() { char byte = read_next_byte(); if(check_data_byte(byte)) return; } set_m2_speed(byte == 127 ? 255 : byte*2);
// Drives m1 backward. void m1_backward() { char byte = read_next_byte(); if(check_data_byte(byte)) return; } set_m1_speed(byte == 127 ? -255 : -byte*2);
// Drives m2 backward. void m2_backward() { char byte = read_next_byte(); if(check_data_byte(byte)) return; } set_m2_speed(byte == 127 ? -255 : -byte*2);
// A buffer to store the music that will play in the background. char music_buffer[100]; // Plays a musical sequence. void do_play() { unsigned char tune_length = read_next_byte(); if(check_data_byte(tune_length)) return;
Page 59 of 68
// Clears the LCD void do_clear() { clear(); } // Displays data to the screen void do_print() { unsigned char string_length = read_next_byte(); if(check_data_byte(string_length)) return; unsigned char i; for(i=0;i<string_length;i++) { unsigned char character; character = read_next_byte(); if(check_data_byte(character)) return; // Before printing to the LCD we need to go to AUTOMATIC mode. // Otherwise, we might miss characters during the lengthy LCD routines. serial_set_mode(SERIAL_AUTOMATIC); print_character(character); serial_set_mode(SERIAL_CHECK);
// Goes to the x,y coordinates on the lcd specified by the two data bytes void do_lcd_goto_xy() { unsigned char x = read_next_byte(); if(check_data_byte(x)) return; unsigned char y = read_next_byte(); if(check_data_byte(y)) return; } lcd_goto_xy(x,y);
// Runs through an automatic calibration sequence void auto_calibrate() { time_reset(); set_motors(60, -60); while(get_ms() < 250) calibrate_line_sensors(IR_EMITTERS_ON); set_motors(-60, 60); while(get_ms() < 750) calibrate_line_sensors(IR_EMITTERS_ON); set_motors(60, -60); while(get_ms() < 1000) calibrate_line_sensors(IR_EMITTERS_ON); set_motors(0, 0);
Page 60 of 68
serial_send_blocking("c",1);
// Turns on PID according to the supplied PID constants void set_pid() { unsigned char constants[5]; unsigned char i; for(i=0;i<5;i++) { constants[i] = read_next_byte(); if(check_data_byte(constants[i])) return; } // make the max speed 2x of the first one, so that it can reach 255 max_speed = (constants[0] == 127 ? 255 : constants[0]*2); // set the other parameters directly p_num = constants[1]; p_den = constants[2]; d_num = constants[3]; d_den = constants[4]; // enable pid pid_enabled = 1;
// Turns off PID void stop_pid() { set_motors(0,0); pid_enabled = 0; } ///////////////////////////////////////////////////////////////////// int main() { pololu_3pi_init(2000); play_mode(PLAY_CHECK); clear(); print("Slave"); // start receiving data at 115.2 kbaud serial_set_baud_rate(115200); serial_set_mode(SERIAL_CHECK); serial_receive_ring(buffer, 100); while(1) {
// wait for a command char command = read_next_byte(); // The list of commands is below: add your own simply by // choosing a command byte and introducing another case // statement. switch(command) { case (char)0x00: // slient error - probable master resetting break; case (char)0x81: send_signature(); break; case (char)0x86: send_raw_sensor_values(); break; case (char)0x87: send_calibrated_sensor_values(1); break; case (char)0xB0: send_trimpot(); break;
Page 61 of 68
Page 62 of 68
Turn on both master and slave. The master will display a Connect message followed by the signature of the slave source code (e.g. 3pi1.0). The master will then instruct the slave to display Connect and play a short tune. Pressing the B botton on the master causes the slave to go through an auto-calibration routine, after which you can drive the slave around using the A and C buttons on the master, while viewing sensor data on the masters LCD. Holding down the B button causes the slave to do PID line following.
Source code
#include <pololu/orangutan.h> #include <string.h> /* * 3pi-serial-master - An example serial master program for the Pololu * 3pi Robot. This can run on any board supported by the library; * it is intended as an example of how to use the master/slave * routines. * * http://www.pololu.com/docs/0J21 * http://www.pololu.com/docs/0J20 * http://www.poolu.com/ */ // Data for generating the characters used in load_custom_characters // and display_readings. By reading levels[] starting at various // offsets, we can generate all of the 7 extra characters needed for a // bargraph. This is also stored in program space. const char levels[] PROGMEM = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111 }; // This function loads custom characters into the LCD. Up to 8 // characters can be loaded; we use them for 6 levels of a bar graph // plus a back arrow and a musical note character. void load_custom_characters() { lcd_load_custom_character(levels+0,0); // no offset, e.g. one bar lcd_load_custom_character(levels+1,1); // two bars lcd_load_custom_character(levels+2,2); // etc... lcd_load_custom_character(levels+4,3); // skip level 3 lcd_load_custom_character(levels+5,4); lcd_load_custom_character(levels+6,5); clear(); // the LCD must be cleared for the characters to take effect } // 10 levels of bar graph characters const char bar_graph_characters[10] = {' ',0,0,1,2,3,3,4,5,255}; void display_levels(unsigned int *sensors) { clear(); int i; for(i=0;i<5;i++) { // Initialize the array of characters that we will use for the // graph. Using the space, an extra copy of the one-bar // character, and character 255 (a full black box), we get 10 // characters in the array. // The variable c will have values from 0 to 9, since // values are in the range of 0 to 1000, and 1000/101 is 9 // with integer math. char c = bar_graph_characters[sensors[i]/101];
Page 63 of 68
// set the motor speeds void slave_set_motors(int speed1, int speed2) { char message[4] = {0xC1, speed1, 0xC5, speed2}; if(speed1 < 0) { message[0] = 0xC2; // m1 backward message[1] = -speed1; } if(speed2 < 0) { message[2] = 0xC6; // m2 backward message[3] = -speed2; } serial_send_blocking(message,4); } // do calibration void slave_calibrate() { serial_send("\xB4",1); int tmp_buffer[5]; // read 10 characters (but we won't use them) serial_receive_blocking((char *)tmp_buffer, 10, 100);
// reset calibration void slave_reset_calibration() { serial_send_blocking("\xB5",1); } // calibrate (waits for a 1-byte response to indicate completion) void slave_auto_calibrate() { int tmp_buffer[1]; serial_send_blocking("\xBA",1); serial_receive_blocking((char *)tmp_buffer, 1, 10000); } // sets up the pid constants on the 3pi for line following void slave_set_pid(char max_speed, char p_num, char p_den, char d_num, char d_den) { char string[6] = "\xBB"; string[1] = max_speed; string[2] = p_num; string[3] = p_den; string[4] = d_num; string[5] = d_den; serial_send_blocking(string,6); } // stops the pid line following void slave_stop_pid() { serial_send_blocking("\xBC", 1); } // clear the slave LCD void slave_clear() { serial_send_blocking("\xB7",1); } // print to the slave LCD void slave_print(char *string) { serial_send_blocking("\xB8", 1); char length = strlen(string); serial_send_blocking(&length, 1); // send the string length
Page 64 of 68
// go to coordinates x,y on the slave LCD void slave_lcd_goto_xy(char x, char y) { serial_send_blocking("\xB9",1); serial_send_blocking(&x,1); serial_send_blocking(&y,1); } int main() { char buffer[20]; // load the bar graph load_custom_characters(); // configure serial clock for 115.2 kbaud serial_set_baud_rate(115200); // wait for the device to show up while(1) { clear(); print("Master"); delay_ms(100); serial_send("\x81",1); if(serial_receive_blocking(buffer, 6, 50)) continue; clear(); print("Connect"); lcd_goto_xy(0,1); buffer[6] = 0; print(buffer); // clear the slave's LCD and display "Connect" and "OK" on two lines // Put OK in the center to test x-y positioning slave_clear(); slave_print("Connect"); slave_lcd_goto_xy(3,1); slave_print("OK"); // play a tune char tune[] = "\xB3 l16o6gab>c"; tune[1] = sizeof(tune)-3; serial_send_blocking(tune,sizeof(tune)-1); // wait wait_for_button(ALL_BUTTONS); // reset calibration slave_reset_calibration(); time_reset(); slave_auto_calibrate(); unsigned char speed1 = 0, speed2 = 0; // read sensors in a loop while(1) { serial_send("\x87",1); // returns calibrated sensor values // read 10 characters if(serial_receive_blocking(buffer, 10, 100)) break; // get the line position serial_send("\xB6", 1); int line_position[1]; if(serial_receive_blocking((char *)line_position, 2, 100)) break;
Page 65 of 68
// get the battery voltage serial_send("\xB1",1); // read 2 bytes int battery_millivolts[1]; if(serial_receive_blocking((char *)battery_millivolts, 2, 100)) break; // display readings display_levels((unsigned int*)buffer); lcd_goto_xy(5,0); line_position[0] /= 4; // to get it into the range of 0-1000 if(line_position[0] == 1000) line_position[0] = 999; // to keep it to a maximum of 3 characters print_long(line_position[0]); print(" "); lcd_goto_xy(0,1); print_long(battery_millivolts[0]); print(" mV "); delay_ms(10); // if button A is pressed, increase motor1 speed if(button_is_pressed(BUTTON_A) && speed1 < 127) speed1 ++; else if(speed1 > 1) speed1 -= 2; else if(speed1 > 0) speed1 = 0; // if button C is pressed, control motor2 if(button_is_pressed(BUTTON_C) && speed2 < 127) speed2 ++; else if(speed2 > 1) speed2 -= 2; else if(speed2 > 0) speed2 = 0; // if button B is pressed, do PID control if(button_is_pressed(BUTTON_B)) slave_set_pid(40, 1, 20, 3, 2); else { slave_stop_pid(); slave_set_motors(speed1, speed2); }
} }
while(1);
Page 66 of 68
Pins PC5, ADC6, and ADC7 are all connected to 3pi hardware via removable shorting blocks. By removing the shorting block, you can use these pins for your own electronics. Pin PC5 can be used as either a digital I/O or an analog input. When its shorting block is in place, it controls the emitters for the IR sensors; when its shorting block is removed, the emitters are always on. Pin ADC6 is a dedicated analog input that connects to a voltage divider circuit that monitors the battery voltage when its shorting block is in place, and pin ADC7 is a dedicated analog input that connects to the user trimmer potentiometer when its shorting block is in place. Note: If you call the Pololu AVR librarys sensor reading functions, the 3pi will drive pin PC5 high for the duration of the sensor read, and it will then drive pin PC5 low. It does this even if the PC5 shorting block is removed. If this behavior will interfere with what you want to connect to PC5, you can modify the library code to initialize the sensors with a bogus emitter pin (e.g. 20 instead of 19). If you are willing to give up the LCD, as is required when you use the expansion kit without cutouts [http://www.pololu.com/catalog/product/978], you gain access to several more I/O lines. Removing the LCD completely frees the three LCD control pins (PB0, PD2, and PD4), and it makes the four LCD data pins (PB1, PB4, PB5, and PD7) available for limited use. If you do use the LCD data pins, you must make sure that their alternate functions do not conflict with whatever you connect to them. Pins PB1, PB4, and PB5 connect to the user pushbuttons, and PD7 connects to the green user LED. It is important to note that PB4 and PB5 are also programming lines, so you must not connect anything here that would interfere with programming. So in summary, pins PD0 and PD1 are completely free digital I/O lines that can be used for general-purpose I/O or for TTL serial communciation. Pins PC5, ADC6, and ADC7 can be freed from 3pi hardware by removing their respective shorting blocks. PC5 can be used as an analog input or a digital I/O, and ADC6 and ADC7 are dedicated analog inputs. Pins PB0, PD2, and PD4 become completely free digital I/O lines once you remove the LCD, and pins PB1, PB4, PB5, and PD7 are digital I/O lines that you can use for certain applications if you are careful not to cause conflicts between them and their alternate functionality. For more information, please see Section 9 for the 3pi pin assignment tables and Section 5.e for the 3pi schematic diagram.
Page 67 of 68
Programming the 3pi Robot from the Arduino Environment programming the 3pi using the Arduino IDE in place of AVR Studio. AVR Libc Home Page [http://www.nongnu.org/avr-libc/]
[http://www.pololu.com/docs/0J17]:
ATmega328P documentation [http://www.atmel.com/dyn/products/product_card.asp?PN=ATmega328P] ATmega168 documentation [http://www.atmel.com/dyn/products/product_card.asp?PN=ATmega168] Tutorial: AVR Programming on the Mac [http://bot-thoughts.blogspot.com/2008/02/avr-programming-on-mac.html] Finally, we would like to hear your comments and questions over at the 3pi Robot Group viewforum.php?f=29] on the Pololu Robotics Forum [http://forum.pololu.com/]!
[http://forum.pololu.com/
Page 68 of 68