Introduction: Tuning the GiggleBot Line Follower - Advanced

In this very short Instructables you are going to tune your own GiggleBot to follow a black line. In this other tutorial GiggleBot Line Follower, we hard-coded the tuning values to work according to that scenario. You might want to make it behave better by coming up with other gains.

In this tutorial, we are showing you 2 scripts that can both be loaded on different BBC micro:bits so that one of them is put into the GiggleBot and with the other one, the 2 buttons are used to go through a menu and tune different parameters. The sending of these updated parameters is done via the radio.

Step 1: Required Components

You will need the following:

  1. A GiggleBot robot for the micro:bit.
  2. x3 AA Batteries
  3. x2 BBC micro:bits - one for the GiggleBot and the other one acting as a remote for tuning parameters.
  4. A battery for a BBC micro:bit - like the one that comes within the BBC micro:bit package.

Get the GiggleBot Robot for the BBC micro:bit here.

Step 2: Setting Up the Tracks & Environment

You also have to actually build your tracks (download, print, cut and tape tiles) and then set up the environment (the IDE and the runtime).

Since this tutorial is very related to this other tutorial entitled GiggleBot Line Follower, just go there and follow steps 2 and 3 and then come back here.

As for the IDE, you can use the Mu editor and for the runtime, you need to download the GiggleBot MicroPython Runtime. The runtime can be downloaded from its documentation here. Head over to the Getting Started chapter of documentation and follow those instructions on setting up the environment. As of this moment, version v0.4.0 of the runtime is used.

Step 3: Setting Up the GiggleBot

Before flashing the runtime to the GiggleBot, make sure you have chosen your desired speed and update rate for the GiggleBot: by default, the speed is set to 100 (base_speed variable) and update rate is set to 70 (update_rate variable).

Given the current implementation, the highest update rate that can be achieved is 70 and if run_neopixels is set to True, then only 50 is achievable. So in a way, you could say that the default update rate is right on the edge of what the BBC micro:bit can do.

Just for the record, the line follower sensor can return updates 100 times a second.

Note: The following script might have missing whitespaces and this seems to be due to some issue in displaying GitHub Gists. Click on the gist to take you to its GitHub page where you can copy-paste the code.

GiggleBot PID Line Follower Tuner (requires a remote to tune it) - xjfls23

