forked from bbulkow/FastLED-idf
A major update that removes different kind of flashes.
This commit is contained in:
27
README.md
27
README.md
@ -4,8 +4,11 @@
|
|||||||
|
|
||||||
This port of FastLED 3.3 runs under the 4.x ESP-IDF development environment. Enjoy.
|
This port of FastLED 3.3 runs under the 4.x ESP-IDF development environment. Enjoy.
|
||||||
|
|
||||||
HUGE UPDATE July 18, 2020. I have ported over Sam Guyer's branch, and now I can do network traffic
|
MASSIVE UPDATE Sept 4, 2020. Even after porting Sam Guyer's branch in July, I still
|
||||||
without having glitches. This is a MASSIVE IMPROVEMENT and you owe Sam huge props.
|
had a huge amount of visual artifacts. I've done a huge analysis and have licked the issue to my
|
||||||
|
satisfaction, and can say the system simply doesn't glitch.
|
||||||
|
|
||||||
|
There are some new tunables, and if you're also fighting glitches, you need to read `components/FastLED-idf/ESP-IDF.md`.
|
||||||
|
|
||||||
Note you must use the ESP-IDF environment, and the ESP-IDF build system. That's how the paths and whatnot are created.
|
Note you must use the ESP-IDF environment, and the ESP-IDF build system. That's how the paths and whatnot are created.
|
||||||
|
|
||||||
@ -81,17 +84,25 @@ people for LED control. 8 channels is basically still a ton of LEDs, even
|
|||||||
if the FastLED ESP32 module is even fancier and multiplexes the use of these
|
if the FastLED ESP32 module is even fancier and multiplexes the use of these
|
||||||
channels.
|
channels.
|
||||||
|
|
||||||
|
With the 800k WS8211 and similar that are now common, the end result of the
|
||||||
|
math and the buffers is you need to fill the RMT hardware buffer about every 35microseconds.
|
||||||
|
Even with an RTOS, it seems this is problematic, using C code. For this reason,
|
||||||
|
the default settings are to use two "memory buffers", which double the depth of the
|
||||||
|
RMT hardware buffer, and means that interrupt jitter of up to about 60us can be absorbed
|
||||||
|
without visual artifact. However, this means getting hardware accelleration with
|
||||||
|
only 4 channels instead of 8. This can be changed back to 8.
|
||||||
|
|
||||||
|
Please see the lengthy discussion under `components/FastLED-idf/ESP-IDF.md` to
|
||||||
|
enable some tracing to find your timers, and similar.
|
||||||
|
|
||||||
The FastLED ESP32 RMT use has two modes: one which uses the "driver", and
|
The FastLED ESP32 RMT use has two modes: one which uses the "driver", and
|
||||||
one which doesn't, and claims to be more efficient due to when it's converting
|
one which doesn't, and claims to be more efficient due to when it's converting
|
||||||
between LED RGB and not.
|
between LED RGB and not.
|
||||||
|
|
||||||
Whether you can use the "direct" mode or not depends on whether you have other
|
Whether you can use the "direct" mode or not depends on whether you have other
|
||||||
users of the RMT driver within ESP-IDF.
|
users of the RMT driver within ESP-IDF - however, *using the ESP-IDF supplied driver is
|
||||||
|
not currently working or supported*. I've grown tired of trying to figure out the
|
||||||
It also depends on the version of ESP-IDF you're using. I've found that the "direct"
|
differences of the different versions.
|
||||||
driver works perfectly well in ESP-IDF 4.0, but with higher versions, there
|
|
||||||
are incompatibilities. Since I haven't found solutions yet, the built-in driver
|
|
||||||
is used with ESP-IDF v 4.1 and above.
|
|
||||||
|
|
||||||
Essentially, if you have the Driver turned on, you shouldn't use the direct mode,
|
Essentially, if you have the Driver turned on, you shouldn't use the direct mode,
|
||||||
and if you want to use the direct mode, you should turn off the driver.
|
and if you want to use the direct mode, you should turn off the driver.
|
||||||
|
@ -1,9 +1,130 @@
|
|||||||
# Port to ESP-IDF
|
# Port to ESP-IDF
|
||||||
|
|
||||||
Describe what had to be done, when we did it
|
THis is based off the 3.3 version of FastLED. The 3.3 version is where a lot of development paused, although there are also
|
||||||
|
a lot of good small fixes. This is actually a port of Sam Guyer's awesome ESP32 focused fork, https://github.com/samguyer/FastLED .
|
||||||
|
|
||||||
|
If one is reporting, there's a few bits here and there to be done, but most of the work is in the platform directory.
|
||||||
|
|
||||||
|
I have not tested the "4 wire" I2S based LEDs or code.
|
||||||
|
|
||||||
|
# see section below about glitches!
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
|
|
||||||
This port is to be used with ESP-IDF version 4.x, which went GA on about Feb, 2020.
|
This port is to be used with ESP-IDF version 4.x, which went GA on about Feb, 2020.
|
||||||
|
|
||||||
The FastLED code is vintage 3.3, which includes sophisticated ESP32 support.
|
I have tested it to the greatest extent, at this point, with the 4.2 release, as its more stable than master
|
||||||
|
but far closer to master than 4.0, which has some breaking changes.
|
||||||
|
|
||||||
|
In a number of cases, the code can't easily be made to support all versions of ESP-IDF.
|
||||||
|
|
||||||
|
In more recent versions of ESP-IDF, there is a new `_ll_` interface for the RMT system. Other posters
|
||||||
|
have re-tooled to that interface instead of writing directly to hardware addresses.
|
||||||
|
|
||||||
|
# menuconfig
|
||||||
|
|
||||||
|
I prefer running my code -O3 optimized. I haven't changed any of the stack depths.
|
||||||
|
|
||||||
|
# Differences
|
||||||
|
|
||||||
|
This code defaults to using two memory buffers instead of 1. There are tradeoffs, and you can change the values. See below.
|
||||||
|
|
||||||
|
It is not clear that using the ESP-IDF interrupt handler works anymore, although it should be tried. With larger
|
||||||
|
memory buffers, and using the translate function, it should work no better or worse than any other LEVEL3 interrupt.
|
||||||
|
|
||||||
|
Recent RMT driver code also includes setting the "ownership" of the shared DRAM. THis was overlooked in the FastLED
|
||||||
|
driver code, but has been implemented. It seemed to make no difference to the stability of the system.
|
||||||
|
|
||||||
|
# Difficulties
|
||||||
|
|
||||||
|
## Timing and glitches
|
||||||
|
|
||||||
|
The greatest difficulty with controlling any WS8211 system with the ESP32 / ESP8266 RMT system is timing.
|
||||||
|
|
||||||
|
The WS8211 single wire protocols require two transitions to make a bit.
|
||||||
|
|
||||||
|
The definition of the RMT interface means you put a time between the transition ( in divided 80mhz intervals, fit into a 15 bit
|
||||||
|
field with the high bit being the value to emit ). A single RMT buffer has 64 values, but we use a "double buffer" strategy
|
||||||
|
(recommended by documentation). This means that 32 values, with 32 bits each, requires re-feeding the buffer about every 35 us.
|
||||||
|
The buffer won't fully run dry until 70us, but at 35us.
|
||||||
|
|
||||||
|
With a 400Khz LED, the RMT buffer is 2x longer in time.
|
||||||
|
|
||||||
|
Interupts can be run in C only at "medium priority", which means that there are a class of activities - such as Wifi - which can
|
||||||
|
create enough jitter to disturb 35us timing requirement. I have observed this with a very simple REST web service using
|
||||||
|
the ESP-IDF supplied web server, which doesn't use Flash in any noticable way, other than executing from it - and still, 50us
|
||||||
|
interrupt jitter was observed.
|
||||||
|
|
||||||
|
The RMT interface allows using more buffering, which will overcome this latency. THis is controlled by `MEMORY_BUFFERS` parameter,
|
||||||
|
which is now configurable at the beginning of `clockless_rmt_esp32.h`. To absorb the latencies that I've seen, I need
|
||||||
|
two memory buffers instead of 1. If you're not using wifi, you can perhaps get away with 1. Maybe if you're doing other things
|
||||||
|
on the CPUs, 2 isn't enough and you're going to have to use 4.
|
||||||
|
|
||||||
|
Increasing this value means you can't use as many RMT hardware channels at the same time. IF you use a value of 2, which
|
||||||
|
works in my environment, the code will only use 4 hardware channels in parallel. If you create 8 LED strings, what should
|
||||||
|
happen is 4 run in parallel, and the other 4 get picked up as those finish, so you'll end up using as much parallelism
|
||||||
|
as you have available.
|
||||||
|
|
||||||
|
In order to tune this variable, you'll find another configuration, which is `FASTLED_ESP32_SHOWTIMING`, also in that clockless H file.
|
||||||
|
If you enable this, for each show call, the number of microseconds between calls will be emitted, and a prominent message
|
||||||
|
when a potential underflow is detected. This will allow you to stress the system, look at the interupt jitter, and decide
|
||||||
|
what setting you'd like for the `MEMORY_BUFFER`s.
|
||||||
|
|
||||||
|
Please note also that I've been testing with the fairly common 800Khz WS8211's. If you're using 400Khz, you can almost certainly
|
||||||
|
go back to 1 `MEMORY_BUFFER`. Likewise, if you've got faster LEDs, you might have to go even higher. The choice is yours.
|
||||||
|
|
||||||
|
## Reproducing the issue on other systems
|
||||||
|
|
||||||
|
I consider that the RTOS, at the highest level of interrupt available to the common user, even with 2 CPUs, in
|
||||||
|
an idle system, 10 uS jitter to be something of a bug. At first, I blamed the FastLED code, but have spent quite a bit
|
||||||
|
of energy exhonerating FastLED and placing the blame on ESP-IDF.
|
||||||
|
|
||||||
|
In order to determine the problem, I also did a quick port of the NeoPixelBus interface, and I also used the sample
|
||||||
|
code for WS8211 LEDs which I found in the ESP-IDF examples directory. All exhibited the same behavior.
|
||||||
|
|
||||||
|
Thus the issue is not in the FastLED library, but simply a jitter issue in the RTOS. It seems that people
|
||||||
|
using Arduino do not use the same TCP/IP stack, and do not suffer from these issues. Almost certainly, they are running at
|
||||||
|
lower priorities or with a different interrupt structure.
|
||||||
|
|
||||||
|
A simple test, using 99% espressif code, would open a simple HTTP endpoint and rest service, connect to WIFI and maintain an IP address,
|
||||||
|
then use the existing WS8211 sample code provided by Espressif. I contend that a web server which is simply returning "404", and
|
||||||
|
is being hit by mutiple requests per second ( I used 4 windows with a refresh interval of 2 seconds, but with cached content ) will
|
||||||
|
exhibit this latency.
|
||||||
|
|
||||||
|
# Todo: Running at a higher priority
|
||||||
|
|
||||||
|
ESP-IDF has a page on running at higher prioity: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/hlinterrupts.html
|
||||||
|
|
||||||
|
This page says (somewhat incorrectly) that C code may not be run from high priority interrupts. The document then
|
||||||
|
disagrees with itself, and the example presented calls C saying some C code is safe, but being a bit cagey about
|
||||||
|
why.
|
||||||
|
|
||||||
|
Given this problem happens with all drivers ( custom and ESP-IDF provided ), writing a high-priority driver in assembly
|
||||||
|
which packes the RMT buffer from a "pixel" format buffer seems a very reasonable optimization. Or, using the current
|
||||||
|
interrupt driver, and simply throwing the disasm into a .S file.
|
||||||
|
|
||||||
|
I would greatly hope that ESP-IDF improves the documentation around higher priority interrupts, and about manipulating the interrupts
|
||||||
|
in the system. I'd like to be able to profile and find what's causing these long delays, and drop their priority a little.
|
||||||
|
The FreeRTOS documentation ( on which ESP-IDF is built ) clearly says that high priority interrupts are required for motor control,
|
||||||
|
and LED control falls into that category. Placing the unnecessary barrier of assembly language isn't a terrible thing,
|
||||||
|
but it's not the right way - show how to write safe C code and allow raising the priority, even if some people will
|
||||||
|
abuse the privledge.
|
||||||
|
|
||||||
|
|
||||||
|
# Todo - why the jitter?
|
||||||
|
|
||||||
|
The large glitches ( 50us and up ) at the highest possible prority level, even with 2 cores of 240Mhz, is almost implausible.
|
||||||
|
|
||||||
|
Possible causes might be linked to the Flash subsystem. However, disabling all flash reads and writes ( the SPI calls ) doesn't change the glitching behavior.
|
||||||
|
|
||||||
|
Increasing network traffic does. Thus, the Wifi system is implicated. Work to do would be to decrease, somehow, the priority of
|
||||||
|
those interrupts. One forum poster said they found a way. As TCP and Wifi are meant to be unreliable, this would cause
|
||||||
|
performance issues, and that would have to be weighed against the nature of the poor LED output.
|
||||||
|
|
||||||
|
# Todo - why visual artifacts?
|
||||||
|
|
||||||
|
I haven't put a scope on, but I'm a little surprised that if you only throw an "R" and not a "G and B" on the wire, a pixel changes.
|
||||||
|
This appears to be the reason you get a single pixel flash, and there's nothing one can do about that other than these deeper buffers.
|
||||||
|
I would hope that other WS8211 type LEDs are a bit more robust and would only change color when they get a full set of R,G, and B.
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
#define FASTLED_INTERNAL
|
#define FASTLED_INTERNAL
|
||||||
#include "FastLED.h"
|
#include "FastLED.h"
|
||||||
|
|
||||||
static const char *TAG = "FastLED";
|
//static const char *TAG = "FastLED";
|
||||||
|
|
||||||
#define FASTLED_ESP32_SHOWTIMING 1
|
|
||||||
|
|
||||||
// -- Forward reference
|
// -- Forward reference
|
||||||
class ESP32RMTController;
|
class ESP32RMTController;
|
||||||
@ -30,18 +29,123 @@ static intr_handle_t gRMT_intr_handle = NULL;
|
|||||||
// Semaphore is not given until all data has been sent
|
// Semaphore is not given until all data has been sent
|
||||||
static xSemaphoreHandle gTX_sem = NULL;
|
static xSemaphoreHandle gTX_sem = NULL;
|
||||||
|
|
||||||
// -- Make sure we can't call show() too quickly
|
// -- Make sure we can't call show() too quickly (fastled library)
|
||||||
CMinWait<50> gWait;
|
CMinWait<50> gWait;
|
||||||
|
|
||||||
static bool gInitialized = false;
|
static bool gInitialized = false;
|
||||||
|
|
||||||
// -- SZG: For debugging purposes
|
/*
|
||||||
|
** general DRAM system for printing during faster IRQs
|
||||||
|
** be careful not to set the size too large, because code that prints
|
||||||
|
** has the tendancy to do a stack alloc of the same size...
|
||||||
|
*/
|
||||||
|
|
||||||
|
// -- BB: For debugging purposes
|
||||||
#if FASTLED_ESP32_SHOWTIMING == 1
|
#if FASTLED_ESP32_SHOWTIMING == 1
|
||||||
static uint32_t gLastFill[8];
|
|
||||||
static int gTooSlow[8];
|
#define MEMORYBUF_SIZE 256
|
||||||
static uint32_t gTotalTime[8];
|
DRAM_ATTR char g_memorybuf[MEMORYBUF_SIZE] = {0};
|
||||||
|
DRAM_ATTR char *g_memorybuf_write = g_memorybuf;
|
||||||
|
|
||||||
|
void IRAM_ATTR memorybuf_add( char *b ) {
|
||||||
|
|
||||||
|
int buflen = strlen(b);
|
||||||
|
|
||||||
|
// don't overflow
|
||||||
|
int bufRemain = sizeof(g_memorybuf) - ( g_memorybuf_write - g_memorybuf );
|
||||||
|
if ( bufRemain == 0 ) return;
|
||||||
|
if (bufRemain < buflen) buflen = bufRemain;
|
||||||
|
|
||||||
|
memcpy(g_memorybuf_write, b, buflen);
|
||||||
|
g_memorybuf_write += buflen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR memorybuf_add( char c ) {
|
||||||
|
|
||||||
|
// don't overflow
|
||||||
|
int bufRemain = sizeof(g_memorybuf) - ( g_memorybuf_write - g_memorybuf );
|
||||||
|
if ( bufRemain < 1 ) return;
|
||||||
|
|
||||||
|
*g_memorybuf_write = c;
|
||||||
|
g_memorybuf_write++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR memorybuf_insert( char *b, int buflen ) {
|
||||||
|
// don't overflow
|
||||||
|
int maxbuf = sizeof(g_memorybuf) - ( g_memorybuf_write - g_memorybuf );
|
||||||
|
if ( maxbuf == 0 ) return;
|
||||||
|
if (maxbuf < buflen) buflen = maxbuf;
|
||||||
|
|
||||||
|
memcpy(g_memorybuf_write, b, buflen);
|
||||||
|
g_memorybuf_write += buflen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// often one wants a separator and an integer, do a helper
|
||||||
|
void IRAM_ATTR memorybuf_int( int i, char sep) {
|
||||||
|
|
||||||
|
// am I full already?
|
||||||
|
int maxbuf = sizeof(g_memorybuf) - ( g_memorybuf_write - g_memorybuf );
|
||||||
|
if ( maxbuf == 0 ) return;
|
||||||
|
|
||||||
|
// for speed, just make sure I have 12 bytes, even though maybe I need fewer
|
||||||
|
// 12 is the number because I need a null which I will fill with sep, and
|
||||||
|
// there's always the chance of a minus
|
||||||
|
if (maxbuf <= 12) return;
|
||||||
|
|
||||||
|
// prep the buf and find the length ( can't copy)
|
||||||
|
itoa(i, g_memorybuf_write, 10);
|
||||||
|
int buflen = strlen(g_memorybuf_write);
|
||||||
|
g_memorybuf_write[buflen] = sep;
|
||||||
|
g_memorybuf_write += (buflen + 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// get from the front... requires a memmove because overlaps.
|
||||||
|
// *len input is the size of the buf, return is the length you got
|
||||||
|
|
||||||
|
// this will always be the most efficient if you ask for a buffer that's as large as the
|
||||||
|
// capture buffer
|
||||||
|
void memorybuf_get(char *b, int *len) {
|
||||||
|
// amount in the buffer
|
||||||
|
int blen = g_memorybuf_write - g_memorybuf ;
|
||||||
|
if ( blen == 0 ) {
|
||||||
|
*len = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (blen > *len) {
|
||||||
|
memcpy(b, g_memorybuf, *len);
|
||||||
|
int olen = blen - *len;
|
||||||
|
memmove(g_memorybuf, g_memorybuf_write - olen, olen);
|
||||||
|
g_memorybuf_write = g_memorybuf + olen;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(b, g_memorybuf, blen);
|
||||||
|
g_memorybuf_write = g_memorybuf;
|
||||||
|
*len = blen;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* FASTLED_ESP32_SHOWTIMING == 1 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
** in later versions of the driver, they very carefully set the "mem_owner"
|
||||||
|
** flag before copying over. Let's do the same.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// probably already defined.
|
||||||
|
#ifndef RMT_MEM_OWNER_SW
|
||||||
|
#define RMT_MEM_OWNER_SW 0
|
||||||
|
#define RMT_MEM_OWNER_HW 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline void rmt_set_mem_owner(rmt_channel_t channel, uint8_t owner)
|
||||||
|
{
|
||||||
|
RMT.conf_ch[(uint16_t)channel].conf1.mem_owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ESP32RMTController::ESP32RMTController(int DATA_PIN, int T1, int T2, int T3)
|
ESP32RMTController::ESP32RMTController(int DATA_PIN, int T1, int T2, int T3)
|
||||||
: mPixelData(0),
|
: mPixelData(0),
|
||||||
mSize(0),
|
mSize(0),
|
||||||
@ -70,6 +174,13 @@ ESP32RMTController::ESP32RMTController(int DATA_PIN, int T1, int T2, int T3)
|
|||||||
gControllers[gNumControllers] = this;
|
gControllers[gNumControllers] = this;
|
||||||
gNumControllers++;
|
gNumControllers++;
|
||||||
|
|
||||||
|
// -- Expected number of CPU cycles between buffer fills
|
||||||
|
mCyclesPerFill = (T1 + T2 + T3) * PULSES_PER_FILL;
|
||||||
|
|
||||||
|
// -- If there is ever an interval greater than 1.75 times
|
||||||
|
// the expected time, then bail out.
|
||||||
|
mMaxCyclesPerFill = mCyclesPerFill + ((mCyclesPerFill * 3)/4);
|
||||||
|
|
||||||
mPin = gpio_num_t(DATA_PIN);
|
mPin = gpio_num_t(DATA_PIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,15 +202,13 @@ void ESP32RMTController::init()
|
|||||||
{
|
{
|
||||||
if (gInitialized) return;
|
if (gInitialized) return;
|
||||||
|
|
||||||
ESP_LOGW(TAG, "controller init");
|
|
||||||
|
|
||||||
for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) {
|
for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) {
|
||||||
gOnChannel[i] = NULL;
|
gOnChannel[i] = NULL;
|
||||||
|
|
||||||
// -- RMT configuration for transmission
|
// -- RMT configuration for transmission
|
||||||
rmt_config_t rmt_tx = RMT_DEFAULT_CONFIG_TX((gpio_num_t)0, rmt_channel_t(i));
|
rmt_config_t rmt_tx = RMT_DEFAULT_CONFIG_TX((gpio_num_t)0, rmt_channel_t(i));
|
||||||
rmt_tx.gpio_num = gpio_num_t(0); // The particular pin will be assigned later
|
rmt_tx.gpio_num = gpio_num_t(0); // The particular pin will be assigned later
|
||||||
rmt_tx.mem_block_num = 1;
|
rmt_tx.mem_block_num = MEM_BLOCK_NUM;
|
||||||
rmt_tx.clk_div = DIVIDER;
|
rmt_tx.clk_div = DIVIDER;
|
||||||
rmt_tx.tx_config.loop_en = false;
|
rmt_tx.tx_config.loop_en = false;
|
||||||
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||||
@ -108,7 +217,12 @@ void ESP32RMTController::init()
|
|||||||
rmt_tx.tx_config.idle_output_en = true;
|
rmt_tx.tx_config.idle_output_en = true;
|
||||||
|
|
||||||
// -- Apply the configuration
|
// -- Apply the configuration
|
||||||
rmt_config(&rmt_tx);
|
// warning: using more than MEM_BLOCK_NUM 1 means sometimes this might fail because
|
||||||
|
// we don't have enough MEM_BLOCKs. Todo: add code to track and only allocate as many channels
|
||||||
|
// as we have memblocks.
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
rmt_config(&rmt_tx)
|
||||||
|
);
|
||||||
|
|
||||||
if (FASTLED_RMT_BUILTIN_DRIVER) {
|
if (FASTLED_RMT_BUILTIN_DRIVER) {
|
||||||
rmt_driver_install(rmt_channel_t(i), 0, 0);
|
rmt_driver_install(rmt_channel_t(i), 0, 0);
|
||||||
@ -131,8 +245,11 @@ void ESP32RMTController::init()
|
|||||||
// interrupt handler must work for all different kinds of
|
// interrupt handler must work for all different kinds of
|
||||||
// strips, so it delegates to the refill function for each
|
// strips, so it delegates to the refill function for each
|
||||||
// specific instantiation of ClocklessController.
|
// specific instantiation of ClocklessController.
|
||||||
if (gRMT_intr_handle == NULL)
|
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);
|
ESP_ERROR_CHECK(
|
||||||
|
esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, interruptHandler, 0, &gRMT_intr_handle)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gInitialized = true;
|
gInitialized = true;
|
||||||
@ -142,6 +259,7 @@ void ESP32RMTController::init()
|
|||||||
// This is the main entry point for the pixel controller
|
// This is the main entry point for the pixel controller
|
||||||
void ESP32RMTController::showPixels()
|
void ESP32RMTController::showPixels()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (gNumStarted == 0) {
|
if (gNumStarted == 0) {
|
||||||
// -- First controller: make sure everything is set up
|
// -- First controller: make sure everything is set up
|
||||||
ESP32RMTController::init();
|
ESP32RMTController::init();
|
||||||
@ -156,7 +274,7 @@ void ESP32RMTController::showPixels()
|
|||||||
gNumStarted++;
|
gNumStarted++;
|
||||||
|
|
||||||
// -- The last call to showPixels is the one responsible for doing
|
// -- The last call to showPixels is the one responsible for doing
|
||||||
// all of the actual worl
|
// all of the actual work
|
||||||
if (gNumStarted == gNumControllers) {
|
if (gNumStarted == gNumControllers) {
|
||||||
gNext = 0;
|
gNext = 0;
|
||||||
|
|
||||||
@ -202,15 +320,19 @@ void ESP32RMTController::showPixels()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FASTLED_ESP32_SHOWTIMING == 1
|
#if FASTLED_ESP32_SHOWTIMING == 1
|
||||||
// uint32_t expected = (2080000L / (1000000000L/F_CPU));
|
// the interrupts may have dumped things to the buffer. Print it.
|
||||||
for (int i = 0; i < gNumControllers; i++) {
|
// warning: this does a fairly large stack allocation.
|
||||||
if (gTooSlow[i] > 0) {
|
char mb[MEMORYBUF_SIZE+1];
|
||||||
printf("Channel %d total time %d too slow %d\n",i,gTotalTime[i],gTooSlow[i]);
|
int mb_len = MEMORYBUF_SIZE;
|
||||||
}
|
memorybuf_get(mb, &mb_len);
|
||||||
}
|
if (mb_len > 0) {
|
||||||
#endif
|
mb[mb_len] = 0;
|
||||||
|
printf(" rmt irq print: %s\n",mb);
|
||||||
|
}
|
||||||
|
#endif /* FASTLED_ESP32_SHOWTIMING == 1 */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Start up the next controller
|
// -- Start up the next controller
|
||||||
@ -230,6 +352,7 @@ void ESP32RMTController::startNext(int channel)
|
|||||||
// for it to finish.
|
// for it to finish.
|
||||||
void ESP32RMTController::startOnChannel(int channel)
|
void ESP32RMTController::startOnChannel(int channel)
|
||||||
{
|
{
|
||||||
|
|
||||||
// -- Assign this channel and configure the RMT
|
// -- Assign this channel and configure the RMT
|
||||||
mRMT_channel = rmt_channel_t(channel);
|
mRMT_channel = rmt_channel_t(channel);
|
||||||
|
|
||||||
@ -254,7 +377,7 @@ void ESP32RMTController::startOnChannel(int channel)
|
|||||||
mCur = 0;
|
mCur = 0;
|
||||||
mWhichHalf = 0;
|
mWhichHalf = 0;
|
||||||
|
|
||||||
// -- Fill both halves of the RMT buffer (a totaly of 64 bits of pixel data)
|
// -- Fill both halves of the RMT buffer (a totality of 64 bits of pixel data)
|
||||||
fillNext();
|
fillNext();
|
||||||
fillNext();
|
fillNext();
|
||||||
|
|
||||||
@ -264,6 +387,7 @@ void ESP32RMTController::startOnChannel(int channel)
|
|||||||
// -- Kick off the transmission
|
// -- Kick off the transmission
|
||||||
tx_start();
|
tx_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Start RMT transmission
|
// -- Start RMT transmission
|
||||||
@ -271,12 +395,8 @@ void ESP32RMTController::startOnChannel(int channel)
|
|||||||
void ESP32RMTController::tx_start()
|
void ESP32RMTController::tx_start()
|
||||||
{
|
{
|
||||||
rmt_tx_start(mRMT_channel, true);
|
rmt_tx_start(mRMT_channel, true);
|
||||||
|
mLastFill = __clock_cycles();
|
||||||
|
|
||||||
#if FASTLED_ESP32_SHOWTIMING == 1
|
|
||||||
gLastFill[mRMT_channel] = __clock_cycles();
|
|
||||||
gTooSlow[mRMT_channel] = 0;
|
|
||||||
gTotalTime[mRMT_channel] = 0;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- A controller is done
|
// -- A controller is done
|
||||||
@ -288,7 +408,6 @@ void ESP32RMTController::tx_start()
|
|||||||
void ESP32RMTController::doneOnChannel(rmt_channel_t channel, void * arg)
|
void ESP32RMTController::doneOnChannel(rmt_channel_t channel, void * arg)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// -- Turn off output on the pin
|
// -- Turn off output on the pin
|
||||||
// SZG: Do I really need to do this?
|
// SZG: Do I really need to do this?
|
||||||
// ESP32RMTController * pController = gOnChannel[channel];
|
// ESP32RMTController * pController = gOnChannel[channel];
|
||||||
@ -321,9 +440,6 @@ void ESP32RMTController::doneOnChannel(rmt_channel_t channel, void * arg)
|
|||||||
// next half of the RMT buffer with data.
|
// next half of the RMT buffer with data.
|
||||||
void IRAM_ATTR ESP32RMTController::interruptHandler(void *arg)
|
void IRAM_ATTR ESP32RMTController::interruptHandler(void *arg)
|
||||||
{
|
{
|
||||||
#if FASTLED_ESP32_SHOWTIMING == 1
|
|
||||||
int64_t now = __clock_cycles();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// -- The basic structure of this code is borrowed from the
|
// -- The basic structure of this code is borrowed from the
|
||||||
// interrupt handler in esp-idf/components/driver/rmt.c
|
// interrupt handler in esp-idf/components/driver/rmt.c
|
||||||
@ -331,64 +447,124 @@ void IRAM_ATTR ESP32RMTController::interruptHandler(void *arg)
|
|||||||
uint8_t channel;
|
uint8_t channel;
|
||||||
|
|
||||||
for (channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; 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];
|
ESP32RMTController * pController = gOnChannel[channel];
|
||||||
if (pController != NULL) {
|
if (pController != NULL) {
|
||||||
|
|
||||||
|
int tx_done_bit = channel * 3;
|
||||||
|
int tx_next_bit = channel + 24;
|
||||||
|
|
||||||
if (intr_st & BIT(tx_next_bit)) {
|
if (intr_st & BIT(tx_next_bit)) {
|
||||||
// -- More to send on this channel
|
// -- More to send on this channel
|
||||||
RMT.int_clr.val |= BIT(tx_next_bit);
|
RMT.int_clr.val |= BIT(tx_next_bit);
|
||||||
pController->fillNext();
|
|
||||||
|
|
||||||
#if FASTLED_ESP32_SHOWTIMING == 1
|
// if timing's NOT ok, have to bail
|
||||||
uint32_t delta = (now - gLastFill[channel]);
|
if (true == pController->timingOk()) {
|
||||||
if (delta > C_NS(50500)) {
|
|
||||||
gTooSlow[channel]++;
|
pController->fillNext();
|
||||||
}
|
|
||||||
gTotalTime[channel] += delta;
|
|
||||||
gLastFill[channel] = now;
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
// -- Transmission is complete on this channel
|
|
||||||
if (intr_st & BIT(tx_done_bit)) {
|
|
||||||
RMT.int_clr.val |= BIT(tx_done_bit);
|
|
||||||
#if FASTLED_ESP32_SHOWTIMING == 1
|
|
||||||
uint32_t delta = (now - gLastFill[channel]);
|
|
||||||
gTotalTime[channel] += delta;
|
|
||||||
#endif
|
|
||||||
doneOnChannel(rmt_channel_t(channel), 0);
|
|
||||||
}
|
}
|
||||||
|
} // -- Transmission is complete on this channel
|
||||||
|
else if (intr_st & BIT(tx_done_bit)) {
|
||||||
|
|
||||||
|
RMT.int_clr.val |= BIT(tx_done_bit);
|
||||||
|
doneOnChannel(rmt_channel_t(channel), 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DRAM_ATTR char g_bail_str[] = "_BAIL_";
|
||||||
|
|
||||||
|
// check to see if there's a bad timing. Returns
|
||||||
|
// we may be behind the necessary timing, so we should bail out of this 'show'.
|
||||||
|
//
|
||||||
|
// returns true if the timing is OK, false if bad
|
||||||
|
|
||||||
|
bool IRAM_ATTR ESP32RMTController::timingOk() {
|
||||||
|
|
||||||
|
// last time is always delayed, don't check that one
|
||||||
|
if (mCur >= mSize) return(true);
|
||||||
|
|
||||||
|
uint32_t delta = __clock_cycles() - mLastFill;
|
||||||
|
|
||||||
|
// interesting test - what if we only write 4? will nothing else light?
|
||||||
|
if ( delta > mMaxCyclesPerFill) {
|
||||||
|
|
||||||
|
#if FASTLED_ESP32_SHOWTIMING == 1
|
||||||
|
memorybuf_add('!');
|
||||||
|
memorybuf_int( CYCLES_TO_US(delta), '-' );
|
||||||
|
memorybuf_int( CYCLES_TO_US(mMaxCyclesPerFill), '-');
|
||||||
|
memorybuf_int( mCur, ':' );
|
||||||
|
memorybuf_int( mSize, ':' );
|
||||||
|
memorybuf_add( g_bail_str );
|
||||||
|
#endif /* FASTLED_ESP32_SHOWTIMING == 1 */
|
||||||
|
|
||||||
|
// how do we bail out? It seems if we simply call rmt_tx_stop,
|
||||||
|
// we'll still flicker on the end. Setting mCur to mSize has the side effect
|
||||||
|
// of triggering the other code that says "we're finished"
|
||||||
|
|
||||||
|
// Old code also set this, hoping it wouldn't send garbage bytes
|
||||||
|
mCur = mSize;
|
||||||
|
|
||||||
|
// other code also set some zeros to make sure there wasn't anything bad.
|
||||||
|
rmt_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_SW);
|
||||||
|
for (uint32_t j = 0; j < PULSES_PER_FILL; j++) {
|
||||||
|
* mRMT_mem_ptr++ = 0;
|
||||||
|
}
|
||||||
|
rmt_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_HW);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FASTLED_ESP32_SHOWTIMING == 1
|
||||||
|
else {
|
||||||
|
memorybuf_int( CYCLES_TO_US(delta), '-' );
|
||||||
|
}
|
||||||
|
#endif /* FASTLED_ESP32_SHOWTIMING == 1 */
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// -- Fill RMT buffer
|
// -- Fill RMT buffer
|
||||||
// Puts 32 bits of pixel data into the next 32 slots in the RMT memory
|
// 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
|
// 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.
|
// long to hold the signal high, followed by how long to hold it low.
|
||||||
void IRAM_ATTR ESP32RMTController::fillNext()
|
void IRAM_ATTR ESP32RMTController::fillNext()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (mCur < mSize) {
|
if (mCur < mSize) {
|
||||||
|
|
||||||
// -- Get the zero and one values into local variables
|
// -- Get the zero and one values into local variables
|
||||||
|
// each one is a "rmt_item_t", which contains two values, which is very convenient
|
||||||
register uint32_t one_val = mOne.val;
|
register uint32_t one_val = mOne.val;
|
||||||
register uint32_t zero_val = mZero.val;
|
register uint32_t zero_val = mZero.val;
|
||||||
|
|
||||||
// -- Use locals for speed
|
// -- Use locals for speed
|
||||||
volatile register uint32_t * pItem = mRMT_mem_ptr;
|
volatile register uint32_t * pItem = mRMT_mem_ptr;
|
||||||
|
|
||||||
// -- Get the next four bytes of pixel data
|
// set the owner to SW --- current driver does this but its not clear it matters
|
||||||
register uint32_t pixeldata = mPixelData[mCur];
|
rmt_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_SW);
|
||||||
mCur++;
|
|
||||||
|
|
||||||
// Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the
|
// Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the
|
||||||
// rmt_item32_t value corresponding to the buffered bit value
|
// 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;
|
for (int i=0; i < PULSES_PER_FILL / 32; i++) {
|
||||||
|
if (mCur < mSize) {
|
||||||
|
register uint32_t thispixel = mPixelData[mCur];
|
||||||
|
for (int j = 0; j < 32; j++) {
|
||||||
|
|
||||||
|
*pItem++ = (thispixel & 0x80000000L) ? one_val : zero_val;
|
||||||
|
// Replaces: RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = val;
|
||||||
|
thispixel <<= 1;
|
||||||
|
}
|
||||||
|
mCur++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if you hit the end, add 0 for signal
|
||||||
|
*pItem++ = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Flip to the other half, resetting the pointer if necessary
|
// -- Flip to the other half, resetting the pointer if necessary
|
||||||
@ -400,11 +576,20 @@ void IRAM_ATTR ESP32RMTController::fillNext()
|
|||||||
|
|
||||||
// -- Store the new pointer back into the object
|
// -- Store the new pointer back into the object
|
||||||
mRMT_mem_ptr = pItem;
|
mRMT_mem_ptr = pItem;
|
||||||
|
|
||||||
|
// set the owner back to HW
|
||||||
|
rmt_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_HW);
|
||||||
|
|
||||||
|
// update the time I last filled
|
||||||
|
mLastFill = __clock_cycles();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// -- No more data; signal to the RMT we are done
|
// -- No more data; signal to the RMT we are done
|
||||||
|
rmt_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_SW);
|
||||||
for (uint32_t j = 0; j < PULSES_PER_FILL; j++) {
|
for (uint32_t j = 0; j < PULSES_PER_FILL; j++) {
|
||||||
* mRMT_mem_ptr++ = 0;
|
* mRMT_mem_ptr++ = 0;
|
||||||
}
|
}
|
||||||
|
rmt_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_HW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Integration into FastLED ClocklessController
|
* Integration into FastLED ClocklessController
|
||||||
|
* Copyright (c) 2020 Brian Bulkowski brian@bulkowski.org
|
||||||
* Copyright (c) 2018,2019,2020 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
|
||||||
@ -138,26 +139,32 @@ __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
|
|
||||||
// Useful for finding out if timing is being messed up by other things
|
|
||||||
// on the processor (WiFi, for example)
|
|
||||||
//#ifndef FASTLED_RMT_SHOW_TIMER
|
|
||||||
//#define FASTLED_RMT_SHOW_TIMER false
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
// -- 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 */
|
/* there is no point in higher dividers, as this parameter only needs to make
|
||||||
#define PULSES_PER_FILL 32 /* Half of the channel buffer */
|
sure the scaling factors of the RMT intervals fit in 15 bits. */
|
||||||
|
|
||||||
|
#define MEM_BLOCK_NUM 2 /* the number of memory blocks. There are 8 for the entire RMT system, and nominally
|
||||||
|
1 per channel. Using a larger number reduces the number of hardware channels that can be used
|
||||||
|
at one time, but increases the resistance to RTOS interrupt jitter. 1 seems to be good enough,
|
||||||
|
but jitter created by wifi might still cause glitches and 2 or more may be reuired. */
|
||||||
|
#define PULSES_PER_CHANNEL (64 * MEM_BLOCK_NUM) /* A channel has a 64 "pulse" buffer of 32 bits (aka Items in RMT interface) */
|
||||||
|
#define PULSES_PER_FILL (PULSES_PER_CHANNEL / 2) /* Half of the channel buffer */
|
||||||
|
// PPF must be a multipel of 32 or fillNext must be re-coded
|
||||||
|
|
||||||
// -- 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
|
||||||
|
// -- according to https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html
|
||||||
|
// the RMT clock is taken at 80 000 000
|
||||||
#define F_CPU_RMT ( 80000000L)
|
#define F_CPU_RMT ( 80000000L)
|
||||||
#define RMT_CYCLES_PER_SEC (F_CPU_RMT/DIVIDER)
|
#define RMT_CYCLES_PER_SEC (F_CPU_RMT/DIVIDER)
|
||||||
#define RMT_CYCLES_PER_ESP_CYCLE (F_CPU / RMT_CYCLES_PER_SEC)
|
#define RMT_CYCLES_PER_ESP_CYCLE (F_CPU / RMT_CYCLES_PER_SEC)
|
||||||
#define ESP_TO_RMT_CYCLES(n) ((n) / (RMT_CYCLES_PER_ESP_CYCLE))
|
#define ESP_TO_RMT_CYCLES(n) ((n) / (RMT_CYCLES_PER_ESP_CYCLE))
|
||||||
|
|
||||||
|
#define CYCLES_TO_US(n) ( (n) / (F_CPU / 1000000L ))
|
||||||
|
|
||||||
// -- Number of cycles to signal the strip to latch
|
// -- Number of cycles to signal the strip to latch
|
||||||
|
// in RMT cycles
|
||||||
#define NS_PER_CYCLE ( 1000000000L / RMT_CYCLES_PER_SEC )
|
#define NS_PER_CYCLE ( 1000000000L / RMT_CYCLES_PER_SEC )
|
||||||
#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)
|
||||||
@ -174,8 +181,17 @@ __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() {
|
|||||||
|
|
||||||
// -- Number of RMT channels to use (up to 8)
|
// -- Number of RMT channels to use (up to 8)
|
||||||
// Redefine this value to 1 to force serial output
|
// Redefine this value to 1 to force serial output
|
||||||
|
// -- todo: this is wrong if MEM_BLOCK_NUM is 3, but at least it's safe
|
||||||
#ifndef FASTLED_RMT_MAX_CHANNELS
|
#ifndef FASTLED_RMT_MAX_CHANNELS
|
||||||
#define FASTLED_RMT_MAX_CHANNELS 8
|
#define FASTLED_RMT_MAX_CHANNELS ( 8 / MEM_BLOCK_NUM )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// use this if you want to try the flash lock
|
||||||
|
// doesn't seem to make any postitive difference
|
||||||
|
//#define FASTLED_ESP32_FLASH_LOCK 1
|
||||||
|
|
||||||
|
#ifndef FASTLED_ESP32_SHOWTIMING
|
||||||
|
#define FASTLED_ESP32_SHOWTIMING 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class ESP32RMTController
|
class ESP32RMTController
|
||||||
@ -192,6 +208,12 @@ private:
|
|||||||
rmt_item32_t mZero;
|
rmt_item32_t mZero;
|
||||||
rmt_item32_t mOne;
|
rmt_item32_t mOne;
|
||||||
|
|
||||||
|
// -- Total expected time to send 32 bits
|
||||||
|
// Each strip should get an interrupt roughly at this interval
|
||||||
|
uint32_t mCyclesPerFill;
|
||||||
|
uint32_t mMaxCyclesPerFill;
|
||||||
|
uint32_t mLastFill;
|
||||||
|
|
||||||
// -- Pixel data
|
// -- Pixel data
|
||||||
uint32_t * mPixelData;
|
uint32_t * mPixelData;
|
||||||
int mSize;
|
int mSize;
|
||||||
@ -215,6 +237,9 @@ public:
|
|||||||
// member variables.
|
// member variables.
|
||||||
ESP32RMTController(int DATA_PIN, int T1, int T2, int T3);
|
ESP32RMTController(int DATA_PIN, int T1, int T2, int T3);
|
||||||
|
|
||||||
|
// -- Get max cycles per fill
|
||||||
|
uint32_t IRAM_ATTR getMaxCyclesPerFill() const { return mMaxCyclesPerFill; }
|
||||||
|
|
||||||
// -- Get or create the pixel data buffer
|
// -- Get or create the pixel data buffer
|
||||||
uint32_t * getPixelBuffer(int size_in_bytes);
|
uint32_t * getPixelBuffer(int size_in_bytes);
|
||||||
|
|
||||||
@ -254,6 +279,16 @@ public:
|
|||||||
// next half of the RMT buffer with data.
|
// next half of the RMT buffer with data.
|
||||||
static void IRAM_ATTR interruptHandler(void *arg);
|
static void IRAM_ATTR interruptHandler(void *arg);
|
||||||
|
|
||||||
|
// -- Determine if there was a long pause
|
||||||
|
// Especially in ESP32, it seems hard to guarentee that interrupts fire
|
||||||
|
// this function checks to see if there has been a long period
|
||||||
|
// so you can abort an RMT send without sending bits that cause flashes
|
||||||
|
//
|
||||||
|
// SIDE EFFECT: Triggers stop of the channel
|
||||||
|
//
|
||||||
|
// return FALSE means one needs to abort
|
||||||
|
bool IRAM_ATTR timingOk();
|
||||||
|
|
||||||
// -- Fill RMT buffer
|
// -- Fill RMT buffer
|
||||||
// Puts 32 bits of pixel data into the next 32 slots in the RMT memory
|
// 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
|
// Each data bit is represented by a 32-bit RMT item that specifies how
|
||||||
|
Reference in New Issue
Block a user