-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSimpleLineFollower.ino
240 lines (208 loc) · 7.85 KB
/
SimpleLineFollower.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/*
* Simple3piLineFollower - 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. Pololu3pi.h includes all of the
// other Orangutan Arduino libraries that can be used to control the
// on-board hardware such as LCD, buzzer, and motor drivers.
#include <Pololu3pi.h>
#include <PololuQTRSensors.h>
#include <OrangutanMotors.h>
#include <OrangutanAnalog.h>
#include <OrangutanLEDs.h>
#include <OrangutanLCD.h>
#include <OrangutanPushbuttons.h>
#include <OrangutanBuzzer.h>
Pololu3pi robot;
unsigned int sensors[5]; // an array to hold sensor values
// This include file allows data to be stored in program space. The
// ATmega168 has 16k of program space compared to 1k of RAM, so large
// pieces of static data should be stored in program space.
#include <avr/pgmspace.h>
// 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()
{
OrangutanLCD::loadCustomCharacter(levels + 0, 0); // no offset, e.g. one bar
OrangutanLCD::loadCustomCharacter(levels + 1, 1); // two bars
OrangutanLCD::loadCustomCharacter(levels + 2, 2); // etc...
OrangutanLCD::loadCustomCharacter(levels + 3, 3);
OrangutanLCD::loadCustomCharacter(levels + 4, 4);
OrangutanLCD::loadCustomCharacter(levels + 5, 5);
OrangutanLCD::loadCustomCharacter(levels + 6, 6);
OrangutanLCD::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.
OrangutanLCD::print(c);
}
}
// Initializes the 3pi, displays a welcome message, calibrates, and
// plays the initial music. This function is automatically called
// by the Arduino framework at the start of program execution.
void setup()
{
unsigned int counter; // used as a simple timer
// 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.
robot.init(2000);
load_custom_characters(); // load the custom characters
// Play welcome music and display a message
OrangutanLCD::printFromProgramSpace(welcome_line1);
OrangutanLCD::gotoXY(0, 1);
OrangutanLCD::printFromProgramSpace(welcome_line2);
OrangutanBuzzer::playFromProgramSpace(welcome);
delay(1000);
OrangutanLCD::clear();
OrangutanLCD::printFromProgramSpace(demo_name_line1);
OrangutanLCD::gotoXY(0, 1);
OrangutanLCD::printFromProgramSpace(demo_name_line2);
delay(1000);
// Display battery voltage and wait for button press
while (!OrangutanPushbuttons::isPressed(BUTTON_B))
{
int bat = OrangutanAnalog::readBatteryMillivolts();
OrangutanLCD::clear();
OrangutanLCD::print(bat);
OrangutanLCD::print("mV");
OrangutanLCD::gotoXY(0, 1);
OrangutanLCD::print("Press B");
delay(100);
}
// Always wait for the button to be released so that 3pi doesn't
// start moving until your hand is away from it.
OrangutanPushbuttons::waitForRelease(BUTTON_B);
delay(1000);
// Auto-calibration: turn right and left while calibrating the
// sensors.
for (counter=0; counter<80; counter++)
{
if (counter < 20 || counter >= 60)
OrangutanMotors::setSpeeds(40, -40);
else
OrangutanMotors::setSpeeds(-40, 40);
// 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.
robot.calibrateLineSensors(IR_EMITTERS_ON);
// Since our counter runs to 80, the total delay will be
// 80*20 = 1600 ms.
delay(20);
}
OrangutanMotors::setSpeeds(0, 0);
// Display calibrated values as a bar graph.
while (!OrangutanPushbuttons::isPressed(BUTTON_B))
{
// Read the sensor values and get the position measurement.
unsigned int position = robot.readLine(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.
OrangutanLCD::clear();
OrangutanLCD::print(position);
OrangutanLCD::gotoXY(0, 1);
display_readings(sensors);
delay(100);
}
OrangutanPushbuttons::waitForRelease(BUTTON_B);
OrangutanLCD::clear();
OrangutanLCD::print("Go!");
// Play music and wait for it to finish before we start driving.
OrangutanBuzzer::playFromProgramSpace(go);
while(OrangutanBuzzer::isPlaying());
}
// The main function. This function is repeatedly called by
// the Arduino framework.
void loop()
{
// 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 = robot.readLine(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.
OrangutanMotors::setSpeeds(0, 100);
// Just for fun, indicate the direction we are turning on
// the LEDs.
OrangutanLEDs::left(HIGH);
OrangutanLEDs::right(LOW);
}
else if (position < 3000)
{
// We are somewhat close to being centered on the line:
// drive straight.
OrangutanMotors::setSpeeds(100, 100);
OrangutanLEDs::left(HIGH);
OrangutanLEDs::right(HIGH);
}
else
{
// We are far to the left of the line: turn right.
OrangutanMotors::setSpeeds(100, 0);
OrangutanLEDs::left(LOW);
OrangutanLEDs::right(HIGH);
}
}