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

Помнится, еще на старом форуме кто-то из участников разместил функцию для переноса данных из массива цветов на ленту. Для тех, кому не нужны навороты существующих библиотек для 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 писал вывод ленты, не мог понять почему цвета сбиваются, оказалось важен уровень сигнала по окончанию передачи всех цветов, добавил лишний байт в выходной поток - проблема исчезла.

Вроде все 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

позволь поинтересоваться, ты использовал PWM+DMA или SPI+DMA
или в лоб bit banding?

Ох….ну спросили, это когда ж было….сделал и забыл.

Посмотрю исходники как с отпуска выйду.

Вроде spi+DMA.

Прочитал про bit banding - ниче не понял - а зачем это вообще может пригодится?

Зачем дублировать одну часть памяти в другую? Или я вообще не понял что это? И каким боком пожно адресные светодиоды этим программировать?

да блин, это я так ногодрыг обозвал))

поумничать захотелось)

Во нашёл в почте исходник, там и spi и pwm можно использовать, для f411

/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#include <stdint.h>
#include <stdio.h>
#include "stm32f4xx.h"
#include "f4base.h"
#include "f4uart.h"
#include "f4spi.h"
#include "f4st7735.h"
#include "f4ws2811.h"
#include "ws281eff.h"
#include "f4ds18b20.h"
#include "f4oneware.h"

extern "C" int __io_putchar(int ch)
{
  ITM_SendChar(ch);
  return ch;
}

