From 90f0481a93fac4195dd8e5895d5457a33ec35a8f Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Thu, 24 Mar 2022 08:56:28 +0100 Subject: [PATCH] Replace firmware implementation of HCSR04 (closes #208) --- pslab/external/HCSR04.py | 49 ---------------- pslab/external/hcsr04.py | 118 +++++++++++++++++++++++++++++++++++++++ tox.ini | 2 +- 3 files changed, 119 insertions(+), 50 deletions(-) delete mode 100644 pslab/external/HCSR04.py create mode 100644 pslab/external/hcsr04.py diff --git a/pslab/external/HCSR04.py b/pslab/external/HCSR04.py deleted file mode 100644 index a7f811ea..00000000 --- a/pslab/external/HCSR04.py +++ /dev/null @@ -1,49 +0,0 @@ -import pslab.protocol as CP - - -class HCSR04: - """Read data from ultrasonic distance sensor HC-SR04/HC-SR05. - - Sensors must have separate trigger and output pins. - First a 10 µs pulse is output on SQ1. SQ1 must be connected to the TRIG pin - on the sensor prior to use. - - Upon receiving this pulse, the sensor emits a sequence of sound pulses, and - the logic level of its output pin (which we will monitor via LA1) is also - set high. The logic level goes LOW when the sound packet returns to the - sensor, or when a timeout occurs. - - The ultrasound sensor outputs a series of eight sound pulses at 40 kHz, - which corresponds to a time period of 25 µs per pulse. These pulses reflect - off of the nearest object in front of the sensor, and return to it. The - time between sending and receiving of the pulse packet is used to estimate - the distance. If the reflecting object is either too far away or absorbs - sound, less than eight pulses may be received, and this can cause a - measurement error of 25 µs which corresponds to 8 mm. - - The sensor requires a 5 V supply. You may set SQ2 to HIGH: - >>> psl.pwm_generator.set_state(sq2=True) - and use that as the power supply. - - Returns 0 in case of timeout. - """ - - def __init__(self, device): - self._device = device - - def estimate_distance(self): - self._device.send_byte(CP.NONSTANDARD_IO) - self._device.send_byte(CP.HCSR04_HEADER) - - timeout_msb = int((0.3 * 64e6)) >> 16 - self._device.send_int(timeout_msb) - - A = self._device.get_long() - B = self._device.get_long() - tmt = self._device.get_int() - self._device.get_ack() - - if (tmt >= timeout_msb or B == 0): - return 0 - - return 330 * (B - A + 20) / 64e6 / 2 diff --git a/pslab/external/hcsr04.py b/pslab/external/hcsr04.py new file mode 100644 index 00000000..2da8936d --- /dev/null +++ b/pslab/external/hcsr04.py @@ -0,0 +1,118 @@ +"""Ultrasonic distance sensors.""" +import time + +from pslab.instrument.logic_analyzer import LogicAnalyzer +from pslab.instrument.waveform_generator import PWMGenerator +from pslab.serial_handler import SerialHandler + + +class HCSR04(LogicAnalyzer, PWMGenerator): + """Read data from ultrasonic distance sensor HC-SR04/HC-SR05. + + These sensors can measure distances between 2 cm to 4 m (SR04) / 4.5 m + (SR05). + + Sensors must have separate trigger and echo pins. First a 10 µs pulse is + output on the trigger pin. The trigger pin must be connected to the TRIG + pin on the sensor prior to use. + + Upon receiving this pulse, the sensor emits a sequence of sound pulses, and + the logic level of its echo pin is also set high. The logic level goes LOW + when the sound packet returns to the sensor, or when a timeout occurs. + Timeout occurs if no echo is received within the time slot determinded by + the sensor's maximum range. + + The ultrasound sensor outputs a series of eight sound pulses at 40 kHz, + which corresponds to a time period of 25 µs per pulse. These pulses reflect + off of the nearest object in front of the sensor, and return to it. The + time between sending and receiving of the pulse packet is used to estimate + the distance. If the reflecting object is either too far away or absorbs + sound, less than eight pulses may be received, and this can cause a + measurement error of 25 µs which corresponds to 8 mm. + + Parameters + ---------- + device : :class:`SerialHandler` + Serial connection to PSLab device. + trig : str, optional + Name of the trigger pin. Defaults to SQ1. + echo : str, optional + Name of the echo pin. Defaults to LA1. + + Example + ------- + In this example the sensor's Vcc pin is connected to the PSLab's PV1 pin, + the Trig pin is connected to SQ1, Echo to LA1, and Gnd to GND. + + >>> import pslab + >>> from pslab.external.hcsr04 import HCSR04 + >>> psl = pslab.ScienceLab() + >>> distance_sensor = HCSR04(psl) + >>> psl.power_supply.pv1 = 5 # Power the sensor. + >>> distance_sensor.estimate_distance() + 2.36666667 + """ + + def __init__( + self, + device: SerialHandler, + trig: str = "SQ1", + echo: str = "LA1", + ): + self._device = device + LogicAnalyzer.__init__(self, self._device) + PWMGenerator.__init__(self, self._device) + self._trig = trig + self._echo = echo + self._measure_period = 60e-3 # Minimum recommended by datasheet. + self._trigger_pulse_length = 10e-6 + + def estimate_distance( + self, + average: int = 10, + speed_of_sound: float = 340, + ) -> float: + """Estimate distance to an object. + + Parameters + ---------- + average : int, optional + Number of times to repeat the measurement and average the results. + Defaults to 10. + speed_of_sound : float, optional + Speed of sound in air. Defaults to 340 m/s. + + Returns + ------- + distance : float + Distance to object in meters. + + Raises + ------ + RuntimeError if the ECHO pin is not LOW at start of measurement. + TimeoutError if the end of the ECHO pulse is not detected (i.e. the + object is too far away). + """ + self.capture( + channels=self._echo, + events=2 * average, + block=False, + ) + self.generate( + channels=self._trig, + frequency=self._measure_period**-1, + duty_cycles=self._trigger_pulse_length / self._measure_period, + ) + time.sleep(self._measure_period * average) + self.set_state(**{self._trig.lower(): 0}) + (t,) = self.fetch_data() + self._sanity_check(len(t), 2 * average) + high_times = t[::2] - t[1::2] + return speed_of_sound * high_times.mean() / 2 * 1e-6 + + def _sanity_check(self, events: int, expected_events: int): + if self.get_initial_states()[self._echo]: + raise RuntimeError("ECHO pin was HIGH when measurement started.") + + if events < expected_events: + raise TimeoutError diff --git a/tox.ini b/tox.ini index 6b61803b..27691d5a 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ commands = coverage run --source pslab -m pytest --record [testenv:lint] deps = -rlint-requirements.txt setenv = - INCLUDE_PSL_FILES = pslab/bus/ pslab/instrument/ pslab/serial_handler.py pslab/cli.py pslab/external/motor.py pslab/external/gas_sensor.py + INCLUDE_PSL_FILES = pslab/bus/ pslab/instrument/ pslab/serial_handler.py pslab/cli.py pslab/external/motor.py pslab/external/gas_sensor.py pslab/external/hcsr04.py commands = black --check {env:INCLUDE_PSL_FILES} flake8 --show-source {env:INCLUDE_PSL_FILES}