1- import machine , time
2- from machine import Pin
1+ import time
2+ from machine import Pin , Timer
33
44class 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