Compare commits

...

89 commits

Author SHA1 Message Date
Tyeth Gundry
4447258dcf
Update library.properties - bump version to 1.4.0 2025-06-17 19:31:16 +01:00
Liz
fc96155cf3
Merge pull request #16 from adafruit/rp2350_fixes
Fix high-numbered RP2350's
2025-06-03 17:58:41 -04:00
Liz
452d910680 match CI clang 2025-06-03 17:40:55 -04:00
Liz
41b8c1bd3b clang 2025-06-03 17:21:01 -04:00
Liz
77c12cf76a update sdfat - adafruit fork include for cpfs examples 2025-06-03 17:01:58 -04:00
ladyada
36d3bea700 Fix high-numbered RP2350's, also removed the 'fixed' pio argument, we will pick the next available PIO with space (which means we can also select pio2 automatically on rp2350) 2025-05-26 12:48:07 -04:00
Tyeth Gundry
07378a4d98
Update library.properties - bump version to 1.3.0 2024-09-18 19:59:40 +01:00
Limor "Ladyada" Fried
8cf8ec9d51
Merge pull request #15 from adafruit/runs-on-amethyst
Add RP235x to many RP2040 references in documentation & comments
2024-09-11 15:10:36 -04:00
377a497540 Add RP235x to many RP2040 references in documentation & comments 2024-09-11 13:53:52 -05:00
Limor "Ladyada" Fried
cb3253f2ec
Merge pull request #14 from adafruit/build-rp2350
run CI on the pico 2350
2024-09-11 13:59:45 -04:00
b75683cf4c run CI on the pico 2350 2024-09-11 11:52:07 -05:00
6d158f300b
Merge pull request #11 from MarcusGarfunkel/ISSUE-10-pio-sm-handling
Destructor frees pio resources.
2024-09-11 11:51:41 -05:00
e8251ad309 Merge remote-tracking branch 'origin/master' into ISSUE-10-pio-sm-handling 2024-09-11 11:26:12 -05:00
Phillip Burgess
5f09a9b8ba clang fix 2023-05-19 14:03:29 -07:00
Phillip Burgess
b01b27dcab Fix clang 2023-05-19 13:51:26 -07:00
Phillip Burgess
87bb882f59 Fix HDR clear() 2023-05-19 13:32:57 -07:00
dherrada
0874ce8775 Update CI action versions 2023-05-12 11:24:05 -04:00
Paint Your Dragon
d4ff50edf6
Merge pull request #12 from adafruit/pb-flexi-video-examples
VideoMSC and VideoSerial now use config file for LED layout
2023-04-20 12:00:38 -07:00
Phillip Burgess
149f6c17b0 Add more skipfiles 2023-04-20 11:44:38 -07:00
Phillip Burgess
4ffe3d3d9e Add ESP32 skip files 2023-04-20 11:10:25 -07:00
Paint Your Dragon
7043878f7c
Merge branch 'master' into pb-flexi-video-examples 2023-04-20 10:37:32 -07:00
Phillip Burgess
03f5921118 "Downbump" version; would-be 1.3 changes moved Adafruit_CPFS 2023-04-20 10:32:32 -07:00
Phillip Burgess
5ab8aa4c37 Update VideoMSC/Serial examples for Adafruit_CPFS class 2023-04-20 10:30:54 -07:00
Phillip Burgess
86997e31cc Update comments 2023-04-03 10:07:57 -07:00
Phillip Burgess
46b8ece698 Fix typo in comments 2023-04-03 09:49:31 -07:00
Marcus Garfunkel
c1354e07fb Destructor frees pio resources. 2023-03-23 00:43:56 -07:00
Phillip Burgess
b14e8643ee clang-format 2023-02-10 10:12:53 -08:00
Phillip Burgess
a962118778 Put JSON sizes back at 1024, tweak default pins, add sketch comments re SAMD 2023-02-10 09:50:09 -08:00
Phillip Burgess
009f6f47f6 Allow for bigger JSON file just in case 2023-02-09 20:34:16 -08:00
Phillip Burgess
aa44008f32 Update moar examples to use JSON config 2023-02-09 15:07:14 -08:00
Phillip Burgess
bf5af30c38 NeoPXL8HDR VideoSerial working 2023-02-09 11:20:21 -08:00
Phillip Burgess
65dd928e1c VideoSerial example working w/new JSON stuff 2023-02-09 09:41:23 -08:00
Phillip Burgess
e2855c6c1b NeoPXL8/VideoSerial example updated to use neopxl8.cfg 2023-02-08 16:56:25 -08:00
Phillip Burgess
5d552dea80 Fix pin drive strength to match main branch 2023-02-08 13:39:10 -08:00
Paint Your Dragon
c1cb252440
Version bump for RP2040 drive strength fix 2023-02-08 10:50:39 -08:00
Paint Your Dragon
82f6496563
RP2040: use 2 mA drive strength to avoid lockup
TBD whether this should be SCORPIO only. Tested & seems OK on both Feather+Wing and SCORPIO, but if it causes Wing problems for others, will add an #ifdef around it.
2023-02-08 10:50:00 -08:00
Phillip Burgess
0b2ff496c4 Big ol' do-over, created FFS class instead of the config lib 2023-02-07 09:23:52 -08:00
Phillip Burgess
832bbebb1b VideoSerial cleanup 2023-02-03 18:34:20 -08:00
Phillip Burgess
2376a11b8a Changed config to be more openly JSON-like, working on VideoSerial example 2023-02-03 16:31:22 -08:00
Phillip Burgess
2fa9dca1ee clang-format 2023-02-03 11:32:51 -08:00
Phillip Burgess
f4ea2c4aea CI is picky about parenthesis 2023-02-03 11:07:43 -08:00
Phillip Burgess
ef216d4bb7 More Doxy, ESP32 CI assistance 2023-02-03 10:46:53 -08:00
Phillip Burgess
ff4e72910e Doxy, comment, add internal flash support 2023-02-03 10:03:09 -08:00
Phillip Burgess
889c4c88ef Fix U&LC, add hooks for extra JSON data 2023-02-02 21:12:20 -08:00
Phillip Burgess
969dcf9a21 Some reorg and cleanup, affects more than just config code now 2023-02-02 18:11:15 -08:00
Phillip Burgess
1ab2557557 WIP, more configurables 2023-02-02 12:14:13 -08:00
Phillip Burgess
72b82f3da7 Config file WIP 2023-02-02 11:57:06 -08:00
Paint Your Dragon
c24c1325b6
Merge pull request #7 from adafruit/pb-rp2040-irq
Use shared DMA IRQ on RP2040
2022-12-26 15:48:27 -08:00
Phillip Burgess
7ecc003814 Version bump for RP2040 DMA fix 2022-12-26 15:31:49 -08:00
Phillip Burgess
be1db47829 clang-format 2022-12-26 15:19:08 -08:00
Phillip Burgess
ef83c28f45 RP2040: use shared (not exclusive) IRQ handler 2022-12-26 15:04:21 -08:00
Phillip Burgess
30eb6ca6b1 Appease clang-format 2022-10-28 19:16:12 -07:00
Phillip Burgess
ba4d8eb035 RP2040: init PIO outputs AFTER state machine claimed 2022-10-28 18:58:26 -07:00
Phillip Burgess
41345938be Appease the clang monster 2022-08-23 13:24:27 -07:00
Phillip Burgess
d9e5679414 ESP32S3: avoid PSRAM for DMA-capable transfers 2022-08-23 13:12:41 -07:00
Paint Your Dragon
80ed7127fc
Merge pull request #5 from adafruit/esp32s3
Add ESP32S3 support and NeoPXL8HDR class
2022-07-07 18:35:15 -07:00
Phillip Burgess
b85e20e143 Appease clang-format 2022-07-07 18:22:30 -07:00
Phillip Burgess
dfefbf6b26 clang-format the Fire examples 2022-07-07 18:02:42 -07:00
Phillip Burgess
4ef03e5e11 More Actions workflow tweaks
VideoMSC skips esp32 because no tinyusb stack
2022-07-07 17:52:33 -07:00
Phillip Burgess
0014905b9b lib.props: add SPIFlash, TinyUSB to deps 2022-07-07 17:38:33 -07:00
Phillip Burgess
92565dfdb4 library.properties: add SdFat to dependencies 2022-07-07 17:30:44 -07:00
Phillip Burgess
3056009e6e Github Actions: use TinyUSB stack in build test (for VideoMSC example) 2022-07-07 17:17:56 -07:00
Phillip Burgess
b2e4d53746 Add ESP32S3 support and NeoPXL8HDR class
ONLY S3 support is added; S2 and original ESP32 are NOT supported
2022-07-07 16:03:53 -07:00
Limor "Ladyada" Fried
c65af944e0
Merge pull request #4 from adafruit/rp2040
whew pio support for rp2040!
2021-09-10 21:21:09 -04:00
lady ada
15050e16f0 klong 2021-09-10 20:38:45 -04:00
lady ada
66ca593d98 reorder 2021-09-10 20:36:06 -04:00
lady ada
f033fc0839 fix 2 more warnings 2021-09-10 20:35:18 -04:00
lady ada
6813cddc06 different test? 2021-09-10 20:31:51 -04:00
lady ada
d6a7f21643 reduce warnings 2021-09-10 20:29:56 -04:00
lady ada
c90cd05442 mebe test more plats 2021-09-10 20:25:57 -04:00
lady ada
aec60fe413 whew pio support for rp2040! 2021-09-10 20:23:50 -04:00
Phillip Burgess
b2fc1008ed Example updated with Adafruit Grand Central pin options 2021-04-02 16:03:07 -07:00
siddacious
9e5afceba4
Update library.properties 2020-02-28 17:50:10 -08:00
Phillip Burgess
a8799d2888 Update strandtest for M4 FeatherWing usage 2020-02-26 22:31:11 -08:00
siddacious
2e254b24d2
Update library.properties 2020-02-17 18:24:22 -08:00
Limor "Ladyada" Fried
7732f2ddf5
Merge pull request #3 from adafruit/actions
Convert to actions
2020-02-10 16:32:39 -05:00
ladyada
f297e540a9 remove travis 2020-02-10 16:26:17 -05:00
ladyada
ac943b71e6 clanged 2020-02-10 16:24:01 -05:00
ladyada
18e6fc5e6a add actions 2020-02-10 16:21:33 -05:00
ladyada
ef6338aa08 fix url 2020-02-10 14:21:08 -05:00
siddacious
9b96a47750
bump 2019-11-27 11:11:45 -08:00
Phillip Burgess
58b622cd9e Add Feather M4 Express pins to example 2019-11-21 21:03:37 -08:00
Jan Hoffmann
6fc1788fc2
Update library.properties 2019-07-04 11:51:51 +02:00
Phillip Burgess
6998ae28d6 Remove mention of ASFcore lib 2018-05-29 20:58:07 -07:00
Phillip Burgess
1b3198e492 Fix DMA lib name better? 2018-05-28 20:56:05 -07:00
Phillip Burgess
3413e604fc Fix Zero DMA lib name 2018-05-28 20:49:56 -07:00
Phillip Burgess
ea5ae002fa Add travis.yml 2018-05-28 20:40:45 -07:00
Phillip Burgess
f110c40db7 Doxygen notes WIP 2018-05-28 20:29:37 -07:00
ladyada
4c028e959a prop 2018-05-20 15:20:19 -04:00
31 changed files with 4942 additions and 346 deletions

46
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,46 @@
Thank you for opening an issue on an Adafruit Arduino library repository. To
improve the speed of resolution please review the following guidelines and
common troubleshooting steps below before creating the issue:
- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use
the forums at http://forums.adafruit.com to ask questions and troubleshoot why
something isn't working as expected. In many cases the problem is a common issue
that you will more quickly receive help from the forum community. GitHub issues
are meant for known defects in the code. If you don't know if there is a defect
in the code then start with troubleshooting on the forum first.
- **If following a tutorial or guide be sure you didn't miss a step.** Carefully
check all of the steps and commands to run have been followed. Consult the
forum if you're unsure or have questions about steps in a guide/tutorial.
- **For Arduino projects check these very common issues to ensure they don't apply**:
- For uploading sketches or communicating with the board make sure you're using
a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes
very hard to tell the difference between a data and charge cable! Try using the
cable with other devices or swapping to another cable to confirm it is not
the problem.
- **Be sure you are supplying adequate power to the board.** Check the specs of
your board and plug in an external power supply. In many cases just
plugging a board into your computer is not enough to power it and other
peripherals.
- **Double check all soldering joints and connections.** Flakey connections
cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints.
- **Ensure you are using an official Arduino or Adafruit board.** We can't
guarantee a clone board will have the same functionality and work as expected
with this code and don't support them.
If you're sure this issue is a defect in the code and checked the steps above
please fill in the following fields to provide enough troubleshooting information.
You may delete the guideline and text above to just leave the following details:
- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE**
- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO
VERSION HERE**
- List the steps to reproduce the problem below (if possible attach a sketch or
copy the sketch code in too): **LIST REPRO STEPS BELOW**

26
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,26 @@
Thank you for creating a pull request to contribute to Adafruit's GitHub code!
Before you open the request please review the following guidelines and tips to
help it be more easily integrated:
- **Describe the scope of your change--i.e. what the change does and what parts
of the code were modified.** This will help us understand any risks of integrating
the code.
- **Describe any known limitations with your change.** For example if the change
doesn't apply to a supported platform of the library please mention it.
- **Please run any tests or examples that can exercise your modified code.** We
strive to not break users of the code and running tests/examples helps with this
process.
Thank you again for contributing! We will try to test and integrate the change
as soon as we can, but be aware we have many GitHub repositories to manage and
can't immediately respond to every request. There is no need to bump or check in
on a pull request (it will clutter the discussion of the request).
Also don't be worried if the request is closed or not integrated--sometimes the
priorities of Adafruit's GitHub code (education, ease of use) might not match the
priorities of the pull request. Don't fret, the open source community thrives on
forks and GitHub makes it easy to keep your changes in a forked repo.
After reviewing the guidelines above you can delete this text from the pull request.

32
.github/workflows/githubci.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Arduino Library CI
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- uses: actions/checkout@v3
- uses: actions/checkout@v3
with:
repository: adafruit/ci-arduino
path: ci
- name: pre-install
run: bash ci/actions_install.sh
- name: test platforms
run: python3 ci/build_platform.py feather_m0_express_tinyusb metro_m4_tinyusb pico_rp2040_tinyusb feather_esp32s3 pico_rp2350_tinyusb
- name: clang
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .
- name: doxygen
env:
GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }}
PRETTYNAME : "Adafruit NeoPXL8 Arduino Library"
run: bash ci/doxy_gen_and_deploy.sh

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Our handy .gitignore for automation ease
Doxyfile*
doxygen_sqlite3.db
html

File diff suppressed because it is too large Load diff

View file

