Непонятки с адресной лентой

Помнится, еще на старом форуме кто-то из участников разместил функцию для переноса данных из массива цветов на ленту. Для тех, кому не нужны навороты существующих библиотек для ws2812.
Тогда сохранил себе в файл, а сейчас дошли руки попробовать. Исходной темы, увы, поиском нее нашел.
В общем, что-то у меня эта функция выдает странные результаты.
Есть: Arduino Nano прошитая как Uno, колечко из 16 светодиодов.
Хочется: чтобы колечко крутилось по спектру: RYGCBM…
Результат: при использовании NeoPixel (раскомментирована первая строка) все так и происходит, при использовании обсуждаемой функции (закомментирована первая строка) - обновляются лишь чуть более половины светодиодов и порядок цветов отличен от желаемого.
Код (можно выбрать библиотеку NeoPixel или обсуждаемую функцию, рас-/за-комментировав первую строку):

Спойлер
//#define NEOPIXEL

#define NumPixels 16
#define DO_PIN 6

#ifdef NEOPIXEL
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

Adafruit_NeoPixel pix(NumPixels, DO_PIN, NEO_GRB + NEO_KHZ800);

#else
/*
 * Copyright (C) 2022, Andrei Egorov - работа с адресными светодиодами
 */

#define NumColors 4

#if DO_PIN > 19
  #error !!! DO_PIN must be less than 20 !!!
#endif
#define DOPORT (DO_PIN>=0 && DO_PIN<=7) ? 0x0B : (DO_PIN>=8 && DO_PIN<=13) ? 0x05 : (DO_PIN>=14 && DO_PIN<=19) ? 0x08 : 0
#define DOPIN (DO_PIN>=0 && DO_PIN<=7) ? DO_PIN : (DO_PIN>=8 && DO_PIN<=13) ? DO_PIN - 8 : (DO_PIN>=14 && DO_PIN<=19) ? DO_PIN - 14 : 0

uint8_t pixels[NumPixels][NumColors];

void __attribute__ ((noinline))send_pixels(uint16_t num, uint8_t *ppixels) {
  static uint32_t endsend=0;
  while ((micros()-endsend)<300);
  asm volatile(
    "IN   __tmp_reg__,%0 \n\t"
    "PUSH __tmp_reg__ \n\t"
    "CLI \n\t"
    "LDI  r19,1<<%3 \n\t"
    "LDI  r18,2 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "label_1: \n\t"
    "BST  __tmp_reg__,7 \n\t"
    "OUT  %2-2,r19 \n\t"
    "BRTC label_2 \n\t"
    "ROL  __tmp_reg__ \n\t"
    "LSL  r18 \n\t"
    "RJMP .+0\n\t"
    "RJMP .+0\n\t"  
    "BRCS label_3 \n\t"
    "NOP \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP label_1 \n\t"
    "label_2: \n\t"
    "BST  __tmp_reg__,7 \n\t"
    "OUT  %2-2,r19 \n\t"
    "ROL  __tmp_reg__ \n\t"
    "LSL  r18 \n\t"
    "RJMP .+0\n\t"
    "RJMP .+0\n\t"
    "BRCC label_1 \n\t"
    "RJMP label_4 \n\t"
    "label_3: \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP .+0\n\t"
    "BST  __tmp_reg__,7 \n\t"
    "label_4: \n\t"
    "OUT  %2-2,r19 \n\t"
    "BRTC label_5 \n\t"
    "RJMP .+0\n\t"
    "LDI  r18,2 \n\t"
    "SBIW %4,1 \n\t"
    "BREQ label_6 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP label_1 \n\t"
    "label_5: \n\t"
    "LDI  r18,2 \n\t"
    "OUT  %2-2,r19 \n\t"
    "SBIW %4,1 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "RJMP .+0\n\t"
    "BRNE label_1 \n\t"
    "RJMP label_7 \n\t"
    "label_6: \n\t"
    "NOP \n\t"
    "OUT  %2-2,r19 \n\t"
    "label_7: \n\t"
    "POP  __tmp_reg__ \n\t"
    "OUT  %0,__tmp_reg__\n\t"
    ::"I" (_SFR_IO_ADDR(SREG)), "e" (ppixels), "I" (_SFR_IO_ADDR(_SFR_IO8(DOPORT))), "M" (DOPIN), "w" (num):"r18","r19"
  );
  endsend=micros();
}

