Skip to content

Commit b8e2d15

Browse files
committed
Reapply "Change the rangefinder class to be non-blocking"
This reverts commit dc6db1c.
1 parent dc6db1c commit b8e2d15

1 file changed

Lines changed: 65 additions & 39 deletions

File tree

XRPLib/rangefinder.py

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import machine, time
2-
from machine import Pin
1+
import time
2+
from machine import Pin, Timer
33

44
class Rangefinder:
55

@@ -16,15 +16,17 @@ def get_default_rangefinder(cls):
1616

1717
def __init__(self, trigger_pin: int|str = "RANGE_TRIGGER", echo_pin: int|str = "RANGE_ECHO", timeout_us:int=500*2*30):
1818
"""
19-
A basic class for using the HC-SR04 Ultrasonic Rangefinder.
19+
A non-blocking class for using the HC-SR04 Ultrasonic Rangefinder.
2020
The sensor range is between 2cm and 4m.
21+
Measurements are taken continuously in the background using a timer
22+
and pin IRQ, so distance() returns immediately with the most recent value.
2123
Timeouts will return a MAX_VALUE (65535) instead of raising an exception.
2224
2325
:param trigger_pin: The number of the pin on the microcontroller that's connected to the ``Trig`` pin on the HC-SR04.
2426
:type trigger_pin: int
2527
:param echo_pin: The number of the pin on the microcontroller that's connected to the ``Echo`` pin on the HC-SR04.
2628
:type echo_pin: int
27-
:param timeout_us: Max microseconds seconds to wait for a response from the sensor before assuming it isn't going to answer. By default set to 30,000 us (0.03 s)
29+
:param timeout_us: Max microseconds to wait for a response from the sensor before assuming it isn't going to answer. By default set to 30,000 us (0.03 s)
2830
:type timeout_us: int
2931
"""
3032
self.timeout_us = timeout_us
@@ -36,56 +38,80 @@ def __init__(self, trigger_pin: int|str = "RANGE_TRIGGER", echo_pin: int|str = "
3638
# Init echo pin (in)
3739
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
3840

39-
self.cms = 0
40-
self.last_echo_time = 0
41-
self.cache_time_us = 3000
41+
self.cms = self.MAX_VALUE
42+
self._echo_start = 0
43+
self._waiting_for_echo = False
44+
self._trigger_time = 0
45+
self._first_reading_done = False
4246

43-
def _send_pulse_and_wait(self):
47+
# Register echo pin IRQ for both rising and falling edges
48+
self.echo.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._echo_handler)
49+
50+
# Start a virtual timer to periodically send trigger pulses
51+
# 60ms period matches the HC-SR04 recommended minimum cycle time
52+
self._timer = Timer(-1)
53+
self._timer.init(period=60, callback=self._trigger_ping)
54+
55+
def _trigger_ping(self, t):
4456
"""
45-
Send the pulse to trigger and listen on echo pin.
46-
We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
57+
Timer callback that sends a trigger pulse to the HC-SR04.
58+
Only ~15us of work per call (negligible blocking).
59+
Also detects timeouts from previous measurements.
4760
"""
48-
self._trigger.value(0) # Stabilize the sensor
61+
if self._waiting_for_echo:
62+
# Check if previous measurement timed out
63+
if time.ticks_diff(time.ticks_us(), self._trigger_time) > self.timeout_us:
64+
self.cms = self.MAX_VALUE
65+
self._waiting_for_echo = False
66+
self._first_reading_done = True
67+
else:
68+
# Still waiting for a valid echo, skip this trigger
69+
return
70+
71+
# Send trigger pulse
72+
self._trigger.value(0)
4973
self._delay_us(5)
5074
self._trigger.value(1)
51-
# Send a 10us pulse.
5275
self._delay_us(10)
5376
self._trigger.value(0)
54-
try:
55-
pulse_time = machine.time_pulse_us(self.echo, 1, self.timeout_us)
56-
return pulse_time
57-
except OSError as exception:
58-
raise exception
77+
self._trigger_time = time.ticks_us()
78+
self._waiting_for_echo = True
5979

60-
def distance(self) -> float:
80+
def _echo_handler(self, pin):
6181
"""
62-
Get the distance in centimeters by measuring the echo pulse time
82+
Pin IRQ handler for the echo pin.
83+
Rising edge: record start time.
84+
Falling edge: compute distance from pulse width.
6385
"""
64-
if time.ticks_diff(time.ticks_us(), self.last_echo_time) < self.cache_time_us and not (self.cms == 65535 or self.cms == 0):
65-
return self.cms
86+
if pin.value() == 1:
87+
# Rising edge - echo pulse started
88+
self._echo_start = time.ticks_us()
89+
else:
90+
# Falling edge - echo pulse ended
91+
if self._waiting_for_echo:
92+
pulse_time = time.ticks_diff(time.ticks_us(), self._echo_start)
93+
if pulse_time > 0:
94+
# Sound speed 343.2 m/s = 0.034320 cm/us = 1cm per 29.1us
95+
# Divide by 2 because pulse travels to target and back
96+
self.cms = (pulse_time / 2) / 29.1
97+
else:
98+
self.cms = self.MAX_VALUE
99+
self._waiting_for_echo = False
100+
self._first_reading_done = True
66101

67-
try:
68-
pulse_time = self._send_pulse_and_wait()
69-
if pulse_time <= 0:
70-
return self.MAX_VALUE
71-
except OSError as exception:
72-
# We don't want programs to crash if the HC-SR04 doesn't see anything in range
73-
# So we catch those errors and return 65535 instead
74-
if exception.args[0] == 110: # 110 = ETIMEDOUT
75-
return self.MAX_VALUE
76-
raise exception
77-
78-
# To calculate the distance we get the pulse_time and divide it by 2
79-
# (the pulse walk the distance twice) and by 29.1 becasue
80-
# the sound speed on air (343.2 m/s), that It's equivalent to
81-
# 0.034320 cm/us that is 1cm each 29.1us
82-
self.cms = (pulse_time / 2) / 29.1
83-
self.last_echo_time = time.ticks_us()
102+
def distance(self) -> float:
103+
"""
104+
Get the most recent distance measurement in centimeters.
105+
Blocks until the first measurement is available, then returns
106+
immediately on all subsequent calls.
107+
"""
108+
while not self._first_reading_done:
109+
pass
84110
return self.cms
85111

86112
def _delay_us(self, delay:int):
87113
"""
88-
Custom implementation of time.sleep_us(), used to get around the bug in MicroPython where time.sleep_us()
114+
Custom implementation of time.sleep_us(), used to get around the bug in MicroPython where time.sleep_us()
89115
doesn't work properly and causes the IDE to hang when uploading the code
90116
"""
91117
start = time.ticks_us()

0 commit comments

Comments
 (0)