@ -1,33 +1,571 @@
// SPDX-FileCopyrightText: 2017 P Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*!
* @file Adafruit_NeoPXL8.h
*
* 8-way concurrent DMA NeoPixel library for SAMD21, SAMD51, RP2040, RP235x,
* and ESP32S3 microcontrollers.
*
* 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.
*
* MIT license, all text here must be included in any redistribution.
*
*/
#ifndef _ADAFRUIT_NEOPXL8_H_
#define _ADAFRUIT_NEOPXL8_H_
#include <Adafruit_NeoPixel.h>
#if defined(ARDUINO_ARCH_RP2040)
#include "../../hardware_dma/include/hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/pio.h"
#include "pico/mutex.h"
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#include <driver/periph_ctrl.h>
#include <esp_private/gdma.h>
#include <esp_rom_gpio.h>
#include <hal/dma_types.h>
#include <hal/gpio_hal.h>
#include <soc/lcd_cam_struct.h>
#else // SAMD
#include <Adafruit_ZeroDMA.h>
#endif
// NEOPXL8 CLASS -----------------------------------------------------------
/*!
@brief Adafruit_NeoPXL8 is a subclass of Adafruit_NeoPixel containing
buffers for DMA-formatted 8-way concurrent output. Once a transfer
is initiated, the original NeoPixel data can then be modified for
the next frame while the transfer operates in the background.
*/
class Adafruit_NeoPXL8 : public Adafruit_NeoPixel {
public:
public:
/*!
@brief NeoPXL8 constructor. Instantiates a new NeoPXL8 object (must
follow with a begin() call to alloc buffers and init hardware).
@param n
Length of each NeoPixel strand (total number of pixels will be
8X this).
@param p
Optional int8_t array of eight pin numbers for NeoPixel strands
0-7. There are specific hardware limitations as to which pins
can be used, see the example sketch. If fewer than 8 outputs are
needed, assign a value of -1 to the unused outputs, keeping in
mind that this will always still use the same amount of memory
as 8-way output. If unspecified (or if NULL is passed), a
default 8-pin setup will be used (see example sketch).
On RP2040 and RP235x, these are GP## numbers, not necessarily the
digital pin numbers silkscreened on the board.
@param t
NeoPixel color data order, same as in Adafruit_NeoPixel library
(optional, default is GRB).
*/
Adafruit_NeoPXL8(uint16_t n, int8_t *p = NULL, neoPixelType t = NEO_GRB);
~Adafruit_NeoPXL8(void);
Adafruit_NeoPXL8(uint16_t n, int8_t *p=NULL, neoPixelType t=NEO_GRB);
~Adafruit_NeoPXL8();
/*!
@brief Allocate buffers and initialize hardware for NeoPXL8 output.
@param dbuf If true, 2X DMA buffers are allocated so that a frame
can be staged while the prior is in mid-transfer.
Might yield slightly improved frame rates in some cases,
others just waste RAM. Super esoteric and mostly for
NeoPXL8HDR's use. Currently ignored on SAMD.
@return true on successful alloc/init, false otherwise.
*/
bool begin(bool dbuf = false);
boolean begin(void),
canStage(void),
canShow(void);
void show(),
stage(),
setBrightness(uint8_t);
uint8_t getBrightness() const;
/*!
@brief Process and issue new data to the NeoPixel strands.
*/
void show(void);
protected:
/*!
@brief Preprocess NeoPixel data into DMA-ready format, but do not issue
to strands yet. Esoteric but potentially useful if aiming to
have pixels refresh at precise intervals (since preprocessing is
not deterministic) -- e.g. 60 Hz -- follow with a call to show()
when the actual data transfer is to occur.
*/
void stage(void);
Adafruit_ZeroDMA dma;
int8_t pins[8]; // Pin list for 8 NeoPixel strips
uint8_t bitmask[8], // Pattern generator bitmask for each pin
*dmaBuf; // Allocated buffer for pixel data + extra
uint32_t *alignedAddr; // long-aligned ptr into dmaBuf (see code)
uint16_t brightness;
boolean staged; // If set, data is ready for DMA trigger
/*!
@brief Poll whether last show() transfer has completed and library is
idle. Of questionable utility, but provided for compatibility
with similarly questionable function in the NeoPixel library.
@return true if show() can be called without blocking, false otherwise.
*/
bool canShow(void) const;
/*!
@brief Poll whether last show() data transfer has completed, but not
necessarily the end-of-data latch. This is the earliest moment
at which one can stage() the next frame of data.
@return true if stage() can safely be called, false otherwise.
*/
bool canStage(void) const;
// Brightness is stored differently here than in normal NeoPixel library.
// In either case it's *specified* the same: 0 (off) to 255 (brightest).
// Classic NeoPixel rearranges this internally so 0 is max, 1 is off and
// 255 is just below max...it's a decision based on how fixed-point math
// is handled in that code. Here it's stored internally as 1 (off) to
// 256 (brightest), requiring a 16-bit value.
/*!
@brief Set strip brightness for subsequent show() calls. Unlike the
Adafruit_NeoPixel library, this function is non-destructive --
original color values passed to setPixelColor() will always be
accurately returned by getPixelColor(), even if brightness is
adjusted.
@param b
Brightness, from 0 (off) to 255 (maximum).
*/
void setBrightness(uint8_t b) { brightness = (uint16_t)b + 1; }
/*!
@brief Query brightness value last assigned with setBrightness().
@return Brightness, from 0 (off) to 255 (maximum).
*/
uint8_t getBrightness(void) const { return brightness - 1; }
/*!
@brief Change the NeoPixel end-of-data latch period. Here be dragons.
@param us Latch time in microseconds. Different manufacturers and
generations of pixels may have different end-of-data latch
periods. For example, early WS2812 pixels recommend a 50 uS
latch, late-model WS2812Bs suggest 300 (the default value).
A shorter latch may allow very slightly faster refresh rates
(a few percent at best -- strand length is a much bigger
influence). Check datasheet for your specific pixels to get
a recommended figure, then you can try empirically dialing
down from there, with the understanding that this change may
not work with other pixels. What you ABSOLUTELY MUST NOT DO
is rely on the bare minimum value that works on your bench,
because addressable LEDs are affected by environmental
factors like temperature; an installation relying on a bare-
minimum figure WILL fail when relocated, e.g. Burning Man.
ALWAYS allow a generous overhead. Better yet, don't mess
with it.
*/
void setLatchTime(uint16_t us = 300) { latchtime = us; };
#if defined(ARDUINO_ARCH_RP2040)
/*!
@brief Callback function used internally by the DMA transfer interrupt.
User code shouldn't access this, but it couldn't be put in the
protected section below because IRQ is outside the class context.
Ignore it, thanks.
@return None (void).
*/
void dma_callback(void);
#endif
protected:
#if defined(ARDUINO_ARCH_RP2040)
PIO pio = NULL; ///< PIO peripheral
uint sm = -1; ///< State machine #
uint offset = 0;
int dma_channel; ///< DMA channel #
dma_channel_config dma_config; ///< DMA configuration
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
gdma_channel_handle_t dma_chan; ///< DMA channel
dma_descriptor_t *desc; ///< DMA descriptor pointer
uint8_t *allocAddr; ///< Allocated buf into which dmaBuf points
uint32_t *alignedAddr[2]; ///< long-aligned ptrs into dmaBuf
#else // SAMD
Adafruit_ZeroDMA dma; ///< DMA object
DmacDescriptor *desc; ///< DMA descriptor pointer
uint8_t *allocAddr; ///< Allocated buffer into which dmaBuf points
uint32_t *alignedAddr[2]; ///< long-aligned ptrs into dmaBuf
#endif
int8_t pins[8]; ///< Pin list for 8 NeoPixel strips
uint8_t bitmask[8]; ///< Pattern generator bitmask for each pin
uint8_t *dmaBuf[2] = {NULL, NULL}; ///< Buffer for pixel data + any extra
uint16_t brightness = 255; ///< Brightness (stored 1-256, not 0-255)
bool staged; ///< If set, data is ready for DMA trigger
uint16_t latchtime = 300; ///< Pixel data latch time, microseconds
uint8_t dbuf_index = 0; ///< 0/1 DMA buffer index
};
// NEOPXL8HDR CLASS --------------------------------------------------------
/*!
@brief Adafruit_NeoPXL8HDR is a subclass of Adafruit_NeoPXL8 with
additions for 16-bits-per-channel color, temporal dithering,
frame blending and gamma correction. This requires inordinate RAM,
and the frequent need for refreshing makes it best suited for
multi-core chips (e.g. RP2040, RP235x).
*/
class Adafruit_NeoPXL8HDR : public Adafruit_NeoPXL8 {
public:
/*!
@brief NeoPXL8HDR constructor. Instantiates a new NeoPXL8HDR object
(must follow with a begin() call to alloc buffers and init
hardware).
@param n
Length of each NeoPixel strand (total number of pixels will be
8X this).
@param p
Optional int8_t array of eight pin numbers for NeoPixel strands
0-7. There are specific hardware limitations as to which pins
can be used, see the example sketch. If fewer than 8 outputs are
needed, assign a value of -1 to the unused outputs, keeping in
mind that this will always still use the same amount of memory
as 8-way output. If unspecified (or if NULL is passed), a
default 8-pin setup will be used (see example sketch).
On RP2040 and RP235x, these are GP## numbers, not necessarily the
digital pin numbers silkscreened on the board.
@param t
NeoPixel color data order, same as in Adafruit_NeoPixel library
(optional, default is GRB).
*/
Adafruit_NeoPXL8HDR(uint16_t n, int8_t *p = NULL, neoPixelType t = NEO_GRB);
~Adafruit_NeoPXL8HDR();
/*!
@brief Allocate buffers and initialize hardware for NeoPXL8 output.
@param blend If true, provide frame-to-frame blending via the
refresh() function between show() calls (uses more RAM).
If false (default), no blending. This is useful ONLY if
sketch can devote time to many refresh() calls, e.g. on
multicore RP2040 or RP235x.
@param bits Number of bits for temporal dithering, 0-8. Higher values
provide more intermediate shades but slower refresh;
dither becomes more apparent. Default is 4, providing
12-bit effective range per channel.
@param dbuf If true, 2X DMA buffers are allocated so that a frame
can be NeoPXL8-staged while the prior is in mid-transfer.
Might yield slightly improved frame rates in some cases,
others just waste RAM. Currently ignored on SAMD.
@return true on successful alloc/init, false otherwise.
*/
bool begin(bool blend = false, uint8_t bits = 4, bool dbuf = false);
/*!
@brief Set peak output brightness for all channels (RGB and W if
present) to the same value. Existing gamma setting is unchanged.
This is for compatibility with existing NeoPixel or NeoPXL8
sketches moved directly to NeoPXL8HDR. New code may prefer
one of the other setBrightness() invocations, which provide
16-bit adjustment plus gamma correction.
@param b Brightness value, 0-255. This is the LEDs' maximum duty
cycle and is not itself gamma-corrected.
@note This is typically set once at program startup and is not
intended as an animation effect in itself, partly because the
top value is an duty cycle and not a gamma-corrected level.
Also, pixels will likely flash if this is called on an active
NeoPXL8HDR object, as gamma tables are rewritten while
blending/dithering is occurring. Well-designed animation code
should handle its own fades!
*/
void setBrightness(uint8_t b);
/*!
@brief Set peak output brightness for all channels (RGB and W if
present) to the same value, and set gamma curve.
@param b Brightness value, 0-65535. This is the LEDs' maximum duty
cycle and is not itself gamma-corrected.
@param y Gamma exponent; 1.0 is linear, 2.6 is a typical correction
factor for NeoPixels.
@note This is typically set once at program startup and is not
intended as an animation effect in itself, partly because the
top value is an duty cycle and not a gamma-corrected level.
Also, pixels will likely flash if this is called on an active
NeoPXL8HDR object, as gamma tables are rewritten while
blending/dithering is occurring. Well-designed animation code
should handle its own fades!
Note to future self: do NOT provide a default gamma value here,
it MUST be specified, even if 1.0. This avoids ambiguity with
the back-compatible setBrightness(uint8_t) above without weird
explicit casts in user code.
*/
void setBrightness(uint16_t b, float y);
/*!
@brief Set peak output brightness for R, G, B channels independently.
Existing gamma setting is unchanged. W brightness, if present,
is unchanged.
@param r Red brightness value, 0-65535. This is the maximum duty cycle
and is not itself gamma-corrected.
@param g Green brightness value, 0-65535.
@param b Blue brightness value, 0-65535.
@note This is typically set once at program startup and is not
intended as an animation effect in itself, partly because the
top value is an duty cycle and not a gamma-corrected level.
Also, pixels will likely flash if this is called on an active
NeoPXL8HDR object, as gamma tables are rewritten while
blending/dithering is occurring. Well-designed animation code
should handle its own fades!
*/
void setBrightness(uint16_t r, uint16_t g, uint16_t b);
/*!
@brief Set peak output brightness for R, G, B, W channels independently.
Existing gamma setting is unchanged.
@param r Red brightness value, 0-65535. This is the maximum duty cycle
and is not itself gamma-corrected.
@param g Green brightness value, 0-65535.
@param b Blue brightness value, 0-65535.
@param w White brightness value, 0-65535. Ignored if NeoPixel strips
are RGB variety with no W.
@note This is typically set once at program startup and is not
intended as an animation effect in itself, partly because the
top value is an duty cycle and not a gamma-corrected level.
Also, pixels will likely flash if this is called on an active
NeoPXL8HDR object, as gamma tables are rewritten while
blending/dithering is occurring. Well-designed animation code
should handle its own fades!
*/
void setBrightness(uint16_t r, uint16_t g, uint16_t b, uint16_t w);
/*!
@brief Set peak output brightness for R, G, B channels independently,
and set gamma curve. W brightness, if present, is unchanged.
@param r Red brightness value, 0-65535. This is the maximum duty cycle
and is not itself gamma-corrected.
@param g Green brightness value, 0-65535.
@param b Blue brightness value, 0-65535.
@param y Gamma exponent; 1.0 is linear, 2.6 is a typical correction
factor for NeoPixels.
@note This is typically set once at program startup and is not
intended as an animation effect in itself, partly because the
top value is an duty cycle and not a gamma-corrected level.
Also, pixels will likely flash if this is called on an active
NeoPXL8HDR object, as gamma tables are rewritten while
blending/dithering is occurring. Well-designed animation code
should handle its own fades!
*/
void setBrightness(uint16_t r, uint16_t g, uint16_t b, float y);
/*!
@brief Set peak output brightness for R, G, B, W channels independently,
and set gamma curve.
@param r Red brightness value, 0-65535. This is the maximum duty cycle
and is not itself gamma-corrected.
@param g Green brightness value, 0-65535.
@param b Blue brightness value, 0-65535.
@param w White brightness value, 0-65535. Ignored if NeoPixel strips
are RGB variety with no W.
@param y Gamma exponent; 1.0 is linear, 2.6 is a typical correction
factor for NeoPixels.
@note This is typically set once at program startup and is not
intended as an animation effect in itself, partly because the
top value is an duty cycle and not a gamma-corrected level.
Also, pixels will likely flash if this is called on an active
NeoPXL8HDR object, as gamma tables are rewritten while
blending/dithering is occurring. Well-designed animation code
should handle its own fades!
*/
void setBrightness(uint16_t r, uint16_t g, uint16_t b, uint16_t w, float y);
/*!
@brief Provide new pixel data to the refresh handler (but does not
actually refresh the strip - use refresh() for that).
*/
void show(void);
/*!
@brief Dither (and blend, if enabled) and issue new data to the
NeoPixel strands.
*/
void refresh(void);
/*!
@brief Overload the stage() function from Adafruit_NeoPXL8.
Does nothing in NeoPXL8HDR, provided for compatibility.
*/
void stage(void) const {};
/*!
@brief Overload the canStage() function from Adafruit_NeoPXL8.
Does nothing in NeoPXL8HDR, provided for compatibility.
@return true always.
*/
bool canStage(void) const { return true; }
/*!
@brief Overload the canShow() function from Adafruit_NeoPXL8.
Does nothing in NeoPXL8HDR, provided for compatibility.
@return true always.
*/
bool canShow(void) const { return true; }
/*!
@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 setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
/*!
@brief Set a pixel's color using separate red, green, blue and white
components (for RGBW NeoPixels).
@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 setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
/*!
@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 setPixelColor(uint16_t n, uint32_t c);
/*!
@brief Set a pixel's color using a 16-bit R, G, B and optionally W
values.
@param n Pixel index, starting from 0.
@param r 16-bit red component.
@param g 16-bit green component.
@param b 16-bit blue component.
@param w 16-bit white component (optional argument, RGBW strips only,
ignored if RGB).
*/
void set16(uint16_t n, uint16_t r, uint16_t g, uint16_t b, uint16_t w = 0);
/*!
@brief Query the color of a previously-set pixel, reduced to 8-bit
components. This is for compatibility with existing NeoPixel
or NeoPXL8 sketches moved directly to NeoPXL8HDR. New code
may prefer get16() instead, which returns the pristine true
16-bit value previously set.
@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 Unlike the NeoPixel library, brightness scaling is not a
destructive operation; the data stored is what was set.
However, color components are reduced from 16 to 8 bits.
"16-bit-aware" code should use get16() instead.
*/
uint32_t getPixelColor(uint16_t n) const;
/*!
@brief Query the color of a previously-set pixel, returning the
original 16-bit values.
@param n Index of pixel to read (0 = first).
@param r Pointer to unsigned 16-bit variable to hold red result,
must be non-NULL, no check performed.
@param g Pointer to unsigned 16-bit variable to hold green result,
must be non-NULL, no check performed.
@param b Pointer to unsigned 16-bit variable to hold blue result,
must be non-NULL, no check performed.
@param w Pointer to unsigned 16-bit variable to hold white result
(if RGBW strip, else 0 if RGB), or NULL to ignore.
@note Unlike the NeoPixel library, the value returned is always equal
to the value previously set; brightness scaling is not a
destructive operation with this library.
*/
void get16(uint16_t n, uint16_t *r, uint16_t *g, uint16_t *b,
uint16_t *w = NULL) const;
/*!
@brief Get a pointer directly to the NeoPXL8HDR data buffer in RAM.
Pixel data is unsigned 16-bit, always RGB (3 words/pixel) or
RGBW (4 words/pixel) order; different NeoPixel hardware color
orders are covered by the library and do not need to be handled
in calling code. Nice.
@return Pointer to NeoPixel buffer (uint16_t* array).
@note This is for high-performance applications where calling set16()
or setPixelColor() on every single pixel would be too slow.
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.
*/
uint16_t *getPixels(void) const { return pixel_buf[2]; }
/*!
@brief Query overall display refresh rate in frames-per-second.
This is only an estimate and requires a moment to stabilize;
will initially be 0. It's really only helpful on RP2040 and RP235x
where the refresh loop runs full-tilt on its own core; on SAMD,
refresh is handled with a fixed timer interrupt.
@return Integer frames (pixel refreshes) per second.
*/
uint32_t getFPS(void) const { return fps; }
/*!
@brief Fill the whole NeoPixel strip with 0 / black / off.
@note Overloaded from Adafruit_NeoPixel because stored different here.
*/
void clear(void) { memset(pixel_buf[2], 0, numBytes * sizeof(uint16_t)); }
protected:
/*!
@brief Recalculate the tables used for gamma correction and temporal
dithering. Used internally, not for user code. Invoked by the
brightness/gamma-setting functions.
*/
void calc_gamma_table(void);
float gfactor; ///< Gamma: 1.0=linear, 2.6=typ
uint16_t *pixel_buf[3] = {NULL, NULL, NULL}; ///< Buffer for NeoPXL8 staging
uint16_t *dither_table = NULL; ///< Temporal dithering lookup
uint32_t last_show_time = 0; ///< micros() @ last show()
uint32_t avg_show_interval = 0; ///< Avergage uS between show()
uint32_t fps = 0; ///< Estimated refreshes/second
uint32_t last_fps_time = 0; ///< micros() @ last estimate
uint16_t g16[4][256]; ///< Gamma look up table
uint16_t brightness_rgbw[4]; ///< Peak brightness/channel
uint8_t dither_bits; ///< # bits for temporal dither
uint8_t dither_index = 0; ///< Current dither_table pos
uint8_t stage_index = 0; ///< Ping-pong pixel_buf
volatile bool new_pixels = true; ///< show()/refresh() sync
#if defined(ARDUINO_ARCH_RP2040)
mutex_t mutex; ///< For synchronizing cores
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
SemaphoreHandle_t mutex; ///< For synchronizing cores
#endif
};
// The DEFAULT_PINS macros provide shortcuts for the most commonly-used pin
// lists on certain boards. For example, with a Feather M0, the default list
// will match an unaltered, factory-fresh NeoPXL8 FeatherWing M0. If ANY pins
// are changed on the FeatherWing, or if using a different pin sequence than
// these defaults, a user sketch must provide its own correct pin list.
// These may work for sloppy quick code but are NOT true in all situations!
#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040_SCORPIO)
#define NEOPXL8_DEFAULT_PINS \
{ 16, 17, 18, 19, 20, 21, 22, 23 }
#elif defined(ADAFRUIT_FEATHER_M0) || defined(ARDUINO_SAMD_FEATHER_M0_EXPRESS)
#define NEOPXL8_DEFAULT_PINS \
{ PIN_SERIAL1_RX, PIN_SERIAL1_TX, MISO, 13, 5, SDA, A4, A3 }
#elif defined(ADAFRUIT_FEATHER_M4_EXPRESS) || \
defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3) || \
defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_NOPSRAM)
#define NEOPXL8_DEFAULT_PINS \
{ SCK, 5, 9, 6, 13, 12, 11, 10 }
#elif defined(ADAFRUIT_METRO_M4_EXPRESS)
#define NEOPXL8_DEFAULT_PINS \
{ 7, 4, 5, 6, 3, 2, 10, 11 }
#elif defined(ADAFRUIT_GRAND_CENTRAL_M4)
#define NEOPXL8_DEFAULT_PINS \
{ 30, 31, 32, 33, 36, 37, 34, 35 }
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define NEOPXL8_DEFAULT_PINS \
{ 6, 7, 9, 8, 13, 12, 11, 10 }
#else
#define NEOPXL8_DEFAULT_PINS \
{ 0, 1, 2, 3, 4, 5, 6, 7 } ///< Generic pin list
#endif
#endif // _ADAFRUIT_NEOPXL8_H_

View file

