203 lines
7.1 KiB
Python
203 lines
7.1 KiB
Python
# SPDX-FileCopyrightText: 2019 Kevin J. Walters for Adafruit Industries
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
### scope-xy-adafruitlogo v1.0
|
|
|
|
"""Output a logo to an oscilloscope in X-Y mode on an Adafruit M4
|
|
board like Feather M4 or PyGamer (best to disconnect headphones).
|
|
"""
|
|
|
|
### copy this file to PyGamer (or other M4 board) as code.py
|
|
|
|
### MIT License
|
|
|
|
### Copyright (c) 2019 Kevin J. Walters
|
|
|
|
### Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
### of this software and associated documentation files (the "Software"), to deal
|
|
### in the Software without restriction, including without limitation the rights
|
|
### to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
### copies of the Software, and to permit persons to whom the Software is
|
|
### furnished to do so, subject to the following conditions:
|
|
|
|
### The above copyright notice and this permission notice shall be included in all
|
|
### copies or substantial portions of the Software.
|
|
|
|
### THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
### IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
### FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
### AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
### LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
### OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
### SOFTWARE.
|
|
|
|
import time
|
|
import math
|
|
import array
|
|
|
|
import board
|
|
import audioio
|
|
import audiocore
|
|
import analogio
|
|
|
|
### Vector data for logo
|
|
import adafruit_logo_vector
|
|
|
|
VECTOR_POINT_SPACING = 3
|
|
|
|
def addpoints(points, min_dist):
|
|
"""Add extra points to any lines if length is greater than min_dist"""
|
|
|
|
newpoints = []
|
|
original_len = len(points)
|
|
for pidx in range(original_len):
|
|
px1, py1 = points[pidx]
|
|
px2, py2 = points[(pidx + 1) % original_len]
|
|
|
|
### Always keep the original point
|
|
newpoints.append((px1, py1))
|
|
|
|
diff_x = px2 - px1
|
|
diff_y = py2 - py1
|
|
dist = math.sqrt(diff_x ** 2 + diff_y ** 2)
|
|
if dist > min_dist:
|
|
### Calculate extra intermediate points plus one
|
|
extrasp1 = int(dist // min_dist) + 1
|
|
for extra_idx in range(1, extrasp1):
|
|
ratio = extra_idx / extrasp1
|
|
newpoints.append((px1 + diff_x * ratio,
|
|
py1 + diff_y * ratio))
|
|
### Two points define a straight line
|
|
### so no need to connect final point back to first
|
|
if original_len == 2:
|
|
break
|
|
|
|
return newpoints
|
|
|
|
### pylint: disable=invalid-name
|
|
### If logo is off centre then correct it here
|
|
if adafruit_logo_vector.offset_x != 0 or adafruit_logo_vector.offset_y != 0:
|
|
data = []
|
|
for part in adafruit_logo_vector.data:
|
|
newpart = []
|
|
for point in part:
|
|
newpart.append((point[0] - adafruit_logo_vector.offset_x,
|
|
point[1] - adafruit_logo_vector.offset_y))
|
|
data.append(newpart)
|
|
else:
|
|
data = adafruit_logo_vector.data
|
|
|
|
|
|
### Add intermediate points to make line segments for each part
|
|
### look like continuous lines on x-y oscilloscope output
|
|
display_data = []
|
|
for part in data:
|
|
display_data.extend(addpoints(part, VECTOR_POINT_SPACING))
|
|
|
|
### PyPortal DACs seem to stop around 53000 and there's 2 100 ohm resistors
|
|
### on output so maybe large values aren't good idea?
|
|
### 32768 and 32000 exhibit this bug but 25000 so far appears to be a
|
|
### workaround, albeit a mysterious one
|
|
### https://github.com/adafruit/circuitpython/issues/1992
|
|
### Using "h" for audiocore.RawSample() DAC range will be 20268 to 45268
|
|
dac_x_min = 0
|
|
dac_y_min = 0
|
|
dac_x_max = 25000
|
|
dac_y_max = 25000
|
|
dac_x_mid = dac_x_max // 2
|
|
dac_y_mid = dac_y_max // 2
|
|
|
|
### Convert the points into format suitable for audio library
|
|
### and scale to the DAC range used by the library
|
|
### Intentionally using "h" data representation here as this happens to
|
|
### cause the CircuitPython audio libraries to make a copy of
|
|
### rawdata which is useful to allow animating code to modify rawdata
|
|
### without affecting current DAC output
|
|
rawdata = array.array("h", (2 * len(display_data)) * [0])
|
|
|
|
range_x = 512.0
|
|
range_y = 512.0
|
|
halfrange_x = range_x / 2
|
|
halfrange_y = range_y / 2
|
|
mid_x = 256.0
|
|
mid_y = 256.0
|
|
mult_x = dac_x_max / range_x
|
|
mult_y = dac_y_max / range_y
|
|
|
|
### https://github.com/adafruit/circuitpython/issues/1992
|
|
print("length of rawdata", len(rawdata))
|
|
|
|
use_wav = True
|
|
poor_wav_bug_workaround = False
|
|
leave_wav_looping = True
|
|
|
|
### A0 will be x, A1 will be y
|
|
if use_wav:
|
|
print("Using audiocore.RawSample for DACs")
|
|
dacs = audioio.AudioOut(board.A0, right_channel=board.A1)
|
|
else:
|
|
print("Using analogio.AnalogOut for DACs")
|
|
a0 = analogio.AnalogOut(board.A0)
|
|
a1 = analogio.AnalogOut(board.A1)
|
|
|
|
### 10Hz is about ok for AudioOut, optimistic for AnalogOut
|
|
frame_t = 1/10
|
|
prev_t = time.monotonic()
|
|
angle = 0 ### in radians
|
|
frame = 1
|
|
while True:
|
|
##print("Transforming data for frame:", frame, "at", prev_t)
|
|
|
|
### Rotate the points of the vector graphic around its centre
|
|
idx = 0
|
|
sine = math.sin(angle)
|
|
cosine = math.cos(angle)
|
|
for px, py in display_data:
|
|
pcx = px - mid_x
|
|
pcy = py - mid_y
|
|
dac_a0_x = round((-sine * pcx + cosine * pcy + halfrange_x) * mult_x)
|
|
### Keep x position within legal values (if needed)
|
|
##dac_a0_x = min(dac_a0_x, dac_x_max)
|
|
##dac_a0_x = max(dac_a0_x, 0)
|
|
dac_a1_y = round((sine * pcy + cosine * pcx + halfrange_y) * mult_y)
|
|
### Keep y position within legal values (if needed)
|
|
##dac_a1_y = min(dac_a1_y, dac_y_max)
|
|
##dac_a1_y = max(dac_a1_y, 0)
|
|
rawdata[idx] = dac_a0_x - dac_x_mid ### adjust for "h" array
|
|
rawdata[idx + 1] = dac_a1_y - dac_y_mid ### adjust for "h" array
|
|
idx += 2
|
|
|
|
if use_wav:
|
|
### 200k (maybe 166.667k) seems to be practical limit
|
|
### 1M permissible but seems same as around 200k
|
|
output_wave = audiocore.RawSample(rawdata,
|
|
channel_count=2,
|
|
sample_rate=200 * 1000)
|
|
|
|
### The image may "warp" sometimes with loop=True due to a strange bug
|
|
### https://github.com/adafruit/circuitpython/issues/1992
|
|
if poor_wav_bug_workaround:
|
|
while True:
|
|
dacs.play(output_wave)
|
|
if time.monotonic() - prev_t >= frame_t:
|
|
break
|
|
else:
|
|
dacs.play(output_wave, loop=True)
|
|
while time.monotonic() - prev_t < frame_t:
|
|
pass
|
|
if not leave_wav_looping:
|
|
dacs.stop()
|
|
else:
|
|
while True:
|
|
### This gives a very flickery image with 4932 points
|
|
### slight flicker at 2552
|
|
### might be ok for 1000
|
|
for idx in range(0, len(rawdata), 2):
|
|
a0.value = rawdata[idx]
|
|
a1.value = rawdata[idx + 1]
|
|
if time.monotonic() - prev_t >= frame_t:
|
|
break
|
|
prev_t = time.monotonic()
|
|
angle += math.pi / 180 * 3 ### 72 degrees per frame
|
|
frame += 1
|