Adafruit_Learning_System_Gu.../CPX_DAC_Guide/code.py
2022-02-23 14:30:29 -05:00

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