@ -1,6 +1,5 @@
# Adafruit_NeoPXL8
DMA-driven 8-way concurrent NeoPixel driver for SAMD21 (M0+) boards.
Requires latest Adafruit_NeoPixel and Adafruit_ZeroDMA libraries.
DMA-driven 8-way concurrent NeoPixel driver for SAMD21 (M0+), SAMD51 (M4), RP2040, RP235x, and ESP32S3 microcontrollers. Requires latest Adafruit_NeoPixel and Adafruit_ZeroDMA libraries.
(Pronounced "NeoPixelate")
@ -12,7 +11,7 @@ The first argument, strand length, applies to EACH strand. Total NeoPixel count
## Logic Levels
The SAMD21 is a 3.3V device, while NeoPixels ideally want 5V logic. One solution is a logic level converter such as the 74HCT125N. Alternately, just powering the NeoPixels from a slightly lower voltage (e.g. 4.5V) is sometimes all it takes!
Microcontrollers compatible with this code are all 3.3V devices, while NeoPixels ideally want 5V logic. One solution is a logic level converter such as the 74HCT125N. Alternately, just powering the NeoPixels from a slightly lower voltage (e.g. 4.5V) is sometimes all it takes!
## Pin MUXing
@ -39,4 +38,16 @@ This keeps the Serial1 and SPI peripherals available, but prevents using I2C. A
The '-1' for a pin assignment disables that NeoPixel output (so this would have only 7 concurrent strands), but those pixels still take up memory and positions among the pixel indices.
Pin MUXing is a hairy thing and over time we'll try to build up some ready-to-use examples for different boards and peripherals. You can also try picking your way through the SAMD21 datasheet or the NeoPXL8 source code for pin/peripheral assignments.
Other boards (such as Grand Central) have an altogether different pinout. See the example for insights.
Pin MUXing is a hairy thing and over time we'll try to build up some ready-to-use examples for different boards and peripherals. You can also try picking your way through the SAMD21/51 datasheet or the NeoPXL8 source code for pin/peripheral assignments.
On RP2040 and RP235x boards, the pins can be within any 8-pin range (e.g. 0-7, or 4-11, etc.). If using fewer than 8 outputs, they do not need to be contiguous, but the lowest and highest pin number must still be within 8-pin range.
On ESP32S3 boards, go wild...there are no pin restrictions.
## NeoPXL8HDR
Adafruit_NeoPXL8HDR is a subclass of Adafruit_NeoPXL8 with additions for 16-bit color, temporal dithering, gamma correction and frame blending. This requires inordinate RAM, and the need for frequent refreshing makes it best suited for multi-core chips (e.g. RP2040 and RP235x).
See examples/NeoPXL8HDR/strandtest for use.

View file

@ -0,0 +1,128 @@
// FIRST TIME HERE? START WITH THE NEOPXL8 strandtest EXAMPLE INSTEAD!
// That code explains and helps troubshoot wiring and NeoPixel color format.
#include <Adafruit_NeoPXL8.h>
// CHANGE these to match your strandtest findings or this WILL NOT WORK:
int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
#define COLOR_ORDER NEO_GRB
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
// Animated Fire Example - OctoWS2811 Library
// http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
//
// Based on the simple algorithm explained here:
// http://caraesnaur.github.io/fire/
//
// This example code is in the public domain.
const unsigned int width = 30;
const unsigned int height = 16; // Each strand spans two successive rows
// These parameters control the fire appearance
// (try controlling these with knobs / analogRead....)
unsigned int heat = width / 5;
unsigned int focus = 9;
unsigned int cool = 26;
const int ledsPerPin = width * height / 8;
Adafruit_NeoPXL8 leds(ledsPerPin, pins, COLOR_ORDER);
// Arrays for fire animation
unsigned char canvas[width*height];
extern const unsigned int fireColor[100];
// Run setup once
void setup() {
if (!leds.begin()) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
leds.show(); // Clear initial LED state
}
// A simple xy() function to turn display matrix coordinates
// into the index numbers NeoPXL8 requires. If your LEDs
// are arranged differently, edit this code...
unsigned int xy(unsigned int x, unsigned int y) {
if ((y & 1) == 0) {
// even numbered rows (0, 2, 4...) are left to right
return y * width + x;
} else {
// odd numbered rows (1, 3, 5...) are right to left
return y * width + width - 1 - x;
}
}
uint32_t lastTime = 0;
// Run repetitively
void loop() {
uint32_t now = millis();
if (now - lastTime >= 45) {
lastTime = now;
animateFire();
}
}
void animateFire() {
unsigned int i, c, n, x, y;
// Step 1: move all data up one line
memmove(canvas + width, canvas, width * (height - 1));
memset(canvas, 0, width);
// Step 2: draw random heatspots on bottom line
i = heat;
if (i > width-8) i = width-8;
while (i > 0) {
x = random(width - 2) + 1;
if (canvas[x] == 0) {
canvas[x] = 99;
i--;
}
}
// Step 3: interpolate
for (y=0; y < height; y++) {
for (x=0; x < width; x++) {
c = canvas[y * width + x] * focus;
n = focus;
if (x > 0) {
c = c + canvas[y * width + (x - 1)];
n = n + 1;
}
if (x < width-1) {
c = c + canvas[y * width + (x + 1)];
n = n + 1;
}
if (y > 0) {
c = c + canvas[(y -1) * width + x];
n = n + 1;
}
if (y < height-1) {
c = c + canvas[(y + 1) * width + x];
n = n + 1;
}
c = (c + (n / 2)) / n;
i = (random(1000) * cool) / 10000;
if (c > i) {
c = c - i;
} else {
c = 0;
}
canvas[y * width + x] = c;
}
}
// Step 4: render canvas to LEDs
for (y=0; y < height; y++) {
for (x=0; x < width; x++) {
c = canvas[((height - 1) - y) * width + x];
leds.setPixelColor(xy(x, y), leds.gamma32(fireColor[c]));
}
}
leds.show();
}

View file

@ -0,0 +1,33 @@
// This is just the linear fireColor[] array from the original OctoWS2811
// Fire example, since NeoPXL8 has its own gamma function.
extern const unsigned int fireColor[100];
#define RGB(r, g, b) (((r) << 16) | ((g) << 8) | (b))
const unsigned int fireColor[100] = {
RGB(0, 0, 0), RGB(5, 0, 0), RGB(10, 0, 0), RGB(14, 0, 0),
RGB(19, 0, 0), RGB(23, 0, 0), RGB(27, 0, 0), RGB(31, 0, 0),
RGB(35, 0, 0), RGB(39, 0, 0), RGB(43, 0, 0), RGB(47, 0, 0),
RGB(51, 0, 0), RGB(55, 0, 0), RGB(59, 0, 0), RGB(63, 0, 0),
RGB(67, 0, 0), RGB(71, 0, 0), RGB(75, 0, 0), RGB(79, 0, 0),
RGB(83, 0, 0), RGB(88, 0, 0), RGB(92, 0, 0), RGB(96, 0, 0),
RGB(100, 0, 0), RGB(104, 0, 0), RGB(108, 0, 0), RGB(112, 0, 0),
RGB(116, 0, 0), RGB(121, 0, 0), RGB(125, 0, 0), RGB(129, 0, 0),
RGB(133, 0, 0), RGB(137, 0, 0), RGB(141, 0, 0), RGB(145, 0, 0),
RGB(149, 0, 0), RGB(153, 0, 0), RGB(157, 0, 0), RGB(161, 0, 0),
RGB(165, 0, 0), RGB(169, 0, 0), RGB(173, 0, 0), RGB(177, 0, 0),
RGB(181, 0, 0), RGB(185, 0, 0), RGB(190, 0, 0), RGB(194, 0, 0),
RGB(198, 0, 0), RGB(202, 0, 0), RGB(205, 2, 0), RGB(207, 6, 0),
RGB(209, 10, 0), RGB(211, 14, 0), RGB(213, 18, 0), RGB(215, 22, 0),
RGB(217, 26, 0), RGB(219, 30, 0), RGB(221, 35, 0), RGB(223, 39, 0),
RGB(225, 43, 0), RGB(227, 47, 0), RGB(229, 51, 0), RGB(231, 55, 0),
RGB(233, 59, 0), RGB(235, 63, 0), RGB(237, 67, 0), RGB(239, 71, 0),
RGB(241, 75, 0), RGB(243, 79, 0), RGB(245, 83, 0), RGB(248, 88, 0),
RGB(250, 92, 0), RGB(252, 96, 0), RGB(254, 100, 0), RGB(255, 105, 1),
RGB(255, 111, 3), RGB(255, 117, 5), RGB(255, 123, 7), RGB(255, 130, 9),
RGB(255, 136, 11), RGB(255, 142, 13), RGB(255, 148, 15), RGB(255, 154, 17),
RGB(255, 160, 19), RGB(255, 166, 21), RGB(255, 172, 23), RGB(255, 179, 25),
RGB(255, 185, 27), RGB(255, 191, 29), RGB(255, 197, 31), RGB(255, 203, 33),
RGB(255, 209, 35), RGB(255, 215, 37), RGB(255, 221, 39), RGB(255, 227, 41),
RGB(255, 234, 44), RGB(255, 240, 46), RGB(255, 246, 48), RGB(255, 252, 50)};

View file

@ -0,0 +1,82 @@
// FIRST TIME HERE? START WITH THE NEOPXL8 strandtest EXAMPLE INSTEAD!
// That code explains and helps troubshoot wiring and NeoPixel color format.
#include <Adafruit_NeoPXL8.h>
// CHANGE these to match your strandtest findings or this WILL NOT WORK:
int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
#define COLOR_ORDER NEO_GRB
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
//PlazINT - Fast Plasma Generator using Integer Math Only
//Edmund "Skorn" Horn
//March 4,2013
//Version 1.0 adapted for OctoWS2811Lib (tested, working...)
//OctoWS2811 Defn. Stuff
#define COLS_LEDs 30 // all of the following params need to be adjusted for screen size
#define ROWS_LEDs 16 // LED_LAYOUT assumed 0 if ROWS_LEDs > 8
#define LEDS_PER_STRIP (COLS_LEDs * ROWS_LEDs / 8)
Adafruit_NeoPXL8 leds(LEDS_PER_STRIP, pins, COLOR_ORDER);
//Byte val 2PI Cosine Wave, offset by 1 PI
//supports fast trig calcs and smooth LED fading/pulsing.
uint8_t const cos_wave[256] PROGMEM =
{0,0,0,0,1,1,1,2,2,3,4,5,6,6,8,9,10,11,12,14,15,17,18,20,22,23,25,27,29,31,33,35,38,40,42,
45,47,49,52,54,57,60,62,65,68,71,73,76,79,82,85,88,91,94,97,100,103,106,109,113,116,119,
122,125,128,131,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,
189,191,194,197,199,202,204,207,209,212,214,216,218,221,223,225,227,229,231,232,234,236,
238,239,241,242,243,245,246,247,248,249,250,251,252,252,253,253,254,254,255,255,255,255,
255,255,255,255,254,254,253,253,252,252,251,250,249,248,247,246,245,243,242,241,239,238,
236,234,232,231,229,227,225,223,221,218,216,214,212,209,207,204,202,199,197,194,191,189,
186,183,180,177,174,171,168,165,162,159,156,153,150,147,144,141,138,135,131,128,125,122,
119,116,113,109,106,103,100,97,94,91,88,85,82,79,76,73,71,68,65,62,60,57,54,52,49,47,45,
42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14,12,11,10,9,8,6,6,5,4,3,2,2,1,1,1,0,0,0,0
};
// Gamma table removed because NeoPixel lib provides this functionality.
void setup() {
if (!leds.begin()) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
leds.setBrightness(100); // Tone it down a little
leds.show(); // Clear initial LED state
}
unsigned long frameCount=25500; // arbitrary seed to calculate the three time displacement variables t,t2,t3
void loop() {
frameCount++ ;
uint16_t t = fastCosineCalc((42 * frameCount)/300); //time displacement - fiddle with these til it looks good...
uint16_t t2 = fastCosineCalc((35 * frameCount)/300);
uint16_t t3 = fastCosineCalc((38 * frameCount)/300);
for (uint8_t y = 0; y < ROWS_LEDs; y++) {
int left2Right, pixelIndex;
if (((y % (ROWS_LEDs/8)) & 1) == 0) {
left2Right = 1;
pixelIndex = y * COLS_LEDs;
} else {
left2Right = -1;
pixelIndex = (y + 1) * COLS_LEDs - 1;
}
for (uint8_t x = 0; x < COLS_LEDs ; x++) {
//Calculate 3 seperate plasma waves, one for each color channel
uint8_t r = fastCosineCalc(((x << 3) + (t >> 1) + fastCosineCalc((t2 + (y << 3)))));
uint8_t g = fastCosineCalc(((y << 3) + t + fastCosineCalc(((t3 >> 2) + (x << 3)))));
uint8_t b = fastCosineCalc(((y << 3) + t2 + fastCosineCalc((t + x + (g >> 2)))));
leds.setPixelColor(pixelIndex, leds.gamma32((r << 16) | (g << 8) | b));
pixelIndex += left2Right;
}
}
leds.show();
}
inline uint8_t fastCosineCalc( uint16_t preWrapVal) {
return (pgm_read_byte_near(cos_wave + (preWrapVal & 255)));
}

View file

@ -0,0 +1,367 @@
// This sketch is Just Too Much for SAMD21 (M0) boards.
// Recommend RP2040/SCORPIO, M4 or ESP32-S3.
// FIRST TIME HERE? START WITH THE NEOPXL8 strandtest EXAMPLE INSTEAD!
// That code explains and helps troubshoot wiring and NeoPixel color format.
// This is a companion to "move2msc" in the extras/Processing folder.
// It plays preconverted videos from the on-board flash filesystem.
#include "SdFat_Adafruit_Fork.h"
#include <Adafruit_NeoPXL8.h>
#include <Adafruit_CPFS.h> // For accessing CIRCUITPY drive
#define ARDUINOJSON_ENABLE_COMMENTS 1
#include <ArduinoJson.h>
// This example is adapted from one in PJRC's OctoWS2811 Library. Original
// comments appear first, and Adafruit_NeoPXL8 changes follow that. Any
// original comments about "SD card" now apply to a board's CIRCUITPY flash
// filesystem instead.
/* OctoWS2811 VideoSDcard.ino - Video on LEDs, played from SD Card
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2014 Paul Stoffregen, PJRC.COM, LLC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Update: The programs to prepare the SD card video file have moved to "extras"
https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
The recommended hardware for SD card playing is:
Teensy 3.1: http://www.pjrc.com/store/teensy31.html
Octo28 Apaptor: http://www.pjrc.com/store/octo28_adaptor.html
SD Adaptor: http://www.pjrc.com/store/wiz820_sd_adaptor.html
Long Pins: http://www.digikey.com/product-search/en?keywords=S1082E-36-ND
See the included "hardware.jpg" image for suggested pin connections,
with 2 cuts and 1 solder bridge needed for the SD card pin 3 chip select.
Required Connections
--------------------
pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips.
pin 14: LED strip #2 All 8 are the same length.
pin 7: LED strip #3
pin 8: LED strip #4 A 100 to 220 ohm resistor should used
pin 6: LED strip #5 between each Teensy pin and the
pin 20: LED strip #6 wire to the LED strip, to minimize
pin 21: LED strip #7 high frequency ringining & noise.
pin 5: LED strip #8
pin 15 & 16 - Connect together, but do not use
pin 4: Do not use
pin 3: SD Card, CS
pin 11: SD Card, MOSI
pin 12: SD Card, MISO
pin 13: SD Card, SCLK
*/
/*
ADAFRUIT_NEOPXL8 UPDATE:
Aside from changes to convert from OctoWS2811 to Adafruit_NeoPXL8, the
big drastic change here is to eliminate many compile-time constants and
instead place these in a JSON configuration file on a board's CIRCUITPY
flash filesystem (though this is Arduino code, we can still make use of
that drive), and declare the LEDs at run time. Other than those
alterations, the code is minimally changed. Paul did the real work. :)
Run-time configuration is stored in CIRCUITPY/neopxl8.cfg and resembles:
{
"pins" : [ 16, 17, 18, 19, 20, 21, 22, 23 ],
"order" : "GRB",
"led_width" : 30,
"led_height" : 16,
"led_layout" : 0
}
If this file is missing, or if any individual elements are unspecified,
defaults will be used (these are noted later in the code). It's possible,
likely even, that there will be additional elements in this file...
for example, some NeoPXL8 code might use a single "length" value rather
than width/height, as not all projects are using a grid. Be warned that
JSON is highly picky and even a single missing or excess comma will stop
everything, so read through it very carefully if encountering an error.
*/
#define FILENAME "mymovie.bin"
// In original code, these were constants LED_WIDTH, LED_HEIGHT and
// LED_LAYOUT. Values here are defaults but can override in config file.
// led_height MUST be a multiple of 8. When 16, 24, 32 are used, each strip
// spans 2, 3, 4 rows. led_layout indicates how strips are arranged.
uint16_t led_width = 30; // Number of LEDs horizontally
uint16_t led_height = 16; // Number of LEDs vertically
uint8_t led_layout = 0; // 0 = even rows left->right, 1 = right->left
FatFile file;
bool playing = false;
uint32_t timeOfLastFrame = 0;
uint8_t *imageBuffer; // LED data from filesystem is staged here
uint32_t imageBufferSize; // Size (in bytes) of imageBuffer
Adafruit_NeoPXL8 *leds; // NeoPXL8 object is allocated after reading config
void error_handler(const char *message, uint16_t speed) {
Serial.print("Error: ");
Serial.println(message);
if (speed) { // Fatal error, blink LED
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
digitalWrite(LED_BUILTIN, (millis() / speed) & 1);
yield(); // Keep filesystem accessible for editing
}
} else { // Not fatal, just show message
Serial.println("Continuing with defaults");
}
}
void setup() {
// CHANGE these to match your strandtest findings (or use .cfg file):
int8_t pins[8] = NEOPXL8_DEFAULT_PINS;
uint16_t order = NEO_GRB;
// Start the CIRCUITPY flash filesystem first. Very important!
FatVolume *fs = Adafruit_CPFS::begin();
// Start Serial AFTER FFS begin, else CIRCUITPY won't show on computer.
Serial.begin(115200);
//while(!Serial);
delay(1000);
Serial.setTimeout(50);
Serial.println("VideoMSC");
if (fs == NULL) {
error_handler("Can't access CIRCUITPY drive", 0);
} else {
StaticJsonDocument<1024> doc;
DeserializationError error;
// Open NeoPXL8 configuration file and attempt to decode JSON data within.
if ((file = fs->open("neopxl8.cfg", FILE_READ))) {
error = deserializeJson(doc, file);
file.close();
} else {
error_handler("neopxl8.cfg not found", 0);
}
if(error) {
error_handler("neopxl8.cfg syntax error", 0);
Serial.print("JSON error: ");
Serial.println(error.c_str());
} else {
// Config is valid, override defaults in program variables...
JsonVariant v = doc["pins"];
if (v.is<JsonArray>()) {
uint8_t n = v.size() < 8 ? v.size() : 8;
for (uint8_t i = 0; i < n; i++)
pins[i] = v[i].as<int>();
}
v = doc["order"];
if (v.is<const char *>()) order = Adafruit_NeoPixel::str2order(v);
led_width = doc["led_width"] | led_width;
led_height = doc["led_height"] | led_height;
led_layout = doc["led_layout"] | led_layout;
} // end JSON OK
} // end filesystem OK
// Any errors after this point are unrecoverable and program will stop.
// Dynamically allocate NeoPXL8 object
leds = new Adafruit_NeoPXL8(led_width * led_height / 8, pins, order);
if (leds == NULL) error_handler("NeoPXL8 allocation", 100);
// Allocate imageBuffer
imageBufferSize = led_width * led_height * 3;
imageBuffer = (uint8_t *)malloc(imageBufferSize);
if (imageBuffer == NULL) error_handler("Image buffer allocation", 200);
if (!leds->begin()) error_handler("NeoPXL8 begin() failed", 500);
// At this point, everything but input file is ready to go!
leds->show(); // LEDs off ASAP
bool status = file.open(FILENAME, O_RDONLY);
if (!status) error_handler("Can't open movie .bin file", 1000);
Serial.println("File opened");
playing = true;
timeOfLastFrame = 0;
}
// read from the SD card, true=ok, false=unable to read
// the SD library is much faster if all reads are 512 bytes
// this function lets us easily read any size, but always
// requests data from the SD library in 512 byte blocks.
//
bool sd_card_read(void *ptr, unsigned int len)
{
static unsigned char buffer[512];
static unsigned int bufpos = 0;
static unsigned int buflen = 0;
unsigned char *dest = (unsigned char *)ptr;
unsigned int n;
while (len > 0) {
if (buflen == 0) {
n = file.read(buffer, 512);
if (n == 0) return false;
buflen = n;
bufpos = 0;
}
unsigned int n = buflen;
if (n > len) n = len;
memcpy(dest, buffer + bufpos, n);
dest += n;
bufpos += n;
buflen -= n;
len -= n;
}
return true;
}
// skip past data from the SD card
void sd_card_skip(unsigned int len)
{
unsigned char buf[256];
while (len > 0) {
unsigned int n = len;
if (n > sizeof(buf)) n = sizeof(buf);
sd_card_read(buf, n);
len -= n;
}
}
void loop()
{
unsigned char header[5];
if (playing) {
if (sd_card_read(header, 5)) {
if (header[0] == '*') {
// found an image frame
unsigned int size = (header[1] | (header[2] << 8)) * 3;
unsigned int usec = header[3] | (header[4] << 8);
unsigned int readsize = size;
//Serial.printf("v: %u %u\n", size, usec);
if (readsize > imageBufferSize) {
readsize = imageBufferSize;
}
if (sd_card_read(imageBuffer, readsize)) {
uint32_t now;
// Serial.printf(", us = %u", (unsigned int)(micros() - timeOfLastFrame));
// Serial.println();
do {
now = micros();
} while ((now - timeOfLastFrame) < usec) ; // wait
timeOfLastFrame = now;
convert_and_show();
} else {
error("unable to read video frame data");
return;
}
if (readsize < size) {
sd_card_skip(size - readsize);
}
} else if (header[0] == '%') {
// found a chunk of audio data
unsigned int size = (header[1] | (header[2] << 8)) * 2;
#if 0
// Serial.printf("a: %u", size);
// Serial.println();
while (size > 0) {
unsigned int len = size;
if (len > 256) len = 256;
int16_t *p = audio.getBuffer();
if (!sd_card_read(p, len)) {
error("unable to read audio frame data");
return;
}
if (len < 256) {
for (int i=len; i < 256; i++) {
*((char *)p + i) = 0; // fill rest of buffer with zero
}
}
audio.playBuffer();
size -= len;
}
#else
file.seekCur(size);
#endif
} else {
error("unknown header");
return;
}
} else {
error("unable to read 5-byte header");
return;
}
} else {
delay(2000);
bool status = file.open(FILENAME, FILE_READ);
if (status) {
Serial.println("File opened");
playing = true;
timeOfLastFrame = micros();
}
}
}
// when any error happens during playback, close the file and restart
void error(const char *str)
{
Serial.print("error: ");
Serial.println(str);
file.close();
playing = false;
}
void convert_and_show() {
uint8_t *ptr = imageBuffer;
if (led_layout == 0) {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
int pixelIndex = y * led_width + x; // Always left-to-right
uint8_t r = leds->gamma8(*ptr++);
uint8_t g = leds->gamma8(*ptr++);
uint8_t b = leds->gamma8(*ptr++);
leds->setPixelColor(pixelIndex, r, g, b);
}
}
} else {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
// Even rows are left-to-right, odd are right-to-left
int pixelIndex = (y & 1) ? (y + 1) * led_width - 1 - x : y * led_width + x;
uint8_t r = leds->gamma8(*ptr++);
uint8_t g = leds->gamma8(*ptr++);
uint8_t b = leds->gamma8(*ptr++);
leds->setPixelColor(pixelIndex, r, g, b);
}
}
}
leds->show();
}

