changeset 0:99a77cc40c20 default tip

Initial commit
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Sat, 19 Dec 2015 22:02:01 +0100
parents
children
files .hgignore gait.py main.py robot.py servo.py
diffstat 5 files changed, 218 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Sat Dec 19 22:02:01 2015 +0100
@@ -0,0 +1,5 @@
+syntax: glob
+*~
+.*.swp
+*.DS_Store
+doc/_build
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gait.py	Sat Dec 19 22:02:01 2015 +0100
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import pyb
+
+
+class Creep:
+    speed = 0.75
+    stride = 14
+    shift = 8
+    shift_steps = 2
+    raise_leg = 10
+    raise_steps = 2
+    next_leg = (
+        (2, 0, 3, 1),   # forward
+        (1, 3, 0, 2),   # backward
+        (1, 2, 3, 0),   # clockwise
+        (3, 0, 1, 2),   # counter-clockwise
+        (2, 3, 1, 0),   # left
+        (3, 2, 0, 1),   # right
+    )
+
+    def __init__(self, robot):
+        self.robot = robot
+        self.shift_x = 0
+        self.shift_y = 0
+        self.on_ground = list(robot.legs)
+        self.dx = 0
+        self.dy = 1
+        self.rotation = 0
+        self.spread = 0
+        leg = robot.legs[0]
+        self.height = leg.tibia
+        self.last_leg = 0
+
+    def move(self):
+        for leg in self.on_ground:
+            dx = self.dx * leg.x_dir * self.speed
+            dy = self.dy * leg.y_dir * self.speed
+            leg.move_to(leg.x - dx, leg.y - dy, -self.height)
+            #leg.rotate_by(-self.rotation * leg.x_dir * leg.y_dir / 2)
+        pyb.delay(25)
+
+    def step(self, leg):
+        dx = (leg.x_dir * self.shift - self.shift_x) / self.shift_steps
+        dy = (leg.y_dir * self.shift - self.shift_y) / self.shift_steps
+        for step in range(self.shift_steps):
+            self.robot.move_by(dx, dy, 0)
+            self.shift_x += dx
+            self.shift_y += dy
+            self.move()
+        self.on_ground.remove(leg)
+        for step in range(self.raise_steps):
+            leg.move_by(0, 0, self.raise_leg)
+            self.move()
+        leg.move_to(
+            leg.home + (self.shift_x + self.dx *
+                        self.stride) * leg.x_dir + self.spread,
+            leg.home + (self.shift_y + self.dy *
+                        self.stride) * leg.y_dir + self.spread,
+            self.raise_leg * self.raise_steps - self.height
+        )
+        self.move()
+        self.move()
+        for step in range(self.raise_steps - 1):
+            leg.move_by(0, 0, -self.raise_leg)
+            self.move()
+        self.on_ground.append(leg)
+        self.move()
+
+    def run(self, steps):
+        for step in range(steps):
+            leg = self.robot.legs[self.last_leg]
+            self.step(leg)
+            self.last_leg = self.next_leg[0][self.last_leg]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.py	Sat Dec 19 22:02:01 2015 +0100
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from robot import Robot
+from gait import Creep
+
+
+robot = Robot()
+gait = Creep(robot)
+#gait.run(16)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robot.py	Sat Dec 19 22:02:01 2015 +0100
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import math
+
+from servo import Servo
+
+
+PI2 = math.pi / 2
+PI4 = math.pi / 4
+
+
+def _solve_triangle(a, b, c):
+    a, b, c = abs(a), abs(b), abs(c)
+    if a + b < c or a + c < b or b + c < a:
+        raise ValueError("Impossible triangle: %r, %r, %r" % (a, b, c))
+    cos = float(a ** 2 + b ** 2 - c ** 2) / (2 * a * b)
+    return math.acos(cos)
+
+
+def _norm(a, b):
+    return math.sqrt(a ** 2 + b ** 2)
+
+
+class Leg:
+    # Lengths of leg segments, in mm
+    coxa =  12    # between the hip and the knee
+    femur = 42    # between the knee and the ankle
+    tibia = 38.5  # between the ankle and the foot
+    base =  21    # from the center of body
+
+    def __init__(self, hip_servo, knee_servo, ankle_servo, x_dir, y_dir):
+        self.servos = (hip_servo, knee_servo, ankle_servo)
+        self.x_dir = x_dir
+        self.y_dir = y_dir
+        self.home = (self.coxa + self.femur) / math.sqrt(2)
+        self.x = self.home
+        self.y = self.home
+        self.z = -self.tibia
+
+    def move_to(self, x, y, z):
+        f = _norm(x, y) - self.coxa
+        d = _norm(f, z)
+        if d > self.femur + self.tibia:
+            raise ValueError("Out of reach")
+        hip = math.atan2(y, x) - PI4
+        knee = _solve_triangle(self.femur, d, self.tibia) - math.atan2(-z, f)
+        ankle = _solve_triangle(self.femur, self.tibia, d) - PI2
+        for servo, angle in zip(self.servos, (hip, knee, ankle)):
+            servo.move(angle)
+        self.x = x
+        self.y = y
+        self.z = z
+
+    def move_by(self, dx=0, dy=0, dz=0):
+        self.move_to(self.x + dx, self.y + dy, self.z + dz)
+
+    def rotate_by(self, radians):
+        x = self.x + self.base
+        y = self.y + self.base
+        nx = x * math.cos(radians) - y * math.sin(radians) - self.base
+        ny = x * math.sin(radians) + y * math.cos(radians) - self.base
+        self.move_to(nx, ny, self.z)
+
+
+class Robot:
+    def __init__(self):
+        self.legs = (
+            Leg(Servo('Y3', 0), Servo( 'Y7', 0), Servo('Y8', 1), -1,  1),
+            Leg(Servo('Y4', 1), Servo('X10', 1), Servo('X9', 0), -1, -1),
+            Leg(Servo('X3', 0), Servo( 'X7', 0), Servo('X8', 1),  1, -1),
+            Leg(Servo('X4', 1), Servo('Y10', 1), Servo('Y9', 0),  1,  1),
+        )
+
+    def move_by(self, dx=0, dy=0, dz=0):
+        for leg in self.legs:
+            leg.move_by(dx * leg.x_dir, dy * leg.y_dir, dz)
+
+    def rotate_by(self, radians):
+        for leg in self.legs:
+            leg.rotate_by(radians)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servo.py	Sat Dec 19 22:02:01 2015 +0100
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import math
+import pyb
+
+
+PI2 = math.pi / 2
+TAU = math.pi * 2
+TIMERS = {
+    # pin: timer, channel
+    'Y8':   (12, 2),
+    'Y7':   (12, 1),
+    'Y3':   (4, 3),
+    'X9':   (4, 1),
+    'X10':  (4, 2),
+    'Y4':   (4, 4),
+    'X8':   (14, 1),
+    'X7':   (13, 1),
+    'X3':   (5, 3),
+    'Y9':   (2, 3),
+    'Y10':  (2, 4),
+    'X4':   (5, 4),
+}
+
+
+class Servo:
+    freq_min = 600
+    freq_max = 2400
+
+    def __init__(self, pin, reverse=False, trim=0):
+        timer, channel = TIMERS[pin]
+        self.timer = pyb.Timer(timer, prescaler=83, period=19999)
+        self.channel = self.timer.channel(channel, pyb.Timer.PWM,
+                                          pin=pyb.Pin(pin))
+        self.reverse = -1 if reverse else 1
+        self.trim = trim
+
+    def move(self, radians):
+        # Move a servo to a position in radians between -PI/2 and PI/2
+        rads = radians * self.reverse + PI2
+        while rads > TAU:
+            rads -= TAU
+        while rads < 0:
+            rads += TAU
+        freq = (self.freq_max - self.freq_min) * rads / math.pi + self.freq_min
+        freq = min(self.freq_max, max(self.freq_min, freq + self.trim))
+        self.channel.pulse_width(int(freq))
+
+    def __del__(self):
+        self.timer.deinit()