Merge branch 'main' into license-changes
This commit is contained in:
commit
ae7f8b0779
201 changed files with 11729 additions and 74 deletions
31
.github/workflows/githubci.yml
vendored
31
.github/workflows/githubci.yml
vendored
|
|
@ -7,8 +7,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
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" ]
|
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" ]
|
||||||
# "trinket_5v", was removed
|
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -39,6 +38,34 @@ jobs:
|
||||||
- name: test platforms
|
- name: test platforms
|
||||||
run: python3 ci/build_platform.py ${{ matrix.arduino-platform }}
|
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:
|
pylint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -124,9 +124,7 @@ def fscale(originalmin, originalmax, newbegin, newend, inputvalue, curve):
|
||||||
|
|
||||||
def drawLine(fromhere, to):
|
def drawLine(fromhere, to):
|
||||||
if fromhere > to:
|
if fromhere > to:
|
||||||
fromheretemp = fromhere
|
to, fromhere = fromhere, to
|
||||||
fromhere = to
|
|
||||||
to = fromheretemp
|
|
||||||
|
|
||||||
for index in range(fromhere, to):
|
for index in range(fromhere, to):
|
||||||
strip[index] = (0, 0, 0)
|
strip[index] = (0, 0, 0)
|
||||||
|
|
|
||||||
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
|
created 28 Mar 2011 by Limor Fried
|
||||||
modified 9 Apr 2012 by Tom Igoe
|
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 the SD library:
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
created Nov 2010 by David A. Mellis
|
created Nov 2010 by David A. Mellis
|
||||||
modified 9 Apr 2012 by Tom Igoe
|
modified 9 Apr 2012 by Tom Igoe
|
||||||
modified 2 Feb 2014 by Scott Fitzgerald
|
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.
|
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/
|
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.
|
MIT License, please attribute.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Chirp Owl written by Becky Stern and T Main for Adafruit Industries
|
Chirp Owl written by Becky Stern and T Main for Adafruit Industries
|
||||||
Tutorial: http://learn.adafruit.com/chirping-plush-owl-toy/
|
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
|
http://learn.adafruit.com/adafruit-trinket-modded-stuffed-animal/animal-sounds
|
||||||
|
|
||||||
based in part on Debounce
|
based in part on Debounce
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Chirp Owl written by Becky Stern and T Main for Adafruit Industries
|
# Chirp Owl written by Becky Stern and T Main for Adafruit Industries
|
||||||
# Tutorial: http://learn.adafruit.com/chirping-plush-owl-toy/
|
# 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
|
# http://learn.adafruit.com/adafruit-trinket-modded-stuffed-animal/animal-sounds
|
||||||
# based in part on Debounce
|
# based in part on Debounce
|
||||||
# created 21 November 2006
|
# created 21 November 2006
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ rainbow_bitmap = displayio.Bitmap(
|
||||||
rainbow_palette = displayio.Palette(255)
|
rainbow_palette = displayio.Palette(255)
|
||||||
|
|
||||||
for i in range(0, 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 y in range(rainbow_bitmap.height):
|
||||||
for x in range(rainbow_bitmap.width):
|
for x in range(rainbow_bitmap.width):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# SPDX-FileCopyrightText: 2016 Damien P. George
|
# SPDX-FileCopyrightText: 2016 Damien P. George
|
||||||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
|
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
|
||||||
# SPDX-FileCopyrightText: 2019 Carter Nelson
|
# SPDX-FileCopyrightText: 2019 Carter Nelson
|
||||||
# SPDX-FileCopyrightText: 2019 Roy Hooper
|
# SPDX-FileCopyrightText: 2019 Rose Hooper
|
||||||
# SPDX-FileCopyrightText: 2020 Jeff Epler
|
# SPDX-FileCopyrightText: 2020 Jeff Epler
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
`neopio` - Neopixel strip driver using RP2040's PIO
|
`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
|
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
|
please support Adafruit and open-source hardware by purchasing
|
||||||
products from [Adafruit](https://www.adafruit.com)!
|
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
|
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.
|
# 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 time
|
||||||
import board
|
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
|
please support Adafruit and open-source hardware by purchasing
|
||||||
products from [Adafruit](https://www.adafruit.com)!
|
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
|
All text above, and the splash screen below must be included in any redistribution
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ def get_unique_pins():
|
||||||
"LED",
|
"LED",
|
||||||
"SWITCH",
|
"SWITCH",
|
||||||
"BUTTON",
|
"BUTTON",
|
||||||
|
"ACCELEROMETER_INTERRUPT",
|
||||||
|
"VOLTAGE_MONITOR",
|
||||||
|
"MICROPHONE_CLOCK",
|
||||||
|
"MICROPHONE_DATA",
|
||||||
]
|
]
|
||||||
if p in dir(board)
|
if p in dir(board)
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import pwmio
|
|
||||||
import pulseio
|
import pulseio
|
||||||
import adafruit_irremote
|
import adafruit_irremote
|
||||||
import neopixel
|
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
|
# Create NeoPixel object to indicate status
|
||||||
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)
|
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)
|
||||||
|
|
||||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
# Create a 'pulseio' 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(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||||
pulseout = pulseio.PulseOut(pwm)
|
|
||||||
|
|
||||||
# Create an encoder that will take numbers and turn them into IR pulses
|
# Create an encoder that will take numbers and turn them into IR pulses
|
||||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],
|
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import time
|
import time
|
||||||
import pulseio
|
import pulseio
|
||||||
import pwmio
|
|
||||||
import board
|
import board
|
||||||
import adafruit_irremote
|
import adafruit_irremote
|
||||||
import digitalio
|
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
|
# Create a decoder that will take pulses and turn them into numbers
|
||||||
decoder = adafruit_irremote.GenericDecode()
|
decoder = adafruit_irremote.GenericDecode()
|
||||||
|
|
||||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
# Create a 'pulseio' 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(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||||
pulseout = pulseio.PulseOut(pwm)
|
|
||||||
# Create an encoder that will take numbers and turn them into NEC IR pulses
|
# Create an encoder that will take numbers and turn them into NEC IR pulses
|
||||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
|
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
|
||||||
zero=[550, 1700], trail=0)
|
zero=[550, 1700], trail=0)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Lucky Cat Maneki-neko with Circuit Playground Express
|
# 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 time
|
||||||
import board
|
import board
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@ import time
|
||||||
from adafruit_circuitplayground.express import cpx
|
from adafruit_circuitplayground.express import cpx
|
||||||
import adafruit_irremote
|
import adafruit_irremote
|
||||||
import pulseio
|
import pulseio
|
||||||
import pwmio
|
|
||||||
import board
|
import board
|
||||||
|
|
||||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
# Create a 'pulseio' 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(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||||
pulseout = pulseio.PulseOut(pwm)
|
|
||||||
# Create an encoder that will take numbers and turn them into NEC IR pulses
|
# Create an encoder that will take numbers and turn them into NEC IR pulses
|
||||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
|
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
|
||||||
zero=[550, 1700], trail=0)
|
zero=[550, 1700], trail=0)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Circuit Playground Express Piñata by Dano Wall for Adafruit Industries
|
# 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 time
|
||||||
import random
|
import random
|
||||||
import board
|
import board
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Close Encounters hat with 10 neopixels by Leslie Birch for Adafruit Industries.
|
Close Encounters hat with 10 neopixels by Leslie Birch for Adafruit Industries.
|
||||||
Notes play with each corresponding light.
|
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
|
http://learn.adafruit.com/adafruit-trinket-modded-stuffed-animal/animal-sounds
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# CircuitPython for the Adafruit Learning System Tutorial
|
# CircuitPython for the Adafruit Learning System Tutorial
|
||||||
# Universal Marionette Kit
|
# 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
|
# MIT License
|
||||||
import time
|
import time
|
||||||
from adafruit_crickit import crickit
|
from adafruit_crickit import crickit
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Stumble Bot, coded in CircuitPython
|
# Stumble Bot, coded in CircuitPython
|
||||||
# Using an Adafruit Circuit Playground Express, Crickit, and 2 servos
|
# 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 time
|
||||||
from digitalio import DigitalInOut, Direction, Pull
|
from digitalio import DigitalInOut, Direction, Pull
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# main.py - code to test the Adafruit CRICKIT board with
|
# main.py - code to test the Adafruit CRICKIT board with
|
||||||
# the BBC micro:bit and MicroPython (NOT CircuitPython)
|
# 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
|
# This code requires the seesaw.py module as a driver
|
||||||
import time
|
import time
|
||||||
import seesaw
|
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
|
ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
|
||||||
good clicky button for input, this code shows how one might instead use
|
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
|
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...
|
while True: # Loop forever...
|
||||||
|
|
||||||
# The try/except here is because VERY INFREQUENTLY the I2C bus will
|
# 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,
|
interpolated_color[2] * 0.85 + target_color[2] * 0.15,
|
||||||
)
|
)
|
||||||
# Fill both rings with interpolated_color, then refresh the LEDs.
|
# 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()
|
glasses.show()
|
||||||
|
|
||||||
# The look-down detection only needs the accelerometer's Y axis.
|
# 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).
|
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
|
||||||
Uses onboard microphone and a lot of math to react to music.
|
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
|
GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics
|
||||||
simulation using accelerometer and math. This uses only the rings, not the
|
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
|
# 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
|
# MIT License
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import time
|
||||||
import board
|
import board
|
||||||
from adafruit_matrixportal.matrixportal import MatrixPortal
|
from adafruit_matrixportal.matrixportal import MatrixPortal
|
||||||
|
|
||||||
EVENT_YEAR = 2020
|
EVENT_YEAR = 2021
|
||||||
EVENT_MONTH = 10
|
EVENT_MONTH = 10
|
||||||
EVENT_DAY = 31
|
EVENT_DAY = 31
|
||||||
EVENT_HOUR = 17
|
EVENT_HOUR = 17
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import time
|
||||||
import busio
|
import busio
|
||||||
import board
|
import board
|
||||||
import pulseio
|
import pulseio
|
||||||
import pwmio
|
|
||||||
import adafruit_irremote
|
import adafruit_irremote
|
||||||
import adafruit_lis3dh
|
import adafruit_lis3dh
|
||||||
|
|
||||||
|
|
@ -26,9 +25,8 @@ TRANSMIT_DELAY = 0.1
|
||||||
i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
|
i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
|
||||||
sensor = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)
|
sensor = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)
|
||||||
|
|
||||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
# Create a 'pulseio' 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(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||||
pulseout = pulseio.PulseOut(pwm)
|
|
||||||
|
|
||||||
# Create an encoder that will take numbers and turn them into IR pulses
|
# Create an encoder that will take numbers and turn them into IR pulses
|
||||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],
|
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Coded for Circuit Playground Express but it may be
|
# Coded for Circuit Playground Express but it may be
|
||||||
# modified for any CircuitPython board with changes to
|
# modified for any CircuitPython board with changes to
|
||||||
# button, thermister and audio board definitions.
|
# button, thermister and audio board definitions.
|
||||||
# Mike Barela for Adafruit Industries, MIT License
|
# Anne Barela for Adafruit Industries, MIT License
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Simple read analog potentiometer on Circuit Playground Express or other board with pin change
|
// 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
|
#define ANALOGPIN A1 // For Circuit Playground Express
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Read analog potentiometer on Circuit Playground Express or other board with changes
|
// 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
|
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
|
||||||
// released under the GPLv3 license to match the rest of the AdaFruit NeoPixel library
|
// released under the GPLv3 license to match the rest of the AdaFruit NeoPixel library
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Isaac Wellish
|
# 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
|
# Make it Move with Crickit guides at learn.adafruit.com
|
||||||
# Power must be plugged into right side of motor 1 on CRICKIT
|
# Power must be plugged into right side of motor 1 on CRICKIT
|
||||||
# to turn counter clock wise
|
# 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
|
# Revised by Ladyada 2019-01-16
|
||||||
|
|
||||||
from adafruit_crickit import crickit
|
from adafruit_crickit import crickit
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Simple paint program for Trellis M4 Express
|
# Simple paint program for Trellis M4 Express
|
||||||
# Press any button it will cycle through a palette of colors!
|
# 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 time
|
||||||
import adafruit_trellism4
|
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
|
please support Adafruit and open-source hardware by purchasing
|
||||||
products from [Adafruit](https://www.adafruit.com)!
|
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
|
All text above, and the splash screen below must be included in any redistribution
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# NeoTrellis Soundbox Remix - CircuitPython
|
# 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
|
# for Adafruit Industries, MIT License
|
||||||
|
|
||||||
import time
|
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.
|
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
|
https://learn.adafruit.com/circuitpython-led-animations/overview
|
||||||
"""
|
"""
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# 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).
|
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.
|
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
|
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/
|
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/).
|
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
|
// 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
|
// 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/
|
// 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.h>
|
||||||
#include <Adafruit_Arcada_Def.h>
|
#include <Adafruit_Arcada_Def.h>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Nintendo R.O.B. control with Accelerometer, code in CircuitPython
|
# Nintendo R.O.B. control with Accelerometer, code in CircuitPython
|
||||||
# Using an Adafruit Circuit Playground Express board with an IR LED
|
# 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
|
# Acknowledgement to info at http://atariage.com/forums/topic/177286
|
||||||
# -any-interest-in-nes-rob-homebrews/ and Limor Ladyada Fried
|
# -any-interest-in-nes-rob-homebrews/ and Limor Ladyada Fried
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Stumble bot, coded in CircuitPython
|
# Stumble bot, coded in CircuitPython
|
||||||
# Using an Adafruit Circuit Playground Express, Crickit, and 2 servos
|
# 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 time
|
||||||
import board
|
import board
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import board
|
import board
|
||||||
import displayio
|
import displayio
|
||||||
import adafruit_imageload
|
import adafruit_imageload
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import board
|
import board
|
||||||
import displayio
|
import displayio
|
||||||
import adafruit_imageload
|
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 board
|
||||||
import displayio
|
import displayio
|
||||||
import adafruit_imageload
|
import adafruit_imageload
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import gc
|
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
|
# pylint: disable=line-too-long
|
||||||
# Fun facts to be read to the player by Minerva
|
# Fun facts to be read to the player by Minerva
|
||||||
FACTS = [
|
FACTS = [
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2020 FoamyGuy for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
# State machine constants
|
# State machine constants
|
||||||
|
|
||||||
# playing the game: draw the map, listen for D-pad buttons to move player
|
# 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):
|
def wrap_nicely(string, max_chars):
|
||||||
""" From: https://www.richa1.com/RichardAlbritton/circuitpython-word-wrap-for-label-text/
|
""" From: https://www.richa1.com/RichardAlbritton/circuitpython-word-wrap-for-label-text/
|
||||||
A helper that will return the string with word-break wrapping.
|
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 (
|
from tilegame_assets.states import (
|
||||||
STATE_MAPWIN,
|
STATE_MAPWIN,
|
||||||
STATE_LOST_SPARKY,
|
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
|
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
|
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.
|
PICCOLO is a tiny Arduino-based audio visualizer.
|
||||||
Hardware requirements:
|
Hardware requirements:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019 Phillip Burgess/paintyourdragon for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD
|
||||||
|
|
||||||
#ifndef FFT_N
|
#ifndef FFT_N
|
||||||
#define FFT_N 128 /* Number of samples (64,128,256,512). */
|
#define FFT_N 128 /* Number of samples (64,128,256,512). */
|
||||||
#endif /* FFT_N */
|
#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
|
// Track Your Treats - Ultimate GPS Shield Halloween Candy Route Tracker
|
||||||
// Author: Tony DiCola
|
// Author: Tony DiCola
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019 Tony DiCola for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// Track Your Treats - FONA808 Shield & Adafruit IO Halloween Candy Route Tracker
|
// Track Your Treats - FONA808 Shield & Adafruit IO Halloween Candy Route Tracker
|
||||||
// Author: Tony DiCola
|
// Author: Tony DiCola
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2018 Limor Fried/ladyada for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
# Code for the Trash Panda tutorial with Adafruit Crickit and Circuit Playground Express
|
# Code for the Trash Panda tutorial with Adafruit Crickit and Circuit Playground Express
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue