Compare commits

...

56 commits

Author SHA1 Message Date
Tyeth Gundry
4447258dcf
Update library.properties - bump version to 1.4.0 2025-06-17 19:31:16 +01:00
Liz
fc96155cf3
Merge pull request #16 from adafruit/rp2350_fixes
Fix high-numbered RP2350's
2025-06-03 17:58:41 -04:00
Liz
452d910680 match CI clang 2025-06-03 17:40:55 -04:00
Liz
41b8c1bd3b clang 2025-06-03 17:21:01 -04:00
Liz
77c12cf76a update sdfat - adafruit fork include for cpfs examples 2025-06-03 17:01:58 -04:00
ladyada
36d3bea700 Fix high-numbered RP2350's, also removed the 'fixed' pio argument, we will pick the next available PIO with space (which means we can also select pio2 automatically on rp2350) 2025-05-26 12:48:07 -04:00
Tyeth Gundry
07378a4d98
Update library.properties - bump version to 1.3.0 2024-09-18 19:59:40 +01:00
Limor "Ladyada" Fried
8cf8ec9d51
Merge pull request #15 from adafruit/runs-on-amethyst
Add RP235x to many RP2040 references in documentation & comments
2024-09-11 15:10:36 -04:00
377a497540 Add RP235x to many RP2040 references in documentation & comments 2024-09-11 13:53:52 -05:00
Limor "Ladyada" Fried
cb3253f2ec
Merge pull request #14 from adafruit/build-rp2350
run CI on the pico 2350
2024-09-11 13:59:45 -04:00
b75683cf4c run CI on the pico 2350 2024-09-11 11:52:07 -05:00
6d158f300b
Merge pull request #11 from MarcusGarfunkel/ISSUE-10-pio-sm-handling
Destructor frees pio resources.
2024-09-11 11:51:41 -05:00
e8251ad309 Merge remote-tracking branch 'origin/master' into ISSUE-10-pio-sm-handling 2024-09-11 11:26:12 -05:00
Phillip Burgess
5f09a9b8ba clang fix 2023-05-19 14:03:29 -07:00
Phillip Burgess
b01b27dcab Fix clang 2023-05-19 13:51:26 -07:00
Phillip Burgess
87bb882f59 Fix HDR clear() 2023-05-19 13:32:57 -07:00
dherrada
0874ce8775 Update CI action versions 2023-05-12 11:24:05 -04:00
Paint Your Dragon
d4ff50edf6
Merge pull request #12 from adafruit/pb-flexi-video-examples
VideoMSC and VideoSerial now use config file for LED layout
2023-04-20 12:00:38 -07:00
Phillip Burgess
149f6c17b0 Add more skipfiles 2023-04-20 11:44:38 -07:00
Phillip Burgess
4ffe3d3d9e Add ESP32 skip files 2023-04-20 11:10:25 -07:00
Paint Your Dragon
7043878f7c
Merge branch 'master' into pb-flexi-video-examples 2023-04-20 10:37:32 -07:00
Phillip Burgess
03f5921118 "Downbump" version; would-be 1.3 changes moved Adafruit_CPFS 2023-04-20 10:32:32 -07:00
Phillip Burgess
5ab8aa4c37 Update VideoMSC/Serial examples for Adafruit_CPFS class 2023-04-20 10:30:54 -07:00
Phillip Burgess
86997e31cc Update comments 2023-04-03 10:07:57 -07:00
Phillip Burgess
46b8ece698 Fix typo in comments 2023-04-03 09:49:31 -07:00
Marcus Garfunkel
c1354e07fb Destructor frees pio resources. 2023-03-23 00:43:56 -07:00
Phillip Burgess
b14e8643ee clang-format 2023-02-10 10:12:53 -08:00
Phillip Burgess
a962118778 Put JSON sizes back at 1024, tweak default pins, add sketch comments re SAMD 2023-02-10 09:50:09 -08:00
Phillip Burgess
009f6f47f6 Allow for bigger JSON file just in case 2023-02-09 20:34:16 -08:00
Phillip Burgess
aa44008f32 Update moar examples to use JSON config 2023-02-09 15:07:14 -08:00
Phillip Burgess
bf5af30c38 NeoPXL8HDR VideoSerial working 2023-02-09 11:20:21 -08:00
Phillip Burgess
65dd928e1c VideoSerial example working w/new JSON stuff 2023-02-09 09:41:23 -08:00
Phillip Burgess
e2855c6c1b NeoPXL8/VideoSerial example updated to use neopxl8.cfg 2023-02-08 16:56:25 -08:00
Phillip Burgess
5d552dea80 Fix pin drive strength to match main branch 2023-02-08 13:39:10 -08:00
Paint Your Dragon
c1cb252440
Version bump for RP2040 drive strength fix 2023-02-08 10:50:39 -08:00
Paint Your Dragon
82f6496563
RP2040: use 2 mA drive strength to avoid lockup
TBD whether this should be SCORPIO only. Tested & seems OK on both Feather+Wing and SCORPIO, but if it causes Wing problems for others, will add an #ifdef around it.
2023-02-08 10:50:00 -08:00
Phillip Burgess
0b2ff496c4 Big ol' do-over, created FFS class instead of the config lib 2023-02-07 09:23:52 -08:00
Phillip Burgess
832bbebb1b VideoSerial cleanup 2023-02-03 18:34:20 -08:00
Phillip Burgess
2376a11b8a Changed config to be more openly JSON-like, working on VideoSerial example 2023-02-03 16:31:22 -08:00
Phillip Burgess
2fa9dca1ee clang-format 2023-02-03 11:32:51 -08:00
Phillip Burgess
f4ea2c4aea CI is picky about parenthesis 2023-02-03 11:07:43 -08:00
Phillip Burgess
ef216d4bb7 More Doxy, ESP32 CI assistance 2023-02-03 10:46:53 -08:00
Phillip Burgess
ff4e72910e Doxy, comment, add internal flash support 2023-02-03 10:03:09 -08:00
Phillip Burgess
889c4c88ef Fix U&LC, add hooks for extra JSON data 2023-02-02 21:12:20 -08:00
Phillip Burgess
969dcf9a21 Some reorg and cleanup, affects more than just config code now 2023-02-02 18:11:15 -08:00
Phillip Burgess
1ab2557557 WIP, more configurables 2023-02-02 12:14:13 -08:00
Phillip Burgess
72b82f3da7 Config file WIP 2023-02-02 11:57:06 -08:00
Paint Your Dragon
c24c1325b6
Merge pull request #7 from adafruit/pb-rp2040-irq
Use shared DMA IRQ on RP2040
2022-12-26 15:48:27 -08:00
Phillip Burgess
7ecc003814 Version bump for RP2040 DMA fix 2022-12-26 15:31:49 -08:00
Phillip Burgess
be1db47829 clang-format 2022-12-26 15:19:08 -08:00
Phillip Burgess
ef83c28f45 RP2040: use shared (not exclusive) IRQ handler 2022-12-26 15:04:21 -08:00
Phillip Burgess
30eb6ca6b1 Appease clang-format 2022-10-28 19:16:12 -07:00
Phillip Burgess
ba4d8eb035 RP2040: init PIO outputs AFTER state machine claimed 2022-10-28 18:58:26 -07:00
Phillip Burgess
41345938be Appease the clang monster 2022-08-23 13:24:27 -07:00
Phillip Burgess
d9e5679414 ESP32S3: avoid PSRAM for DMA-capable transfers 2022-08-23 13:12:41 -07:00
Paint Your Dragon
80ed7127fc
Merge pull request #5 from adafruit/esp32s3
Add ESP32S3 support and NeoPXL8HDR class
2022-07-07 18:35:15 -07:00
16 changed files with 978 additions and 623 deletions

View file

@ -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 .

View file

@ -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,12 +135,14 @@ 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
lastBitTime = micros();
sending = 0;
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) {
@ -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];
@ -476,131 +498,133 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
#else // SAMD
// Double-buffered DMA out is currently NOT supported on SAMD.
// Code's there but it causes weird flickering. All the pointer
// work looks right, I'm just speculating that this might have
// something to do with HDR refresh being timer interrupt-driven,
// that certain elements of the class might need to be declared
// volatile, which currently causes compilation mayhem.
// What with the timer interrupt, and needing to share cycles
// with the main thread of execution, I'm not sure it's helpful
// on SAMD anyway, mostly an RP2040 thing.
dbuf = false;
// Double-buffered DMA out is currently NOT supported on SAMD.
// Code's there but it causes weird flickering. All the pointer
// work looks right, I'm just speculating that this might have
// something to do with HDR refresh being timer interrupt-driven,
// that certain elements of the class might need to be declared
// volatile, which currently causes compilation mayhem.
// What with the timer interrupt, and needing to share cycles
// with the main thread of execution, I'm not sure it's helpful
// on SAMD anyway, mostly an RP2040 thing.
dbuf = false;
uint32_t buf_size = numLEDs * bytesPerPixel * 3 + EXTRASTARTBYTES + 3;
uint32_t alloc_size = dbuf ? buf_size * 2 : buf_size;
uint32_t buf_size = numLEDs * bytesPerPixel * 3 + EXTRASTARTBYTES + 3;
// uint32_t alloc_size = dbuf ? buf_size * 2 : buf_size;
if ((allocAddr = (uint8_t *)malloc(buf_size))) {
int i;
if ((allocAddr = (uint8_t *)malloc(buf_size))) {
int i;
dma.setTrigger(TCC0_DMAC_ID_OVF);
dma.setAction(DMA_TRIGGER_ACTON_BEAT);
dma.setTrigger(TCC0_DMAC_ID_OVF);
dma.setAction(DMA_TRIGGER_ACTON_BEAT);
// Get address of first byte that's on a 32-bit boundary and at least
// EXTRASTARTBYTES into dmaBuf. This is where pixel data starts.
alignedAddr[0] =
(uint32_t *)((uint32_t)(&allocAddr[EXTRASTARTBYTES + 3]) & ~3);
// Get address of first byte that's on a 32-bit boundary and at least
// EXTRASTARTBYTES into dmaBuf. This is where pixel data starts.
alignedAddr[0] =
(uint32_t *)((uint32_t)(&allocAddr[EXTRASTARTBYTES + 3]) & ~3);
// DMA transfer then starts EXTRABYTES back from this to stabilize
dmaBuf[0] = (uint8_t *)alignedAddr[0] - EXTRASTARTBYTES;
memset(dmaBuf[0], 0, EXTRASTARTBYTES); // Initialize start with zeros
// DMA transfer then starts EXTRABYTES back from this to stabilize
dmaBuf[0] = (uint8_t *)alignedAddr[0] - EXTRASTARTBYTES;
memset(dmaBuf[0], 0, EXTRASTARTBYTES); // Initialize start with zeros
if (dbuf) {
alignedAddr[1] =
(uint32_t *)((uint32_t)(&allocAddr[buf_size + EXTRASTARTBYTES + 3]) &
~3);
dmaBuf[1] = (uint8_t *)alignedAddr[1] - EXTRASTARTBYTES;
memset(dmaBuf[1], 0, EXTRASTARTBYTES);
} else {
alignedAddr[1] = alignedAddr[0];
dmaBuf[1] = dmaBuf[0];
}
if (dbuf) {
alignedAddr[1] =
(uint32_t
*)((uint32_t)(&allocAddr[buf_size + EXTRASTARTBYTES + 3]) &
~3);
dmaBuf[1] = (uint8_t *)alignedAddr[1] - EXTRASTARTBYTES;
memset(dmaBuf[1], 0, EXTRASTARTBYTES);
} else {
alignedAddr[1] = alignedAddr[0];
dmaBuf[1] = dmaBuf[0];
}
uint8_t *dst = &((uint8_t *)(&TCC0->PATT))[1]; // PAT.vec.PGV
dma.allocate();
desc = dma.addDescriptor(dmaBuf[dbuf_index], // source
dst, // destination
buf_size -
3, // count (don't include alignment bytes!)
DMA_BEAT_SIZE_BYTE, // size per
true, // increment source
false); // don't increment destination
uint8_t *dst = &((uint8_t *)(&TCC0->PATT))[1]; // PAT.vec.PGV
dma.allocate();
desc = dma.addDescriptor(dmaBuf[dbuf_index], // source
dst, // destination
buf_size -
3, // count (don't include alignment bytes!)
DMA_BEAT_SIZE_BYTE, // size per
true, // increment source
false); // don't increment destination
dma.setCallback(dmaCallback);
dma.setCallback(dmaCallback);
#ifdef __SAMD51__
// Set up generic clock gen 2 as source for TCC0
// Datasheet recommends setting GENCTRL register in a single write,
// so a temp value is used here to more easily construct a value.
GCLK_GENCTRL_Type genctrl;
genctrl.bit.SRC = GCLK_GENCTRL_SRC_DFLL_Val; // 48 MHz source
genctrl.bit.GENEN = 1; // Enable
genctrl.bit.OE = 1;
genctrl.bit.DIVSEL = 0; // Do not divide clock source
genctrl.bit.DIV = 0;
GCLK->GENCTRL[2].reg = genctrl.reg;
while (GCLK->SYNCBUSY.bit.GENCTRL1 == 1)
;
// Set up generic clock gen 2 as source for TCC0
// Datasheet recommends setting GENCTRL register in a single write,
// so a temp value is used here to more easily construct a value.
GCLK_GENCTRL_Type genctrl;
genctrl.bit.SRC = GCLK_GENCTRL_SRC_DFLL_Val; // 48 MHz source
genctrl.bit.GENEN = 1; // Enable
genctrl.bit.OE = 1;
genctrl.bit.DIVSEL = 0; // Do not divide clock source
genctrl.bit.DIV = 0;
GCLK->GENCTRL[2].reg = genctrl.reg;
while (GCLK->SYNCBUSY.bit.GENCTRL1 == 1)
;
GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN = 0;
while (GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN)
; // Wait for disable
GCLK_PCHCTRL_Type pchctrl;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK2_Val;
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[TCC0_GCLK_ID].reg = pchctrl.reg;
while (!GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN)
; // Wait for enable
GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN = 0;
while (GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN)
; // Wait for disable
GCLK_PCHCTRL_Type pchctrl;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK2_Val;
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[TCC0_GCLK_ID].reg = pchctrl.reg;
while (!GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN)
; // Wait for enable
#else
// Enable GCLK for TCC0
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_ID(GCM_TCC0_TCC1));
while (GCLK->STATUS.bit.SYNCBUSY == 1)
;
// Enable GCLK for TCC0
GCLK->CLKCTRL.reg =
(uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_ID(GCM_TCC0_TCC1));
while (GCLK->STATUS.bit.SYNCBUSY == 1)
;
#endif
// Disable TCC before configuring it
TCC0->CTRLA.bit.ENABLE = 0;
while (TCC0->SYNCBUSY.bit.ENABLE)
;
// Disable TCC before configuring it
TCC0->CTRLA.bit.ENABLE = 0;
while (TCC0->SYNCBUSY.bit.ENABLE)
;
TCC0->CTRLA.bit.PRESCALER = TCC_CTRLA_PRESCALER_DIV1_Val; // 1:1 Prescale
TCC0->CTRLA.bit.PRESCALER = TCC_CTRLA_PRESCALER_DIV1_Val; // 1:1 Prescale
TCC0->WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; // Normal PWM mode
while (TCC0->SYNCBUSY.bit.WAVE)
;
TCC0->WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; // Normal PWM mode
while (TCC0->SYNCBUSY.bit.WAVE)
;
TCC0->CC[0].reg = 0; // No PWM out
while (TCC0->SYNCBUSY.bit.CC0)
;
TCC0->CC[0].reg = 0; // No PWM out
while (TCC0->SYNCBUSY.bit.CC0)
;
// 2.4 GHz clock: 3 DMA xfers per NeoPixel bit = 800 KHz
// 2.4 GHz clock: 3 DMA xfers per NeoPixel bit = 800 KHz
#ifdef __SAMD51__
TCC0->PER.reg = ((48000000 + 1200000) / 2400000) - 1;
TCC0->PER.reg = ((48000000 + 1200000) / 2400000) - 1;
#else
TCC0->PER.reg = ((F_CPU + 1200000) / 2400000) - 1;
TCC0->PER.reg = ((F_CPU + 1200000) / 2400000) - 1;
#endif
while (TCC0->SYNCBUSY.bit.PER)
;
while (TCC0->SYNCBUSY.bit.PER)
;
uint8_t enableMask = 0x00; // Bitmask of pattern gen outputs
for (i = 0; i < 8; i++) {
if ((bitmask[i] = configurePin(pins[i]))) // assign AND test!
enableMask |= bitmask[i];
uint8_t enableMask = 0x00; // Bitmask of pattern gen outputs
for (i = 0; i < 8; i++) {
if ((bitmask[i] = configurePin(pins[i]))) // assign AND test!
enableMask |= bitmask[i];
}
TCC0->PATT.vec.PGV = 0; // Set all pattern outputs to 0
while (TCC0->SYNCBUSY.bit.PATT)
;
TCC0->PATT.vec.PGE = enableMask; // Enable pattern outputs
while (TCC0->SYNCBUSY.bit.PATT)
;
TCC0->CTRLA.bit.ENABLE = 1;
while (TCC0->SYNCBUSY.bit.ENABLE)
;
return true; // Success!
}
TCC0->PATT.vec.PGV = 0; // Set all pattern outputs to 0
while (TCC0->SYNCBUSY.bit.PATT)
;
TCC0->PATT.vec.PGE = enableMask; // Enable pattern outputs
while (TCC0->SYNCBUSY.bit.PATT)
;
TCC0->CTRLA.bit.ENABLE = 1;
while (TCC0->SYNCBUSY.bit.ENABLE)
;
return true; // Success!
}
#endif // end SAMD
@ -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

View file

@ -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_

View file

@ -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.

View file

@ -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)));
}

View 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
@ -49,7 +52,7 @@ https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
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.
@ -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. :)
#define FILENAME "mymovie.bin"
Run-time configuration is stored in CIRCUITPY/neopxl8.cfg and resembles:
const int ledsPerStrip = LED_WIDTH * LED_HEIGHT / 8;
uint8_t imageBuffer[LED_WIDTH * LED_HEIGHT * 3];
uint32_t timeOfLastFrame = 0;
bool playing = false;
{
"pins" : [ 16, 17, 18, 19, 20, 21, 22, 23 ],
"order" : "GRB",
"led_width" : 30,
"led_height" : 16,
"led_layout" : 0
}
Adafruit_NeoPXL8 leds(ledsPerStrip, pins, COLOR_ORDER);
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.
*/
// 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
#define FILENAME "mymovie.bin"
Adafruit_SPIFlash flash(&flashTransport);
// 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
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);
//while(!Serial);
delay(1000);
Serial.setTimeout(50);
Serial.println("VideoMSC");
if (!leds.begin()) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
leds.show();
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) 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;
} else {
pixelIndex = y * LED_WIDTH + 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 {
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);
}
#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);
}
}
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();
}

View 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,13 +33,13 @@ 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
Required Connections
--------------------
pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips.
@ -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);
}
leds.show();
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() {
@ -134,7 +242,7 @@ void loop() {
// 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
@ -152,7 +260,7 @@ void loop() {
// 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.
//
@ -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;
} else {
pixelIndex = y * LED_WIDTH + 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 {
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);
}
#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);
}
}
leds.show();
leds->show();
}

View 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
@ -50,7 +53,7 @@ https://github.com/PaulStoffregen/OctoWS2811/tree/master/extras
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.
@ -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. :)
#define FILENAME "mymovie.bin"
Run-time configuration is stored in CIRCUITPY/neopxl8.cfg and resembles:
const int ledsPerStrip = LED_WIDTH * LED_HEIGHT / 8;
uint8_t imageBuffer[LED_WIDTH * LED_HEIGHT * 3];
uint32_t timeOfLastFrame = 0;
bool playing = false;
{
"pins" : [ 16, 17, 18, 19, 20, 21, 22, 23 ],
"order" : "GRB",
"led_width" : 30,
"led_height" : 16,
"led_layout" : 0
}
Adafruit_NeoPXL8HDR leds(ledsPerStrip, pins, COLOR_ORDER);
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.
*/
// 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
#define FILENAME "mymovie.bin"
Adafruit_SPIFlash flash(&flashTransport);
// 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
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) ;
//while(!Serial);
delay(1000);
Serial.setTimeout(50);
Serial.println("VideoMSC");
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();
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)
@ -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;
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);
}
#endif
uint8_t r = *ptr++;
uint8_t g = *ptr++;
uint8_t b = *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();
}

View 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,13 +34,13 @@ 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
Required Connections
--------------------
pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips.
@ -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
}
} else { // Not fatal, just show message
Serial.println("Continuing with defaults");
}
leds.setBrightness(65535, 2.6); // Full brightness, 2.6 gamma factor
leds.show();
}
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)
@ -205,7 +311,7 @@ void loop() {
// 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
@ -223,7 +329,7 @@ void loop() {
// 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.
//
@ -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;
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);
}
#endif
uint8_t r = *ptr++;
uint8_t g = *ptr++;
uint8_t b = *ptr++;
leds.setPixelColor(pixelIndex, r, g, b);
}
}
leds.show();
leds->show();
}

View file

@ -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