From e55ff9b97cd3cc6661f90e0fbef3daee0592f51c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 9 May 2022 06:11:16 -0500 Subject: [PATCH] Add 7-segment examples --- examples/pioasm_7seg.py | 178 ++++++++++++++++++++++++ examples/pioasm_7seg_fader.py | 253 ++++++++++++++++++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 examples/pioasm_7seg.py create mode 100644 examples/pioasm_7seg_fader.py diff --git a/examples/pioasm_7seg.py b/examples/pioasm_7seg.py new file mode 100644 index 0000000..7bd5805 --- /dev/null +++ b/examples/pioasm_7seg.py @@ -0,0 +1,178 @@ +# SPDX-FileCopyrightText: 2022 Jeff Epler, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""Drive a 7-segment display entirely from the PIO peripheral + +By updating the buffer being written to the display, the shown digits can be changed. + +The main program repeatedly shows random digits which 'lock' after a short +time. After all digits have locked, it blanks for a short time and then repeats. +It also demonstrates the use of `asyncio` to perform multiple tasks. + +This example is designed for a Raspberry Pi Pico and bare LED display. For +simplicity, it is wired without any current limiting resistors, instead relying +on a combination of the RP2040's pin drive strength and the 1/4 duty cycle to +limit LED current to an acceptable level, and longevity of the display was not +a priority. + +Before integrating a variant of this example code in a project, evaluate +whether your design needs to add current-limiting resistors. + +https://www.adafruit.com/product/4864 +https://www.adafruit.com/product/865 + +Wiring: + * Pico GP15 to LED matrix 1 (E SEG) + * Pico GP14 to LED matrix 2 (D SEG) + * Pico GP13 to LED matrix 3 (DP SEG) + * Pico GP12 to LED matrix 4 (C SEG) + * Pico GP11 to LED matrix 5 (G SEG) + * Pico GP10 to LED matrix 6 (COM4) + * Pico GP9 to LED matrix 7 (COLON COM) + * Pico GP22 to LED matrix 8 (COLON SEG) + * Pico GP21 to LED matrix 9 (B SEG) + * Pico GP20 to LED matrix 10 (COM3) + * Pico GP19 to LED matrix 11 (COM2) + * Pico GP18 to LED matrix 12 (F SEG) + * Pico GP17 to LED matrix 13 (A SEG) + * Pico GP16 to LED matrix 14 (COM1) +""" + +import asyncio +import random +import array +import board +import rp2pio +import adafruit_pioasm + +_program = adafruit_pioasm.Program( + """ + out pins, 14 ; set the pins to their new state + """ +) + +# Display Pins 1-7 are GP 15-9 +# Display Pins 8-12 are GP 22-16 +COM1_WT = 1 << 7 +COM2_WT = 1 << 10 +COM3_WT = 1 << 11 +COM4_WT = 1 << 1 +COMC_WT = 1 << 0 + +SEGA_WT = 1 << 8 +SEGB_WT = 1 << 12 +SEGC_WT = 1 << 3 +SEGD_WT = 1 << 5 +SEGE_WT = 1 << 6 +SEGF_WT = 1 << 9 +SEGG_WT = 1 << 2 + +SEGDP_WT = 1 << 4 +SEGCOL_WT = 1 << 13 + +ALL_COM = COM1_WT | COM2_WT | COM3_WT | COM4_WT | COMC_WT + +SEG_WT = [ + SEGA_WT, + SEGB_WT, + SEGC_WT, + SEGD_WT, + SEGE_WT, + SEGF_WT, + SEGG_WT, + SEGDP_WT, + SEGCOL_WT, +] +COM_WT = [COM1_WT, COM2_WT, COM3_WT, COM4_WT, COMC_WT] + +DIGITS = [ + 0b0111111, # 0 + 0b0000110, # 1 + 0b1011011, # 2 + 0b1001111, # 3 + 0b1100110, # 4 + 0b1101101, # 5 + 0b1111100, # 6 + 0b0000111, # 7 + 0b1111111, # 8 + 0b1101111, # 9 +] + + +def make_digit_wt(v): + val = ALL_COM + seg = DIGITS[v] + for i in range(8): + if seg & (1 << i): + val |= SEG_WT[i] + return val + + +DIGITS_WT = [make_digit_wt(i) for i in range(10)] + + +class SMSevenSegment: + def __init__(self, first_pin=board.GP9): + self._buf = array.array("H", (DIGITS_WT[0] & ~COM_WT[i] for i in range(4))) + self._sm = rp2pio.StateMachine( + _program.assembled, + frequency=2000, + first_out_pin=first_pin, + out_pin_count=14, + auto_pull=True, + pull_threshold=14, + **_program.pio_kwargs, + ) + self._sm.background_write(loop=self._buf) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.deinit() + + def deinit(self): + self._sm.deinit() + + def __setitem__(self, i, v): + if v is None: + self._buf[i] = 0 + else: + self._buf[i] = DIGITS_WT[v] & ~COM_WT[i] + + +async def digit_locker(s, i, wait): + delay = 30 + d = random.randint(0, 9) + while delay < 300: + d = (d + random.randint(1, 9)) % 10 # Tick to a new digit other than 'd' + s[i] = d + await asyncio.sleep(delay / 1000) + if wait: + wait -= 1 + else: + delay = delay * 1.1 + + +def shuffle(seq): + for i in range(len(seq) - 1): + j = random.randrange(i + 1, len(seq)) + seq[i], seq[j] = seq[j], seq[i] + + +async def main(): + waits = [100, 175, 225, 250] + with SMSevenSegment(board.GP9) as s: + while True: + shuffle(waits) + await asyncio.gather( + *(digit_locker(s, i, di) for i, di in enumerate(waits)) + ) + await asyncio.sleep(1) + for i in range(4): + s[i] = None + await asyncio.sleep(0.5) + + +asyncio.run(main()) diff --git a/examples/pioasm_7seg_fader.py b/examples/pioasm_7seg_fader.py new file mode 100644 index 0000000..f6d5117 --- /dev/null +++ b/examples/pioasm_7seg_fader.py @@ -0,0 +1,253 @@ +# SPDX-FileCopyrightText: 2022 Jeff Epler, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""Drive a 7-segment display entirely from the PIO peripheral + +Each segment is driven with a 'breathing' waveform that runs at its own pace. +It also demonstrates the use of `asyncio` to perform multiple tasks. + +This example is designed for a Raspberry Pi Pico and bare LED display. For +simplicity, it is wired without any current limiting resistors, instead relying +on a combination of the RP2040's pin drive strength and the 1/45 duty cycle to +limit LED current to an acceptable level, and longevity of the display was not +a priority. + +Before integrating a variant of this example code in a project, evaluate +whether your design needs to add current-limiting resistors. + +https://www.adafruit.com/product/4864 +https://www.adafruit.com/product/865 + +Wiring: + * Pico GP15 to LED matrix 1 (E SEG) + * Pico GP14 to LED matrix 2 (D SEG) + * Pico GP13 to LED matrix 3 (DP SEG) + * Pico GP12 to LED matrix 4 (C SEG) + * Pico GP11 to LED matrix 5 (G SEG) + * Pico GP10 to LED matrix 6 (COM4) + * Pico GP9 to LED matrix 7 (COLON COM) + * Pico GP22 to LED matrix 8 (COLON SEG) + * Pico GP21 to LED matrix 9 (B SEG) + * Pico GP20 to LED matrix 10 (COM3) + * Pico GP19 to LED matrix 11 (COM2) + * Pico GP18 to LED matrix 12 (F SEG) + * Pico GP17 to LED matrix 13 (A SEG) + * Pico GP16 to LED matrix 14 (COM1) +""" + +import asyncio +import random +import array +import board +import rp2pio +from ulab import numpy as np +import adafruit_pioasm + +_pio_source = """ + mov pins, null ; turn all pins off + pull + out pindirs, {n} ; set the new direction + pull + out pins, {n} ; set the new values + pull + out y, 32 +delay: + jmp y--, delay + """ + +# Display Pins 1-7 are GP 15-9 [need to re-wire 9] +# Display Pins 8-12 are GP 22-16 [need to re-wire 22] +# GP# Display# Function +# 15 (+ 6) 1 E SEG +# 14 (+ 5) 2 D SEG +# 13 (+ 4) 3 DP SEG +# 12 (+ 3) 4 C SEG +# 11 (+ 2) 5 G SEG +# 10 (+ 1) 6 COM4 +# 9 (+ 0) 7 COLON COM +# 22 (+13) 8 COLON SEG +# 21 (+12) 9 B SEG +# 20 (+11) 10 COM3 +# 19 (+10) 11 COM2 +# 18 (+ 9) 12 F SEG +# 17 (+ 8) 13 A SEG +# 16 (+ 7) 14 COM1 + +COM1_WT = 1 << 7 +COM2_WT = 1 << 10 +COM3_WT = 1 << 11 +COM4_WT = 1 << 1 +COMC_WT = 1 << 0 + +SEGA_WT = 1 << 8 +SEGB_WT = 1 << 12 +SEGC_WT = 1 << 3 +SEGD_WT = 1 << 5 +SEGE_WT = 1 << 6 +SEGF_WT = 1 << 9 +SEGG_WT = 1 << 2 + +SEGDP_WT = 1 << 4 +SEGCOL_WT = 1 << 13 + +ALL_COM = COM1_WT | COM2_WT | COM3_WT | COM4_WT | COMC_WT + +SEG_WT = [ + SEGA_WT, + SEGB_WT, + SEGC_WT, + SEGD_WT, + SEGE_WT, + SEGF_WT, + SEGG_WT, + SEGDP_WT, + SEGCOL_WT, +] +COM_WT = [COM1_WT, COM2_WT, COM3_WT, COM4_WT, COMC_WT] + +DIGITS = [ + 0b0111111, # 0 + 0b0000110, # 1 + 0b1011011, # 2 + 0b1001111, # 3 + 0b1100110, # 4 + 0b1101101, # 5 + 0b1111100, # 6 + 0b0000111, # 7 + 0b1111111, # 8 + 0b1101111, # 9 +] + + +def make_digit_wt(v): + val = ALL_COM + seg = DIGITS[v] + for i in range(8): + if seg & (1 << i): + val |= SEG_WT[i] + return val + + +class LedFader: + def __init__( + self, first_pin, pin_count, cathode_weights, anode_weights, levels=64 + ): # pylint: disable=too-many-arguments + self._cathode_weights = cathode_weights + self._anode_weights = anode_weights + self._stream = array.array("L", [0, 0, 1]) * ( + 1 + len(cathode_weights) * len(anode_weights) + ) + self._levels = levels + self._max_count = levels * len(self) + self._total = len(self) + + program = adafruit_pioasm.Program(_pio_source.format(n=pin_count)) + self._sm = rp2pio.StateMachine( # pylint: disable=too-many-arguments + program.assembled, + frequency=125_000_000, + first_out_pin=first_pin, + out_pin_count=14, + auto_pull=True, + pull_threshold=14, + **program.pio_kwargs, + ) + print( + f"Note: approximate refresh rate {self._sm.frequency / self._max_count:.0f}Hz" + ) + self._sm.background_write(loop=self._stream) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.deinit() + + def deinit(self): + self._sm.deinit() + + def __setitem__(self, i, v): + if not 0 <= v < self._levels: + raise ValueError() + + c = i % len(self._cathode_weights) + r = i // len(self._cathode_weights) + if not v: + self._total = self._total - self._stream[3 * i + 2] + 1 + self._stream[3 * i] = 0 + self._stream[3 * i + 1] = 0 + self._stream[3 * i + 2] = 1 + else: + self._total = self._total - self._stream[3 * i + 2] + v + self._stream[3 * i] = self._cathode_weights[c] | self._anode_weights[r] + self._stream[3 * i + 1] = self._cathode_weights[c] + self._stream[3 * i + 2] = v + self._stream[3 * len(self) + 2] = self._max_count - self._total + + def __len__(self): + return len(self._stream) // 3 - 1 + + +class CyclicSignal: + def __init__(self, data, phase=0): + self._data = data + self._phase = 0 + self.phase = phase + self._scale = len(self._data) - 1 + + @property + def phase(self): + return self._phase + + @phase.setter + def phase(self, value): + self._phase = value % 1 + + @property + def value(self): + idxf = self._phase * len(self._data) + idx = int(idxf) + frac = idxf % 1 + idx1 = (idx + 1) % len(self._data) + val = self._data[idx] + val1 = self._data[idx1] + return val + (val1 - val) * frac + + def advance(self, delta): + self._phase = (self._phase + delta) % 1 + + +sine = (np.sin(np.linspace(0, 2 * np.pi, 50, endpoint=False)) * 0.5 + 0.5) ** 2.2 * 64 + + +async def segment_throbber(c, i): + signal = CyclicSignal(sine, random.random()) + velocity = random.random() * 0.04 + 0.005 + + while True: + signal.advance(velocity) + c[i] = int(signal.value) + await asyncio.sleep(0) + + +async def main(): + with LedFader( + board.GP9, + 14, + ( + SEGA_WT, + SEGB_WT, + SEGC_WT, + SEGD_WT, + SEGE_WT, + SEGF_WT, + SEGG_WT, + SEGDP_WT, + SEGCOL_WT, + ), + (COM1_WT, COM2_WT, COM3_WT, COM4_WT, COMC_WT), + ) as c: + await asyncio.gather(*(segment_throbber(c, i) for i in range(len(c)))) + + +asyncio.run(main())