#endif

#define tickRange (256*6) // 0-225:R-Y, 256-511:Y-G, 512-767:G-C, 768-1023:C-B, 1024-1279:B-M, 1280-1535:M-R
//                           Rm G+ B0   R- Gm B0     R0 Gm B+     R0 G- Bm      R+ G0 Bm       Rm G0 B-
//                   R       ^^^^^^^^^^^\\\\\\\\\\\\\___________________________///////////////^^^^^^^^^^^
//                   G       ///////////^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\__________________________
//                   B       ________________________/////////////^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\
  
#define stepi 4

byte getValue(int pos) { // G: первые 256 нарастает, следующие 512 - максимум, след. 256 убывает, остальные 512 - 0
  while(pos < 0) pos += tickRange;
  while(pos >= tickRange) pos -= tickRange;
  if(pos < 256) return pos;
  if(pos < 768) return 255;
  if(pos < 1024) return 1023 - pos;
  return 0;
}

void setup () {
  Serial.begin(115200);
  Serial.println("Start");
  pinMode(13, OUTPUT);

#ifdef NEOPIXEL
   pix.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
#else
  asm volatile(
    "SBI %0-1,%1 \n\t"
    "CBI %0,%1 \n\t"
    ::"I" (_SFR_IO_ADDR(_SFR_IO8(DOPORT))), "I" (DOPIN)//, "r" (DO)
  );
#endif
  Serial.print("End Prepare NumPixels: ");
  Serial.println(NumPixels);
}

#define angle (tickRange/NumPixels)

void oneStep() {
    long t0 = micros();
    static int i = 0;
    byte j;
    i += stepi;
    if (i >= tickRange) i -= tickRange;
    for(int k = 0; k < NumPixels; k++) {
      byte b0 = getValue(i - 512 - k*angle);
      byte b1 = getValue(i       - k*angle);
      byte b2 = getValue(i + 512 - k*angle);
#ifdef NEOPIXEL      
      pix.setPixelColor(k, pix.Color(b0, b1, b2));
#else
      pixels[k][0] = b0;
      pixels[k][1] = b1;
      pixels[k][2] = b2;
#endif
    }
    long t1 = micros();
#ifdef NEOPIXEL      
    pix.show();   // Send the updated pixel colors to the hardware.
#else
    send_pixels(NumPixels*NumColors, *pixels);
#endif
    long t2 = micros();
    static long tt;
    if(i == 256) {
      Serial.print(t1-t0);
      Serial.print('\t');
      Serial.print(t2-t1);
      Serial.print('\t');
      Serial.println(t2-tt);
    }
    tt = t2;
}

void loop() {
  static unsigned long previousMillis = 0;  
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 5) {
    previousMillis = currentMillis;
    digitalWrite(13, !digitalRead(13));
    oneStep();
  }
}

Это мой код - NumСolors почему 4 ?

Пытался и 3, и 4.
Попытка поставить 4 вызвана тем, что при NumColors = 3 пять из 16 светодиодов остаются темными и не участвуют в “празднике жизни”. Ну то есть идея такая - если указанного количества не хватает до конца, его нужно увеличить.

Тогда меняйте 16 на 21, но лучше смотрите как заполняется массив.
При 4 - в массиве просто появятся лишние нули и они полетят в ленту …
И порядок цветов GBR у ленты скорее всего - то же надо учесть при заполнении массива.