int main(void)
{
	SystemInit();
	f4initPC13led();
	f4setPC13led(0);
	STM32F411CEU6startHSErcc();
	SystemCoreClockUpdate();
	printf("Start! CLK = %d\n", (int)SystemCoreClock);
	f4initPA0key();
	f4initTIM4();
	f4initUART2();
	f4initSPI1();
	f4st7735init();
	printf("RCC->CR      0x%x\n", (unsigned int)RCC->CR);
	printf("RCC->CFGR  0x%x\n", (unsigned int)RCC->CFGR);
	printf("RCC->PLLCFGR  0x%x\n", (unsigned int)RCC->PLLCFGR);
	f4initDS18B20();
	if (f4readROMds18b20()) {
		f4saveTHTLCFG();
		puts("DS18B20 ready");
	}
	//f4initWS281XbySPI4();
	f4initWS281XbyDMA();
	// test strip
	unsigned char colorsData[count_bytes_in_buf];
	/*colorsData[0] = 0xFF; colorsData[1] = 0x00; colorsData[2] = 0x00; colorsData[3] = 0x00;
	colorsData[4] = 0x00; colorsData[5] = 0xFF; colorsData[6] = 0x00; colorsData[7] = 0x00;
	colorsData[8] = 0x00; colorsData[9] = 0x00; colorsData[10] = 0xFF; colorsData[11] = 0x00;
	colorsData[12] = 0x00; colorsData[13] = 0x00; colorsData[14] = 0x00; colorsData[15] = 0xFF;
	colorsData[16] = 0xFF; colorsData[17] = 0x00; colorsData[18] = 0x00; colorsData[19] = 0x00;
	colorsData[20] = 0x00; colorsData[21] = 0xFF; colorsData[22] = 0x00; colorsData[23] = 0x00;
	colorsData[24] = 0x00; colorsData[25] = 0x00; colorsData[26] = 0xFF; colorsData[27] = 0x00;
	colorsData[28] = 0x00; colorsData[29] = 0x00; colorsData[30] = 0x00; colorsData[31] = 0xFF;
	colorsData[32] = 0x00; colorsData[33] = 0x00; colorsData[34] = 0x00; colorsData[35] = 0x00;
	colorsData[36] = 0xFF; colorsData[37] = 0xFF; colorsData[38] = 0xFF; colorsData[39] = 0xFF;*/
	//f4sendWS281XbySPI4((unsigned char *)&colorsData, count_bytes_in_buf);
	//f4sendWS281XbyDMA((unsigned char *)&colorsData, count_bytes_in_buf);
	// end strip
	f4st7735speedtest();
	//delay_ms(2000UL);
	f4st7735fillScreen(ST7735_RED);
	f4st7735fillScreen(ST7735_BLUE);
    /* Loop forever */
	puts("loop begin");
	while (1) {
		__attribute__((unused)) bool statusSPIdma = f4busySPIDMA();
		__attribute__((unused)) bool statusWS2811dma = f4busyWS2811byDMA();
		//f4setPC13led(f4readPA0key()); // test key
		static unsigned long timerBlink = 0UL;
		if ((millis() - timerBlink) >= 1000UL) {
			timerBlink = millis();
			f4togPC13led();
			/*static char counter = 0;
	        printf("counter = %d\n", counter++);*/
			// test move strip
			/*if (!f4busyWS2811byDMA()) {
				unsigned char firstColors[ws281x_bytes_per_pixel];
				for(unsigned short i=0; i<ws281x_bytes_per_pixel; ++i) {firstColors[i] = colorsData[i];}
				for(unsigned short i=ws281x_bytes_per_pixel; i<count_bytes_in_buf; ++i) {colorsData[i-ws281x_bytes_per_pixel] = colorsData[i];}
				for(unsigned short i=0; i<ws281x_bytes_per_pixel; ++i) {colorsData[count_bytes_in_buf-ws281x_bytes_per_pixel+i] = firstColors[i];}
				f4sendWS281XbyDMA((unsigned char *)&colorsData, count_bytes_in_buf);
			}*/
			// strip SPI
			//f4sendWS281XbySPI4((unsigned char *)&colorsData, count_bytes_in_buf);
			// end strip
			if (!f4busySPIDMA()) f4st7735printUInt(millis(), ST7735_WHITE, ST7735_BLACK, 0, 0, 0);
			// end text - begin DB18B20
			static bool flagConvert = true;
			if (flagConvert) {
				flagConvert = false;
				f4convertDS18B20();
			} else {
				flagConvert = true;
				//printf("temp = %f 'C\n", f4tempDS18B20());
				float localTemp = f4tempDS18B20();
				if (localTemp < 0.0) {
					f4st7735printOneChar('-', ST7735_WHITE, ST7735_BLACK, 0, 0, 50);
					localTemp *= -1.0;
				}
				unsigned short cel = localTemp;
				f4st7735printUInt(cel, ST7735_WHITE, ST7735_BLACK, 0, 8, 50);
				f4st7735printOneChar('.', ST7735_WHITE, ST7735_BLACK, 0, 52, 50);
				cel = (localTemp * 100.0);
				cel %= 100;
				f4st7735printUInt(cel, ST7735_WHITE, ST7735_BLACK, 0, 64, 50);
			}
			// end DS18B20
		}
		f4uartLoop(true);
		ws281loopWork((unsigned char *)&colorsData);
	}
}

/*
 * f4ws2811.cpp
 *
 *  Created on: Oct 29, 2024
 *      Author: seleznev_a
 */

#include "stm32f4xx.h"
#include "f4ws2811.h"
#include <stdint.h>
#include <stdio.h>

unsigned char ws281Ybrval; // яркость в процентах
unsigned long ws281XrealSizeDMAbuf; // реальный размер данных в буфере, кратно количеству пикселей * размер пикселя * 8
volatile unsigned char ws281XbufPWM[ws281x_max_count_bytes]; // сам буфер для отправки // +51 байт для подлнялия GPIO вниз после отправки последнего байта + SPI Rsp
bool workTIMDMA;

void f4setBrBytes(unsigned char inVal) {
	if (inVal>ws281y_max_bright_percent) ws281Ybrval = ws281y_max_bright_percent;
	else if (inVal==0) ws281Ybrval = 1;
	else ws281Ybrval = inVal;
}

void f4sendSPI4(unsigned char cmd) {
    *((__IO uint8_t *)&SPI1->DR) = cmd;
	while ((!(SPI4->SR & SPI_SR_TXE)) && (SPI4->SR & SPI_SR_BSY));
}

void f4writeSPI4(unsigned char * buff, unsigned long buff_size) {
	for(unsigned long i = 0; i < buff_size; ++i) {
	    while(!(SPI4->SR & SPI_SR_TXE)); // do not remark - its not good
	    *((__IO uint8_t *)&SPI4->DR) = *(buff++);
	}
	while ((!(SPI4->SR & SPI_SR_TXE)) && (SPI4->SR & SPI_SR_BSY));
}