View file

@ -0,0 +1,392 @@
// This sketch is Just Too Much for SAMD21 (M0) boards.
// Recommend RP2040/SCORPIO, M4 or ESP32-S3.
// FIRST TIME HERE? START WITH THE NEOPXL8 strandtest EXAMPLE INSTEAD!
// That code explains and helps troubshoot wiring and NeoPixel color format.
// This is a companion to "move2serial" in the extras/Processing folder.
#include "SdFat_Adafruit_Fork.h"
#include <Adafruit_NeoPXL8.h>
#include <Adafruit_CPFS.h> // For accessing CIRCUITPY drive
#define ARDUINOJSON_ENABLE_COMMENTS 1
#include <ArduinoJson.h>
// This example is adapted from one in PJRC's OctoWS2811 Library. Original
// comments appear first, and Adafruit_NeoPXL8 changes follow that.
/* OctoWS2811 VideoDisplay.ino - Video on LEDs, from a PC, Mac, Raspberry Pi
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Update: The movie2serial program which transmit data has moved to "extras"
https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
Required Connections
--------------------
pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips.
pin 14: LED strip #2 All 8 are the same length.
pin 7: LED strip #3
pin 8: LED strip #4 A 100 to 220 ohm resistor should used
pin 6: LED strip #5 between each Teensy pin and the
pin 20: LED strip #6 wire to the LED strip, to minimize
pin 21: LED strip #7 high frequency ringining & noise.
pin 5: LED strip #8
pin 15 & 16 - Connect together, but do not use
pin 4: Do not use
pin 3: Do not use as PWM. Normal use is ok.
pin 12: Frame Sync
When using more than 1 Teensy to display a video image, connect
the Frame Sync signal between every board. All boards will
synchronize their WS2811 update using this signal.
Beware of image distortion from long LED strip lengths. During
the WS2811 update, the LEDs update in sequence, not all at the
same instant! The first pixel updates after 30 microseconds,
the second pixel after 60 us, and so on. A strip of 120 LEDs
updates in 3.6 ms, which is 10.8% of a 30 Hz video frame time.
Doubling the strip length to 240 LEDs increases the lag to 21.6%
of a video frame. For best results, use shorter length strips.
Multiple boards linked by the frame sync signal provides superior
video timing accuracy.
A Multi-TT USB hub should be used if 2 or more Teensy boards
are connected. The Multi-TT feature allows proper USB bandwidth
allocation. Single-TT hubs, or direct connection to multiple
ports on the same motherboard, may give poor performance.
*/
/*
ADAFRUIT_NEOPXL8 UPDATE:
Aside from changes to convert from OctoWS2811 to Adafruit_NeoPXL8, the
big drastic change here is to eliminate many compile-time constants and
instead place these in a JSON configuration file on a board's CIRCUITPY
flash filesystem (though this is Arduino code, we can still make use of
that drive), and declare the LEDs at run time. Other than those
alterations, the code is minimally changed. Paul did the real work. :)
Run-time configuration is stored in CIRCUITPY/neopxl8.cfg and resembles:
{
"pins" : [ 16, 17, 18, 19, 20, 21, 22, 23 ],
"order" : "GRB",
"sync_pin" : -1,
"led_width" : 30,
"led_height" : 16,
"led_layout" : 0,
"video_xoffset" : 0,
"video_yoffset" : 0,
"video_width" : 100,
"video_height" : 100
}
If this file is missing, or if any individual elements are unspecified,
defaults will be used (these are noted later in the code). It's possible,
likely even, that there will be additional elements in this file...
for example, some NeoPXL8 code might use a single "length" value rather
than width/height, as not all projects are using a grid. Be warned that
JSON is highly picky and even a single missing or excess comma will stop
everything, so read through it very carefully if encountering an error.
*/
// In original code, these were constants LED_WIDTH, LED_HEIGHT and
// LED_LAYOUT. Values here are defaults but can override in config file.
// led_height MUST be a multiple of 8. When 16, 24, 32 are used, each strip
// spans 2, 3, 4 rows. led_layout indicates how strips are arranged.
uint16_t led_width = 30; // Number of LEDs horizontally
uint16_t led_height = 16; // Number of LEDs vertically
uint8_t led_layout = 0; // 0 = even rows left->right, 1 = right->left
// The portion of the video image to show on this set of LEDs. All 4 numbers
// are percentages, from 0 to 100. For a large LED installation with many
// boards driving groups of LEDs, these parameters allow you to program each
// one to tell the video application which portion of the video it displays.
// By reading these numbers, the video application can automatically configure
// itself, regardless of which serial port COM number or device names are
// assigned to each board by your operating system. 0/0/100/100 displays the
// entire image on one board's LEDs. With two boards, this could be split
// between them, 0/0/100/50 for the top and 0/50/100/50 for the bottom.
// As with the led_* values, defaults do NOT need to be specified here,
// that's done in setup().
uint8_t video_xoffset = 0;
uint8_t video_yoffset = 0;
uint8_t video_width = 100;
uint8_t video_height = 100;
uint8_t *imageBuffer; // Serial LED data is received here
uint32_t imageBufferSize; // Size (in bytes) of imageBuffer
int8_t sync_pin = -1; // If multiple boards, wire pins together
uint32_t lastFrameSyncTime = 0;
Adafruit_NeoPXL8 *leds; // NeoPXL8 object is allocated after reading config
void error_handler(const char *message, uint16_t speed) {
Serial.print("Error: ");
Serial.println(message);
if (speed) { // Fatal error, blink LED
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
digitalWrite(LED_BUILTIN, (millis() / speed) & 1);
yield(); // Keep filesystem accessible for editing
}
} else { // Not fatal, just show message
Serial.println("Continuing with defaults");
}
}
void setup() {
// CHANGE these to match your strandtest findings (or use .cfg file):
int8_t pins[8] = NEOPXL8_DEFAULT_PINS;
uint16_t order = NEO_GRB;
// Start the CIRCUITPY flash filesystem first. Very important!
FatVolume *fs = Adafruit_CPFS::begin();
// Start Serial AFTER FFS begin, else CIRCUITPY won't show on computer.
Serial.begin(115200);
//while(!Serial);
delay(1000);
Serial.setTimeout(50);
if (fs == NULL) {
error_handler("Can't access CIRCUITPY drive", 0);
} else {
FatFile file;
StaticJsonDocument<1024> doc;
DeserializationError error;
// Open NeoPXL8 configuration file and attempt to decode JSON data within.
if ((file = fs->open("neopxl8.cfg", FILE_READ))) {
error = deserializeJson(doc, file);
file.close();
} else {
error_handler("neopxl8.cfg not found", 0);
}
if(error) {
error_handler("neopxl8.cfg syntax error", 0);
Serial.print("JSON error: ");
Serial.println(error.c_str());
} else {
// Config is valid, override defaults in program variables...
JsonVariant v = doc["pins"];
if (v.is<JsonArray>()) {
uint8_t n = v.size() < 8 ? v.size() : 8;
for (uint8_t i = 0; i < n; i++)
pins[i] = v[i].as<int>();
}
v = doc["order"];
if (v.is<const char *>()) order = Adafruit_NeoPixel::str2order(v);
sync_pin = doc["sync_pin"] | sync_pin;
led_width = doc["led_width"] | led_width;
led_height = doc["led_height"] | led_height;
led_layout = doc["led_layout"] | led_layout;
video_xoffset = doc["video_xoffset"] | video_xoffset;
video_yoffset = doc["video_yoffset"] | video_yoffset;
video_width = doc["video_width"] | video_width;
video_height = doc["video_height"] | video_height;
} // end JSON OK
} // end filesystem OK
// Any errors after this point are unrecoverable and program will stop.
// Dynamically allocate NeoPXL8 object
leds = new Adafruit_NeoPXL8(led_width * led_height / 8, pins, order);
if (leds == NULL) error_handler("NeoPXL8 allocation", 100);
// Allocate imageBuffer
imageBufferSize = led_width * led_height * 3;
imageBuffer = (uint8_t *)malloc(imageBufferSize);
if (imageBuffer == NULL) error_handler("Image buffer allocation", 200);
if (!leds->begin()) error_handler("NeoPXL8 begin() failed", 500);
// At this point, everything is fully configured, allocated and started!
leds->show(); // LEDs off ASAP
if (sync_pin >= 0) pinMode(sync_pin, INPUT_PULLUP);
}
void loop() {
//
// wait for a Start-Of-Message character:
//
// '*' = Frame of image data, with frame sync pulse to be sent
// a specified number of microseconds after reception of
// the first byte (typically at 75% of the frame time, to
// allow other boards to fully receive their data).
// Normally '*' is used when the sender controls the pace
// of playback by transmitting each frame as it should
// appear.
//
// '$' = Frame of image data, with frame sync pulse to be sent
// a specified number of microseconds after the previous
// frame sync. Normally this is used when the sender
// transmits each frame as quickly as possible, and we
// control the pacing of video playback by updating the
// LEDs based on time elapsed from the previous frame.
//
// '%' = Frame of image data, to be displayed with a frame sync
// pulse is received from another board. In a multi-board
// system, the sender would normally transmit one '*' or '$'
// message and '%' messages to all other boards, so every
// Teensy 3.0 updates at the exact same moment.
//
// '@' = Reset the elapsed time, used for '$' messages. This
// should be sent before the first '$' message, so many
// frames are not played quickly if time as elapsed since
// startup or prior video playing.
//
// '?' = Query LED and Video parameters. Teensy 3.0 responds
// with a comma delimited list of information.
//
int startChar = Serial.read();
if (startChar == '*') {
// receive a "master" frame - we send the frame sync to other boards
// the sender is controlling the video pace. The 16 bit number is
// how far into this frame to send the sync to other boards.
unsigned int startAt = micros();
unsigned int usecUntilFrameSync = 0;
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
if (count != 2) return;
count = Serial.readBytes((char *)imageBuffer, imageBufferSize);
if (count == imageBufferSize) {
unsigned int endAt = micros();
unsigned int usToWaitBeforeSyncOutput = 100;
if (endAt - startAt < usecUntilFrameSync) {
usToWaitBeforeSyncOutput = usecUntilFrameSync - (endAt - startAt);
}
digitalWrite(sync_pin, HIGH);
pinMode(sync_pin, OUTPUT);
delayMicroseconds(usToWaitBeforeSyncOutput);
digitalWrite(sync_pin, LOW);
// WS2811 update begins immediately after falling edge of frame sync
convert_and_show();
}
} else if (startChar == '$') {
// receive a "master" frame - we send the frame sync to other boards
// we are controlling the video pace. The 16 bit number is how long
// after the prior frame sync to wait until showing this frame
unsigned int usecUntilFrameSync = 0;
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
if (count != 2) return;
count = Serial.readBytes((char *)imageBuffer, imageBufferSize);
if (count == imageBufferSize) {
digitalWrite(sync_pin, HIGH);
pinMode(sync_pin, OUTPUT);
uint32_t now, elapsed;
do {
now = micros();
elapsed = now - lastFrameSyncTime;
} while (elapsed < usecUntilFrameSync); // wait
lastFrameSyncTime = now;
digitalWrite(sync_pin, LOW);
// WS2811 update begins immediately after falling edge of frame sync
convert_and_show();
}
} else if (startChar == '%') {
// receive a "slave" frame - wait to show it until the frame sync arrives
pinMode(sync_pin, INPUT_PULLUP);
unsigned int unusedField = 0;
int count = Serial.readBytes((char *)&unusedField, 2);
if (count != 2) return;
count = Serial.readBytes((char *)imageBuffer, imageBufferSize);
if (count == imageBufferSize) {
uint32_t startTime = millis();
while (digitalRead(sync_pin) != HIGH && (millis() - startTime) < 30) ; // wait for sync high
while (digitalRead(sync_pin) != LOW && (millis() - startTime) < 30) ; // wait for sync high->low
// WS2811 update begins immediately after falling edge of frame sync
if ((millis() - startTime) < 30) {
convert_and_show();
}
}
} else if (startChar == '@') {
// reset the elapsed frame time, for startup of '$' message playing
lastFrameSyncTime = micros();
} else if (startChar == '?') {
// when the video application asks, give it all our info
// for easy and automatic configuration
Serial.print(led_width);
Serial.write(',');
Serial.print(led_height);
Serial.write(',');
Serial.print(led_layout);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(video_xoffset);
Serial.write(',');
Serial.print(video_yoffset);
Serial.write(',');
Serial.print(video_width);
Serial.write(',');
Serial.print(video_height);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(0);
Serial.println();
} else if (startChar >= 0) {
// discard unknown characters
}
}
void convert_and_show() {
uint8_t *ptr = imageBuffer;
if (led_layout == 0) {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
int pixelIndex = y * led_width + x; // Always left-to-right
uint8_t r = leds->gamma8(*ptr++);
uint8_t g = leds->gamma8(*ptr++);
uint8_t b = leds->gamma8(*ptr++);
leds->setPixelColor(pixelIndex, r, g, b);
}
}
} else {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
// Even rows are left-to-right, odd are right-to-left
int pixelIndex = (y & 1) ? (y + 1) * led_width - 1 - x : y * led_width + x;
uint8_t r = leds->gamma8(*ptr++);
uint8_t g = leds->gamma8(*ptr++);
uint8_t b = leds->gamma8(*ptr++);
leds->setPixelColor(pixelIndex, r, g, b);
}
}
}
leds->show();
}

View file