Это я тоже делал. Но если бы выложил этот вариант, был бы вопрос: почему 21? Кстати , 21 все равно мало, а вот 32 - хватает. Но - мерцают, будь они неладны.
А NeoPixel дает именно то, что хочется и именно при NumPixels = 16. И без излишнего мерцания.
В общем, вариант с раскомментированной строкой - это именно то, что хотелось бы видеть.

Массив заполняется в строках 145-147. Не вижу разницы с заполнением Neopixel в строке 143.

Да, я уже обнаружил, что первый цвет зеленый, но пока это не важно, то, что я посылаю, абсолютно симметрично по цветам, только они сдвинуты относительно друг друга на 120 градусов.
Да и никто не мешает как угодно перетасовать цвета, когда будет правильно отображаться именно то, что записано в массив.

Выкинуть библиотеки, написать самим.
ЗЫ. Весной на stm32 писал вывод ленты, не мог понять почему цвета сбиваются, оказалось важен уровень сигнала по окончанию передачи всех цветов, добавил лишний байт в выходной поток - проблема исчезла.

1 лайк

Вроде все 16 горят и общая картина похожа с и без библиотекой. А плата на какой частоте у вас ? Скетч под 16 МГц.

А вот у меня “общая картина” заметно различается (отличие функции от Неопиксель):

  1. “Картина” захватывает только 9 светодиодов из 16 (при 4 цветах было 11).
  2. Сильно мерцает (Неопиксель дает ровную по времени яркость).

Приведу более наглядный пример (заменим строки, начиная с 142, цель - зажечь единственный светодиод):

      if(k == 3) {
#ifdef NEOPIXEL      
        pix.setPixelColor(k, pix.Color(b0, b1, b2));
#else
        pixels[k][0] = b0;
        pixels[k][1] = b1;
        pixels[k][2] = b2;
#endif
      }

Полный текст:

Спойлер
#define NEOPIXEL

#define NumPixels 16
#define DO_PIN 6

#ifdef NEOPIXEL
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

Adafruit_NeoPixel pix(NumPixels, DO_PIN, NEO_GRB + NEO_KHZ800);

#else
/*
 * Copyright (C) 2022, Andrei Egorov - работа с адресными светодиодами
 */

#define NumColors 3

#if DO_PIN > 19
  #error !!! DO_PIN must be less than 20 !!!
#endif
#define DOPORT (DO_PIN>=0 && DO_PIN<=7) ? 0x0B : (DO_PIN>=8 && DO_PIN<=13) ? 0x05 : (DO_PIN>=14 && DO_PIN<=19) ? 0x08 : 0
#define DOPIN (DO_PIN>=0 && DO_PIN<=7) ? DO_PIN : (DO_PIN>=8 && DO_PIN<=13) ? DO_PIN - 8 : (DO_PIN>=14 && DO_PIN<=19) ? DO_PIN - 14 : 0

uint8_t pixels[NumPixels][NumColors];

