├── .gitignore ├── .gitmodules ├── MAX6675 ├── demo.py ├── demo_continuous.py └── max6675.py ├── LICENSE ├── HCSR04 ├── hcsr04_demo.py └── hcsr04.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 -------------------------------------------------------------------------------- /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/demo_continuous.py: -------------------------------------------------------------------------------- 1 | """ 2 | Temperature Monitoring using MAX6675 with ESP32 3 | Author: Er-Rajas 4 | Description: 5 | This script continuously reads temperature from the MAX6675 thermocouple sensor 6 | and prints the values over serial. Designed for MicroPython on ESP32. 7 | 8 | Adjust the delay on line 24 as needed: `time.sleep([desired_delay])` 9 | 10 | """ 11 | 12 | from max6675 import MAX6675 13 | from machine import Pin 14 | import time 15 | 16 | so = Pin(12, Pin.IN) 17 | sck = Pin(2, Pin.OUT) 18 | cs = Pin(14, Pin.OUT) 19 | sensor = MAX6675(sck, cs, so) 20 | try: 21 | while True: 22 | temperature = sensor.read() 23 | print("Temperature: {:.2f} °C".format(temperature)) 24 | time.sleep(1) # adjust delay (in seconds) as needed 25 | 26 | except KeyboardInterrupt: 27 | print("\nStopped by user.") -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | # Modified by Er-Rajas 6 | # Replaced .low() / .high() with .value(0) / .value(1) 7 | 8 | from hcsr04 import HCSR04 9 | import time 10 | from machine import Pin 11 | 12 | # Parameters 13 | trig_pin_no = 14 14 | echo_pin_no = 13 15 | led_pin_no = 4 16 | max_dist = 2 # Maximum distance that will triger LED (in meters) 17 | led_time = 1 # Time for which the LED stays lit (in seconds) 18 | 19 | # Initialization 20 | led = Pin(led_pin_no, Pin.OUT) 21 | s = HCSR04(trig_pin_no, echo_pin_no) 22 | start = None 23 | 24 | # Infinite loop, kill with Ctrl-C 25 | while True: 26 | # Measure the distance 27 | d = s.distance() 28 | 29 | # If the sensor itself is reporting something in range and its closer 30 | # than max_dist, triger the LED 31 | if d is not None and d < max_dist: 32 | led.value(1) # Lit up LED 33 | start = time.ticks_ms() # Save trigger time 34 | print(d) # Prints the distance that trigerred LED 35 | 36 | # Check if the LED is still lit and if so, check the timeout 37 | if start is not None and time.ticks_diff(time.ticks_ms(), start) > led_time * 1000: 38 | led.value(0) # Turn off LED 39 | start = None # Null the triger time => LED is off 40 | 41 | # Optional sleep to make terminal easier to read and prevent 42 | # buffer congestion. 43 | time.sleep(0.1) 44 | -------------------------------------------------------------------------------- /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 | Modified by Er-Rajas 7 | Replaced .low() / .high() with .value(0) / .value(1) 8 | """ 9 | from machine import Pin, time_pulse_us 10 | from time import sleep_us 11 | 12 | 13 | class HCSR04: 14 | """HC-SR04 ultrasonic ranging module class.""" 15 | 16 | def __init__(self, trig_Pin, echo_Pin): 17 | """Initialize Input(echo) and Output(trig) Pins.""" 18 | self._trig = Pin(trig_Pin, Pin.OUT) 19 | self._echo = Pin(echo_Pin, Pin.IN) 20 | self._sound_speed = 340 # m/s 21 | 22 | def _pulse(self): 23 | """Trigger ultrasonic module with 10us pulse.""" 24 | self._trig.value(1) 25 | sleep_us(10) 26 | self._trig.value(0) 27 | 28 | def distance(self): 29 | """Measure pulse length and return calculated distance [m].""" 30 | self._pulse() 31 | try: 32 | pulse_width_s = time_pulse_us(self._echo, Pin.high, 30000) / 1000000 33 | except OSError: 34 | # Measurement timed out 35 | return None 36 | 37 | dist_m = (pulse_width_s / 2) * self._sound_speed 38 | return dist_m 39 | 40 | def calibration(self, known_dist_m): 41 | """Calibrate speed of sound.""" 42 | self._sound_speed = known_dist_m / self.distance() * self._sound_speed 43 | print("Speed of sound was successfully calibrated! \n" + 44 | "Current value: " + str(self._sound_speed) + " m/s") 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MAX6675/max6675.py: -------------------------------------------------------------------------------- 1 | # Modified by Er-Rajas 2 | # Replaced .low() / .high() with .value(0) / .value(1) 3 | 4 | 5 | import time 6 | 7 | 8 | class MAX6675: 9 | MEASUREMENT_PERIOD_MS = 220 10 | 11 | def __init__(self, sck, cs, so): 12 | """ 13 | Creates new object for controlling MAX6675 14 | :param sck: SCK (clock) pin, must be configured as Pin.OUT 15 | :param cs: CS (select) pin, must be configured as Pin.OUT 16 | :param so: SO (data) pin, must be configured as Pin.IN 17 | """ 18 | # Thermocouple 19 | self._sck = sck 20 | self._sck.value(0) 21 | 22 | self._cs = cs 23 | self._cs.value(1) 24 | 25 | self._so = so 26 | self._so.value(0) 27 | 28 | self._last_measurement_start = 0 29 | self._last_read_temp = 0 30 | self._error = 0 31 | 32 | def _cycle_sck(self): 33 | self._sck.value(1) 34 | time.sleep_us(1) 35 | self._sck.value(0) 36 | time.sleep_us(1) 37 | 38 | def refresh(self): 39 | """ 40 | Start a new measurement. 41 | """ 42 | self._cs.value(0) 43 | time.sleep_us(10) 44 | self._cs.value(1) 45 | self._last_measurement_start = time.ticks_ms() 46 | 47 | def ready(self): 48 | """ 49 | Signals if measurement is finished. 50 | :return: True if measurement is ready for reading. 51 | """ 52 | return time.ticks_ms() - self._last_measurement_start > MAX6675.MEASUREMENT_PERIOD_MS 53 | 54 | def error(self): 55 | """ 56 | Returns error bit of last reading. If this bit is set (=1), there's problem with the 57 | thermocouple - it can be damaged or loosely connected 58 | :return: Error bit value 59 | """ 60 | return self._error 61 | 62 | def read(self): 63 | """ 64 | Reads last measurement and starts a new one. If new measurement is not ready yet, returns last value. 65 | Note: The last measurement can be quite old (e.g. since last call to `read`). 66 | To refresh measurement, call `refresh` and wait for `ready` to become True before reading. 67 | :return: Measured temperature 68 | """ 69 | # Check if new reading is available 70 | if self.ready(): 71 | # Bring CS pin low to start protocol for reading result of 72 | # the conversion process. Forcing the pin down outputs 73 | # first (dummy) sign bit 15. 74 | self._cs.value(0) 75 | time.sleep_us(10) 76 | 77 | # Read temperature bits 14-3 from MAX6675. 78 | value = 0 79 | for i in range(12): 80 | # SCK should resemble clock signal and new SO value 81 | # is presented at falling edge 82 | self._cycle_sck() 83 | value += self._so.value() << (11 - i) 84 | 85 | # Read the TC Input pin to check if the input is open 86 | self._cycle_sck() 87 | self._error = self._so.value() 88 | 89 | # Read the last two bits to complete protocol 90 | for i in range(2): 91 | self._cycle_sck() 92 | 93 | # Finish protocol and start new measurement 94 | self._cs.value(1) 95 | self._last_measurement_start = time.ticks_ms() 96 | 97 | self._last_read_temp = value * 0.25 98 | 99 | return self._last_read_temp 100 | --------------------------------------------------------------------------------