Robot
JavaScript
Compiler
Language
Videos
Simulator
Games
Examples
Download
Cheat sheet

Line Following Examples


Overview

Line following works when the values used in setting the motor's power or robot's steering are set correctly. This overview will contain instructions you can use to easily configure the line following program for your robot. Line following programs use reflected light. Light emitted from the sensor is reflected off of the surface and returned back to the sensor. The amount of light reflected is measured by the sensor. Areas of the surface which are lighter reflect more light. Darker areas of the surface absorb light. Therefore, the amount of reflected light can be used as a measure indicating to the robot if the sensor is over a light or dark area.

To follow a line, you want to try to get the robot to find the midpoint between the light and dark. In other words, the robot should follow the edge of the line. If the robot sees too much light, then it should turn toward the line. If the robot sees too much dark, then it should turn away from the line.

A variable will be used to indicate to the robot, what the midpoint should be between light and dark. This variable
will be called the 'target'. The difference between the lightSensorPct() and the target will be used for steering. If the robot does not turn enough to follow curves in the line, then you may need to multiply the difference between the lightSensorPct() and the target by a value called the 'gain'.

Tuning your Line Following Function

For optimal performance, the values for 'target' and 'gain' should be tuned for the particular line you are following
and the environment the robot is operating in. The ambient light in a room can have an effect on the performance.
A darker room will require a lower target value than a lighter room. The best way to set these values is to initially
set the 'target' and then set the 'gain'.

The best way to set the target is to start with a gain set to 1. Then set the power in the syncMotors function to zero
as in:
gain=1
syncMotors(B, C, 0, gain*(lightSensorPct()-target))
Now look at the display as you push the robot across the line. You should see positive and negative numbers.
The target will need adjusting if you see more positive than negative numbers (or see more negative numbers than positive).
Decrease the target if you see more positive than negative numbers.
Increase the target if you see more negative than positive numbers.
For example, if you see +45 and -10, you will need to decrease the target by about 20.
If you see +20 and -30, you will need to increase the target by about 5.
If you see +80 and -20, you will need to decrease the target by 30.
If you see +50 and -30, you will need to decrease the target by 10.

The target is set correctly once you have about an equal value between positive and negative numbers (e.g., +30 and -30)

To set the gain, restore power to the syncMotors function and let the robot follow the line.
If there are curves in the line, does the robot follow them?
Increase the gain if the robot is not turning enough to follow the curves in the line.

If the robot wiggles back and forth as it follows the line, try decreasing the amount of sleep. Instead
of sleep(100), try sleep(20).

Line Following Example 1: One Sensor Steering with Constant Speed

Example:
target = 40
gain = 1.5
// The values for these variables may need to be adjusted higher or lower
// Watch the numbers shown on the display.
// Before adjusting the target, first set the gain to 1.
// Adjust the value of the target higher or lower so that you will see positive and negative numbers that are about equally distanced from zero (e.g., +45 and -45).
// If the robot is moving too much for you to see the numbers, lower the power level parameter in the syncMotors() function below.
// The target value should represent the midpoint between light and dark areas.
// When you have finished setting the target, you should see values shown on the display that range from +target to -target.
// The gain value is an adjustment to control how much the robot steers in relation to the target value.
// Typical values for the gain will range from 0.5 to 2.0
// If the robot turns too much, lower the gain.  If the robot is not turning enough, increase the gain.
// To get the robot to follow the other side of the line, you can multiply the gain by -1 as in:  gain = -1.5
constantPower = 20
while true {
  drawText(10,10, gain*(lightSensorPct()-target)+'  ', 2)
  syncMotors(2, 3, constantPower, gain*(lightSensorPct()-target))
  clearRect(0,90, 178, 38)  // clears the bottom of the LCD screen
// Locate a filled rectangle at the midpoint of the screen (90) minus 1/2 of the
//   width of the box minus the difference between the light sensor and the target
  fillRect((90-30*0.5)-(lightSensorPct()-target),90, 30, 38)
  sleep(100)
}

// Please note, if the robot is turning away from the line, multiply the gain by -1 in the formulas above as in: -1*gain*(lightSensorPct()-target)
// If the robot does not turn fast enough, try increasing the gain by 0.3
// If the gain is too low, the robot may be unable to turn enough to follow curves in the line.
// If the gain is too high, the robot may be unable to follow straight parts of the line.
// If the robot wiggles back and forth as it follows the line, try lowering the value of sleep from sleep(100) to sleep(20)

Line Following Example 2: If-Block Based Steering

