From 4fb6f9351acbea235cdbd120f7bb8a457c040348 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 7 May 2022 13:49:03 -0700 Subject: [PATCH] Dmx512 and Ws2821 for ESP8266 (#565) * refactor esp8266 DMA core * refactor NeoUtil * DMX512 * WS2821 --- keywords.txt | 4 + src/NeoPixelBus.h | 3 + src/internal/HtmlColor.h | 6 +- src/internal/HtmlColorNames.cpp | 3 + src/internal/HtmlColorShortNames.cpp | 3 + src/internal/NeoEsp8266DmaMethod.h | 325 ++--------------- src/internal/NeoEsp8266I2sDmx512Method.h | 335 ++++++++++++++++++ ...Method.cpp => NeoEsp8266I2sMethodCore.cpp} | 6 +- src/internal/NeoEsp8266I2sMethodCore.h | 325 +++++++++++++++++ src/internal/NeoUtil.h | 69 ++++ 10 files changed, 779 insertions(+), 300 deletions(-) create mode 100644 src/internal/NeoEsp8266I2sDmx512Method.h rename src/internal/{NeoEsp8266DmaMethod.cpp => NeoEsp8266I2sMethodCore.cpp} (89%) create mode 100644 src/internal/NeoEsp8266I2sMethodCore.h create mode 100644 src/internal/NeoUtil.h diff --git a/keywords.txt b/keywords.txt index 6216d23..77ac15c 100644 --- a/keywords.txt +++ b/keywords.txt @@ -88,6 +88,10 @@ NeoEsp8266DmaInvertedTm1829Method KEYWORD1 NeoEsp8266DmaInvertedApa106Method KEYWORD1 NeoEsp8266DmaInverted800KbpsMethod KEYWORD1 NeoEsp8266DmaInverted400KbpsMethod KEYWORD1 +NeoEsp8266Dmx512Method KEYWORD1 +NeoEsp8266Ws2821Method KEYWORD1 +NeoEsp8266Dmx512InvertedMethod KEYWORD1 +NeoEsp8266Ws2821InvertedMethod KEYWORD1 NeoEsp8266Uart0Ws2813Method KEYWORD1 NeoEsp8266Uart0Ws2812xMethod KEYWORD1 NeoEsp8266Uart0Ws2812Method KEYWORD1 diff --git a/src/NeoPixelBus.h b/src/NeoPixelBus.h index d1a36d9..b54dd2b 100644 --- a/src/NeoPixelBus.h +++ b/src/NeoPixelBus.h @@ -46,6 +46,8 @@ License along with NeoPixel. If not, see // '_state' flags for internal state #define NEO_DIRTY 0x80 // a change was made to pixel data that requires a show +#include "internal/NeoUtil.h" + #include "internal/NeoHueBlend.h" #include "internal/NeoSettings.h" @@ -101,6 +103,7 @@ License along with NeoPixel. If not, see #if defined(ARDUINO_ARCH_ESP8266) #include "internal/NeoEsp8266DmaMethod.h" +#include "internal/NeoEsp8266I2sDmx512Method.h" #include "internal/NeoEsp8266UartMethod.h" #include "internal/NeoEspBitBangMethod.h" diff --git a/src/internal/HtmlColor.h b/src/internal/HtmlColor.h index 25329eb..3ca2ea9 100644 --- a/src/internal/HtmlColor.h +++ b/src/internal/HtmlColor.h @@ -25,7 +25,7 @@ License along with NeoPixel. If not, see -------------------------------------------------------------------------*/ #pragma once -#include + #include "RgbColor.h" #define MAX_HTML_COLOR_NAME_LEN 21 @@ -35,10 +35,6 @@ License along with NeoPixel. If not, see #define pgm_read_ptr(addr) (*reinterpret_cast(addr)) #endif -#ifndef countof -#define countof(array) (sizeof(array)/sizeof(array[0])) -#endif - // ------------------------------------------------------------------------ // HtmlColorPair represents an association between a name and a HTML color code // ------------------------------------------------------------------------ diff --git a/src/internal/HtmlColorNames.cpp b/src/internal/HtmlColorNames.cpp index b1098ea..cc3226c 100644 --- a/src/internal/HtmlColorNames.cpp +++ b/src/internal/HtmlColorNames.cpp @@ -24,6 +24,9 @@ License along with NeoPixel. If not, see . -------------------------------------------------------------------------*/ + +#include +#include "NeoUtil.h" #include "HtmlColor.h" #include "HtmlColorNameStrings.h" diff --git a/src/internal/HtmlColorShortNames.cpp b/src/internal/HtmlColorShortNames.cpp index 3c4e27c..3b4c47f 100644 --- a/src/internal/HtmlColorShortNames.cpp +++ b/src/internal/HtmlColorShortNames.cpp @@ -24,6 +24,9 @@ License along with NeoPixel. If not, see . -------------------------------------------------------------------------*/ + +#include +#include "NeoUtil.h" #include "HtmlColor.h" #include "HtmlColorNameStrings.h" diff --git a/src/internal/NeoEsp8266DmaMethod.h b/src/internal/NeoEsp8266DmaMethod.h index 5865aa6..4faf290 100644 --- a/src/internal/NeoEsp8266DmaMethod.h +++ b/src/internal/NeoEsp8266DmaMethod.h @@ -31,51 +31,12 @@ License along with NeoPixel. If not, see #pragma once #ifdef ARDUINO_ARCH_ESP8266 - -#include "Arduino.h" - -extern "C" -{ -#include "osapi.h" -#include "ets_sys.h" - -#include "i2s_reg.h" - -#ifdef ARDUINO_ESP8266_MAJOR //this define was added in ESP8266 Arduino Core version v3.0.1 - #include "core_esp8266_i2s.h" //for Arduino core >= 3.0.1 -#else - #include "i2s.h" //for Arduino core <= 3.0.0 -#endif - -#include "eagle_soc.h" -#include "esp8266_peri.h" -#include "slc_register.h" - -#include "osapi.h" -#include "ets_sys.h" -#include "user_interface.h" - -#if !defined(__CORE_ESP8266_VERSION_H) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) - void rom_i2c_writeReg_Mask(uint32_t block, uint32_t host_id, uint32_t reg_add, uint32_t Msb, uint32_t Lsb, uint32_t indata); -#endif -} - -struct slc_queue_item -{ - uint32 blocksize : 12; - uint32 datalen : 12; - uint32 unused : 5; - uint32 sub_sof : 1; - uint32 eof : 1; - uint32 owner : 1; - uint8* buf_ptr; - struct slc_queue_item* next_link_ptr; -}; +#include "NeoEsp8266I2sMethodCore.h" class NeoEsp8266DmaSpeedBase { public: - static const uint8_t Level = 0x00; + static const uint8_t IdleLevel = 0; static uint16_t Convert(uint8_t value) { const uint16_t bitpatterns[16] = @@ -93,7 +54,7 @@ public: class NeoEsp8266DmaInvertedSpeedBase { public: - static const uint8_t Level = 0xFF; + static const uint8_t IdleLevel = 1; static uint16_t Convert(uint8_t value) { const uint16_t bitpatterns[16] = @@ -111,8 +72,8 @@ public: class NeoEsp8266DmaSpeed800KbpsBase : public NeoEsp8266DmaSpeedBase { public: - const static uint32_t I2sClockDivisor = 3; - const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t I2sClockDivisor = 3; // 0-63 + const static uint32_t I2sBaseClockDivisor = 16; // 0-63 const static uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800khz speed }; @@ -149,8 +110,8 @@ public: class NeoEsp8266DmaSpeed400Kbps : public NeoEsp8266DmaSpeedBase { public: - const static uint32_t I2sClockDivisor = 6; - const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t I2sClockDivisor = 6; // 0-63 + const static uint32_t I2sBaseClockDivisor = 16; // 0-63 const static uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed const static uint32_t ResetTimeUs = 50; }; @@ -158,8 +119,8 @@ public: class NeoEsp8266DmaSpeedApa106 : public NeoEsp8266DmaSpeedBase { public: - const static uint32_t I2sClockDivisor = 4; - const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t I2sClockDivisor = 4; // 0-63 + const static uint32_t I2sBaseClockDivisor = 16; // 0-63 const static uint32_t ByteSendTimeUs = 17; // us it takes to send a single pixel element const static uint32_t ResetTimeUs = 50; }; @@ -169,8 +130,8 @@ public: class NeoEsp8266DmaInvertedSpeed800KbpsBase : public NeoEsp8266DmaInvertedSpeedBase { public: - const static uint32_t I2sClockDivisor = 3; - const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t I2sClockDivisor = 3; // 0-63 + const static uint32_t I2sBaseClockDivisor = 16; // 0-63 const static uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800khz speed }; @@ -207,8 +168,8 @@ public: class NeoEsp8266DmaInvertedSpeed400Kbps : public NeoEsp8266DmaInvertedSpeedBase { public: - const static uint32_t I2sClockDivisor = 6; - const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t I2sClockDivisor = 6; // 0-63 + const static uint32_t I2sBaseClockDivisor = 16; // 0-63 const static uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed const static uint32_t ResetTimeUs = 50; }; @@ -216,89 +177,15 @@ public: class NeoEsp8266DmaInvertedSpeedApa106 : public NeoEsp8266DmaInvertedSpeedBase { public: - const static uint32_t I2sClockDivisor = 4; - const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t I2sClockDivisor = 4; // 0-63 + const static uint32_t I2sBaseClockDivisor = 16; // 0-63 const static uint32_t ByteSendTimeUs = 17; // us it takes to send a single pixel element const static uint32_t ResetTimeUs = 50; }; -enum NeoDmaState -{ - NeoDmaState_Idle, - NeoDmaState_Pending, - NeoDmaState_Sending, - NeoDmaState_Zeroing, -}; -const uint16_t c_maxDmaBlockSize = 4095; -const uint16_t c_dmaBytesPerPixelBytes = 4; -const uint8_t c_I2sPin = 3; // due to I2S hardware, the pin used is restricted to this - -class NeoEsp8266DmaMethodCore -{ -protected: - static NeoEsp8266DmaMethodCore* s_this; // for the ISR - - volatile NeoDmaState _dmaState; - - slc_queue_item* _i2sBufDesc; // dma block descriptors - uint16_t _i2sBufDescCount; // count of block descriptors in _i2sBufDesc - // This routine is called as soon as the DMA routine has something to tell us. All we - // handle here is the RX_EOF_INT status, which indicate the DMA has sent a buffer whose - // descriptor has the 'EOF' field set to 1. - // in the case of this code, the second to last state descriptor - static void IRAM_ATTR i2s_slc_isr(void) - { - ETS_SLC_INTR_DISABLE(); - - uint32_t slc_intr_status = SLCIS; - - SLCIC = 0xFFFFFFFF; - - if ((slc_intr_status & SLCIRXEOF) && s_this) - { - switch (s_this->_dmaState) - { - case NeoDmaState_Idle: - break; - - case NeoDmaState_Pending: - { - slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA; - - // data block has pending data waiting to send, prepare it - // point last state block to top - (finished_item + 1)->next_link_ptr = s_this->_i2sBufDesc; - - s_this->_dmaState = NeoDmaState_Sending; - } - break; - - case NeoDmaState_Sending: - { - slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA; - - // the data block had actual data sent - // point last state block to first state block thus - // just looping and not sending the data blocks - (finished_item + 1)->next_link_ptr = finished_item; - - s_this->_dmaState = NeoDmaState_Zeroing; - } - break; - - case NeoDmaState_Zeroing: - s_this->_dmaState = NeoDmaState_Idle; - break; - } - } - - ETS_SLC_INTR_ENABLE(); - } -}; - -template class NeoEsp8266DmaMethodBase : NeoEsp8266DmaMethodCore +template class NeoEsp8266DmaMethodBase : NeoEsp8266I2sMethodCore { public: typedef NeoNoSettings SettingsObject; @@ -306,30 +193,25 @@ public: NeoEsp8266DmaMethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) : _sizeData(pixelCount * elementSize + settingsSize) { - uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize; - uint16_t dmaSettingsSize = c_dmaBytesPerPixelBytes * settingsSize; + size_t dmaPixelSize = DmaBytesPerPixelBytes * elementSize; + size_t dmaSettingsSize = DmaBytesPerPixelBytes * settingsSize; - _i2sBufferSize = pixelCount * dmaPixelSize + dmaSettingsSize; + size_t i2sBufferSize = pixelCount * dmaPixelSize + dmaSettingsSize; + + // normally 24 bytes creates the minimum 50us latch per spec, but + // with the new logic, this latch is used to space between mulitple states + // buffer size = (24 * (reset time / 50)) / 6 + size_t i2sZeroesSize = (24L * (T_SPEED::ResetTimeUs / 50L)) / 6L; + + size_t is2BufMaxBlockSize = (c_maxDmaBlockSize / dmaPixelSize) * dmaPixelSize; _data = static_cast(malloc(_sizeData)); // data cleared later in Begin() - _i2sBuffer = static_cast(malloc(_i2sBufferSize)); - // no need to initialize it, it gets overwritten on every send - - // _i2sBuffer[0] = 0b11101000; // debug, 1 bit then 0 bit - - memset(_i2sZeroes, T_SPEED::Level, sizeof(_i2sZeroes)); - - _is2BufMaxBlockSize = (c_maxDmaBlockSize / dmaPixelSize) * dmaPixelSize; - - _i2sBufDescCount = (_i2sBufferSize / _is2BufMaxBlockSize) + 1 + 2; // need two more for state/latch blocks - _i2sBufDesc = (slc_queue_item*)malloc(_i2sBufDescCount * sizeof(slc_queue_item)); - - s_this = this; // store this for the ISR + AllocateI2s(i2sBufferSize, i2sZeroesSize, is2BufMaxBlockSize, T_SPEED::IdleLevel); } - NeoEsp8266DmaMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + NeoEsp8266DmaMethodBase([[maybe_unused]] uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : NeoEsp8266DmaMethodBase(pixelCount, elementSize, settingsSize) { } @@ -351,129 +233,19 @@ public: yield(); } - StopDma(); - - s_this = nullptr; - pinMode(c_I2sPin, INPUT); + FreeI2s(); free(_data); - free(_i2sBuffer); - free(_i2sBufDesc); } bool IsReadyToUpdate() const { - return (_dmaState == NeoDmaState_Idle); + return IsIdle(); } void Initialize() { - StopDma(); - - pinMode(c_I2sPin, FUNCTION_1); // I2S0_DATA - - uint8_t* is2Buffer = _i2sBuffer; - uint32_t is2BufferSize = _i2sBufferSize; - uint16_t indexDesc; - - // prepare main data block decriptors that point into our one static dma buffer - for (indexDesc = 0; indexDesc < (_i2sBufDescCount - 2); indexDesc++) - { - uint32_t blockSize = (is2BufferSize > _is2BufMaxBlockSize) ? _is2BufMaxBlockSize : is2BufferSize; - - _i2sBufDesc[indexDesc].owner = 1; - _i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally - _i2sBufDesc[indexDesc].sub_sof = 0; - _i2sBufDesc[indexDesc].datalen = blockSize; - _i2sBufDesc[indexDesc].blocksize = blockSize; - _i2sBufDesc[indexDesc].buf_ptr = is2Buffer; - _i2sBufDesc[indexDesc].unused = 0; - _i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast(&(_i2sBufDesc[indexDesc + 1])); - - is2Buffer += blockSize; - is2BufferSize -= blockSize; - } - - // prepare the two state/latch descriptors - for (; indexDesc < _i2sBufDescCount; indexDesc++) - { - _i2sBufDesc[indexDesc].owner = 1; - _i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally - _i2sBufDesc[indexDesc].sub_sof = 0; - _i2sBufDesc[indexDesc].datalen = sizeof(_i2sZeroes); - _i2sBufDesc[indexDesc].blocksize = sizeof(_i2sZeroes); - _i2sBufDesc[indexDesc].buf_ptr = _i2sZeroes; - _i2sBufDesc[indexDesc].unused = 0; - _i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast(&(_i2sBufDesc[indexDesc + 1])); - } - - // the first state block will trigger the interrupt - _i2sBufDesc[indexDesc - 2].eof = 1; - // the last state block will loop to the first state block by defualt - _i2sBufDesc[indexDesc - 1].next_link_ptr = reinterpret_cast(&(_i2sBufDesc[indexDesc - 2])); - - // setup the rest of i2s DMA - // - ETS_SLC_INTR_DISABLE(); - - // start off in sending state as that is what it will be all setup to be - // for the interrupt - _dmaState = NeoDmaState_Sending; - - SLCC0 |= SLCRXLR | SLCTXLR; - SLCC0 &= ~(SLCRXLR | SLCTXLR); - SLCIC = 0xFFFFFFFF; - - // Configure DMA - SLCC0 &= ~(SLCMM << SLCM); // clear DMA MODE - SLCC0 |= (1 << SLCM); // set DMA MODE to 1 - SLCRXDC |= SLCBINR | SLCBTNR; // enable INFOR_NO_REPLACE and TOKEN_NO_REPLACE - SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); // disable RX_FILL, RX_EOF_MODE and RX_FILL_MODE - - // Feed DMA the 1st buffer desc addr - // To send data to the I2S subsystem, counter-intuitively we use the RXLINK part, not the TXLINK as you might - // expect. The TXLINK part still needs a valid DMA descriptor, even if it's unused: the DMA engine will throw - // an error at us otherwise. Just feed it any random descriptor. - SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address - // set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid - SLCTXL |= (uint32)&(_i2sBufDesc[_i2sBufDescCount-1]) << SLCTXLA; - SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address - // set RX descriptor address. use first of the data addresses - SLCRXL |= (uint32)&(_i2sBufDesc[0]) << SLCRXLA; - - ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); - SLCIE = SLCIRXEOF; // Enable only for RX EOF interrupt - - ETS_SLC_INTR_ENABLE(); - - //Start transmission - SLCTXL |= SLCTXLS; - SLCRXL |= SLCRXLS; - - I2S_CLK_ENABLE(); - I2SIC = 0x3F; - I2SIE = 0; - - //Reset I2S - I2SC &= ~(I2SRST); - I2SC |= I2SRST; - I2SC &= ~(I2SRST); - - // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) - I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); - I2SFC |= I2SDE; //Enable DMA - // Set RX/TX CHAN_MOD=0 - I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); - - // set the rate - uint32_t i2s_clock_div = T_SPEED::I2sClockDivisor & I2SCDM; - uint8_t i2s_bck_div = T_SPEED::I2sBaseClockDivisor & I2SBDM; - - //!trans master, !bits mod, rece slave mod, rece msb shift, right first, msb right - I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); - I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (i2s_bck_div << I2SBD) | (i2s_clock_div << I2SCD); - - I2SC |= I2STXS; // Start transmission + InitializeI2s(T_SPEED::I2sClockDivisor, T_SPEED::I2sBaseClockDivisor); } void IRAM_ATTR Update(bool) @@ -504,20 +276,12 @@ public: } private: + // due to encoding required for i2s, we need 4 bytes to encode the pulses + static const uint16_t DmaBytesPerPixelBytes = 4; + const size_t _sizeData; // Size of '_data' buffer uint8_t* _data; // Holds LED color values - size_t _i2sBufferSize; // total size of _i2sBuffer - uint8_t* _i2sBuffer; // holds the DMA buffer that is referenced by _i2sBufDesc - - // normally 24 bytes creates the minimum 50us latch per spec, but - // with the new logic, this latch is used to space between mulitple states - // buffer size = (24 * (reset time / 50)) / 6 - uint8_t _i2sZeroes[(24L * (T_SPEED::ResetTimeUs / 50L)) / 6L]; - - uint16_t _is2BufMaxBlockSize; // max size based on size of a pixel of a single block - - void FillBuffers() { uint16_t* pDma = (uint16_t*)_i2sBuffer; @@ -529,27 +293,6 @@ private: } } - void StopDma() - { - ETS_SLC_INTR_DISABLE(); - - // Disable any I2S send or receive - I2SC &= ~(I2STXS | I2SRXS); - - // Reset I2S - I2SC &= ~(I2SRST); - I2SC |= I2SRST; - I2SC &= ~(I2SRST); - - - SLCIC = 0xFFFFFFFF; - SLCIE = 0; - SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address - SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address - - pinMode(c_I2sPin, INPUT); - } - uint32_t getPixelTime() const { return (T_SPEED::ByteSendTimeUs * this->_sizeData); diff --git a/src/internal/NeoEsp8266I2sDmx512Method.h b/src/internal/NeoEsp8266I2sDmx512Method.h new file mode 100644 index 0000000..4f760e8 --- /dev/null +++ b/src/internal/NeoEsp8266I2sDmx512Method.h @@ -0,0 +1,335 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp8266. + +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 +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_ESP8266 +#include "NeoEsp8266I2sMethodCore.h" + + +class NeoEsp8266I2sDmx512SpeedBase +{ +public: + // 4.2 us bit send, 250Kbps + static const uint32_t I2sClockDivisor = 21; // 0-63 + static const uint32_t I2sBaseClockDivisor = 32; // 0-63 + static const uint32_t ByteSendTimeUs = 47; // us it takes to send a single pixel element + static const uint32_t MtbpUs = 100; // min 88 + // DMX requires the first slot to be zero + static const size_t HeaderSize = 1; +}; + +class NeoEsp8266I2sDmx512Speed : public NeoEsp8266I2sDmx512SpeedBase +{ +public: + static const uint8_t MtbpLevel = 0x1; // high + static const uint8_t StartBit = 0b00000000; + static const uint8_t StopBits = 0b00000011; + static const uint32_t BreakMab = 0x00000007; // Break + Mab + + static uint8_t Convert(uint8_t value) + { + // DMX requires LSB order + return NeoUtil::Reverse8Bits( value ); + } +}; + +class NeoEsp8266I2sDmx512InvertedSpeed : public NeoEsp8266I2sDmx512SpeedBase +{ +public: + static const uint8_t MtbpLevel = 0x00; // low + static const uint8_t StartBit = 0b00000001; + static const uint8_t StopBits = 0b00000000; + static const uint32_t BreakMab = 0xfffffff8; // Break + Mab + + static uint8_t Convert(uint8_t value) + { + // DMX requires LSB order + return NeoUtil::Reverse8Bits( ~value ); + } +}; + + +class NeoEsp8266I2sWs2821SpeedBase +{ +public: + // 1.4 us bit send, 750Kbps + static const uint32_t I2sClockDivisor = 7; // 0-63 + static const uint32_t I2sBaseClockDivisor = 32; // 0-63 + static const uint32_t ByteSendTimeUs = 16; // us it takes to send a single pixel element + static const uint32_t MtbpUs = 33; // min 88 + // DMX/WS2821 requires the first slot to be zero + static const size_t HeaderSize = 1; +}; + +class NeoEsp8266I2sWs2821Speed : public NeoEsp8266I2sWs2821SpeedBase +{ +public: + static const uint8_t MtbpLevel = 0x1; // high + static const uint8_t StartBit = 0b00000000; + static const uint8_t StopBits = 0b00000011; + static const uint32_t BreakMab = 0x00000007; // Break + Mab + + static uint8_t Convert(uint8_t value) + { + // DMX requires LSB order + return NeoUtil::Reverse8Bits(value); + } +}; + +class NeoEsp8266I2sWs2821InvertedSpeed : public NeoEsp8266I2sWs2821SpeedBase +{ +public: + static const uint8_t MtbpLevel = 0x00; // low + static const uint8_t StartBit = 0b00000001; + static const uint8_t StopBits = 0b00000000; + static const uint32_t BreakMab = 0xfffffff8; // Break + Mab + + static uint8_t Convert(uint8_t value) + { + // DMX requires LSB order + return NeoUtil::Reverse8Bits(~value); + } +}; + +template class NeoEsp8266I2sDmx512MethodBase : NeoEsp8266I2sMethodCore +{ +public: + typedef NeoNoSettings SettingsObject; + + NeoEsp8266I2sDmx512MethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _sizeData(pixelCount * elementSize + settingsSize + T_SPEED::HeaderSize) + { + size_t dmaPixelBits = I2sBitsPerPixelBytes * elementSize; + size_t dmaSettingsBits = I2sBitsPerPixelBytes * (settingsSize + T_SPEED::HeaderSize); + + // bits + half rounding byte of bits / bits per byte + size_t i2sBufferSize = (pixelCount * dmaPixelBits + dmaSettingsBits + 4) / 8; + + i2sBufferSize = i2sBufferSize + sizeof(T_SPEED::BreakMab); + + // size is rounded up to nearest I2sByteBoundarySize + i2sBufferSize = NeoUtil::RoundUp(i2sBufferSize, I2sByteBoundarySize); + + // 4.2 us per bit + size_t i2sZeroesBitsSize = (T_SPEED::MtbpUs) / 4; + size_t i2sZeroesSize = NeoUtil::RoundUp(i2sZeroesBitsSize, 8) / 8; + + // protocol limits use of full block size to I2sByteBoundarySize + size_t is2BufMaxBlockSize = (c_maxDmaBlockSize / I2sByteBoundarySize) * I2sByteBoundarySize; + + _data = static_cast(malloc(_sizeData)); + // first "slot" cleared due to protocol requiring it to be zero + memset(_data, 0x00, 1); + + AllocateI2s(i2sBufferSize, i2sZeroesSize, is2BufMaxBlockSize, T_SPEED::MtbpLevel); + } + + NeoEsp8266I2sDmx512MethodBase([[maybe_unused]] uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + NeoEsp8266I2sDmx512MethodBase(pixelCount, elementSize, settingsSize) + { + } + + ~NeoEsp8266I2sDmx512MethodBase() + { + uint8_t waits = 1; + while (!IsReadyToUpdate()) + { + waits = 2; + yield(); + } + + // wait for any pending sends to complete + // due to internal i2s caching/send delays, this can more that once the data size + uint32_t time = micros(); + while ((micros() - time) < ((getPixelTime() + T_SPEED::MtbpUs) * waits)) + { + yield(); + } + + FreeI2s(); + + free(_data); + } + + bool IsReadyToUpdate() const + { + return IsIdle(); + } + + void Initialize() + { + InitializeI2s(T_SPEED::I2sClockDivisor, T_SPEED::I2sBaseClockDivisor); + } + + void IRAM_ATTR Update(bool) + { + // wait for not actively sending data + while (!IsReadyToUpdate()) + { + yield(); + } + FillBuffers(); + + // toggle state so the ISR reacts + _dmaState = NeoDmaState_Pending; + } + + uint8_t* getData() const + { + return _data + T_SPEED::HeaderSize; + }; + + size_t getDataSize() const + { + return _sizeData - T_SPEED::HeaderSize; + } + + void applySettings([[maybe_unused]] const SettingsObject& settings) + { + } + +private: + // given 11 sending bits per pixel byte, + static const uint16_t I2sBitsPerPixelBytes = 11; + // i2s sends 4 byte elements, + static const uint16_t I2sByteBoundarySize = 4; + + const size_t _sizeData; // Size of '_data' buffer + uint8_t* _data; // Holds LED color values + + // encodes the data with start and stop bits + // input buffer is bytes + // output stream is uint31_t + static void Encoder(const uint8_t* pSrc, const uint8_t* pSrcEnd, + uint32_t* pOutput, const uint32_t* pOutputEnd) + { + static const uint32_t Mtbp = 0xffffffff * T_SPEED::MtbpLevel; + const uint8_t* pData = pSrc; + + int8_t outputBit = 32; + uint32_t output = 0; + + // DATA stream, one start, two stop + while (pData < pSrcEnd) + { + uint8_t data = T_SPEED::Convert( *(pData++) ); + + if (outputBit > 10) + { + // simple + outputBit -= 1; + output |= T_SPEED::StartBit << outputBit; + + outputBit -= 8; + output |= data << outputBit; + + outputBit -= 2; + output |= T_SPEED::StopBits << outputBit; + } + else + { + // split across an output uint32_t + // handle start bit + if (outputBit < 1) + { + *(pOutput++) = output; + output = 0; + outputBit += 32; + } + outputBit -= 1; + output |= (T_SPEED::StartBit << outputBit); + + // handle data bits + if (outputBit < 8) + { + output |= data >> (8 - outputBit); + + *(pOutput++) = output; + output = 0; + outputBit += 32; + } + outputBit -= 8; + output |= data << outputBit; + + // handle stop bits + if (outputBit < 2) + { + output |= T_SPEED::StopBits >> (2 - outputBit); + + *(pOutput++) = output; + output = 0; + outputBit += 32; + } + outputBit -= 2; + output |= T_SPEED::StopBits << outputBit; + } + } + if (outputBit > 0) + { + // padd last output uint32_t with Mtbp + output |= Mtbp >> (32 - outputBit); + *(pOutput++) = output; + } + // fill the rest of the output with Mtbp + while (pOutput < pOutputEnd) + { + *(pOutput++) = Mtbp; + } + } + + + void FillBuffers() + { + uint32_t* pDma32 = reinterpret_cast(_i2sBuffer); + const uint32_t* pDma32End = reinterpret_cast(_i2sBuffer + _i2sBufferSize); + + // first put Break and MAB at front + // + // BREAK 121.8us @ 4.2us per bit + // MAB 12.6us + *(pDma32++) = T_SPEED::BreakMab; + + Encoder(_data, _data + _sizeData, pDma32, pDma32End); + } + + uint32_t getPixelTime() const + { + return (T_SPEED::ByteSendTimeUs * this->_sizeData); + }; + +}; + + +// normal +typedef NeoEsp8266I2sDmx512MethodBase NeoEsp8266Dmx512Method; +typedef NeoEsp8266I2sDmx512MethodBase NeoEsp8266Ws2821Method; + +// inverted +typedef NeoEsp8266I2sDmx512MethodBase NeoEsp8266Dmx512InvertedMethod; +typedef NeoEsp8266I2sDmx512MethodBase NeoEsp8266Ws2821InvertedMethod; + +#endif diff --git a/src/internal/NeoEsp8266DmaMethod.cpp b/src/internal/NeoEsp8266I2sMethodCore.cpp similarity index 89% rename from src/internal/NeoEsp8266DmaMethod.cpp rename to src/internal/NeoEsp8266I2sMethodCore.cpp index 4bfc7d3..87a2633 100644 --- a/src/internal/NeoEsp8266DmaMethod.cpp +++ b/src/internal/NeoEsp8266I2sMethodCore.cpp @@ -25,12 +25,10 @@ License along with NeoPixel. If not, see -------------------------------------------------------------------------*/ #include -#include "NeoSettings.h" -#include "NeoBusChannel.h" -#include "NeoEsp8266DmaMethod.h" +#include "NeoEsp8266I2sMethodCore.h" #ifdef ARDUINO_ARCH_ESP8266 -NeoEsp8266DmaMethodCore* NeoEsp8266DmaMethodCore::s_this; +NeoEsp8266I2sMethodCore* NeoEsp8266I2sMethodCore::s_this; #endif \ No newline at end of file diff --git a/src/internal/NeoEsp8266I2sMethodCore.h b/src/internal/NeoEsp8266I2sMethodCore.h new file mode 100644 index 0000000..2884f38 --- /dev/null +++ b/src/internal/NeoEsp8266I2sMethodCore.h @@ -0,0 +1,325 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp8266. + +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 +. +-------------------------------------------------------------------------*/ + +#pragma once +#ifdef ARDUINO_ARCH_ESP8266 + +#include "Arduino.h" + +extern "C" +{ +#include "osapi.h" +#include "ets_sys.h" + +#include "i2s_reg.h" + +#ifdef ARDUINO_ESP8266_MAJOR //this define was added in ESP8266 Arduino Core version v3.0.1 +#include "core_esp8266_i2s.h" //for Arduino core >= 3.0.1 +#else +#include "i2s.h" //for Arduino core <= 3.0.0 +#endif + +#include "eagle_soc.h" +#include "esp8266_peri.h" +#include "slc_register.h" + +#include "osapi.h" +#include "ets_sys.h" +#include "user_interface.h" + +#if !defined(__CORE_ESP8266_VERSION_H) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) + void rom_i2c_writeReg_Mask(uint32_t block, uint32_t host_id, uint32_t reg_add, uint32_t Msb, uint32_t Lsb, uint32_t indata); +#endif +} + +struct slc_queue_item +{ + uint32 blocksize : 12; + uint32 datalen : 12; + uint32 unused : 5; + uint32 sub_sof : 1; + uint32 eof : 1; + uint32 owner : 1; + uint8* buf_ptr; + struct slc_queue_item* next_link_ptr; +}; + +enum NeoDmaState +{ + NeoDmaState_Idle, + NeoDmaState_Pending, + NeoDmaState_Sending, + NeoDmaState_Zeroing, +}; + +const uint16_t c_maxDmaBlockSize = 4095; + +const uint8_t c_I2sPin = 3; // due to I2S hardware, the pin used is restricted to this + +class NeoEsp8266I2sMethodCore +{ +protected: + static NeoEsp8266I2sMethodCore* s_this; // for the ISR + + volatile NeoDmaState _dmaState; + + slc_queue_item* _i2sBufDesc; // dma block descriptors + uint16_t _i2sBufDescCount; // count of block descriptors in _i2sBufDesc + + size_t _i2sBufferSize; // total size of _i2sBuffer + uint8_t* _i2sBuffer; // holds the DMA buffer that is referenced by _i2sBufDesc + + size_t _i2sZeroesSize; // total size of _i2sZeroes + uint8_t* _i2sZeroes; + + uint16_t _is2BufMaxBlockSize; // max size based on size of a pixel of a single block + + + // This routine is called as soon as the DMA routine has something to tell us. All we + // handle here is the RX_EOF_INT status, which indicate the DMA has sent a buffer whose + // descriptor has the 'EOF' field set to 1. + // in the case of this code, the second to last state descriptor + static void IRAM_ATTR i2s_slc_isr(void) + { + ETS_SLC_INTR_DISABLE(); + + uint32_t slc_intr_status = SLCIS; + + SLCIC = 0xFFFFFFFF; + + if ((slc_intr_status & SLCIRXEOF) && s_this) + { + switch (s_this->_dmaState) + { + case NeoDmaState_Idle: + break; + + case NeoDmaState_Pending: + { + slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA; + + // data block has pending data waiting to send, prepare it + // point last state block to top + (finished_item + 1)->next_link_ptr = s_this->_i2sBufDesc; + + s_this->_dmaState = NeoDmaState_Sending; + } + break; + + case NeoDmaState_Sending: + { + slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA; + + // the data block had actual data sent + // point last state block to first state block thus + // just looping and not sending the data blocks + (finished_item + 1)->next_link_ptr = finished_item; + + s_this->_dmaState = NeoDmaState_Zeroing; + } + break; + + case NeoDmaState_Zeroing: + s_this->_dmaState = NeoDmaState_Idle; + break; + } + } + + ETS_SLC_INTR_ENABLE(); + } + + NeoEsp8266I2sMethodCore() + { }; + + void AllocateI2s(const size_t i2sBufferSize, + const size_t i2sZeroesSize, + const size_t is2BufMaxBlockSize, + const uint8_t idleLevel) + { + _i2sBufferSize = i2sBufferSize; + _i2sZeroesSize = i2sZeroesSize; + _is2BufMaxBlockSize = is2BufMaxBlockSize; + + _i2sBuffer = static_cast(malloc(_i2sBufferSize)); + // no need to initialize it, it gets overwritten on every send + _i2sZeroes = static_cast(malloc(_i2sZeroesSize)); + memset(_i2sZeroes, idleLevel * 0xff, _i2sZeroesSize); + + _i2sBufDescCount = (_i2sBufferSize / _is2BufMaxBlockSize) + 1 + 2; // need two more for state/latch blocks + _i2sBufDesc = (slc_queue_item*)malloc(_i2sBufDescCount * sizeof(slc_queue_item)); + + s_this = this; // store this for the ISR + } + + void FreeI2s() + { + StopI2s(); + + s_this = nullptr; + pinMode(c_I2sPin, INPUT); + + free(_i2sBuffer); + free(_i2sBufDesc); + free(_i2sZeroes); + } + + bool IsIdle() const + { + return (_dmaState == NeoDmaState_Idle); + } + + void InitializeI2s(const uint32_t i2sClockDivisor, const uint32_t i2sBaseClockDivisor) + { + StopI2s(); + + pinMode(c_I2sPin, FUNCTION_1); // I2S0_DATA + + uint8_t* is2Buffer = _i2sBuffer; + uint32_t is2BufferSize = _i2sBufferSize; + uint16_t indexDesc; + + // prepare main data block decriptors that point into our one static dma buffer + for (indexDesc = 0; indexDesc < (_i2sBufDescCount - 2); indexDesc++) + { + uint32_t blockSize = (is2BufferSize > _is2BufMaxBlockSize) ? _is2BufMaxBlockSize : is2BufferSize; + + _i2sBufDesc[indexDesc].owner = 1; + _i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally + _i2sBufDesc[indexDesc].sub_sof = 0; + _i2sBufDesc[indexDesc].datalen = blockSize; + _i2sBufDesc[indexDesc].blocksize = blockSize; + _i2sBufDesc[indexDesc].buf_ptr = is2Buffer; + _i2sBufDesc[indexDesc].unused = 0; + _i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast(&(_i2sBufDesc[indexDesc + 1])); + + is2Buffer += blockSize; + is2BufferSize -= blockSize; + } + + // prepare the two state/latch descriptors + for (; indexDesc < _i2sBufDescCount; indexDesc++) + { + _i2sBufDesc[indexDesc].owner = 1; + _i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally + _i2sBufDesc[indexDesc].sub_sof = 0; + _i2sBufDesc[indexDesc].datalen = sizeof(_i2sZeroes); + _i2sBufDesc[indexDesc].blocksize = sizeof(_i2sZeroes); + _i2sBufDesc[indexDesc].buf_ptr = _i2sZeroes; + _i2sBufDesc[indexDesc].unused = 0; + _i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast(&(_i2sBufDesc[indexDesc + 1])); + } + + // the first state block will trigger the interrupt + _i2sBufDesc[indexDesc - 2].eof = 1; + // the last state block will loop to the first state block by defualt + _i2sBufDesc[indexDesc - 1].next_link_ptr = reinterpret_cast(&(_i2sBufDesc[indexDesc - 2])); + + // setup the rest of i2s DMA + // + ETS_SLC_INTR_DISABLE(); + + // start off in sending state as that is what it will be all setup to be + // for the interrupt + _dmaState = NeoDmaState_Sending; + + SLCC0 |= SLCRXLR | SLCTXLR; + SLCC0 &= ~(SLCRXLR | SLCTXLR); + SLCIC = 0xFFFFFFFF; + + // Configure DMA + SLCC0 &= ~(SLCMM << SLCM); // clear DMA MODE + SLCC0 |= (1 << SLCM); // set DMA MODE to 1 + SLCRXDC |= SLCBINR | SLCBTNR; // enable INFOR_NO_REPLACE and TOKEN_NO_REPLACE + SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); // disable RX_FILL, RX_EOF_MODE and RX_FILL_MODE + + // Feed DMA the 1st buffer desc addr + // To send data to the I2S subsystem, counter-intuitively we use the RXLINK part, not the TXLINK as you might + // expect. The TXLINK part still needs a valid DMA descriptor, even if it's unused: the DMA engine will throw + // an error at us otherwise. Just feed it any random descriptor. + SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address + // set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid + SLCTXL |= (uint32) & (_i2sBufDesc[_i2sBufDescCount - 1]) << SLCTXLA; + SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address + // set RX descriptor address. use first of the data addresses + SLCRXL |= (uint32) & (_i2sBufDesc[0]) << SLCRXLA; + + ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); + SLCIE = SLCIRXEOF; // Enable only for RX EOF interrupt + + ETS_SLC_INTR_ENABLE(); + + //Start transmission + SLCTXL |= SLCTXLS; + SLCRXL |= SLCRXLS; + + I2S_CLK_ENABLE(); + I2SIC = 0x3F; + I2SIE = 0; + + //Reset I2S + I2SC &= ~(I2SRST); + I2SC |= I2SRST; + I2SC &= ~(I2SRST); + + // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) + I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); + I2SFC |= I2SDE; //Enable DMA + // Set RX/TX CHAN_MOD=0 + I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); + + // set the rate + uint32_t i2s_clock_div = i2sClockDivisor & I2SCDM; + uint8_t i2s_bck_div = i2sBaseClockDivisor & I2SBDM; + + //!trans master, !bits mod, rece slave mod, rece msb shift, right first, msb right + I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); + I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (i2s_bck_div << I2SBD) | (i2s_clock_div << I2SCD); + + I2SC |= I2STXS; // Start transmission + } + + void StopI2s() + { + ETS_SLC_INTR_DISABLE(); + + // Disable any I2S send or receive + I2SC &= ~(I2STXS | I2SRXS); + + // Reset I2S + I2SC &= ~(I2SRST); + I2SC |= I2SRST; + I2SC &= ~(I2SRST); + + SLCIC = 0xFFFFFFFF; + SLCIE = 0; + SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address + SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address + + pinMode(c_I2sPin, INPUT); + } +}; + +#endif // ARDUINO_ARCH_ESP8266 \ No newline at end of file diff --git a/src/internal/NeoUtil.h b/src/internal/NeoUtil.h new file mode 100644 index 0000000..67bb187 --- /dev/null +++ b/src/internal/NeoUtil.h @@ -0,0 +1,69 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions + +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 +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifndef countof +#define countof(array) (sizeof(array)/sizeof(array[0])) +#endif + +class NeoUtil +{ +private: + static constexpr uint8_t Reverse8BitsLookup[16] = { + 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; + +public: + inline static uint8_t Reverse8Bits(uint8_t n) + { + return (Reverse8BitsLookup[n & 0b1111] << 4) | Reverse8BitsLookup[n >> 4]; + } + + inline static size_t RoundUp(size_t numToRound, size_t multiple) + { + return ((numToRound + multiple - 1) / multiple) * multiple; + } + + // alternatives that proved to be slower but left for more periodic testing + /* + // marginally slower than the table + static uint8_t Reverse8Bits(uint8_t b) + { + b = (b & 0b11110000) >> 4 | (b & 0b00001111) << 4; + b = (b & 0b11001100) >> 2 | (b & 0b00110011) << 2; + b = (b & 0b10101010) >> 1 | (b & 0b01010101) << 1; + return b; + } + */ + + /* WAY TO SLOW + static uint8_t Reverse8Bits(uint8_t b) + { + return (b * 0x0202020202ULL & 0x010884422010ULL) % 1023; + } + */ +}; \ No newline at end of file