void f4initSPI4(void) {
	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // GPIO port A
	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; // GPIO port B
	RCC->APB2ENR |= RCC_APB2ENR_SPI4EN; // SPI ON
    // pin SCK PB13 AF5 push-pull AF06
    GPIOB->MODER &= ~GPIO_MODER_MODER13; // 00 — clear bits
    GPIOB->MODER |= GPIO_MODER_MODER13_1; // 10 — режим альтернативной функции.
    GPIOB->MODER &= ~GPIO_MODER_MODER13_0; // 10 — режим альтернативной функции.
    GPIOB->OTYPER &= ~GPIO_OTYPER_OT_13; // 0 - двухтактный выход или push-pull сокращено PP (после сброса)
    GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13; // 11 — 50 MHz
    GPIOB->AFR[1] |= GPIO_AFRH_AFRH5_2 | GPIO_AFRH_AFRH5_1; // pin 6 (13-8=5 ->> 5*4 bits) alt func 6 (2+4)
    // pin MISO PA11 input pull-up AF06
    GPIOA->MODER &= ~GPIO_MODER_MODER11; // 00 — clear bits
    GPIOA->MODER |= GPIO_MODER_MODER11_1; // 10 — режим альтернативной функции.
    GPIOA->MODER &= ~GPIO_MODER_MODER11_0; // 10 — режим альтернативной функции.
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR11; // 00 — reset bits
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR11_1; // 01 — подтяжка к плюсу питания или pull-up сокращено PU
    GPIOA->PUPDR |= GPIO_PUPDR_PUPDR11_0; // 01 — подтяжка к плюсу питания или pull-up сокращено PU
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR11; // 11 — 50 MHz
    GPIOA->AFR[1] |= GPIO_AFRH_AFRH3_2 | GPIO_AFRH_AFRH3_1; // pin 3 (11-8=3 ->> 3*4 bits) alt func 6 (2+4)
    // pin MOSI PA1 AF5 push-pull
    GPIOA->MODER |= GPIO_MODER_MODER1_1; // 10 — режим альтернативной функции.
    GPIOA->MODER &= ~GPIO_MODER_MODER1_0; // 10 — режим альтернативной функции.
    GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1; // 0 - двухтактный выход или push-pull сокращено PP (после сброса)
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR1; // 11 — 50 MHz
    GPIOA->AFR[0] |= GPIO_AFRL_AFRL1_2 | GPIO_AFRL_AFRL1_0; // pin 1 ->> 1*4 bits alt func 5 (1+4)
    // init SPI4
    SPI4->CR1 = 0;
    SPI4->CR2 = 0;
    SPI4->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1; // fPCLK / 16 // one period = 1.25 usec
    SPI4->CR1 |= SPI_CR1_CPHA; // Clock phase // 1
    SPI4->CR1 &= ~SPI_CR1_DFF; // 0: 8-bit data frame format is selected for transmission/reception
    SPI4->CR1 |= SPI_CR1_SSM; // Программное управление SS
    SPI4->CR1 |= SPI_CR1_SSI; // I/O value of the NSS pin is ignored
    SPI4->CR1 |= SPI_CR1_MSTR; // master mode
    SPI4->CR1 |= SPI_CR1_SPE; // включить SPI
}

void f4initWS281XbySPI4(void) {
	f4setBrBytes(80);
	f4initSPI4();
}

void prepareDMAbufSPI(const unsigned char * inData, const unsigned short dataCount) { // заполняем буфер для ШИМ
   	for (unsigned short i=0; i<lebRstBus; ++i) {ws281XbufPWM[i] = 0x00;} // rst bus 50usec
   	ws281XrealSizeDMAbuf = lebRstBus; // rst bus 50usec
    for (unsigned short i=0; i<dataCount; ++i) {
    	unsigned char inByte = *(inData+i);
    	inByte = (unsigned char)(((unsigned short)((unsigned short)(inByte)*(unsigned short)(ws281Ybrval)))/100);
    	for (unsigned char j=8; j>0; j--) {
    		if (inByte & (1<<(j-1))) ws281XbufPWM[ws281XrealSizeDMAbuf] = 0xF0;
    		else ws281XbufPWM[ws281XrealSizeDMAbuf] = 0xC0;
    	    ++ws281XrealSizeDMAbuf;
    	}
    }
   	ws281XrealSizeDMAbuf += dataCount * 8;
    ws281XbufPWM[ws281XrealSizeDMAbuf] = 0x00;
    ++ws281XrealSizeDMAbuf;
}

