-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathokhi.pio
548 lines (471 loc) · 26.2 KB
/
okhi.pio
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
; MIT License - okhi - Open Keylogger Hardware Implant
; ---------------------------------------------------------------------------
; Copyright (c) [2024] by David Reguera Garcia aka Dreg
; https://github.com/therealdreg/okhi
; https://www.rootkit.es
; X @therealdreg
; ---------------------------------------------------------------------------
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in all
; copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.
; ---------------------------------------------------------------------------
; WARNING: BULLSHIT CODE X-)
; ---------------------------------------------------------------------------
; Dreg's note: I have tried to document everything as best as possible and make the code and project
; as accessible as possible for beginners. There may be errors (I am a lazy bastard using COPILOT)
; if you find any, please make a PR
; I'm still a novice with the Pico SDK & RP2040, so please bear with me if there are unnecessary things ;-)
; I want to avoid mental uncertainty when solving problems and knowing where things are,
; so ALL the programs in PIO0/1 are in the same place and order in each execution.
; Ref: http://www.Computer-Engineering.org (Author: Adam Chapweske)
; The Data and Clock lines are both open collector (pullup +5v).
; Data sent from the device to the host is read on the falling edge of the clock signal;
; data sent from the host to the device is read on the rising edge.
; The clock frequency must be in the range 10 - 16.7 kHz. This means clock must be
; high for 30 - 50 microseconds and low for 30 - 50 microseconds
; you should modify/sample the Data line in the middle of each cell.
; I.e. 15 - 25 microseconds after the appropriate clock transition.
; keyboard always generates the clock signal, but the host always has ultimate control over communication.
; Data = high, Clock = high: Idle state.
; Data = high, Clock = low: Communication Inhibited.
; Data = low, Clock = high: Host Request-to-Send
; All data is transmitted one byte at a time and each byte is sent in a frame consisting of 11-12 bits. These bits are:
; 1 start bit. This is always 0.
; 8 data bits, least significant bit first.
; 1 parity bit (odd parity).
; 1 stop bit. This is always 1.
; 1 acknowledge bit (host-to-device communication only)
; The clock frequency is 10-16.7 kHz. The time from the rising edge of a clock pulse to a Data transition must be at
; least 5 microseconds. The time from a data transition to the falling edge of a clock pulse must be at least 5 microseconds
; and no greater than 25 microseconds.
; The host may inhibit communication at any time by pulling the Clock line low for at least 100 microseconds.
; If a transmission is inhibited before the 11th clock pulse, the device must abort the current transmission and prepare
; to retransmit the current "chunk" of data when host releases Clock. A "chunk" of data could be a make code, break code,
; device ID, mouse movement packet, etc. For example, if a keyboard is interrupted while sending the second byte of a
; two-byte break code, it will need to retransmit both bytes of that break code, not just the one that was interrupted.
; If the host pulls clock low before the first high-to-low clock transition, or after the falling edge of the last clock pulse,
; the keyboard/mouse does not need to retransmit any data. However, if new data is created that needs to be transmitted,
; it will have to be buffered until the host releases Clock. Keyboards have a 16-byte buffer for this purpose.
; If more than 16 bytes worth of keystrokes occur, further keystrokes will be ignored until there's room in the buffer.
; PS/2 device always generates the clock signal. If the host wants to send data, it must first put the Clock and Data
; lines in a "Request-to-send" state as follows:
; Inhibit communication by pulling Clock low for at least 100 microseconds.
; Apply "Request-to-send" by pulling Data low, then release Clock.
; The device should check for this state at intervals not to exceed 10 milliseconds.
; host must follow to send data to a PS/2 device:
; 1) Bring the Clock line low for at least 100 microseconds.
; 2) Bring the Data line low.
; 3) Release the Clock line.
; 4) Wait for the device to bring the Clock line low.
; 5) Set/reset the Data line to send the first data bit
; 6) Wait for the device to bring Clock high.
; 7) Wait for the device to bring Clock low.
; 8) Repeat steps 5-7 for the other seven data bits and the parity bit
; 9) Release the Data line.
; 10) Wait for the device to bring Data low.
; 11) Wait for the device to bring Clock low.
; 12) Wait for the device to release Data and Clock
; ------
; WARNING: Do not remove lines that seem unnecessary (like mov isr, null).
; They cannot be optimized out due to potential issues with garbage values in the ISR register.
; MCU is changing PC register in any moment, so we need to avoid garbage values in the ISR register.
; In PS2 communication, data is transmitted least significant bit (LSB) first, requiring
; bit-by-bit sampling and right-shifting in the ISR to maintain the correct order in memory,
; ensuring the LSB arrives first and the most significant bit (MSB) ends up in the correct position
; when the full byte is reconstructed;
; however, this shifting places the complete 8-bit value in the most significant byte of the 32-bit word,
; and there is no automatic way to shift the data to the right and have the bits end up
; in the lower part of the word, so this process must be handled manually;
; in C, you can read this byte from:
; io_rw_8* rxfifo_shift = (io_rw_8*)&pio->rxf[sm] + 3
; where this offset (+3) accesses the top byte of the 32-bit word where the data resides;
; alternatively, adding an IN NULL, 24 instruction in the PIO code shifts in 24 zero bits
; to align the 8-bit data into bits [7:0] of the ISR before pushing, but since
; my PIO code does not include 'IN NULL, 24' (Whenever possible, you should save instructions in PIO)
; you must manually access the top byte as mentioned;
; By the way, you can also use DMA logic to grab that uppermost byte.
; ---- PIO0 AREA ---------------------------------------------------------------------
.program inhibited_signal
; PIO0_0, Must run at 1 MHz (each cycle ~1 microsecond)
; This PIO program monitors the PS/2 CLOCK and DAT lines to detect an
; "inhibited communication" condition: CLOCK stays LOW + DAT stays HIGH
; for ~100 microseconds.
; Normally, an inhibition period of around 100 µs would be used. However, to
; accommodate some PS/2-USB adapters with slightly shorter timing, this code
; uses ~90 µs instead because IMO is enough for our purposes.
; WARNING: 'mov isr, null' lines are ESSENTIAL for preventing garbage
; values in the ISR register. Make sure they are not removed or
; "optimized out" in actual usage.
; IRQ0: inhibited communication detected
; IRQ1: no RTS detected
; FIFO_JOIN_RX: RX FIFO length: 8 words deep, TX FIFO is disabled
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; JMP PIN : CLOCK
; Shift: shift ISR to right, autopush = false, threshold = 0 bits
.wrap_target
restart:
mov isr, null ; 00 Clear the ISR to remove any leftover data
wait_for_inhibited:
set x, 30 ; 01 At 1 MHz, each loop iteration ~3 µs => 30 * 3 = ~90 µs
check_inhibited:
jmp pin wait_for_inhibited ; 02 If CLOCK is HIGH, not inhibited => go back
jmp pin wait_for_inhibited ; 03 Double-check CLOCK is still LOW
jmp x-- check_inhibited ; 04 Decrement X, loop until time expires or CLOCK goes HIGH
irq nowait 0 ; 05 Trigger IRQ0: "Inhibited communication" detected
wait 1 pin 1 ; 06 Wait until CLOCK goes HIGH again
in pins, 1 ; 07 Sample DAT pin (1 bit) into the ISR
mov y, isr ; 08 Move the captured bit from ISR to register Y
jmp !y, restart ; 09 If DAT is LOW => valid RTS detected => restart
irq nowait 1 ; 10 If DAT is HIGH => "No RTS" => trigger IRQ1
.wrap ; Restart program
.program device_to_host
; PIO0_1, Must run ~133.6 kHz, 7.5 microseconds per cycle
; it is expected to have no fewer than 8 PIO state machine cycles for each keyboard
; clock cycle the data transition occurs when the Clock line is low
; This PIO program captures PS/2 packets from the keyboard (device) to the host.
; It listens for a start bit signal (external trigger), then reads 8 data bits,
; and finally skips the parity and stop bits. The 8 data bits are then pushed.
; On device-to-host communication, data is read on the falling edge of the clock signal (CLOCK LOW).
; Why the Host May Need to Reset This State Machine:
; - Some PS/2-USB adapters exhibit glitches and strange signals between valid
; packets.
; - If, during packet capture, communication is disrupted (INHIBIT, RTS, or IDLE),
; or if a glitch occurs, the host may decide to reset this SM before the push
; instruction executes, preventing garbage data from entering the FIFO. For this reason
; ISR is cleared at the beginning of the program because host can reset the SM at any time.
; Important Notes:
; - Do NOT move the 'push' instruction or enable AUTOPUSH unless you fully
; understand the side effects. Its current location has been validated in
; multiple scenarios.
; - Seemingly useless waits/delays are intentional. They provide time for the
; host to detect anomalies and reset this SM before an incorrect push occurs.
; - The jmp pin is helping to discard malformed data.
; WARNING: 'mov isr, null' lines are ESSENTIAL for preventing garbage
; values in the ISR register. Make sure they are not removed or
; "optimized out" in actual usage.
; FIFO_JOIN_RX: RX FIFO length: 8 words deep, TX FIFO is disabled
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; JMP PIN: EXTERNAL SIGNAL TO START CAPTURING
; shift ISR to right, autopush: false, threshold: 0 bits
.wrap_target
wait_for_start_signal:
jmp pin wait_for_start_signal ; 00 Keep looping until the external signal is received
mov isr, null ; 01 Clear the ISR of any residual bits before capturing a new packet
skip_start_bit:
wait 0 pin 1 ; 02 Wait for PS/2 CLOCK to go LOW (skip start-bit)
wait 1 pin 1 ; 03 Wait for PS/2 CLOCK to go HIGH (skip start-bit)
proceed_to_capture:
set x, 7 ; 04 Prepare scratch register X to count 8 bits
capture_8_bits_loop:
wait 0 pin 1 [1] ; 05 Wait for CLOCK LOW (falling edge)
in pins, 1 ; 06 Read 1 data bit from the DAT pin
wait 1 pin 1 [1] ; 07 Wait for CLOCK HIGH (rising edge)
jmp x-- capture_8_bits_loop ; 08 Loop until all 8 bits are captured
skip_parity_and_stop_bits:
wait 0 pin 1 [1] ; 09 Wait for CLOCK LOW of the parity bit
jmp pin wait_for_start_signal [1] ; 10 If pin is HIGH. The host inform us that we need to reset the SM before the PUSH
push noblock ; 11 Push the 8 captured bits
wait 1 pin 1 ; 12 Wait for CLOCK HIGH of the parity bit
wait 0 pin 1 [1] ; 13 Wait for CLOCK LOW of the stop bit
wait 1 pin 1 ; 14 Wait for CLOCK HIGH of the stop bit
.wrap ; Restart program
; ---- PIO1 AREA ---------------------------------------------------------------------
.program host_to_device
; PIO1_0, Must run ~133.6 kHz, 7.5 microseconds per cycle
; it is expected to have no fewer than 8 PIO state machine cycles for each keyboard clock cycle
; the data transition occurs when the Clock line is high
; This PIO program captures PS/2 packets from the host to the keyboard (device).
; On host-to-device communication, data is read on the rising edge of the clock signal (CLOCK HIGH).
; BUT! ACK bit is read on the falling edge of the clock signal (CLOCK LOW).
; Normally, an inhibition period of around 100 µs would be used. However, to
; accommodate some PS/2-USB adapters with slightly shorter timing, this code
; uses ~90 µs instead because IMO is enough for our purposes.
; Why the Host May Need to Reset This State Machine:
; - Some PS/2-USB adapters exhibit glitches and strange signals between valid
; packets.
; - If, during packet capture, communication is disrupted (INHIBIT, RTS, or IDLE),
; or if a glitch occurs, the host may decide to reset this SM before the push
; instruction executes, preventing garbage data from entering the FIFO. For this reason
; ISR is cleared at the beginning of the program because host can reset the SM at any time.
; Important Notes:
; - Do NOT move the 'push' instruction or enable AUTOPUSH unless you fully
; understand the side effects. Its current location has been validated in
; multiple scenarios.
; - Seemingly useless waits/delays are intentional. They provide time for the
; host to detect anomalies and reset this SM before an incorrect push occurs.
; WARNING: 'mov isr, null' lines are ESSENTIAL for preventing garbage
; values in the ISR register. Make sure they are not removed or
; "optimized out" in actual usage.
; FIFO_JOIN_RX: RX FIFO length: 8 words deep, TX FIFO is disabled
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; JMP PIN: CLOCK
; shift ISR to right, autopush: false, threshold: 0 bits
.wrap_target
mov isr, null ; 00 Clear the ISR to remove any leftover data
wait_for_inhibition:
set x, 5 ; 01 Each iteration of the loop is ~2 instructions => ~12 instructions total => ~90 µs
check_inhibition_loop:
jmp pin wait_for_inhibition ; 02 If CLOCK is HIGH => not inhibited => go back
jmp x--, check_inhibition_loop ; 03 Decrement X until ~90 µs or until CLOCK goes HIGH
skip_start_bit:
wait 1 pin 1 ; 04 Wait for CLOCK to go HIGH (skip start bit)
wait 0 pin 1 ; 05 Wait for CLOCK LOW (skip start bit)
proceed_to_capture:
set x, 7 ; 06 Prepare to capture 8 bits
capture_8_bits_loop:
wait 1 pin 1 [1] ; 07 Wait for CLOCK rising edge
in pins, 1 ; 08 Read 1 bit from DAT
wait 0 pin 1 [1] ; 09 Wait for CLOCK falling edge
jmp x-- capture_8_bits_loop ; 10 Decrement X until all 8 bits are captured
skip_parity_and_stop_bits:
wait 1 pin 1 [1] ; 11 Wait for CLOCK to go HIGH (skip parity bit)
push noblock ; 12 Push the captured 8 bits
wait 0 pin 1 ; 13 Wait for CLOCK to go LOW
wait 1 pin 1 [1] ; 14 Wait for CLOCK HIGH
skip_ack_bit:
wait 0 pin 1 [1] ; 15 Skip the ACK bit (CLOCK LOW)
; NOTE on the ACK Bit:
; The ACK bit transitions while CLOCK is HIGH, whereas other 11 bits
; transition while CLOCK is LOW. If you need to capture the ACK bit,
; replace lines 14-15 with something like:
; wait 0 pin 1 [1]
; in pins, 1
.wrap ; Restart program
.program idle_signal
; PIO1_1, must run at 1 MHz (each PIO cycle ~1 microsecond)
; This PIO program detects an IDLE state in PS/2 lines where both CLOCK and DAT
; remain HIGH for at least ~100 µs.
; ~90 µs is used here because IMO is enough for our purposes.
; IRQ0: IDLE detected
; FIFO_JOIN_RX: RX FIFO length: 8 words deep, TX FIFO is disabled
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; JMP PIN: CLOCK
.wrap_target
wait_for_idle:
set x, 30 ; 00 Prepare ~90 µs window (30 * 3 instructions @ 1 µs each)
check_idle_loop:
mov osr, !pins ; 01 Read and invert DAT & CLOCK (2 bits) into OSR
out y, 2 ; 02 Move these 2 inverted bits from OSR into register Y
jmp !y, next_idle_check ; 03 If y == 0 => original pins == 1 => DAT & CLOCK both HIGH => possible IDLE
not_idle_detected:
jmp wait_for_idle ; 04 If DAT or CLOCK wasn't HIGH => not IDLE => restart detection
next_idle_check:
jmp x--, check_idle_loop ; 05 Decrement X and re-check until ~90 µs or a LOW pin is found
idle_detected:
irq nowait 0 ; 06 IDLE confirmed => trigger IRQ0
.wrap ; Restart program
; PIO??? AREA ---------------------------------------------------------------------
.program two_pin_mirror_reverse_inverter
; PIO???, should run at the highest possible frequency.
; This program reads two consecutive input pins (DAT on pin 0, CLOCK on pin 1),
; then outputs their inverted states on two consecutive pins in reverse order:
; - OUT PIN 0: Inverted CLOCK
; - OUT PIN 1: Inverted DAT
;
; Essentially:
; 1 → 0
; 0 → 1
; and the pin positions are swapped.
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; OUT PIN 0: CLOCK (inverted)
; OUT PIN 1: DAT (inverted)
; NO USE AUTOPUSH, CONFIGURE THIS SM WITH DEFAULT PICO-SDK SETTINGS
.wrap_target
in pins, 2 ; 00 sample both pins
mov y, ::isr ; 01 store the value in Y in reverse order
mov pins, !y ; 02 output the inverted values
.wrap ; Restart the program
.program two_pin_inverter
; PIO???, should run at the highest possible frequency.
; This program reads two consecutive input pins (DAT on pin 0, CLOCK on pin 1),
; then outputs their inverted states on two consecutive pins in the same order:
; - OUT PIN 0: Inverted DAT
; - OUT PIN 1: Inverted CLOCK
;
; Essentially, the pin positions are mirrored, and the states are inverted.
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; OUT PIN 0: DAT (inverter)
; OUT PIN 1: CLOCK (inverter)
.wrap_target
mov pins, !pins ; 00 sample and output inverted values
.wrap ; Restart the program
.program two_pin_mirror
; PIO???, should run at the highest possible frequency.
; This program reads two consecutive input pins (DAT on pin 0, CLOCK on pin 1),
; then outputs their states on two consecutive pins in the same order:
; - OUT PIN 0: DAT
; - OUT PIN 1: CLOCK
;
; Essentially, the pin positions are mirrored.
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; OUT PIN 0: DAT
; OUT PIN 1: CLOCK
.wrap_target
mov pins, pins ; 00 sample and output the same values
.wrap ; Restart the program
.program two_pin_reverse
; PIO???, should run at the highest possible frequency.
; This program reads two consecutive input pins (DAT on pin 0, CLOCK on pin 1),
; then outputs their states on two consecutive pins in reverse order:
; - OUT PIN 0: CLOCK
; - OUT PIN 1: DAT
;
; Essentially, the pin positions are swapped.
; IN PIN 0: DAT
; IN PIN 1: CLOCK
; OUT PIN 0: CLOCK
; OUT PIN 1: DAT
; NO USE AUTOPUSH, CONFIGURE THIS SM WITH DEFAULT PICO-SDK SETTINGS
.wrap_target
in pins, 2 ; 00 sample both pins
mov pins, ::isr ; 01 output the values in reverse order
.wrap ; Restart the program
.program glitch_det_fast_rise
; PIO???, Must run at 8 MHz (each instruction takes 0.125 µs).
; It detects very short pulses ("glitches") on a clock input pin, measuring
; from a falling edge to the next rising edge. Glitches of ~8 µs or shorter
; are considered detected.
; - We need to observe a ~8 µs window, which corresponds to 64 instructions
; at 8 MHz (64 × 0.125 µs = 8 µs).
; - The loop uses 32 iterations, each taking 2 instructions,
; resulting in a total of 64 instructions for the detection window.
; - If the clock pin goes HIGH during these iterations, we register it
; as a glitch.
; Normal clock (no glitch):
; ___ _________ ________
; CLOCK: |________| |________| |________
;
; With a short glitch
; ___ __ _____ _________
; CLOCK: |________| || |________| |________
; ↑
; short glitch (~8 µs)
; The PIO program detects this fast rise (transition to HIGH)
; happening sooner than expected, which qualifies it as a glitch.
; The 'x' value is used to send the glitch event to the host and at same time is the time of the glitch.
; At 8MHz is a low resolution counter for very short pulses but it is better than nothing.
; AUTOPUSH must be true, 32 bit threshold
; IN PIN 0: CLOCK
; JMP PIN: CLOCK
.wrap_target
init_detection:
set x, 31 ; 00 Initialize X for 16 loop iterations
wait 1 pin, 0 ; 01 Ensure the CLOCK pin goes HIGH first (avoids false triggers)
wait 0 pin, 0 ; 02 Now wait until the CLOCK pin goes LOW (start point for measuring)
check_for_glitch:
jmp pin, glitch_detected ; 03 If the pin transitions HIGH prematurely, it's a glitch
jmp x--, check_for_glitch ; 04 If still LOW, repeat up to 16 times
jmp init_detection ; 05 No glitch detected in this window; restart
glitch_detected:
in x, 32 ; 06 Read the CLOCK pin to register the glitch event
.wrap ; Restart program
.program glitch_det_fast_fall
; PIO???, Must run at 8 MHz (each instruction takes 0.125 µs).
; The same as glitch_det_fast_rise but for detecting the opposite transition.
; Normal clock (no glitch):
; ___ _________ ________
; CLOCK: |________| |________| |________
;
; With a short glitch
; ___ _________ _________
; CLOCK: |___||___| |________| |________
; ↑
; short glitch (~8 µs)
; The PIO program detects this fast fall (transition to LOW)
; The 'x' value is used to send the glitch event to the host and at same time is the time of the glitch.
; At 8MHz is a low resolution counter for very short pulses but it is better than nothing.
; AUTOPUSH must be true, 32 bit threshold
; IN PIN 0: CLOCK
; JMP PIN: CLOCK
.wrap_target
init_detection:
set x, 31 ; 00 Initialize X for 16 loop iterations
wait 0 pin, 0 ; 01 Ensure the CLOCK pin goes LOW first (avoids false triggers)
wait 1 pin, 0 ; 02 Now wait until the CLOCK pin goes HIGH (start point for measuring)
check_for_glitch:
jmp pin, no_glitch_detected ; 03 If the pin transitions LOW prematurely, it's a glitch
glitch_detected:
in x, 32 ; 04 Read the CLOCK pin to register the glitch event
jmp init_detection ; 05 Restart detection
no_glitch_detected:
jmp x--, check_for_glitch ; 06 If still HIGH, repeat up to 16 times
.wrap ; Restart program
.program rise_counter
; PIO???, Must run at 125 MHz (each instruction takes 8 ns).
; This program measures the time between a falling edge and the next rising edge
; on the CLOCK pin by counting instructions. We do the following:
; 1) Initialize counter to 0xFFFFFFFF
; 2) Wait for the CLOCK pin to go HIGH, then LOW (i.e., a falling edge).
; 3) Enter a loop:
; - If pin goes HIGH (rising edge), jump to "capture_time".
; - Else decrement counter and keep looping until it hits zero.
; 4) Once the rising edge is detected, we push the current counter.
; This value indicates how many loop iterations remained.
; Time Calculation:
; - Each PIO instruction takes 8 ns at 125 MHz.
; - The loop uses 2 instructions per iteration, so each iteration = 16 ns.
; - The final measured time in nanoseconds is (0xFFFFFFFF - x) * 16 ns
; (since X started at 0xFFFFFFFF and was decremented each iteration).
; With a 16 ns resolution, this method is not perfect for measuring very short pulses.
; I have seen PS2-USB adapters with glitches shorter than 16 ns, but it's better than nothing.
; Overclocking RP to 250 MHz can increase the resolution to 8 ns.
; (Configuring the PIO clock divider to 1).
; TO-DO This SM should run in parallel and in a synchronized way with
; the glitch detection SM to know the time of the glitch.
; AUTOPUSH must be true, 32-bit threshold.
; IN PIN 0: CLOCK
; JMP PIN: CLOCK
.wrap_target
initialize_counter:
mov x, !null ; 00 X = 0xFFFFFFFF (maximum count value)
wait 1 pin, 0 ; 01 Wait until CLOCK pin goes HIGH (start of falling edge detection)
wait 0 pin, 0 ; 02 Wait until CLOCK pin goes LOW (falling edge)
count_loop:
jmp pin, capture_time ; 03 If pin goes HIGH (rising edge), jump to capture
jmp x--, count_loop ; 04 Decrement X and continue looping
capture_time:
in x, 32 ; 05 Push X into the RX FIFO
.wrap ; Restart program
.program fall_counter
; PIO???, Must run at 125 MHz (each instruction takes 8 ns).
; The same as rise_counter but for detecting the opposite transition.
; TO-DO This SM should run in parallel and in a synchronized way with
; the glitch detection SM to know the time of the glitch.
; AUTOPUSH must be true, 32-bit threshold.
; IN PIN 0: CLOCK
; JMP PIN: CLOCK
.wrap_target
initialize_counter:
mov x, !null ; 00 X = 0xFFFFFFFF (maximum count value)
wait 0 pin, 0 ; 01 Wait until CLOCK pin goes LOW
wait 1 pin, 0 ; 02 Then wait until CLOCK pin goes HIGH (rising edge)
count_loop:
jmp pin, check_fall ; 03 Continue looping while pin is HIGH
jmp capture_time ; 04 If pin goes LOW (falling edge), jump to capture
check_fall:
jmp x--, count_loop ; 05 Decrement X and continue looping
capture_time:
in x, 32 ; 06 Push X into the RX FIFO
.wrap ; Restart program