I'm working on a TWDD line-following robot using qtr-8a reflectance sensor array. The sensors are connected to BeagleBone Black, and BBB is sending the speed serially to an arduino Due. My approach is using a PID controller for the sensor so the error equals to zero when the robot is centred on the line and a positive/negative error depending on the robot's position. Applying Trial and error method I finally reached a Kp value that tracks straight lines perfectly. However, I'm still unable to turn and stay on the line even on a similarly smooth turns. I guess this is related to the Kd value. I'm not using the integral part Ki since the error is keep increasing. I tried to set conditions when the robots is drifting away from the line but it's not working probably (even without the conditions it is somehow turning smoother but then losing the line) I'm using the following draft code:

from bbio import *
import time

integral = 0
last_prop = 0
Kp = 20
Ki= 0
Kd = 150
amax = list(0 for i in range(0,8))
amin = list(1024 for i in range(0,8))
timeout = time.time() + 10

# Read ADC data from MCP3008
# ch: 0-7, ADC channel
# cs: 0-1, SPI chip select
# See MCP3008 datasheet p.21
def adc_read(cs, ch):
    spidata = SPI1.transfer(cs, [1,(8+ch)<<4, 0])
    data = ((spidata[1] & 3) << 8) + spidata[2]

    return data

def setup():
# SPI1 setup
    pinMode(GPIO1_7, OUTPUT)
    digitalWrite(GPIO1_7, LOW)
    SPI1.setMaxFrequency(0,50000)  # => ~47kHz, higher gives occasional false readings
  #  SPI1.setMaxFrequency(1,50000)

# reading the IR sensor data    
def read_sensors():
    sensors = []
    for i in range(8):

    return sensors
# calculating the error from PID controller
def calc_pid(x,sp):
    global  integral, last_prop , Kp, Ki ,Kd
    set_point = sp
    pos = sensor_average(x)/sensor_sum(x)
    prop = pos - set_point
    integral = integral + prop
    deriv = prop - last_prop
    last_prop = prop
    error = (prop*Kp + integral*Ki + deriv*Kd)/100

    return error

def get_position(s):
    return sensor_average(s)/sensor_sum(s)        

def sensor_average(x):
    avg = 0
    for i in range(8):
        avg += x[i]*i*100

    return avg

def sensor_sum(x):
    sum = 0  
    for i in range(8):
        sum += x[i] 
    return sum

def get_sensor(x):
    j= read_sensors()[x]
    return j

def calc_setpoint(x,y):
    avg = 0
    sum = 0
    for i in range(8):
        avg += (x[i]-y[i])*i*100
        sum += x[i]-y[i]
    return avg/sum
# calibrate the sensors reading to fit any given line
def calibrate():
    global amin
    global amax

    while(time.time() < timeout):
        for i in range(0,8):
            amin[i] = min(amin[i],read_sensors()[i])
            amax[i] = max(amax[i],read_sensors()[i])
            digitalWrite(GPIO1_7, HIGH)

# calculating the correspondent speed
def calc_speed(error):
    avg_speed = 150
    min = 100
    pos = get_position(read_sensors())
    speed = []
    if(error < -35):
        right = avg_speed -(2*error)
        left = avg_speed + (2*error)
    elif(error > 35):
        right =avg_speed - (2*error)
        left = avg_speed + (2*error)
        right = avg_speed - (error)
        left = avg_speed + (error)
    return speed

def loop():

    s = read_sensors()
    setpoint = calc_setpoint(amax,amin)
    position = get_position(s)
    err = calc_pid(s,setpoint)

    print err

    #print "divided by 100:"
    speeds = calc_speed(err) 
    print speeds
    right_motor = speeds[0]
    left_motor = speeds[1]




PS: the sent speed over serial is limited to 255 and I'm multiplying it by a factor from the Arduino side.


2 Answers 2


While calculating speeds, you are multiplying by 2*error. If PID (or PD) calculates an error, I believe it should be fed directly to the motors because in each loop it does not account for an additional 2* multiplier in speed in the next loop. That would act like an unforeseen impulse that is unaccounted for by the PID. So for starters, I would suggest try removing the 2*error term, compare to a smaller error (or as required) and go without the last else-if condition like below:

if(error < -20):
        right = avg_speed -(error)
        left = avg_speed + (error)
elif(error > 20):
        right =avg_speed - (error)
        left = avg_speed + (error)

Secondly; If you are going with hit-&-trial calculation of the PID constants, I would suggest, start with just P and get a decent line following (with manageable oscillations at low speeds). Then add an integral term (PI controller) and test it out.The Differential term can be added once the other two are giving a suitable response.

  • $\begingroup$ first thanks for your reply. The multiplied error is only an approach I was trying so the robot would respond quicker for big errors (away from the line). I was using only right = avg_speed -(error) left = avg_speed + (error) but it didn't work as wanted for slightly sharper turns. Regarding the PD controller, I'm able to follow straight lines perfectly with my P value. Adding the D part enhanced the performance a bit for turns. However, Adding the Integral part caused a big overshoots, even the error value kept increasing when I added the integral term. $\endgroup$
    – DieDen
    Commented Dec 28, 2016 at 11:53
  • $\begingroup$ Well, for quicker response, always increase the proportional constant.Because it is directly multiplied with error, so it effects it more. You can incorporate the multiplier by 2 by increasing the proportional constant by say 1.5 to start with. As for the integral part, since PID is best designed once a system model is developed first (normal control systems) so hit-and-trial will be difficult to get right immediately. PD is easier to correct as the effects are move visible, while integral increases/decreases over time hence difficult to keep track of without mathematical models. $\endgroup$
    – Atif Anwer
    Commented Dec 29, 2016 at 2:05

I believe what you observe with the integral part is windup. You might fix that by limiting the overall value of the I part, or resetting it once the error is small or zeroes out or changes sign (zero crossing). All are known approaches dealing with windup (basically the integral keeps collecting until it saturates and then the system becomes unstable).



Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.