|
| 1 | +"""Ultrasonic distance sensors.""" |
| 2 | +import time |
| 3 | + |
| 4 | +from pslab.instrument.logic_analyzer import LogicAnalyzer |
| 5 | +from pslab.instrument.waveform_generator import PWMGenerator |
| 6 | +from pslab.serial_handler import SerialHandler |
| 7 | + |
| 8 | + |
| 9 | +class HCSR04(LogicAnalyzer, PWMGenerator): |
| 10 | + """Read data from ultrasonic distance sensor HC-SR04/HC-SR05. |
| 11 | +
|
| 12 | + These sensors can measure distances between 2 cm to 4 m (SR04) / 4.5 m |
| 13 | + (SR05). |
| 14 | +
|
| 15 | + Sensors must have separate trigger and echo pins. First a 10 µs pulse is |
| 16 | + output on the trigger pin. The trigger pin must be connected to the TRIG |
| 17 | + pin on the sensor prior to use. |
| 18 | +
|
| 19 | + Upon receiving this pulse, the sensor emits a sequence of sound pulses, and |
| 20 | + the logic level of its echo pin is also set high. The logic level goes LOW |
| 21 | + when the sound packet returns to the sensor, or when a timeout occurs. |
| 22 | + Timeout occurs if no echo is received within the time slot determinded by |
| 23 | + the sensor's maximum range. |
| 24 | +
|
| 25 | + The ultrasound sensor outputs a series of eight sound pulses at 40 kHz, |
| 26 | + which corresponds to a time period of 25 µs per pulse. These pulses reflect |
| 27 | + off of the nearest object in front of the sensor, and return to it. The |
| 28 | + time between sending and receiving of the pulse packet is used to estimate |
| 29 | + the distance. If the reflecting object is either too far away or absorbs |
| 30 | + sound, less than eight pulses may be received, and this can cause a |
| 31 | + measurement error of 25 µs which corresponds to 8 mm. |
| 32 | +
|
| 33 | + Parameters |
| 34 | + ---------- |
| 35 | + device : :class:`SerialHandler` |
| 36 | + Serial connection to PSLab device. |
| 37 | + trig : str, optional |
| 38 | + Name of the trigger pin. Defaults to SQ1. |
| 39 | + echo : str, optional |
| 40 | + Name of the echo pin. Defaults to LA1. |
| 41 | +
|
| 42 | + Example |
| 43 | + ------- |
| 44 | + In this example the sensor's Vcc pin is connected to the PSLab's PV1 pin, |
| 45 | + the Trig pin is connected to SQ1, Echo to LA1, and Gnd to GND. |
| 46 | +
|
| 47 | + >>> import pslab |
| 48 | + >>> from pslab.external.hcsr04 import HCSR04 |
| 49 | + >>> psl = pslab.ScienceLab() |
| 50 | + >>> distance_sensor = HCSR04(psl) |
| 51 | + >>> psl.power_supply.pv1 = 5 # Power the sensor. |
| 52 | + >>> distance_sensor.estimate_distance() |
| 53 | + 2.36666667 |
| 54 | + """ |
| 55 | + |
| 56 | + def __init__( |
| 57 | + self, |
| 58 | + device: SerialHandler, |
| 59 | + trig: str = "SQ1", |
| 60 | + echo: str = "LA1", |
| 61 | + ): |
| 62 | + self._device = device |
| 63 | + LogicAnalyzer.__init__(self, self._device) |
| 64 | + PWMGenerator.__init__(self, self._device) |
| 65 | + self._trig = trig |
| 66 | + self._echo = echo |
| 67 | + self._measure_period = 60e-3 # Minimum recommended by datasheet. |
| 68 | + self._trigger_pulse_length = 10e-6 |
| 69 | + |
| 70 | + def estimate_distance( |
| 71 | + self, |
| 72 | + average: int = 10, |
| 73 | + speed_of_sound: float = 340, |
| 74 | + ) -> float: |
| 75 | + """Estimate distance to an object. |
| 76 | +
|
| 77 | + Parameters |
| 78 | + ---------- |
| 79 | + average : int, optional |
| 80 | + Number of times to repeat the measurement and average the results. |
| 81 | + Defaults to 10. |
| 82 | + speed_of_sound : float, optional |
| 83 | + Speed of sound in air. Defaults to 340 m/s. |
| 84 | +
|
| 85 | + Returns |
| 86 | + ------- |
| 87 | + distance : float |
| 88 | + Distance to object in meters. |
| 89 | +
|
| 90 | + Raises |
| 91 | + ------ |
| 92 | + RuntimeError if the ECHO pin is not LOW at start of measurement. |
| 93 | + TimeoutError if the end of the ECHO pulse is not detected (i.e. the |
| 94 | + object is too far away). |
| 95 | + """ |
| 96 | + self.capture( |
| 97 | + channels=self._echo, |
| 98 | + events=2 * average, |
| 99 | + block=False, |
| 100 | + ) |
| 101 | + self.generate( |
| 102 | + channels=self._trig, |
| 103 | + frequency=self._measure_period**-1, |
| 104 | + duty_cycles=self._trigger_pulse_length / self._measure_period, |
| 105 | + ) |
| 106 | + time.sleep(self._measure_period * average) |
| 107 | + self.set_state(**{self._trig.lower(): 0}) |
| 108 | + (t,) = self.fetch_data() |
| 109 | + self._sanity_check(len(t), 2 * average) |
| 110 | + high_times = t[::2] - t[1::2] |
| 111 | + return speed_of_sound * high_times.mean() / 2 * 1e-6 |
| 112 | + |
| 113 | + def _sanity_check(self, events: int, expected_events: int): |
| 114 | + if self.get_initial_states()[self._echo]: |
| 115 | + raise RuntimeError("ECHO pin was HIGH when measurement started.") |
| 116 | + |
| 117 | + if events < expected_events: |
| 118 | + raise TimeoutError |
0 commit comments