void __attribute__ ((noinline))send_pixels(uint16_t num, uint8_t *ppixels) {
  static uint32_t endsend=0;
  while ((micros()-endsend)<300);
  asm volatile(
    "IN   __tmp_reg__,%0 \n\t"
    "PUSH __tmp_reg__ \n\t"
    "CLI \n\t"
    "LDI  r19,1<<%3 \n\t"
    "LDI  r18,2 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "label_1: \n\t"
    "BST  __tmp_reg__,7 \n\t"
    "OUT  %2-2,r19 \n\t"
    "BRTC label_2 \n\t"
    "ROL  __tmp_reg__ \n\t"
    "LSL  r18 \n\t"
    "RJMP .+0\n\t"
    "RJMP .+0\n\t"  
    "BRCS label_3 \n\t"
    "NOP \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP label_1 \n\t"
    "label_2: \n\t"
    "BST  __tmp_reg__,7 \n\t"
    "OUT  %2-2,r19 \n\t"
    "ROL  __tmp_reg__ \n\t"
    "LSL  r18 \n\t"
    "RJMP .+0\n\t"
    "RJMP .+0\n\t"
    "BRCC label_1 \n\t"
    "RJMP label_4 \n\t"
    "label_3: \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP .+0\n\t"
    "BST  __tmp_reg__,7 \n\t"
    "label_4: \n\t"
    "OUT  %2-2,r19 \n\t"
    "BRTC label_5 \n\t"
    "RJMP .+0\n\t"
    "LDI  r18,2 \n\t"
    "SBIW %4,1 \n\t"
    "BREQ label_6 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP label_1 \n\t"
    "label_5: \n\t"
    "LDI  r18,2 \n\t"
    "OUT  %2-2,r19 \n\t"
    "SBIW %4,1 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "RJMP .+0\n\t"
    "BRNE label_1 \n\t"
    "RJMP label_7 \n\t"
    "label_6: \n\t"
    "NOP \n\t"
    "OUT  %2-2,r19 \n\t"
    "label_7: \n\t"
    "POP  __tmp_reg__ \n\t"
    "OUT  %0,__tmp_reg__\n\t"
    ::"I" (_SFR_IO_ADDR(SREG)), "e" (ppixels), "I" (_SFR_IO_ADDR(_SFR_IO8(DOPORT))), "M" (DOPIN), "w" (num):"r18","r19"
  );
  endsend=micros();
}

#endif

#define tickRange (256*6) // 0-225:R-Y, 256-511:Y-G, 512-767:G-C, 768-1023:C-B, 1024-1279:B-M, 1280-1535:M-R
//                           Rm G+ B0   R- Gm B0     R0 Gm B+     R0 G- Bm      R+ G0 Bm       Rm G0 B-
//                   R       ^^^^^^^^^^^\\\\\\\\\\\\\___________________________///////////////^^^^^^^^^^^
//                   G       ///////////^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\__________________________
//                   B       ________________________/////////////^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\
  
#define stepi 4

byte getValue(int pos) { // G: первые 256 нарастает, следующие 512 - максимум, след. 256 убывает, остальные 512 - 0
  while(pos < 0) pos += tickRange;
  while(pos >= tickRange) pos -= tickRange;
  if(pos < 256) return pos;
  if(pos < 768) return 255;
  if(pos < 1024) return 1023 - pos;
  return 0;
}

void setup () {
  Serial.begin(115200);
  Serial.println("Start");
  pinMode(13, OUTPUT);

#ifdef NEOPIXEL
   pix.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
#else
  asm volatile(
    "SBI %0-1,%1 \n\t"
    "CBI %0,%1 \n\t"
    ::"I" (_SFR_IO_ADDR(_SFR_IO8(DOPORT))), "I" (DOPIN)//, "r" (DO)
  );
#endif
  Serial.print("End Prepare NumPixels: ");
  Serial.println(NumPixels);
}

#define angle (tickRange/NumPixels)

void oneStep() {
    long t0 = micros();
    static int i = 0;
    byte j;
    i += stepi;
    if (i >= tickRange) i -= tickRange;
    for(int k = 0; k < NumPixels; k++) {
      byte b0 = getValue(i - 512 - k*angle);
      byte b1 = getValue(i       - k*angle);
      byte b2 = getValue(i + 512 - k*angle);
      if(k == 3) {
#ifdef NEOPIXEL      
        pix.setPixelColor(k, pix.Color(b0, b1, b2));
#else
        pixels[k][0] = b0;
        pixels[k][1] = b1;
        pixels[k][2] = b2;
#endif
      }
    }
    long t1 = micros();
#ifdef NEOPIXEL      
    pix.show();   // Send the updated pixel colors to the hardware.
#else
    send_pixels(NumPixels*NumColors, *pixels);
#endif
    long t2 = micros();
    static long tt;
    if(i == 256) {
      Serial.print(t1-t0);
      Serial.print('\t');
      Serial.print(t2-t1);
      Serial.print('\t');
      Serial.println(t2-tt);
    }
    tt = t2;
}