from microbit import*
from gigglebot import*
from utime import sleep_ms, ticks_us
import radio
import ustruct
# initialize radio and GB neopixels
radio.on()
neo = init()
# timing
update_rate =70
# default gain values
Kp =0.0
Ki =0.0
Kd =0.0
setpoint =0.5
trigger_point =0.0
min_speed_percent =0.2
base_speed =100
last_position = setpoint
integral =0.0
run_neopixels =False
center_pixel =5# where the center pixel of the smile is located on the GB
# turquoise = tuple(map(lambda x: int(x / 5), (64, 224, 208))) # color to use to draw the error with the neopixels
# turquoise = (12, 44, 41) # which is exactly the above turquoise commented above this
error_width_per_pixel =0.5/3# max error divided by the number of segments between each neopixel
defupper_bound_linear_speed_reducer(abs_error, trigger_point, upper_bound, smallest_motor_power, highest_motor_power):
global base_speed
if abs_error >= trigger_point:
# x0 = 0.0
# y0 = 0.0
# x1 = upper_bound - trigger_point
# y1 = 1.0
# x = abs_error - trigger_point
# y = y0 + (x - x0) * (y1 - y0) / (x1 - x0)
# same as
y = (abs_error - trigger_point) / (upper_bound - trigger_point)
motor_power = base_speed * (smallest_motor_power + (1- y) * (highest_motor_power - smallest_motor_power))
return motor_power
else:
return base_speed * highest_motor_power
run =False
previous_error =0
total_time =0.0
total_counts =0
whileTrue:
# if button a is pressed then start following
if button_a.is_pressed():
run =True
# but if button b is pressed stop the line follower
if button_b.is_pressed():
run =False
integral =0.0
previous_error =0.0
display.scroll('{} - {}'.format(total_time, total_counts), delay=100, wait=False)
total_time =0.0
total_counts =0
pixels_off()
stop()
sleep_ms(500)
if run isTrue:
# read the line sensors
start_time = ticks_us()
# check if we have updated the Kp/Kd gains with a remote
try:
Kp, Ki, Kd, trigger_point, min_speed_percent = ustruct.unpack('fffff', radio.receive_bytes())
set_eyes()
exceptTypeError:
pass
right, left = read_sensor(LINE_SENSOR, BOTH)
# line is on the left when position < 0.5
# line is on the right when position > 0.5
# line is in the middle when position = 0.5
# it's a weighted arithmetic mean
try:
position = right /float(left + right)
exceptZeroDivisionError:
position =0.5
if position ==0: position =0.001
if position ==1: position =0.999
# use a PD controller
error = position - setpoint
integral += error
correction = Kp * error + Ki * integral + Kd * (error - previous_error)
previous_error = error
# calculate motor speeds
motor_speed = upper_bound_linear_speed_reducer(abs(error), setpoint * trigger_point, setpoint, min_speed_percent, 1.0)
leftMotorSpeed = motor_speed + correction
rightMotorSpeed = motor_speed - correction
# light up the neopixels to show to which direction the GiggleBot has to go
if run_neopixels isTrueand total_counts %3==0:
for i inb'\x00\x01\x02\x03\x04\x05\x06\x07\x08':
neo[i] = (0, 0, 0)
for i inb'\x00\x01\x02\x03':
ifabs(error) > error_width_per_pixel * i:
if error <0:
neo[center_pixel + i] = (12, 44, 41)
else:
neo[center_pixel - i] = (12, 44, 41)
else:
percent =1- (error_width_per_pixel * i -abs(error)) / error_width_per_pixel
# light up the current pixel
if error <0:
# neo[center_pixel + i] = tuple(map(lambda x: int(x * percentage), turquoise))
neo[center_pixel + i] = (int(12* percent), int(44* percent), int(41* percent))
else:
# neo[center_pixel - i] = tuple(map(lambda x: int(x * percentage), turquoise))
neo[center_pixel - i] = (int(12* percent), int(44* percent), int(41* percent))
break
neo.show()
try:
# clip the motors
if leftMotorSpeed >100:
leftMotorSpeed =100
rightMotorSpeed = rightMotorSpeed - leftMotorSpeed +100
if rightMotorSpeed >100:
rightMotorSpeed =100
leftMotorSpeed = leftMotorSpeed - rightMotorSpeed +100
if leftMotorSpeed <-100:
leftMotorSpeed =-100
if rightMotorSpeed <-100:
rightMotorSpeed =-100
# actuate the motors
set_speed(leftMotorSpeed, rightMotorSpeed)
drive()
# print((error, motor_speed))
except:
# in case we get into some unfixable issue
pass
# and maintain the loop frequency
end_time = ticks_us()
delay_diff = (end_time - start_time) /1000
total_time += delay_diff
total_counts +=1
if1.0/ update_rate - delay_diff >0:
sleep(1.0/ update_rate - delay_diff)

Step 4: Setting Up the Tuner (Remote)

The next thing we have to do is flash the runtime + script to the 2nd BBC micro:bit. This second micro:bit will act as a remote to the GiggleBot, which will be used to tune the following parameters:

  1. Kp = proportional gain for the PID controller.
  2. Ki = integral gain for the PID controller.
  3. Kd = derivative gain for the PID controller.
  4. trigger_point = the point expressed in percentages between the minimum and maximum speeds of the GiggleBot where the speed starts getting reduced linearly until it reaches the minimum speed.
  5. min_speed_percent = the minimum speed expressed in percentage of the maximum speed.

The other 2 remaining variables that can be tuned are directly hard coded in the script that sits on the GiggleBot: the update_rate and base_speed which represents the maximum speed. As described in the documentation, the maximum speed that can be set for the GiggleBot is 100, which is also the default value for our GiggleBot.

Note: The following script might have missing whitespaces and this seems to be due to some issue in displaying GitHub Gists. Click on the gist to take you to its GitHub page where you can copy-paste the code.

GiggleBot Remote PID Line Follower Tuner (requires the other part) - xjfls23

