From a5b2f836033ee621188f2f91fa952c7bfc39cefa Mon Sep 17 00:00:00 2001 From: Alex Wulff Date: Mon, 27 Jun 2022 13:53:37 -0400 Subject: [PATCH] Actually added new directories --- pico-light-voice | 1 - pico-light-voice/.gitignore | 5 + pico-light-voice/CMakeLists.txt | 88 +++ pico-light-voice/LICENSE | 201 +++++ pico-light-voice/README.md | 15 + .../pico_neopixels/Adafruit_NeoPixel.cpp | 712 ++++++++++++++++++ .../pico_neopixels/CMakeLists.txt | 15 + .../include/Adafruit_NeoPixel.h | 350 +++++++++ .../include/Adafruit_NeoPixel.hpp | 360 +++++++++ .../pico_neopixels/pico_sdk_import.cmake | 62 ++ .../pico_neopixels/ws2812byte.pio | 46 ++ pico-light-voice/pico_sdk_import.cmake | 64 ++ pico-light-voice/source/main.cpp | 217 ++++++ pico-voice-v1 | 1 - pico-voice-v1/.gitignore | 5 + pico-voice-v1/CMakeLists.txt | 68 ++ pico-voice-v1/LICENSE | 201 +++++ pico-voice-v1/README.md | 12 + pico-voice-v1/pico_sdk_import.cmake | 64 ++ pico-voice-v1/source/main.cpp | 143 ++++ 20 files changed, 2628 insertions(+), 2 deletions(-) delete mode 160000 pico-light-voice create mode 100644 pico-light-voice/.gitignore create mode 100755 pico-light-voice/CMakeLists.txt create mode 100644 pico-light-voice/LICENSE create mode 100644 pico-light-voice/README.md create mode 100644 pico-light-voice/pico_neopixels/Adafruit_NeoPixel.cpp create mode 100644 pico-light-voice/pico_neopixels/CMakeLists.txt create mode 100644 pico-light-voice/pico_neopixels/include/Adafruit_NeoPixel.h create mode 100644 pico-light-voice/pico_neopixels/include/Adafruit_NeoPixel.hpp create mode 100644 pico-light-voice/pico_neopixels/pico_sdk_import.cmake create mode 100644 pico-light-voice/pico_neopixels/ws2812byte.pio create mode 100644 pico-light-voice/pico_sdk_import.cmake create mode 100644 pico-light-voice/source/main.cpp delete mode 160000 pico-voice-v1 create mode 100644 pico-voice-v1/.gitignore create mode 100755 pico-voice-v1/CMakeLists.txt create mode 100644 pico-voice-v1/LICENSE create mode 100644 pico-voice-v1/README.md create mode 100644 pico-voice-v1/pico_sdk_import.cmake create mode 100644 pico-voice-v1/source/main.cpp diff --git a/pico-light-voice b/pico-light-voice deleted file mode 160000 index 5a82588..0000000 --- a/pico-light-voice +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5a825884753207656080b091d87490348452670c diff --git a/pico-light-voice/.gitignore b/pico-light-voice/.gitignore new file mode 100644 index 0000000..02c448c --- /dev/null +++ b/pico-light-voice/.gitignore @@ -0,0 +1,5 @@ +build/ +*.DS_Store +edge-impulse-sdk/ +model-parameters/ +tflite-model/ \ No newline at end of file diff --git a/pico-light-voice/CMakeLists.txt b/pico-light-voice/CMakeLists.txt new file mode 100755 index 0000000..e497e57 --- /dev/null +++ b/pico-light-voice/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.13.1) + +set(MODEL_FOLDER .) +set(EI_SDK_FOLDER edge-impulse-sdk) + +include(pico_sdk_import.cmake) + +project(pico-voice C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 11) + +pico_sdk_init() + +add_executable(pico-voice + source/main.cpp + ) + +include(${MODEL_FOLDER}/edge-impulse-sdk/cmake/utils.cmake) + +# enable usb output, disable uart output +pico_enable_stdio_usb(pico-voice 1) +pico_enable_stdio_uart(pico-voice 0) + +target_include_directories(pico-voice PRIVATE + ${MODEL_FOLDER} + ${MODEL_FOLDER}/classifer + ${MODEL_FOLDER}/tflite-model + ${MODEL_FOLDER}/model-parameters + ) + +target_include_directories(pico-voice PRIVATE + ${EI_SDK_FOLDER} + ${EI_SDK_FOLDER}/third_party/ruy + ${EI_SDK_FOLDER}/third_party/gemmlowp + ${EI_SDK_FOLDER}/third_party/flatbuffers/include + ${EI_SDK_FOLDER}/third_party + ${EI_SDK_FOLDER}/tensorflow + ${EI_SDK_FOLDER}/dsp + ${EI_SDK_FOLDER}/classifier + ${EI_SDK_FOLDER}/anomaly + ${EI_SDK_FOLDER}/CMSIS/NN/Include + ${EI_SDK_FOLDER}/CMSIS/DSP/PrivateInclude + ${EI_SDK_FOLDER}/CMSIS/DSP/Include + ${EI_SDK_FOLDER}/CMSIS/Core/Include + ) + +include_directories(${INCLUDES}) + +# find model source files +RECURSIVE_FIND_FILE(MODEL_FILES "${MODEL_FOLDER}/tflite-model" "*.cpp") +RECURSIVE_FIND_FILE(SOURCE_FILES "${EI_SDK_FOLDER}" "*.cpp") +RECURSIVE_FIND_FILE(CC_FILES "${EI_SDK_FOLDER}" "*.cc") +RECURSIVE_FIND_FILE(S_FILES "${EI_SDK_FOLDER}" "*.s") +RECURSIVE_FIND_FILE(C_FILES "${EI_SDK_FOLDER}" "*.c") +list(APPEND SOURCE_FILES ${S_FILES}) +list(APPEND SOURCE_FILES ${C_FILES}) +list(APPEND SOURCE_FILES ${CC_FILES}) +list(APPEND SOURCE_FILES ${MODEL_FILES}) + +# add all sources to the project +target_sources(pico-voice PRIVATE ${SOURCE_FILES}) + +# now do Neopixel Library +add_library(pico_neopixel INTERFACE) + +pico_generate_pio_header(pico_neopixel ${CMAKE_CURRENT_LIST_DIR}/pico_neopixels/ws2812byte.pio) + +target_sources(pico_neopixel INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/pico_neopixels/Adafruit_NeoPixel.cpp +) + +pico_enable_stdio_usb(pico_neopixel 1) +pico_enable_stdio_uart(pico_neopixel 0) + +target_include_directories(pico_neopixel INTERFACE ${CMAKE_CURRENT_LIST_DIR}/pico_neopixels/include) + +target_link_libraries(pico_neopixel INTERFACE pico_stdlib hardware_pio pico_malloc pico_mem_ops) + +# Finish up +target_link_libraries(pico-voice + hardware_adc + hardware_dma + pico_stdlib + pico_neopixel + pico_multicore + ) + +pico_add_extra_outputs(pico-voice) diff --git a/pico-light-voice/LICENSE b/pico-light-voice/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/pico-light-voice/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pico-light-voice/README.md b/pico-light-voice/README.md new file mode 100644 index 0000000..515c7f1 --- /dev/null +++ b/pico-light-voice/README.md @@ -0,0 +1,15 @@ +# pico-light-voice1 + +This program combines a voice recognition model with Neopixels to make a low-cost lighting controller. Much of the ML code is from Edge Impulse's pico [standalone repo](https://github.com/edgeimpulse/example-standalone-inferencing-pico), and the Neopixel code is from [this fantastic port](https://github.com/martinkooij/pi-pico-adafruit-neopixels) that converts the Neopixel routines to efficient PIO code. + +To use this starting point, you need to drop in the `edge-impulse-sdk`, `model-parameters`, and `tflite-model` folders generated when exporting your model for C++. + +## Program operation + +This code really pushes the Pico to its limits! Simultaneously it's sampling from the ADC, controlling Neopixels using PIO, doing ML processing on one core, and operating a lighting state machine on the second core. Pretty neat! + +## Modifications + +If you train your model on floating-point WAV files sampled at 5 kHz (see the pico-daq folder in this repository) then you shouldn't need to change much other than the results of the inferencing. + +If you trained your data on some other format, you will need to modify how data gets copied into the `features` buffer as well as things like the sample rate. \ No newline at end of file diff --git a/pico-light-voice/pico_neopixels/Adafruit_NeoPixel.cpp b/pico-light-voice/pico_neopixels/Adafruit_NeoPixel.cpp new file mode 100644 index 0000000..81778b4 --- /dev/null +++ b/pico-light-voice/pico_neopixels/Adafruit_NeoPixel.cpp @@ -0,0 +1,712 @@ +/*! + * @file Adafruit_NeoPixel.cpp + * + * @mainpage Arduino Library for driving Adafruit NeoPixel addressable LEDs, + * FLORA RGB Smart Pixels and compatible devicess -- WS2811, WS2812, WS2812B, + * SK6812, etc. + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's NeoPixel library for the + * Arduino platform, allowing a broad range of microcontroller boards + * (most AVR boards, many ARM devices, ESP8266 and ESP32, among others) + * to control Adafruit NeoPixels, FLORA RGB Smart Pixels and compatible + * devices -- WS2811, WS2812, WS2812B, SK6812, etc. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing products + * from Adafruit! + * + * @section author Author + * + * Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries, + * with contributions by PJRC, Michael Miller and other members of the + * open source community. + * + * @section license License + * + * This file is part of the Adafruit_NeoPixel library. + * + * Adafruit_NeoPixel is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Adafruit_NeoPixel is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with NeoPixel. If not, see + * . + * + */ + +#include "Adafruit_NeoPixel.hpp" +#include "pico/stdio.h" +#include "pico/malloc.h" +//#include "pico/mem_ops.h" +#include +#include +#include + +//#define DEBUG0 // high level debugging +//#define DEBUG1 // low level debugging + +#ifdef DEBUG0 +#define PRINTF0(...) printf(__VA_ARGS__) +#else +#define PRINTF0(...) +#endif + +#ifdef DEBUG1 +#define PRINTF1(...) printf(__VA_ARGS__) +#else +#define PRINTF1(...) +#endif + + +/*! + @brief NeoPixel constructor when length, pin and pixel type are known + at compile-time. + @param n Number of NeoPixels in strand. + @param p Arduino pin number which will drive the NeoPixel data in. + @param t Pixel type -- add together NEO_* constants defined in + Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for + NeoPixels expecting an 800 KHz (vs 400 KHz) data stream + with color bytes expressed in green, red, blue order per + pixel. + @return Adafruit_NeoPixel object. Call the begin() function before use. +*/ +Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, uint16_t p, neoPixelType t) : + begun(false), brightness(0), pixels(NULL), opixels(NULL), brightfr(NULL), brightfg(NULL), brightfb(NULL), brightfw(NULL) { + PRINTF1("In constructor 1\n"); + endTime = get_absolute_time() ; + PRINTF1("In constructor 2\n"); + setPin(p); + PRINTF1("In constructor 3\n"); + updateType(t); + PRINTF1("In constructor 4\n"); + updateLength(n); + +} + +/*! + @brief "Empty" NeoPixel constructor when length, pin and/or pixel type + are not known at compile-time, and must be initialized later with + updateType(), updateLength() and setPin(). + @return Adafruit_NeoPixel object. Call the begin() function before use. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax (length, pin, + type). +*/ +Adafruit_NeoPixel::Adafruit_NeoPixel() : +#if defined(NEO_KHZ400) + is800KHz(true), +#endif + begun(false), numLEDs(0), numBytes(0), pin(-1), brightness(0), pixels(NULL), opixels(NULL), brightfr(NULL), brightfg(NULL), brightfb(NULL), brightfw(NULL), rOffset(1), gOffset(0), bOffset(2), wOffset(1){ + endTime = get_absolute_time(); +} + +/*! + @brief Deallocate Adafruit_NeoPixel object, set data pin back to INPUT, unclaim the statemachine +*/ +Adafruit_NeoPixel::~Adafruit_NeoPixel() { + PRINTF0("In destructor\n ===>\n"); + memset(pixels, 0, numBytes); + show() ; + sleep_ms(20) ; + PRINTF1("End init = %d, pin = %d, 800kHz = %d, length = %d, pio= %d, sm = %d, offset = %d, no_sm = [%d, %d]\n ", begun, pin, is800KHz, numLEDs, pio_get_index(pio), sm, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset,pio_no_sm[0], pio_no_sm[1] ); + PRINTF1("going to free\n"); + free(pixels); // unclaim the memory for the pixels + free(opixels); // unclaim the memory for the pixels + PRINTF1("freed pixels\n"); + pio_sm_unclaim(pio,sm); // unclaim the state machine + pio_no_sm[pio_get_index(pio)]-- ; + if (pio_no_sm[pio_get_index(pio)] == 0 ) { // if no sm on the pio instance, remove the program + pio_remove_program (pio, &ws2812byte_program, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset ); + if (pio_get_index(pio) == 0) {pio0_offset = -1;} else {pio1_offset = -1;}; + }; + + PRINTF0("End destruructor = %d, pin = %d, 800kHz = %d, length = %d, pio= %d, sm = %d, offset = %d, no_sm = [%d, %d]\n ", begun, pin, is800KHz, numLEDs, pio_get_index(pio), sm, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset,pio_no_sm[0], pio_no_sm[1] ); +} + +/*! + @brief Configure NeoPixel pin for output. +*/ +void Adafruit_NeoPixel::begin(void) { + // backwards comptability + // PRINTF0("In begin Begun = %d, pin = %d, length = %d\n", begun, pin, numLEDs); +} + +/*! + @brief Change the length of a previously-declared Adafruit_NeoPixel + strip object. Old data is deallocated and new data is cleared. + Pin number and pixel format are unchanged. + @param n New length of strip, in pixels. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax (length, pin, + type). +*/ +void Adafruit_NeoPixel::updateLength(uint16_t n) { + free(pixels); // Free existing data (if any) + + // Allocate new data -- note: ALL PIXELS ARE CLEARED + numBytes = n * ((wOffset == rOffset) ? 3 : 4); + if((pixels = (uint8_t *)malloc(numBytes))) { + memset(pixels, 0, numBytes); + numLEDs = n; + } else { + numLEDs = numBytes = 0; + } + + if (brightfr != NULL) { + free(opixels) ; + if((opixels = (uint8_t *)malloc(numBytes))) { + memset(opixels, 0, numBytes); + numLEDs = n; + } else { + numLEDs = numBytes = 0; + }; + }; + +} + +/*! + @brief Change the pixel format of a previously-declared + Adafruit_NeoPixel strip object. If format changes from one of + the RGB variants to an RGBW variant (or RGBW to RGB), the old + data will be deallocated and new data is cleared. Otherwise, + the old data will remain in RAM and is not reordered to the + new format, so it's advisable to follow up with clear(). + @param t Pixel type -- add together NEO_* constants defined in + Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for + NeoPixels expecting an 800 KHz (vs 400 KHz) data stream + with color bytes expressed in green, red, blue order per + pixel. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax + (length, pin, type). +*/ +void Adafruit_NeoPixel::updateType(neoPixelType t) { + bool oldThreeBytesPerPixel = (wOffset == rOffset); // false if RGBW + + wOffset = (t >> 6) & 0b11; // See notes in header file + rOffset = (t >> 4) & 0b11; // regarding R/G/B/W offsets + gOffset = (t >> 2) & 0b11; + bOffset = t & 0b11; +#if defined(NEO_KHZ400) + is800KHz = (t < 256); // 400 KHz flag is 1<<8 +#endif + + // If bytes-per-pixel has changed (and pixel data was previously + // allocated), re-allocate to new size. Will clear any data. + if(pixels) { + bool newThreeBytesPerPixel = (wOffset == rOffset); + if(newThreeBytesPerPixel != oldThreeBytesPerPixel) updateLength(numLEDs); + } +} + + +void Adafruit_NeoPixel::rp2040Init(uint8_t set_pin) +{ + PRINTF0("IN RP2040 INIT now\n"); + // get free sm & pio and store these in the protected variables of the class + int resultsm; + bool canpio; + resultsm = pio_claim_unused_sm(pio0,false); + PRINTF1("resultsm = %d\n",resultsm); + if (resultsm != -1) { + if (pio0_offset == -1) { + canpio = pio_can_add_program(pio0, &ws2812byte_program); + PRINTF1("canpio = %d\n",canpio); + if (canpio) pio0_offset = pio_add_program(pio0, &ws2812byte_program); + }; + pio = pio0; + }; + if (resultsm == -1 || pio0_offset == -1) { + resultsm = pio_claim_unused_sm(pio1,false); + if (resultsm != -1) { + if (pio1_offset == -1) { + canpio = pio_can_add_program(pio1, &ws2812byte_program); + if (canpio) pio1_offset = pio_add_program(pio0, &ws2812byte_program); + }; + pio = pio1; + } + }; + + if (resultsm == -1 || (pio0_offset == -1 && pio1_offset == -1)) { + sm = -1 ; + return ; + } + + pio_no_sm[pio_get_index(pio)]++ ; + + pin = set_pin ; + sm = resultsm ; + + PRINTF1("End init = %d, pin = %d, 800kHz = %d, length = %d, pio= %d, sm = %d, offset = %d, no_sm = [%d, %d]\n ", begun, pin, is800KHz, numLEDs, pio_get_index(pio), sm, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset,pio_no_sm[0], pio_no_sm[1] ); + + if (is800KHz) + { + // 800kHz, 8 bit transfers + ws2812byte_program_init(pio, sm, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset, pin, 800000, 8); + } + else + { + // 400kHz, 8 bit transfers + ws2812byte_program_init(pio, sm, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset, pin, 400000, 8); + } ; + begun = true ; + PRINTF0("exit INIT pio %d, sm %d\n", pio_get_index(pio),sm); +} + +void Adafruit_NeoPixel::rp2040changepin(uint8_t set_pin) +{ + pin = set_pin ; + int resultpio = pio_get_index(pio); + + if (is800KHz) + { + // 800kHz, 8 bit transfers + ws2812byte_program_init(pio, sm, (resultpio == 0) ? pio0_offset : pio1_offset, pin, 800000, 8); + } + else + { + // 400kHz, 8 bit transfers + ws2812byte_program_init(pio, sm, (resultpio == 0) ? pio0_offset : pio1_offset, pin, 400000, 8); + } +} + +void Adafruit_NeoPixel::rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz) +{ + PRINTF0("In Show,"); + if (!begun) + { + // On first pass through initialise the PIO + rp2040Init(pin); + begun = true ; + } + + if (sm == -1) { return ; } + +// PRINTF1("START TO SHOW = %d, pin = %d, 800kHz = %d, length = %d, pio= %d, sm = %d, offset = %d, no_sm = [%d, %d]\n ", begun, pin, is800KHz, numLEDs, pio_get_index(pio), sm, (pio_get_index(pio) == 0) ? pio0_offset : pio1_offset,pio_no_sm[0], pio_no_sm[1] ); + while(numBytes--) + // Bits for transmission must be shifted to top 8 bits + pio_sm_put_blocking(pio, sm, ((uint32_t)*pixels++)<< 24); +} + + +/*! + @brief Transmit pixel data in RAM to NeoPixels. + @note On most architectures, interrupts are temporarily disabled in + order to achieve the correct NeoPixel signal timing. This means + that the Arduino millis() and micros() functions, which require + interrupts, will lose small intervals of time whenever this + function is called (about 30 microseconds per RGB pixel, 40 for + RGBW pixels). There's no easy fix for this, but a few + specialized alternative or companion libraries exist that use + very device-specific peripherals to work around it. +*/ +void Adafruit_NeoPixel::show(void) { + + if(!pixels) return; + + rp2040Show(pin, pixels, numBytes, is800KHz); + +} + +/*! + @brief Set/change the NeoPixel output pin number. Previous pin, + if any, is set to INPUT and the new pin is set to OUTPUT. + @param p pin number. +*/ +void Adafruit_NeoPixel::setPin(uint16_t p) { + if (!begun) { + pin = p ; + return; + }; + rp2040changepin(pin); +} + +/*! + @brief Set a pixel's color using separate red, green and blue + components. If using RGBW pixels, white will be set to 0. + @param n Pixel index, starting from 0. + @param r Red brightness, 0 = minimum (off), 255 = maximum. + @param g Green brightness, 0 = minimum (off), 255 = maximum. + @param b Blue brightness, 0 = minimum (off), 255 = maximum. +*/ +void Adafruit_NeoPixel::setPixelColor( + uint16_t n, uint8_t r, uint8_t g, uint8_t b) { + setPixelColor(n,r,g,b,0); +} + +/*! + @brief Set a pixel's color using separate red, green, blue and white + components (for RGBW NeoPixels only). + @param n Pixel index, starting from 0. + @param r Red brightness, 0 = minimum (off), 255 = maximum. + @param g Green brightness, 0 = minimum (off), 255 = maximum. + @param b Blue brightness, 0 = minimum (off), 255 = maximum. + @param w White brightness, 0 = minimum (off), 255 = maximum, ignored + if using RGB pixels. +*/ +void Adafruit_NeoPixel::setPixelColor( + uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + + if(n < numLEDs) { + if(brightness) { // See notes in setBrightness() + r = (r * brightness) >> 8; + g = (g * brightness) >> 8; + b = (b * brightness) >> 8; + w = (w * brightness) >> 8; + } + uint8_t *p, *po; + if (brightfr == NULL) { + if(wOffset == rOffset) { // Is an RGB-type strip + p = &pixels[n * 3]; // 3 bytes per pixel + } else { // Is a WRGB-type strip + p = &pixels[n * 4]; // 4 bytes per pixel + p[wOffset] = w; // set W + } + p[rOffset] = r; // R,G,B always stored + p[gOffset] = g; + p[bOffset] = b; + } else { + if(wOffset == rOffset) { // Is an RGB-type strip + po = &opixels[n * 3]; // 3 bytes per pixel + p = &pixels[n * 3]; + } else { // Is a WRGB-type strip + po = &opixels[n * 4]; // 4 bytes per pixel + p = &pixels[n * 4]; + po[wOffset] = w; // set W + p[wOffset] = brightfw(w); + } + po[rOffset] = r; // R,G,B always stored + po[gOffset] = g; + po[bOffset] = b; + p[rOffset] = brightfr(r); + p[gOffset] = brightfg(g); + p[bOffset] = brightfb(b); + } + + } +} + +/*! + @brief Set a pixel's color using a 32-bit 'packed' RGB or RGBW value. + @param n Pixel index, starting from 0. + @param c 32-bit color value. Most significant byte is white (for RGBW + pixels) or ignored (for RGB pixels), next is red, then green, + and least significant byte is blue. +*/ +void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint32_t c) { + uint8_t w = (uint8_t)(c >> 24); + uint8_t r = (uint8_t)(c >> 16); + uint8_t g = (uint8_t)(c >> 8); + uint8_t b = (uint8_t)c; + setPixelColor(n,r,g,b,w); +} + +/*! + @brief Fill all or part of the NeoPixel strip with a color. + @param c 32-bit color value. Most significant byte is white (for + RGBW pixels) or ignored (for RGB pixels), next is red, + then green, and least significant byte is blue. If all + arguments are unspecified, this will be 0 (off). + @param first Index of first pixel to fill, starting from 0. Must be + in-bounds, no clipping is performed. 0 if unspecified. + @param count Number of pixels to fill, as a positive value. Passing + 0 or leaving unspecified will fill to end of strip. +*/ +void Adafruit_NeoPixel::fill(uint32_t c, uint16_t first, uint16_t count) { + uint16_t i, end; + + if(first >= numLEDs) { + return; // If first LED is past end of strip, nothing to do + } + + // Calculate the index ONE AFTER the last pixel to fill + if(count == 0) { + // Fill to end of strip + end = numLEDs; + } else { + // Ensure that the loop won't go past the last pixel + end = first + count; + if(end > numLEDs) end = numLEDs; + } + + for(i = first; i < end; i++) { + this->setPixelColor(i, c); + } +} + +/*! + @brief Convert hue, saturation and value into a packed 32-bit RGB color + that can be passed to setPixelColor() or other RGB-compatible + functions. + @param hue An unsigned 16-bit value, 0 to 65535, representing one full + loop of the color wheel, which allows 16-bit hues to "roll + over" while still doing the expected thing (and allowing + more precision than the wheel() function that was common to + prior NeoPixel examples). + @param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255 + (max or pure hue). Default of 255 if unspecified. + @param val Value (brightness), 8-bit value, 0 (min / black / off) to + 255 (max or full brightness). Default of 255 if unspecified. + @return Packed 32-bit RGB with the most significant byte set to 0 -- the + white element of WRGB pixels is NOT utilized. Result is linearly + but not perceptually correct, so you may want to pass the result + through the gamma32() function (or your own gamma-correction + operation) else colors may appear washed out. This is not done + automatically by this function because coders may desire a more + refined gamma-correction function than the simplified + one-size-fits-all operation of gamma32(). Diffusing the LEDs also + really seems to help when using low-saturation colors. +*/ +uint32_t Adafruit_NeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { + + uint8_t r, g, b; + + // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover; + // 0 is not the start of pure red, but the midpoint...a few values above + // zero and a few below 65536 all yield pure red (similarly, 32768 is the + // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values + // each for red, green, blue) really only allows for 1530 distinct hues + // (not 1536, more on that below), but the full unsigned 16-bit type was + // chosen for hue so that one's code can easily handle a contiguous color + // wheel by allowing hue to roll over in either direction. + hue = (hue * 1530L + 32768) / 65536; + // Because red is centered on the rollover point (the +32768 above, + // essentially a fixed-point +0.5), the above actually yields 0 to 1530, + // where 0 and 1530 would yield the same thing. Rather than apply a + // costly modulo operator, 1530 is handled as a special case below. + + // So you'd think that the color "hexcone" (the thing that ramps from + // pure red, to pure yellow, to pure green and so forth back to red, + // yielding six slices), and with each color component having 256 + // possible values (0-255), might have 1536 possible items (6*256), + // but in reality there's 1530. This is because the last element in + // each 256-element slice is equal to the first element of the next + // slice, and keeping those in there this would create small + // discontinuities in the color wheel. So the last element of each + // slice is dropped...we regard only elements 0-254, with item 255 + // being picked up as element 0 of the next slice. Like this: + // Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0 + // Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0 + // Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254 + // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why + // the constants below are not the multiples of 256 you might expect. + + // Convert hue to R,G,B (nested ifs faster than divide+mod+switch): + if(hue < 510) { // Red to Green-1 + b = 0; + if(hue < 255) { // Red to Yellow-1 + r = 255; + g = hue; // g = 0 to 254 + } else { // Yellow to Green-1 + r = 510 - hue; // r = 255 to 1 + g = 255; + } + } else if(hue < 1020) { // Green to Blue-1 + r = 0; + if(hue < 765) { // Green to Cyan-1 + g = 255; + b = hue - 510; // b = 0 to 254 + } else { // Cyan to Blue-1 + g = 1020 - hue; // g = 255 to 1 + b = 255; + } + } else if(hue < 1530) { // Blue to Red-1 + g = 0; + if(hue < 1275) { // Blue to Magenta-1 + r = hue - 1020; // r = 0 to 254 + b = 255; + } else { // Magenta to Red-1 + r = 255; + b = 1530 - hue; // b = 255 to 1 + } + } else { // Last 0.5 Red (quicker than % operator) + r = 255; + g = b = 0; + } + + // Apply saturation and value to R,G,B, pack into 32-bit result: + uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 + uint16_t s1 = 1 + sat; // 1 to 256; same reason + uint8_t s2 = 255 - sat; // 255 to 0 + return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | + (((((g * s1) >> 8) + s2) * v1) & 0xff00) | + ( ((((b * s1) >> 8) + s2) * v1) >> 8); +} + +/*! + @brief Query the color of a previously-set pixel. + @param n Index of pixel to read (0 = first). + @return 'Packed' 32-bit RGB or WRGB value. Most significant byte is white + (for RGBW pixels) or 0 (for RGB pixels), next is red, then green, + and least significant byte is blue. + @note If the strip brightness has been changed from the default value + of 255, the color read from a pixel may not exactly match what + was previously written with one of the setPixelColor() functions. + This gets more pronounced at lower brightness levels. +*/ +uint32_t Adafruit_NeoPixel::getPixelColor(uint16_t n) const { + if(n >= numLEDs) return 0; // Out of bounds, return no color. + + uint8_t *p, *po; + + if (brightfr == NULL) { + if(wOffset == rOffset) { // Is RGB-type device + p = &pixels[n * 3]; + if(brightness) { + // Stored color was decimated by setBrightness(). Returned value + // attempts to scale back to an approximation of the original 24-bit + // value used when setting the pixel color, but there will always be + // some error -- those bits are simply gone. Issue is most + // pronounced at low brightness levels. + return (((uint32_t)(p[rOffset] << 8) / brightness) << 16) | + (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | + ( (uint32_t)(p[bOffset] << 8) / brightness ); + } else { + // No brightness adjustment has been made -- return 'raw' color + return ((uint32_t)p[rOffset] << 16) | + ((uint32_t)p[gOffset] << 8) | + (uint32_t)p[bOffset]; + } + } else { // Is RGBW-type device + p = &pixels[n * 4]; + if(brightness) { // Return scaled color + return (((uint32_t)(p[wOffset] << 8) / brightness) << 24) | + (((uint32_t)(p[rOffset] << 8) / brightness) << 16) | + (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | + ( (uint32_t)(p[bOffset] << 8) / brightness ); + } else { // Return raw color + return ((uint32_t)p[wOffset] << 24) | + ((uint32_t)p[rOffset] << 16) | + ((uint32_t)p[gOffset] << 8) | + (uint32_t)p[bOffset]; + } + } + } else { // else take the values from the original array + if(wOffset == rOffset) { // Is RGB-type device + po = &opixels[n * 3]; + return ((uint32_t)po[rOffset] << 16) | + ((uint32_t)po[gOffset] << 8) | + (uint32_t)po[bOffset]; + } else { // Is RGBW-type device + po = &opixels[n * 4]; + return ((uint32_t)po[wOffset] << 24) | + ((uint32_t)po[rOffset] << 16) | + ((uint32_t)po[gOffset] << 8) | + (uint32_t)po[bOffset]; + } + } +} + + +/*! + @brief Adjust output brightness. Does not immediately affect what's + currently displayed on the LEDs. The next call to show() will + refresh the LEDs at this level. + @param b Brightness setting, 0=minimum (off), 255=brightest. + @note This was intended for one-time use in one's setup() function, + not as an animation effect in itself. Because of the way this + library "pre-multiplies" LED colors in RAM, changing the + brightness is often a "lossy" operation -- what you write to + pixels isn't necessary the same as what you'll read back. + Repeated brightness changes using this function exacerbate the + problem. Smart programs therefore treat the strip as a + write-only resource, maintaining their own state to render each + frame of an animation, not relying on read-modify-write. +*/ +void Adafruit_NeoPixel::setBrightness(uint8_t b) { + // Stored brightness value is different than what's passed. + // This simplifies the actual scaling math later, allowing a fast + // 8x8-bit multiply and taking the MSB. 'brightness' is a uint8_t, + // adding 1 here may (intentionally) roll over...so 0 = max brightness + // (color values are interpreted literally; no scaling), 1 = min + // brightness (off), 255 = just below max brightness. + uint8_t newBrightness = b + 1; + if(newBrightness != brightness) { // Compare against prior value + // Brightness has changed -- re-scale existing data in RAM, + // This process is potentially "lossy," especially when increasing + // brightness. The tight timing in the WS2811/WS2812 code means there + // aren't enough free cycles to perform this scaling on the fly as data + // is issued. So we make a pass through the existing color data in RAM + // and scale it (subsequent graphics commands also work at this + // brightness level). If there's a significant step up in brightness, + // the limited number of steps (quantization) in the old data will be + // quite visible in the re-scaled version. For a non-destructive + // change, you'll need to re-render the full strip data. C'est la vie. + uint8_t c, + *ptr = pixels, + oldBrightness = brightness - 1; // De-wrap old brightness value + uint16_t scale; + if(oldBrightness == 0) scale = 0; // Avoid /0 + else if(b == 255) scale = 65535 / oldBrightness; + else scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness; + for(uint16_t i=0; i> 8; + } + brightness = newBrightness; + } +} + +void Adafruit_NeoPixel::setBrightnessFunctions(pBrightnessFunc fr, pBrightnessFunc fg, pBrightnessFunc fb, pBrightnessFunc fw) { + + if (opixels == NULL & numLEDs != 0) { + opixels = (uint8_t *)malloc(numBytes); + memcpy(opixels,pixels,numBytes); + } + + brightfr = fr; + brightfg = fg; + brightfb = fb; + brightfw = fw; + + for (int i = 0 ; i < numLEDs ; i++) { + uint32_t pixel; + pixel = getPixelColor(i); + setPixelColor(i,pixel); + } +}; + + + +/*! + @brief Retrieve the last-set brightness value for the strip. + @return Brightness value: 0 = minimum (off), 255 = maximum. +*/ +uint8_t Adafruit_NeoPixel::getBrightness(void) const { + return brightness - 1; +} + +/*! + @brief Fill the whole NeoPixel strip with 0 / black / off. +*/ +void Adafruit_NeoPixel::clear(void) { + memset(pixels, 0, numBytes); +} + +// A 32-bit variant of gamma8() that applies the same function +// to all components of a packed RGB or WRGB value. +uint32_t Adafruit_NeoPixel::gamma32(uint32_t x) { + uint8_t *y = (uint8_t *)&x; + // All four bytes of a 32-bit value are filtered even if RGB (not WRGB), + // to avoid a bunch of shifting and masking that would be necessary for + // properly handling different endianisms (and each byte is a fairly + // trivial operation, so it might not even be wasting cycles vs a check + // and branch for the RGB case). In theory this might cause trouble *if* + // someone's storing information in the unused most significant byte + // of an RGB value, but this seems exceedingly rare and if it's + // encountered in reality they can mask values going in or coming out. + for(uint8_t i=0; i<4; i++) y[i] = gamma8(y[i]); + return x; // Packed 32-bit return +} diff --git a/pico-light-voice/pico_neopixels/CMakeLists.txt b/pico-light-voice/pico_neopixels/CMakeLists.txt new file mode 100644 index 0000000..52dfe21 --- /dev/null +++ b/pico-light-voice/pico_neopixels/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(pico_neopixel INTERFACE) + +pico_generate_pio_header(pico_neopixel ${CMAKE_CURRENT_LIST_DIR}/ws2812byte.pio) + +target_sources(pico_neopixel INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/Adafruit_NeoPixel.cpp +) + +pico_enable_stdio_usb(pico_neopixel 1) +pico_enable_stdio_uart(pico_neopixel 0) + +target_include_directories(pico_neopixel INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + +# Pull in pico libraries that we need +target_link_libraries(pico_neopixel INTERFACE pico_stdlib hardware_pio pico_malloc pico_mem_ops) \ No newline at end of file diff --git a/pico-light-voice/pico_neopixels/include/Adafruit_NeoPixel.h b/pico-light-voice/pico_neopixels/include/Adafruit_NeoPixel.h new file mode 100644 index 0000000..e216303 --- /dev/null +++ b/pico-light-voice/pico_neopixels/include/Adafruit_NeoPixel.h @@ -0,0 +1,350 @@ +/*! + * @file Adafruit_NeoPixel.h + * + * This is part of Adafruit's NeoPixel library for the Arduino platform, + * allowing a broad range of microcontroller boards (most AVR boards, + * many ARM devices, ESP8266 and ESP32, among others) to control Adafruit + * NeoPixels, FLORA RGB Smart Pixels and compatible devices -- WS2811, + * WS2812, WS2812B, SK6812, etc. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing products + * from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries, + * with contributions by PJRC, Michael Miller and other members of the + * open source community. + * + * This file is part of the Adafruit_NeoPixel library. + * + * Adafruit_NeoPixel is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Adafruit_NeoPixel is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with NeoPixel. If not, see + * . + * + */ + + +#pragma once +#include "Adafruit_NeoPixel.h" +#include "pico/stdlib" +#include "hardware/pio" +#include "pico/time" +#include "ws2812byte.pio.h" + + + +// The order of primary colors in the NeoPixel data stream can vary among +// device types, manufacturers and even different revisions of the same +// item. The third parameter to the Adafruit_NeoPixel constructor encodes +// the per-pixel byte offsets of the red, green and blue primaries (plus +// white, if present) in the data stream -- the following #defines provide +// an easier-to-use named version for each permutation. e.g. NEO_GRB +// indicates a NeoPixel-compatible device expecting three bytes per pixel, +// with the first byte transmitted containing the green value, second +// containing red and third containing blue. The in-memory representation +// of a chain of NeoPixels is the same as the data-stream order; no +// re-ordering of bytes is required when issuing data to the chain. +// Most of these values won't exist in real-world devices, but it's done +// this way so we're ready for it (also, if using the WS2811 driver IC, +// one might have their pixels set up in any weird permutation). + +// Bits 5,4 of this value are the offset (0-3) from the first byte of a +// pixel to the location of the red color byte. Bits 3,2 are the green +// offset and 1,0 are the blue offset. If it is an RGBW-type device +// (supporting a white primary in addition to R,G,B), bits 7,6 are the +// offset to the white byte...otherwise, bits 7,6 are set to the same value +// as 5,4 (red) to indicate an RGB (not RGBW) device. +// i.e. binary representation: +// 0bWWRRGGBB for RGBW devices +// 0bRRRRGGBB for RGB + +// RGB NeoPixel permutations; white and red offsets are always same +// Offset: W R G B +#define NEO_RGB ((0<<6) | (0<<4) | (1<<2) | (2)) ///< Transmit as R,G,B +#define NEO_RBG ((0<<6) | (0<<4) | (2<<2) | (1)) ///< Transmit as R,B,G +#define NEO_GRB ((1<<6) | (1<<4) | (0<<2) | (2)) ///< Transmit as G,R,B +#define NEO_GBR ((2<<6) | (2<<4) | (0<<2) | (1)) ///< Transmit as G,B,R +#define NEO_BRG ((1<<6) | (1<<4) | (2<<2) | (0)) ///< Transmit as B,R,G +#define NEO_BGR ((2<<6) | (2<<4) | (1<<2) | (0)) ///< Transmit as B,G,R + +// RGBW NeoPixel permutations; all 4 offsets are distinct +// Offset: W R G B +#define NEO_WRGB ((0<<6) | (1<<4) | (2<<2) | (3)) ///< Transmit as W,R,G,B +#define NEO_WRBG ((0<<6) | (1<<4) | (3<<2) | (2)) ///< Transmit as W,R,B,G +#define NEO_WGRB ((0<<6) | (2<<4) | (1<<2) | (3)) ///< Transmit as W,G,R,B +#define NEO_WGBR ((0<<6) | (3<<4) | (1<<2) | (2)) ///< Transmit as W,G,B,R +#define NEO_WBRG ((0<<6) | (2<<4) | (3<<2) | (1)) ///< Transmit as W,B,R,G +#define NEO_WBGR ((0<<6) | (3<<4) | (2<<2) | (1)) ///< Transmit as W,B,G,R + +#define NEO_RWGB ((1<<6) | (0<<4) | (2<<2) | (3)) ///< Transmit as R,W,G,B +#define NEO_RWBG ((1<<6) | (0<<4) | (3<<2) | (2)) ///< Transmit as R,W,B,G +#define NEO_RGWB ((2<<6) | (0<<4) | (1<<2) | (3)) ///< Transmit as R,G,W,B +#define NEO_RGBW ((3<<6) | (0<<4) | (1<<2) | (2)) ///< Transmit as R,G,B,W +#define NEO_RBWG ((2<<6) | (0<<4) | (3<<2) | (1)) ///< Transmit as R,B,W,G +#define NEO_RBGW ((3<<6) | (0<<4) | (2<<2) | (1)) ///< Transmit as R,B,G,W + +#define NEO_GWRB ((1<<6) | (2<<4) | (0<<2) | (3)) ///< Transmit as G,W,R,B +#define NEO_GWBR ((1<<6) | (3<<4) | (0<<2) | (2)) ///< Transmit as G,W,B,R +#define NEO_GRWB ((2<<6) | (1<<4) | (0<<2) | (3)) ///< Transmit as G,R,W,B +#define NEO_GRBW ((3<<6) | (1<<4) | (0<<2) | (2)) ///< Transmit as G,R,B,W +#define NEO_GBWR ((2<<6) | (3<<4) | (0<<2) | (1)) ///< Transmit as G,B,W,R +#define NEO_GBRW ((3<<6) | (2<<4) | (0<<2) | (1)) ///< Transmit as G,B,R,W + +#define NEO_BWRG ((1<<6) | (2<<4) | (3<<2) | (0)) ///< Transmit as B,W,R,G +#define NEO_BWGR ((1<<6) | (3<<4) | (2<<2) | (0)) ///< Transmit as B,W,G,R +#define NEO_BRWG ((2<<6) | (1<<4) | (3<<2) | (0)) ///< Transmit as B,R,W,G +#define NEO_BRGW ((3<<6) | (1<<4) | (2<<2) | (0)) ///< Transmit as B,R,G,W +#define NEO_BGWR ((2<<6) | (3<<4) | (1<<2) | (0)) ///< Transmit as B,G,W,R +#define NEO_BGRW ((3<<6) | (2<<4) | (1<<2) | (0)) ///< Transmit as B,G,R,W + +// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device. +// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is +// the default if unspecified. Because flash space is very limited on ATtiny +// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on +// those chips, though it can be enabled by removing the ifndef/endif below, +// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on +// other MCUs to remove v1 support and save a little space. + + +#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission +#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission + +typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor + +// These two tables are declared outside the Adafruit_NeoPixel class +// because some boards may require oldschool compilers that don't +// handle the C++11 constexpr keyword. + +/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255). + Copy & paste this snippet into a Python REPL to regenerate: +import math +for x in range(256): + print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))), + if x&15 == 15: print +*/ +static const uint8_t _NeoPixelSineTable[256] = { + 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, + 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, + 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, + 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255, + 255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, + 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220, + 218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, + 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131, + 128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82, + 79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, + 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, + 10, 9, 7, 6, 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, + 10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, + 37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, + 79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124}; + +/* Similar to above, but for an 8-bit gamma-correction table. + Copy & paste this snippet into a Python REPL to regenerate: +import math +gamma=2.6 +for x in range(256): + print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))), + if x&15 == 15: print +*/ +static const uint8_t _NeoPixelGammaTable[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, + 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, + 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, + 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, + 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 75, + 76, 77, 78, 80, 81, 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, + 97, 99,100,102,103,105,106,108,109,111,112,114,115,117,119,120, + 122,124,125,127,129,130,132,134,136,137,139,141,143,145,146,148, + 150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180, + 182,184,186,188,191,193,195,197,199,202,204,206,209,211,213,215, + 218,220,223,225,227,230,232,235,237,240,242,245,247,250,252,255}; + +// gloal hardware resource variables keeping track of configurations an usage on the pi Pico RP2040. +static int pio0_offset = -1; // offset of loaded pio neopixel program on pio0; -1 if no program loaded +static int pio1_offset = -1; // offset of loaded pio neopixel program on pio1; -1 if no program loaded +static int pio_no_sm[2] = {0,0} ; // number of state machines in use for Neopixel + + +/*! + @brief Class that stores state and functions for interacting with + Adafruit NeoPixels and compatible devices. +*/ +class Adafruit_NeoPixel { + + public: + + // Constructor: number of LEDs, pin number, LED type + Adafruit_NeoPixel(uint16_t n, uint16_t pin=0, + neoPixelType type=NEO_GRB + NEO_KHZ800); + Adafruit_NeoPixel(void); + ~Adafruit_NeoPixel(); + + void begin(void); + void show(void); + void setPin(uint16_t p); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, + uint8_t w); + void setPixelColor(uint16_t n, uint32_t c); + void fill(uint32_t c=0, uint16_t first=0, uint16_t count=0); + void setBrightness(uint8_t); + void clear(void); + void updateLength(uint16_t n); + void updateType(neoPixelType t); + /*! + @brief Check whether a call to show() will start sending data + immediately or will 'block' for a required interval. NeoPixels + require a short quiet time (about 300 microseconds) after the + last bit is received before the data 'latches' and new data can + start being received. Usually one's sketch is implicitly using + this time to generate a new frame of animation...but if it + finishes very quickly, this function could be used to see if + there's some idle time available for some low-priority + concurrent task. + @return 1 or true if show() will start sending immediately, 0 or false + if show() would block (meaning some idle time is available). + */ + bool canShow(void) { + int64_t howlongago = absolute_time_diff_us (endTime, get_absolute_time()); + return (howlongago >= 300L); + } + /*! + @brief Get a pointer directly to the NeoPixel data buffer in RAM. + Pixel data is stored in a device-native format (a la the NEO_* + constants) and is not translated here. Applications that access + this buffer will need to be aware of the specific data format + and handle colors appropriately. + @return Pointer to NeoPixel buffer (uint8_t* array). + @note This is for high-performance applications where calling + setPixelColor() on every single pixel would be too slow (e.g. + POV or light-painting projects). There is no bounds checking + on the array, creating tremendous potential for mayhem if one + writes past the ends of the buffer. Great power, great + responsibility and all that. + */ + uint8_t *getPixels(void) const { return pixels; }; + uint8_t getBrightness(void) const; + /*! + @brief Retrieve the pin number used for NeoPixel data output. + @return Arduino pin number (-1 if not set). + */ + int16_t getPin(void) const { return pin; }; + /*! + @brief Return the number of pixels in an Adafruit_NeoPixel strip object. + @return Pixel count (0 if not set). + */ + uint16_t numPixels(void) const { return numLEDs; } + uint32_t getPixelColor(uint16_t n) const; + /*! + @brief An 8-bit integer sine wave function, not directly compatible + with standard trigonometric units like radians or degrees. + @param x Input angle, 0-255; 256 would loop back to zero, completing + the circle (equivalent to 360 degrees or 2 pi radians). + One can therefore use an unsigned 8-bit variable and simply + add or subtract, allowing it to overflow/underflow and it + still does the expected contiguous thing. + @return Sine result, 0 to 255, or -128 to +127 if type-converted to + a signed int8_t, but you'll most likely want unsigned as this + output is often used for pixel brightness in animation effects. + */ + static uint8_t sine8(uint8_t x) { + return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out + } + /*! + @brief An 8-bit gamma-correction function for basic pixel brightness + adjustment. Makes color transitions appear more perceptially + correct. + @param x Input brightness, 0 (minimum or off/black) to 255 (maximum). + @return Gamma-adjusted brightness, can then be passed to one of the + setPixelColor() functions. This uses a fixed gamma correction + exponent of 2.6, which seems reasonably okay for average + NeoPixels in average tasks. If you need finer control you'll + need to provide your own gamma-correction function instead. + */ + static uint8_t gamma8(uint8_t x) { + return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out + } + /*! + @brief Convert separate red, green and blue values into a single + "packed" 32-bit RGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @return 32-bit packed RGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + function. Packed RGB format is predictable, regardless of + LED strand color order. + */ + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + /*! + @brief Convert separate red, green, blue and white values into a + single "packed" 32-bit WRGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @param w White brightness, 0 to 255. + @return 32-bit packed WRGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + function. Packed WRGB format is predictable, regardless of + LED strand color order. + */ + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + static uint32_t ColorHSV(uint16_t hue, uint8_t sat=255, uint8_t val=255); + /*! + @brief A gamma-correction function for 32-bit packed RGB or WRGB + colors. Makes color transitions appear more perceptially + correct. + @param x 32-bit packed RGB or WRGB color. + @return Gamma-adjusted packed color, can then be passed in one of the + setPixelColor() functions. Like gamma8(), this uses a fixed + gamma correction exponent of 2.6, which seems reasonably okay + for average NeoPixels in average tasks. If you need finer + control you'll need to provide your own gamma-correction + function instead. + */ + static uint32_t gamma32(uint32_t x); + + private: + void rp2040Init(uint8_t pin, bool is800KHz) ; + void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz); + + protected: + + bool is800KHz; ///< true if 800 KHz pixels + bool begun; ///< true if begin() previously called + uint16_t numLEDs; ///< Number of RGB LEDs in strip + uint16_t numBytes; ///< Size of 'pixels' buffer below + int16_t pin; ///< Output pin number (-1 if not yet set) + uint8_t brightness; ///< Strip brightness 0-255 (stored as +1) + uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each) + uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel + uint8_t gOffset; ///< Index of green byte + uint8_t bOffset; ///< Index of blue byte + uint8_t wOffset; ///< Index of white (==rOffset if no white) + absolute_time_t endTime; ///< Latch timing reference + PIO pio; ///< chosen pio for this object + uint sm; ///. + * + */ + + +#pragma once +#include "pico/stdio.h" +#include "hardware/pio.h" +#include "pico/time.h" +#include "ws2812byte.pio.h" + + + +// The order of primary colors in the NeoPixel data stream can vary among +// device types, manufacturers and even different revisions of the same +// item. The third parameter to the Adafruit_NeoPixel constructor encodes +// the per-pixel byte offsets of the red, green and blue primaries (plus +// white, if present) in the data stream -- the following #defines provide +// an easier-to-use named version for each permutation. e.g. NEO_GRB +// indicates a NeoPixel-compatible device expecting three bytes per pixel, +// with the first byte transmitted containing the green value, second +// containing red and third containing blue. The in-memory representation +// of a chain of NeoPixels is the same as the data-stream order; no +// re-ordering of bytes is required when issuing data to the chain. +// Most of these values won't exist in real-world devices, but it's done +// this way so we're ready for it (also, if using the WS2811 driver IC, +// one might have their pixels set up in any weird permutation). + +// Bits 5,4 of this value are the offset (0-3) from the first byte of a +// pixel to the location of the red color byte. Bits 3,2 are the green +// offset and 1,0 are the blue offset. If it is an RGBW-type device +// (supporting a white primary in addition to R,G,B), bits 7,6 are the +// offset to the white byte...otherwise, bits 7,6 are set to the same value +// as 5,4 (red) to indicate an RGB (not RGBW) device. +// i.e. binary representation: +// 0bWWRRGGBB for RGBW devices +// 0bRRRRGGBB for RGB + +// RGB NeoPixel permutations; white and red offsets are always same +// Offset: W R G B +#define NEO_RGB ((0<<6) | (0<<4) | (1<<2) | (2)) ///< Transmit as R,G,B +#define NEO_RBG ((0<<6) | (0<<4) | (2<<2) | (1)) ///< Transmit as R,B,G +#define NEO_GRB ((1<<6) | (1<<4) | (0<<2) | (2)) ///< Transmit as G,R,B +#define NEO_GBR ((2<<6) | (2<<4) | (0<<2) | (1)) ///< Transmit as G,B,R +#define NEO_BRG ((1<<6) | (1<<4) | (2<<2) | (0)) ///< Transmit as B,R,G +#define NEO_BGR ((2<<6) | (2<<4) | (1<<2) | (0)) ///< Transmit as B,G,R + +// RGBW NeoPixel permutations; all 4 offsets are distinct +// Offset: W R G B +#define NEO_WRGB ((0<<6) | (1<<4) | (2<<2) | (3)) ///< Transmit as W,R,G,B +#define NEO_WRBG ((0<<6) | (1<<4) | (3<<2) | (2)) ///< Transmit as W,R,B,G +#define NEO_WGRB ((0<<6) | (2<<4) | (1<<2) | (3)) ///< Transmit as W,G,R,B +#define NEO_WGBR ((0<<6) | (3<<4) | (1<<2) | (2)) ///< Transmit as W,G,B,R +#define NEO_WBRG ((0<<6) | (2<<4) | (3<<2) | (1)) ///< Transmit as W,B,R,G +#define NEO_WBGR ((0<<6) | (3<<4) | (2<<2) | (1)) ///< Transmit as W,B,G,R + +#define NEO_RWGB ((1<<6) | (0<<4) | (2<<2) | (3)) ///< Transmit as R,W,G,B +#define NEO_RWBG ((1<<6) | (0<<4) | (3<<2) | (2)) ///< Transmit as R,W,B,G +#define NEO_RGWB ((2<<6) | (0<<4) | (1<<2) | (3)) ///< Transmit as R,G,W,B +#define NEO_RGBW ((3<<6) | (0<<4) | (1<<2) | (2)) ///< Transmit as R,G,B,W +#define NEO_RBWG ((2<<6) | (0<<4) | (3<<2) | (1)) ///< Transmit as R,B,W,G +#define NEO_RBGW ((3<<6) | (0<<4) | (2<<2) | (1)) ///< Transmit as R,B,G,W + +#define NEO_GWRB ((1<<6) | (2<<4) | (0<<2) | (3)) ///< Transmit as G,W,R,B +#define NEO_GWBR ((1<<6) | (3<<4) | (0<<2) | (2)) ///< Transmit as G,W,B,R +#define NEO_GRWB ((2<<6) | (1<<4) | (0<<2) | (3)) ///< Transmit as G,R,W,B +#define NEO_GRBW ((3<<6) | (1<<4) | (0<<2) | (2)) ///< Transmit as G,R,B,W +#define NEO_GBWR ((2<<6) | (3<<4) | (0<<2) | (1)) ///< Transmit as G,B,W,R +#define NEO_GBRW ((3<<6) | (2<<4) | (0<<2) | (1)) ///< Transmit as G,B,R,W + +#define NEO_BWRG ((1<<6) | (2<<4) | (3<<2) | (0)) ///< Transmit as B,W,R,G +#define NEO_BWGR ((1<<6) | (3<<4) | (2<<2) | (0)) ///< Transmit as B,W,G,R +#define NEO_BRWG ((2<<6) | (1<<4) | (3<<2) | (0)) ///< Transmit as B,R,W,G +#define NEO_BRGW ((3<<6) | (1<<4) | (2<<2) | (0)) ///< Transmit as B,R,G,W +#define NEO_BGWR ((2<<6) | (3<<4) | (1<<2) | (0)) ///< Transmit as B,G,W,R +#define NEO_BGRW ((3<<6) | (2<<4) | (1<<2) | (0)) ///< Transmit as B,G,R,W + +// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device. +// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is +// the default if unspecified. Because flash space is very limited on ATtiny +// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on +// those chips, though it can be enabled by removing the ifndef/endif below, +// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on +// other MCUs to remove v1 support and save a little space. + + +#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission +#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission + +typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +typedef uint8_t (* pBrightnessFunc)(uint8_t value) ; // pointer to a brigness conversion function + +// These two tables are declared outside the Adafruit_NeoPixel class +// because some boards may require oldschool compilers that don't +// handle the C++11 constexpr keyword. + +/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255). + Copy & paste this snippet into a Python REPL to regenerate: +import math +for x in range(256): + print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))), + if x&15 == 15: print +*/ +static const uint8_t _NeoPixelSineTable[256] = { + 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, + 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, + 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, + 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255, + 255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, + 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220, + 218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, + 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131, + 128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82, + 79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, + 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, + 10, 9, 7, 6, 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, + 10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, + 37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, + 79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124}; + +/* Similar to above, but for an 8-bit gamma-correction table. + Copy & paste this snippet into a Python REPL to regenerate: +import math +gamma=2.6 +for x in range(256): + print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))), + if x&15 == 15: print +*/ +static const uint8_t _NeoPixelGammaTable[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, + 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, + 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, + 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, + 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 75, + 76, 77, 78, 80, 81, 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, + 97, 99,100,102,103,105,106,108,109,111,112,114,115,117,119,120, + 122,124,125,127,129,130,132,134,136,137,139,141,143,145,146,148, + 150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180, + 182,184,186,188,191,193,195,197,199,202,204,206,209,211,213,215, + 218,220,223,225,227,230,232,235,237,240,242,245,247,250,252,255}; + +// gloal hardware resource variables keeping track of configurations an usage on the pi Pico RP2040. +static int pio0_offset = -1; // offset of loaded pio neopixel program on pio0; -1 if no program loaded +static int pio1_offset = -1; // offset of loaded pio neopixel program on pio1; -1 if no program loaded +static int pio_no_sm[2] = {0,0} ; // number of state machines in use for Neopixel + +static uint8_t neopixels_gamma8(uint8_t x) { + return _NeoPixelGammaTable[x]; // 0-255 in, 0-255 out + } + +/*! + @brief Class that stores state and functions for interacting with + Adafruit NeoPixels and compatible devices. +*/ +class Adafruit_NeoPixel { + + public: + + // Constructor: number of LEDs, pin number, LED type + Adafruit_NeoPixel(uint16_t n, uint16_t pin=0, + neoPixelType type=NEO_GRB + NEO_KHZ800); + Adafruit_NeoPixel(void); + ~Adafruit_NeoPixel(); + + void begin(void); + void show(void); + void setBrightnessFunctions(pBrightnessFunc fr, pBrightnessFunc fg, pBrightnessFunc fb, pBrightnessFunc fw); + void setPin(uint16_t p); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, + uint8_t w); + void setPixelColor(uint16_t n, uint32_t c); + void fill(uint32_t c=0, uint16_t first=0, uint16_t count=0); + void setBrightness(uint8_t); + void clear(void); + void updateLength(uint16_t n); + void updateType(neoPixelType t); + /*! + @brief Check whether a call to show() will start sending data + immediately or will 'block' for a required interval. NeoPixels + require a short quiet time (about 300 microseconds) after the + last bit is received before the data 'latches' and new data can + start being received. Usually one's sketch is implicitly using + this time to generate a new frame of animation...but if it + finishes very quickly, this function could be used to see if + there's some idle time available for some low-priority + concurrent task. + @return 1 or true if show() will start sending immediately, 0 or false + if show() would block (meaning some idle time is available). + */ + bool canShow(void) { + int64_t howlongago = absolute_time_diff_us (endTime, get_absolute_time()); + return (howlongago >= 300L); + } + /*! + @brief Get a pointer directly to the NeoPixel data buffer in RAM. + Pixel data is stored in a device-native format (a la the NEO_* + constants) and is not translated here. Applications that access + this buffer will need to be aware of the specific data format + and handle colors appropriately. + @return Pointer to NeoPixel buffer (uint8_t* array). + @note This is for high-performance applications where calling + setPixelColor() on every single pixel would be too slow (e.g. + POV or light-painting projects). There is no bounds checking + on the array, creating tremendous potential for mayhem if one + writes past the ends of the buffer. Great power, great + responsibility and all that. + */ + uint8_t *getPixels(void) const { return pixels; }; + uint8_t getBrightness(void) const; + /*! + @brief Retrieve the pin number used for NeoPixel data output. + @return Arduino pin number (-1 if not set). + */ + int16_t getPin(void) const { return pin; }; + /*! + @brief Return the number of pixels in an Adafruit_NeoPixel strip object. + @return Pixel count (0 if not set). + */ + uint16_t numPixels(void) const { return numLEDs; } + uint32_t getPixelColor(uint16_t n) const; + /*! + @brief An 8-bit integer sine wave function, not directly compatible + with standard trigonometric units like radians or degrees. + @param x Input angle, 0-255; 256 would loop back to zero, completing + the circle (equivalent to 360 degrees or 2 pi radians). + One can therefore use an unsigned 8-bit variable and simply + add or subtract, allowing it to overflow/underflow and it + still does the expected contiguous thing. + @return Sine result, 0 to 255, or -128 to +127 if type-converted to + a signed int8_t, but you'll most likely want unsigned as this + output is often used for pixel brightness in animation effects. + */ + static uint8_t sine8(uint8_t x) { + return _NeoPixelSineTable[x]; // 0-255 in, 0-255 out + } + /*! + @brief An 8-bit gamma-correction function for basic pixel brightness + adjustment. Makes color transitions appear more perceptially + correct. + @param x Input brightness, 0 (minimum or off/black) to 255 (maximum). + @return Gamma-adjusted brightness, can then be passed to one of the + setPixelColor() functions. This uses a fixed gamma correction + exponent of 2.6, which seems reasonably okay for average + NeoPixels in average tasks. If you need finer control you'll + need to provide your own gamma-correction function instead. + */ + static uint8_t gamma8(uint8_t x) { + return _NeoPixelGammaTable[x]; // 0-255 in, 0-255 out + } + /*! + @brief Convert separate red, green and blue values into a single + "packed" 32-bit RGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @return 32-bit packed RGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + function. Packed RGB format is predictable, regardless of + LED strand color order. + */ + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + /*! + @brief Convert separate red, green, blue and white values into a + single "packed" 32-bit WRGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @param w White brightness, 0 to 255. + @return 32-bit packed WRGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + function. Packed WRGB format is predictable, regardless of + LED strand color order. + */ + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + static uint32_t ColorHSV(uint16_t hue, uint8_t sat=255, uint8_t val=255); + /*! + @brief A gamma-correction function for 32-bit packed RGB or WRGB + colors. Makes color transitions appear more perceptially + correct. + @param x 32-bit packed RGB or WRGB color. + @return Gamma-adjusted packed color, can then be passed in one of the + setPixelColor() functions. Like gamma8(), this uses a fixed + gamma correction exponent of 2.6, which seems reasonably okay + for average NeoPixels in average tasks. If you need finer + control you'll need to provide your own gamma-correction + function instead. + */ + static uint32_t gamma32(uint32_t x); + + + void rp2040Init(uint8_t pin) ; + void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz); + void rp2040changepin(uint8_t set_pin); + + protected: + + bool is800KHz; ///< true if 800 KHz pixels + bool begun; ///< true if the state machines & pio has started (after the first show). + uint16_t numLEDs; ///< Number of RGB LEDs in strip + uint16_t numBytes; ///< Size of 'pixels' buffer below + int16_t pin; ///< Output pin number (-1 if not yet set) + uint8_t brightness; ///< Strip brightness 0-255 (stored as +1) + uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each) + uint8_t *opixels; ///< Hold originally set LED color values ( 3 or 4 bytes each) + uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel + uint8_t gOffset; ///< Index of green byte + uint8_t bOffset; ///< Index of blue byte + uint8_t wOffset; ///< Index of white (==rOffset if no white) + absolute_time_t endTime; ///< Latch timing reference + PIO pio; ///< chosen pio for this object + uint sm; ////external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/pico-light-voice/pico_neopixels/ws2812byte.pio b/pico-light-voice/pico_neopixels/ws2812byte.pio new file mode 100644 index 0000000..02ef00b --- /dev/null +++ b/pico-light-voice/pico_neopixels/ws2812byte.pio @@ -0,0 +1,46 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program ws2812byte +.side_set 1 + +.define public T1 2 +.define public T2 5 +.define public T3 3 + + +.wrap_target +bitloop: + out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [T2 - 1] ; Or drive low, for a short pulse +.wrap + +% c-sdk { +#include "hardware/clocks.h" + +static inline void ws2812byte_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, uint bits) { + + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + + pio_sm_config c = ws2812byte_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, bits); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + int cycles_per_bit = ws2812byte_T1 + ws2812byte_T2 + ws2812byte_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%} + diff --git a/pico-light-voice/pico_sdk_import.cmake b/pico-light-voice/pico_sdk_import.cmake new file mode 100644 index 0000000..f63ee3f --- /dev/null +++ b/pico-light-voice/pico_sdk_import.cmake @@ -0,0 +1,64 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# todo document + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/pico-light-voice/source/main.cpp b/pico-light-voice/source/main.cpp new file mode 100644 index 0000000..2527776 --- /dev/null +++ b/pico-light-voice/source/main.cpp @@ -0,0 +1,217 @@ +#include "ei_run_classifier.h" +#include "Adafruit_NeoPixel.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +// ############ ADC and Model Stuff ############ + +#define NSAMP 5000 +// set this to determine sample rate +// 0 = 500,000 Hz +// 960 = 50,000 Hz +// 9600 = 5,000 Hz +#define CLOCK_DIV 9600 +#define CAPTURE_CHANNEL 0 +#define LED_PIN 25 + +float features[NSAMP]; +uint16_t capture_buf[NSAMP]; + +// ############ Lights Stuff ############ +#define PIN 7 + +// ############ Functions ############ +int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) { + memcpy(out_ptr, features + offset, length * sizeof(float)); + return 0; +} + +void core1_entry() { + Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, + NEO_GRB + NEO_KHZ800); + strip.begin(); + strip.setBrightness(64); + strip.show(); + + // tell the other core we're ready for data + multicore_fifo_push_blocking(0); + + uint32_t state = 0; + bool sent_req = false; + uint32_t loop_idx = 0; + while (1) { + // check for new state information + if (multicore_fifo_rvalid()) { + uint32_t val = multicore_fifo_pop_blocking(); + + // 0 means state unchanged + if (val != 0) { + state = val; + } + + sent_req = false; + } + + else if (!sent_req) { + // tell the other core we're ready for data + multicore_fifo_push_blocking(0); + // make sure we don't fill up the other queue + sent_req = true; + } + + // turn on + if (state == 1) { + uint16_t i, j; + + for(j=0; j<256; j++) { // 5 cycles of all colors on wheel + for(i=0; i< strip.numPixels(); i++) { + uint8_t WheelPos = ((i * 256 / strip.numPixels()) + j) & 255; + uint32_t c = 0; + + WheelPos = 255 - WheelPos; + if(WheelPos < 85) { + c = strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + if(WheelPos < 170) { + WheelPos -= 85; + c = strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); + } + WheelPos -= 170; + c = strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); + + strip.setPixelColor(i, c); + } + + strip.show(); + sleep_ms(10); + } + } + + // turn off + else if (state == 2) { + strip.clear(); + strip.show(); + sleep_ms(200); + } + } +} + +int main() +{ + stdio_usb_init(); + stdio_init_all(); + + multicore_launch_core1(core1_entry); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + ei_impulse_result_t result = {nullptr}; + + signal_t features_signal; + features_signal.total_length = NSAMP; + features_signal.get_data = &raw_feature_get_data; + + if (NSAMP != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) { + while (1) { + printf("Input frame size incorrect!\n"); + sleep_ms(2000); + } + } + + adc_gpio_init(26 + CAPTURE_CHANNEL); + + adc_init(); + adc_select_input(CAPTURE_CHANNEL); + adc_fifo_setup( + true, // Write conversions to the sample FIFO + true, // Enable DMA data request (DREQ) + 1, // DREQ (and IRQ) true when >= 1 sample there + false, // Disable err bit + false // No 8-bit shift + ); + + // set sample rate + adc_set_clkdiv(CLOCK_DIV); + + sleep_ms(1000); + // Set up the DMA to start xfer data as soon as it appears in FIFO + uint dma_chan = dma_claim_unused_channel(true); + dma_channel_config cfg = dma_channel_get_default_config(dma_chan); + + // Reading from constant address, writing to incrementing byte address + channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16); + channel_config_set_read_increment(&cfg, false); + channel_config_set_write_increment(&cfg, true); + + // Pace transfers based on availability of ADC samples + channel_config_set_dreq(&cfg, DREQ_ADC); + + while (true) { + adc_fifo_drain(); + adc_run(false); + + dma_channel_configure(dma_chan, &cfg, + capture_buf, // dst + &adc_hw->fifo, // src + NSAMP, // transfer count + true // start immediately + ); + + gpio_put(LED_PIN, 1); + adc_run(true); + + // invoke the impulse + EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, + false); + + if (res != 0) { + printf("ERROR: Edge Impulse Model Returned %d", res); + return 1; + } + + if (EI_CLASSIFIER_HAS_ANOMALY == 1) printf("Anomaly!\n"); + + const float thresh = 0.9; + + uint32_t state = 0; + for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { + if (ix == 0 && result.classification[ix].value > thresh) { + printf("GO\n"); + state = 1; + } + + if (ix == 2 && result.classification[ix].value > thresh) { + printf("STOP\n"); + state = 2; + } + } + + if (multicore_fifo_rvalid()) { + multicore_fifo_pop_blocking(); + multicore_fifo_push_blocking(state); + } + + gpio_put(LED_PIN, 0); + dma_channel_wait_for_finish_blocking(dma_chan); + + // copy everything to feature buffer to run model + // this is probably slow, idk + uint64_t sum = 0; + for (uint32_t i=0; i/external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# todo document + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/pico-voice-v1/source/main.cpp b/pico-voice-v1/source/main.cpp new file mode 100644 index 0000000..983473c --- /dev/null +++ b/pico-voice-v1/source/main.cpp @@ -0,0 +1,143 @@ +#include "ei_run_classifier.h" + +#include +#include +#include +#include +#include +#include +#include + +#define NSAMP 5000 +// set this to determine sample rate +// 0 = 500,000 Hz +// 960 = 50,000 Hz +// 9600 = 5,000 Hz +#define CLOCK_DIV 9600 + +#define CAPTURE_CHANNEL 0 +#define LED_PIN 25 + +float features[NSAMP]; +uint16_t capture_buf[NSAMP]; + +int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) +{ + memcpy(out_ptr, features + offset, length * sizeof(float)); + return 0; +} + +int main() +{ + stdio_usb_init(); + stdio_init_all(); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + ei_impulse_result_t result = {nullptr}; + + signal_t features_signal; + features_signal.total_length = NSAMP; + features_signal.get_data = &raw_feature_get_data; + + if (NSAMP != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) { + while (1) { + printf("Input frame size incorrect!\n"); + sleep_ms(2000); + } + } + + adc_gpio_init(26 + CAPTURE_CHANNEL); + + adc_init(); + adc_select_input(CAPTURE_CHANNEL); + adc_fifo_setup( + true, // Write conversions to the sample FIFO + true, // Enable DMA data request (DREQ) + 1, // DREQ (and IRQ) true when >= 1 sample there + false, // Disable err bit + false // No 8-bit shift + ); + + // set sample rate + adc_set_clkdiv(CLOCK_DIV); + + sleep_ms(1000); + // Set up the DMA to start xfer data as soon as it appears in FIFO + uint dma_chan = dma_claim_unused_channel(true); + dma_channel_config cfg = dma_channel_get_default_config(dma_chan); + + // Reading from constant address, writing to incrementing byte address + channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16); + channel_config_set_read_increment(&cfg, false); + channel_config_set_write_increment(&cfg, true); + + // Pace transfers based on availability of ADC samples + channel_config_set_dreq(&cfg, DREQ_ADC); + + while (true) { + adc_fifo_drain(); + adc_run(false); + + dma_channel_configure(dma_chan, &cfg, + capture_buf, // dst + &adc_hw->fifo, // src + NSAMP, // transfer count + true // start immediately + ); + + gpio_put(LED_PIN, 1); + adc_run(true); + + // invoke the impulse + EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, + false); + + if (res != 0) { + printf("run_classifier returned: %d\n", res); + return 1; + } + + // uncomment this for timing information + /* + printf("DSP: %d ms., Class.: %d ms., Anomaly: %d ms \n", + result.timing.dsp, result.timing.classification, + result.timing.anomaly); + */ + + if (EI_CLASSIFIER_HAS_ANOMALY == 1) printf("Anomaly!\n"); + + + const float thresh = 0.9; + for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { + //printf("%.5f", result.classification[ix].value); + //if (ix != EI_CLASSIFIER_LABEL_COUNT - 1) printf(", "); + + + if (ix == 0 && result.classification[ix].value > thresh) + printf("GO\n"); + if (ix == 2 && result.classification[ix].value > thresh) + printf("STOP\n"); + + } + + //printf("\n"); + + gpio_put(LED_PIN, 0); + dma_channel_wait_for_finish_blocking(dma_chan); + + // Copy everything to feature buffer to run model. In my training, + // I fed the model float values from WAVs so we need to bring the + // sample level to zero and convert to floats + uint64_t sum = 0; + for (uint32_t i=0; i