Merge branch 'license-changes' of https://github.com/TheKitty/Adafruit_Learning_System_Guides into license-changes

This commit is contained in:
Anne Barela 2021-10-21 13:54:23 -04:00
commit 2c286c0dc6
203 changed files with 11735 additions and 77 deletions

View file

@ -7,8 +7,7 @@ jobs:
strategy:
fail-fast: false
matrix:
arduino-platform: ["cpb", "cpc", "cpx_ada", "esp32", "esp8266", "feather32u4", "feather_m0_express", "feather_m4_express", "feather_rp2040", "flora", "funhouse", "gemma", "gemma_m0", "hallowing_m0", "hallowing_m4_tinyusb", "magtag", "metro_m0", "metro_m0_tinyusb", "metro_m4", "metro_m4_tinyusb", "monster_m4sk", "monster_m4sk_tinyusb", "neokeytrinkey_m0", "neotrellis_m4", "nrf52832", "nrf52840", "protrinket_5v", "proxlighttrinkey_m0", "pybadge", "pygamer", "pyportal", "qt2040_trinkey", "qtpy_m0", "rotarytrinkey_m0", "slidetrinkey_m0", "trinket_m0", "uno", "trinket_5v" ]
# "trinket_5v", was removed
arduino-platform: ["cpb", "cpc", "cpx_ada", "esp32", "esp8266", "feather32u4", "feather_m0_express", "feather_m4_express", "feather_rp2040", "flora", "funhouse", "gemma", "gemma_m0", "hallowing_m0", "hallowing_m4_tinyusb", "magtag", "metro_m0", "metro_m0_tinyusb", "metro_m4", "metro_m4_tinyusb", "monster_m4sk", "monster_m4sk_tinyusb", "neokeytrinkey_m0", "neotrellis_m4", "nrf52832", "nrf52840", "protrinket_5v", "proxlighttrinkey_m0", "pybadge", "pygamer", "pyportal", "qt2040_trinkey", "qtpy_m0", "rotarytrinkey_m0", "slidetrinkey_m0", "trinket_m0", "uno", "trinket_5v", "ledglasses_nrf52840" ]
runs-on: ubuntu-18.04
steps:
@ -39,6 +38,34 @@ jobs:
- name: test platforms
run: python3 ci/build_platform.py ${{ matrix.arduino-platform }}
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: |
build/*.hex
build/*.bin
build/*.uf2
- name: Zip release files
if: startsWith(github.ref, 'refs/tags/')
run: |
if [ -d build ]; then
(
echo "Built from Adafruit Learning System Guides `git describe --tags` for ${{ matrix.arduino-platform }}"
echo "Source code: https://github.com/adafruit/"
echo "Adafruit Learning System: https://learn.adafruit.com/"
) > build/README.txt
cd build && zip -9 -o ${{ matrix.arduino-platform }}.zip *.hex *.bin *.uf2 *.txt
fi
- name: Create release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: build/${{ matrix.arduino-platform }}.zip
fail_on_unmatched_files: false
body: "Select the zip file corresponding to your board from the list below."
pylint:
runs-on: ubuntu-latest

View file

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2014, 2018 Phil Burgess and Mikey Sklar for Adafruit Industries
# SPDX-FileCopyrightText: 2014 Phil Burgess for Adafruit Industries
# SPDX-FileCopyrightText: 2018 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
#

View file

@ -1 +0,0 @@
3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.py 137: Consider using tuple unpacking for swapping variables (consider-swap-variables)

View file

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2013, 2017 Phil Burgess and Mikey Sklar for Adafruit Industries
# SPDX-FileCopyrightText: 2013 Phil Burgess for Adafruit Industries
# SPDX-FileCopyrightText: 2017 Mikey Sklar for Adafruit Industries
#
# SPDX-License-Identifier: BSD
@ -124,9 +125,7 @@ def fscale(originalmin, originalmax, newbegin, newend, inputvalue, curve):
def drawLine(fromhere, to):
if fromhere > to:
fromheretemp = fromhere
fromhere = to
to = fromheretemp
to, fromhere = fromhere, to
for index in range(fromhere, to):
strip[index] = (0, 0, 0)

View file

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2014, 2017 HerrRausB https://github.com/HerrRausB, Mikey Sklar for Adafruit Industries
# SPDX-FileCopyrightText: 2014 HerrRausB https://github.com/HerrRausB
# SPDX-FileCopyrightText: 2017 Mikey Sklar for Adafruit Industries
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#

View file

@ -14,7 +14,7 @@
created 28 Mar 2011 by Limor Fried
modified 9 Apr 2012 by Tom Igoe
modified 12 Apr 2018 by Mike Barela
modified 12 Apr 2018 by Anne Barela
*/
// include the SD library:
#include <SPI.h>

View file

@ -12,7 +12,7 @@
created Nov 2010 by David A. Mellis
modified 9 Apr 2012 by Tom Igoe
modified 2 Feb 2014 by Scott Fitzgerald
modified 12 Apr 2018 by Mike Barela
modified 12 Apr 2018 by Anne Barela
This example code is in the public domain.

View file

@ -13,7 +13,7 @@ Then you should be able to see the **CIRCUITPY** drive when connected via USB.
CircuitPython resources are at https://CircuitPython.Org/
Code written by Dano Wall and Mike Barela for Adafruit Industries.
Code written by Dano Wall and Anne Barela for Adafruit Industries.
MIT License, please attribute.

View file

@ -2,7 +2,7 @@
Chirp Owl written by Becky Stern and T Main for Adafruit Industries
Tutorial: http://learn.adafruit.com/chirping-plush-owl-toy/
Includes animal sounds by Mike Barela
Includes animal sounds by Anne Barela
http://learn.adafruit.com/adafruit-trinket-modded-stuffed-animal/animal-sounds
based in part on Debounce

View file

@ -1,7 +1,7 @@
# Chirp Owl written by Becky Stern and T Main for Adafruit Industries
# Tutorial: http://learn.adafruit.com/chirping-plush-owl-toy/
# Includes animal sounds by Mike Barela
# Includes animal sounds by Anne Barela
# http://learn.adafruit.com/adafruit-trinket-modded-stuffed-animal/animal-sounds
# based in part on Debounce
# created 21 November 2006

View file

