| @@ -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 | |||||