Параллельная отрисовка адресных светодиодов WS2812B

Sergo_ST

★★★★★★✩
15 Мар 2020
798
731
Сделал небольшую процедуру для параллельной отрисовки 8-ми линий адресных светодиодов WS2812B. Отрисовка именно параллельная, а не последовательная тк за один фрейм отправляется один бит на каждую из линий. Тк весь порт D занят(D0..D7), UART будет не доступен.

Не знаю сильно ли кому это будет нужно, но вдруг...
C++:
#define LEDS_NUM 5 //светодиодов на каждой линии
uint8_t ledsBuf[LEDS_NUM * 24];

const uint8_t maskBit[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; //маска анализа бита
const uint8_t setBit[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,}; //маска установки бита
const uint8_t clrBit[] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F}; //маска очистки бита

//---------------------------------Передача массива данных на шины-----------------------------
void ledWrite(uint8_t* data, uint16_t size) {
  __asm__ __volatile__ (
    "LD r21, X+           \n\t" //загрузили байт из масива
    "CLR r20              \n\t" //очистка всех разрядов регистра
    "OUT %[PORT], r20     \n\t" //LOW на выход пинов
    "LDI r19, 200         \n\t" //счетчик сигнала reset(50мкс)
    //-----------------------------------------------------------------------------------------
    "_LOOP_DELAY_%=:      \n\t" //цикл задержки
    "NOP                  \n\t" //пропускаем цикл
    "DEC r19              \n\t" //декремент счетчика циклов
    "BRNE _LOOP_DELAY_%=  \n\t" //переход в начало цикла задержки
    //-----------------------------------------------------------------------------------------
    "CLI                  \n\t" //запретили прерывания
    "_BYTE_START_%=:      \n\t" //начало цикла отправки байта
    //-----------------------------------------------------------------------------------------
    "SER r20              \n\t" //установка всех разрядов регистра
    "OUT %[PORT], r20     \n\t" //HIGH на выход пинов
    "NOP                  \n\t" //пропускаем цикл
    "NOP                  \n\t" //пропускаем цикл
    "AND r20, r21         \n\t" //очищаем необходимые биты
    "OUT %[PORT], r20     \n\t" //LOW на выход установленных пинов
    "NOP                  \n\t" //пропускаем цикл
    "LD r21, X+           \n\t" //загрузили байт из масива
    "CLR r20              \n\t" //очистка всех разрядов регистра
    "SBIW %[COUNT], 1     \n\t" //отнимаем от счетчика байт
    "OUT %[PORT], r20     \n\t" //LOW на выход пинов
    //-----------------------------------------------------------------------------------------
    "BRNE _BYTE_START_%=  \n\t" //переход к загрузке нового байта
    "SEI                  \n\t" //разрешили прерывания
    :
    :"x"(data),
    [COUNT]"w"(size),
    [PORT]"I"(_SFR_IO_ADDR(PORTD))
    :"r19", "r20", "r21"
  );
}
//---------------------------------Инициализация шины-------------------------------------
void ledInit(void) {
  DDRD = 0xFF; //устанавливаем D0-D7 как выходы
  PORTD = 0xFF; //HIGH на пины D0-D7
  for (uint16_t i = 0; i < sizeof(ledsBuf); i++) ledsBuf[i] = 0; //очищаем массив данных
}
//-----------------------------Отрисовка всех светодиодов----------------------------------
void ledShow(void) {
  ledWrite(ledsBuf, sizeof(ledsBuf)); //отрисовываем массив
}
//-----------------------------Установка цвета светодиода----------------------------------
void setGRB(uint8_t line, uint16_t led, uint8_t G, uint8_t R, uint8_t B) {
  uint16_t temp = led * 24; //находим начало буфера
  for (uint8_t i = 0; i < 8; i++) { //записываем новый цвет светодиода
    if (G & maskBit[i]) ledsBuf[temp + i] |= setBit[line]; //устанавливаем бит зелёного цвета
    else ledsBuf[temp + i] &= clrBit[line]; //очищаем бит зелёного цвета

    if (R & maskBit[i]) ledsBuf[temp + i + 8] |= setBit[line]; //устанавливаем бит красного цвета
    else ledsBuf[temp + i + 8] &= clrBit[line]; //очищаем бит красного цвета

    if (B & maskBit[i]) ledsBuf[temp + i + 16] |= setBit[line]; //устанавливаем бит синего цвета
    else ledsBuf[temp + i + 16] &= clrBit[line]; //очищаем бит синего цвета
  }
}
Описание процедур:
  • ledInit() - инициализирует порт D, очищает массив светодиодов.
  • ledShow() - отправляет массив данных на линии.
  • setGRB(линия(0..7), светодиод(0..LEDS_NUM - 1), зеленый(0..255), красный(0..255), синий(0..255)) - устанавливает цвет для конкретного светодиода.
