3
3
This library provides a means of examining the behaviour of a running
4
4
` uasyncio ` system. The device under test is linked to a Raspberry Pi Pico. The
5
5
latter displays the behaviour of the host by pin changes and/or optional print
6
- statements. Communication with the Pico is uni-directional via a UART so only a
7
- single GPIO pin is used - at last a use for the ESP8266 transmit-only UART(1) .
6
+ statements. A logic analyser or scope provides an insight into the way an
7
+ asynchronous application is working .
8
8
9
- A logic analyser or scope provides an insight into the way an asynchronous
10
- application is working.
9
+ Communication with the Pico may be by UART or SPI, and is uni-directional from
10
+ system under test to Pico. If a UART is used only one GPIO pin is used; at last
11
+ a use for the ESP8266 transmit-only UART(1). SPI requires three - mosi, sck and
12
+ cs/.
11
13
12
14
Where an application runs multiple concurrent tasks it can be difficult to
13
15
locate a task which is hogging CPU time. Long blocking periods can also result
14
16
from several tasks each of which can block for a period. If, on occasion, these
15
17
are scheduled in succession, the times can add. The monitor issues a trigger
16
- when the blocking period exceeds a threshold. With a logic analyser the system
17
- state at the time of the transient event may be examined.
18
+ pulse when the blocking period exceeds a threshold. With a logic analyser the
19
+ system state at the time of the transient event may be examined.
18
20
19
21
The following image shows the ` quick_test.py ` code being monitored at the point
20
22
when a task hogs the CPU. The top line 00 shows the "hog detect" trigger. Line
21
23
02 shows the fast running ` hog_detect ` task which cannot run at the time of the
22
- trigger. Lines 01 and 03 show the ` foo ` and ` bar ` tasks.
24
+ trigger because another task is hogging the CPU. Lines 01 and 03 show the ` foo `
25
+ and ` bar ` tasks.
23
26
![ Image] ( ./monitor.jpg )
24
27
28
+ ### Breaking changes to support SPI
29
+
30
+ The ` set_uart ` method is replaced by ` set_device ` . Pin mappings on the Pico
31
+ have changed.
32
+
25
33
## 1.1 Pre-requisites
26
34
27
35
The device being monitored must run firmware V1.17 or later. The ` uasyncio `
28
- version should be V3 (as included in the firmware).
36
+ version should be V3 (included in the firmware).
29
37
30
38
## 1.2 Usage
31
39
32
- Example script ` quick_test.py ` provides a usage example.
40
+ Example script ` quick_test.py ` provides a usage example. It may be adapted to
41
+ use a UART or SPI interface: see commented-out code.
42
+
43
+ ### 1.2.1 Interface selection set_device()
33
44
34
- An application to be monitored typically has the following setup code:
45
+ An application to be monitored needs setup code to initialise the interface.
46
+ This comprises a call to ` monitor.set_device ` with an initialised UART or SPI
47
+ device. The Pico must be set up to match the interface chosen on the host: see
48
+ [ section 4] ( ./README.md#4-the-pico-code ) .
49
+
50
+ In the case of a UART an initialised UART with 1MHz baudrate is passed:
51
+ ``` python
52
+ from machine import UART
53
+ from monitor import monitor, monitor_init, hog_detect, set_device
54
+ set_device(UART(2 , 1_000_000 )) # Baudrate MUST be 1MHz.
55
+ ```
56
+ In the case of SPI initialised SPI and cs/ Pin instances are passed:
35
57
``` python
36
- from monitor import monitor, monitor_init, hog_detect, set_uart
37
- set_uart(2 ) # Define device under test UART no.
58
+ from machine import Pin, SPI
59
+ from monitor import monitor, monitor_init, hog_detect, set_device
60
+ set_device(SPI(2 , baudrate = 5_000_000 ), Pin(' X6' , Pin.OUT )) # Device under test SPI
38
61
```
39
- On application start it should issue
62
+ The SPI instance must have default args; the one exception being baudrate which
63
+ may be any value. I have tested up to 30MHz but there is no benefit in running
64
+ above 1MHz. Hard or soft SPI may be used. It should be possible to share the
65
+ bus with other devices, although I haven't tested this.
66
+
67
+ ### 1.2.2 Monitoring
68
+
69
+ On startup, after defining the interface, an application should issue:
40
70
``` python
41
71
monitor_init()
42
72
```
@@ -52,14 +82,14 @@ The decorator args are as follows:
52
82
2 . An optional arg defining the maximum number of concurrent instances of the
53
83
task to be independently monitored (default 1).
54
84
55
- Whenever the code runs, a pin on the Pico will go high, and when the code
85
+ Whenever the coroutine runs, a pin on the Pico will go high, and when the code
56
86
terminates it will go low. This enables the behaviour of the system to be
57
87
viewed on a logic analyser or via console output on the Pico. This behavior
58
88
works whether the code terminates normally, is cancelled or has a timeout.
59
89
60
90
In the example above, when ` my_coro ` starts, the pin defined by ` ident==2 `
61
- (GPIO 4 ) will go high. When it ends, the pin will go low. If, while it is
62
- running, a second instance of ` my_coro ` is launched, the next pin (GPIO 5 ) will
91
+ (GPIO 5 ) will go high. When it ends, the pin will go low. If, while it is
92
+ running, a second instance of ` my_coro ` is launched, the next pin (GPIO 6 ) will
63
93
go high. Pins will go low when the relevant instance terminates, is cancelled,
64
94
or times out. If more instances are started than were specified to the
65
95
decorator, a warning will be printed on the host. All excess instances will be
@@ -87,9 +117,9 @@ will cause the pin to go high for 30s, even though the task is consuming no
87
117
resources for that period.
88
118
89
119
To provide a clue about CPU hogging, a ` hog_detect ` coroutine is provided. This
90
- has ` ident=0 ` and, if used, is monitored on GPIO 2 . It loops, yielding to the
120
+ has ` ident=0 ` and, if used, is monitored on GPIO 3 . It loops, yielding to the
91
121
scheduler. It will therefore be scheduled in round-robin fashion at speed. If
92
- long gaps appear in the pulses on GPIO 2 , other tasks are hogging the CPU.
122
+ long gaps appear in the pulses on GPIO 3 , other tasks are hogging the CPU.
93
123
Usage of this is optional. To use, issue
94
124
``` python
95
125
import uasyncio as asyncio
@@ -139,55 +169,61 @@ It is advisable not to use the context manager with a function having the
139
169
140
170
# 3. Pico Pin mapping
141
171
142
- The Pico GPIO numbers start at 2 to allow for UART(0) and also have a gap where
143
- GPIO's are used for particular purposes. This is the mapping between ` ident `
144
- GPIO no. and Pico PCB pin, with the pins for the timer and the UART link also
172
+ The Pico GPIO numbers used by idents start at 3 and have a gap where the Pico
173
+ uses GPIO's for particular purposes. This is the mapping between ` ident ` GPIO
174
+ no. and Pico PCB pin. Pins for the timer and the UART/SPI link are also
145
175
identified:
146
176
147
- | ident | GPIO | pin |
148
- | :-----:| :----:| :----:|
149
- | uart | 1 | 2 |
150
- | 0 | 2 | 4 |
151
- | 1 | 3 | 5 |
152
- | 2 | 4 | 6 |
153
- | 3 | 5 | 7 |
154
- | 4 | 6 | 9 |
155
- | 5 | 7 | 10 |
156
- | 6 | 8 | 11 |
157
- | 7 | 9 | 12 |
158
- | 8 | 10 | 14 |
159
- | 9 | 11 | 15 |
160
- | 10 | 12 | 16 |
161
- | 11 | 13 | 17 |
162
- | 12 | 14 | 19 |
163
- | 13 | 15 | 20 |
164
- | 14 | 16 | 21 |
165
- | 15 | 17 | 22 |
166
- | 16 | 18 | 24 |
167
- | 17 | 19 | 25 |
168
- | 18 | 20 | 26 |
169
- | 19 | 21 | 27 |
170
- | 20 | 22 | 29 |
171
- | 21 | 26 | 31 |
172
- | 22 | 27 | 32 |
173
- | timer | 28 | 34 |
174
-
175
- The host's UART ` txd ` pin should be connected to Pico GPIO 1 (pin 2). There
176
- must be a link between ` Gnd ` pins on the host and Pico.
177
+ | ident | GPIO | pin |
178
+ | :-------:| :----:| :----:|
179
+ | nc/mosi | 0 | 1 |
180
+ | rxd/sck | 1 | 2 |
181
+ | nc/cs/ | 2 | 4 |
182
+ | 0 | 3 | 5 |
183
+ | 1 | 4 | 6 |
184
+ | 2 | 5 | 7 |
185
+ | 3 | 6 | 9 |
186
+ | 4 | 7 | 10 |
187
+ | 5 | 8 | 11 |
188
+ | 6 | 9 | 12 |
189
+ | 7 | 10 | 14 |
190
+ | 8 | 11 | 15 |
191
+ | 9 | 12 | 16 |
192
+ | 10 | 13 | 17 |
193
+ | 11 | 14 | 19 |
194
+ | 12 | 15 | 20 |
195
+ | 13 | 16 | 21 |
196
+ | 14 | 17 | 22 |
197
+ | 15 | 18 | 24 |
198
+ | 16 | 19 | 25 |
199
+ | 17 | 20 | 26 |
200
+ | 18 | 21 | 27 |
201
+ | 19 | 22 | 29 |
202
+ | 20 | 26 | 31 |
203
+ | 21 | 27 | 32 |
204
+ | timer | 28 | 34 |
205
+
206
+ For a UART interface the host's UART ` txd ` pin should be connected to Pico GPIO
207
+ 1 (pin 2).
208
+
209
+ For SPI the host's ` mosi ` goes to GPIO 0 (pin 1), and ` sck ` to GPIO 1 (pin 2).
210
+ The host's CS Pin is connected to GPIO 2 (pin 4).
211
+
212
+ There must be a link between ` Gnd ` pins on the host and Pico.
177
213
178
214
# 4. The Pico code
179
215
180
- Monitoring of the UART with default behaviour is started as follows:
216
+ Monitoring via the UART with default behaviour is started as follows:
181
217
``` python
182
218
from monitor_pico import run
183
219
run()
184
220
```
185
221
By default the Pico does not produce console output and the timer has a period
186
222
of 100ms - pin 28 will pulse if ident 0 is inactive for over 100ms. These
187
223
behaviours can be modified by the following ` run ` args:
188
- 1 . ` period=100 ` Define the timer period in ms.
224
+ 1 . ` period=100 ` Define the hog_detect timer period in ms.
189
225
2 . ` verbose=() ` Determines which ` ident ` values should produce console output.
190
- 3 . ` device="uart" ` Provides for future use of other interfaces .
226
+ 3 . ` device="uart" ` Set to "spi" for an SPI interface .
191
227
192
228
Thus to run such that idents 4 and 7 produce console output, with hogging
193
229
reported if blocking is for more than 60ms, issue
@@ -198,10 +234,12 @@ run(60, (4, 7))
198
234
199
235
# 5. Performance and design notes
200
236
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.
237
+ Using a UART the latency between a monitored coroutine starting to run and the
238
+ Pico pin going high is about 23μs. With SPI I measured -12μs. This isn't as
239
+ absurd as it sounds: a negative latency is the effect of the decorator which
240
+ sends the character before the coroutine starts. These values are small in the
241
+ context of ` uasyncio ` : scheduling delays are on the order of 150μs or greater
242
+ depending on the platform. See ` quick_test.py ` for a way to measure latency.
205
243
206
244
The use of decorators is intended to ease debugging: they are readily turned on
207
245
and off by commenting out.
@@ -219,11 +257,3 @@ which can be scheduled at a high rate, can't overflow the UART buffer. The
219
257
220
258
This project was inspired by
221
259
[ 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.
0 commit comments