Skip to content

Commit e444725

Browse files
committed
monitor: Reduce latency, adapt for future SPI option.
1 parent 47944b2 commit e444725

File tree

4 files changed

+69
-36
lines changed

4 files changed

+69
-36
lines changed

v3/as_demos/monitor/README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ of 100ms - pin 28 will pulse if ident 0 is inactive for over 100ms. These
187187
behaviours can be modified by the following `run` args:
188188
1. `period=100` Define the timer period in ms.
189189
2. `verbose=()` Determines which `ident` values should produce console output.
190+
3. `device="uart"` Provides for future use of other interfaces.
190191

191192
Thus to run such that idents 4 and 7 produce console output, with hogging
192193
reported if blocking is for more than 60ms, issue
@@ -195,7 +196,12 @@ from monitor_pico import run
195196
run(60, (4, 7))
196197
```
197198

198-
# 5. Design notes
199+
# 5. Performance and design notes
200+
201+
The latency between a monitored coroutine starting to run and the Pico pin
202+
going high is about 20μs. This isn't as absurd as it sounds: theoretically the
203+
latency could be negative as the effect of the decorator is to send the
204+
character before the coroutine starts.
199205

200206
The use of decorators is intended to ease debugging: they are readily turned on
201207
and off by commenting out.
@@ -213,3 +219,11 @@ which can be scheduled at a high rate, can't overflow the UART buffer. The
213219

214220
This project was inspired by
215221
[this GitHub thread](https://github.com/micropython/micropython/issues/7456).
222+
223+
# 6. Work in progress
224+
225+
It is intended to add an option for SPI communication; `monitor.py` has a
226+
`set_device` method which can be passed an instance of an initialised SPI
227+
object. The Pico `run` method will be able to take a `device="spi"` arg which
228+
will expect an SPI connection on pins 0 (sck) and 1 (data). This requires a
229+
limited implementation of an SPI slave using the PIO, which I will do soon.

v3/as_demos/monitor/monitor.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
import uasyncio as asyncio
88
from machine import UART
99

10-
uart = None
10+
device = None
1111
def set_uart(n): # Monitored app defines interface
12-
global uart
13-
uart = UART(n, 1_000_000)
12+
global device
13+
device = UART(n, 1_000_000)
14+
15+
# For future use with SPI
16+
# Pass initialised instance of some device
17+
def set_device(dev):
18+
global device
19+
device = dev
1420

1521
_available = set(range(0, 23)) # Valid idents are 0..22
1622

@@ -38,22 +44,22 @@ async def wrapped_coro(*args, **kwargs):
3844
instance += 1
3945
if instance > max_instances:
4046
print(f'Monitor {n:02} max_instances reached')
41-
uart.write(v)
47+
device.write(v)
4248
try:
4349
res = await coro(*args, **kwargs)
4450
except asyncio.CancelledError:
4551
raise
4652
finally:
4753
d |= 0x20
4854
v = bytes(chr(d), 'utf8')
49-
uart.write(v)
55+
device.write(v)
5056
instance -= 1
5157
return res
5258
return wrapped_coro
5359
return decorator
5460

5561
def monitor_init():
56-
uart.write(b'z')
62+
device.write(b'z')
5763

5864
# Optionally run this to show up periods of blocking behaviour
5965
@monitor(0)
@@ -73,9 +79,9 @@ def decorator(func):
7379
dend = 0x60 + n
7480
vend = bytes(chr(dend), 'utf8')
7581
def wrapped_func(*args, **kwargs):
76-
uart.write(vstart)
82+
device.write(vstart)
7783
res = func(*args, **kwargs)
78-
uart.write(vend)
84+
device.write(vend)
7985
return res
8086
return wrapped_func
8187
return decorator
@@ -92,9 +98,9 @@ def __init__(self, n):
9298
self.vend = bytes(chr(self.dend), 'utf8')
9399

94100
def __enter__(self):
95-
uart.write(self.vstart)
101+
device.write(self.vstart)
96102
return self
97103

98104
def __exit__(self, type, value, traceback):
99-
uart.write(self.vend)
105+
device.write(self.vend)
100106
return False # Don't silence exceptions

v3/as_demos/monitor/monitor_pico.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
# Pin goes high if use count > 0 else low.
1010
# incoming numbers are 0..22 which map onto 23 GPIO pins
1111

12-
from machine import UART, Pin, Timer
12+
from machine import UART, Pin, Timer, freq
13+
14+
freq(250_000_000)
1315

1416
# Valid GPIO pins
1517
# GP0,1 are UART 0 so pins are 2..22, 26..27
1618
PIN_NOS = list(range(2,23)) + list(range(26, 28))
17-
uart = UART(0, 1_000_000) # rx on GP1
1819

1920
pin_t = Pin(28, Pin.OUT)
2021
def _cb(_):
@@ -30,29 +31,37 @@ def _cb(_):
3031
for pin_no in PIN_NOS:
3132
pins.append([Pin(pin_no, Pin.OUT), 0, False])
3233

33-
def run(period=100, verbose=[]):
34+
# native reduced latency to 10μs but killed the hog detector: timer never timed out.
35+
# Also locked up Pico so ctrl-c did not interrupt.
36+
#@micropython.native
37+
def run(period=100, verbose=[], device="uart"):
3438
global t_ms
3539
t_ms = period
3640
for x in verbose:
3741
pins[x][2] = True
42+
# Provide for future devices. Must support a blocking read.
43+
if device == "uart":
44+
uart = UART(0, 1_000_000) # rx on GPIO 1
45+
def read():
46+
while not uart.any():
47+
pass
48+
return ord(uart.read(1))
49+
3850
while True:
39-
while not uart.any():
40-
pass
41-
x = ord(uart.read(1))
42-
if not 0x40 <= x <= 0x7f: # Get an initial 0
43-
continue
44-
if x == 0x7a: # Init: program under test has restarted
45-
for w in range(len(pins)):
46-
pins[w][1] = 0
47-
continue
48-
if x == 0x40:
49-
tim.init(period=t_ms, mode=Timer.ONE_SHOT, callback=_cb)
50-
i = x & 0x1f # Key: 0x40 (ord('@')) is pin ID 0
51-
d = -1 if x & 0x20 else 1
52-
pins[i][1] += d
53-
if pins[i][1]: # Count > 0 turn pin on
54-
pins[i][0](1)
55-
else:
56-
pins[i][0](0)
57-
if pins[i][2]:
58-
print(f'ident {i} count {pins[i][1]}')
51+
if x := read(): # Get an initial 0 on UART
52+
if x == 0x7a: # Init: program under test has restarted
53+
for pin in pins:
54+
pin[1] = 0
55+
continue
56+
if x == 0x40: # Retrigger hog detector.
57+
tim.init(period=t_ms, mode=Timer.ONE_SHOT, callback=_cb)
58+
p = pins[x & 0x1f] # Key: 0x40 (ord('@')) is pin ID 0
59+
if x & 0x20: # Going down
60+
p[1] -= 1
61+
if not p[1]: # Instance count is zero
62+
p[0](0)
63+
else:
64+
p[0](1)
65+
p[1] += 1
66+
if p[2]:
67+
print(f'ident {i} count {p[1]}')

v3/as_demos/monitor/quick_test.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import uasyncio as asyncio
44
import time
5+
from machine import Pin
56
from monitor import monitor, monitor_init, hog_detect, set_uart
67

78
set_uart(2) # Define interface to use
89

910
@monitor(1)
10-
async def foo(t):
11+
async def foo(t, pin):
12+
pin(1) # Measure latency
13+
pin(0)
1114
await asyncio.sleep_ms(t)
1215

1316
@monitor(2)
@@ -22,10 +25,11 @@ async def bar(t):
2225

2326
async def main():
2427
monitor_init()
28+
test_pin = Pin('X6', Pin.OUT)
2529
asyncio.create_task(hog_detect())
2630
asyncio.create_task(hog()) # Will hog for 500ms after 5 secs
2731
while True:
28-
ft = asyncio.create_task(foo(100))
32+
asyncio.create_task(foo(100, test_pin))
2933
await bar(150)
3034
await asyncio.sleep_ms(50)
3135

0 commit comments

Comments
 (0)