diff --git a/src/internal/NeoEsp8266UartMethod.cpp b/src/internal/NeoEsp8266UartMethod.cpp
new file mode 100644
index 0000000..96e62eb
--- /dev/null
+++ b/src/internal/NeoEsp8266UartMethod.cpp
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+NeoPixel library helper functions for Esp8266 UART hardware
+
+Written by Michael C. Miller.
+
+I invest time and resources providing this open source code,
+please support me by dontating (see https://github.com/Makuna/NeoPixelBus)
+
+-------------------------------------------------------------------------
+This file is part of the Makuna/NeoPixelBus library.
+
+NeoPixelBus is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation, either version 3 of
+the License, or (at your option) any later version.
+
+NeoPixelBus is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with NeoPixel. If not, see
+.
+-------------------------------------------------------------------------*/
+
+#ifdef ARDUINO_ARCH_ESP8266
+#include "NeoEsp8266UartMethod.h"
+#include
+extern "C"
+{
+ #include
+ #include
+ #include
+ #include
+}
+
+#define UART1 1
+#define UART1_INV_MASK (0x3f << 19)
+
+// Gets the number of bytes waiting in the TX FIFO of UART1
+static inline uint8_t getUartTxFifoLength()
+{
+ return (U1S >> USTXC) & 0xff;
+}
+
+// Append a byte to the TX FIFO of UART1
+// You must ensure the TX FIFO isn't full
+static inline void enqueue(uint8_t byte)
+{
+ U1F = byte;
+}
+
+static const uint8_t* esp8266_uart1_async_buf;
+static const uint8_t* esp8266_uart1_async_buf_end;
+
+NeoEsp8266Uart::NeoEsp8266Uart(uint8_t pin, uint16_t pixelCount, size_t elementSize)
+{
+ _sizePixels = pixelCount * elementSize;
+ _pixels = (uint8_t*)malloc(_sizePixels);
+ memset(_pixels, 0x00, _sizePixels);
+}
+
+NeoEsp8266Uart::~NeoEsp8266Uart()
+{
+ free(_pixels);
+
+ // Wait until the TX fifo is empty. This way we avoid broken frames
+ // when destroying & creating a NeoPixelBus to change its length.
+ while (getUartTxFifoLength() > 0)
+ {
+ yield();
+ }
+}
+
+void NeoEsp8266Uart::InitializeUart(uint32_t uartBaud)
+{
+ // Configure the serial line with 1 start bit (0), 6 data bits and 1 stop bit (1)
+ Serial1.begin(uartBaud, SERIAL_6N1, SERIAL_TX_ONLY);
+
+ // Invert the TX voltage associated with logic level so:
+ // - A logic level 0 will generate a Vcc signal
+ // - A logic level 1 will generate a Gnd signal
+ CLEAR_PERI_REG_MASK(UART_CONF0(UART1), UART1_INV_MASK);
+ SET_PERI_REG_MASK(UART_CONF0(UART1), (BIT(22)));
+}
+
+void NeoEsp8266Uart::UpdateUart()
+{
+ // Since the UART can finish sending queued bytes in the FIFO in
+ // the background, instead of waiting for the FIFO to flush
+ // we annotate the start time of the frame so we can calculate
+ // when it will finish.
+ _startTime = micros();
+
+ // Then keep filling the FIFO until done
+ const uint8_t* ptr = _pixels;
+ const uint8_t* end = ptr + _sizePixels;
+ while (ptr != end)
+ {
+ ptr = FillUartFifo(ptr, end);
+ }
+}
+
+const uint8_t* ICACHE_RAM_ATTR NeoEsp8266Uart::FillUartFifo(const uint8_t* pixels, const uint8_t* end)
+{
+ // Remember: UARTs send less significant bit (LSB) first so
+ // pushing ABCDEF byte will generate a 0FEDCBA1 signal,
+ // including a LOW(0) start & a HIGH(1) stop bits.
+ // Also, we have configured UART to invert logic levels, so:
+ const uint8_t _uartData[4] = {
+ 0b110111, // On wire: 1 000 100 0 [Neopixel reads 00]
+ 0b000111, // On wire: 1 000 111 0 [Neopixel reads 01]
+ 0b110100, // On wire: 1 110 100 0 [Neopixel reads 10]
+ 0b000100, // On wire: 1 110 111 0 [NeoPixel reads 11]
+ };
+ uint8_t avail = (UART_TX_FIFO_SIZE - getUartTxFifoLength()) / 4;
+ if (end - pixels > avail)
+ {
+ end = pixels + avail;
+ }
+ while (pixels < end)
+ {
+ uint8_t subpix = *pixels++;
+ enqueue(_uartData[(subpix >> 6) & 0x3]);
+ enqueue(_uartData[(subpix >> 4) & 0x3]);
+ enqueue(_uartData[(subpix >> 2) & 0x3]);
+ enqueue(_uartData[ subpix & 0x3]);
+ }
+ return pixels;
+}
+
+NeoEsp8266AsyncUart::NeoEsp8266AsyncUart(uint8_t pin, uint16_t pixelCount, size_t elementSize)
+ : NeoEsp8266Uart(pin, pixelCount, elementSize)
+{
+ _asyncPixels = (uint8_t*)malloc(_sizePixels);
+}
+
+NeoEsp8266AsyncUart::~NeoEsp8266AsyncUart()
+{
+ // Remember: the UART interrupt can be sending data from _asyncPixels in the background
+ while (esp8266_uart1_async_buf != esp8266_uart1_async_buf_end)
+ {
+ yield();
+ }
+ free(_asyncPixels);
+}
+
+void ICACHE_RAM_ATTR NeoEsp8266AsyncUart::InitializeUart(uint32_t uartBaud)
+{
+ NeoEsp8266Uart::InitializeUart(uartBaud);
+
+ // Disable all interrupts
+ ETS_UART_INTR_DISABLE();
+
+ // Clear the RX & TX FIFOS
+ SET_PERI_REG_MASK(UART_CONF0(UART1), UART_RXFIFO_RST | UART_TXFIFO_RST);
+ CLEAR_PERI_REG_MASK(UART_CONF0(UART1), UART_RXFIFO_RST | UART_TXFIFO_RST);
+
+ // Set the interrupt handler
+ ETS_UART_INTR_ATTACH(IntrHandler, NULL);
+
+ // Set tx fifo trigger. 80 bytes gives us 200 microsecs to refill the FIFO
+ WRITE_PERI_REG(UART_CONF1(UART1), 80 << UART_TXFIFO_EMPTY_THRHD_S);
+
+ // Disable RX & TX interrupts. It is enabled by uart.c in the SDK
+ CLEAR_PERI_REG_MASK(UART_INT_ENA(UART1), UART_RXFIFO_FULL_INT_ENA | UART_TXFIFO_EMPTY_INT_ENA);
+
+ // Clear all pending interrupts in UART1
+ WRITE_PERI_REG(UART_INT_CLR(UART1), 0xffff);
+
+ // Reenable interrupts
+ ETS_UART_INTR_ENABLE();
+}
+
+void NeoEsp8266AsyncUart::UpdateUart()
+{
+ // Instruct ESP8266 hardware uart1 to send the pixels asynchronously
+ esp8266_uart1_async_buf = _pixels;
+ esp8266_uart1_async_buf_end = _pixels + _sizePixels;
+ SET_PERI_REG_MASK(UART_INT_ENA(1), UART_TXFIFO_EMPTY_INT_ENA);
+
+ // Annotate when we started to send bytes, so we can calculate when we are ready to send again
+ _startTime = micros();
+
+ // Copy the pixels to the idle buffer and swap them
+ memcpy(_asyncPixels, _pixels, _sizePixels);
+ std::swap(_asyncPixels, _pixels);
+}
+
+void ICACHE_RAM_ATTR NeoEsp8266AsyncUart::IntrHandler(void* param)
+{
+ // Interrupt handler is shared between UART0 & UART1
+ if (READ_PERI_REG(UART_INT_ST(UART1))) //any UART1 stuff
+ {
+ // Fill the FIFO with new data
+ esp8266_uart1_async_buf = FillUartFifo(esp8266_uart1_async_buf, esp8266_uart1_async_buf_end);
+ // Disable TX interrupt when done
+ if (esp8266_uart1_async_buf == esp8266_uart1_async_buf_end)
+ {
+ CLEAR_PERI_REG_MASK(UART_INT_ENA(UART1), UART_TXFIFO_EMPTY_INT_ENA);
+ }
+ // Clear all interrupts flags (just in case)
+ WRITE_PERI_REG(UART_INT_CLR(UART1), 0xffff);
+ }
+
+ if (READ_PERI_REG(UART_INT_ST(UART0)))
+ {
+ // TODO: gdbstub uses the interrupt of UART0, but there is no way to call its
+ // interrupt handler gdbstub_uart_hdlr since it's static.
+ WRITE_PERI_REG(UART_INT_CLR(UART0), 0xffff);
+ }
+}
+
+#endif
+
diff --git a/src/internal/NeoEsp8266UartMethod.h b/src/internal/NeoEsp8266UartMethod.h
index 3c577df..3c17fd9 100644
--- a/src/internal/NeoEsp8266UartMethod.h
+++ b/src/internal/NeoEsp8266UartMethod.h
@@ -1,5 +1,5 @@
/*-------------------------------------------------------------------------
-NeoPixel library helper functions for Esp8266.
+NeoPixel library helper functions for Esp8266 UART hardware
Written by Michael C. Miller.
@@ -27,64 +27,96 @@ License along with NeoPixel. If not, see
#pragma once
#ifdef ARDUINO_ARCH_ESP8266
+#include
-extern "C"
+// NeoEsp8266Uart contains all the low level details that doesn't
+// depend on the transmission speed, and therefore, it isn't a template
+class NeoEsp8266Uart
{
-#include "eagle_soc.h"
-#include "uart_register.h"
-}
+protected:
+ NeoEsp8266Uart(uint8_t pin, uint16_t pixelCount, size_t elementSize);
-// due to linker overriding ICACHE_RAM_ATTR for cpp files, this function was
-// moved into a NeoPixelEsp8266.c file.
-extern "C" void ICACHE_RAM_ATTR esp8266_uart1_send_pixels(uint8_t* pixels, uint8_t* end);
+ ~NeoEsp8266Uart();
+ void InitializeUart(uint32_t uartBaud);
+
+ void UpdateUart();
+
+ static const uint8_t* ICACHE_RAM_ATTR FillUartFifo(const uint8_t* pixels, const uint8_t* end);
+
+ size_t _sizePixels; // Size of '_pixels' buffer below
+ uint8_t* _pixels; // Holds LED color values
+ uint32_t _startTime; // Microsecond count when last update started
+};
+
+// NeoEsp8266AsyncUart handles all transmission asynchronously using interrupts
+//
+// This UART controller uses two buffers that are swapped in every call to
+// NeoPixelBus.Show(). One buffer contains the data that is being sent
+// asynchronosly and another buffer contains the data that will be send
+// in the next call to NeoPixelBus.Show().
+//
+// Therefore, the result of NeoPixelBus.Pixels() is invalidated after
+// every call to NeoPixelBus.Show() and must not be cached.
+class NeoEsp8266AsyncUart: public NeoEsp8266Uart
+{
+protected:
+ NeoEsp8266AsyncUart(uint8_t pin, uint16_t pixelCount, size_t elementSize);
+
+ ~NeoEsp8266AsyncUart();
+
+ void InitializeUart(uint32_t uartBaud);
+
+ void UpdateUart();
+
+private:
+ static void ICACHE_RAM_ATTR IntrHandler(void* param);
+
+ uint8_t* _asyncPixels; // Holds a copy of LED color values taken when UpdateUart began
+};
+
+// NeoEsp8266UartSpeed800Kbps contains the timing constant used to get NeoPixelBus running at 800Khz
class NeoEsp8266UartSpeed800Kbps
{
public:
- static const uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800mhz speed
+ static const uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800khz speed
static const uint32_t UartBaud = 3200000; // 800mhz, 4 serial bytes per NeoByte
};
+// NeoEsp8266UartSpeed800Kbps contains the timing constant used to get NeoPixelBus running at 400Khz
class NeoEsp8266UartSpeed400Kbps
{
public:
- static const uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400mhz speed
+ static const uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed
static const uint32_t UartBaud = 1600000; // 400mhz, 4 serial bytes per NeoByte
};
-#define UART1 1
-#define UART1_INV_MASK (0x3f << 19)
-
-template class NeoEsp8266UartMethodBase
+// NeoEsp8266UartMethodBase is a light shell arround NeoEsp8266Uart or NeoEsp8266AsyncUart that
+// implements the methods needed to operate as a NeoPixelBus method.
+template
+class NeoEsp8266UartMethodBase: public T_BASE
{
public:
NeoEsp8266UartMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize)
+ : T_BASE(pin, pixelCount, elementSize)
{
- _sizePixels = pixelCount * elementSize;
- _pixels = (uint8_t*)malloc(_sizePixels);
- memset(_pixels, 0x00, _sizePixels);
- }
-
- ~NeoEsp8266UartMethodBase()
- {
- free(_pixels);
}
bool IsReadyToUpdate() const
{
- uint32_t delta = micros() - _endTime;
-
- return (delta >= 50L && delta <= (4294967296L - getPixelTime()));
+ uint32_t delta = micros() - this->_startTime;
+ return delta >= getPixelTime() + 50;
}
void Initialize()
{
- Serial1.begin(T_SPEED::UartBaud, SERIAL_6N1, SERIAL_TX_ONLY);
+ this->InitializeUart(T_SPEED::UartBaud);
- CLEAR_PERI_REG_MASK(UART_CONF0(UART1), UART1_INV_MASK);
- SET_PERI_REG_MASK(UART_CONF0(UART1), (BIT(22)));
-
- _endTime = micros();
+ // Inverting logic levels can generate a phantom bit in the led strip bus
+ // We need to delay 50+ microseconds the output stream to force a data
+ // latch and discard this bit. Otherwise, that bit would be prepended to
+ // the first frame corrupting it.
+ this->_startTime = micros() - getPixelTime();
}
void Update()
@@ -95,43 +127,34 @@ public:
// subsequent round of data until the latch time has elapsed. This
// allows the mainline code to start generating the next frame of data
// rather than stalling for the latch.
-
- while (!IsReadyToUpdate())
+ while (!this->IsReadyToUpdate())
{
yield();
}
-
- // since uart is async buffer send, we have to calc the endtime that it will take
- // to correctly manage the data latch in the above code
- // add the calculated time to the current time
- _endTime = micros() + getPixelTime();
-
- // esp hardware uart sending of data
- esp8266_uart1_send_pixels(_pixels, _pixels + _sizePixels);
+ this->UpdateUart();
}
uint8_t* getPixels() const
{
- return _pixels;
+ return this->_pixels;
};
size_t getPixelsSize() const
{
- return _sizePixels;
+ return this->_sizePixels;
};
private:
uint32_t getPixelTime() const
{
- return (T_SPEED::ByteSendTimeUs * _sizePixels);
+ return (T_SPEED::ByteSendTimeUs * this->_sizePixels);
};
-
- size_t _sizePixels; // Size of '_pixels' buffer below
- uint8_t* _pixels; // Holds LED color values
- uint32_t _endTime; // Latch timing reference
};
-typedef NeoEsp8266UartMethodBase NeoEsp8266Uart800KbpsMethod;
-typedef NeoEsp8266UartMethodBase NeoEsp8266Uart400KbpsMethod;
+typedef NeoEsp8266UartMethodBase NeoEsp8266Uart800KbpsMethod;
+typedef NeoEsp8266UartMethodBase NeoEsp8266Uart400KbpsMethod;
+typedef NeoEsp8266UartMethodBase NeoEsp8266AsyncUart800KbpsMethod;
+typedef NeoEsp8266UartMethodBase NeoEsp8266AsyncUart400KbpsMethod;
+
+#endif
-#endif
\ No newline at end of file
diff --git a/src/internal/NeoPixelEsp8266.c b/src/internal/NeoPixelEsp8266.c
index 0ba4e82..73e0366 100644
--- a/src/internal/NeoPixelEsp8266.c
+++ b/src/internal/NeoPixelEsp8266.c
@@ -29,31 +29,6 @@ License along with NeoPixel. If not, see
#include
#include
-void ICACHE_RAM_ATTR esp8266_uart1_send_pixels(uint8_t* pixels, uint8_t* end)
-{
- const uint8_t _uartData[4] = { 0b00110111, 0b00000111, 0b00110100, 0b00000100 };
- const uint8_t _uartFifoTrigger = 124; // tx fifo should be 128 bytes. minus the four we need to send
-
- do
- {
- uint8_t subpix = *pixels++;
- uint8_t buf[4] = { _uartData[(subpix >> 6) & 3],
- _uartData[(subpix >> 4) & 3],
- _uartData[(subpix >> 2) & 3],
- _uartData[subpix & 3] };
-
- // now wait till this the FIFO buffer has room to send more
- while (((U1S >> USTXC) & 0xff) > _uartFifoTrigger);
-
- for (uint8_t i = 0; i < 4; i++)
- {
- // directly write the byte to transfer into the UART1 FIFO register
- U1F = buf[i];
- }
-
- } while (pixels < end);
-}
-
inline uint32_t _getCycleCount()
{
uint32_t ccount;