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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy