Absence of a ‘scope ongoing, want a frequency counter which is not limited by ability to read out / print -> this will depend on sampling but that is fine. So, DMA to a static block of memory, then print from that block of memory as and when data are wanted. At the moment only printing high, but low would just involve a second SM.
from uctypes import addressof
from machine import mem32, Pin, PWM
from array import array
import rp2
import time
# set up clock signal
pin = Pin(0, Pin.OUT)
pwm = PWM(pin)
pwm.freq(1000)
pwm.duty_u16(0x8000)
# configure the scratch buffer - two spots, only use one
scratch = array("I", [0x0, 0x0])
address = addressof(scratch)
# DREQ definitions
DREQ_PIO0_RX0 = 4 << 15
# register definitions
PIO0_BASE = 0x50200000
PIO0_CTRL = PIO0_BASE + 0x00
PIO0_FSTAT = PIO0_BASE + 0x04
PIO0_RXF0 = PIO0_BASE + 0x20
# DMA registers
DMA_BASE = 0x50000000
CH0_READ_ADDR = DMA_BASE + 0x0
CH0_WRITE_ADDR = DMA_BASE + 0x4
CH0_TRANS_COUNT = DMA_BASE + 0x8
CH0_CTRL_TRIG = DMA_BASE + 0xC
QUIET = 0x1 << 21
DATA_SIZE = 0x2 << 2
ENABLE = 0x1
mem32[CH0_READ_ADDR] = PIO0_RXF0
mem32[CH0_WRITE_ADDR] = address
mem32[CH0_TRANS_COUNT] = 1 << 16
mem32[CH0_CTRL_TRIG] = QUIET + DREQ_PIO0_RX0 + DATA_SIZE + ENABLE
@rp2.asm_pio()
def count_high():
mov(x, invert(null))
jmp(x_dec, "wait")
label("wait")
wait(1, pin, 0)
nop()
label("high")
jmp(x_dec, "next")
label("next")
jmp(pin, "high")
mov(isr, x)
push()
sm = rp2.StateMachine(0, count_high, jmp_pin=pin, in_base=pin)
BUSY = 0x1 << 24
sm.active(1)
while mem32[CH0_CTRL_TRIG] & BUSY:
print("%d ns" % int((0xFFFFFFFF - scratch[0]) * 16.0))
time.sleep(0.2)
continue
sm.active(0)
Main utility of a proper frequency counter would be recording the frequency and duty cycle, for which recording the low and high together is necessary. Cannot do this with one state machine, but can do it with two of them:
from uctypes import addressof
from machine import mem32, Pin, PWM
from array import array
import rp2
import math
import time
import random
# set up clock signal
pin = Pin(0, Pin.OUT)
pwm = PWM(pin)
pwm.freq(1000)
pwm.duty_ns(500_000)
scratch = array("I", [0x0, 0x0])
address = addressof(scratch)
# DREQ definitions
DREQ_PIO0_RX0 = 4 << 15
DREQ_PIO0_RX1 = 5 << 15
# register definitions
PIO0_BASE = 0x50200000
PIO0_CTRL = PIO0_BASE + 0x00
PIO0_FSTAT = PIO0_BASE + 0x04
PIO0_RXF0 = PIO0_BASE + 0x20
PIO0_RXF1 = PIO0_BASE + 0x24
# DMA registers
DMA_BASE = 0x50000000
CH0_READ_ADDR = DMA_BASE + 0x0
CH0_WRITE_ADDR = DMA_BASE + 0x4
CH0_TRANS_COUNT = DMA_BASE + 0x8
CH0_CTRL_TRIG = DMA_BASE + 0xC
CH1_READ_ADDR = DMA_BASE + 0x40
CH1_WRITE_ADDR = DMA_BASE + 0x44
CH1_TRANS_COUNT = DMA_BASE + 0x48
CH1_CTRL_TRIG = DMA_BASE + 0x4C
QUIET = 0x1 << 21
DATA_SIZE = 0x2 << 2
ENABLE = 0x1
mem32[CH0_READ_ADDR] = PIO0_RXF0
mem32[CH0_WRITE_ADDR] = address
mem32[CH0_TRANS_COUNT] = 4000
mem32[CH0_CTRL_TRIG] = QUIET + DREQ_PIO0_RX0 + DATA_SIZE + ENABLE
mem32[CH1_READ_ADDR] = PIO0_RXF1
mem32[CH1_WRITE_ADDR] = address + 4
mem32[CH1_TRANS_COUNT] = 4000
mem32[CH1_CTRL_TRIG] = QUIET + DREQ_PIO0_RX1 + DATA_SIZE + ENABLE
@rp2.asm_pio()
def count_high():
mov(x, invert(null))
jmp(x_dec, "wait")
label("wait")
wait(1, pin, 0)
nop()
label("high")
jmp(x_dec, "next")
label("next")
jmp(pin, "high")
mov(isr, x)
push()
@rp2.asm_pio()
def count_low():
mov(x, invert(null))
jmp(x_dec, "wait")
label("wait")
wait(0, pin, 0)
label("next")
jmp(pin, "out")
jmp(x_dec, "next")
label("out")
mov(isr, x)
push()
sm0 = rp2.StateMachine(0, count_low, jmp_pin=pin, in_base=pin)
sm1 = rp2.StateMachine(1, count_high, jmp_pin=pin, in_base=pin)
BUSY = 0x1 << 24
mem32[PIO0_CTRL] = 0x3
while (mem32[CH0_CTRL_TRIG] & BUSY) or (mem32[CH1_CTRL_TRIG] & BUSY):
time.sleep(0.5)
low = int((0xFFFFFFFF - scratch[0]) * 16.0)
high = int((0xFFFFFFFF - scratch[1]) * 16.0)
t = high + low
f = 1.0e9 / t
d = high / t
print(f"{d:.3f} @ {f:.3f}Hz")
continue
mem32[PIO0_CTRL] = 0x0
Pretty happy with this - the rearrangement of the PIO loop was particularly pleasing, though I am not sure yet this is cycle exact (I need to have a fresh cup of coffee and a really careful count).
This will do: 0.500 @ 1000.000Hz. Obviously I would ideally connect it to an external or more noisy source, perhaps a Python loop running on thread 2 toggling the GPIO…