Merge branch 'main' into spdx_-3
This commit is contained in:
commit
fc3080cd5b
108 changed files with 11349 additions and 124 deletions
31
.github/workflows/githubci.yml
vendored
31
.github/workflows/githubci.yml
vendored
|
|
@ -7,8 +7,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arduino-platform: ["uno", "nrf52832", "cpx_ada", "pyportal", "protrinket_3v", "protrinket_5v", "metro_m0", "esp8266", "esp32", "trinket_3v", "gemma", "flora", "feather32u4", "feather_m0_express", "gemma_m0", "trinket_m0", "hallowing_m0", "monster_m4sk", "hallowing_m4", "hallowing_m4_tinyusb", "neotrellis_m4", "pybadge", "cpb", "cpc", "funhouse", "magtag"]
|
||||
# "trinket_5v", was removed
|
||||
arduino-platform: ["cpb", "cpc", "cpx_ada", "esp32", "esp8266", "feather32u4", "feather_m0_express", "feather_m4_express", "feather_rp2040", "flora", "funhouse", "gemma", "gemma_m0", "hallowing_m0", "hallowing_m4_tinyusb", "magtag", "metro_m0", "metro_m0_tinyusb", "metro_m4", "metro_m4_tinyusb", "monster_m4sk", "monster_m4sk_tinyusb", "neokeytrinkey_m0", "neotrellis_m4", "nrf52832", "nrf52840", "protrinket_5v", "proxlighttrinkey_m0", "pybadge", "pygamer", "pyportal", "qt2040_trinkey", "qtpy_m0", "rotarytrinkey_m0", "slidetrinkey_m0", "trinket_m0", "uno", "trinket_5v", "ledglasses_nrf52840" ]
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
|
|
@ -39,6 +38,34 @@ jobs:
|
|||
- name: test platforms
|
||||
run: python3 ci/build_platform.py ${{ matrix.arduino-platform }}
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ github.event.repository.name }}.${{ github.sha }}
|
||||
path: |
|
||||
build/*.hex
|
||||
build/*.bin
|
||||
build/*.uf2
|
||||
|
||||
- name: Zip release files
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
if [ -d build ]; then
|
||||
(
|
||||
echo "Built from Adafruit Learning System Guides `git describe --tags` for ${{ matrix.arduino-platform }}"
|
||||
echo "Source code: https://github.com/adafruit/"
|
||||
echo "Adafruit Learning System: https://learn.adafruit.com/"
|
||||
) > build/README.txt
|
||||
cd build && zip -9 -o ${{ matrix.arduino-platform }}.zip *.hex *.bin *.uf2 *.txt
|
||||
fi
|
||||
|
||||
- name: Create release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: build/${{ matrix.arduino-platform }}.zip
|
||||
fail_on_unmatched_files: false
|
||||
body: "Select the zip file corresponding to your board from the list below."
|
||||
|
||||
pylint:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ confidence=
|
|||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call
|
||||
disable=too-many-instance-attributes,len-as-condition,too-few-public-methods,anomalous-backslash-in-string,no-else-return,simplifiable-if-statement,too-many-arguments,duplicate-code,no-name-in-module,no-member,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,missing-docstring,invalid-name,bad-whitespace,consider-using-enumerate,unexpected-keyword-arg
|
||||
disable=too-many-instance-attributes,len-as-condition,too-few-public-methods,anomalous-backslash-in-string,no-else-return,simplifiable-if-statement,too-many-arguments,duplicate-code,no-name-in-module,no-member,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,missing-docstring,invalid-name,bad-whitespace,consider-using-enumerate,unexpected-keyword-arg,consider-using-f-string
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#define BITMAP_WIDTH 64
|
||||
#define BITMAP_HEIGHT 32
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <Adafruit_LIS3DH.h> // For accelerometer
|
||||
#include <Adafruit_PixelDust.h> // For simulation
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
const uint8_t PROGMEM bitmap_2021[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2018 Bill Earl and Mikey Sklar for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*******************************************************************
|
||||
Bionic Eye sketch for Adafruit Trinket.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 Bill Earl and Mikey Sklar for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Bionic Eye sketch for Adafruit Trinket.
|
||||
#
|
||||
# written by Bill Earl for Arduino
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2014 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Fiery demon horns (rawr!) for Adafruit Trinket/Gemma.
|
||||
// Adafruit invests time and resources providing this open source code,
|
||||
// please support Adafruit and open-source hardware by purchasing
|
||||
|
|
@ -26,7 +30,7 @@ struct {
|
|||
long fade; // Decreases brightness as wave moves
|
||||
|
||||
// Gamma correction improves appearance of midrange colors
|
||||
uint8_t gamma[] PROGMEM = {
|
||||
const uint8_t gamma[] PROGMEM = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2014 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Fiery demon horns (rawr!) for Adafruit Trinket/Gemma.
|
||||
# Adafruit invests time and resources providing this open source code,
|
||||
# please support Adafruit and open-source hardware by purchasing
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2014 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Adafruit Trinket+NeoPixel animation for Daft Punk-inspired helmet.
|
||||
// Contains some ATtiny85-specific stuff; won't run as-is on Uno, etc.
|
||||
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2014 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Adafruit Trinket+NeoPixel animation for Daft Punk-inspired helmet.
|
||||
# Contains some ATtiny85-specific stuff; won't run as-is on Uno, etc.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2018 Mikey Sklar for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#ifdef __AVR__
|
||||
#include <avr/power.h>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2017 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Fiery demon horns (rawr!) for Adafruit Trinket/Gemma.
|
||||
// Adafruit invests time and resources providing this open source code,
|
||||
// please support Adafruit and open-source hardware by purchasing
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2018 Phil Burgess and Mikey Sklar for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Fiery demon horns (rawr!) for Adafruit Trinket/Gemma.
|
||||
# Adafruit invests time and resources providing this open source code,
|
||||
# please support Adafruit and open-source hardware by purchasing
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -120,9 +120,7 @@ def fscale(originalmin, originalmax, newbegin, newend, inputvalue, curve):
|
|||
|
||||
def drawLine(fromhere, to):
|
||||
if fromhere > to:
|
||||
fromheretemp = fromhere
|
||||
fromhere = to
|
||||
to = fromheretemp
|
||||
to, fromhere = fromhere, to
|
||||
|
||||
for index in range(fromhere, to):
|
||||
strip[index] = (0, 0, 0)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Talking A, B, Cs Soundboards: Animal ABCs and "E is for Electronics" ABCs
|
||||
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
// SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#include <RotaryEncoder.h>
|
||||
|
||||
#define PIN_ENCODER_A 13
|
||||
#define PIN_ENCODER_B 12
|
||||
#define COM_A 11
|
||||
#define COM_B SDA
|
||||
#define BUTTON_UP 5
|
||||
#define BUTTON_LEFT SCL
|
||||
#define BUTTON_DOWN 9
|
||||
#define BUTTON_RIGHT 6
|
||||
#define BUTTON_IN 10
|
||||
|
||||
RotaryEncoder encoder(PIN_ENCODER_A, PIN_ENCODER_B, RotaryEncoder::LatchMode::TWO03);
|
||||
// This interrupt will do our encoder reading/checking!
|
||||
void checkPosition() {
|
||||
encoder.tick(); // just call tick() to check the state.
|
||||
}
|
||||
int last_rotary = 0;
|
||||
|
||||
|
||||
#define NUMPIXELS 12
|
||||
Adafruit_NeoPixel pixels(NUMPIXELS, A0, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
while (!Serial);
|
||||
Serial.println("ANO Rotary Encoder Demo");
|
||||
|
||||
pinMode(COM_A, OUTPUT);
|
||||
digitalWrite(COM_A, LOW);
|
||||
pinMode(COM_B, OUTPUT);
|
||||
digitalWrite(COM_B, LOW);
|
||||
|
||||
attachInterrupt(PIN_ENCODER_A, checkPosition, CHANGE);
|
||||
attachInterrupt(PIN_ENCODER_B, checkPosition, CHANGE);
|
||||
|
||||
pinMode(BUTTON_UP, INPUT_PULLUP);
|
||||
pinMode(BUTTON_DOWN, INPUT_PULLUP);
|
||||
pinMode(BUTTON_LEFT, INPUT_PULLUP);
|
||||
pinMode(BUTTON_RIGHT, INPUT_PULLUP);
|
||||
pinMode(BUTTON_IN, INPUT_PULLUP);
|
||||
pixels.begin();
|
||||
pixels.setBrightness(30);
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
|
||||
void loop(void) {
|
||||
// read encoder
|
||||
int curr_rotary = encoder.getPosition();
|
||||
RotaryEncoder::Direction direction = encoder.getDirection();
|
||||
|
||||
pixels.clear();
|
||||
if (curr_rotary != last_rotary) {
|
||||
Serial.print("Encoder value: ");
|
||||
Serial.print(curr_rotary);
|
||||
Serial.print(" direction: ");
|
||||
Serial.println((int)direction);
|
||||
}
|
||||
last_rotary = curr_rotary;
|
||||
|
||||
pixels.setPixelColor((curr_rotary + (1000*NUMPIXELS)) % NUMPIXELS, pixels.Color(0, 150, 0));
|
||||
|
||||
if (! digitalRead(BUTTON_UP)) {
|
||||
pixels.setPixelColor(0, pixels.Color(150, 0, 0));
|
||||
}
|
||||
if (! digitalRead(BUTTON_LEFT)) {
|
||||
pixels.setPixelColor(NUMPIXELS/4, pixels.Color(150, 0, 0));
|
||||
}
|
||||
if (! digitalRead(BUTTON_DOWN)) {
|
||||
pixels.setPixelColor(NUMPIXELS/2, pixels.Color(150, 0, 0));
|
||||
}
|
||||
if (! digitalRead(BUTTON_RIGHT)) {
|
||||
pixels.setPixelColor(NUMPIXELS*3/4, pixels.Color(150, 0, 0));
|
||||
}
|
||||
if (! digitalRead(BUTTON_IN)) {
|
||||
pixels.fill(pixels.Color(50, 50, 50));
|
||||
}
|
||||
pixels.show();
|
||||
|
||||
delay(20);
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
CircuitPython ANO Rotary Encoder and NeoPixel Ring example.
|
||||
"""
|
||||
import board
|
||||
import digitalio
|
||||
import rotaryio
|
||||
import neopixel
|
||||
|
||||
# The pin assignments for the breakout pins. Update this is you are not using a Feather.
|
||||
ENCA = board.D13
|
||||
ENCB = board.D12
|
||||
COMA = board.D11
|
||||
SW1 = board.D10
|
||||
SW2 = board.D9
|
||||
SW3 = board.D6
|
||||
SW4 = board.D5
|
||||
SW5 = board.SCL
|
||||
COMB = board.SDA
|
||||
|
||||
# Rotary encoder setup
|
||||
encoder = rotaryio.IncrementalEncoder(ENCA, ENCB)
|
||||
last_position = None
|
||||
|
||||
# NeoPixel ring setup. Update num_pixels if using a different ring.
|
||||
num_pixels = 12
|
||||
pixels = neopixel.NeoPixel(board.A0, num_pixels, auto_write=False)
|
||||
|
||||
# Set the COMA and COMB pins LOW. This is only necessary when using the direct-to-Feather or other
|
||||
# GPIO-based wiring method. If connecting COMA and COMB to ground, you do not need to include this.
|
||||
com_a = digitalio.DigitalInOut(COMA)
|
||||
com_a.switch_to_output()
|
||||
com_a = False
|
||||
com_b = digitalio.DigitalInOut(COMB)
|
||||
com_b.switch_to_output()
|
||||
com_b = False
|
||||
|
||||
# Button pin setup
|
||||
button_pins = (SW1, SW2, SW3, SW4, SW5)
|
||||
buttons = []
|
||||
for button_pin in button_pins:
|
||||
pin = digitalio.DigitalInOut(button_pin)
|
||||
pin.switch_to_input(digitalio.Pull.UP)
|
||||
buttons.append(pin)
|
||||
|
||||
while True:
|
||||
position = encoder.position
|
||||
if last_position is None or position != last_position:
|
||||
print("Position: {}".format(position))
|
||||
last_position = position
|
||||
|
||||
pixels.fill((0, 0, 0))
|
||||
pixels[position % num_pixels] = (0, 150, 0)
|
||||
|
||||
if not buttons[0].value:
|
||||
print("Center button!")
|
||||
pixels.fill((100, 100, 100))
|
||||
|
||||
if not buttons[1].value:
|
||||
print("Up button!")
|
||||
pixels[0] = (150, 0 ,0)
|
||||
|
||||
if not buttons[2].value:
|
||||
print("Left button!")
|
||||
pixels[3] = (150, 0, 0)
|
||||
|
||||
if not buttons[3].value:
|
||||
print("Down button!")
|
||||
pixels[6] = (150, 0, 0)
|
||||
|
||||
if not buttons[4].value:
|
||||
print("Right button!")
|
||||
pixels[9] = (150, 0, 0)
|
||||
|
||||
pixels.show()
|
||||
|
|
@ -5,7 +5,11 @@
|
|||
* Rainbow swirl example for 3W LED.
|
||||
*/
|
||||
|
||||
#if defined(__SAMD21G18A__) || defined(__AVR_ATmega32U4__)
|
||||
#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#endif
|
||||
|
||||
#if defined(__SAMD21G18A__) || defined(__AVR_ATmega32U4__) || defined(NRF52840_XXAA)
|
||||
// No green PWM on 32u4
|
||||
#define POWER_PIN 10
|
||||
#define RED_LED 11
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@
|
|||
*/
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#endif
|
||||
|
||||
// NeoPixel strip length, in pixels
|
||||
#define NUM_PIXELS 30
|
||||
|
||||
#if defined(__SAMD21G18A__) || defined(__AVR_ATmega32U4__)
|
||||
#if defined(__SAMD21G18A__) || defined(__AVR_ATmega32U4__) || defined(NRF52840_XXAA)
|
||||
#define NEOPIXEL_PIN 5
|
||||
#define POWER_PIN 10
|
||||
#elif defined(__AVR_ATmega328P__)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@
|
|||
* Print to the Serial Montior when a switch is pressed.
|
||||
*/
|
||||
|
||||
#if defined(__SAMD21G18A__) || defined(__AVR_ATmega32U4__)
|
||||
#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#endif
|
||||
|
||||
#if defined(__SAMD21G18A__) || defined(__AVR_ATmega32U4__) || defined(NRF52840_XXAA)
|
||||
#define SWITCH_PIN 9
|
||||
#elif defined(__AVR_ATmega328P__)
|
||||
#define SWITCH_PIN 9
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ rainbow_bitmap = displayio.Bitmap(
|
|||
rainbow_palette = displayio.Palette(255)
|
||||
|
||||
for i in range(0, 255):
|
||||
rainbow_palette[i] = int("".join("%02x" % i for i in colorwheel(i)), 16)
|
||||
rainbow_palette[i] = colorwheel(i)
|
||||
|
||||
for y in range(rainbow_bitmap.height):
|
||||
for x in range(rainbow_bitmap.width):
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ def get_unique_pins():
|
|||
"LED",
|
||||
"SWITCH",
|
||||
"BUTTON",
|
||||
"ACCELEROMETER_INTERRUPT",
|
||||
"VOLTAGE_MONITOR",
|
||||
"MICROPHONE_CLOCK",
|
||||
"MICROPHONE_DATA",
|
||||
]
|
||||
if p in dir(board)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import time
|
||||
import board
|
||||
import pwmio
|
||||
import pulseio
|
||||
import adafruit_irremote
|
||||
import neopixel
|
||||
|
|
@ -12,9 +11,8 @@ TRANSMIT_DELAY = 15 # change this as desired to affect game dynamics, or just l
|
|||
# Create NeoPixel object to indicate status
|
||||
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)
|
||||
|
||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
pulseout = pulseio.PulseOut(pwm)
|
||||
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
|
||||
# Create an encoder that will take numbers and turn them into IR pulses
|
||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import time
|
||||
import pulseio
|
||||
import pwmio
|
||||
import board
|
||||
import adafruit_irremote
|
||||
import digitalio
|
||||
|
|
@ -22,9 +21,8 @@ pulsein = pulseio.PulseIn(board.IR_RX, maxlen=120, idle_state=True)
|
|||
# Create a decoder that will take pulses and turn them into numbers
|
||||
decoder = adafruit_irremote.GenericDecode()
|
||||
|
||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
pulseout = pulseio.PulseOut(pwm)
|
||||
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
# Create an encoder that will take numbers and turn them into NEC IR pulses
|
||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
|
||||
zero=[550, 1700], trail=0)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ import time
|
|||
from adafruit_circuitplayground.express import cpx
|
||||
import adafruit_irremote
|
||||
import pulseio
|
||||
import pwmio
|
||||
import board
|
||||
|
||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
pulseout = pulseio.PulseOut(pwm)
|
||||
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
# Create an encoder that will take numbers and turn them into NEC IR pulses
|
||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], one=[550, 550],
|
||||
zero=[550, 1700], trail=0)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
|
||||
good clicky button for input, this code shows how one might instead use
|
||||
the onboard accelerometer for interactions*.
|
||||
|
||||
Worn normally, the LED rings are simply lit a solid color.
|
||||
TAP the eyeglass frames to cycle among a list of available colors.
|
||||
LOOK DOWN to light the LED rings bright white -- for navigating steps
|
||||
or finding the right key. LOOK BACK UP to return to solid color.
|
||||
This uses only the rings, not the matrix portion.
|
||||
|
||||
* Like, if you have big ol' monster hands, that little button can be
|
||||
hard to click, y'know?
|
||||
*/
|
||||
|
||||
#include <Adafruit_IS31FL3741.h> // For LED driver
|
||||
#include <Adafruit_LIS3DH.h> // For accelerometer
|
||||
#include <Adafruit_Sensor.h> // For m/s^2 accel units
|
||||
|
||||
Adafruit_LIS3DH accel;
|
||||
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
|
||||
|
||||
// Here's a list of colors that we cycle through when tapped, specified
|
||||
// as {R,G,B} values from 0-255. These are intentionally a bit dim --
|
||||
// both to save battery and to make the "ground light" mode more dramatic.
|
||||
// Rather than primary color red/green/blue sequence which is just so
|
||||
// over-done at this point, let's use some HALLOWEEN colors!
|
||||
uint8_t colors[][3] = {
|
||||
{27, 9, 0}, // Orange
|
||||
{12, 0, 24}, // Purple
|
||||
{5, 31, 0}, // Green
|
||||
};
|
||||
#define NUM_COLORS (sizeof colors / sizeof colors[0]) // List length
|
||||
uint8_t looking_down_color[] = {255, 255, 255}; // Max white
|
||||
|
||||
uint8_t color_index = 0; // Begin at first color in list
|
||||
uint8_t *target_color; // Pointer to color we're aiming for
|
||||
float interpolated_color[] = {0.0, 0.0, 0.0}; // Current color along the way
|
||||
float filtered_y; // De-noised accelerometer reading
|
||||
bool looking_down; // Set true when glasses are oriented downward
|
||||
sensors_event_t event; // For accelerometer conversion
|
||||
uint32_t last_tap_time = 0; // For accelerometer tap de-noising
|
||||
|
||||
// Crude error handler, prints message to Serial console, flashes LED
|
||||
void err(char *str, uint8_t hz) {
|
||||
Serial.println(str);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
|
||||
}
|
||||
|
||||
void setup() { // Runs once at program start...
|
||||
|
||||
// Initialize hardware
|
||||
Serial.begin(115200);
|
||||
if (! accel.begin()) err("LIS3DH not found", 5);
|
||||
if (! glasses.begin()) err("IS3741 not found", 2);
|
||||
|
||||
// Configure accelerometer and get initial state
|
||||
accel.setClick(1, 100); // Set threshold for single tap
|
||||
accel.getEvent(&event); // Current accel in m/s^2
|
||||
// Check accelerometer to see if we've started in the looking-down state,
|
||||
// set the target color (what we're aiming for) appropriately. Only the
|
||||
// Y axis is needed for this.
|
||||
filtered_y = event.acceleration.y;
|
||||
looking_down = (filtered_y > 5.0);
|
||||
// If initially looking down, aim for the look-down color,
|
||||
// else aim for the first item in the color list.
|
||||
target_color = looking_down ? looking_down_color : colors[color_index];
|
||||
|
||||
// Configure glasses for max brightness, enable output
|
||||
glasses.setLEDscaling(0xFF);
|
||||
glasses.setGlobalCurrent(0xFF);
|
||||
glasses.enable(true);
|
||||
}
|
||||
|
||||
void loop() { // Repeat forever...
|
||||
|
||||
// interpolated_color blends from the prior to the next ("target")
|
||||
// LED ring colors, with a pleasant ease-out effect.
|
||||
for(uint8_t i=0; i<3; i++) { // R, G, B
|
||||
interpolated_color[i] = interpolated_color[i] * 0.97 + target_color[i] * 0.03;
|
||||
}
|
||||
// Convert separate red, green, blue to "packed" 24-bit RGB value
|
||||
uint32_t rgb = ((int)interpolated_color[0] << 16) |
|
||||
((int)interpolated_color[1] << 8) |
|
||||
(int)interpolated_color[2];
|
||||
// Fill both rings with packed color, then refresh the LEDs.
|
||||
glasses.left_ring.fill(rgb);
|
||||
glasses.right_ring.fill(rgb);
|
||||
glasses.show();
|
||||
|
||||
// The look-down detection only needs the accelerometer's Y axis.
|
||||
// This works with the Glasses Driver mounted on either temple,
|
||||
// with the glasses arms "open" (as when worn).
|
||||
accel.getEvent(&event);
|
||||
// Smooth the accelerometer reading the same way RGB colors are
|
||||
// interpolated. This avoids false triggers from jostling around.
|
||||
filtered_y = filtered_y * 0.97 + event.acceleration.y * 0.03;
|
||||
|
||||
// The threshold between "looking down" and "looking up" depends
|
||||
// on which of those states we're currently in. This is an example
|
||||
// of hysteresis in software...a change of direction requires a
|
||||
// little extra push before it takes, which avoids oscillating if
|
||||
// there was just a single threshold both ways.
|
||||
if (looking_down) { // Currently in the looking-down state...
|
||||
(void)accel.getClick(); // Discard any taps while looking down
|
||||
if (filtered_y < 3.5) { // Have we crossed the look-up threshold?
|
||||
target_color = colors[color_index]; // Back to list color
|
||||
looking_down = false; // We're looking up now!
|
||||
}
|
||||
} else { // Currently in the looking-up state...
|
||||
if (filtered_y > 5.0) { // Crossed the look-down threshold?
|
||||
target_color = looking_down_color; // Aim for white
|
||||
looking_down = true; // We're looking down now!
|
||||
} else if (accel.getClick()) {
|
||||
// No look up/down change, but the accelerometer registered
|
||||
// a tap. Compare this against the last time we sensed one,
|
||||
// and only do things if it's been more than half a second.
|
||||
// This avoids spurious double-taps that can occur no matter
|
||||
// how carefully the tap threshold was set.
|
||||
uint32_t now = millis();
|
||||
uint32_t elapsed = now - last_tap_time;
|
||||
if (elapsed > 500) {
|
||||
// A good tap was detected. Cycle to the next color in
|
||||
// the list and note the time of this tap.
|
||||
color_index = (color_index + 1) % NUM_COLORS;
|
||||
target_color = colors[color_index];
|
||||
last_tap_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
|
||||
good clicky button for input, this code shows how one might instead use
|
||||
|
|
@ -50,14 +54,6 @@ target_color = (255, 255, 255) if looking_down else colors[color_index]
|
|||
interpolated_color = (0, 0, 0) # LEDs off at startup, they'll ramp up
|
||||
|
||||
|
||||
def fill_color(color):
|
||||
"""Given an (R,G,B) tuple, fill both LED rings with this color."""
|
||||
# Convert tuple to a 'packed' 24-bit value
|
||||
packed = (int(color[0]) << 16) | (int(color[1]) << 8) | int(color[2])
|
||||
for i in range(24):
|
||||
glasses.left_ring[i] = glasses.right_ring[i] = packed
|
||||
|
||||
|
||||
while True: # Loop forever...
|
||||
|
||||
# The try/except here is because VERY INFREQUENTLY the I2C bus will
|
||||
|
|
@ -75,7 +71,14 @@ while True: # Loop forever...
|
|||
interpolated_color[2] * 0.85 + target_color[2] * 0.15,
|
||||
)
|
||||
# Fill both rings with interpolated_color, then refresh the LEDs.
|
||||
fill_color(interpolated_color)
|
||||
# fill_color(interpolated_color)
|
||||
packed = (
|
||||
(int(interpolated_color[0]) << 16)
|
||||
| (int(interpolated_color[1]) << 8)
|
||||
| int(interpolated_color[2])
|
||||
)
|
||||
glasses.left_ring.fill(packed)
|
||||
glasses.right_ring.fill(packed)
|
||||
glasses.show()
|
||||
|
||||
# The look-down detection only needs the accelerometer's Y axis.
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
|
||||
Uses onboard microphone and a lot of math to react to music.
|
||||
REQUIRES Adafruit_ZeroFFT LIBRARY, install via Arduino Library manager.
|
||||
*/
|
||||
|
||||
#include <Adafruit_IS31FL3741.h> // For LED driver
|
||||
#include <PDM.h> // For microphone
|
||||
#include <Adafruit_ZeroFFT.h> // For math
|
||||
|
||||
// FFT/SPECTRUM CONFIG ----
|
||||
|
||||
#define NUM_SAMPLES 512 // Audio & FFT buffer, MUST be a power of two
|
||||
#define SPECTRUM_SIZE (NUM_SAMPLES / 2) // Output spectrum is 1/2 of FFT output
|
||||
// Bottom of spectrum tends to be noisy, while top often exceeds musical
|
||||
// range and is just harmonics, so clip both ends off:
|
||||
#define LOW_BIN 5 // Lowest bin of spectrum that contributes to graph
|
||||
#define HIGH_BIN 150 // Highest bin "
|
||||
|
||||
// GLOBAL VARIABLES -------
|
||||
|
||||
Adafruit_EyeLights_buffered glasses; // LED matrix is buffered for smooth animation
|
||||
extern PDMClass PDM; // Microphone
|
||||
short audio_buf[3][NUM_SAMPLES]; // Audio input buffers, 16-bit signed
|
||||
uint8_t active_buf = 0; // Buffer # into which audio is currently recording
|
||||
volatile int samples_read = 0; // # of samples read into current buffer thus far
|
||||
volatile bool mic_on = false; // true when reading from mic, false when full/stopped
|
||||
float spectrum[SPECTRUM_SIZE]; // FFT results are stored & further processed here
|
||||
float dynamic_level = 10.0; // For adapting to changing audio volume
|
||||
int frames; // For frames-per-second calculation
|
||||
uint32_t start_time; // Ditto
|
||||
|
||||
struct { // Values associated with each column of the matrix
|
||||
int first_bin; // First spectrum bin index affecting column
|
||||
int num_bins; // Number of spectrum bins affecting column
|
||||
float *bin_weights; // List of spectrum bin weightings
|
||||
uint32_t color; // GFX-style 'RGB565' color for column
|
||||
float top; // Current column top position
|
||||
float dot; // Current column 'falling dot' position
|
||||
float velocity; // Current velocity of falling dot
|
||||
} column_table[18];
|
||||
|
||||
// Crude error handler, prints message to Serial console, flashes LED
|
||||
void err(char *str, uint8_t hz) {
|
||||
Serial.println(str);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
|
||||
}
|
||||
|
||||
void setup() { // Runs once at program start...
|
||||
|
||||
Serial.begin(115200);
|
||||
//while(!Serial);
|
||||
if (! glasses.begin()) err("IS3741 not found", 2);
|
||||
|
||||
// FFT/SPECTRUM SETUP -----
|
||||
|
||||
uint8_t spectrum_bits = (int)log2f((float)SPECTRUM_SIZE); // e.g. 8 = 256 bin spectrum
|
||||
// Scale LOW_BIN and HIGH_BIN to 0.0 to 1.0 equivalent range in spectrum
|
||||
float low_frac = log2f((float)LOW_BIN) / (float)spectrum_bits;
|
||||
float frac_range = log2((float)HIGH_BIN) / (float)spectrum_bits - low_frac;
|
||||
// Serial.printf("%d %f %f\n", spectrum_bits, low_frac, frac_range);
|
||||
|
||||
// To keep the display lively, tables are precomputed where each column of
|
||||
// the matrix (of which there are few) is the sum value and weighting of
|
||||
// several bins from the FFT spectrum output (of which there are many).
|
||||
// The tables also help visually linearize the output so octaves are evenly
|
||||
// spaced, as on a piano keyboard, whereas the source spectrum data is
|
||||
// spaced by frequency in Hz.
|
||||
|
||||
for (int column=0; column<18; column++) {
|
||||
// Determine the lower and upper frequency range for this column, as
|
||||
// fractions within the scaled 0.0 to 1.0 spectrum range. 0.95 below
|
||||
// creates slight frequency overlap between columns, looks nicer.
|
||||
float lower = low_frac + frac_range * ((float)column / 18.0 * 0.95);
|
||||
float upper = low_frac + frac_range * ((float)(column + 1) / 18.0);
|
||||
float mid = (lower + upper) * 0.5; // Center of lower-to-upper range
|
||||
float half_width = (upper - lower) * 0.5 + 1e-2; // 1/2 of lower-to-upper range
|
||||
// Map fractions back to spectrum bin indices that contribute to column
|
||||
int first_bin = int(pow(2, (float)spectrum_bits * lower) + 1e-4);
|
||||
int last_bin = int(pow(2, (float)spectrum_bits * upper) + 1e-4);
|
||||
//Serial.printf("%d %d %d\n", column, first_bin, last_bin);
|
||||
float total_weight = 0.0; // Accumulate weight for this bin
|
||||
int num_bins = last_bin - first_bin + 1;
|
||||
// Allocate space for bin weights for column, stop everything if out of RAM.
|
||||
column_table[column].bin_weights = (float *)malloc(num_bins * sizeof(float));
|
||||
if (column_table[column].bin_weights == NULL) err("Malloc fail", 10);
|
||||
for (int bin_index = first_bin; bin_index <= last_bin; bin_index++) {
|
||||
// Find distance from column's overall center to individual bin's
|
||||
// center, expressed as 0.0 (bin at center) to 1.0 (bin at limit of
|
||||
// lower-to-upper range).
|
||||
float bin_center = log2f((float)bin_index + 0.5) / (float)spectrum_bits;
|
||||
float dist = fabs(bin_center - mid) / half_width;
|
||||
if (dist < 1.0) { // Filter out a few math stragglers at either end
|
||||
// Bin weights have a cubic falloff curve within range:
|
||||
dist = 1.0 - dist; // Invert dist so 1.0 is at center
|
||||
float bin_weight = (((3.0 - (dist * 2.0)) * dist) * dist);
|
||||
column_table[column].bin_weights[bin_index - first_bin] = bin_weight;
|
||||
total_weight += bin_weight;
|
||||
}
|
||||
}
|
||||
//Serial.println(column);
|
||||
// Scale bin weights so total is 1.0 for each column, but then mute
|
||||
// lower columns slightly and boost higher columns. It graphs better.
|
||||
for (int i=0; i<num_bins; i++) {
|
||||
column_table[column].bin_weights[i] = column_table[column].bin_weights[i] /
|
||||
total_weight * (0.6 + (float)i / 18.0 * 2.0);
|
||||
//Serial.printf(" %f\n", column_table[column].bin_weights[i]);
|
||||
}
|
||||
column_table[column].first_bin = first_bin;
|
||||
column_table[column].num_bins = num_bins;
|
||||
column_table[column].color = glasses.color565(glasses.ColorHSV(
|
||||
57600UL * column / 18, 255, 255)); // Red (0) to purple (57600)
|
||||
column_table[column].top = 6.0; // Start off bottom of graph
|
||||
column_table[column].dot = 6.0;
|
||||
column_table[column].velocity = 0.0;
|
||||
}
|
||||
|
||||
for (int i=0; i<SPECTRUM_SIZE; i++) spectrum[i] = 0.0;
|
||||
|
||||
// HARDWARE SETUP ---------
|
||||
|
||||
// Configure glasses for max brightness, enable output
|
||||
glasses.setLEDscaling(0xFF);
|
||||
glasses.setGlobalCurrent(0xFF);
|
||||
glasses.enable(true);
|
||||
|
||||
// Configure PDM mic, mono 16 KHz
|
||||
PDM.onReceive(onPDMdata);
|
||||
PDM.begin(1, 16000);
|
||||
|
||||
start_time = millis();
|
||||
}
|
||||
|
||||
void loop() { // Repeat forever...
|
||||
|
||||
short *audio_data; // Pointer to newly-received audio
|
||||
|
||||
while (mic_on) yield(); // Wait for next buffer to finish recording
|
||||
// Full buffer received -- active_buf is index to new data
|
||||
audio_data = &audio_buf[active_buf][0]; // New data is here
|
||||
active_buf = 1 - active_buf; // Swap buffers to record into other one,
|
||||
mic_on = true; // and start recording next batch
|
||||
|
||||
// Perform FFT operation on newly-received data,
|
||||
// results go back into the same buffer.
|
||||
ZeroFFT(audio_data, NUM_SAMPLES);
|
||||
|
||||
// Convert FFT output to spectrum. log(y) looks better than raw data.
|
||||
// Only LOW_BIN to HIGH_BIN elements are needed.
|
||||
for(int i=LOW_BIN; i<=HIGH_BIN; i++) {
|
||||
spectrum[i] = (audio_data[i] > 0) ? log((float)audio_data[i]) : 0.0;
|
||||
}
|
||||
|
||||
// Find min & max range of spectrum bin values, with limits.
|
||||
float lower = spectrum[LOW_BIN], upper = spectrum[LOW_BIN];
|
||||
for (int i=LOW_BIN+1; i<=HIGH_BIN; i++) {
|
||||
if (spectrum[i] < lower) lower = spectrum[i];
|
||||
if (spectrum[i] > upper) upper = spectrum[i];
|
||||
}
|
||||
//Serial.printf("%f %f\n", lower, upper);
|
||||
if (upper < 2.5) upper = 2.5;
|
||||
|
||||
// Adjust dynamic level to current spectrum output, keeps the graph
|
||||
// 'lively' as ambient volume changes. Sparkle but don't saturate.
|
||||
if (upper > dynamic_level) {
|
||||
// Got louder. Move level up quickly but allow initial "bump."
|
||||
dynamic_level = dynamic_level * 0.5 + upper * 0.5;
|
||||
} else {
|
||||
// Got quieter. Ease level down, else too many bumps.
|
||||
dynamic_level = dynamic_level * 0.75 + lower * 0.25;
|
||||
}
|
||||
|
||||
// Apply vertical scale to spectrum data. Results may exceed
|
||||
// matrix height...that's OK, adds impact!
|
||||
float scale = 15.0 / (dynamic_level - lower);
|
||||
for (int i=LOW_BIN; i<=HIGH_BIN; i++) {
|
||||
spectrum[i] = (spectrum[i] - lower) * scale;
|
||||
}
|
||||
|
||||
// Clear screen, filter and draw each column of the display...
|
||||
glasses.fill(0);
|
||||
for(int column=0; column<18; column++) {
|
||||
int first_bin = column_table[column].first_bin;
|
||||
// Start BELOW matrix and accumulate bin weights UP, saves math
|
||||
float column_top = 7.0;
|
||||
for (int bin_offset=0; bin_offset<column_table[column].num_bins; bin_offset++) {
|
||||
column_top -= spectrum[first_bin + bin_offset] * column_table[column].bin_weights[bin_offset];
|
||||
}
|
||||
// Column top positions are filtered to appear less 'twitchy' --
|
||||
// last data still has a 30% influence on current positions.
|
||||
column_top = (column_top * 0.7) + (column_table[column].top * 0.3);
|
||||
column_table[column].top = column_top;
|
||||
|
||||
if(column_top < column_table[column].dot) { // Above current falling dot?
|
||||
column_table[column].dot = column_top - 0.5; // Move dot up
|
||||
column_table[column].velocity = 0.0; // and clear out velocity
|
||||
} else {
|
||||
column_table[column].dot += column_table[column].velocity; // Move dot down
|
||||
column_table[column].velocity += 0.015; // and accelerate
|
||||
}
|
||||
|
||||
// Draw column and peak dot
|
||||
int itop = (int)column_top; // Quantize column top to pixel space
|
||||
glasses.drawLine(column, itop, column, itop + 20, column_table[column].color);
|
||||
glasses.drawPixel(column, (int)column_table[column].dot, 0xE410);
|
||||
}
|
||||
|
||||
glasses.show(); // Buffered mode MUST use show() to refresh matrix
|
||||
|
||||
frames += 1;
|
||||
uint32_t elapsed = millis() - start_time;
|
||||
//Serial.println(frames * 1000 / elapsed);
|
||||
}
|
||||
|
||||
// PDM mic interrupt handler, called when new data is ready
|
||||
void onPDMdata() {
|
||||
//digitalWrite(LED_BUILTIN, millis() & 1024); // Debug heartbeat
|
||||
if (int bytes_to_read = PDM.available()) {
|
||||
if (mic_on) {
|
||||
int byte_limit = (NUM_SAMPLES - samples_read) * 2; // Space remaining,
|
||||
bytes_to_read = min(bytes_to_read, byte_limit); // don't overflow!
|
||||
PDM.read(&audio_buf[active_buf][samples_read], bytes_to_read);
|
||||
samples_read += bytes_to_read / 2; // Increment counter
|
||||
if (samples_read >= NUM_SAMPLES) { // Buffer full?
|
||||
mic_on = false; // Stop and
|
||||
samples_read = 0; // reset counter for next time
|
||||
}
|
||||
} else {
|
||||
// Mic is off (code is busy) - must read but discard data.
|
||||
// audio_buf[2] is a 'bit bucket' for this.
|
||||
PDM.read(audio_buf[2], bytes_to_read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
|
||||
Uses onboard microphone and a lot of math to react to music.
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
MOVE-AND-BLINK EYES for Adafruit EyeLights (LED Glasses + Driver).
|
||||
|
||||
I'd written a very cool squash-and-stretch effect for the eye movement,
|
||||
but unfortunately the resolution is such that the pupils just look like
|
||||
circles regardless. I'm keeping it in despite the added complexity,
|
||||
because this WILL look great later on a bigger matrix or a TFT/OLED,
|
||||
and this way the hard parts won't require a re-write at such time.
|
||||
It's a really adorable effect with enough pixels.
|
||||
*/
|
||||
|
||||
#include <Adafruit_IS31FL3741.h> // For LED driver
|
||||
|
||||
// CONFIGURABLES ------------------------
|
||||
|
||||
#define RADIUS 3.4 // Size of pupil (3X because of downsampling later)
|
||||
|
||||
uint8_t eye_color[3] = { 255, 128, 0 }; // Amber pupils
|
||||
uint8_t ring_open_color[3] = { 75, 75, 75 }; // Color of LED rings when eyes open
|
||||
uint8_t ring_blink_color[3] = { 50, 25, 0 }; // Color of LED ring "eyelid" when blinking
|
||||
|
||||
// Some boards have just one I2C interface, but some have more...
|
||||
TwoWire *i2c = &Wire; // e.g. change this to &Wire1 for QT Py RP2040
|
||||
|
||||
// GLOBAL VARIABLES ---------------------
|
||||
|
||||
Adafruit_EyeLights_buffered glasses(true); // Buffered spex + 3X canvas
|
||||
GFXcanvas16 *canvas; // Pointer to canvas object
|
||||
|
||||
// Reading through the code, you'll see a lot of references to this "3X"
|
||||
// space. This is referring to the glasses' optional "offscreen" drawing
|
||||
// canvas that's 3 times the resolution of the LED matrix (i.e. 15 pixels
|
||||
// tall instead of 5), which gets scaled down to provide some degree of
|
||||
// antialiasing. It's why the pupils have soft edges and can make
|
||||
// fractional-pixel motions.
|
||||
|
||||
float cur_pos[2] = { 9.0, 7.5 }; // Current position of eye in canvas space
|
||||
float next_pos[2] = { 9.0, 7.5 }; // Next position "
|
||||
bool in_motion = false; // true = eyes moving, false = eyes paused
|
||||
uint8_t blink_state = 0; // 0, 1, 2 = unblinking, closing, opening
|
||||
uint32_t move_start_time = 0; // For animation timekeeping
|
||||
uint32_t move_duration = 0;
|
||||
uint32_t blink_start_time = 0;
|
||||
uint32_t blink_duration = 0;
|
||||
float y_pos[13]; // Coords of LED ring pixels in canvas space
|
||||
uint32_t ring_open_color_packed; // ring_open_color[] as packed RGB integer
|
||||
uint16_t eye_color565; // eye_color[] as a GFX packed '565' value
|
||||
uint32_t frames = 0; // For frames-per-second calculation
|
||||
uint32_t start_time;
|
||||
|
||||
// These offsets position each pupil on the canvas grid and make them
|
||||
// fixate slightly (converge on a point) so they're not always aligned
|
||||
// the same on the pixel grid, which would be conspicuously pixel-y.
|
||||
float x_offset[2] = { 5.0, 31.0 };
|
||||
// These help perform x-axis clipping on the rasterized ellipses,
|
||||
// so they don't "bleed" outside the rings and require erasing.
|
||||
int box_x_min[2] = { 3, 33 };
|
||||
int box_x_max[2] = { 21, 51 };
|
||||
|
||||
#define GAMMA 2.6 // For color correction, shouldn't need changing
|
||||
|
||||
|
||||
// HELPER FUNCTIONS ---------------------
|
||||
|
||||
// Crude error handler, prints message to Serial console, flashes LED
|
||||
void err(char *str, uint8_t hz) {
|
||||
Serial.println(str);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
|
||||
}
|
||||
|
||||
// Given an [R,G,B] color, apply gamma correction, return packed RGB integer.
|
||||
uint32_t gammify(uint8_t color[3]) {
|
||||
uint32_t rgb[3];
|
||||
for (uint8_t i=0; i<3; i++) {
|
||||
rgb[i] = uint32_t(pow((float)color[i] / 255.0, GAMMA) * 255 + 0.5);
|
||||
}
|
||||
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
||||
}
|
||||
|
||||
// Given two [R,G,B] colors and a blend ratio (0.0 to 1.0), interpolate between
|
||||
// the two colors and return a gamma-corrected in-between color as a packed RGB
|
||||
// integer. No bounds clamping is performed on blend value, be nice.
|
||||
uint32_t interp(uint8_t color1[3], uint8_t color2[3], float blend) {
|
||||
float inv = 1.0 - blend; // Weighting of second color
|
||||
uint8_t rgb[3];
|
||||
for(uint8_t i=0; i<3; i++) {
|
||||
rgb[i] = (int)((float)color1[i] * blend + (float)color2[i] * inv);
|
||||
}
|
||||
return gammify(rgb);
|
||||
}
|
||||
|
||||
// Rasterize an arbitrary ellipse into the offscreen 3X canvas, given
|
||||
// foci point1 and point2 and with area determined by global RADIUS
|
||||
// (when foci are same point; a circle). Foci and radius are all
|
||||
// floating point values, which adds to the buttery impression. 'rect'
|
||||
// is a bounding rect of which pixels are likely affected. Canvas is
|
||||
// assumed cleared before arriving here.
|
||||
void rasterize(float point1[2], float point2[2], int rect[4]) {
|
||||
float perimeter, d;
|
||||
float dx = point2[0] - point1[0];
|
||||
float dy = point2[1] - point1[1];
|
||||
float d2 = dx * dx + dy * dy; // Dist between foci, squared
|
||||
if (d2 <= 0.0) {
|
||||
// Foci are in same spot - it's a circle
|
||||
perimeter = 2.0 * RADIUS;
|
||||
d = 0.0;
|
||||
} else {
|
||||
// Foci are separated - it's an ellipse.
|
||||
d = sqrt(d2); // Distance between foci
|
||||
float c = d * 0.5; // Center-to-foci distance
|
||||
// This is an utterly brute-force way of ellipse-filling based on
|
||||
// the "two nails and a string" metaphor...we have the foci points
|
||||
// and just need the string length (triangle perimeter) to yield
|
||||
// an ellipse with area equal to a circle of 'radius'.
|
||||
// c^2 = a^2 - b^2 <- ellipse formula
|
||||
// a = r^2 / b <- substitute
|
||||
// c^2 = (r^2 / b)^2 - b^2
|
||||
// b = sqrt(((c^2) + sqrt((c^4) + 4 * r^4)) / 2) <- solve for b
|
||||
float c2 = c * c;
|
||||
float b2 = (c2 + sqrt((c2 * c2) + 4 * (RADIUS * RADIUS * RADIUS * RADIUS))) * 0.5;
|
||||
// By my math, perimeter SHOULD be...
|
||||
// perimeter = d + 2 * sqrt(b2 + c2);
|
||||
// ...but for whatever reason, working approach here is really...
|
||||
perimeter = d + 2 * sqrt(b2);
|
||||
}
|
||||
|
||||
// Like I'm sure there's a way to rasterize this by spans rather than
|
||||
// all these square roots on every pixel, but for now...
|
||||
for (int y=rect[1]; y<rect[3]; y++) { // For each row...
|
||||
float y5 = (float)y + 0.5; // Pixel center
|
||||
float dy1 = y5 - point1[1]; // Y distance from pixel to first point
|
||||
float dy2 = y5 - point2[1]; // " to second
|
||||
dy1 *= dy1; // Y1^2
|
||||
dy2 *= dy2; // Y2^2
|
||||
for (int x=rect[0]; x<rect[2]; x++) { // For each column...
|
||||
float x5 = (float)x + 0.5; // Pixel center
|
||||
float dx1 = x5 - point1[0]; // X distance from pixel to first point
|
||||
float dx2 = x5 - point2[0]; // " to second
|
||||
float d1 = sqrt(dx1 * dx1 + dy1); // 2D distance to first point
|
||||
float d2 = sqrt(dx2 * dx2 + dy2); // " to second
|
||||
if ((d1 + d2 + d) <= perimeter) { // Point inside ellipse?
|
||||
canvas->drawPixel(x, y, eye_color565);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ONE-TIME INITIALIZATION --------------
|
||||
|
||||
void setup() {
|
||||
// Initialize hardware
|
||||
Serial.begin(115200);
|
||||
if (! glasses.begin(IS3741_ADDR_DEFAULT, i2c)) err("IS3741 not found", 2);
|
||||
|
||||
canvas = glasses.getCanvas();
|
||||
if (!canvas) err("Can't allocate canvas", 5);
|
||||
|
||||
i2c->setClock(1000000); // 1 MHz I2C for extra butteriness
|
||||
|
||||
// Configure glasses for reduced brightness, enable output
|
||||
glasses.setLEDscaling(0xFF);
|
||||
glasses.setGlobalCurrent(20);
|
||||
glasses.enable(true);
|
||||
|
||||
// INITIALIZE TABLES & OTHER GLOBALS ----
|
||||
|
||||
// Pre-compute the Y position of 1/2 of the LEDs in a ring, relative
|
||||
// to the 3X canvas resolution, so ring & matrix animation can be aligned.
|
||||
for (uint8_t i=0; i<13; i++) {
|
||||
float angle = (float)i / 24.0 * M_PI * 2.0;
|
||||
y_pos[i] = 10.0 - cos(angle) * 12.0;
|
||||
}
|
||||
|
||||
// Convert some colors from [R,G,B] (easier to specify) to packed integers
|
||||
ring_open_color_packed = gammify(ring_open_color);
|
||||
eye_color565 = glasses.color565(eye_color[0], eye_color[1], eye_color[2]);
|
||||
|
||||
start_time = millis(); // For frames-per-second math
|
||||
}
|
||||
|
||||
// MAIN LOOP ----------------------------
|
||||
|
||||
void loop() {
|
||||
canvas->fillScreen(0);
|
||||
|
||||
// The eye animation logic is a carry-over from like a billion
|
||||
// prior eye projects, so this might be comment-light.
|
||||
uint32_t now = micros(); // 'Snapshot' the time once per frame
|
||||
|
||||
float upper, lower, ratio;
|
||||
|
||||
// Blink logic
|
||||
uint32_t elapsed = now - blink_start_time; // Time since start of blink event
|
||||
if (elapsed > blink_duration) { // All done with event?
|
||||
blink_start_time = now; // A new one starts right now
|
||||
elapsed = 0;
|
||||
blink_state++; // Cycle closing/opening/paused
|
||||
if (blink_state == 1) { // Starting new blink...
|
||||
blink_duration = random(60000, 120000);
|
||||
} else if (blink_state == 2) { // Switching closing to opening...
|
||||
blink_duration *= 2; // Opens at half the speed
|
||||
} else { // Switching to pause in blink
|
||||
blink_state = 0;
|
||||
blink_duration = random(500000, 4000000);
|
||||
}
|
||||
}
|
||||
if (blink_state) { // If currently in a blink...
|
||||
float ratio = (float)elapsed / (float)blink_duration; // 0.0-1.0 as it closes
|
||||
if (blink_state == 2) ratio = 1.0 - ratio; // 1.0-0.0 as it opens
|
||||
upper = ratio * 15.0 - 4.0; // Upper eyelid pos. in 3X space
|
||||
lower = 23.0 - ratio * 8.0; // Lower eyelid pos. in 3X space
|
||||
}
|
||||
|
||||
// Eye movement logic. Two points, 'p1' and 'p2', are the foci of an
|
||||
// ellipse. p1 moves from current to next position a little faster
|
||||
// than p2, creating a "squash and stretch" effect (frame rate and
|
||||
// resolution permitting). When motion is stopped, the two points
|
||||
// are at the same position.
|
||||
float p1[2], p2[2];
|
||||
elapsed = now - move_start_time; // Time since start of move event
|
||||
if (in_motion) { // Currently moving?
|
||||
if (elapsed > move_duration) { // If end of motion reached,
|
||||
in_motion = false; // Stop motion and
|
||||
memcpy(&p1, &next_pos, sizeof next_pos); // set everything to new position
|
||||
memcpy(&p2, &next_pos, sizeof next_pos);
|
||||
memcpy(&cur_pos, &next_pos, sizeof next_pos);
|
||||
move_duration = random(500000, 1500000); // Wait this long
|
||||
} else { // Still moving
|
||||
// Determine p1, p2 position in time
|
||||
float delta[2];
|
||||
delta[0] = next_pos[0] - cur_pos[0];
|
||||
delta[1] = next_pos[1] - cur_pos[1];
|
||||
ratio = (float)elapsed / (float)move_duration;
|
||||
if (ratio < 0.6) { // First 60% of move time, p1 is in motion
|
||||
// Easing function: 3*e^2-2*e^3 0.0 to 1.0
|
||||
float e = ratio / 0.6; // 0.0 to 1.0
|
||||
e = 3 * e * e - 2 * e * e * e;
|
||||
p1[0] = cur_pos[0] + delta[0] * e;
|
||||
p1[1] = cur_pos[1] + delta[1] * e;
|
||||
} else { // Last 40% of move time
|
||||
memcpy(&p1, &next_pos, sizeof next_pos); // p1 has reached end position
|
||||
}
|
||||
if (ratio > 0.3) { // Last 70% of move time, p2 is in motion
|
||||
float e = (ratio - 0.3) / 0.7; // 0.0 to 1.0
|
||||
e = 3 * e * e - 2 * e * e * e; // Easing func.
|
||||
p2[0] = cur_pos[0] + delta[0] * e;
|
||||
p2[1] = cur_pos[1] + delta[1] * e;
|
||||
} else { // First 30% of move time
|
||||
memcpy(&p2, &cur_pos, sizeof cur_pos); // p2 waits at start position
|
||||
}
|
||||
}
|
||||
} else { // Eye is stopped
|
||||
memcpy(&p1, &cur_pos, sizeof cur_pos); // Both foci at current eye position
|
||||
memcpy(&p2, &cur_pos, sizeof cur_pos);
|
||||
if (elapsed > move_duration) { // Pause time expired?
|
||||
in_motion = true; // Start up new motion!
|
||||
move_start_time = now;
|
||||
move_duration = random(150000, 250000);
|
||||
float angle = (float)random(1000) / 1000.0 * M_PI * 2.0;
|
||||
float dist = (float)random(750) / 100.0;
|
||||
next_pos[0] = 9.0 + cos(angle) * dist;
|
||||
next_pos[1] = 7.5 + sin(angle) * dist * 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the raster part of each eye...
|
||||
for (uint8_t e=0; e<2; e++) {
|
||||
// Each eye's foci are offset slightly, to fixate toward center
|
||||
float p1a[2], p2a[2];
|
||||
p1a[0] = p1[0] + x_offset[e];
|
||||
p2a[0] = p2[0] + x_offset[e];
|
||||
p1a[1] = p2a[1] = p1[1];
|
||||
// Compute bounding rectangle (in 3X space) of ellipse
|
||||
// (min X, min Y, max X, max Y). Like the ellipse rasterizer,
|
||||
// this isn't optimal, but will suffice.
|
||||
int bounds[4];
|
||||
bounds[0] = max(int(min(p1a[0], p2a[0]) - RADIUS), box_x_min[e]);
|
||||
bounds[1] = max(max(int(min(p1a[1], p2a[1]) - RADIUS), 0), (int)upper);
|
||||
bounds[2] = min(int(max(p1a[0], p2a[0]) + RADIUS + 1), box_x_max[e]);
|
||||
bounds[3] = min(int(max(p1a[1], p2a[1]) + RADIUS + 1), 15);
|
||||
rasterize(p1a, p2a, bounds); // Render ellipse into buffer
|
||||
}
|
||||
|
||||
// If the eye is currently blinking, and if the top edge of the eyelid
|
||||
// overlaps the bitmap, draw lines across the bitmap as if eyelids.
|
||||
if (blink_state and upper >= 0.0) {
|
||||
int iu = (int)upper;
|
||||
canvas->drawLine(box_x_min[0], iu, box_x_max[0] - 1, iu, eye_color565);
|
||||
canvas->drawLine(box_x_min[1], iu, box_x_max[1] - 1, iu, eye_color565);
|
||||
}
|
||||
|
||||
glasses.scale(); // Smooth filter 3X canvas to LED grid
|
||||
|
||||
// Matrix and rings share a few pixels. To make the rings take
|
||||
// precedence, they're drawn later. So blink state is revisited now...
|
||||
if (blink_state) { // In mid-blink?
|
||||
for (uint8_t i=0; i<13; i++) { // Half an LED ring, top-to-bottom...
|
||||
float a = min(max(y_pos[i] - upper + 1.0, 0.0), 3.0);
|
||||
float b = min(max(lower - y_pos[i] + 1.0, 0.0), 3.0);
|
||||
ratio = a * b / 9.0; // Proximity of LED to eyelid edges
|
||||
uint32_t packed = interp(ring_open_color, ring_blink_color, ratio);
|
||||
glasses.left_ring.setPixelColor(i, packed);
|
||||
glasses.right_ring.setPixelColor(i, packed);
|
||||
if ((i > 0) && (i < 12)) {
|
||||
uint8_t j = 24 - i; // Mirror half-ring to other side
|
||||
glasses.left_ring.setPixelColor(j, packed);
|
||||
glasses.right_ring.setPixelColor(j, packed);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glasses.left_ring.fill(ring_open_color_packed);
|
||||
glasses.right_ring.fill(ring_open_color_packed);
|
||||
}
|
||||
|
||||
glasses.show();
|
||||
|
||||
frames += 1;
|
||||
elapsed = millis() - start_time;
|
||||
Serial.println(frames * 1000 / elapsed);
|
||||
}
|
||||
338
EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes_CircuitPython/code.py
Executable file
338
EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes_CircuitPython/code.py
Executable file
|
|
@ -0,0 +1,338 @@
|
|||
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
MOVE-AND-BLINK EYES for Adafruit EyeLights (LED Glasses + Driver).
|
||||
|
||||
I'd written a very cool squash-and-stretch effect for the eye movement,
|
||||
but unfortunately the resolution and frame rate are such that the pupils
|
||||
just look like circles regardless. I'm keeping it in despite the added
|
||||
complexity, because CircuitPython devices WILL get faster, LED matrix
|
||||
densities WILL improve, and this way the code won't require a re-write
|
||||
at such a later time. It's a really adorable effect with enough pixels.
|
||||
"""
|
||||
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
from supervisor import reload
|
||||
import board
|
||||
from busio import I2C
|
||||
import adafruit_is31fl3741
|
||||
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
|
||||
|
||||
|
||||
# CONFIGURABLES ------------------------
|
||||
|
||||
eye_color = (255, 128, 0) # Amber pupils
|
||||
ring_open_color = (75, 75, 75) # Color of LED rings when eyes open
|
||||
ring_blink_color = (50, 25, 0) # Color of LED ring "eyelid" when blinking
|
||||
|
||||
radius = 3.4 # Size of pupil (3X because of downsampling later)
|
||||
|
||||
# Reading through the code, you'll see a lot of references to this "3X"
|
||||
# space. What it's referring to is a bitmap that's 3 times the resolution
|
||||
# of the LED matrix (i.e. 15 pixels tall instead of 5), which gets scaled
|
||||
# down to provide some degree of antialiasing. It's why the pupils have
|
||||
# soft edges and can make fractional-pixel motions.
|
||||
# Because of the way the downsampling is done, the eyelid edge when drawn
|
||||
# across the eye will always be the same hue as the pupils, it can't be
|
||||
# set independently like the ring blink color.
|
||||
|
||||
gamma = 2.6 # For color adjustment. Leave as-is.
|
||||
|
||||
|
||||
# CLASSES & FUNCTIONS ------------------
|
||||
|
||||
|
||||
class Eye:
|
||||
"""Holds per-eye positional data; each covers a different area of the
|
||||
overall LED matrix."""
|
||||
|
||||
def __init__(self, left, xoff):
|
||||
self.left = left # Leftmost column on LED matrix
|
||||
self.x_offset = xoff # Horizontal offset (3X space) to fixate
|
||||
|
||||
def smooth(self, data, rect):
|
||||
"""Scale bitmap (in 'data') to LED array, with smooth 1:3
|
||||
downsampling. 'rect' is a 4-tuple rect of which pixels get
|
||||
filtered (anything outside is cleared to 0), saves a few cycles."""
|
||||
# Quantize bounds rect from 3X space to LED matrix space.
|
||||
rect = (
|
||||
rect[0] // 3, # Left
|
||||
rect[1] // 3, # Top
|
||||
(rect[2] + 2) // 3, # Right
|
||||
(rect[3] + 2) // 3, # Bottom
|
||||
)
|
||||
for y in range(rect[1]): # Erase rows above top
|
||||
for x in range(6):
|
||||
glasses.pixel(self.left + x, y, 0)
|
||||
for y in range(rect[1], rect[3]): # Each row, top to bottom...
|
||||
pixel_sum = bytearray(6) # Initialize row of pixel sums to 0
|
||||
for y1 in range(3): # 3 rows of bitmap...
|
||||
row = data[y * 3 + y1] # Bitmap data for current row
|
||||
for x in range(rect[0], rect[2]): # Column, left to right
|
||||
x3 = x * 3
|
||||
# Accumulate 3 pixels of bitmap into pixel_sum
|
||||
pixel_sum[x] += row[x3] + row[x3 + 1] + row[x3 + 2]
|
||||
# 'pixel_sum' will now contain values from 0-9, indicating the
|
||||
# number of set pixels in the corresponding section of the 3X
|
||||
# bitmap. 'colormap' expands the sum to 24-bit RGB space.
|
||||
for x in range(rect[0]): # Erase any columns to left
|
||||
glasses.pixel(self.left + x, y, 0)
|
||||
for x in range(rect[0], rect[2]): # Column, left to right
|
||||
glasses.pixel(self.left + x, y, colormap[pixel_sum[x]])
|
||||
for x in range(rect[2], 6): # Erase columns to right
|
||||
glasses.pixel(self.left + x, y, 0)
|
||||
for y in range(rect[3], 5): # Erase rows below bottom
|
||||
for x in range(6):
|
||||
glasses.pixel(self.left + x, y, 0)
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def rasterize(data, point1, point2, rect):
|
||||
"""Rasterize an arbitrary ellipse into the 'data' bitmap (3X pixel
|
||||
space), given foci point1 and point2 and with area determined by global
|
||||
'radius' (when foci are same point; a circle). Foci and radius are all
|
||||
floating point values, which adds to the buttery impression. 'rect' is
|
||||
a 4-tuple rect of which pixels are likely affected. Data is assumed 0
|
||||
before arriving here; no clearing is performed."""
|
||||
|
||||
dx = point2[0] - point1[0]
|
||||
dy = point2[1] - point1[1]
|
||||
d2 = dx * dx + dy * dy # Dist between foci, squared
|
||||
if d2 <= 0:
|
||||
# Foci are in same spot - it's a circle
|
||||
perimeter = 2 * radius
|
||||
d = 0
|
||||
else:
|
||||
# Foci are separated - it's an ellipse.
|
||||
d = d2 ** 0.5 # Distance between foci
|
||||
c = d * 0.5 # Center-to-foci distance
|
||||
# This is an utterly brute-force way of ellipse-filling based on
|
||||
# the "two nails and a string" metaphor...we have the foci points
|
||||
# and just need the string length (triangle perimeter) to yield
|
||||
# an ellipse with area equal to a circle of 'radius'.
|
||||
# c^2 = a^2 - b^2 <- ellipse formula
|
||||
# a = r^2 / b <- substitute
|
||||
# c^2 = (r^2 / b)^2 - b^2
|
||||
# b = sqrt(((c^2) + sqrt((c^4) + 4 * r^4)) / 2) <- solve for b
|
||||
b2 = ((c ** 2) + (((c ** 4) + 4 * (radius ** 4)) ** 0.5)) * 0.5
|
||||
# By my math, perimeter SHOULD be...
|
||||
# perimeter = d + 2 * ((b2 + (c ** 2)) ** 0.5)
|
||||
# ...but for whatever reason, working approach here is really...
|
||||
perimeter = d + 2 * (b2 ** 0.5)
|
||||
|
||||
# Like I'm sure there's a way to rasterize this by spans rather than
|
||||
# all these square roots on every pixel, but for now...
|
||||
for y in range(rect[1], rect[3]): # For each row...
|
||||
y5 = y + 0.5 # Pixel center
|
||||
dy1 = y5 - point1[1] # Y distance from pixel to first point
|
||||
dy2 = y5 - point2[1] # " to second
|
||||
dy1 *= dy1 # Y1^2
|
||||
dy2 *= dy2 # Y2^2
|
||||
for x in range(rect[0], rect[2]): # For each column...
|
||||
x5 = x + 0.5 # Pixel center
|
||||
dx1 = x5 - point1[0] # X distance from pixel to first point
|
||||
dx2 = x5 - point2[0] # " to second
|
||||
d1 = (dx1 * dx1 + dy1) ** 0.5 # 2D distance to first point
|
||||
d2 = (dx2 * dx2 + dy2) ** 0.5 # " to second
|
||||
if (d1 + d2 + d) <= perimeter:
|
||||
data[y][x] = 1 # Point is inside ellipse
|
||||
|
||||
|
||||
def gammify(color):
|
||||
"""Given an (R,G,B) color tuple, apply gamma correction and return
|
||||
a packed 24-bit RGB integer."""
|
||||
rgb = [int(((color[x] / 255) ** gamma) * 255 + 0.5) for x in range(3)]
|
||||
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]
|
||||
|
||||
|
||||
def interp(color1, color2, blend):
|
||||
"""Given two (R,G,B) color tuples and a blend ratio (0.0 to 1.0),
|
||||
interpolate between the two colors and return a gamma-corrected
|
||||
in-between color as a packed 24-bit RGB integer. No bounds clamping
|
||||
is performed on blend value, be nice."""
|
||||
inv = 1.0 - blend # Weighting of second color
|
||||
return gammify([color1[x] * blend + color2[x] * inv for x in range(3)])
|
||||
|
||||
|
||||
# HARDWARE SETUP -----------------------
|
||||
|
||||
# Manually declare I2C (not board.I2C() directly) to access 1 MHz speed...
|
||||
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
|
||||
|
||||
# Initialize the IS31 LED driver, buffered for smoother animation
|
||||
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
|
||||
glasses.show() # Clear any residue on startup
|
||||
glasses.global_current = 20 # Just middlin' bright, please
|
||||
|
||||
|
||||
# INITIALIZE TABLES & OTHER GLOBALS ----
|
||||
|
||||
# This table is for mapping 3x3 averaged bitmap values (0-9) to
|
||||
# RGB colors. Avoids a lot of shift-and-or on every pixel.
|
||||
colormap = []
|
||||
for n in range(10):
|
||||
colormap.append(gammify([n / 9 * eye_color[x] for x in range(3)]))
|
||||
|
||||
# Pre-compute the Y position of 1/2 of the LEDs in a ring, relative
|
||||
# to the 3X bitmap resolution, so ring & matrix animation can be aligned.
|
||||
y_pos = []
|
||||
for n in range(13):
|
||||
angle = n / 24 * math.pi * 2
|
||||
y_pos.append(10 - math.cos(angle) * 12)
|
||||
|
||||
# Pre-compute color of LED ring in fully open (unblinking) state
|
||||
ring_open_color_packed = gammify(ring_open_color)
|
||||
|
||||
# A single pre-computed scanline of "eyelid edge during blink" can be
|
||||
# stuffed into the 3X raster as needed, avoids setting pixels manually.
|
||||
eyelid = (
|
||||
b"\x01\x01\x00\x01\x01\x00\x01\x01\x00" b"\x01\x01\x00\x01\x01\x00\x01\x01\x00"
|
||||
) # 2/3 of pixels set
|
||||
|
||||
# Initialize eye position and move/blink animation timekeeping
|
||||
cur_pos = next_pos = (9, 7.5) # Current, next eye position in 3X space
|
||||
in_motion = False # True = eyes moving, False = eyes paused
|
||||
blink_state = 0 # 0, 1, 2 = unblinking, closing, opening
|
||||
move_start_time = move_duration = blink_start_time = blink_duration = 0
|
||||
|
||||
# Two eye objects. The first starts at column 1 of the matrix with its
|
||||
# pupil offset by +2 (in 3X space), second at column 11 with -2 offset.
|
||||
# The offsets make the pupils fixate slightly (converge on a point), so
|
||||
# the two pupils aren't always aligned the same on the pixel grid, which
|
||||
# would be conspicuously pixel-y.
|
||||
eyes = [Eye(1, 2), Eye(11, -2)]
|
||||
|
||||
frames, start_time = 0, time.monotonic() # For frames/second calculation
|
||||
|
||||
|
||||
# MAIN LOOP ----------------------------
|
||||
|
||||
while True:
|
||||
# The try/except here is because VERY INFREQUENTLY the I2C bus will
|
||||
# encounter an error when accessing the LED driver, whether from bumping
|
||||
# around the wires or sometimes an I2C device just gets wedged. To more
|
||||
# robustly handle the latter, the code will restart if that happens.
|
||||
try:
|
||||
|
||||
# The eye animation logic is a carry-over from like a billion
|
||||
# prior eye projects, so this might be comment-light.
|
||||
now = time.monotonic() # 'Snapshot' the time once per frame
|
||||
|
||||
# Blink logic
|
||||
elapsed = now - blink_start_time # Time since start of blink event
|
||||
if elapsed > blink_duration: # All done with event?
|
||||
blink_start_time = now # A new one starts right now
|
||||
elapsed = 0
|
||||
blink_state += 1 # Cycle closing/opening/paused
|
||||
if blink_state == 1: # Starting new blink...
|
||||
blink_duration = random.uniform(0.06, 0.12)
|
||||
elif blink_state == 2: # Switching closing to opening...
|
||||
blink_duration *= 2 # Opens at half the speed
|
||||
else: # Switching to pause in blink
|
||||
blink_state = 0
|
||||
blink_duration = random.uniform(0.5, 4)
|
||||
if blink_state: # If currently in a blink...
|
||||
ratio = elapsed / blink_duration # 0.0-1.0 as it closes
|
||||
if blink_state == 2:
|
||||
ratio = 1.0 - ratio # 1.0-0.0 as it opens
|
||||
upper = ratio * 15 - 4 # Upper eyelid pos. in 3X space
|
||||
lower = 23 - ratio * 8 # Lower eyelid pos. in 3X space
|
||||
|
||||
# Eye movement logic. Two points, 'p1' and 'p2', are the foci of an
|
||||
# ellipse. p1 moves from current to next position a little faster
|
||||
# than p2, creating a "squash and stretch" effect (frame rate and
|
||||
# resolution permitting). When motion is stopped, the two points
|
||||
# are at the same position.
|
||||
elapsed = now - move_start_time # Time since start of move event
|
||||
if in_motion: # Currently moving?
|
||||
if elapsed > move_duration: # If end of motion reached,
|
||||
in_motion = False # Stop motion and
|
||||
p1 = p2 = cur_pos = next_pos # Set to new position
|
||||
move_duration = random.uniform(0.5, 1.5) # Wait this long
|
||||
else: # Still moving
|
||||
# Determine p1, p2 position in time
|
||||
delta = (next_pos[0] - cur_pos[0], next_pos[1] - cur_pos[1])
|
||||
ratio = elapsed / move_duration
|
||||
if ratio < 0.6: # First 60% of move time
|
||||
# p1 is in motion
|
||||
# Easing function: 3*e^2-2*e^3 0.0 to 1.0
|
||||
e = ratio / 0.6 # 0.0 to 1.0
|
||||
e = 3 * e * e - 2 * e * e * e
|
||||
p1 = (cur_pos[0] + delta[0] * e, cur_pos[1] + delta[1] * e)
|
||||
else: # Last 40% of move time
|
||||
p1 = next_pos # p1 has reached end position
|
||||
if ratio > 0.3: # Last 60% of move time
|
||||
# p2 is in motion
|
||||
e = (ratio - 0.3) / 0.7 # 0.0 to 1.0
|
||||
e = 3 * e * e - 2 * e * e * e # Easing func.
|
||||
p2 = (cur_pos[0] + delta[0] * e, cur_pos[1] + delta[1] * e)
|
||||
else: # First 40% of move time
|
||||
p2 = cur_pos # p2 waits at start position
|
||||
else: # Eye is stopped
|
||||
p1 = p2 = cur_pos # Both foci at current eye position
|
||||
if elapsed > move_duration: # Pause time expired?
|
||||
in_motion = True # Start up new motion!
|
||||
move_start_time = now
|
||||
move_duration = random.uniform(0.15, 0.25)
|
||||
angle = random.uniform(0, math.pi * 2)
|
||||
dist = random.uniform(0, 7.5)
|
||||
next_pos = (
|
||||
9 + math.cos(angle) * dist,
|
||||
7.5 + math.sin(angle) * dist * 0.8,
|
||||
)
|
||||
|
||||
# Draw the raster part of each eye...
|
||||
for eye in eyes:
|
||||
# Allocate/clear the 3X bitmap buffer
|
||||
bitmap = [bytearray(6 * 3) for _ in range(5 * 3)]
|
||||
# Each eye's foci are offset slightly, to fixate toward center
|
||||
p1a = (p1[0] + eye.x_offset, p1[1])
|
||||
p2a = (p2[0] + eye.x_offset, p2[1])
|
||||
# Compute bounding rectangle (in 3X space) of ellipse
|
||||
# (min X, min Y, max X, max Y). Like the ellipse rasterizer,
|
||||
# this isn't optimal, but will suffice.
|
||||
bounds = (
|
||||
max(int(min(p1a[0], p2a[0]) - radius), 0),
|
||||
max(int(min(p1a[1], p2a[1]) - radius), 0, int(upper)),
|
||||
min(int(max(p1a[0], p2a[0]) + radius + 1), 18),
|
||||
min(int(max(p1a[1], p2a[1]) + radius + 1), 15, int(lower) + 1),
|
||||
)
|
||||
rasterize(bitmap, p1a, p2a, bounds) # Render ellipse into buffer
|
||||
# If the eye is currently blinking, and if the top edge of the
|
||||
# eyelid overlaps the bitmap, draw a scanline across the bitmap
|
||||
# and update the bounds rect so the whole width of the bitmap
|
||||
# is scaled.
|
||||
if blink_state and upper >= 0:
|
||||
bitmap[int(upper)] = eyelid
|
||||
bounds = (0, int(upper), 18, bounds[3])
|
||||
eye.smooth(bitmap, bounds) # 1:3 downsampling for eye
|
||||
|
||||
# Matrix and rings share a few pixels. To make the rings take
|
||||
# precedence, they're drawn later. So blink state is revisited now...
|
||||
if blink_state: # In mid-blink?
|
||||
for i in range(13): # Half an LED ring, top-to-bottom...
|
||||
a = min(max(y_pos[i] - upper + 1, 0), 3)
|
||||
b = min(max(lower - y_pos[i] + 1, 0), 3)
|
||||
ratio = a * b / 9 # Proximity of LED to eyelid edges
|
||||
packed = interp(ring_open_color, ring_blink_color, ratio)
|
||||
glasses.left_ring[i] = glasses.right_ring[i] = packed
|
||||
if 0 < i < 12:
|
||||
i = 24 - i # Mirror half-ring to other side
|
||||
glasses.left_ring[i] = glasses.right_ring[i] = packed
|
||||
else:
|
||||
glasses.left_ring.fill(ring_open_color_packed)
|
||||
glasses.right_ring.fill(ring_open_color_packed)
|
||||
|
||||
glasses.show() # Buffered mode MUST use show() to refresh matrix
|
||||
|
||||
except OSError: # See "try" notes above regarding rare I2C errors.
|
||||
print("Restarting")
|
||||
reload()
|
||||
|
||||
frames += 1
|
||||
elapsed = time.monotonic() - start_time
|
||||
print(frames / elapsed)
|
||||
202
EyeLights_Bluetooth_Scroller/EyeLights_Bluetooth_Scroller.ino
Normal file
202
EyeLights_Bluetooth_Scroller/EyeLights_Bluetooth_Scroller.ino
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
BLUETOOTH SCROLLING MESSAGE for Adafruit EyeLights (LED Glasses + Driver).
|
||||
Use BLUEFRUIT CONNECT app on iOS or Android to connect to LED glasses.
|
||||
Use the app's UART input to enter a new message.
|
||||
Use the app's Color Picker (under "Controller") to change text color.
|
||||
This is based on the glassesdemo-3-smooth example from the
|
||||
Adafruit_IS31FL3741 library, with Bluetooth additions on top. If this
|
||||
code all seems a bit too much, you can start with that example (or the two
|
||||
that precede it) to gain an understanding of the LED glasses basics, then
|
||||
return here to see what the extra Bluetooth layers do.
|
||||
*/
|
||||
|
||||
#include <Adafruit_IS31FL3741.h> // For LED driver
|
||||
#include <bluefruit.h> // For Bluetooth communication
|
||||
#include <EyeLightsCanvasFont.h> // Smooth scrolly font for glasses
|
||||
|
||||
// These items are over in the packetParser.cpp tab:
|
||||
extern uint8_t packetbuffer[];
|
||||
extern uint8_t readPacket(BLEUart *ble, uint16_t timeout);
|
||||
extern int8_t packetType(uint8_t *buf, uint8_t len);
|
||||
extern float parsefloat(uint8_t *buffer);
|
||||
extern void printHex(const uint8_t * data, const uint32_t numBytes);
|
||||
|
||||
// GLOBAL VARIABLES -------
|
||||
|
||||
// 'Buffered' glasses for buttery animation,
|
||||
// 'true' to allocate a drawing canvas for smooth graphics:
|
||||
Adafruit_EyeLights_buffered glasses(true);
|
||||
GFXcanvas16 *canvas; // Pointer to glasses' canvas object
|
||||
// Because 'canvas' is a pointer, always use -> when calling
|
||||
// drawing functions there. 'glasses' is an object in itself,
|
||||
// so . is used when calling its functions.
|
||||
|
||||
char message[51] = "Run Bluefruit Connect app"; // Scrolling message
|
||||
int16_t text_x; // Message position on canvas
|
||||
int16_t text_min; // Leftmost position before restarting scroll
|
||||
|
||||
BLEUart bleuart; // Bluetooth low energy UART
|
||||
|
||||
int8_t last_packet_type = 99; // Last BLE packet type, init to nonsense value
|
||||
|
||||
// ONE-TIME SETUP ---------
|
||||
|
||||
void setup() { // Runs once at program start...
|
||||
|
||||
Serial.begin(115200);
|
||||
//while(!Serial);
|
||||
|
||||
// Configure and start the BLE UART service
|
||||
Bluefruit.begin();
|
||||
Bluefruit.setTxPower(4);
|
||||
bleuart.begin();
|
||||
startAdv(); // Set up and start advertising
|
||||
|
||||
if (!glasses.begin()) err("IS3741 not found", 2);
|
||||
|
||||
canvas = glasses.getCanvas();
|
||||
if (!canvas) err("Can't allocate canvas", 5);
|
||||
|
||||
// Configure glasses for full brightness and enable output
|
||||
glasses.setLEDscaling(0xFF);
|
||||
glasses.setGlobalCurrent(0xFF);
|
||||
glasses.enable(true);
|
||||
|
||||
// Set up for scrolling text, initialize color and position
|
||||
canvas->setFont(&EyeLightsCanvasFont);
|
||||
canvas->setTextWrap(false); // Allow text to extend off edges
|
||||
canvas->setTextColor(glasses.color565(0x303030)); // Dim white to start
|
||||
reposition_text(); // Sets up initial position & scroll limit
|
||||
}
|
||||
|
||||
// Crude error handler, prints message to Serial console, flashes LED
|
||||
void err(char *str, uint8_t hz) {
|
||||
Serial.println(str);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
|
||||
}
|
||||
|
||||
// Set up, start BLE advertising
|
||||
void startAdv(void) {
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
|
||||
// Include the BLE UART (AKA 'NUS') 128-bit UUID
|
||||
Bluefruit.Advertising.addService(bleuart);
|
||||
|
||||
// Secondary Scan Response packet (optional)
|
||||
// Since there is no room for 'Name' in Advertising packet
|
||||
Bluefruit.ScanResponse.addName();
|
||||
|
||||
// Start Advertising
|
||||
// - Enable auto advertising if disconnected
|
||||
// - Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
// - Timeout for fast mode is 30 seconds
|
||||
// - Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
//
|
||||
// For recommended advertising interval
|
||||
// https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
}
|
||||
|
||||
// MAIN LOOP --------------
|
||||
|
||||
void loop() { // Repeat forever...
|
||||
// The packet read timeout (9 ms here) also determines the text
|
||||
// scrolling speed -- if no data is received over BLE in that time,
|
||||
// the function exits and returns here with len=0.
|
||||
uint8_t len = readPacket(&bleuart, 9);
|
||||
if (len) {
|
||||
int8_t type = packetType(packetbuffer, len);
|
||||
// The Bluefruit Connect app can return a variety of data from
|
||||
// a phone's sensors. To keep this example relatively simple,
|
||||
// we'll only look at color and text, but here's where others
|
||||
// would go if we were to extend this. See Bluefruit library
|
||||
// examples for the packet data formats. packetParser.cpp
|
||||
// has a couple functions not used in this code but that may be
|
||||
// helpful in interpreting these other packet types.
|
||||
switch(type) {
|
||||
case 0: // Accelerometer
|
||||
Serial.println("Accel");
|
||||
break;
|
||||
case 1: // Gyro:
|
||||
Serial.println("Gyro");
|
||||
break;
|
||||
case 2: // Magnetometer
|
||||
Serial.println("Mag");
|
||||
break;
|
||||
case 3: // Quaternion
|
||||
Serial.println("Quat");
|
||||
break;
|
||||
case 4: // Button
|
||||
Serial.println("Button");
|
||||
break;
|
||||
case 5: // Color
|
||||
Serial.println("Color");
|
||||
// packetbuffer[2] through [4] contain R, G, B byte values.
|
||||
// Because the drawing canvas uses lower-precision '565' color,
|
||||
// and because glasses.scale() applies gamma correction and may
|
||||
// quantize the dimmest colors to 0, set a brightness floor here
|
||||
// so text isn't invisible.
|
||||
for (uint8_t i=2; i<=4; i++) {
|
||||
if (packetbuffer[i] < 0x20) packetbuffer[i] = 0x20;
|
||||
}
|
||||
canvas->setTextColor(glasses.color565(glasses.Color(
|
||||
packetbuffer[2], packetbuffer[3], packetbuffer[4])));
|
||||
break;
|
||||
case 6: // Location
|
||||
Serial.println("Location");
|
||||
break;
|
||||
default: // -1
|
||||
// Packet is not one of the Bluefruit Connect types. Most programs
|
||||
// will ignore/reject it as not valud, but in this case we accept
|
||||
// it as a freeform string for the scrolling message.
|
||||
if (last_packet_type != -1) {
|
||||
// If prior data was a packet, this is a new freeform string,
|
||||
// initialize the message string with it...
|
||||
strncpy(message, (char *)packetbuffer, 20);
|
||||
} else {
|
||||
// If prior data was also a freeform string, concatenate this onto
|
||||
// the message (up to the max message length). BLE packets can only
|
||||
// be so large, so long strings are broken into multiple packets.
|
||||
uint8_t message_len = strlen(message);
|
||||
uint8_t max_append = sizeof message - 1 - message_len;
|
||||
strncpy(&message[message_len], (char *)packetbuffer, max_append);
|
||||
len = message_len + max_append;
|
||||
}
|
||||
message[len] = 0; // End of string NUL char
|
||||
Serial.println(message);
|
||||
reposition_text(); // Reset text off right edge of canvas
|
||||
}
|
||||
last_packet_type = type; // Save packet type for next pass
|
||||
} else {
|
||||
last_packet_type = 99; // BLE read timeout, reset last type to nonsense
|
||||
}
|
||||
|
||||
canvas->fillScreen(0); // Clear the whole drawing canvas
|
||||
// Update text to new position, and draw on canvas
|
||||
if (--text_x < text_min) { // If text scrolls off left edge,
|
||||
text_x = canvas->width(); // reset position off right edge
|
||||
}
|
||||
canvas->setCursor(text_x, canvas->height());
|
||||
canvas->print(message);
|
||||
glasses.scale(); // 1:3 downsample canvas to LED matrix
|
||||
glasses.show(); // MUST call show() to update matrix
|
||||
}
|
||||
|
||||
// When new message text is assigned, call this to reset its position
|
||||
// off the right edge and calculate column where scrolling resets.
|
||||
void reposition_text() {
|
||||
uint16_t w, h, ignore;
|
||||
canvas->getTextBounds(message, 0, 0, (int16_t *)&ignore, (int16_t *)&ignore, &w, &ignore);
|
||||
text_x = canvas->width();
|
||||
text_min = -w; // Off left edge this many pixels
|
||||
}
|
||||
119
EyeLights_Bluetooth_Scroller/packetParser.cpp
Normal file
119
EyeLights_Bluetooth_Scroller/packetParser.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#include <bluefruit.h>
|
||||
|
||||
// packetbuffer holds inbound data
|
||||
#define READ_BUFSIZE 20
|
||||
uint8_t packetbuffer[READ_BUFSIZE + 1]; // +1 is for NUL string terminator
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Casts the four bytes at the specified address to a float.
|
||||
@param ptr Pointer into packet buffer.
|
||||
@returns Floating-point value.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
float parsefloat(uint8_t *ptr) {
|
||||
float f; // Make a suitably-aligned float variable,
|
||||
memcpy(&f, ptr, 4); // because in-buffer instance might be misaligned!
|
||||
return f; // (You can't always safely parse in-place)
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Prints a series of bytes in 0xNN hexadecimal notation.
|
||||
@param buf Pointer to array of byte data.
|
||||
@param len Data length in bytes.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void printHex(const uint8_t *buf, const uint32_t len) {
|
||||
for (uint32_t i=0; i < len; i++) {
|
||||
Serial.print(F("0x"));
|
||||
if (buf[i] <= 0xF) Serial.write('0'); // Zero-pad small values
|
||||
Serial.print(buf[i], HEX);
|
||||
if (i < (len - 1)) Serial.write(' '); // Space between bytes
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static const struct { // Special payloads from Bluefruit Connect app...
|
||||
char id; // Packet type identifier
|
||||
uint8_t len; // Size of complete, well-formed packet of this type
|
||||
} _app_packet[] = {
|
||||
{'A', 15}, // Accelerometer
|
||||
{'G', 15}, // Gyro
|
||||
{'M', 15}, // Magnetometer
|
||||
{'Q', 19}, // Quaterion
|
||||
{'B', 5}, // Button
|
||||
{'C', 6}, // Color
|
||||
{'L', 15}, // Location
|
||||
};
|
||||
#define NUM_PACKET_TYPES (sizeof _app_packet / sizeof _app_packet[0])
|
||||
#define SHORTEST_PACKET_LEN 5 // Button, for now
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Given packet data, identify if it's one of the known
|
||||
Bluefruit Connect app packet types.
|
||||
@param buf Pointer to packet data.
|
||||
@param len Size of packet in bytes.
|
||||
@returns Packet type index (0 to NUM_PACKET_TYPES-1) if recognized,
|
||||
-1 if unrecognized.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int8_t packetType(uint8_t *buf, uint8_t len) {
|
||||
if ((len >= SHORTEST_PACKET_LEN) && (buf[0] == '!')) {
|
||||
for (int8_t type=0; type<NUM_PACKET_TYPES; type++) {
|
||||
if ((buf[1] == _app_packet[type].id) &&
|
||||
(len == _app_packet[type].len)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // Length too short for a packet, or not a recognized type
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Wait for incoming data and determine if it's one of the
|
||||
special Bluefruit Connect app packet types.
|
||||
@param ble Pointer to BLE UART object.
|
||||
timeout Character read timeout in milliseconds.
|
||||
@returns Length of data, or 0 if checksum is invalid for the type of
|
||||
packet detected.
|
||||
@note Packet buffer is not cleared. Calling function is expected
|
||||
to check return value before deciding whether to act on the
|
||||
data.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint8_t readPacket(BLEUart *ble, uint16_t timeout) {
|
||||
int8_t type = -1; // App packet type, -1 if unknown or freeform string
|
||||
uint8_t len = 0, xsum = 255; // Packet length and ~checksum so far
|
||||
uint32_t now, start_time = millis();
|
||||
do {
|
||||
now = millis();
|
||||
if (ble->available()) {
|
||||
char c = ble->read();
|
||||
if (c == '!') { // '!' resets buffer to start
|
||||
len = 0;
|
||||
xsum = 255;
|
||||
}
|
||||
packetbuffer[len++] = c;
|
||||
// Stop when buffer's full or packet type recognized
|
||||
if ((len >= READ_BUFSIZE) ||
|
||||
((type = packetType(packetbuffer, len)) >= 0)) break;
|
||||
start_time = now; // Reset timeout on char received
|
||||
xsum -= c; // Not last char, do checksum math
|
||||
type = -1; // Reset packet type finder
|
||||
}
|
||||
} while((now - start_time) < timeout);
|
||||
|
||||
// If packet type recognized, verify checksum (else freeform string)
|
||||
if ((type >= 0) && (xsum != packetbuffer[len-1])) { // Last byte = checksum
|
||||
Serial.print("Packet checksum mismatch: ");
|
||||
printHex(packetbuffer, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
packetbuffer[len] = 0; // Terminate packet string
|
||||
|
||||
return len; // Checksum is valid for packet, or it's a freeform string
|
||||
}
|
||||
135
EyeLights_Fire/EyeLights_Fire/EyeLights_Fire.ino
Normal file
135
EyeLights_Fire/EyeLights_Fire/EyeLights_Fire.ino
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
FIRE EFFECT for Adafruit EyeLights (LED Glasses + Driver).
|
||||
A demoscene classic that produces a cool analog-esque look with
|
||||
modest means, iteratively scrolling and blurring raster data.
|
||||
*/
|
||||
|
||||
#include <Adafruit_IS31FL3741.h> // For LED driver
|
||||
|
||||
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
|
||||
|
||||
// The raster data is intentionally one row taller than the LED matrix.
|
||||
// Each frame, random noise is put in the bottom (off matrix) row. There's
|
||||
// also an extra column on either side, to avoid needing edge clipping when
|
||||
// neighboring pixels (left, center, right) are averaged later.
|
||||
float data[6][20]; // 2D array where elements are accessed as data[y][x]
|
||||
|
||||
// Each element in the raster is a single value representing brightness.
|
||||
// A pre-computed lookup table maps these to RGB colors. This one happens
|
||||
// to have 32 elements, but as we're not on an actual paletted hardware
|
||||
// framebuffer it could be any size really (with suitable changes throughout).
|
||||
uint32_t colormap[32];
|
||||
#define GAMMA 2.6
|
||||
|
||||
// Crude error handler, prints message to Serial console, flashes LED
|
||||
void err(char *str, uint8_t hz) {
|
||||
Serial.println(str);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
|
||||
}
|
||||
|
||||
void setup() { // Runs once at program start...
|
||||
|
||||
// Initialize hardware
|
||||
Serial.begin(115200);
|
||||
if (! glasses.begin()) err("IS3741 not found", 2);
|
||||
|
||||
// Configure glasses for reduced brightness, enable output
|
||||
glasses.setLEDscaling(0xFF);
|
||||
glasses.setGlobalCurrent(20);
|
||||
glasses.enable(true);
|
||||
|
||||
memset(data, 0, sizeof data);
|
||||
|
||||
for(uint8_t i=0; i<32; i++) {
|
||||
float n = i * 3.0 / 31.0; // 0.0 <= n <= 3.0 from start to end of map
|
||||
float r, g, b;
|
||||
if (n <= 1) { // 0.0 <= n <= 1.0 : black to red
|
||||
r = n; // r,g,b are initially calculated 0 to 1 range
|
||||
g = b = 0.0;
|
||||
} else if (n <= 2) { // 1.0 <= n <= 2.0 : red to yellow
|
||||
r = 1.0;
|
||||
g = n - 1.0;
|
||||
b = 0.0;
|
||||
} else { // 2.0 <= n <= 3.0 : yellow to white
|
||||
r = g = 1.0;
|
||||
b = n - 2.0;
|
||||
}
|
||||
// Gamma correction linearizes perceived brightness, then scale to
|
||||
// 0-255 for LEDs and store as a 'packed' RGB color.
|
||||
colormap[i] = (uint32_t(pow(r, GAMMA) * 255.0) << 16) |
|
||||
(uint32_t(pow(g, GAMMA) * 255.0) << 8) |
|
||||
uint32_t(pow(b, GAMMA) * 255.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Linearly interpolate a range of brightnesses between two LEDs of
|
||||
// one eyeglass ring, mapping through the global color table. LED range
|
||||
// is non-inclusive; the first and last LEDs (which overlap matrix pixels)
|
||||
// are not set. led2 MUST be > led1. LED indices may be >= 24 to 'wrap
|
||||
// around' the seam at the top of the ring.
|
||||
void interp(bool isRight, int led1, int led2, float level1, float level2) {
|
||||
int span = led2 - led1 + 1; // Number of LEDs
|
||||
float delta = level2 - level1; // Difference in brightness
|
||||
for (int led = led1 + 1; led < led2; led++) { // For each LED in-between,
|
||||
float ratio = (float)(led - led1) / span; // interpolate brightness level
|
||||
uint32_t color = colormap[min(31, int(level1 + delta * ratio))];
|
||||
if (isRight) glasses.right_ring.setPixelColor(led % 24, color);
|
||||
else glasses.left_ring.setPixelColor(led % 24, color);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() { // Repeat forever...
|
||||
// At the start of each frame, fill the bottom (off matrix) row
|
||||
// with random noise. To make things less strobey, old data from the
|
||||
// prior frame still has about 1/3 'weight' here. There's no special
|
||||
// real-world significance to the 85, it's just an empirically-
|
||||
// derived fudge factor that happens to work well with the size of
|
||||
// the color map.
|
||||
for (uint8_t x=1; x<19; x++) {
|
||||
data[5][x] = 0.33 * data[5][x] + 0.67 * ((float)random(1000) / 1000.0) * 85.0;
|
||||
}
|
||||
// If this were actual SRS BZNS 31337 D3M0SC3N3 code, great care
|
||||
// would be taken to avoid floating-point math. But with few pixels,
|
||||
// and so this code might be less obtuse, a casual approach is taken.
|
||||
|
||||
// Each row (except last) is then processed, top-to-bottom. This
|
||||
// order is important because it's an iterative algorithm...the
|
||||
// output of each frame serves as input to the next, and the steps
|
||||
// below (looking at the pixels below each row) are what makes the
|
||||
// "flames" appear to move "up."
|
||||
for (uint8_t y=0; y<5; y++) { // Current row of pixels
|
||||
float *y1 = &data[y + 1][0]; // One row down
|
||||
for (uint8_t x = 1; x < 19; x++) { // Skip left, right columns in data
|
||||
// Each pixel is sort of the average of the three pixels
|
||||
// under it (below left, below center, below right), but not
|
||||
// exactly. The below center pixel has more 'weight' than the
|
||||
// others, and the result is scaled to intentionally land
|
||||
// short, making each row bit darker as they move up.
|
||||
data[y][x] = (y1[x] + ((y1[x - 1] + y1[x + 1]) * 0.33)) * 0.35;
|
||||
glasses.drawPixel(x - 1, y, glasses.color565(colormap[min(31, int(data[y][x]))]));
|
||||
// Remember that the LED matrix uses GFX-style "565" colors,
|
||||
// hence the round trip through color565() here, whereas the LED
|
||||
// rings (referenced in interp()) use NeoPixel-style 24-bit colors
|
||||
// (those can reference colormap[] directly).
|
||||
}
|
||||
}
|
||||
|
||||
// That's all well and good for the matrix, but what about the extra
|
||||
// LEDs in the rings? Since these don't align to the pixel grid,
|
||||
// rather than trying to extend the raster data and filter it in
|
||||
// somehow, we'll fill those arcs with colors interpolated from the
|
||||
// endpoints where rings and matrix intersect. Maybe not perfect,
|
||||
// but looks okay enough!
|
||||
interp(false, 7, 17, data[4][8], data[4][1]); // Left ring bottom
|
||||
interp(false, 21, 29, data[0][2], data[1][8]); // Left ring top
|
||||
interp(true, 7, 17, data[4][18], data[4][11]); // Right ring bottom
|
||||
interp(true, 19, 27, data[1][11], data[0][17]); // Right ring top
|
||||
|
||||
glasses.show();
|
||||
delay(25);
|
||||
}
|
||||
131
EyeLights_Fire/EyeLights_Fire_CircuitPython/code.py
Executable file
131
EyeLights_Fire/EyeLights_Fire_CircuitPython/code.py
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
FIRE EFFECT for Adafruit EyeLights (LED Glasses + Driver).
|
||||
A demoscene classic that produces a cool analog-esque look with
|
||||
modest means, iteratively scrolling and blurring raster data.
|
||||
"""
|
||||
|
||||
import random
|
||||
from supervisor import reload
|
||||
import board
|
||||
from busio import I2C
|
||||
import adafruit_is31fl3741
|
||||
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
|
||||
|
||||
|
||||
# HARDWARE SETUP ---------
|
||||
|
||||
# Manually declare I2C (not board.I2C() directly) to access 1 MHz speed...
|
||||
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
|
||||
|
||||
# Initialize the IS31 LED driver, buffered for smoother animation
|
||||
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
|
||||
glasses.show() # Clear any residue on startup
|
||||
glasses.global_current = 20 # Just middlin' bright, please
|
||||
|
||||
|
||||
# INITIALIZE TABLES ------
|
||||
|
||||
# The raster data is intentionally one row taller than the LED matrix.
|
||||
# Each frame, random noise is put in the bottom (off matrix) row. There's
|
||||
# also an extra column on either side, to avoid needing edge clipping when
|
||||
# neighboring pixels (left, center, right) are averaged later.
|
||||
data = [[0] * (glasses.width + 2) for _ in range(glasses.height + 1)]
|
||||
# (2D array where elements are accessed as data[y][x], initialized to 0)
|
||||
|
||||
# Each element in the raster is a single value representing brightness.
|
||||
# A pre-computed lookup table maps these to RGB colors. This one happens
|
||||
# to have 32 elements, but as we're not on an actual paletted hardware
|
||||
# framebuffer it could be any size really (with suitable changes throughout).
|
||||
gamma = 2.6
|
||||
colormap = []
|
||||
for n in range(32):
|
||||
n *= 3 / 31 # 0.0 <= n <= 3.0 from start to end of map
|
||||
if n <= 1: # 0.0 <= n <= 1.0 : black to red
|
||||
r = n # r,g,b are initially calculated 0 to 1 range
|
||||
g = b = 0
|
||||
elif n <= 2: # 1.0 <= n <= 2.0 : red to yellow
|
||||
r = 1
|
||||
g = n - 1
|
||||
b = 0
|
||||
else: # 2.0 <= n <= 3.0 : yellow to white
|
||||
r = g = 1
|
||||
b = n - 2
|
||||
r = int((r ** gamma) * 255) # Gamma correction linearizes
|
||||
g = int((g ** gamma) * 255) # perceived brightness, then
|
||||
b = int((b ** gamma) * 255) # scale to 0-255 for LEDs and
|
||||
colormap.append((r << 16) | (g << 8) | b) # store as 'packed' RGB color
|
||||
|
||||
|
||||
# UTILITY FUNCTIONS -----
|
||||
|
||||
|
||||
def interp(ring, led1, led2, level1, level2):
|
||||
"""Linearly interpolate a range of brightnesses between two LEDs of
|
||||
one eyeglass ring, mapping through the global color table. LED range
|
||||
is non-inclusive; the first and last LEDs (which overlap matrix pixels)
|
||||
are not set. led2 MUST be > led1. LED indices may be >= 24 to 'wrap
|
||||
around' the seam at the top of the ring."""
|
||||
span = led2 - led1 + 1 # Number of LEDs
|
||||
delta = level2 - level1 # Difference in brightness
|
||||
for led in range(led1 + 1, led2): # For each LED in-between,
|
||||
ratio = (led - led1) / span # interpolate brightness level
|
||||
ring[led % 24] = colormap[min(31, int(level1 + delta * ratio))]
|
||||
|
||||
|
||||
# MAIN LOOP -------------
|
||||
|
||||
while True:
|
||||
# The try/except here is because VERY INFREQUENTLY the I2C bus will
|
||||
# encounter an error when accessing the LED driver, whether from bumping
|
||||
# around the wires or sometimes an I2C device just gets wedged. To more
|
||||
# robustly handle the latter, the code will restart if that happens.
|
||||
try:
|
||||
|
||||
# At the start of each frame, fill the bottom (off matrix) row
|
||||
# with random noise. To make things less strobey, old data from the
|
||||
# prior frame still has about 1/3 'weight' here. There's no special
|
||||
# real-world significance to the 85, it's just an empirically-
|
||||
# derived fudge factor that happens to work well with the size of
|
||||
# the color map.
|
||||
for x in range(1, 19):
|
||||
data[5][x] = 0.33 * data[5][x] + 0.67 * random.random() * 85
|
||||
# If this were actual SRS BZNS 31337 D3M0SC3N3 code, great care
|
||||
# would be taken to avoid floating-point math. But with few pixels,
|
||||
# and so this code might be less obtuse, a casual approach is taken.
|
||||
|
||||
# Each row (except last) is then processed, top-to-bottom. This
|
||||
# order is important because it's an iterative algorithm...the
|
||||
# output of each frame serves as input to the next, and the steps
|
||||
# below (looking at the pixels below each row) are what makes the
|
||||
# "flames" appear to move "up."
|
||||
for y in range(5): # Current row of pixels
|
||||
y1 = data[y + 1] # One row down
|
||||
for x in range(1, 19): # Skip left, right columns in data
|
||||
# Each pixel is sort of the average of the three pixels
|
||||
# under it (below left, below center, below right), but not
|
||||
# exactly. The below center pixel has more 'weight' than the
|
||||
# others, and the result is scaled to intentionally land
|
||||
# short, making each row bit darker as they move up.
|
||||
data[y][x] = (y1[x] + ((y1[x - 1] + y1[x + 1]) * 0.33)) * 0.35
|
||||
glasses.pixel(x - 1, y, colormap[min(31, int(data[y][x]))])
|
||||
|
||||
# That's all well and good for the matrix, but what about the extra
|
||||
# LEDs in the rings? Since these don't align to the pixel grid,
|
||||
# rather than trying to extend the raster data and filter it in
|
||||
# somehow, we'll fill those arcs with colors interpolated from the
|
||||
# endpoints where rings and matrix intersect. Maybe not perfect,
|
||||
# but looks okay enough!
|
||||
interp(glasses.left_ring, 7, 17, data[4][8], data[4][1])
|
||||
interp(glasses.left_ring, 21, 29, data[0][2], data[2][8])
|
||||
interp(glasses.right_ring, 7, 17, data[4][18], data[4][11])
|
||||
interp(glasses.right_ring, 19, 27, data[2][11], data[0][17])
|
||||
|
||||
glasses.show() # Buffered mode MUST use show() to refresh matrix
|
||||
|
||||
except OSError: # See "try" notes above regarding rare I2C errors.
|
||||
print("Restarting")
|
||||
reload()
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics
|
||||
simulation using accelerometer and math. This uses only the rings, not the
|
||||
matrix portion. Adapted from Bill Earl's STEAM-Punk Goggles project:
|
||||
https://learn.adafruit.com/steam-punk-goggles
|
||||
*/
|
||||
|
||||
#include <Adafruit_IS31FL3741.h> // For LED driver
|
||||
#include <Adafruit_LIS3DH.h> // For accelerometer
|
||||
#include <Adafruit_Sensor.h> // For m/s^2 accel units
|
||||
|
||||
Adafruit_LIS3DH accel;
|
||||
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
|
||||
|
||||
// A small class for our pendulum simulation.
|
||||
class Pendulum {
|
||||
public:
|
||||
// Constructor. Pass pointer to EyeLights ring, and a 3-byte color array.
|
||||
Pendulum(Adafruit_EyeLights_Ring_buffered *r, uint8_t *c) {
|
||||
ring = r;
|
||||
color = c;
|
||||
// Initial pendulum position, plus axle friction, are randomized
|
||||
// so rings don't spin in perfect lockstep.
|
||||
angle = random(1000);
|
||||
momentum = 0.0;
|
||||
friction = 0.94 + random(300) * 0.0001; // Inverse friction, really
|
||||
}
|
||||
|
||||
// Given a pixel index (0-23) and a scaling factor (0.0-1.0),
|
||||
// interpolate between LED "off" color (at 0.0) and this item's fully-
|
||||
// lit color (at 1.0) and set pixel to the result.
|
||||
void interp(uint8_t pixel, float scale) {
|
||||
// Convert separate red, green, blue to "packed" 24-bit RGB value
|
||||
ring->setPixelColor(pixel,
|
||||
(int(color[0] * scale) << 16) |
|
||||
(int(color[1] * scale) << 8) |
|
||||
int(color[2] * scale));
|
||||
}
|
||||
|
||||
// Given an accelerometer reading, run one cycle of the pendulum
|
||||
// physics simulation and render the corresponding LED ring.
|
||||
void iterate(sensors_event_t &event) {
|
||||
// Minus here is because LED pixel indices run clockwise vs. trigwise.
|
||||
// 0.006 is just an empirically-derived scaling fudge factor that looks
|
||||
// good; smaller values for more sluggish rings, higher = more twitch.
|
||||
momentum = momentum * friction - 0.006 *
|
||||
(cos(angle) * event.acceleration.z +
|
||||
sin(angle) * event.acceleration.x);
|
||||
angle += momentum;
|
||||
|
||||
// Scale pendulum angle into pixel space
|
||||
float midpoint = fmodf(angle * 12.0 / M_PI, 24.0);
|
||||
|
||||
// Go around the whole ring, setting each pixel based on proximity
|
||||
// (this is also to erase the prior position)...
|
||||
for (uint8_t i=0; i<24; i++) {
|
||||
float dist = fabs(midpoint - (float)i); // Pixel to pendulum distance...
|
||||
if (dist > 12.0) // If it crosses the "seam" at top,
|
||||
dist = 24.0 - dist; // take the shorter path.
|
||||
if (dist > 5.0) // Not close to pendulum,
|
||||
ring->setPixelColor(i, 0); // erase pixel.
|
||||
else if (dist < 2.0) // Close to pendulum,
|
||||
interp(i, 1.0); // solid color
|
||||
else // Anything in-between,
|
||||
interp(i, (5.0 - dist) / 3.0); // interpolate
|
||||
}
|
||||
}
|
||||
private:
|
||||
Adafruit_EyeLights_Ring_buffered *ring; // -> glasses ring
|
||||
uint8_t *color; // -> array of 3 uint8_t's [R,G,B]
|
||||
float angle; // Current position around ring
|
||||
float momentum; // Current 'oomph'
|
||||
float friction; // A scaling constant to dampen motion
|
||||
};
|
||||
|
||||
Pendulum pendulums[] = {
|
||||
Pendulum(&glasses.left_ring, (uint8_t[3]){0, 20, 50}), // Cerulean blue,
|
||||
Pendulum(&glasses.right_ring, (uint8_t[3]){0, 20, 50}), // 50 is plenty bright!
|
||||
};
|
||||
#define N_PENDULUMS (sizeof pendulums / sizeof pendulums[0])
|
||||
|
||||
// Crude error handler, prints message to Serial console, flashes LED
|
||||
void err(char *str, uint8_t hz) {
|
||||
Serial.println(str);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
|
||||
}
|
||||
|
||||
void setup() { // Runs once at program start...
|
||||
|
||||
// Initialize hardware
|
||||
Serial.begin(115200);
|
||||
if (! accel.begin()) err("LIS3DH not found", 5);
|
||||
if (! glasses.begin()) err("IS3741 not found", 2);
|
||||
|
||||
// Configure glasses for max brightness, enable output
|
||||
glasses.setLEDscaling(0xFF);
|
||||
glasses.setGlobalCurrent(0xFF);
|
||||
glasses.enable(true);
|
||||
}
|
||||
|
||||
void loop() { // Repeat forever...
|
||||
sensors_event_t event;
|
||||
accel.getEvent(&event); // Read accelerometer once
|
||||
for (uint8_t i=0; i<N_PENDULUMS; i++) { // For each pendulum...
|
||||
pendulums[i].iterate(event); // Do math with accel data
|
||||
}
|
||||
glasses.show();
|
||||
}
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics
|
||||
simulation using accelerometer and math. This uses only the rings, not the
|
||||
17
EyeLights_LED_Glasses_and_Driver/Digital_Input/code.py
Normal file
17
EyeLights_LED_Glasses_and_Driver/Digital_Input/code.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
CircuitPython Digital Input Example - Blinking an LED using a button switch.
|
||||
"""
|
||||
import board
|
||||
import digitalio
|
||||
|
||||
led = digitalio.DigitalInOut(board.LED)
|
||||
led.direction = digitalio.Direction.OUTPUT
|
||||
|
||||
button = digitalio.DigitalInOut(board.SWITCH)
|
||||
button.switch_to_input(pull=digitalio.Pull.UP)
|
||||
|
||||
while True:
|
||||
if not button.value:
|
||||
led.value = True
|
||||
else:
|
||||
led.value = False
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
AUDIO SPECTRUM LIGHT SHOW for Adafruit EyeLights (LED Glasses + Driver).
|
||||
Uses onboard microphone and a lot of math to react to music.
|
||||
"""
|
||||
|
||||
from array import array
|
||||
from math import log
|
||||
from time import monotonic
|
||||
from supervisor import reload
|
||||
import board
|
||||
from audiobusio import PDMIn
|
||||
from busio import I2C
|
||||
import adafruit_is31fl3741
|
||||
from adafruit_is31fl3741.adafruit_rgbmatrixqt import Adafruit_RGBMatrixQT
|
||||
from rainbowio import colorwheel
|
||||
from ulab import numpy as np
|
||||
from ulab.scipy.signal import spectrogram
|
||||
|
||||
|
||||
# FFT/SPECTRUM CONFIG ----
|
||||
|
||||
fft_size = 256 # Sample size for Fourier transform, MUST be power of two
|
||||
spectrum_size = fft_size // 2 # Output spectrum is 1/2 of FFT result
|
||||
# Bottom of spectrum tends to be noisy, while top often exceeds musical
|
||||
# range and is just harmonics, so clip both ends off:
|
||||
low_bin = 10 # Lowest bin of spectrum that contributes to graph
|
||||
high_bin = 75 # Highest bin "
|
||||
|
||||
|
||||
# HARDWARE SETUP ---------
|
||||
|
||||
# Manually declare I2C (not board.I2C() directly) to access 1 MHz speed...
|
||||
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
|
||||
|
||||
# Initialize the IS31 LED driver, buffered for smoother animation
|
||||
#glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
|
||||
glasses = Adafruit_RGBMatrixQT(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
|
||||
|
||||
glasses.show() # Clear any residue on startup
|
||||
glasses.set_led_scaling(0xFF)
|
||||
glasses.global_current = 5 # Not too bright please
|
||||
glasses.enable = True
|
||||
|
||||
# Initialize mic and allocate recording buffer (default rate is 16 MHz)
|
||||
mic = PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, bit_depth=16)
|
||||
rec_buf = array("H", [0] * fft_size) # 16-bit audio samples
|
||||
|
||||
|
||||
# FFT/SPECTRUM SETUP -----
|
||||
|
||||
# To keep the display lively, tables are precomputed where each column of
|
||||
# the matrix (of which there are few) is the sum value and weighting of
|
||||
# several bins from the FFT spectrum output (of which there are many).
|
||||
# The tables also help visually linearize the output so octaves are evenly
|
||||
# spaced, as on a piano keyboard, whereas the source spectrum data is
|
||||
# spaced by frequency in Hz.
|
||||
column_table = []
|
||||
|
||||
spectrum_bits = log(spectrum_size, 2) # e.g. 7 for 128-bin spectrum
|
||||
# Scale low_bin and high_bin to 0.0 to 1.0 equivalent range in spectrum
|
||||
low_frac = log(low_bin, 2) / spectrum_bits
|
||||
frac_range = log(high_bin, 2) / spectrum_bits - low_frac
|
||||
|
||||
for column in range(glasses.width):
|
||||
# Determine the lower and upper frequency range for this column, as
|
||||
# fractions within the scaled 0.0 to 1.0 spectrum range. 0.95 below
|
||||
# creates slight frequency overlap between columns, looks nicer.
|
||||
lower = low_frac + frac_range * (column / glasses.width * 0.95)
|
||||
upper = low_frac + frac_range * ((column + 1) / glasses.width)
|
||||
mid = (lower + upper) * 0.5 # Center of lower-to-upper range
|
||||
half_width = (upper - lower) * 0.5 # 1/2 of lower-to-upper range
|
||||
# Map fractions back to spectrum bin indices that contribute to column
|
||||
first_bin = int(2 ** (spectrum_bits * lower) + 1e-4)
|
||||
last_bin = int(2 ** (spectrum_bits * upper) + 1e-4)
|
||||
bin_weights = [] # Each spectrum bin's weighting will be added here
|
||||
for bin_index in range(first_bin, last_bin + 1):
|
||||
# Find distance from column's overall center to individual bin's
|
||||
# center, expressed as 0.0 (bin at center) to 1.0 (bin at limit of
|
||||
# lower-to-upper range).
|
||||
bin_center = log(bin_index + 0.5, 2) / spectrum_bits
|
||||
dist = abs(bin_center - mid) / half_width
|
||||
if dist < 1.0: # Filter out a few math stragglers at either end
|
||||
# Bin weights have a cubic falloff curve within range:
|
||||
dist = 1.0 - dist # Invert dist so 1.0 is at center
|
||||
bin_weights.append(((3.0 - (dist * 2.0)) * dist) * dist)
|
||||
# Scale bin weights so total is 1.0 for each column, but then mute
|
||||
# lower columns slightly and boost higher columns. It graphs better.
|
||||
total = sum(bin_weights)
|
||||
bin_weights = [
|
||||
(weight / total) * (0.8 + idx / glasses.width * 1.4)
|
||||
for idx, weight in enumerate(bin_weights)
|
||||
]
|
||||
# List w/five elements is stored for each column:
|
||||
# 0: Index of the first spectrum bin that impacts this column.
|
||||
# 1: A list of bin weights, starting from index above, length varies.
|
||||
# 2: Color for drawing this column on the LED matrix. The 225 is on
|
||||
# purpose, providing hues from red to purple, leaving out magenta.
|
||||
# 3: Current height of the 'falling dot', updated each frame
|
||||
# 4: Current velocity of the 'falling dot', updated each frame
|
||||
column_table.append(
|
||||
[
|
||||
first_bin - low_bin,
|
||||
bin_weights,
|
||||
colorwheel(225 * column / glasses.width),
|
||||
glasses.height,
|
||||
0.0,
|
||||
]
|
||||
)
|
||||
# print(column_table)
|
||||
|
||||
|
||||
# MAIN LOOP -------------
|
||||
|
||||
dynamic_level = 10 # For responding to changing volume levels
|
||||
frames, start_time = 0, monotonic() # For frames-per-second calc
|
||||
|
||||
while True:
|
||||
# The try/except here is because VERY INFREQUENTLY the I2C bus will
|
||||
# encounter an error when accessing the LED driver, whether from bumping
|
||||
# around the wires or sometimes an I2C device just gets wedged. To more
|
||||
# robustly handle the latter, the code will restart if that happens.
|
||||
try:
|
||||
mic.record(rec_buf, fft_size) # Record batch of 16-bit samples
|
||||
samples = np.array(rec_buf) # Convert to ndarray
|
||||
# Compute spectrogram and trim results. Only the left half is
|
||||
# normally needed (right half is mirrored), but we trim further as
|
||||
# only the low_bin to high_bin elements are interesting to graph.
|
||||
spectrum = spectrogram(samples)[low_bin : high_bin + 1]
|
||||
# Linearize spectrum output. spectrogram() is always nonnegative,
|
||||
# but add a tiny value to change any zeros to nonzero numbers
|
||||
# (avoids rare 'inf' error)
|
||||
spectrum = np.log(spectrum + 1e-7)
|
||||
# Determine minimum & maximum across all spectrum bins, with limits
|
||||
lower = max(np.min(spectrum), 4)
|
||||
upper = min(max(np.max(spectrum), lower + 6), 20)
|
||||
|
||||
# Adjust dynamic level to current spectrum output, keeps the graph
|
||||
# 'lively' as ambient volume changes. Sparkle but don't saturate.
|
||||
if upper > dynamic_level:
|
||||
# Got louder. Move level up quickly but allow initial "bump."
|
||||
dynamic_level = upper * 0.7 + dynamic_level * 0.3
|
||||
else:
|
||||
# Got quieter. Ease level down, else too many bumps.
|
||||
dynamic_level = dynamic_level * 0.5 + lower * 0.5
|
||||
|
||||
# Apply vertical scale to spectrum data. Results may exceed
|
||||
# matrix height...that's OK, adds impact!
|
||||
#data = (spectrum - lower) * (7 / (dynamic_level - lower))
|
||||
data = (spectrum - lower) * ((glasses.height + 2) / (dynamic_level - lower))
|
||||
|
||||
for column, element in enumerate(column_table):
|
||||
# Start BELOW matrix and accumulate bin weights UP, saves math
|
||||
first_bin = element[0]
|
||||
column_top = glasses.height + 1
|
||||
for bin_offset, weight in enumerate(element[1]):
|
||||
column_top -= data[first_bin + bin_offset] * weight
|
||||
|
||||
if column_top < element[3]: # Above current falling dot?
|
||||
element[3] = column_top - 0.5 # Move dot up
|
||||
element[4] = 0 # and clear out velocity
|
||||
else:
|
||||
element[3] += element[4] # Move dot down
|
||||
element[4] += 0.2 # and accelerate
|
||||
|
||||
column_top = int(column_top) # Quantize to pixel space
|
||||
for row in range(column_top): # Erase area above column
|
||||
glasses.pixel(column, row, 0)
|
||||
for row in range(column_top, glasses.height): # Draw column
|
||||
glasses.pixel(column, row, element[2])
|
||||
glasses.pixel(column, int(element[3]), 0xE08080) # Draw peak dot
|
||||
|
||||
glasses.show() # Buffered mode MUST use show() to refresh matrix
|
||||
|
||||
frames += 1
|
||||
# print(frames / (monotonic() - start_time), "FPS")
|
||||
|
||||
except OSError: # See "try" notes above regarding rare I2C errors.
|
||||
print("Restarting")
|
||||
reload()
|
||||
|
|
@ -22,7 +22,7 @@ LSM303 compass;
|
|||
|
||||
#define LAT_LON_SIZE 311
|
||||
|
||||
float lat_lon[LAT_LON_SIZE][2] PROGMEM = {
|
||||
const float lat_lon[LAT_LON_SIZE][2] PROGMEM = {
|
||||
{40.767272, -73.993928},
|
||||
{40.719115, -74.006666},
|
||||
{40.711174, -74.000165},
|
||||
|
|
@ -475,7 +475,7 @@ void loop() // run over and over again
|
|||
//Serial.print("Altitude: "); Serial.println(GPS.altitude);
|
||||
//Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
|
||||
compass.read();
|
||||
int heading = compass.heading((LSM303::vector){0,-1,0});
|
||||
int heading = compass.heading((LSM303::vector<int16_t>){0,-1,0});
|
||||
Serial.print("Heading: ");
|
||||
Serial.println(heading);
|
||||
if ((calc_bearing(fLat, fLon, targetLat, targetLon) - heading) > 0) {
|
||||
|
|
@ -639,8 +639,8 @@ unsigned long calc_dist(float flat1, float flon1, float flat2, float flon2)
|
|||
int find_closest_location(float current_lat, float current_lon)
|
||||
{
|
||||
int closest = 0;
|
||||
unsigned long minDistance = -1;
|
||||
unsigned long tempDistance;
|
||||
signed long minDistance = -1;
|
||||
signed long tempDistance;
|
||||
for (int i=0; i < LAT_LON_SIZE; i++) {
|
||||
float target_lat = pgm_read_float(&lat_lon[i][0]);
|
||||
float target_lon = pgm_read_float(&lat_lon[i][1]);
|
||||
|
|
@ -729,7 +729,7 @@ void GoForward (uint32_t c, uint8_t wait) {
|
|||
|
||||
// Slightly different, this makes the rainbow equally distributed throughout
|
||||
void rainbowCycle(uint8_t wait) {
|
||||
uint16_t i, j;
|
||||
uint16_t i;
|
||||
|
||||
//for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
|
||||
for(i=0; i< 10; i++) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import time
|
|||
import busio
|
||||
import board
|
||||
import pulseio
|
||||
import pwmio
|
||||
import adafruit_irremote
|
||||
import adafruit_lis3dh
|
||||
|
||||
|
|
@ -26,9 +25,8 @@ TRANSMIT_DELAY = 0.1
|
|||
i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
|
||||
sensor = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)
|
||||
|
||||
# Create a 'pwmio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pwm = pwmio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
pulseout = pulseio.PulseOut(pwm)
|
||||
# Create a 'pulseio' output, to send infrared signals on the IR transmitter @ 38KHz
|
||||
pulseout = pulseio.PulseOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
|
||||
|
||||
# Create an encoder that will take numbers and turn them into IR pulses
|
||||
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500],
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ void setup() {
|
|||
// in which they are initialized. HID MUST be initialized before
|
||||
// Serial, which must be initialized before the display.
|
||||
|
||||
int status = arcada.begin(); // Save status for Serial print later
|
||||
int status = arcada.arcadaBegin(); // Save status for Serial print later
|
||||
|
||||
// HID (keyboard) initialization
|
||||
usb_hid.setPollInterval(2);
|
||||
|
|
@ -157,7 +157,7 @@ void setup() {
|
|||
// Display initialization. This is all part of the persnickety sequence!
|
||||
arcada.displayBegin();
|
||||
arcada.setBacklight(255);
|
||||
arcada.fillScreen(ARCADA_BLACK);
|
||||
arcada.display->fillScreen(ARCADA_BLACK);
|
||||
|
||||
// Audio initialization
|
||||
AudioMemory(10);
|
||||
|
|
@ -192,10 +192,10 @@ void setup() {
|
|||
}
|
||||
|
||||
// At start, draw the entire blank/neutral face centered on screen
|
||||
arcada.fillScreen(ARCADA_BLACK); // Erase any warnBoxes first
|
||||
arcada.drawRGBBitmap(
|
||||
(arcada.width() - FACE_WIDTH ) / 2,
|
||||
(arcada.height() - FACE_HEIGHT) / 2,
|
||||
arcada.display->fillScreen(ARCADA_BLACK); // Erase any warnBoxes first
|
||||
arcada.display->drawRGBBitmap(
|
||||
(arcada.display->width() - FACE_WIDTH ) / 2,
|
||||
(arcada.display->height() - FACE_HEIGHT) / 2,
|
||||
(uint16_t *)face, FACE_WIDTH, FACE_HEIGHT);
|
||||
|
||||
// Create an offscreen framebuffer that's just the bounding
|
||||
|
|
@ -426,7 +426,7 @@ void loop() {
|
|||
// while a screen update is currently in progress. (This is assuming
|
||||
// SPI DMA is enabled in Adafruit_SPITFT.h. If it is not, that's OK,
|
||||
// this function call simply compiles to nothing in that case.)
|
||||
arcada.dmaWait();
|
||||
arcada.display->dmaWait();
|
||||
|
||||
if(openRows) {
|
||||
// Draw the open section of the eyes, then draw the pupils on top
|
||||
|
|
@ -484,7 +484,7 @@ void loop() {
|
|||
// is used...this is fastest as it can continue in the background
|
||||
// (while we process input on the next frame).
|
||||
arcada.blitFrameBuffer(
|
||||
(arcada.width() - FACE_WIDTH ) / 2 + 13,
|
||||
(arcada.height() - FACE_HEIGHT) / 2 + 25,
|
||||
(arcada.display->width() - FACE_WIDTH ) / 2 + 13,
|
||||
(arcada.display->height() - FACE_HEIGHT) / 2 + 25,
|
||||
false, true); // Non-blocking, big-endian
|
||||
}
|
||||
|
|
|
|||
0
MIDI_FeatherWing/.feather_m4_express_tinyusb.test.only
Normal file
0
MIDI_FeatherWing/.feather_m4_express_tinyusb.test.only
Normal file
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])
|
||||
|
|
@ -90,25 +90,28 @@ void setup() {
|
|||
// initialize
|
||||
|
||||
Serial.println("Hello! Arcada version of game");
|
||||
if (!arcada.begin()) {
|
||||
if (!arcada.arcadaBegin()) {
|
||||
Serial.print("Failed to begin");
|
||||
strip.fill(RED);
|
||||
strip.show();
|
||||
while (1);
|
||||
}
|
||||
|
||||
arcada.displayBegin(); // Initialize display code
|
||||
arcada.setBacklight(0); // Initial display off
|
||||
arcada.display->setRotation(0); // Rotate to portrait
|
||||
arcada.display->fillScreen(background); // Clear screen if necessary
|
||||
arcada.display->setTextColor(ARCADA_GREEN, ARCADA_BLACK);
|
||||
arcada.display->setTextSize(2);
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" Bounce!");
|
||||
|
||||
if( !arcada.hasAccel() ) {
|
||||
strip.fill(YELLOW);
|
||||
strip.show();
|
||||
arcada.haltBox("An accelerometer is required for this gamne");
|
||||
}
|
||||
arcada.displayBegin(); // Initialize display code
|
||||
arcada.setBacklight(0); // Initial display off
|
||||
arcada.setRotation(0); // Rotate to portrait
|
||||
arcada.fillScreen(background); // Clear screen if necessary
|
||||
arcada.setTextColor(ARCADA_GREEN, ARCADA_BLACK);
|
||||
arcada.setTextSize(2);
|
||||
arcada.println(" ");
|
||||
arcada.println(" Bounce!");
|
||||
|
||||
// Set up the logo bitmap
|
||||
int logo_origin_x = (128 - 2*LOGO_WIDTH ) / 2;
|
||||
int logo_origin_y = (160 - 2*LOGO_HEIGHT) / 2;
|
||||
|
|
@ -118,34 +121,34 @@ void setup() {
|
|||
if(c & (0x80 >> (x & 7))) {
|
||||
int xx = logo_origin_x+2*x;
|
||||
int yy = logo_origin_y+2*y;
|
||||
arcada.drawPixel(xx, yy, ARCADA_WHITE);
|
||||
arcada.drawPixel(xx+1, yy, ARCADA_WHITE);
|
||||
arcada.drawPixel(xx, yy+1, ARCADA_WHITE);
|
||||
arcada.drawPixel(xx+1, yy+1, ARCADA_WHITE);
|
||||
arcada.display->drawPixel(xx, yy, ARCADA_WHITE);
|
||||
arcada.display->drawPixel(xx+1, yy, ARCADA_WHITE);
|
||||
arcada.display->drawPixel(xx, yy+1, ARCADA_WHITE);
|
||||
arcada.display->drawPixel(xx+1, yy+1, ARCADA_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
arcada.println(" ");
|
||||
arcada.println(" ");
|
||||
arcada.println(" ");
|
||||
arcada.println(" ");
|
||||
arcada.println(" ");
|
||||
arcada.setTextColor(ARCADA_ORANGE, ARCADA_BLACK);
|
||||
arcada.setTextSize(1);
|
||||
arcada.println(" ");
|
||||
arcada.println(" Adafruit");
|
||||
arcada.println(" Industries");
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" ");
|
||||
arcada.display->setTextColor(ARCADA_ORANGE, ARCADA_BLACK);
|
||||
arcada.display->setTextSize(1);
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" Adafruit");
|
||||
arcada.display->println(" Industries");
|
||||
for (int i=0; i<220; i++) { // Display initial text
|
||||
arcada.setBacklight(i);
|
||||
delay(14);
|
||||
}
|
||||
arcada.setBacklight(250);
|
||||
arcada.setTextColor(ST7735_WHITE, ST7735_BLACK);
|
||||
arcada.println(" ");
|
||||
arcada.println(" ");
|
||||
arcada.println(" Press Start");
|
||||
arcada.display->setTextColor(ST7735_WHITE, ST7735_BLACK);
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" ");
|
||||
arcada.display->println(" Press Start");
|
||||
while(!(arcada.readButtons() & ARCADA_BUTTONMASK_START)) ; // wait for start button
|
||||
arcada.fillScreen(background); // Clear screen
|
||||
arcada.display->fillScreen(background); // Clear screen
|
||||
|
||||
arcada.enableSpeaker(1); // Enable the speaker and play opening tones
|
||||
pinMode(sound_pin, OUTPUT);
|
||||
|
|
@ -187,7 +190,7 @@ void loop() {
|
|||
|
||||
int acc_avg(int pin) {
|
||||
int avg = 0;
|
||||
arcada.accel.getEvent(&event);
|
||||
arcada.accel->getEvent(&event);
|
||||
switch (pin) {
|
||||
case acc_pinX:
|
||||
for(int i = 0; i < 50; i++) {
|
||||
|
|
@ -214,7 +217,7 @@ int acc_avg(int pin) {
|
|||
|
||||
int acc_readX() {
|
||||
int val;
|
||||
arcada.accel.getEvent(&event);
|
||||
arcada.accel->getEvent(&event);
|
||||
val = -1 * constrain((event.acceleration.y - acc_avgY)*7, -64, 64); // use Y for orientation
|
||||
// Serial.print("Acceleration: "); // joystick is at top
|
||||
// Serial.println(val);
|
||||
|
|
@ -241,8 +244,8 @@ void BonusReset(){
|
|||
nextBonus = random(30, 60);
|
||||
b_pendingPlatform = false;
|
||||
b_remaining = 0;
|
||||
arcada.fillRect(0, s_height+2, 96, 10, background);
|
||||
arcada.fillRect(0, s_height+1, 96, 1, w_color);
|
||||
arcada.display->fillRect(0, s_height+2, 96, 10, background);
|
||||
arcada.display->fillRect(0, s_height+1, 96, 1, w_color);
|
||||
EndUseBonus();
|
||||
}
|
||||
|
||||
|
|
@ -250,8 +253,8 @@ void ClearBonus(){
|
|||
EndUseBonus();
|
||||
b_remaining = 0;
|
||||
p_color = p_color_default;
|
||||
arcada.fillRect(0, s_height+2, 96, 10, background);
|
||||
arcada.fillRect(0, s_height+1, s_width-1, 1, w_color);
|
||||
arcada.display->fillRect(0, s_height+2, 96, 10, background);
|
||||
arcada.display->fillRect(0, s_height+1, s_width-1, 1, w_color);
|
||||
b_used = false;
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +264,7 @@ void GetBonus(){
|
|||
b_pendingPlatform = false;
|
||||
beep(sound_pin, 3500, 50); // SOUND
|
||||
p_color = b_colors[b_onUseID];
|
||||
arcada.fillRect(0, s_height+2, 96, 10, background);
|
||||
arcada.display->fillRect(0, s_height+2, 96, 10, background);
|
||||
|
||||
switch (b_onUseID){
|
||||
case 0:
|
||||
|
|
@ -286,19 +289,19 @@ void GetBonus(){
|
|||
break;
|
||||
}
|
||||
if(b_remaining > 1) {
|
||||
arcada.fillRect(95, s_height+2, 1, 10, b_colors[b_onUseID]);
|
||||
arcada.fillRect( 0, s_height+1, 95, 1, b_colors[b_onUseID]);
|
||||
arcada.display->fillRect(95, s_height+2, 1, 10, b_colors[b_onUseID]);
|
||||
arcada.display->fillRect( 0, s_height+1, 95, 1, b_colors[b_onUseID]);
|
||||
}
|
||||
}
|
||||
|
||||
void UseBonus(){
|
||||
if(b_remaining){
|
||||
b_used = true;
|
||||
arcada.fillRect(b_remaining*95/b_max, s_height+2, 1, 10, background);
|
||||
arcada.drawPixel(b_remaining*95/b_max, s_height+1, w_color);
|
||||
arcada.display->fillRect(b_remaining*95/b_max, s_height+2, 1, 10, background);
|
||||
arcada.display->drawPixel(b_remaining*95/b_max, s_height+1, w_color);
|
||||
b_remaining --;
|
||||
arcada.fillRect(b_remaining*95/b_max, s_height+2, 1, 10, b_colors[b_onUseID]);
|
||||
arcada.drawPixel(b_remaining*95/b_max, s_height+1, b_colors[b_onUseID]);
|
||||
arcada.display->fillRect(b_remaining*95/b_max, s_height+2, 1, 10, b_colors[b_onUseID]);
|
||||
arcada.display->drawPixel(b_remaining*95/b_max, s_height+1, b_colors[b_onUseID]);
|
||||
|
||||
switch (b_onUseID){
|
||||
case 0:
|
||||
|
|
@ -378,20 +381,20 @@ void CheckButtons(){
|
|||
drawChar(s_width-6, textline2, speaker_icon, ARCADA_RED); // Put character in lower right corner to indicate speaker off
|
||||
} else {
|
||||
sound_on = 1;
|
||||
arcada.fillRect( s_width-6, textline2, s_width-1, textline2+9, background); // Blank lower right character = sound on
|
||||
arcada.display->fillRect( s_width-6, textline2, s_width-1, textline2+9, background); // Blank lower right character = sound on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawPlayer(){
|
||||
CheckButtons(); // check more frequently
|
||||
arcada.fillRect(p_lastX, p_lastY, p_width, p_height, background); // erase previous pos
|
||||
arcada.display->fillRect(p_lastX, p_lastY, p_width, p_height, background); // erase previous pos
|
||||
if(p_lastX > (s_width-1 - p_width)) // if across the edge of the screen
|
||||
arcada.fillRect(0, p_lastY, p_lastX + p_width - s_width, p_height, background);
|
||||
arcada.display->fillRect(0, p_lastY, p_lastX + p_width - s_width, p_height, background);
|
||||
|
||||
arcada.fillRect(p_X, p_Y, p_width, p_height, p_color); // draw new pos
|
||||
arcada.display->fillRect(p_X, p_Y, p_width, p_height, p_color); // draw new pos
|
||||
if(p_X > (s_width-1 - p_width)) // if across the edge of the screen
|
||||
arcada.fillRect(0, p_Y, p_X + p_width - s_width, p_height, p_color);
|
||||
arcada.display->fillRect(0, p_Y, p_X + p_width - s_width, p_height, p_color);
|
||||
}
|
||||
|
||||
void MovePlayer(){
|
||||
|
|
@ -429,7 +432,7 @@ void CollideBorders() {
|
|||
}
|
||||
}
|
||||
else {
|
||||
arcada.fillRect(p_lastX, p_lastY, p_width, p_height, background);
|
||||
arcada.display->fillRect(p_lastX, p_lastY, p_width, p_height, background);
|
||||
p_width--;
|
||||
p_height--;
|
||||
b_lives--;
|
||||
|
|
@ -439,8 +442,8 @@ void CollideBorders() {
|
|||
}
|
||||
|
||||
void ScoreSetup(){
|
||||
arcada.fillRect(0, s_height+2, 128, 10, background);
|
||||
arcada.fillRect(0, s_height+1, 128, 1, w_color);
|
||||
arcada.display->fillRect(0, s_height+2, 128, 10, background);
|
||||
arcada.display->fillRect(0, s_height+1, 128, 1, w_color);
|
||||
// highscore = EEPROM.read(0);
|
||||
// highscore += (EEPROM.read(1)<<8);
|
||||
if(highscore > 64000){
|
||||
|
|
@ -478,11 +481,11 @@ void ScoreReset(){
|
|||
|
||||
void flashMessage() {
|
||||
uint8_t i;
|
||||
arcada.fillRect( 0, textline2, s_width-10, textline2+9, background);
|
||||
arcada.display->fillRect( 0, textline2, s_width-10, textline2+9, background);
|
||||
for(i=0; i<5; i++) {
|
||||
drawString(1, textline2, "HIGHSCORE!", p_color_default, 1);
|
||||
delay(400);
|
||||
arcada.fillRect( 0, textline2, s_width-10, textline2+9, background);
|
||||
arcada.display->fillRect( 0, textline2, s_width-10, textline2+9, background);
|
||||
delay(400);
|
||||
}
|
||||
drawString(1, textline2, "High score:", w_color, 1);
|
||||
|
|
@ -507,7 +510,7 @@ void ScoreAdd(){ // add to the current score lower left
|
|||
|
||||
// transform from int to string and display it
|
||||
void drawInt(unsigned int num, byte nx, byte ny, unsigned int color, unsigned int color2) {
|
||||
arcada.fillRect(nx, ny, 29, 7, color2);
|
||||
arcada.display->fillRect(nx, ny, 29, 7, color2);
|
||||
drawChar(nx+24, ny, 48+(num%10), color);
|
||||
|
||||
if(num > 9) {
|
||||
|
|
@ -593,26 +596,26 @@ void CollideWorld() {
|
|||
|
||||
void DrawWorld() {
|
||||
for(byte i=0; i<w_size; i++) {
|
||||
arcada.fillRect(world[i][2], world[i][3], world[i][5], 1, background);
|
||||
arcada.fillRect(world[i][0], world[i][1], world[i][4], 1, w_color);
|
||||
arcada.display->fillRect(world[i][2], world[i][3], world[i][5], 1, background);
|
||||
arcada.display->fillRect(world[i][0], world[i][1], world[i][4], 1, w_color);
|
||||
}
|
||||
if(b_pendingPlatform){
|
||||
arcada.fillRect(world[b_platformID][0], world[b_platformID][1], world[b_platformID][4], 1, b_colors[b_pendingID]);
|
||||
arcada.display->fillRect(world[b_platformID][0], world[b_platformID][1], world[b_platformID][4], 1, b_colors[b_pendingID]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void drawString(byte x, byte y, char *text, uint16_t color, bool wrap) { // replicate tft.drawString
|
||||
arcada.setCursor(x,y);
|
||||
arcada.setTextColor(color);
|
||||
arcada.setTextWrap(wrap);
|
||||
arcada.print(text);
|
||||
arcada.display->setCursor(x,y);
|
||||
arcada.display->setTextColor(color);
|
||||
arcada.display->setTextWrap(wrap);
|
||||
arcada.display->print(text);
|
||||
}
|
||||
|
||||
void drawChar(byte x, byte y, char text, uint16_t color) { // replicate tft.drawChar
|
||||
arcada.setCursor(x,y);
|
||||
arcada.setTextColor(color);
|
||||
arcada.print(text);
|
||||
arcada.display->setCursor(x,y);
|
||||
arcada.display->setTextColor(color);
|
||||
arcada.display->print(text);
|
||||
Serial.println(text);
|
||||
}
|
||||
|
||||
|
|
|
|||
0
USB_SNES_Gamepad/teensySNES_Portal/.none.test.only
Normal file
0
USB_SNES_Gamepad/teensySNES_Portal/.none.test.only
Normal file
0
USB_SNES_Gamepad/teensySNES_onebutton/.none.test.only
Normal file
0
USB_SNES_Gamepad/teensySNES_onebutton/.none.test.only
Normal file
0
USB_SNES_Gamepad/teensySNES_stellakey/.none.test.only
Normal file
0
USB_SNES_Gamepad/teensySNES_stellakey/.none.test.only
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue