Compare commits
56 commits
| 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 |
16 changed files with 978 additions and 623 deletions
8
.github/workflows/githubci.yml
vendored
8
.github/workflows/githubci.yml
vendored
|
|
@ -7,11 +7,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-python@v1
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: adafruit/ci-arduino
|
||||
path: ci
|
||||
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
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
|
||||
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 .
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
* @file Adafruit_NeoPXL8.cpp
|
||||
*
|
||||
* @mainpage 8-way concurrent DMA NeoPixel library for SAMD21, SAMD51,
|
||||
* RP2040 and ESP32S3 microcontrollers.
|
||||
* RP2040, RP235x, and ESP32S3 microcontrollers.
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
*
|
||||
* Adafruit_NeoPXL8 is an Arduino library that leverages hardware features
|
||||
* unique to some Atmel SAMD21 and SAMD51 microcontrollers, plus the
|
||||
* Raspberry Pi RP2040 and Espressif ESP32S3 (not S2, etc.) chips, to
|
||||
* Raspberry Pi RP2040, RP235x, and Espressif ESP32S3 (not S2, etc.) chips, to
|
||||
* communicate with large numbers of NeoPixels with very low CPU utilization
|
||||
* and without losing track of time. It was originally designed for the
|
||||
* Adafruit Feather M0 board with NeoPXL8 FeatherWing interface/adapter,
|
||||
|
|
@ -41,9 +41,9 @@
|
|||
* gamma correction. This requires inordinate RAM, and the frequent need
|
||||
* for refreshing makes it best suited for multi-core chips (e.g. RP2040).
|
||||
*
|
||||
* RP2040 support requires Philhower core (not Arduino mbed core).
|
||||
* Also on RP2040, pin numbers passed to constructor are GP## indices,
|
||||
* not necessarily the digital pin numbers silkscreened on the board.
|
||||
* RP2040 and RP235x support requires Philhower core (not Arduino mbed core).
|
||||
* Also on RP2040 and RP235x, pin numbers passed to constructor are GP##
|
||||
* indices, not necessarily the digital pin numbers silkscreened on the board.
|
||||
*
|
||||
* 0/1 bit timing does not precisely match NeoPixel/WS2812/SK6812 datasheet
|
||||
* specs, but it seems to work well enough. Use at your own peril.
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
// NeoPixel spec)...usually only affects the 1st pixel, subsequent pixels OK
|
||||
// due to signal reshaping through the 1st.
|
||||
|
||||
static const int8_t defaultPins[] = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
static const int8_t defaultPins[] = NEOPXL8_DEFAULT_PINS;
|
||||
static volatile bool sending = 0; // Set while DMA transfer is active
|
||||
static volatile uint32_t lastBitTime; // micros() when last bit issued
|
||||
|
||||
|
|
@ -109,6 +109,9 @@ Adafruit_NeoPXL8::Adafruit_NeoPXL8(uint16_t n, int8_t *p, neoPixelType t)
|
|||
static Adafruit_NeoPXL8 *neopxl8_ptr = NULL;
|
||||
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
// note that ARDUINO_ARCH_RP2040 blocks also apply to RP235x
|
||||
|
||||
#define DMA_IRQ_N 1 ///< Can be 0 or 1, no functional difference, 1 looks cool
|
||||
|
||||
// PIO code. As currently written, uses 2/9 and 5/9 duty cycle for '0' and
|
||||
// '1' bits respectively. This does not match the datasheet, but works well
|
||||
|
|
@ -132,13 +135,15 @@ static const struct pio_program neopxl8_program = {
|
|||
};
|
||||
|
||||
// Called at end of DMA transfer. Clears 'sending' flag and notes start of
|
||||
// NeoPixel latch time. Done as a callback (from the IRQ below) because
|
||||
// NeoPixel latch time. Done as a callback (from the IRQ below) because it
|
||||
// needs access to a protected NeoPXL8 member (dma_channel).
|
||||
void Adafruit_NeoPXL8::dma_callback() {
|
||||
dma_hw->ints0 = 1u << dma_channel; // Clear IRQ
|
||||
if (dma_irqn_get_channel_status(DMA_IRQ_N, dma_channel)) {
|
||||
dma_irqn_acknowledge_channel(DMA_IRQ_N, dma_channel); // Clear IRQ
|
||||
lastBitTime = micros();
|
||||
sending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void dma_finish_irq(void) {
|
||||
if (neopxl8_ptr) {
|
||||
|
|
@ -269,13 +274,18 @@ static void dmaCallback(Adafruit_ZeroDMA *dma) {
|
|||
|
||||
Adafruit_NeoPXL8::~Adafruit_NeoPXL8() {
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
pio_sm_set_enabled(pio, sm, false);
|
||||
pio_remove_program(pio, &neopxl8_program, offset);
|
||||
pio_sm_unclaim(pio, sm);
|
||||
dma_channel_abort(dma_channel);
|
||||
dma_channel_unclaim(dma_channel);
|
||||
if (dmaBuf[0])
|
||||
free(dmaBuf[0]);
|
||||
irq_remove_handler(DMA_IRQ_N == 0 ? DMA_IRQ_0 : DMA_IRQ_1, dma_finish_irq);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
gdma_reset(dma_chan);
|
||||
if (allocAddr)
|
||||
free(allocAddr);
|
||||
heap_caps_free(allocAddr);
|
||||
#else
|
||||
dma.abort();
|
||||
if (allocAddr)
|
||||
|
|
@ -284,11 +294,7 @@ Adafruit_NeoPXL8::~Adafruit_NeoPXL8() {
|
|||
neopxl8_ptr = NULL;
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
bool Adafruit_NeoPXL8::begin(bool dbuf, PIO pio_instance) {
|
||||
#else
|
||||
bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
||||
#endif
|
||||
Adafruit_NeoPixel::begin(); // Call base class begin() function 1st
|
||||
if (pixels) { // Successful malloc of NeoPixel buffer?
|
||||
uint8_t bytesPerPixel = (wOffset == rOffset) ? 3 : 4;
|
||||
|
|
@ -298,9 +304,6 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
neopxl8_ptr = this; // Save object pointer for interrupt
|
||||
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
|
||||
pio = pio_instance;
|
||||
|
||||
// Validate pins, must be within any 8 consecutive GPIO bits
|
||||
int16_t least_pin = 0x7FFF, most_pin = -1;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
|
|
@ -321,23 +324,20 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
// If no double buffering, point both to same space
|
||||
dmaBuf[1] = dbuf ? &dmaBuf[0][buf_size] : dmaBuf[0];
|
||||
|
||||
// Set up PIO outputs
|
||||
uint32_t pindir_mask = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (pins[i] >= 0) {
|
||||
pio_gpio_init(pio, pins[i]);
|
||||
pindir_mask = 1 << pins[i];
|
||||
bitmask[i] = 1 << (pins[i] - least_pin);
|
||||
}
|
||||
}
|
||||
// Func not working? Or using it wrong?
|
||||
// pio_sm_set_pindirs_with_mask(pio, sm, pindir_mask, pindir_mask);
|
||||
// For now, set all 8 as outputs, even if in-betweens are skipped
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, least_pin, 8, true);
|
||||
|
||||
// Set up PIO code & clock
|
||||
uint offset = pio_add_program(pio, &neopxl8_program);
|
||||
sm = pio_claim_unused_sm(pio, true); // 0-3
|
||||
// Find a PIO with enough available space in its instruction memory
|
||||
pio = NULL;
|
||||
|
||||
if (!pio_claim_free_sm_and_add_program_for_gpio_range(
|
||||
&neopxl8_program, &pio, &sm, &offset, least_pin, 8, true)) {
|
||||
pio = NULL;
|
||||
sm = -1;
|
||||
offset = 0;
|
||||
return false; // No PIO available
|
||||
}
|
||||
|
||||
// offset = pio_add_program(pio, &neopxl8_program);
|
||||
// sm = pio_claim_unused_sm(pio, true); // 0-3
|
||||
pio_sm_config conf = pio_get_default_sm_config();
|
||||
conf.pinctrl = 0; // SDK fails to set this
|
||||
sm_config_set_wrap(&conf, offset, offset + neopxl8_program.length - 1);
|
||||
|
|
@ -350,6 +350,21 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
pio_sm_init(pio, sm, offset, &conf);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
|
||||
// Set up PIO outputs
|
||||
uint32_t pindir_mask = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (pins[i] >= 0) {
|
||||
pio_gpio_init(pio, pins[i]);
|
||||
gpio_set_drive_strength(pins[i], GPIO_DRIVE_STRENGTH_2MA);
|
||||
pindir_mask = 1 << pins[i];
|
||||
bitmask[i] = 1 << (pins[i] - least_pin);
|
||||
}
|
||||
}
|
||||
// Func not working? Or using it wrong?
|
||||
// pio_sm_set_pindirs_with_mask(pio, sm, pindir_mask, pindir_mask);
|
||||
// For now, set all 8 as outputs, even if in-betweens are skipped
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, least_pin, 8, true);
|
||||
|
||||
// Set up DMA transfer
|
||||
dma_channel = dma_claim_unused_channel(false); // Don't panic
|
||||
|
||||
|
|
@ -364,9 +379,15 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
dmaBuf[dbuf_index], // src
|
||||
buf_size, false);
|
||||
// Set up end-of-DMA interrupt
|
||||
irq_set_exclusive_handler(DMA_IRQ_0, dma_finish_irq);
|
||||
irq_add_shared_handler(DMA_IRQ_N == 0 ? DMA_IRQ_0 : DMA_IRQ_1,
|
||||
dma_finish_irq,
|
||||
PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
||||
#if (DMA_IRQ_N == 0)
|
||||
dma_channel_set_irq0_enabled(dma_channel, true);
|
||||
irq_set_enabled(DMA_IRQ_0, true);
|
||||
#else
|
||||
dma_channel_set_irq1_enabled(dma_channel, true);
|
||||
#endif
|
||||
irq_set_enabled(DMA_IRQ_N == 0 ? DMA_IRQ_0 : DMA_IRQ_1, true);
|
||||
|
||||
return true; // Success!
|
||||
}
|
||||
|
|
@ -379,13 +400,14 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
uint32_t alloc_size =
|
||||
num_desc * sizeof(dma_descriptor_t) + (dbuf ? buf_size * 2 : buf_size);
|
||||
|
||||
if ((allocAddr = (uint8_t *)malloc(alloc_size))) {
|
||||
if ((allocAddr = (uint8_t *)heap_caps_malloc(
|
||||
alloc_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT))) {
|
||||
|
||||
// Find first 32-bit aligned address following descriptor list
|
||||
alignedAddr[0] =
|
||||
(uint32_t
|
||||
*)((uint32_t)(
|
||||
&allocAddr[num_desc * sizeof(dma_descriptor_t) + 3]) &
|
||||
*)((uint32_t)(&allocAddr[num_desc * sizeof(dma_descriptor_t) +
|
||||
3]) &
|
||||
~3);
|
||||
dmaBuf[0] = (uint8_t *)alignedAddr[0];
|
||||
|
||||
|
|
@ -488,7 +510,7 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
dbuf = false;
|
||||
|
||||
uint32_t buf_size = numLEDs * bytesPerPixel * 3 + EXTRASTARTBYTES + 3;
|
||||
uint32_t alloc_size = dbuf ? buf_size * 2 : buf_size;
|
||||
// uint32_t alloc_size = dbuf ? buf_size * 2 : buf_size;
|
||||
|
||||
if ((allocAddr = (uint8_t *)malloc(buf_size))) {
|
||||
int i;
|
||||
|
|
@ -507,7 +529,8 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
|
||||
if (dbuf) {
|
||||
alignedAddr[1] =
|
||||
(uint32_t *)((uint32_t)(&allocAddr[buf_size + EXTRASTARTBYTES + 3]) &
|
||||
(uint32_t
|
||||
*)((uint32_t)(&allocAddr[buf_size + EXTRASTARTBYTES + 3]) &
|
||||
~3);
|
||||
dmaBuf[1] = (uint8_t *)alignedAddr[1] - EXTRASTARTBYTES;
|
||||
memset(dmaBuf[1], 0, EXTRASTARTBYTES);
|
||||
|
|
@ -553,7 +576,8 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
|
|||
; // Wait for enable
|
||||
#else
|
||||
// Enable GCLK for TCC0
|
||||
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
|
||||
GCLK->CLKCTRL.reg =
|
||||
(uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
|
||||
GCLK_CLKCTRL_ID(GCM_TCC0_TCC1));
|
||||
while (GCLK->STATUS.bit.SYNCBUSY == 1)
|
||||
;
|
||||
|
|
@ -834,13 +858,7 @@ Adafruit_NeoPXL8HDR::~Adafruit_NeoPXL8HDR() {
|
|||
free(pixel_buf[0]);
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
bool Adafruit_NeoPXL8HDR::begin(bool blend, uint8_t bits, bool dbuf,
|
||||
PIO pio_instance) {
|
||||
#else
|
||||
bool Adafruit_NeoPXL8HDR::begin(bool blend, uint8_t bits, bool dbuf) {
|
||||
#endif
|
||||
|
||||
// If blend flag is set, allocate 3X pixel buffers, else 2X (for
|
||||
// temporal dithering only). Result is the buffer size in 16-bit
|
||||
// words (not bytes).
|
||||
|
|
@ -851,15 +869,12 @@ bool Adafruit_NeoPXL8HDR::begin(bool blend, uint8_t bits, bool dbuf) {
|
|||
if ((pixel_buf[0] = (uint16_t *)malloc(buf_size * sizeof(uint16_t)))) {
|
||||
if ((dither_table =
|
||||
(uint16_t *)malloc((1 << dither_bits) * sizeof(uint16_t)))) {
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
if (Adafruit_NeoPXL8::begin(dbuf, pio_instance)) {
|
||||
mutex_init(&mutex);
|
||||
#else
|
||||
if (Adafruit_NeoPXL8::begin(dbuf)) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
mutex_init(&mutex);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
mutex = xSemaphoreCreateMutex();
|
||||
#endif // end ESP32S3
|
||||
#endif // end !RP2040
|
||||
#endif // end ESP32S3/RP2040
|
||||
|
||||
// All allocations & initializations were successful.
|
||||
// Generate bit-flip table for ordered dithering...
|
||||
|
|
@ -1278,7 +1293,7 @@ DMA-capable peripherals is exploited for byte-wide concurrent output
|
|||
(specifically the TCC0 pattern generator, which is normally used for
|
||||
motor control or some such). Although SAMD51 does have PORT DMA, the
|
||||
pattern generator approach is used there regardless, so similar code
|
||||
can be used for both chips. On RP2040, PIO code is used.
|
||||
can be used for both chips. On RP2040 and RP235x, PIO code is used.
|
||||
|
||||
To issue 8 bits in parallel, all bytes of NeoPixel data must be "turned
|
||||
sideways" in RAM so all the bit 7's are issued concurrently, then all
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
/*!
|
||||
* @file Adafruit_NeoPXL8.h
|
||||
*
|
||||
* 8-way concurrent DMA NeoPixel library for SAMD21, SAMD51, RP2040, and
|
||||
* ESP32S3 microcontrollers.
|
||||
* 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
|
||||
|
|
@ -63,8 +63,8 @@ public:
|
|||
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, these are GP## numbers, not necessarily the digital
|
||||
pin numbers silkscreened on the board.
|
||||
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).
|
||||
|
|
@ -81,11 +81,7 @@ public:
|
|||
NeoPXL8HDR's use. Currently ignored on SAMD.
|
||||
@return true on successful alloc/init, false otherwise.
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
bool begin(bool dbuf = false, PIO pio_instance = pio0);
|
||||
#else
|
||||
bool begin(bool dbuf = false);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Process and issue new data to the NeoPixel strands.
|
||||
|
|
@ -175,8 +171,9 @@ public:
|
|||
|
||||
protected:
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
PIO pio; ///< PIO peripheral
|
||||
uint8_t sm; ///< State machine #
|
||||
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)
|
||||
|
|
@ -206,7 +203,7 @@ protected:
|
|||
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).
|
||||
multi-core chips (e.g. RP2040, RP235x).
|
||||
*/
|
||||
class Adafruit_NeoPXL8HDR : public Adafruit_NeoPXL8 {
|
||||
|
||||
|
|
@ -226,8 +223,8 @@ public:
|
|||
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, these are GP## numbers, not necessarily the digital
|
||||
pin numbers silkscreened on the board.
|
||||
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).
|
||||
|
|
@ -241,7 +238,7 @@ public:
|
|||
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.
|
||||
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
|
||||
|
|
@ -252,12 +249,7 @@ public:
|
|||
others just waste RAM. Currently ignored on SAMD.
|
||||
@return true on successful alloc/init, false otherwise.
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
bool begin(bool blend = false, uint8_t bits = 4, bool dbuf = false,
|
||||
PIO pio_instance = pio0);
|
||||
#else
|
||||
bool begin(bool blend = false, uint8_t bits = 4, bool dbuf = false);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Set peak output brightness for all channels (RGB and W if
|
||||
|
|
@ -505,13 +497,19 @@ public:
|
|||
/*!
|
||||
@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 where
|
||||
the refresh loop runs full-tilt on its own core; on SAMD,
|
||||
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
|
||||
|
|
@ -539,4 +537,35 @@ protected:
|
|||
#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_
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Adafruit_NeoPXL8
|
||||
DMA-driven 8-way concurrent NeoPixel driver for SAMD21 (M0+), SAMD51 (M4), RP2040 and ESP32S3 microcontrollers. 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")
|
||||
|
||||
|
|
@ -42,12 +42,12 @@ Other boards (such as Grand Central) have an altogether different pinout. See th
|
|||
|
||||
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 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 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).
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -78,7 +78,5 @@ void loop() {
|
|||
}
|
||||
|
||||
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));
|
||||
return (pgm_read_byte_near(cos_wave + (preWrapVal & 255)));
|
||||
}
|
||||
|
|
|
|||
0
examples/NeoPXL8/VideoMSC/.feather_esp32s3.test.skip
Normal file
0
examples/NeoPXL8/VideoMSC/.feather_esp32s3.test.skip
Normal file
|
|
@ -1,30 +1,33 @@
|
|||
// 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>
|
||||
|
||||
// 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
|
||||
|
||||
#define SYNC_PIN -1 // -1 = not used
|
||||
|
||||
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
|
||||
// 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:
|
||||
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.
|
||||
|
|
@ -33,9 +36,9 @@ int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
|
|||
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.
|
||||
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
|
||||
|
|
@ -69,71 +72,139 @@ https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
|
|||
pin 13: SD Card, SCLK
|
||||
*/
|
||||
|
||||
#include "SPI.h"
|
||||
#include "SdFat.h"
|
||||
#include "Adafruit_SPIFlash.h"
|
||||
#include "Adafruit_TinyUSB.h"
|
||||
/*
|
||||
ADAFRUIT_NEOPXL8 UPDATE:
|
||||
|
||||
#define LED_WIDTH 30 // number of LEDs horizontally
|
||||
#define LED_HEIGHT 16 // number of LEDs vertically (must be multiple of 8)
|
||||
#define LED_LAYOUT 1 // 0 = even rows left->right, 1 = even rows right->left
|
||||
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"
|
||||
|
||||
const int ledsPerStrip = LED_WIDTH * LED_HEIGHT / 8;
|
||||
uint8_t imageBuffer[LED_WIDTH * LED_HEIGHT * 3];
|
||||
uint32_t timeOfLastFrame = 0;
|
||||
bool playing = false;
|
||||
// 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
|
||||
|
||||
Adafruit_NeoPXL8 leds(ledsPerStrip, pins, COLOR_ORDER);
|
||||
|
||||
// From tinyUSB msc_external_flash example
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
Adafruit_FlashTransport_RP2040 flashTransport;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
Adafruit_FlashTransport_ESP32 flashTransport;
|
||||
#else
|
||||
#if defined(EXTERNAL_FLASH_USE_QSPI)
|
||||
Adafruit_FlashTransport_QSPI flashTransport;
|
||||
#elif defined(EXTERNAL_FLASH_USE_SPI)
|
||||
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, EXTERNAL_FLASH_USE_SPI);
|
||||
#else
|
||||
#error No QSPI/SPI flash are defined on your board variant.h !
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Adafruit_SPIFlash flash(&flashTransport);
|
||||
|
||||
FatFileSystem fatfs; // file system object from SdFat
|
||||
FatFile file;
|
||||
Adafruit_USBD_MSC usb_msc; // USB Mass Storage object
|
||||
bool fs_changed = true; // Set to true when PC write to flash
|
||||
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() {
|
||||
// Flash setup from tinyUSB msc_external_flash example
|
||||
flash.begin();
|
||||
// Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively
|
||||
usb_msc.setID("Adafruit", "External Flash", "1.0");
|
||||
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); // Set callback
|
||||
// Set disk size, block size should be 512 regardless of spi flash page size
|
||||
usb_msc.setCapacity(flash.size()/512, 512);
|
||||
usb_msc.setUnitReady(true); // MSC is ready for read/write
|
||||
usb_msc.begin();
|
||||
fatfs.begin(&flash); // Init file system on the flash
|
||||
// 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(50);
|
||||
delay(1000);
|
||||
Serial.setTimeout(50);
|
||||
Serial.println("VideoMSC");
|
||||
|
||||
if (!leds.begin()) {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
|
||||
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);
|
||||
}
|
||||
leds.show();
|
||||
|
||||
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) stopWithErrorMessage("Could not read " FILENAME);
|
||||
if (!status) error_handler("Can't open movie .bin file", 1000);
|
||||
Serial.println("File opened");
|
||||
playing = true;
|
||||
timeOfLastFrame = 0;
|
||||
|
|
@ -196,8 +267,8 @@ void loop()
|
|||
unsigned int usec = header[3] | (header[4] << 8);
|
||||
unsigned int readsize = size;
|
||||
//Serial.printf("v: %u %u\n", size, usec);
|
||||
if (readsize > sizeof(imageBuffer)) {
|
||||
readsize = sizeof(imageBuffer);
|
||||
if (readsize > imageBufferSize) {
|
||||
readsize = imageBufferSize;
|
||||
}
|
||||
if (sd_card_read(imageBuffer, readsize)) {
|
||||
uint32_t now;
|
||||
|
|
@ -268,53 +339,29 @@ void error(const char *str)
|
|||
playing = false;
|
||||
}
|
||||
|
||||
// when an error happens during setup, give up and print a message
|
||||
// to the serial monitor.
|
||||
void stopWithErrorMessage(const char *str)
|
||||
{
|
||||
while (1) {
|
||||
Serial.println(str);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void convert_and_show() {
|
||||
uint8_t *ptr = imageBuffer;
|
||||
for (int y=0; y<LED_HEIGHT; y++) {
|
||||
for (int x=0; x<LED_WIDTH; x++) {
|
||||
int pixelIndex;
|
||||
#if (LED_LAYOUT == 0)
|
||||
// Always left-to-right
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
#else
|
||||
// Even rows are left-to-right, odd are right-to-left
|
||||
if (y & 1) {
|
||||
pixelIndex = (y + 1) * LED_WIDTH - 1 - x;
|
||||
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 {
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
}
|
||||
#endif
|
||||
uint8_t r = leds.gamma8(*ptr++);
|
||||
uint8_t g = leds.gamma8(*ptr++);
|
||||
uint8_t b = leds.gamma8(*ptr++);
|
||||
leds.setPixelColor(pixelIndex, r, g, b);
|
||||
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();
|
||||
}
|
||||
|
||||
// More code from tinyUSB msc_external_flash example; commented there
|
||||
|
||||
int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) {
|
||||
return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1;
|
||||
}
|
||||
|
||||
int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) {
|
||||
return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1;
|
||||
}
|
||||
|
||||
void msc_flush_cb (void) {
|
||||
flash.syncBlocks();
|
||||
fatfs.cacheClear();
|
||||
fs_changed = true;
|
||||
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
|
|
@ -1,29 +1,30 @@
|
|||
// 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>
|
||||
|
||||
// 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
|
||||
|
||||
#define SYNC_PIN -1 // -1 = not used
|
||||
|
||||
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
|
||||
// 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:
|
||||
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.
|
||||
|
|
@ -32,9 +33,9 @@ int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
|
|||
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.
|
||||
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
|
||||
|
|
@ -74,53 +75,160 @@ https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
|
|||
ports on the same motherboard, may give poor performance.
|
||||
*/
|
||||
|
||||
// The actual arrangement of the LEDs connected to this Teensy 3.0 board.
|
||||
// LED_HEIGHT *must* be a multiple of 8. When 16, 24, 32 are used, each
|
||||
// strip spans 2, 3, 4 rows. LED_LAYOUT indicates the direction the strips
|
||||
// are arranged. If 0, each strip begins on the left for its first row,
|
||||
// then goes right to left for its second row, then left to right,
|
||||
// zig-zagging for each successive row.
|
||||
#define LED_WIDTH 30 // number of LEDs horizontally
|
||||
#define LED_HEIGHT 16 // number of LEDs vertically (must be multiple of 8)
|
||||
#define LED_LAYOUT 1 // 0 = even rows left->right, 1 = even rows right->left
|
||||
/*
|
||||
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
|
||||
// Teensy 3.0 boards driving groups of LEDs, these parameters allow you to
|
||||
// program each Teensy 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 Teensy 3.0 by your operating system.
|
||||
#define VIDEO_XOFFSET 0
|
||||
#define VIDEO_YOFFSET 0 // display entire image
|
||||
#define VIDEO_WIDTH 100
|
||||
#define VIDEO_HEIGHT 100
|
||||
// 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;
|
||||
|
||||
//#define VIDEO_XOFFSET 0
|
||||
//#define VIDEO_YOFFSET 0 // display upper half
|
||||
//#define VIDEO_WIDTH 100
|
||||
//#define VIDEO_HEIGHT 50
|
||||
|
||||
//#define VIDEO_XOFFSET 0
|
||||
//#define VIDEO_YOFFSET 50 // display lower half
|
||||
//#define VIDEO_WIDTH 100
|
||||
//#define VIDEO_HEIGHT 50
|
||||
|
||||
|
||||
const int ledsPerStrip = LED_WIDTH * LED_HEIGHT / 8;
|
||||
uint8_t imageBuffer[LED_WIDTH * LED_HEIGHT * 3];
|
||||
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(ledsPerStrip, pins, COLOR_ORDER);
|
||||
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() {
|
||||
pinMode(SYNC_PIN, INPUT_PULLUP); // Frame Sync
|
||||
// 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 (!leds.begin()) {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
|
||||
|
||||
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);
|
||||
}
|
||||
leds.show();
|
||||
|
||||
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() {
|
||||
|
|
@ -166,17 +274,17 @@ void loop() {
|
|||
unsigned int usecUntilFrameSync = 0;
|
||||
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
|
||||
if (count != 2) return;
|
||||
count = Serial.readBytes((char *)imageBuffer, sizeof(imageBuffer));
|
||||
if (count == sizeof(imageBuffer)) {
|
||||
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);
|
||||
digitalWrite(sync_pin, HIGH);
|
||||
pinMode(sync_pin, OUTPUT);
|
||||
delayMicroseconds(usToWaitBeforeSyncOutput);
|
||||
digitalWrite(SYNC_PIN, LOW);
|
||||
digitalWrite(sync_pin, LOW);
|
||||
// WS2811 update begins immediately after falling edge of frame sync
|
||||
convert_and_show();
|
||||
}
|
||||
|
|
@ -188,32 +296,32 @@ void loop() {
|
|||
unsigned int usecUntilFrameSync = 0;
|
||||
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
|
||||
if (count != 2) return;
|
||||
count = Serial.readBytes((char *)imageBuffer, sizeof(imageBuffer));
|
||||
if (count == sizeof(imageBuffer)) {
|
||||
digitalWrite(SYNC_PIN, HIGH);
|
||||
pinMode(SYNC_PIN, OUTPUT);
|
||||
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);
|
||||
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);
|
||||
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, sizeof(imageBuffer));
|
||||
if (count == sizeof(imageBuffer)) {
|
||||
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
|
||||
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();
|
||||
|
|
@ -226,23 +334,23 @@ void loop() {
|
|||
} else if (startChar == '?') {
|
||||
// when the video application asks, give it all our info
|
||||
// for easy and automatic configuration
|
||||
Serial.print(LED_WIDTH);
|
||||
Serial.print(led_width);
|
||||
Serial.write(',');
|
||||
Serial.print(LED_HEIGHT);
|
||||
Serial.print(led_height);
|
||||
Serial.write(',');
|
||||
Serial.print(LED_LAYOUT);
|
||||
Serial.print(led_layout);
|
||||
Serial.write(',');
|
||||
Serial.print(0);
|
||||
Serial.write(',');
|
||||
Serial.print(0);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_XOFFSET);
|
||||
Serial.print(video_xoffset);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_YOFFSET);
|
||||
Serial.print(video_yoffset);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_WIDTH);
|
||||
Serial.print(video_width);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_HEIGHT);
|
||||
Serial.print(video_height);
|
||||
Serial.write(',');
|
||||
Serial.print(0);
|
||||
Serial.write(',');
|
||||
|
|
@ -258,25 +366,27 @@ void loop() {
|
|||
|
||||
void convert_and_show() {
|
||||
uint8_t *ptr = imageBuffer;
|
||||
for (int y=0; y<LED_HEIGHT; y++) {
|
||||
for (int x=0; x<LED_WIDTH; x++) {
|
||||
int pixelIndex;
|
||||
#if (LED_LAYOUT == 0)
|
||||
// Always left-to-right
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
#else
|
||||
// Even rows are left-to-right, odd are right-to-left
|
||||
if (y & 1) {
|
||||
pixelIndex = (y + 1) * LED_WIDTH - 1 - x;
|
||||
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 {
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
}
|
||||
#endif
|
||||
uint8_t r = leds.gamma8(*ptr++);
|
||||
uint8_t g = leds.gamma8(*ptr++);
|
||||
uint8_t b = leds.gamma8(*ptr++);
|
||||
leds.setPixelColor(pixelIndex, r, g, b);
|
||||
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();
|
||||
}
|
||||
leds->show();
|
||||
}
|
||||
|
|
|
|||
0
examples/NeoPXL8HDR/VideoMSC/.feather_esp32s3.test.skip
Normal file
0
examples/NeoPXL8HDR/VideoMSC/.feather_esp32s3.test.skip
Normal file
|
|
@ -1,3 +1,6 @@
|
|||
// 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.
|
||||
|
|
@ -5,27 +8,27 @@
|
|||
// 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>
|
||||
|
||||
// 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
|
||||
|
||||
#define SYNC_PIN -1 // -1 = not used
|
||||
|
||||
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
|
||||
// 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:
|
||||
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.
|
||||
|
|
@ -34,9 +37,9 @@ int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
|
|||
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.
|
||||
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
|
||||
|
|
@ -70,45 +73,66 @@ https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
|
|||
pin 13: SD Card, SCLK
|
||||
*/
|
||||
|
||||
#include "SPI.h"
|
||||
#include "SdFat.h"
|
||||
#include "Adafruit_SPIFlash.h"
|
||||
#include "Adafruit_TinyUSB.h"
|
||||
/*
|
||||
ADAFRUIT_NEOPXL8 UPDATE:
|
||||
|
||||
#define LED_WIDTH 30 // number of LEDs horizontally
|
||||
#define LED_HEIGHT 16 // number of LEDs vertically (must be multiple of 8)
|
||||
#define LED_LAYOUT 1 // 0 = even rows left->right, 1 = even rows right->left
|
||||
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"
|
||||
|
||||
const int ledsPerStrip = LED_WIDTH * LED_HEIGHT / 8;
|
||||
uint8_t imageBuffer[LED_WIDTH * LED_HEIGHT * 3];
|
||||
uint32_t timeOfLastFrame = 0;
|
||||
bool playing = false;
|
||||
// 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
|
||||
|
||||
Adafruit_NeoPXL8HDR leds(ledsPerStrip, pins, COLOR_ORDER);
|
||||
|
||||
// From tinyUSB msc_external_flash example
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
Adafruit_FlashTransport_RP2040 flashTransport;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
Adafruit_FlashTransport_ESP32 flashTransport;
|
||||
#else
|
||||
#if defined(EXTERNAL_FLASH_USE_QSPI)
|
||||
Adafruit_FlashTransport_QSPI flashTransport;
|
||||
#elif defined(EXTERNAL_FLASH_USE_SPI)
|
||||
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, EXTERNAL_FLASH_USE_SPI);
|
||||
#else
|
||||
#error No QSPI/SPI flash are defined on your board variant.h !
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Adafruit_SPIFlash flash(&flashTransport);
|
||||
|
||||
FatFileSystem fatfs; // file system object from SdFat
|
||||
FatFile file;
|
||||
Adafruit_USBD_MSC usb_msc; // USB Mass Storage object
|
||||
bool fs_changed = true; // Set to true when PC write to flash
|
||||
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)
|
||||
|
||||
|
|
@ -117,14 +141,14 @@ bool fs_changed = true; // Set to true when PC write to flash
|
|||
// free for animation logic in the regular loop() function.
|
||||
|
||||
void loop1() {
|
||||
leds.refresh();
|
||||
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(100);
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
|
@ -136,7 +160,7 @@ void setup1() {
|
|||
void loop0(void *param) {
|
||||
for(;;) {
|
||||
yield();
|
||||
leds.refresh();
|
||||
if (leds) leds->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,33 +179,87 @@ void TC3_Handler() {
|
|||
}
|
||||
|
||||
void timerCallback(void) {
|
||||
leds.refresh();
|
||||
if (leds) leds->refresh();
|
||||
}
|
||||
|
||||
#endif // end SAMD
|
||||
|
||||
void setup() {
|
||||
// Flash setup from tinyUSB msc_external_flash example
|
||||
flash.begin();
|
||||
// Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively
|
||||
usb_msc.setID("Adafruit", "External Flash", "1.0");
|
||||
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); // Set callback
|
||||
// Set disk size, block size should be 512 regardless of spi flash page size
|
||||
usb_msc.setCapacity(flash.size()/512, 512);
|
||||
usb_msc.setUnitReady(true); // MSC is ready for read/write
|
||||
usb_msc.begin();
|
||||
fatfs.begin(&flash); // Init file system on the flash
|
||||
// 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 (!leds.begin()) {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
|
||||
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);
|
||||
}
|
||||
leds.setBrightness(65535, 2.6); // Full brightness, 2.6 gamma factor
|
||||
leds.show();
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -205,12 +283,6 @@ void setup() {
|
|||
zerotimer.enable(true);
|
||||
|
||||
#endif // end SAMD
|
||||
|
||||
bool status = file.open(FILENAME, O_RDONLY);
|
||||
if (!status) stopWithErrorMessage("Could not read " FILENAME);
|
||||
Serial.println("File opened");
|
||||
playing = true;
|
||||
timeOfLastFrame = 0;
|
||||
}
|
||||
|
||||
// REMAINDER OF CODE IS NEARLY IDENTICAL TO NON-HDR VERSION. This version
|
||||
|
|
@ -273,8 +345,8 @@ void loop()
|
|||
unsigned int usec = header[3] | (header[4] << 8);
|
||||
unsigned int readsize = size;
|
||||
//Serial.printf("v: %u %u\n", size, usec);
|
||||
if (readsize > sizeof(imageBuffer)) {
|
||||
readsize = sizeof(imageBuffer);
|
||||
if (readsize > imageBufferSize) {
|
||||
readsize = imageBufferSize;
|
||||
}
|
||||
if (sd_card_read(imageBuffer, readsize)) {
|
||||
uint32_t now;
|
||||
|
|
@ -345,53 +417,29 @@ void error(const char *str)
|
|||
playing = false;
|
||||
}
|
||||
|
||||
// when an error happens during setup, give up and print a message
|
||||
// to the serial monitor.
|
||||
void stopWithErrorMessage(const char *str)
|
||||
{
|
||||
while (1) {
|
||||
Serial.println(str);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void convert_and_show() {
|
||||
uint8_t *ptr = imageBuffer;
|
||||
for (int y=0; y<LED_HEIGHT; y++) {
|
||||
for (int x=0; x<LED_WIDTH; x++) {
|
||||
int pixelIndex;
|
||||
#if (LED_LAYOUT == 0)
|
||||
// Always left-to-right
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
#else
|
||||
// Even rows are left-to-right, odd are right-to-left
|
||||
if (y & 1) {
|
||||
pixelIndex = (y + 1) * LED_WIDTH - 1 - x;
|
||||
} else {
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
}
|
||||
#endif
|
||||
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);
|
||||
leds->setPixelColor(pixelIndex, r, g, b);
|
||||
}
|
||||
}
|
||||
leds.show();
|
||||
} 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);
|
||||
}
|
||||
|
||||
// More code from tinyUSB msc_external_flash example; commented there
|
||||
|
||||
int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) {
|
||||
return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1;
|
||||
}
|
||||
|
||||
int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) {
|
||||
return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1;
|
||||
}
|
||||
|
||||
void msc_flush_cb (void) {
|
||||
flash.syncBlocks();
|
||||
fatfs.cacheClear();
|
||||
fs_changed = true;
|
||||
leds->show();
|
||||
}
|
||||
|
|
|
|||
0
examples/NeoPXL8HDR/VideoSerial/.esp32.test.skip
Normal file
0
examples/NeoPXL8HDR/VideoSerial/.esp32.test.skip
Normal file
|
|
@ -1,30 +1,31 @@
|
|||
// 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>
|
||||
|
||||
// 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
|
||||
|
||||
#define SYNC_PIN -1 // -1 = not used
|
||||
|
||||
// This example is minimally adapted from one in PJRC's OctoWS2811 Library:
|
||||
// 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:
|
||||
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.
|
||||
|
|
@ -33,9 +34,9 @@ int8_t pins[8] = { 6, 7, 9, 8, 13, 12, 11, 10 };
|
|||
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.
|
||||
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
|
||||
|
|
@ -75,44 +76,68 @@ https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
|
|||
ports on the same motherboard, may give poor performance.
|
||||
*/
|
||||
|
||||
// The actual arrangement of the LEDs connected to this Teensy 3.0 board.
|
||||
// LED_HEIGHT *must* be a multiple of 8. When 16, 24, 32 are used, each
|
||||
// strip spans 2, 3, 4 rows. LED_LAYOUT indicates the direction the strips
|
||||
// are arranged. If 0, each strip begins on the left for its first row,
|
||||
// then goes right to left for its second row, then left to right,
|
||||
// zig-zagging for each successive row.
|
||||
#define LED_WIDTH 30 // number of LEDs horizontally
|
||||
#define LED_HEIGHT 16 // number of LEDs vertically (must be multiple of 8)
|
||||
#define LED_LAYOUT 1 // 0 = even rows left->right, 1 = even rows right->left
|
||||
/*
|
||||
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
|
||||
// Teensy 3.0 boards driving groups of LEDs, these parameters allow you to
|
||||
// program each Teensy 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 Teensy 3.0 by your operating system.
|
||||
#define VIDEO_XOFFSET 0
|
||||
#define VIDEO_YOFFSET 0 // display entire image
|
||||
#define VIDEO_WIDTH 100
|
||||
#define VIDEO_HEIGHT 100
|
||||
// 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;
|
||||
|
||||
//#define VIDEO_XOFFSET 0
|
||||
//#define VIDEO_YOFFSET 0 // display upper half
|
||||
//#define VIDEO_WIDTH 100
|
||||
//#define VIDEO_HEIGHT 50
|
||||
|
||||
//#define VIDEO_XOFFSET 0
|
||||
//#define VIDEO_YOFFSET 50 // display lower half
|
||||
//#define VIDEO_WIDTH 100
|
||||
//#define VIDEO_HEIGHT 50
|
||||
|
||||
|
||||
const int ledsPerStrip = LED_WIDTH * LED_HEIGHT / 8;
|
||||
uint8_t imageBuffer[LED_WIDTH * LED_HEIGHT * 3];
|
||||
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(ledsPerStrip, pins, COLOR_ORDER);
|
||||
Adafruit_NeoPXL8HDR *leds = NULL; // NeoPXL8HDR object is allocated after reading config
|
||||
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
|
||||
|
|
@ -121,7 +146,7 @@ Adafruit_NeoPXL8HDR leds(ledsPerStrip, pins, COLOR_ORDER);
|
|||
// free for animation logic in the regular loop() function.
|
||||
|
||||
void loop1() {
|
||||
leds.refresh();
|
||||
if (leds) leds->refresh();
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
|
@ -133,7 +158,7 @@ void loop1() {
|
|||
void loop0(void *param) {
|
||||
for(;;) {
|
||||
yield();
|
||||
leds.refresh();
|
||||
if (leds) leds->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,20 +177,101 @@ void TC3_Handler() {
|
|||
}
|
||||
|
||||
void timerCallback(void) {
|
||||
leds.refresh();
|
||||
if (leds) leds->refresh();
|
||||
}
|
||||
|
||||
#endif // end SAMD
|
||||
|
||||
void setup() {
|
||||
pinMode(SYNC_PIN, INPUT_PULLUP); // Frame Sync
|
||||
Serial.setTimeout(50);
|
||||
if (!leds.begin()) {
|
||||
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() / 500) & 1);
|
||||
for (;;) {
|
||||
digitalWrite(LED_BUILTIN, (millis() / speed) & 1);
|
||||
yield(); // Keep filesystem accessible for editing
|
||||
}
|
||||
leds.setBrightness(65535, 2.6); // Full brightness, 2.6 gamma factor
|
||||
leds.show();
|
||||
} 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)
|
||||
|
||||
|
|
@ -237,17 +343,17 @@ void loop() {
|
|||
unsigned int usecUntilFrameSync = 0;
|
||||
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
|
||||
if (count != 2) return;
|
||||
count = Serial.readBytes((char *)imageBuffer, sizeof(imageBuffer));
|
||||
if (count == sizeof(imageBuffer)) {
|
||||
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);
|
||||
digitalWrite(sync_pin, HIGH);
|
||||
pinMode(sync_pin, OUTPUT);
|
||||
delayMicroseconds(usToWaitBeforeSyncOutput);
|
||||
digitalWrite(SYNC_PIN, LOW);
|
||||
digitalWrite(sync_pin, LOW);
|
||||
// WS2811 update begins immediately after falling edge of frame sync
|
||||
convert_and_show();
|
||||
}
|
||||
|
|
@ -259,32 +365,32 @@ void loop() {
|
|||
unsigned int usecUntilFrameSync = 0;
|
||||
int count = Serial.readBytes((char *)&usecUntilFrameSync, 2);
|
||||
if (count != 2) return;
|
||||
count = Serial.readBytes((char *)imageBuffer, sizeof(imageBuffer));
|
||||
if (count == sizeof(imageBuffer)) {
|
||||
digitalWrite(SYNC_PIN, HIGH);
|
||||
pinMode(SYNC_PIN, OUTPUT);
|
||||
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);
|
||||
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);
|
||||
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, sizeof(imageBuffer));
|
||||
if (count == sizeof(imageBuffer)) {
|
||||
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
|
||||
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();
|
||||
|
|
@ -297,23 +403,23 @@ void loop() {
|
|||
} else if (startChar == '?') {
|
||||
// when the video application asks, give it all our info
|
||||
// for easy and automatic configuration
|
||||
Serial.print(LED_WIDTH);
|
||||
Serial.print(led_width);
|
||||
Serial.write(',');
|
||||
Serial.print(LED_HEIGHT);
|
||||
Serial.print(led_height);
|
||||
Serial.write(',');
|
||||
Serial.print(LED_LAYOUT);
|
||||
Serial.print(led_layout);
|
||||
Serial.write(',');
|
||||
Serial.print(0);
|
||||
Serial.write(',');
|
||||
Serial.print(0);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_XOFFSET);
|
||||
Serial.print(video_xoffset);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_YOFFSET);
|
||||
Serial.print(video_yoffset);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_WIDTH);
|
||||
Serial.print(video_width);
|
||||
Serial.write(',');
|
||||
Serial.print(VIDEO_HEIGHT);
|
||||
Serial.print(video_height);
|
||||
Serial.write(',');
|
||||
Serial.print(0);
|
||||
Serial.write(',');
|
||||
|
|
@ -329,25 +435,27 @@ void loop() {
|
|||
|
||||
void convert_and_show() {
|
||||
uint8_t *ptr = imageBuffer;
|
||||
for (int y=0; y<LED_HEIGHT; y++) {
|
||||
for (int x=0; x<LED_WIDTH; x++) {
|
||||
int pixelIndex;
|
||||
#if (LED_LAYOUT == 0)
|
||||
// Always left-to-right
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
#else
|
||||
// Even rows are left-to-right, odd are right-to-left
|
||||
if (y & 1) {
|
||||
pixelIndex = (y + 1) * LED_WIDTH - 1 - x;
|
||||
} else {
|
||||
pixelIndex = y * LED_WIDTH + x;
|
||||
}
|
||||
#endif
|
||||
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);
|
||||
leds->setPixelColor(pixelIndex, r, g, b);
|
||||
}
|
||||
}
|
||||
leds.show();
|
||||
} 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name=Adafruit NeoPXL8
|
||||
version=1.2.0
|
||||
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
|
||||
|
|
@ -7,4 +7,4 @@ paragraph=Arduino library for controlling 8 NeoPixel LED strips using DMA on ATS
|
|||
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
|
||||
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