void loop() {
  static unsigned long previousMillis = 0;  
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 5) {
    previousMillis = currentMillis;
    digitalWrite(13, !digitalRead(13));
    oneStep();
  }
}

Номер пикселя варьируем: 3 или 4.
Теперь Неопиксель, как и положено, плавно по спектру переливается указанным светодиодом. Функция же мигает одним и тем же вторым светодиодом, причем при k==3 - в красно-фиолетовой части спектра, а при k==4 - в красно-зеленой.

Я когда писал этот код - гонял в железе на ws2813 и по минимумам таймингов 250 и 625.


В интернете куча разных таймингов для этих лент - ХЗ кому верить !

Есть у кого лента под рукой - запустите код:

Спойлер
//#define NEOPIXEL

#define NumPixels 16
#define DO_PIN 6

#ifdef NEOPIXEL
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

Adafruit_NeoPixel pix(NumPixels, DO_PIN, NEO_GRB + NEO_KHZ800);

#else
/*
 * Copyright (C) 2022, Andrei Egorov - работа с адресными светодиодами
 */

#define NumColors 3

#if DO_PIN > 19
  #error !!! DO_PIN must be less than 20 !!!
#endif
#define DOPORT (DO_PIN>=0 && DO_PIN<=7) ? 0x0B : (DO_PIN>=8 && DO_PIN<=13) ? 0x05 : (DO_PIN>=14 && DO_PIN<=19) ? 0x08 : 0
#define DOPIN (DO_PIN>=0 && DO_PIN<=7) ? DO_PIN : (DO_PIN>=8 && DO_PIN<=13) ? DO_PIN - 8 : (DO_PIN>=14 && DO_PIN<=19) ? DO_PIN - 14 : 0

uint8_t pixels[NumPixels][NumColors];

void __attribute__ ((noinline))send_pixels(uint16_t num, uint8_t *ppixels) {
  static uint32_t endsend=0;
  while ((micros()-endsend)<300);
  asm volatile(
    "IN   __tmp_reg__,%0 \n\t"
    "PUSH __tmp_reg__ \n\t"
    "CLI \n\t"
    "LDI  r19,1<<%3 \n\t"
    "LDI  r18,2 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "label_1: \n\t"
    "BST  __tmp_reg__,7 \n\t"
    "OUT  %2-2,r19 \n\t"
    "BRTC label_2 \n\t"
    "ROL  __tmp_reg__ \n\t"
    "LSL  r18 \n\t"
    "RJMP .+0\n\t"
    "RJMP .+0\n\t"  
    "BRCS label_3 \n\t"
    "NOP \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP label_1 \n\t"
    "label_2: \n\t"
    "BST  __tmp_reg__,7 \n\t"
    "OUT  %2-2,r19 \n\t"
    "ROL  __tmp_reg__ \n\t"
    "LSL  r18 \n\t"
    "RJMP .+0\n\t"
    "RJMP .+0\n\t"
    "BRCC label_1 \n\t"
    "RJMP label_4 \n\t"
    "label_3: \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP .+0\n\t"
    "BST  __tmp_reg__,7 \n\t"
    "label_4: \n\t"
    "OUT  %2-2,r19 \n\t"
    "BRTC label_5 \n\t"
    "RJMP .+0\n\t"
    "LDI  r18,2 \n\t"
    "SBIW %4,1 \n\t"
    "BREQ label_6 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "OUT  %2-2,r19 \n\t"
    "RJMP label_1 \n\t"
    "label_5: \n\t"
    "LDI  r18,2 \n\t"
    "OUT  %2-2,r19 \n\t"
    "SBIW %4,1 \n\t"
    "LD   __tmp_reg__,%a1+ \n\t"
    "RJMP .+0\n\t"
    "BRNE label_1 \n\t"
    "RJMP label_7 \n\t"
    "label_6: \n\t"
    "NOP \n\t"
    "OUT  %2-2,r19 \n\t"
    "label_7: \n\t"
    "POP  __tmp_reg__ \n\t"
    "OUT  %0,__tmp_reg__\n\t"
    ::"I" (_SFR_IO_ADDR(SREG)), "e" (ppixels), "I" (_SFR_IO_ADDR(_SFR_IO8(DOPORT))), "M" (DOPIN), "w" (num):"r18","r19"
  );
  endsend=micros();
}

