Skip to content

Commit 41fad77

Browse files
committed
Make the rangefinder class work with mupliple instances
1 parent 13122e9 commit 41fad77

1 file changed

Lines changed: 52 additions & 14 deletions

File tree

XRPLib/rangefinder.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
class Rangefinder:
55

66
_DEFAULT_RANGEFINDER_INSTANCE = None
7+
_instances = []
8+
_timer = None
9+
_current_index = 0
710

811
@classmethod
912
def get_default_rangefinder(cls):
@@ -14,12 +17,36 @@ def get_default_rangefinder(cls):
1417
cls._DEFAULT_RANGEFINDER_INSTANCE = cls()
1518
return cls._DEFAULT_RANGEFINDER_INSTANCE
1619

20+
@classmethod
21+
def _start_shared_timer(cls):
22+
"""
23+
Start the shared timer if it isn't already running.
24+
One timer cycles through all rangefinder instances to prevent acoustic crosstalk.
25+
"""
26+
if cls._timer is None:
27+
cls._timer = Timer(-1)
28+
cls._timer.init(mode=Timer.PERIODIC, period=60, callback=cls._timer_callback)
29+
30+
@classmethod
31+
def _timer_callback(cls, _t):
32+
"""
33+
Shared timer callback that triggers one rangefinder per tick,
34+
cycling through all instances in round-robin order.
35+
"""
36+
if not cls._instances:
37+
return
38+
instance = cls._instances[cls._current_index]
39+
instance._do_ping()
40+
cls._current_index = (cls._current_index + 1) % len(cls._instances)
41+
1742
def __init__(self, trigger_pin: int|str = "RANGE_TRIGGER", echo_pin: int|str = "RANGE_ECHO", timeout_us:int=500*2*30):
1843
"""
1944
A non-blocking class for using the HC-SR04 Ultrasonic Rangefinder.
2045
The sensor range is between 2cm and 4m.
21-
Measurements are taken continuously in the background using a timer
46+
Measurements are taken continuously in the background using a shared timer
2247
and pin IRQ, so distance() returns immediately with the most recent value.
48+
When multiple rangefinders exist, they are pinged sequentially to prevent
49+
acoustic crosstalk.
2350
Timeouts will return a MAX_VALUE (65535) instead of raising an exception.
2451
2552
:param trigger_pin: The number of the pin on the microcontroller that's connected to the ``Trig`` pin on the HC-SR04.
@@ -38,29 +65,40 @@ def __init__(self, trigger_pin: int|str = "RANGE_TRIGGER", echo_pin: int|str = "
3865
# Init echo pin (in)
3966
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
4067

68+
# Cache time functions as instance attributes for use in hard IRQ context,
69+
# where global module lookups are not allowed
70+
self._ticks_us = time.ticks_us
71+
self._ticks_diff = time.ticks_diff
72+
4173
self.cms = self.MAX_VALUE
4274
self._echo_start = 0
4375
self._waiting_for_echo = False
4476
self._trigger_time = 0
4577
self._first_reading_done = False
4678

79+
# Pre-bind methods as instance attributes so the shared timer callback
80+
# can call them without creating bound method objects (which would
81+
# allocate memory in hard IRQ context)
82+
self._do_ping = self._trigger_ping
83+
self._do_echo = self._echo_handler
84+
4785
# 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)
86+
self.echo.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._do_echo)
4987

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(mode=Timer.PERIODIC, period=60, callback=self._trigger_ping)
88+
# Register this instance and start the shared timer
89+
Rangefinder._instances.append(self)
90+
Rangefinder._start_shared_timer()
5491

55-
def _trigger_ping(self, t):
92+
def _trigger_ping(self):
5693
"""
57-
Timer callback that sends a trigger pulse to the HC-SR04.
94+
Send a trigger pulse to the HC-SR04.
95+
Called by the shared timer callback.
5896
Only ~15us of work per call (negligible blocking).
5997
Also detects timeouts from previous measurements.
6098
"""
6199
if self._waiting_for_echo:
62100
# Check if previous measurement timed out
63-
if time.ticks_diff(time.ticks_us(), self._trigger_time) > self.timeout_us:
101+
if self._ticks_diff(self._ticks_us(), self._trigger_time) > self.timeout_us:
64102
self.cms = self.MAX_VALUE
65103
self._waiting_for_echo = False
66104
self._first_reading_done = True
@@ -74,7 +112,7 @@ def _trigger_ping(self, t):
74112
self._trigger.value(1)
75113
self._delay_us(10)
76114
self._trigger.value(0)
77-
self._trigger_time = time.ticks_us()
115+
self._trigger_time = self._ticks_us()
78116
self._waiting_for_echo = True
79117

80118
def _echo_handler(self, pin):
@@ -85,11 +123,11 @@ def _echo_handler(self, pin):
85123
"""
86124
if pin.value() == 1:
87125
# Rising edge - echo pulse started
88-
self._echo_start = time.ticks_us()
126+
self._echo_start = self._ticks_us()
89127
else:
90128
# Falling edge - echo pulse ended
91129
if self._waiting_for_echo:
92-
pulse_time = time.ticks_diff(time.ticks_us(), self._echo_start)
130+
pulse_time = self._ticks_diff(self._ticks_us(), self._echo_start)
93131
if pulse_time > 0:
94132
# Sound speed 343.2 m/s = 0.034320 cm/us = 1cm per 29.1us
95133
# Divide by 2 because pulse travels to target and back
@@ -114,6 +152,6 @@ def _delay_us(self, delay:int):
114152
Custom implementation of time.sleep_us(), used to get around the bug in MicroPython where time.sleep_us()
115153
doesn't work properly and causes the IDE to hang when uploading the code
116154
"""
117-
start = time.ticks_us()
118-
while time.ticks_diff(time.ticks_us(), start) < delay:
155+
start = self._ticks_us()
156+
while self._ticks_diff(self._ticks_us(), start) < delay:
119157
pass

0 commit comments

Comments
 (0)