Replace with build derived from inky-frame.
176
.github/workflows/micropython.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: MicroPython Firmware
|
||||
name: MicroPython
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -6,149 +6,93 @@ on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
MICROPYTHON_VERSION: v1.20.0
|
||||
PIMORONI_PICO_VERSION: 302d6ae0ebccbec8b676a5eb5e1ec9b273f4eecd
|
||||
|
||||
jobs:
|
||||
deps:
|
||||
runs-on: ubuntu-20.04
|
||||
name: Dependencies
|
||||
steps:
|
||||
- name: Workspace Cache
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{runner.workspace}}
|
||||
key: workspace-micropython-${{env.MICROPYTHON_VERSION}}
|
||||
restore-keys: |
|
||||
workspace-micropython-${{env.MICROPYTHON_VERSION}}
|
||||
|
||||
# Check out MicroPython
|
||||
- name: Checkout MicroPython
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: micropython/micropython
|
||||
ref: ${{env.MICROPYTHON_VERSION}}
|
||||
submodules: false # MicroPython submodules are hideously broken
|
||||
path: micropython
|
||||
|
||||
- name: Fetch base MicroPython submodules
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
working-directory: micropython
|
||||
run: git submodule update --init
|
||||
|
||||
- name: Fetch Pico SDK submodules
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
working-directory: micropython/lib/pico-sdk
|
||||
run: git submodule update --init
|
||||
|
||||
- name: Build mpy-cross
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
working-directory: micropython/mpy-cross
|
||||
run: make
|
||||
|
||||
build:
|
||||
needs: deps
|
||||
name: Build ${{matrix.name}}
|
||||
runs-on: ubuntu-20.04
|
||||
name: MicroPython ${{ matrix.name }}
|
||||
runs-on: ubuntu-24.04
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: Cosmic Unicorn
|
||||
shortname: cosmic_unicorn
|
||||
board: PIMORONI_COSMIC_UNICORN
|
||||
- name: Galactic Unicorn
|
||||
shortname: galactic_unicorn
|
||||
board: PIMORONI_GALACTIC_UNICORN
|
||||
- name: pico_w_unicorn
|
||||
- name: pico2_w_unicorn
|
||||
|
||||
env:
|
||||
RELEASE_FILE: pimoroni-${{matrix.shortname}}-${{github.event.release.tag_name || github.sha}}-micropython.uf2
|
||||
FIRMWARE_DIR: "$GITHUB_WORKSPACE/unicorn/firmware"
|
||||
BOARD_DIR: "$GITHUB_WORKSPACE/unicorn/firmware/${{matrix.board}}"
|
||||
# MicroPython version will be contained in github.event.release.tag_name for releases
|
||||
RELEASE_FILE: ${{ matrix.name }}-${{ github.event.release.tag_name || github.sha }}-micropython
|
||||
CI_PROJECT_ROOT: ${{ github.workspace }}/src-${{ github.sha }}
|
||||
CI_BUILD_ROOT: ${{ github.workspace }}
|
||||
|
||||
steps:
|
||||
- name: Compiler Cache
|
||||
uses: actions/cache@v3
|
||||
- name: Compiler Cache Fixup
|
||||
run: |
|
||||
mkdir -p /home/runner/.ccache
|
||||
|
||||
- name: "CCache: Restore saved cache"
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /home/runner/.ccache
|
||||
key: ccache-micropython-${{matrix.shortname}}-${{github.ref}}-${{github.sha}}
|
||||
key: ccache-micropython-${{ matrix.name }}-${{ github.ref }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-micropython-${{matrix.shortname}}-${{github.ref}}
|
||||
ccache-micropython-${{matrix.shortname}}-
|
||||
ccache-micropython-${{ matrix.name }}-${{ github.ref }}
|
||||
ccache-micropython-${{ matrix.name }}-
|
||||
|
||||
- name: Workspace Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{runner.workspace}}
|
||||
key: workspace-micropython-${{env.MICROPYTHON_VERSION}}
|
||||
restore-keys: |
|
||||
workspace-micropython-${{env.MICROPYTHON_VERSION}}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Checkout Project"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
path: unicorn
|
||||
path: ${{ env.CI_PROJECT_ROOT }}
|
||||
|
||||
# Check out Pimoroni Pico
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Arm GNU Toolchain (arm-none-eabi-gcc)"
|
||||
uses: carlosperate/arm-none-eabi-gcc-action@v1
|
||||
with:
|
||||
repository: pimoroni/pimoroni-pico
|
||||
ref: ${{env.PIMORONI_PICO_VERSION}}
|
||||
submodules: true
|
||||
path: pimoroni-pico
|
||||
release: '13.3.Rel1'
|
||||
|
||||
# HACK: Patch startup overclock into Pico SDK
|
||||
- name: "HACK: Startup Overclock Patch"
|
||||
- name: "Prepare tools & dependencies"
|
||||
shell: bash
|
||||
working-directory: micropython/lib/pico-sdk
|
||||
run: |
|
||||
git apply "${{env.FIRMWARE_DIR}}/startup_overclock.patch"
|
||||
source $CI_PROJECT_ROOT/ci/micropython.sh && ci_debug
|
||||
mkdir -p $CI_BUILD_ROOT
|
||||
ci_apt_install_build_deps
|
||||
ci_prepare_all
|
||||
|
||||
# Install apt packages
|
||||
- name: Install CCache & Compiler
|
||||
- name: "MicroPython: Configure"
|
||||
shell: bash
|
||||
run:
|
||||
sudo apt update && sudo apt install ccache gcc-arm-none-eabi
|
||||
|
||||
# Build firmware
|
||||
- name: Configure MicroPython
|
||||
shell: bash
|
||||
working-directory: micropython/ports/rp2
|
||||
run: |
|
||||
cmake -S . -B build-${{matrix.shortname}} -DPICO_BUILD_DOCS=0 -DUSER_C_MODULES=${{env.BOARD_DIR}}/micropython.cmake -DMICROPY_BOARD_DIR=${{env.BOARD_DIR}} -DMICROPY_BOARD=${{env.BOARD}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
source $CI_PROJECT_ROOT/ci/micropython.sh && ci_debug
|
||||
micropython_version
|
||||
ci_cmake_configure ${{ matrix.name }}
|
||||
|
||||
- name: Build MicroPython
|
||||
- name: "MicroPython: Build"
|
||||
shell: bash
|
||||
working-directory: micropython/ports/rp2
|
||||
run: |
|
||||
ccache --zero-stats || true
|
||||
cmake --build build-${{matrix.shortname}} -j 1
|
||||
ccache --show-stats || true
|
||||
source $CI_PROJECT_ROOT/ci/micropython.sh && ci_debug
|
||||
python3 -m venv "$CI_BUILD_ROOT/.dir2uf2"
|
||||
source "$CI_BUILD_ROOT/.dir2uf2/bin/activate"
|
||||
ci_cmake_build ${{ matrix.name }}
|
||||
mv "$CI_BUILD_ROOT/${{ matrix.name }}.uf2" "$CI_BUILD_ROOT/$RELEASE_FILE.uf2"
|
||||
mv "$CI_BUILD_ROOT/${{ matrix.name }}-with-filesystem.uf2" "$CI_BUILD_ROOT/$RELEASE_FILE-with-filesystem.uf2"
|
||||
|
||||
- name: Rename .uf2 for artifact
|
||||
shell: bash
|
||||
working-directory: micropython/ports/rp2/build-${{matrix.shortname}}
|
||||
run: |
|
||||
cp firmware.uf2 ${{env.RELEASE_FILE}}
|
||||
|
||||
- name: Store .uf2 as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: "Artifacts: Upload .uf2"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{env.RELEASE_FILE}}
|
||||
path: micropython/ports/rp2/build-${{matrix.shortname}}/${{env.RELEASE_FILE}}
|
||||
name: ${{ env.RELEASE_FILE }}.uf2
|
||||
path: ${{ env.CI_BUILD_ROOT }}/${{ env.RELEASE_FILE }}.uf2
|
||||
|
||||
- name: "Artifacts: Upload .uf2 (With Filesystem)"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FILE }}-with-filesystem.uf2
|
||||
path: ${{ env.CI_BUILD_ROOT }}/${{ env.RELEASE_FILE }}-with-filesystem.uf2
|
||||
|
||||
- name: Upload .uf2
|
||||
- name: "Release: Upload .uf2"
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
asset_path: micropython/ports/rp2/build-${{matrix.shortname}}/${{env.RELEASE_FILE}}
|
||||
upload_url: ${{github.event.release.upload_url}}
|
||||
asset_name: ${{env.RELEASE_FILE}}
|
||||
asset_content_type: application/octet-stream
|
||||
files: ${{ env.CI_BUILD_ROOT }}/${{ env.RELEASE_FILE }}.uf2
|
||||
|
||||
- name: "Release: Upload .uf2 (With Filesystem)"
|
||||
if: github.event_name == 'release'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ${{ env.CI_BUILD_ROOT }}/${{ env.RELEASE_FILE }}-with-filesystem.uf2
|
||||
|
||||
21
LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Pimoroni Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
22
README.md
@ -1 +1,21 @@
|
||||
# Cosmic Unicorn & Galactic Unicorn
|
||||
# Pico-powered Unicorn
|
||||
## Galactic, Cosmic, Stellar!
|
||||
|
||||
This repository is home to the MicroPython firmware and examples for
|
||||
Galactic, Cosmic and Stellar Unicorn - our Pico-powered LED matrix boards.
|
||||
|
||||
## Download Firmware
|
||||
|
||||
There are two choices, a regular build that just updates the firmware on
|
||||
your board and a "-with-examples" build which includes a ready-to-go set
|
||||
of examples (everything in [examples/launcher](examples/launcher))
|
||||
that you can interact with right on your display.
|
||||
|
||||
:warning: If you've changed any of the code on your board then back up before
|
||||
flashing "-with-examples" - *your files will be erased!*
|
||||
|
||||
You can find the latest release at [https://github.com/pimoroni/unicorn/releases/latest](https://github.com/pimoroni/unicorn/releases/latest).
|
||||
|
||||
## Guides & Documentation
|
||||
|
||||
## Get Unicorn
|
||||
14
boards/manifest-common.py
Normal file
@ -0,0 +1,14 @@
|
||||
# https://github.com/micropython/micropython-lib/blob/master/micropython/bundles/bundle-networking/manifest.py
|
||||
require("bundle-networking")
|
||||
require("urllib.urequest")
|
||||
require("umqtt.simple")
|
||||
|
||||
# SD Card
|
||||
require("sdcard")
|
||||
|
||||
# Bluetooth
|
||||
require("aioble")
|
||||
|
||||
freeze("../../pimoroni-pico/micropython/modules_py", "pimoroni.py")
|
||||
freeze("../../pimoroni-pico/micropython/modules_py", "boot.py")
|
||||
freeze("../../pimoroni-pico/micropython/modules_py", "lte.py")
|
||||
2
boards/pico2_w_unicorn/manifest.py
Normal file
@ -0,0 +1,2 @@
|
||||
include("$(PORT_DIR)/boards/manifest.py")
|
||||
include("../manifest-common.py")
|
||||
3
boards/pico2_w_unicorn/manifest.txt
Normal file
@ -0,0 +1,3 @@
|
||||
*.py
|
||||
lib/*
|
||||
lib/*/*
|
||||
17
boards/pico2_w_unicorn/mpconfigboard.cmake
Normal file
@ -0,0 +1,17 @@
|
||||
# cmake file for Pimoroni Inky with Raspberry Pi Pico W
|
||||
set(MICROPY_BOARD RPI_PICO_W)
|
||||
set(PICO_BOARD "pico2_w")
|
||||
|
||||
# The C malloc is needed by cyw43-driver Bluetooth and Pimoroni Pico modules
|
||||
set(MICROPY_C_HEAP_SIZE 4096)
|
||||
|
||||
set(MICROPY_PY_LWIP ON)
|
||||
set(MICROPY_PY_NETWORK_CYW43 ON)
|
||||
|
||||
# Bluetooth
|
||||
set(MICROPY_PY_BLUETOOTH ON)
|
||||
set(MICROPY_BLUETOOTH_BTSTACK ON)
|
||||
set(MICROPY_PY_BLUETOOTH_CYW43 ON)
|
||||
|
||||
# Board specific version of the frozen manifest
|
||||
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
|
||||
14
boards/pico2_w_unicorn/mpconfigboard.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Board and hardware specific configuration
|
||||
#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico2 W (Unicorn)"
|
||||
|
||||
// Leave 50/50 split - 2MB firmware, 2MB user filesystem
|
||||
#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - (2 * 1024 * 1024))
|
||||
|
||||
// Enable networking.
|
||||
#define MICROPY_PY_NETWORK_PPP_LWIP (1)
|
||||
#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "Unicorn2W"
|
||||
#include "enable_cyw43.h"
|
||||
|
||||
// For debugging mbedtls - also set
|
||||
// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose
|
||||
// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1
|
||||
@ -27,4 +27,4 @@ GP28,GPIO28
|
||||
WL_GPIO0,EXT_GPIO0
|
||||
WL_GPIO1,EXT_GPIO1
|
||||
WL_GPIO2,EXT_GPIO2
|
||||
LED,EXT_GPIO0
|
||||
LED,EXT_GPIO0
|
||||
|
7
boards/pico2_w_unicorn/usermodules.cmake
Normal file
@ -0,0 +1,7 @@
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
|
||||
|
||||
include(galactic_unicorn/micropython)
|
||||
include(cosmic_unicorn/micropython)
|
||||
include(stellar_unicorn/micropython)
|
||||
|
||||
include(usermod-common)
|
||||
2
boards/pico_w_unicorn/manifest.py
Normal file
@ -0,0 +1,2 @@
|
||||
include("$(PORT_DIR)/boards/manifest.py")
|
||||
include("../manifest-common.py")
|
||||
3
boards/pico_w_unicorn/manifest.txt
Normal file
@ -0,0 +1,3 @@
|
||||
*.py
|
||||
lib/*
|
||||
lib/*/*
|
||||
17
boards/pico_w_unicorn/mpconfigboard.cmake
Normal file
@ -0,0 +1,17 @@
|
||||
# cmake file for Pimoroni Inky with Raspberry Pi Pico W
|
||||
set(MICROPY_BOARD RPI_PICO_W)
|
||||
set(PICO_BOARD "pico_w")
|
||||
|
||||
# The C malloc is needed by cyw43-driver Bluetooth and Pimoroni Pico modules
|
||||
set(MICROPY_C_HEAP_SIZE 4096)
|
||||
|
||||
set(MICROPY_PY_LWIP ON)
|
||||
set(MICROPY_PY_NETWORK_CYW43 ON)
|
||||
|
||||
# Bluetooth
|
||||
set(MICROPY_PY_BLUETOOTH ON)
|
||||
set(MICROPY_BLUETOOTH_BTSTACK ON)
|
||||
set(MICROPY_PY_BLUETOOTH_CYW43 ON)
|
||||
|
||||
# Board specific version of the frozen manifest
|
||||
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
|
||||
14
boards/pico_w_unicorn/mpconfigboard.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Board and hardware specific configuration
|
||||
#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico W (Unicorn)"
|
||||
|
||||
// Leave 848k for user filesystem
|
||||
#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - (1200 * 1024))
|
||||
|
||||
// Enable networking.
|
||||
#define MICROPY_PY_NETWORK_PPP_LWIP (1)
|
||||
#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "Unicorn"
|
||||
#include "enable_cyw43.h"
|
||||
|
||||
// For debugging mbedtls - also set
|
||||
// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose
|
||||
// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1
|
||||
@ -27,4 +27,4 @@ GP28,GPIO28
|
||||
WL_GPIO0,EXT_GPIO0
|
||||
WL_GPIO1,EXT_GPIO1
|
||||
WL_GPIO2,EXT_GPIO2
|
||||
LED,EXT_GPIO0
|
||||
LED,EXT_GPIO0
|
||||
|
3
boards/pico_w_unicorn/usermodules.cmake
Normal file
@ -0,0 +1,3 @@
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
|
||||
|
||||
include(usermod-common)
|
||||
@ -1,10 +1,16 @@
|
||||
set(PIMORONI_PICO_PATH ../../../../pimoroni-pico)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../pimoroni_pico_import.cmake)
|
||||
if(NOT DEFINED PIMORONI_PICO_PATH)
|
||||
set(PIMORONI_PICO_PATH ${CMAKE_CURRENT_LIST_DIR}/../pimoroni-pico)
|
||||
endif()
|
||||
include(${PIMORONI_PICO_PATH}/pimoroni_pico_import.cmake)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR}/../../)
|
||||
include_directories(${PIMORONI_PICO_PATH}/micropython)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
|
||||
# Drivers, etc
|
||||
list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}")
|
||||
# modules_py/modules_py
|
||||
list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython")
|
||||
# All regular modules
|
||||
list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules")
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
@ -20,24 +26,33 @@ include(bitmap_fonts/micropython)
|
||||
include(picographics/micropython)
|
||||
|
||||
# Pico Graphics Extra
|
||||
include(pngdec/micropython)
|
||||
include(jpegdec/micropython)
|
||||
include(qrcode/micropython/micropython)
|
||||
|
||||
# Sensors & Breakouts
|
||||
include(micropython-common-breakouts)
|
||||
include(pcf85063a/micropython)
|
||||
|
||||
# Utility
|
||||
include(adcfft/micropython)
|
||||
|
||||
# LEDs & Matrices
|
||||
include(plasma/micropython)
|
||||
include(galactic_unicorn/micropython)
|
||||
include(cosmic_unicorn/micropython)
|
||||
include(stellar_unicorn/micropython)
|
||||
|
||||
# ULAB
|
||||
include(micropython-common-ulab)
|
||||
enable_ulab()
|
||||
# Servos & Motors
|
||||
include(pwm/micropython)
|
||||
include(servo/micropython)
|
||||
include(encoder/micropython)
|
||||
include(motor/micropython)
|
||||
|
||||
# Still required for version.py
|
||||
include(modules_py/modules_py)
|
||||
|
||||
# C++ Magic Memory
|
||||
include(cppmem/micropython)
|
||||
|
||||
# Disable build-busting C++ exceptions
|
||||
include(micropython-disable-exceptions)
|
||||
135
ci/micropython.sh
Normal file
@ -0,0 +1,135 @@
|
||||
export TERM=${TERM:="xterm-256color"}
|
||||
|
||||
MICROPYTHON_FLAVOUR="peterharperuk"
|
||||
MICROPYTHON_VERSION="pico2_w_changes"
|
||||
|
||||
PIMORONI_PICO_FLAVOUR="pimoroni"
|
||||
PIMORONI_PICO_VERSION="feature/picovector2-and-layers"
|
||||
|
||||
PY_DECL_VERSION="v0.0.3"
|
||||
DIR2UF2_VERSION="v0.0.9"
|
||||
|
||||
if [ -z ${CI_PROJECT_ROOT+x} ]; then
|
||||
SCRIPT_PATH="$(dirname $0)"
|
||||
CI_PROJECT_ROOT=$(realpath "$SCRIPT_PATH/..")
|
||||
fi
|
||||
|
||||
if [ -z ${CI_BUILD_ROOT+x} ]; then
|
||||
CI_BUILD_ROOT=$(pwd)
|
||||
fi
|
||||
|
||||
|
||||
function log_success {
|
||||
echo -e "$(tput setaf 2)$1$(tput sgr0)"
|
||||
}
|
||||
|
||||
function log_inform {
|
||||
echo -e "$(tput setaf 6)$1$(tput sgr0)"
|
||||
}
|
||||
|
||||
function log_warning {
|
||||
echo -e "$(tput setaf 1)$1$(tput sgr0)"
|
||||
}
|
||||
|
||||
function ci_pimoroni_pico_clone {
|
||||
log_inform "Using Pimoroni Pico $PIMORONI_PICO_FLAVOUR/$PIMORONI_PICO_VERSION"
|
||||
git clone https://github.com/$PIMORONI_PICO_FLAVOUR/pimoroni-pico "$CI_BUILD_ROOT/pimoroni-pico"
|
||||
cd "$CI_BUILD_ROOT/pimoroni-pico" || return 1
|
||||
git checkout $PIMORONI_PICO_VERSION
|
||||
git submodule update --init
|
||||
cd "$CI_BUILD_ROOT"
|
||||
}
|
||||
|
||||
function ci_micropython_clone {
|
||||
log_inform "Using MicroPython $MICROPYTHON_FLAVOUR/$MICROPYTHON_VERSION"
|
||||
git clone https://github.com/$MICROPYTHON_FLAVOUR/micropython "$CI_BUILD_ROOT/micropython"
|
||||
cd "$CI_BUILD_ROOT/micropython" || return 1
|
||||
git checkout $MICROPYTHON_VERSION
|
||||
git submodule update --init lib/pico-sdk
|
||||
git submodule update --init lib/cyw43-driver
|
||||
git submodule update --init lib/lwip
|
||||
git submodule update --init lib/mbedtls
|
||||
git submodule update --init lib/micropython-lib
|
||||
git submodule update --init lib/tinyusb
|
||||
git submodule update --init lib/btstack
|
||||
cd "$CI_BUILD_ROOT"
|
||||
}
|
||||
|
||||
function ci_tools_clone {
|
||||
mkdir -p "$CI_BUILD_ROOT/tools"
|
||||
git clone https://github.com/gadgetoid/py_decl -b "$PY_DECL_VERSION" "$CI_BUILD_ROOT/tools/py_decl"
|
||||
git clone https://github.com/gadgetoid/dir2uf2 -b "$DIR2UF2_VERSION" "$CI_BUILD_ROOT/tools/dir2uf2"
|
||||
}
|
||||
|
||||
function ci_micropython_build_mpy_cross {
|
||||
cd "$CI_BUILD_ROOT/micropython/mpy-cross" || return 1
|
||||
ccache --zero-stats || true
|
||||
CROSS_COMPILE="ccache " make
|
||||
ccache --show-stats || true
|
||||
cd "$CI_BUILD_ROOT"
|
||||
}
|
||||
|
||||
function ci_apt_install_build_deps {
|
||||
sudo apt update && sudo apt install ccache python3-virtualenvwrapper virtualenvwrapper
|
||||
}
|
||||
|
||||
function ci_prepare_all {
|
||||
ci_tools_clone
|
||||
ci_micropython_clone
|
||||
ci_pimoroni_pico_clone
|
||||
ci_micropython_build_mpy_cross
|
||||
}
|
||||
|
||||
function ci_debug {
|
||||
log_inform "Project root: $CI_PROJECT_ROOT"
|
||||
log_inform "Build root: $CI_BUILD_ROOT"
|
||||
}
|
||||
|
||||
function micropython_version {
|
||||
BOARD=$1
|
||||
echo "MICROPY_GIT_TAG=$MICROPYTHON_VERSION, $BOARD $TAG_OR_SHA" >> $GITHUB_ENV
|
||||
echo "MICROPY_GIT_HASH=$MICROPYTHON_VERSION-$TAG_OR_SHA" >> $GITHUB_ENV
|
||||
}
|
||||
|
||||
function ci_cmake_configure {
|
||||
BOARD=$1
|
||||
MICROPY_BOARD_DIR=$CI_PROJECT_ROOT/boards/$BOARD
|
||||
if [ ! -f "$MICROPY_BOARD_DIR/usermodules.cmake" ]; then
|
||||
log_warning "Invalid board: $MICROPY_BOARD_DIR"
|
||||
return 1
|
||||
fi
|
||||
BUILD_DIR="$CI_BUILD_ROOT/build-$BOARD"
|
||||
cmake -S $CI_BUILD_ROOT/micropython/ports/rp2 -B "$BUILD_DIR" \
|
||||
-DPICOTOOL_FORCE_FETCH_FROM_GIT=1 \
|
||||
-DPICO_BUILD_DOCS=0 \
|
||||
-DPICO_NO_COPRO_DIS=1 \
|
||||
-DPIMORONI_PICO_PATH="$CI_BUILD_ROOT/pimoroni-pico" \
|
||||
-DUSER_C_MODULES="$MICROPY_BOARD_DIR/usermodules.cmake" \
|
||||
-DMICROPY_BOARD_DIR="$MICROPY_BOARD_DIR" \
|
||||
-DMICROPY_BOARD="$BOARD" \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
}
|
||||
|
||||
function ci_cmake_build {
|
||||
BOARD=$1
|
||||
MICROPY_BOARD_DIR=$CI_PROJECT_ROOT/boards/$BOARD
|
||||
EXAMPLES_DIR=$CI_PROJECT_ROOT/examples/launcher/
|
||||
TOOLS_DIR=$CI_BUILD_ROOT/tools
|
||||
BUILD_DIR="$CI_BUILD_ROOT/build-$BOARD"
|
||||
ccache --zero-stats || true
|
||||
cmake --build $BUILD_DIR -j 2
|
||||
ccache --show-stats || true
|
||||
if [ -d "$TOOLS_DIR/py_decl" ]; then
|
||||
log_inform "Tools found, verifying .uf2 with py_decl..."
|
||||
python3 "$TOOLS_DIR/py_decl/py_decl.py" --to-json --verify "$BUILD_DIR/firmware.uf2"
|
||||
fi
|
||||
log_inform "Copying .uf2 to $(pwd)/$BOARD.uf2"
|
||||
cp "$BUILD_DIR/firmware.uf2" $BOARD.uf2
|
||||
|
||||
if [ -f "$MICROPY_BOARD_DIR/manifest.txt" ] && [ -d "$TOOLS_DIR/dir2uf2" ]; then
|
||||
log_inform "Creating $(pwd)/$BOARD-with-filesystem.uf2"
|
||||
python3 -m pip install littlefs-python==0.12.0
|
||||
$TOOLS_DIR/dir2uf2/dir2uf2 --fs-compact --sparse --append-to "$(pwd)/$BOARD.uf2" --manifest "$MICROPY_BOARD_DIR/manifest.txt" --filename with-filesystem.uf2 "$EXAMPLES_DIR"
|
||||
fi
|
||||
}
|
||||
164
docs/inky_frame.md
Normal file
@ -0,0 +1,164 @@
|
||||
# Inky Frame (MicroPython) <!-- omit in toc -->
|
||||
|
||||
Most of your interaction with Inky Frame will be via the PicoGraphics library, but we have created an `inky_frame` module to help you read the onboard A, B, C, D and E buttons and control the LEDs.
|
||||
|
||||
- [Pico Graphics](#pico-graphics)
|
||||
- [Colour \& Dithering](#colour--dithering)
|
||||
- [Images \& JPEGs](#images--jpegs)
|
||||
- [Buttons \& LEDs](#buttons--leds)
|
||||
- [Status LEDs](#status-leds)
|
||||
- [Battery Power \& RTC](#battery-power--rtc)
|
||||
- [Function Reference](#function-reference)
|
||||
- [Wakeup States](#wakeup-states)
|
||||
- [RTC](#rtc)
|
||||
- [Other](#other)
|
||||
- [RAM Usage](#ram-usage)
|
||||
- [Accessing The Framebuffer](#accessing-the-framebuffer)
|
||||
|
||||
|
||||
## Pico Graphics
|
||||
|
||||
You can draw on Inky Frame using our PicoGraphics display library.
|
||||
- [PicoGraphics MicroPython function reference](https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/picographics/README.md)
|
||||
|
||||
### Colour & Dithering
|
||||
|
||||
Inky Frame supports eight native colours, those that it's physically capable of displaying. These are:
|
||||
|
||||
* `BLACK` = 0
|
||||
* `WHITE` = 1
|
||||
* `GREEN` = 2
|
||||
* `BLUE` = 3
|
||||
* `RED` = 4
|
||||
* `YELLOW` = 5
|
||||
* `ORANGE` = 6
|
||||
* `TAUPE` = 7 (only used for dither on 4.0" and 5.7" displays †)
|
||||
|
||||
:info: † - the "taupe" colour (also known as clear) is the colour used to reset the display to a default, clear state. We misuse this as an extra colour on 4.0" and 5.7", but the 7.3" display clear colour is a sort of muddy, greenish gradient that's not consistent enough. You can still use it, by all means, but it wont be considered for dithering.
|
||||
|
||||
These colours are available as constants in the `inky_frame` module so you don't have to remember which number corresponds to which, eg:
|
||||
|
||||
```python
|
||||
display.set_pen(inky_frame.YELLOW)
|
||||
```
|
||||
|
||||
You can use colours outside of these eight by using `create_pen(r, g, b)` or `create_pen_hsv(h, s, v)`. eg:
|
||||
|
||||
```python
|
||||
my_colour = display.create_pen(255, 128, 0)
|
||||
display.set_pen(my_colour)
|
||||
```
|
||||
|
||||
PicoGraphics will do its best to match your colour choice by mixing the available colours with a dither pattern. This works best on large areas of colour such as backgrounds, big UI elements or chunky text. See the `inky_frame_dithering.py` example for a demonstration.
|
||||
|
||||
:info: Due to the lack of light blue or pink native colours, Inky Frame isn't very good at reproducing pink, purple, violet or light blue, cyan and teal.
|
||||
|
||||
|
||||
### Images & JPEGs
|
||||
|
||||
You can use the `jpegdec` (JPEG decoding) module to display JPEG files on Inky, but you should be aware of some caveats:
|
||||
|
||||
1. JPEGs are compressed and lossy, a small JPEG displayed on Inky might show random specks of colour where you don't expect them as PicoGraphics tries its best to dither noise to the nearest available colours.
|
||||
2. JPEGs are, by default, dithered to the 7 (or 8 on 4.0 and 5.7) available colours. This uses "ordered dither," which is fast, but not very pretty.
|
||||
3. You can turn off dithering with `jpeg.decode(dither=False)` for a posterised effect.
|
||||
|
||||
## Buttons & LEDs
|
||||
|
||||
Inky Frame has five user buttons labelled A to E, with LEDs. The buttons are connected via a shift-register and available in the `inky_frame` module as `button_a`, `button_b`, `button_c`, `button_d` and `button_e`.
|
||||
|
||||
Each button has some convenient methods for checking if it's pressed:
|
||||
|
||||
* `raw()` - get the current state of the button with no debounce (returns `True` the first time it's called if the corresponding button)
|
||||
* `read()` - read the current button state, observing debounce (50ms)
|
||||
|
||||
Additionally each button has some methods for controlling its associated LED:
|
||||
|
||||
* `led_on()` - turn the LED on
|
||||
* `led_off()` - turn the LED off
|
||||
* `led_toggle()` - toggle the LED
|
||||
* `led_brightness(0.5)` - set the LED brightness (from 0.0 to 1.0)
|
||||
|
||||
### Status LEDs
|
||||
|
||||
In addition to the button LEDs there are two status LEDs, `busy` and `wifi`, which are available as:
|
||||
|
||||
* `led_busy` - connected to pin `LED_BUSY`
|
||||
* `led_wifi` - connected to pin `LED_WIFI`
|
||||
|
||||
These are instances of the `pimoroni.PWMLED` class, and have the following methods:
|
||||
|
||||
* `on()` - turn the LED on
|
||||
* `off()` - turn the LED off
|
||||
* `toggle()` - toggle the LED
|
||||
* `brightness(0.5)` - set the LED brightness (from 0.0 to 1.0)
|
||||
|
||||
:info: `toggle()` is provided for compatibility with the `Pin()` class. It's a little odd, since it changes the LED brightness to `1.0 - current_brightness`.
|
||||
|
||||
## Battery Power & RTC
|
||||
|
||||
When running on battery power, Inky Frame's buttons cause it to wake from a power-off state and start running your code. Additionally an onboard PCF85063A real-time clock runs continuously from battery and can wake up your Frame automatically.
|
||||
|
||||
The Inky Frame library includes a number of convenience functions to set the clock and sleep your device. If you want accurate time you must check if the clock is set - usually a simple check against the year works - connect to a network, and set via NTP (Network Time Protocol).
|
||||
|
||||
Inky Frame technically has *two* real-time clocks, the external RTC we've added - which remains continuously powered by battery - and the internal RTC of the Pico W. For convenience and compatibility with other code examples we recommend using the external RTC only to set the time on the internal one which makes functions like `time.localtime()` work as you'd expect.
|
||||
|
||||
```python
|
||||
import time
|
||||
import machine
|
||||
import inky_frame
|
||||
|
||||
inky_frame.pcf_to_pico_rtc() # Sync Inky RTC time to Pico's RTC
|
||||
|
||||
year, month, day, dow, hour, minute, second, _ = machine.RTC().datetime()
|
||||
|
||||
if year < 2023:
|
||||
# Connect to network
|
||||
inky_frame.set_time() # Sets both the Inky and Pico RTC
|
||||
|
||||
print(time.localtime())
|
||||
```
|
||||
|
||||
### Function Reference
|
||||
|
||||
#### Wakeup States
|
||||
|
||||
For your convenience these wakeup state functions also check the *current* state of their associated event, this allows you to run code from Thonny with a button held down - for example - to test how your deployed code will behave on battery-
|
||||
|
||||
* `inky_frame.woken_by_rtc()` - Returns `True` if the RTC caused a wakeup, or if the RTC ALARM is currently raised.
|
||||
* `inky_frame.woken_by_button()` - Returns `True` if a button caused a wakeup, or if a button is currently pressed.
|
||||
* `inky_frame.woken_by_ext_trigger()` Returns `True` if the external trigger caused a wakeup, or if the trigger is currently asserted.
|
||||
|
||||
#### RTC
|
||||
|
||||
* `inky_frame.set_time()` - Attempt to run `ntptime.settime()` and set the time on both RTCs
|
||||
* `inky_frame.pcf_to_pico_rtc()` - Sync from Inky's RTC to the Pico W's RTC
|
||||
* `inky_frame.pico_rtc_to_pcf()` - Sync from Pico W's RTC to Inky's RTC (sometime useful since Thonny sets the Pico W RTC automatically)
|
||||
* `inky_frame.sleep_for(minutes)` - Set the RTC alarm for a number of minutes and cut the power. This will completely power off the Pico W, but leave the Inky RTC running to wake it back up.
|
||||
|
||||
:info: You can access all Inky RTC (PCF85063A) functions via `inky_frame.rtc`
|
||||
|
||||
#### Other
|
||||
|
||||
* `inky_frame.turn_off()` - Cut the power to the Pico W (on battery only), only an alarm event or a button press can wake it back up.
|
||||
|
||||
## RAM Usage
|
||||
|
||||
Both Inky 4.0" and 5.7" use only the Pico's onboard RAM. It's quite cozy. The frame buffers are 3-bits-per-pixel, making Inky 4.0"
|
||||
|
||||
On 7.3" we had to add a PSRAM chip to act as the display's framebuffer. Right now it acts exclusively as a framebuffer, but that frees up some Pico RAM to work with so you can do more with that 7.3" panel.
|
||||
|
||||
The Inky frame buffer sizes are as follows:
|
||||
|
||||
* 4.0" - 640x400 - 96,000 bytes.
|
||||
* 5.7" - 600x448 - 100,800 bytes.
|
||||
* 7.3" - 800x480 - 144,000 bytes.
|
||||
|
||||
Since MicroPython on a Pico W has only 166k that would have left *just* 22k on 7.3", instead you get (almost) all 166k to play with since the PicoGraphics instance itself uses only 8544 bytes!
|
||||
|
||||
### Accessing The Framebuffer
|
||||
|
||||
PicoGraphics has undocumented support for accessing its raw framebuffer using `memoryview(graphics)`.
|
||||
|
||||
This is useful for copying raw binary images (effectively valid Inky frame buffers saved to a file) avoiding JPEG compression and so forth. For some dicussion about why and how you might do this, see: https://github.com/pimoroni/pimoroni-pico/issues/681
|
||||
|
||||
:warning: This *does not work* for Inky 7.3, since there is no framebuffer in memory. PicoGraphics will raise a `ValueError: No local framebuffer.` if you try. We aim to fix this with some hardfault handling sorcery.
|
||||
@ -1,84 +0,0 @@
|
||||
add_executable(
|
||||
cosmic_rainbow_text
|
||||
cosmic_rainbow_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_rainbow_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_rainbow_text)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_rainbow
|
||||
cosmic_rainbow.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_rainbow 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_rainbow)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_eighties_super_computer
|
||||
cosmic_eighties_super_computer.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_eighties_super_computer 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_eighties_super_computer)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_fire_effect
|
||||
cosmic_fire_effect.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_fire_effect 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_fire_effect)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_scroll_text
|
||||
cosmic_scroll_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_scroll_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_scroll_text)
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_lava_lamp
|
||||
cosmic_lava_lamp.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_lava_lamp 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_lava_lamp)
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
float lifetime[32][32];
|
||||
float age[32][32];
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
if(age[x][y] < lifetime[x][y] * 0.3f) {
|
||||
graphics.set_pen(230, 150, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(age[x][y] < lifetime[x][y] * 0.5f) {
|
||||
float decay = (lifetime[x][y] * 0.5f - age[x][y]) * 5.0f;
|
||||
graphics.set_pen(decay * 230, decay * 150, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
if(age[x][y] >= lifetime[x][y]) {
|
||||
age[x][y] = 0.0f;
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
}
|
||||
|
||||
age[x][y] += 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,115 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
int width = 32;
|
||||
int height = 33;
|
||||
|
||||
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
|
||||
float heat[2000] = {0.0f};
|
||||
|
||||
void set(int x, int y, float v) {
|
||||
heat[x + y * width] = v;
|
||||
}
|
||||
|
||||
float get(int x, int y) {
|
||||
/*if(x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return 0.0f;
|
||||
}*/
|
||||
x = x < 0 ? 0 : x;
|
||||
x = x >= width ? width - 1 : x;
|
||||
|
||||
return heat[x + y * width];
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
cosmic_unicorn.init();
|
||||
cosmic_unicorn.set_brightness(0.2);
|
||||
|
||||
bool landscape = true;
|
||||
/*
|
||||
while(true) {
|
||||
cosmic_unicorn.set_pixel(0, 0, 255, 0, 0);
|
||||
cosmic_unicorn.set_pixel(1, 1, 0, 255, 0);
|
||||
cosmic_unicorn.set_pixel(2, 2, 0, 0, 255);
|
||||
}*/
|
||||
|
||||
while(true) {
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
|
||||
for(int y = 0; y < height; y++) {
|
||||
for(int x = 0; x < width; x++) {
|
||||
float value = get(x, y);
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
if(value > 0.5f) {
|
||||
graphics.set_pen(255, 255, 180);
|
||||
}else if(value > 0.4f) {
|
||||
graphics.set_pen(220, 160, 0);
|
||||
}else if(value > 0.3f) {
|
||||
graphics.set_pen(180, 30, 0);
|
||||
}else if(value > 0.22f) {
|
||||
graphics.set_pen(20, 20, 20);
|
||||
}
|
||||
|
||||
if(landscape) {
|
||||
graphics.pixel(Point(x, y));
|
||||
}else{
|
||||
graphics.pixel(Point(y, x));
|
||||
}
|
||||
|
||||
// update this pixel by averaging the below pixels
|
||||
float average = (get(x, y) + get(x, y + 2) + get(x, y + 1) + get(x - 1, y + 1) + get(x + 1, y + 1)) / 5.0f;
|
||||
|
||||
// damping factor to ensure flame tapers out towards the top of the displays
|
||||
average *= 0.98f;
|
||||
|
||||
// update the heat map with our newly averaged value
|
||||
set(x, y, average);
|
||||
}
|
||||
}
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
// clear the bottom row and then add a new fire seed to it
|
||||
for(int x = 0; x < width; x++) {
|
||||
set(x, height - 1, 0.0f);
|
||||
}
|
||||
|
||||
// add a new random heat source
|
||||
int source_count = landscape ? 5 : 1;
|
||||
for(int c = 0; c < source_count; c++) {
|
||||
int px = (rand() % (width - 4)) + 2;
|
||||
set(px , height - 2, 1.0f);
|
||||
set(px + 1, height - 2, 1.0f);
|
||||
set(px - 1, height - 2, 1.0f);
|
||||
set(px , height - 1, 1.0f);
|
||||
set(px + 1, height - 1, 1.0f);
|
||||
set(px - 1, height - 1, 1.0f);
|
||||
}
|
||||
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,148 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
struct blob_t {
|
||||
float x, y;
|
||||
float r;
|
||||
float dx, dy;
|
||||
};
|
||||
|
||||
constexpr int blob_count = 20;
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
cosmic_unicorn.init();
|
||||
cosmic_unicorn.set_brightness(0.5);
|
||||
|
||||
// randomise blob start positions, directions, and size
|
||||
std::array<blob_t, blob_count> blobs;
|
||||
for(auto &blob : blobs) {
|
||||
blob.x = rand() % 32;
|
||||
blob.y = rand() % 32;
|
||||
blob.r = ((rand() % 40) / 10.0f) + 5.0f;
|
||||
blob.dx = ((rand() % 2) / 10.0f) - 0.05f;
|
||||
blob.dy = ((rand() % 3) / 10.0f) - 0.1f;
|
||||
}
|
||||
|
||||
float hue = 0.0f;
|
||||
|
||||
while(true) {
|
||||
// allow user to adjust brightness
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
uint start_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
// calculate the influence of each blob on the liquid based
|
||||
// on their distance to each pixel. this causes blobs to
|
||||
// "merge" into each other when we use fixed thresholds to
|
||||
// determine which colour to draw with
|
||||
float liquid[32][32] = {0.0f};
|
||||
for(auto &blob : blobs) {
|
||||
float r_sq = blob.r * blob.r;
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
float d_sq = (x - blob.x) * (x - blob.x) + (y - blob.y) * (y - blob.y);
|
||||
if(d_sq <= r_sq) {
|
||||
// add this blobs influence to this pixel
|
||||
liquid[x][y] += 1.0f - (d_sq / r_sq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the blob positions
|
||||
for(auto &blob : blobs) {
|
||||
blob.x += blob.dx;
|
||||
blob.y += blob.dy;
|
||||
|
||||
// if we hit the edge then bounce!
|
||||
if(blob.x < 0.0f || blob.x >= 11.0f) {
|
||||
blob.dx *= -1.0f;
|
||||
}
|
||||
if(blob.y < 0.0f || blob.y >= 53.0f) {
|
||||
blob.dy *= -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// rotate the hue
|
||||
hue += 0.001f;
|
||||
|
||||
// calculate dark, medium, and bright shades for rendering the
|
||||
// lava
|
||||
uint8_t dark_r, dark_g, dark_b;
|
||||
from_hsv(hue, 1.0f, 0.3f, dark_r, dark_g, dark_b);
|
||||
uint8_t mid_r, mid_g, mid_b;
|
||||
from_hsv(hue, 1.0f, 0.6f, mid_r, mid_g, mid_b);
|
||||
uint8_t bright_r, bright_g, bright_b;
|
||||
from_hsv(hue, 1.0f, 1.0f, bright_r, bright_g, bright_b);
|
||||
|
||||
// clear the canvas
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
// render the lava
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
float v = liquid[x][y];
|
||||
|
||||
// select a colour for this pixel based on how much
|
||||
// "blobfluence" there is at this position in the liquid
|
||||
if(v >= 1.5f) {
|
||||
graphics.set_pen(bright_r, bright_g, bright_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}else if(v >= 1.25f) {
|
||||
graphics.set_pen(mid_r, mid_g, mid_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}else if(v >= 1.0f) {
|
||||
graphics.set_pen(dark_r, dark_g, dark_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint end_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
printf("rendering took %dms\n", end_ms - start_ms);
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,184 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
|
||||
int w = graphics.measure_text(t, s);
|
||||
p.x += (53 / 2) - (w / 2);
|
||||
p.y += (11 / 2);
|
||||
graphics.text(t, Point(p.x, p.y), -1, s, a);
|
||||
//graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
|
||||
//graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
|
||||
//graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
|
||||
}
|
||||
|
||||
struct star_t {
|
||||
float dx, dy, x, y, a;
|
||||
|
||||
uint8_t brightness() {
|
||||
int b = a / 5;
|
||||
return b > 15 ? 15 : b;
|
||||
}
|
||||
};
|
||||
|
||||
void init_star(star_t &s) {
|
||||
s.x = ((rand() % 100) / 5.0f) - 10.0f;
|
||||
s.y = ((rand() % 100) / 10.0f) - 5.0f;
|
||||
|
||||
s.dx = s.x / 10.0f;
|
||||
s.dy = s.y / 10.0f;
|
||||
s.a = 0;
|
||||
}
|
||||
|
||||
void step_star(star_t &s) {
|
||||
s.x += s.dx;
|
||||
s.y += s.dy;
|
||||
s.a++;
|
||||
|
||||
if(s.a > 100) {
|
||||
init_star(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
uint8_t hue_map[53][3];
|
||||
for(int i = 0; i < 53; i++) {
|
||||
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
star_t stars[100];
|
||||
for(int i = 0; i < 100; i++) {
|
||||
init_star(stars[i]);
|
||||
stars[i].a = i;
|
||||
}
|
||||
|
||||
gpio_set_function(28, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(28, GPIO_OUT);
|
||||
|
||||
for(int i = 0; i < 10; i++) {
|
||||
gpio_put(28, !gpio_get(28));
|
||||
sleep_ms(100);
|
||||
}
|
||||
sleep_ms(1000);
|
||||
|
||||
gpio_put(28,true);
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
|
||||
|
||||
float i = 0;
|
||||
|
||||
float hue_offset = 0.0f;
|
||||
|
||||
bool animate = true;
|
||||
|
||||
float stripe_width = 3.0f;
|
||||
float speed = 1.0f;
|
||||
float curve = 0.0f;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(animate) {
|
||||
i += speed;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_VOLUME_UP)) {
|
||||
curve += 0.05;
|
||||
if(hue_offset > 1.0f) hue_offset = 1.0f;
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_VOLUME_DOWN)) {
|
||||
curve -= 0.05;
|
||||
if(hue_offset < 0.0f) hue_offset = 0.0f;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_SLEEP)) {
|
||||
animate = false;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_A)) {
|
||||
speed += 0.05f;
|
||||
speed = speed >= 10.0f ? 10.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_B)) {
|
||||
speed -= 0.05f;
|
||||
speed = speed <= 0.0f ? 0.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_C)) {
|
||||
stripe_width += 0.05f;
|
||||
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_D)) {
|
||||
stripe_width -= 0.05f;
|
||||
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
|
||||
}
|
||||
|
||||
for(int x = 0; x < 32; x++) {
|
||||
for(int y = 0; y < 32; y++) {
|
||||
int v = ((sin((x + y) / stripe_width + (sin((y * 3.1415927f * 2.0f) / 11.0f) * curve) + i / 15.0f) + 1.5f) / 2.5f) * 255.0f;
|
||||
|
||||
uint8_t r = (hue_map[x][0] * v) / 256;
|
||||
uint8_t g = (hue_map[x][1] * v) / 256;
|
||||
uint8_t b = (hue_map[x][2] * v) / 256;
|
||||
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
}
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
printf("%d\n", cosmic_unicorn.light());
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
|
||||
int w = graphics.measure_text(t, s);
|
||||
p.x += (32 / 2) - (w / 2);
|
||||
p.y += (32 / 2);
|
||||
graphics.text(t, Point(p.x, p.y), -1, s, a);
|
||||
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
|
||||
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
|
||||
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
uint8_t hue_map[32][3];
|
||||
for(int i = 0; i < 32; i++) {
|
||||
from_hsv(i / 32.0f, 1.0f, 0.5f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
graphics.set_font("sans");
|
||||
uint i = 0;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
|
||||
float x = (sin((i) / 50.0f) * 90.0f);
|
||||
float y = (cos((i) / 40.0f) * 5.0f);
|
||||
graphics.set_pen(255, 255, 255);
|
||||
text("Galactic Unicorn", Point(x, y), s, a);
|
||||
uint8_t *p = (uint8_t *)graphics.frame_buffer;
|
||||
for(size_t i = 0; i < 32 * 32; i++) {
|
||||
int x = i % 32;
|
||||
int y = i / 32;
|
||||
uint r = *p++;
|
||||
uint g = *p++;
|
||||
uint b = *p++;
|
||||
p++;
|
||||
|
||||
if(r > 0) {
|
||||
r = hue_map[x][0];
|
||||
g = hue_map[x][1];
|
||||
b = hue_map[x][2];
|
||||
}
|
||||
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
}
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
float scroll = -32.0f;
|
||||
|
||||
while(true) {
|
||||
//uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.25f;
|
||||
|
||||
if(scroll > width) {
|
||||
scroll = -32.0f;
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
uint8_t r = 0, g = 0, b = 0;
|
||||
from_hsv(scroll / 100.0f, 1.0f, 0.5f, r, g, b);
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.text(message, Point(0 - scroll, 14), -1, 0.55);
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
add_executable(
|
||||
rainbow_text
|
||||
rainbow_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(rainbow_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(rainbow_text)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
rainbow
|
||||
rainbow.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(rainbow 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(rainbow)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
nostalgia_prompt
|
||||
nostalgia_prompt.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(nostalgia_prompt pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(nostalgia_prompt 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(nostalgia_prompt)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
eighties_super_computer
|
||||
eighties_super_computer.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(eighties_super_computer 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(eighties_super_computer)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
fire_effect
|
||||
fire_effect.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(fire_effect 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(fire_effect)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
scroll_text
|
||||
scroll_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(scroll_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(scroll_text)
|
||||
|
||||
|
||||
add_executable(
|
||||
balls
|
||||
balls.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(balls pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(balls 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(balls)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
lava_lamp
|
||||
lava_lamp.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(lava_lamp 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(lava_lamp)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
feature_test
|
||||
feature_test.cpp
|
||||
audio_samples.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(feature_test pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(feature_test 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(feature_test)
|
||||
@ -1,87 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
float heat[53][13] = {0.0f};
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 12; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
if(heat[x][y] > 0.5f) {
|
||||
graphics.set_pen(255, 255, 180);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(heat[x][y] > 0.4f) {
|
||||
graphics.set_pen(220, 160, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(heat[x][y] > 0.3f) {
|
||||
graphics.set_pen(180, 50, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(heat[x][y] > 0.2f) {
|
||||
graphics.set_pen(40, 40, 40);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
// update this pixel by averaging the below pixels
|
||||
if(x == 0) {
|
||||
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x + 1][y + 1]) / 4.0f;
|
||||
} else if(x == 52) {
|
||||
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1]) / 4.0f;
|
||||
} else {
|
||||
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1] + heat[x + 1][y + 1]) / 5.0f;
|
||||
}
|
||||
|
||||
heat[x][y] -= 0.01f;
|
||||
heat[x][y] = heat[x][y] < 0.0f ? 0.0f: heat[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
// clear the bottom row and then add a new fire seed to it
|
||||
for(int x = 0; x < 53; x++) {
|
||||
heat[x][11] = 0.0f;
|
||||
}
|
||||
|
||||
// add a new random heat source
|
||||
for(int c = 0; c < 5; c++) {
|
||||
int px = (rand() % 51) + 1;
|
||||
heat[px][11] = 1.0f;
|
||||
heat[px + 1][11] = 1.0f;
|
||||
heat[px - 1][11] = 1.0f;
|
||||
heat[px][12] = 1.0f;
|
||||
heat[px + 1][12] = 1.0f;
|
||||
heat[px - 1][12] = 1.0f;
|
||||
}
|
||||
|
||||
sleep_ms(50);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
float lifetime[53][11];
|
||||
float age[53][11];
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
for(int y = 0; y < 11; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 11; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
if(age[x][y] < lifetime[x][y] * 0.3f) {
|
||||
graphics.set_pen(230, 150, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(age[x][y] < lifetime[x][y] * 0.5f) {
|
||||
float decay = (lifetime[x][y] * 0.5f - age[x][y]) * 5.0f;
|
||||
graphics.set_pen(decay * 230, decay * 150, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
if(age[x][y] >= lifetime[x][y]) {
|
||||
age[x][y] = 0.0f;
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
}
|
||||
|
||||
age[x][y] += 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,164 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// left_channel_bin and right_channel_bin are in audio_samples.cpp
|
||||
extern uint8_t left_channel_bin[];
|
||||
extern uint32_t left_channel_bin_len;
|
||||
extern uint8_t right_channel_bin[];
|
||||
extern uint32_t right_channel_bin_len;
|
||||
|
||||
void gradient(uint8_t r, uint8_t g, uint8_t b) {
|
||||
for(int y = 0; y < 12; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
// graphics.set_pen((r * x) / 52, (g * x) / 52, (b * x) / 52);
|
||||
graphics.set_pen(x, x, x);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*shader_func_t)(int ms, int x, int y);
|
||||
|
||||
void grid(int ms, int x, int y) {
|
||||
int v = (x + y + (ms / 1000)) % 2 == 0 ? 255 : 0;
|
||||
graphics.set_pen(v, v, v);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
void shader_fill(int ms, shader_func_t f) {
|
||||
for(int y = 0; y < 12; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
f(ms, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void outline_text(std::string text) {
|
||||
uint ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
graphics.set_font("bitmap8");
|
||||
uint8_t v = (sin(ms / 100.0f) + 1.0f) * 127.0f;
|
||||
uint w = graphics.measure_text(text, 1);
|
||||
|
||||
int x = 53 / 2 - w / 2 + 1, y = 2;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.text(text, Point(x - 1, y - 1), -1, 1);
|
||||
graphics.text(text, Point(x , y - 1), -1, 1);
|
||||
graphics.text(text, Point(x + 1, y - 1), -1, 1);
|
||||
graphics.text(text, Point(x - 1, y ), -1, 1);
|
||||
graphics.text(text, Point(x + 1, y ), -1, 1);
|
||||
graphics.text(text, Point(x - 1, y + 1), -1, 1);
|
||||
graphics.text(text, Point(x , y + 1), -1, 1);
|
||||
graphics.text(text, Point(x + 1, y + 1), -1, 1);
|
||||
|
||||
graphics.set_pen(v, v, v);
|
||||
graphics.text(text, Point(x, y), -1, 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
//galactic_unicorn.set_brightness(0.5f);
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01f);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01f);
|
||||
}
|
||||
|
||||
uint ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
uint8_t test = (ms / 1000) % 4;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
switch(test) {
|
||||
case 0: {
|
||||
printf("grid pattern");
|
||||
shader_fill(ms, grid);
|
||||
}break;
|
||||
|
||||
case 1: {
|
||||
printf("green gradient");
|
||||
gradient(0, 255, 0);
|
||||
}break;
|
||||
|
||||
case 2: {
|
||||
printf("blue gradient");
|
||||
gradient(0, 0, 255);
|
||||
}break;
|
||||
|
||||
case 3: {
|
||||
printf("white gradient");
|
||||
gradient(255, 255, 255);
|
||||
}break;
|
||||
}
|
||||
|
||||
printf("%d\n", galactic_unicorn.light());
|
||||
|
||||
std::string text = "";
|
||||
static bool was_a_pressed = false;
|
||||
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
|
||||
if(!was_a_pressed) {
|
||||
galactic_unicorn.play_sample(left_channel_bin, left_channel_bin_len);
|
||||
}
|
||||
was_a_pressed = true;
|
||||
}else{
|
||||
was_a_pressed = false;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
|
||||
text = "Button A";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_B)) {
|
||||
text = "Button B";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_C)) {
|
||||
text = "Button C";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_D)) {
|
||||
text = "Button D";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_UP)) {
|
||||
text = "Louder!";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_DOWN)) {
|
||||
text = "quieter";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_UP)) {
|
||||
text = "Brighter!";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_DOWN)) {
|
||||
text = "Darker";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_SLEEP)) {
|
||||
text = "Zzz... zzz...";
|
||||
}
|
||||
|
||||
outline_text(text);
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(50);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
int width = 53;
|
||||
int height = 15;
|
||||
|
||||
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
|
||||
float heat[1000] = {0.0f};
|
||||
|
||||
void set(int x, int y, float v) {
|
||||
heat[x + y * width] = v;
|
||||
}
|
||||
|
||||
float get(int x, int y) {
|
||||
/*if(x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return 0.0f;
|
||||
}*/
|
||||
x = x < 0 ? 0 : x;
|
||||
x = x >= width ? width - 1 : x;
|
||||
|
||||
return heat[x + y * width];
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
galactic_unicorn.set_brightness(0.5);
|
||||
|
||||
bool landscape = true;
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
|
||||
landscape = true;
|
||||
width = 53;
|
||||
height = 15;
|
||||
memset(heat, 0, sizeof(heat));
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
|
||||
landscape = false;
|
||||
width = 11;
|
||||
height = 55;
|
||||
memset(heat, 0, sizeof(heat));
|
||||
}
|
||||
|
||||
for(int y = 0; y < height; y++) {
|
||||
for(int x = 0; x < width; x++) {
|
||||
float value = get(x, y);
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
if(value > 0.5f) {
|
||||
graphics.set_pen(255, 255, 180);
|
||||
}else if(value > 0.4f) {
|
||||
graphics.set_pen(220, 160, 0);
|
||||
}else if(value > 0.3f) {
|
||||
graphics.set_pen(180, 30, 0);
|
||||
}else if(value > 0.22f) {
|
||||
graphics.set_pen(20, 20, 20);
|
||||
}
|
||||
|
||||
if(landscape) {
|
||||
graphics.pixel(Point(x, y));
|
||||
}else{
|
||||
graphics.pixel(Point(y, x));
|
||||
}
|
||||
|
||||
// update this pixel by averaging the below pixels
|
||||
float average = (get(x, y) + get(x, y + 2) + get(x, y + 1) + get(x - 1, y + 1) + get(x + 1, y + 1)) / 5.0f;
|
||||
|
||||
// damping factor to ensure flame tapers out towards the top of the displays
|
||||
average *= landscape ? 0.95f : 0.99f;
|
||||
|
||||
// update the heat map with our newly averaged value
|
||||
set(x, y, average);
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
// clear the bottom row and then add a new fire seed to it
|
||||
for(int x = 0; x < width; x++) {
|
||||
set(x, height - 1, 0.0f);
|
||||
}
|
||||
|
||||
// add a new random heat source
|
||||
int source_count = landscape ? 5 : 1;
|
||||
for(int c = 0; c < source_count; c++) {
|
||||
int px = (rand() % (width - 4)) + 2;
|
||||
set(px , height - 2, 1.0f);
|
||||
set(px + 1, height - 2, 1.0f);
|
||||
set(px - 1, height - 2, 1.0f);
|
||||
set(px , height - 1, 1.0f);
|
||||
set(px + 1, height - 1, 1.0f);
|
||||
set(px - 1, height - 1, 1.0f);
|
||||
}
|
||||
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
struct blob_t {
|
||||
float x, y;
|
||||
float r;
|
||||
float dx, dy;
|
||||
};
|
||||
|
||||
constexpr int blob_count = 20;
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
galactic_unicorn.set_brightness(0.5);
|
||||
|
||||
// randomise blob start positions, directions, and size
|
||||
std::array<blob_t, blob_count> blobs;
|
||||
for(auto &blob : blobs) {
|
||||
blob.x = rand() % 11;
|
||||
blob.y = rand() % 53;
|
||||
blob.r = ((rand() % 40) / 10.0f) + 5.0f;
|
||||
blob.dx = ((rand() % 2) / 10.0f) - 0.05f;
|
||||
blob.dy = ((rand() % 3) / 10.0f) - 0.1f;
|
||||
}
|
||||
|
||||
float hue = 0.0f;
|
||||
|
||||
while(true) {
|
||||
// allow user to adjust brightness
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
uint start_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
// calculate the influence of each blob on the liquid based
|
||||
// on their distance to each pixel. this causes blobs to
|
||||
// "merge" into each other when we use fixed thresholds to
|
||||
// determine which colour to draw with
|
||||
float liquid[11][53] = {0.0f};
|
||||
for(auto &blob : blobs) {
|
||||
float r_sq = blob.r * blob.r;
|
||||
for(int y = 0; y < 53; y++) {
|
||||
for(int x = 0; x < 11; x++) {
|
||||
float d_sq = (x - blob.x) * (x - blob.x) + (y - blob.y) * (y - blob.y);
|
||||
if(d_sq <= r_sq) {
|
||||
// add this blobs influence to this pixel
|
||||
liquid[x][y] += 1.0f - (d_sq / r_sq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the blob positions
|
||||
for(auto &blob : blobs) {
|
||||
blob.x += blob.dx;
|
||||
blob.y += blob.dy;
|
||||
|
||||
// if we hit the edge then bounce!
|
||||
if(blob.x < 0.0f || blob.x >= 11.0f) {
|
||||
blob.dx *= -1.0f;
|
||||
}
|
||||
if(blob.y < 0.0f || blob.y >= 53.0f) {
|
||||
blob.dy *= -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// rotate the hue
|
||||
hue += 0.001f;
|
||||
|
||||
// calculate dark, medium, and bright shades for rendering the
|
||||
// lava
|
||||
uint8_t dark_r, dark_g, dark_b;
|
||||
from_hsv(hue, 1.0f, 0.3f, dark_r, dark_g, dark_b);
|
||||
uint8_t mid_r, mid_g, mid_b;
|
||||
from_hsv(hue, 1.0f, 0.6f, mid_r, mid_g, mid_b);
|
||||
uint8_t bright_r, bright_g, bright_b;
|
||||
from_hsv(hue, 1.0f, 1.0f, bright_r, bright_g, bright_b);
|
||||
|
||||
// clear the canvas
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
// render the lava
|
||||
for(int y = 0; y < 53; y++) {
|
||||
for(int x = 0; x < 11; x++) {
|
||||
float v = liquid[x][y];
|
||||
|
||||
// select a colour for this pixel based on how much
|
||||
// "blobfluence" there is at this position in the liquid
|
||||
if(v >= 1.5f) {
|
||||
graphics.set_pen(bright_r, bright_g, bright_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}else if(v >= 1.25f) {
|
||||
graphics.set_pen(mid_r, mid_g, mid_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}else if(v >= 1.0f) {
|
||||
graphics.set_pen(dark_r, dark_g, dark_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint end_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
printf("rendering took %dms\n", end_ms - start_ms);
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
std::array<std::string, 11> c64 = {
|
||||
"",
|
||||
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX",
|
||||
" OO OO OO OOOO OO OO OO OO XXXXXXX",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX",
|
||||
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX",
|
||||
" OOOO OO OO OO OO OO OO XXXXXXX",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX",
|
||||
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX",
|
||||
" XXXXXXX"
|
||||
};
|
||||
|
||||
std::array<std::string, 11> spectrum = {
|
||||
"",
|
||||
" O OOOO OOOO OOOOO O O O O XXXXXXXX",
|
||||
" O O O O O O O O O O O X XXXXXX",
|
||||
" O O O O O O O X XXXXXX",
|
||||
" O O O OOOOOO O O X XXXXXX",
|
||||
" O O O O O O O X XXXXXX",
|
||||
" OOOOOO OOOO O O OOOOO X XXXXXX",
|
||||
" X X",
|
||||
" XXXXXXXX"
|
||||
};
|
||||
|
||||
std::array<std::string, 11> bbc_micro = {
|
||||
"",
|
||||
" OOOOO OO OOOO OOO OOOO O ",
|
||||
" O O O O O O O O O O ",
|
||||
" O O O O O O O O ",
|
||||
" OOOOO O O OOOO O O O ",
|
||||
" O O OOOOOO O O O O ",
|
||||
" O O O O O O O O O O ",
|
||||
" OOOOO O O OOOO OOO OOOO O ",
|
||||
" XXXXXXX"
|
||||
};
|
||||
|
||||
|
||||
constexpr uint PROMPT_C64 = 0;
|
||||
constexpr uint PROMPT_SPECTRUM = 1;
|
||||
constexpr uint PROMPT_BBC_MICRO = 2;
|
||||
uint prompt = 0;
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
prompt = (time_ms / 3000) % 3;
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
std::array<std::string, 11> image = c64;
|
||||
if(prompt == PROMPT_C64) {
|
||||
image = c64;
|
||||
}else if(prompt == PROMPT_SPECTRUM) {
|
||||
image = spectrum;
|
||||
}else if(prompt == PROMPT_BBC_MICRO) {
|
||||
image = bbc_micro;
|
||||
}
|
||||
|
||||
if(prompt == PROMPT_C64) {
|
||||
graphics.set_pen(20, 20, 120);
|
||||
}else if(prompt == PROMPT_SPECTRUM){
|
||||
graphics.set_pen(180, 150, 150);
|
||||
}else if(prompt == PROMPT_BBC_MICRO){
|
||||
graphics.set_pen(0, 0, 0);
|
||||
}
|
||||
|
||||
graphics.clear();
|
||||
|
||||
if(prompt == PROMPT_C64) {
|
||||
graphics.set_pen(230, 210, 250);
|
||||
}else if(prompt == PROMPT_SPECTRUM){
|
||||
graphics.set_pen(0, 0, 0);
|
||||
}else if(prompt == PROMPT_BBC_MICRO){
|
||||
graphics.set_pen(255, 255, 255);
|
||||
}
|
||||
|
||||
for(size_t y = 0; y < image.size(); y++) {
|
||||
for(size_t x = 0; x < image[y].length(); x++) {
|
||||
// draw the prompt text
|
||||
if(image[y][x] == 'O') {
|
||||
graphics.pixel(Point(x, y + 1));
|
||||
}
|
||||
|
||||
// draw the caret blinking
|
||||
if(image[y][x] == 'X' && (time_ms / 300) % 2) {
|
||||
graphics.pixel(Point(x, y + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,684 +0,0 @@
|
||||
#pragma once
|
||||
// Copyright(c) 2021 Björn Ottosson
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this softwareand associated documentation files(the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions :
|
||||
// The above copyright noticeand this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include <cmath>
|
||||
#include <cfloat>
|
||||
|
||||
namespace ok_color
|
||||
{
|
||||
|
||||
struct Lab { float L; float a; float b; };
|
||||
struct RGB { float r; float g; float b; };
|
||||
struct HSV { float h; float s; float v; };
|
||||
struct HSL { float h; float s; float l; };
|
||||
struct LC { float L; float C; };
|
||||
|
||||
// Alternative representation of (L_cusp, C_cusp)
|
||||
// Encoded so S = C_cusp/L_cusp and T = C_cusp/(1-L_cusp)
|
||||
// The maximum value for C in the triangle is then found as fmin(S*L, T*(1-L)), for a given L
|
||||
struct ST { float S; float T; };
|
||||
|
||||
constexpr float pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062f;
|
||||
|
||||
float clamp(float x, float min, float max)
|
||||
{
|
||||
if (x < min)
|
||||
return min;
|
||||
if (x > max)
|
||||
return max;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
float sgn(float x)
|
||||
{
|
||||
return (float)(0.f < x) - (float)(x < 0.f);
|
||||
}
|
||||
|
||||
float srgb_transfer_function(float a)
|
||||
{
|
||||
return .0031308f >= a ? 12.92f * a : 1.055f * powf(a, .4166666666666667f) - .055f;
|
||||
}
|
||||
|
||||
float srgb_transfer_function_inv(float a)
|
||||
{
|
||||
return .04045f < a ? powf((a + .055f) / 1.055f, 2.4f) : a / 12.92f;
|
||||
}
|
||||
|
||||
Lab linear_srgb_to_oklab(RGB c)
|
||||
{
|
||||
float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
|
||||
float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
|
||||
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
|
||||
|
||||
float l_ = cbrtf(l);
|
||||
float m_ = cbrtf(m);
|
||||
float s_ = cbrtf(s);
|
||||
|
||||
return {
|
||||
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
|
||||
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
|
||||
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
|
||||
};
|
||||
}
|
||||
|
||||
RGB oklab_to_linear_srgb(Lab c)
|
||||
{
|
||||
float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
|
||||
float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
|
||||
float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
|
||||
|
||||
float l = l_ * l_ * l_;
|
||||
float m = m_ * m_ * m_;
|
||||
float s = s_ * s_ * s_;
|
||||
|
||||
return {
|
||||
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
|
||||
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
|
||||
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
|
||||
};
|
||||
}
|
||||
|
||||
// Finds the maximum saturation possible for a given hue that fits in sRGB
|
||||
// Saturation here is defined as S = C/L
|
||||
// a and b must be normalized so a^2 + b^2 == 1
|
||||
float compute_max_saturation(float a, float b)
|
||||
{
|
||||
// Max saturation will be when one of r, g or b goes below zero.
|
||||
|
||||
// Select different coefficients depending on which component goes below zero first
|
||||
float k0, k1, k2, k3, k4, wl, wm, ws;
|
||||
|
||||
if (-1.88170328f * a - 0.80936493f * b > 1)
|
||||
{
|
||||
// Red component
|
||||
k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
|
||||
wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
|
||||
}
|
||||
else if (1.81444104f * a - 1.19445276f * b > 1)
|
||||
{
|
||||
// Green component
|
||||
k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
|
||||
wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blue component
|
||||
k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
|
||||
wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
|
||||
}
|
||||
|
||||
// Approximate max saturation using a polynomial:
|
||||
float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
|
||||
|
||||
// Do one step Halley's method to get closer
|
||||
// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
|
||||
// this should be sufficient for most applications, otherwise do two/three steps
|
||||
|
||||
float k_l = +0.3963377774f * a + 0.2158037573f * b;
|
||||
float k_m = -0.1055613458f * a - 0.0638541728f * b;
|
||||
float k_s = -0.0894841775f * a - 1.2914855480f * b;
|
||||
|
||||
{
|
||||
float l_ = 1.f + S * k_l;
|
||||
float m_ = 1.f + S * k_m;
|
||||
float s_ = 1.f + S * k_s;
|
||||
|
||||
float l = l_ * l_ * l_;
|
||||
float m = m_ * m_ * m_;
|
||||
float s = s_ * s_ * s_;
|
||||
|
||||
float l_dS = 3.f * k_l * l_ * l_;
|
||||
float m_dS = 3.f * k_m * m_ * m_;
|
||||
float s_dS = 3.f * k_s * s_ * s_;
|
||||
|
||||
float l_dS2 = 6.f * k_l * k_l * l_;
|
||||
float m_dS2 = 6.f * k_m * k_m * m_;
|
||||
float s_dS2 = 6.f * k_s * k_s * s_;
|
||||
|
||||
float f = wl * l + wm * m + ws * s;
|
||||
float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
|
||||
float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
|
||||
|
||||
S = S - f * f1 / (f1 * f1 - 0.5f * f * f2);
|
||||
}
|
||||
|
||||
return S;
|
||||
}
|
||||
|
||||
// finds L_cusp and C_cusp for a given hue
|
||||
// a and b must be normalized so a^2 + b^2 == 1
|
||||
LC find_cusp(float a, float b)
|
||||
{
|
||||
// First, find the maximum saturation (saturation S = C/L)
|
||||
float S_cusp = compute_max_saturation(a, b);
|
||||
|
||||
// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
|
||||
RGB rgb_at_max = oklab_to_linear_srgb({ 1, S_cusp * a, S_cusp * b });
|
||||
float L_cusp = cbrtf(1.f / fmax(fmax(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
|
||||
float C_cusp = L_cusp * S_cusp;
|
||||
|
||||
return { L_cusp , C_cusp };
|
||||
}
|
||||
|
||||
// Finds intersection of the line defined by
|
||||
// L = L0 * (1 - t) + t * L1;
|
||||
// C = t * C1;
|
||||
// a and b must be normalized so a^2 + b^2 == 1
|
||||
float find_gamut_intersection(float a, float b, float L1, float C1, float L0, LC cusp)
|
||||
{
|
||||
// Find the intersection for upper and lower half seprately
|
||||
float t;
|
||||
if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.f)
|
||||
{
|
||||
// Lower half
|
||||
|
||||
t = cusp.C * L0 / (C1 * cusp.L + cusp.C * (L0 - L1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Upper half
|
||||
|
||||
// First intersect with triangle
|
||||
t = cusp.C * (L0 - 1.f) / (C1 * (cusp.L - 1.f) + cusp.C * (L0 - L1));
|
||||
|
||||
// Then one step Halley's method
|
||||
{
|
||||
float dL = L1 - L0;
|
||||
float dC = C1;
|
||||
|
||||
float k_l = +0.3963377774f * a + 0.2158037573f * b;
|
||||
float k_m = -0.1055613458f * a - 0.0638541728f * b;
|
||||
float k_s = -0.0894841775f * a - 1.2914855480f * b;
|
||||
|
||||
float l_dt = dL + dC * k_l;
|
||||
float m_dt = dL + dC * k_m;
|
||||
float s_dt = dL + dC * k_s;
|
||||
|
||||
|
||||
// If higher accuracy is required, 2 or 3 iterations of the following block can be used:
|
||||
{
|
||||
float L = L0 * (1.f - t) + t * L1;
|
||||
float C = t * C1;
|
||||
|
||||
float l_ = L + C * k_l;
|
||||
float m_ = L + C * k_m;
|
||||
float s_ = L + C * k_s;
|
||||
|
||||
float l = l_ * l_ * l_;
|
||||
float m = m_ * m_ * m_;
|
||||
float s = s_ * s_ * s_;
|
||||
|
||||
float ldt = 3 * l_dt * l_ * l_;
|
||||
float mdt = 3 * m_dt * m_ * m_;
|
||||
float sdt = 3 * s_dt * s_ * s_;
|
||||
|
||||
float ldt2 = 6 * l_dt * l_dt * l_;
|
||||
float mdt2 = 6 * m_dt * m_dt * m_;
|
||||
float sdt2 = 6 * s_dt * s_dt * s_;
|
||||
|
||||
float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1;
|
||||
float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
|
||||
float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
|
||||
|
||||
float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
|
||||
float t_r = -r * u_r;
|
||||
|
||||
float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1;
|
||||
float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
|
||||
float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
|
||||
|
||||
float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
|
||||
float t_g = -g * u_g;
|
||||
|
||||
float b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1;
|
||||
float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
|
||||
float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
|
||||
|
||||
float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
|
||||
float t_b = -b * u_b;
|
||||
|
||||
t_r = u_r >= 0.f ? t_r : FLT_MAX;
|
||||
t_g = u_g >= 0.f ? t_g : FLT_MAX;
|
||||
t_b = u_b >= 0.f ? t_b : FLT_MAX;
|
||||
|
||||
t += fmin(t_r, fmin(t_g, t_b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
|
||||
{
|
||||
// Find the cusp of the gamut triangle
|
||||
LC cusp = find_cusp(a, b);
|
||||
|
||||
return find_gamut_intersection(a, b, L1, C1, L0, cusp);
|
||||
}
|
||||
|
||||
RGB gamut_clip_preserve_chroma(RGB rgb)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L0 = clamp(L, 0, 1);
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1 - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_project_to_0_5(RGB rgb)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L0 = 0.5;
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1 - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_project_to_L_cusp(RGB rgb)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
|
||||
float L0 = cusp.L;
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
|
||||
float L_clipped = L0 * (1 - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha = 0.05f)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float Ld = L - 0.5f;
|
||||
float e1 = 0.5f + fabs(Ld) + alpha * C;
|
||||
float L0 = 0.5f * (1.f + sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * fabs(Ld))));
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1.f - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha = 0.05f)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
|
||||
float Ld = L - cusp.L;
|
||||
float k = 2.f * (Ld > 0 ? 1.f - cusp.L : cusp.L);
|
||||
|
||||
float e1 = 0.5f * k + fabs(Ld) + alpha * C / k;
|
||||
float L0 = cusp.L + 0.5f * (sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * k * fabs(Ld))));
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1.f - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
float toe(float x)
|
||||
{
|
||||
constexpr float k_1 = 0.206f;
|
||||
constexpr float k_2 = 0.03f;
|
||||
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
|
||||
return 0.5f * (k_3 * x - k_1 + sqrtf((k_3 * x - k_1) * (k_3 * x - k_1) + 4 * k_2 * k_3 * x));
|
||||
}
|
||||
|
||||
float toe_inv(float x)
|
||||
{
|
||||
constexpr float k_1 = 0.206f;
|
||||
constexpr float k_2 = 0.03f;
|
||||
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
|
||||
return (x * x + k_1 * x) / (k_3 * (x + k_2));
|
||||
}
|
||||
|
||||
ST to_ST(LC cusp)
|
||||
{
|
||||
float L = cusp.L;
|
||||
float C = cusp.C;
|
||||
return { C / L, C / (1 - L) };
|
||||
}
|
||||
|
||||
// Returns a smooth approximation of the location of the cusp
|
||||
// This polynomial was created by an optimization process
|
||||
// It has been designed so that S_mid < S_max and T_mid < T_max
|
||||
ST get_ST_mid(float a_, float b_)
|
||||
{
|
||||
float S = 0.11516993f + 1.f / (
|
||||
+7.44778970f + 4.15901240f * b_
|
||||
+ a_ * (-2.19557347f + 1.75198401f * b_
|
||||
+ a_ * (-2.13704948f - 10.02301043f * b_
|
||||
+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_
|
||||
)))
|
||||
);
|
||||
|
||||
float T = 0.11239642f + 1.f / (
|
||||
+1.61320320f - 0.68124379f * b_
|
||||
+ a_ * (+0.40370612f + 0.90148123f * b_
|
||||
+ a_ * (-0.27087943f + 0.61223990f * b_
|
||||
+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_
|
||||
)))
|
||||
);
|
||||
|
||||
return { S, T };
|
||||
}
|
||||
|
||||
struct Cs { float C_0; float C_mid; float C_max; };
|
||||
Cs get_Cs(float L, float a_, float b_)
|
||||
{
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
|
||||
float C_max = find_gamut_intersection(a_, b_, L, 1, L, cusp);
|
||||
ST ST_max = to_ST(cusp);
|
||||
|
||||
// Scale factor to compensate for the curved part of gamut shape:
|
||||
float k = C_max / fmin((L * ST_max.S), (1 - L) * ST_max.T);
|
||||
|
||||
float C_mid;
|
||||
{
|
||||
ST ST_mid = get_ST_mid(a_, b_);
|
||||
|
||||
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
|
||||
float C_a = L * ST_mid.S;
|
||||
float C_b = (1.f - L) * ST_mid.T;
|
||||
C_mid = 0.9f * k * sqrtf(sqrtf(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));
|
||||
}
|
||||
|
||||
float C_0;
|
||||
{
|
||||
// for C_0, the shape is independent of hue, so ST are constant. Values picked to roughly be the average values of ST.
|
||||
float C_a = L * 0.4f;
|
||||
float C_b = (1.f - L) * 0.8f;
|
||||
|
||||
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
|
||||
C_0 = sqrtf(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));
|
||||
}
|
||||
|
||||
return { C_0, C_mid, C_max };
|
||||
}
|
||||
|
||||
RGB okhsl_to_srgb(HSL hsl)
|
||||
{
|
||||
float h = hsl.h;
|
||||
float s = hsl.s;
|
||||
float l = hsl.l;
|
||||
|
||||
if (l == 1.0f)
|
||||
{
|
||||
return { 1.f, 1.f, 1.f };
|
||||
}
|
||||
|
||||
else if (l == 0.f)
|
||||
{
|
||||
return { 0.f, 0.f, 0.f };
|
||||
}
|
||||
|
||||
float a_ = cosf(2.f * pi * h);
|
||||
float b_ = sinf(2.f * pi * h);
|
||||
float L = toe_inv(l);
|
||||
|
||||
Cs cs = get_Cs(L, a_, b_);
|
||||
float C_0 = cs.C_0;
|
||||
float C_mid = cs.C_mid;
|
||||
float C_max = cs.C_max;
|
||||
|
||||
float mid = 0.8f;
|
||||
float mid_inv = 1.25f;
|
||||
|
||||
float C, t, k_0, k_1, k_2;
|
||||
|
||||
if (s < mid)
|
||||
{
|
||||
t = mid_inv * s;
|
||||
|
||||
k_1 = mid * C_0;
|
||||
k_2 = (1.f - k_1 / C_mid);
|
||||
|
||||
C = t * k_1 / (1.f - k_2 * t);
|
||||
}
|
||||
else
|
||||
{
|
||||
t = (s - mid)/ (1 - mid);
|
||||
|
||||
k_0 = C_mid;
|
||||
k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
|
||||
k_2 = (1.f - (k_1) / (C_max - C_mid));
|
||||
|
||||
C = k_0 + t * k_1 / (1.f - k_2 * t);
|
||||
}
|
||||
|
||||
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
|
||||
return {
|
||||
srgb_transfer_function(rgb.r),
|
||||
srgb_transfer_function(rgb.g),
|
||||
srgb_transfer_function(rgb.b),
|
||||
};
|
||||
}
|
||||
|
||||
HSL srgb_to_okhsl(RGB rgb)
|
||||
{
|
||||
Lab lab = linear_srgb_to_oklab({
|
||||
srgb_transfer_function_inv(rgb.r),
|
||||
srgb_transfer_function_inv(rgb.g),
|
||||
srgb_transfer_function_inv(rgb.b)
|
||||
});
|
||||
|
||||
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L = lab.L;
|
||||
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
|
||||
|
||||
Cs cs = get_Cs(L, a_, b_);
|
||||
float C_0 = cs.C_0;
|
||||
float C_mid = cs.C_mid;
|
||||
float C_max = cs.C_max;
|
||||
|
||||
// Inverse of the interpolation in okhsl_to_srgb:
|
||||
|
||||
float mid = 0.8f;
|
||||
float mid_inv = 1.25f;
|
||||
|
||||
float s;
|
||||
if (C < C_mid)
|
||||
{
|
||||
float k_1 = mid * C_0;
|
||||
float k_2 = (1.f - k_1 / C_mid);
|
||||
|
||||
float t = C / (k_1 + k_2 * C);
|
||||
s = t * mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
float k_0 = C_mid;
|
||||
float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
|
||||
float k_2 = (1.f - (k_1) / (C_max - C_mid));
|
||||
|
||||
float t = (C - k_0) / (k_1 + k_2 * (C - k_0));
|
||||
s = mid + (1.f - mid) * t;
|
||||
}
|
||||
|
||||
float l = toe(L);
|
||||
return { h, s, l };
|
||||
}
|
||||
|
||||
|
||||
RGB okhsv_to_srgb(HSV hsv)
|
||||
{
|
||||
float h = hsv.h;
|
||||
float s = hsv.s;
|
||||
float v = hsv.v;
|
||||
|
||||
float a_ = cosf(2.f * pi * h);
|
||||
float b_ = sinf(2.f * pi * h);
|
||||
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
ST ST_max = to_ST(cusp);
|
||||
float S_max = ST_max.S;
|
||||
float T_max = ST_max.T;
|
||||
float S_0 = 0.5f;
|
||||
float k = 1 - S_0 / S_max;
|
||||
|
||||
// first we compute L and V as if the gamut is a perfect triangle:
|
||||
|
||||
// L, C when v==1:
|
||||
float L_v = 1 - s * S_0 / (S_0 + T_max - T_max * k * s);
|
||||
float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);
|
||||
|
||||
float L = v * L_v;
|
||||
float C = v * C_v;
|
||||
|
||||
// then we compensate for both toe and the curved top part of the triangle:
|
||||
float L_vt = toe_inv(L_v);
|
||||
float C_vt = C_v * L_vt / L_v;
|
||||
|
||||
float L_new = toe_inv(L);
|
||||
C = C * L_new / L;
|
||||
L = L_new;
|
||||
|
||||
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
|
||||
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
|
||||
|
||||
L = L * scale_L;
|
||||
C = C * scale_L;
|
||||
|
||||
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
|
||||
return {
|
||||
srgb_transfer_function(rgb.r),
|
||||
srgb_transfer_function(rgb.g),
|
||||
srgb_transfer_function(rgb.b),
|
||||
};
|
||||
}
|
||||
|
||||
HSV srgb_to_okhsv(RGB rgb)
|
||||
{
|
||||
Lab lab = linear_srgb_to_oklab({
|
||||
srgb_transfer_function_inv(rgb.r),
|
||||
srgb_transfer_function_inv(rgb.g),
|
||||
srgb_transfer_function_inv(rgb.b)
|
||||
});
|
||||
|
||||
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L = lab.L;
|
||||
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
|
||||
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
ST ST_max = to_ST(cusp);
|
||||
float S_max = ST_max.S;
|
||||
float T_max = ST_max.T;
|
||||
float S_0 = 0.5f;
|
||||
float k = 1 - S_0 / S_max;
|
||||
|
||||
// first we find L_v, C_v, L_vt and C_vt
|
||||
|
||||
float t = T_max / (C + L * T_max);
|
||||
float L_v = t * L;
|
||||
float C_v = t * C;
|
||||
|
||||
float L_vt = toe_inv(L_v);
|
||||
float C_vt = C_v * L_vt / L_v;
|
||||
|
||||
// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
|
||||
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
|
||||
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
|
||||
|
||||
L = L / scale_L;
|
||||
C = C / scale_L;
|
||||
|
||||
C = C * toe(L) / L;
|
||||
L = toe(L);
|
||||
|
||||
// we can now compute v and s:
|
||||
|
||||
float v = L / L_v;
|
||||
float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
|
||||
|
||||
return { h, s, v };
|
||||
}
|
||||
|
||||
} // namespace ok_color
|
||||
@ -1,200 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
|
||||
int w = graphics.measure_text(t, s);
|
||||
p.x += (53 / 2) - (w / 2);
|
||||
p.y += (11 / 2);
|
||||
graphics.text(t, Point(p.x, p.y), -1, s, a);
|
||||
//graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
|
||||
//graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
|
||||
//graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
|
||||
}
|
||||
|
||||
struct star_t {
|
||||
float dx, dy, x, y, a;
|
||||
|
||||
uint8_t brightness() {
|
||||
int b = a / 5;
|
||||
return b > 15 ? 15 : b;
|
||||
}
|
||||
};
|
||||
|
||||
void init_star(star_t &s) {
|
||||
s.x = ((rand() % 100) / 5.0f) - 10.0f;
|
||||
s.y = ((rand() % 100) / 10.0f) - 5.0f;
|
||||
|
||||
s.dx = s.x / 10.0f;
|
||||
s.dy = s.y / 10.0f;
|
||||
s.a = 0;
|
||||
}
|
||||
|
||||
void step_star(star_t &s) {
|
||||
s.x += s.dx;
|
||||
s.y += s.dy;
|
||||
s.a++;
|
||||
|
||||
if(s.a > 100) {
|
||||
init_star(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
uint8_t hue_map[53][3];
|
||||
for(int i = 0; i < 53; i++) {
|
||||
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
star_t stars[100];
|
||||
for(int i = 0; i < 100; i++) {
|
||||
init_star(stars[i]);
|
||||
stars[i].a = i;
|
||||
}
|
||||
|
||||
gpio_set_function(28, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(28, GPIO_OUT);
|
||||
|
||||
for(int i = 0; i < 10; i++) {
|
||||
gpio_put(28, !gpio_get(28));
|
||||
sleep_ms(100);
|
||||
}
|
||||
sleep_ms(1000);
|
||||
|
||||
gpio_put(28,true);
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
/*
|
||||
bool a_pressed = false;
|
||||
bool b_pressed = false;
|
||||
bool x_pressed = false;
|
||||
bool y_pressed = false;
|
||||
*/
|
||||
graphics.set_font("bitmap8");
|
||||
|
||||
|
||||
|
||||
float i = 0;
|
||||
|
||||
float hue_offset = 0.0f;
|
||||
|
||||
bool animate = true;
|
||||
|
||||
float stripe_width = 3.0f;
|
||||
float speed = 1.0f;
|
||||
float curve = 0.0f;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(animate) {
|
||||
i += speed;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_UP)) {
|
||||
curve += 0.05;
|
||||
if(hue_offset > 1.0f) hue_offset = 1.0f;
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_DOWN)) {
|
||||
curve -= 0.05;
|
||||
if(hue_offset < 0.0f) hue_offset = 0.0f;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_SLEEP)) {
|
||||
animate = false;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
|
||||
speed += 0.05f;
|
||||
speed = speed >= 10.0f ? 10.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
|
||||
speed -= 0.05f;
|
||||
speed = speed <= 0.0f ? 0.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_C)) {
|
||||
stripe_width += 0.05f;
|
||||
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_D)) {
|
||||
stripe_width -= 0.05f;
|
||||
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
|
||||
}
|
||||
|
||||
/*
|
||||
graphics.set_pen(255, 255, 255);
|
||||
float s = 0.65f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
float x = (sin(i / 74.0f) * 80.0f) * s;
|
||||
float y = (cos(i / 43.0f) * 6.0f) * s;
|
||||
text("Chester smells!", Point(x, y - 3), s, a);
|
||||
*/
|
||||
|
||||
for(int x = 0; x < 53; x++) {
|
||||
for(int y = 0; y < 11; y++) {
|
||||
int v = ((sin((x + y) / stripe_width + (sin((y * 3.1415927f * 2.0f) / 11.0f) * curve) + i / 15.0f) + 1.5f) / 2.5f) * 255.0f;
|
||||
|
||||
uint8_t r = (hue_map[x][0] * v) / 256;
|
||||
uint8_t g = (hue_map[x][1] * v) / 256;
|
||||
uint8_t b = (hue_map[x][2] * v) / 256;
|
||||
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
}
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
printf("%d\n", galactic_unicorn.light());
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
|
||||
int w = graphics.measure_text(t, s);
|
||||
p.x += (53 / 2) - (w / 2);
|
||||
p.y += (11 / 2);
|
||||
graphics.text(t, Point(p.x, p.y), -1, s, a);
|
||||
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
|
||||
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
|
||||
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
uint8_t hue_map[53][3];
|
||||
for(int i = 0; i < 53; i++) {
|
||||
from_hsv(i / 53.0f, 1.0f, 0.1f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
graphics.set_font("sans");
|
||||
uint i = 0;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
|
||||
i++;
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
|
||||
float x = (sin((i) / 50.0f) * 90.0f);
|
||||
float y = (cos((i) / 40.0f) * 5.0f);
|
||||
graphics.set_pen(255, 255, 255);
|
||||
text("Galactic Unicorn", Point(x, y), s, a);
|
||||
|
||||
uint8_t *p = (uint8_t *)graphics.frame_buffer;
|
||||
for(size_t i = 0; i < 53 * 11; i++) {
|
||||
int x = i % 53;
|
||||
int y = i / 53;
|
||||
uint r = *p++;
|
||||
uint g = *p++;
|
||||
uint b = *p++;
|
||||
p++;
|
||||
|
||||
if(r > 0) {
|
||||
r = hue_map[x][0];
|
||||
g = hue_map[x][1];
|
||||
b = hue_map[x][2];
|
||||
}
|
||||
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
float scroll = -53.0f;
|
||||
|
||||
while(true) {
|
||||
//uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.25f;
|
||||
|
||||
if(scroll > width) {
|
||||
scroll = -53.0f;
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
ok_color::HSL hsl{scroll / 100.0f, 1.0f, 0.5f};
|
||||
ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl);
|
||||
graphics.set_pen(rgb.r * 255, rgb.g * 255, rgb.b * 255);
|
||||
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
|
||||
/*graphics.text(message, Point(0 - scroll + 1, 5), -1, 0.5);
|
||||
graphics.text(message, Point(0 - scroll, 5 + 1), -1, 0.5);
|
||||
graphics.text(message, Point(0 - scroll + 1, 5 + 1), -1, 0.5);*/
|
||||
/*
|
||||
for(int x = 0; x < 53; x++) {
|
||||
for(int y = 0; y < 2; y++) {
|
||||
float b = sin((x - y * 3 + int(scroll) + 100) / 3.0f);
|
||||
graphics.set_pen(180 * b, 150 * b, 0);
|
||||
graphics.pixel(Point(52 - x, y));
|
||||
graphics.pixel(Point(52 - x, y + 9));
|
||||
}
|
||||
}*/
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
- [Feature Test With Audio](#feature-test-with-audio)
|
||||
- [Fire Effect](#fire-effect)
|
||||
- [Lava Lamp](#lava-lamp)
|
||||
- [Light Sensor](#light-sensor)
|
||||
- [Nostalgia Prompt](#nostalgia-prompt)
|
||||
- [Rainbow](#rainbow)
|
||||
- [Scrolling Text](#scrolling-text)
|
||||
@ -85,6 +86,14 @@ A pretty, procedural fire effect. Switch between landscape fire and vertical fir
|
||||
|
||||
A 70s-tastic, procedural rainbow lava lamp. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Light Sensor
|
||||
|
||||
[light_sensor.py](light_sensor.py)
|
||||
|
||||
Reads data from the on board light sensor and displays the brightness level of the environment. The display is by default set to auto brightness i.e reacts to the brightness of the environment.
|
||||
- Button A turns auto brightness off
|
||||
- Button B turns auto brightness on
|
||||
|
||||
### Nostalgia Prompt
|
||||
|
||||
[nostalgia_prompt.py](nostalgia_prompt.py)
|
||||
@ -174,3 +183,6 @@ If you want to get the demo reel that Cosmic Unicorn ships with back, copy the c
|
||||
Here are some cool Cosmic Unicorn community projects and resources that you might find useful / inspirational! Note that code at the links below has not been tested by us and we're not able to offer support with it.
|
||||
|
||||
- :link: [Green Energy Display with Cosmic Unicorn](https://www.hackster.io/andreas-motzek/clock-and-green-energy-display-with-cosmic-unicorn-641dcb)
|
||||
- :link: [cosmic-emoji-react - paint emojis from a computer, phone or tablet](https://github.com/chriscareycode/cosmic-unicorn/tree/main/cosmic-emoji-react)
|
||||
- :link: [cosmic-paste - paste images from the clipboard to Cosmic Unicorn](https://github.com/chriscareycode/cosmic-unicorn/tree/main/cosmic-paste)
|
||||
- :link: [Halloweenicorn - PIR enabled scarer](https://github.com/mrglennjones/Halloweenicorn)
|
||||
BIN
examples/cosmic_unicorn/audio/Pew1.wav
Normal file
BIN
examples/cosmic_unicorn/audio/Pew2.wav
Normal file
BIN
examples/cosmic_unicorn/audio/Pew3.wav
Normal file
276
examples/cosmic_unicorn/audio/audio.py
Normal file
@ -0,0 +1,276 @@
|
||||
# SPDX-FileCopyrightText: 2023 Christopher Parrott for Pimoroni Ltd
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import os
|
||||
import math
|
||||
import struct
|
||||
from machine import I2S, Pin
|
||||
|
||||
"""
|
||||
A class for playing Wav files out of an I2S audio amp. It can also play pure tones.
|
||||
This code is based heavily on the work of Mike Teachman, at:
|
||||
https://github.com/miketeachman/micropython-i2s-examples/blob/master/examples/wavplayer.py
|
||||
"""
|
||||
|
||||
|
||||
class WavPlayer:
|
||||
# Internal states
|
||||
PLAY = 0
|
||||
PAUSE = 1
|
||||
FLUSH = 2
|
||||
STOP = 3
|
||||
NONE = 4
|
||||
|
||||
MODE_WAV = 0
|
||||
MODE_TONE = 1
|
||||
|
||||
# Default buffer length
|
||||
SILENCE_BUFFER_LENGTH = 1000
|
||||
WAV_BUFFER_LENGTH = 10000
|
||||
INTERNAL_BUFFER_LENGTH = 20000
|
||||
|
||||
TONE_SAMPLE_RATE = 44_100
|
||||
TONE_BITS_PER_SAMPLE = 16
|
||||
TONE_FULL_WAVES = 2
|
||||
|
||||
def __init__(self, id, sck_pin, ws_pin, sd_pin, amp_enable=None, ibuf_len=INTERNAL_BUFFER_LENGTH, root="/"):
|
||||
self.__id = id
|
||||
self.__sck_pin = sck_pin
|
||||
self.__ws_pin = ws_pin
|
||||
self.__sd_pin = sd_pin
|
||||
self.__ibuf_len = ibuf_len
|
||||
self.__enable = None
|
||||
|
||||
if amp_enable is not None:
|
||||
self.__enable = Pin(amp_enable, Pin.OUT)
|
||||
|
||||
# Set the directory to search for files in
|
||||
self.set_root(root)
|
||||
|
||||
self.__state = WavPlayer.NONE
|
||||
self.__mode = WavPlayer.MODE_WAV
|
||||
self.__wav_file = None
|
||||
self.__loop_wav = False
|
||||
self.__first_sample_offset = None
|
||||
self.__flush_count = 0
|
||||
self.__audio_out = None
|
||||
|
||||
# Allocate a small array of blank audio samples used for silence
|
||||
self.__silence_samples = bytearray(self.SILENCE_BUFFER_LENGTH)
|
||||
|
||||
# Allocate a larger array for WAV audio samples, using a memoryview for more efficient access
|
||||
self.__wav_samples_mv = memoryview(bytearray(self.WAV_BUFFER_LENGTH))
|
||||
|
||||
# Reserve a variable for audio samples used for tones
|
||||
self.__tone_samples = None
|
||||
self.__queued_samples = None
|
||||
|
||||
def set_root(self, root):
|
||||
self.__root = root.rstrip("/") + "/"
|
||||
|
||||
def play_wav(self, wav_file, loop=False):
|
||||
if os.listdir(self.__root).count(wav_file) == 0:
|
||||
raise ValueError(f"'{wav_file}' not found")
|
||||
|
||||
self.__stop_i2s() # Stop any active playback and terminate the I2S instance
|
||||
|
||||
self.__wav_file = open(self.__root + wav_file, "rb") # Open the chosen WAV file in read-only, binary mode
|
||||
self.__loop_wav = loop # Record if the user wants the file to loop
|
||||
|
||||
# Parse the WAV file, returning the necessary parameters to initialise I2S communication
|
||||
format, sample_rate, bits_per_sample, self.__first_sample_offset, self.sample_size = WavPlayer.__parse_wav(self.__wav_file)
|
||||
|
||||
# Keep a track of total bytes read from WAV File
|
||||
self.total_bytes_read = 0
|
||||
|
||||
self.__wav_file.seek(self.__first_sample_offset) # Advance to first byte of sample data
|
||||
|
||||
self.__start_i2s(bits=bits_per_sample,
|
||||
format=format,
|
||||
rate=sample_rate,
|
||||
state=WavPlayer.PLAY,
|
||||
mode=WavPlayer.MODE_WAV)
|
||||
|
||||
def play_tone(self, frequency, amplitude):
|
||||
if frequency < 20.0 or frequency > 20_000:
|
||||
raise ValueError("frequency out of range. Expected between 20Hz and 20KHz")
|
||||
|
||||
if amplitude < 0.0 or amplitude > 1.0:
|
||||
raise ValueError("amplitude out of range. Expected 0.0 to 1.0")
|
||||
|
||||
# Create a buffer containing the pure tone samples
|
||||
samples_per_cycle = self.TONE_SAMPLE_RATE // frequency
|
||||
sample_size_in_bytes = self.TONE_BITS_PER_SAMPLE // 8
|
||||
samples = bytearray(self.TONE_FULL_WAVES * samples_per_cycle * sample_size_in_bytes)
|
||||
range = pow(2, self.TONE_BITS_PER_SAMPLE) // 2
|
||||
|
||||
format = "<h" if self.TONE_BITS_PER_SAMPLE == 16 else "<l"
|
||||
|
||||
# Populate the buffer with multiple cycles to avoid it completing too quickly and causing drop outs
|
||||
for i in range(samples_per_cycle * self.TONE_FULL_WAVES):
|
||||
sample = int((range - 1) * (math.sin(2 * math.pi * i / samples_per_cycle)) * amplitude)
|
||||
struct.pack_into(format, samples, i * sample_size_in_bytes, sample)
|
||||
|
||||
# Are we not already playing tones?
|
||||
if not (self.__mode == WavPlayer.MODE_TONE and (self.__state == WavPlayer.PLAY or self.__state == WavPlayer.PAUSE)):
|
||||
self.__stop_i2s() # Stop any active playback and terminate the I2S instance
|
||||
self.__tone_samples = samples
|
||||
self.__start_i2s(bits=self.TONE_BITS_PER_SAMPLE,
|
||||
format=I2S.MONO,
|
||||
rate=self.TONE_SAMPLE_RATE,
|
||||
state=WavPlayer.PLAY,
|
||||
mode=WavPlayer.MODE_TONE)
|
||||
else:
|
||||
self.__queued_samples = samples
|
||||
self.__state = WavPlayer.PLAY
|
||||
|
||||
def pause(self):
|
||||
if self.__state == WavPlayer.PLAY:
|
||||
self.__state = WavPlayer.PAUSE # Enter the pause state on the next callback
|
||||
|
||||
def resume(self):
|
||||
if self.__state == WavPlayer.PAUSE:
|
||||
self.__state = WavPlayer.PLAY # Enter the play state on the next callback
|
||||
|
||||
def stop(self):
|
||||
if self.__state == WavPlayer.PLAY or self.__state == WavPlayer.PAUSE:
|
||||
if self.__mode == WavPlayer.MODE_WAV:
|
||||
# Enter the flush state on the next callback and close the file
|
||||
# It is done in this order to prevent the callback entering the play
|
||||
# state after we close the file but before we change the state)
|
||||
self.__state = WavPlayer.FLUSH
|
||||
self.__wav_file.close()
|
||||
else:
|
||||
self.__state = WavPlayer.STOP
|
||||
|
||||
def is_playing(self):
|
||||
return self.__state != WavPlayer.NONE and self.__state != WavPlayer.STOP
|
||||
|
||||
def is_paused(self):
|
||||
return self.__state == WavPlayer.PAUSE
|
||||
|
||||
def __start_i2s(self, bits=16, format=I2S.MONO, rate=44_100, state=STOP, mode=MODE_WAV):
|
||||
import gc
|
||||
gc.collect()
|
||||
self.__audio_out = I2S(
|
||||
self.__id,
|
||||
sck=self.__sck_pin,
|
||||
ws=self.__ws_pin,
|
||||
sd=self.__sd_pin,
|
||||
mode=I2S.TX,
|
||||
bits=bits,
|
||||
format=format,
|
||||
rate=rate,
|
||||
ibuf=self.__ibuf_len,
|
||||
)
|
||||
|
||||
self.__state = state
|
||||
self.__mode = mode
|
||||
self.__flush_count = self.__ibuf_len // self.SILENCE_BUFFER_LENGTH + 1
|
||||
self.__audio_out.irq(self.__i2s_callback)
|
||||
self.__audio_out.write(self.__silence_samples)
|
||||
|
||||
if self.__enable is not None:
|
||||
self.__enable.on()
|
||||
|
||||
def __stop_i2s(self):
|
||||
self.stop() # Stop any active playback
|
||||
while self.is_playing(): # and wait for it to complete
|
||||
pass
|
||||
|
||||
if self.__enable is not None:
|
||||
self.__enable.off()
|
||||
|
||||
if self.__audio_out is not None:
|
||||
self.__audio_out.deinit() # Deinit any active I2S comms
|
||||
|
||||
self.__state == WavPlayer.NONE # Return to the none state
|
||||
|
||||
def __i2s_callback(self, arg):
|
||||
# PLAY
|
||||
if self.__state == WavPlayer.PLAY:
|
||||
if self.__mode == WavPlayer.MODE_WAV:
|
||||
num_read = self.__wav_file.readinto(self.__wav_samples_mv) # Read the next section of the WAV file
|
||||
self.total_bytes_read += num_read
|
||||
# Have we reached the end of the file?
|
||||
if num_read == 0:
|
||||
# Do we want to loop the WAV playback?
|
||||
if self.__loop_wav:
|
||||
_ = self.__wav_file.seek(self.__first_sample_offset) # Play again, so advance to first byte of sample data
|
||||
else:
|
||||
self.__wav_file.close() # Stop playing, so close the file
|
||||
self.__state = WavPlayer.FLUSH # and enter the flush state on the next callback
|
||||
|
||||
self.__audio_out.write(self.__silence_samples) # In both cases play silence to end this callback
|
||||
else:
|
||||
if num_read > 0 and num_read < self.WAV_BUFFER_LENGTH:
|
||||
num_read = num_read - (self.total_bytes_read - self.sample_size)
|
||||
self.__audio_out.write(self.__wav_samples_mv[: num_read]) # We are within the file, so write out the next audio samples
|
||||
else:
|
||||
if self.__queued_samples is not None:
|
||||
self.__tone_samples = self.__queued_samples
|
||||
self.__queued_samples = None
|
||||
self.__audio_out.write(self.__tone_samples)
|
||||
|
||||
# PAUSE or STOP
|
||||
elif self.__state == WavPlayer.PAUSE or self.__state == WavPlayer.STOP:
|
||||
self.__audio_out.write(self.__silence_samples) # Play silence
|
||||
|
||||
# FLUSH
|
||||
elif self.__state == WavPlayer.FLUSH:
|
||||
# Flush is used to allow the residual audio samples in the internal buffer to be written
|
||||
# to the I2S peripheral. This step avoids part of the sound file from being cut off
|
||||
if self.__flush_count > 0:
|
||||
self.__flush_count -= 1
|
||||
else:
|
||||
self.__state = WavPlayer.STOP # Enter the stop state on the next callback
|
||||
self.__audio_out.write(self.__silence_samples) # Play silence
|
||||
|
||||
# NONE
|
||||
elif self.__state == WavPlayer.NONE:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def __parse_wav(wav_file):
|
||||
chunk_ID = wav_file.read(4)
|
||||
if chunk_ID != b"RIFF":
|
||||
raise ValueError("WAV chunk ID invalid")
|
||||
_ = wav_file.read(4) # chunk_size
|
||||
format = wav_file.read(4)
|
||||
if format != b"WAVE":
|
||||
raise ValueError("WAV format invalid")
|
||||
sub_chunk1_ID = wav_file.read(4)
|
||||
if sub_chunk1_ID != b"fmt ":
|
||||
raise ValueError("WAV sub chunk 1 ID invalid")
|
||||
_ = wav_file.read(4) # sub_chunk1_size
|
||||
_ = struct.unpack("<H", wav_file.read(2))[0] # audio_format
|
||||
num_channels = struct.unpack("<H", wav_file.read(2))[0]
|
||||
|
||||
if num_channels == 1:
|
||||
format = I2S.MONO
|
||||
else:
|
||||
format = I2S.STEREO
|
||||
|
||||
sample_rate = struct.unpack("<I", wav_file.read(4))[0]
|
||||
# if sample_rate != 44_100 and sample_rate != 48_000:
|
||||
# raise ValueError(f"WAV sample rate of {sample_rate} invalid. Only 44.1KHz or 48KHz audio are supported")
|
||||
|
||||
_ = struct.unpack("<I", wav_file.read(4))[0] # byte_rate
|
||||
_ = struct.unpack("<H", wav_file.read(2))[0] # block_align
|
||||
bits_per_sample = struct.unpack("<H", wav_file.read(2))[0]
|
||||
|
||||
# usually the sub chunk2 ID ("data") comes next, but
|
||||
# some online MP3->WAV converters add
|
||||
# binary data before "data". So, read a fairly large
|
||||
# block of bytes and search for "data".
|
||||
|
||||
binary_block = wav_file.read(200)
|
||||
offset = binary_block.find(b"data")
|
||||
if offset == -1:
|
||||
raise ValueError("WAV sub chunk 2 ID not found")
|
||||
|
||||
wav_file.seek(40)
|
||||
sub_chunk2_size = struct.unpack("<I", wav_file.read(4))[0]
|
||||
|
||||
return (format, sample_rate, bits_per_sample, 44 + offset, sub_chunk2_size)
|
||||
BIN
examples/cosmic_unicorn/audio/beepboop.wav
Normal file
BIN
examples/cosmic_unicorn/audio/buttonbeep.wav
Normal file
91
examples/cosmic_unicorn/audio/countdown_with_alarm.py
Normal file
@ -0,0 +1,91 @@
|
||||
from machine import Timer
|
||||
from audio import WavPlayer
|
||||
from cosmic import CosmicUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
|
||||
import time
|
||||
|
||||
cu = CosmicUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
graphics.set_font("bitmap6")
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
BLUE = graphics.create_pen(0, 0, 255)
|
||||
CLEAR = graphics.create_pen(0, 0, 0)
|
||||
RED = graphics.create_pen(255, 0, 0)
|
||||
GREEN = graphics.create_pen(0, 255, 0)
|
||||
cu.set_brightness(0.7)
|
||||
|
||||
audio = WavPlayer(0, 10, 11, 9, amp_enable=22)
|
||||
|
||||
|
||||
class Countdown(object):
|
||||
def __init__(self):
|
||||
self.timer_running = False
|
||||
self.total_seconds = 0
|
||||
self.timer = None
|
||||
|
||||
def process_input(self):
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
|
||||
self.total_seconds += 1
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
|
||||
if self.total_seconds > 0:
|
||||
self.total_seconds -= 1
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
|
||||
self.start_timer()
|
||||
|
||||
def display_time(self):
|
||||
seconds = self.total_seconds % (24 * 3600)
|
||||
seconds %= 3600
|
||||
minutes = seconds // 60
|
||||
seconds %= 60
|
||||
|
||||
# Add leading zeros to the minutes and seconds
|
||||
if len(str(minutes)) == 1:
|
||||
minutes = "0{}".format(minutes)
|
||||
if len(str(seconds)) == 1:
|
||||
seconds = "0{}".format(seconds)
|
||||
|
||||
return "{}:{}".format(minutes, seconds)
|
||||
|
||||
def draw(self):
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
|
||||
graphics.set_pen(BLUE)
|
||||
graphics.circle(0, 0, 12)
|
||||
graphics.set_pen(GREEN)
|
||||
graphics.circle(25, 30, 5)
|
||||
graphics.set_pen(RED)
|
||||
graphics.circle(0, 32, 12)
|
||||
|
||||
graphics.set_pen(CLEAR)
|
||||
graphics.rectangle(0, 11, CosmicUnicorn.WIDTH, 9)
|
||||
|
||||
graphics.set_pen(WHITE)
|
||||
graphics.text(self.display_time(), 4, 12, -1, 1)
|
||||
cu.update(graphics)
|
||||
|
||||
def start_timer(self):
|
||||
if not self.timer_running:
|
||||
self.timer = Timer(mode=Timer.PERIODIC, period=1000, callback=self.countdown)
|
||||
self.timer_running = True
|
||||
|
||||
def reset(self):
|
||||
self.timer.deinit()
|
||||
self.timer_running = False
|
||||
|
||||
def countdown(self, arg):
|
||||
|
||||
if self.total_seconds == 0:
|
||||
audio.play_wav("doorbell.wav", False)
|
||||
self.reset()
|
||||
else:
|
||||
self.total_seconds -= 1
|
||||
|
||||
|
||||
count = Countdown()
|
||||
|
||||
while 1:
|
||||
count.process_input()
|
||||
count.draw()
|
||||
time.sleep(0.07)
|
||||
BIN
examples/cosmic_unicorn/audio/doorbell.wav
Normal file
74
examples/cosmic_unicorn/audio/example_menu_with_sound.py
Normal file
@ -0,0 +1,74 @@
|
||||
from audio import WavPlayer
|
||||
from cosmic import CosmicUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
|
||||
from time import sleep
|
||||
|
||||
cu = CosmicUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
audio = WavPlayer(0, 10, 11, 9, amp_enable=22)
|
||||
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
RED = graphics.create_pen(255, 0, 0)
|
||||
|
||||
|
||||
class Menu(object):
|
||||
|
||||
def __init__(self):
|
||||
self.items = ["Pew 1", "Pew 2", "Pew 3"]
|
||||
self.selected = 0
|
||||
|
||||
# A function to draw only the menu elements.
|
||||
# Helps to keep our main function tidy!
|
||||
def draw_menu(self):
|
||||
graphics.set_pen(WHITE)
|
||||
|
||||
for item in range(len(self.items)):
|
||||
if self.selected == item:
|
||||
graphics.set_pen(RED)
|
||||
|
||||
graphics.text(self.items[item], 0, 2 + item * 10, 31, 1)
|
||||
graphics.set_pen(WHITE)
|
||||
|
||||
# Make changes based on the currently selected menu item
|
||||
def process_selected(self):
|
||||
if self.selected == 0:
|
||||
audio.play_wav("Pew1.wav", False)
|
||||
|
||||
if self.selected == 1:
|
||||
audio.play_wav("Pew2.wav", False)
|
||||
|
||||
if self.selected == 2:
|
||||
audio.play_wav("Pew3.wav", False)
|
||||
|
||||
|
||||
menu = Menu()
|
||||
graphics.set_font("bitmap6")
|
||||
cu.set_brightness(0.7)
|
||||
|
||||
while True:
|
||||
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
audio.play_wav("buttonbeep.wav", False)
|
||||
if menu.selected + 1 < len(menu.items):
|
||||
menu.selected += 1
|
||||
else:
|
||||
menu.selected = 0
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
audio.play_wav("buttonbeep.wav", False)
|
||||
if menu.selected > 0:
|
||||
menu.selected -= 1
|
||||
else:
|
||||
menu.selected = len(menu.items) - 1
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
|
||||
menu.process_selected()
|
||||
|
||||
menu.draw_menu()
|
||||
|
||||
cu.update(graphics)
|
||||
sleep(0.2)
|
||||
8
examples/cosmic_unicorn/audio/simple_playback.py
Normal file
@ -0,0 +1,8 @@
|
||||
from audio import WavPlayer
|
||||
|
||||
sound = WavPlayer(0, 10, 11, 9, amp_enable=22)
|
||||
|
||||
sound.play_wav("beepboop.wav", False)
|
||||
|
||||
while sound.is_playing():
|
||||
pass
|
||||
@ -158,11 +158,13 @@ down_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, mach
|
||||
|
||||
|
||||
def adjust_utc_offset(pin):
|
||||
global utc_offset
|
||||
global utc_offset, last_second
|
||||
if pin == up_button:
|
||||
utc_offset += 1
|
||||
last_second = None
|
||||
if pin == down_button:
|
||||
utc_offset -= 1
|
||||
last_second = None
|
||||
|
||||
|
||||
up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
|
||||
@ -214,9 +216,11 @@ sync_time()
|
||||
while True:
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
cu.adjust_brightness(+0.01)
|
||||
last_second = None
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
cu.adjust_brightness(-0.01)
|
||||
last_second = None
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
|
||||
sync_time()
|
||||
@ -152,8 +152,6 @@ tone_b = 0
|
||||
# The current synth beat
|
||||
beat = 0
|
||||
|
||||
text = ""
|
||||
|
||||
|
||||
def next_beat():
|
||||
global beat
|
||||
@ -312,6 +310,8 @@ while True:
|
||||
# print("white gradient")
|
||||
gradient(255, 255, 255)
|
||||
|
||||
text = ""
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
|
||||
text = "PlaySyn"
|
||||
|
||||
@ -63,20 +63,12 @@ def status_handler(mode, status, ip):
|
||||
# Create web server application
|
||||
app = webserver()
|
||||
|
||||
# Static page
|
||||
html_file = open('index.html', 'r')
|
||||
|
||||
# WIFI settings
|
||||
WIFI_COUNTRY = "GB" # Change to your local two-letter ISO 3166-1 country code
|
||||
|
||||
|
||||
# Index page
|
||||
@app.route('/')
|
||||
async def index(request, response):
|
||||
# Start HTTP response with content-type text/html
|
||||
await response.start_html()
|
||||
# Send actual HTML page
|
||||
await response.send(html_file.read())
|
||||
await response.send_file('index.html', content_type='text/html')
|
||||
|
||||
|
||||
# HTTP redirection
|
||||
@ -120,7 +112,7 @@ def outline_text(text, x, y):
|
||||
|
||||
def run():
|
||||
# Setup wifi
|
||||
network_manager = NetworkManager(WIFI_COUNTRY, status_handler=status_handler)
|
||||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
|
||||
app.add_resource(text, '/update')
|
||||
|
||||
165
examples/cosmic_unicorn/light_sensor.py
Normal file
@ -0,0 +1,165 @@
|
||||
import time
|
||||
from cosmic import CosmicUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
|
||||
|
||||
"""
|
||||
Light sensoring feature for the cosmic unicorn
|
||||
Uses the onboard light sensor to detect the light in the environment
|
||||
The brightness level is displayed as percentage.
|
||||
Brightness of the display is auto adjusted to the brightness level of the environment
|
||||
Press A to turn auto brightness on
|
||||
Press B to turn auto brightness off
|
||||
"""
|
||||
# set up unicorn and drawing variables
|
||||
cu = CosmicUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
WIDTH, HEIGHT = graphics.get_bounds()
|
||||
BLACK = graphics.create_pen(0, 0, 0)
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
GREY = graphics.create_pen(20, 20, 20)
|
||||
HUE_START = 0
|
||||
HUE_END = 100
|
||||
graphics.set_font("bitmap8")
|
||||
|
||||
# Text display sleep time in ms
|
||||
TEXT_SLEEP = 500
|
||||
|
||||
|
||||
# the onboard light sensor has a wide range from 0 t0 4095
|
||||
# It is therefore needed to set a lower max and a higher minimum
|
||||
MIN_LS_VALUE = 10
|
||||
MAX_LS_VALUE = 295 # 4095 to use the full range
|
||||
MIN_RANGE = 0.1
|
||||
MAX_RANGE = 1
|
||||
|
||||
# Rate of display change i.e the lower the value the slower the transition
|
||||
TRANSITION_RATE = 1.0 / 72.0
|
||||
|
||||
|
||||
# perform linear interpolation to map a range of values to discrete
|
||||
def map_range(
|
||||
x,
|
||||
min_input=MIN_LS_VALUE,
|
||||
max_input=MAX_LS_VALUE,
|
||||
min_output=MIN_RANGE,
|
||||
max_output=MAX_RANGE,
|
||||
):
|
||||
return (x - min_input) * (max_output - min_output) / (
|
||||
max_input - min_input
|
||||
) + min_output
|
||||
|
||||
|
||||
# gets the light sensor value from onboard sensor and interpolates it
|
||||
# clamps the brightness value it outside the ranges specified
|
||||
def calculate_brightness(prev_brightness_val):
|
||||
current_lsv = cu.light()
|
||||
current_brightness_val = map_range(current_lsv)
|
||||
|
||||
# uses the previous value to smooth out display changes reducing flickering
|
||||
brightness_diff = current_brightness_val - prev_brightness_val
|
||||
brightness_val = prev_brightness_val + (brightness_diff * TRANSITION_RATE)
|
||||
if brightness_val > 1:
|
||||
brightness_val = 1
|
||||
elif brightness_val < 0.1:
|
||||
brightness_val = 0.1
|
||||
|
||||
return brightness_val
|
||||
|
||||
|
||||
# sets up a handy function we can call to clear the screen
|
||||
def clear():
|
||||
graphics.set_pen(BLACK)
|
||||
graphics.clear()
|
||||
|
||||
|
||||
def draw_percentage(x, y):
|
||||
graphics.rectangle(x + 1, y + 1, 2, 2)
|
||||
graphics.line(x + 1, y + 5, x + 6, y)
|
||||
graphics.rectangle(x + 4, y + 4, 2, 2)
|
||||
|
||||
|
||||
# draws a sun icon
|
||||
def draw_sun(x, y, r):
|
||||
circle_x = x + 3 + r
|
||||
circle_y = y + 3 + r
|
||||
graphics.circle(circle_x, circle_y, r)
|
||||
graphics.line(circle_x, y, circle_x, y + 2)
|
||||
graphics.line(x, circle_y, x + 2, circle_y)
|
||||
graphics.line(circle_x, (y + 5 + 2 * r), circle_x, (y + 5 + 2 * r) + 2)
|
||||
graphics.line((x + 5 + 2 * r), circle_y, (x + 5 + 2 * r) + 2, circle_y)
|
||||
graphics.line(
|
||||
circle_x + 1 + r, circle_y - 1 - r, circle_x + 3 + r, circle_y - 3 - r
|
||||
)
|
||||
graphics.line(
|
||||
circle_x + 1 + r, circle_y + 1 + r, circle_x + 3 + r, circle_y + 3 + r
|
||||
)
|
||||
graphics.line(
|
||||
circle_x - 1 - r, circle_y - 1 - r, circle_x - 3 - r, circle_y - 3 - r
|
||||
)
|
||||
graphics.line(
|
||||
circle_x - 1 - r, circle_y + 1 + r, circle_x - 3 - r, circle_y + 3 + r
|
||||
)
|
||||
|
||||
|
||||
mode = "auto"
|
||||
last = time.ticks_ms()
|
||||
|
||||
brightness_value = MIN_RANGE # set the initial brightness level to the minimum
|
||||
while True:
|
||||
current = time.ticks_ms()
|
||||
|
||||
# set the display brightness
|
||||
brightness_value = calculate_brightness(brightness_value)
|
||||
cu.set_brightness(brightness_value)
|
||||
|
||||
bp = (brightness_value / MAX_RANGE) * 100 # gets brightness value in percentage relative to the MAX_LS_VALUE set
|
||||
|
||||
# deactivate auto brightness by pressing A
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
|
||||
print("Auto brightness off")
|
||||
mode = "off"
|
||||
|
||||
# reactivate auto brightness by pressing A
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
|
||||
print("Auto brightness on")
|
||||
mode = "auto"
|
||||
|
||||
# set brightness to default value if off
|
||||
if mode == "off":
|
||||
cu.set_brightness(0.5)
|
||||
|
||||
# set text update rate after a certain time to reduce flickering
|
||||
if current - last >= TEXT_SLEEP:
|
||||
clear()
|
||||
|
||||
# calculate colour from the brightness value
|
||||
hue = max(0, HUE_START + ((bp - 0) * (HUE_END - HUE_START) / (100 - 0)))
|
||||
|
||||
# create pens with this colour (and with the high / low colours)
|
||||
CURRENT_COLOUR = graphics.create_pen_hsv(hue / 360, 1.0, 0.8)
|
||||
HIGH_COLOUR = graphics.create_pen_hsv(HUE_END / 360, 1.0, 0.8)
|
||||
LOW_COLOUR = graphics.create_pen_hsv(HUE_START / 360, 1.0, 0.8)
|
||||
|
||||
# draw the text
|
||||
graphics.set_pen(CURRENT_COLOUR)
|
||||
graphics.text("BRT: ", 0, 1, scale=1)
|
||||
graphics.text(f"{bp:.0f}", 7, 23, scale=1)
|
||||
draw_percentage((WIDTH - 10), 23)
|
||||
|
||||
# draw sun icon
|
||||
draw_sun(0, 10, 2)
|
||||
|
||||
# draw a bar for the background
|
||||
bar_width = WIDTH - 12
|
||||
graphics.set_pen(GREY)
|
||||
graphics.rectangle(13, 10, bar_width, 11)
|
||||
|
||||
# draw a bar for the current brightness percentage
|
||||
graphics.set_pen(CURRENT_COLOUR)
|
||||
graphics.rectangle(13, 10, int((bp / 100) * bar_width), 11)
|
||||
|
||||
last = current
|
||||
|
||||
# time to update the display
|
||||
cu.update(graphics)
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |