Compare commits
89 commits
experiment
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4447258dcf | ||
|
|
fc96155cf3 | ||
|
|
452d910680 | ||
|
|
41b8c1bd3b | ||
|
|
77c12cf76a | ||
|
|
36d3bea700 | ||
|
|
07378a4d98 | ||
|
|
8cf8ec9d51 | ||
| 377a497540 | |||
|
|
cb3253f2ec | ||
| b75683cf4c | |||
| 6d158f300b | |||
| e8251ad309 | |||
|
|
5f09a9b8ba | ||
|
|
b01b27dcab | ||
|
|
87bb882f59 | ||
|
|
0874ce8775 | ||
|
|
d4ff50edf6 | ||
|
|
149f6c17b0 | ||
|
|
4ffe3d3d9e | ||
|
|
7043878f7c | ||
|
|
03f5921118 | ||
|
|
5ab8aa4c37 | ||
|
|
86997e31cc | ||
|
|
46b8ece698 | ||
|
|
c1354e07fb | ||
|
|
b14e8643ee | ||
|
|
a962118778 | ||
|
|
009f6f47f6 | ||
|
|
aa44008f32 | ||
|
|
bf5af30c38 | ||
|
|
65dd928e1c | ||
|
|
e2855c6c1b | ||
|
|
5d552dea80 | ||
|
|
c1cb252440 | ||
|
|
82f6496563 | ||
|
|
0b2ff496c4 | ||
|
|
832bbebb1b | ||
|
|
2376a11b8a | ||
|
|
2fa9dca1ee | ||
|
|
f4ea2c4aea | ||
|
|
ef216d4bb7 | ||
|
|
ff4e72910e | ||
|
|
889c4c88ef | ||
|
|
969dcf9a21 | ||
|
|
1ab2557557 | ||
|
|
72b82f3da7 | ||
|
|
c24c1325b6 | ||
|
|
7ecc003814 | ||
|
|
be1db47829 | ||
|
|
ef83c28f45 | ||
|
|
30eb6ca6b1 | ||
|
|
ba4d8eb035 | ||
|
|
41345938be | ||
|
|
d9e5679414 | ||
|
|
80ed7127fc | ||
|
|
b85e20e143 | ||
|
|
dfefbf6b26 | ||
|
|
4ef03e5e11 | ||
|
|
0014905b9b | ||
|
|
92565dfdb4 | ||
|
|
3056009e6e | ||
|
|
b2e4d53746 | ||
|
|
c65af944e0 | ||
|
|
15050e16f0 | ||
|
|
66ca593d98 | ||
|
|
f033fc0839 | ||
|
|
6813cddc06 | ||
|
|
d6a7f21643 | ||
|
|
c90cd05442 | ||
|
|
aec60fe413 | ||
|
|
b2fc1008ed | ||
|
|
9e5afceba4 | ||
|
|
a8799d2888 | ||
|
|
2e254b24d2 | ||
|
|
7732f2ddf5 | ||
|
|
f297e540a9 | ||
|
|
ac943b71e6 | ||
|
|
18e6fc5e6a | ||
|
|
ef6338aa08 | ||
|
|
9b96a47750 | ||
|
|
58b622cd9e | ||
|
|
6fc1788fc2 | ||
|
|
6998ae28d6 | ||
|
|
1b3198e492 | ||
|
|
3413e604fc | ||
|
|
ea5ae002fa | ||
|
|
f110c40db7 | ||
|
|
4c028e959a |
31 changed files with 4942 additions and 346 deletions
46
.github/ISSUE_TEMPLATE.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE.md
vendored
Normal 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
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
32
.github/workflows/githubci.yml
vendored
Normal 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
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Our handy .gitignore for automation ease
|
||||
Doxyfile*
|
||||
doxygen_sqlite3.db
|
||||
html
|
||||
1414
Adafruit_NeoPXL8.cpp
1414
Adafruit_NeoPXL8.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -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_
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -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.
|
||||
|
|
|
|||
128
examples/NeoPXL8/Fire/Fire.ino
Normal file
128
examples/NeoPXL8/Fire/Fire.ino
Normal 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();
|
||||
}
|
||||
33
examples/NeoPXL8/Fire/FireColor.cpp
Normal file
33
examples/NeoPXL8/Fire/FireColor.cpp
Normal 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)};
|
||||
82
examples/NeoPXL8/PlasmaAnimation/PlasmaAnimation.ino
Normal file
82
examples/NeoPXL8/PlasmaAnimation/PlasmaAnimation.ino
Normal 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)));
|
||||
}
|
||||
0
examples/NeoPXL8/VideoMSC/.esp32.test.skip
Normal file
0
examples/NeoPXL8/VideoMSC/.esp32.test.skip
Normal file
0
examples/NeoPXL8/VideoMSC/.feather_esp32s3.test.skip
Normal file
0
examples/NeoPXL8/VideoMSC/.feather_esp32s3.test.skip
Normal file
367
examples/NeoPXL8/VideoMSC/VideoMSC.ino
Normal file
367
examples/NeoPXL8/VideoMSC/VideoMSC.ino
Normal 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();
|
||||
}
|
||||
0
examples/NeoPXL8/VideoSerial/.esp32.test.skip
Normal file
0
examples/NeoPXL8/VideoSerial/.esp32.test.skip
Normal file
0
examples/NeoPXL8/VideoSerial/.feather_esp32s3.test.skip
Normal file
0
examples/NeoPXL8/VideoSerial/.feather_esp32s3.test.skip
Normal file
392
examples/NeoPXL8/VideoSerial/VideoSerial.ino
Normal file
392
examples/NeoPXL8/VideoSerial/VideoSerial.ino
Normal 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();
|
||||
}
|
||||
215
examples/NeoPXL8/strandtest/strandtest.ino
Normal file
215
examples/NeoPXL8/strandtest/strandtest.ino
Normal 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 library’s
|
||||
// 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));
|
||||
}
|
||||
199
examples/NeoPXL8HDR/Fire/Fire.ino
Normal file
199
examples/NeoPXL8HDR/Fire/Fire.ino
Normal 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();
|
||||
}
|
||||
33
examples/NeoPXL8HDR/Fire/FireColor.cpp
Normal file
33
examples/NeoPXL8HDR/Fire/FireColor.cpp
Normal 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)};
|
||||
154
examples/NeoPXL8HDR/PlasmaAnimation/PlasmaAnimation.ino
Normal file
154
examples/NeoPXL8HDR/PlasmaAnimation/PlasmaAnimation.ino
Normal 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));
|
||||
}
|
||||
0
examples/NeoPXL8HDR/VideoMSC/.esp32.test.skip
Normal file
0
examples/NeoPXL8HDR/VideoMSC/.esp32.test.skip
Normal file
0
examples/NeoPXL8HDR/VideoMSC/.feather_esp32s3.test.skip
Normal file
0
examples/NeoPXL8HDR/VideoMSC/.feather_esp32s3.test.skip
Normal file
445
examples/NeoPXL8HDR/VideoMSC/VideoMSC.ino
Normal file
445
examples/NeoPXL8HDR/VideoMSC/VideoMSC.ino
Normal 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();
|
||||
}
|
||||
0
examples/NeoPXL8HDR/VideoSerial/.esp32.test.skip
Normal file
0
examples/NeoPXL8HDR/VideoSerial/.esp32.test.skip
Normal file
461
examples/NeoPXL8HDR/VideoSerial/VideoSerial.ino
Normal file
461
examples/NeoPXL8HDR/VideoSerial/VideoSerial.ino
Normal 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();
|
||||
}
|
||||
170
examples/NeoPXL8HDR/strandtest/strandtest.ino
Normal file
170
examples/NeoPXL8HDR/strandtest/strandtest.ino
Normal 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);
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
||||
169
extras/Processing/movie2msc/movie2msc.pde
Normal file
169
extras/Processing/movie2msc/movie2msc.pde
Normal 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;
|
||||
}
|
||||
}
|
||||
225
extras/Processing/movie2serial/movie2serial.pde
Normal file
225
extras/Processing/movie2serial/movie2serial.pde
Normal 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
10
library.properties
Normal 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
|
||||
Loading…
Reference in a new issue