Merge branch 'license-changes' of https://github.com/TheKitty/Adafruit_Learning_System_Guides into license-changes
This commit is contained in:
commit
2c286c0dc6
203 changed files with 11735 additions and 77 deletions
31
.github/workflows/githubci.yml
vendored
31
.github/workflows/githubci.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
0
3D_Printed_LED_Microphone_Flag/.gemma_m0.generate
Normal file
0
3D_Printed_LED_Microphone_Flag/.gemma_m0.generate
Normal 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
0
3D_Printed_Unicorn_Horn/.gemma_m0.generate
Normal file
0
3D_Printed_Unicorn_Horn/.gemma_m0.generate
Normal file
0
3D_Printed_Unicorn_Horn/.gemma_m0.test.only
Normal file
0
3D_Printed_Unicorn_Horn/.gemma_m0.test.only
Normal 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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ def get_unique_pins():
|
|||
"LED",
|
||||
"SWITCH",
|
||||
"BUTTON",
|
||||
"ACCELEROMETER_INTERRUPT",
|
||||
"VOLTAGE_MONITOR",
|
||||
"MICROPHONE_CLOCK",
|
||||
"MICROPHONE_DATA",
|
||||
]
|
||||
if p in dir(board)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
64
EyeLights_BMP_Animation/code.py
Executable 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
|
||||
145
EyeLights_BMP_Animation/eyelights_anim.py
Executable file
145
EyeLights_BMP_Animation/eyelights_anim.py
Executable 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)
|
||||
BIN
EyeLights_BMP_Animation/matrix.bmp
Executable file
BIN
EyeLights_BMP_Animation/matrix.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
EyeLights_BMP_Animation/rings.bmp
Executable file
BIN
EyeLights_BMP_Animation/rings.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -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);
|
||||
}
|
||||
338
EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes_CircuitPython/code.py
Executable file
338
EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes_CircuitPython/code.py
Executable 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)
|
||||
202
EyeLights_Bluetooth_Scroller/EyeLights_Bluetooth_Scroller.ino
Normal file
202
EyeLights_Bluetooth_Scroller/EyeLights_Bluetooth_Scroller.ino
Normal 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
|
||||
}
|
||||
119
EyeLights_Bluetooth_Scroller/packetParser.cpp
Normal file
119
EyeLights_Bluetooth_Scroller/packetParser.cpp
Normal 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
|
||||
}
|
||||
135
EyeLights_Fire/EyeLights_Fire/EyeLights_Fire.ino
Normal file
135
EyeLights_Fire/EyeLights_Fire/EyeLights_Fire.ino
Normal 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);
|
||||
}
|
||||
131
EyeLights_Fire/EyeLights_Fire_CircuitPython/code.py
Executable file
131
EyeLights_Fire/EyeLights_Fire_CircuitPython/code.py
Executable 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()
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
17
EyeLights_LED_Glasses_and_Driver/Digital_Input/code.py
Normal file
17
EyeLights_LED_Glasses_and_Driver/Digital_Input/code.py
Normal 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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
47
Pico_Four_Keypad/code.py
Normal 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])
|
||||
|
|
@ -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/).
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import board
|
||||
import displayio
|
||||
import adafruit_imageload
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import board
|
||||
import displayio
|
||||
import adafruit_imageload
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
import random
|
||||
import gc
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue