Hi,
I am getting back into playing with the 3PI’s, and I have made some modifications to get familiar with how everything works as a second excercize.
I’ve made my robot stop when not on the line so that i don’t have to chase it if it loses the line.
I also added a thing to detect if the battery voltage is too low so that i will know not to program it.
and I added a thing to let me change the speed using the buttons on the 3PI so i wouldn’t have to keep going back to program it.
i was also playing with the PID values so they are different that the default.
en.wikipedia.org/wiki/PID_contr … ual_tuning
I tested it and it works for me so I figured I would post it. It is for Arduino.
I am trying to get it to be as fast as the default program from the m3PI but haven’t got it to work yet. (need more PID Tuning)
/*
*
* PID3piLineFollower - demo code for the Pololu 3pi Robot
*
* ATMega328P
*
* This code will follow a black line on a white background, using a
* PID-based algorithm.
*
* https://www.pololu.com/docs/0J21
* https://www.pololu.com
* https://forum.pololu.com
*
*/
#define PROGNUMBER "#4" // I use this so I know from the screen which thing is running
#define minAcceptableBattery 4800
// The following libraries will be needed by this demo
#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
unsigned int last_proportional = 0;
unsigned int position;
int proportional;
int derivative;
long integral;
double k_P = 0.1; // PID Proportional value. original = 0.05
double k_I = 0.0001; // PID integral value. original = 0.0001
double k_D = 3.0; // PID derivative value. original = 1.5
int n;
int lostLine = 0;
int maximumSpeed = 180;
// 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 = "PID LF";
const char demo_name_line2[] PROGMEM = PROGNUMBER;
// A couple of simple tunes, stored in program space.
const char welcome[] PROGMEM = ">g32>>c32";
const char go[] PROGMEM = "L16 cdegreg4";
const char lowBat[] PROGMEM = "L16 gegegegc4";
const char singleBeep[] PROGMEM = "L16 c";
const char robotStopped[] PROGMEM = "L16 cgefrrrcgef"; // no idea what this sound will be
// 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);
stopOnLowBattery(); // Check For Low Battery
allowUserSelectSpeed(); // allow the user to select the speed
// Always wait for the button to be released so that 3pi doesn't
// start moving until your hand is away from it.
// 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.
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);
n=1;
while(OrangutanBuzzer::isPlaying());
}
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.
position = robot.readLine(sensors, IR_EMITTERS_ON);
// The "proportional" term should be 0 when we are on the line.
proportional = (int)position - 2000;
stopIfNotOnLine();
// Compute the derivative (change) and integral (sum) of the
// position.
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 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. You can adjust the constants by which
// the proportional, integral, and derivative terms are multiplied to
// improve performance.
int power_difference = proportional*k_P + integral*k_I+ derivative*k_D;
// Compute the actual motor settings. We never set either motor
// to a negative value.
if (power_difference > maximumSpeed)
power_difference = maximumSpeed;
if (power_difference < -maximumSpeed)
power_difference = -maximumSpeed;
if (power_difference < 0)
OrangutanMotors::setSpeeds(maximumSpeed + power_difference, maximumSpeed);
else
OrangutanMotors::setSpeeds(maximumSpeed, maximumSpeed - power_difference);
}
void allowUserSelectSpeed()
{
int SpeedChange = 1;
while (!OrangutanPushbuttons::isPressed(BUTTON_B))
{
if(OrangutanPushbuttons::isPressed(BUTTON_A))
{
maximumSpeed -= 5;
SpeedChange=1;
if (maximumSpeed < 40) maximumSpeed = 40;
}
if(OrangutanPushbuttons::isPressed(BUTTON_C))
{
maximumSpeed += 5;
SpeedChange = 1;
if (maximumSpeed > 255) maximumSpeed = 255;
}
if(SpeedChange==1){
// add delay when there is a change
OrangutanLCD::clear();
OrangutanLCD::print("Max:");
OrangutanLCD::print(maximumSpeed);
OrangutanLCD::gotoXY(0, 1);
OrangutanLCD::print("Press B");
delay(150);
}
SpeedChange = 0;
delay(100);
}
// stop it running out of hand
OrangutanPushbuttons::waitForRelease(BUTTON_B);
delay(1000);
}
void stopIfNotOnLine()
{
n++;
if (n==100)
{
if((proportional==2000) || (proportional==-2000))
{
n=0;
lostLine++;
if (lostLine==5)
{
OrangutanMotors::setSpeeds(0, 0);
while(1)
{
OrangutanBuzzer::playFromProgramSpace(robotStopped);
delay(4000);
OrangutanLCD::clear();
OrangutanLCD::print("LostLine");
OrangutanLCD::gotoXY(0, 1);
OrangutanLCD::print(maximumSpeed);
}
} // (lostLine==5)
}
else
{
if (lostLine > 0) lostLine--;
} // close if((proportional==2000) || (proportional==-2000))
n=0;
} // close if (n==100)
}
void stopOnLowBattery()
{
int batV = OrangutanAnalog::readBatteryMillivolts();
if (batV < minAcceptableBattery)
{
OrangutanLCD::clear();
OrangutanLCD::print("Low Batt");
OrangutanLCD::gotoXY(0, 1);
OrangutanLCD::print(batV);
OrangutanLCD::print("Mv");
while(1)
{
OrangutanBuzzer::playFromProgramSpace(lowBat);
delay(2000);
}
}
}