Files
FastLED-idf/platforms/esp/32/clockless_rmt_esp32.cpp
T

726 lines
22 KiB
C++

#define FASTLED_INTERNAL
#include "FastLED.h"
//static const char *TAG = "FastLED";
#include "esp_idf_version.h"
#include "hal/rmt_ll.h"
#include "driver/rmt.h"
// RMTMEM address is declared in <target>.peripherals.ld
extern "C" rmt_mem_t RMTMEM;
// -- 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 SemaphoreHandle_t gTX_sem = NULL;
// -- Make sure we can't call show() too quickly (fastled library)
CMinWait<55> gWait;
static bool gInitialized = false;
/*
** 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
#define MEMORYBUF_SIZE 256
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 */
/*
** Internal functions that need to be exposed
**
** In 4.0, there's one code structure -- the "ll" interfaces have not been exposed, but
** one can use the rmt_ functions without setting up the driver.
*
** In ESP-IDF 4.1, the functions route through a different structure, which is only set up
** when the internal driver is called.... but one can bypass with the ll functions.
**
** In 4.2, the structures changed again, to go more directly.
**
** Really, epressif. You have so much error checking in your code, trying to protect embedded
** programmers for this and that, and keep changing code that probably doesn't need to change.
*/
#if ( ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)) && ( ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 2, 0))
#define USE_FASTLED_RMT_FNS 1
#include <hal/rmt_ll.h>
esp_err_t fastled_set_tx_thr_intr_en(rmt_channel_t channel, bool en, uint16_t evt_thresh)
{
/* regs is rmt_dev_t, which is the static "RMT" */
rmt_ll_set_tx_limit(&RMT, channel, evt_thresh);
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true);
return(ESP_OK);
}
esp_err_t fastled_set_tx_intr_en(rmt_channel_t channel, bool en)
{
/* regs is rmt_dev_t, which is the static "RMT" */
rmt_ll_enable_tx_end_interrupt(&RMT, channel, en);
return(ESP_OK);
}
esp_err_t fastled_tx_start(rmt_channel_t channel, bool tx_idx_rst)
{
if (tx_idx_rst) {
rmt_ll_reset_tx_pointer(&RMT, channel);
}
rmt_ll_clear_tx_end_interrupt(&RMT, channel);
rmt_ll_enable_tx_end_interrupt(&RMT, channel, true);
rmt_ll_start_tx(&RMT, channel);
return ESP_OK;
}
#else
#define USE_FASTLED_RMT_FNS 0
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 2, 0)
/*
** 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
static inline void fastled_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)
: 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++;
// -- 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);
}
// -- Get or create the buffer for the pixel data
// We can't allocate it ahead of time because we don't have
// the PixelController object until show is called.
uint32_t * ESP32RMTController::getPixelBuffer(int size_in_bytes)
{
if (mPixelData == 0) {
mSize = ((size_in_bytes-1) / sizeof(uint32_t)) + 1;
mPixelData = (uint32_t *) calloc( mSize, sizeof(uint32_t));
}
return mPixelData;
}
// -- Initialize RMT subsystem
// This only needs to be done once
void ESP32RMTController::init()
{
if (gInitialized) return;
// -- Create a semaphore to block execution until all the controllers are done
if (gTX_sem == NULL) {
gTX_sem = xSemaphoreCreateBinary();
xSemaphoreGive(gTX_sem);
}
for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) {
gOnChannel[i] = NULL;
// if you are using MEM_BLOCK_NUM, the RMT channel won't be the same as the "channel number"
rmt_channel_t rmt_channel = rmt_channel_t(i * MEM_BLOCK_NUM);
// -- RMT configuration for transmission
// NOTE: In ESP-IDF 4.1++, there is a #define to init, but that doesn't exist
// in earlier versions
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
rmt_config_t rmt_tx = RMT_DEFAULT_CONFIG_TX(gpio_num_t(0), rmt_channel);
#else
rmt_config_t rmt_tx;
memset((void*) &rmt_tx, 0, sizeof(rmt_tx));
rmt_tx.channel = rmt_channel;
rmt_tx.rmt_mode = RMT_MODE_TX;
rmt_tx.gpio_num = gpio_num_t(0); // The particular pin will be assigned later
#endif
rmt_tx.mem_block_num = MEM_BLOCK_NUM;
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
ESP_ERROR_CHECK( rmt_config(&rmt_tx) );
if (FASTLED_RMT_BUILTIN_DRIVER) {
ESP_ERROR_CHECK( rmt_driver_install(rmt_channel, 0, 0) );
}
else {
// -- Set up the RMT to send 32 bits of the pulse buffer and then
// generate an interrupt. When we get this interrupt we
// fill the other part in preparation (like double-buffering)
#if USE_FASTLED_RMT_FNS
ESP_ERROR_CHECK( fastled_set_tx_thr_intr_en(rmt_channel, true, PULSES_PER_FILL) );
#else
ESP_ERROR_CHECK( rmt_set_tx_thr_intr_en(rmt_channel, true, PULSES_PER_FILL) );
#endif
}
}
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_ERROR_CHECK(
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();
#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 work
if (gNumStarted == gNumControllers) {
gNext = 0;
// -- This Take always succeeds immediately
xSemaphoreTake(gTX_sem, portMAX_DELAY);
// -- Make sure it's been at least 50us since last show
// this is very conservative if you have multiple channels,
// arguably there should be a wait on the startnext of each LED string
gWait.wait();
// -- First, fill all the available channels and start them
int channel = 0;
while ( (channel < FASTLED_RMT_MAX_CHANNELS) && (gNext < gNumControllers) ) {
ESP32RMTController::startNext(channel);
channel++;
}
// -- Wait here while the data is sent. The interrupt handler
// will keep refilling the RMT buffers until it is all
// done; then it gives the semaphore back.
xSemaphoreTake(gTX_sem, portMAX_DELAY);
xSemaphoreGive(gTX_sem);
// -- Make sure we don't call showPixels too quickly
gWait.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
#if FASTLED_ESP32_SHOWTIMING == 1
// the interrupts may have dumped things to the buffer. Print it.
// warning: this does a fairly large stack allocation.
char mb[MEMORYBUF_SIZE+1];
int mb_len = MEMORYBUF_SIZE;
memorybuf_get(mb, &mb_len);
if (mb_len > 0) {
mb[mb_len] = 0;
printf(" rmt irq print: %s\n",mb);
}
#endif /* FASTLED_ESP32_SHOWTIMING == 1 */
}
}
// -- 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)
{
// -- Store a reference to this controller, so we can get it
// inside the interrupt handler
gOnChannel[channel] = this;
// the RMT channel depends on the MEM_BLOCK
mRMT_channel = rmt_channel_t(channel * MEM_BLOCK_NUM);
// -- Assign the pin to this channel
#ifdef OLD_IDF
rmt_set_pin(mRMT_channel, RMT_MODE_TX, mPin);
#else
rmt_set_gpio(mRMT_channel, RMT_MODE_TX, mPin, false);
#endif
if (FASTLED_RMT_BUILTIN_DRIVER) {
// -- Use the built-in RMT driver to send all the data in one shot
rmt_register_tx_end_callback(doneOnRMTChannel, reinterpret_cast<void *>(channel));
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 and the RMT buffer
mRMT_mem_start = & (RMTMEM.chan[mRMT_channel].data32[0].val);
mRMT_mem_ptr = mRMT_mem_start;
mCur = 0;
mWhichHalf = 0;
// -- Fill both halves of the RMT buffer (a totality of 64 bits of pixel data)
fillNext();
fillNext();
// -- Turn on the interrupts
#if USE_FASTLED_RMT_FNS
fastled_set_tx_intr_en(mRMT_channel, true);
#else
rmt_set_tx_intr_en(mRMT_channel, true);
#endif
// -- Kick off the transmission
tx_start();
}
}
// -- Start RMT transmission
// Setting this RMT flag is what actually kicks off the peripheral
void ESP32RMTController::tx_start()
{
#if USE_FASTLED_RMT_FNS
fastled_tx_start(mRMT_channel, true);
#else
rmt_tx_start(mRMT_channel, true);
#endif
mLastFill = __clock_cycles();
}
// In the case of the build-in driver, they specify the RMT channel
// so we use the arg instead
void ESP32RMTController::doneOnRMTChannel(rmt_channel_t channel, void * arg)
{
doneOnChannel((int) arg, (void *) 0);
}
// -- 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(int channel, void * arg)
{
// -- Turn off output on the pin
// SZG: Do I really need to do this?
// ESP32RMTController * pController = gOnChannel[channel];
// 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 {
portBASE_TYPE HPTaskAwoken = 0;
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);
}
}
}
// -- 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++) {
ESP32RMTController * pController = gOnChannel[channel];
if (pController != NULL) {
int rmt_channel = pController->mRMT_channel;
int tx_done_bit = rmt_channel * 3;
int tx_next_bit = rmt_channel + 24;
if (intr_st & BIT(tx_next_bit)) {
// -- More to send on this channel
RMT.int_clr.val |= BIT(tx_next_bit);
// if timing's NOT ok, have to bail
if (true == pController->timingOk()) {
pController->fillNext();
}
} // -- Transmission is complete on this channel
else if (intr_st & BIT(tx_done_bit)) {
RMT.int_clr.val |= BIT(tx_done_bit);
doneOnChannel(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.
fastled_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_SW);
for (uint32_t j = 0; j < PULSES_PER_FILL; j++) {
* mRMT_mem_ptr++ = 0;
}
fastled_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
// 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
// each one is a "rmt_item_t", which contains two values, which is very convenient
FASTLED_REGISTER uint32_t one_val = mOne.val;
FASTLED_REGISTER uint32_t zero_val = mZero.val;
// -- Use locals for speed
volatile FASTLED_REGISTER uint32_t * pItem = mRMT_mem_ptr;
// set the owner to SW --- current driver does this but its not clear it matters
fastled_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_SW);
// Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the
// rmt_item32_t value corresponding to the buffered bit value
for (int i=0; i < PULSES_PER_FILL / 32; i++) {
if (mCur < mSize) {
FASTLED_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
mWhichHalf++;
if (mWhichHalf == 2) {
pItem = mRMT_mem_start;
mWhichHalf = 0;
}
// -- Store the new pointer back into the object
mRMT_mem_ptr = pItem;
// set the owner back to HW
fastled_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_HW);
// update the time I last filled
mLastFill = __clock_cycles();
} else {
// -- No more data; signal to the RMT we are done
fastled_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_SW);
for (uint32_t j = 0; j < PULSES_PER_FILL; j++) {
* mRMT_mem_ptr++ = 0;
}
fastled_set_mem_owner(mRMT_channel, RMT_MEM_OWNER_HW);
}
}
// -- 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)
{
mCurPulse = 0;
// maybe we already have a buffer of the right size, it's likely
if (mBuffer && (mBufferSize == size_in_bytes * 8))
return;
if (mBuffer) { free(mBuffer); mBuffer = 0; }
// -- Each byte has 8 bits, each bit needs a 32-bit RMT item
mBufferSize = size_in_bytes * 8;
mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t) );
}
// -- 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 (uint32_t j = 0; j < 8; j++) {
mBuffer[mCurPulse] = (byteval & 0x80000000L) ? mOne : mZero;
byteval <<= 1;
mCurPulse++;
}
}