@ -0,0 +1,215 @@
/*
*** THIS IS THE PLACE TO START! *** Adafruit_NeoPXL8 has complex wiring
options that vary from board to board. This example discusses all that and
lights pixels in predictable test patterns as a wiring diagnostic tool.
Subsequent examples forego the long explanantion and just handle their task.
Figure out your connections here, then copy your findings into other code.
Pixel-setting operations are the same as Adafruit_NeoPixel and are not
explained here -- for that, see examples included with Adafruit_NeoPixel.
REQUIREMENTS:
* Adafruit_NeoPixel library.
* For M0 and M4 boards (SAMD21, SAMD51): Adafruit_ZeroDMA library.
* For RP2040 boards: install Earle F. Philhower's "Raspberry Pi Pico/RP2040"
board package.
* For ESP32S3 boards: install Espressif ESP32 package. ONLY the S3 is
* supported; no S2, C3 or original ESP32.
* NeoPixels with suitable wiring and adequate power supply.
* 5V-powered NeoPixels may require a logic level shifter (e.g. 75HCT245),
or use a NeoPXL8 FeatherWing or Friend. There are DIFFERENT VERSIONS of
the NeoPXL8 FeatherWing for M0 and M4 Feathers. For non-Feather boards,
consider the NeoPXL8 Friend breakout board.
PRODUCT LINKS:
* NeoPXL8 FeatherWing M0: https://www.adafruit.com/product/3249
* NeoPXL8 FeatherWing M4: https://www.adafruit.com/product/4537
* NeoPXL8 Friend: https://www.adafruit.com/product/3975
* NeoPixels: https://www.adafruit.com/category/168
RESOURCES:
* NeoPixel Uberguide: https://learn.adafruit.com/adafruit-neopixel-uberguide
*/
#include <Adafruit_NeoPXL8.h>
#define NUM_LEDS 60 // NeoPixels PER STRAND, total number is 8X this!
#define COLOR_ORDER NEO_GRB // NeoPixel color format (see Adafruit_NeoPixel)
// In a moment we'll declare a global Adafruit_NeoPXL8 object.
// The constructor expects three arguments:
// * The number of NeoPixels PER STRAND (there can be up to 8 strands).
// * A uint8_t array of 8 output pins, or pass NULL to use pins 0-7 on Metro
// Express or Arduino Zero boards.
// * NeoPixel color order, same as with the Adafruit_NeoPixel library.
// Different types and revisions of NeoPixel and WS2812-compatible LEDs
// expect color data in a particular order.
// Two of these were #defined above for easy use. But the middle one --
// the pin array -- requires a whole DISCUSSION. What follows are some pin
// arrays for different situations. MOST ARE COMMENTED OUT HERE, idea being
// that you would enable one or another, or come up with your own list
// following the rules explained here...
// To use a default pin setup (pins 0-7 on Adafruit Metro M0/M4, Arduino
// Zero, etc.), NULL can be used in place of the pin array. Comment this out
// if using one of the pin lists that follow, or your own list:
int8_t *pins = NULL; // COMMENT THIS OUT IF USING A PIN LIST BELOW
// In most situations you'll declare an int8_t array of 8 elements, one per
// pin. You can use fewer than 8 outputs by placing a -1 in one or more
// places. The array MUST have 8 elements, no more or less, and each board
// has SPECIFIC RULES about pin choices. Within that list and those rules,
// valid pins can be arranged in any order. For example: if integrating
// NeoPXL8 into an existing FadeCandy or OctoWS2811 installation, you might
// need to reverse or reorder the pin list to get a coherent LED pattern.
// M0 AND M4 BOARDS (SAMD21, SAMD51 MICROCONTROLLERS) ----------------------
// For Feather M0 and M4, the corresponding NeoPXL8 FeatherWings are NOT
// interchangeable -- you must have a matched Feather and 'Wing!
// Here's a pinout that works with the Feather M0 (NOT M4) w/NeoPXL8 M0
// FeatherWing as it ships from the factory:
// int8_t pins[8] = { PIN_SERIAL1_RX, PIN_SERIAL1_TX, MISO, 13, 5, SDA, A4, A3 };
// 5 pins on the M0 Featherwing have configurable pads that can be cut and
// solder-jumpered to altername pins, in case the default connections
// interfere with a needed peripheral (Serial1, I2C or SPI). You do NOT need
// to use all 5 alternates; pick and choose as needed! But if changing all 5,
// they would be:
// int8_t pins[8] = { 12, 10, 11, 13, SCK, MOSI, A4, A3 };
// Notice the last two are unchanged; those outputs are not reconfigurable.
// Here's a pinout that works with the Feather M4 (not M0) w/NeoPXL8 M4
// FeatherWing in the factory configuration:
// int8_t pins[8] = { 13, 12, 11, 10, SCK, 5, 9, 6 };
// Similar to the M0 Wing above where the first 5 pins are configurable, on
// M4 the last 4 can be changed with some cutting/soldering:
// int8_t pins[8] = { 13, 12, 11, 10, PIN_SERIAL1_RX, PIN_SERIAL1_TX, SCL, SDA };
// Notice the first four are unchanged; those outputs are not reconfigurable.
// Here's a pinout that works on the Metro M4:
// int8_t pins[8] = { 7, 4, 5, 6, 3, 2, 10, 11 };
// An alternate set of pins on Metro M4, but only 7 outputs:
// int8_t pins[8] = { 9, 8, 0, 1, 13, 12, -1, SCK };
// For Grand Central, here are primary and alternate pin options:
// int8_t pins[8] = { 30, 31, 32, 33, 36, 37, 34, 35 };
// int8_t pins[8] = { 30, 31, 32, 33, 15, 14, 27, 26 };
// For other SAMD21/SAMD51 (M0 and M4) boards not listed here: NeoPXL8
// relies on these chip's "pattern generator" peripheral, which is only
// availabe on certain pins. This requires some schematic and/or datasheet
// sleuthing to identify PCC/DATA[0] through [7] pins.
// RP2040 BOARDS -----------------------------------------------------------
// IMPORTANT: when used with RP2040 devices, the pin array requires "GPxx"
// pin numbers, which sometimes vary from the Arduino pin numbers
// silkscreened on the board's top side. Some boards helpfully use Arduino
// numbers on top, with GPxx equivalents on the bottom side for reference.
// The GPxx numbers MUST be within any contiguous range of 8 pins, though
// they can be re-ordered within that range, or unused elements set to -1.
// The M4 FeatherWing *almost* aligns with the Feather RP2040, but requires
// cutting the trace between the "n0" SCK selector pad, then soldering a
// wire from the n0 center pad (there's no via) to D4. You can then use this
// array to access all 8 outputs:
// int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 }; // GPxx indices!
// On Feather RP2040, corresponds to top-labeled 4, 5, 9, 6, 13, 12, 11, 10.
// There are no alternate pins for Feather RP2040, since this is the only
// 8-contiguous-bits combination, though you can reverse, reorder or use -1.
// For the Feather RP2040 SCORPIO, use this list:
// int8_t pins[8] = { 16, 17, 18, 19, 20, 21, 22, 23 };
// For Raspberry Pi Pico, you can use any 8 contiguous GPIO pins (e.g. the
// default 0-7) with a level shifter or NeoPXL8 Friend.
// ESP32S3 BOARDS ----------------------------------------------------------
// These allow ANY 8 pins for output...so you can use the NeoPXL8 M0 or M4
// FeatherWings unmodified, with one of the pin lists provided earlier.
// LET'S DO THE THING! -----------------------------------------------------
// Here's the global constructor as explained near the start:
Adafruit_NeoPXL8 leds(NUM_LEDS, pins, COLOR_ORDER);
// For this demo we use a table of 8 hues, one for each strand of pixels:
static uint8_t colors[8][3] = {
255, 0, 0, // Row 0: Red
255, 160, 0, // Row 1: Orange
255, 255, 0, // Row 2: Yellow
0, 255, 0, // Row 3: Green
0, 255, 255, // Row 4: Cyan
0, 0, 255, // Row 5: Blue
192, 0, 255, // Row 6: Purple
255, 0, 255 // Row 7: Magenta
};
// setup() runs once on program startup:
void setup() {
// Start NeoPXL8. If begin() returns false, either an invalid pin list
// was provided, or requested too many pixels for available RAM.
if (!leds.begin()) {
// Blink the onboard LED if that happens.
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
// Otherwise, NeoPXL8 is now running, we can continue.
leds.setBrightness(32); // Tone it down, NeoPixels are BRIGHT!
// Cycle all pixels red/green/blue on startup. If you see a different
// sequence, COLOR_ORDER doesn't match your particular NeoPixel type.
// If you get a jumble of colors, you're using RGBW NeoPixels with an
// RGB order. Try different COLOR_ORDER values until code and hardware
// are in harmony.
for (uint32_t color = 0xFF0000; color > 0; color >>= 8) {
leds.fill(color);
leds.show();
delay(500);
}
// Light each strip in sequence. This helps verify the mapping of pins to
// a physical LED installation. If strips flash out of sequence, you can
// either re-wire, or just change the order of the pins[] array.
for (int i=0; i<8; i++) {
if (pins && (pins[i] < 0)) continue; // No pixels on this pin
leds.fill(0);
uint32_t color = leds.Color(colors[i][0], colors[i][1], colors[i][2]);
leds.fill(color, i * NUM_LEDS, NUM_LEDS);
leds.show();
delay(300);
}
// The other examples do not include the above two tests. It's assumed at
// that point that your code and hardware are confirmed in sync, making
// these redundant.
}
// loop() runs over and over indefinitely. We use this to render each frame
// of a repeating animation cycle based on elapsed time:
void loop() {
uint32_t now = millis(); // Get time once at start of each frame
for(uint8_t r=0; r<8; r++) { // For each row...
for(int p=0; p<NUM_LEDS; p++) { // For each pixel of row...
leds.setPixelColor(r * NUM_LEDS + p, rain(now, r, p));
}
}
leds.show();
}
// Given current time in milliseconds, row number (0-7) and pixel number
// along row (0 - (NUM_LEDS-1)), first calculate brightness (b) of pixel,
// then multiply row color by this and run it through NeoPixel librarys
// gamma-correction table.
uint32_t rain(uint32_t now, uint8_t row, int pixelNum) {
uint8_t frame = now / 4; // uint8_t rolls over for a 0-255 range
uint16_t b = 256 - ((frame - row * 32 + pixelNum * 256 / NUM_LEDS) & 0xFF);
return leds.Color(leds.gamma8((colors[row][0] * b) >> 8),
leds.gamma8((colors[row][1] * b) >> 8),
leds.gamma8((colors[row][2] * b) >> 8));
}

View file

@ -0,0 +1,199 @@
// FIRST TIME HERE? START WITH THE NEOPXL8 (not HDR) strandtest EXAMPLE!
// That code explains and helps troubshoot wiring and NeoPixel color format.
// Then move on to NeoPXL8HDR strandtest, THEN try this one.
#include <Adafruit_NeoPXL8.h>
// CHANGE these to match your strandtest findings or this WILL NOT WORK:
int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
#define COLOR_ORDER NEO_GRB
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
// Animated Fire Example - OctoWS2811 Library
// http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
//
// Based on the simple algorithm explained here:
// http://caraesnaur.github.io/fire/
//
// This example code is in the public domain.
const unsigned int width = 30;
const unsigned int height = 16; // Each strand spans two successive rows
// These parameters control the fire appearance
// (try controlling these with knobs / analogRead....)
unsigned int heat = width / 5;
unsigned int focus = 9;
unsigned int cool = 26;
const int ledsPerPin = width * height / 8;
Adafruit_NeoPXL8HDR leds(ledsPerPin, pins, COLOR_ORDER);
// Arrays for fire animation
unsigned char canvas[width*height];
extern const unsigned int fireColor[100];
#if defined(ARDUINO_ARCH_RP2040)
// On RP2040, the refresh() function is called in a tight loop on the
// second core (via the loop1() function). The first core is then 100%
// free for animation logic in the regular loop() function.
void loop1() {
leds.refresh();
}
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// On ESP32S3, refresh() is called in a tight loop on core 0. The other
// core (#1), where Arduino code normally runs, is then 100% free for
// animation logic in the loop() function.
void loop0(void *param) {
for(;;) {
yield();
leds.refresh();
}
}
#else // SAMD
// SAMD, being single-core, uses a timer interrupt for refresh().
// As written, this uses Timer/Counter 3, but with some minor edits
// you can change that if it interferes with other code.
#include "Adafruit_ZeroTimer.h"
Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3);
void TC3_Handler() {
Adafruit_ZeroTimer::timerHandler(3);
}
void timerCallback(void) {
leds.refresh();
}
#endif // end SAMD
// Run setup once
void setup() {
if (!leds.begin()) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
leds.setBrightness(65535, 2.6); // Full brightness, 2.6 gamma factor
leds.show(); // Clear initial LED state
#if defined(CONFIG_IDF_TARGET_ESP32S3)
// Run loop0() on core 0 (Arduino uses core 1) w/16K stack
(void)xTaskCreatePinnedToCore(loop0, "NeoPXL8HDR", 16384, NULL, 0, NULL, 0);
#elif defined(__SAMD51__) || defined(_SAMD21_)
// Setup diverges again for SAMD51 only, to activate the timer code shown
// earlier. The timer is currently configured for a 120 Hz refresh. This
// saps a fair percentage of time from the main thread, despite all the
// DMA going on, which is why RP2040 is most recommended for NeoPXL8HDR.
zerotimer.enable(false);
zerotimer.configure(TC_CLOCK_PRESCALER_DIV16, // 3 MHz clock
TC_COUNTER_SIZE_16BIT, // 16-bit counter
TC_WAVE_GENERATION_MATCH_PWM); // Freq or PWM mode
zerotimer.setCompare(0, 3000000 / 120); // 120 Hz refresh
zerotimer.setCallback(true, TC_CALLBACK_CC_CHANNEL0, timerCallback);
zerotimer.enable(true);
#endif // end SAMD
}
// REMAINDER OF CODE IS NEARLY IDENTICAL TO NON-HDR VERSION. This version
// doesn't require the gamma correction lookup, handled by the HDR class.
// A simple xy() function to turn display matrix coordinates
// into the index numbers NeoPXL8 requires. If your LEDs
// are arranged differently, edit this code...
unsigned int xy(unsigned int x, unsigned int y) {
if ((y & 1) == 0) {
// even numbered rows (0, 2, 4...) are left to right
return y * width + x;
} else {
// odd numbered rows (1, 3, 5...) are right to left
return y * width + width - 1 - x;
}
}
uint32_t lastTime = 0;
// Run repetitively
void loop() {
uint32_t now = millis();
if (now - lastTime >= 45) {
lastTime = now;
animateFire();
}
}
void animateFire() {
unsigned int i, c, n, x, y;
// Step 1: move all data up one line
memmove(canvas + width, canvas, width * (height - 1));
memset(canvas, 0, width);
// Step 2: draw random heatspots on bottom line
i = heat;
if (i > width-8) i = width-8;
while (i > 0) {
x = random(width - 2) + 1;
if (canvas[x] == 0) {
canvas[x] = 99;
i--;
}
}
// Step 3: interpolate
for (y=0; y < height; y++) {
for (x=0; x < width; x++) {
c = canvas[y * width + x] * focus;
n = focus;
if (x > 0) {
c = c + canvas[y * width + (x - 1)];
n = n + 1;
}
if (x < width-1) {
c = c + canvas[y * width + (x + 1)];
n = n + 1;
}
if (y > 0) {
c = c + canvas[(y -1) * width + x];
n = n + 1;
}
if (y < height-1) {
c = c + canvas[(y + 1) * width + x];
n = n + 1;
}
c = (c + (n / 2)) / n;
i = (random(1000) * cool) / 10000;
if (c > i) {
c = c - i;
} else {
c = 0;
}
canvas[y * width + x] = c;
}
}
// Step 4: render canvas to LEDs
for (y=0; y < height; y++) {
for (x=0; x < width; x++) {
c = canvas[((height - 1) - y) * width + x];
leds.setPixelColor(xy(x, y), fireColor[c]);
}
}
leds.show();
}

View file

@ -0,0 +1,33 @@
// This is just the linear fireColor[] array from the original OctoWS2811
// Fire example, since NeoPXL8 has its own gamma function.
extern const unsigned int fireColor[100];
#define RGB(r, g, b) (((r) << 16) | ((g) << 8) | (b))
const unsigned int fireColor[100] = {
RGB(0, 0, 0), RGB(5, 0, 0), RGB(10, 0, 0), RGB(14, 0, 0),
RGB(19, 0, 0), RGB(23, 0, 0), RGB(27, 0, 0), RGB(31, 0, 0),
RGB(35, 0, 0), RGB(39, 0, 0), RGB(43, 0, 0), RGB(47, 0, 0),
RGB(51, 0, 0), RGB(55, 0, 0), RGB(59, 0, 0), RGB(63, 0, 0),
RGB(67, 0, 0), RGB(71, 0, 0), RGB(75, 0, 0), RGB(79, 0, 0),
RGB(83, 0, 0), RGB(88, 0, 0), RGB(92, 0, 0), RGB(96, 0, 0),
RGB(100, 0, 0), RGB(104, 0, 0), RGB(108, 0, 0), RGB(112, 0, 0),
RGB(116, 0, 0), RGB(121, 0, 0), RGB(125, 0, 0), RGB(129, 0, 0),
RGB(133, 0, 0), RGB(137, 0, 0), RGB(141, 0, 0), RGB(145, 0, 0),
RGB(149, 0, 0), RGB(153, 0, 0), RGB(157, 0, 0), RGB(161, 0, 0),
RGB(165, 0, 0), RGB(169, 0, 0), RGB(173, 0, 0), RGB(177, 0, 0),
RGB(181, 0, 0), RGB(185, 0, 0), RGB(190, 0, 0), RGB(194, 0, 0),
RGB(198, 0, 0), RGB(202, 0, 0), RGB(205, 2, 0), RGB(207, 6, 0),
RGB(209, 10, 0), RGB(211, 14, 0), RGB(213, 18, 0), RGB(215, 22, 0),
RGB(217, 26, 0), RGB(219, 30, 0), RGB(221, 35, 0), RGB(223, 39, 0),
RGB(225, 43, 0), RGB(227, 47, 0), RGB(229, 51, 0), RGB(231, 55, 0),
RGB(233, 59, 0), RGB(235, 63, 0), RGB(237, 67, 0), RGB(239, 71, 0),
RGB(241, 75, 0), RGB(243, 79, 0), RGB(245, 83, 0), RGB(248, 88, 0),
RGB(250, 92, 0), RGB(252, 96, 0), RGB(254, 100, 0), RGB(255, 105, 1),
RGB(255, 111, 3), RGB(255, 117, 5), RGB(255, 123, 7), RGB(255, 130, 9),
RGB(255, 136, 11), RGB(255, 142, 13), RGB(255, 148, 15), RGB(255, 154, 17),
RGB(255, 160, 19), RGB(255, 166, 21), RGB(255, 172, 23), RGB(255, 179, 25),
RGB(255, 185, 27), RGB(255, 191, 29), RGB(255, 197, 31), RGB(255, 203, 33),
RGB(255, 209, 35), RGB(255, 215, 37), RGB(255, 221, 39), RGB(255, 227, 41),
RGB(255, 234, 44), RGB(255, 240, 46), RGB(255, 246, 48), RGB(255, 252, 50)};

View file

