diff --git a/README.md b/README.md index 8e9ed8e..3c80bd1 100644 --- a/README.md +++ b/README.md @@ -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 ``` -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 -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. @@ -97,4 +120,16 @@ 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 \ No newline at end of file +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 \ No newline at end of file diff --git a/motors/README.md b/motors/README.md new file mode 100644 index 0000000..3caedac --- /dev/null +++ b/motors/README.md @@ -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/) \ No newline at end of file diff --git a/motors/boot.py b/motors/boot.py new file mode 100644 index 0000000..e69de29 diff --git a/motors/main.py b/motors/main.py new file mode 100644 index 0000000..e69de29 diff --git a/motors/motor.py b/motors/motor.py new file mode 100644 index 0000000..f80e5b8 --- /dev/null +++ b/motors/motor.py @@ -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) diff --git a/motors/pca9685.py b/motors/pca9685.py new file mode 100644 index 0000000..b9cc8fe --- /dev/null +++ b/motors/pca9685.py @@ -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(' 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) diff --git a/scripts/profile b/scripts/profile new file mode 100644 index 0000000..4e15780 --- /dev/null +++ b/scripts/profile @@ -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} $@ +} \ No newline at end of file