From 38d97dbca3a1921433e0a60f793071002635be6f Mon Sep 17 00:00:00 2001 From: Brian Bulkowski Date: Sat, 18 Jul 2020 19:22:02 -0700 Subject: [PATCH] Update to sam guyer's FastLED fork. Should reduce or eliminate flashing. --- components/FastLED-idf/CMakeLists.txt | 1 + components/FastLED-idf/fastspi.h | 5 + components/FastLED-idf/lib8tion.h | 38 +- components/FastLED-idf/lib8tion/random8.h | 10 +- components/FastLED-idf/pixelset.h | 6 +- components/FastLED-idf/pixeltypes.h | 16 +- components/FastLED-idf/platforms.h | 4 +- .../platforms/esp/32/clockless_esp32.h.orig | 786 ------------------ .../platforms/esp/32/clockless_i2s_esp32.h | 18 +- .../platforms/esp/32/clockless_rmt_esp32.cpp | 387 +++++++++ .../platforms/esp/32/clockless_rmt_esp32.h | 621 ++++---------- sdkconfig | 13 +- 12 files changed, 628 insertions(+), 1277 deletions(-) delete mode 100644 components/FastLED-idf/platforms/esp/32/clockless_esp32.h.orig create mode 100644 components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp diff --git a/components/FastLED-idf/CMakeLists.txt b/components/FastLED-idf/CMakeLists.txt index 73c530b..c27873f 100644 --- a/components/FastLED-idf/CMakeLists.txt +++ b/components/FastLED-idf/CMakeLists.txt @@ -13,6 +13,7 @@ set(srcs "wiring.cpp" "hal/esp32-hal-misc.c" "hal/esp32-hal-gpio.c" + "platforms/esp/32/clockless_rmt_esp32.cpp" ) # everything needs the ESP32 flag, not sure why this won't work diff --git a/components/FastLED-idf/fastspi.h b/components/FastLED-idf/fastspi.h index 38e8eab..2245ffe 100644 --- a/components/FastLED-idf/fastspi.h +++ b/components/FastLED-idf/fastspi.h @@ -49,6 +49,11 @@ template class SPIOutput : public NRF52SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; #endif +#if defined(FASTLED_APOLLO3) && defined(FASTLED_ALL_PINS_HARDWARE_SPI) +template +class SPIOutput : public APOLLO3HardwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {}; +#endif + #if defined(SPI_DATA) && defined(SPI_CLOCK) #if defined(FASTLED_TEENSY3) && defined(ARM_HARDWARE_SPI) diff --git a/components/FastLED-idf/lib8tion.h b/components/FastLED-idf/lib8tion.h index 24c5d0a..62db2b1 100644 --- a/components/FastLED-idf/lib8tion.h +++ b/components/FastLED-idf/lib8tion.h @@ -181,7 +181,7 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun #if !defined(__AVR__) #include // for memmove, memcpy, and memset if not defined here -#endif +#endif // end of !defined(__AVR__) #if defined(__arm__) @@ -195,7 +195,7 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun // Generic ARM #define QADD8_C 1 #define QADD7_C 1 -#endif +#endif // end of defined(FASTLED_TEENSY3) #define QSUB8_C 1 #define SCALE8_C 1 @@ -213,6 +213,30 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun #define AVG15_C 1 #define BLEND8_C 1 +// end of #if defined(__arm__) + +#elif defined(ARDUINO_ARCH_APOLLO3) + +// Default to using the standard C functions for now +#define QADD8_C 1 +#define QADD7_C 1 +#define QSUB8_C 1 +#define SCALE8_C 1 +#define SCALE16BY8_C 1 +#define SCALE16_C 1 +#define ABS8_C 1 +#define MUL8_C 1 +#define QMUL8_C 1 +#define ADD8_C 1 +#define SUB8_C 1 +#define EASE8_C 1 +#define AVG8_C 1 +#define AVG7_C 1 +#define AVG16_C 1 +#define AVG15_C 1 +#define BLEND8_C 1 + +// end of #elif defined(ARDUINO_ARCH_APOLLO3) #elif defined(__AVR__) @@ -274,7 +298,9 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun #define QMUL8_AVRASM 0 #define EASE8_AVRASM 0 #define BLEND8_AVRASM 0 -#endif +#endif // end of !defined(LIB8_ATTINY) + +// end of #elif defined(__AVR__) #else @@ -811,6 +837,9 @@ public: #ifdef FASTLED_ARM int operator*(int v) { return (v*i) + ((v*f)>>F); } #endif +#ifdef FASTLED_APOLLO3 + int operator*(int v) { return (v*i) + ((v*f)>>F); } +#endif }; template static uint32_t operator*(uint32_t v, q & q) { return q * v; } @@ -820,6 +849,9 @@ template static int16_t operator*(int16_t v, q & q #ifdef FASTLED_ARM template static int operator*(int v, q & q) { return q * v; } #endif +#ifdef FASTLED_APOLLO3 +template static int operator*(int v, q & q) { return q * v; } +#endif /// A 4.4 integer (4 bits integer, 4 bits fraction) typedef q q44; diff --git a/components/FastLED-idf/lib8tion/random8.h b/components/FastLED-idf/lib8tion/random8.h index ba60cf5..d834abd 100644 --- a/components/FastLED-idf/lib8tion/random8.h +++ b/components/FastLED-idf/lib8tion/random8.h @@ -12,13 +12,19 @@ #define FASTLED_RAND16_2053 ((uint16_t)(2053)) #define FASTLED_RAND16_13849 ((uint16_t)(13849)) +#if defined(LIB8_ATTINY) +#define APPLY_FASTLED_RAND16_2053(x) (x << 11) + (x << 2) + x +#else +#define APPLY_FASTLED_RAND16_2053(x) (x * FASTLED_RAND16_2053) +#endif + /// random number seed extern uint16_t rand16seed;// = RAND16_SEED; /// Generate an 8-bit random number LIB8STATIC uint8_t random8() { - rand16seed = (rand16seed * FASTLED_RAND16_2053) + FASTLED_RAND16_13849; + rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849; // return the sum of the high and low bytes, for better // mixing and non-sequential correlation return (uint8_t)(((uint8_t)(rand16seed & 0xFF)) + @@ -28,7 +34,7 @@ LIB8STATIC uint8_t random8() /// Generate a 16 bit random number LIB8STATIC uint16_t random16() { - rand16seed = (rand16seed * FASTLED_RAND16_2053) + FASTLED_RAND16_13849; + rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849; return rand16seed; } diff --git a/components/FastLED-idf/pixelset.h b/components/FastLED-idf/pixelset.h index 9c69176..c9272ef 100644 --- a/components/FastLED-idf/pixelset.h +++ b/components/FastLED-idf/pixelset.h @@ -7,9 +7,9 @@ #include #endif -/// Represents a set of CRGB led objects. Provides the [] array operator, and works like a normal array in that case. -/// This should be kept in sync with the set of functions provided by CRGB as well as functions in colorutils. Note -/// that a pixel set is a window into another set of led data, it is not its own set of led data. +///// Represents a set of CRGB led objects. Provides the [] array operator, and works like a normal array in that case. +///// This should be kept in sync with the set of functions provided by CRGB as well as functions in colorutils. Note +///// that a pixel set is a window into another set of led data, it is not its own set of led data. template class CPixelView { public: diff --git a/components/FastLED-idf/pixeltypes.h b/components/FastLED-idf/pixeltypes.h index ff327fd..f4e5706 100644 --- a/components/FastLED-idf/pixeltypes.h +++ b/components/FastLED-idf/pixeltypes.h @@ -38,6 +38,18 @@ struct CHSV { uint8_t raw[3]; }; + /// Array access operator to index into the chsv object + inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline)) + { + return raw[x]; + } + + /// Array access operator to index into the chsv object + inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) + { + return raw[x]; + } + /// default values are UNITIALIZED inline CHSV() __attribute__((always_inline)) { @@ -106,7 +118,7 @@ struct CRGB { uint8_t raw[3]; }; - /// Array access operator to index into the crgb object + /// Array access operator to index into the crgb object inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline)) { return raw[x]; @@ -478,7 +490,7 @@ struct CRGB { uint8_t max = red; if( green > max) max = green; if( blue > max) max = blue; - + // stop div/0 when color is black if(max > 0) { uint16_t factor = ((uint16_t)(limit) * 256) / max; diff --git a/components/FastLED-idf/platforms.h b/components/FastLED-idf/platforms.h index 411a00f..7969c9e 100644 --- a/components/FastLED-idf/platforms.h +++ b/components/FastLED-idf/platforms.h @@ -1,8 +1,6 @@ #ifndef __INC_PLATFORMS_H #define __INC_PLATFORMS_H -#define ESP32 - #include "FastLED.h" #include "fastled_config.h" @@ -36,6 +34,8 @@ #include "platforms/esp/8266/fastled_esp8266.h" #elif defined(ESP32) #include "platforms/esp/32/fastled_esp32.h" +#elif defined(ARDUINO_ARCH_APOLLO3) +#include "platforms/apollo3/fastled_apollo3.h" #else // AVR platforms #include "platforms/avr/fastled_avr.h" diff --git a/components/FastLED-idf/platforms/esp/32/clockless_esp32.h.orig b/components/FastLED-idf/platforms/esp/32/clockless_esp32.h.orig deleted file mode 100644 index e0cd00d..0000000 --- a/components/FastLED-idf/platforms/esp/32/clockless_esp32.h.orig +++ /dev/null @@ -1,786 +0,0 @@ -/* - * Integration into FastLED ClocklessController 2017 Thomas Basler - * - * Modifications Copyright (c) 2017 Martin F. Falatic - * - * Modifications Copyright (c) 2018 Samuel Z. Guyer - * - * ESP32 support is provided using the RMT peripheral device -- a unit - * on the chip designed specifically for generating (and receiving) - * precisely-timed digital signals. Nominally for use in infrared - * remote controls, we use it to generate the signals for clockless - * LED strips. The main advantage of using the RMT device is that, - * once programmed, it generates the signal asynchronously, allowing - * the CPU to continue executing other code. It is also not vulnerable - * to interrupts or other timing problems that could disrupt the signal. - * - * The implementation strategy is borrowed from previous work and from - * the RMT support built into the ESP32 IDF. The RMT device has 8 - * channels, which can be programmed independently to send sequences - * of high/low bits. Memory for each channel is limited, however, so - * in order to send a long sequence of bits, we need to continuously - * refill the buffer until all the data is sent. To do this, we fill - * half the buffer and then set an interrupt to go off when that half - * is sent. Then we refill that half while the second half is being - * sent. This strategy effectively overlaps computation (by the CPU) - * and communication (by the RMT). - * - * Since the RMT device only has 8 channels, we need a strategy to - * allow more than 8 LED controllers. Our driver assigns controllers - * to channels on the fly, queuing up controllers as necessary until a - * channel is free. The main showPixels routine just fires off the - * first 8 controllers; the interrupt handler starts new controllers - * asynchronously as previous ones finish. So, for example, it can - * send the data for 8 controllers simultaneously, but 16 controllers - * would take approximately twice as much time. - * - * There is a #define that allows a program to control the total - * number of channels that the driver is allowed to use. It defaults - * to 8 -- use all the channels. Setting it to 1, for example, results - * in fully serial output: - * - * #define FASTLED_RMT_MAX_CHANNELS 1 - * - * OTHER RMT APPLICATIONS - * - * The default FastLED driver takes over control of the RMT interrupt - * handler, making it hard to use the RMT device for other - * (non-FastLED) purposes. You can change it's behavior to use the ESP - * core driver instead, allowing other RMT applications to - * co-exist. To switch to this mode, add the following directive - * before you include FastLED.h: - * - * #define FASTLED_RMT_BUILTIN_DRIVER - * - * There may be a performance penalty for using this mode. We need to - * compute the RMT signal for the entire LED strip ahead of time, - * rather than overlapping it with communication. We also need a large - * buffer to hold the signal specification. Each bit of pixel data is - * represented by a 32-bit pulse specification, so it is a 32X blow-up - * in memory use. - * - * - * Based on public domain code created 19 Nov 2016 by Chris Osborn - * http://insentricity.com * - * - */ -/* - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -FASTLED_NAMESPACE_BEGIN - -#ifdef __cplusplus -extern "C" { -#endif - -#include "esp32-hal.h" -#include "esp_intr.h" -#include "driver/gpio.h" -#include "driver/rmt.h" -#include "driver/periph_ctrl.h" -#include "freertos/semphr.h" -#include "soc/rmt_struct.h" - -#include "esp_log.h" - -#ifdef __cplusplus -} -#endif - -__attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { - uint32_t cyc; - __asm__ __volatile__ ("rsr %0,ccount":"=a" (cyc)); - return cyc; -} - -#define FASTLED_HAS_CLOCKLESS 1 - -// -- 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 */ - -// -- Convert ESP32 cycles back into nanoseconds -#define ESPCLKS_TO_NS(_CLKS) (((long)(_CLKS) * 1000L) / F_CPU_MHZ) - -// -- Convert nanoseconds into RMT cycles -#define F_CPU_RMT ( 80000000L) -#define NS_PER_SEC (1000000000L) -#define CYCLES_PER_SEC (F_CPU_RMT/DIVIDER) -#define NS_PER_CYCLE ( NS_PER_SEC / CYCLES_PER_SEC ) -#define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE ) - -// -- Convert ESP32 cycles to RMT cycles -#define TO_RMT_CYCLES(_CLKS) NS_TO_CYCLES(ESPCLKS_TO_NS(_CLKS)) - -// -- Number of cycles to signal the strip to latch -#define RMT_RESET_DURATION NS_TO_CYCLES(50000) - -// -- Core or custom driver -#ifndef FASTLED_RMT_BUILTIN_DRIVER -#define FASTLED_RMT_BUILTIN_DRIVER false -#endif - -// -- Max number of controllers we can support -#ifndef FASTLED_RMT_MAX_CONTROLLERS -#define FASTLED_RMT_MAX_CONTROLLERS 32 -#endif - -// -- Number of RMT channels to use (up to 8) -// Redefine this value to 1 to force serial output -#ifndef FASTLED_RMT_MAX_CHANNELS -#define FASTLED_RMT_MAX_CHANNELS 8 -#endif - -// -- Array of all controllers -static CLEDController * gControllers[FASTLED_RMT_MAX_CONTROLLERS]; - -// -- Current set of active controllers, indexed by the RMT -// channel assigned to them. -static CLEDController * gOnChannel[FASTLED_RMT_MAX_CHANNELS]; - -static int gNumControllers = 0; -static int gNumStarted = 0; -static int gNumDone = 0; -static int gNext = 0; - -static intr_handle_t gRMT_intr_handle = NULL; - -// -- Global semaphore for the whole show process -// Semaphore is not given until all data has been sent -static xSemaphoreHandle gTX_sem = NULL; - -static bool gInitialized = false; - -template -class ClocklessController : public CPixelLEDController -{ - // -- RMT has 8 channels, numbered 0 to 7 - rmt_channel_t mRMT_channel; - - // -- Store the GPIO pin - gpio_num_t mPin; -<<<<<<< HEAD - - // -- This instantiation forces a check on the pin choice - FastPin mFastPin; - - // -- Timing values for zero and one bits, derived from T1, T2, and T3 - rmt_item32_t mZero; - rmt_item32_t mOne; - -======= - - // -- Timing values for zero and one bits, derived from T1, T2, and T3 - rmt_item32_t mZero; - rmt_item32_t mOne; - ->>>>>>> upstream/master - // -- State information for keeping track of where we are in the pixel data - PixelController * mPixels = NULL; - void * mPixelSpace = NULL; - uint8_t mRGB_channel; - uint16_t mCurPulse; - - // -- Buffer to hold all of the pulses. For the version that uses - // the RMT driver built into the ESP core. - rmt_item32_t * mBuffer; - uint16_t mBufferSize; - -public: - - virtual void init() - { - // -- Precompute rmt items corresponding to a zero bit and a one bit - // according to the timing values given in the template instantiation - // T1H - mOne.level0 = 1; - mOne.duration0 = TO_RMT_CYCLES(T1+T2); - // T1L - mOne.level1 = 0; - mOne.duration1 = TO_RMT_CYCLES(T3); - - // T0H - mZero.level0 = 1; - mZero.duration0 = TO_RMT_CYCLES(T1); - // T0L - mZero.level1 = 0; - mZero.duration1 = TO_RMT_CYCLES(T2 + T3); - -<<<<<<< HEAD - gControllers[gNumControllers] = this; - gNumControllers++; - - mPin = gpio_num_t(DATA_PIN); -======= - gControllers[gNumControllers] = this; - gNumControllers++; - - mPin = gpio_num_t(DATA_PIN); ->>>>>>> upstream/master - } - - virtual uint16_t getMaxRefreshRate() const { return 400; } - -protected: - - void initRMT() - { -<<<<<<< HEAD - // -- Only need to do this once - if (gInitialized) return; - - for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { - gOnChannel[i] = NULL; - - // -- RMT configuration for transmission - rmt_config_t rmt_tx; - rmt_tx.channel = rmt_channel_t(i); - rmt_tx.rmt_mode = RMT_MODE_TX; - rmt_tx.gpio_num = mPin; // The particular pin will be assigned later - rmt_tx.mem_block_num = 1; - rmt_tx.clk_div = DIVIDER; - rmt_tx.tx_config.loop_en = false; - rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; - rmt_tx.tx_config.carrier_en = false; - rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; - rmt_tx.tx_config.idle_output_en = true; - - // -- Apply the configuration - rmt_config(&rmt_tx); - - 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 - // 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); - } - } - - // -- Create a semaphore to block execution until all the controllers are done - if (gTX_sem == NULL) { - gTX_sem = xSemaphoreCreateBinary(); - xSemaphoreGive(gTX_sem); - } - - if ( ! FASTLED_RMT_BUILTIN_DRIVER) { - // -- Allocate the interrupt if we have not done so yet. This - // interrupt handler must work for all different kinds of - // 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); - } - - gInitialized = true; - } - - virtual void showPixels(PixelController & pixels) - { - if (gNumStarted == 0) { - // -- First controller: make sure everything is set up - initRMT(); - xSemaphoreTake(gTX_sem, portMAX_DELAY); - } - - // -- Initialize the local state, save a pointer to the pixel - // data. We need to make a copy because pixels is a local - // variable in the calling function, and this data structure - // needs to outlive this call to showPixels. - - if (mPixels != NULL) delete mPixels; - mPixels = new PixelController(pixels); - - // -- Keep track of the number of strips we've seen - gNumStarted++; - - // -- The last call to showPixels is the one responsible for doing - // all of the actual worl - if (gNumStarted == gNumControllers) { - gNext = 0; - - // -- First, fill all the available channels - int channel = 0; - while (channel < FASTLED_RMT_MAX_CHANNELS && gNext < gNumControllers) { - startNext(channel); - channel++; - } - - // -- 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); - - // -- Reset the counters - gNumStarted = 0; - gNumDone = 0; - gNext = 0; - } - } - - // -- Start up the next controller - // This method is static so that it can dispatch to the appropriate - // startOnChannel method of the given controller. - static void startNext(int channel) - { - if (gNext < gNumControllers) { - ClocklessController * pController = static_cast(gControllers[gNext]); - pController->startOnChannel(channel); - gNext++; - } - } - - virtual void startOnChannel(int channel) - { - // -- Assign this channel and configure the RMT - mRMT_channel = rmt_channel_t(channel); - - // -- Store a reference to this controller, so we can get it - // inside the interrupt handler - gOnChannel[channel] = this; - - // -- Assign the pin to this channel - rmt_set_pin(mRMT_channel, RMT_MODE_TX, mPin); - - if (FASTLED_RMT_BUILTIN_DRIVER) { - // -- Use the built-in RMT driver to send all the data in one shot - rmt_register_tx_end_callback(doneOnChannel, 0); - writeAllRMTItems(); - } 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. - mCurPulse = 0; - mRGB_channel = 0; - - // -- Fill both halves of the buffer - fillHalfRMTBuffer(); - fillHalfRMTBuffer(); - - // -- Turn on the interrupts - rmt_set_tx_intr_en(mRMT_channel, true); - - // -- Start the RMT TX operation - rmt_tx_start(mRMT_channel, true); - } - } - - static void doneOnChannel(rmt_channel_t channel, void * arg) - { - ClocklessController * controller = static_cast(gOnChannel[channel]); - portBASE_TYPE HPTaskAwoken = 0; - - // -- Turn off output on the pin - gpio_matrix_out(controller->mPin, 0x100, 0, 0); - - gOnChannel[channel] = NULL; - gNumDone++; - - if (gNumDone == gNumControllers) { - // -- If this is the last controller, signal that we are all done - xSemaphoreGiveFromISR(gTX_sem, &HPTaskAwoken); - if(HPTaskAwoken == pdTRUE) portYIELD_FROM_ISR(); - } else { - // -- Otherwise, if there are still controllers waiting, then - // start the next one on this channel - if (gNext < gNumControllers) - startNext(channel); - } -======= - // -- Only need to do this once - if (gInitialized) return; - - for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { - gOnChannel[i] = NULL; - - // -- RMT configuration for transmission - rmt_config_t rmt_tx; - rmt_tx.channel = rmt_channel_t(i); - rmt_tx.rmt_mode = RMT_MODE_TX; - rmt_tx.gpio_num = mPin; // The particular pin will be assigned later - rmt_tx.mem_block_num = 1; - rmt_tx.clk_div = DIVIDER; - rmt_tx.tx_config.loop_en = false; - rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; - rmt_tx.tx_config.carrier_en = false; - rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; - rmt_tx.tx_config.idle_output_en = true; - - // -- Apply the configuration - rmt_config(&rmt_tx); - - 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 - // 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); - } - } - - // -- Create a semaphore to block execution until all the controllers are done - if (gTX_sem == NULL) { - gTX_sem = xSemaphoreCreateBinary(); - xSemaphoreGive(gTX_sem); - } - - if ( ! FASTLED_RMT_BUILTIN_DRIVER) { - // -- Allocate the interrupt if we have not done so yet. This - // interrupt handler must work for all different kinds of - // 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); - } - - gInitialized = true; - } - - virtual void showPixels(PixelController & pixels) - { - if (gNumStarted == 0) { - // -- First controller: make sure everything is set up - initRMT(); - xSemaphoreTake(gTX_sem, portMAX_DELAY); - } - - // -- Initialize the local state, save a pointer to the pixel - // data. We need to make a copy because pixels is a local - // variable in the calling function, and this data structure - // needs to outlive this call to showPixels. - - if (mPixels != NULL) delete mPixels; - mPixels = new PixelController(pixels); - - // -- Keep track of the number of strips we've seen - gNumStarted++; - - // -- The last call to showPixels is the one responsible for doing - // all of the actual worl - if (gNumStarted == gNumControllers) { - gNext = 0; - - // -- First, fill all the available channels - int channel = 0; - while (channel < FASTLED_RMT_MAX_CHANNELS && gNext < gNumControllers) { - startNext(channel); - channel++; - } - - // -- 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); - - // -- Reset the counters - gNumStarted = 0; - gNumDone = 0; - gNext = 0; - } - } - - // -- Start up the next controller - // This method is static so that it can dispatch to the appropriate - // startOnChannel method of the given controller. - static void startNext(int channel) - { - if (gNext < gNumControllers) { - ClocklessController * pController = static_cast(gControllers[gNext]); - pController->startOnChannel(channel); - gNext++; - } - } - - virtual void startOnChannel(int channel) - { - // -- Assign this channel and configure the RMT - mRMT_channel = rmt_channel_t(channel); - - // -- Store a reference to this controller, so we can get it - // inside the interrupt handler - gOnChannel[channel] = this; - - // -- Assign the pin to this channel - rmt_set_pin(mRMT_channel, RMT_MODE_TX, mPin); - - if (FASTLED_RMT_BUILTIN_DRIVER) { - // -- Use the built-in RMT driver to send all the data in one shot - rmt_register_tx_end_callback(doneOnChannel, 0); - writeAllRMTItems(); - } 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. - mCurPulse = 0; - mRGB_channel = 0; - - // -- Fill both halves of the buffer - fillHalfRMTBuffer(); - fillHalfRMTBuffer(); - - // -- Turn on the interrupts - rmt_set_tx_intr_en(mRMT_channel, true); - - // -- Start the RMT TX operation - rmt_tx_start(mRMT_channel, true); - } - } - - static void doneOnChannel(rmt_channel_t channel, void * arg) - { - ClocklessController * controller = static_cast(gOnChannel[channel]); - portBASE_TYPE HPTaskAwoken = 0; - - // -- Turn off output on the pin - gpio_matrix_out(controller->mPin, 0x100, 0, 0); - - gOnChannel[channel] = NULL; - gNumDone++; - - if (gNumDone == gNumControllers) { - // -- If this is the last controller, signal that we are all done - xSemaphoreGiveFromISR(gTX_sem, &HPTaskAwoken); - if(HPTaskAwoken == pdTRUE) portYIELD_FROM_ISR(); - } else { - // -- Otherwise, if there are still controllers waiting, then - // start the next one on this channel - if (gNext < gNumControllers) - startNext(channel); - } ->>>>>>> upstream/master - } - - static IRAM_ATTR void interruptHandler(void *arg) - { - // -- The basic structure of this code is borrowed from the - // interrupt handler in esp-idf/components/driver/rmt.c - uint32_t intr_st = RMT.int_st.val; - uint8_t channel; - - for (channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; channel++) { - int tx_done_bit = channel * 3; - int tx_next_bit = channel + 24; - - if (gOnChannel[channel] != NULL) { - -<<<<<<< HEAD - ClocklessController * controller = static_cast(gOnChannel[channel]); - - // -- More to send on this channel - if (intr_st & BIT(tx_next_bit)) { - RMT.int_clr.val |= BIT(tx_next_bit); - - // -- Refill the half of the buffer that we just finished, - // allowing the other half to proceed. - controller->fillHalfRMTBuffer(); - } - - // -- Transmission is complete on this channel - if (intr_st & BIT(tx_done_bit)) { - RMT.int_clr.val |= BIT(tx_done_bit); - doneOnChannel(rmt_channel_t(channel), 0); -======= - ClocklessController * controller = static_cast(gOnChannel[channel]); - - // -- More to send on this channel - if (intr_st & BIT(tx_next_bit)) { - RMT.int_clr.val |= BIT(tx_next_bit); - - // -- Refill the half of the buffer that we just finished, - // allowing the other half to proceed. - controller->fillHalfRMTBuffer(); - } - - // -- Transmission is complete on this channel - if (intr_st & BIT(tx_done_bit)) { - RMT.int_clr.val |= BIT(tx_done_bit); - doneOnChannel(rmt_channel_t(channel), 0); ->>>>>>> upstream/master - } - } - } - } - - virtual void fillHalfRMTBuffer() - { - // -- Fill half of the RMT pulse buffer - - // The buffer holds 64 total pulse items, so this loop converts - // as many pixels as can fit in half of the buffer (MAX_PULSES = - // 32 items). In our case, each pixel consists of three bytes, - // each bit turns into one pulse item -- 24 items per pixel. So, - // each half of the buffer can hold 1 and 1/3 of a pixel. - - // The member variable mCurPulse keeps track of which of the 64 - // items we are writing. During the first call to this method it - // fills 0-31; in the second call it fills 32-63, and then wraps - // back around to zero. - - // When we run out of pixel data, just fill the remaining items - // with zero pulses. - - uint16_t pulse_count = 0; // Ranges from 0-31 (half a buffer) - uint32_t byteval = 0; - uint32_t one_val = mOne.val; - uint32_t zero_val = mZero.val; - bool done_strip = false; - - while (pulse_count < MAX_PULSES) { - if (! mPixels->has(1)) { -<<<<<<< HEAD - if (mCurPulse > 0) { - // -- Extend the last pulse to force the strip to latch. Honestly, I'm not - // sure if this is really necessary. - // RMTMEM.chan[mRMT_channel].data32[mCurPulse-1].duration1 = RMT_RESET_DURATION; - } -======= ->>>>>>> upstream/master - done_strip = true; - break; - } - - // -- Cycle through the R,G, and B values in the right order - switch (mRGB_channel) { - case 0: - byteval = mPixels->loadAndScale0(); - mRGB_channel = 1; - break; - case 1: - byteval = mPixels->loadAndScale1(); - mRGB_channel = 2; - break; - case 2: - byteval = mPixels->loadAndScale2(); - mPixels->advanceData(); - mPixels->stepDithering(); - mRGB_channel = 0; - break; - default: - break; - } - - 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 - for (register uint32_t j = 0; j < 8; j++) { - uint32_t val = (byteval & 0x80000000L) ? one_val : zero_val; - RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = val; - byteval <<= 1; - mCurPulse++; - pulse_count++; - } -<<<<<<< HEAD -======= - - if (done_strip) - RMTMEM.chan[mRMT_channel].data32[mCurPulse-1].duration1 = RMT_RESET_DURATION; ->>>>>>> upstream/master - } - - if (done_strip) { - // -- And fill the remaining items with zero pulses. The zero values triggers - // the tx_done interrupt. - while (pulse_count < MAX_PULSES) { - RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = 0; - mCurPulse++; - pulse_count++; - } - } - - // -- When we have filled the back half the buffer, reset the position to the first half - if (mCurPulse >= MAX_PULSES*2) - mCurPulse = 0; - } - - virtual void writeAllRMTItems() - { - // -- Compute the pulse values for the whole strip at once. - // Requires a large buffer -<<<<<<< HEAD - mBufferSize = mPixels->size() * 3 * 8; -======= - mBufferSize = mPixels->size() * 3 * 8; ->>>>>>> upstream/master - - // TODO: need a specific number here - if (mBuffer == NULL) { - mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t)); - } - - mCurPulse = 0; - mRGB_channel = 0; - uint32_t byteval = 0; - while (mPixels->has(1)) { - // -- Cycle through the R,G, and B values in the right order - switch (mRGB_channel) { - case 0: - byteval = mPixels->loadAndScale0(); - mRGB_channel = 1; - break; - case 1: - byteval = mPixels->loadAndScale1(); - mRGB_channel = 2; - break; - case 2: - byteval = mPixels->loadAndScale2(); - mPixels->advanceData(); - mPixels->stepDithering(); - mRGB_channel = 0; - break; - default: - break; - } - - 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 - for (register uint32_t j = 0; j < 8; j++) { - mBuffer[mCurPulse] = (byteval & 0x80000000L) ? mOne : mZero; - byteval <<= 1; - mCurPulse++; - } - } - - mBuffer[mCurPulse-1].duration1 = RMT_RESET_DURATION; - assert(mCurPulse == mBufferSize); - -<<<<<<< HEAD - rmt_write_items(mRMT_channel, mBuffer, mBufferSize, false); -======= - rmt_write_items(mRMT_channel, mBuffer, mBufferSize, false); ->>>>>>> upstream/master - } -}; - -FASTLED_NAMESPACE_END diff --git a/components/FastLED-idf/platforms/esp/32/clockless_i2s_esp32.h b/components/FastLED-idf/platforms/esp/32/clockless_i2s_esp32.h index fd5157a..6fd067b 100644 --- a/components/FastLED-idf/platforms/esp/32/clockless_i2s_esp32.h +++ b/components/FastLED-idf/platforms/esp/32/clockless_i2s_esp32.h @@ -199,7 +199,10 @@ class ClocklessController : public CPixelLEDController // -- Save the pixel controller PixelController * mPixels; -public: + // -- Make sure we can't call show() too quickly + CMinWait<50> mWait; + + public: void init() { @@ -363,7 +366,7 @@ protected: freq=1/(CLOCK_DIVIDER_N+(double)CLOCK_DIVIDER_B/CLOCK_DIVIDER_A); freq=freq*I2S_BASE_CLK; // Serial.printf("calculted for i2s frequency:%f Mhz N:%d B:%d A:%d\n",freq/1000000,CLOCK_DIVIDER_N,CLOCK_DIVIDER_B,CLOCK_DIVIDER_A); - double pulseduration=1000000000/freq; + // double pulseduration=1000000000/freq; // Serial.printf("Pulse duration: %f ns\n",pulseduration); // gPulsesPerBit = (T1ns + T2ns + T3ns)/FASTLED_I2S_NS_PER_PULSE; @@ -510,8 +513,8 @@ protected: // -- Allocate i2s interrupt SET_PERI_REG_BITS(I2S_INT_ENA_REG(I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S); - esp_err_t e = esp_intr_alloc(interruptSource, 0, // ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL3, - &interruptHandler, 0, &gI2S_intr_handle); + esp_intr_alloc(interruptSource, 0, // ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL3, + &interruptHandler, 0, &gI2S_intr_handle); // -- Create a semaphore to block execution until all the controllers are done if (gTX_sem == NULL) { @@ -574,6 +577,9 @@ protected: fillBuffer(); fillBuffer(); + // -- Make sure it's been at least 50ms since last show + mWait.wait(); + i2sStart(); // -- Wait here while the rest of the data is sent. The interrupt handler @@ -584,6 +590,8 @@ protected: i2sStop(); + mWait.mark(); + // -- Reset the counters gNumStarted = 0; } @@ -645,7 +653,7 @@ protected: } // -- Transpose and encode the pixel data for the DMA buffer - int buf_index = 0; + // int buf_index = 0; for (int channel = 0; channel < NUM_COLOR_CHANNELS; channel++) { // -- Tranpose each array: all the bit 7's, then all the bit 6's, ... diff --git a/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp b/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp new file mode 100644 index 0000000..2a31dd4 --- /dev/null +++ b/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp @@ -0,0 +1,387 @@ + + +#define FASTLED_INTERNAL +#include "FastLED.h" + +// -- Forward reference +class ESP32RMTController; + +// -- Array of all controllers +// This array is filled at the time controllers are registered +// (Usually when the sketch calls addLeds) +static ESP32RMTController * gControllers[FASTLED_RMT_MAX_CONTROLLERS]; + +// -- Current set of active controllers, indexed by the RMT +// channel assigned to them. +static ESP32RMTController * gOnChannel[FASTLED_RMT_MAX_CHANNELS]; + +static int gNumControllers = 0; +static int gNumStarted = 0; +static int gNumDone = 0; +static int gNext = 0; + +static intr_handle_t gRMT_intr_handle = NULL; + + +// -- Global semaphore for the whole show process +// Semaphore is not given until all data has been sent +static xSemaphoreHandle gTX_sem = NULL; + +static bool gInitialized = false; + +ESP32RMTController::ESP32RMTController(int DATA_PIN, int T1, int T2, int T3) + : mPixelData(0), + mSize(0), + mCur(0), + mWhichHalf(0), + mBuffer(0), + mBufferSize(0), + mCurPulse(0) +{ + // -- Precompute rmt items corresponding to a zero bit and a one bit + // according to the timing values given in the template instantiation + // T1H + mOne.level0 = 1; + mOne.duration0 = ESP_TO_RMT_CYCLES(T1+T2); // TO_RMT_CYCLES(T1+T2); + // T1L + mOne.level1 = 0; + mOne.duration1 = ESP_TO_RMT_CYCLES(T3); // TO_RMT_CYCLES(T3); + + // T0H + mZero.level0 = 1; + mZero.duration0 = ESP_TO_RMT_CYCLES(T1); // TO_RMT_CYCLES(T1); + // T0L + mZero.level1 = 0; + mZero.duration1 = ESP_TO_RMT_CYCLES(T2+T3); // TO_RMT_CYCLES(T2 + T3); + + gControllers[gNumControllers] = this; + gNumControllers++; + + mPin = gpio_num_t(DATA_PIN); +} + +// -- Getters and setters for use in ClocklessController +uint8_t * ESP32RMTController::getPixelData(int size_in_bytes) +{ + if (mPixelData == 0) { + mSize = size_in_bytes; + mPixelData = (uint8_t *) calloc( mSize, sizeof(uint8_t)); + } + return mPixelData; +} + +// -- Initialize RMT subsystem +// This only needs to be done once +void ESP32RMTController::init() +{ + if (gInitialized) return; + + for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { + gOnChannel[i] = NULL; + + // -- RMT configuration for transmission + rmt_config_t rmt_tx; + rmt_tx.channel = rmt_channel_t(i); + rmt_tx.rmt_mode = RMT_MODE_TX; + rmt_tx.gpio_num = gpio_num_t(0); // The particular pin will be assigned later + rmt_tx.mem_block_num = 1; + rmt_tx.clk_div = DIVIDER; + rmt_tx.tx_config.loop_en = false; + rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; + rmt_tx.tx_config.carrier_en = false; + rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; + rmt_tx.tx_config.idle_output_en = true; + + // -- Apply the configuration + rmt_config(&rmt_tx); + + if (FASTLED_RMT_BUILTIN_DRIVER) { + rmt_driver_install(rmt_channel_t(i), 0, 0); + } else { + // -- 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 part in preparation (kind of like double-buffering) + rmt_set_tx_thr_intr_en(rmt_channel_t(i), true, PULSES_PER_FILL); + } + } + + // -- Create a semaphore to block execution until all the controllers are done + if (gTX_sem == NULL) { + gTX_sem = xSemaphoreCreateBinary(); + xSemaphoreGive(gTX_sem); + } + + if ( ! FASTLED_RMT_BUILTIN_DRIVER) { + // -- Allocate the interrupt if we have not done so yet. This + // interrupt handler must work for all different kinds of + // 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, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, interruptHandler, 0, &gRMT_intr_handle); + } + + gInitialized = true; +} + +// -- Show this string of pixels +// This is the main entry point for the pixel controller +void ESP32RMTController::showPixels() +{ + if (gNumStarted == 0) { + // -- First controller: make sure everything is set up + ESP32RMTController::init(); + xSemaphoreTake(gTX_sem, portMAX_DELAY); + +#if FASTLED_ESP32_FLASH_LOCK == 1 + // -- Make sure no flash operations happen right now + spi_flash_op_lock(); +#endif + } + + // -- Keep track of the number of strips we've seen + gNumStarted++; + + // -- The last call to showPixels is the one responsible for doing + // all of the actual worl + if (gNumStarted == gNumControllers) { + gNext = 0; + + // -- First, fill all the available channels + int channel = 0; + while (channel < FASTLED_RMT_MAX_CHANNELS && gNext < gNumControllers) { + ESP32RMTController::startNext(channel); + channel++; + } + + // -- Make sure it's been at least 50us since last show + mWait.wait(); + + // -- Start them all + for (int i = 0; i < channel; i++) { + ESP32RMTController * pController = gControllers[i]; + pController->tx_start(); + } + + // -- 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); + + mWait.mark(); + + // -- Reset the counters + gNumStarted = 0; + gNumDone = 0; + gNext = 0; + +#if FASTLED_ESP32_FLASH_LOCK == 1 + // -- Release the lock on flash operations + spi_flash_op_unlock(); +#endif + } +} + +// -- Start up the next controller +// This method is static so that it can dispatch to the +// appropriate startOnChannel method of the given controller. +void ESP32RMTController::startNext(int channel) +{ + if (gNext < gNumControllers) { + ESP32RMTController * pController = gControllers[gNext]; + pController->startOnChannel(channel); + gNext++; + } +} + +// -- Start this controller on the given channel +// This function just initiates the RMT write; it does not wait +// for it to finish. +void ESP32RMTController::startOnChannel(int channel) +{ + // -- Assign this channel and configure the RMT + mRMT_channel = rmt_channel_t(channel); + + // -- Store a reference to this controller, so we can get it + // inside the interrupt handler + gOnChannel[channel] = this; + + // -- Assign the pin to this channel + rmt_set_pin(mRMT_channel, RMT_MODE_TX, mPin); + + if (FASTLED_RMT_BUILTIN_DRIVER) { + // -- Use the built-in RMT driver to send all the data in one shot + rmt_register_tx_end_callback(doneOnChannel, 0); + rmt_write_items(mRMT_channel, mBuffer, mBufferSize, false); + } else { + // -- Use our custom driver to send the data incrementally + + // -- Initialize the counters that keep track of where we are in + // the pixel data. + mRMT_mem_ptr = & (RMTMEM.chan[mRMT_channel].data32[0].val); + mCur = 0; + mWhichHalf = 0; + + // -- Store 2 pixels worth of data (two "buffers" full) + fillNext(); + fillNext(); + + // -- Turn on the interrupts + rmt_set_tx_intr_en(mRMT_channel, true); + } +} + +// -- Start RMT transmission +// Setting this RMT flag is what actually kicks off the peripheral +void ESP32RMTController::tx_start() +{ + // dev->conf_ch[channel].conf1.tx_start = 1; + rmt_tx_start(mRMT_channel, true); +} + +// -- A controller is done +// This function is called when a controller finishes writing +// its data. It is called either by the custom interrupt +// handler (below), or as a callback from the built-in +// interrupt handler. It is static because we don't know which +// controller is done until we look it up. +void ESP32RMTController::doneOnChannel(rmt_channel_t channel, void * arg) +{ + ESP32RMTController * pController = gOnChannel[channel]; + portBASE_TYPE HPTaskAwoken = 0; + + // -- Turn off output on the pin + // SZG: Do I really need to do this? + // gpio_matrix_out(pController->mPin, 0x100, 0, 0); + + gOnChannel[channel] = NULL; + gNumDone++; + + if (gNumDone == gNumControllers) { + // -- If this is the last controller, signal that we are all done + if (FASTLED_RMT_BUILTIN_DRIVER) { + xSemaphoreGive(gTX_sem); + } else { + xSemaphoreGiveFromISR(gTX_sem, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) portYIELD_FROM_ISR(); + } + } else { + // -- Otherwise, if there are still controllers waiting, then + // start the next one on this channel + if (gNext < gNumControllers) { + startNext(channel); + pController->tx_start(); + } + } +} + +// -- Custom interrupt handler +// This interrupt handler handles two cases: a controller is +// done writing its data, or a controller needs to fill the +// next half of the RMT buffer with data. +void IRAM_ATTR ESP32RMTController::interruptHandler(void *arg) +{ + // -- The basic structure of this code is borrowed from the + // interrupt handler in esp-idf/components/driver/rmt.c + uint32_t intr_st = RMT.int_st.val; + uint8_t channel; + + for (channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; channel++) { + int tx_done_bit = channel * 3; + int tx_next_bit = channel + 24; + + ESP32RMTController * pController = gOnChannel[channel]; + if (pController != NULL) { + + // -- More to send on this channel + if (intr_st & BIT(tx_next_bit)) { + RMT.int_clr.val |= BIT(tx_next_bit); + + // -- Refill the half of the buffer that we just finished, + // allowing the other half to proceed. + pController->fillNext(); + } else { + // -- Transmission is complete on this channel + if (intr_st & BIT(tx_done_bit)) { + RMT.int_clr.val |= BIT(tx_done_bit); + doneOnChannel(rmt_channel_t(channel), 0); + } + } + } + } +} + +// -- Fill RMT buffer +// Puts 32 bits of pixel data into the next 32 slots in the RMT memory +// Each data bit is represented by a 32-bit RMT item that specifies how +// long to hold the signal high, followed by how long to hold it low. +void IRAM_ATTR ESP32RMTController::fillNext() +{ + if (mCur < mSize) { + // -- Get the zero and one values into local variables + uint32_t one_val = mOne.val; + uint32_t zero_val = mZero.val; + + // -- Fill 32 slots in the RMT memory + uint8_t a = mPixelData[mCur++]; + uint8_t b = mPixelData[mCur++]; + uint8_t c = mPixelData[mCur++]; + uint8_t d = mPixelData[mCur++]; + register uint32_t pixeldata = a << 24 | b << 16 | c << 8 | d; + + // -- Use locals for speed + volatile register uint32_t * pItem = mRMT_mem_ptr; + + // 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 < PULSES_PER_FILL; j++) { + *pItem++ = (pixeldata & 0x80000000L) ? one_val : zero_val; + // Replaces: RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = val; + + pixeldata <<= 1; + } + + // -- Flip to the other half, resetting the pointer if necessary + mWhichHalf++; + if (mWhichHalf == 2) { + pItem = & (RMTMEM.chan[mRMT_channel].data32[0].val); + mWhichHalf = 0; + } + + // -- Store the new pointer back into the object + mRMT_mem_ptr = pItem; + } else { + // -- No more data; signal to the RMT we are done + for (uint32_t j = 0; j < PULSES_PER_FILL; j++) { + * mRMT_mem_ptr++ = 0; + } + } +} + +// -- Init pulse buffer +// Set up the buffer that will hold all of the pulse items for this +// controller. +// This function is only used when the built-in RMT driver is chosen +void ESP32RMTController::initPulseBuffer(int size_in_bytes) +{ + if (mBuffer == 0) { + // -- Each byte has 8 bits, each bit needs a 32-bit RMT item + int size = size_in_bytes * 8 * 4; + + mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t)); + } + mCurPulse = 0; +} + +// -- Convert a byte into RMT pulses +// This function is only used when the built-in RMT driver is chosen +void ESP32RMTController::convertByte(uint32_t byteval) +{ + // -- Write one byte's worth of RMT pulses to the big buffer + byteval <<= 24; + for (register uint32_t j = 0; j < 8; j++) { + mBuffer[mCurPulse] = (byteval & 0x80000000L) ? mOne : mZero; + byteval <<= 1; + mCurPulse++; + } +} diff --git a/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.h b/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.h index 3a58b52..41b4a50 100644 --- a/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.h +++ b/components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.h @@ -1,6 +1,6 @@ /* * Integration into FastLED ClocklessController - * Copyright (c) 2018 Samuel Z. Guyer + * Copyright (c) 2018,2019,2020 Samuel Z. Guyer * Copyright (c) 2017 Thomas Basler * Copyright (c) 2017 Martin F. Falatic * @@ -49,7 +49,7 @@ * co-exist. To switch to this mode, add the following directive * before you include FastLED.h: * - * #define FASTLED_RMT_BUILTIN_DRIVER + * #define FASTLED_RMT_BUILTIN_DRIVER 1 * * There may be a performance penalty for using this mode. We need to * compute the RMT signal for the entire LED strip ahead of time, @@ -58,6 +58,27 @@ * represented by a 32-bit pulse specification, so it is a 32X blow-up * in memory use. * + * NEW: Use of Flash memory on the ESP32 can interfere with the timing + * of pixel output. The ESP-IDF system code disables all other + * code running on *either* core during these operation. To prevent + * this from happening, define this flag. It will force flash + * operations to wait until the show() is done. + * + * #define FASTLED_ESP32_FLASH_LOCK 1 + * + * NEW (June 2020): The RMT controller has been split into two + * classes: ClocklessController, which is an instantiation of the + * FastLED CPixelLEDController template, and ESP32RMTController, + * which just handles driving the RMT peripheral. One benefit of + * this design is that ESP32RMTContoller is not a template, so + * its methods can be marked with the IRAM_ATTR and end up in + * IRAM memory. Another benefit is that all of the color channel + * processing is done up-front, in the templated class, so we + * can fill the RMT buffers more quickly. + * + * IN THEORY, this design would also allow FastLED.show() to + * send the data while the program continues to prepare the next + * frame of data. * * Based on public domain code created 19 Nov 2016 by Chris Osborn * http://insentricity.com * @@ -101,8 +122,8 @@ extern "C" { #include "esp_log.h" -// needed to work around issue with driver problem in 4.1 and master around 2020 -#include "esp_idf_version.h" +extern void spi_flash_op_lock(void); +extern void spi_flash_op_unlock(void); #ifdef __cplusplus } @@ -117,17 +138,18 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { #define FASTLED_HAS_CLOCKLESS 1 #define NUM_COLOR_CHANNELS 3 +// NOT CURRENTLY IMPLEMENTED: // -- Set to true to print debugging information about timing // Useful for finding out if timing is being messed up by other things // on the processor (WiFi, for example) -#ifndef FASTLED_RMT_SHOW_TIMER -#define FASTLED_RMT_SHOW_TIMER false -#endif +//#ifndef FASTLED_RMT_SHOW_TIMER +//#define FASTLED_RMT_SHOW_TIMER false +//#endif // -- Configuration constants #define DIVIDER 2 /* 4, 8 still seem to work, but timings become marginal */ #define MAX_PULSES 64 /* A channel has a 64 "pulse" buffer */ -#define PULSES_PER_FILL 24 /* One pixel's worth of pulses */ +#define PULSES_PER_FILL 32 /* Half of the channel buffer */ // -- Convert ESP32 CPU cycles to RMT device cycles, taking into account the divider #define F_CPU_RMT ( 80000000L) @@ -140,21 +162,11 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { #define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE ) #define RMT_RESET_DURATION NS_TO_CYCLES(50000) -// -- Core or custom driver --- 'builtin' is the core driver which is supposedly slower +// -- Core or custom driver #ifndef FASTLED_RMT_BUILTIN_DRIVER - -// NOTE! -// there is an upstream issue with using the custom driver. This is in https://github.com/espressif/esp-idf/issues/5476 -// In this, it states that in order to use one of the functions, the upstream must be modified. -// -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) -#define FASTLED_RMT_BUILTIN_DRIVER true -#else #define FASTLED_RMT_BUILTIN_DRIVER false #endif -#endif - // -- Max number of controllers we can support #ifndef FASTLED_RMT_MAX_CONTROLLERS #define FASTLED_RMT_MAX_CONTROLLERS 32 @@ -166,228 +178,163 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { #define FASTLED_RMT_MAX_CHANNELS 8 #endif -// -- Array of all controllers -static CLEDController * gControllers[FASTLED_RMT_MAX_CONTROLLERS]; - -// -- Current set of active controllers, indexed by the RMT -// channel assigned to them. -static CLEDController * gOnChannel[FASTLED_RMT_MAX_CHANNELS]; - -static int gNumControllers = 0; -static int gNumStarted = 0; -static int gNumDone = 0; -static int gNext = 0; - -static intr_handle_t gRMT_intr_handle = NULL; - -// -- Global semaphore for the whole show process -// Semaphore is not given until all data has been sent -static xSemaphoreHandle gTX_sem = NULL; - -static bool gInitialized = false; - -// convert an integer channel into their enums. -// -static rmt_channel_t fastled_get_rmt_channel(int ch) { - assert((ch >= 0) && (ch < 8)); - switch (ch) { - case 0: - return(RMT_CHANNEL_0); - case 1: - return(RMT_CHANNEL_1); - case 2: - return(RMT_CHANNEL_2); - case 3: - return(RMT_CHANNEL_3); - case 4: - return(RMT_CHANNEL_4); - case 5: - return(RMT_CHANNEL_5); - case 6: - return(RMT_CHANNEL_6); - case 7: - return(RMT_CHANNEL_7); - } - return(RMT_CHANNEL_0); -} - - - -template -class ClocklessController : public CPixelLEDController +class ESP32RMTController { +private: + // -- RMT has 8 channels, numbered 0 to 7 rmt_channel_t mRMT_channel; // -- Store the GPIO pin gpio_num_t mPin; - // -- This instantiation forces a check on the pin choice - FastPin mFastPin; - // -- Timing values for zero and one bits, derived from T1, T2, and T3 rmt_item32_t mZero; rmt_item32_t mOne; - // -- Save the pixel controller - PixelController * mPixels; - int mCurColor; - uint16_t mCurPulse; + // -- Pixel data + uint8_t * mPixelData; + int mSize; + int mCur; + + // -- RMT memory volatile uint32_t * mRMT_mem_ptr; + int mWhichHalf; // -- Buffer to hold all of the pulses. For the version that uses // the RMT driver built into the ESP core. rmt_item32_t * mBuffer; uint16_t mBufferSize; + int mCurPulse; + + // -- Make sure we can't call show() too quickly + CMinWait<50> mWait; public: + // -- Constructor + // Mainly just stores the template parameters from the LEDController as + // member variables. + ESP32RMTController(int DATA_PIN, int T1, int T2, int T3); + + // -- Getters and setters for use in ClocklessController + uint8_t * getPixelData(int size_in_bytes); + + // -- Initialize RMT subsystem + // This only needs to be done once + static void init(); + + // -- Show this string of pixels + // This is the main entry point for the pixel controller + void IRAM_ATTR showPixels(); + + // -- Start up the next controller + // This method is static so that it can dispatch to the + // appropriate startOnChannel method of the given controller. + static void IRAM_ATTR startNext(int channel); + + // -- Start this controller on the given channel + // This function just initiates the RMT write; it does not wait + // for it to finish. + void IRAM_ATTR startOnChannel(int channel); + + // -- Start RMT transmission + // Setting this RMT flag is what actually kicks off the peripheral + void IRAM_ATTR tx_start(); + + // -- A controller is done + // This function is called when a controller finishes writing + // its data. It is called either by the custom interrupt + // handler (below), or as a callback from the built-in + // interrupt handler. It is static because we don't know which + // controller is done until we look it up. + static void IRAM_ATTR doneOnChannel(rmt_channel_t channel, void * arg); + + // -- Custom interrupt handler + // This interrupt handler handles two cases: a controller is + // done writing its data, or a controller needs to fill the + // next half of the RMT buffer with data. + static void IRAM_ATTR interruptHandler(void *arg); + + // -- Fill RMT buffer + // Puts 32 bits of pixel data into the next 32 slots in the RMT memory + // Each data bit is represented by a 32-bit RMT item that specifies how + // long to hold the signal high, followed by how long to hold it low. + void IRAM_ATTR fillNext(); + + // -- Init pulse buffer + // Set up the buffer that will hold all of the pulse items for this + // controller. + // This function is only used when the built-in RMT driver is chosen + void initPulseBuffer(int size_in_bytes); + + // -- Convert a byte into RMT pulses + // This function is only used when the built-in RMT driver is chosen + void convertByte(uint32_t byteval); +}; + +template +class ClocklessController : public CPixelLEDController +{ +private: + + // -- The actual controller object for ESP32 + ESP32RMTController mRMTController; + + // -- This instantiation forces a check on the pin choice + FastPin mFastPin; + +public: + + ClocklessController() + : mRMTController(DATA_PIN, T1, T2, T3) + {} + void init() { - // -- Allocate space to save the pixel controller - // during parallel output - mPixels = (PixelController *) malloc(sizeof(PixelController)); - - // -- Precompute rmt items corresponding to a zero bit and a one bit - // according to the timing values given in the template instantiation - // T1H - mOne.level0 = 1; - mOne.duration0 = ESP_TO_RMT_CYCLES(T1+T2); // TO_RMT_CYCLES(T1+T2); - // T1L - mOne.level1 = 0; - mOne.duration1 = ESP_TO_RMT_CYCLES(T3); // TO_RMT_CYCLES(T3); - - // T0H - mZero.level0 = 1; - mZero.duration0 = ESP_TO_RMT_CYCLES(T1); // TO_RMT_CYCLES(T1); - // T0L - mZero.level1 = 0; - mZero.duration1 = ESP_TO_RMT_CYCLES(T2+T3); // TO_RMT_CYCLES(T2 + T3); - - gControllers[gNumControllers] = this; - gNumControllers++; - - mPin = gpio_num_t(DATA_PIN); + // mRMTController = new ESP32RMTController(DATA_PIN, T1, T2, T3); } virtual uint16_t getMaxRefreshRate() const { return 400; } protected: - void initRMT() + // -- Load pixel data + // This method loads all of the pixel data into a separate buffer for use by + // by the RMT driver. Copying does two important jobs: it fixes the color + // order for the pixels, and it performs the scaling/adjusting ahead of time. + void loadPixelData(PixelController & pixels) { - for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { - gOnChannel[i] = NULL; + // -- Make sure the buffer is allocated + int size = pixels.size() * 3; + uint8_t * pData = mRMTController.getPixelData(size); - // -- RMT configuration for transmission --- different in different ESP versions -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) - rmt_config_t rmt_tx = RMT_DEFAULT_CONFIG_TX(mPin, fastled_get_rmt_channel(i) ); -#else - rmt_config_t rmt_tx; - memset(&rmt_tx, 0, sizeof(rmt_tx)); - rmt_tx.channel = fastled_get_rmt_channel(i); - rmt_tx.gpio_num = mPin; - rmt_tx.mem_block_num = 1; - rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; -#endif // version before 4.1 - - rmt_tx.clk_div = DIVIDER; - // don't wish to have a carrier applied. Therefore carrier_en is false and the extra parameters don't matter. - rmt_tx.tx_config.loop_en = false; - rmt_tx.tx_config.carrier_en = false; - - rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; - rmt_tx.tx_config.idle_output_en = true; - - // -- Apply the configuration - rmt_config(&rmt_tx); - - if (FASTLED_RMT_BUILTIN_DRIVER) { - rmt_driver_install(rmt_channel_t(i), 0, 0); - } else { - // -- 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 part in preparation (kind of like double-buffering) - rmt_set_tx_thr_intr_en(rmt_channel_t(i), true, PULSES_PER_FILL); - } + // -- Read out the pixel data using the pixel controller methods that + // perform the scaling and adjustments + int count = 0; + while (pixels.has(1)) { + *pData++ = pixels.loadAndScale0(); + *pData++ = pixels.loadAndScale1(); + *pData++ = pixels.loadAndScale2(); + pixels.advanceData(); + pixels.stepDithering(); + count += 3; } - // -- Create a semaphore to block execution until all the controllers are done - if (gTX_sem == NULL) { - gTX_sem = xSemaphoreCreateBinary(); - xSemaphoreGive(gTX_sem); - } - - - // this was crashing in 4.0. I am hoping that registering the IRS through rmt_isr_register does the right thing. - - if ( ! FASTLED_RMT_BUILTIN_DRIVER) { - // -- Allocate the interrupt if we have not done so yet. This - // interrupt handler must work for all different kinds of - // 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, ESP_INTR_FLAG_LEVEL3, interruptHandler, 0, &gRMT_intr_handle); - } - - gInitialized = true; + assert(count == size); } // -- Show pixels // This is the main entry point for the controller. - virtual void IRAM_ATTR showPixels(PixelController & pixels) + virtual void showPixels(PixelController & pixels) { - if (gNumStarted == 0) { - // -- First controller: make sure everything is set up - // -- Only need to do this once - if ( ! gInitialized) { - initRMT(); - } - xSemaphoreTake(gTX_sem, portMAX_DELAY); - } - - if (FASTLED_RMT_BUILTIN_DRIVER) + if (FASTLED_RMT_BUILTIN_DRIVER) { convertAllPixelData(pixels); - else { - // -- Initialize the local state, save a pointer to the pixel - // data. We need to make a copy because pixels is a local - // variable in the calling function, and this data structure - // needs to outlive this call to showPixels. - (*mPixels) = pixels; + } else { + loadPixelData(pixels); } - // -- Keep track of the number of strips we've seen - gNumStarted++; - - // -- The last call to showPixels is the one responsible for doing - // all of the actual work - if (gNumStarted == gNumControllers) { - gNext = 0; - - // -- First, fill all the available channels - int channel = 0; - while (channel < FASTLED_RMT_MAX_CHANNELS && gNext < gNumControllers) { - startNext(channel); - channel++; - } - - // -- Start them all - for (int i = 0; i < channel; i++) { - ClocklessController * pController = static_cast(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); - - // -- Reset the counters - gNumStarted = 0; - gNumDone = 0; - gNext = 0; - } + mRMTController.showPixels(); } // -- Convert all pixels to RMT pulses @@ -396,285 +343,25 @@ protected: // up-front. void convertAllPixelData(PixelController & pixels) { - // -- Compute the pulse values for the whole strip at once. - // Requires a large buffer - mBufferSize = pixels.size() * 3 * 8; - - if (mBuffer == NULL) { - mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t)); - } + // -- Make sure the data buffer is allocated + mRMTController.initPulseBuffer(pixels.size() * 3); // -- Cycle through the R,G, and B values in the right order, // storing the pulses in the big buffer - mCurPulse = 0; uint32_t byteval; while (pixels.has(1)) { byteval = pixels.loadAndScale0(); - convertByte(byteval); + mRMTController.convertByte(byteval); byteval = pixels.loadAndScale1(); - convertByte(byteval); + mRMTController.convertByte(byteval); byteval = pixels.loadAndScale2(); - convertByte(byteval); + mRMTController.convertByte(byteval); pixels.advanceData(); pixels.stepDithering(); } - - mBuffer[mCurPulse-1].duration1 = RMT_RESET_DURATION; - assert(mCurPulse == mBufferSize); - } - - void convertByte(uint32_t byteval) - { - // -- Write one byte's worth of RMT pulses to the big buffer - byteval <<= 24; - for (register uint32_t j = 0; j < 8; j++) { - mBuffer[mCurPulse] = (byteval & 0x80000000L) ? mOne : mZero; - byteval <<= 1; - mCurPulse++; - } - } - - // -- Start up the next controller - // This method is static so that it can dispatch to the - // appropriate startOnChannel method of the given controller. - static void IRAM_ATTR startNext(int channel) - { - if (gNext < gNumControllers) { - ClocklessController * pController = static_cast(gControllers[gNext]); - pController->startOnChannel(channel); - gNext++; - } - } - - // -- Start this controller on the given channel - // This function just initiates the RMT write; it does not wait - // for it to finish. - void IRAM_ATTR startOnChannel(int channel) - { - // -- Assign this channel and configure the RMT - mRMT_channel = rmt_channel_t(channel); - - // -- Store a reference to this controller, so we can get it - // inside the interrupt handler - gOnChannel[channel] = this; - - // -- Assign the pin to this channel - rmt_set_pin(mRMT_channel, RMT_MODE_TX, mPin); - - if (FASTLED_RMT_BUILTIN_DRIVER) { - // -- Use the built-in RMT driver to send all the data in one shot - rmt_register_tx_end_callback(doneOnChannel, 0); - rmt_write_items(mRMT_channel, mBuffer, mBufferSize, false); - } else { - // -- Use our custom driver to send the data incrementally - - // -- 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; - - // -- Store 2 pixels worth of data (two "buffers" full) - fillNext(); - fillNext(); - - // -- Turn on the interrupts - rmt_set_tx_intr_en(mRMT_channel, true); - } - } - - // -- A controller is done - // This function is called when a controller finishes writing - // its data. It is called either by the custom interrupt - // handler (below), or as a callback from the built-in - // interrupt handler. It is static because we don't know which - // controller is done until we look it up. - static void IRAM_ATTR doneOnChannel(rmt_channel_t channel, void * arg) - { - ClocklessController * controller = static_cast(gOnChannel[channel]); - portBASE_TYPE HPTaskAwoken = 0; - - // -- Turn off output on the pin - gpio_matrix_out(controller->mPin, 0x100, 0, 0); - - gOnChannel[channel] = NULL; - gNumDone++; - - if (gNumDone == gNumControllers) { - // -- If this is the last controller, signal that we are all done - xSemaphoreGiveFromISR(gTX_sem, &HPTaskAwoken); - if(HPTaskAwoken == pdTRUE) portYIELD_FROM_ISR(); - } else { - // -- Otherwise, if there are still controllers waiting, then - // start the next one on this channel - 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); - } - } - } - - // -- Custom interrupt handler - // This interrupt handler handles two cases: a controller is - // done writing its data, or a controller needs to fill the - // next half of the RMT buffer with data. - static void IRAM_ATTR interruptHandler(void *arg) - { - // -- The basic structure of this code is borrowed from the - // interrupt handler in esp-idf/components/driver/rmt.c - uint32_t intr_st = RMT.int_st.val; - uint8_t channel; - - for (channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; channel++) { - int tx_done_bit = channel * 3; - int tx_next_bit = channel + 24; - - if (gOnChannel[channel] != NULL) { - - // -- More to send on this channel - if (intr_st & BIT(tx_next_bit)) { - RMT.int_clr.val |= BIT(tx_next_bit); - - // -- Refill the half of the buffer that we just finished, - // allowing the other half to proceed. - ClocklessController * controller = static_cast(gOnChannel[channel]); - controller->fillNext(); - } else { - // -- Transmission is complete on this channel - if (intr_st & BIT(tx_done_bit)) { - RMT.int_clr.val |= BIT(tx_done_bit); - doneOnChannel(rmt_channel_t(channel), 0); - } - } - } - } - } - - // -- 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)) { - // bb compiler complains - //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 - // -- 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() - { - uint32_t one_val = mOne.val; - uint32_t zero_val = mZero.val; - - // -- Convert (up to) 32 bits of the raw pixel data into - // into RMT pulses that encode the zeros and ones. - int pulses = 0; - register uint32_t byteval; - while (pulses < 32 && mPixels->has(1)) { - // -- Get one byte - // -- 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 <<= 24; - // 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 < 8; j++) { - uint32_t val = (byteval & 0x80000000L) ? one_val : zero_val; - * mRMT_mem_ptr++ = val; - // Replaces: RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = val; - byteval <<= 1; - mCurPulse++; - } - pulses += 8; - } - - // -- When we reach the end of the pixel data, fill the rest of the - // RMT buffer with 0's, which signals to the device that we're done. - if ( ! mPixels->has(1) ) { - while (pulses < 32) { - * mRMT_mem_ptr++ = 0; - // Replaces: RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = 0; - mCurPulse++; - pulses++; - } - } - - // -- When we have filled the back half the buffer, reset the position to the first half - if (mCurPulse == MAX_PULSES) { - mRMT_mem_ptr = & (RMTMEM.chan[mRMT_channel].data32[0].val); - mCurPulse = 0; - } } }; + FASTLED_NAMESPACE_END diff --git a/sdkconfig b/sdkconfig index 8336c94..b3560d4 100644 --- a/sdkconfig +++ b/sdkconfig @@ -307,10 +307,9 @@ CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 CONFIG_ESP_CONSOLE_UART_DEFAULT=y # CONFIG_ESP_CONSOLE_UART_CUSTOM is not set -# CONFIG_ESP_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_NONE is not set +CONFIG_ESP_CONSOLE_UART=y CONFIG_ESP_CONSOLE_UART_NUM=0 -CONFIG_ESP_CONSOLE_UART_TX_GPIO=1 -CONFIG_ESP_CONSOLE_UART_RX_GPIO=3 CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 CONFIG_ESP_INT_WDT=y CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 @@ -647,6 +646,7 @@ CONFIG_LWIP_TCP_QUEUE_OOSEQ=y CONFIG_LWIP_TCP_OVERSIZE_MSS=y # CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set # CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +CONFIG_LWIP_TCP_RTO_TIME=3000 # end of TCP # @@ -879,6 +879,7 @@ CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 # # Auto-detect flash chips @@ -986,7 +987,6 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 CONFIG_WPA_MBEDTLS_CRYPTO=y # CONFIG_WPA_DEBUG_PRINT is not set # CONFIG_WPA_TESTING_OPTIONS is not set -# CONFIG_WPA_TLS_V12 is not set # CONFIG_WPA_WPS_WARS is not set # end of Supplicant @@ -1078,10 +1078,9 @@ CONFIG_MAIN_TASK_STACK_SIZE=3584 CONFIG_IPC_TASK_STACK_SIZE=1024 CONFIG_CONSOLE_UART_DEFAULT=y # CONFIG_CONSOLE_UART_CUSTOM is not set -# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y CONFIG_CONSOLE_UART_NUM=0 -CONFIG_CONSOLE_UART_TX_GPIO=1 -CONFIG_CONSOLE_UART_RX_GPIO=3 CONFIG_CONSOLE_UART_BAUDRATE=115200 CONFIG_INT_WDT=y CONFIG_INT_WDT_TIMEOUT_MS=300