Merge pull request #16 from adafruit/rp2350_fixes

Fix high-numbered RP2350's
This commit is contained in:
Liz 2025-06-03 17:58:41 -04:00 committed by GitHub
commit fc96155cf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 128 additions and 136 deletions

View file

@ -294,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;
@ -308,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++) {
@ -332,8 +325,19 @@ bool Adafruit_NeoPXL8::begin(bool dbuf) {
dmaBuf[1] = dbuf ? &dmaBuf[0][buf_size] : dmaBuf[0];
// Set up PIO code & clock
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);
@ -494,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
@ -852,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).
@ -869,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...

View file

@ -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,11 +171,11 @@ 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
uint offset;
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
gdma_channel_handle_t dma_chan; ///< DMA channel
dma_descriptor_t *desc; ///< DMA descriptor pointer
@ -253,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

View file

@ -7,6 +7,7 @@
// 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

View file

@ -6,6 +6,7 @@
// 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

View file

@ -8,6 +8,7 @@
// 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

View file

@ -7,6 +7,7 @@
// 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