Merge pull request #47 from jepler/example-updates-background

enhance examples
This commit is contained in:
Jeff Epler 2022-05-09 06:59:24 -05:00 committed by GitHub
commit aa651b1e00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 478 additions and 30 deletions

178
examples/pioasm_7seg.py Normal file
View file

@ -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())

View file

@ -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())

View file

@ -17,8 +17,8 @@ based on the 'DIT duration' of about 128ms (1MHz / 32 / 4000).
https://en.wikipedia.org/wiki/Morse_code
"""
import time
import array
import time
from board import LED
from rp2pio import StateMachine
from adafruit_pioasm import Program
@ -62,13 +62,6 @@ T = DIT + LETTER_SPACE
SOS = S + O + S + WORD_SPACE
TEST = T + E + S + T + WORD_SPACE
# 8 slots of the shortest possible led-off time
# A background write is 'complete' as soon as all data has been placed
# in the StateMachine's FIFO. This FIFO has either 4 or 8 entries.
# By adding 8 very short "led off" times, we can have our 'sm.writing' test
# tell us when the actual letter data has all been
FILLER = array.array("H", [LED_OFF | 1]) * 8
sm = StateMachine(
pio_code.assembled,
frequency=1_000_000,
@ -94,11 +87,11 @@ while True:
("TEST", TEST),
):
print(f"Sending out {plain}", end="")
sm.background_write(morse + FILLER)
while sm.writing:
sm.background_write(morse)
sm.clear_txstall()
while not sm.txstall:
print(end=".")
time.sleep(0.1)
print()
print("Message all sent to StateMachine")
time.sleep(1)
print("Message all sent to StateMachine (including emptying FIFO)")
print()

View file

@ -6,12 +6,17 @@
The NeoPixelBackground class defined here is largely compatible with the
standard NeoPixel class, except that the ``show()`` method returns immediately,
writing data to the LEDs in the background.
writing data to the LEDs in the background, and setting `auto_write` to true
causes the data to be continuously sent to the LEDs all the time.
Writing the LED data in the background will allow more time for your
Python code to run, so it may be possible to slightly increase the refresh
rate of your LEDs or do more complicated processing.
Because the pixelbuf storage is also being written out 'live', it is possible
(even with auto-show 'false') to experience tearing, where the LEDs are a
combination of old and new values at the same time.
The demonstration code, under ``if __name__ == '__main__':`` is intended
for the Adafruit MacroPad, with 12 NeoPixel LEDs. It shows a cycling rainbow
pattern across all the LEDs.
@ -19,7 +24,6 @@ pattern across all the LEDs.
import struct
import adafruit_pixelbuf
from ulab import numpy as np
from rp2pio import StateMachine
from adafruit_pioasm import Program
@ -40,7 +44,7 @@ _program = Program(
.side_set 1 opt
.wrap_target
pull block side 0
out y, 16 side 0 ; get count of NeoPixel bits
out y, 32 side 0 ; get count of NeoPixel bits
bitloop:
pull ifempty side 0 ; drive low
@ -53,8 +57,8 @@ do_zero:
jmp y--, bitloop side 0 [4] ; drive low for a zero (short pulse)
end_sequence:
pull block side 0 ; get fresh 16 bit delay value
out y, 16 side 0 ; get delay count
pull block side 0 ; get fresh delay value
out y, 32 side 0 ; get delay count
wait_reset:
jmp y--, wait_reset side 0 ; wait until delay elapses
.wrap
@ -76,47 +80,67 @@ class NeoPixelBackground( # pylint: disable=too-few-public-methods
byte_count = bpp * n
bit_count = byte_count * 8
padding_count = byte_count % 2
padding_count = -byte_count % 4
if bit_count > 65536:
raise ValueError("Too many pixels")
# backwards, so that ulab byteswap corrects it!
header = struct.pack(">H", (bit_count - 1) & 0xFFFF)
trailer = b"\0" * padding_count + struct.pack(">H", 3840)
# backwards, so that dma byteswap corrects it!
header = struct.pack(">L", bit_count - 1)
trailer = b"\0" * padding_count + struct.pack(">L", 3840)
self._sm = StateMachine(
_program.assembled,
auto_pull=False,
first_sideset_pin=pin,
out_shift_right=False,
pull_threshold=16,
pull_threshold=32,
frequency=12_800_000,
**_program.pio_kwargs,
)
self._first = True
super().__init__(
n,
brightness=brightness,
byteorder=pixel_order,
auto_write=auto_write,
auto_write=False,
header=header,
trailer=trailer,
)
self._auto_write = False
self._auto_writing = False
self.auto_write = auto_write
@property
def auto_write(self):
return self._auto_write
@auto_write.setter
def auto_write(self, value):
self._auto_write = bool(value)
if not value and self._auto_writing:
self._sm.background_write()
self._auto_writing = False
elif value:
self.show()
def _transmit(self, buf):
self._sm.background_write(np.frombuffer(buf, dtype=np.uint16).byteswap())
if self._auto_write:
if not self._auto_writing:
self._sm.background_write(loop=memoryview(buf).cast("L"), swap=True)
self._auto_writing = True
else:
self._sm.background_write(memoryview(buf).cast("L"), swap=True)
if __name__ == "__main__":
import board
import rainbowio
import time
import supervisor
NEOPIXEL = board.NEOPIXEL
NUM_PIXELS = 12
pixels = NeoPixelBackground(NEOPIXEL, NUM_PIXELS)
i = 0
while True:
pixels.fill(rainbowio.colorwheel(i := (i + 1) % 256))
time.sleep(0.01)
# Around 1 cycle per second
pixels.fill(rainbowio.colorwheel(supervisor.ticks_ms() // 4))