forked from bbulkow/FastLED-idf
Update to sam guyer's FastLED fork. Should reduce or eliminate flashing.
This commit is contained in:
@@ -13,6 +13,7 @@ set(srcs
|
|||||||
"wiring.cpp"
|
"wiring.cpp"
|
||||||
"hal/esp32-hal-misc.c"
|
"hal/esp32-hal-misc.c"
|
||||||
"hal/esp32-hal-gpio.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
|
# everything needs the ESP32 flag, not sure why this won't work
|
||||||
|
@@ -49,6 +49,11 @@ template<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _SPI_CLOCK_DIVIDER>
|
|||||||
class SPIOutput : public NRF52SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
class SPIOutput : public NRF52SPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(FASTLED_APOLLO3) && defined(FASTLED_ALL_PINS_HARDWARE_SPI)
|
||||||
|
template<uint8_t _DATA_PIN, uint8_t _CLOCK_PIN, uint32_t _SPI_CLOCK_DIVIDER>
|
||||||
|
class SPIOutput : public APOLLO3HardwareSPIOutput<_DATA_PIN, _CLOCK_PIN, _SPI_CLOCK_DIVIDER> {};
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(SPI_DATA) && defined(SPI_CLOCK)
|
#if defined(SPI_DATA) && defined(SPI_CLOCK)
|
||||||
|
|
||||||
#if defined(FASTLED_TEENSY3) && defined(ARM_HARDWARE_SPI)
|
#if defined(FASTLED_TEENSY3) && defined(ARM_HARDWARE_SPI)
|
||||||
|
@@ -181,7 +181,7 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun
|
|||||||
#if !defined(__AVR__)
|
#if !defined(__AVR__)
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
// for memmove, memcpy, and memset if not defined here
|
// for memmove, memcpy, and memset if not defined here
|
||||||
#endif
|
#endif // end of !defined(__AVR__)
|
||||||
|
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun
|
|||||||
// Generic ARM
|
// Generic ARM
|
||||||
#define QADD8_C 1
|
#define QADD8_C 1
|
||||||
#define QADD7_C 1
|
#define QADD7_C 1
|
||||||
#endif
|
#endif // end of defined(FASTLED_TEENSY3)
|
||||||
|
|
||||||
#define QSUB8_C 1
|
#define QSUB8_C 1
|
||||||
#define SCALE8_C 1
|
#define SCALE8_C 1
|
||||||
@@ -213,6 +213,30 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun
|
|||||||
#define AVG15_C 1
|
#define AVG15_C 1
|
||||||
#define BLEND8_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__)
|
#elif defined(__AVR__)
|
||||||
|
|
||||||
@@ -274,7 +298,9 @@ Lib8tion is pronounced like 'libation': lie-BAY-shun
|
|||||||
#define QMUL8_AVRASM 0
|
#define QMUL8_AVRASM 0
|
||||||
#define EASE8_AVRASM 0
|
#define EASE8_AVRASM 0
|
||||||
#define BLEND8_AVRASM 0
|
#define BLEND8_AVRASM 0
|
||||||
#endif
|
#endif // end of !defined(LIB8_ATTINY)
|
||||||
|
|
||||||
|
// end of #elif defined(__AVR__)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@@ -811,6 +837,9 @@ public:
|
|||||||
#ifdef FASTLED_ARM
|
#ifdef FASTLED_ARM
|
||||||
int operator*(int v) { return (v*i) + ((v*f)>>F); }
|
int operator*(int v) { return (v*i) + ((v*f)>>F); }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef FASTLED_APOLLO3
|
||||||
|
int operator*(int v) { return (v*i) + ((v*f)>>F); }
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T, int F, int I> static uint32_t operator*(uint32_t v, q<T,F,I> & q) { return q * v; }
|
template<class T, int F, int I> static uint32_t operator*(uint32_t v, q<T,F,I> & q) { return q * v; }
|
||||||
@@ -820,6 +849,9 @@ template<class T, int F, int I> static int16_t operator*(int16_t v, q<T,F,I> & q
|
|||||||
#ifdef FASTLED_ARM
|
#ifdef FASTLED_ARM
|
||||||
template<class T, int F, int I> static int operator*(int v, q<T,F,I> & q) { return q * v; }
|
template<class T, int F, int I> static int operator*(int v, q<T,F,I> & q) { return q * v; }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef FASTLED_APOLLO3
|
||||||
|
template<class T, int F, int I> static int operator*(int v, q<T,F,I> & q) { return q * v; }
|
||||||
|
#endif
|
||||||
|
|
||||||
/// A 4.4 integer (4 bits integer, 4 bits fraction)
|
/// A 4.4 integer (4 bits integer, 4 bits fraction)
|
||||||
typedef q<uint8_t, 4,4> q44;
|
typedef q<uint8_t, 4,4> q44;
|
||||||
|
@@ -12,13 +12,19 @@
|
|||||||
#define FASTLED_RAND16_2053 ((uint16_t)(2053))
|
#define FASTLED_RAND16_2053 ((uint16_t)(2053))
|
||||||
#define FASTLED_RAND16_13849 ((uint16_t)(13849))
|
#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
|
/// random number seed
|
||||||
extern uint16_t rand16seed;// = RAND16_SEED;
|
extern uint16_t rand16seed;// = RAND16_SEED;
|
||||||
|
|
||||||
/// Generate an 8-bit random number
|
/// Generate an 8-bit random number
|
||||||
LIB8STATIC uint8_t random8()
|
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
|
// return the sum of the high and low bytes, for better
|
||||||
// mixing and non-sequential correlation
|
// mixing and non-sequential correlation
|
||||||
return (uint8_t)(((uint8_t)(rand16seed & 0xFF)) +
|
return (uint8_t)(((uint8_t)(rand16seed & 0xFF)) +
|
||||||
@@ -28,7 +34,7 @@ LIB8STATIC uint8_t random8()
|
|||||||
/// Generate a 16 bit random number
|
/// Generate a 16 bit random number
|
||||||
LIB8STATIC uint16_t random16()
|
LIB8STATIC uint16_t random16()
|
||||||
{
|
{
|
||||||
rand16seed = (rand16seed * FASTLED_RAND16_2053) + FASTLED_RAND16_13849;
|
rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849;
|
||||||
return rand16seed;
|
return rand16seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,9 +7,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Represents a set of CRGB led objects. Provides the [] array operator, and works like a normal array in that case.
|
///// 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
|
///// 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.
|
///// that a pixel set is a window into another set of led data, it is not its own set of led data.
|
||||||
template<class PIXEL_TYPE>
|
template<class PIXEL_TYPE>
|
||||||
class CPixelView {
|
class CPixelView {
|
||||||
public:
|
public:
|
||||||
|
@@ -38,6 +38,18 @@ struct CHSV {
|
|||||||
uint8_t raw[3];
|
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
|
/// default values are UNITIALIZED
|
||||||
inline CHSV() __attribute__((always_inline))
|
inline CHSV() __attribute__((always_inline))
|
||||||
{
|
{
|
||||||
@@ -106,7 +118,7 @@ struct CRGB {
|
|||||||
uint8_t raw[3];
|
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))
|
inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline))
|
||||||
{
|
{
|
||||||
return raw[x];
|
return raw[x];
|
||||||
@@ -478,7 +490,7 @@ struct CRGB {
|
|||||||
uint8_t max = red;
|
uint8_t max = red;
|
||||||
if( green > max) max = green;
|
if( green > max) max = green;
|
||||||
if( blue > max) max = blue;
|
if( blue > max) max = blue;
|
||||||
|
|
||||||
// stop div/0 when color is black
|
// stop div/0 when color is black
|
||||||
if(max > 0) {
|
if(max > 0) {
|
||||||
uint16_t factor = ((uint16_t)(limit) * 256) / max;
|
uint16_t factor = ((uint16_t)(limit) * 256) / max;
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
#ifndef __INC_PLATFORMS_H
|
#ifndef __INC_PLATFORMS_H
|
||||||
#define __INC_PLATFORMS_H
|
#define __INC_PLATFORMS_H
|
||||||
|
|
||||||
#define ESP32
|
|
||||||
|
|
||||||
#include "FastLED.h"
|
#include "FastLED.h"
|
||||||
|
|
||||||
#include "fastled_config.h"
|
#include "fastled_config.h"
|
||||||
@@ -36,6 +34,8 @@
|
|||||||
#include "platforms/esp/8266/fastled_esp8266.h"
|
#include "platforms/esp/8266/fastled_esp8266.h"
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
#include "platforms/esp/32/fastled_esp32.h"
|
#include "platforms/esp/32/fastled_esp32.h"
|
||||||
|
#elif defined(ARDUINO_ARCH_APOLLO3)
|
||||||
|
#include "platforms/apollo3/fastled_apollo3.h"
|
||||||
#else
|
#else
|
||||||
// AVR platforms
|
// AVR platforms
|
||||||
#include "platforms/avr/fastled_avr.h"
|
#include "platforms/avr/fastled_avr.h"
|
||||||
|
@@ -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 <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 __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 <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>
|
|
||||||
{
|
|
||||||
// -- 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<DATA_PIN> 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<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;
|
|
||||||
|
|
||||||
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<RGB_ORDER> & 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<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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- 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);
|
|
||||||
|
|
||||||
// -- Start the RMT TX operation
|
|
||||||
rmt_tx_start(mRMT_channel, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
// -- 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<RGB_ORDER> & 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<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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- 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);
|
|
||||||
|
|
||||||
// -- Start the RMT TX operation
|
|
||||||
rmt_tx_start(mRMT_channel, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
>>>>>>> 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<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);
|
|
||||||
=======
|
|
||||||
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);
|
|
||||||
>>>>>>> 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
|
|
@@ -199,7 +199,10 @@ class ClocklessController : public CPixelLEDController<RGB_ORDER>
|
|||||||
// -- Save the pixel controller
|
// -- Save the pixel controller
|
||||||
PixelController<RGB_ORDER> * mPixels;
|
PixelController<RGB_ORDER> * mPixels;
|
||||||
|
|
||||||
public:
|
// -- Make sure we can't call show() too quickly
|
||||||
|
CMinWait<50> mWait;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
void init()
|
void init()
|
||||||
{
|
{
|
||||||
@@ -363,7 +366,7 @@ protected:
|
|||||||
freq=1/(CLOCK_DIVIDER_N+(double)CLOCK_DIVIDER_B/CLOCK_DIVIDER_A);
|
freq=1/(CLOCK_DIVIDER_N+(double)CLOCK_DIVIDER_B/CLOCK_DIVIDER_A);
|
||||||
freq=freq*I2S_BASE_CLK;
|
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);
|
// 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);
|
// Serial.printf("Pulse duration: %f ns\n",pulseduration);
|
||||||
// gPulsesPerBit = (T1ns + T2ns + T3ns)/FASTLED_I2S_NS_PER_PULSE;
|
// gPulsesPerBit = (T1ns + T2ns + T3ns)/FASTLED_I2S_NS_PER_PULSE;
|
||||||
|
|
||||||
@@ -510,8 +513,8 @@ protected:
|
|||||||
|
|
||||||
// -- Allocate i2s interrupt
|
// -- 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);
|
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,
|
esp_intr_alloc(interruptSource, 0, // ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL3,
|
||||||
&interruptHandler, 0, &gI2S_intr_handle);
|
&interruptHandler, 0, &gI2S_intr_handle);
|
||||||
|
|
||||||
// -- Create a semaphore to block execution until all the controllers are done
|
// -- Create a semaphore to block execution until all the controllers are done
|
||||||
if (gTX_sem == NULL) {
|
if (gTX_sem == NULL) {
|
||||||
@@ -574,6 +577,9 @@ protected:
|
|||||||
fillBuffer();
|
fillBuffer();
|
||||||
fillBuffer();
|
fillBuffer();
|
||||||
|
|
||||||
|
// -- Make sure it's been at least 50ms since last show
|
||||||
|
mWait.wait();
|
||||||
|
|
||||||
i2sStart();
|
i2sStart();
|
||||||
|
|
||||||
// -- Wait here while the rest of the data is sent. The interrupt handler
|
// -- Wait here while the rest of the data is sent. The interrupt handler
|
||||||
@@ -584,6 +590,8 @@ protected:
|
|||||||
|
|
||||||
i2sStop();
|
i2sStop();
|
||||||
|
|
||||||
|
mWait.mark();
|
||||||
|
|
||||||
// -- Reset the counters
|
// -- Reset the counters
|
||||||
gNumStarted = 0;
|
gNumStarted = 0;
|
||||||
}
|
}
|
||||||
@@ -645,7 +653,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -- Transpose and encode the pixel data for the DMA buffer
|
// -- 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++) {
|
for (int channel = 0; channel < NUM_COLOR_CHANNELS; channel++) {
|
||||||
|
|
||||||
// -- Tranpose each array: all the bit 7's, then all the bit 6's, ...
|
// -- Tranpose each array: all the bit 7's, then all the bit 6's, ...
|
||||||
|
387
components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp
Normal file
387
components/FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp
Normal file
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Integration into FastLED ClocklessController
|
* 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 Thomas Basler
|
||||||
* Copyright (c) 2017 Martin F. Falatic
|
* Copyright (c) 2017 Martin F. Falatic
|
||||||
*
|
*
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
* co-exist. To switch to this mode, add the following directive
|
* co-exist. To switch to this mode, add the following directive
|
||||||
* before you include FastLED.h:
|
* 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
|
* 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,
|
* 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
|
* represented by a 32-bit pulse specification, so it is a 32X blow-up
|
||||||
* in memory use.
|
* 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 <fozztexx@fozztexx.com>
|
* Based on public domain code created 19 Nov 2016 by Chris Osborn <fozztexx@fozztexx.com>
|
||||||
* http://insentricity.com *
|
* http://insentricity.com *
|
||||||
@@ -101,8 +122,8 @@ extern "C" {
|
|||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
// needed to work around issue with driver problem in 4.1 and master around 2020
|
extern void spi_flash_op_lock(void);
|
||||||
#include "esp_idf_version.h"
|
extern void spi_flash_op_unlock(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
@@ -117,17 +138,18 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() {
|
|||||||
#define FASTLED_HAS_CLOCKLESS 1
|
#define FASTLED_HAS_CLOCKLESS 1
|
||||||
#define NUM_COLOR_CHANNELS 3
|
#define NUM_COLOR_CHANNELS 3
|
||||||
|
|
||||||
|
// NOT CURRENTLY IMPLEMENTED:
|
||||||
// -- Set to true to print debugging information about timing
|
// -- Set to true to print debugging information about timing
|
||||||
// Useful for finding out if timing is being messed up by other things
|
// Useful for finding out if timing is being messed up by other things
|
||||||
// on the processor (WiFi, for example)
|
// on the processor (WiFi, for example)
|
||||||
#ifndef FASTLED_RMT_SHOW_TIMER
|
//#ifndef FASTLED_RMT_SHOW_TIMER
|
||||||
#define FASTLED_RMT_SHOW_TIMER false
|
//#define FASTLED_RMT_SHOW_TIMER false
|
||||||
#endif
|
//#endif
|
||||||
|
|
||||||
// -- Configuration constants
|
// -- Configuration constants
|
||||||
#define DIVIDER 2 /* 4, 8 still seem to work, but timings become marginal */
|
#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 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
|
// -- Convert ESP32 CPU cycles to RMT device cycles, taking into account the divider
|
||||||
#define F_CPU_RMT ( 80000000L)
|
#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 NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE )
|
||||||
#define RMT_RESET_DURATION NS_TO_CYCLES(50000)
|
#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
|
#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
|
#define FASTLED_RMT_BUILTIN_DRIVER false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// -- Max number of controllers we can support
|
// -- Max number of controllers we can support
|
||||||
#ifndef FASTLED_RMT_MAX_CONTROLLERS
|
#ifndef FASTLED_RMT_MAX_CONTROLLERS
|
||||||
#define FASTLED_RMT_MAX_CONTROLLERS 32
|
#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
|
#define FASTLED_RMT_MAX_CHANNELS 8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// -- Array of all controllers
|
class ESP32RMTController
|
||||||
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 <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>
|
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
// -- RMT has 8 channels, numbered 0 to 7
|
// -- RMT has 8 channels, numbered 0 to 7
|
||||||
rmt_channel_t mRMT_channel;
|
rmt_channel_t mRMT_channel;
|
||||||
|
|
||||||
// -- Store the GPIO pin
|
// -- Store the GPIO pin
|
||||||
gpio_num_t mPin;
|
gpio_num_t mPin;
|
||||||
|
|
||||||
// -- This instantiation forces a check on the pin choice
|
|
||||||
FastPin<DATA_PIN> mFastPin;
|
|
||||||
|
|
||||||
// -- Timing values for zero and one bits, derived from T1, T2, and T3
|
// -- Timing values for zero and one bits, derived from T1, T2, and T3
|
||||||
rmt_item32_t mZero;
|
rmt_item32_t mZero;
|
||||||
rmt_item32_t mOne;
|
rmt_item32_t mOne;
|
||||||
|
|
||||||
// -- Save the pixel controller
|
// -- Pixel data
|
||||||
PixelController<RGB_ORDER> * mPixels;
|
uint8_t * mPixelData;
|
||||||
int mCurColor;
|
int mSize;
|
||||||
uint16_t mCurPulse;
|
int mCur;
|
||||||
|
|
||||||
|
// -- RMT memory
|
||||||
volatile uint32_t * mRMT_mem_ptr;
|
volatile uint32_t * mRMT_mem_ptr;
|
||||||
|
int mWhichHalf;
|
||||||
|
|
||||||
// -- Buffer to hold all of the pulses. For the version that uses
|
// -- Buffer to hold all of the pulses. For the version that uses
|
||||||
// the RMT driver built into the ESP core.
|
// the RMT driver built into the ESP core.
|
||||||
rmt_item32_t * mBuffer;
|
rmt_item32_t * mBuffer;
|
||||||
uint16_t mBufferSize;
|
uint16_t mBufferSize;
|
||||||
|
int mCurPulse;
|
||||||
|
|
||||||
|
// -- Make sure we can't call show() too quickly
|
||||||
|
CMinWait<50> mWait;
|
||||||
|
|
||||||
public:
|
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 <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>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
// -- The actual controller object for ESP32
|
||||||
|
ESP32RMTController mRMTController;
|
||||||
|
|
||||||
|
// -- This instantiation forces a check on the pin choice
|
||||||
|
FastPin<DATA_PIN> mFastPin;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ClocklessController()
|
||||||
|
: mRMTController(DATA_PIN, T1, T2, T3)
|
||||||
|
{}
|
||||||
|
|
||||||
void init()
|
void init()
|
||||||
{
|
{
|
||||||
// -- Allocate space to save the pixel controller
|
// mRMTController = new ESP32RMTController(DATA_PIN, T1, T2, T3);
|
||||||
// during parallel output
|
|
||||||
mPixels = (PixelController<RGB_ORDER> *) malloc(sizeof(PixelController<RGB_ORDER>));
|
|
||||||
|
|
||||||
// -- 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual uint16_t getMaxRefreshRate() const { return 400; }
|
virtual uint16_t getMaxRefreshRate() const { return 400; }
|
||||||
|
|
||||||
protected:
|
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<RGB_ORDER> & pixels)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) {
|
// -- Make sure the buffer is allocated
|
||||||
gOnChannel[i] = NULL;
|
int size = pixels.size() * 3;
|
||||||
|
uint8_t * pData = mRMTController.getPixelData(size);
|
||||||
|
|
||||||
// -- RMT configuration for transmission --- different in different ESP versions
|
// -- Read out the pixel data using the pixel controller methods that
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0)
|
// perform the scaling and adjustments
|
||||||
rmt_config_t rmt_tx = RMT_DEFAULT_CONFIG_TX(mPin, fastled_get_rmt_channel(i) );
|
int count = 0;
|
||||||
#else
|
while (pixels.has(1)) {
|
||||||
rmt_config_t rmt_tx;
|
*pData++ = pixels.loadAndScale0();
|
||||||
memset(&rmt_tx, 0, sizeof(rmt_tx));
|
*pData++ = pixels.loadAndScale1();
|
||||||
rmt_tx.channel = fastled_get_rmt_channel(i);
|
*pData++ = pixels.loadAndScale2();
|
||||||
rmt_tx.gpio_num = mPin;
|
pixels.advanceData();
|
||||||
rmt_tx.mem_block_num = 1;
|
pixels.stepDithering();
|
||||||
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
count += 3;
|
||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Create a semaphore to block execution until all the controllers are done
|
assert(count == size);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Show pixels
|
// -- Show pixels
|
||||||
// This is the main entry point for the controller.
|
// This is the main entry point for the controller.
|
||||||
virtual void IRAM_ATTR showPixels(PixelController<RGB_ORDER> & pixels)
|
virtual void showPixels(PixelController<RGB_ORDER> & pixels)
|
||||||
{
|
{
|
||||||
if (gNumStarted == 0) {
|
if (FASTLED_RMT_BUILTIN_DRIVER) {
|
||||||
// -- 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)
|
|
||||||
convertAllPixelData(pixels);
|
convertAllPixelData(pixels);
|
||||||
else {
|
} else {
|
||||||
// -- Initialize the local state, save a pointer to the pixel
|
loadPixelData(pixels);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Keep track of the number of strips we've seen
|
mRMTController.showPixels();
|
||||||
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<ClocklessController*>(gControllers[i]);
|
|
||||||
rmt_tx_start(pController->mRMT_channel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Wait here while the rest of the data is sent. The interrupt handler
|
|
||||||
// will keep refilling the RMT buffers until it is all sent; then it
|
|
||||||
// gives the semaphore back.
|
|
||||||
xSemaphoreTake(gTX_sem, portMAX_DELAY);
|
|
||||||
xSemaphoreGive(gTX_sem);
|
|
||||||
|
|
||||||
// -- Reset the counters
|
|
||||||
gNumStarted = 0;
|
|
||||||
gNumDone = 0;
|
|
||||||
gNext = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Convert all pixels to RMT pulses
|
// -- Convert all pixels to RMT pulses
|
||||||
@@ -396,285 +343,25 @@ protected:
|
|||||||
// up-front.
|
// up-front.
|
||||||
void convertAllPixelData(PixelController<RGB_ORDER> & pixels)
|
void convertAllPixelData(PixelController<RGB_ORDER> & pixels)
|
||||||
{
|
{
|
||||||
// -- Compute the pulse values for the whole strip at once.
|
// -- Make sure the data buffer is allocated
|
||||||
// Requires a large buffer
|
mRMTController.initPulseBuffer(pixels.size() * 3);
|
||||||
mBufferSize = pixels.size() * 3 * 8;
|
|
||||||
|
|
||||||
if (mBuffer == NULL) {
|
|
||||||
mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Cycle through the R,G, and B values in the right order,
|
// -- Cycle through the R,G, and B values in the right order,
|
||||||
// storing the pulses in the big buffer
|
// storing the pulses in the big buffer
|
||||||
mCurPulse = 0;
|
|
||||||
|
|
||||||
uint32_t byteval;
|
uint32_t byteval;
|
||||||
while (pixels.has(1)) {
|
while (pixels.has(1)) {
|
||||||
byteval = pixels.loadAndScale0();
|
byteval = pixels.loadAndScale0();
|
||||||
convertByte(byteval);
|
mRMTController.convertByte(byteval);
|
||||||
byteval = pixels.loadAndScale1();
|
byteval = pixels.loadAndScale1();
|
||||||
convertByte(byteval);
|
mRMTController.convertByte(byteval);
|
||||||
byteval = pixels.loadAndScale2();
|
byteval = pixels.loadAndScale2();
|
||||||
convertByte(byteval);
|
mRMTController.convertByte(byteval);
|
||||||
pixels.advanceData();
|
pixels.advanceData();
|
||||||
pixels.stepDithering();
|
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<ClocklessController*>(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<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);
|
|
||||||
// -- 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<ClocklessController*>(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
|
FASTLED_NAMESPACE_END
|
||||||
|
13
sdkconfig
13
sdkconfig
@@ -307,10 +307,9 @@ CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y
|
|||||||
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
|
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
|
||||||
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
|
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
|
||||||
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
|
# 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_NUM=0
|
||||||
CONFIG_ESP_CONSOLE_UART_TX_GPIO=1
|
|
||||||
CONFIG_ESP_CONSOLE_UART_RX_GPIO=3
|
|
||||||
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
|
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
|
||||||
CONFIG_ESP_INT_WDT=y
|
CONFIG_ESP_INT_WDT=y
|
||||||
CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
|
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_MSS=y
|
||||||
# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set
|
# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set
|
||||||
# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set
|
# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set
|
||||||
|
CONFIG_LWIP_TCP_RTO_TIME=3000
|
||||||
# end of TCP
|
# end of TCP
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -879,6 +879,7 @@ CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y
|
|||||||
CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y
|
CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y
|
||||||
CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20
|
CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20
|
||||||
CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1
|
CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1
|
||||||
|
CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192
|
||||||
|
|
||||||
#
|
#
|
||||||
# Auto-detect flash chips
|
# Auto-detect flash chips
|
||||||
@@ -986,7 +987,6 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
|
|||||||
CONFIG_WPA_MBEDTLS_CRYPTO=y
|
CONFIG_WPA_MBEDTLS_CRYPTO=y
|
||||||
# CONFIG_WPA_DEBUG_PRINT is not set
|
# CONFIG_WPA_DEBUG_PRINT is not set
|
||||||
# CONFIG_WPA_TESTING_OPTIONS is not set
|
# CONFIG_WPA_TESTING_OPTIONS is not set
|
||||||
# CONFIG_WPA_TLS_V12 is not set
|
|
||||||
# CONFIG_WPA_WPS_WARS is not set
|
# CONFIG_WPA_WPS_WARS is not set
|
||||||
# end of Supplicant
|
# end of Supplicant
|
||||||
|
|
||||||
@@ -1078,10 +1078,9 @@ CONFIG_MAIN_TASK_STACK_SIZE=3584
|
|||||||
CONFIG_IPC_TASK_STACK_SIZE=1024
|
CONFIG_IPC_TASK_STACK_SIZE=1024
|
||||||
CONFIG_CONSOLE_UART_DEFAULT=y
|
CONFIG_CONSOLE_UART_DEFAULT=y
|
||||||
# CONFIG_CONSOLE_UART_CUSTOM is not set
|
# 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_NUM=0
|
||||||
CONFIG_CONSOLE_UART_TX_GPIO=1
|
|
||||||
CONFIG_CONSOLE_UART_RX_GPIO=3
|
|
||||||
CONFIG_CONSOLE_UART_BAUDRATE=115200
|
CONFIG_CONSOLE_UART_BAUDRATE=115200
|
||||||
CONFIG_INT_WDT=y
|
CONFIG_INT_WDT=y
|
||||||
CONFIG_INT_WDT_TIMEOUT_MS=300
|
CONFIG_INT_WDT_TIMEOUT_MS=300
|
||||||
|
Reference in New Issue
Block a user