2. UniFeatherBot Module

2.1. Hardware

The UniFeatherBot uses a Feather M4 Express module from Adafruit Industries and a Unipolar Feather to run a pair of 28BYJ-48 stepper motors through the use of a MCP23008 i2c 8-bit port expander connected to a UNL2803A Darlington transistor array.

2.2. UniFeatherBot Interface

The unifeatherbot module implements the UniFeatherBot class that contains the interface methods needed by the turtlebot module to take Turtle Graphics commands and turn them into signals the Unipolar Feather uses to move the robot. User programs use the functions inherited from the TurtlePlot class for drawing.

You do not have to be familiar with how the unifeatherbot module works in order to use the UniFeatherBot. It is presented here in case you want to understand how the hardware interface works or modify it to work with different hardware.

2.3. UniFeatherBot Source

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
.. module:: unifeatherbot
   :synopsis: functions to control the UniFeatherBot

UniFeatherBot Class Functions
=============================

The `unifeatherbot` module contains the `UniFeatherBot` class used to provide
the hardware interface needed to run a small robot using an |adafruit|
feather device running CircuitPython using a |unifeather| board.

"""

# pylint: disable-msg=import-error
import time
from math import pi

import board
import busio
import pulseio

from adafruit_motor import servo
from adafruit_mcp230xx.mcp23008 import MCP23008
from turtleplot import TurtlePlot

# pylint: disable-msg=invalid-name
const = lambda x: x

_PEN_UP_ANGLE = const(90)       # servo angle for pen up
_PEN_DOWN_ANGLE = const(30)     # servo angle for pen down
_STEPS_PER_REV = const(4076)    # stepper steps per revolution
_WHEEL_DIAMETER = const(65) 	# in mm (increase = spiral out)
_WHEELBASE = const(116)         # in mm (increase = spiral in)
_LEFT_MOTOR = const(0)          # left motor index
_RIGHT_MOTOR = const(1)         # right motor index

_WHEEL_BPI = _WHEELBASE * pi
_STEPS_PER_MM = _STEPS_PER_REV / (_WHEEL_DIAMETER * pi)
_MOTORS = (_LEFT_MOTOR, _RIGHT_MOTOR)

_STEP_MASKS = (
    0b1000, 0b1100, 0b0100, 0b0110, 0b0010, 0b0011, 0b0001, 0b1001
)

# pylint: disable=too-many-instance-attributes, too-many-public-methods
class UniFeatherBot(TurtlePlot):
    """
    Initialize the UniFeatherBot
    """
    def __init__(self):
        """
        Initialize the turtleplotbot
        """
        self._current_step = [0, 0]         # current step indexes
        self._step_delay = 1500             # delay between steps
        self._pen_delay = 0.25              # delay for pen raise or lower

        self.i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
        self.mcp23008 = MCP23008(self.i2c)

        self.mcp23008.iodir = 0x00   # MCP23008 pins output
        self.mcp23008.gpio = 0x00    # all pins low

        self._pen_servo = servo.Servo(
            pulseio.PWMOut(board.D9, duty_cycle=2 ** 15, frequency=50))

        self._pen_servo.angle = _PEN_UP_ANGLE

        super().__init__()

    def _movesteppers(self, left, right):
        """
        Internal routine to step steppers

        Note:
            Both steppers always move the same distance, but not
            always in the same direction. De-energizes the stepper
            coils after moving to save power.

        Args:
            left (float or integer): millimeters to move left stepper
            right (float or integer): millimeters to move right stepper
        """
        steppers = [int(left * _STEPS_PER_MM), int(right * _STEPS_PER_MM)]
        steps = abs(steppers[_LEFT_MOTOR])

        for _ in range(steps):
            # pylint: disable=no-member
            last = time.monotonic_ns()
            out = 0
            for motor in _MOTORS:
                if steppers[motor]:
                    self._current_step[motor] &= 0x07
                    mask = _STEP_MASKS[self._current_step[motor]]
                    out |= mask << 4 if motor else mask

                    if steppers[motor] > 0:
                        self._current_step[motor] -= 1

                    if steppers[motor] < 0:
                        self._current_step[motor] += 1

            self.mcp23008.gpio = out
            while (time.monotonic_ns() - last) < self._step_delay:
                pass

        # de-energize stepper coils between moves to save power
        self.mcp23008.gpio = 0x00   # all pins low

    def _turn(self, angle):
        """
        Turn the UniFeatherBot left angle degrees

        Args:
            angle (integer or float): turn left degrees
        """
        distance = _WHEEL_BPI * (angle / 360.0)
        self._movesteppers(-distance, distance)

    def _move(self, distance):
        """
        Move the UniFeatherBot distance in millimeters

        Args:
            distance (integer or float):
        """
        self._movesteppers(distance, distance)

    def _pen(self, down):
        """
        lower or raise the UniFeatherBot's pen

        Args:
                down (boolean):
        """
        if down:
            self._pen_servo.angle = _PEN_DOWN_ANGLE
        else:
            self._pen_servo.angle = _PEN_UP_ANGLE
        # pylint: disable=no-member
        time.sleep(self._pen_delay)

    def done(self):
        """
        Raise pen and turn off the stepper motors.
        """
        self.penup()
        self.mcp23008.gpio(0x00)    # all pins low

2.4. UniFeatherBot Reference

class unifeatherbot.UniFeatherBot

Initialize the UniFeatherBot

done()

Raise pen and turn off the stepper motors.