4.2. turtleplotbot module

4.2.1. Hardware

The TurtlePlotBot uses an ESP32 Microcontroller module and a Esp32 DrawBot Board to run a pair of 28BYJ-48 stepper motors. The stepper motors are driven by a UNL2803A Darlington transistor array. The UNL2803A is connected to a MCP23017 i2c 16-bit port expander to run the stepper motors and provide an additional 8-bit I/O port.


_images/hello.jpg

TurtlePlotBot

4.2.2. Interface functions

The turtleplotbot module contains the TurtlePlotBot class used to provide the hardware interface needed by the turtlebot module to run the robot using an ESP32 Microcontroller device and a Esp32 DrawBot Board.

User programs use the functions inherited from the TurtlePlot class for moving and drawing.

class turtleplotbot.TurtlePlotBot(scl=22, sda=21)

Initialize the TurtlePlotBot

Parameters
  • i2c (machine.I2C) – The I2C peripheral to use.

  • to creating a device using the pins (Defaults) –

  • in _SCL_PIN and _SDA_PIN. (defined) –

_movesteppers(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.

Parameters
  • left (float or integer) – millimeters to move left stepper

  • right (float or integer) – millimeters to move right stepper

_turn(angle)

Turn TurtlePlotBot left angle degrees

Parameters

angle (integer or float) – turn left degrees

This Method overrides the TurtlePlotBot method

_move(distance)

Move the TurtlePlotBot distance millimeters

Parameters

distance (integer or float) –

This Method overrides the TurtlePlotBot method

_pen(down)

lower or raise the pen

Parameters

down (boolean) –

This Method overrides the TurtlePlotBot method

done()

Raise pen and turn off the stepper motors.

4.2.3. turtleplotbot implementation

  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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""
.. module:: turtleplotbot
   :synopsis: functions to control the ESP-32 based TurtlePlotBot

TurtlePlotBot Class Functions
============================

The `turtleplotbot` module contains the `TurtlePlotBot` class used to provide the
hardware interface needed by the `turtleplotbot` module to run a
`TurtlePlotBot` using an ESP-32 device and a |esp32bot|.

.. topic::Library Dependencies

   `micropython-servo
   <https://bitbucket.org/thesheep/micropython-servo/src/default/>`_
   by `Radomir Dopieralski
   <https://bitbucket.org/%7Bb0f7a17e-f9c6-4d4d-ad93-a016dd5a2f8d%7D/>`_

   `MicroPython ssd1306 display driver
   <https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py>`_

"""

#pylint: disable-msg=import-error
import time
from math import pi
import machine
import mcp23017
from servo import Servo
from turtleplot import TurtlePlot

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

#pylint: disable-msg=bad-whitespace
_I2C_ADDR       = const(0x20)       # MCP23008 i2c address
_SCL_PIN        = const(22)         # i2c SCL Pin
_SDA_PIN        = const(21)         # i2c SDA Pin
_SERVO_PIN      = const(13)         # Servo control pin

_PEN_UP_ANGLE   = const(90)         # servo angle for pen up
_PEN_DOWN_ANGLE = const(180)        # servo angle for pen down
_STEPS_PER_REV  = const(4076)       # stepper steps per revolution
_WHEEL_DIAMETER = 64.5    	        # in mm (increase = spiral out)
_WHEELBASE      = 112.5             # 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
)

class TurtlePlotBot(TurtlePlot):  # pylint: disable=too-many-instance-attributes, too-many-public-methods
    """
    Initialize the TurtlePlotBot

    Args:
        i2c (machine.I2C): The I2C peripheral to use.
        Defaults to creating a device using the pins
        defined in _SCL_PIN and _SDA_PIN.
    """
    def __init__(self, scl=_SCL_PIN, sda=_SDA_PIN):
        """
        Initialize the turtleplotbot, optionally passing an i2c object to use.
        """
        self._current_step = [0, 0]         # current step indexes
        self._step_delay = 1000             # us delay between steps
        self._pen_delay = 250               # ms delay for pen raise or lower

        self.mcp23017 = mcp23017.MCP23017(
            machine.I2C(
                scl=machine.Pin(scl),
                sda=machine.Pin(sda),
                freq=100000),
            0x20)

        # pylint: disable=no-member

        self.mcp23017.porta.mode = 0x00 # porta output
        self.mcp23017.porta.gpio = 0x00 # all pins low

        self._pen_servo = Servo(
            machine.Pin(_SERVO_PIN, machine.Pin.OUT),
            freq=50,
            min_us=600,
            max_us=2400,
            angle=180)

        time.sleep_ms(100)
        self._pen_servo.write_angle(degrees=_PEN_UP_ANGLE)

        self.rst = machine.Pin(16, machine.Pin.OUT)     # power pin for LCD display
        self.rst.value(1)                               # power on

        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.ticks_us()
            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.mcp23017.porta.gpio = out

            while time.ticks_diff(time.ticks_us(), last) < self._step_delay:
                time.sleep_us(100)

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


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

        Args:
            angle (integer or float): turn left degrees

        This Method overrides the TurtlePlotBot method
        """
        distance = _WHEEL_BPI * (angle / 360.0)
        self._movesteppers(-distance, -distance)


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

        Args:
            distance (integer or float):

        This Method overrides the TurtlePlotBot method
        """
        self._movesteppers(-distance, distance)


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

        Args:
                down (boolean):

        This Method overrides the TurtlePlotBot method
        """
        if down:
            self._pen_servo.write_angle(degrees=_PEN_DOWN_ANGLE)
        else:
            self._pen_servo.write_angle(degrees=_PEN_UP_ANGLE)
        # pylint: disable=no-member
        time.sleep_ms(self._pen_delay)


    def done(self):
        """
        Raise pen and turn off the stepper motors.
        """
        self.penup()
        self._pen_servo.deinit()
        self.mcp23017.porta.gpio = 0x00 # all outputs to zero