Skip to content

Commit 86b2243

Browse files
committed
Add NotifierDelay
Add a version of PreciseDelay that uses FPGA interrupts instead of busy waiting. Caveat: Users must manually clean up the notifier handle once this object is no longer in use. Also change our usage of PreciseDelay to NotifierDelay. Also use NotifierDelay in magicbot's test mode to avoid drift.
1 parent 417ba4e commit 86b2243

File tree

4 files changed

+56
-9
lines changed

4 files changed

+56
-9
lines changed

magicbot/magicrobot.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import wpilib
77

8-
from robotpy_ext.misc import PreciseDelay
8+
from robotpy_ext.misc import NotifierDelay
99
from robotpy_ext.autonomous import AutonomousModeSelector
1010

1111
from robotpy_ext.misc.orderedclass import OrderedClass
@@ -295,7 +295,7 @@ def disabled(self):
295295
ds_attached = None
296296
wpilib.LiveWindow.setEnabled(False)
297297

298-
delay = PreciseDelay(self.control_loop_wait_time)
298+
delay = NotifierDelay(self.control_loop_wait_time)
299299

300300
self._on_mode_disable_components()
301301
try:
@@ -318,6 +318,8 @@ def disabled(self):
318318
self.robotPeriodic()
319319
delay.wait()
320320

321+
delay.free()
322+
321323
def operatorControl(self):
322324
"""
323325
This function is called in teleoperated mode. You should not
@@ -333,7 +335,7 @@ def operatorControl(self):
333335
self.__nt.putBoolean('is_ds_attached', self.ds.isDSAttached())
334336
wpilib.LiveWindow.setEnabled(False)
335337

336-
delay = PreciseDelay(self.control_loop_wait_time)
338+
delay = NotifierDelay(self.control_loop_wait_time)
337339

338340
# initialize things
339341
self._on_mode_enable_components()
@@ -356,6 +358,7 @@ def operatorControl(self):
356358

357359
delay.wait()
358360

361+
delay.free()
359362
self._on_mode_disable_components()
360363

361364
def test(self):
@@ -365,6 +368,8 @@ def test(self):
365368
self.__nt.putBoolean('is_ds_attached', self.ds.isDSAttached())
366369
wpilib.LiveWindow.setEnabled(True)
367370

371+
delay = NotifierDelay(self.control_loop_wait_time)
372+
368373
try:
369374
self.testInit()
370375
except:
@@ -378,7 +383,9 @@ def test(self):
378383

379384
self._update_feedback()
380385
self.robotPeriodic()
381-
wpilib.Timer.delay(self.control_loop_wait_time)
386+
delay.wait()
387+
388+
delay.free()
382389

383390
def _on_mode_enable_components(self):
384391
# initialize things

robotpy_ext/autonomous/selector.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import logging
88
logger = logging.getLogger('autonomous')
99

10-
from ..misc.precise_delay import PreciseDelay
10+
from ..misc.precise_delay import NotifierDelay
1111

1212
import wpilib
1313

@@ -227,7 +227,7 @@ def run(self, control_loop_wait_time=0.020, iter_fn=None, on_exception=None):
227227
# Autonomous control loop
228228
#
229229

230-
delay = PreciseDelay(control_loop_wait_time)
230+
delay = NotifierDelay(control_loop_wait_time)
231231

232232
while self.ds.isAutonomous() and self.ds.isEnabled():
233233

@@ -254,6 +254,7 @@ def run(self, control_loop_wait_time=0.020, iter_fn=None, on_exception=None):
254254
on_exception(forceReport=True)
255255

256256
logger.info("Autonomous mode ended")
257+
delay.free()
257258

258259
#
259260
# Internal methods used to implement autonomous mode switching, and

robotpy_ext/misc/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11

2-
from .precise_delay import PreciseDelay
2+
from .precise_delay import NotifierDelay, PreciseDelay
33
from .periodic_filter import PeriodicFilter

robotpy_ext/misc/precise_delay.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,44 @@ def _wait_unit_tests(self):
7777
# - TODO: should we just always use this in simulated mode?
7878

7979
wpilib.Timer.delay(self.delay_period)
80-
81-
80+
81+
82+
class NotifierDelay:
83+
"""Synchronizes a timing loop against interrupts from the FPGA.
84+
85+
This will delay so that the next invocation of your loop happens at
86+
precisely the same period, assuming that your loop does not take longer
87+
than the specified period.
88+
"""
89+
90+
def __init__(self, delay_period: float) -> None:
91+
""":param delay_period: The period's amount of time (in seconds)."""
92+
if delay_period < 0.001:
93+
raise ValueError("You probably don't want to delay less than 1ms!")
94+
95+
# Convert the delay period to microseconds, as FPGA timestamps are microseconds
96+
self.delay_period = int(delay_period * 1e6)
97+
self._notifier = hal.initializeNotifier()
98+
self._expiry_time = wpilib.RobotController.getFPGATime() + self.delay_period
99+
self._update_alarm()
100+
101+
wpilib.Resource._add_global_resource(self)
102+
103+
def free(self) -> None:
104+
"""Clean up the notifier.
105+
106+
Call this method once you are done using this object.
107+
Do not use this object after this method is called.
108+
"""
109+
handle = self._notifier
110+
hal.stopNotifier(handle)
111+
hal.cleanNotifier(handle)
112+
113+
def wait(self) -> None:
114+
"""Wait until the delay period has passed."""
115+
hal.waitForNotifierAlarm(self._notifier)
116+
self._expiry_time += self.delay_period
117+
self._update_alarm()
118+
119+
def _update_alarm(self) -> None:
120+
hal.updateNotifierAlarm(self._notifier, self._expiry_time)

0 commit comments

Comments
 (0)