Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/FastLED/FastLED.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Guyer <sam.guyer@gmail.com>2019-12-27 03:09:45 +0300
committerSam Guyer <sam.guyer@gmail.com>2019-12-27 03:09:45 +0300
commitac97d9f1efc5028cf83501821062a788266efe9c (patch)
tree0205411c5656c49516ab8190ec595b8a768392e6
parent23e99173e8286e7bcddc5c2e5f9248f2209c0c14 (diff)
Rewriting of the fill routine with the goal of reducing latency and vulnerability to interrupts. One part is making the fill routine faster by only pushing one pixel worth of data at a time. The second part is waiting until all the buffers are full before starting
-rw-r--r--platforms/esp/32/clockless_rmt_esp32.h170
1 files changed, 101 insertions, 69 deletions
diff --git a/platforms/esp/32/clockless_rmt_esp32.h b/platforms/esp/32/clockless_rmt_esp32.h
index 26d7cbac..f75fbce9 100644
--- a/platforms/esp/32/clockless_rmt_esp32.h
+++ b/platforms/esp/32/clockless_rmt_esp32.h
@@ -123,7 +123,8 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() {
// -- Configuration constants
#define DIVIDER 2 /* 4, 8 still seem to work, but timings become marginal */
-#define MAX_PULSES 32 /* A channel has a 64 "pulse" buffer - we use half per pass */
+#define MAX_PULSES 64 /* A channel has a 64 "pulse" buffer */
+#define PULSES_PER_FILL 24 /* One pixel's worth of pulses */
// -- Convert ESP32 CPU cycles to RMT device cycles, taking into account the divider
#define F_CPU_RMT ( 80000000L)
@@ -199,11 +200,6 @@ class ClocklessController : public CPixelLEDController<RGB_ORDER>
rmt_item32_t * mBuffer;
uint16_t mBufferSize;
- // -- Timing information for debugging
- uint32_t mLastFillTime;
- uint32_t mTotalTime;
- uint32_t mTimeCount;
-
public:
void init()
@@ -262,10 +258,10 @@ protected:
if (FASTLED_RMT_BUILTIN_DRIVER) {
rmt_driver_install(rmt_channel_t(i), 0, 0);
} else {
- // -- Set up the RMT to send 1/2 of the pulse buffer and then
+ // -- Set up the RMT to send 1 pixel of the pulse buffer and then
// generate an interrupt. When we get this interrupt we
- // fill the other half in preparation (kind of like double-buffering)
- rmt_set_tx_thr_intr_en(rmt_channel_t(i), true, MAX_PULSES);
+ // fill the other part in preparation (kind of like double-buffering)
+ rmt_set_tx_thr_intr_en(rmt_channel_t(i), true, PULSES_PER_FILL);
}
}
@@ -281,43 +277,12 @@ protected:
// strips, so it delegates to the refill function for each
// specific instantiation of ClocklessController.
if (gRMT_intr_handle == NULL)
- esp_intr_alloc(ETS_RMT_INTR_SOURCE, 0, interruptHandler, 0, &gRMT_intr_handle);
+ esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_LEVEL3, interruptHandler, 0, &gRMT_intr_handle);
}
gInitialized = true;
}
- virtual void IRAM_ATTR initTimer()
- {
- mLastFillTime = 0;
- mTotalTime = 0;
- mTimeCount = 0;
- }
-
- virtual void IRAM_ATTR updateTimer()
- {
- uint32_t current = __clock_cycles();
- if (mLastFillTime != 0) {
- mTotalTime += (current - mLastFillTime);
- mTimeCount++;
- }
- mLastFillTime = current;
- }
-
- virtual void IRAM_ATTR printTimer()
- {
- if (mTimeCount > 0) {
- uint32_t ave = mTotalTime / mTimeCount;
- Serial.print("Controller on pin ");
- Serial.print(mPin);
- Serial.print(" : ");
- Serial.print(ave);
- Serial.print(" cycles per fill with ");
- Serial.print(mTimeCount);
- Serial.println(" fills");
- }
- }
-
// -- Show pixels
// This is the main entry point for the controller.
virtual void IRAM_ATTR showPixels(PixelController<RGB_ORDER> & pixels)
@@ -341,9 +306,6 @@ protected:
(*mPixels) = pixels;
}
- // -- Keep track of timing between buffer fills, for debugging
- initTimer();
-
// -- Keep track of the number of strips we've seen
gNumStarted++;
@@ -359,19 +321,18 @@ protected:
channel++;
}
+ // -- Start them all
+ for (int i = 0; i < channel; i++) {
+ ClocklessController * pController = static_cast<ClocklessController*>(gControllers[i]);
+ rmt_tx_start(pController->mRMT_channel, true);
+ }
+
// -- Wait here while the rest of the data is sent. The interrupt handler
// will keep refilling the RMT buffers until it is all sent; then it
// gives the semaphore back.
xSemaphoreTake(gTX_sem, portMAX_DELAY);
xSemaphoreGive(gTX_sem);
- if (FASTLED_RMT_SHOW_TIMER) {
- for (int i = 0; i < gNumControllers; i++) {
- ClocklessController * pController = static_cast<ClocklessController*>(gControllers[i]);
- pController->printTimer();
- }
- }
-
// -- Reset the counters
gNumStarted = 0;
gNumDone = 0;
@@ -383,7 +344,7 @@ protected:
// This function is only used when the user chooses to use the
// built-in RMT driver, which needs all of the RMT pulses
// up-front.
- virtual void convertAllPixelData(PixelController<RGB_ORDER> & pixels)
+ void convertAllPixelData(PixelController<RGB_ORDER> & pixels)
{
// -- Compute the pulse values for the whole strip at once.
// Requires a large buffer
@@ -458,25 +419,18 @@ protected:
} else {
// -- Use our custom driver to send the data incrementally
- // -- Turn on the interrupts
- rmt_set_tx_intr_en(mRMT_channel, true);
-
// -- Initialize the counters that keep track of where we are in
// the pixel data.
mRMT_mem_ptr = & (RMTMEM.chan[mRMT_channel].data32[0].val);
mCurPulse = 0;
mCurColor = 0;
- // -- Fill both halves of the buffer
- fillHalfRMTBuffer();
- fillHalfRMTBuffer();
- updateTimer();
+ // -- Store 2 pixels worth of data (two "buffers" full)
+ fillNext();
+ fillNext();
// -- Turn on the interrupts
rmt_set_tx_intr_en(mRMT_channel, true);
-
- // -- Start the RMT TX operation
- rmt_tx_start(mRMT_channel, true);
}
}
@@ -504,8 +458,12 @@ protected:
} else {
// -- Otherwise, if there are still controllers waiting, then
// start the next one on this channel
- if (gNext < gNumControllers)
+ if (gNext < gNumControllers) {
startNext(channel);
+ // -- Start the RMT TX operation
+ // (I'm not sure if this is necessary here)
+ rmt_tx_start(controller->mRMT_channel, true);
+ }
}
}
@@ -533,8 +491,7 @@ protected:
// -- Refill the half of the buffer that we just finished,
// allowing the other half to proceed.
ClocklessController * controller = static_cast<ClocklessController*>(gOnChannel[channel]);
- controller->updateTimer();
- controller->fillHalfRMTBuffer();
+ controller->fillNext();
} else {
// -- Transmission is complete on this channel
if (intr_st & BIT(tx_done_bit)) {
@@ -546,6 +503,58 @@ protected:
}
}
+ // -- Fill RMT buffer
+ // Puts one pixel's worth of data into the next 24 slots in the RMT memory
+ void IRAM_ATTR fillNext()
+ {
+ if (mPixels->has(1)) {
+ uint32_t t1 = __clock_cycles();
+
+ uint32_t one_val = mOne.val;
+ uint32_t zero_val = mZero.val;
+
+ // -- Get a pixel's worth of data
+ uint8_t byte0 = mPixels->loadAndScale0();
+ uint8_t byte1 = mPixels->loadAndScale1();
+ uint8_t byte2 = mPixels->loadAndScale2();
+ mPixels->advanceData();
+ mPixels->stepDithering();
+
+ // -- Fill 24 slots in the RMT memory
+ register uint32_t pixel = byte0 << 24 | byte1 << 16 | byte2 << 8;
+
+ // -- Use locals for speed
+ volatile register uint32_t * pItem = mRMT_mem_ptr;
+ register uint16_t curPulse = mCurPulse;
+
+ // Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the
+ // rmt_item32_t value corresponding to the buffered bit value
+ for (register uint32_t j = 0; j < 24; j++) {
+ uint32_t val = (pixel & 0x80000000L) ? one_val : zero_val;
+ *pItem++ = val;
+ // Replaces: RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = val;
+
+ pixel <<= 1;
+ curPulse++;
+
+ if (curPulse == MAX_PULSES) {
+ pItem = & (RMTMEM.chan[mRMT_channel].data32[0].val);
+ curPulse = 0;
+ }
+ }
+
+ // -- Store the new values back into the object
+ mCurPulse = curPulse;
+ mRMT_mem_ptr = pItem;
+ } else {
+ // -- No more data; signal to the RMT we are done
+ for (uint32_t j = 0; j < 8; j++) {
+ * mRMT_mem_ptr++ = 0;
+ }
+ }
+ }
+
+ // NO LONGER USED
uint8_t IRAM_ATTR getNextByte() __attribute__ ((always_inline))
{
uint8_t byte;
@@ -574,12 +583,14 @@ protected:
return byte;
}
+
+ // NO LONGER USED
// -- Fill the RMT buffer
// This function fills the next 32 slots in the RMT write
// buffer with pixel data. It also handles the case where the
// pixel data is exhausted, so we need to fill the RMT buffer
// with zeros to signal that it's done.
- virtual void IRAM_ATTR fillHalfRMTBuffer()
+ void IRAM_ATTR fillHalfRMTBuffer()
{
uint32_t one_val = mOne.val;
uint32_t zero_val = mZero.val;
@@ -587,10 +598,31 @@ protected:
// -- Convert (up to) 32 bits of the raw pixel data into
// into RMT pulses that encode the zeros and ones.
int pulses = 0;
- uint32_t byteval;
+ register uint32_t byteval;
while (pulses < 32 && mPixels->has(1)) {
// -- Get one byte
- byteval = getNextByte();
+ // -- Cycle through the color channels
+ switch (mCurColor) {
+ case 0:
+ byteval = mPixels->loadAndScale0();
+ break;
+ case 1:
+ byteval = mPixels->loadAndScale1();
+ break;
+ case 2:
+ byteval = mPixels->loadAndScale2();
+ mPixels->advanceData();
+ mPixels->stepDithering();
+ break;
+ default:
+ // -- This is bad!
+ byteval = 0;
+ }
+
+ mCurColor++;
+ if (mCurColor == NUM_COLOR_CHANNELS) mCurColor = 0;
+
+ // byteval = getNextByte();
byteval <<= 24;
// Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the
// rmt_item32_t value corresponding to the buffered bit value
@@ -616,7 +648,7 @@ protected:
}
// -- When we have filled the back half the buffer, reset the position to the first half
- if (mCurPulse >= MAX_PULSES*2) {
+ if (mCurPulse == MAX_PULSES) {
mRMT_mem_ptr = & (RMTMEM.chan[mRMT_channel].data32[0].val);
mCurPulse = 0;
}