From 8933c307c56761131726f344de8a20179039f050 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sun, 23 Dec 2018 14:30:01 -0800 Subject: [PATCH] Uart0 support (#239) --- examples/NeoPixelTest/NeoPixelTest.ino | 9 +- .../NeoPixelAnimation/NeoPixelAnimation.ino | 5 +- .../NeoPixelFunFadeInOut.ino | 4 +- .../NeoPixelFunLoop/NeoPixelFunLoop.ino | 4 +- .../NeoPixelFunRandomChange.ino | 4 +- keywords.txt | 42 ++- library.json | 2 +- library.properties | 2 +- src/internal/NeoEsp8266UartMethod.cpp | 226 +++++-------- src/internal/NeoEsp8266UartMethod.h | 302 ++++++++++++++++-- 10 files changed, 386 insertions(+), 214 deletions(-) diff --git a/examples/NeoPixelTest/NeoPixelTest.ino b/examples/NeoPixelTest/NeoPixelTest.ino index 6d323b8..415c853 100644 --- a/examples/NeoPixelTest/NeoPixelTest.ino +++ b/examples/NeoPixelTest/NeoPixelTest.ino @@ -25,9 +25,7 @@ NeoPixelBus strip(PixelCount, PixelPin); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use. // There are other Esp8266 alternative methods that provide more pin options, but also have // other side effects. -//NeoPixelBus strip(PixelCount); -// -// NeoEsp8266Uart800KbpsMethod uses GPI02 instead +// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods // You can also use one of these for Esp8266, // each having their own restrictions @@ -38,9 +36,10 @@ NeoPixelBus strip(PixelCount, PixelPin); //NeoPixelBus strip(PixelCount, PixelPin); // Uart method is good for the Esp-01 or other pin restricted modules +// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods // NOTE: These will ignore the PIN and use GPI02 pin -//NeoPixelBus strip(PixelCount, PixelPin); -//NeoPixelBus strip(PixelCount, PixelPin); +//NeoPixelBus strip(PixelCount, PixelPin); +//NeoPixelBus strip(PixelCount, PixelPin); // The bitbang method is really only good if you are not using WiFi features of the ESP // It works with all but pin 16 diff --git a/examples/animations/NeoPixelAnimation/NeoPixelAnimation.ino b/examples/animations/NeoPixelAnimation/NeoPixelAnimation.ino index 66b4d4a..4f0386a 100644 --- a/examples/animations/NeoPixelAnimation/NeoPixelAnimation.ino +++ b/examples/animations/NeoPixelAnimation/NeoPixelAnimation.ino @@ -29,10 +29,7 @@ NeoPixelBus strip(PixelCount, PixelPin); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use. // There are other Esp8266 alternative methods that provide more pin options, but also have // other side effects. -//NeoPixelBus strip(PixelCount); -// -// NeoEsp8266Uart800KbpsMethod uses GPI02 instead - +// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods // NeoPixel animation time management object NeoPixelAnimator animations(PixelCount, NEO_CENTISECONDS); diff --git a/examples/animations/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino b/examples/animations/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino index 71e6b54..f6c065c 100644 --- a/examples/animations/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino +++ b/examples/animations/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino @@ -16,9 +16,7 @@ NeoPixelBus strip(PixelCount, PixelPin); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use. // There are other Esp8266 alternative methods that provide more pin options, but also have // other side effects. -//NeoPixelBus strip(PixelCount); -// -// NeoEsp8266Uart800KbpsMethod uses GPI02 instead +// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods NeoPixelAnimator animations(AnimationChannels); // NeoPixel animation management object diff --git a/examples/animations/NeoPixelFunLoop/NeoPixelFunLoop.ino b/examples/animations/NeoPixelFunLoop/NeoPixelFunLoop.ino index 3dea4c0..c8a7788 100644 --- a/examples/animations/NeoPixelFunLoop/NeoPixelFunLoop.ino +++ b/examples/animations/NeoPixelFunLoop/NeoPixelFunLoop.ino @@ -29,9 +29,7 @@ NeoPixelBus strip(PixelCount, PixelPin); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use. // There are other Esp8266 alternative methods that provide more pin options, but also have // other side effects. -//NeoPixelBus strip(PixelCount); -// -// NeoEsp8266Uart800KbpsMethod uses GPI02 instead +// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods // what is stored for state is specific to the need, in this case, the colors and // the pixel to animate; diff --git a/examples/animations/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino b/examples/animations/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino index 17d6b6a..8e88667 100644 --- a/examples/animations/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino +++ b/examples/animations/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino @@ -14,9 +14,7 @@ NeoPixelBus strip(PixelCount, PixelPin); // For Esp8266, the Pin is omitted and it uses GPIO3 due to DMA hardware use. // There are other Esp8266 alternative methods that provide more pin options, but also have // other side effects. -//NeoPixelBus strip(PixelCount); -// -// NeoEsp8266Uart800KbpsMethod uses GPI02 instead +// for details see wiki linked here https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods NeoPixelAnimator animations(PixelCount); // NeoPixel animation management object diff --git a/keywords.txt b/keywords.txt index fc8d795..625cd16 100644 --- a/keywords.txt +++ b/keywords.txt @@ -31,20 +31,34 @@ NeoEsp8266DmaWs2812xMethod KEYWORD1 NeoEsp8266DmaSk6812Method KEYWORD1 NeoEsp8266Dma800KbpsMethod KEYWORD1 NeoEsp8266Dma400KbpsMethod KEYWORD1 -NeoEsp8266UartWs2813Method KEYWORD1 -NeoEsp8266UartWs2812xMethod KEYWORD1 -NeoEsp8266UartWs2812Method KEYWORD1 -NeoEsp8266UartSk6812Method KEYWORD1 -NeoEsp8266UartLc8812Method KEYWORD1 -NeoEsp8266Uart800KbpsMethod KEYWORD1 -NeoEsp8266Uart400KbpsMethod KEYWORD1 -NeoEsp8266AsyncUartWs2813Method KEYWORD1 -NeoEsp8266AsyncUartWs2812xMethod KEYWORD1 -NeoEsp8266AsyncUartWs2812Method KEYWORD1 -NeoEsp8266AsyncUartSk6812Method KEYWORD1 -NeoEsp8266AsyncUartLc8812Method KEYWORD1 -NeoEsp8266AsyncUart800KbpsMethod KEYWORD1 -NeoEsp8266AsyncUart400KbpsMethod KEYWORD1 +NeoEsp8266Uart0Ws2813Method KEYWORD1 +NeoEsp8266Uart0Ws2812xMethod KEYWORD1 +NeoEsp8266Uart0Ws2812Method KEYWORD1 +NeoEsp8266Uart0Sk6812Method KEYWORD1 +NeoEsp8266Uart0Lc8812Method KEYWORD1 +NeoEsp8266Uart0800KbpsMethod KEYWORD1 +NeoEsp8266Uart0400KbpsMethod KEYWORD1 +NeoEsp8266AsyncUart0Ws2813Method KEYWORD1 +NeoEsp8266AsyncUart0Ws2812xMethod KEYWORD1 +NeoEsp8266AsyncUart0Ws2812Method KEYWORD1 +NeoEsp8266AsyncUart0Sk6812Method KEYWORD1 +NeoEsp8266AsyncUart0Lc8812Method KEYWORD1 +NeoEsp8266AsyncUart0800KbpsMethod KEYWORD1 +NeoEsp8266AsyncUart0400KbpsMethod KEYWORD1 +NeoEsp8266Uart1Ws2813Method KEYWORD1 +NeoEsp8266Uart1Ws2812xMethod KEYWORD1 +NeoEsp8266Uart1Ws2812Method KEYWORD1 +NeoEsp8266Uart1Sk6812Method KEYWORD1 +NeoEsp8266Uart1Lc8812Method KEYWORD1 +NeoEsp8266Uart1800KbpsMethod KEYWORD1 +NeoEsp8266Uart1400KbpsMethod KEYWORD1 +NeoEsp8266AsyncUart1Ws2813Method KEYWORD1 +NeoEsp8266AsyncUart1Ws2812xMethod KEYWORD1 +NeoEsp8266AsyncUart1Ws2812Method KEYWORD1 +NeoEsp8266AsyncUart1Sk6812Method KEYWORD1 +NeoEsp8266AsyncUart1Lc8812Method KEYWORD1 +NeoEsp8266AsyncUart1800KbpsMethod KEYWORD1 +NeoEsp8266AsyncUart1400KbpsMethod KEYWORD1 NeoEsp8266BitBangWs2813Method KEYWORD1 NeoEsp8266BitBangWs2812xMethod KEYWORD1 NeoEsp8266BitBangWs2812Method KEYWORD1 diff --git a/library.json b/library.json index d6db833..1e24823 100644 --- a/library.json +++ b/library.json @@ -8,7 +8,7 @@ "type": "git", "url": "https://github.com/Makuna/NeoPixelBus" }, - "version": "2.3.5", + "version": "2.4.0", "frameworks": "arduino", "platforms": "*" } diff --git a/library.properties b/library.properties index 8bcb937..b5c1414 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=NeoPixelBus by Makuna -version=2.3.5 +version=2.4.0 author=Michael C. Miller (makuna@live.com) maintainer=Michael C. Miller (makuna@live.com) sentence=A library that makes controlling NeoPixels (WS2811, WS2812, WS2813 & SK6812) and DotStars (APA102) easy. diff --git a/src/internal/NeoEsp8266UartMethod.cpp b/src/internal/NeoEsp8266UartMethod.cpp index 7bfc3e0..c7d0ff9 100644 --- a/src/internal/NeoEsp8266UartMethod.cpp +++ b/src/internal/NeoEsp8266UartMethod.cpp @@ -29,186 +29,114 @@ License along with NeoPixel. If not, see #include extern "C" { - #include +// #include #include - #include - #include +// #include +// #include } -#define UART1 1 -#define UART1_INV_MASK (0x3f << 19) +volatile NeoEsp8266UartInterruptContext* NeoEsp8266UartInterruptContext::s_uartInteruptContext[] = { nullptr, nullptr }; -// Gets the number of bytes waiting in the TX FIFO of UART1 -static inline uint8_t getUartTxFifoLength() +void NeoEsp8266UartInterruptContext::StartSending(uint8_t uartNum, uint8_t* start, uint8_t* end) { - return (U1S >> USTXC) & 0xff; + // send the pixels asynchronously + _asyncBuff = start; + _asyncBuffEnd = end; + + // enable the transmit interrupt + USIE(uartNum) |= (1 << UIFE); } -// 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) +void NeoEsp8266UartInterruptContext::Attach(uint8_t uartNum) { - U1F = byte; -} - -static const uint8_t* esp8266_uart1_async_buf; -static const uint8_t* esp8266_uart1_async_buf_end; - -NeoEsp8266Uart::NeoEsp8266Uart(uint16_t pixelCount, size_t elementSize) -{ - _sizePixels = pixelCount * elementSize; - _pixels = (uint8_t*)malloc(_sizePixels); - memset(_pixels, 0x00, _sizePixels); -} - -NeoEsp8266Uart::~NeoEsp8266Uart() -{ - // 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(); - } - - free(_pixels); -} - -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(uint16_t pixelCount, size_t elementSize) - : NeoEsp8266Uart(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); + const uint32_t fifoResetFlags = (1 << UCTXRST) | (1 << UCRXRST); + USC0(uartNum) |= fifoResetFlags; + USC0(uartNum) &= ~(fifoResetFlags); - // Set the interrupt handler - ETS_UART_INTR_ATTACH(IntrHandler, NULL); + // attach the ISR if needed + if (s_uartInteruptContext[0] == nullptr && + s_uartInteruptContext[1] == nullptr) + { + ETS_UART_INTR_ATTACH(Isr, s_uartInteruptContext); + } + + // attach the context + s_uartInteruptContext[uartNum] = this; // 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); + USC1(uartNum) = (80 << UCFET); - // 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); + // Disable RX & TX interrupts. It maybe still enabled by uart.c in the SDK + USIE(uartNum) &= ~((1 << UIFF) | (1 << UIFE)); // Clear all pending interrupts in UART1 - WRITE_PERI_REG(UART_INT_CLR(UART1), 0xffff); + USIC(uartNum) = 0xffff; // Reenable interrupts ETS_UART_INTR_ENABLE(); } -void NeoEsp8266AsyncUart::UpdateUart() +void NeoEsp8266UartInterruptContext::Detach(uint8_t uartNum) { - // 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); + // Disable interrupts + ETS_UART_INTR_DISABLE(); - // 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 + if (s_uartInteruptContext[uartNum] != nullptr) { - // 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) + // turn off uart + USC1(uartNum) = 0; + USIC(uartNum) = 0xffff; + USIE(uartNum) = 0; + + s_uartInteruptContext[uartNum] = nullptr; + + if (s_uartInteruptContext[0] == nullptr && + s_uartInteruptContext[1] == nullptr) { - CLEAR_PERI_REG_MASK(UART_INT_ENA(UART1), UART_TXFIFO_EMPTY_INT_ENA); + // detach our ISR + ETS_UART_INTR_ATTACH(NULL, NULL); + + // return so we don't enable interrupts since there is no ISR anymore + return; } - // Clear all interrupts flags (just in case) - WRITE_PERI_REG(UART_INT_CLR(UART1), 0xffff); } - if (READ_PERI_REG(UART_INT_ST(UART0))) + // Reenable interrupts + ETS_UART_INTR_ENABLE(); +} + +void ICACHE_RAM_ATTR NeoEsp8266UartInterruptContext::Isr(void* param) +{ + // make sure this is for us + if (param == s_uartInteruptContext) { - // 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); + // Interrupt handler is shared between UART0 & UART1 + // so we need to test for both + for (uint8_t uartNum = 0; uartNum < 2; uartNum++) + { + if (USIS(uartNum) && s_uartInteruptContext[uartNum] != nullptr) + { + // Fill the FIFO with new data + s_uartInteruptContext[uartNum]->_asyncBuff = FillUartFifo( + uartNum, + s_uartInteruptContext[uartNum]->_asyncBuff, + s_uartInteruptContext[uartNum]->_asyncBuffEnd); + + // Disable TX interrupt when done + if (s_uartInteruptContext[uartNum]->_asyncBuff == s_uartInteruptContext[uartNum]->_asyncBuffEnd) + { + // clear the TX FIFO Empty + USIE(uartNum) &= ~(1 << UIFE); + } + + // Clear all interrupts flags (just in case) + USIC(uartNum) = 0xffff; + } + } } } diff --git a/src/internal/NeoEsp8266UartMethod.h b/src/internal/NeoEsp8266UartMethod.h index 337617a..bc307d0 100644 --- a/src/internal/NeoEsp8266UartMethod.h +++ b/src/internal/NeoEsp8266UartMethod.h @@ -29,27 +29,207 @@ License along with NeoPixel. If not, see #ifdef ARDUINO_ARCH_ESP8266 #include -// NeoEsp8266Uart contains all the low level details that doesn't -// depend on the transmission speed, and therefore, it isn't a template -class NeoEsp8266Uart +// this template method class is used to track the data being sent on the uart +// when using the default serial ISR installed by the core +// used with NeoEsp8266Uart and NeoEsp8266AsyncUart classes +// +class NeoEsp8266UartContext +{ +public: + // Gets the number of bytes waiting in the TX FIFO + static inline uint8_t ICACHE_RAM_ATTR GetTxFifoLength(uint8_t uartNum) + { + return (USS(uartNum) >> USTXC) & 0xff; + } + // Append a byte to the TX FIFO + static inline void ICACHE_RAM_ATTR Enqueue(uint8_t uartNum, uint8_t value) + { + USF(uartNum) = value; + } + + static const volatile uint8_t* ICACHE_RAM_ATTR FillUartFifo(uint8_t uartNum, + const volatile uint8_t* pixels, + const volatile 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 - GetTxFifoLength(uartNum)) / 4; + if (end - pixels > avail) + { + end = pixels + avail; + } + while (pixels < end) + { + uint8_t subpix = *pixels++; + Enqueue(uartNum, _uartData[(subpix >> 6) & 0x3]); + Enqueue(uartNum, _uartData[(subpix >> 4) & 0x3]); + Enqueue(uartNum, _uartData[(subpix >> 2) & 0x3]); + Enqueue(uartNum, _uartData[subpix & 0x3]); + } + return pixels; + } +}; + +// this template method class is used to track the data being sent on the uart +// when using our own UART ISR +// used with NeoEsp8266Uart and NeoEsp8266AsyncUart classes +// +class NeoEsp8266UartInterruptContext : NeoEsp8266UartContext +{ +public: + NeoEsp8266UartInterruptContext() : + _asyncBuff(nullptr), + _asyncBuffEnd(nullptr) + { + } + + bool IsSending() + { + return (_asyncBuff != _asyncBuffEnd); + } + + void StartSending(uint8_t uartNum, uint8_t* start, uint8_t* end); + void Attach(uint8_t uartNum); + void Detach(uint8_t uartNum); + +private: + volatile const uint8_t* _asyncBuff; + volatile const uint8_t* _asyncBuffEnd; + volatile static NeoEsp8266UartInterruptContext* s_uartInteruptContext[2]; + + static void ICACHE_RAM_ATTR Isr(void* param); +}; + +// this template feature class is used a base for all others and contains +// common methods +// +class UartFeatureBase { protected: - NeoEsp8266Uart(uint16_t pixelCount, size_t elementSize); + static void ConfigUart(uint8_t uartNum) + { + // clear all invert bits + USC0(uartNum) &= ~((1 << UCDTRI) | (1 << UCRTSI) | (1 << UCTXI) | (1 << UCDSRI) | (1 << UCCTSI) | (1 << UCRXI)); + // 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 + USC0(uartNum) |= (1 << UCTXI); + } +}; - ~NeoEsp8266Uart(); +// this template feature class is used to define the specifics for uart0 +// used with NeoEsp8266Uart and NeoEsp8266AsyncUart classes +// +class UartFeature0 : UartFeatureBase +{ +public: + static const uint32_t Index = 0; + static void Init(uint32_t baud) + { + // Configure the serial line with 1 start bit (0), 6 data bits and 1 stop bit (1) + Serial.begin(baud, SERIAL_6N1, SERIAL_TX_ONLY); + ConfigUart(Index); + } +}; - void InitializeUart(uint32_t uartBaud); - - void UpdateUart(); - - static const uint8_t* ICACHE_RAM_ATTR FillUartFifo(const uint8_t* pixels, const uint8_t* end); +// this template feature class is used to define the specifics for uart1 +// used with NeoEsp8266Uart and NeoEsp8266AsyncUart classes +// +class UartFeature1 : UartFeatureBase +{ +public: + static const uint32_t Index = 1; + static void Init(uint32_t baud) + { + // Configure the serial line with 1 start bit (0), 6 data bits and 1 stop bit (1) + Serial1.begin(baud, SERIAL_6N1, SERIAL_TX_ONLY); + ConfigUart(Index); + } +}; +// this template method class is used a base for all others and contains +// common properties and methods +// +// used by NeoEsp8266Uart and NeoEsp8266AsyncUart +// +class NeoEsp8266UartBase +{ +protected: size_t _sizePixels; // Size of '_pixels' buffer below uint8_t* _pixels; // Holds LED color values uint32_t _startTime; // Microsecond count when last update started + + NeoEsp8266UartBase(uint16_t pixelCount, size_t elementSize) + { + _sizePixels = pixelCount * elementSize; + _pixels = (uint8_t*)malloc(_sizePixels); + memset(_pixels, 0x00, _sizePixels); + } + + ~NeoEsp8266UartBase() + { + free(_pixels); + } + }; -// NeoEsp8266AsyncUart handles all transmission asynchronously using interrupts +// this template method class is used to glue uart feature and context for +// synchronous uart method +// +// used by NeoEsp8266UartMethodBase +// +template class NeoEsp8266Uart : public NeoEsp8266UartBase +{ +protected: + + NeoEsp8266Uart(uint16_t pixelCount, size_t elementSize) : + NeoEsp8266UartBase(pixelCount, elementSize) + { + } + + ~NeoEsp8266Uart() + { + // Wait until the TX fifo is empty. This way we avoid broken frames + // when destroying & creating a NeoPixelBus to change its length. + while (T_UARTCONTEXT::GetTxFifoLength(T_UARTFEATURE::Index) > 0) + { + yield(); + } + } + + void InitializeUart(uint32_t uartBaud) + { + T_UARTFEATURE::Init(uartBaud); + } + + void 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 = const_cast(T_UARTCONTEXT::FillUartFifo(T_UARTFEATURE::Index, ptr, end)); + } + } +}; + +// this template method class is used to glue uart feature and context for +// asynchronously uart method // // This UART controller uses two buffers that are swapped in every call to // NeoPixelBus.Show(). One buffer contains the data that is being sent @@ -58,19 +238,56 @@ protected: // // Therefore, the result of NeoPixelBus.Pixels() is invalidated after // every call to NeoPixelBus.Show() and must not be cached. -class NeoEsp8266AsyncUart: public NeoEsp8266Uart +// +// used by NeoEsp8266UartMethodBase +// +template class NeoEsp8266AsyncUart : public NeoEsp8266UartBase { protected: - NeoEsp8266AsyncUart(uint16_t pixelCount, size_t elementSize); + NeoEsp8266AsyncUart(uint16_t pixelCount, size_t elementSize) : + NeoEsp8266UartBase(pixelCount, elementSize) + { + _asyncPixels = (uint8_t*)malloc(_sizePixels); + } - ~NeoEsp8266AsyncUart(); + ~NeoEsp8266AsyncUart() + { + // Remember: the UART interrupt can be sending data from _asyncPixels in the background + while (_context.IsSending()) + { + yield(); + } + // detach context, which will disable intr, may disable ISR + _context.Detach(T_UARTFEATURE::Index); + + free(_asyncPixels); + } - void InitializeUart(uint32_t uartBaud); + void ICACHE_RAM_ATTR InitializeUart(uint32_t uartBaud) + { + T_UARTFEATURE::Init(uartBaud); + + // attach the context, which will enable the ISR + _context.Attach(T_UARTFEATURE::Index); + } - void UpdateUart(); + void UpdateUart() + { + // Instruct ESP8266 hardware uart to send the pixels asynchronously + _context.StartSending(T_UARTFEATURE::Index, + _pixels, + _pixels + _sizePixels); + + // 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); + } private: - static void ICACHE_RAM_ATTR IntrHandler(void* param); + T_UARTCONTEXT _context; uint8_t* _asyncPixels; // Holds a copy of LED color values taken when UpdateUart began }; @@ -175,22 +392,45 @@ private: }; }; -typedef NeoEsp8266UartMethodBase NeoEsp8266UartWs2812xMethod; -typedef NeoEsp8266UartMethodBase NeoEsp8266UartSk6812Method; -typedef NeoEsp8266UartMethodBase NeoEsp8266Uart800KbpsMethod; -typedef NeoEsp8266UartMethodBase NeoEsp8266Uart400KbpsMethod; +// uart 0 +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart0Ws2812xMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart0Sk6812Method; +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart0800KbpsMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart0400KbpsMethod; -typedef NeoEsp8266UartWs2812xMethod NeoEsp8266UartWs2813Method; -typedef NeoEsp8266Uart800KbpsMethod NeoEsp8266UartWs2812Method; -typedef NeoEsp8266UartSk6812Method NeoEsp8266UartLc8812Method; +typedef NeoEsp8266Uart0Ws2812xMethod NeoEsp8266Uart0Ws2813Method; +typedef NeoEsp8266Uart0800KbpsMethod NeoEsp8266Uart0Ws2812Method; +typedef NeoEsp8266Uart0Sk6812Method NeoEsp8266Uart0Lc8812Method; -typedef NeoEsp8266UartMethodBase NeoEsp8266AsyncUartWs2812xMethod; -typedef NeoEsp8266UartMethodBase NeoEsp8266AsyncUartSk6812Method; -typedef NeoEsp8266UartMethodBase NeoEsp8266AsyncUart800KbpsMethod; -typedef NeoEsp8266UartMethodBase NeoEsp8266AsyncUart400KbpsMethod; +// uart 1 +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart1Ws2812xMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart1Sk6812Method; +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart1800KbpsMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266Uart1400KbpsMethod; + +typedef NeoEsp8266Uart1Ws2812xMethod NeoEsp8266Uart1Ws2813Method; +typedef NeoEsp8266Uart1800KbpsMethod NeoEsp8266Uart1Ws2812Method; +typedef NeoEsp8266Uart1Sk6812Method NeoEsp8266Uart1Lc8812Method; + +// uart 0 async +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart0Ws2812xMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart0Sk6812Method; +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart0800KbpsMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart0400KbpsMethod; + +typedef NeoEsp8266AsyncUart0Ws2812xMethod NeoEsp8266AsyncUart0Ws2813Method; +typedef NeoEsp8266AsyncUart0800KbpsMethod NeoEsp8266AsyncUart0Ws2812Method; +typedef NeoEsp8266AsyncUart0Sk6812Method NeoEsp8266AsyncUart0Lc8812Method; + +// uart 1 async +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart1Ws2812xMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart1Sk6812Method; +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart1800KbpsMethod; +typedef NeoEsp8266UartMethodBase> NeoEsp8266AsyncUart1400KbpsMethod; + +typedef NeoEsp8266AsyncUart1Ws2812xMethod NeoEsp8266AsyncUart1Ws2813Method; +typedef NeoEsp8266AsyncUart1800KbpsMethod NeoEsp8266AsyncUart1Ws2812Method; +typedef NeoEsp8266AsyncUart1Sk6812Method NeoEsp8266AsyncUart1Lc8812Method; -typedef NeoEsp8266AsyncUartWs2812xMethod NeoEsp8266AsyncUartWs2813Method; -typedef NeoEsp8266AsyncUart800KbpsMethod NeoEsp8266AsyncUartWs2812Method; -typedef NeoEsp8266AsyncUartSk6812Method NeoEsp8266AsyncUartLc8812Method; #endif