@@ -0,0 +1,48 @@ | |||||
$fn=64; | |||||
baseplate_hole_distance = 56; | |||||
battery_x = 105; | |||||
battery_y = 35; | |||||
battery_z = 25; | |||||
frame_x = 10; | |||||
frame_y = 62; | |||||
frame_z = 3; | |||||
bec_x = 35; | |||||
bec_y = 40; | |||||
bec_z = 4; | |||||
cage_x = bec_x; | |||||
cage_y = bec_y; | |||||
cage_z = battery_z + bec_z + 3; | |||||
module cube_centered(x,y,z) { | |||||
translate([-x/2, -y/2, 0]) cube([x, y, z]); | |||||
} | |||||
module cube_round_corners(x,y,z) { | |||||
hull() { | |||||
translate([-x/2, -y/2, 0]) cylinder(d=3, h=z); | |||||
translate([ x/2, -y/2, 0]) cylinder(d=3, h=z); | |||||
translate([-x/2, y/2, 0]) cylinder(d=3, h=z); | |||||
translate([ x/2, y/2, 0]) cylinder(d=3, h=z); | |||||
} | |||||
} | |||||
difference() { | |||||
union() { | |||||
cube_round_corners(frame_x, frame_y, frame_z); | |||||
cube_round_corners(cage_x, cage_y, cage_z); | |||||
translate([0, 0, cage_z]) cube_round_corners(bec_x, bec_y, bec_z); | |||||
} | |||||
cube_centered(battery_x, battery_y, battery_z); | |||||
translate([0, -baseplate_hole_distance/2, 0]) cylinder(d=3, h=3); | |||||
translate([0, baseplate_hole_distance/2, 0]) cylinder(d=3, h=3); | |||||
translate([0, 0, battery_z+3]) cube_centered(bec_x, bec_y, 100); | |||||
translate([0, 0, battery_z]) cube_centered(bec_x-9, bec_y-12, 100); | |||||
} | |||||
@@ -0,0 +1,24 @@ | |||||
servo_holes_x_left = 26.9; | |||||
servo_holes_x_right = 12.1; | |||||
servo_holes_x = servo_holes_x_left + servo_holes_x_right; | |||||
servo_axis_x_offset = servo_holes_x / 2 - servo_holes_x_right; | |||||
servo_body_x = 32.4; | |||||
servo_body_y = 16.8; | |||||
servo_body_border = 20; | |||||
difference() { | |||||
translate([-servo_body_x/2 - servo_body_border, -servo_body_x/2 - servo_body_border, 0]) cube([servo_body_x + servo_body_border * 2, servo_body_x + servo_body_border * 2, 2]); | |||||
translate([-servo_body_x/2 - servo_axis_x_offset, -servo_body_y/2, ]) cube([servo_body_x, servo_body_y, 2]); | |||||
} | |||||
translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, 30]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, 60]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, 90]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, -30]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, -60]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, -90]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); |
@@ -0,0 +1,24 @@ | |||||
servo_holes_x_left = 34.2; | |||||
servo_holes_x_right = 14.2; | |||||
servo_holes_x = servo_holes_x_left + servo_holes_x_right; | |||||
servo_axis_x_offset = servo_holes_x / 2 - servo_holes_x_right; | |||||
servo_body_x = 40.6; | |||||
servo_body_y = 19.8; | |||||
servo_body_border = 20; | |||||
difference() { | |||||
translate([-servo_body_x/2 - servo_body_border, -servo_body_x/2 - servo_body_border, 0]) cube([servo_body_x + servo_body_border * 2, servo_body_x + servo_body_border * 2, 2]); | |||||
translate([-servo_body_x/2 - servo_axis_x_offset, -servo_body_y/2, ]) cube([servo_body_x, servo_body_y, 2]); | |||||
} | |||||
translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, 30]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, 60]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, 90]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, -30]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, -60]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); | |||||
rotate([0, 0, -90]) translate([15, -0.5, 2]) cube([servo_body_border, 1, 1]); |
@@ -0,0 +1,13 @@ | |||||
$fn=64; | |||||
difference() { | |||||
hull() { | |||||
translate([ 0, 3, 0]) cylinder(d=1, h=1.2); | |||||
translate([ 0, -3, 0]) cylinder(d=1, h=1.2); | |||||
translate([ 20, 3, 0]) cylinder(d=1, h=1.2); | |||||
translate([ 20, -3, 0]) cylinder(d=1, h=1.2); | |||||
translate([ 50, 0, 0]) cylinder(d=0.1, h=1.2); | |||||
} | |||||
translate([ 5, 0, 0]) cylinder(d=2.5, h=2); | |||||
translate([20, 0, 0]) cylinder(d=2.5, h=2); | |||||
} |
@@ -0,0 +1,37 @@ | |||||
$fn=64; | |||||
hex_hole_x = 69; | |||||
hex_hole_y = 51; | |||||
pca9685_hole_x = 56; | |||||
pca9685_hole_y = 19; | |||||
pca9685_width = 28; | |||||
module corner_holes(x, y, diameter, height) { | |||||
translate([ x/2, y/2, 0]) cylinder(d=diameter, h=height); | |||||
translate([-x/2, y/2, 0]) cylinder(d=diameter, h=height); | |||||
translate([ x/2, -y/2, 0]) cylinder(d=diameter, h=height); | |||||
translate([-x/2, -y/2, 0]) cylinder(d=diameter, h=height); | |||||
} | |||||
difference() { | |||||
union() { | |||||
hull() { | |||||
corner_holes(hex_hole_x, hex_hole_y, 6,3); | |||||
} | |||||
translate([0, pca9685_width/2, 0]) corner_holes(pca9685_hole_x, pca9685_hole_y, 6, 6); | |||||
translate([0, -pca9685_width/2, 0]) corner_holes(pca9685_hole_x, pca9685_hole_y, 6, 6); | |||||
} | |||||
translate([0, pca9685_width/2, 0]) corner_holes(pca9685_hole_x, pca9685_hole_y, 3, 6); | |||||
translate([0, -pca9685_width/2, 0]) corner_holes(pca9685_hole_x, pca9685_hole_y, 3, 6); | |||||
hull() { | |||||
translate([0, pca9685_width/2, 0]) corner_holes(pca9685_hole_x-9, pca9685_hole_y-9, 3, 3); | |||||
translate([0, -pca9685_width/2, 0]) corner_holes(pca9685_hole_x-9, pca9685_hole_y-9, 3, 3); | |||||
} | |||||
corner_holes(hex_hole_x, hex_hole_y, 3,6); | |||||
} |
@@ -0,0 +1,30 @@ | |||||
$fn=64; | |||||
rpi_hole_x = 58; | |||||
rpi_hole_y = 49; | |||||
hex_hole_x = 69; | |||||
hex_hole_y = 51; | |||||
module corner_holes(x, y, diameter, height) { | |||||
translate([ x/2, y/2, 0]) cylinder(d=diameter, h=height); | |||||
translate([-x/2, y/2, 0]) cylinder(d=diameter, h=height); | |||||
translate([ x/2, -y/2, 0]) cylinder(d=diameter, h=height); | |||||
translate([-x/2, -y/2, 0]) cylinder(d=diameter, h=height); | |||||
} | |||||
difference() { | |||||
union() { | |||||
hull() { | |||||
corner_holes(hex_hole_x, hex_hole_y, 6,3); | |||||
} | |||||
corner_holes(rpi_hole_x, rpi_hole_y, 5,6); | |||||
} | |||||
corner_holes(hex_hole_x, hex_hole_y, 3,6); | |||||
corner_holes(rpi_hole_x, rpi_hole_y, 2.3,6); | |||||
hull() { | |||||
corner_holes(hex_hole_x - 9, rpi_hole_y - 9, 3, 6); | |||||
} | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 591.0714285714287, | |||||
"slope": 11.178571428571427 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 572.1428571428573, | |||||
"slope": 11.595238095238093 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 558.2142857142858, | |||||
"slope": 11.511904761904761 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 641.0714285714287, | |||||
"slope": 11.845238095238093 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 578.2142857142858, | |||||
"slope": 11.511904761904761 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 521.0714285714287, | |||||
"slope": 11.845238095238093 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 701.4285714285713, | |||||
"slope": 10.952380952380953 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 609.6428571428572, | |||||
"slope": 10.892857142857142 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 620.3571428571429, | |||||
"slope": 11.202380952380953 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 746.7857142857143, | |||||
"slope": 10.892857142857142 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 705.0000000000002, | |||||
"slope": 10.928571428571427 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 600.0, | |||||
"slope": 10.904761904761903 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 607.4999999999999, | |||||
"slope": 10.964285714285715 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 682.1428571428572, | |||||
"slope": 11.071428571428571 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 806.0714285714287, | |||||
"slope": 11.25 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 769.6428571428571, | |||||
"slope": 10.797619047619047 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 593.9285714285716, | |||||
"slope": 10.94047619047619 | |||||
} |
@@ -0,0 +1,5 @@ | |||||
{ | |||||
"description": "", | |||||
"intercept": 740.7142857142857, | |||||
"slope": 11.214285714285715 | |||||
} |
@@ -0,0 +1,103 @@ | |||||
import time | |||||
import board | |||||
import busio | |||||
import sshkeyboard | |||||
import sys | |||||
import scipy.stats | |||||
import json | |||||
from adafruit_motor import servo | |||||
from adafruit_pca9685 import PCA9685 | |||||
i2c = busio.I2C(board.SCL, board.SDA) | |||||
pca = PCA9685(i2c) | |||||
pca.frequency = 50 | |||||
servo_pwm = pca.channels[0] | |||||
#servo = servo.Servo(pca.channels[0], actuation_range = 180, min_pulse=1000, max_pulse=2000) | |||||
#servo.angle = 90 | |||||
def usec_to_duty_cycle(usec): | |||||
return int(usec * 0xffff / 20000) | |||||
print("""Calibrate Servo: | |||||
s - center (to 1500 usec) | |||||
a - move CCW (100 usec) | |||||
q - move CCW ( 10 usec) | |||||
d - move CW (100 usec) | |||||
e - move CW ( 10 usec) | |||||
<enter> - store value | |||||
Move Servo to 0 degrees now.""") | |||||
usec = 1500 | |||||
angle = 0 | |||||
angle_values = [] | |||||
usec_values = [] | |||||
servo_pwm.duty_cycle = usec_to_duty_cycle(usec) | |||||
def press(key): | |||||
global usec | |||||
global angle | |||||
global angle_values | |||||
global usec_values | |||||
if key == 'a': | |||||
usec -= 100 | |||||
if key == 'd': | |||||
usec += 100 | |||||
if key == 's': | |||||
usec = 1500 | |||||
if key == 'q': | |||||
usec -= 10 | |||||
if key == 'e': | |||||
usec += 10 | |||||
if key == 'enter': | |||||
print(f"Angle: {angle} degrees") | |||||
print(f"Pulse length: {usec} usec") | |||||
angle_values.append(angle) | |||||
usec_values.append(usec) | |||||
angle += 30 | |||||
if angle > 180: | |||||
sshkeyboard.stop_listening() | |||||
return | |||||
print(f"Move Servo to {angle} degress now.") | |||||
print(usec) | |||||
servo_pwm.duty_cycle = usec_to_duty_cycle(usec) | |||||
sshkeyboard.listen_keyboard( on_press = press ) | |||||
result = scipy.stats.linregress(angle_values, usec_values) | |||||
print(f"0 degree value: {round(result.intercept)}") | |||||
print(f"180 degree value: {round(result.intercept + 180 * result.slope)}") | |||||
filename = input("Save as: ") | |||||
if filename != "": | |||||
description = input("Description: ") | |||||
if not filename.endswith(".json"): | |||||
filename += ".json" | |||||
calibration = { | |||||
'description': description, | |||||
'intercept': result.intercept, | |||||
'slope': result.slope | |||||
} | |||||
with open(filename, "w") as out: | |||||
out.write(json.dumps(calibration, indent = 4)) | |||||
@@ -0,0 +1,42 @@ | |||||
import serial | |||||
import datetime | |||||
import bitarray | |||||
import bitarray.util | |||||
s = serial.Serial("/dev/ttyAMA0", baudrate=100000, parity=serial.PARITY_ODD, stopbits = serial.STOPBITS_TWO, bytesize=serial.EIGHTBITS) | |||||
counter = 0 | |||||
synchronized = False | |||||
last_t = datetime.datetime.now() | |||||
while not synchronized: | |||||
rx = s.read(1) | |||||
t = datetime.datetime.now() | |||||
delta_t = (t-last_t).microseconds | |||||
last_t = t | |||||
if delta_t > 1000 and rx == b'\x0f': | |||||
counter += 1 | |||||
if counter > 2: | |||||
synchronized = True | |||||
s.read(24) | |||||
print("Synchronized!\n\n") | |||||
while True: | |||||
rx=s.read(25) | |||||
if rx[0] != 15 or rx[24] != 0: | |||||
print("Invalid frame!") | |||||
flags = int(rx[23]) | |||||
ba = bitarray.bitarray(endian='little') | |||||
ba.frombytes(rx[1:22]) | |||||
channel = [] | |||||
for i in range(0, 15): | |||||
channel.append(bitarray.util.ba2int(ba[i*11:i*11 + 11])) | |||||
print(" ".join([str(x) for x in channel]), " " * 20, end="\r") | |||||
@@ -0,0 +1,30 @@ | |||||
import sys | |||||
import time | |||||
import board | |||||
import busio | |||||
import json | |||||
from adafruit_motor import servo | |||||
from adafruit_pca9685 import PCA9685 | |||||
filename = sys.argv[1] | |||||
with open(filename, "r") as f: | |||||
calibration = json.load(f) | |||||
print(calibration) | |||||
i2c = busio.I2C(board.SCL, board.SDA) | |||||
pca = PCA9685(i2c) | |||||
pca.frequency = 50 | |||||
servo_pwm = pca.channels[0] | |||||
servo = servo.Servo(pca.channels[0], actuation_range = 180, min_pulse=round(calibration['intercept']), max_pulse=round(calibration['intercept'] + 180 * calibration['slope'])) | |||||
servo.angle = 90 | |||||
time.sleep(2) | |||||
servo.angle = 0 | |||||
time.sleep(2) | |||||
servo.angle = 180 | |||||
time.sleep(2) | |||||
servo.angle = 90 |