@ -44,7 +44,7 @@ rainbow_bitmap = displayio.Bitmap(
rainbow_palette = displayio.Palette(255)
for i in range(0, 255):
rainbow_palette[i] = int("".join("%02x" % i for i in colorwheel(i)), 16)
rainbow_palette[i] = colorwheel(i)
for y in range(rainbow_bitmap.height):
for x in range(rainbow_bitmap.width):

View file

@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: 2016 Damien P. George
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2019 Carter Nelson
# SPDX-FileCopyrightText: 2019 Roy Hooper
# SPDX-FileCopyrightText: 2019 Rose Hooper
# SPDX-FileCopyrightText: 2020 Jeff Epler
#
# SPDX-License-Identifier: MIT
@ -10,7 +10,7 @@
`neopio` - Neopixel strip driver using RP2040's PIO
===================================================
* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Roy Hooper, Jeff Epler
* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper, Jeff Epler
"""
import adafruit_pioasm

View file

@ -14,7 +14,7 @@ Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from [Adafruit](https://www.adafruit.com)!
MIT license, designed and guide written by Dano Wall, code by Mike Barela
MIT license, designed and guide written by Dano Wall, code by Anne Barela
All text above, and the splash screen below must be included in any redistribution

View file

@ -1,5 +1,5 @@
# Write the time for the Adafruit DS3231 real-time clock.
# Limor Fried/Mike Barela for Adafruit Industries
# Limor Fried/Anne Barela for Adafruit Industries
import time
import board

View file

@ -10,7 +10,7 @@ Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from [Adafruit](https://www.adafruit.com)!
MIT license, guide written by Mike Barela, code by Limor Fried
MIT license, guide written by Anne Barela, code by Limor Fried
All text above, and the splash screen below must be included in any redistribution

View file

@ -29,6 +29,10 @@ def get_unique_pins():
"LED",
"SWITCH",
"BUTTON",
"ACCELEROMETER_INTERRUPT",
"VOLTAGE_MONITOR",
"MICROPHONE_CLOCK",
"MICROPHONE_DATA",
]
if p in dir(board)
]

View file

@ -1,6 +1,5 @@
import time
import board
import pwmio
import pulseio
import adafruit_irremote
import neopixel
@ -12,9 +11,8 @@ TRANSMIT_DELAY = 15 # change this as desired to affect game dynamics, or just l
# Create NeoPixel object to indicate status
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
pulseout = pulseio.PulseOut(pwm)
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
# Create an encoder that will take numbers and turn them into IR pulses
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],

View file

@ -1,6 +1,5 @@
import time
import pulseio
import pwmio
import board
import adafruit_irremote
import digitalio
@ -22,9 +21,8 @@ pulsein = pulseio.PulseIn(board.IR_RX, maxlen=120, idle_state=True)
# Create a decoder that will take pulses and turn them into numbers
decoder = adafruit_irremote.GenericDecode()
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
pulseout = pulseio.PulseOut(pwm)
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
# Create an encoder that will take numbers and turn them into NEC IR pulses
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
zero=[550, 1700], trail=0)

View file

@ -1,5 +1,5 @@
# Lucky Cat Maneki-neko with Circuit Playground Express
# Mike Barela for Adafruit Industries, MIT License
# Anne Barela for Adafruit Industries, MIT License
import time
import board

View file

@ -2,12 +2,10 @@ import time
from adafruit_circuitplayground.express import cpx
import adafruit_irremote
import pulseio
import pwmio
import board
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
pulseout = pulseio.PulseOut(pwm)
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
# Create an encoder that will take numbers and turn them into NEC IR pulses
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
zero=[550, 1700], trail=0)

View file

@ -1,5 +1,5 @@
# Circuit Playground Express Piñata by Dano Wall for Adafruit Industries
# CircuitPython code by Mike Barela for Adafruit Industries, MIT License
# CircuitPython code by Anne Barela for Adafruit Industries, MIT License
import time
import random
import board

View file

@ -1,7 +1,7 @@
/*
Close Encounters hat with 10 neopixels by Leslie Birch for Adafruit Industries.
Notes play with each corresponding light.
Based on code by Becky Stern, Mike Barela and T Main for Adafruit Industries
Based on code by Becky Stern, Anne Barela and T Main for Adafruit Industries
http://learn.adafruit.com/adafruit-trinket-modded-stuffed-animal/animal-sounds
*/

View file

@ -1,6 +1,6 @@
# CircuitPython for the Adafruit Learning System Tutorial
# Universal Marionette Kit
# Project by Dano Wall, code by Mike Barela for Adafruit Industries
# Project by Dano Wall, code by Anne Barela for Adafruit Industries
# MIT License
import time
from adafruit_crickit import crickit

View file

@ -1,6 +1,6 @@
# Stumble Bot, coded in CircuitPython
# Using an Adafruit Circuit Playground Express, Crickit, and 2 servos
# Dano Wall, Mike Barela for Adafruit Industries, MIT License, May, 2018
# Dano Wall, Anne Barela for Adafruit Industries, MIT License, May, 2018
#
import time
from digitalio import DigitalInOut, Direction, Pull

View file

@ -1,6 +1,6 @@
# main.py - code to test the Adafruit CRICKIT board with
# the BBC micro:bit and MicroPython (NOT CircuitPython)
# MIT License by Limor Fried and Mike Barela, 2019
# MIT License by Limor Fried and Anne Barela, 2019
# This code requires the seesaw.py module as a driver
import time
import seesaw

View file

@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*
ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
good clicky button for input, this code shows how one might instead use
the onboard accelerometer for interactions*.
Worn normally, the LED rings are simply lit a solid color.
TAP the eyeglass frames to cycle among a list of available colors.
LOOK DOWN to light the LED rings bright white -- for navigating steps
or finding the right key. LOOK BACK UP to return to solid color.
This uses only the rings, not the matrix portion.
* Like, if you have big ol' monster hands, that little button can be
hard to click, y'know?
*/
#include <Adafruit_IS31FL3741.h> // For LED driver
#include <Adafruit_LIS3DH.h> // For accelerometer
#include <Adafruit_Sensor.h> // For m/s^2 accel units
Adafruit_LIS3DH accel;
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
// Here's a list of colors that we cycle through when tapped, specified
// as {R,G,B} values from 0-255. These are intentionally a bit dim --
// both to save battery and to make the "ground light" mode more dramatic.
// Rather than primary color red/green/blue sequence which is just so
// over-done at this point, let's use some HALLOWEEN colors!
uint8_t colors[][3] = {
{27, 9, 0}, // Orange
{12, 0, 24}, // Purple
{5, 31, 0}, // Green
};
#define NUM_COLORS (sizeof colors / sizeof colors[0]) // List length
uint8_t looking_down_color[] = {255, 255, 255}; // Max white
uint8_t color_index = 0; // Begin at first color in list
uint8_t *target_color; // Pointer to color we're aiming for
float interpolated_color[] = {0.0, 0.0, 0.0}; // Current color along the way
float filtered_y; // De-noised accelerometer reading
bool looking_down; // Set true when glasses are oriented downward
sensors_event_t event; // For accelerometer conversion
uint32_t last_tap_time = 0; // For accelerometer tap de-noising
// Crude error handler, prints message to Serial console, flashes LED
void err(char *str, uint8_t hz) {
Serial.println(str);
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
}
void setup() { // Runs once at program start...
// Initialize hardware
Serial.begin(115200);
if (! accel.begin()) err("LIS3DH not found", 5);
if (! glasses.begin()) err("IS3741 not found", 2);
// Configure accelerometer and get initial state
accel.setClick(1, 100); // Set threshold for single tap
accel.getEvent(&event); // Current accel in m/s^2
// Check accelerometer to see if we've started in the looking-down state,
// set the target color (what we're aiming for) appropriately. Only the
// Y axis is needed for this.
filtered_y = event.acceleration.y;
looking_down = (filtered_y > 5.0);
// If initially looking down, aim for the look-down color,
// else aim for the first item in the color list.
target_color = looking_down ? looking_down_color : colors[color_index];
// Configure glasses for max brightness, enable output
glasses.setLEDscaling(0xFF);
glasses.setGlobalCurrent(0xFF);
glasses.enable(true);
}
void loop() { // Repeat forever...
// interpolated_color blends from the prior to the next ("target")
// LED ring colors, with a pleasant ease-out effect.
for(uint8_t i=0; i<3; i++) { // R, G, B
interpolated_color[i] = interpolated_color[i] * 0.97 + target_color[i] * 0.03;
}
// Convert separate red, green, blue to "packed" 24-bit RGB value
uint32_t rgb = ((int)interpolated_color[0] << 16) |
((int)interpolated_color[1] << 8) |
(int)interpolated_color[2];
// Fill both rings with packed color, then refresh the LEDs.
glasses.left_ring.fill(rgb);
glasses.right_ring.fill(rgb);
glasses.show();
// The look-down detection only needs the accelerometer's Y axis.
// This works with the Glasses Driver mounted on either temple,
// with the glasses arms "open" (as when worn).
accel.getEvent(&event);
// Smooth the accelerometer reading the same way RGB colors are
// interpolated. This avoids false triggers from jostling around.
filtered_y = filtered_y * 0.97 + event.acceleration.y * 0.03;
// The threshold between "looking down" and "looking up" depends
// on which of those states we're currently in. This is an example
// of hysteresis in software...a change of direction requires a
// little extra push before it takes, which avoids oscillating if
// there was just a single threshold both ways.
if (looking_down) { // Currently in the looking-down state...
(void)accel.getClick(); // Discard any taps while looking down
if (filtered_y < 3.5) { // Have we crossed the look-up threshold?
target_color = colors[color_index]; // Back to list color
looking_down = false; // We're looking up now!
}
} else { // Currently in the looking-up state...
if (filtered_y > 5.0) { // Crossed the look-down threshold?
target_color = looking_down_color; // Aim for white
looking_down = true; // We're looking down now!
} else if (accel.getClick()) {
// No look up/down change, but the accelerometer registered
// a tap. Compare this against the last time we sensed one,
// and only do things if it's been more than half a second.
// This avoids spurious double-taps that can occur no matter
// how carefully the tap threshold was set.
uint32_t now = millis();
uint32_t elapsed = now - last_tap_time;
if (elapsed > 500) {
// A good tap was detected. Cycle to the next color in
// the list and note the time of this tap.
color_index = (color_index + 1) % NUM_COLORS;
target_color = colors[color_index];
last_tap_time = now;
}
}
}
}

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
good clicky button for input, this code shows how one might instead use
@ -50,14 +54,6 @@ target_color = (255, 255, 255) if looking_down else colors[color_index]
interpolated_color = (0, 0, 0) # LEDs off at startup, they'll ramp up
def fill_color(color):
"""Given an (R,G,B) tuple, fill both LED rings with this color."""
# Convert tuple to a 'packed' 24-bit value
packed = (int(color[0]) << 16) | (int(color[1]) << 8) | int(color[2])
for i in range(24):
glasses.left_ring[i] = glasses.right_ring[i] = packed
while True: # Loop forever...
# The try/except here is because VERY INFREQUENTLY the I2C bus will
@ -75,7 +71,14 @@ while True: # Loop forever...
interpolated_color[2] * 0.85 + target_color[2] * 0.15,
)
# Fill both rings with interpolated_color, then refresh the LEDs.
fill_color(interpolated_color)
# fill_color(interpolated_color)
packed = (
(int(interpolated_color[0]) << 16)
| (int(interpolated_color[1]) << 8)
| int(interpolated_color[2])
)
glasses.left_ring.fill(packed)
glasses.right_ring.fill(packed)
glasses.show()
# The look-down detection only needs the accelerometer's Y axis.

View file

@ -0,0 +1,239 @@
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
Uses onboard microphone and a lot of math to react to music.
REQUIRES Adafruit_ZeroFFT LIBRARY, install via Arduino Library manager.
*/
#include <Adafruit_IS31FL3741.h> // For LED driver
#include <PDM.h> // For microphone
#include <Adafruit_ZeroFFT.h> // For math
// FFT/SPECTRUM CONFIG ----
#define NUM_SAMPLES 512 // Audio & FFT buffer, MUST be a power of two
#define SPECTRUM_SIZE (NUM_SAMPLES / 2) // Output spectrum is 1/2 of FFT output
// Bottom of spectrum tends to be noisy, while top often exceeds musical
// range and is just harmonics, so clip both ends off:
#define LOW_BIN 5 // Lowest bin of spectrum that contributes to graph
#define HIGH_BIN 150 // Highest bin "
// GLOBAL VARIABLES -------
Adafruit_EyeLights_buffered glasses; // LED matrix is buffered for smooth animation
extern PDMClass PDM; // Microphone
short audio_buf[3][NUM_SAMPLES]; // Audio input buffers, 16-bit signed
uint8_t active_buf = 0; // Buffer # into which audio is currently recording
volatile int samples_read = 0; // # of samples read into current buffer thus far
volatile bool mic_on = false; // true when reading from mic, false when full/stopped
float spectrum[SPECTRUM_SIZE]; // FFT results are stored & further processed here
float dynamic_level = 10.0; // For adapting to changing audio volume
int frames; // For frames-per-second calculation
uint32_t start_time; // Ditto
struct { // Values associated with each column of the matrix
int first_bin; // First spectrum bin index affecting column
int num_bins; // Number of spectrum bins affecting column
float *bin_weights; // List of spectrum bin weightings
uint32_t color; // GFX-style 'RGB565' color for column
float top; // Current column top position
float dot; // Current column 'falling dot' position
float velocity; // Current velocity of falling dot
} column_table[18];
// Crude error handler, prints message to Serial console, flashes LED
void err(char *str, uint8_t hz) {
Serial.println(str);
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
}
void setup() { // Runs once at program start...
Serial.begin(115200);
//while(!Serial);
if (! glasses.begin()) err("IS3741 not found", 2);
// FFT/SPECTRUM SETUP -----
uint8_t spectrum_bits = (int)log2f((float)SPECTRUM_SIZE); // e.g. 8 = 256 bin spectrum
// Scale LOW_BIN and HIGH_BIN to 0.0 to 1.0 equivalent range in spectrum
float low_frac = log2f((float)LOW_BIN) / (float)spectrum_bits;
float frac_range = log2((float)HIGH_BIN) / (float)spectrum_bits - low_frac;
// Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
// To keep the display lively, tables are precomputed where each column of
// the matrix (of which there are few) is the sum value and weighting of
// several bins from the FFT spectrum output (of which there are many).
// The tables also help visually linearize the output so octaves are evenly
// spaced, as on a piano keyboard, whereas the source spectrum data is
// spaced by frequency in Hz.
for (int column=0; column<18; column++) {
// Determine the lower and upper frequency range for this column, as
// fractions within the scaled 0.0 to 1.0 spectrum range. 0.95 below
// creates slight frequency overlap between columns, looks nicer.
float lower = low_frac + frac_range * ((float)column / 18.0 * 0.95);
float upper = low_frac + frac_range * ((float)(column + 1) / 18.0);
float mid = (lower + upper) * 0.5; // Center of lower-to-upper range
float half_width = (upper - lower) * 0.5 + 1e-2; // 1/2 of lower-to-upper range
// Map fractions back to spectrum bin indices that contribute to column
int first_bin = int(pow(2, (float)spectrum_bits * lower) + 1e-4);
int last_bin = int(pow(2, (float)spectrum_bits * upper) + 1e-4);
//Serial.printf("%d %d %d\n", column, first_bin, last_bin);
float total_weight = 0.0; // Accumulate weight for this bin
int num_bins = last_bin - first_bin + 1;
// Allocate space for bin weights for column, stop everything if out of RAM.
column_table[column].bin_weights = (float *)malloc(num_bins * sizeof(float));
if (column_table[column].bin_weights == NULL) err("Malloc fail", 10);
for (int bin_index = first_bin; bin_index <= last_bin; bin_index++) {
// Find distance from column's overall center to individual bin's
// center, expressed as 0.0 (bin at center) to 1.0 (bin at limit of
// lower-to-upper range).
float bin_center = log2f((float)bin_index + 0.5) / (float)spectrum_bits;
float dist = fabs(bin_center - mid) / half_width;
if (dist < 1.0) { // Filter out a few math stragglers at either end
// Bin weights have a cubic falloff curve within range:
dist = 1.0 - dist; // Invert dist so 1.0 is at center
float bin_weight = (((3.0 - (dist * 2.0)) * dist) * dist);
column_table[column].bin_weights[bin_index - first_bin] = bin_weight;
total_weight += bin_weight;
}
}
//Serial.println(column);
// Scale bin weights so total is 1.0 for each column, but then mute
// lower columns slightly and boost higher columns. It graphs better.
for (int i=0; i<num_bins; i++) {
column_table[column].bin_weights[i] = column_table[column].bin_weights[i] /
total_weight * (0.6 + (float)i / 18.0 * 2.0);
//Serial.printf(" %f\n", column_table[column].bin_weights[i]);
}
column_table[column].first_bin = first_bin;
column_table[column].num_bins = num_bins;
column_table[column].color = glasses.color565(glasses.ColorHSV(
57600UL * column / 18, 255, 255)); // Red (0) to purple (57600)
column_table[column].top = 6.0; // Start off bottom of graph
column_table[column].dot = 6.0;
column_table[column].velocity = 0.0;
}
for (int i=0; i<SPECTRUM_SIZE; i++) spectrum[i] = 0.0;
// HARDWARE SETUP ---------
// Configure glasses for max brightness, enable output
glasses.setLEDscaling(0xFF);
glasses.setGlobalCurrent(0xFF);
glasses.enable(true);
// Configure PDM mic, mono 16 KHz
PDM.onReceive(onPDMdata);
PDM.begin(1, 16000);
start_time = millis();
}
void loop() { // Repeat forever...
short *audio_data; // Pointer to newly-received audio
while (mic_on) yield(); // Wait for next buffer to finish recording
// Full buffer received -- active_buf is index to new data
audio_data = &audio_buf[active_buf][0]; // New data is here
active_buf = 1 - active_buf; // Swap buffers to record into other one,
mic_on = true; // and start recording next batch
// Perform FFT operation on newly-received data,
// results go back into the same buffer.
ZeroFFT(audio_data, NUM_SAMPLES);
// Convert FFT output to spectrum. log(y) looks better than raw data.
// Only LOW_BIN to HIGH_BIN elements are needed.
for(int i=LOW_BIN; i<=HIGH_BIN; i++) {
spectrum[i] = (audio_data[i] > 0) ? log((float)audio_data[i]) : 0.0;
}
// Find min & max range of spectrum bin values, with limits.
float lower = spectrum[LOW_BIN], upper = spectrum[LOW_BIN];
for (int i=LOW_BIN+1; i<=HIGH_BIN; i++) {
if (spectrum[i] < lower) lower = spectrum[i];
if (spectrum[i] > upper) upper = spectrum[i];
}
//Serial.printf("%f %f\n", lower, upper);
if (upper < 2.5) upper = 2.5;
// Adjust dynamic level to current spectrum output, keeps the graph
// 'lively' as ambient volume changes. Sparkle but don't saturate.
if (upper > dynamic_level) {
// Got louder. Move level up quickly but allow initial "bump."
dynamic_level = dynamic_level * 0.5 + upper * 0.5;
} else {
// Got quieter. Ease level down, else too many bumps.
dynamic_level = dynamic_level * 0.75 + lower * 0.25;
}
// Apply vertical scale to spectrum data. Results may exceed
// matrix height...that's OK, adds impact!
float scale = 15.0 / (dynamic_level - lower);
for (int i=LOW_BIN; i<=HIGH_BIN; i++) {
spectrum[i] = (spectrum[i] - lower) * scale;
}
// Clear screen, filter and draw each column of the display...
glasses.fill(0);
for(int column=0; column<18; column++) {
int first_bin = column_table[column].first_bin;
// Start BELOW matrix and accumulate bin weights UP, saves math
float column_top = 7.0;
for (int bin_offset=0; bin_offset<column_table[column].num_bins; bin_offset++) {
column_top -= spectrum[first_bin + bin_offset] * column_table[column].bin_weights[bin_offset];
}
// Column top positions are filtered to appear less 'twitchy' --
// last data still has a 30% influence on current positions.
column_top = (column_top * 0.7) + (column_table[column].top * 0.3);
column_table[column].top = column_top;
if(column_top < column_table[column].dot) { // Above current falling dot?
column_table[column].dot = column_top - 0.5; // Move dot up
column_table[column].velocity = 0.0; // and clear out velocity
} else {
column_table[column].dot += column_table[column].velocity; // Move dot down
column_table[column].velocity += 0.015; // and accelerate
}
// Draw column and peak dot
int itop = (int)column_top; // Quantize column top to pixel space
glasses.drawLine(column, itop, column, itop + 20, column_table[column].color);
glasses.drawPixel(column, (int)column_table[column].dot, 0xE410);
}
glasses.show(); // Buffered mode MUST use show() to refresh matrix
frames += 1;
uint32_t elapsed = millis() - start_time;
//Serial.println(frames * 1000 / elapsed);
}
// PDM mic interrupt handler, called when new data is ready
void onPDMdata() {
//digitalWrite(LED_BUILTIN, millis() & 1024); // Debug heartbeat
if (int bytes_to_read = PDM.available()) {
if (mic_on) {
int byte_limit = (NUM_SAMPLES - samples_read) * 2; // Space remaining,
bytes_to_read = min(bytes_to_read, byte_limit); // don't overflow!
PDM.read(&audio_buf[active_buf][samples_read], bytes_to_read);
samples_read += bytes_to_read / 2; // Increment counter
if (samples_read >= NUM_SAMPLES) { // Buffer full?
mic_on = false; // Stop and
samples_read = 0; // reset counter for next time
}
} else {
// Mic is off (code is busy) - must read but discard data.
// audio_buf[2] is a 'bit bucket' for this.
PDM.read(audio_buf[2], bytes_to_read);
}
}
}

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
Uses onboard microphone and a lot of math to react to music.

64
EyeLights_BMP_Animation/code.py Executable file
View file

@ -0,0 +1,64 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
EyeLightsAnim example for Adafruit EyeLights (LED Glasses + Driver).
The accompanying eyelights_anim.py provides pre-drawn frame-by-frame
animation from BMP images. Sort of a catch-all for modest projects that may
want to implement some animation without having to express that animation
entirely in code. The idea is based upon two prior projects:
https://learn.adafruit.com/32x32-square-pixel-display/overview
learn.adafruit.com/circuit-playground-neoanim-using-bitmaps-to-animate-neopixels
The 18x5 matrix and the LED rings are regarded as distinct things, fed from
two separate BMPs (or can use just one or the other). The former guide above
uses the vertical axis for time (like a strip of movie film), while the
latter uses the horizontal axis for time (as in audio or video editing).
Despite this contrast, the same conventions are maintained here to avoid
conflicting explanations...what worked in those guides is what works here,
only the resolutions are different. See also the example BMPs.
"""
import time
import board
from busio import I2C
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
from eyelights_anim import EyeLightsAnim
# HARDWARE SETUP -----------------------
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
# Initialize the IS31 LED driver, buffered for smoother animation
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
glasses.show() # Clear any residue on startup
glasses.global_current = 20 # Just middlin' bright, please
# ANIMATION SETUP ----------------------
# Two indexed-color BMP filenames are specified: first is for the LED matrix
# portion, second is for the LED rings -- or pass None for one or the other
# if not animating that part. The two elements, matrix and rings, share a
# few LEDs in common...by default the rings appear "on top" of the matrix,
# or you can optionally pass a third argument of False to have the rings
# underneath. There's that one odd unaligned pixel between the two though,
# so this may only rarely be desirable.
anim = EyeLightsAnim(glasses, "matrix.bmp", "rings.bmp")
# MAIN LOOP ----------------------------
# This example just runs through a repeating cycle. If you need something
# else, like ping-pong animation, or frames based on a specific time, the
# anim.frame() function can optionally accept two arguments: an index for
# the matrix animation, and an index for the rings.
while True:
anim.frame() # Advance matrix and rings by 1 frame and wrap around
glasses.show() # Update LED matrix
time.sleep(0.02) # Pause briefly

View file

@ -0,0 +1,145 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
EyeLightsAnim provides EyeLights LED glasses with pre-drawn frame-by-frame
animation from BMP images. Sort of a catch-all for modest projects that may
want to implement some animation without having to express that animation
entirely in code. The idea is based upon two prior projects:
https://learn.adafruit.com/32x32-square-pixel-display/overview
learn.adafruit.com/circuit-playground-neoanim-using-bitmaps-to-animate-neopixels
The 18x5 matrix and the LED rings are regarded as distinct things, fed from
two separate BMPs (or can use just one or the other). The former guide above
uses the vertical axis for time (like a strip of movie film), while the
latter uses the horizontal axis for time (as in audio or video editing).
Despite this contrast, the same conventions are maintained here to avoid
conflicting explanations...what worked in those guides is what works here,
only the resolutions are different."""
import displayio
import adafruit_imageload
def gamma_adjust(palette):
"""Given a color palette that was returned by adafruit_imageload, apply
gamma correction and place results back in original palette. This makes
LED brightness and colors more perceptually linear, to better match how
the source BMP might've appeared on screen."""
for index, entry in enumerate(palette):
palette[index] = sum(
[
int(((((entry >> shift) & 0xFF) / 255) ** 2.6) * 255 + 0.5) << shift
for shift in range(16, -1, -8)
]
)
class EyeLightsAnim:
"""Class encapsulating BMP image-based frame animation for the matrix
and rings of an LED_Glasses object."""
def __init__(self, glasses, matrix_filename, ring_filename, rings_on_top=True):
"""Constructor for EyeLightsAnim. Accepts an LED_Glasses object and
filenames for two indexed-color BMP images: first is a "sprite
sheet" for animating on the matrix portion of the glasses, second is
a pixels-over-time graph for the rings portion. Either filename may
be None if not used. Because the matrix and rings share some pixels
in common, the last argument determines the "stacking order" - which
of the two bitmaps is drawn later or "on top." Default of True
places the rings over the matrix, False gives the matrix priority.
It's possible to use transparent palette indices but that may be
more trouble than it's worth."""
self.glasses = glasses
self.matrix_bitmap = self.ring_bitmap = None
self.rings_on_top = rings_on_top
if matrix_filename:
self.matrix_bitmap, self.matrix_palette = adafruit_imageload.load(
matrix_filename, bitmap=displayio.Bitmap, palette=displayio.Palette
)
if (self.matrix_bitmap.width < glasses.width) or (
self.matrix_bitmap.height < glasses.height
):
raise ValueError("Matrix bitmap must be at least 18x5 pixels")
gamma_adjust(self.matrix_palette)
self.tiles_across = self.matrix_bitmap.width // glasses.width
self.tiles_down = self.matrix_bitmap.height // glasses.height
self.matrix_frames = self.tiles_across * self.tiles_down
self.matrix_frame = self.matrix_frames - 1
if ring_filename:
self.ring_bitmap, self.ring_palette = adafruit_imageload.load(
ring_filename, bitmap=displayio.Bitmap, palette=displayio.Palette
)
if self.ring_bitmap.height < 48:
raise ValueError("Ring bitmap must be at least 48 pixels tall")
gamma_adjust(self.ring_palette)
self.ring_frames = self.ring_bitmap.width
self.ring_frame = self.ring_frames - 1
def draw_matrix(self, matrix_frame=None):
"""Draw the matrix portion of EyeLights from one frame of the matrix
bitmap "sprite sheet." Can either request a specific frame index
(starting from 0), or pass None (or no arguments) to advance by one
frame, "wrapping around" to beginning if needed. For internal use by
library; user code should call frame(), not this function."""
if matrix_frame: # Go to specific frame
self.matrix_frame = matrix_frame
else: # Advance one frame forward
self.matrix_frame += 1
self.matrix_frame %= self.matrix_frames # Wrap to valid range
xoffset = self.matrix_frame % self.tiles_across * self.glasses.width
yoffset = self.matrix_frame // self.tiles_across * self.glasses.height
for y in range(self.glasses.height):
y1 = y + yoffset
for x in range(self.glasses.width):
idx = self.matrix_bitmap[x + xoffset, y1]
if not self.matrix_palette.is_transparent(idx):
self.glasses.pixel(x, y, self.matrix_palette[idx])
def draw_rings(self, ring_frame=None):
"""Draw the rings portion of EyeLights from one frame of the rings
bitmap graph. Can either request a specific frame index (starting
from 0), or pass None (or no arguments) to advance by one frame,
'wrapping around' to beginning if needed. For internal use by
library; user code should call frame(), not this function."""
if ring_frame: # Go to specific frame
self.ring_frame = ring_frame
else: # Advance one frame forward
self.ring_frame += 1
self.ring_frame %= self.ring_frames # Wrap to valid range
for y in range(24):
idx = self.ring_bitmap[self.ring_frame, y]
if not self.ring_palette.is_transparent(idx):
self.glasses.left_ring[y] = self.ring_palette[idx]
idx = self.ring_bitmap[self.ring_frame, y + 24]
if not self.ring_palette.is_transparent(idx):
self.glasses.right_ring[y] = self.ring_palette[idx]
def frame(self, matrix_frame=None, ring_frame=None):
"""Draw one frame of animation to the matrix and/or rings portions
of EyeLights. Frame index (starting from 0) for matrix and rings
respectively can be passed as arguments, or either/both may be None
to advance by one frame, 'wrapping around' to beginning if needed.
Because some pixels are shared in common between matrix and rings,
the "stacking order" -- which of the two appears "on top", is
specified as an argument to the constructor."""
if self.matrix_bitmap and self.rings_on_top:
self.draw_matrix(matrix_frame)
if self.ring_bitmap:
self.draw_rings(ring_frame)
if self.matrix_bitmap and not self.rings_on_top:
self.draw_matrix(matrix_frame)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
EyeLights_BMP_Animation/rings.bmp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,326 @@
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*
MOVE-AND-BLINK EYES for Adafruit EyeLights (LED Glasses + Driver).
I'd written a very cool squash-and-stretch effect for the eye movement,
but unfortunately the resolution is such that the pupils just look like
circles regardless. I'm keeping it in despite the added complexity,
because this WILL look great later on a bigger matrix or a TFT/OLED,
and this way the hard parts won't require a re-write at such time.
It's a really adorable effect with enough pixels.
*/
#include <Adafruit_IS31FL3741.h> // For LED driver
// CONFIGURABLES ------------------------
#define RADIUS 3.4 // Size of pupil (3X because of downsampling later)
uint8_t eye_color[3] = { 255, 128, 0 }; // Amber pupils
uint8_t ring_open_color[3] = { 75, 75, 75 }; // Color of LED rings when eyes open
uint8_t ring_blink_color[3] = { 50, 25, 0 }; // Color of LED ring "eyelid" when blinking
// Some boards have just one I2C interface, but some have more...
TwoWire *i2c = &Wire; // e.g. change this to &Wire1 for QT Py RP2040
// GLOBAL VARIABLES ---------------------
Adafruit_EyeLights_buffered glasses(true); // Buffered spex + 3X canvas
GFXcanvas16 *canvas; // Pointer to canvas object
// Reading through the code, you'll see a lot of references to this "3X"
// space. This is referring to the glasses' optional "offscreen" drawing
// canvas that's 3 times the resolution of the LED matrix (i.e. 15 pixels
// tall instead of 5), which gets scaled down to provide some degree of
// antialiasing. It's why the pupils have soft edges and can make
// fractional-pixel motions.
float cur_pos[2] = { 9.0, 7.5 }; // Current position of eye in canvas space
float next_pos[2] = { 9.0, 7.5 }; // Next position "
bool in_motion = false; // true = eyes moving, false = eyes paused
uint8_t blink_state = 0; // 0, 1, 2 = unblinking, closing, opening
uint32_t move_start_time = 0; // For animation timekeeping
uint32_t move_duration = 0;
uint32_t blink_start_time = 0;
uint32_t blink_duration = 0;
float y_pos[13]; // Coords of LED ring pixels in canvas space
uint32_t ring_open_color_packed; // ring_open_color[] as packed RGB integer
uint16_t eye_color565; // eye_color[] as a GFX packed '565' value
uint32_t frames = 0; // For frames-per-second calculation
uint32_t start_time;
// These offsets position each pupil on the canvas grid and make them
// fixate slightly (converge on a point) so they're not always aligned
// the same on the pixel grid, which would be conspicuously pixel-y.
float x_offset[2] = { 5.0, 31.0 };
// These help perform x-axis clipping on the rasterized ellipses,
// so they don't "bleed" outside the rings and require erasing.
int box_x_min[2] = { 3, 33 };
int box_x_max[2] = { 21, 51 };
#define GAMMA 2.6 // For color correction, shouldn't need changing
// HELPER FUNCTIONS ---------------------
// Crude error handler, prints message to Serial console, flashes LED
void err(char *str, uint8_t hz) {
Serial.println(str);
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
}
// Given an [R,G,B] color, apply gamma correction, return packed RGB integer.
uint32_t gammify(uint8_t color[3]) {
uint32_t rgb[3];
for (uint8_t i=0; i<3; i++) {
rgb[i] = uint32_t(pow((float)color[i] / 255.0, GAMMA) * 255 + 0.5);
}
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
}
// Given two [R,G,B] colors and a blend ratio (0.0 to 1.0), interpolate between
// the two colors and return a gamma-corrected in-between color as a packed RGB
// integer. No bounds clamping is performed on blend value, be nice.
uint32_t interp(uint8_t color1[3], uint8_t color2[3], float blend) {
float inv = 1.0 - blend; // Weighting of second color
uint8_t rgb[3];
for(uint8_t i=0; i<3; i++) {
rgb[i] = (int)((float)color1[i] * blend + (float)color2[i] * inv);
}
return gammify(rgb);
}
// Rasterize an arbitrary ellipse into the offscreen 3X canvas, given
// foci point1 and point2 and with area determined by global RADIUS
// (when foci are same point; a circle). Foci and radius are all
// floating point values, which adds to the buttery impression. 'rect'
// is a bounding rect of which pixels are likely affected. Canvas is
// assumed cleared before arriving here.
void rasterize(float point1[2], float point2[2], int rect[4]) {
float perimeter, d;
float dx = point2[0] - point1[0];
float dy = point2[1] - point1[1];
float d2 = dx * dx + dy * dy; // Dist between foci, squared
if (d2 <= 0.0) {
// Foci are in same spot - it's a circle
perimeter = 2.0 * RADIUS;
d = 0.0;
} else {
// Foci are separated - it's an ellipse.
d = sqrt(d2); // Distance between foci
float c = d * 0.5; // Center-to-foci distance
// This is an utterly brute-force way of ellipse-filling based on
// the "two nails and a string" metaphor...we have the foci points
// and just need the string length (triangle perimeter) to yield
// an ellipse with area equal to a circle of 'radius'.
// c^2 = a^2 - b^2 <- ellipse formula
// a = r^2 / b <- substitute
// c^2 = (r^2 / b)^2 - b^2
// b = sqrt(((c^2) + sqrt((c^4) + 4 * r^4)) / 2) <- solve for b
float c2 = c * c;
float b2 = (c2 + sqrt((c2 * c2) + 4 * (RADIUS * RADIUS * RADIUS * RADIUS))) * 0.5;
// By my math, perimeter SHOULD be...
// perimeter = d + 2 * sqrt(b2 + c2);
// ...but for whatever reason, working approach here is really...
perimeter = d + 2 * sqrt(b2);
}
// Like I'm sure there's a way to rasterize this by spans rather than
// all these square roots on every pixel, but for now...
for (int y=rect[1]; y<rect[3]; y++) { // For each row...
float y5 = (float)y + 0.5; // Pixel center
float dy1 = y5 - point1[1]; // Y distance from pixel to first point
float dy2 = y5 - point2[1]; // " to second
dy1 *= dy1; // Y1^2
dy2 *= dy2; // Y2^2
for (int x=rect[0]; x<rect[2]; x++) { // For each column...
float x5 = (float)x + 0.5; // Pixel center
float dx1 = x5 - point1[0]; // X distance from pixel to first point
float dx2 = x5 - point2[0]; // " to second
float d1 = sqrt(dx1 * dx1 + dy1); // 2D distance to first point
float d2 = sqrt(dx2 * dx2 + dy2); // " to second
if ((d1 + d2 + d) <= perimeter) { // Point inside ellipse?
canvas->drawPixel(x, y, eye_color565);
}
}
}
}
// ONE-TIME INITIALIZATION --------------
void setup() {
// Initialize hardware
Serial.begin(115200);
if (! glasses.begin(IS3741_ADDR_DEFAULT, i2c)) err("IS3741 not found", 2);
canvas = glasses.getCanvas();
if (!canvas) err("Can't allocate canvas", 5);
i2c->setClock(1000000); // 1 MHz I2C for extra butteriness
// Configure glasses for reduced brightness, enable output
glasses.setLEDscaling(0xFF);
glasses.setGlobalCurrent(20);
glasses.enable(true);
// INITIALIZE TABLES & OTHER GLOBALS ----
// Pre-compute the Y position of 1/2 of the LEDs in a ring, relative
// to the 3X canvas resolution, so ring & matrix animation can be aligned.
for (uint8_t i=0; i<13; i++) {
float angle = (float)i / 24.0 * M_PI * 2.0;
y_pos[i] = 10.0 - cos(angle) * 12.0;
}
// Convert some colors from [R,G,B] (easier to specify) to packed integers
ring_open_color_packed = gammify(ring_open_color);
eye_color565 = glasses.color565(eye_color[0], eye_color[1], eye_color[2]);
start_time = millis(); // For frames-per-second math
}
// MAIN LOOP ----------------------------
void loop() {
canvas->fillScreen(0);
// The eye animation logic is a carry-over from like a billion
// prior eye projects, so this might be comment-light.
uint32_t now = micros(); // 'Snapshot' the time once per frame
float upper, lower, ratio;
// Blink logic
uint32_t elapsed = now - blink_start_time; // Time since start of blink event
if (elapsed > blink_duration) { // All done with event?
blink_start_time = now; // A new one starts right now
elapsed = 0;
blink_state++; // Cycle closing/opening/paused
if (blink_state == 1) { // Starting new blink...
blink_duration = random(60000, 120000);
} else if (blink_state == 2) { // Switching closing to opening...
blink_duration *= 2; // Opens at half the speed
} else { // Switching to pause in blink
blink_state = 0;
blink_duration = random(500000, 4000000);
}
}
if (blink_state) { // If currently in a blink...
float ratio = (float)elapsed / (float)blink_duration; // 0.0-1.0 as it closes
if (blink_state == 2) ratio = 1.0 - ratio; // 1.0-0.0 as it opens
upper = ratio * 15.0 - 4.0; // Upper eyelid pos. in 3X space
lower = 23.0 - ratio * 8.0; // Lower eyelid pos. in 3X space
}
// Eye movement logic. Two points, 'p1' and 'p2', are the foci of an
// ellipse. p1 moves from current to next position a little faster
// than p2, creating a "squash and stretch" effect (frame rate and
// resolution permitting). When motion is stopped, the two points
// are at the same position.
float p1[2], p2[2];
elapsed = now - move_start_time; // Time since start of move event
if (in_motion) { // Currently moving?
if (elapsed > move_duration) { // If end of motion reached,
in_motion = false; // Stop motion and
memcpy(&p1, &next_pos, sizeof next_pos); // set everything to new position
memcpy(&p2, &next_pos, sizeof next_pos);
memcpy(&cur_pos, &next_pos, sizeof next_pos);
move_duration = random(500000, 1500000); // Wait this long
} else { // Still moving
// Determine p1, p2 position in time
float delta[2];
delta[0] = next_pos[0] - cur_pos[0];
delta[1] = next_pos[1] - cur_pos[1];
ratio = (float)elapsed / (float)move_duration;
if (ratio < 0.6) { // First 60% of move time, p1 is in motion
// Easing function: 3*e^2-2*e^3 0.0 to 1.0
float e = ratio / 0.6; // 0.0 to 1.0
e = 3 * e * e - 2 * e * e * e;
p1[0] = cur_pos[0] + delta[0] * e;
p1[1] = cur_pos[1] + delta[1] * e;
} else { // Last 40% of move time
memcpy(&p1, &next_pos, sizeof next_pos); // p1 has reached end position
}
if (ratio > 0.3) { // Last 70% of move time, p2 is in motion
float e = (ratio - 0.3) / 0.7; // 0.0 to 1.0
e = 3 * e * e - 2 * e * e * e; // Easing func.
p2[0] = cur_pos[0] + delta[0] * e;
p2[1] = cur_pos[1] + delta[1] * e;
} else { // First 30% of move time
memcpy(&p2, &cur_pos, sizeof cur_pos); // p2 waits at start position
}
}
} else { // Eye is stopped
memcpy(&p1, &cur_pos, sizeof cur_pos); // Both foci at current eye position
memcpy(&p2, &cur_pos, sizeof cur_pos);
if (elapsed > move_duration) { // Pause time expired?
in_motion = true; // Start up new motion!
move_start_time = now;
move_duration = random(150000, 250000);
float angle = (float)random(1000) / 1000.0 * M_PI * 2.0;
float dist = (float)random(750) / 100.0;
next_pos[0] = 9.0 + cos(angle) * dist;
next_pos[1] = 7.5 + sin(angle) * dist * 0.8;
}
}
// Draw the raster part of each eye...
for (uint8_t e=0; e<2; e++) {
// Each eye's foci are offset slightly, to fixate toward center
float p1a[2], p2a[2];
p1a[0] = p1[0] + x_offset[e];
p2a[0] = p2[0] + x_offset[e];
p1a[1] = p2a[1] = p1[1];
// Compute bounding rectangle (in 3X space) of ellipse
// (min X, min Y, max X, max Y). Like the ellipse rasterizer,
// this isn't optimal, but will suffice.
int bounds[4];
bounds[0] = max(int(min(p1a[0], p2a[0]) - RADIUS), box_x_min[e]);
bounds[1] = max(max(int(min(p1a[1], p2a[1]) - RADIUS), 0), (int)upper);
bounds[2] = min(int(max(p1a[0], p2a[0]) + RADIUS + 1), box_x_max[e]);
bounds[3] = min(int(max(p1a[1], p2a[1]) + RADIUS + 1), 15);
rasterize(p1a, p2a, bounds); // Render ellipse into buffer
}
// If the eye is currently blinking, and if the top edge of the eyelid
// overlaps the bitmap, draw lines across the bitmap as if eyelids.
if (blink_state and upper >= 0.0) {
int iu = (int)upper;
canvas->drawLine(box_x_min[0], iu, box_x_max[0] - 1, iu, eye_color565);
canvas->drawLine(box_x_min[1], iu, box_x_max[1] - 1, iu, eye_color565);
}
glasses.scale(); // Smooth filter 3X canvas to LED grid
// Matrix and rings share a few pixels. To make the rings take
// precedence, they're drawn later. So blink state is revisited now...
if (blink_state) { // In mid-blink?
for (uint8_t i=0; i<13; i++) { // Half an LED ring, top-to-bottom...
float a = min(max(y_pos[i] - upper + 1.0, 0.0), 3.0);
float b = min(max(lower - y_pos[i] + 1.0, 0.0), 3.0);
ratio = a * b / 9.0; // Proximity of LED to eyelid edges
uint32_t packed = interp(ring_open_color, ring_blink_color, ratio);
glasses.left_ring.setPixelColor(i, packed);
glasses.right_ring.setPixelColor(i, packed);
if ((i > 0) && (i < 12)) {
uint8_t j = 24 - i; // Mirror half-ring to other side
glasses.left_ring.setPixelColor(j, packed);
glasses.right_ring.setPixelColor(j, packed);
}
}
} else {
glasses.left_ring.fill(ring_open_color_packed);
glasses.right_ring.fill(ring_open_color_packed);
}
glasses.show();
frames += 1;
elapsed = millis() - start_time;
Serial.println(frames * 1000 / elapsed);
}

View file

@ -0,0 +1,338 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
MOVE-AND-BLINK EYES for Adafruit EyeLights (LED Glasses + Driver).
I'd written a very cool squash-and-stretch effect for the eye movement,
but unfortunately the resolution and frame rate are such that the pupils
just look like circles regardless. I'm keeping it in despite the added
complexity, because CircuitPython devices WILL get faster, LED matrix
densities WILL improve, and this way the code won't require a re-write
at such a later time. It's a really adorable effect with enough pixels.
"""
import math
import random
import time
from supervisor import reload
import board
from busio import I2C
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
# CONFIGURABLES ------------------------
eye_color = (255, 128, 0) # Amber pupils
ring_open_color = (75, 75, 75) # Color of LED rings when eyes open
ring_blink_color = (50, 25, 0) # Color of LED ring "eyelid" when blinking
radius = 3.4 # Size of pupil (3X because of downsampling later)
# Reading through the code, you'll see a lot of references to this "3X"
# space. What it's referring to is a bitmap that's 3 times the resolution
# of the LED matrix (i.e. 15 pixels tall instead of 5), which gets scaled
# down to provide some degree of antialiasing. It's why the pupils have
# soft edges and can make fractional-pixel motions.
# Because of the way the downsampling is done, the eyelid edge when drawn
# across the eye will always be the same hue as the pupils, it can't be
# set independently like the ring blink color.
gamma = 2.6 # For color adjustment. Leave as-is.
# CLASSES & FUNCTIONS ------------------
class Eye:
"""Holds per-eye positional data; each covers a different area of the
overall LED matrix."""
def __init__(self, left, xoff):
self.left = left # Leftmost column on LED matrix
self.x_offset = xoff # Horizontal offset (3X space) to fixate
def smooth(self, data, rect):
"""Scale bitmap (in 'data') to LED array, with smooth 1:3
downsampling. 'rect' is a 4-tuple rect of which pixels get
filtered (anything outside is cleared to 0), saves a few cycles."""
# Quantize bounds rect from 3X space to LED matrix space.
rect = (
rect[0] // 3, # Left
rect[1] // 3, # Top
(rect[2] + 2) // 3, # Right
(rect[3] + 2) // 3, # Bottom
)
for y in range(rect[1]): # Erase rows above top
for x in range(6):
glasses.pixel(self.left + x, y, 0)
for y in range(rect[1], rect[3]): # Each row, top to bottom...
pixel_sum = bytearray(6) # Initialize row of pixel sums to 0
for y1 in range(3): # 3 rows of bitmap...
row = data[y * 3 + y1] # Bitmap data for current row
for x in range(rect[0], rect[2]): # Column, left to right
x3 = x * 3
# Accumulate 3 pixels of bitmap into pixel_sum
pixel_sum[x] += row[x3] + row[x3 + 1] + row[x3 + 2]
# 'pixel_sum' will now contain values from 0-9, indicating the
# number of set pixels in the corresponding section of the 3X
# bitmap. 'colormap' expands the sum to 24-bit RGB space.
for x in range(rect[0]): # Erase any columns to left
glasses.pixel(self.left + x, y, 0)
for x in range(rect[0], rect[2]): # Column, left to right
glasses.pixel(self.left + x, y, colormap[pixel_sum[x]])
for x in range(rect[2], 6): # Erase columns to right
glasses.pixel(self.left + x, y, 0)
for y in range(rect[3], 5): # Erase rows below bottom
for x in range(6):
glasses.pixel(self.left + x, y, 0)
# pylint: disable=too-many-locals
def rasterize(data, point1, point2, rect):
"""Rasterize an arbitrary ellipse into the 'data' bitmap (3X pixel
space), given foci point1 and point2 and with area determined by global
'radius' (when foci are same point; a circle). Foci and radius are all
floating point values, which adds to the buttery impression. 'rect' is
a 4-tuple rect of which pixels are likely affected. Data is assumed 0
before arriving here; no clearing is performed."""
dx = point2[0] - point1[0]
dy = point2[1] - point1[1]
d2 = dx * dx + dy * dy # Dist between foci, squared
if d2 <= 0:
# Foci are in same spot - it's a circle
perimeter = 2 * radius
d = 0
else:
# Foci are separated - it's an ellipse.
d = d2 ** 0.5 # Distance between foci
c = d * 0.5 # Center-to-foci distance
# This is an utterly brute-force way of ellipse-filling based on
# the "two nails and a string" metaphor...we have the foci points
# and just need the string length (triangle perimeter) to yield
# an ellipse with area equal to a circle of 'radius'.
# c^2 = a^2 - b^2 <- ellipse formula
# a = r^2 / b <- substitute
# c^2 = (r^2 / b)^2 - b^2
# b = sqrt(((c^2) + sqrt((c^4) + 4 * r^4)) / 2) <- solve for b
b2 = ((c ** 2) + (((c ** 4) + 4 * (radius ** 4)) ** 0.5)) * 0.5
# By my math, perimeter SHOULD be...
# perimeter = d + 2 * ((b2 + (c ** 2)) ** 0.5)
# ...but for whatever reason, working approach here is really...
perimeter = d + 2 * (b2 ** 0.5)
# Like I'm sure there's a way to rasterize this by spans rather than
# all these square roots on every pixel, but for now...
for y in range(rect[1], rect[3]): # For each row...
y5 = y + 0.5 # Pixel center
dy1 = y5 - point1[1] # Y distance from pixel to first point
dy2 = y5 - point2[1] # " to second
dy1 *= dy1 # Y1^2
dy2 *= dy2 # Y2^2
for x in range(rect[0], rect[2]): # For each column...
x5 = x + 0.5 # Pixel center
dx1 = x5 - point1[0] # X distance from pixel to first point
dx2 = x5 - point2[0] # " to second
d1 = (dx1 * dx1 + dy1) ** 0.5 # 2D distance to first point
d2 = (dx2 * dx2 + dy2) ** 0.5 # " to second
if (d1 + d2 + d) <= perimeter:
data[y][x] = 1 # Point is inside ellipse
def gammify(color):
"""Given an (R,G,B) color tuple, apply gamma correction and return
a packed 24-bit RGB integer."""
rgb = [int(((color[x] / 255) ** gamma) * 255 + 0.5) for x in range(3)]
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]
def interp(color1, color2, blend):
"""Given two (R,G,B) color tuples and a blend ratio (0.0 to 1.0),
interpolate between the two colors and return a gamma-corrected
in-between color as a packed 24-bit RGB integer. No bounds clamping
is performed on blend value, be nice."""
inv = 1.0 - blend # Weighting of second color
return gammify([color1[x] * blend + color2[x] * inv for x in range(3)])
# HARDWARE SETUP -----------------------
# Manually declare I2C (not board.I2C() directly) to access 1 MHz speed...
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
# Initialize the IS31 LED driver, buffered for smoother animation
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
glasses.show() # Clear any residue on startup
glasses.global_current = 20 # Just middlin' bright, please
# INITIALIZE TABLES & OTHER GLOBALS ----
# This table is for mapping 3x3 averaged bitmap values (0-9) to
# RGB colors. Avoids a lot of shift-and-or on every pixel.
colormap = []
for n in range(10):
colormap.append(gammify([n / 9 * eye_color[x] for x in range(3)]))
# Pre-compute the Y position of 1/2 of the LEDs in a ring, relative
# to the 3X bitmap resolution, so ring & matrix animation can be aligned.
y_pos = []
for n in range(13):
angle = n / 24 * math.pi * 2
y_pos.append(10 - math.cos(angle) * 12)
# Pre-compute color of LED ring in fully open (unblinking) state
ring_open_color_packed = gammify(ring_open_color)
# A single pre-computed scanline of "eyelid edge during blink" can be
# stuffed into the 3X raster as needed, avoids setting pixels manually.
eyelid = (
b"\x01\x01\x00\x01\x01\x00\x01\x01\x00" b"\x01\x01\x00\x01\x01\x00\x01\x01\x00"
) # 2/3 of pixels set
# Initialize eye position and move/blink animation timekeeping
cur_pos = next_pos = (9, 7.5) # Current, next eye position in 3X space
in_motion = False # True = eyes moving, False = eyes paused
blink_state = 0 # 0, 1, 2 = unblinking, closing, opening
move_start_time = move_duration = blink_start_time = blink_duration = 0
# Two eye objects. The first starts at column 1 of the matrix with its
# pupil offset by +2 (in 3X space), second at column 11 with -2 offset.
# The offsets make the pupils fixate slightly (converge on a point), so
# the two pupils aren't always aligned the same on the pixel grid, which
# would be conspicuously pixel-y.
eyes = [Eye(1, 2), Eye(11, -2)]
frames, start_time = 0, time.monotonic() # For frames/second calculation
# MAIN LOOP ----------------------------
while True:
# The try/except here is because VERY INFREQUENTLY the I2C bus will
# encounter an error when accessing the LED driver, whether from bumping
# around the wires or sometimes an I2C device just gets wedged. To more
# robustly handle the latter, the code will restart if that happens.
try:
# The eye animation logic is a carry-over from like a billion
# prior eye projects, so this might be comment-light.
now = time.monotonic() # 'Snapshot' the time once per frame
# Blink logic
elapsed = now - blink_start_time # Time since start of blink event
if elapsed > blink_duration: # All done with event?
blink_start_time = now # A new one starts right now
elapsed = 0
blink_state += 1 # Cycle closing/opening/paused
if blink_state == 1: # Starting new blink...
blink_duration = random.uniform(0.06, 0.12)
elif blink_state == 2: # Switching closing to opening...
blink_duration *= 2 # Opens at half the speed
else: # Switching to pause in blink
blink_state = 0
blink_duration = random.uniform(0.5, 4)
if blink_state: # If currently in a blink...
ratio = elapsed / blink_duration # 0.0-1.0 as it closes
if blink_state == 2:
ratio = 1.0 - ratio # 1.0-0.0 as it opens
upper = ratio * 15 - 4 # Upper eyelid pos. in 3X space
lower = 23 - ratio * 8 # Lower eyelid pos. in 3X space
# Eye movement logic. Two points, 'p1' and 'p2', are the foci of an
# ellipse. p1 moves from current to next position a little faster
# than p2, creating a "squash and stretch" effect (frame rate and
# resolution permitting). When motion is stopped, the two points
# are at the same position.
elapsed = now - move_start_time # Time since start of move event
if in_motion: # Currently moving?
if elapsed > move_duration: # If end of motion reached,
in_motion = False # Stop motion and
p1 = p2 = cur_pos = next_pos # Set to new position
move_duration = random.uniform(0.5, 1.5) # Wait this long
else: # Still moving
# Determine p1, p2 position in time
delta = (next_pos[0] - cur_pos[0], next_pos[1] - cur_pos[1])
ratio = elapsed / move_duration
if ratio < 0.6: # First 60% of move time
# p1 is in motion
# Easing function: 3*e^2-2*e^3 0.0 to 1.0
e = ratio / 0.6 # 0.0 to 1.0
e = 3 * e * e - 2 * e * e * e
p1 = (cur_pos[0] + delta[0] * e, cur_pos[1] + delta[1] * e)
else: # Last 40% of move time
p1 = next_pos # p1 has reached end position
if ratio > 0.3: # Last 60% of move time
# p2 is in motion
e = (ratio - 0.3) / 0.7 # 0.0 to 1.0
e = 3 * e * e - 2 * e * e * e # Easing func.
p2 = (cur_pos[0] + delta[0] * e, cur_pos[1] + delta[1] * e)
else: # First 40% of move time
p2 = cur_pos # p2 waits at start position
else: # Eye is stopped
p1 = p2 = cur_pos # Both foci at current eye position
if elapsed > move_duration: # Pause time expired?
in_motion = True # Start up new motion!
move_start_time = now
move_duration = random.uniform(0.15, 0.25)
angle = random.uniform(0, math.pi * 2)
dist = random.uniform(0, 7.5)
next_pos = (
9 + math.cos(angle) * dist,
7.5 + math.sin(angle) * dist * 0.8,
)
# Draw the raster part of each eye...
for eye in eyes:
# Allocate/clear the 3X bitmap buffer
bitmap = [bytearray(6 * 3) for _ in range(5 * 3)]
# Each eye's foci are offset slightly, to fixate toward center
p1a = (p1[0] + eye.x_offset, p1[1])
p2a = (p2[0] + eye.x_offset, p2[1])
# Compute bounding rectangle (in 3X space) of ellipse
# (min X, min Y, max X, max Y). Like the ellipse rasterizer,
# this isn't optimal, but will suffice.
bounds = (
max(int(min(p1a[0], p2a[0]) - radius), 0),
max(int(min(p1a[1], p2a[1]) - radius), 0, int(upper)),
min(int(max(p1a[0], p2a[0]) + radius + 1), 18),
min(int(max(p1a[1], p2a[1]) + radius + 1), 15, int(lower) + 1),
)
rasterize(bitmap, p1a, p2a, bounds) # Render ellipse into buffer
# If the eye is currently blinking, and if the top edge of the
# eyelid overlaps the bitmap, draw a scanline across the bitmap
# and update the bounds rect so the whole width of the bitmap
# is scaled.
if blink_state and upper >= 0:
bitmap[int(upper)] = eyelid
bounds = (0, int(upper), 18, bounds[3])
eye.smooth(bitmap, bounds) # 1:3 downsampling for eye
# Matrix and rings share a few pixels. To make the rings take
# precedence, they're drawn later. So blink state is revisited now...
if blink_state: # In mid-blink?
for i in range(13): # Half an LED ring, top-to-bottom...
a = min(max(y_pos[i] - upper + 1, 0), 3)
b = min(max(lower - y_pos[i] + 1, 0), 3)
ratio = a * b / 9 # Proximity of LED to eyelid edges
packed = interp(ring_open_color, ring_blink_color, ratio)
glasses.left_ring[i] = glasses.right_ring[i] = packed
if 0 < i < 12:
i = 24 - i # Mirror half-ring to other side
glasses.left_ring[i] = glasses.right_ring[i] = packed
else:
glasses.left_ring.fill(ring_open_color_packed)
glasses.right_ring.fill(ring_open_color_packed)
glasses.show() # Buffered mode MUST use show() to refresh matrix
except OSError: # See "try" notes above regarding rare I2C errors.
print("Restarting")
reload()
frames += 1
elapsed = time.monotonic() - start_time
print(frames / elapsed)

View file

@ -0,0 +1,202 @@
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*
BLUETOOTH SCROLLING MESSAGE for Adafruit EyeLights (LED Glasses + Driver).
Use BLUEFRUIT CONNECT app on iOS or Android to connect to LED glasses.
Use the app's UART input to enter a new message.
Use the app's Color Picker (under "Controller") to change text color.
This is based on the glassesdemo-3-smooth example from the
Adafruit_IS31FL3741 library, with Bluetooth additions on top. If this
code all seems a bit too much, you can start with that example (or the two
that precede it) to gain an understanding of the LED glasses basics, then
return here to see what the extra Bluetooth layers do.
*/
#include <Adafruit_IS31FL3741.h> // For LED driver
#include <bluefruit.h> // For Bluetooth communication
#include <EyeLightsCanvasFont.h> // Smooth scrolly font for glasses
// These items are over in the packetParser.cpp tab:
extern uint8_t packetbuffer[];
extern uint8_t readPacket(BLEUart *ble, uint16_t timeout);
extern int8_t packetType(uint8_t *buf, uint8_t len);
extern float parsefloat(uint8_t *buffer);
extern void printHex(const uint8_t * data, const uint32_t numBytes);
// GLOBAL VARIABLES -------
// 'Buffered' glasses for buttery animation,
// 'true' to allocate a drawing canvas for smooth graphics:
Adafruit_EyeLights_buffered glasses(true);
GFXcanvas16 *canvas; // Pointer to glasses' canvas object
// Because 'canvas' is a pointer, always use -> when calling
// drawing functions there. 'glasses' is an object in itself,
// so . is used when calling its functions.
char message[51] = "Run Bluefruit Connect app"; // Scrolling message
int16_t text_x; // Message position on canvas
int16_t text_min; // Leftmost position before restarting scroll
BLEUart bleuart; // Bluetooth low energy UART
int8_t last_packet_type = 99; // Last BLE packet type, init to nonsense value
// ONE-TIME SETUP ---------
void setup() { // Runs once at program start...
Serial.begin(115200);
//while(!Serial);
// Configure and start the BLE UART service
Bluefruit.begin();
Bluefruit.setTxPower(4);
bleuart.begin();
startAdv(); // Set up and start advertising
if (!glasses.begin()) err("IS3741 not found", 2);
canvas = glasses.getCanvas();
if (!canvas) err("Can't allocate canvas", 5);
// Configure glasses for full brightness and enable output
glasses.setLEDscaling(0xFF);
glasses.setGlobalCurrent(0xFF);
glasses.enable(true);
// Set up for scrolling text, initialize color and position
canvas->setFont(&EyeLightsCanvasFont);
canvas->setTextWrap(false); // Allow text to extend off edges
canvas->setTextColor(glasses.color565(0x303030)); // Dim white to start
reposition_text(); // Sets up initial position & scroll limit
}
// Crude error handler, prints message to Serial console, flashes LED
void err(char *str, uint8_t hz) {
Serial.println(str);
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
}
// Set up, start BLE advertising
void startAdv(void) {
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
// Include the BLE UART (AKA 'NUS') 128-bit UUID
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)
// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.addName();
// Start Advertising
// - Enable auto advertising if disconnected
// - Interval: fast mode = 20 ms, slow mode = 152.5 ms
// - Timeout for fast mode is 30 seconds
// - Start(timeout) with timeout = 0 will advertise forever (until connected)
//
// For recommended advertising interval
// https://developer.apple.com/library/content/qa/qa1931/_index.html
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
}
// MAIN LOOP --------------
void loop() { // Repeat forever...
// The packet read timeout (9 ms here) also determines the text
// scrolling speed -- if no data is received over BLE in that time,
// the function exits and returns here with len=0.
uint8_t len = readPacket(&bleuart, 9);
if (len) {
int8_t type = packetType(packetbuffer, len);
// The Bluefruit Connect app can return a variety of data from
// a phone's sensors. To keep this example relatively simple,
// we'll only look at color and text, but here's where others
// would go if we were to extend this. See Bluefruit library
// examples for the packet data formats. packetParser.cpp
// has a couple functions not used in this code but that may be
// helpful in interpreting these other packet types.
switch(type) {
case 0: // Accelerometer
Serial.println("Accel");
break;
case 1: // Gyro:
Serial.println("Gyro");
break;
case 2: // Magnetometer
Serial.println("Mag");
break;
case 3: // Quaternion
Serial.println("Quat");
break;
case 4: // Button
Serial.println("Button");
break;
case 5: // Color
Serial.println("Color");
// packetbuffer[2] through [4] contain R, G, B byte values.
// Because the drawing canvas uses lower-precision '565' color,
// and because glasses.scale() applies gamma correction and may
// quantize the dimmest colors to 0, set a brightness floor here
// so text isn't invisible.
for (uint8_t i=2; i<=4; i++) {
if (packetbuffer[i] < 0x20) packetbuffer[i] = 0x20;
}
canvas->setTextColor(glasses.color565(glasses.Color(
packetbuffer[2], packetbuffer[3], packetbuffer[4])));
break;
case 6: // Location
Serial.println("Location");
break;
default: // -1
// Packet is not one of the Bluefruit Connect types. Most programs
// will ignore/reject it as not valud, but in this case we accept
// it as a freeform string for the scrolling message.
if (last_packet_type != -1) {
// If prior data was a packet, this is a new freeform string,
// initialize the message string with it...
strncpy(message, (char *)packetbuffer, 20);
} else {
// If prior data was also a freeform string, concatenate this onto
// the message (up to the max message length). BLE packets can only
// be so large, so long strings are broken into multiple packets.
uint8_t message_len = strlen(message);
uint8_t max_append = sizeof message - 1 - message_len;
strncpy(&message[message_len], (char *)packetbuffer, max_append);
len = message_len + max_append;
}
message[len] = 0; // End of string NUL char
Serial.println(message);
reposition_text(); // Reset text off right edge of canvas
}
last_packet_type = type; // Save packet type for next pass
} else {
last_packet_type = 99; // BLE read timeout, reset last type to nonsense
}
canvas->fillScreen(0); // Clear the whole drawing canvas
// Update text to new position, and draw on canvas
if (--text_x < text_min) { // If text scrolls off left edge,
text_x = canvas->width(); // reset position off right edge
}
canvas->setCursor(text_x, canvas->height());
canvas->print(message);
glasses.scale(); // 1:3 downsample canvas to LED matrix
glasses.show(); // MUST call show() to update matrix
}
// When new message text is assigned, call this to reset its position
// off the right edge and calculate column where scrolling resets.
void reposition_text() {
uint16_t w, h, ignore;
canvas->getTextBounds(message, 0, 0, (int16_t *)&ignore, (int16_t *)&ignore, &w, &ignore);
text_x = canvas->width();
text_min = -w; // Off left edge this many pixels
}

View file

@ -0,0 +1,119 @@
#include <bluefruit.h>
// packetbuffer holds inbound data
#define READ_BUFSIZE 20
uint8_t packetbuffer[READ_BUFSIZE + 1]; // +1 is for NUL string terminator
/**************************************************************************/
/*!
@brief Casts the four bytes at the specified address to a float.
@param ptr Pointer into packet buffer.
@returns Floating-point value.
*/
/**************************************************************************/
float parsefloat(uint8_t *ptr) {
float f; // Make a suitably-aligned float variable,
memcpy(&f, ptr, 4); // because in-buffer instance might be misaligned!
return f; // (You can't always safely parse in-place)
}
/**************************************************************************/
/*!
@brief Prints a series of bytes in 0xNN hexadecimal notation.
@param buf Pointer to array of byte data.
@param len Data length in bytes.
*/
/**************************************************************************/
void printHex(const uint8_t *buf, const uint32_t len) {
for (uint32_t i=0; i < len; i++) {
Serial.print(F("0x"));
if (buf[i] <= 0xF) Serial.write('0'); // Zero-pad small values
Serial.print(buf[i], HEX);
if (i < (len - 1)) Serial.write(' '); // Space between bytes
}
Serial.println();
}
static const struct { // Special payloads from Bluefruit Connect app...
char id; // Packet type identifier
uint8_t len; // Size of complete, well-formed packet of this type
} _app_packet[] = {
{'A', 15}, // Accelerometer
{'G', 15}, // Gyro
{'M', 15}, // Magnetometer
{'Q', 19}, // Quaterion
{'B', 5}, // Button
{'C', 6}, // Color
{'L', 15}, // Location
};
#define NUM_PACKET_TYPES (sizeof _app_packet / sizeof _app_packet[0])
#define SHORTEST_PACKET_LEN 5 // Button, for now
/**************************************************************************/
/*!
@brief Given packet data, identify if it's one of the known
Bluefruit Connect app packet types.
@param buf Pointer to packet data.
@param len Size of packet in bytes.
@returns Packet type index (0 to NUM_PACKET_TYPES-1) if recognized,
-1 if unrecognized.
*/
/**************************************************************************/
int8_t packetType(uint8_t *buf, uint8_t len) {
if ((len >= SHORTEST_PACKET_LEN) && (buf[0] == '!')) {
for (int8_t type=0; type<NUM_PACKET_TYPES; type++) {
if ((buf[1] == _app_packet[type].id) &&
(len == _app_packet[type].len)) {
return type;
}
}
}
return -1; // Length too short for a packet, or not a recognized type
}
/**************************************************************************/
/*!
@brief Wait for incoming data and determine if it's one of the
special Bluefruit Connect app packet types.
@param ble Pointer to BLE UART object.
timeout Character read timeout in milliseconds.
@returns Length of data, or 0 if checksum is invalid for the type of
packet detected.
@note Packet buffer is not cleared. Calling function is expected
to check return value before deciding whether to act on the
data.
*/
/**************************************************************************/
uint8_t readPacket(BLEUart *ble, uint16_t timeout) {
int8_t type = -1; // App packet type, -1 if unknown or freeform string
uint8_t len = 0, xsum = 255; // Packet length and ~checksum so far
uint32_t now, start_time = millis();
do {
now = millis();
if (ble->available()) {
char c = ble->read();
if (c == '!') { // '!' resets buffer to start
len = 0;
xsum = 255;
}
packetbuffer[len++] = c;
// Stop when buffer's full or packet type recognized
if ((len >= READ_BUFSIZE) ||
((type = packetType(packetbuffer, len)) >= 0)) break;
start_time = now; // Reset timeout on char received
xsum -= c; // Not last char, do checksum math
type = -1; // Reset packet type finder
}
} while((now - start_time) < timeout);
// If packet type recognized, verify checksum (else freeform string)
if ((type >= 0) && (xsum != packetbuffer[len-1])) { // Last byte = checksum
Serial.print("Packet checksum mismatch: ");
printHex(packetbuffer, len);
return 0;
}
packetbuffer[len] = 0; // Terminate packet string
return len; // Checksum is valid for packet, or it's a freeform string
}

View file

@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*
FIRE EFFECT for Adafruit EyeLights (LED Glasses + Driver).
A demoscene classic that produces a cool analog-esque look with
modest means, iteratively scrolling and blurring raster data.
*/
#include <Adafruit_IS31FL3741.h> // For LED driver
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
// The raster data is intentionally one row taller than the LED matrix.
// Each frame, random noise is put in the bottom (off matrix) row. There's
// also an extra column on either side, to avoid needing edge clipping when
// neighboring pixels (left, center, right) are averaged later.
float data[6][20]; // 2D array where elements are accessed as data[y][x]
// Each element in the raster is a single value representing brightness.
// A pre-computed lookup table maps these to RGB colors. This one happens
// to have 32 elements, but as we're not on an actual paletted hardware
// framebuffer it could be any size really (with suitable changes throughout).
uint32_t colormap[32];
#define GAMMA 2.6
// Crude error handler, prints message to Serial console, flashes LED
void err(char *str, uint8_t hz) {
Serial.println(str);
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
}
void setup() { // Runs once at program start...
// Initialize hardware
Serial.begin(115200);
if (! glasses.begin()) err("IS3741 not found", 2);
// Configure glasses for reduced brightness, enable output
glasses.setLEDscaling(0xFF);
glasses.setGlobalCurrent(20);
glasses.enable(true);
memset(data, 0, sizeof data);
for(uint8_t i=0; i<32; i++) {
float n = i * 3.0 / 31.0; // 0.0 <= n <= 3.0 from start to end of map
float r, g, b;
if (n <= 1) { // 0.0 <= n <= 1.0 : black to red
r = n; // r,g,b are initially calculated 0 to 1 range
g = b = 0.0;
} else if (n <= 2) { // 1.0 <= n <= 2.0 : red to yellow
r = 1.0;
g = n - 1.0;
b = 0.0;
} else { // 2.0 <= n <= 3.0 : yellow to white
r = g = 1.0;
b = n - 2.0;
}
// Gamma correction linearizes perceived brightness, then scale to
// 0-255 for LEDs and store as a 'packed' RGB color.
colormap[i] = (uint32_t(pow(r, GAMMA) * 255.0) << 16) |
(uint32_t(pow(g, GAMMA) * 255.0) << 8) |
uint32_t(pow(b, GAMMA) * 255.0);
}
}
// Linearly interpolate a range of brightnesses between two LEDs of
// one eyeglass ring, mapping through the global color table. LED range
// is non-inclusive; the first and last LEDs (which overlap matrix pixels)
// are not set. led2 MUST be > led1. LED indices may be >= 24 to 'wrap
// around' the seam at the top of the ring.
void interp(bool isRight, int led1, int led2, float level1, float level2) {
int span = led2 - led1 + 1; // Number of LEDs
float delta = level2 - level1; // Difference in brightness
for (int led = led1 + 1; led < led2; led++) { // For each LED in-between,
float ratio = (float)(led - led1) / span; // interpolate brightness level
uint32_t color = colormap[min(31, int(level1 + delta * ratio))];
if (isRight) glasses.right_ring.setPixelColor(led % 24, color);
else glasses.left_ring.setPixelColor(led % 24, color);
}
}
void loop() { // Repeat forever...
// At the start of each frame, fill the bottom (off matrix) row
// with random noise. To make things less strobey, old data from the
// prior frame still has about 1/3 'weight' here. There's no special
// real-world significance to the 85, it's just an empirically-
// derived fudge factor that happens to work well with the size of
// the color map.
for (uint8_t x=1; x<19; x++) {
data[5][x] = 0.33 * data[5][x] + 0.67 * ((float)random(1000) / 1000.0) * 85.0;
}
// If this were actual SRS BZNS 31337 D3M0SC3N3 code, great care
// would be taken to avoid floating-point math. But with few pixels,
// and so this code might be less obtuse, a casual approach is taken.
// Each row (except last) is then processed, top-to-bottom. This
// order is important because it's an iterative algorithm...the
// output of each frame serves as input to the next, and the steps
// below (looking at the pixels below each row) are what makes the
// "flames" appear to move "up."
for (uint8_t y=0; y<5; y++) { // Current row of pixels
float *y1 = &data[y + 1][0]; // One row down
for (uint8_t x = 1; x < 19; x++) { // Skip left, right columns in data
// Each pixel is sort of the average of the three pixels
// under it (below left, below center, below right), but not
// exactly. The below center pixel has more 'weight' than the
// others, and the result is scaled to intentionally land
// short, making each row bit darker as they move up.
data[y][x] = (y1[x] + ((y1[x - 1] + y1[x + 1]) * 0.33)) * 0.35;
glasses.drawPixel(x - 1, y, glasses.color565(colormap[min(31, int(data[y][x]))]));
// Remember that the LED matrix uses GFX-style "565" colors,
// hence the round trip through color565() here, whereas the LED
// rings (referenced in interp()) use NeoPixel-style 24-bit colors
// (those can reference colormap[] directly).
}
}
// That's all well and good for the matrix, but what about the extra
// LEDs in the rings? Since these don't align to the pixel grid,
// rather than trying to extend the raster data and filter it in
// somehow, we'll fill those arcs with colors interpolated from the
// endpoints where rings and matrix intersect. Maybe not perfect,
// but looks okay enough!
interp(false, 7, 17, data[4][8], data[4][1]); // Left ring bottom
interp(false, 21, 29, data[0][2], data[1][8]); // Left ring top
interp(true, 7, 17, data[4][18], data[4][11]); // Right ring bottom
interp(true, 19, 27, data[1][11], data[0][17]); // Right ring top
glasses.show();
delay(25);
}

View file

@ -0,0 +1,131 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
FIRE EFFECT for Adafruit EyeLights (LED Glasses + Driver).
A demoscene classic that produces a cool analog-esque look with
modest means, iteratively scrolling and blurring raster data.
"""
import random
from supervisor import reload
import board
from busio import I2C
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
# HARDWARE SETUP ---------
# Manually declare I2C (not board.I2C() directly) to access 1 MHz speed...
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
# Initialize the IS31 LED driver, buffered for smoother animation
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
glasses.show() # Clear any residue on startup
glasses.global_current = 20 # Just middlin' bright, please
# INITIALIZE TABLES ------
# The raster data is intentionally one row taller than the LED matrix.
# Each frame, random noise is put in the bottom (off matrix) row. There's
# also an extra column on either side, to avoid needing edge clipping when
# neighboring pixels (left, center, right) are averaged later.
data = [[0] * (glasses.width + 2) for _ in range(glasses.height + 1)]
# (2D array where elements are accessed as data[y][x], initialized to 0)
# Each element in the raster is a single value representing brightness.
# A pre-computed lookup table maps these to RGB colors. This one happens
# to have 32 elements, but as we're not on an actual paletted hardware
# framebuffer it could be any size really (with suitable changes throughout).
gamma = 2.6
colormap = []
for n in range(32):
n *= 3 / 31 # 0.0 <= n <= 3.0 from start to end of map
if n <= 1: # 0.0 <= n <= 1.0 : black to red
r = n # r,g,b are initially calculated 0 to 1 range
g = b = 0
elif n <= 2: # 1.0 <= n <= 2.0 : red to yellow
r = 1
g = n - 1
b = 0
else: # 2.0 <= n <= 3.0 : yellow to white
r = g = 1
b = n - 2
r = int((r ** gamma) * 255) # Gamma correction linearizes
g = int((g ** gamma) * 255) # perceived brightness, then
b = int((b ** gamma) * 255) # scale to 0-255 for LEDs and
colormap.append((r << 16) | (g << 8) | b) # store as 'packed' RGB color
# UTILITY FUNCTIONS -----
def interp(ring, led1, led2, level1, level2):
"""Linearly interpolate a range of brightnesses between two LEDs of
one eyeglass ring, mapping through the global color table. LED range
is non-inclusive; the first and last LEDs (which overlap matrix pixels)
are not set. led2 MUST be > led1. LED indices may be >= 24 to 'wrap
around' the seam at the top of the ring."""
span = led2 - led1 + 1 # Number of LEDs
delta = level2 - level1 # Difference in brightness
for led in range(led1 + 1, led2): # For each LED in-between,
ratio = (led - led1) / span # interpolate brightness level
ring[led % 24] = colormap[min(31, int(level1 + delta * ratio))]
# MAIN LOOP -------------
while True:
# The try/except here is because VERY INFREQUENTLY the I2C bus will
# encounter an error when accessing the LED driver, whether from bumping
# around the wires or sometimes an I2C device just gets wedged. To more
# robustly handle the latter, the code will restart if that happens.
try:
# At the start of each frame, fill the bottom (off matrix) row
# with random noise. To make things less strobey, old data from the
# prior frame still has about 1/3 'weight' here. There's no special
# real-world significance to the 85, it's just an empirically-
# derived fudge factor that happens to work well with the size of
# the color map.
for x in range(1, 19):
data[5][x] = 0.33 * data[5][x] + 0.67 * random.random() * 85
# If this were actual SRS BZNS 31337 D3M0SC3N3 code, great care
# would be taken to avoid floating-point math. But with few pixels,
# and so this code might be less obtuse, a casual approach is taken.
# Each row (except last) is then processed, top-to-bottom. This
# order is important because it's an iterative algorithm...the
# output of each frame serves as input to the next, and the steps
# below (looking at the pixels below each row) are what makes the
# "flames" appear to move "up."
for y in range(5): # Current row of pixels
y1 = data[y + 1] # One row down
for x in range(1, 19): # Skip left, right columns in data
# Each pixel is sort of the average of the three pixels
# under it (below left, below center, below right), but not
# exactly. The below center pixel has more 'weight' than the
# others, and the result is scaled to intentionally land
# short, making each row bit darker as they move up.
data[y][x] = (y1[x] + ((y1[x - 1] + y1[x + 1]) * 0.33)) * 0.35
glasses.pixel(x - 1, y, colormap[min(31, int(data[y][x]))])
# That's all well and good for the matrix, but what about the extra
# LEDs in the rings? Since these don't align to the pixel grid,
# rather than trying to extend the raster data and filter it in
# somehow, we'll fill those arcs with colors interpolated from the
# endpoints where rings and matrix intersect. Maybe not perfect,
# but looks okay enough!
interp(glasses.left_ring, 7, 17, data[4][8], data[4][1])
interp(glasses.left_ring, 21, 29, data[0][2], data[2][8])
interp(glasses.right_ring, 7, 17, data[4][18], data[4][11])
interp(glasses.right_ring, 19, 27, data[2][11], data[0][17])
glasses.show() # Buffered mode MUST use show() to refresh matrix
except OSError: # See "try" notes above regarding rare I2C errors.
print("Restarting")
reload()

View file

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*
GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics
simulation using accelerometer and math. This uses only the rings, not the
matrix portion. Adapted from Bill Earl's STEAM-Punk Goggles project:
https://learn.adafruit.com/steam-punk-goggles
*/
#include <Adafruit_IS31FL3741.h> // For LED driver
#include <Adafruit_LIS3DH.h> // For accelerometer
#include <Adafruit_Sensor.h> // For m/s^2 accel units
Adafruit_LIS3DH accel;
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
// A small class for our pendulum simulation.
class Pendulum {
public:
// Constructor. Pass pointer to EyeLights ring, and a 3-byte color array.
Pendulum(Adafruit_EyeLights_Ring_buffered *r, uint8_t *c) {
ring = r;
color = c;
// Initial pendulum position, plus axle friction, are randomized
// so rings don't spin in perfect lockstep.
angle = random(1000);
momentum = 0.0;
friction = 0.94 + random(300) * 0.0001; // Inverse friction, really
}
// Given a pixel index (0-23) and a scaling factor (0.0-1.0),
// interpolate between LED "off" color (at 0.0) and this item's fully-
// lit color (at 1.0) and set pixel to the result.
void interp(uint8_t pixel, float scale) {
// Convert separate red, green, blue to "packed" 24-bit RGB value
ring->setPixelColor(pixel,
(int(color[0] * scale) << 16) |
(int(color[1] * scale) << 8) |
int(color[2] * scale));
}
// Given an accelerometer reading, run one cycle of the pendulum
// physics simulation and render the corresponding LED ring.
void iterate(sensors_event_t &event) {
// Minus here is because LED pixel indices run clockwise vs. trigwise.
// 0.006 is just an empirically-derived scaling fudge factor that looks
// good; smaller values for more sluggish rings, higher = more twitch.
momentum = momentum * friction - 0.006 *
(cos(angle) * event.acceleration.z +
sin(angle) * event.acceleration.x);
angle += momentum;
// Scale pendulum angle into pixel space
float midpoint = fmodf(angle * 12.0 / M_PI, 24.0);
// Go around the whole ring, setting each pixel based on proximity
// (this is also to erase the prior position)...
for (uint8_t i=0; i<24; i++) {
float dist = fabs(midpoint - (float)i); // Pixel to pendulum distance...
if (dist > 12.0) // If it crosses the "seam" at top,
dist = 24.0 - dist; // take the shorter path.
if (dist > 5.0) // Not close to pendulum,
ring->setPixelColor(i, 0); // erase pixel.
else if (dist < 2.0) // Close to pendulum,
interp(i, 1.0); // solid color
else // Anything in-between,
interp(i, (5.0 - dist) / 3.0); // interpolate
}
}
private:
Adafruit_EyeLights_Ring_buffered *ring; // -> glasses ring
uint8_t *color; // -> array of 3 uint8_t's [R,G,B]
float angle; // Current position around ring
float momentum; // Current 'oomph'
float friction; // A scaling constant to dampen motion
};
Pendulum pendulums[] = {
Pendulum(&glasses.left_ring, (uint8_t[3]){0, 20, 50}), // Cerulean blue,
Pendulum(&glasses.right_ring, (uint8_t[3]){0, 20, 50}), // 50 is plenty bright!
};
#define N_PENDULUMS (sizeof pendulums / sizeof pendulums[0])
// Crude error handler, prints message to Serial console, flashes LED
void err(char *str, uint8_t hz) {
Serial.println(str);
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
}
void setup() { // Runs once at program start...
// Initialize hardware
Serial.begin(115200);
if (! accel.begin()) err("LIS3DH not found", 5);
if (! glasses.begin()) err("IS3741 not found", 2);
// Configure glasses for max brightness, enable output
glasses.setLEDscaling(0xFF);
glasses.setGlobalCurrent(0xFF);
glasses.enable(true);
}
void loop() { // Repeat forever...
sensors_event_t event;
accel.getEvent(&event); // Read accelerometer once
for (uint8_t i=0; i<N_PENDULUMS; i++) { // For each pendulum...
pendulums[i].iterate(event); // Do math with accel data
}
glasses.show();
}

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics
simulation using accelerometer and math. This uses only the rings, not the

View file

@ -0,0 +1,17 @@
"""
CircuitPython Digital Input Example - Blinking an LED using a button switch.
"""
import board
import digitalio
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
button = digitalio.DigitalInOut(board.SWITCH)
button.switch_to_input(pull=digitalio.Pull.UP)
while True:
if not button.value:
led.value = True
else:
led.value = False

View file

@ -0,0 +1,183 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
Uses onboard microphone and a lot of math to react to music.
"""
from array import array
from math import log
from time import monotonic
from supervisor import reload
import board
from audiobusio import PDMIn
from busio import I2C
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_rgbmatrixqt import Adafruit_RGBMatrixQT
from rainbowio import colorwheel
from ulab import numpy as np
from ulab.scipy.signal import spectrogram
# FFT/SPECTRUM CONFIG ----
fft_size = 256 # Sample size for Fourier transform, MUST be power of two
spectrum_size = fft_size // 2 # Output spectrum is 1/2 of FFT result
# Bottom of spectrum tends to be noisy, while top often exceeds musical
# range and is just harmonics, so clip both ends off:
low_bin = 10 # Lowest bin of spectrum that contributes to graph
high_bin = 75 # Highest bin "
# HARDWARE SETUP ---------
# Manually declare I2C (not board.I2C() directly) to access 1 MHz speed...
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
# Initialize the IS31 LED driver, buffered for smoother animation
#glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
glasses = Adafruit_RGBMatrixQT(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
glasses.show() # Clear any residue on startup
glasses.set_led_scaling(0xFF)
glasses.global_current = 5 # Not too bright please
glasses.enable = True
# Initialize mic and allocate recording buffer (default rate is 16 MHz)
mic = PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, bit_depth=16)
rec_buf = array("H", [0] * fft_size) # 16-bit audio samples
# FFT/SPECTRUM SETUP -----
# To keep the display lively, tables are precomputed where each column of
# the matrix (of which there are few) is the sum value and weighting of
# several bins from the FFT spectrum output (of which there are many).
# The tables also help visually linearize the output so octaves are evenly
# spaced, as on a piano keyboard, whereas the source spectrum data is
# spaced by frequency in Hz.
column_table = []
spectrum_bits = log(spectrum_size, 2) # e.g. 7 for 128-bin spectrum
# Scale low_bin and high_bin to 0.0 to 1.0 equivalent range in spectrum
low_frac = log(low_bin, 2) / spectrum_bits
frac_range = log(high_bin, 2) / spectrum_bits - low_frac
for column in range(glasses.width):
# Determine the lower and upper frequency range for this column, as
# fractions within the scaled 0.0 to 1.0 spectrum range. 0.95 below
# creates slight frequency overlap between columns, looks nicer.
lower = low_frac + frac_range * (column / glasses.width * 0.95)
upper = low_frac + frac_range * ((column + 1) / glasses.width)
mid = (lower + upper) * 0.5 # Center of lower-to-upper range
half_width = (upper - lower) * 0.5 # 1/2 of lower-to-upper range
# Map fractions back to spectrum bin indices that contribute to column
first_bin = int(2 ** (spectrum_bits * lower) + 1e-4)
last_bin = int(2 ** (spectrum_bits * upper) + 1e-4)
bin_weights = [] # Each spectrum bin's weighting will be added here
for bin_index in range(first_bin, last_bin + 1):
# Find distance from column's overall center to individual bin's
# center, expressed as 0.0 (bin at center) to 1.0 (bin at limit of
# lower-to-upper range).
bin_center = log(bin_index + 0.5, 2) / spectrum_bits
dist = abs(bin_center - mid) / half_width
if dist < 1.0: # Filter out a few math stragglers at either end
# Bin weights have a cubic falloff curve within range:
dist = 1.0 - dist # Invert dist so 1.0 is at center
bin_weights.append(((3.0 - (dist * 2.0)) * dist) * dist)
# Scale bin weights so total is 1.0 for each column, but then mute
# lower columns slightly and boost higher columns. It graphs better.
total = sum(bin_weights)
bin_weights = [
(weight / total) * (0.8 + idx / glasses.width * 1.4)
for idx, weight in enumerate(bin_weights)
]
# List w/five elements is stored for each column:
# 0: Index of the first spectrum bin that impacts this column.
# 1: A list of bin weights, starting from index above, length varies.
# 2: Color for drawing this column on the LED matrix. The 225 is on
# purpose, providing hues from red to purple, leaving out magenta.
# 3: Current height of the 'falling dot', updated each frame
# 4: Current velocity of the 'falling dot', updated each frame
column_table.append(
[
first_bin - low_bin,
bin_weights,
colorwheel(225 * column / glasses.width),
glasses.height,
0.0,
]
)
# print(column_table)
# MAIN LOOP -------------
dynamic_level = 10 # For responding to changing volume levels
frames, start_time = 0, monotonic() # For frames-per-second calc
while True:
# The try/except here is because VERY INFREQUENTLY the I2C bus will
# encounter an error when accessing the LED driver, whether from bumping
# around the wires or sometimes an I2C device just gets wedged. To more
# robustly handle the latter, the code will restart if that happens.
try:
mic.record(rec_buf, fft_size) # Record batch of 16-bit samples
samples = np.array(rec_buf) # Convert to ndarray
# Compute spectrogram and trim results. Only the left half is
# normally needed (right half is mirrored), but we trim further as
# only the low_bin to high_bin elements are interesting to graph.
spectrum = spectrogram(samples)[low_bin : high_bin + 1]
# Linearize spectrum output. spectrogram() is always nonnegative,
# but add a tiny value to change any zeros to nonzero numbers
# (avoids rare 'inf' error)
spectrum = np.log(spectrum + 1e-7)
# Determine minimum & maximum across all spectrum bins, with limits
lower = max(np.min(spectrum), 4)
upper = min(max(np.max(spectrum), lower + 6), 20)
# Adjust dynamic level to current spectrum output, keeps the graph
# 'lively' as ambient volume changes. Sparkle but don't saturate.
if upper > dynamic_level:
# Got louder. Move level up quickly but allow initial "bump."
dynamic_level = upper * 0.7 + dynamic_level * 0.3
else:
# Got quieter. Ease level down, else too many bumps.
dynamic_level = dynamic_level * 0.5 + lower * 0.5
# Apply vertical scale to spectrum data. Results may exceed
# matrix height...that's OK, adds impact!
#data = (spectrum - lower) * (7 / (dynamic_level - lower))
data = (spectrum - lower) * ((glasses.height + 2) / (dynamic_level - lower))
for column, element in enumerate(column_table):
# Start BELOW matrix and accumulate bin weights UP, saves math
first_bin = element[0]
column_top = glasses.height + 1
for bin_offset, weight in enumerate(element[1]):
column_top -= data[first_bin + bin_offset] * weight
if column_top < element[3]: # Above current falling dot?
element[3] = column_top - 0.5 # Move dot up
element[4] = 0 # and clear out velocity
else:
element[3] += element[4] # Move dot down
element[4] += 0.2 # and accelerate
column_top = int(column_top) # Quantize to pixel space
for row in range(column_top): # Erase area above column
glasses.pixel(column, row, 0)
for row in range(column_top, glasses.height): # Draw column
glasses.pixel(column, row, element[2])
glasses.pixel(column, int(element[3]), 0xE08080) # Draw peak dot
glasses.show() # Buffered mode MUST use show() to refresh matrix
frames += 1
# print(frames / (monotonic() - start_time), "FPS")
except OSError: # See "try" notes above regarding rare I2C errors.
print("Restarting")
reload()

View file

@ -1,5 +1,5 @@
# Adafruit Grand Central Robot Xylophone Demo Program
# Dano Wall and Mike Barela for Adafruit Industries
# Dano Wall and Anne Barela for Adafruit Industries
# MIT License
import time

View file

@ -2,7 +2,7 @@ import time
import board
from adafruit_matrixportal.matrixportal import MatrixPortal
EVENT_YEAR = 2020
EVENT_YEAR = 2021
EVENT_MONTH = 10
EVENT_DAY = 31
EVENT_HOUR = 17

View file

@ -2,7 +2,6 @@ import time
import busio
import board
import pulseio
import pwmio
import adafruit_irremote
import adafruit_lis3dh
@ -26,9 +25,8 @@ TRANSMIT_DELAY = 0.1
i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
sensor = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
pulseout = pulseio.PulseOut(pwm)
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
# Create an encoder that will take numbers and turn them into IR pulses
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],

View file

@ -2,7 +2,7 @@
# Coded for Circuit Playground Express but it may be
# modified for any CircuitPython board with changes to
# button, thermister and audio board definitions.
# Mike Barela for Adafruit Industries, MIT License
# Anne Barela for Adafruit Industries, MIT License
import time
import board

View file

@ -1,5 +1,5 @@
// Simple read analog potentiometer on Circuit Playground Express or other board with pin change
// Mike Barela for Adafruit Industries 9/2018
// Anne Barela for Adafruit Industries 9/2018
#define ANALOGPIN A1 // For Circuit Playground Express

View file

@ -1,5 +1,5 @@
// Read analog potentiometer on Circuit Playground Express or other board with changes
// Mike Barela for Adafruit Industries 9/2018 based on
// Anne Barela for Adafruit Industries 9/2018 based on
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
// released under the GPLv3 license to match the rest of the AdaFruit NeoPixel library

View file

@ -1,5 +1,5 @@
# Isaac Wellish
# Code adapted from Mike Barela's Hello World of Robotics and
# Code adapted from Anne Barela's Hello World of Robotics and
# Make it Move with Crickit guides at learn.adafruit.com
# Power must be plugged into right side of motor 1 on CRICKIT
# to turn counter clock wise

View file

@ -1,4 +1,4 @@
# Music Box code in CircuitPython - Dano Wall and Mike Barela
# Music Box code in CircuitPython - Dano Wall and Anne Barela
# Revised by Ladyada 2019-01-16
from adafruit_crickit import crickit

View file

@ -1,7 +1,7 @@
# Simple paint program for Trellis M4 Express
# Press any button it will cycle through a palette of colors!
#
# Mike Barela for Adafruit Industries November, 2018
# Anne Barela for Adafruit Industries November, 2018
#
import time
import adafruit_trellism4

View file

@ -8,7 +8,7 @@ Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from [Adafruit](https://www.adafruit.com)!
MIT license, code by Mike Barela, project by the Ruiz Brothers
MIT license, code by Anne Barela, project by the Ruiz Brothers
All text above, and the splash screen below must be included in any redistribution

View file

@ -1,5 +1,5 @@
# NeoTrellis Soundbox Remix - CircuitPython
# Noe and Pedro Ruiz, code by Mike Barela
# Noe and Pedro Ruiz, code by Anne Barela
# for Adafruit Industries, MIT License
import time

View file

@ -4,7 +4,7 @@ https://learn.adafruit.com/no-solder-papercraft-crystal-light-strand
Circuit Playground Bluetooth with LED strand attached runs 4 different variable animation modes.
Code by Roy Hooper using Adafruit's LED Animation Library:
Code by Rose Hooper using Adafruit's LED Animation Library:
https://learn.adafruit.com/circuitpython-led-animations/overview
"""
# pylint: disable=attribute-defined-outside-init

47
Pico_Four_Keypad/code.py Normal file
View file

@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: 2021 john park for Adafruit Industries
# SPDX-License-Identifier: MIT
# Pico Four Key USB HID Keypad
import board
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
kpd = Keyboard(usb_hid.devices)
# define buttons
num_keys = 4
pins = (
board.GP0,
board.GP1,
board.GP2,
board.GP3
)
keys = []
for pin in pins:
tmp_pin = DigitalInOut(pin)
tmp_pin.pull = Pull.UP
keys.append(Debouncer(tmp_pin))
keymap = {
(0): ("Select all", [Keycode.GUI, Keycode.A]),
(1): ("Cut", [Keycode.GUI, Keycode.X]),
(2): ("Copy", [Keycode.GUI, Keycode.C]),
(3): ("Paste", [Keycode.GUI, Keycode.V])
}
print("\nWelcome to keypad")
print("keymap:")
for k in range(num_keys):
print("\t", (keymap[k][0]))
while True:
for i in range(num_keys):
keys[i].update()
if keys[i].fell:
print(keymap[i][0])
kpd.send(*keymap[i][1])

View file

@ -8,10 +8,10 @@ All the required libraries are listed at https://learn.adafruit.com/adafruit-pyb
Two files in the bounce directory: bounce.ino (the game) and logo.h (data for the Adafruit logo used on the splash screen).
Load both into the Arduino IDE.
Mike Barela, June 3, 2019 for Adafruit Industries
Anne Barela, June 3, 2019 for Adafruit Industries
This is a mod of game by R0D0T posted on http://r0d0t.tumblr.com/post/29641975900 and
Hackaday http://hackaday.com/2012/10/01/fantastic-programming-makes-this-arduino-gaming-device-something-special/
which was published for Arduino Esplora by Mike Barela 2013
which was published for Arduino Esplora by Anne Barela 2013
Support Open Source development by buying your materials at [Adafruit.com](https://www.adafruit.com/).

View file

@ -1,10 +1,10 @@
// Adafruit Arcada based Level Bounce Game
//
// Mike Barela, June 3, 2019 for Adafruit Industries
// Anne Barela, June 3, 2019 for Adafruit Industries
//
// Mod of game by R0D0T posted on http://r0d0t.tumblr.com/post/29641975900 and
// Hackaday http://hackaday.com/2012/10/01/fantastic-programming-makes-this-arduino-gaming-device-something-special/
// which was published for Arduino Esplora by Mike Barela 2013
// which was published for Arduino Esplora by Anne Barela 2013
#include <Adafruit_Arcada.h>
#include <Adafruit_Arcada_Def.h>

View file

@ -1,6 +1,6 @@
# Nintendo R.O.B. control with Accelerometer, code in CircuitPython
# Using an Adafruit Circuit Playground Express board with an IR LED
# Mike Barela for Adafruit Industries, MIT License, May, 2018
# Anne Barela for Adafruit Industries, MIT License, May, 2018
# Acknowledgement to info at http://atariage.com/forums/topic/177286
# -any-interest-in-nes-rob-homebrews/ and Limor Ladyada Fried

View file

@ -1,6 +1,6 @@
# Stumble bot, coded in CircuitPython
# Using an Adafruit Circuit Playground Express, Crickit, and 2 servos
# Dano Wall, Mike Barela for Adafruit Industries, MIT License, May, 2018
# Dano Wall, Anne Barela for Adafruit Industries, MIT License, May, 2018
#
import time
import board

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import board
import displayio
import adafruit_imageload

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import board
import displayio
import adafruit_imageload

View file

@ -1,3 +1,8 @@
# SPDX-FileCopyrightText: 2019 Carter Nelson for Adafruit Industries
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import board
import displayio
import adafruit_imageload

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import random
import gc

View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2018 Limor Fried/ladyada for Adafruit Industries
# SPDX-FileCopyrightText: 2019 Brennen Bearnes for Adafruit Industries
#
# SPDX-License-Identifier: MIT

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# pylint: disable=line-too-long
# Fun facts to be read to the player by Minerva
FACTS = [

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# State machine constants
# playing the game: draw the map, listen for D-pad buttons to move player

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
def wrap_nicely(string, max_chars):
""" From: https://www.richa1.com/RichardAlbritton/circuitpython-word-wrap-for-label-text/
A helper that will return the string with word-break wrapping.

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
#
# SPDX-License-Identifier: MIT
from tilegame_assets.states import (
STATE_MAPWIN,
STATE_LOST_SPARKY,

View file

@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
In this Demo we will drive two servos based on the Tilt along the Y and Z axis
of the BNO055 9-Degrees of Freedom IMU Sensor. This could easily be extended

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2019 Phillip Burgess/paintyourdragon for Adafruit Industries
//
// SPDX-License-Identifier: BSD
/*
PICCOLO is a tiny Arduino-based audio visualizer.
Hardware requirements:

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2019 Phillip Burgess/paintyourdragon for Adafruit Industries
//
// SPDX-License-Identifier: BSD
#ifndef FFT_N
#define FFT_N 128 /* Number of samples (64,128,256,512). */
#endif /* FFT_N */

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2019 Tony DiCola for Adafruit Industries
//
// SPDX-License-Identifier: MIT
// Track Your Treats - Ultimate GPS Shield Halloween Candy Route Tracker
// Author: Tony DiCola
//

Some files were not shown because too many files have changed in this diff Show more