First commit.
This commit is contained in:
@ -0,0 +1,168 @@
|
||||
#ifndef __INC_CLOCKLESS_BLOCK_ESP8266_H
|
||||
#define __INC_CLOCKLESS_BLOCK_ESP8266_H
|
||||
|
||||
#define FASTLED_HAS_BLOCKLESS 1
|
||||
|
||||
#define PORT_MASK (((1<<LANES)-1) & 0x0000FFFFL)
|
||||
#define MIN(X,Y) (((X)<(Y)) ? (X):(Y))
|
||||
#define USED_LANES (MIN(LANES,4))
|
||||
#define REAL_FIRST_PIN 12
|
||||
#define LAST_PIN (12 + USED_LANES - 1)
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
extern uint32_t _frame_cnt;
|
||||
extern uint32_t _retry_cnt;
|
||||
#endif
|
||||
|
||||
template <uint8_t LANES, int FIRST_PIN, int T1, int T2, int T3, EOrder RGB_ORDER = GRB, int XTRA0 = 0, bool FLIP = false, int WAIT_TIME = 5>
|
||||
class InlineBlockClocklessController : public CPixelLEDController<RGB_ORDER, LANES, PORT_MASK> {
|
||||
typedef typename FastPin<FIRST_PIN>::port_ptr_t data_ptr_t;
|
||||
typedef typename FastPin<FIRST_PIN>::port_t data_t;
|
||||
|
||||
data_t mPinMask;
|
||||
data_ptr_t mPort;
|
||||
CMinWait<WAIT_TIME> mWait;
|
||||
public:
|
||||
virtual int size() { return CLEDController::size() * LANES; }
|
||||
|
||||
virtual void showPixels(PixelController<RGB_ORDER, LANES, PORT_MASK> & pixels) {
|
||||
// mWait.wait();
|
||||
/*uint32_t clocks = */
|
||||
int cnt=FASTLED_INTERRUPT_RETRY_COUNT;
|
||||
while(!showRGBInternal(pixels) && cnt--) {
|
||||
ets_intr_unlock();
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
_retry_cnt++;
|
||||
#endif
|
||||
delayMicroseconds(WAIT_TIME * 10);
|
||||
ets_intr_lock();
|
||||
}
|
||||
// #if FASTLED_ALLOW_INTTERUPTS == 0
|
||||
// Adjust the timer
|
||||
// long microsTaken = CLKS_TO_MICROS(clocks);
|
||||
// MS_COUNTER += (1 + (microsTaken / 1000));
|
||||
// #endif
|
||||
|
||||
// mWait.mark();
|
||||
}
|
||||
|
||||
template<int PIN> static void initPin() {
|
||||
if(PIN >= REAL_FIRST_PIN && PIN <= LAST_PIN) {
|
||||
_ESPPIN<PIN, 1<<(PIN & 0xFF)>::setOutput();
|
||||
// FastPin<PIN>::setOutput();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void init() {
|
||||
// Only supportd on pins 12-15
|
||||
// SZG: This probably won't work (check pins definitions in fastpin_esp32)
|
||||
initPin<12>();
|
||||
initPin<13>();
|
||||
initPin<14>();
|
||||
initPin<15>();
|
||||
mPinMask = FastPin<FIRST_PIN>::mask();
|
||||
mPort = FastPin<FIRST_PIN>::port();
|
||||
|
||||
// Serial.print("Mask is "); Serial.println(PORT_MASK);
|
||||
}
|
||||
|
||||
virtual uint16_t getMaxRefreshRate() const { return 400; }
|
||||
|
||||
typedef union {
|
||||
uint8_t bytes[8];
|
||||
uint16_t shorts[4];
|
||||
uint32_t raw[2];
|
||||
} Lines;
|
||||
|
||||
#define ESP_ADJUST 0 // (2*(F_CPU/24000000))
|
||||
#define ESP_ADJUST2 0
|
||||
template<int BITS,int PX> __attribute__ ((always_inline)) inline static void writeBits(register uint32_t & last_mark, register Lines & b, PixelController<RGB_ORDER, LANES, PORT_MASK> &pixels) { // , register uint32_t & b2) {
|
||||
Lines b2 = b;
|
||||
transpose8x1_noinline(b.bytes,b2.bytes);
|
||||
|
||||
register uint8_t d = pixels.template getd<PX>(pixels);
|
||||
register uint8_t scale = pixels.template getscale<PX>(pixels);
|
||||
|
||||
for(register uint32_t i = 0; i < USED_LANES; i++) {
|
||||
while((__clock_cycles() - last_mark) < (T1+T2+T3));
|
||||
last_mark = __clock_cycles();
|
||||
*FastPin<FIRST_PIN>::sport() = PORT_MASK << REAL_FIRST_PIN;
|
||||
|
||||
uint32_t nword = ((uint32_t)(~b2.bytes[7-i]) & PORT_MASK) << REAL_FIRST_PIN;
|
||||
while((__clock_cycles() - last_mark) < (T1-6));
|
||||
*FastPin<FIRST_PIN>::cport() = nword;
|
||||
|
||||
while((__clock_cycles() - last_mark) < (T1+T2));
|
||||
*FastPin<FIRST_PIN>::cport() = PORT_MASK << REAL_FIRST_PIN;
|
||||
|
||||
b.bytes[i] = pixels.template loadAndScale<PX>(pixels,i,d,scale);
|
||||
}
|
||||
|
||||
for(register uint32_t i = USED_LANES; i < 8; i++) {
|
||||
while((__clock_cycles() - last_mark) < (T1+T2+T3));
|
||||
last_mark = __clock_cycles();
|
||||
*FastPin<FIRST_PIN>::sport() = PORT_MASK << REAL_FIRST_PIN;
|
||||
|
||||
uint32_t nword = ((uint32_t)(~b2.bytes[7-i]) & PORT_MASK) << REAL_FIRST_PIN;
|
||||
while((__clock_cycles() - last_mark) < (T1-6));
|
||||
*FastPin<FIRST_PIN>::cport() = nword;
|
||||
|
||||
while((__clock_cycles() - last_mark) < (T1+T2));
|
||||
*FastPin<FIRST_PIN>::cport() = PORT_MASK << REAL_FIRST_PIN;
|
||||
}
|
||||
}
|
||||
|
||||
// This method is made static to force making register Y available to use for data on AVR - if the method is non-static, then
|
||||
// gcc will use register Y for the this pointer.
|
||||
static uint32_t showRGBInternal(PixelController<RGB_ORDER, LANES, PORT_MASK> &allpixels) {
|
||||
|
||||
// Setup the pixel controller and load/scale the first byte
|
||||
Lines b0;
|
||||
|
||||
for(int i = 0; i < USED_LANES; i++) {
|
||||
b0.bytes[i] = allpixels.loadAndScale0(i);
|
||||
}
|
||||
allpixels.preStepFirstByteDithering();
|
||||
|
||||
ets_intr_lock();
|
||||
uint32_t _start = __clock_cycles();
|
||||
uint32_t last_mark = _start;
|
||||
|
||||
while(allpixels.has(1)) {
|
||||
// Write first byte, read next byte
|
||||
writeBits<8+XTRA0,1>(last_mark, b0, allpixels);
|
||||
|
||||
// Write second byte, read 3rd byte
|
||||
writeBits<8+XTRA0,2>(last_mark, b0, allpixels);
|
||||
allpixels.advanceData();
|
||||
|
||||
// Write third byte
|
||||
writeBits<8+XTRA0,0>(last_mark, b0, allpixels);
|
||||
|
||||
#if (FASTLED_ALLOW_INTERRUPTS == 1)
|
||||
ets_intr_unlock();
|
||||
#endif
|
||||
|
||||
allpixels.stepDithering();
|
||||
|
||||
#if (FASTLED_ALLOW_INTERRUPTS == 1)
|
||||
ets_intr_lock();
|
||||
// if interrupts took longer than 45µs, punt on the current frame
|
||||
if((int32_t)(__clock_cycles()-last_mark) > 0) {
|
||||
if((int32_t)(__clock_cycles()-last_mark) > (T1+T2+T3+((WAIT_TIME-INTERRUPT_THRESHOLD)*CLKS_PER_US))) { ets_intr_unlock(); return 0; }
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
ets_intr_unlock();
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
_frame_cnt++;
|
||||
#endif
|
||||
return __clock_cycles() - _start;
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
#endif
|
@ -0,0 +1,786 @@
|
||||
/*
|
||||
* 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
|
@ -0,0 +1,767 @@
|
||||
/*
|
||||
* I2S Driver
|
||||
*
|
||||
* Copyright (c) 2019 Yves Bazin
|
||||
* Copyright (c) 2019 Samuel Z. Guyer
|
||||
* Derived from lots of code examples from other people.
|
||||
*
|
||||
* The I2S implementation can drive up to 24 strips in parallel, but
|
||||
* with the following limitation: all the strips must have the same
|
||||
* timing (i.e., they must all use the same chip).
|
||||
*
|
||||
* To enable the I2S driver, add the following line *before* including
|
||||
* FastLED.h (no other changes are necessary):
|
||||
*
|
||||
* #define FASTLED_ESP32_I2S true
|
||||
*
|
||||
* The overall strategy is to use the parallel mode of the I2S "audio"
|
||||
* peripheral to send up to 24 bits in parallel to 24 different pins.
|
||||
* Unlike the RMT peripheral the I2S system cannot send bits of
|
||||
* different lengths. Instead, we set the I2S data clock fairly high
|
||||
* and then encode a signal as a series of bits.
|
||||
*
|
||||
* For example, with a clock divider of 10 the data clock will be
|
||||
* 8MHz, so each bit is 125ns. The WS2812 expects a "1" bit to be
|
||||
* encoded as a HIGH signal for around 875ns, followed by LOW for
|
||||
* 375ns. Sending the following pattern results in the right shape
|
||||
* signal:
|
||||
*
|
||||
* 1111111000 WS2812 "1" bit encoded as 10 125ns pulses
|
||||
*
|
||||
* The I2S peripheral expects the bits for all 24 outputs to be packed
|
||||
* into a single 32-bit word. The complete signal is a series of these
|
||||
* 32-bit values -- one for each bit for each strip. The pixel data,
|
||||
* however, is stored "serially" as a series of RGB values separately
|
||||
* for each strip. To prepare the data we need to do three things: (1)
|
||||
* take 1 pixel from each strip, and (2) tranpose the bits so that
|
||||
* they are in the parallel form, (3) translate each data bit into the
|
||||
* bit pattern that encodes the signal for that bit. This code is in
|
||||
* the fillBuffer() method:
|
||||
*
|
||||
* 1. Read 1 pixel from each strip into an array; store this data by
|
||||
* color channel (e.g., all the red bytes, then all the green
|
||||
* bytes, then all the blue bytes). For three color channels, the
|
||||
* array is 3 X 24 X 8 bits.
|
||||
*
|
||||
* 2. Tranpose the array so that it is 3 X 8 X 24 bits. The hardware
|
||||
* wants the data in 32-bit chunks, so the actual form is 3 X 8 X
|
||||
* 32, with the low 8 bits unused.
|
||||
*
|
||||
* 3. Take each group of 24 parallel bits and "expand" them into a
|
||||
* pattern according to the encoding. For example, with a 8MHz
|
||||
* data clock, each data bit turns into 10 I2s pulses, so 24
|
||||
* parallel data bits turn into 10 X 24 pulses.
|
||||
*
|
||||
* We send data to the I2S peripheral using the DMA interface. We use
|
||||
* two DMA buffers, so that we can fill one buffer while the other
|
||||
* buffer is being sent. Each DMA buffer holds the fully-expanded
|
||||
* pulse pattern for one pixel on up to 24 strips. The exact amount of
|
||||
* memory required depends on the number of color channels and the
|
||||
* number of pulses used to encode each bit.
|
||||
*
|
||||
* We get an interrupt each time a buffer is sent; we then fill that
|
||||
* buffer while the next one is being sent. The DMA interface allows
|
||||
* us to configure the buffers as a circularly linked list, so that it
|
||||
* can automatically start on the next buffer.
|
||||
*/
|
||||
/*
|
||||
* 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
|
||||
|
||||
#pragma message "NOTE: ESP32 support using I2S parallel driver. All strips must use the same chipset"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "soc/i2s_reg.h"
|
||||
#include "soc/i2s_struct.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/periph_ctrl.h"
|
||||
#include "rom/lldesc.h"
|
||||
#include "esp_intr.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
|
||||
#define NUM_COLOR_CHANNELS 3
|
||||
|
||||
// -- Choose which I2S device to use
|
||||
#ifndef I2S_DEVICE
|
||||
#define I2S_DEVICE 0
|
||||
#endif
|
||||
|
||||
// -- Max number of controllers we can support
|
||||
#ifndef FASTLED_I2S_MAX_CONTROLLERS
|
||||
#define FASTLED_I2S_MAX_CONTROLLERS 24
|
||||
#endif
|
||||
|
||||
// -- I2S clock
|
||||
#define I2S_BASE_CLK (80000000L)
|
||||
#define I2S_MAX_CLK (20000000L) //more tha a certain speed and the I2s looses some bits
|
||||
#define I2S_MAX_PULSE_PER_BIT 20 //put it higher to get more accuracy but it could decrease the refresh rate without real improvement
|
||||
// -- Convert ESP32 cycles back into nanoseconds
|
||||
#define ESPCLKS_TO_NS(_CLKS) (((long)(_CLKS) * 1000L) / F_CPU_MHZ)
|
||||
|
||||
// -- Array of all controllers
|
||||
static CLEDController * gControllers[FASTLED_I2S_MAX_CONTROLLERS];
|
||||
static int gNumControllers = 0;
|
||||
static int gNumStarted = 0;
|
||||
|
||||
// -- Global semaphore for the whole show process
|
||||
// Semaphore is not given until all data has been sent
|
||||
static xSemaphoreHandle gTX_sem = NULL;
|
||||
|
||||
// -- One-time I2S initialization
|
||||
static bool gInitialized = false;
|
||||
|
||||
// -- Interrupt handler
|
||||
static intr_handle_t gI2S_intr_handle = NULL;
|
||||
|
||||
// -- A pointer to the memory-mapped structure: I2S0 or I2S1
|
||||
static i2s_dev_t * i2s;
|
||||
|
||||
// -- I2S goes to these pins until we remap them using the GPIO matrix
|
||||
static int i2s_base_pin_index;
|
||||
|
||||
// --- I2S DMA buffers
|
||||
struct DMABuffer {
|
||||
lldesc_t descriptor;
|
||||
uint8_t * buffer;
|
||||
};
|
||||
|
||||
#define NUM_DMA_BUFFERS 2
|
||||
static DMABuffer * dmaBuffers[NUM_DMA_BUFFERS];
|
||||
|
||||
// -- Bit patterns
|
||||
// For now, we require all strips to be the same chipset, so these
|
||||
// are global variables.
|
||||
|
||||
static int gPulsesPerBit = 0;
|
||||
static uint32_t gOneBit[40] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
static uint32_t gZeroBit[40] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
|
||||
// -- Counters to track progress
|
||||
static int gCurBuffer = 0;
|
||||
static bool gDoneFilling = false;
|
||||
static int ones_for_one;
|
||||
static int ones_for_zero;
|
||||
|
||||
// -- Temp buffers for pixels and bits being formatted for DMA
|
||||
static uint8_t gPixelRow[NUM_COLOR_CHANNELS][32];
|
||||
static uint8_t gPixelBits[NUM_COLOR_CHANNELS][8][4];
|
||||
static int CLOCK_DIVIDER_N;
|
||||
static int CLOCK_DIVIDER_A;
|
||||
static int CLOCK_DIVIDER_B;
|
||||
|
||||
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>
|
||||
{
|
||||
// -- Store the GPIO pin
|
||||
gpio_num_t mPin;
|
||||
|
||||
// -- This instantiation forces a check on the pin choice
|
||||
FastPin<DATA_PIN> mFastPin;
|
||||
|
||||
// -- Save the pixel controller
|
||||
PixelController<RGB_ORDER> * mPixels;
|
||||
|
||||
public:
|
||||
|
||||
void init()
|
||||
{
|
||||
i2sInit();
|
||||
|
||||
// -- Allocate space to save the pixel controller
|
||||
// during parallel output
|
||||
mPixels = (PixelController<RGB_ORDER> *) malloc(sizeof(PixelController<RGB_ORDER>));
|
||||
|
||||
gControllers[gNumControllers] = this;
|
||||
int my_index = gNumControllers;
|
||||
gNumControllers++;
|
||||
|
||||
// -- Set up the pin We have to do two things: configure the
|
||||
// actual GPIO pin, and route the output from the default
|
||||
// pin (determined by the I2S device) to the pin we
|
||||
// want. We compute the default pin using the index of this
|
||||
// controller in the array. This order is crucial because
|
||||
// the bits must go into the DMA buffer in the same order.
|
||||
mPin = gpio_num_t(DATA_PIN);
|
||||
|
||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DATA_PIN], PIN_FUNC_GPIO);
|
||||
gpio_set_direction(mPin, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT);
|
||||
pinMode(mPin,OUTPUT);
|
||||
gpio_matrix_out(mPin, i2s_base_pin_index + my_index, false, false);
|
||||
}
|
||||
|
||||
virtual uint16_t getMaxRefreshRate() const { return 400; }
|
||||
|
||||
protected:
|
||||
|
||||
static int pgcd(int smallest,int precision,int a,int b,int c)
|
||||
{
|
||||
int pgc_=1;
|
||||
for( int i=smallest;i>0;i--)
|
||||
{
|
||||
|
||||
if( a%i<=precision && b%i<=precision && c%i<=precision)
|
||||
{
|
||||
pgc_=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pgc_;
|
||||
}
|
||||
|
||||
/** Compute pules/bit patterns
|
||||
*
|
||||
* This is Yves Bazin's mad code for computing the pulse pattern
|
||||
* and clock timing given the target signal given by T1, T2, and
|
||||
* T3. In general, these parameters are interpreted as follows:
|
||||
*
|
||||
* a "1" bit is encoded by setting the pin HIGH to T1+T2 ns, then LOW for T3 ns
|
||||
* a "0" bit is encoded by setting the pin HIGH to T1 ns, then LOW for T2+T3 ns
|
||||
*
|
||||
*/
|
||||
static void initBitPatterns()
|
||||
{
|
||||
// Precompute the bit patterns based on the I2S sample rate
|
||||
// Serial.println("Setting up fastled using I2S");
|
||||
|
||||
// -- First, convert back to ns from CPU clocks
|
||||
uint32_t T1ns = ESPCLKS_TO_NS(T1);
|
||||
uint32_t T2ns = ESPCLKS_TO_NS(T2);
|
||||
uint32_t T3ns = ESPCLKS_TO_NS(T3);
|
||||
|
||||
// Serial.print("T1 = "); Serial.print(T1); Serial.print(" ns "); Serial.println(T1ns);
|
||||
// Serial.print("T2 = "); Serial.print(T2); Serial.print(" ns "); Serial.println(T2ns);
|
||||
// Serial.print("T3 = "); Serial.print(T3); Serial.print(" ns "); Serial.println(T3ns);
|
||||
|
||||
/*
|
||||
We calculate the best pcgd to the timing
|
||||
ie
|
||||
WS2811 77 77 154 => 1 1 2 => nb pulses= 4
|
||||
WS2812 60 150 90 => 2 5 3 => nb pulses=10
|
||||
*/
|
||||
int smallest=0;
|
||||
if (T1>T2)
|
||||
smallest=T2;
|
||||
else
|
||||
smallest=T1;
|
||||
if(smallest>T3)
|
||||
smallest=T3;
|
||||
double freq=(double)1/(double)(T1ns + T2ns + T3ns);
|
||||
// Serial.printf("chipset frequency:%f Khz\n", 1000000L*freq);
|
||||
// Serial.printf("smallest %d\n",smallest);
|
||||
int pgc_=1;
|
||||
int precision=0;
|
||||
pgc_=pgcd(smallest,precision,T1,T2,T3);
|
||||
//Serial.printf("%f\n",I2S_MAX_CLK/(1000000000L*freq));
|
||||
while(pgc_==1 || (T1/pgc_ +T2/pgc_ +T3/pgc_)>I2S_MAX_PULSE_PER_BIT) //while(pgc_==1 || (T1/pgc_ +T2/pgc_ +T3/pgc_)>I2S_MAX_CLK/(1000000000L*freq))
|
||||
{
|
||||
precision++;
|
||||
pgc_=pgcd(smallest,precision,T1,T2,T3);
|
||||
//Serial.printf("%d %d\n",pgc_,(a+b+c)/pgc_);
|
||||
}
|
||||
pgc_=pgcd(smallest,precision,T1,T2,T3);
|
||||
// Serial.printf("pgcd %d precision:%d\n",pgc_,precision);
|
||||
// Serial.printf("nb pulse per bit:%d\n",T1/pgc_ +T2/pgc_ +T3/pgc_);
|
||||
gPulsesPerBit=(int)T1/pgc_ +(int)T2/pgc_ +(int)T3/pgc_;
|
||||
/*
|
||||
we calculate the duration of one pulse nd htre base frequency of the led
|
||||
ie WS2812B F=1/(250+625+375)=800kHz or 1250ns
|
||||
as we need 10 pulses each pulse is 125ns => frequency 800Khz*10=8MHz
|
||||
WS2811 T=320+320+641=1281ns qnd we need 4 pulses => pulse duration 320.25ns =>frequency 3.1225605Mhz
|
||||
|
||||
*/
|
||||
|
||||
freq=1000000000L*freq*gPulsesPerBit;
|
||||
// Serial.printf("needed frequency (nbpiulse per bit)*(chispset frequency):%f Mhz\n",freq/1000000);
|
||||
|
||||
/*
|
||||
we do calculate the needed N a and b
|
||||
as f=basefred/(N+b/a);
|
||||
as a is max 63 the precision for the decimal is 1/63
|
||||
|
||||
*/
|
||||
|
||||
CLOCK_DIVIDER_N=(int)((double)I2S_BASE_CLK/freq);
|
||||
double v=I2S_BASE_CLK/freq-CLOCK_DIVIDER_N;
|
||||
|
||||
double prec=(double)1/63;
|
||||
int a=1;
|
||||
int b=0;
|
||||
CLOCK_DIVIDER_A=1;
|
||||
CLOCK_DIVIDER_B=0;
|
||||
for(a=1;a<64;a++)
|
||||
{
|
||||
for(b=0;b<a;b++)
|
||||
{
|
||||
//printf("%d %d %f %f %f\n",b,a,v,(double)v*(double)a,fabsf(v-(double)b/a));
|
||||
if(fabsf(v-(double)b/a) <= prec/2)
|
||||
break;
|
||||
}
|
||||
if(fabsf(v-(double)b/a) ==0)
|
||||
{
|
||||
CLOCK_DIVIDER_A=a;
|
||||
CLOCK_DIVIDER_B=b;
|
||||
break;
|
||||
}
|
||||
if(fabsf(v-(double)b/a) < prec/2)
|
||||
{
|
||||
if (fabsf(v-(double)b/a) <fabsf(v-(double)CLOCK_DIVIDER_B/CLOCK_DIVIDER_A))
|
||||
{
|
||||
CLOCK_DIVIDER_A=a;
|
||||
CLOCK_DIVIDER_B=b;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
//top take care of an issue with double 0.9999999999
|
||||
if(CLOCK_DIVIDER_A==CLOCK_DIVIDER_B)
|
||||
{
|
||||
CLOCK_DIVIDER_A=1;
|
||||
CLOCK_DIVIDER_B=0;
|
||||
CLOCK_DIVIDER_N++;
|
||||
}
|
||||
|
||||
//printf("%d %d %f %f %d\n",CLOCK_DIVIDER_B,CLOCK_DIVIDER_A,(double)CLOCK_DIVIDER_B/CLOCK_DIVIDER_A,v,CLOCK_DIVIDER_N);
|
||||
//Serial.printf("freq %f %f\n",freq,I2S_BASE_CLK/(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;
|
||||
// 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;
|
||||
// Serial.printf("Pulse duration: %f ns\n",pulseduration);
|
||||
// gPulsesPerBit = (T1ns + T2ns + T3ns)/FASTLED_I2S_NS_PER_PULSE;
|
||||
|
||||
//Serial.print("Pulses per bit: "); Serial.println(gPulsesPerBit);
|
||||
|
||||
//int ones_for_one = ((T1ns + T2ns - 1)/FASTLED_I2S_NS_PER_PULSE) + 1;
|
||||
ones_for_one = T1/pgc_ +T2/pgc_;
|
||||
//Serial.print("One bit: target ");
|
||||
//Serial.print(T1ns+T2ns); Serial.print("ns --- ");
|
||||
//Serial.print(ones_for_one); Serial.print(" 1 bits");
|
||||
//Serial.print(" = "); Serial.print(ones_for_one * FASTLED_I2S_NS_PER_PULSE); Serial.println("ns");
|
||||
// Serial.printf("one bit : target %d ns --- %d pulses 1 bit = %f ns\n",T1ns+T2ns,ones_for_one ,ones_for_one*pulseduration);
|
||||
|
||||
|
||||
int i = 0;
|
||||
while ( i < ones_for_one ) {
|
||||
gOneBit[i] = 0xFFFFFF00;
|
||||
i++;
|
||||
}
|
||||
while ( i < gPulsesPerBit ) {
|
||||
gOneBit[i] = 0x00000000;
|
||||
i++;
|
||||
}
|
||||
|
||||
//int ones_for_zero = ((T1ns - 1)/FASTLED_I2S_NS_PER_PULSE) + 1;
|
||||
ones_for_zero =T1/pgc_ ;
|
||||
// Serial.print("Zero bit: target ");
|
||||
// Serial.print(T1ns); Serial.print("ns --- ");
|
||||
//Serial.print(ones_for_zero); Serial.print(" 1 bits");
|
||||
//Serial.print(" = "); Serial.print(ones_for_zero * FASTLED_I2S_NS_PER_PULSE); Serial.println("ns");
|
||||
// Serial.printf("Zero bit : target %d ns --- %d pulses 1 bit = %f ns\n",T1ns,ones_for_zero ,ones_for_zero*pulseduration);
|
||||
i = 0;
|
||||
while ( i < ones_for_zero ) {
|
||||
gZeroBit[i] = 0xFFFFFF00;
|
||||
i++;
|
||||
}
|
||||
while ( i < gPulsesPerBit ) {
|
||||
gZeroBit[i] = 0x00000000;
|
||||
i++;
|
||||
}
|
||||
|
||||
memset(gPixelRow, 0, NUM_COLOR_CHANNELS * 32);
|
||||
memset(gPixelBits, 0, NUM_COLOR_CHANNELS * 32);
|
||||
}
|
||||
|
||||
static DMABuffer * allocateDMABuffer(int bytes)
|
||||
{
|
||||
DMABuffer * b = (DMABuffer *)heap_caps_malloc(sizeof(DMABuffer), MALLOC_CAP_DMA);
|
||||
|
||||
b->buffer = (uint8_t *)heap_caps_malloc(bytes, MALLOC_CAP_DMA);
|
||||
memset(b->buffer, 0, bytes);
|
||||
|
||||
b->descriptor.length = bytes;
|
||||
b->descriptor.size = bytes;
|
||||
b->descriptor.owner = 1;
|
||||
b->descriptor.sosf = 1;
|
||||
b->descriptor.buf = b->buffer;
|
||||
b->descriptor.offset = 0;
|
||||
b->descriptor.empty = 0;
|
||||
b->descriptor.eof = 1;
|
||||
b->descriptor.qe.stqe_next = 0;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static void i2sInit()
|
||||
{
|
||||
// -- Only need to do this once
|
||||
if (gInitialized) return;
|
||||
|
||||
// -- Construct the bit patterns for ones and zeros
|
||||
initBitPatterns();
|
||||
|
||||
// -- Choose whether to use I2S device 0 or device 1
|
||||
// Set up the various device-specific parameters
|
||||
int interruptSource;
|
||||
if (I2S_DEVICE == 0) {
|
||||
i2s = &I2S0;
|
||||
periph_module_enable(PERIPH_I2S0_MODULE);
|
||||
interruptSource = ETS_I2S0_INTR_SOURCE;
|
||||
i2s_base_pin_index = I2S0O_DATA_OUT0_IDX;
|
||||
} else {
|
||||
i2s = &I2S1;
|
||||
periph_module_enable(PERIPH_I2S1_MODULE);
|
||||
interruptSource = ETS_I2S1_INTR_SOURCE;
|
||||
i2s_base_pin_index = I2S1O_DATA_OUT0_IDX;
|
||||
}
|
||||
|
||||
// -- Reset everything
|
||||
i2sReset();
|
||||
i2sReset_DMA();
|
||||
i2sReset_FIFO();
|
||||
|
||||
// -- Main configuration
|
||||
i2s->conf.tx_msb_right = 1;
|
||||
i2s->conf.tx_mono = 0;
|
||||
i2s->conf.tx_short_sync = 0;
|
||||
i2s->conf.tx_msb_shift = 0;
|
||||
i2s->conf.tx_right_first = 1; // 0;//1;
|
||||
i2s->conf.tx_slave_mod = 0;
|
||||
|
||||
// -- Set parallel mode
|
||||
i2s->conf2.val = 0;
|
||||
i2s->conf2.lcd_en = 1;
|
||||
i2s->conf2.lcd_tx_wrx2_en = 0; // 0 for 16 or 32 parallel output
|
||||
i2s->conf2.lcd_tx_sdx2_en = 0; // HN
|
||||
|
||||
// -- Set up the clock rate and sampling
|
||||
i2s->sample_rate_conf.val = 0;
|
||||
i2s->sample_rate_conf.tx_bits_mod = 32; // Number of parallel bits/pins
|
||||
i2s->sample_rate_conf.tx_bck_div_num = 1;
|
||||
i2s->clkm_conf.val = 0;
|
||||
i2s->clkm_conf.clka_en = 0;
|
||||
|
||||
// -- Data clock is computed as Base/(div_num + (div_b/div_a))
|
||||
// Base is 80Mhz, so 80/(10 + 0/1) = 8Mhz
|
||||
// One cycle is 125ns
|
||||
i2s->clkm_conf.clkm_div_a = CLOCK_DIVIDER_A;
|
||||
i2s->clkm_conf.clkm_div_b = CLOCK_DIVIDER_B;
|
||||
i2s->clkm_conf.clkm_div_num = CLOCK_DIVIDER_N;
|
||||
|
||||
i2s->fifo_conf.val = 0;
|
||||
i2s->fifo_conf.tx_fifo_mod_force_en = 1;
|
||||
i2s->fifo_conf.tx_fifo_mod = 3; // 32-bit single channel data
|
||||
i2s->fifo_conf.tx_data_num = 32; // fifo length
|
||||
i2s->fifo_conf.dscr_en = 1; // fifo will use dma
|
||||
|
||||
i2s->conf1.val = 0;
|
||||
i2s->conf1.tx_stop_en = 0;
|
||||
i2s->conf1.tx_pcm_bypass = 1;
|
||||
|
||||
i2s->conf_chan.val = 0;
|
||||
i2s->conf_chan.tx_chan_mod = 1; // Mono mode, with tx_msb_right = 1, everything goes to right-channel
|
||||
|
||||
i2s->timing.val = 0;
|
||||
|
||||
// -- Allocate two DMA buffers
|
||||
dmaBuffers[0] = allocateDMABuffer(32 * NUM_COLOR_CHANNELS * gPulsesPerBit);
|
||||
dmaBuffers[1] = allocateDMABuffer(32 * NUM_COLOR_CHANNELS * gPulsesPerBit);
|
||||
|
||||
// -- Arrange them as a circularly linked list
|
||||
dmaBuffers[0]->descriptor.qe.stqe_next = &(dmaBuffers[1]->descriptor);
|
||||
dmaBuffers[1]->descriptor.qe.stqe_next = &(dmaBuffers[0]->descriptor);
|
||||
|
||||
// -- Allocate i2s interrupt
|
||||
SET_PERI_REG_BITS(I2S_INT_ENA_REG(I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);
|
||||
esp_err_t e = esp_intr_alloc(interruptSource, 0, // ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL3,
|
||||
&interruptHandler, 0, &gI2S_intr_handle);
|
||||
|
||||
// -- Create a semaphore to block execution until all the controllers are done
|
||||
if (gTX_sem == NULL) {
|
||||
gTX_sem = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(gTX_sem);
|
||||
}
|
||||
|
||||
// Serial.println("Init I2S");
|
||||
gInitialized = true;
|
||||
}
|
||||
|
||||
/** Clear DMA buffer
|
||||
*
|
||||
* Yves' clever trick: initialize the bits that we know must be 0
|
||||
* or 1 regardless of what bit they encode.
|
||||
*/
|
||||
static void empty( uint32_t *buf)
|
||||
{
|
||||
for(int i=0;i<8*NUM_COLOR_CHANNELS;i++)
|
||||
{
|
||||
int offset=gPulsesPerBit*i;
|
||||
for(int j=0;j<ones_for_zero;j++)
|
||||
buf[offset+j]=0xffffffff;
|
||||
|
||||
for(int j=ones_for_one;j<gPulsesPerBit;j++)
|
||||
buf[offset+j]=0;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Show pixels
|
||||
// This is the main entry point for the controller.
|
||||
virtual void showPixels(PixelController<RGB_ORDER> & pixels)
|
||||
{
|
||||
if (gNumStarted == 0) {
|
||||
// -- First controller: make sure everything is set up
|
||||
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.
|
||||
(*mPixels) = pixels;
|
||||
|
||||
// -- Keep track of the number of strips we've seen
|
||||
gNumStarted++;
|
||||
|
||||
// Serial.print("Show pixels ");
|
||||
// Serial.println(gNumStarted);
|
||||
|
||||
// -- The last call to showPixels is the one responsible for doing
|
||||
// all of the actual work
|
||||
if (gNumStarted == gNumControllers) {
|
||||
empty((uint32_t*)dmaBuffers[0]->buffer);
|
||||
empty((uint32_t*)dmaBuffers[1]->buffer);
|
||||
gCurBuffer = 0;
|
||||
gDoneFilling = false;
|
||||
|
||||
// -- Prefill both buffers
|
||||
fillBuffer();
|
||||
fillBuffer();
|
||||
|
||||
i2sStart();
|
||||
|
||||
// -- Wait here while the rest of the data is sent. The interrupt handler
|
||||
// will keep refilling the DMA buffers until it is all sent; then it
|
||||
// gives the semaphore back.
|
||||
xSemaphoreTake(gTX_sem, portMAX_DELAY);
|
||||
xSemaphoreGive(gTX_sem);
|
||||
|
||||
i2sStop();
|
||||
|
||||
// -- Reset the counters
|
||||
gNumStarted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Custom interrupt handler
|
||||
static IRAM_ATTR void interruptHandler(void *arg)
|
||||
{
|
||||
if (i2s->int_st.out_eof) {
|
||||
i2s->int_clr.val = i2s->int_raw.val;
|
||||
|
||||
if ( ! gDoneFilling) {
|
||||
fillBuffer();
|
||||
} else {
|
||||
portBASE_TYPE HPTaskAwoken = 0;
|
||||
xSemaphoreGiveFromISR(gTX_sem, &HPTaskAwoken);
|
||||
if(HPTaskAwoken == pdTRUE) portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Fill DMA buffer
|
||||
*
|
||||
* This is where the real work happens: take a row of pixels (one
|
||||
* from each strip), transpose and encode the bits, and store
|
||||
* them in the DMA buffer for the I2S peripheral to read.
|
||||
*/
|
||||
static void fillBuffer()
|
||||
{
|
||||
// -- Alternate between buffers
|
||||
volatile uint32_t * buf = (uint32_t *) dmaBuffers[gCurBuffer]->buffer;
|
||||
gCurBuffer = (gCurBuffer + 1) % NUM_DMA_BUFFERS;
|
||||
|
||||
// -- Get the requested pixel from each controller. Store the
|
||||
// data for each color channel in a separate array.
|
||||
uint32_t has_data_mask = 0;
|
||||
for (int i = 0; i < gNumControllers; i++) {
|
||||
// -- Store the pixels in reverse controller order starting at index 23
|
||||
// This causes the bits to come out in the right position after we
|
||||
// transpose them.
|
||||
int bit_index = 23-i;
|
||||
ClocklessController * pController = static_cast<ClocklessController*>(gControllers[i]);
|
||||
if (pController->mPixels->has(1)) {
|
||||
gPixelRow[0][bit_index] = pController->mPixels->loadAndScale0();
|
||||
gPixelRow[1][bit_index] = pController->mPixels->loadAndScale1();
|
||||
gPixelRow[2][bit_index] = pController->mPixels->loadAndScale2();
|
||||
pController->mPixels->advanceData();
|
||||
pController->mPixels->stepDithering();
|
||||
|
||||
// -- Record that this controller still has data to send
|
||||
has_data_mask |= (1 << (i+8));
|
||||
}
|
||||
}
|
||||
|
||||
// -- None of the strips has data? We are done.
|
||||
if (has_data_mask == 0) {
|
||||
gDoneFilling = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// -- Transpose and encode the pixel data for the DMA buffer
|
||||
int buf_index = 0;
|
||||
for (int channel = 0; channel < NUM_COLOR_CHANNELS; channel++) {
|
||||
|
||||
// -- Tranpose each array: all the bit 7's, then all the bit 6's, ...
|
||||
transpose32(gPixelRow[channel], gPixelBits[channel][0] );
|
||||
|
||||
//Serial.print("Channel: "); Serial.print(channel); Serial.print(" ");
|
||||
for (int bitnum = 0; bitnum < 8; bitnum++) {
|
||||
uint8_t * row = (uint8_t *) (gPixelBits[channel][bitnum]);
|
||||
uint32_t bit = (row[0] << 24) | (row[1] << 16) | (row[2] << 8) | row[3];
|
||||
|
||||
/* SZG: More general, but too slow:
|
||||
for (int pulse_num = 0; pulse_num < gPulsesPerBit; pulse_num++) {
|
||||
buf[buf_index++] = has_data_mask & ( (bit & gOneBit[pulse_num]) | (~bit & gZeroBit[pulse_num]) );
|
||||
}
|
||||
*/
|
||||
|
||||
// -- Only fill in the pulses that are different between the "0" and "1" encodings
|
||||
for(int pulse_num = ones_for_zero; pulse_num < ones_for_one; pulse_num++) {
|
||||
buf[bitnum*gPulsesPerBit+channel*8*gPulsesPerBit+pulse_num] = has_data_mask & bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void transpose32(uint8_t * pixels, uint8_t * bits)
|
||||
{
|
||||
transpose8rS32(& pixels[0], 1, 4, & bits[0]);
|
||||
transpose8rS32(& pixels[8], 1, 4, & bits[1]);
|
||||
transpose8rS32(& pixels[16], 1, 4, & bits[2]);
|
||||
//transpose8rS32(& pixels[24], 1, 4, & bits[3]); Can only use 24 bits
|
||||
}
|
||||
|
||||
/** Transpose 8x8 bit matrix
|
||||
* From Hacker's Delight
|
||||
*/
|
||||
static void transpose8rS32(uint8_t * A, int m, int n, uint8_t * B)
|
||||
{
|
||||
uint32_t x, y, t;
|
||||
|
||||
// Load the array and pack it into x and y.
|
||||
|
||||
x = (A[0]<<24) | (A[m]<<16) | (A[2*m]<<8) | A[3*m];
|
||||
y = (A[4*m]<<24) | (A[5*m]<<16) | (A[6*m]<<8) | A[7*m];
|
||||
|
||||
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
|
||||
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
|
||||
|
||||
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
|
||||
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
|
||||
|
||||
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
|
||||
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
|
||||
x = t;
|
||||
|
||||
B[0]=x>>24; B[n]=x>>16; B[2*n]=x>>8; B[3*n]=x;
|
||||
B[4*n]=y>>24; B[5*n]=y>>16; B[6*n]=y>>8; B[7*n]=y;
|
||||
}
|
||||
|
||||
/** Start I2S transmission
|
||||
*/
|
||||
static void i2sStart()
|
||||
{
|
||||
// esp_intr_disable(gI2S_intr_handle);
|
||||
// Serial.println("I2S start");
|
||||
i2sReset();
|
||||
//Serial.println(dmaBuffers[0]->sampleCount());
|
||||
i2s->lc_conf.val=I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN;
|
||||
i2s->out_link.addr = (uint32_t) & (dmaBuffers[0]->descriptor);
|
||||
i2s->out_link.start = 1;
|
||||
////vTaskDelay(5);
|
||||
i2s->int_clr.val = i2s->int_raw.val;
|
||||
// //vTaskDelay(5);
|
||||
i2s->int_ena.out_dscr_err = 1;
|
||||
//enable interrupt
|
||||
////vTaskDelay(5);
|
||||
esp_intr_enable(gI2S_intr_handle);
|
||||
// //vTaskDelay(5);
|
||||
i2s->int_ena.val = 0;
|
||||
i2s->int_ena.out_eof = 1;
|
||||
|
||||
//start transmission
|
||||
i2s->conf.tx_start = 1;
|
||||
}
|
||||
|
||||
static void i2sReset()
|
||||
{
|
||||
// Serial.println("I2S reset");
|
||||
const unsigned long lc_conf_reset_flags = I2S_IN_RST_M | I2S_OUT_RST_M | I2S_AHBM_RST_M | I2S_AHBM_FIFO_RST_M;
|
||||
i2s->lc_conf.val |= lc_conf_reset_flags;
|
||||
i2s->lc_conf.val &= ~lc_conf_reset_flags;
|
||||
|
||||
const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M;
|
||||
i2s->conf.val |= conf_reset_flags;
|
||||
i2s->conf.val &= ~conf_reset_flags;
|
||||
}
|
||||
|
||||
static void i2sReset_DMA()
|
||||
{
|
||||
i2s->lc_conf.in_rst=1; i2s->lc_conf.in_rst=0;
|
||||
i2s->lc_conf.out_rst=1; i2s->lc_conf.out_rst=0;
|
||||
}
|
||||
|
||||
static void i2sReset_FIFO()
|
||||
{
|
||||
i2s->conf.rx_fifo_reset=1; i2s->conf.rx_fifo_reset=0;
|
||||
i2s->conf.tx_fifo_reset=1; i2s->conf.tx_fifo_reset=0;
|
||||
}
|
||||
|
||||
static void i2sStop()
|
||||
{
|
||||
// Serial.println("I2S stop");
|
||||
esp_intr_disable(gI2S_intr_handle);
|
||||
i2sReset();
|
||||
i2s->conf.rx_start = 0;
|
||||
i2s->conf.tx_start = 0;
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
@ -0,0 +1,658 @@
|
||||
/*
|
||||
* Integration into FastLED ClocklessController
|
||||
* Copyright (c) 2018 Samuel Z. Guyer
|
||||
* Copyright (c) 2017 Thomas Basler
|
||||
* Copyright (c) 2017 Martin F. Falatic
|
||||
*
|
||||
* 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
|
||||
#define NUM_COLOR_CHANNELS 3
|
||||
|
||||
// -- 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
|
||||
#define DIVIDER 2 /* 4, 8 still seem to work, but timings become marginal */
|
||||
#define MAX_PULSES 64 /* A channel has a 64 "pulse" buffer */
|
||||
#define PULSES_PER_FILL 24 /* One pixel's worth of pulses */
|
||||
|
||||
// -- Convert ESP32 CPU cycles to RMT device cycles, taking into account the divider
|
||||
#define F_CPU_RMT ( 80000000L)
|
||||
#define RMT_CYCLES_PER_SEC (F_CPU_RMT/DIVIDER)
|
||||
#define RMT_CYCLES_PER_ESP_CYCLE (F_CPU / RMT_CYCLES_PER_SEC)
|
||||
#define ESP_TO_RMT_CYCLES(n) ((n) / (RMT_CYCLES_PER_ESP_CYCLE))
|
||||
|
||||
// -- Number of cycles to signal the strip to latch
|
||||
#define NS_PER_CYCLE ( 1000000000L / RMT_CYCLES_PER_SEC )
|
||||
#define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE )
|
||||
#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;
|
||||
|
||||
// -- 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;
|
||||
|
||||
// -- Save the pixel controller
|
||||
PixelController<RGB_ORDER> * mPixels;
|
||||
int mCurColor;
|
||||
uint16_t mCurPulse;
|
||||
volatile uint32_t * mRMT_mem_ptr;
|
||||
|
||||
// -- 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:
|
||||
|
||||
void init()
|
||||
{
|
||||
// -- Allocate space to save the pixel controller
|
||||
// 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; }
|
||||
|
||||
protected:
|
||||
|
||||
void initRMT()
|
||||
{
|
||||
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 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_LEVEL3, interruptHandler, 0, &gRMT_intr_handle);
|
||||
}
|
||||
|
||||
gInitialized = true;
|
||||
}
|
||||
|
||||
// -- Show pixels
|
||||
// This is the main entry point for the controller.
|
||||
virtual void IRAM_ATTR showPixels(PixelController<RGB_ORDER> & pixels)
|
||||
{
|
||||
if (gNumStarted == 0) {
|
||||
// -- First controller: make sure everything is set up
|
||||
// -- Only need to do this once
|
||||
if ( ! gInitialized) {
|
||||
initRMT();
|
||||
}
|
||||
xSemaphoreTake(gTX_sem, portMAX_DELAY);
|
||||
}
|
||||
|
||||
if (FASTLED_RMT_BUILTIN_DRIVER)
|
||||
convertAllPixelData(pixels);
|
||||
else {
|
||||
// -- Initialize the local state, save a pointer to the pixel
|
||||
// data. We need to make a copy because pixels is a local
|
||||
// variable in the calling function, and this data structure
|
||||
// needs to outlive this call to showPixels.
|
||||
(*mPixels) = pixels;
|
||||
}
|
||||
|
||||
// -- 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++;
|
||||
}
|
||||
|
||||
// -- 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
|
||||
// This function is only used when the user chooses to use the
|
||||
// built-in RMT driver, which needs all of the RMT pulses
|
||||
// up-front.
|
||||
void convertAllPixelData(PixelController<RGB_ORDER> & pixels)
|
||||
{
|
||||
// -- Compute the pulse values for the whole strip at once.
|
||||
// Requires a large buffer
|
||||
mBufferSize = pixels.size() * 3 * 8;
|
||||
|
||||
if (mBuffer == NULL) {
|
||||
mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t));
|
||||
}
|
||||
|
||||
// -- Cycle through the R,G, and B values in the right order,
|
||||
// storing the pulses in the big buffer
|
||||
mCurPulse = 0;
|
||||
|
||||
uint32_t byteval;
|
||||
while (pixels.has(1)) {
|
||||
byteval = pixels.loadAndScale0();
|
||||
convertByte(byteval);
|
||||
byteval = pixels.loadAndScale1();
|
||||
convertByte(byteval);
|
||||
byteval = pixels.loadAndScale2();
|
||||
convertByte(byteval);
|
||||
pixels.advanceData();
|
||||
pixels.stepDithering();
|
||||
}
|
||||
|
||||
mBuffer[mCurPulse-1].duration1 = RMT_RESET_DURATION;
|
||||
assert(mCurPulse == mBufferSize);
|
||||
}
|
||||
|
||||
void convertByte(uint32_t byteval)
|
||||
{
|
||||
// -- Write one byte's worth of RMT pulses to the big buffer
|
||||
byteval <<= 24;
|
||||
for (register uint32_t j = 0; j < 8; j++) {
|
||||
mBuffer[mCurPulse] = (byteval & 0x80000000L) ? mOne : mZero;
|
||||
byteval <<= 1;
|
||||
mCurPulse++;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Start up the next controller
|
||||
// This method is static so that it can dispatch to the
|
||||
// appropriate startOnChannel method of the given controller.
|
||||
static void IRAM_ATTR startNext(int channel)
|
||||
{
|
||||
if (gNext < gNumControllers) {
|
||||
ClocklessController * pController = static_cast<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)) {
|
||||
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
|
||||
uint8_t IRAM_ATTR getNextByte() __attribute__ ((always_inline))
|
||||
{
|
||||
uint8_t byte;
|
||||
|
||||
// -- Cycle through the color channels
|
||||
switch (mCurColor) {
|
||||
case 0:
|
||||
byte = mPixels->loadAndScale0();
|
||||
break;
|
||||
case 1:
|
||||
byte = mPixels->loadAndScale1();
|
||||
break;
|
||||
case 2:
|
||||
byte = mPixels->loadAndScale2();
|
||||
mPixels->advanceData();
|
||||
mPixels->stepDithering();
|
||||
break;
|
||||
default:
|
||||
// -- This is bad!
|
||||
byte = 0;
|
||||
}
|
||||
|
||||
mCurColor++;
|
||||
if (mCurColor == NUM_COLOR_CHANNELS) mCurColor = 0;
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
|
||||
// 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 = getNextByte();
|
||||
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
|
11
.pio/libdeps/local/FastLED/platforms/esp/32/fastled_esp32.h
Normal file
11
.pio/libdeps/local/FastLED/platforms/esp/32/fastled_esp32.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastpin_esp32.h"
|
||||
|
||||
#ifdef FASTLED_ESP32_I2S
|
||||
#include "clockless_i2s_esp32.h"
|
||||
#else
|
||||
#include "clockless_rmt_esp32.h"
|
||||
#endif
|
||||
|
||||
// #include "clockless_block_esp32.h"
|
115
.pio/libdeps/local/FastLED/platforms/esp/32/fastpin_esp32.h
Normal file
115
.pio/libdeps/local/FastLED/platforms/esp/32/fastpin_esp32.h
Normal file
@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
template<uint8_t PIN, uint32_t MASK> class _ESPPIN {
|
||||
|
||||
public:
|
||||
typedef volatile uint32_t * port_ptr_t;
|
||||
typedef uint32_t port_t;
|
||||
|
||||
inline static void setOutput() { pinMode(PIN, OUTPUT); }
|
||||
inline static void setInput() { pinMode(PIN, INPUT); }
|
||||
|
||||
inline static void hi() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) GPIO.out_w1ts = MASK;
|
||||
else GPIO.out1_w1ts.val = MASK;
|
||||
}
|
||||
|
||||
inline static void lo() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) GPIO.out_w1tc = MASK;
|
||||
else GPIO.out1_w1tc.val = MASK;
|
||||
}
|
||||
|
||||
inline static void set(register port_t val) __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) GPIO.out = val;
|
||||
else GPIO.out1.val = val;
|
||||
}
|
||||
|
||||
inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
|
||||
|
||||
inline static void toggle() __attribute__ ((always_inline)) {
|
||||
if(PIN < 32) { GPIO.out ^= MASK; }
|
||||
else { GPIO.out1.val ^=MASK; }
|
||||
}
|
||||
|
||||
inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { hi(); }
|
||||
inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { lo(); }
|
||||
inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; }
|
||||
|
||||
inline static port_t hival() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) return GPIO.out | MASK;
|
||||
else return GPIO.out1.val | MASK;
|
||||
}
|
||||
|
||||
inline static port_t loval() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) return GPIO.out & ~MASK;
|
||||
else return GPIO.out1.val & ~MASK;
|
||||
}
|
||||
|
||||
inline static port_ptr_t port() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) return &GPIO.out;
|
||||
else return &GPIO.out1.val;
|
||||
}
|
||||
|
||||
inline static port_ptr_t sport() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) return &GPIO.out_w1ts;
|
||||
else return &GPIO.out1_w1ts.val;
|
||||
}
|
||||
|
||||
inline static port_ptr_t cport() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) return &GPIO.out_w1tc;
|
||||
else return &GPIO.out1_w1tc.val;
|
||||
}
|
||||
|
||||
inline static port_t mask() __attribute__ ((always_inline)) { return MASK; }
|
||||
|
||||
inline static bool isset() __attribute__ ((always_inline)) {
|
||||
if (PIN < 32) return GPIO.out & MASK;
|
||||
else return GPIO.out1.val & MASK;
|
||||
}
|
||||
};
|
||||
|
||||
#define _FL_DEFPIN(PIN) template<> class FastPin<PIN> : public _ESPPIN<PIN, ((PIN<32)?((uint32_t)1 << PIN):((uint32_t)1 << (PIN-32)))> {};
|
||||
|
||||
_FL_DEFPIN(0);
|
||||
_FL_DEFPIN(1); // WARNING: Using TX causes flashiness when uploading
|
||||
_FL_DEFPIN(2);
|
||||
_FL_DEFPIN(3); // WARNING: Using RX causes flashiness when uploading
|
||||
_FL_DEFPIN(4);
|
||||
_FL_DEFPIN(5);
|
||||
|
||||
// -- These pins are not safe to use:
|
||||
// _FL_DEFPIN(6,6); _FL_DEFPIN(7,7); _FL_DEFPIN(8,8);
|
||||
// _FL_DEFPIN(9,9); _FL_DEFPIN(10,10); _FL_DEFPIN(11,11);
|
||||
|
||||
_FL_DEFPIN(12);
|
||||
_FL_DEFPIN(13);
|
||||
_FL_DEFPIN(14);
|
||||
_FL_DEFPIN(15);
|
||||
_FL_DEFPIN(16);
|
||||
_FL_DEFPIN(17);
|
||||
_FL_DEFPIN(18);
|
||||
_FL_DEFPIN(19);
|
||||
|
||||
// No pin 20 : _FL_DEFPIN(20,20);
|
||||
|
||||
_FL_DEFPIN(21); // Works, but note that GPIO21 is I2C SDA
|
||||
_FL_DEFPIN(22); // Works, but note that GPIO22 is I2C SCL
|
||||
_FL_DEFPIN(23);
|
||||
|
||||
// No pin 24 : _FL_DEFPIN(24,24);
|
||||
|
||||
_FL_DEFPIN(25);
|
||||
_FL_DEFPIN(26);
|
||||
_FL_DEFPIN(27);
|
||||
|
||||
// No pin 28-31: _FL_DEFPIN(28,28); _FL_DEFPIN(29,29); _FL_DEFPIN(30,30); _FL_DEFPIN(31,31);
|
||||
|
||||
// Need special handling for pins > 31
|
||||
_FL_DEFPIN(32);
|
||||
_FL_DEFPIN(33);
|
||||
|
||||
#define HAS_HARDWARE_PIN_SUPPORT
|
||||
|
||||
FASTLED_NAMESPACE_END
|
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP32
|
||||
#define ESP32
|
||||
#endif
|
||||
|
||||
#define FASTLED_ESP32
|
||||
|
||||
// Use system millis timer
|
||||
#define FASTLED_HAS_MILLIS
|
||||
|
||||
typedef volatile uint32_t RoReg;
|
||||
typedef volatile uint32_t RwReg;
|
||||
typedef unsigned long prog_uint32_t;
|
||||
|
||||
|
||||
// Default to NOT using PROGMEM here
|
||||
#ifndef FASTLED_USE_PROGMEM
|
||||
# define FASTLED_USE_PROGMEM 0
|
||||
#endif
|
||||
|
||||
#ifndef FASTLED_ALLOW_INTERRUPTS
|
||||
# define FASTLED_ALLOW_INTERRUPTS 1
|
||||
# define INTERRUPT_THRESHOLD 0
|
||||
#endif
|
||||
|
||||
#define NEED_CXX_BITS
|
||||
|
||||
// These can be overridden
|
||||
# define FASTLED_ESP32_RAW_PIN_ORDER
|
||||
|
||||
// #define cli() os_intr_lock();
|
||||
// #define sei() os_intr_lock();
|
@ -0,0 +1,159 @@
|
||||
#ifndef __INC_CLOCKLESS_BLOCK_ESP8266_H
|
||||
#define __INC_CLOCKLESS_BLOCK_ESP8266_H
|
||||
|
||||
#define FASTLED_HAS_BLOCKLESS 1
|
||||
|
||||
#define FIX_BITS(bits) (((bits & 0x0fL) << 12) | (bits & 0x30))
|
||||
|
||||
#define MIN(X,Y) (((X)<(Y)) ? (X):(Y))
|
||||
#define USED_LANES (MIN(LANES, 6))
|
||||
#define PORT_MASK (((1 << USED_LANES)-1) & 0x0000FFFFL)
|
||||
#define PIN_MASK FIX_BITS(PORT_MASK)
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
extern uint32_t _frame_cnt;
|
||||
extern uint32_t _retry_cnt;
|
||||
#endif
|
||||
|
||||
template <uint8_t LANES, int FIRST_PIN, int T1, int T2, int T3, EOrder RGB_ORDER = GRB, int XTRA0 = 0, bool FLIP = false, int WAIT_TIME = 50>
|
||||
class InlineBlockClocklessController : public CPixelLEDController<RGB_ORDER, LANES, PORT_MASK> {
|
||||
typedef typename FastPin<FIRST_PIN>::port_ptr_t data_ptr_t;
|
||||
typedef typename FastPin<FIRST_PIN>::port_t data_t;
|
||||
|
||||
CMinWait<WAIT_TIME> mWait;
|
||||
public:
|
||||
virtual int size() { return CLEDController::size() * LANES; }
|
||||
|
||||
virtual void showPixels(PixelController<RGB_ORDER, LANES, PORT_MASK> & pixels) {
|
||||
// mWait.wait();
|
||||
/*uint32_t clocks = */
|
||||
int cnt=FASTLED_INTERRUPT_RETRY_COUNT;
|
||||
while(!showRGBInternal(pixels) && cnt--) {
|
||||
os_intr_unlock();
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
_retry_cnt++;
|
||||
#endif
|
||||
delayMicroseconds(WAIT_TIME * 10);
|
||||
os_intr_lock();
|
||||
}
|
||||
// #if FASTLED_ALLOW_INTTERUPTS == 0
|
||||
// Adjust the timer
|
||||
// long microsTaken = CLKS_TO_MICROS(clocks);
|
||||
// MS_COUNTER += (1 + (microsTaken / 1000));
|
||||
// #endif
|
||||
|
||||
// mWait.mark();
|
||||
}
|
||||
|
||||
template<int PIN> static void initPin() {
|
||||
_ESPPIN<PIN, 1<<(PIN & 0xFF)>::setOutput();
|
||||
}
|
||||
|
||||
virtual void init() {
|
||||
void (* funcs[])() ={initPin<12>, initPin<13>, initPin<14>, initPin<15>, initPin<4>, initPin<5>};
|
||||
|
||||
for (uint8_t i = 0; i < USED_LANES; ++i) {
|
||||
funcs[i]();
|
||||
}
|
||||
}
|
||||
|
||||
virtual uint16_t getMaxRefreshRate() const { return 400; }
|
||||
|
||||
typedef union {
|
||||
uint8_t bytes[8];
|
||||
uint16_t shorts[4];
|
||||
uint32_t raw[2];
|
||||
} Lines;
|
||||
|
||||
#define ESP_ADJUST 0 // (2*(F_CPU/24000000))
|
||||
#define ESP_ADJUST2 0
|
||||
template<int BITS,int PX> __attribute__ ((always_inline)) inline static void writeBits(register uint32_t & last_mark, register Lines & b, PixelController<RGB_ORDER, LANES, PORT_MASK> &pixels) { // , register uint32_t & b2) {
|
||||
Lines b2 = b;
|
||||
transpose8x1_noinline(b.bytes,b2.bytes);
|
||||
|
||||
register uint8_t d = pixels.template getd<PX>(pixels);
|
||||
register uint8_t scale = pixels.template getscale<PX>(pixels);
|
||||
|
||||
for(register uint32_t i = 0; i < USED_LANES; i++) {
|
||||
while((__clock_cycles() - last_mark) < (T1+T2+T3));
|
||||
last_mark = __clock_cycles();
|
||||
*FastPin<FIRST_PIN>::sport() = PIN_MASK;
|
||||
|
||||
uint32_t nword = (uint32_t)(~b2.bytes[7-i]);
|
||||
while((__clock_cycles() - last_mark) < (T1-6));
|
||||
*FastPin<FIRST_PIN>::cport() = FIX_BITS(nword);
|
||||
|
||||
while((__clock_cycles() - last_mark) < (T1+T2));
|
||||
*FastPin<FIRST_PIN>::cport() = PIN_MASK;
|
||||
|
||||
b.bytes[i] = pixels.template loadAndScale<PX>(pixels,i,d,scale);
|
||||
}
|
||||
|
||||
for(register uint32_t i = USED_LANES; i < 8; i++) {
|
||||
while((__clock_cycles() - last_mark) < (T1+T2+T3));
|
||||
last_mark = __clock_cycles();
|
||||
*FastPin<FIRST_PIN>::sport() = PIN_MASK;
|
||||
|
||||
uint32_t nword = (uint32_t)(~b2.bytes[7-i]);
|
||||
while((__clock_cycles() - last_mark) < (T1-6));
|
||||
*FastPin<FIRST_PIN>::cport() = FIX_BITS(nword);
|
||||
|
||||
while((__clock_cycles() - last_mark) < (T1+T2));
|
||||
*FastPin<FIRST_PIN>::cport() = PIN_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
// This method is made static to force making register Y available to use for data on AVR - if the method is non-static, then
|
||||
// gcc will use register Y for the this pointer.
|
||||
static uint32_t ICACHE_RAM_ATTR showRGBInternal(PixelController<RGB_ORDER, LANES, PORT_MASK> &allpixels) {
|
||||
|
||||
// Setup the pixel controller and load/scale the first byte
|
||||
Lines b0;
|
||||
|
||||
for(int i = 0; i < USED_LANES; i++) {
|
||||
b0.bytes[i] = allpixels.loadAndScale0(i);
|
||||
}
|
||||
allpixels.preStepFirstByteDithering();
|
||||
|
||||
os_intr_lock();
|
||||
uint32_t _start = __clock_cycles();
|
||||
uint32_t last_mark = _start;
|
||||
|
||||
while(allpixels.has(1)) {
|
||||
// Write first byte, read next byte
|
||||
writeBits<8+XTRA0,1>(last_mark, b0, allpixels);
|
||||
|
||||
// Write second byte, read 3rd byte
|
||||
writeBits<8+XTRA0,2>(last_mark, b0, allpixels);
|
||||
allpixels.advanceData();
|
||||
|
||||
// Write third byte
|
||||
writeBits<8+XTRA0,0>(last_mark, b0, allpixels);
|
||||
|
||||
#if (FASTLED_ALLOW_INTERRUPTS == 1)
|
||||
os_intr_unlock();
|
||||
#endif
|
||||
|
||||
allpixels.stepDithering();
|
||||
|
||||
#if (FASTLED_ALLOW_INTERRUPTS == 1)
|
||||
os_intr_lock();
|
||||
// if interrupts took longer than 45µs, punt on the current frame
|
||||
if((int32_t)(__clock_cycles()-last_mark) > 0) {
|
||||
if((int32_t)(__clock_cycles()-last_mark) > (T1+T2+T3+((WAIT_TIME-INTERRUPT_THRESHOLD)*CLKS_PER_US))) { os_intr_unlock(); return 0; }
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
os_intr_unlock();
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
_frame_cnt++;
|
||||
#endif
|
||||
return __clock_cycles() - _start;
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
#endif
|
@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
extern uint32_t _frame_cnt;
|
||||
extern uint32_t _retry_cnt;
|
||||
#endif
|
||||
|
||||
// Info on reading cycle counter from https://github.com/kbeckmann/nodemcu-firmware/blob/ws2812-dual/app/modules/ws2812.c
|
||||
__attribute__ ((always_inline)) inline static uint32_t __clock_cycles() {
|
||||
uint32_t cyc;
|
||||
__asm__ __volatile__ ("rsr %0,ccount":"=a" (cyc));
|
||||
return cyc;
|
||||
}
|
||||
|
||||
#define FASTLED_HAS_CLOCKLESS 1
|
||||
|
||||
template <int DATA_PIN, int T1, int T2, int T3, EOrder RGB_ORDER = RGB, int XTRA0 = 0, bool FLIP = false, int WAIT_TIME = 50>
|
||||
class ClocklessController : public CPixelLEDController<RGB_ORDER> {
|
||||
typedef typename FastPin<DATA_PIN>::port_ptr_t data_ptr_t;
|
||||
typedef typename FastPin<DATA_PIN>::port_t data_t;
|
||||
|
||||
data_t mPinMask;
|
||||
data_ptr_t mPort;
|
||||
CMinWait<WAIT_TIME> mWait;
|
||||
public:
|
||||
virtual void init() {
|
||||
FastPin<DATA_PIN>::setOutput();
|
||||
mPinMask = FastPin<DATA_PIN>::mask();
|
||||
mPort = FastPin<DATA_PIN>::port();
|
||||
}
|
||||
|
||||
virtual uint16_t getMaxRefreshRate() const { return 400; }
|
||||
|
||||
protected:
|
||||
|
||||
virtual void showPixels(PixelController<RGB_ORDER> & pixels) {
|
||||
// mWait.wait();
|
||||
int cnt = FASTLED_INTERRUPT_RETRY_COUNT;
|
||||
while((showRGBInternal(pixels)==0) && cnt--) {
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
_retry_cnt++;
|
||||
#endif
|
||||
os_intr_unlock();
|
||||
delayMicroseconds(WAIT_TIME);
|
||||
os_intr_lock();
|
||||
}
|
||||
// mWait.mark();
|
||||
}
|
||||
|
||||
#define _ESP_ADJ (0)
|
||||
#define _ESP_ADJ2 (0)
|
||||
|
||||
template<int BITS> __attribute__ ((always_inline)) inline static void writeBits(register uint32_t & last_mark, register uint32_t b) {
|
||||
b <<= 24; b = ~b;
|
||||
for(register uint32_t i = BITS; i > 0; i--) {
|
||||
while((__clock_cycles() - last_mark) < (T1+T2+T3));
|
||||
last_mark = __clock_cycles();
|
||||
FastPin<DATA_PIN>::hi();
|
||||
|
||||
while((__clock_cycles() - last_mark) < T1);
|
||||
if(b & 0x80000000L) { FastPin<DATA_PIN>::lo(); }
|
||||
b <<= 1;
|
||||
|
||||
while((__clock_cycles() - last_mark) < (T1+T2));
|
||||
FastPin<DATA_PIN>::lo();
|
||||
}
|
||||
}
|
||||
|
||||
// This method is made static to force making register Y available to use for data on AVR - if the method is non-static, then
|
||||
// gcc will use register Y for the this pointer.
|
||||
static uint32_t ICACHE_RAM_ATTR showRGBInternal(PixelController<RGB_ORDER> pixels) {
|
||||
// Setup the pixel controller and load/scale the first byte
|
||||
pixels.preStepFirstByteDithering();
|
||||
register uint32_t b = pixels.loadAndScale0();
|
||||
pixels.preStepFirstByteDithering();
|
||||
os_intr_lock();
|
||||
uint32_t start = __clock_cycles();
|
||||
uint32_t last_mark = start;
|
||||
while(pixels.has(1)) {
|
||||
// Write first byte, read next byte
|
||||
writeBits<8+XTRA0>(last_mark, b);
|
||||
b = pixels.loadAndScale1();
|
||||
|
||||
// Write second byte, read 3rd byte
|
||||
writeBits<8+XTRA0>(last_mark, b);
|
||||
b = pixels.loadAndScale2();
|
||||
|
||||
// Write third byte, read 1st byte of next pixel
|
||||
writeBits<8+XTRA0>(last_mark, b);
|
||||
b = pixels.advanceAndLoadAndScale0();
|
||||
|
||||
#if (FASTLED_ALLOW_INTERRUPTS == 1)
|
||||
os_intr_unlock();
|
||||
#endif
|
||||
|
||||
pixels.stepDithering();
|
||||
|
||||
#if (FASTLED_ALLOW_INTERRUPTS == 1)
|
||||
os_intr_lock();
|
||||
// if interrupts took longer than 45µs, punt on the current frame
|
||||
if((int32_t)(__clock_cycles()-last_mark) > 0) {
|
||||
if((int32_t)(__clock_cycles()-last_mark) > (T1+T2+T3+((WAIT_TIME-INTERRUPT_THRESHOLD)*CLKS_PER_US))) { sei(); return 0; }
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
os_intr_unlock();
|
||||
#ifdef FASTLED_DEBUG_COUNT_FRAME_RETRIES
|
||||
_frame_cnt++;
|
||||
#endif
|
||||
return __clock_cycles() - start;
|
||||
}
|
||||
};
|
||||
|
||||
FASTLED_NAMESPACE_END
|
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastpin_esp8266.h"
|
||||
#include "clockless_esp8266.h"
|
||||
#include "clockless_block_esp8266.h"
|
101
.pio/libdeps/local/FastLED/platforms/esp/8266/fastpin_esp8266.h
Normal file
101
.pio/libdeps/local/FastLED/platforms/esp/8266/fastpin_esp8266.h
Normal file
@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
struct FASTLED_ESP_IO {
|
||||
volatile uint32_t _GPO;
|
||||
volatile uint32_t _GPOS;
|
||||
volatile uint32_t _GPOC;
|
||||
};
|
||||
|
||||
#define _GPB (*(FASTLED_ESP_IO*)(0x60000000+(0x300)))
|
||||
|
||||
|
||||
template<uint8_t PIN, uint32_t MASK> class _ESPPIN {
|
||||
|
||||
public:
|
||||
typedef volatile uint32_t * port_ptr_t;
|
||||
typedef uint32_t port_t;
|
||||
|
||||
inline static void setOutput() { pinMode(PIN, OUTPUT); }
|
||||
inline static void setInput() { pinMode(PIN, INPUT); }
|
||||
|
||||
inline static void hi() __attribute__ ((always_inline)) { if(PIN < 16) { _GPB._GPOS = MASK; } else { GP16O |= MASK; } }
|
||||
inline static void lo() __attribute__ ((always_inline)) { if(PIN < 16) { _GPB._GPOC = MASK; } else { GP16O &= ~MASK; } }
|
||||
inline static void set(register port_t val) __attribute__ ((always_inline)) { if(PIN < 16) { _GPB._GPO = val; } else { GP16O = val; }}
|
||||
|
||||
inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
|
||||
|
||||
inline static void toggle() __attribute__ ((always_inline)) { if(PIN < 16) { _GPB._GPO ^= MASK; } else { GP16O ^= MASK; } }
|
||||
|
||||
inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { hi(); }
|
||||
inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { lo(); }
|
||||
inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; }
|
||||
|
||||
inline static port_t hival() __attribute__ ((always_inline)) { if (PIN<16) { return GPO | MASK; } else { return GP16O | MASK; } }
|
||||
inline static port_t loval() __attribute__ ((always_inline)) { if (PIN<16) { return GPO & ~MASK; } else { return GP16O & ~MASK; } }
|
||||
inline static port_ptr_t port() __attribute__ ((always_inline)) { if(PIN<16) { return &_GPB._GPO; } else { return &GP16O; } }
|
||||
inline static port_ptr_t sport() __attribute__ ((always_inline)) { return &_GPB._GPOS; } // there is no GP160 support for this
|
||||
inline static port_ptr_t cport() __attribute__ ((always_inline)) { return &_GPB._GPOC; }
|
||||
inline static port_t mask() __attribute__ ((always_inline)) { return MASK; }
|
||||
|
||||
inline static bool isset() __attribute__ ((always_inline)) { return (PIN < 16) ? (GPO & MASK) : (GP16O & MASK); }
|
||||
};
|
||||
|
||||
#define _FL_DEFPIN(PIN, REAL_PIN) template<> class FastPin<PIN> : public _ESPPIN<REAL_PIN, (1<<(REAL_PIN & 0xFF))> {};
|
||||
|
||||
|
||||
#ifdef FASTLED_ESP8266_RAW_PIN_ORDER
|
||||
#define MAX_PIN 16
|
||||
_FL_DEFPIN(0,0); _FL_DEFPIN(1,1); _FL_DEFPIN(2,2); _FL_DEFPIN(3,3);
|
||||
_FL_DEFPIN(4,4); _FL_DEFPIN(5,5);
|
||||
|
||||
// These pins should be disabled, as they always cause WDT resets
|
||||
// _FL_DEFPIN(6,6); _FL_DEFPIN(7,7);
|
||||
// _FL_DEFPIN(8,8); _FL_DEFPIN(9,9); _FL_DEFPIN(10,10); _FL_DEFPIN(11,11);
|
||||
|
||||
_FL_DEFPIN(12,12); _FL_DEFPIN(13,13); _FL_DEFPIN(14,14); _FL_DEFPIN(15,15);
|
||||
_FL_DEFPIN(16,16);
|
||||
|
||||
#define PORTA_FIRST_PIN 12
|
||||
#elif defined(FASTLED_ESP8266_D1_PIN_ORDER)
|
||||
#define MAX_PIN 15
|
||||
_FL_DEFPIN(0,3);
|
||||
_FL_DEFPIN(1,1);
|
||||
_FL_DEFPIN(2,16);
|
||||
_FL_DEFPIN(3,5);
|
||||
_FL_DEFPIN(4,4);
|
||||
_FL_DEFPIN(5,14);
|
||||
_FL_DEFPIN(6,12);
|
||||
_FL_DEFPIN(7,13);
|
||||
_FL_DEFPIN(8,0);
|
||||
_FL_DEFPIN(9,2);
|
||||
_FL_DEFPIN(10,15);
|
||||
_FL_DEFPIN(11,13);
|
||||
_FL_DEFPIN(12,12);
|
||||
_FL_DEFPIN(13,14);
|
||||
_FL_DEFPIN(14,4);
|
||||
_FL_DEFPIN(15,5);
|
||||
|
||||
#define PORTA_FIRST_PIN 12
|
||||
|
||||
#else // if defined(FASTLED_ESP8266_NODEMCU_PIN_ORDER)
|
||||
#define MAX_PIN 10
|
||||
|
||||
// This seems to be the standard Dxx pin mapping on most of the esp boards that i've found
|
||||
_FL_DEFPIN(0,16); _FL_DEFPIN(1,5); _FL_DEFPIN(2,4); _FL_DEFPIN(3,0);
|
||||
_FL_DEFPIN(4,2); _FL_DEFPIN(5,14); _FL_DEFPIN(6,12); _FL_DEFPIN(7,13);
|
||||
_FL_DEFPIN(8,15); _FL_DEFPIN(9,3); _FL_DEFPIN(10,1);
|
||||
|
||||
#define PORTA_FIRST_PIN 6
|
||||
|
||||
// The rest of the pins - these are generally not available
|
||||
// _FL_DEFPIN(11,6);
|
||||
// _FL_DEFPIN(12,7); _FL_DEFPIN(13,8); _FL_DEFPIN(14,9); _FL_DEFPIN(15,10);
|
||||
// _FL_DEFPIN(16,11);
|
||||
|
||||
#endif
|
||||
|
||||
#define HAS_HARDWARE_PIN_SUPPORT
|
||||
|
||||
#define FASTLED_NAMESPACE_END
|
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP8266
|
||||
#define ESP8266
|
||||
#endif
|
||||
|
||||
#define FASTLED_ESP8266
|
||||
|
||||
// Use system millis timer
|
||||
#define FASTLED_HAS_MILLIS
|
||||
|
||||
typedef volatile uint32_t RoReg;
|
||||
typedef volatile uint32_t RwReg;
|
||||
typedef uint32_t prog_uint32_t;
|
||||
|
||||
|
||||
// Default to NOT using PROGMEM here
|
||||
#ifndef FASTLED_USE_PROGMEM
|
||||
# define FASTLED_USE_PROGMEM 0
|
||||
#endif
|
||||
|
||||
#ifndef FASTLED_ALLOW_INTERRUPTS
|
||||
# define FASTLED_ALLOW_INTERRUPTS 1
|
||||
# define INTERRUPT_THRESHOLD 0
|
||||
#endif
|
||||
|
||||
#define NEED_CXX_BITS
|
||||
|
||||
// These can be overridden
|
||||
#if !defined(FASTLED_ESP8266_RAW_PIN_ORDER) && !defined(FASTLED_ESP8266_NODEMCU_PIN_ORDER) && !defined(FASTLED_ESP8266_D1_PIN_ORDER)
|
||||
# ifdef ARDUINO_ESP8266_NODEMCU
|
||||
# define FASTLED_ESP8266_NODEMCU_PIN_ORDER
|
||||
# else
|
||||
# define FASTLED_ESP8266_RAW_PIN_ORDER
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// #define cli() os_intr_lock();
|
||||
// #define sei() os_intr_lock();
|
Reference in New Issue
Block a user