/
LineFollower.ino
165 lines (136 loc) · 4.74 KB
/
LineFollower.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
/* This example uses the line sensors on the Zumo 32U4 to follow
a black line on a white background, using a PID-based algorithm.
It works decently on courses with smooth, 6" radius curves and
has been tested with Zumos using 75:1 HP motors. Modifications
might be required for it to work well on different courses or
with different motors.
This demo requires a Zumo 32U4 Front Sensor Array to be
connected, and jumpers on the front sensor array must be
installed in order to connect pin 4 to DN4 and pin 20 to DN2. */
#include <Wire.h>
#include <Zumo32U4.h>
// This is the maximum speed the motors will be allowed to turn.
// A maxSpeed of 400 lets the motors go at top speed. Decrease
// this value to impose a speed limit.
const uint16_t maxSpeed = 400;
Zumo32U4Buzzer buzzer;
Zumo32U4LineSensors lineSensors;
Zumo32U4Motors motors;
Zumo32U4ButtonA buttonA;
Zumo32U4LCD lcd;
int16_t lastError = 0;
#define NUM_SENSORS 5
unsigned int lineSensorValues[NUM_SENSORS];
// Sets up special characters in the LCD so that we can display
// bar graphs.
void loadCustomCharacters()
{
static const char levels[] PROGMEM = {
0, 0, 0, 0, 0, 0, 0, 63, 63, 63, 63, 63, 63, 63
};
lcd.loadCustomCharacter(levels + 0, 0); // 1 bar
lcd.loadCustomCharacter(levels + 1, 1); // 2 bars
lcd.loadCustomCharacter(levels + 2, 2); // 3 bars
lcd.loadCustomCharacter(levels + 3, 3); // 4 bars
lcd.loadCustomCharacter(levels + 4, 4); // 5 bars
lcd.loadCustomCharacter(levels + 5, 5); // 6 bars
lcd.loadCustomCharacter(levels + 6, 6); // 7 bars
}
void printBar(uint8_t height)
{
if (height > 8) { height = 8; }
const char barChars[] = {' ', 0, 1, 2, 3, 4, 5, 6, 255};
lcd.print(barChars[height]);
}
void calibrateSensors()
{
lcd.clear();
// Wait 1 second and then begin automatic sensor calibration
// by rotating in place to sweep the sensors over the line
delay(1000);
for(uint16_t i = 0; i < 120; i++)
{
if (i > 30 && i <= 90)
{
motors.setSpeeds(-200, 200);
}
else
{
motors.setSpeeds(200, -200);
}
lineSensors.calibrate();
}
motors.setSpeeds(0, 0);
}
// Displays a bar graph of sensor readings on the LCD.
// Returns after the user presses A.
void showReadings()
{
lcd.clear();
while(!buttonA.getSingleDebouncedPress())
{
lineSensors.readCalibrated(lineSensorValues);
lcd.gotoXY(0, 0);
for (uint8_t i = 0; i < NUM_SENSORS; i++)
{
uint8_t barHeight = map(lineSensorValues[i], 0, 1000, 0, 8);
printBar(barHeight);
}
}
}
void setup()
{
// Uncomment if necessary to correct motor directions:
//motors.flipLeftMotor(true);
//motors.flipRightMotor(true);
lineSensors.initFiveSensors();
loadCustomCharacters();
// Play a little welcome song
buzzer.play(">g32>>c32");
// Wait for button A to be pressed and released.
lcd.clear();
lcd.print(F("Press A"));
lcd.gotoXY(0, 1);
lcd.print(F("to calib"));
buttonA.waitForButton();
calibrateSensors();
showReadings();
// Play music and wait for it to finish before we start driving.
lcd.clear();
lcd.print(F("Go!"));
buzzer.play("L16 cdegreg4");
while(buzzer.isPlaying());
}
void loop()
{
// Get the position of the line. Note that we *must* provide
// the "lineSensorValues" argument to readLine() here, even
// though we are not interested in the individual sensor
// readings.
int16_t position = lineSensors.readLine(lineSensorValues);
// Our "error" is how far we are away from the center of the
// line, which corresponds to position 2000.
int16_t error = position - 2000;
// Get motor speed difference using proportional and derivative
// PID terms (the integral term is generally not very useful
// for line following). Here we are using a proportional
// constant of 1/4 and a derivative constant of 6, which should
// work decently for many Zumo motor choices. You probably
// want to use trial and error to tune these constants for your
// particular Zumo and line course.
int16_t speedDifference = error / 4 + 6 * (error - lastError);
lastError = error;
// Get individual motor speeds. The sign of speedDifference
// determines if the robot turns left or right.
int16_t leftSpeed = (int16_t)maxSpeed + speedDifference;
int16_t rightSpeed = (int16_t)maxSpeed - speedDifference;
// Constrain our motor speeds to be between 0 and maxSpeed.
// One motor will always be turning at maxSpeed, and the other
// will be at maxSpeed-|speedDifference| if that is positive,
// else it will be stationary. For some applications, you
// might want to allow the motor speed to go negative so that
// it can spin in reverse.
leftSpeed = constrain(leftSpeed, 0, (int16_t)maxSpeed);
rightSpeed = constrain(rightSpeed, 0, (int16_t)maxSpeed);
motors.setSpeeds(leftSpeed, rightSpeed);
}