@ -0,0 +1,154 @@
// FIRST TIME HERE? START WITH THE NEOPXL8 (not HDR) strandtest EXAMPLE!
// That code explains and helps troubshoot wiring and NeoPixel color format.
// Then move on to NeoPXL8HDR strandtest, THEN try this one.
#include <Adafruit_NeoPXL8.h>
// CHANGE these to match your strandtest findings or this WILL NOT WORK:
int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
#define COLOR_ORDER NEO_GRB
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
//PlazINT - Fast Plasma Generator using Integer Math Only
//Edmund "Skorn" Horn
//March 4,2013
//Version 1.0 adapted for OctoWS2811Lib (tested, working...)
//OctoWS2811 Defn. Stuff
#define COLS_LEDs 30 // all of the following params need to be adjusted for screen size
#define ROWS_LEDs 16 // LED_LAYOUT assumed 0 if ROWS_LEDs > 8
#define LEDS_PER_STRIP (COLS_LEDs * ROWS_LEDs / 8)
Adafruit_NeoPXL8HDR leds(LEDS_PER_STRIP, pins, COLOR_ORDER);
//Byte val 2PI Cosine Wave, offset by 1 PI
//supports fast trig calcs and smooth LED fading/pulsing.
uint8_t const cos_wave[256] PROGMEM =
{0,0,0,0,1,1,1,2,2,3,4,5,6,6,8,9,10,11,12,14,15,17,18,20,22,23,25,27,29,31,33,35,38,40,42,
45,47,49,52,54,57,60,62,65,68,71,73,76,79,82,85,88,91,94,97,100,103,106,109,113,116,119,
122,125,128,131,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,
189,191,194,197,199,202,204,207,209,212,214,216,218,221,223,225,227,229,231,232,234,236,
238,239,241,242,243,245,246,247,248,249,250,251,252,252,253,253,254,254,255,255,255,255,
255,255,255,255,254,254,253,253,252,252,251,250,249,248,247,246,245,243,242,241,239,238,
236,234,232,231,229,227,225,223,221,218,216,214,212,209,207,204,202,199,197,194,191,189,
186,183,180,177,174,171,168,165,162,159,156,153,150,147,144,141,138,135,131,128,125,122,
119,116,113,109,106,103,100,97,94,91,88,85,82,79,76,73,71,68,65,62,60,57,54,52,49,47,45,
42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14,12,11,10,9,8,6,6,5,4,3,2,2,1,1,1,0,0,0,0
};
// Gamma table removed because NeoPXL8HDR provides this functionality.
#if defined(ARDUINO_ARCH_RP2040)
// On RP2040, the refresh() function is called in a tight loop on the
// second core (via the loop1() function). The first core is then 100%
// free for animation logic in the regular loop() function.
void loop1() {
leds.refresh();
}
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// On ESP32S3, refresh() is called in a tight loop on core 0. The other
// core (#1), where Arduino code normally runs, is then 100% free for
// animation logic in the loop() function.
void loop0(void *param) {
for(;;) {
yield();
leds.refresh();
}
}
#else // SAMD
// SAMD, being single-core, uses a timer interrupt for refresh().
// As written, this uses Timer/Counter 3, but with some minor edits
// you can change that if it interferes with other code.
#include "Adafruit_ZeroTimer.h"
Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3);
void TC3_Handler() {
Adafruit_ZeroTimer::timerHandler(3);
}
void timerCallback(void) {
leds.refresh();
}
#endif // end SAMD
void setup() {
if (!leds.begin()) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
leds.setBrightness(25000, 2.6); // Less bright, enable gamma
leds.show(); // Clear initial LED state
#if defined(CONFIG_IDF_TARGET_ESP32S3)
// Run loop0() on core 0 (Arduino uses core 1) w/16K stack
(void)xTaskCreatePinnedToCore(loop0, "NeoPXL8HDR", 16384, NULL, 0, NULL, 0);
#elif defined(__SAMD51__) || defined(_SAMD21_)
// Setup diverges again for SAMD51 only, to activate the timer code shown
// earlier. The timer is currently configured for a 120 Hz refresh. This
// saps a fair percentage of time from the main thread, despite all the
// DMA going on, which is why RP2040 is most recommended for NeoPXL8HDR.
zerotimer.enable(false);
zerotimer.configure(TC_CLOCK_PRESCALER_DIV16, // 3 MHz clock
TC_COUNTER_SIZE_16BIT, // 16-bit counter
TC_WAVE_GENERATION_MATCH_PWM); // Freq or PWM mode
zerotimer.setCompare(0, 3000000 / 120); // 120 Hz refresh
zerotimer.setCallback(true, TC_CALLBACK_CC_CHANNEL0, timerCallback);
zerotimer.enable(true);
#endif // end SAMD
}
// REMAINDER OF CODE IS NEARLY IDENTICAL TO NON-HDR VERSION. This version
// doesn't require the gamma correction lookup, handled by the HDR class.
unsigned long frameCount=25500; // arbitrary seed to calculate the three time displacement variables t,t2,t3
void loop() {
frameCount++ ;
uint16_t t = fastCosineCalc((42 * frameCount)/400); //time displacement - fiddle with these til it looks good...
uint16_t t2 = fastCosineCalc((35 * frameCount)/400);
uint16_t t3 = fastCosineCalc((38 * frameCount)/400);
for (uint8_t y = 0; y < ROWS_LEDs; y++) {
int left2Right, pixelIndex;
if (((y % (ROWS_LEDs/8)) & 1) == 0) {
left2Right = 1;
pixelIndex = y * COLS_LEDs;
} else {
left2Right = -1;
pixelIndex = (y + 1) * COLS_LEDs - 1;
}
for (uint8_t x = 0; x < COLS_LEDs ; x++) {
//Calculate 3 seperate plasma waves, one for each color channel
uint8_t r = fastCosineCalc(((x << 3) + (t >> 1) + fastCosineCalc((t2 + (y << 3)))));
uint8_t g = fastCosineCalc(((y << 3) + t + fastCosineCalc(((t3 >> 2) + (x << 3)))));
uint8_t b = fastCosineCalc(((y << 3) + t2 + fastCosineCalc((t + x + (g >> 2)))));
leds.setPixelColor(pixelIndex, r, g, b);
pixelIndex += left2Right;
}
}
leds.show();
}
inline uint8_t fastCosineCalc( uint16_t preWrapVal) {
uint8_t wrapVal = (preWrapVal % 255);
if (wrapVal<0) wrapVal=255+wrapVal;
return (pgm_read_byte_near(cos_wave+wrapVal));
}

View file

@ -0,0 +1,445 @@
// This sketch is Just Too Much for SAMD21 (M0) and SAMD51 (M4) boards.
// Recommend RP2040/SCORPIO or ESP32-S3.
// FIRST TIME HERE? START WITH THE NEOPXL8 (not HDR) strandtest EXAMPLE!
// That code explains and helps troubshoot wiring and NeoPixel color format.
// Then move on to NeoPXL8HDR strandtest, THEN try this one.
// This is a companion to "move2msc" in the extras/Processing folder.
// It plays preconverted videos from the on-board flash filesystem.
#include "SdFat_Adafruit_Fork.h"
#include <Adafruit_NeoPXL8.h>
#include <Adafruit_CPFS.h> // For accessing CIRCUITPY drive
#define ARDUINOJSON_ENABLE_COMMENTS 1
#include <ArduinoJson.h>
// This example is adapted from one in PJRC's OctoWS2811 Library. Original
// comments appear first, and Adafruit_NeoPXL8 changes follow that. Any
// original comments about "SD card" now apply to a board's CIRCUITPY flash
// filesystem instead.
/* OctoWS2811 VideoSDcard.ino - Video on LEDs, played from SD Card
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2014 Paul Stoffregen, PJRC.COM, LLC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Update: The programs to prepare the SD card video file have moved to "extras"
https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
The recommended hardware for SD card playing is:
Teensy 3.1: http://www.pjrc.com/store/teensy31.html
Octo28 Apaptor: http://www.pjrc.com/store/octo28_adaptor.html
SD Adaptor: http://www.pjrc.com/store/wiz820_sd_adaptor.html
Long Pins: http://www.digikey.com/product-search/en?keywords=S1082E-36-ND
See the included "hardware.jpg" image for suggested pin connections,
with 2 cuts and 1 solder bridge needed for the SD card pin 3 chip select.
Required Connections
--------------------
pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips.
pin 14: LED strip #2 All 8 are the same length.
pin 7: LED strip #3
pin 8: LED strip #4 A 100 to 220 ohm resistor should used
pin 6: LED strip #5 between each Teensy pin and the
pin 20: LED strip #6 wire to the LED strip, to minimize
pin 21: LED strip #7 high frequency ringining & noise.
pin 5: LED strip #8
pin 15 & 16 - Connect together, but do not use
pin 4: Do not use
pin 3: SD Card, CS
pin 11: SD Card, MOSI
pin 12: SD Card, MISO
pin 13: SD Card, SCLK
*/
/*
ADAFRUIT_NEOPXL8 UPDATE:
Aside from changes to convert from OctoWS2811 to Adafruit_NeoPXL8, the
big drastic change here is to eliminate many compile-time constants and
instead place these in a JSON configuration file on a board's CIRCUITPY
flash filesystem (though this is Arduino code, we can still make use of
that drive), and declare the LEDs at run time. Other than those
alterations, the code is minimally changed. Paul did the real work. :)
Run-time configuration is stored in CIRCUITPY/neopxl8.cfg and resembles:
{
"pins" : [ 16, 17, 18, 19, 20, 21, 22, 23 ],
"order" : "GRB",
"led_width" : 30,
"led_height" : 16,
"led_layout" : 0
}
If this file is missing, or if any individual elements are unspecified,
defaults will be used (these are noted later in the code). It's possible,
likely even, that there will be additional elements in this file...
for example, some NeoPXL8 code might use a single "length" value rather
than width/height, as not all projects are using a grid. Be warned that
JSON is highly picky and even a single missing or excess comma will stop
everything, so read through it very carefully if encountering an error.
*/
#define FILENAME "mymovie.bin"
// In original code, these were constants LED_WIDTH, LED_HEIGHT and
// LED_LAYOUT. Values here are defaults but can override in config file.
// led_height MUST be a multiple of 8. When 16, 24, 32 are used, each strip
// spans 2, 3, 4 rows. led_layout indicates how strips are arranged.
uint16_t led_width = 30; // Number of LEDs horizontally
uint16_t led_height = 16; // Number of LEDs vertically
uint8_t led_layout = 0; // 0 = even rows left->right, 1 = right->left
FatFile file;
bool playing = false;
uint32_t timeOfLastFrame = 0;
uint8_t *imageBuffer; // LED data from filesystem is staged here
uint32_t imageBufferSize; // Size (in bytes) of imageBuffer
Adafruit_NeoPXL8HDR *leds = NULL;
void error_handler(const char *message, uint16_t speed) {
Serial.print("Error: ");
Serial.println(message);
if (speed) { // Fatal error, blink LED
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
digitalWrite(LED_BUILTIN, (millis() / speed) & 1);
yield(); // Keep filesystem accessible for editing
}
} else { // Not fatal, just show message
Serial.println("Continuing with defaults");
}
}
#if defined(ARDUINO_ARCH_RP2040)
// On RP2040, the refresh() function is called in a tight loop on the
// second core (via the loop1() function). The first core is then 100%
// free for animation logic in the regular loop() function.
void loop1() {
if (leds) leds->refresh();
}
// Pause just a moment before starting the refresh() loop.
// Mass storage has a lot to do on startup and the sketch locks up
// without this. So far it's only needed on the MSC+HDR example.
void setup1() {
delay(1000);
}
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// On ESP32S3, refresh() is called in a tight loop on core 0. The other
// core (#1), where Arduino code normally runs, is then 100% free for
// animation logic in the loop() function.
void loop0(void *param) {
for(;;) {
yield();
if (leds) leds->refresh();
}
}
#else // SAMD
// SAMD, being single-core, uses a timer interrupt for refresh().
// As written, this uses Timer/Counter 3, but with some minor edits
// you can change that if it interferes with other code.
#include "Adafruit_ZeroTimer.h"
Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3);
void TC3_Handler() {
Adafruit_ZeroTimer::timerHandler(3);
}
void timerCallback(void) {
if (leds) leds->refresh();
}
#endif // end SAMD
void setup() {
// CHANGE these to match your strandtest findings (or use .cfg file):
int8_t pins[8] = NEOPXL8_DEFAULT_PINS;
uint16_t order = NEO_GRB;
// Start the CIRCUITPY flash filesystem first. Very important!
FatVolume *fs = Adafruit_CPFS::begin();
// Start Serial AFTER FFS begin, else CIRCUITPY won't show on computer.
Serial.begin(115200);
//while(!Serial);
delay(1000);
Serial.setTimeout(50);
Serial.println("VideoMSC");
if (fs == NULL) {
error_handler("Can't access CIRCUITPY drive", 0);
} else {
StaticJsonDocument<1024> doc;
DeserializationError error;
// Open NeoPXL8 configuration file and attempt to decode JSON data within.
if ((file = fs->open("neopxl8.cfg", FILE_READ))) {
error = deserializeJson(doc, file);
file.close();
} else {
error_handler("neopxl8.cfg not found", 0);
}
if(error) {
error_handler("neopxl8.cfg syntax error", 0);
Serial.print("JSON error: ");
Serial.println(error.c_str());
} else {
// Config is valid, override defaults in program variables...
JsonVariant v = doc["pins"];
if (v.is<JsonArray>()) {
uint8_t n = v.size() < 8 ? v.size() : 8;
for (uint8_t i = 0; i < n; i++)
pins[i] = v[i].as<int>();
}
v = doc["order"];
if (v.is<const char *>()) order = Adafruit_NeoPixel::str2order(v);
led_width = doc["led_width"] | led_width;
led_height = doc["led_height"] | led_height;
led_layout = doc["led_layout"] | led_layout;
} // end JSON OK
} // end filesystem OK
// Any errors after this point are unrecoverable and program will stop.
// Dynamically allocate NeoPXL8 object
leds = new Adafruit_NeoPXL8HDR(led_width * led_height / 8, pins, order);
if (leds == NULL) error_handler("NeoPXL8 allocation", 100);
// Allocate imageBuffer
imageBufferSize = led_width * led_height * 3;
imageBuffer = (uint8_t *)malloc(imageBufferSize);
if (imageBuffer == NULL) error_handler("Image buffer allocation", 200);
if (!leds->begin()) error_handler("NeoPXL8 begin() failed", 500);
// At this point, everything but input file is ready to go!
leds->setBrightness(65535, 2.6); // Full brightness, 2.6 gamma factor
leds->show(); // LEDs off ASAP
bool status = file.open(FILENAME, O_RDONLY);
if (!status) error_handler("Can't open movie .bin file", 1000);
Serial.println("File opened");
delay(500); // Give MSC a moment to compose itself
playing = true;
timeOfLastFrame = 0;
#if defined(CONFIG_IDF_TARGET_ESP32S3)
// Run loop0() on core 0 (Arduino uses core 1) w/16K stack
(void)xTaskCreatePinnedToCore(loop0, "NeoPXL8HDR", 16384, NULL, 0, NULL, 0);
#elif defined(__SAMD51__) || defined(_SAMD21_)
// Setup diverges again for SAMD51 only, to activate the timer code shown
// earlier. The timer is currently configured for a 120 Hz refresh. This
// saps a fair percentage of time from the main thread, despite all the
// DMA going on, which is why RP2040 is most recommended for NeoPXL8HDR.
zerotimer.enable(false);
zerotimer.configure(TC_CLOCK_PRESCALER_DIV16, // 3 MHz clock
TC_COUNTER_SIZE_16BIT, // 16-bit counter
TC_WAVE_GENERATION_MATCH_PWM); // Freq or PWM mode
zerotimer.setCompare(0, 3000000 / 120); // 120 Hz refresh
zerotimer.setCallback(true, TC_CALLBACK_CC_CHANNEL0, timerCallback);
zerotimer.enable(true);
#endif // end SAMD
}
// REMAINDER OF CODE IS NEARLY IDENTICAL TO NON-HDR VERSION. This version
// doesn't require the gamma correction lookup, handled by the HDR class.
// read from the SD card, true=ok, false=unable to read
// the SD library is much faster if all reads are 512 bytes
// this function lets us easily read any size, but always
// requests data from the SD library in 512 byte blocks.
//
bool sd_card_read(void *ptr, unsigned int len)
{
static unsigned char buffer[512];
static unsigned int bufpos = 0;
static unsigned int buflen = 0;
unsigned char *dest = (unsigned char *)ptr;
unsigned int n;
while (len > 0) {
if (buflen == 0) {
n = file.read(buffer, 512);
if (n == 0) return false;
buflen = n;
bufpos = 0;
}
unsigned int n = buflen;
if (n > len) n = len;
memcpy(dest, buffer + bufpos, n);
dest += n;
bufpos += n;
buflen -= n;
len -= n;
}
return true;
}
// skip past data from the SD card
void sd_card_skip(unsigned int len)
{
unsigned char buf[256];
while (len > 0) {
unsigned int n = len;
if (n > sizeof(buf)) n = sizeof(buf);
sd_card_read(buf, n);
len -= n;
}
}
void loop()
{
unsigned char header[5];
if (playing) {
if (sd_card_read(header, 5)) {
if (header[0] == '*') {
// found an image frame
unsigned int size = (header[1] | (header[2] << 8)) * 3;
unsigned int usec = header[3] | (header[4] << 8);
unsigned int readsize = size;
//Serial.printf("v: %u %u\n", size, usec);
if (readsize > imageBufferSize) {
readsize = imageBufferSize;
}
if (sd_card_read(imageBuffer, readsize)) {
uint32_t now;
// Serial.printf(", us = %u", (unsigned int)(micros() - timeOfLastFrame));
// Serial.println();
do {
now = micros();
} while ((now - timeOfLastFrame) < usec) ; // wait
timeOfLastFrame = now;
convert_and_show();
} else {
error("unable to read video frame data");
return;
}
if (readsize < size) {
sd_card_skip(size - readsize);
}
} else if (header[0] == '%') {
// found a chunk of audio data
unsigned int size = (header[1] | (header[2] << 8)) * 2;
#if 0
// Serial.printf("a: %u", size);
// Serial.println();
while (size > 0) {
unsigned int len = size;
if (len > 256) len = 256;
int16_t *p = audio.getBuffer();
if (!sd_card_read(p, len)) {
error("unable to read audio frame data");
return;
}
if (len < 256) {
for (int i=len; i < 256; i++) {
*((char *)p + i) = 0; // fill rest of buffer with zero
}
}
audio.playBuffer();
size -= len;
}
#else
file.seekCur(size);
#endif
} else {
error("unknown header");
return;
}
} else {
error("unable to read 5-byte header");
return;
}
} else {
delay(2000);
bool status = file.open(FILENAME, FILE_READ);
if (status) {
Serial.println("File opened");
playing = true;
timeOfLastFrame = micros();
}
}
}
// when any error happens during playback, close the file and restart
void error(const char *str)
{
Serial.print("error: ");
Serial.println(str);
file.close();
playing = false;
}
void convert_and_show() {
uint8_t *ptr = imageBuffer;
if (led_layout == 0) {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
int pixelIndex = y * led_width + x; // Always left-to-right
uint8_t r = *ptr++;
uint8_t g = *ptr++;
uint8_t b = *ptr++;
leds->setPixelColor(pixelIndex, r, g, b);
}
}
} else {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
// Even rows are left-to-right, odd are right-to-left
int pixelIndex = (y & 1) ? (y + 1) * led_width - 1 - x : y * led_width + x;
uint8_t r = *ptr++;
uint8_t g = *ptr++;
uint8_t b = *ptr++;
leds->setPixelColor(pixelIndex, r, g, b);
}
}
}
leds->show();
}

View file

