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
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
```
Then pip install the requirements listed in the provided file.
```
pip install -r requirements.txt
```
pip install esptool
### 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
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:
```
screen /dev/tty.SLAB_USBtoUART 115200
screen /dev/cu.SLAB_USBtoUART 115200
```
This should connect you to the boards REPL
@ -52,7 +75,7 @@ e tool from adafruit.
```
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.
@ -98,3 +121,15 @@ This should create an MQTT sensor that you will find on your Overview homepage a
### 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
## 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