diff options
author | Sam Guyer <sam.guyer@gmail.com> | 2018-05-08 22:10:15 +0300 |
---|---|---|
committer | Daniel Garcia <danielgarcia@gmail.com> | 2018-05-08 22:10:15 +0300 |
commit | d41619400bbe77fe14dd1ccdca7521b152f460ef (patch) | |
tree | 529c91fec5858b46b528ee27765d1d006e8ba298 | |
parent | 6fd236684e31c7703dfba697e86b2b8e9028b75f (diff) |
ESP32 support using the RMT peripheral device (#522)
* Support for ESP32
Credit to Rina Shkrabova for the first cut.
* Clean up interrupt handling
I think there was actually an error in the interrupt enabling/disabling, but I also cleaned it up so that it is more clear how interrupts are handled.
* Better interrupt handling
* Added RMT version
Not fully portable yet, though. The timing numbers are hard-wired for WS2812, and the RMT channel is also hard-wired.
* Fixed the timing
Timing is now computed from T1, T2, amd T3 instead of being hard-wired.
* Better buffer management
The RMT signal is sent in 10-pixel chunks, using double-buffering to hide the latency when possible. Also: assign RMT channels sequentially.
* Total rewrite using Martin's code
* Better comments
* Fixed the timing calculation
We were not doing the conversion from ESP32 cycles to RMT cycles correctly. Now it all works!
* Added Martin's changes
* Removed confusing comments
* Added my name!
* Fixed ESP32 compile problem
On ESP platforms the dev kit provides the function __cxa_pure_virtual, so there is no need to define it.
* honor WAIT_TIME
for chipsets that need it (for example TM1829)
* Better interrupt handling
Suggested by @h3ndrik : allocated the interrupt once at the initialization and then just turn it on and off. This is the strategy that the ESP32 core uses also.
* Major refactoring
Two major changes to the RMT driver. First, I realized that we can have only one interrupt handler attached to the RMT peripheral, so it needs to be able to handle all of the attached strips. To accomplish this, I store each ClocklessController in an array indexed by its RMT channel. The interrupt handler can then take the channel that triggered it and index into the array to get the right controller.
The second major change is that I replaced all of the explicit bit twiddling of the RMT configurartion with calls to the proper functions in ESP32 core. That should make the code more stable if the core changes.
* Fixed the interrupt dispatch
Since the interrupt handler is global for all channels, we need to store not just the controller, but also the buffer refill function for each strip.
* Added a demo
This version of DemoReel100 spins off a separate task on core 0 that just performs the FastLED.show() operations. Regular code running on core 1 (the default for Arduino) signals this task to request a show().
* Avoid unnecessary timeouts
Replaced a 500ms delay in the show task with MAX_DELAY. There's really no point in timing out (and crashing the program) just because the application hasn't called show.
* Parallel output
Reworked the code again in order to support parallel output, which is now the default mode. You can also now ask it to use the built-in RMT driver if you have other parts of your code that need the RMT peripheral.
Two #defines control choices -- put either or both of these before including FastLED.h:
#define FASTLED_RMT_CORE_DRIVER
Uses the ESP core RMT driver. To do this, though, it allocates a big buffer to hold all of the pixel bits, so there is a memory and compute cost.
#define FASTLED_RMT_SERIAL_OUTPUT
Force serial output of each strip.
* Documentation
Describing the implementation and the compile-time switches
* Removing files that should not be there
* Fixed synchronization
The previous checkin had bugs in the syncronization that caused problems in parallel mode when strips are different lengths.
* Fixed a stupid bug
Made the code bullet-proof in a few ways, but most importantly fixed a terrible integer underflow bug in the code that fills the RMT buffer.
* Another major overhaul
The big change in this version is the ability to support more than 8 controllers. Instead of assigning RMT channels to controllers in a fixed mapping, channels are assigned on the fly, allowing the driver to reuse channels as they become available.
* Oops
Didn't mean to check these in.
* Fixed built-in driver mode
Fixed the code so that it works with the built-in RMT driver. There's nothing special to do to enable it -- just #define FASTLED_RMT_BUILTIN_DRIVER true
* Cleanup
Fixing some documentation and configuration stuff
-rw-r--r-- | FastLED.cpp | 2 | ||||
-rw-r--r-- | examples/DemoReelESP32/DemoReelESP32.ino | 181 | ||||
-rw-r--r-- | fastled_delay.h | 6 | ||||
-rw-r--r-- | platforms/esp/32/clockless_esp32.h | 613 |
4 files changed, 713 insertions, 89 deletions
diff --git a/FastLED.cpp b/FastLED.cpp index d3d389ba..94f23021 100644 --- a/FastLED.cpp +++ b/FastLED.cpp @@ -239,7 +239,7 @@ extern "C" void yield(void) { } #ifdef NEED_CXX_BITS namespace __cxxabiv1 { - #ifndef ESP8266 + #if !defined(ESP8266) && !defined(ESP32) extern "C" void __cxa_pure_virtual (void) {} #endif diff --git a/examples/DemoReelESP32/DemoReelESP32.ino b/examples/DemoReelESP32/DemoReelESP32.ino new file mode 100644 index 00000000..3a32d4c4 --- /dev/null +++ b/examples/DemoReelESP32/DemoReelESP32.ino @@ -0,0 +1,181 @@ +#include "FastLED.h" + +FASTLED_USING_NAMESPACE + +// FastLED "100-lines-of-code" demo reel, showing just a few +// of the kinds of animation patterns you can quickly and easily +// compose using FastLED. +// +// This example also shows one easy way to define multiple +// animations patterns and have them automatically rotate. +// +// -Mark Kriegsman, December 2014 + +#if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000) +#warning "Requires FastLED 3.1 or later; check github for latest code." +#endif + +#define DATA_PIN 12 +//#define CLK_PIN 4 +#define LED_TYPE WS2811 +#define COLOR_ORDER GRB +#define NUM_LEDS 27 +CRGB leds[NUM_LEDS]; + +#define BRIGHTNESS 60 +#define FRAMES_PER_SECOND 120 + +// -- The core to run FastLED.show() +#define FASTLED_SHOW_CORE 0 + +// -- Task handles for use in the notifications +static TaskHandle_t FastLEDshowTaskHandle = 0; +static TaskHandle_t userTaskHandle = 0; + +/** show() for ESP32 + * Call this function instead of FastLED.show(). It signals core 0 to issue a show, + * then waits for a notification that it is done. + */ +void FastLEDshowESP32() +{ + if (userTaskHandle == 0) { + // -- Store the handle of the current task, so that the show task can + // notify it when it's done + userTaskHandle = xTaskGetCurrentTaskHandle(); + + // -- Trigger the show task + xTaskNotifyGive(FastLEDshowTaskHandle); + + // -- Wait to be notified that it's done + const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 ); + ulTaskNotifyTake(pdTRUE, xMaxBlockTime); + userTaskHandle = 0; + } +} + +/** show Task + * This function runs on core 0 and just waits for requests to call FastLED.show() + */ +void FastLEDshowTask(void *pvParameters) +{ + // -- Run forever... + for(;;) { + // -- Wait for the trigger + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + // -- Do the show (synchronously) + FastLED.show(); + + // -- Notify the calling task + xTaskNotifyGive(userTaskHandle); + } +} + +void setup() { + delay(3000); // 3 second delay for recovery + Serial.begin(115200); + + // tell FastLED about the LED strip configuration + FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); + //FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); + + // set master brightness control + FastLED.setBrightness(BRIGHTNESS); + + int core = xPortGetCoreID(); + Serial.print("Main code running on core "); + Serial.println(core); + + // -- Create the FastLED show task + xTaskCreatePinnedToCore(FastLEDshowTask, "FastLEDshowTask", 2048, NULL, 2, &FastLEDshowTaskHandle, FASTLED_SHOW_CORE); +} + + +// List of patterns to cycle through. Each is defined as a separate function below. +typedef void (*SimplePatternList[])(); +SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm }; + +uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current +uint8_t gHue = 0; // rotating "base color" used by many of the patterns + +void loop() +{ + // Call the current pattern function once, updating the 'leds' array + gPatterns[gCurrentPatternNumber](); + + // send the 'leds' array out to the actual LED strip + FastLEDshowESP32(); + // FastLED.show(); + // insert a delay to keep the framerate modest + FastLED.delay(1000/FRAMES_PER_SECOND); + + // do some periodic updates + EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow + EVERY_N_SECONDS( 10 ) { nextPattern(); } // change patterns periodically +} + +#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) + +void nextPattern() +{ + // add one to the current pattern number, and wrap around at the end + gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns); +} + +void rainbow() +{ + // FastLED's built-in rainbow generator + fill_rainbow( leds, NUM_LEDS, gHue, 7); +} + +void rainbowWithGlitter() +{ + // built-in FastLED rainbow, plus some random sparkly glitter + rainbow(); + addGlitter(80); +} + +void addGlitter( fract8 chanceOfGlitter) +{ + if( random8() < chanceOfGlitter) { + leds[ random16(NUM_LEDS) ] += CRGB::White; + } +} + +void confetti() +{ + // random colored speckles that blink in and fade smoothly + fadeToBlackBy( leds, NUM_LEDS, 10); + int pos = random16(NUM_LEDS); + leds[pos] += CHSV( gHue + random8(64), 200, 255); +} + +void sinelon() +{ + // a colored dot sweeping back and forth, with fading trails + fadeToBlackBy( leds, NUM_LEDS, 20); + int pos = beatsin16( 13, 0, NUM_LEDS-1 ); + leds[pos] += CHSV( gHue, 255, 192); +} + +void bpm() +{ + // colored stripes pulsing at a defined Beats-Per-Minute (BPM) + uint8_t BeatsPerMinute = 62; + CRGBPalette16 palette = PartyColors_p; + uint8_t beat = beatsin8( BeatsPerMinute, 64, 255); + for( int i = 0; i < NUM_LEDS; i++) { //9948 + leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10)); + } +} + +void juggle() { + // eight colored dots, weaving in and out of sync with each other + fadeToBlackBy( leds, NUM_LEDS, 20); + byte dothue = 0; + for( int i = 0; i < 8; i++) { + leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255); + dothue += 32; + } +} + diff --git a/fastled_delay.h b/fastled_delay.h index 0fd8de09..4649f7d0 100644 --- a/fastled_delay.h +++ b/fastled_delay.h @@ -34,6 +34,12 @@ public: // Default is now just 'nop', with special case for AVR +// ESP32 core has it's own definition of NOP, so undef it first +#ifdef ESP32 +#undef NOP +#undef NOP2 +#endif + #if defined(__AVR__) # define FL_NOP __asm__ __volatile__ ("cp r0,r0\n"); # define FL_NOP2 __asm__ __volatile__ ("rjmp .+0"); diff --git a/platforms/esp/32/clockless_esp32.h b/platforms/esp/32/clockless_esp32.h index 0ed9224b..6ab27700 100644 --- a/platforms/esp/32/clockless_esp32.h +++ b/platforms/esp/32/clockless_esp32.h @@ -1,13 +1,111 @@ +/* + * 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 <fozztexx@fozztexx.com> + * 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 FASTLED_DEBUG_COUNT_FRAME_RETRIES -extern uint32_t _frame_cnt; -extern uint32_t _retry_cnt; +#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 -// Info on reading cycle counter from https://github.com/kbeckmann/nodemcu-firmware/blob/ws2812-dual/app/modules/ws2812.c __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { uint32_t cyc; __asm__ __volatile__ ("rsr %0,ccount":"=a" (cyc)); @@ -16,109 +114,448 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { #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 <int DATA_PIN, int T1, int T2, int T3, EOrder RGB_ORDER = RGB, int XTRA0 = 0, bool FLIP = false, int WAIT_TIME = 5> -class ClocklessController : public CPixelLEDController<RGB_ORDER> { +class ClocklessController : public CPixelLEDController<RGB_ORDER> +{ + // -- RMT has 8 channels, numbered 0 to 7 + rmt_channel_t mRMT_channel; + + // -- Store the GPIO pin + gpio_num_t mPin; - typedef typename FastPin<DATA_PIN>::port_ptr_t data_ptr_t; - typedef typename FastPin<DATA_PIN>::port_t data_t; + // -- Timing values for zero and one bits, derived from T1, T2, and T3 + rmt_item32_t mZero; + rmt_item32_t mOne; + + // -- State information for keeping track of where we are in the pixel data + PixelController<RGB_ORDER> * 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; - data_t mPinMask; - data_ptr_t mPort; - CMinWait<WAIT_TIME> mWait; public: - virtual void init() { - FastPin<DATA_PIN>::setOutput(); - mPinMask = FastPin<DATA_PIN>::mask(); - mPort = FastPin<DATA_PIN>::port(); + + 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); + + gControllers[gNumControllers] = this; + gNumControllers++; + + mPin = gpio_num_t(DATA_PIN); } virtual uint16_t getMaxRefreshRate() const { return 400; } protected: - virtual void showPixels(PixelController<RGB_ORDER> & pixels) { - mWait.wait(); - int cnt = FASTLED_INTERRUPT_RETRY_COUNT; - while((showRGBInternal(pixels)==0) && cnt--) { -#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES - _retry_cnt++; -#endif - ets_intr_unlock(); - // interrupts(); - delayMicroseconds(WAIT_TIME); - ets_intr_lock(); - // noInterrupts(); + void initRMT() + { + // -- 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); } - // ets_intr_unlock(); - mWait.mark(); + + gInitialized = true; } -#define _ESP_ADJ (0) -#define _ESP_ADJ2 (0) + virtual void showPixels(PixelController<RGB_ORDER> & pixels) + { + if (gNumStarted == 0) { + // -- First controller: make sure everything is set up + initRMT(); + xSemaphoreTake(gTX_sem, portMAX_DELAY); + } - template<int BITS> __attribute__ ((always_inline)) inline static void writeBits(register uint32_t & last_mark, register uint32_t b) { - b = ~b; b <<= 24; - for(register uint32_t i = BITS; i > 0; i--) { - while((__clock_cycles() - last_mark) < (T1+T2+T3)); - last_mark = __clock_cycles(); - FastPin<DATA_PIN>::hi(); - - while((__clock_cycles() - last_mark) < T1); - if(b & 0x80000000L) { FastPin<DATA_PIN>::lo(); } - b <<= 1; - - while((__clock_cycles() - last_mark) < (T1+T2)); - FastPin<DATA_PIN>::lo(); + // -- 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<RGB_ORDER>(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; } } - // This method is made static to force making register Y available to use for data on AVR - if the method is non-static, then - // gcc will use register Y for the this pointer. - static uint32_t showRGBInternal(PixelController<RGB_ORDER> pixels) { - // Setup the pixel controller and load/scale the first byte - pixels.preStepFirstByteDithering(); - register uint32_t b = pixels.loadAndScale0(); - pixels.preStepFirstByteDithering(); - ets_intr_lock(); - // noInterrupts(); - uint32_t start = __clock_cycles(); - uint32_t last_mark = start; - while(pixels.has(1)) { - // Write first byte, read next byte - writeBits<8+XTRA0>(last_mark, b); - b = pixels.loadAndScale1(); - - // Write second byte, read 3rd byte - writeBits<8+XTRA0>(last_mark, b); - b = pixels.loadAndScale2(); - - // Write third byte, read 1st byte of next pixel - writeBits<8+XTRA0>(last_mark, b); - b = pixels.advanceAndLoadAndScale0(); - -#if (FASTLED_ALLOW_INTERRUPTS == 1) - ets_intr_unlock(); - // interrupts(); -#endif - - pixels.stepDithering(); + // -- 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<ClocklessController*>(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); -#if (FASTLED_ALLOW_INTERRUPTS == 1) - ets_intr_lock(); - // noInterrupts(); - // if interrupts took longer than 45µs, punt on the current frame - if((int32_t)(__clock_cycles()-last_mark) > 0) { - if((int32_t)(__clock_cycles()-last_mark) > (T1+T2+T3+((WAIT_TIME-INTERRUPT_THRESHOLD)*CLKS_PER_US))) { sei(); return 0; } - } -#endif - }; + // -- Start the RMT TX operation + rmt_tx_start(mRMT_channel, true); + } + } - ets_intr_unlock(); - // interrupts(); -#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES - _frame_cnt++; -#endif - return __clock_cycles() - start; + static void doneOnChannel(rmt_channel_t channel, void * arg) + { + ClocklessController * controller = static_cast<ClocklessController*>(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); + } + } + + 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) { + + ClocklessController * controller = static_cast<ClocklessController*>(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); + } + } + } + } + + 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)) { + 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++; + } + + if (done_strip) + RMTMEM.chan[mRMT_channel].data32[mCurPulse-1].duration1 = RMT_RESET_DURATION; + } + + 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 + mBufferSize = mPixels->size() * 3 * 8; + + // 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); + + rmt_write_items(mRMT_channel, mBuffer, mBufferSize, false); } }; |