Example:
target = 20
defaultPower = 50
forever {
  if lightSensorPercent() < target {
    setMotor(B, 0)
    setMotor(C, defaultPower)
  } else {
    setMotor(B, defaultPower)
    setMotor(C, 0)
  }
}

Line Following Example 3: Stepped Switching

The basic idea in this line following program is to increase the number of steps of steering. The typical implementation will have steps such as:
  1. Turn Sharply to the Left (Motor B 60% power; Motor C Stopped)
  2. Turn Gradually to the Left (Motor B 60% power; Motor 30% power)
  3. Go Straight (Motors B and C 60% power)
  4. Turn Gradually to the Right (Motor B 30% power; Motor 60% power)
  5. Turn Sharply to the Right (Motor B Stopped; Motor C 60% power)
The range of values returned by a calibrated light sensor is 100. Divide this by 20 to get the five steps needed for the switch.

Example:
defaultPower = 30
while true {
  var currLight = lightSensorPct()
  drawText(10,10, currLight+'  ', 2)
  clearRect(0,90, 178,128)
  if currLight <= 20 {
    setMotor(B, 0)
    setMotor(C, defaultPower / 2)
    fillRect(30,90, 30, 38)
  } else if currLight <= 40 {
    setMotor(B, defaultPower / 2)
    setMotor(C, defaultPower)
    fillRect(50,90, 30, 38)
  } else if currLight <= 60 {
    setMotor(B, defaultPower)
    setMotor(C, defaultPower)
    fillRect(70,90, 30, 38)
  } else if currLight <= 80 {
    setMotor(B, defaultPower)
    setMotor(C, defaultPower / 2)
    fillRect(110,90, 30, 38)
  } else {
    setMotor(B, defaultPower / 2)
    setMotor(C, 0)
    fillRect(130,90, 30, 38)
  }
  sleep(100)  // This number controls how frequently the robot will check the sensor
}

Line Following Example 4: One Sensor Proportional Power Level

The basic idea in this line following program is to increase the power proportionally based on the values from the Light Sensor.

Since the light range for a Light Sensor is 100, we can divide the current light sensor percent by 100 to convert it to a portion to be multiplied by the Default Power level. This becomes the power level for Motor B. The difference between the Default Power Level and the power for Motor B becomes the power for Motor C.

You can change which side of the line the robot follows by changing which motor gets the initial power.

Example:
defaultPower = 40
currPower = 0
while true {
  currPower = lightSensorPct()/100 * defaultPower
  setMotor(B, currPower)
  setMotor(C, defaultPower - currPower)
  drawText(10,10, currPower+'  ', 2)
  drawText(110,10, (defaultPower - currPower) + '  ', 2)
  clearRect(0,90, 178, 38)  // clears the bottom of the LCD screen
  fillRect((90-30*0.5)-(lightSensorPct()-50),90, 30, 38)
  sleep(100)
}

Line Following Example 5: Proportional Power Level With Constant

The basic idea in this line following program is to increase the speed of the robot by adding a constant level of power to each motor in addition to the proportionally adjusted power from the Light Sensor.

Tip: Try to keep the robot moving forward by adding a constant power to both motors.
Since the light range for a Light Sensor is 100, we can divide the current light sensor percent by 100 to convert it to a portion to be multiplied by the Default Power level. Then add the Constant power. This becomes the power level for Motor B. The difference between the Default Power Level and the product of the Default power and the light sensor becomes the power for Motor C. Don't forget to add the Constant power to Motor C as well.

In summary, both motors get the Constant power. But one motor gets more, or less, of the variable power depending on the value obtained from the light sensor.

Tuning

Troubleshooting
Example:
defaultPower = 55
constantPower = 10
currPower = 0
forever {
  currPower = lightSensorPct()/100 * defaultPower
  setMotor(B, currPower + constantPower)
  setMotor(C, (defaultPower - currPower) + constantPower)
}
Example: (if the robot is going backwards instead of forwards)
defaultPower = 55
constantPower = 10
currPower = 0
direction = -1
while(true) {
  currPower = lightSensorPct()/100 * defaultPower
  setMotor(B, direction * (currPower + constantPower))
  setMotor(C, direction * ((defaultPower - currPower) + constantPower))
}

Line Following Example 6: Proportional Power (PID Minus the ID)

The basic idea in this line following program is to adjust the speed of each motor of the robot by adding (or subtracting) a variable level of power from a constant level of power.

