Dmx512 and Ws2821 for ESP8266 (#565)

* refactor esp8266 DMA core
* refactor NeoUtil
* DMX512
* WS2821
This commit is contained in:
Michael Miller
2022-05-07 13:49:03 -07:00
committed by GitHub
parent 083b8ab9f2
commit 4fb6f9351a
10 changed files with 779 additions and 300 deletions

View File

@@ -88,6 +88,10 @@ NeoEsp8266DmaInvertedTm1829Method KEYWORD1
NeoEsp8266DmaInvertedApa106Method KEYWORD1 NeoEsp8266DmaInvertedApa106Method KEYWORD1
NeoEsp8266DmaInverted800KbpsMethod KEYWORD1 NeoEsp8266DmaInverted800KbpsMethod KEYWORD1
NeoEsp8266DmaInverted400KbpsMethod KEYWORD1 NeoEsp8266DmaInverted400KbpsMethod KEYWORD1
NeoEsp8266Dmx512Method KEYWORD1
NeoEsp8266Ws2821Method KEYWORD1
NeoEsp8266Dmx512InvertedMethod KEYWORD1
NeoEsp8266Ws2821InvertedMethod KEYWORD1
NeoEsp8266Uart0Ws2813Method KEYWORD1 NeoEsp8266Uart0Ws2813Method KEYWORD1
NeoEsp8266Uart0Ws2812xMethod KEYWORD1 NeoEsp8266Uart0Ws2812xMethod KEYWORD1
NeoEsp8266Uart0Ws2812Method KEYWORD1 NeoEsp8266Uart0Ws2812Method KEYWORD1

View File

@@ -46,6 +46,8 @@ License along with NeoPixel. If not, see
// '_state' flags for internal state // '_state' flags for internal state
#define NEO_DIRTY 0x80 // a change was made to pixel data that requires a show #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/NeoHueBlend.h"
#include "internal/NeoSettings.h" #include "internal/NeoSettings.h"
@@ -101,6 +103,7 @@ License along with NeoPixel. If not, see
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
#include "internal/NeoEsp8266DmaMethod.h" #include "internal/NeoEsp8266DmaMethod.h"
#include "internal/NeoEsp8266I2sDmx512Method.h"
#include "internal/NeoEsp8266UartMethod.h" #include "internal/NeoEsp8266UartMethod.h"
#include "internal/NeoEspBitBangMethod.h" #include "internal/NeoEspBitBangMethod.h"

View File

@@ -25,7 +25,7 @@ License along with NeoPixel. If not, see
-------------------------------------------------------------------------*/ -------------------------------------------------------------------------*/
#pragma once #pragma once
#include <Arduino.h>
#include "RgbColor.h" #include "RgbColor.h"
#define MAX_HTML_COLOR_NAME_LEN 21 #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<const void* const *>(addr)) #define pgm_read_ptr(addr) (*reinterpret_cast<const void* const *>(addr))
#endif #endif
#ifndef countof
#define countof(array) (sizeof(array)/sizeof(array[0]))
#endif
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// HtmlColorPair represents an association between a name and a HTML color code // HtmlColorPair represents an association between a name and a HTML color code
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@@ -24,6 +24,9 @@ License along with NeoPixel. If not, see
<http://www.gnu.org/licenses/>. <http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/ -------------------------------------------------------------------------*/
#include <Arduino.h>
#include "NeoUtil.h"
#include "HtmlColor.h" #include "HtmlColor.h"
#include "HtmlColorNameStrings.h" #include "HtmlColorNameStrings.h"

View File

@@ -24,6 +24,9 @@ License along with NeoPixel. If not, see
<http://www.gnu.org/licenses/>. <http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/ -------------------------------------------------------------------------*/
#include <Arduino.h>
#include "NeoUtil.h"
#include "HtmlColor.h" #include "HtmlColor.h"
#include "HtmlColorNameStrings.h" #include "HtmlColorNameStrings.h"

View File

@@ -31,51 +31,12 @@ License along with NeoPixel. If not, see
#pragma once #pragma once
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
#include "NeoEsp8266I2sMethodCore.h"
#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;
};
class NeoEsp8266DmaSpeedBase class NeoEsp8266DmaSpeedBase
{ {
public: public:
static const uint8_t Level = 0x00; static const uint8_t IdleLevel = 0;
static uint16_t Convert(uint8_t value) static uint16_t Convert(uint8_t value)
{ {
const uint16_t bitpatterns[16] = const uint16_t bitpatterns[16] =
@@ -93,7 +54,7 @@ public:
class NeoEsp8266DmaInvertedSpeedBase class NeoEsp8266DmaInvertedSpeedBase
{ {
public: public:
static const uint8_t Level = 0xFF; static const uint8_t IdleLevel = 1;
static uint16_t Convert(uint8_t value) static uint16_t Convert(uint8_t value)
{ {
const uint16_t bitpatterns[16] = const uint16_t bitpatterns[16] =
@@ -111,8 +72,8 @@ public:
class NeoEsp8266DmaSpeed800KbpsBase : public NeoEsp8266DmaSpeedBase class NeoEsp8266DmaSpeed800KbpsBase : public NeoEsp8266DmaSpeedBase
{ {
public: public:
const static uint32_t I2sClockDivisor = 3; const static uint32_t I2sClockDivisor = 3; // 0-63
const static uint32_t I2sBaseClockDivisor = 16; 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 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 class NeoEsp8266DmaSpeed400Kbps : public NeoEsp8266DmaSpeedBase
{ {
public: public:
const static uint32_t I2sClockDivisor = 6; const static uint32_t I2sClockDivisor = 6; // 0-63
const static uint32_t I2sBaseClockDivisor = 16; 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 ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed
const static uint32_t ResetTimeUs = 50; const static uint32_t ResetTimeUs = 50;
}; };
@@ -158,8 +119,8 @@ public:
class NeoEsp8266DmaSpeedApa106 : public NeoEsp8266DmaSpeedBase class NeoEsp8266DmaSpeedApa106 : public NeoEsp8266DmaSpeedBase
{ {
public: public:
const static uint32_t I2sClockDivisor = 4; const static uint32_t I2sClockDivisor = 4; // 0-63
const static uint32_t I2sBaseClockDivisor = 16; 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 ByteSendTimeUs = 17; // us it takes to send a single pixel element
const static uint32_t ResetTimeUs = 50; const static uint32_t ResetTimeUs = 50;
}; };
@@ -169,8 +130,8 @@ public:
class NeoEsp8266DmaInvertedSpeed800KbpsBase : public NeoEsp8266DmaInvertedSpeedBase class NeoEsp8266DmaInvertedSpeed800KbpsBase : public NeoEsp8266DmaInvertedSpeedBase
{ {
public: public:
const static uint32_t I2sClockDivisor = 3; const static uint32_t I2sClockDivisor = 3; // 0-63
const static uint32_t I2sBaseClockDivisor = 16; 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 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 class NeoEsp8266DmaInvertedSpeed400Kbps : public NeoEsp8266DmaInvertedSpeedBase
{ {
public: public:
const static uint32_t I2sClockDivisor = 6; const static uint32_t I2sClockDivisor = 6; // 0-63
const static uint32_t I2sBaseClockDivisor = 16; 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 ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed
const static uint32_t ResetTimeUs = 50; const static uint32_t ResetTimeUs = 50;
}; };
@@ -216,89 +177,15 @@ public:
class NeoEsp8266DmaInvertedSpeedApa106 : public NeoEsp8266DmaInvertedSpeedBase class NeoEsp8266DmaInvertedSpeedApa106 : public NeoEsp8266DmaInvertedSpeedBase
{ {
public: public:
const static uint32_t I2sClockDivisor = 4; const static uint32_t I2sClockDivisor = 4; // 0-63
const static uint32_t I2sBaseClockDivisor = 16; 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 ByteSendTimeUs = 17; // us it takes to send a single pixel element
const static uint32_t ResetTimeUs = 50; 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 template<typename T_SPEED> class NeoEsp8266DmaMethodBase : NeoEsp8266I2sMethodCore
// 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<typename T_SPEED> class NeoEsp8266DmaMethodBase : NeoEsp8266DmaMethodCore
{ {
public: public:
typedef NeoNoSettings SettingsObject; typedef NeoNoSettings SettingsObject;
@@ -306,30 +193,25 @@ public:
NeoEsp8266DmaMethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) : NeoEsp8266DmaMethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
_sizeData(pixelCount * elementSize + settingsSize) _sizeData(pixelCount * elementSize + settingsSize)
{ {
uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize; size_t dmaPixelSize = DmaBytesPerPixelBytes * elementSize;
uint16_t dmaSettingsSize = c_dmaBytesPerPixelBytes * settingsSize; 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<uint8_t*>(malloc(_sizeData)); _data = static_cast<uint8_t*>(malloc(_sizeData));
// data cleared later in Begin() // data cleared later in Begin()
_i2sBuffer = static_cast<uint8_t*>(malloc(_i2sBufferSize)); AllocateI2s(i2sBufferSize, i2sZeroesSize, is2BufMaxBlockSize, T_SPEED::IdleLevel);
// 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
} }
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) NeoEsp8266DmaMethodBase(pixelCount, elementSize, settingsSize)
{ {
} }
@@ -351,129 +233,19 @@ public:
yield(); yield();
} }
StopDma(); FreeI2s();
s_this = nullptr;
pinMode(c_I2sPin, INPUT);
free(_data); free(_data);
free(_i2sBuffer);
free(_i2sBufDesc);
} }
bool IsReadyToUpdate() const bool IsReadyToUpdate() const
{ {
return (_dmaState == NeoDmaState_Idle); return IsIdle();
} }
void Initialize() void Initialize()
{ {
StopDma(); InitializeI2s(T_SPEED::I2sClockDivisor, T_SPEED::I2sBaseClockDivisor);
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<struct slc_queue_item*>(&(_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<struct slc_queue_item*>(&(_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<struct slc_queue_item*>(&(_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
} }
void IRAM_ATTR Update(bool) void IRAM_ATTR Update(bool)
@@ -504,20 +276,12 @@ public:
} }
private: 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 const size_t _sizeData; // Size of '_data' buffer
uint8_t* _data; // Holds LED color values 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() void FillBuffers()
{ {
uint16_t* pDma = (uint16_t*)_i2sBuffer; 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 uint32_t getPixelTime() const
{ {
return (T_SPEED::ByteSendTimeUs * this->_sizeData); return (T_SPEED::ByteSendTimeUs * this->_sizeData);

View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#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<typename T_SPEED> 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<uint8_t*>(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<uint32_t*>(_i2sBuffer);
const uint32_t* pDma32End = reinterpret_cast<uint32_t*>(_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<NeoEsp8266I2sDmx512Speed> NeoEsp8266Dmx512Method;
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sWs2821Speed> NeoEsp8266Ws2821Method;
// inverted
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sDmx512InvertedSpeed> NeoEsp8266Dmx512InvertedMethod;
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sWs2821InvertedSpeed> NeoEsp8266Ws2821InvertedMethod;
#endif

View File

@@ -25,12 +25,10 @@ License along with NeoPixel. If not, see
-------------------------------------------------------------------------*/ -------------------------------------------------------------------------*/
#include <Arduino.h> #include <Arduino.h>
#include "NeoSettings.h" #include "NeoEsp8266I2sMethodCore.h"
#include "NeoBusChannel.h"
#include "NeoEsp8266DmaMethod.h"
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
NeoEsp8266DmaMethodCore* NeoEsp8266DmaMethodCore::s_this; NeoEsp8266I2sMethodCore* NeoEsp8266I2sMethodCore::s_this;
#endif #endif

View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#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<uint8_t*>(malloc(_i2sBufferSize));
// no need to initialize it, it gets overwritten on every send
_i2sZeroes = static_cast<uint8_t*>(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<struct slc_queue_item*>(&(_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<struct slc_queue_item*>(&(_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<struct slc_queue_item*>(&(_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

69
src/internal/NeoUtil.h Normal file
View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#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;
}
*/
};