#endif

#define tickRange (256*6) // 0-225:R-Y, 256-511:Y-G, 512-767:G-C, 768-1023:C-B, 1024-1279:B-M, 1280-1535:M-R
//                           Rm G+ B0   R- Gm B0     R0 Gm B+     R0 G- Bm      R+ G0 Bm       Rm G0 B-
//                   R       ^^^^^^^^^^^\\\\\\\\\\\\\___________________________///////////////^^^^^^^^^^^
//                   G       ///////////^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\__________________________
//                   B       ________________________/////////////^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\
  
#define stepi 4

byte getValue(int pos) { // G: первые 256 нарастает, следующие 512 - максимум, след. 256 убывает, остальные 512 - 0
  while(pos < 0) pos += tickRange;
  while(pos >= tickRange) pos -= tickRange;
  if(pos < 256) return pos;
  if(pos < 768) return 255;
  if(pos < 1024) return 1023 - pos;
  return 0;
}

void setup () {
  Serial.begin(115200);
  Serial.println("Start");
  pinMode(13, OUTPUT);

#ifdef NEOPIXEL
   pix.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
#else
  asm volatile(
    "SBI %0-1,%1 \n\t"
    "CBI %0,%1 \n\t"
    ::"I" (_SFR_IO_ADDR(_SFR_IO8(DOPORT))), "I" (DOPIN)//, "r" (DO)
  );
#endif
  Serial.print("End Prepare NumPixels: ");
  Serial.println(NumPixels);
}

#define angle (tickRange/NumPixels)

void oneStep() {
    long t0 = micros();
    static int i = 0;
    byte j;
    i += stepi;
    if (i >= tickRange) i -= tickRange;
    for(int k = 0; k < NumPixels; k++) {
      byte b0 = getValue(i - 512 - k*angle);
      byte b1 = getValue(i       - k*angle);
      byte b2 = getValue(i + 512 - k*angle);
#ifdef NEOPIXEL      
      pix.setPixelColor(k, pix.Color(b0, b1, b2));
#else
      pixels[k][0] = b1;
      pixels[k][1] = b0;
      pixels[k][2] = b2;
#endif
    }
    long t1 = micros();
#ifdef NEOPIXEL      
    pix.show();   // Send the updated pixel colors to the hardware.
#else
    send_pixels(NumPixels*NumColors, *pixels);
#endif
    long t2 = micros();
    static long tt;
    if(i == 256) {
      Serial.print(t1-t0);
      Serial.print('\t');
      Serial.print(t2-t1);
      Serial.print('\t');
      Serial.println(t2-tt);
    }
    tt = t2;
}

void loop() {
  static unsigned long previousMillis = 0;  
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 5) {
    previousMillis = currentMillis;
    digitalWrite(13, !digitalRead(13));
    oneStep();
  }
}

Проверил еще на 4-х разных кольцах/лентах/планках (кольцо на 12, кольцо на 24, лента на 144 и планка на 8).
Правильно работает везде, кроме того злосчастного кольца, на котором я начинал.
Неопиксель правильно работает на всех пяти.
Похоже, действительно где-то тайминги вне допустимого диапазона, но большая часть устройств это “хавает”.

И - да, функция отрабатывает быстрее (348-352мкс), чем метод Неопиксель (496мкс) - все на 16 диодах.

16 пикселей * 3 байта * 8 бит * 14 тактов / 16000000 тактов в секунду = 0.000336 секунды

плюс постоянная величина на вход выход, проверку и сохранение endsend