from microbit import*
from utime import sleep_ms
import radio
import ustruct
# 1st element is the Kp gain
# 2nd element is the Ki gain
# 3rd element is the Kd gain
# 4th element is the trigger_point for motors to lower down the speed (0 -> 1)
# 5th element is the min speed for motors as expressed in percentages (0 -> 1)
gains = [0.0, 0.0, 0.0, 1.0, 0.0]
stepSize =0.1
# 0 and 1 for 1st element
# 2 and 3 for 2nd element
currentSetting =0
defshowMenu():
display.scroll('{} - {}'.format(currentSetting, gains[int(currentSetting /2)]), delay=100, wait=False)
radio.on()
showMenu()
whileTrue:
updated =False
if button_a.is_pressed():
currentSetting = (currentSetting +1) % (2*5)
updated =True
if button_b.is_pressed():
if currentSetting %2==0:
# increase gain when currentSetting is 0 or 2 or ..
ifint(currentSetting /2) in [0, 2]:
gains[int(currentSetting /2)] +=10* stepSize
else:
gains[int(currentSetting /2)] += stepSize
else:
# increase gain when currentSetting is 1 or 3 or ..
ifint(currentSetting /2) in [0, 2]:
gains[int(currentSetting /2)] -=10* stepSize
else:
gains[int(currentSetting /2)] -= stepSize
radio.send_bytes(ustruct.pack('fffff', *gains))
updated =True
if updated:
showMenu()
sleep_ms(200)

Step 5: Tuning the GiggleBot

Place the GiggleBot on the track, turn it on and let it run. In the meantime, you'll constantly have to put it back on the track and tune the gains/parameters with the other BBC micro:bit that you're holding in your hand.

To start the GiggleBot, press button A on the GiggleBot's BBC micro:bit and to stop it and thus reset its state press on button B.

On the remote BBC micro:bit, pressing button A will take you through every option in its menu and button B increases/decreases the corresponding value. It's like setting the clock on an old car's dashboard. The options are like this:

  1. 0-1 options are for the Kp gain.
  2. 2-3 options are for the Ki gain.
  3. 4-5 options are for the Kd gain.
  4. 6-7 options are for setting the setpoint for the moment when the motors start slowing down.
  5. 8-9 options are for setting the minimum speed.

Keep in mind that even numbers in the menu are for increasing the corresponding values and for the odd ones it is exactly the opposite.

Also, when pressing button B on the GiggleBot's BBC micro:bit, you will see on its Neopixel-made screen the number of elapsed milliseconds since the last reset and the number of cycles that the robot has gone through - with these 2 you can calculate the update rate of the robot.

Lastly and most importantly, I have come up with 2 tunings for the GiggleBot. One of them is for when the Neopixel LEDs are turned off and the other is for when it's otherwise. The Neopixel LEDs are used to show to which direction the error has accumulated.

1st set of tuning the parameters (with NeoPixel LEDs off)

  1. Kp = 32.0
  2. Ki = 0.5
  3. Kd = 80.0
  4. trigger_setpoint = 0.3 (which is 30%)
  5. min_speed_percent = 0.2 (which is 20%)
  6. base_speed = 100 (aka maximum speed)
  7. update_rate = 70 (running @70Hz)

2nd set of tuning the parameters (with the NeoPixel LEDs on)

  1. Kp = 25.0
  2. Ki = 0.5
  3. Kd = 35.0
  4. trigger_setpoint = 0.3 (which is 30%)
  5. min_speed_percent = 0.3 (which is 30%)
  6. base_speed = 70 (aka maximum speed)
  7. update_rate = 50 (running @50Hz)
  8. Also, variable run_neopixels has to be set to True in the script that gets loaded on the GiggleBot's BBC micro:bit. This will make the NeoPixel LEDs flash in such a way that they indicate towards which direction the error gets accumulated.

Step 6: GiggleBot Running With the NeoPixels Turned Off

This is an example of running the GiggleBot with the 1st tuning parameters found in the previous step. This example has the NeoPixel LEDs turned off.

Step 7: GiggleBot Running With the Neopixels Turned On

This is an example of running the GiggleBot with the 2nd set of tuning parameters found in step 5. This example has the NeoPixel LEDs turned on.

Notice how in this example, the GiggleBot has a harder time following the line - that's because the Neopixel LEDs are "eating" the CPU time of the BBC micro:bit. That's why we had to reduce the update rate from 70 down to 50.