FastLED 3.9.15
Loading...
Searching...
No Matches
/home/runner/work/FastLED/FastLED/src/fl/math/transposition.h

Transpose Wave8Byte waveforms into PARLIO bit-parallel format (ISR-safe)

Transpose Wave8Byte waveforms into PARLIO bit-parallel format (ISR-safe) This function transposes Wave8Byte waveform data (8 bytes per lane representing 64 pulses) into PARLIO's bit-packed parallel format for multi-lane transmission.

Input Format: Wave8Byte structures

Output Format: PARLIO bit-parallel format

Supported Data Widths: 1, 2, 4, 8, 16 lanes

Parameters
laneWaveformsArray of Wave8Byte data (data_width lanes × 8 bytes each)
data_widthNumber of parallel lanes (1, 2, 4, 8, or 16)
outputBufferOutput buffer for bit-packed data
Returns
Number of bytes written to output buffer (8 for ≤8 lanes, 16 for 16 lanes)
Note
ISR-safe: inline function, no allocations
Output size: 8 bytes (data_width ≤ 8) or 16 bytes (data_width = 16)
Returns 0 if data_width is invalid (not 1, 2, 4, 8, or 16)
uint8_t laneWaveforms[16 * 8]; // 16 lanes × 8 bytes
uint8_t output[16]; // Max output size
size_t written = transpose_wave8byte_parlio(laneWaveforms, 8, output);
#pragma once
#include "fl/stl/int.h"
#include "fl/stl/span.h"
#include "fl/stl/cstring.h"
namespace fl {
// ============================================================================
// Core 8x1 Bit Transpose Functions
// ============================================================================
// Note: These transpose functions are used across multiple platforms,
// so they are defined for all targets
typedef union {
fl::u8 raw;
struct {
fl::u32 a0:1;
fl::u32 a1:1;
fl::u32 a2:1;
fl::u32 a3:1;
fl::u32 a4:1;
fl::u32 a5:1;
fl::u32 a6:1;
fl::u32 a7:1;
};
} just8bits;
typedef struct {
fl::u32 a0:1;
fl::u32 a1:1;
fl::u32 a2:1;
fl::u32 a3:1;
fl::u32 a4:1;
fl::u32 a5:1;
fl::u32 a6:1;
fl::u32 a7:1;
fl::u32 b0:1;
fl::u32 b1:1;
fl::u32 b2:1;
fl::u32 b3:1;
fl::u32 b4:1;
fl::u32 b5:1;
fl::u32 b6:1;
fl::u32 b7:1;
fl::u32 c0:1;
fl::u32 c1:1;
fl::u32 c2:1;
fl::u32 c3:1;
fl::u32 c4:1;
fl::u32 c5:1;
fl::u32 c6:1;
fl::u32 c7:1;
fl::u32 d0:1;
fl::u32 d1:1;
fl::u32 d2:1;
fl::u32 d3:1;
fl::u32 d4:1;
fl::u32 d5:1;
fl::u32 d6:1;
fl::u32 d7:1;
} sub4;
typedef union {
fl::u32 word[2];
fl::u8 bytes[8];
struct {
sub4 a;
sub4 b;
};
} bitswap_type;
void transpose8x1_noinline(unsigned char *A, unsigned char *B) FL_NOEXCEPT;
FASTLED_FORCE_INLINE void transpose8x1(unsigned char *A, unsigned char *B) FL_NOEXCEPT {
fl::u32 x, y, t;
// Load the array and pack it into x and y.
y = *(fl::u32*)(A);
x = *(fl::u32*)(A+4);
// pre-transform x
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
// pre-transform y
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
// final transform
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
x = t;
*((u32*)B) = y;
*((u32*)(B+4)) = x;
}
FASTLED_FORCE_INLINE void transpose8x1_MSB(unsigned char *A, unsigned char *B) FL_NOEXCEPT {
fl::u32 x, y, t;
// Load the array and pack it into x and y.
y = *(fl::u32*)(A);
x = *(fl::u32*)(A+4);
// pre-transform x
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
// pre-transform y
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
// final transform
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
x = t;
B[7] = y; y >>= 8;
B[6] = y; y >>= 8;
B[5] = y; y >>= 8;
B[4] = y;
B[3] = x; x >>= 8;
B[2] = x; x >>= 8;
B[1] = x; x >>= 8;
B[0] = x;
}
template<int m, int n>
FASTLED_FORCE_INLINE void transpose8(unsigned char *A, unsigned char *B) FL_NOEXCEPT {
fl::u32 x, y, t;
// Load the array and pack it into x and y.
if(m == 1) {
y = *(fl::u32*)(A);
x = *(fl::u32*)(A+4);
} else {
x = (fl::u32(A[0])<<24) | (fl::u32(A[m])<<16) | (fl::u32(A[2*m])<<8) | A[3*m];
y = (fl::u32(A[4*m])<<24) | (fl::u32(A[5*m])<<16) | (fl::u32(A[6*m])<<8) | A[7*m];
}
// pre-transform x
t = (x ^ (x >> 7)) & 0x00AA00AA; x = x ^ t ^ (t << 7);
t = (x ^ (x >>14)) & 0x0000CCCC; x = x ^ t ^ (t <<14);
// pre-transform y
t = (y ^ (y >> 7)) & 0x00AA00AA; y = y ^ t ^ (t << 7);
t = (y ^ (y >>14)) & 0x0000CCCC; y = y ^ t ^ (t <<14);
// final transform
t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F);
y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F);
x = t;
B[7*n] = y; y >>= 8;
B[6*n] = y; y >>= 8;
B[5*n] = y; y >>= 8;
B[4*n] = y;
B[3*n] = x; x >>= 8;
B[2*n] = x; x >>= 8;
B[n] = x; x >>= 8;
B[0] = x;
}
// ============================================================================
// Low-Level ISR-Safe Transposition Primitives
// ============================================================================
const u8* lane0_byte,
const u8* lane1_byte,
u8* output,
size_t num_bytes
const u8* const lanes[4],
u8* output,
size_t num_bytes
const u8* const lanes[8],
u8* output,
size_t num_bytes
const u8* const lanes[16],
u8* output,
size_t num_bytes
template<typename TSource>
const TSource* const lanes[],
size_t num_lanes,
u8* output,
size_t num_items
// Implementation of inline ISR-safe primitives
const u8* lane0_byte,
const u8* lane1_byte,
u8* output,
size_t num_bytes
for (size_t byte_idx = 0; byte_idx < num_bytes; byte_idx++) {
u8 a = lane0_byte[byte_idx];
u8 b = lane1_byte[byte_idx];
// dest[0] contains bit pairs for positions 7,6,5,4 (MSB first)
output[byte_idx * 2 + 0] =
((a >> 7) & 0x01) << 0 | ((b >> 7) & 0x01) << 1 |
((a >> 6) & 0x01) << 2 | ((b >> 6) & 0x01) << 3 |
((a >> 5) & 0x01) << 4 | ((b >> 5) & 0x01) << 5 |
((a >> 4) & 0x01) << 6 | ((b >> 4) & 0x01) << 7;
// dest[1] contains bit pairs for positions 3,2,1,0 (LSB)
output[byte_idx * 2 + 1] =
((a >> 3) & 0x01) << 0 | ((b >> 3) & 0x01) << 1 |
((a >> 2) & 0x01) << 2 | ((b >> 2) & 0x01) << 3 |
((a >> 1) & 0x01) << 4 | ((b >> 1) & 0x01) << 5 |
((a >> 0) & 0x01) << 6 | ((b >> 0) & 0x01) << 7;
}
}
const u8* const lanes[4],
u8* output,
size_t num_bytes
for (size_t byte_idx = 0; byte_idx < num_bytes; byte_idx++) {
u8 a = lanes[0][byte_idx];
u8 b = lanes[1][byte_idx];
u8 c = lanes[2][byte_idx];
u8 d = lanes[3][byte_idx];
u8* dest = &output[byte_idx * 4];
dest[0] = ((a >> 7) & 0x01) << 0 | ((b >> 7) & 0x01) << 1 | ((c >> 7) & 0x01) << 2 | ((d >> 7) & 0x01) << 3 |
((a >> 6) & 0x01) << 4 | ((b >> 6) & 0x01) << 5 | ((c >> 6) & 0x01) << 6 | ((d >> 6) & 0x01) << 7;
dest[1] = ((a >> 5) & 0x01) << 0 | ((b >> 5) & 0x01) << 1 | ((c >> 5) & 0x01) << 2 | ((d >> 5) & 0x01) << 3 |
((a >> 4) & 0x01) << 4 | ((b >> 4) & 0x01) << 5 | ((c >> 4) & 0x01) << 6 | ((d >> 4) & 0x01) << 7;
dest[2] = ((a >> 3) & 0x01) << 0 | ((b >> 3) & 0x01) << 1 | ((c >> 3) & 0x01) << 2 | ((d >> 3) & 0x01) << 3 |
((a >> 2) & 0x01) << 4 | ((b >> 2) & 0x01) << 5 | ((c >> 2) & 0x01) << 6 | ((d >> 2) & 0x01) << 7;
dest[3] = ((a >> 1) & 0x01) << 0 | ((b >> 1) & 0x01) << 1 | ((c >> 1) & 0x01) << 2 | ((d >> 1) & 0x01) << 3 |
((a >> 0) & 0x01) << 4 | ((b >> 0) & 0x01) << 5 | ((c >> 0) & 0x01) << 6 | ((d >> 0) & 0x01) << 7;
}
}
const u8* const lanes[8],
u8* output,
size_t num_bytes
for (size_t byte_idx = 0; byte_idx < num_bytes; byte_idx++) {
// Pack 8 bytes into a single 64-bit register
// This reduces register pressure and enables parallel bit extraction
u64 packed =
((u64)lanes[0][byte_idx] << 0) |
((u64)lanes[1][byte_idx] << 8) |
((u64)lanes[2][byte_idx] << 16) |
((u64)lanes[3][byte_idx] << 24) |
((u64)lanes[4][byte_idx] << 32) |
((u64)lanes[5][byte_idx] << 40) |
((u64)lanes[6][byte_idx] << 48) |
((u64)lanes[7][byte_idx] << 56);
u8* dest = &output[byte_idx * 8];
// Extract bits in parallel (compiler can optimize independent shifts)
for (int bit = 7; bit >= 0; bit--) {
dest[7 - bit] =
((packed >> (bit + 0)) & 0x01) << 0 |
((packed >> (bit + 8)) & 0x01) << 1 |
((packed >> (bit + 16)) & 0x01) << 2 |
((packed >> (bit + 24)) & 0x01) << 3 |
((packed >> (bit + 32)) & 0x01) << 4 |
((packed >> (bit + 40)) & 0x01) << 5 |
((packed >> (bit + 48)) & 0x01) << 6 |
((packed >> (bit + 56)) & 0x01) << 7;
}
}
}
const u8* const lanes[16],
u8* output,
size_t num_bytes
for (size_t byte_idx = 0; byte_idx < num_bytes; byte_idx++) {
// Pack lanes 0-7 into first 64-bit register
u64 packed_lo =
((u64)lanes[0][byte_idx] << 0) |
((u64)lanes[1][byte_idx] << 8) |
((u64)lanes[2][byte_idx] << 16) |
((u64)lanes[3][byte_idx] << 24) |
((u64)lanes[4][byte_idx] << 32) |
((u64)lanes[5][byte_idx] << 40) |
((u64)lanes[6][byte_idx] << 48) |
((u64)lanes[7][byte_idx] << 56);
// Pack lanes 8-15 into second 64-bit register
u64 packed_hi =
((u64)lanes[8][byte_idx] << 0) |
((u64)lanes[9][byte_idx] << 8) |
((u64)lanes[10][byte_idx] << 16) |
((u64)lanes[11][byte_idx] << 24) |
((u64)lanes[12][byte_idx] << 32) |
((u64)lanes[13][byte_idx] << 40) |
((u64)lanes[14][byte_idx] << 48) |
((u64)lanes[15][byte_idx] << 56);
u8* dest = &output[byte_idx * 16];
// Extract bits in parallel from both packed registers
for (int bit = 7; bit >= 0; bit--) {
dest[7 - bit] =
((packed_lo >> (bit + 0)) & 0x01) << 0 |
((packed_lo >> (bit + 8)) & 0x01) << 1 |
((packed_lo >> (bit + 16)) & 0x01) << 2 |
((packed_lo >> (bit + 24)) & 0x01) << 3 |
((packed_lo >> (bit + 32)) & 0x01) << 4 |
((packed_lo >> (bit + 40)) & 0x01) << 5 |
((packed_lo >> (bit + 48)) & 0x01) << 6 |
((packed_lo >> (bit + 56)) & 0x01) << 7;
dest[15 - bit] =
((packed_hi >> (bit + 0)) & 0x01) << 0 |
((packed_hi >> (bit + 8)) & 0x01) << 1 |
((packed_hi >> (bit + 16)) & 0x01) << 2 |
((packed_hi >> (bit + 24)) & 0x01) << 3 |
((packed_hi >> (bit + 32)) & 0x01) << 4 |
((packed_hi >> (bit + 40)) & 0x01) << 5 |
((packed_hi >> (bit + 48)) & 0x01) << 6 |
((packed_hi >> (bit + 56)) & 0x01) << 7;
}
}
}
template<typename TSource>
const TSource* const lanes[],
size_t num_lanes,
u8* output,
size_t num_items
constexpr size_t bits_per_item = sizeof(TSource) * 8;
for (size_t item_idx = 0; item_idx < num_items; item_idx++) {
u8* dest = &output[item_idx * bits_per_item];
// Process each bit position in the source data (MSB to LSB)
for (size_t bit_pos = 0; bit_pos < bits_per_item; bit_pos++) {
size_t src_bit = (bits_per_item - 1) - bit_pos;
u8 output_byte = 0;
// Extract bit from each lane (up to 8 lanes per output byte)
for (size_t lane = 0; lane < num_lanes && lane < 8; lane++) {
TSource src_value = lanes[lane][item_idx];
u8 bit = (src_value >> src_bit) & 0x01;
output_byte |= (bit << (7 - lane));
}
dest[bit_pos] = output_byte;
}
}
}
// ============================================================================
// SPI Multi-Lane Transposer
// ============================================================================
class SPITransposer {
public:
struct LaneData {
};
static bool transpose2(const fl::optional<LaneData>& lane0,
const fl::optional<LaneData>& lane1,
fl::span<u8> output,
const char** error = nullptr) FL_NOEXCEPT;
static bool transpose4(const fl::optional<LaneData>& lane0,
const fl::optional<LaneData>& lane1,
const fl::optional<LaneData>& lane2,
const fl::optional<LaneData>& lane3,
fl::span<u8> output,
const char** error = nullptr) FL_NOEXCEPT;
static bool transpose8(const fl::optional<LaneData> lanes[8],
fl::span<u8> output,
const char** error = nullptr) FL_NOEXCEPT;
static bool transpose16(const fl::optional<LaneData> lanes[16],
fl::span<u8> output,
const char** error = nullptr) FL_NOEXCEPT;
private:
static u8 getLaneByte(const LaneData& lane, size_t byte_idx, size_t max_size) FL_NOEXCEPT;
};
// ============================================================================
// Parallel Strip Transposer (RP2040/RP2350 PIO)
// ============================================================================
const u8* const input[8],
u8* output,
u16 num_leds,
u8 bytes_per_led
// Process each LED
for (u16 led = 0; led < num_leds; led++) {
u8 temp_input[8];
// Process each byte in the LED
for (u8 byte_idx = 0; byte_idx < bytes_per_led; byte_idx++) {
// Collect one byte from each strip for this byte position
for (int strip = 0; strip < 8; strip++) {
temp_input[strip] = input[strip][led * bytes_per_led + byte_idx];
}
// Transpose 8 bytes → 8 bytes (1 bit from each strip per output byte)
transpose8x1_MSB(temp_input, output);
// Advance output pointer by 8 bytes
output += 8;
}
}
}
const u8* const input[4],
u8* output,
u16 num_leds,
u8 bytes_per_led
// Process each LED
for (u16 led = 0; led < num_leds; led++) {
// Process each byte in the LED
for (u8 byte_idx = 0; byte_idx < bytes_per_led; byte_idx++) {
// Collect one byte from each strip for this byte position
u8 strip_bytes[4];
for (int strip = 0; strip < 4; strip++) {
strip_bytes[strip] = input[strip][led * bytes_per_led + byte_idx];
}
// Transpose: extract each bit position from all 4 strips
for (int bit = 7; bit >= 0; bit--) {
u8 output_byte = 0;
// Pack bits from all 4 strips into lower 4 bits
for (int strip = 0; strip < 4; strip++) {
output_byte |= ((strip_bytes[strip] >> bit) & 1) << strip;
}
*output++ = output_byte;
}
}
}
}
const u8* const input[2],
u8* output,
u16 num_leds,
u8 bytes_per_led
// Process each LED
for (u16 led = 0; led < num_leds; led++) {
// Process each byte in the LED
for (u8 byte_idx = 0; byte_idx < bytes_per_led; byte_idx++) {
// Collect one byte from each strip for this byte position
u8 strip_bytes[2];
strip_bytes[0] = input[0][led * bytes_per_led + byte_idx];
strip_bytes[1] = input[1][led * bytes_per_led + byte_idx];
// Transpose: extract each bit position from both strips
for (int bit = 7; bit >= 0; bit--) {
u8 output_byte =
((strip_bytes[0] >> bit) & 1) |
(((strip_bytes[1] >> bit) & 1) << 1);
*output++ = output_byte;
}
}
}
}
return num_leds * bytes_per_led * 8;
}
inline bool transpose_strips(
u8 num_strips,
const u8* const* input,
u8* output,
u16 num_leds,
u8 bytes_per_led
switch (num_strips) {
case 8:
transpose_8strips(input, output, num_leds, bytes_per_led);
return true;
case 4:
transpose_4strips(input, output, num_leds, bytes_per_led);
return true;
case 2:
transpose_2strips(input, output, num_leds, bytes_per_led);
return true;
default:
return false; // Invalid strip count
}
}
// ============================================================================
// PARLIO Wave8 Transposer (ESP32-S3 Parallel I/O)
// ============================================================================
template<size_t DATA_WIDTH>
const u8* FL_RESTRICT_PARAM laneWaveforms,
u8* FL_RESTRICT_PARAM outputBuffer
constexpr size_t bytes_per_lane = 8; // sizeof(Wave8Byte)
constexpr size_t pulsesPerByte = 64; // 8 bits × 8 pulses per bit
size_t outputIdx = 0;
// Note: Using regular if statements (C++11 compatible)
// Compiler optimizes away dead branches for constant template parameters
if (DATA_WIDTH == 8) {
// Special optimized case for 8 lanes with bit packing
// Optimized: Hoist packing outside inner loop to reduce redundant operations
for (size_t bit_pos = 0; bit_pos < 8; bit_pos++) {
// Pack 8 wave8_byte values into a single 64-bit register for parallel extraction
// This packing is done once per bit_pos (8 times) instead of 64 times
u64 packed =
((u64)laneWaveforms[0 * bytes_per_lane + bit_pos] << 0) |
((u64)laneWaveforms[1 * bytes_per_lane + bit_pos] << 8) |
((u64)laneWaveforms[2 * bytes_per_lane + bit_pos] << 16) |
((u64)laneWaveforms[3 * bytes_per_lane + bit_pos] << 24) |
((u64)laneWaveforms[4 * bytes_per_lane + bit_pos] << 32) |
((u64)laneWaveforms[5 * bytes_per_lane + bit_pos] << 40) |
((u64)laneWaveforms[6 * bytes_per_lane + bit_pos] << 48) |
((u64)laneWaveforms[7 * bytes_per_lane + bit_pos] << 56);
// Inner loop: extract 8 pulses from the packed data
for (size_t pulse_bit = 0; pulse_bit < 8; pulse_bit++) {
// Extract pulse bits in parallel (compiler can optimize independent shifts)
outputBuffer[outputIdx++] =
((packed >> (7 - pulse_bit + 0)) & 0x01) << 0 |
((packed >> (7 - pulse_bit + 8)) & 0x01) << 1 |
((packed >> (7 - pulse_bit + 16)) & 0x01) << 2 |
((packed >> (7 - pulse_bit + 24)) & 0x01) << 3 |
((packed >> (7 - pulse_bit + 32)) & 0x01) << 4 |
((packed >> (7 - pulse_bit + 40)) & 0x01) << 5 |
((packed >> (7 - pulse_bit + 48)) & 0x01) << 6 |
((packed >> (7 - pulse_bit + 56)) & 0x01) << 7;
}
}
} else if (DATA_WIDTH <= 8) {
// Pack into single bytes (compile-time branch elimination via template instantiation)
// Guard against division by zero when DATA_WIDTH > 8 (shouldn't execute this branch, but compiler still evaluates it)
const size_t ticksPerByte = (DATA_WIDTH > 8) ? 1 : (8 / DATA_WIDTH);
const size_t numOutputBytes = (pulsesPerByte + ticksPerByte - 1) / ticksPerByte;
for (size_t outputByteIdx = 0; outputByteIdx < numOutputBytes; outputByteIdx++) {
u8 outputByte = 0;
for (size_t t = 0; t < ticksPerByte; t++) {
size_t pulse_idx = outputByteIdx * ticksPerByte + t;
if (pulse_idx >= pulsesPerByte)
break;
size_t bit_pos = pulse_idx / 8;
size_t pulse_bit = pulse_idx % 8;
for (size_t lane = 0; lane < DATA_WIDTH; lane++) {
const u8* laneWaveform = laneWaveforms + (lane * bytes_per_lane);
u8 wave8_byte = laneWaveform[bit_pos];
u8 pulse = (wave8_byte >> (7 - pulse_bit)) & 1;
size_t bitPos = t * DATA_WIDTH + lane;
outputByte |= (pulse << bitPos);
}
}
outputBuffer[outputIdx++] = outputByte;
}
} else if (DATA_WIDTH == 16) {
// Pack into 16-bit words (compile-time branch)
// Optimized: Software pipelining + output buffering
// Process 2 bit positions in parallel for better ILP, and batch writes for better cache efficiency
// Output buffer: accumulate 16 words (32 bytes) before writing
// This aligns with typical 32-byte cache lines and reduces memory write overhead
u8 writeBuffer[32];
size_t writeIdx = 0;
for (size_t bit_pos = 0; bit_pos < 8; bit_pos += 2) {
// Pack 16 wave8_byte values for TWO bit positions simultaneously
// This enables instruction-level parallelism and better register utilization
u64 packed_lo_0 =
((u64)laneWaveforms[0 * bytes_per_lane + bit_pos + 0] << 0) |
((u64)laneWaveforms[1 * bytes_per_lane + bit_pos + 0] << 8) |
((u64)laneWaveforms[2 * bytes_per_lane + bit_pos + 0] << 16) |
((u64)laneWaveforms[3 * bytes_per_lane + bit_pos + 0] << 24) |
((u64)laneWaveforms[4 * bytes_per_lane + bit_pos + 0] << 32) |
((u64)laneWaveforms[5 * bytes_per_lane + bit_pos + 0] << 40) |
((u64)laneWaveforms[6 * bytes_per_lane + bit_pos + 0] << 48) |
((u64)laneWaveforms[7 * bytes_per_lane + bit_pos + 0] << 56);
u64 packed_hi_0 =
((u64)laneWaveforms[8 * bytes_per_lane + bit_pos + 0] << 0) |
((u64)laneWaveforms[9 * bytes_per_lane + bit_pos + 0] << 8) |
((u64)laneWaveforms[10 * bytes_per_lane + bit_pos + 0] << 16) |
((u64)laneWaveforms[11 * bytes_per_lane + bit_pos + 0] << 24) |
((u64)laneWaveforms[12 * bytes_per_lane + bit_pos + 0] << 32) |
((u64)laneWaveforms[13 * bytes_per_lane + bit_pos + 0] << 40) |
((u64)laneWaveforms[14 * bytes_per_lane + bit_pos + 0] << 48) |
((u64)laneWaveforms[15 * bytes_per_lane + bit_pos + 0] << 56);
u64 packed_lo_1 =
((u64)laneWaveforms[0 * bytes_per_lane + bit_pos + 1] << 0) |
((u64)laneWaveforms[1 * bytes_per_lane + bit_pos + 1] << 8) |
((u64)laneWaveforms[2 * bytes_per_lane + bit_pos + 1] << 16) |
((u64)laneWaveforms[3 * bytes_per_lane + bit_pos + 1] << 24) |
((u64)laneWaveforms[4 * bytes_per_lane + bit_pos + 1] << 32) |
((u64)laneWaveforms[5 * bytes_per_lane + bit_pos + 1] << 40) |
((u64)laneWaveforms[6 * bytes_per_lane + bit_pos + 1] << 48) |
((u64)laneWaveforms[7 * bytes_per_lane + bit_pos + 1] << 56);
u64 packed_hi_1 =
((u64)laneWaveforms[8 * bytes_per_lane + bit_pos + 1] << 0) |
((u64)laneWaveforms[9 * bytes_per_lane + bit_pos + 1] << 8) |
((u64)laneWaveforms[10 * bytes_per_lane + bit_pos + 1] << 16) |
((u64)laneWaveforms[11 * bytes_per_lane + bit_pos + 1] << 24) |
((u64)laneWaveforms[12 * bytes_per_lane + bit_pos + 1] << 32) |
((u64)laneWaveforms[13 * bytes_per_lane + bit_pos + 1] << 40) |
((u64)laneWaveforms[14 * bytes_per_lane + bit_pos + 1] << 48) |
((u64)laneWaveforms[15 * bytes_per_lane + bit_pos + 1] << 56);
// Inner loop: interleave extraction from both bit positions
// This allows CPU to execute independent operations in parallel
for (size_t pulse_bit = 0; pulse_bit < 8; pulse_bit++) {
// Extract pulse bits for first bit position
u16 outputWord_0 =
((packed_lo_0 >> (7 - pulse_bit + 0)) & 0x01) << 0 |
((packed_lo_0 >> (7 - pulse_bit + 8)) & 0x01) << 1 |
((packed_lo_0 >> (7 - pulse_bit + 16)) & 0x01) << 2 |
((packed_lo_0 >> (7 - pulse_bit + 24)) & 0x01) << 3 |
((packed_lo_0 >> (7 - pulse_bit + 32)) & 0x01) << 4 |
((packed_lo_0 >> (7 - pulse_bit + 40)) & 0x01) << 5 |
((packed_lo_0 >> (7 - pulse_bit + 48)) & 0x01) << 6 |
((packed_lo_0 >> (7 - pulse_bit + 56)) & 0x01) << 7 |
((packed_hi_0 >> (7 - pulse_bit + 0)) & 0x01) << 8 |
((packed_hi_0 >> (7 - pulse_bit + 8)) & 0x01) << 9 |
((packed_hi_0 >> (7 - pulse_bit + 16)) & 0x01) << 10 |
((packed_hi_0 >> (7 - pulse_bit + 24)) & 0x01) << 11 |
((packed_hi_0 >> (7 - pulse_bit + 32)) & 0x01) << 12 |
((packed_hi_0 >> (7 - pulse_bit + 40)) & 0x01) << 13 |
((packed_hi_0 >> (7 - pulse_bit + 48)) & 0x01) << 14 |
((packed_hi_0 >> (7 - pulse_bit + 56)) & 0x01) << 15;
// Extract pulse bits for second bit position
u16 outputWord_1 =
((packed_lo_1 >> (7 - pulse_bit + 0)) & 0x01) << 0 |
((packed_lo_1 >> (7 - pulse_bit + 8)) & 0x01) << 1 |
((packed_lo_1 >> (7 - pulse_bit + 16)) & 0x01) << 2 |
((packed_lo_1 >> (7 - pulse_bit + 24)) & 0x01) << 3 |
((packed_lo_1 >> (7 - pulse_bit + 32)) & 0x01) << 4 |
((packed_lo_1 >> (7 - pulse_bit + 40)) & 0x01) << 5 |
((packed_lo_1 >> (7 - pulse_bit + 48)) & 0x01) << 6 |
((packed_lo_1 >> (7 - pulse_bit + 56)) & 0x01) << 7 |
((packed_hi_1 >> (7 - pulse_bit + 0)) & 0x01) << 8 |
((packed_hi_1 >> (7 - pulse_bit + 8)) & 0x01) << 9 |
((packed_hi_1 >> (7 - pulse_bit + 16)) & 0x01) << 10 |
((packed_hi_1 >> (7 - pulse_bit + 24)) & 0x01) << 11 |
((packed_hi_1 >> (7 - pulse_bit + 32)) & 0x01) << 12 |
((packed_hi_1 >> (7 - pulse_bit + 40)) & 0x01) << 13 |
((packed_hi_1 >> (7 - pulse_bit + 48)) & 0x01) << 14 |
((packed_hi_1 >> (7 - pulse_bit + 56)) & 0x01) << 15;
// Write to buffer instead of directly to output
writeBuffer[writeIdx++] = outputWord_0 & 0xFF;
writeBuffer[writeIdx++] = (outputWord_0 >> 8) & 0xFF;
writeBuffer[writeIdx++] = outputWord_1 & 0xFF;
writeBuffer[writeIdx++] = (outputWord_1 >> 8) & 0xFF;
}
// Flush buffer when full (16 words = 32 bytes)
// This triggers efficient burst writes that align with cache lines
if (writeIdx == 32) {
fl::memcpy(&outputBuffer[outputIdx], writeBuffer, 32);
outputIdx += 32;
writeIdx = 0;
}
}
} else {
// Invalid DATA_WIDTH (compile-time error if template instantiated with wrong value)
return 0;
}
return outputIdx;
}
const u8* FL_RESTRICT_PARAM laneWaveforms,
size_t data_width,
u8* FL_RESTRICT_PARAM outputBuffer
// Dispatch to template specialization based on runtime data_width
// Compiler generates optimized code for each specialization (no runtime branching)
switch (data_width) {
case 1:
return transpose_wave8byte_parlio_template<1>(laneWaveforms, outputBuffer);
case 2:
return transpose_wave8byte_parlio_template<2>(laneWaveforms, outputBuffer);
case 4:
return transpose_wave8byte_parlio_template<4>(laneWaveforms, outputBuffer);
case 8:
return transpose_wave8byte_parlio_template<8>(laneWaveforms, outputBuffer);
case 16:
return transpose_wave8byte_parlio_template<16>(laneWaveforms, outputBuffer);
default:
// Invalid data_width
return 0;
}
}
} // namespace fl
int y
Definition simple.h:93
int x
Definition simple.h:92
static bool transpose8(const fl::optional< LaneData > lanes[8], fl::span< u8 > output, const char **error=nullptr) FL_NOEXCEPT
Transpose 8 lanes of data into interleaved octal-SPI format.
static bool transpose4(const fl::optional< LaneData > &lane0, const fl::optional< LaneData > &lane1, const fl::optional< LaneData > &lane2, const fl::optional< LaneData > &lane3, fl::span< u8 > output, const char **error=nullptr) FL_NOEXCEPT
Transpose 4 lanes of data into interleaved quad-SPI format.
static bool transpose16(const fl::optional< LaneData > lanes[16], fl::span< u8 > output, const char **error=nullptr) FL_NOEXCEPT
Transpose 16 lanes of data into interleaved hex-SPI format.
static bool transpose2(const fl::optional< LaneData > &lane0, const fl::optional< LaneData > &lane1, fl::span< u8 > output, const char **error=nullptr) FL_NOEXCEPT
Transpose 2 lanes of data into interleaved dual-SPI format.
static u8 getLaneByte(const LaneData &lane, size_t byte_idx, size_t max_size) FL_NOEXCEPT
Get byte from lane at given index, handling padding automatically.
fl::span< const u8 > payload
Actual LED data for this lane.
fl::span< const u8 > padding_frame
Black LED frame for padding (repeating pattern)
static uint32_t t
Definition Luminova.h:55
unsigned char u8
Definition stdint.h:131
fl::u64 u64
Definition s16x16x4.h:221
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
FASTLED_FORCE_INLINE FL_IRAM size_t transpose_wave8byte_parlio(const u8 *FL_RESTRICT_PARAM laneWaveforms, size_t data_width, u8 *FL_RESTRICT_PARAM outputBuffer) FL_NOEXCEPT
unsigned char u8
Definition stdint.h:131
FASTLED_FORCE_INLINE void transpose8x1_MSB(unsigned char *A, unsigned char *B) FL_NOEXCEPT
Simplified 8x1 bit transpose with MSB-first output.
FL_DISABLE_WARNING_PUSH unsigned char * B
FASTLED_FORCE_INLINE void transpose_2strips(const u8 *const input[2], u8 *output, u16 num_leds, u8 bytes_per_led) FL_NOEXCEPT
Transpose 2 LED strips into parallel bit format.
void transpose_2lane_inline(const u8 *lane0_byte, const u8 *lane1_byte, u8 *output, size_t num_bytes) FL_NOEXCEPT
Low-level bit-interleaving primitive for 2 lanes (ISR-safe)
Optional< T > optional
Definition optional.h:16
FASTLED_FORCE_INLINE void transpose8(unsigned char *A, unsigned char *B) FL_NOEXCEPT
Templated 8x8 bit transpose with custom stride.
void transpose8x1_noinline(unsigned char *A, unsigned char *B) FL_NOEXCEPT
Simplified 8x1 bit transpose (non-inline version)
bool transpose_strips(u8 num_strips, const u8 *const *input, u8 *output, u16 num_leds, u8 bytes_per_led) FL_NOEXCEPT
Helper to transpose N strips with automatic dispatch.
void transpose_8lane_inline(const u8 *const lanes[8], u8 *output, size_t num_bytes) FL_NOEXCEPT
Low-level bit-interleaving primitive for 8 lanes (ISR-safe)
FASTLED_FORCE_INLINE void transpose8x1(unsigned char *A, unsigned char *B) FL_NOEXCEPT
Simplified 8x1 bit transpose (inline version)
FASTLED_FORCE_INLINE void transpose_4strips(const u8 *const input[4], u8 *output, u16 num_leds, u8 bytes_per_led) FL_NOEXCEPT
Transpose 4 LED strips into parallel bit format.
FASTLED_FORCE_INLINE u32 calculate_transpose_buffer_size(u16 num_leds, u8 bytes_per_led) FL_NOEXCEPT
Calculate output buffer size needed for transposed data.
FASTLED_FORCE_INLINE FL_IRAM FL_OPTIMIZE_FUNCTION size_t transpose_wave8byte_parlio_template(const u8 *FL_RESTRICT_PARAM laneWaveforms, u8 *FL_RESTRICT_PARAM outputBuffer) FL_NOEXCEPT
Template specialization of transpose for compile-time data_width (optimization)
void transpose_generic_inline(const TSource *const lanes[], size_t num_lanes, u8 *output, size_t num_items) FL_NOEXCEPT
Generic bit-interleaving primitive for N lanes with M-bit source data (ISR-safe)
void transpose_16lane_inline(const u8 *const lanes[16], u8 *output, size_t num_bytes) FL_NOEXCEPT
Low-level bit-interleaving primitive for 16 lanes (ISR-safe)
FASTLED_FORCE_INLINE void transpose_8strips(const u8 *const input[8], u8 *output, u16 num_leds, u8 bytes_per_led) FL_NOEXCEPT
Transpose 8 LED strips into parallel bit format.
void transpose_4lane_inline(const u8 *const lanes[4], u8 *output, size_t num_bytes) FL_NOEXCEPT
Low-level bit-interleaving primitive for 4 lanes (ISR-safe)
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FL_UNROLL(N)
#define FASTLED_FORCE_INLINE
#define FL_OPTIMIZATION_LEVEL_O3_END
#define FL_OPTIMIZE_FUNCTION
#define FL_IRAM
#define FL_RESTRICT_PARAM
#define FL_NOEXCEPT