Adding motors and updating scripts

master
Drew Bednar 5 years ago
parent 5ffdc6e63c
commit 3a26cf4b16

@ -1,24 +1,47 @@
# MicroPython ESP8266 # MicroPython ESP8266/ESP32
## Installation ## Installation
You will want to make sure you are installing the latest version of [Micropython](hhttps://micropython.org/). Download the appropriate [.bin for your chip](https://micropython.org/download) and flash it to your board using the [esptool](https://github.com/espressif/esptool). You will most likely need the UART driver You will want to make sure you are installing the latest version of [Micropython](hhttps://micropython.org/). Download the appropriate [.bin for your chip](https://micropython.org/download) and flash it to your board using the [esptool](https://github.com/espressif/esptool). You will most likely need the UART driver for this operation. In my case my board uses the [Silicon Labs CP210x driver](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers).
### Create a new virtualenv
Virtualenv's separate third party packages from your system Python installation. It is a good practice to install 3rd party packages into a separate virtualenv to avoid dependency conflicts.
```
python3 -m venv env
source env/bin/activate
``` ```
pip install esptool Then pip install the requirements listed in the provided file.
``` ```
pip install -r requirements.txt
```
### Finding your board
I am using a Mac and after installing the CP210x driver and plugging in the board I have a new Silicon Labs device:
```
(env) drewbednar@MacBook-Pro micropython % ls -la /dev/*.SLAB*
crw-rw-rw- 1 root wheel 18, 5 Feb 2 11:03 /dev/cu.SLAB_USBtoUART
crw-rw-rw- 1 root wheel 18, 4 Feb 2 11:03 /dev/tty.SLAB_USBtoUART
```
The file type position lists this as a 'c' or character device. The `cu.SLAB_USBtoUART` and `tty.SLAB_USBtoUART`
are the same device, but tty is supposed to be used for devices calling into Unix/Linux where
cu is a "Call-Up" device meant for calling other devices (e.g. modems). Since we are calling from the Mac to
the dev board we will be using the `/dev/cu.SLAB_USBtoUART` character file for connecting to the board.
### Flashing your board ### Flashing your board
First be sure to erase the flash:
``` ```
esptool.py -p /dev/tty.SLAB_USBtoUART erase_flash esptool.py -p /dev/cu.SLAB_USBtoUART erase_flash
``` ```
Then you will need to write the flash to your board. Be sure to connect with an appropriate baud rate: Then you will need to write the flash to your board. Be sure to connect with an appropriate baud rate. These boards
can do 115200.
``` ```
esptool.py --port /dev/tty.SLAB_USBtoUART --baud 115200 write_flash --flash_size=detect 0 esp8266-20170108-v1.8.7.bin esptool.py --port /dev/cu.SLAB_USBtoUART --baud 115200 write_flash --flash_size=detect 0 esp8266-20170108-v1.8.7.bin
``` ```
@ -27,7 +50,7 @@ esptool.py --port /dev/tty.SLAB_USBtoUART --baud 115200 write_flash --flash_size
If you are using a mac just leverage the `screen` program: If you are using a mac just leverage the `screen` program:
``` ```
screen /dev/tty.SLAB_USBtoUART 115200 screen /dev/cu.SLAB_USBtoUART 115200
``` ```
This should connect you to the boards REPL This should connect you to the boards REPL
@ -52,7 +75,7 @@ e tool from adafruit.
``` ```
ampy --help ampy --help
ampy -p /dev/tty.SLAB_USBtoUART -b 115200 ls ampy -p /dev/cu.SLAB_USBtoUART -b 115200 ls
``` ```
See the basic directory within this project for more detailed usage of the ampy package. See the basic directory within this project for more detailed usage of the ampy package.
@ -97,4 +120,16 @@ This should create an MQTT sensor that you will find on your Overview homepage a
### Testing your new topic ### Testing your new topic
Let's assume you haven't built the ESP8266 sensor. You can still test your service through the Hassio Developer tools at `http://hassio:8123/dev-service`. Select `mqtt.publish` and just craft a simple payload. Once you call the service you should see the result in the Sensor's component on your Overview Let's assume you haven't built the ESP8266 sensor. You can still test your service through the Hassio Developer tools at `http://hassio:8123/dev-service`. Select `mqtt.publish` and just craft a simple payload. Once you call the service you should see the result in the Sensor's component on your Overview
## Appendix
- https://learn.adafruit.com/micropython-hardware-analog-i-o/overview
- https://learn.adafruit.com/micropython-hardware-digital-i-slash-o
- https://learn.adafruit.com/micropython-displays-drawing-shapes
- https://learn.adafruit.com/micropython-basics-load-files-and-run-code/overview
- https://learn.adafruit.com/circuitpython-hardware-lis3dh-accelerometer
- https://learn.adafruit.com/micropython-for-samd21
- https://learn.adafruit.com/micropython-hardware-i2c-devices
- https://learn.adafruit.com/micropython-hardware-spi-devices
- https://learn.adafruit.com/building-and-running-micropython-on-the-esp8266/overview

@ -0,0 +1,13 @@
# Motors (servos, steppers, DC)
The modules for this came from this legacy Adafruit repo https://github.com/adafruit/micropython-adafruit-pca9685. Look at it's
[Docs](https://micropython-pca9685.readthedocs.io/en/latest/)
## I2C Servo driver (PCA9685)
Using the [PCA9685](https://www.adafruit.com/product/815) from adafruit, we can drive multiple
servos using PWM. A large amount of the content for this read my comes from the adafruit article
on [PCA9685 with Micropython](https://learn.adafruit.com/micropython-hardware-pca9685-pwm-and-servo-driver/micropython)
The git repo https://github.com/adafruit/micropython-adafruit-pca9685 is still up. Look at it's
[Docs](https://micropython-pca9685.readthedocs.io/en/latest/)

@ -0,0 +1,45 @@
import pca9685
_DC_MOTORS = ((8, 9, 10), (13, 12, 11), (2, 3, 4), (7, 6, 5))
class DCMotors:
def __init__(self, i2c, address=0x60, freq=1600):
self.pca9685 = pca9685.PCA9685(i2c, address)
self.pca9685.freq(freq)
def _pin(self, pin, value=None):
if value is None:
return bool(self.pca9685.pwm(pin)[0])
if value:
self.pca9685.pwm(pin, 4096, 0)
else:
self.pca9685.pwm(pin, 0, 0)
def speed(self, index, value=None):
pwm, in2, in1 = _DC_MOTORS[index]
if value is None:
value = self.pca9685.duty(pwm)
if self._pin(in2) and not self._pin(in1):
value = -value
return value
if value > 0:
# Forward
self._pin(in2, False)
self._pin(in1, True)
elif value < 0:
# Backward
self._pin(in1, False)
self._pin(in2, True)
else:
# Release
self._pin(in1, False)
self._pin(in2, False)
self.pca9685.duty(pwm, abs(value))
def brake(self, index):
pwm, in2, in1 = _DC_MOTORS[index]
self._pin(in1, True)
self._pin(in2, True)
self.pca9685.duty(pwm, 0)

@ -0,0 +1,59 @@
import ustruct
import time
class PCA9685:
def __init__(self, i2c, address=0x40):
self.i2c = i2c
self.address = address
self.reset()
def _write(self, address, value):
self.i2c.writeto_mem(self.address, address, bytearray([value]))
def _read(self, address):
return self.i2c.readfrom_mem(self.address, address, 1)[0]
def reset(self):
self._write(0x00, 0x00) # Mode1
def freq(self, freq=None):
if freq is None:
return int(25000000.0 / 4096 / (self._read(0xfe) - 0.5))
prescale = int(25000000.0 / 4096.0 / freq + 0.5)
old_mode = self._read(0x00) # Mode 1
self._write(0x00, (old_mode & 0x7F) | 0x10) # Mode 1, sleep
self._write(0xfe, prescale) # Prescale
self._write(0x00, old_mode) # Mode 1
time.sleep_us(5)
self._write(0x00, old_mode | 0xa1) # Mode 1, autoincrement on
def pwm(self, index, on=None, off=None):
if on is None or off is None:
data = self.i2c.readfrom_mem(self.address, 0x06 + 4 * index, 4)
return ustruct.unpack('<HH', data)
data = ustruct.pack('<HH', on, off)
self.i2c.writeto_mem(self.address, 0x06 + 4 * index, data)
def duty(self, index, value=None, invert=False):
if value is None:
pwm = self.pwm(index)
if pwm == (0, 4096):
value = 0
elif pwm == (4096, 0):
value = 4095
value = pwm[1]
if invert:
value = 4095 - value
return value
if not 0 <= value <= 4095:
raise ValueError("Out of range")
if invert:
value = 4095 - value
if value == 0:
self.pwm(index, 0, 4096)
elif value == 4095:
self.pwm(index, 4096, 0)
else:
self.pwm(index, 0, value)

@ -0,0 +1,35 @@
import pca9685
import math
class Servos:
def __init__(self, i2c, address=0x40, freq=50, min_us=600, max_us=2400,
degrees=180):
self.period = 1000000 / freq
self.min_duty = self._us2duty(min_us)
self.max_duty = self._us2duty(max_us)
self.degrees = degrees
self.freq = freq
self.pca9685 = pca9685.PCA9685(i2c, address)
self.pca9685.freq(freq)
def _us2duty(self, value):
return int(4095 * value / self.period)
def position(self, index, degrees=None, radians=None, us=None, duty=None):
span = self.max_duty - self.min_duty
if degrees is not None:
duty = self.min_duty + span * degrees / self.degrees
elif radians is not None:
duty = self.min_duty + span * radians / math.radians(self.degrees)
elif us is not None:
duty = self._us2duty(us)
elif duty is not None:
pass
else:
return self.pca9685.duty(index)
duty = min(self.max_duty, max(self.min_duty, int(duty)))
self.pca9685.duty(index, duty)
def release(self, index):
self.pca9685.duty(index, 0)

@ -0,0 +1,170 @@
# Stepper Motor Shield/Wing Driver
# Based on Adafruit Motorshield library:
# https://github.com/adafruit/Adafruit_Motor_Shield_V2_Library
# Author: Tony DiCola
import pca9685
# Constants that specify the direction and style of steps.
FORWARD = const(1)
BACKWARD = const(2)
SINGLE = const(1)
DOUBLE = const(2)
INTERLEAVE = const(3)
MICROSTEP = const(4)
# Not a const so users can change this global to 8 or 16 to change step size
MICROSTEPS = 16
# Microstepping curves (these are constants but need to be tuples/indexable):
_MICROSTEPCURVE8 = (0, 50, 98, 142, 180, 212, 236, 250, 255)
_MICROSTEPCURVE16 = (0, 25, 50, 74, 98, 120, 141, 162, 180, 197, 212, 225, 236, 244, 250, 253, 255)
# Define PWM outputs for each of two available steppers.
# Each tuple defines for a stepper: pwma, ain2, ain1, pwmb, bin2, bin1
_STEPPERS = ((8, 9, 10, 13, 12, 11), (2, 3, 4, 7, 6, 5))
class StepperMotor:
def __init__(self, pca, pwma, ain2, ain1, pwmb, bin2, bin1):
self.pca9685 = pca
self.pwma = pwma
self.ain2 = ain2
self.ain1 = ain1
self.pwmb = pwmb
self.bin2 = bin2
self.bin1 = bin1
self.currentstep = 0
def _pwm(self, pin, value):
if value > 4095:
self.pca9685.pwm(pin, 4096, 0)
else:
self.pca9685.pwm(pin, 0, value)
def _pin(self, pin, value):
if value:
self.pca9685.pwm(pin, 4096, 0)
else:
self.pca9685.pwm(pin, 0, 0)
def onestep(self, direction, style):
ocra = 255
ocrb = 255
# Adjust current steps based on the direction and type of step.
if style == SINGLE:
if (self.currentstep//(MICROSTEPS//2)) % 2:
if direction == FORWARD:
self.currentstep += MICROSTEPS//2
else:
self.currentstep -= MICROSTEPS//2
else:
if direction == FORWARD:
self.currentstep += MICROSTEPS
else:
self.currentstep -= MICROSTEPS
elif style == DOUBLE:
if not (self.currentstep//(MICROSTEPS//2)) % 2:
if direction == FORWARD:
self.currentstep += MICROSTEPS//2
else:
self.currentstep -= MICROSTEPS//2
else:
if direction == FORWARD:
self.currentstep += MICROSTEPS
else:
self.currentstep -= MICROSTEPS
elif style == INTERLEAVE:
if direction == FORWARD:
self.currentstep += MICROSTEPS//2
else:
self.currentstep -= MICROSTEPS//2
elif style == MICROSTEP:
if direction == FORWARD:
self.currentstep += 1
else:
self.currentstep -= 1
self.currentstep += MICROSTEPS*4
self.currentstep %= MICROSTEPS*4
ocra = 0
ocrb = 0
if MICROSTEPS == 8:
curve = _MICROSTEPCURVE8
elif MICROSTEPS == 16:
curve = _MICROSTEPCURVE16
else:
raise RuntimeError('MICROSTEPS must be 8 or 16!')
if 0 <= self.currentstep < MICROSTEPS:
ocra = curve[MICROSTEPS - self.currentstep]
ocrb = curve[self.currentstep]
elif MICROSTEPS <= self.currentstep < MICROSTEPS*2:
ocra = curve[self.currentstep - MICROSTEPS]
ocrb = curve[MICROSTEPS*2 - self.currentstep]
elif MICROSTEPS*2 <= self.currentstep < MICROSTEPS*3:
ocra = curve[MICROSTEPS*3 - self.currentstep]
ocrb = curve[self.currentstep - MICROSTEPS*2]
elif MICROSTEPS*3 <= self.currentstep < MICROSTEPS*4:
ocra = curve[self.currentstep - MICROSTEPS*3]
ocrb = curve[MICROSTEPS*4 - self.currentstep]
self.currentstep += MICROSTEPS*4
self.currentstep %= MICROSTEPS*4
# Set PWM outputs.
self._pwm(self.pwma, ocra*16)
self._pwm(self.pwmb, ocrb*16)
latch_state = 0
# Determine which coils to energize:
if style == MICROSTEP:
if 0 <= self.currentstep < MICROSTEPS:
latch_state |= 0x3
elif MICROSTEPS <= self.currentstep < MICROSTEPS*2:
latch_state |= 0x6
elif MICROSTEPS*2 <= self.currentstep < MICROSTEPS*3:
latch_state |= 0xC
elif MICROSTEPS*3 <= self.currentstep < MICROSTEPS*4:
latch_state |= 0x9
else:
latch_step = self.currentstep//(MICROSTEPS//2)
if latch_step == 0:
latch_state |= 0x1 # energize coil 1 only
elif latch_step == 1:
latch_state |= 0x3 # energize coil 1+2
elif latch_step == 2:
latch_state |= 0x2 # energize coil 2 only
elif latch_step == 3:
latch_state |= 0x6 # energize coil 2+3
elif latch_step == 4:
latch_state |= 0x4 # energize coil 3 only
elif latch_step == 5:
latch_state |= 0xC # energize coil 3+4
elif latch_step == 6:
latch_state |= 0x8 # energize coil 4 only
elif latch_step == 7:
latch_state |= 0x9 # energize coil 1+4
# Energize coils as appropriate:
if latch_state & 0x1:
self._pin(self.ain2, True)
else:
self._pin(self.ain2, False)
if latch_state & 0x2:
self._pin(self.bin1, True)
else:
self._pin(self.bin1, False)
if latch_state & 0x4:
self._pin(self.ain1, True)
else:
self._pin(self.ain1, False)
if latch_state & 0x8:
self._pin(self.bin2, True)
else:
self._pin(self.bin2, False)
return self.currentstep
class Steppers:
def __init__(self, i2c, address=0x60, freq=1600):
self.pca9685 = pca9685.PCA9685(i2c, address)
self.pca9685.freq(freq)
def get_stepper(self, num):
pwma, ain2, ain1, pwmb, bin2, bin1 = _STEPPERS[num]
return StepperMotor(self.pca9685, pwma, ain2, ain1, pwmb, bin2, bin1)

@ -0,0 +1,14 @@
export AMPY_PORT=/dev/cu.SLAB_USBtoUART
export BAUD_RATE=115200
function repl_connect (){
echo "Connecting to $AMPY_PORT"
screen $AMPY_PORT $BAUD_RATE
}
function upy (){
_command=${1}
shift
# echo "command $_command params $@"
ampy -p $AMPY_PORT -b $BAUD_RATE ${_command} $@
}
Loading…
Cancel
Save