В LEDS_NUM указывается не общее количество светодиодов, а количество светодиодов на одной линии. Количество светодиодов на всех линиях должно быть одинаковым.

По цифрам:
  • Отрисовать 512 светодиодов можно за 2мс(примерно 500к/с), но узким горлышком выступает упаковка цвета в массив(если его ускорить, то профиту будет в разы больше).
  • Запись одного светодиода занимает 23мкс. Те, если мы будем заполнять светодиоды последовательно по одному, то это будет занимать также 2мс(против 15,4мс), если же все светодиоды разом то 13,7мс(против17,4).
  • Но даже при одинаковом времени отрисовки, с заблокированными прерываниями мы будем находиться 2мс(против 15,4).
 
Изменено:
  • Лойс +1
Реакции: te238s и kostyamat

IamNikolay

★★★✩✩✩✩
15 Янв 2020
820
175
Если представить что это матрица и ленты расположены горизонтально, подключены слева, то:
при заполнении лент поочередно получим загрузку изображения сверху вниз,
а если, отправлять по биту на каждую ленту - загрузка изображения будет слева направо.
Вот и вся разница.
так в чем смысл?
дает ли это прирост скорости заполнения?
есть сравнительные тесты?
 

Sergo_ST

★★★★★★✩
15 Мар 2020
798
731
@IamNikolay, Чтобы было более понятно, можно представить что мы передаём информацию на одну ленту размером в 32 светодиода, если брать в среднем что один светодиод у нас отрисовывается 30мкс + 50мкс сигнал ресет в самом начале, то это займёт у нас 1010мкс. Для последовательной передачи на 8 линий это займёт примерно 8080мкс, а для параллельной передачи это будет занимать всё те же 1010мкс.
 

IamNikolay

★★★✩✩✩✩
15 Янв 2020
820
175
Библиотеку octo и её модификации смотрели? Там как раз оптимизация отрисовки для больших матриц и разделение на каналы
 

Sergo_ST

★★★★★★✩
15 Мар 2020
798
731
Так это совсем под другие камни, тем более 32-х битные... Под AVR модификации я не нашел чтоб сравнить...

И судя по коду там используется DMA, так что процессор почти не участвует в отрисовке. В AVR увы такого нету ((
 

IamNikolay

★★★✩✩✩✩
15 Янв 2020
820
175
На AVR матрицу больших размеров с прямой отрисовкой с видео - не сделать.
в FastLED octo тоже есть, но не смотрел как вывод устроен
 

Sergo_ST

★★★★★★✩
15 Мар 2020
798
731
Опять же, в ченджлоге написано что паралельный вывод различных платформах ARM. Для AVR вроде как ничего такого нету.

Тут смысл не в отрисовке видео, а просто ускорить вывод.
 
Изменено:

kostyamat

★★★★★★✩
29 Окт 2019
1,097
630
@Sergo_ST, имхо, главный тормоз в этой связке сам AVR, и малое количество ОЗУ на борту, а не вывод на ленту. Хотя ваша наработка имеет некоторый смысл, ибо больше процессорного времени остается на обсчет картинки, вместо вывода на матрицу. Но все же.
 

poty

★★★★★★✩
19 Фев 2020
2,956
886
@Sergo_ST, не совсем. Хотел ещё в момент публикации об этом сказать, но упустил время.
Есть множество плюсов в уменьшении времени вывода. В ряде тем (и не только здесь) вывод на ленту комбинируется с другими real-time активностями (допустим, с управлением энкодером). Длительный вывод с запретом всех прерываний нарушает работу этих элементов, так что уменьшение времени в несколько раз - это классный трюк.
Есть и ещё ряд применений, в частности, управление задаваемой частью экрана. К сожалению, не могу выложить код - писался заказчику - но получилось сильно оптимизировать вывод видео на довольно большую матрицу 32х256 просто переключая на изменяемые участки экрана (с помощью внешних МК), то есть, в памяти не хранится весь буфер, а только то, что изменяется. Задумывалась ещё большая оптимизация за счёт того, что фоновая заливка, имеющая одинаковый цвет по всему полю, не занятому картинкой, не будет хранится, а подставляться из "виртуальных байт", но не было времени довести до ума, ограниченно работала, но не всегда.
 
  • Лойс +1
Реакции: Sergo_ST

Sergo_ST

★★★★★★✩
15 Мар 2020
798
731
@poty, Я уже про эту тему совсем забыл...😅
Да, согласен, если есть прерывания, то такое длительное время запрета уже становится критичным в некоторых случаях)