@ -0,0 +1,461 @@
// This sketch is Just Too Much for SAMD21 (M0) and SAMD51 (M4) boards.
// Recommend RP2040/SCORPIO or ESP32-S3.
// FIRST TIME HERE? START WITH THE NEOPXL8 (not HDR) strandtest EXAMPLE!
// That code explains and helps troubshoot wiring and NeoPixel color format.
// Then move on to NeoPXL8HDR strandtest, THEN try this one.
// This is a companion to "move2serial" in the extras/Processing folder.
#include "SdFat_Adafruit_Fork.h"
#include <Adafruit_NeoPXL8.h>
#include <Adafruit_CPFS.h> // For accessing CIRCUITPY drive
#define ARDUINOJSON_ENABLE_COMMENTS 1
#include <ArduinoJson.h>
// This example is adapted from one in PJRC's OctoWS2811 Library. Original
// comments appear first, followed by Adafruit_NeoPXL8 changes.
/* OctoWS2811 VideoDisplay.ino - Video on LEDs, from a PC, Mac, Raspberry Pi
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Update: The movie2serial program which transmit data has moved to "extras"
https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
Required Connections
--------------------
pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips.
pin 14: LED strip #2 All 8 are the same length.
pin 7: LED strip #3
pin 8: LED strip #4 A 100 to 220 ohm resistor should used
pin 6: LED strip #5 between each Teensy pin and the
pin 20: LED strip #6 wire to the LED strip, to minimize
pin 21: LED strip #7 high frequency ringining & noise.
pin 5: LED strip #8
pin 15 & 16 - Connect together, but do not use
pin 4: Do not use
pin 3: Do not use as PWM. Normal use is ok.
pin 12: Frame Sync
When using more than 1 Teensy to display a video image, connect
the Frame Sync signal between every board. All boards will
synchronize their WS2811 update using this signal.
Beware of image distortion from long LED strip lengths. During
the WS2811 update, the LEDs update in sequence, not all at the
same instant! The first pixel updates after 30 microseconds,
the second pixel after 60 us, and so on. A strip of 120 LEDs
updates in 3.6 ms, which is 10.8% of a 30 Hz video frame time.
Doubling the strip length to 240 LEDs increases the lag to 21.6%
of a video frame. For best results, use shorter length strips.
Multiple boards linked by the frame sync signal provides superior
video timing accuracy.
A Multi-TT USB hub should be used if 2 or more Teensy boards
are connected. The Multi-TT feature allows proper USB bandwidth
allocation. Single-TT hubs, or direct connection to multiple
ports on the same motherboard, may give poor performance.
*/
/*
ADAFRUIT_NEOPXL8 UPDATE:
Aside from changes to convert from OctoWS2811 to Adafruit_NeoPXL8, the
big drastic change here is to eliminate many compile-time constants and
instead place these in a JSON configuration file on a board's CIRCUITPY
flash filesystem (though this is Arduino code, we can still make use of
that drive), and declare the LEDs at run time. Other than those
alterations, the code is minimally changed. Paul did the real work. :)
Run-time configuration is stored in CIRCUITPY/neopxl8.cfg and resembles:
{
"pins" : [ 16, 17, 18, 19, 20, 21, 22, 23 ],
"order" : "GRB",
"sync_pin" : -1,
"led_width" : 30,
"led_height" : 16,
"led_layout" : 0,
"video_xoffset" : 0,
"video_yoffset" : 0,
"video_width" : 100,
"video_height" : 100
}
If this file is missing, or if any individual elements are unspecified,
defaults will be used (these are noted later in the code). It's possible,
likely even, that there will be additional elements in this file...
for example, some NeoPXL8 code might use a single "length" value rather
than width/height, as not all projects are using a grid. Be warned that
JSON is highly picky and even a single missing or excess comma will stop
everything, so read through it very carefully if encountering an error.
*/
// In original code, these were constants LED_WIDTH, LED_HEIGHT and
// LED_LAYOUT. Values here are defaults but can override in config file.
// led_height MUST be a multiple of 8. When 16, 24, 32 are used, each strip
// spans 2, 3, 4 rows. led_layout indicates how strips are arranged.
uint16_t led_width = 30; // Number of LEDs horizontally
uint16_t led_height = 16; // Number of LEDs vertically
uint8_t led_layout = 0; // 0 = even rows left->right, 1 = right->left
// The portion of the video image to show on this set of LEDs. All 4 numbers
// are percentages, from 0 to 100. For a large LED installation with many
// boards driving groups of LEDs, these parameters allow you to program each
// one to tell the video application which portion of the video it displays.
// By reading these numbers, the video application can automatically configure
// itself, regardless of which serial port COM number or device names are
// assigned to each board by your operating system. 0/0/100/100 displays the
// entire image on one board's LEDs. With two boards, this could be split
// between them, 0/0/100/50 for the top and 0/50/100/50 for the bottom.
uint8_t video_xoffset = 0;
uint8_t video_yoffset = 0;
uint8_t video_width = 100;
uint8_t video_height = 100;
uint8_t *imageBuffer; // Serial LED data is received here
uint32_t imageBufferSize; // Size (in bytes) of imageBuffer
int8_t sync_pin = -1; // If multiple boards, wire pins together
uint32_t lastFrameSyncTime = 0;
Adafruit_NeoPXL8HDR *leds = NULL; // NeoPXL8HDR object is allocated after reading config
#if defined(ARDUINO_ARCH_RP2040)
// On RP2040, the refresh() function is called in a tight loop on the
// second core (via the loop1() function). The first core is then 100%
// free for animation logic in the regular loop() function.
void loop1() {
if (leds) leds->refresh();
}
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// On ESP32S3, refresh() is called in a tight loop on core 0. The other
// core (#1), where Arduino code normally runs, is then 100% free for
// animation logic in the loop() function.
void loop0(void *param) {
for(;;) {
yield();
if (leds) leds->refresh();
}
}
#else // SAMD
// SAMD, being single-core, uses a timer interrupt for refresh().
// As written, this uses Timer/Counter 3, but with some minor edits
// you can change that if it interferes with other code.
#include "Adafruit_ZeroTimer.h"
Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3);
void TC3_Handler() {
Adafruit_ZeroTimer::timerHandler(3);
}
void timerCallback(void) {
if (leds) leds->refresh();
}
#endif // end SAMD
void error_handler(const char *message, uint16_t speed) {
Serial.print("Error: ");
Serial.println(message);
if (speed) { // Fatal error, blink LED
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
digitalWrite(LED_BUILTIN, (millis() / speed) & 1);
yield(); // Keep filesystem accessible for editing
}
} else { // Not fatal, just show message
Serial.println("Continuing with defaults");
}
}
void setup() {
// CHANGE these to match your strandtest findings (or use .cfg file):
int8_t pins[8] = NEOPXL8_DEFAULT_PINS;
uint16_t order = NEO_GRB;
// Start the CIRCUITPY flash filesystem first. Very important!
FatVolume *fs = Adafruit_CPFS::begin();
// Start Serial AFTER FFS begin, else CIRCUITPY won't show on computer.
Serial.begin(115200);
//while(!Serial);
delay(1000);
Serial.setTimeout(50);
if (fs == NULL) {
error_handler("Can't access CIRCUITPY drive", 0);
} else {
FatFile file;
StaticJsonDocument<1024> doc;
DeserializationError error;
// Open NeoPXL8 configuration file and attempt to decode JSON data within.
if ((file = fs->open("neopxl8.cfg", FILE_READ))) {
error = deserializeJson(doc, file);
file.close();
} else {
error_handler("neopxl8.cfg not found", 0);
}
if(error) {
error_handler("neopxl8.cfg syntax error", 0);
Serial.print("JSON error: ");
Serial.println(error.c_str());
} else {
// Config is valid, override defaults in program variables...
JsonVariant v = doc["pins"];
if (v.is<JsonArray>()) {
uint8_t n = v.size() < 8 ? v.size() : 8;
for (uint8_t i = 0; i < n; i++)
pins[i] = v[i].as<int>();
}
v = doc["order"];
if (v.is<const char *>()) order = Adafruit_NeoPixel::str2order(v);
sync_pin = doc["sync_pin"] | sync_pin;
led_width = doc["led_width"] | led_width;
led_height = doc["led_height"] | led_height;
led_layout = doc["led_layout"] | led_layout;
video_xoffset = doc["video_xoffset"] | video_xoffset;
video_yoffset = doc["video_yoffset"] | video_yoffset;
video_width = doc["video_width"] | video_width;
video_height = doc["video_height"] | video_height;
} // end JSON OK
} // end filesystem OK
// Any errors after this point are unrecoverable and program will stop.
// Dynamically allocate NeoPXL8HDR object
leds = new Adafruit_NeoPXL8HDR(led_width * led_height / 8, pins, order);
if (leds == NULL) error_handler("NeoPXL8HDR allocation", 100);
// Allocate imageBuffer
imageBufferSize = led_width * led_height * 3;
imageBuffer = (uint8_t *)malloc(imageBufferSize);
if (imageBuffer == NULL) error_handler("Image buffer allocation", 200);
if (!leds->begin()) error_handler("NeoPXL8HDR begin() failed", 500);
// At this point, everything is fully configured, allocated and started!
leds->setBrightness(65535, 2.6); // Full brightness, 2.6 gamma factor
leds->show(); // LEDs off ASAP
if (sync_pin >= 0) pinMode(sync_pin, INPUT_PULLUP);
#if defined(CONFIG_IDF_TARGET_ESP32S3)
// Run loop0() on core 0 (Arduino uses core 1) w/16K stack
(void)xTaskCreatePinnedToCore(loop0, "NeoPXL8HDR", 16384, NULL, 0, NULL, 0);
#elif defined(__SAMD51__) || defined(_SAMD21_)
// Setup diverges again for SAMD51 only, to activate the timer code shown
// earlier. The timer is currently configured for a 120 Hz refresh. This
// saps a fair percentage of time from the main thread, despite all the
// DMA going on, which is why RP2040 is most recommended for NeoPXL8HDR.
zerotimer.enable(false);
zerotimer.configure(TC_CLOCK_PRESCALER_DIV16, // 3 MHz clock
TC_COUNTER_SIZE_16BIT, // 16-bit counter
TC_WAVE_GENERATION_MATCH_PWM); // Freq or PWM mode
zerotimer.setCompare(0, 3000000 / 120); // 120 Hz refresh
zerotimer.setCallback(true, TC_CALLBACK_CC_CHANNEL0, timerCallback);
zerotimer.enable(true);
#endif // end SAMD
}
// REMAINDER OF CODE IS NEARLY IDENTICAL TO NON-HDR VERSION. This version
// doesn't require the gamma correction lookup, handled by the HDR class.
void loop() {
//
// wait for a Start-Of-Message character:
//
// '*' = Frame of image data, with frame sync pulse to be sent
// a specified number of microseconds after reception of
// the first byte (typically at 75% of the frame time, to
// allow other boards to fully receive their data).
// Normally '*' is used when the sender controls the pace
// of playback by transmitting each frame as it should
// appear.
//
// '$' = Frame of image data, with frame sync pulse to be sent
// a specified number of microseconds after the previous
// frame sync. Normally this is used when the sender
// transmits each frame as quickly as possible, and we
// control the pacing of video playback by updating the
// LEDs based on time elapsed from the previous frame.
//
// '%' = Frame of image data, to be displayed with a frame sync
// pulse is received from another board. In a multi-board
// system, the sender would normally transmit one '*' or '$'
// message and '%' messages to all other boards, so every
// Teensy 3.0 updates at the exact same moment.
//
// '@' = Reset the elapsed time, used for '$' messages. This
// should be sent before the first '$' message, so many
// frames are not played quickly if time as elapsed since
// startup or prior video playing.
//
// '?' = Query LED and Video parameters. Teensy 3.0 responds
// with a comma delimited list of information.
//
int startChar = Serial.read();
if (startChar == '*') {
// receive a "master" frame - we send the frame sync to other boards
// the sender is controlling the video pace. The 16 bit number is
// how far into this frame to send the sync to other boards.
unsigned int startAt = micros();
unsigned int usecUntilFrameSync = 0;
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
if (count != 2) return;
count = Serial.readBytes((char *)imageBuffer, imageBufferSize);
if (count == imageBufferSize) {
unsigned int endAt = micros();
unsigned int usToWaitBeforeSyncOutput = 100;
if (endAt - startAt < usecUntilFrameSync) {
usToWaitBeforeSyncOutput = usecUntilFrameSync - (endAt - startAt);
}
digitalWrite(sync_pin, HIGH);
pinMode(sync_pin, OUTPUT);
delayMicroseconds(usToWaitBeforeSyncOutput);
digitalWrite(sync_pin, LOW);
// WS2811 update begins immediately after falling edge of frame sync
convert_and_show();
}
} else if (startChar == '$') {
// receive a "master" frame - we send the frame sync to other boards
// we are controlling the video pace. The 16 bit number is how long
// after the prior frame sync to wait until showing this frame
unsigned int usecUntilFrameSync = 0;
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
if (count != 2) return;
count = Serial.readBytes((char *)imageBuffer, imageBufferSize);
if (count == imageBufferSize) {
digitalWrite(sync_pin, HIGH);
pinMode(sync_pin, OUTPUT);
uint32_t now, elapsed;
do {
now = micros();
elapsed = now - lastFrameSyncTime;
} while (elapsed < usecUntilFrameSync); // wait
lastFrameSyncTime = now;
digitalWrite(sync_pin, LOW);
// WS2811 update begins immediately after falling edge of frame sync
convert_and_show();
}
} else if (startChar == '%') {
// receive a "slave" frame - wait to show it until the frame sync arrives
pinMode(sync_pin, INPUT_PULLUP);
unsigned int unusedField = 0;
int count = Serial.readBytes((char *)&unusedField, 2);
if (count != 2) return;
count = Serial.readBytes((char *)imageBuffer, imageBufferSize);
if (count == imageBufferSize) {
uint32_t startTime = millis();
while (digitalRead(sync_pin) != HIGH && (millis() - startTime) < 30) ; // wait for sync high
while (digitalRead(sync_pin) != LOW && (millis() - startTime) < 30) ; // wait for sync high->low
// WS2811 update begins immediately after falling edge of frame sync
if ((millis() - startTime) < 30) {
convert_and_show();
}
}
} else if (startChar == '@') {
// reset the elapsed frame time, for startup of '$' message playing
lastFrameSyncTime = micros();
} else if (startChar == '?') {
// when the video application asks, give it all our info
// for easy and automatic configuration
Serial.print(led_width);
Serial.write(',');
Serial.print(led_height);
Serial.write(',');
Serial.print(led_layout);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(video_xoffset);
Serial.write(',');
Serial.print(video_yoffset);
Serial.write(',');
Serial.print(video_width);
Serial.write(',');
Serial.print(video_height);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(0);
Serial.write(',');
Serial.print(0);
Serial.println();
} else if (startChar >= 0) {
// discard unknown characters
}
}
void convert_and_show() {
uint8_t *ptr = imageBuffer;
if (led_layout == 0) {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
int pixelIndex = y * led_width + x; // Always left-to-right
uint8_t r = *ptr++;
uint8_t g = *ptr++;
uint8_t b = *ptr++;
leds->setPixelColor(pixelIndex, r, g, b);
}
}
} else {
for (int y=0; y<led_height; y++) {
for (int x=0; x<led_width; x++) {
// Even rows are left-to-right, odd are right-to-left
int pixelIndex = (y & 1) ? (y + 1) * led_width - 1 - x : y * led_width + x;
uint8_t r = *ptr++;
uint8_t g = *ptr++;
uint8_t b = *ptr++;
leds->setPixelColor(pixelIndex, r, g, b);
}
}
}
leds->show();
}

View file