Background: This method is based on a part of the classic PID controller. PID is short for Proportional, Integral, Derivative. A PID controller calculates an "error" value as the difference between a measured light value and the desired value (usually 50% if following a line). The controller attempts to minimize the error through the use of a formula. The PID controller formula involves three separate parameters: the proportional, the integral and derivative. Using a NXT robot, we can obtain excellent line following using just the "P" part of the formula. The Integral and Derivative are not needed at this level.

Since the light range for a Light Sensor is 100, we can divide the current light sensor percent by 100 to convert it to a portion to be multiplied by the Default Power level. This becomes the power level for Motor B. The difference between the Default Power Level and the power for Motor B becomes the power for Motor C.

Tuning

Troubleshooting
Example:
constantPower = 45
variablePower = 20
Kp = 0
while true {
  Kp = ((lightSensorPct() - 50) / 50) * variablePower;
  setMotor(B, constantPower + Kp)
  setMotor(C, constantPower - Kp)
}
Example: (if the robot is going backwards instead of forwards)
constantPower = 45
variablePower = 20
direction = -1
Kp = 0
while true {
  Kp = ((lightSensorPct() - 50) / 50) * variablePower;
  setMotor(B, direction * (constantPower + Kp))
  setMotor(C, direction * (constantPower - Kp))
}

Line Following Example 7: Proportional Power with Two Sensors

The basic idea in this line following program is to get the robot to follow the line using a light sensor on each side of the line. Optimally, the two light sensors should each see gray (half over the line and half over the white background). This program will subtract the value from one sensor from the value of the other sensor (i.e., it will calculate the difference between the two sensors). If the difference between the sensors is zero (i.e., each sensor is receiving the same amount of light), then the robot will apply 50% power to each motor (driving the robot straight). If one sensor sees more light than the other, then the difference between them will not be zero. The robot will then apply a greater percentage of power to one motor than to the other.

The function getLightDiffPortion() is created here to calculate a portion of the default power that should be applied to motor B. The function will return the ratio of the numerator divided by 200. This will result in a real number from 0.00 to 1.00. This portion will be multiplied by the default power to determine the power for Motor B. Motor C will then receive the difference between the default power and the power given to Motor B.

Example:
function getLightDiffPortion() {
  // This function reads two light sensors.  The port number is passed as a parameter to the lightSensorPct() function.
  // In the example below, lightSensors are on ports 1 and 4.
  // Add 100 to the difference between the two numerators
  numerator = lightSensorPct(1) - lightSensorPct(4) + 100
  // At this point, the variable numerator will be a number from 0 to 200
  // For example: if LS(1) is 100% and LS(4) is 0%, the numerator will be 200
  //    if LS(1) is 0% and LS(4) is 100%, the numerator will be 0
  //    if LS(1) is 30% and LS(4) is 30%, then the numerator will be 100
  // Dividing the numerator by 200 will result in a real number from 0.00 to 1.00.
  // Subtracting with 0.50 gives a result from -0.50 to +0.50
  return (numerator / 200) - 0.50
}
defaultPower = 45
while true {
  currPower = getLightDiffPortion() * defaultPower
  setMotor(B, defaultPower + currPower)
  setMotor(C, defaultPower - currPower)
}
Example: (with a constantPower)
function getLightDiffPortion() {
  // This function reads two light sensors.  
  // The port number is passed as a parameter to the lightSensorPct() function.
  // In the example below, lightSensors are on ports 1 and 4.
  // Add 100 to the difference between the two numerators
  numerator = lightSensorPct(1) - lightSensorPct(4) + 100
  // At this point, the numerator will be a number from 0 to 200
  // For example: if LS(1) is 100% and LS(4) is 0%, the numerator will be 200
  //    if LS(1) is 0% and LS(4) is 100%, the numerator will be 0
  //    if LS(1) is 30% and LS(4) is 30%, then the numerator will be 100
  // Dividing the numerator by 200 will result in a real number from 0.00 to 1.00.
  // Subtracting with 0.50 gives a result from -0.50 to +0.50
  return (numerator / 200) - 0.50
}
variablePower = 30
constantPower = 15
forever {
  currPower = getLightDiffPortion() * variablePower
  setMotor(B, constantPower + currPower)
  setMotor(C, constantPower - currPower)
}

Line Following Example 8: Steering Controlled By Two Sensors

Steering is a numeric value that ranges from -200 to +200. To drive straight, the steering value is zero. In between -200 and 0 and +200 and 0 are different combinations of power applied to the two motors. Values of steering that are between -99 and +99 result in the motors turning in the same direction. Values between -200 to -101 and +200 to +101 result in the motors turning in opposite directions. Steering that has a value of either +100 or -100 will result in a swing turn in which only one motor is turning.