void f4sendWS281XbySPI4(unsigned char * inData, unsigned short dataCount) {
	prepareDMAbufSPI(inData, dataCount);
	f4writeSPI4((unsigned char *)&ws281XbufPWM, ws281XrealSizeDMAbuf);
}

void f4initPA1pwmT2_CH2_AF01(void) {
	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // GPIO port A
	GPIOA->MODER |= GPIO_MODER_MODER1_1; // 10 — режим альтернативной функции.
    GPIOA->MODER &= ~GPIO_MODER_MODER1_0; // 10 — режим альтернативной функции.
    GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1; // 0 - двухтактный выход или push-pull сокращено PP (после сброса)
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR1; // 11 — 50 MHz
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1; // no pull-up down
    GPIOA->AFR[0] |= GPIO_AFRL_AFRL1_0;
}

void f4initTIM2pwmCH2(void) {
	RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // clock Timer2 ON
	TIM2->CNT = 0; // Очищаем счетный регистр
	TIM2->PSC = 0; // делитель x1 100 MHz
	TIM2->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM2->CCR2 = ws281x_ccr_timer_full_high;
	TIM2->CCMR1 &= ~TIM_CCMR1_OC2M; //сбрасываем все биты OCxM
	TIM2->CCMR1 |= TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; // настроить канал // PWM Mode 1 // Output Compare 2 preload enable
	TIM2->CCER |= TIM_CCER_CC2E; // настроим на выход канал 2
	TIM2->BDTR |= TIM_BDTR_MOE; // разрешим использовать выводы таймера как выходы
	TIM2->CR1 &= ~TIM_CR1_DIR; // считаем вверх
	TIM2->CR1 |= TIM_CR1_ARPE;    //Регистры таймера с буферизацией
	TIM2->DIER |= TIM_DIER_CC2DE; // Разрешить запрос DMA // CC2 DMA request enabled
}

void f4initDMA1CH3S6_T2_CH2(void) {
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // DMA1
    RCC->AHB1LPENR |= RCC_AHB1LPENR_DMA1LPEN;
    DMA1_Stream6->CR &= ~DMA_SxCR_CHSEL; // 000: channel 0 selected
    DMA1_Stream6->CR |= DMA_SxCR_CHSEL_0; // 011: channel 3 selected // T2_CH2
    DMA1_Stream6->CR |= DMA_SxCR_CHSEL_1; // 011: channel 3 selected // T2_CH2
	DMA1_Stream6->PAR = (unsigned long)((unsigned char *)(&TIM2->CCR2)); //заносим адрес регистра куда кидать
	DMA1_Stream6->M0AR = (unsigned long)((unsigned char *)(&ws281XbufPWM[0])); //заносим адрес данных в регистр CMAR
	DMA1_Stream6->M1AR = 0;
	DMA1_Stream6->NDTR = ws281XrealSizeDMAbuf; // передаем нужное количество
	DMA1_Stream6->CR &= ~DMA_SxCR_PL; //приоритет низкий
	DMA1_Stream6->CR |= DMA_SxCR_PL_0;
	DMA1_Stream6->CR &= ~DMA_SxCR_MSIZE; // Memory data size // 00: byte (8-bit)
	DMA1_Stream6->CR |= DMA_SxCR_MSIZE_1; // 01-16bit 10-32bit
	DMA1_Stream6->CR &= ~DMA_SxCR_PSIZE; // Peripheral data size // 00: byte (8-bit)
	DMA1_Stream6->CR |= DMA_SxCR_PSIZE_1; // 01-16bit 10-32bit
	DMA1_Stream6->CR |= DMA_SxCR_MINC; // 1: Memory address pointer is incremented after each data transfer (increment is done according to MSIZE)
	DMA1_Stream6->CR &= ~DMA_SxCR_PINC; // 0: Peripheral address pointer is fixed
	DMA1_Stream6->CR &= ~DMA_SxCR_CIRC; // 0: Circular mode disabled
	DMA1_Stream6->CR &= ~DMA_SxCR_DIR; // 00: Peripheral-to-memory
	DMA1_Stream6->CR |= DMA_SxCR_DIR_0; // 01: Memory-to-peripheral
}

