BalanceBot code and STLs
This commit is contained in:
parent
87721625ec
commit
7ca8256b29
3 changed files with 9119 additions and 0 deletions
213
BalanceBot/code.py
Normal file
213
BalanceBot/code.py
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
"""
|
||||||
|
Self balancing 2 wheeled bot using a PID controller.
|
||||||
|
See https://en.wikipedia.org/wiki/PID_controller
|
||||||
|
|
||||||
|
Adafruit invests time and resources providing this open source code.
|
||||||
|
Please support Adafruit and open source hardware by purchasing
|
||||||
|
products from Adafruit!
|
||||||
|
|
||||||
|
Written by Dave Astels for Adafruit Industries
|
||||||
|
Copyright (c) 2018 Adafruit Industries
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
All text above must be included in any redistribution.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import busio
|
||||||
|
import board
|
||||||
|
import time
|
||||||
|
import array
|
||||||
|
import math
|
||||||
|
from adafruit_seesaw.seesaw import Seesaw
|
||||||
|
from adafruit_seesaw.pwmout import PWMOut
|
||||||
|
from adafruit_motor import servo
|
||||||
|
from digitalio import DigitalInOut, Direction
|
||||||
|
import adafruit_lsm9ds0
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
SAMPLE_INTERVAL = 0.02
|
||||||
|
ACCEL_ZERO_ADJUST = 0.25
|
||||||
|
|
||||||
|
SERVO1_ZERO_ADJUST = -0.015
|
||||||
|
SERVO2_ZERO_ADJUST = -0.015
|
||||||
|
|
||||||
|
# Tilt signal LED
|
||||||
|
led = DigitalInOut(board.D13)
|
||||||
|
led.direction = Direction.OUTPUT
|
||||||
|
led.value = False
|
||||||
|
|
||||||
|
# i@c: accelerometer and CRICKIT
|
||||||
|
i2c = busio.I2C(board.SCL, board.SDA)
|
||||||
|
sensor = adafruit_lsm9ds0.LSM9DS0_I2C(i2c)
|
||||||
|
seesaw = Seesaw(i2c)
|
||||||
|
|
||||||
|
# UART
|
||||||
|
uart = busio.UART(board.TX, board.RX, baudrate=115200)
|
||||||
|
|
||||||
|
# Servos
|
||||||
|
pwm1 = PWMOut(seesaw, 17)
|
||||||
|
pwm1.frequency = 50
|
||||||
|
servo1 = servo.ContinuousServo(pwm1, min_pulse=500, max_pulse=2500)
|
||||||
|
|
||||||
|
pwm2 = PWMOut(seesaw, 16)
|
||||||
|
pwm2.frequency = 50
|
||||||
|
servo2 = servo.ContinuousServo(pwm2, min_pulse=500, max_pulse=2500)
|
||||||
|
|
||||||
|
# Stop the servos
|
||||||
|
servo1.throttle = SERVO1_ZERO_ADJUST
|
||||||
|
servo2.throttle = SERVO2_ZERO_ADJUST
|
||||||
|
|
||||||
|
|
||||||
|
# A line was received via the UART, process it
|
||||||
|
|
||||||
|
def process_command(cmd, kp, ki, kd):
|
||||||
|
"""Process a command line from the UART.
|
||||||
|
cmd is the list of bytes received via the UART
|
||||||
|
kp, ki, and kd are the current constant values
|
||||||
|
Returns new constant values (unchanged in the cases of an error)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def report(kp, ki, kd):
|
||||||
|
"""Inner function to write the constants to the UART"""
|
||||||
|
uart.write("KP: {0: 0.3f} KI: {1: 0.3f} KD: {2: 0.3f}\r\n".format(kp, ki, kd))
|
||||||
|
|
||||||
|
if cmd[0] == 63:
|
||||||
|
report(kp, ki, kd)
|
||||||
|
elif cmd[0] not in [b'p', b'i', b'd']:
|
||||||
|
uart.write("Bad parameter\r\n")
|
||||||
|
elif len(cmd) > 1:
|
||||||
|
var = cmd[0]
|
||||||
|
op = cmd[1] # =/+/-
|
||||||
|
if op not in [b'=', b'+', b'-']:
|
||||||
|
uart.write("Bad operation\r\n")
|
||||||
|
return (kp, ki, kd)
|
||||||
|
|
||||||
|
value = 0.0
|
||||||
|
try:
|
||||||
|
value = float(cmd[2:]) # the value to adjust by
|
||||||
|
except ValueError:
|
||||||
|
uart.write("Bad value\r\n")
|
||||||
|
return (kp, ki, kd)
|
||||||
|
|
||||||
|
if var == b'p':
|
||||||
|
if op == b'=':
|
||||||
|
kp = value
|
||||||
|
elif op == b'+':
|
||||||
|
kp += value
|
||||||
|
else:
|
||||||
|
kp -= value
|
||||||
|
elif var == b'i':
|
||||||
|
if op == b'=':
|
||||||
|
ki = value
|
||||||
|
elif op == b'+':
|
||||||
|
ki += value
|
||||||
|
else:
|
||||||
|
ki -= value
|
||||||
|
else:
|
||||||
|
if op == b'=':
|
||||||
|
kd = value
|
||||||
|
elif op == b'+':
|
||||||
|
kd += value
|
||||||
|
else:
|
||||||
|
kd -= value
|
||||||
|
report(kp, ki, kd)
|
||||||
|
else:
|
||||||
|
uart.write("Bad command\r\n")
|
||||||
|
|
||||||
|
return (kp, ki, kd)
|
||||||
|
|
||||||
|
|
||||||
|
def limit(x):
|
||||||
|
"""Limit the argument to the range -1.0 to 1.0"""
|
||||||
|
return max([-1.0, min([1.0, x])])
|
||||||
|
|
||||||
|
def run():
|
||||||
|
kp = 2.000
|
||||||
|
ki = 1.000
|
||||||
|
kd = 0.050
|
||||||
|
iterm = 0.0
|
||||||
|
dterm = 0.0
|
||||||
|
|
||||||
|
extreme_z_count = 0
|
||||||
|
error = 0.0
|
||||||
|
previous_error = 0.0
|
||||||
|
previous_time = 0.0
|
||||||
|
output = 0.0
|
||||||
|
cmd_buffer = array.array("B", [0] * 8)
|
||||||
|
cmd_buffer_index = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
loop_start_time = time.monotonic()
|
||||||
|
|
||||||
|
# Process UART input and process complete lines
|
||||||
|
if uart.in_waiting > 0:
|
||||||
|
ch = uart.read(1)
|
||||||
|
if cmd_buffer_index > 7:
|
||||||
|
cmd_buffer_index = 0
|
||||||
|
uart.write("command too long.. ignored\r\n")
|
||||||
|
else:
|
||||||
|
uart.write(ch)
|
||||||
|
if ch == b'\r':
|
||||||
|
if cmd_buffer_index > 0:
|
||||||
|
kp, ki, kd = process_command(cmd_buffer[0:cmd_buffer_index], kp, ki, kd)
|
||||||
|
cmd_buffer_index = 0
|
||||||
|
else:
|
||||||
|
cmd_buffer[cmd_buffer_index] = ch[0]
|
||||||
|
cmd_buffer_index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
_, _, z = sensor.accelerometer
|
||||||
|
# print("z: {0: 0.3f}, adj: {1: 0.3f}".format(z, z + ACCEL_ZERO_ADJUST))
|
||||||
|
|
||||||
|
# Check if the bot fell over: a high Z value for a second.
|
||||||
|
# If so stop the servos and blink the tilt LED
|
||||||
|
# When it's righted, continue
|
||||||
|
if math.fabs(z) >= 6.0:
|
||||||
|
extreme_z_count += 1
|
||||||
|
if extreme_z_count > 50:
|
||||||
|
servo1.throttle = 0.0
|
||||||
|
servo2.throttle = 0.0
|
||||||
|
while math.fabs(z) >= 6.0:
|
||||||
|
led.value = True
|
||||||
|
time.sleep(0.2)
|
||||||
|
led.value = False
|
||||||
|
time.sleep(0.2)
|
||||||
|
_, _, z = sensor.accelerometer
|
||||||
|
# print("z: {0: 03f}".format(z))
|
||||||
|
extreme_z_count = 0
|
||||||
|
previous_error = 0.0
|
||||||
|
iterm = 0.0
|
||||||
|
previous_time = loop_start_time
|
||||||
|
error = 0.0
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
extreme_z_count = 0
|
||||||
|
|
||||||
|
# process the error
|
||||||
|
error = (z + ACCEL_ZERO_ADJUST) / 10.0
|
||||||
|
|
||||||
|
delta_time = loop_start_time - previous_time
|
||||||
|
delta_error = error - previous_error
|
||||||
|
|
||||||
|
if previous_time > 0.0:
|
||||||
|
iterm += error * delta_time
|
||||||
|
dterm = 0.0
|
||||||
|
if delta_time > 0:
|
||||||
|
dterm = delta_error / delta_time
|
||||||
|
|
||||||
|
output = limit((kp * error) + (ki * iterm) + (kd * dterm))
|
||||||
|
servo1.throttle = limit(output + SERVO1_ZERO_ADJUST)
|
||||||
|
servo2.throttle = limit((-1 * output) + SERVO2_ZERO_ADJUST)
|
||||||
|
|
||||||
|
previous_error = error
|
||||||
|
previous_time = loop_start_time
|
||||||
|
|
||||||
|
|
||||||
|
# print("error: {0: 0.3f}, iterm: {1: 0.3f}, dterm: {2: 0.3f}, output: {3: 0.3f}".format(error, iterm, dterm, output))
|
||||||
|
|
||||||
|
time.sleep(SAMPLE_INTERVAL - (time.monotonic() - loop_start_time))
|
||||||
|
|
||||||
|
run()
|
||||||
8906
BalanceBot/m3-thumbwheel.stl
Normal file
8906
BalanceBot/m3-thumbwheel.stl
Normal file
File diff suppressed because it is too large
Load diff
BIN
BalanceBot/servomount.stl
Normal file
BIN
BalanceBot/servomount.stl
Normal file
Binary file not shown.
Loading…
Reference in a new issue