To control steering using two light sensors, just multiply the difference between the light sensors by a constant called gain (0.5 to 2). If both light sensors are the same, then the difference between them would be zero and the product of this difference times the constant would also be zero resulting in the robot diving straight.

If one sensor has a value higher than the other sensor, then the difference between them times the constant would enable the robot to turn toward the center of the line.

Adjusting the Gain: If the line you are following is relatively straight, you can reduce the gain to reduce excess turning by the robot. If the line you are following has turns, you can increase the gain to help the robot turn more as needed.
Example:
// This program reads two light sensors.  
// The port number is passed as a parameter to the lightSensorPct() function.
// In the example below, lightSensors are on ports 1 and 4.
gain = 1.5
constantPower = 20
while true {
  syncMotors(B, C, constantPower, (lightSensorPct(1) - lightSensorPct(4)) * gain)
  // Graph the line
  clearRect(0,90, 178, 38)  // clears the bottom of the LCD screen
  // Draw a box representing the approximate position of the line
  fillRect((90-30*0.5)-((lightSensorPct(1) - lightSensorPct(4)) * gain),90, 30, 38)
  sleep(100)
}

// Please note, if the robot is turning away from the line, reverse the light sensor ports used in the formulas above.
// If the robot does not turn fast enough, try increasing the gain by 0.3
// If the gain is too low, the robot may be unable to turn enough to follow curves in the line.
// If the gain is too high, the robot may be unable to follow straight parts of the line.
// If the robot wiggles back and forth as it follows the line, try lowering the value of sleep from sleep(100) to sleep(20)

Line Following Example 9: Steering Controlled by a Light Sensor Event Listener

What are Events?: Events are triggered by changes in the sensor values. When a sensor detects a change in the environment, it will send a message indicating that a change has been detected. You can create functions to monitor (i.e., listen) for these changes.
JavaScript allows you to handle various events that the computer (or robot in this language) may encounter.

In RobotJavaScript, you can handle events that occur in the sensors, motor encoders, and buttons on the robot.

For example, a touch sensor event occurs when the touch sensor is pressed.
A touch sensor event also occurs when the depressed touch sensor is subsequently released.
A light sensor event occurs whenever there is a change in the light sensor percent.
A gyro sensor event occurs whenever the gyro sensor detects rotation.
An ultrasonic sensor event occurs whenever the ultrasonic sensor detects a change in distance.
A motor encoder event occurs whenever the motor turns.

A function will handle the changes in the light sensor. Those changes will be converted into steering that will control the robot.

Example:
target = 40
gain = 1.5
var somethingThere = false               // Create a flag variable to help monitor the state of the lineDetection.  Initialize it to false.

                                         // Create a 'handler' for the lightSensor event
function toFollowLine(event) {           // The event object is passed as a parameter into the handler function.  
                                         // This variable is often identified as 'e' or 'event'
  if !somethingThere {                   // If there is NOT (!) somethingThere in front of the robot, (i.e., nothing in front of the robot)
    drawText(10,10, gain*(event.value-target)+'  ', 2)
    syncMotors(B, C, 20, gain*(event.value-target))
    clearRect(0,90, 178, 38)             // clears the bottom of the LCD screen
                                         // Locate a filled rectangle at the midpoint of the screen (90) minus 1/2 of the width
                                         // of the box minus the difference between the light sensor and the target
    fillRect((90-30*0.5)-(event.value-target),90, 30, 38)
  }
}

                                         // The ultrasonicSensor event handler function will change this flag to true when an obstacle has been detected.
                                         // Create a 'handler' for the ultrasonicSensor event
function toDetectSomething(event) {      // The event object is passed as a parameter into the handler function.
                                         // This variable is often identified as 'e' or 'event'
  if event.value<5 {                     // If the value of the event is less than 5, then there is an obstacle in front of the robot.  Set the variable to true.
    somethingThere=true  
    stopAllMotors()                      // The line has been detected, you can stop moving now.
  }
  if event.value>9 {                     // If the value of the event is greater than 9, then the obstacle has moved away. 
    somethingThere=false                 // Set the variable to false which will allow the line following to continue.
    syncMotors(B, C, 20)                 // Obstacle has been cleared.  Start the motors again.
  }
}

                                         // Add a few 'event listeners'
addEventListener('lightSensor', toFollowLine)
addEventListener('ultrasonicSensor', toDetectSomething)

waitHereWhile true                       // Infinite loop