void f4initWS281XbyDMA(void) {
	f4setBrBytes(80);
	workTIMDMA = false;
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
	RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
	f4initPA1pwmT2_CH2_AF01();
	f4initTIM2pwmCH2();
	f4initDMA1CH3S6_T2_CH2();
}

void prepareDMAbufTIM(const unsigned char * inData, const unsigned short dataCount) { // заполняем буфер для ШИМ
	unsigned short busDMAlen = lebRstBus * 4;
   	for (unsigned short i=0; i<busDMAlen; ++i) {ws281XbufPWM[i] = 0x00;} // rst bus 50usec
   	ws281XrealSizeDMAbuf = busDMAlen; // rst bus 50usec
    for (unsigned short i=0; i<dataCount; ++i) {
    	unsigned char inByte = *(inData+i);
    	inByte = (unsigned char)(((unsigned short)((unsigned short)(inByte)*(unsigned short)(ws281Ybrval)))/100);
    	for (unsigned char j=8; j>0; j--) {
    		if (inByte & (1<<(j-1))) ws281XbufPWM[ws281XrealSizeDMAbuf] = ws281x_ccr_th1_timer;
    		else ws281XbufPWM[ws281XrealSizeDMAbuf] = ws281x_ccr_th0_timer;
    		++ws281XrealSizeDMAbuf;
    		ws281XbufPWM[ws281XrealSizeDMAbuf] = 0;
    		++ws281XrealSizeDMAbuf;
    		ws281XbufPWM[ws281XrealSizeDMAbuf] = 0;
    		++ws281XrealSizeDMAbuf;
    		ws281XbufPWM[ws281XrealSizeDMAbuf] = 0;
    		++ws281XrealSizeDMAbuf;
    	}
    }
   	for (unsigned short i=0; i<4; ++i) {ws281XbufPWM[ws281XrealSizeDMAbuf+i] = 0x00;} // last byte 0
    ws281XrealSizeDMAbuf += 4;
}

void f4sendWS281XbyDMA(unsigned char * inData, unsigned short dataCount) {
	workTIMDMA = true;
	prepareDMAbufTIM(inData, dataCount);
	DMA1_Stream6->NDTR = ws281XrealSizeDMAbuf; // передаем нужное количество
	TIM2->CR1 |= TIM_CR1_CEN; // start tim
	DMA1_Stream6->CR |= DMA_SxCR_EN; // запускаем DMA
}

bool f4busyWS2811byDMA(void) {
	if (workTIMDMA) {
		if (DMA1->HISR & DMA_HISR_TCIF6) { // проверка занятости потока DMA
			workTIMDMA = false;
			DMA1->HIFCR |= DMA_HIFCR_CTCIF6; // сброс флага DMA Transfer Complete
			DMA1_Stream6->CR &= ~DMA_SxCR_EN; // отключаем DMA
			while(DMA1_Stream6->CR & DMA_SxCR_EN); // ждем как DMA отключится
			TIM2->CR1 &= ~TIM_CR1_CEN; // останавливаем таймер
		} else {
			return true;
		}
	}
	return false;
}

unsigned char f4getBrBytes(void) {
	return ws281Ybrval;
}

пасиб.

Можно читать состояние бита без маски

Можно менять состояние бита атоммарно одной командой

А это чтобы вместо чтение-маска-запись устанавливать бит в нужное состояние единственной командой.

Адресное пространство - 4 Гига. Не жалко.

Адресные светодиоды управляются единственным пином. Почему бы не использовать для этого пмна специализированную команду?