@ -0,0 +1,170 @@
/*
*** FIRST TIME HERE? START W/THE NEOPXL8 (not HDR) strandtest INSTEAD! ***
That code explains and helps troubshoot wiring and NeoPixel color format.
Adafruit_NeoPXL8HDR is a subclass of Adafruit_NeoPXL8 that provides
16-bits-per-channel color, temporal dithering, and gamma correction.
This is a minimal demonstration of "HDR" vs regular NeoPXL8 to show the
extra setup required. It runs an animation loop similar to the non-HDR
strandtest example, with the color fills on startup removed...those are
for troubleshooting wiring and color order...work through that in the
easier non-HDR strandtest and then copy your findings here. You'll
find MUCH more documentation in that example.
Most important thing to note here is that different chips (RP2040, SAMD51)
require very different startup code. Animation loop is identical to NeoPXL8,
just the setup changes. SAMD21 is NOT supported; HDR is just too demanding.
SAMD51 requires the Adafruit_ZeroTimer library.
*/
#include <Adafruit_NeoPXL8.h>
// CHANGE these to match your strandtest findings or this WILL NOT WORK:
int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
#define COLOR_ORDER NEO_GRB // NeoPixel color format (see Adafruit_NeoPixel)
#define NUM_LEDS 60 // NeoPixels PER STRAND, total number is 8X this!
// The arguments to the Adafruit_NeoPXL8HDR constructor are
// identical to Adafruit_NeoPXL8, just the name changes, adding "HDR":
Adafruit_NeoPXL8HDR leds(NUM_LEDS, pins, COLOR_ORDER);
// SETUP (VERY DIFFERENT from "regular" NeoPXL8) ---------------------------
#if defined(ARDUINO_ARCH_RP2040)
// On RP2040, the refresh() function is called in a tight loop on the
// second core (via the loop1() function). The first core is then 100%
// free for animation logic in the regular loop() function.
void loop1() {
leds.refresh();
}
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// On ESP32S3, refresh() is called in a tight loop on core 0. The other
// core (#1), where Arduino code normally runs, is then 100% free for
// animation logic in the loop() function.
void loop0(void *param) {
for(;;) {
yield();
leds.refresh();
}
}
#else // SAMD
// SAMD, being single-core, uses a timer interrupt for refresh().
// As written, this uses Timer/Counter 3, but with some minor edits
// you can change that if it interferes with other code.
#include "Adafruit_ZeroTimer.h"
Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3);
void TC3_Handler() {
Adafruit_ZeroTimer::timerHandler(3);
}
void timerCallback(void) {
leds.refresh();
}
#endif // end SAMD
void setup() {
// In Adafruit_NeoPXL8, begin() normally doesn't take arguments. The HDR
// version has some extra flags to select frame blending, added "depth"
// for temporal dithering, and whether to double-buffer DMA pixel writes:
if (!leds.begin(true, 4, true)) {
// If begin() returns false, either an invalid pin list was provided,
// or requested too many pixels for available memory (HDR requires
// INORDINATE RAM). Blink onboard LED to indicate startup problem.
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
// NeoPXL8HDR can provide automatic gamma-correction, but it's disabled
// by default; pixel values correlate to linear duty cycle. This is so
// existing NeoPixel or NeoPXL8 code brought over to NeoPXL8HDR looks the
// same, no surprises. When reworking a project for HDR, it's best to
// enable, animation code then doesn't need to process this. Gamma curve
// (2.6 in this example) is set at the same time as global brightness.
// Notice the brightness uses a 16-bit value (0-65535), not 8-bit
// (0-255) as in NeoPixel and NeoPXL8:
leds.setBrightness(65535 / 8, 2.6); // 1/8 max duty cycle, 2.6 gamma factor
#if defined(CONFIG_IDF_TARGET_ESP32S3)
// Run loop0() on core 0 (Arduino uses core 1) w/16K stack
(void)xTaskCreatePinnedToCore(loop0, "NeoPXL8HDR", 16384, NULL, 0, NULL, 0);
#elif defined(__SAMD51__) || defined(_SAMD21_)
// Setup diverges again for SAMD51 only, to activate the timer code shown
// earlier. The timer is currently configured for a 120 Hz refresh. This
// saps a fair percentage of time from the main thread, despite all the
// DMA going on, which is why RP2040 is most recommended for NeoPXL8HDR.
zerotimer.enable(false);
zerotimer.configure(TC_CLOCK_PRESCALER_DIV16, // 3 MHz clock
TC_COUNTER_SIZE_16BIT, // 16-bit counter
TC_WAVE_GENERATION_MATCH_PWM); // Freq or PWM mode
zerotimer.setCompare(0, 3000000 / 120); // 120 Hz refresh
zerotimer.setCallback(true, TC_CALLBACK_CC_CHANNEL0, timerCallback);
zerotimer.enable(true);
#endif // end SAMD
// The startup colors are REMOVED in this example. It's assumed at
// this point (via non-HDR strandtest) that your code and hardware
// are confirmed in sync, making these tests redundant.
}
// ANIMATION LOOP (similar to "regular" NeoPXL8) ---------------------------
// The loop() function is identical to the non-HDR example. Although
// NeoPXL8HDR can handle 16-bits-per-channel colors, we're using 8-bit
// here (packed into a 32-bit value) to show how existing NeoPixel or
// NeoPXL8 code can carry over directly. The library will expand these
// to 16 bits behind the scenes.
void loop() {
uint32_t now = millis(); // Get time once at start of each frame
for(uint8_t r=0; r<8; r++) { // For each row...
for(int p=0; p<NUM_LEDS; p++) { // For each pixel of row...
leds.setPixelColor(r * NUM_LEDS + p, rain(now, r, p));
}
}
leds.show();
}
// Pixel strand colors, same code as non-HDR strandtest:
static uint8_t colors[8][3] = {
255, 0, 0, // Row 0: Red
255, 160, 0, // Row 1: Orange
255, 255, 0, // Row 2: Yellow
0, 255, 0, // Row 3: Green
0, 255, 255, // Row 4: Cyan
0, 0, 255, // Row 5: Blue
192, 0, 255, // Row 6: Purple
255, 0, 255 // Row 7: Magenta
};
// This function changes very slightly from the non-HDR strandtest example.
// That code had to apply gamma correction on is own. It's not necessary
// with the HDR class once configured (see setBrightness() in setup()),
// we get that "free" now.
uint32_t rain(uint32_t now, uint8_t row, int pixelNum) {
uint8_t frame = now / 4; // uint8_t rolls over for a 0-255 range
uint16_t b = 256 - ((frame - row * 32 + pixelNum * 256 / NUM_LEDS) & 0xFF);
return leds.Color((colors[row][0] * b) >> 8,
(colors[row][1] * b) >> 8,
(colors[row][2] * b) >> 8);
}

View file

@ -1,90 +0,0 @@
// Example/diagnostic for the Adafruit_NeoPXL8 library. Each of 8 strands
// is a distinct color, helps identify which pin goes to which strand.
// For more complete usage of NeoPixel operations, see the examples
// included with the 'regular' Adafruit_NeoPixel library.
// Also requires LATEST Adafruit_NeoPixel, Adafruit_ZeroDMA and
// Adafruit_ASFcore libraries.
// May require a logic level shifter (e.g. 75HCT245) for 5V pixels,
// or use NeoPXL8 Featherwing for Adafruit Feather M0 boards.
#include <Adafruit_NeoPXL8.h>
#define NUM_LED 64 // Per strand. Total number of pixels is 8X this!
// Second argument to constructor is an optional 8-byte pin list,
// or pass NULL to use pins 0-7 on Metro Express, Arduino Zero, etc.
Adafruit_NeoPXL8 leds(NUM_LED, NULL, NEO_GRB);
// Here's a pinout that works with the Feather M0. These are the
// default connections for the 2x8 header on the NeoPXL8 Featherwing,
// which is 1:1 compatible with Fadecandy cabling:
//int8_t pins[8] = { PIN_SERIAL1_RX, PIN_SERIAL1_TX, MISO, 13, 5, SDA, A4, A3 };
//Adafruit_NeoPXL8 leds(NUM_LED, pins, NEO_GRB);
// If using the Featherwing RJ45 connections, the pin order can be
// reversed if 1:1 compatibility with OctoWS2811 cabling is desired:
// int8_t pins[8] = { A3, A4, SDA, 5, 13, MISO, PIN_SERIAL1_TX, PIN_SERIAL1_RX };
// 5 pins on the Featherwing have reconfigurable jumpers, in case the
// default pin connections interfere with a needed peripheral (Serial1,
// I2C or SPI). You do NOT need to use all 5 alternates; you can pick
// and choose as needed! But if changing all 5, they would be:
//int8_t pins[8] = { 12, 10, 11, 13, SCK, MOSI, A4, A3 };
// And again, reverse the order for OctoWS2811-compatible cabling:
// int8_t pins[8] = { A3, A4, MOSI, SCK, 13, 11, 10, 12 };
// Here's a pinout that works on the Metro M4:
//int8_t pins[8] = { 7, 4, 5, 6, 3, 2, 10, 11 };
// Alternate pins on Metro M4:
//int8_t pins[8] = { 9, 8, 0, 1, 13, 12, -1, SCK };
// MOSI *should* work for bit 6, but does not. Datasheet error?
void setup() {
leds.begin();
leds.setBrightness(32);
}
uint8_t frame = 0;
void loop() {
for(uint8_t r=0; r<8; r++) { // For each row...
for(int p=0; p<NUM_LED; p++) { // For each pixel of row...
leds.setPixelColor(r * NUM_LED + p, rain(r, p));
}
}
leds.show();
frame++;
}
uint8_t colors[8][3] = { // RGB colors for the 8 rows...
255, 0, 0, // Row 0: Red
255, 160, 0, // Row 1: Orange
255, 255, 0, // Row 2: Yellow
0, 255, 0, // Row 3: Green
0, 255, 255, // Row 4: Cyan
0, 0, 255, // Row 5: Blue
192, 0, 255, // Row 6: Purple
255, 0, 255 // Row 7: Magenta
};
// Gamma-correction table improves the appearance of midrange colors
#define _GAMMA_ 2.6
const int _GBASE_ = __COUNTER__ + 1; // Index of 1st __COUNTER__ ref below
#define _G1_ (uint8_t)(pow((__COUNTER__ - _GBASE_) / 255.0, _GAMMA_) * 255.0 + 0.5),
#define _G2_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ // Expands to 8 lines
#define _G3_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ // Expands to 64 lines
const uint8_t gamma8[] = { _G3_ _G3_ _G3_ _G3_ }; // 256 lines
// Given row number (0-7) and pixel number along row (0 - (NUM_LED-1)),
// first calculate brightness (b) of pixel, then multiply row color by
// this and run it through gamma-correction table.
uint32_t rain(uint8_t row, int pixelNum) {
uint16_t b = 256 - ((frame - row * 32 + pixelNum * 256 / NUM_LED) & 0xFF);
return ((uint32_t)gamma8[(colors[row][0] * b) >> 8] << 16) |
((uint32_t)gamma8[(colors[row][1] * b) >> 8] << 8) |
gamma8[(colors[row][2] * b) >> 8];
}

View file

@ -0,0 +1,169 @@
// This example is minimally adapted from one in PJRC's OctoWS2811 Library.
// Changes primarily focus on data format: this version always writes in
// RGB order with no gamma correction, rows always left-to-right; these
// operations now happen on the microcontroller side with the corresponding
// Arduino sketch (VideoMSC); the colorWiring() function and gamma[] table
// are removed. To work with current version of Processing, new Movie() had
// to move into setup(), and end-of-video detection changed.
// Mentions of "SD card" throughout are left intact here, though the
// corresponding playback code actually uses onboard flash storage, not SD.
// THIS IS CODE FOR PROCESSING (processing.org), NOT ARDUINO.
// Processing is oddly persnickety and you might need to run this twice
// to convert a full video rather than just the first frame.
/* OctoWS2811 movie2sdcard.pde - Convert video for SD card playing, with
Teensy 3.1 running OctoWS2811 VideoSDcard.ino
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2014 Paul Stoffregen, PJRC.COM, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// To configure this program, edit the following:
//
// 1: Change myMovie to open a video file of your choice ;-)
// Also change the output file name.
//
// 2: Edit ledWidth, ledHeight for your LEDs
//
// 3: Edit framerate. This configures the speed VideoSDcard
// will play your video data. It's also critical for merging
// audio to be played by Teensy 3.1's digital to analog output.
//
import processing.video.*;
import processing.serial.*;
import java.io.*;
Movie myMovie;
int ledWidth = 30; // size of LED panel
int ledHeight = 16;
double framerate = 23.98; // You MUST set this to the movie's frame rate
// Processing does not seem to have a way to detect it.
FileOutputStream myFile; // edit output filename below...
PImage ledImage;
long elapsed_picoseconds=0L;
long elapsed_microseconds=0L;
long picoseconds_per_frame = (long)(1e12 / framerate + 0.5);
boolean fileopen=true;
PImage tmpImage;
void setup() {
myMovie = new Movie(this, "/Users/me/Desktop/mymovie.mp4");
try {
myFile = new FileOutputStream("/Users/me/Desktop/mymovie.bin");
} catch (Exception e) {
exit();
}
ledImage = createImage(ledWidth, ledHeight, RGB);
size(720, 560); // create the window
myMovie.play(); // start the movie :-)
}
int lastEventTime = 0;
// movieEvent runs for each new frame of movie data
void movieEvent(Movie m) {
// read the movie's next frame
m.read();
lastEventTime = millis();
elapsed_picoseconds += picoseconds_per_frame;
int usec = (int)((elapsed_picoseconds / 1000000L) - elapsed_microseconds);
elapsed_microseconds += (long)usec;
println("usec = " + usec);
// copy the movie's image to the LED image
// Processing is acting strange and won't antialias a copy-with-scale
// from a Movie, so make an interim full-size copy, resize that, then
// copy the result to ledImage (referenced here and in draw()).
tmpImage = createImage(m.width, m.height, RGB);
tmpImage.copy(m, 0, 0, m.width, m.height, 0, 0, m.width, m.height);
tmpImage.resize(ledWidth, ledHeight);
ledImage.copy(tmpImage, 0, 0, ledWidth, ledHeight, 0, 0, ledWidth, ledHeight);
// convert the LED image to raw data
byte[] ledData = new byte[(ledWidth * ledHeight * 3) + 5];
image2data(ledImage, ledData);
ledData[0] = '*'; // first Teensy is the frame sync master
ledData[1] = (byte)(ledWidth * ledHeight);
ledData[2] = (byte)((ledWidth * ledHeight) >> 8);
ledData[3] = (byte)(usec); // request the frame sync pulse
ledData[4] = (byte)(usec >> 8); // at 75% of the frame time
// send the raw data to the LEDs :-)
//ledSerial[i].write(ledData);
try {
myFile.write(ledData);
} catch (Exception e) {
exit();
}
}
// image2data converts an image to OctoWS2811's raw data format.
// The number of vertical pixels in the image must be a multiple
// of 8. The data array must be the proper size for the image.
void image2data(PImage image, byte[] data) {
int offset = 5;
int i;
for (i = 0; i < image.width * image.height; i++) {
int pixel = image.pixels[i];
data[offset++] = byte(pixel >> 16);
data[offset++] = byte(pixel >> 8);
data[offset++] = byte(pixel);
}
}
// draw runs every time the screen is redrawn - show the movie...
void draw() {
// If nothing read from movie in last second, assume end, close output
if ((millis() - lastEventTime) > 1000) {
if (fileopen) {
println("movie stop, closing output file");
try {
myFile.close();
} catch (Exception e) {
}
exit();
}
} else {
image(myMovie, 0, 80);
image(ledImage, 240 - ledWidth / 2, 10);
}
}
// respond to mouse clicks as pause/play
boolean isPlaying = true;
void mousePressed() {
if (isPlaying) {
myMovie.pause();
isPlaying = false;
} else {
myMovie.play();
isPlaying = true;
}
}

View file

@ -0,0 +1,225 @@
// This example is minimally adapted from one in PJRC's OctoWS2811 Library.
// Changes primarily focus on data format: this version always issues in
// RGB order with no gamma correction; these operations now happen on the
// microcontroller side with the corresponding Arduino sketch (VideoDisplay);
// the colorWiring() function and gamma[] table are removed.
// THIS IS CODE FOR PROCESSING (processing.org), NOT ARDUINO.
/* OctoWS2811 movie2serial.pde - Transmit video data to 1 or more
Teensy 3.0 boards running OctoWS2811 VideoDisplay.ino
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2018 Paul Stoffregen, PJRC.COM, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Linux systems (including Raspberry Pi) require 49-teensy.rules in
// /etc/udev/rules.d/, and gstreamer compatible with Processing's
// video library.
// To configure this program, edit the following sections:
//
// 1: change myMovie to open a video file of your choice ;-)
//
// 2: edit the serialConfigure() lines in setup() for your
// serial device names (Mac, Linux) or COM ports (Windows)
//
// 3: if playing 50 or 60 Hz progressive video (or faster),
// edit framerate in movieEvent().
import processing.video.*;
import processing.serial.*;
import java.awt.Rectangle;
Movie myMovie;
int numPorts=0; // the number of serial ports in use
int maxPorts=24; // maximum number of serial ports
Serial[] ledSerial = new Serial[maxPorts]; // each port's actual Serial port
Rectangle[] ledArea = new Rectangle[maxPorts]; // the area of the movie each port gets, in % (0-100)
boolean[] ledLayout = new boolean[maxPorts]; // layout of rows, true = even is left->right
PImage[] ledImage = new PImage[maxPorts]; // image sent to each port
int errorCount=0;
float framerate=0;
void settings() {
size(480, 400); // create the window
}
void setup() {
String[] list = Serial.list();
delay(20);
println("Serial Ports List:");
println(list);
serialConfigure("/dev/tty.usbmodem148201"); // change this to your port name
// can add extra serialConfigure() ports here if needed
if (errorCount > 0) exit();
myMovie = new Movie(this, "/tmp/BigBuckBunny.mp4");
myMovie.loop(); // start the movie :-)
}
// movieEvent runs for each new frame of movie data
void movieEvent(Movie m) {
println("movieEvent");
// read the movie's next frame
m.read();
//if (framerate == 0) framerate = m.getSourceFrameRate();
framerate = 30.0; // TODO, how to read the frame rate???
for (int i=0; i < numPorts; i++) {
// copy a portion of the movie's image to the LED image
int xoffset = percentage(m.width, ledArea[i].x);
int yoffset = percentage(m.height, ledArea[i].y);
int xwidth = percentage(m.width, ledArea[i].width);
int yheight = percentage(m.height, ledArea[i].height);
ledImage[i].copy(m, xoffset, yoffset, xwidth, yheight,
0, 0, ledImage[i].width, ledImage[i].height);
// convert the LED image to raw data
byte[] ledData = new byte[(ledImage[i].width * ledImage[i].height * 3) + 3];
image2data(ledImage[i], ledData, ledLayout[i]);
if (i == 0) {
ledData[0] = '*'; // first Teensy is the frame sync master
int usec = (int)((1000000.0 / framerate) * 0.75);
ledData[1] = (byte)(usec); // request the frame sync pulse
ledData[2] = (byte)(usec >> 8); // at 75% of the frame time
} else {
ledData[0] = '%'; // others sync to the master board
ledData[1] = 0;
ledData[2] = 0;
}
// send the raw data to the LEDs :-)
ledSerial[i].write(ledData);
}
}
// image2data converts an image to OctoWS2811's raw data format.
// The number of vertical pixels in the image must be a multiple
// of 8. The data array must be the proper size for the image.
void image2data(PImage image, byte[] data, boolean layout) {
int offset = 3;
int i;
for (i = 0; i < image.width * image.height; i++) {
int pixel = image.pixels[i];
data[offset++] = byte(pixel >> 16);
data[offset++] = byte(pixel >> 8);
data[offset++] = byte(pixel);
}
}
// ask a Teensy board for its LED configuration, and set up the info for it.
void serialConfigure(String portName) {
if (numPorts >= maxPorts) {
println("too many serial ports, please increase maxPorts");
errorCount++;
return;
}
try {
ledSerial[numPorts] = new Serial(this, portName);
if (ledSerial[numPorts] == null) throw new NullPointerException();
ledSerial[numPorts].write('?');
} catch (Throwable e) {
println("Serial port " + portName + " does not exist or is non-functional");
errorCount++;
return;
}
delay(50);
String line = ledSerial[numPorts].readStringUntil(10);
if (line == null) {
println("Serial port " + portName + " is not responding.");
println("Is it really a Teensy 3.0 running VideoDisplay?");
errorCount++;
return;
}
String param[] = line.split(",");
if (param.length != 12) {
println("Error: port " + portName + " did not respond to LED config query");
errorCount++;
return;
}
// only store the info and increase numPorts if Teensy responds properly
ledImage[numPorts] = new PImage(Integer.parseInt(param[0]), Integer.parseInt(param[1]), RGB);
ledArea[numPorts] = new Rectangle(Integer.parseInt(param[5]), Integer.parseInt(param[6]),
Integer.parseInt(param[7]), Integer.parseInt(param[8]));
ledLayout[numPorts] = (Integer.parseInt(param[5]) == 0);
numPorts++;
}
// draw runs every time the screen is redrawn - show the movie...
void draw() {
//println("draw");
// show the original video
image(myMovie, 0, 80);
// then try to show what was most recently sent to the LEDs
// by displaying all the images for each port.
for (int i=0; i < numPorts; i++) {
// compute the intended size of the entire LED array
int xsize = percentageInverse(ledImage[i].width, ledArea[i].width);
int ysize = percentageInverse(ledImage[i].height, ledArea[i].height);
// computer this image's position within it
int xloc = percentage(xsize, ledArea[i].x);
int yloc = percentage(ysize, ledArea[i].y);
// show what should appear on the LEDs
image(ledImage[i], 240 - xsize / 2 + xloc, 10 + yloc);
}
}
// respond to mouse clicks as pause/play
boolean isPlaying = true;
void mousePressed() {
if (isPlaying) {
myMovie.pause();
isPlaying = false;
} else {
myMovie.play();
isPlaying = true;
}
}
// scale a number by a percentage, from 0 to 100
int percentage(int num, int percent) {
double mult = percentageFloat(percent);
double output = num * mult;
return (int)output;
}
// scale a number by the inverse of a percentage, from 0 to 100
int percentageInverse(int num, int percent) {
double div = percentageFloat(percent);
double output = num / div;
return (int)output;
}
// convert an integer from 0 to 100 to a float percentage
// from 0.0 to 1.0. Special cases for 1/3, 1/6, 1/7, etc
// are handled automatically to fix integer rounding.
double percentageFloat(int percent) {
if (percent == 33) return 1.0 / 3.0;
if (percent == 17) return 1.0 / 6.0;
if (percent == 14) return 1.0 / 7.0;
if (percent == 13) return 1.0 / 8.0;
if (percent == 11) return 1.0 / 9.0;
if (percent == 9) return 1.0 / 11.0;
if (percent == 8) return 1.0 / 12.0;
return (double)percent / 100.0;
}

10
library.properties Normal file
View file

@ -0,0 +1,10 @@
name=Adafruit NeoPXL8
version=1.4.0
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=Arduino library for controlling 8 NeoPixel LED strips using DMA on ATSAMD21, ATSAMD51, RP2040 and ESP32S3
paragraph=Arduino library for controlling 8 NeoPixel LED strips using DMA on ATSAMD21, ATSAMD51, RP2040 and ESP32S3
category=Display
url=https://github.com/adafruit/Adafruit_NeoPXL8
architectures=samd, rp2040, esp32
depends=Adafruit NeoPixel, Adafruit Zero DMA Library, Adafruit ZeroTimer Library, SdFat - Adafruit Fork, Adafruit SPIFlash, Adafruit TinyUSB Library, ArduinoJson, Adafruit InternalFlash, FlashStorage, Adafruit CPFS