Here is an example that emulates piezo tone ringers of 1980s phones (like Telefon 01 or Tritel 85 series, etc.) using 2 state machines of any PIO using RX-FIFOs in TXGET mode. One state machine emulates a tone ringer IC (like U4076B/SAA1094-2/M1094, TCM1532, etc.) that plays a repeated 2-4 tone sequence. The other state machine creates the ringing cadences (like for EU/GB/US); basically an extended LED blinker. The inputs are actually inverted open-drain outputs with pull-ups, so that they can be controlled with switches or the Pico 2 itself (only the large passive piezo-ceramic transducer is required for the demo):
Code:
import rp2class StateMachine(rp2.StateMachine): # augment with putget() def __init__(self, id, *args, **kwargs): super().__init__(id) self._RXF_PUTGET = 0x5020_0128 + (id >> 2 << 20) + (id & 3) * 0x10 if args or kwargs: self.init(*args, **kwargs) def putget(self, index, value=None): if value is not None: machine.mem32[self._RXF_PUTGET + index * 4] = value return machine.mem32[self._RXF_PUTGET + index * 4]def gcd(a, b): # partially emulate math.gcd() return gcd(b, a % b) if b != 0 else adef lcm(x, *y): # partially emulate math.lcm() return lcm(x // gcd(x, y[0]) * y[0], *y[1:]) if y else xclass ToneRinger: def __init__(self, id, pin, out_base, rate=9, freq=1625/6, sequence=(3, 4, 5)): self.rate = self.freq = self.sequence = None self.sm = StateMachine(id, self.tone_sequence, in_base=out_base, out_base=out_base, jmp_pin=pin) self(rate, freq, sequence, True) # sequence rate/Hz, ratio 1 tuning/Hz, integer ratio tone sequence def __call__(self, rate=None, freq=None, sequence=None, restart=False): self.rate = rate or self.rate self.freq = freq or self.freq self.sequence = sequence or self.sequence common = lcm(*self.sequence) divisor = round(machine.freq() / (common * 2 * self.freq)) factor = round(machine.freq() / (common * divisor * self.rate)) delay = [common // i * divisor - 7 for i in self.sequence] repeats = [i * factor for i in self.sequence] while len(delay) < 4: # pad with single-repeat delays delay = [delay[0], delay[0] - 3] + delay[1:] repeats = [repeats[0] - 1, 0] + repeats[1:] if restart: self.sm.active(False) for i in range(4): # poke values reversed self.sm.putget(i, delay[3 - i] << 12 | repeats[3 - i]) if restart: self.sm.restart() self.sm.active(True) @staticmethod @rp2.asm_pio(out_init=[rp2.PIO.OUT_LOW]*2, out_shiftdir=rp2.PIO.SHIFT_RIGHT) def tone_sequence(): label("restart") mov (pins, null) # difference 0 on out pins set (x, 0b01) # out pins order is +/- set (y, 3) # start with 1st in reversed sequence label("wait") jmp (pin, "wait") mov (pins, x) wrap_target() label("wrap_target") word (0x8090) # mov (osr, rxfifo[y]) out (x, 12) # repeats (square wave half-cycles): 12 lower bits label("tone") mov (isr, y) # save y mov (y, osr) # delay cycles (per half-cycle): 20 upper bits label("delay") jmp (y_dec, "delay") mov (y, isr) # restore y jmp (pin, "restart") mov (pins, invert(pins)) # toggle pins (in_base == out_base) jmp (x_dec, "tone") jmp (y_dec, "wrap_target") wrap()ToneRinger.tone_sequence[4] |= 1 << 14 # fifo_join=rp2.PIO.JOIN_TXGET fixupclass CadenceGenerator: def __init__(self, id, pin, out_base, cadence=(1, 4)): self.sm = StateMachine(id, self.ring_sequence, out_base=out_base, jmp_pin=pin) self(cadence, True) # cadence sequence/s def __call__(self, cadence=None, restart=True): if cadence: delay = [round((machine.freq() * i - 8) / 2) for i in cadence] while len(delay) < 4: # pad with 0-delays delay = delay[:-1] + [0, delay[-1] - 2] if restart: self.sm.active(False) if cadence: for i in range(4): # poke values reversed self.sm.putget(i, delay[3 - i]) if restart: self.sm.restart() self.sm.active(True) @rp2.asm_pio(out_init=[rp2.PIO.IN_LOW]*1) # L-type open-circuit output (inverted open-drain) def ring_sequence(): label("restart") word (0xa063) # mov (pindirs, null) mov (isr, null) # state for pindirs (because it is not readable) set (y, 3) # start with 1st in reversed cadence label("wait") jmp (pin, "wait") word (0xa06b) # mov (pindirs, invert(null)) wrap_target() label("wrap_target") word (0x8090) # mov (osr, rxfifo[y]) mov (x, osr) jmp (not_x, "skip") label("delay") jmp (pin, "restart") jmp (x_dec, "delay") word (0xa066) # mov (pindirs, isr) mov (isr, invert(isr)) # toggle pindirs state label("skip") jmp (y_dec, "wrap_target") wrap()CadenceGenerator.ring_sequence[4] |= 1 << 14 # fifo_join=rp2.PIO.JOIN_TXGET fixupfrom machine import Pinfrom time import sleepenable = Pin(10, Pin.OPEN_DRAIN, Pin.PULL_UP)indicator = Pin(19, Pin.OPEN_DRAIN, Pin.PULL_UP)piezo = Pin(14, Pin.OUT, None, value=0), Pin(15, Pin.OUT, None, value=0)cadence = CadenceGenerator(1, pin=enable, out_base=indicator)ringer = ToneRinger(0, pin=indicator, out_base=piezo[0])enable(0)sleep(20)enable(1)sleep(1)ringer(18)cadence((0.4, 2, 0.4, 0.2))enable(0)sleep(20)enable(1)sleep(1)ringer(rate=9.8, freq=2500/15, sequence=(8, 7))cadence((2, 4))enable(0)sleep(20)enable(1)
Statistics: Posted by PicoTinker — Fri Jan 10, 2025 5:24 am