├── .gitignore ├── .gitmodules ├── HCSR04 ├── hcsr04.py └── hcsr04_demo.py ├── LICENSE ├── MAX6675 ├── demo.py └── max6675.py ├── PCF8574 └── pcf8574.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.mpy -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "PCA9685"] 2 | path = PCA9685 3 | url = https://github.com/adafruit/micropython-adafruit-pca9685 -------------------------------------------------------------------------------- /HCSR04/hcsr04.py: -------------------------------------------------------------------------------- 1 | """ 2 | Micropython module for HC-SR04 ultrasonic ranging module. 3 | Compatible with ESP8266. 4 | Based on work of Euter2: 5 | https://github.com/Euter2/MicroPython/blob/master/ultrasonic.py 6 | """ 7 | from machine import Pin, time_pulse_us 8 | from time import sleep_us 9 | 10 | 11 | class HCSR04: 12 | """HC-SR04 ultrasonic ranging module class.""" 13 | 14 | def __init__(self, trig_Pin, echo_Pin): 15 | """Initialize Input(echo) and Output(trig) Pins.""" 16 | self._trig = Pin(trig_Pin, Pin.OUT) 17 | self._echo = Pin(echo_Pin, Pin.IN) 18 | self._sound_speed = 340 # m/s 19 | 20 | def _pulse(self): 21 | """Trigger ultrasonic module with 10us pulse.""" 22 | self._trig.high() 23 | sleep_us(10) 24 | self._trig.low() 25 | 26 | def distance(self): 27 | """Measure pulse length and return calculated distance [m].""" 28 | self._pulse() 29 | try: 30 | pulse_width_s = time_pulse_us(self._echo, Pin.high, 30000) / 1000000 31 | except OSError: 32 | # Measurement timed out 33 | return None 34 | 35 | dist_m = (pulse_width_s / 2) * self._sound_speed 36 | return dist_m 37 | 38 | def calibration(self, known_dist_m): 39 | """Calibrate speed of sound.""" 40 | self._sound_speed = known_dist_m / self.distance() * self._sound_speed 41 | print("Speed of sound was successfully calibrated! \n" + 42 | "Current value: " + str(self._sound_speed) + " m/s") 43 | -------------------------------------------------------------------------------- /HCSR04/hcsr04_demo.py: -------------------------------------------------------------------------------- 1 | # Demonstation of HCSR04 2 | # Following code periodicaly measures the distance using HCSR04 sensor 3 | # and trigers LED if an object was measured closer than specified 4 | # distance threshold. 5 | 6 | from hcsr04 import HCSR04 7 | import time 8 | from machine import Pin 9 | 10 | # Parameters 11 | trig_pin_no = 14 12 | echo_pin_no = 13 13 | led_pin_no = 4 14 | max_dist = 2 # Maximum distance that will triger LED (in meters) 15 | led_time = 1 # Time for which the LED stays lit (in seconds) 16 | 17 | # Initialization 18 | led = Pin(led_pin_no, Pin.OUT) 19 | s = HCSR04(trig_pin_no, echo_pin_no) 20 | start = None 21 | 22 | # Infinite loop, kill with Ctrl-C 23 | while True: 24 | # Measure the distance 25 | d = s.distance() 26 | 27 | # If the sensor itself is reporting something in range and its closer 28 | # than max_dist, triger the LED 29 | if d is not None and d < max_dist: 30 | led.high() # Lit up LED 31 | start = time.ticks_ms() # Save trigger time 32 | print(d) # Prints the distance that trigerred LED 33 | 34 | # Check if the LED is still lit and if so, check the timeout 35 | if start is not None and time.ticks_diff(time.ticks_ms(), start) > led_time * 1000: 36 | led.low() # Turn off LED 37 | start = None # Null the triger time => LED is off 38 | 39 | # Optional sleep to make terminal easier to read and prevent 40 | # buffer congestion. 41 | time.sleep(0.1) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ivan Sevcik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MAX6675/demo.py: -------------------------------------------------------------------------------- 1 | from max6675 import MAX6675 2 | from machine import Pin 3 | import time 4 | 5 | so = Pin(12, Pin.IN) 6 | sck = Pin(14, Pin.OUT) 7 | cs = Pin(16, Pin.OUT) 8 | 9 | max = MAX6675(sck, cs , so) 10 | 11 | for _ in range(10): 12 | print(max.read()) 13 | time.sleep(1) -------------------------------------------------------------------------------- /MAX6675/max6675.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class MAX6675: 5 | MEASUREMENT_PERIOD_MS = 220 6 | 7 | def __init__(self, sck, cs, so): 8 | """ 9 | Creates new object for controlling MAX6675 10 | :param sck: SCK (clock) pin, must be configured as Pin.OUT 11 | :param cs: CS (select) pin, must be configured as Pin.OUT 12 | :param so: SO (data) pin, must be configured as Pin.IN 13 | """ 14 | # Thermocouple 15 | self._sck = sck 16 | self._sck.low() 17 | 18 | self._cs = cs 19 | self._cs.high() 20 | 21 | self._so = so 22 | self._so.low() 23 | 24 | self._last_measurement_start = 0 25 | self._last_read_temp = 0 26 | self._error = 0 27 | 28 | def _cycle_sck(self): 29 | self._sck.high() 30 | time.sleep_us(1) 31 | self._sck.low() 32 | time.sleep_us(1) 33 | 34 | def refresh(self): 35 | """ 36 | Start a new measurement. 37 | """ 38 | self._cs.low() 39 | time.sleep_us(10) 40 | self._cs.high() 41 | self._last_measurement_start = time.ticks_ms() 42 | 43 | def ready(self): 44 | """ 45 | Signals if measurement is finished. 46 | :return: True if measurement is ready for reading. 47 | """ 48 | return time.ticks_ms() - self._last_measurement_start > MAX6675.MEASUREMENT_PERIOD_MS 49 | 50 | def error(self): 51 | """ 52 | Returns error bit of last reading. If this bit is set (=1), there's problem with the 53 | thermocouple - it can be damaged or loosely connected 54 | :return: Error bit value 55 | """ 56 | return self._error 57 | 58 | def read(self): 59 | """ 60 | Reads last measurement and starts a new one. If new measurement is not ready yet, returns last value. 61 | Note: The last measurement can be quite old (e.g. since last call to `read`). 62 | To refresh measurement, call `refresh` and wait for `ready` to become True before reading. 63 | :return: Measured temperature 64 | """ 65 | # Check if new reading is available 66 | if self.ready(): 67 | # Bring CS pin low to start protocol for reading result of 68 | # the conversion process. Forcing the pin down outputs 69 | # first (dummy) sign bit 15. 70 | self._cs.low() 71 | time.sleep_us(10) 72 | 73 | # Read temperature bits 14-3 from MAX6675. 74 | value = 0 75 | for i in range(12): 76 | # SCK should resemble clock signal and new SO value 77 | # is presented at falling edge 78 | self._cycle_sck() 79 | value += self._so.value() << (11 - i) 80 | 81 | # Read the TC Input pin to check if the input is open 82 | self._cycle_sck() 83 | self._error = self._so.value() 84 | 85 | # Read the last two bits to complete protocol 86 | for i in range(2): 87 | self._cycle_sck() 88 | 89 | # Finish protocol and start new measurement 90 | self._cs.high() 91 | self._last_measurement_start = time.ticks_ms() 92 | 93 | self._last_read_temp = value * 0.25 94 | 95 | return self._last_read_temp 96 | -------------------------------------------------------------------------------- /PCF8574/pcf8574.py: -------------------------------------------------------------------------------- 1 | # TODO: Implement input interupts if needed 2 | class PCF8574: 3 | def __init__(self, i2c, address): 4 | self._i2c = i2c 5 | self._address = address 6 | self._input = 0 # Buffers the result of read in memory 7 | self._input_mask = 0 # Mask specifying which pins are set as input 8 | self._output = 0 # The state of pins set for output 9 | self._write() 10 | 11 | def _read(self): 12 | self._input = self._i2c.readfrom(self._address, 1)[0] & self._input_mask 13 | 14 | def _write(self): 15 | self._i2c.writeto(self._address, bytes([self._output | self._input_mask])) 16 | 17 | def read(self, pin): 18 | bit_mask = 1 << pin 19 | self._input_mask |= bit_mask 20 | self._output &= ~bit_mask 21 | self._write() # Update input mask before reading 22 | self._read() 23 | return (self._input & bit_mask) >> pin 24 | 25 | def read8(self): 26 | self._input_mask = 0xFF 27 | self._output = 0 28 | self._write() # Update input mask before reading 29 | self._read() 30 | return self._input 31 | 32 | def write(self, pin, value): 33 | bit_mask = 1 << pin 34 | self._input_mask &= ~bit_mask 35 | self._output = self._output | bit_mask if value else self._output & (~bit_mask) 36 | self._write() 37 | 38 | def write8(self, value): 39 | self._input_mask = 0 40 | self._output = value 41 | self._write() 42 | 43 | def set(self): 44 | self.write8(0xFF) 45 | 46 | def clear(self): 47 | self.write8(0x0) 48 | 49 | def toggle(self, pin): 50 | bit_mask = 1 << pin 51 | self._input_mask &= ~bit_mask 52 | self._output ^= bit_mask 53 | self._write() 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-hw-lib 2 | Micropython (ESP8266) library for hardware peripherals. 3 | The collection includes external repositories, therefore it is required to run `git submodule update --init` after cloning repository. 4 | 5 | ## Usage 6 | The scripts are simple python modules that should be placed directly into micropython filesystem and imported, e.g: 7 | 8 | `from pcf8574 import PCF8574`. 9 | 10 | ## Precompilation 11 | ### Bytecode 12 | To save some space and time at import, individual files can be cross-compiled prior to transfering them to filesystem. This will generate micropython bytecode `.mpy` files that can be imported just like normal scripts with command above. The cross-compiler is part of micropython source and first needs to be built. Aftwerwards, it can be used like this: 13 | 14 | `MICROPYTHON_ROOT_DIR/mpy-cross/mpy-cross pcf8574.py` 15 | 16 | You can also use [uPyLoader](https://github.com/BetaRavener/uPyLoader) as a graphical interface to mpy-cross. Simply select files in left pane and press `Compile`. The bytecode files will be generated in same directory as source and are ready for transfer into device. 17 | 18 | ### Freezing module 19 | However, these files still reside in filesystem. To optimize further, they can be "freezed" (saved) into Flash memory. This will make them permanent part of the micropython binary. This can be done by placing them inside target's `modules` or `scripts` folder, e.g: 20 | 21 | `MICROPYTHON_ROOT_DIR/esp8266/modules/` 22 | 23 | `MICROPYTHON_ROOT_DIR/esp8266/scripts/` 24 | 25 | The difference between the two is that scripts in `modules` folder gets compiled into bytecode with `mpy-cross` tool like above, while those in `scripts` will be just freezed into Flash. Note that only `.py` scripts should be placed in both folders as anything else can cause firmware corruption. After placing scripts in either of them, rebuild is needed. This will generate new firmware image that can be flashed like usual. The scripts are immediately available using the `import` syntax from above. 26 | --------------------------------------------------------------------------------