From 68e48b79574e90397a9b77e32d93b7a71631a605 Mon Sep 17 00:00:00 2001 From: Unai Uribarri Date: Sat, 4 Jun 2016 11:24:28 +0200 Subject: [PATCH] Add new method NeoEsp8266AsyncUart*KbpsMethod Add two new methods NeoEsp8266AsyncUart800KbpsMethod & NeoEsp8266AsyncUart400KbpsMethod, which uses UART1 interrupts to output all pixels asynchronously. Also, refactor old UART code to share as much code as possible. --- src/internal/NeoEsp8266UartMethod.cpp | 216 ++++++++++++++++++++++++++ src/internal/NeoEsp8266UartMethod.h | 123 +++++++++------ src/internal/NeoPixelEsp8266.c | 25 --- 3 files changed, 289 insertions(+), 75 deletions(-) create mode 100644 src/internal/NeoEsp8266UartMethod.cpp diff --git a/src/internal/NeoEsp8266UartMethod.cpp b/src/internal/NeoEsp8266UartMethod.cpp new file mode 100644 index 00000000..96e62eba --- /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 3c577dfc..3c17fd97 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); - - CLEAR_PERI_REG_MASK(UART_CONF0(UART1), UART1_INV_MASK); - SET_PERI_REG_MASK(UART_CONF0(UART1), (BIT(22))); + this->InitializeUart(T_SPEED::UartBaud); - _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 @@ template class NeoEsp8266UartMethodBase // 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 0ba4e827..73e03669 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;