Hello @TheSilverBullet, thank you for sharing code.
Glad to find other inquiring minds I was trying to reproduce and improve
I constructed a PulseTimer class that has 2 period (=16 ns at 125 MHz) resolution. The idea is to use jmp(pin, LABEL) to get out of the loop with one instruction.
The same logic is used in
https://github.com/dhylands/upy-example ... measure.py btw., with an obscure delay [1], as I noticed now.
Code: Select all
class PulseTimer:
def __init__(self, pin, sm_id=4): # Pin should be a machine.Pin instance
self.sm = StateMachine(sm_id, self.sm_d_pulse, freq=125_000_000, in_base=pin, jmp_pin=pin)
self.sm.restart()
self.sm.active(1) # start the StateMachine
@asm_pio()
def sm_d_pulse():
wait(0, pin, 0) # in case pin is still high, at start, wait for low
label('start')
mov(x, null) # initialize X to zero
mov(x, invert(x))
wait(1, pin, 0) # now being low, wait for high, which starts the measurement
label('loop')
jmp(x_dec, 'here') # decrement x, jump nowhere; note that invert, decrement, invert = increment
label('here')
jmp(pin, 'loop') # in case pin is high, repeat counting
mov(isr, invert(x)) # we send the (again inverted) counter value to FIFO
push(noblock) # (in case of block: and halt, till it's read)
jmp('start')
def has_data(self):
return self.sm.rx_fifo() > 0
def read(self):
return self.sm.get() if self.sm.rx_fifo() else None
def duration_ns(self):
return self.sm.get()*16+8 if self.sm.rx_fifo() else None # additional 8: we tested
Idea: One could go extreme and employ 2 PIO state machines in parallel with changed order of jmp(x_dec, 'here') and jmp(pin, 'loop') to get 1 period resolution.
I observed that my assembler function did not turn off the pin in PWM mode. I measured small latencies also in the 9-17 us range for switching off programmatically.
So I had to use your viper code @TheSilverBullet. I learnt that the SIO has no way to enable output against special function PWM.
New assembler function is here:
Code: Select all
@micropython.asm_thumb
def _callb_pinclr_asm(r0): # r0 is the pin id: id(pinX)
ldr(r3, [r0, 4]) # r3 is pin_number now
data(2, 0x4d00) # ldr r5, [pc, #0] load r5 with data following the branch
b(HERE0)
data(4, 0x10005001) # r5 = GPIO0_CTRL: 0x40014004 but we put 0x40014004>>2 here
label(HERE0)
lsl(r5, r5, 2) # correct r5
lsl(r3, r3, 3)
add(r5, r5, r3) # r5 = GPIOx_CTRL of pin x given by r0
ldr(r2, [r5, 0]) # save CTRL bits in r2
mov(r1, 0x32)
lsl(r1, r1, 8) # r1 = (3<<12) | (2<<8)
str(r1, [r5, 0]) # override OEOVER=0x3: enable output and OUTOVER=0x2: drive output low in GPIO0_CTRL
nop()
nop()
nop() # 3 nops are enough for pin to settle !?!
# mov(r4, 3)
# label(LOOP)
# sub(r4, 1)
# cmp(r4, 0)
# bgt(LOOP)
str(r2, [r5, 0]) # restore CTRL bits in GPIOx_CTRL register
With these functions I did measurements at different PWM frequencies.
I also observed that the first 2-3 interrupts had excess latencies.
I found no such regularity as you in the measured latencies.
Statistical evaluation of latencies 11..2000 (excluding the 10 first) of PWM runs at different frequencies yielded:
Code: Select all
PWM_Frequ: 500 Min: 11224 Median: 15240.0 Max: 28328 Mean: 16567.5
PWM_Frequ: 1000 Min: 10008 Median: 15240.0 Max: 25912 Mean: 14701.7
PWM_Frequ: 2000 Min: 7576 Median: 15240.0 Max: 16056 Mean: 13546.3
PWM_Frequ: 5000 Min: 6760 Median: 7960.0 Max: 16056 Mean: 10152.8
PWM_Frequ: 10000 Min: 6760 Median: 7576.0 Max: 12168 Mean: 7569.5
PWM_Frequ: 20000 Min: 6760 Median: 6776.0 Max: 11080 Mean: 7101.5
PWM_Frequ: 30000 Min: 6760 Median: 6760.0 Max: 8104 Mean: 6844.3
PWM_Frequ: 40000 Min: 6248 Median: 6248.0 Max: 6248 Mean: 6248.0
You can see the latencies going down a lot with increasing PWM frequencies, which seems to confirm Roberts assumption that caching might be the reason for the long latencies at the beginning of runs.
Also I think that a max of 6.2 us in the "heavy duty" situation (and practically no variation) seems phenomenal. Unbelievable almost, could I have something missed?