Line Following Robot that learns PID values

I created a robot that attempts to learn good values for a PID controller using simulated annealing. This code should be fairly simple to port to other robots with only minor changes. For instance, if your robot is particularly fast or your track is particularly short, you might want to change a threshold I have for a minimum lap time. (Else it will throw out most runs.)

I plan on making a PCB board for this and adding faster motors in the future to see how the algorithm works on a fast robot. I predict it will actually do quite well. (This will require me to put better voltage regulators and external motor controllers that can handle the extra power.)

Anyway, here’s the vid:

And here’s the code… (Needs to be cleaned up bit.)

I tore off the trimpot on the baby orangutan to get the 8th analog pin available to my IR Senor array. From what I could tell, there was no other way to make that pin available. If someone knows something I don’t about that pin, let me know.


Thanks for sharing your project with us! Are your motors at max speed right now? If not, one fun improvement would be to try gradually ramping up the motor speed once it starts performing well enough at its current speed.

One thing I noticed is that your robot seems to move in choppy steps as it goes around the turns, and I think it’s because of the way you’re computing your error. With the sensors you’re using, it should be possible to get much smoother movement from PID, but that requires more resolution of your error variable. Right now you seem to basically just be figuring out which sensor the line most under:

        //Calculate Error
        for (unsigned char i = 0; i < NUM_LINE_SENSORS; i++)
                if (lineSensor[i] > 400)
                        if (i < 4) //NUM_LINE_SENSORS/2
                                actual -= (4 - i); //actual -= 2 * (NUM_LINE_SENSORS/2 - i)
                                actual += (i - 3); //actual += 2 * (i - NUM_LINE_SENSORS/2 - 1)

There is a lot of information you’re losing with this approach, however. Have you seen the QTR line sensor code in the Pololu AVR library? If you don’t want to call the functions yourself, you could fairly easily implement the formula used by the qtr_read_line() function yourself. This formula gives you a lot more resolution for the line position.

You can access the AVR’s eighth analog input from the pin labeled AD7 on the board, but you have to remove the trimpot by desoldering it or cutting the appropriate traces in order to keep it from interfering with an external signal connected to that pin.

- Ben

I was ramping when I was using your HP motors, but since took out the ramping to remove one more variable while debugging a problem at some point. (It could probably go back in now.)

Also, yes it is set to full speed. They are your 50:1 micro metal gear motors. (Not the HP’s.) You will see here that the forward component is set to 1000, which is way higher than the robot will ever go. (It was about 100 during development) To get maximum speed out of the motors and retain the full range for steering I check if the forward component + the turn component is greater than my maximum motor speed, and I subtract out the forward component until the outside wheel is at exactly maximum speed. (Should have used the variable I created for this.) Here is that portion of code:

        static const int
                forward = 1000;
                //ramp = 2;
        //static int
        //      leftMotorSpeed = 0,
        //      rightMotorSpeed = 0;
                desiredLeftMotorSpeed = 0,
                desiredRightMotorSpeed = 0,
                forwardComponent = forward / 2,
                turnComponent = turnRate / 2;

                //Retain full ability to turn even when speed is maxxed.
                if (forwardComponent + abs(turnComponent) > 255)
                        forwardComponent = 255 - abs(turnComponent);

Yes, I know… I have analog sensors and I’m using them like digital sensors. I should probably use the extra information instead of tossing it out, but I didn’t budget the time for that. The most important thing was to get it to learn. Now that the class project is over, I will probably do many more upgrades, I should make sure what you mentioned is on that list, but I’m also thinking of trying to get it to figure out what the thresholds of the line are. This way it can hopefully work with a dark grey line on a light grey surface, or even a white line on a black surface… I’ll have some ideas, but I need some time to flesh out exactly how I should approach it.

No I haven’t, actually I’m probably going to feel pretty dumb when I do… I didn’t realize those were there.

Yea, that’s what I thought. Thank you!