Я уже писал про то, как максимально эффективно управлять адресными светодиодами WS2813 с помощью аппаратных средств Attiny817: http://forum.amperka.ru/threads/attiny817-ws2812.18338/ Сегодня я возьму ленту из таких светодиодов. Общее количество светодиодов на ней - 450 (но если быть точнее - 448). Для такого количества светодиодов мне уже не хватит ОЗУ Attiny817 (в ней всего 512 байт. Этого хватает для 117 светодиодов), т.к. для проявления эффектов мне необходимо хранить всю информацию по каждому светодиоду. Поэтому я возьму Attiny1614 - в ней уже 2 кБ ОЗУ. Для обработки эффектов удобнее всего работать в пространстве HSV. Но для светодиодов информацию необходимо выводить в пространстве RGB24. Для этого я буду использовать следующую функцию конвертер: Код (C++): void HSV2RGB_no_pallete (HSV_t* HSV, RGB_t* RGB) { uint8_t base; if (HSV->S == 0) { RGB->R = HSV->V; RGB->G = HSV->V; RGB->B = HSV->V; } else { base = ((255 - HSV->S) * HSV->V) / 256; switch (HSV->H / HSV_SECTION_6) { case 0: RGB->R = HSV->V; RGB->G = (((HSV->V - base) * HSV->H) / HSV_SECTION_6) + base; RGB->B = base; break; case 1: RGB->R = (((HSV->V - base) * (HSV_SECTION_6 - (HSV->H % HSV_SECTION_6))) / HSV_SECTION_6) + base; RGB->G = HSV->V; RGB->B = base; break; case 2: RGB->R = base; RGB->G = HSV->V; RGB->B = (((HSV->V - base) * HSV->H) / HSV_SECTION_6) + base; break; case 3: RGB->R = base; RGB->G = (((HSV->V - base) * (HSV_SECTION_6 - (HSV->H % HSV_SECTION_6))) / HSV_SECTION_6) + base; RGB->B = HSV->V; break; case 4: RGB->R = (((HSV->V - base) * HSV->H) / HSV_SECTION_6) + base; RGB->G = base; RGB->B = HSV->V; break; case 5: RGB->R = HSV->V; RGB->G = base; RGB->B = (((HSV->V - base) * (HSV_SECTION_6 - (HSV->H % HSV_SECTION_6))) / HSV_SECTION_6) + base; break; } } } В ней где-то спрятался баг: накладывается пара цветов. Позже найду и исправлю. Определю константы: Код (C++): // Количество светодиодов #define LED_qty 448 // Количество элементов в структуре RGB_t #define RGB_qty 3 //sizeof(RGB_t) #define Max_brightness 255 #define Min_brightness 50 #define TCA_period 15 #define TCA_cmp2 8 // Определения таймера #define start 1 #define stop 0 #define res_period 200 // 40us - минимальная пауза для формирования защелкивания значений // Режимы эффектов #define no_effects 0 #define snowflake 1 #define fade 2 #define rainbow 3 Необходимые структуры: Код (C++): typedef struct { uint8_t B; /* Red */ uint8_t R; /* Green */ uint8_t G; /* Blue */ } RGB_t; typedef struct { uint16_t H; /* Hue */ uint8_t S; /* Saturation */ uint8_t V; /* Value */ } HSV_t; Для цветности я задаю все 360 градусов. Жирую! Для хранения информации о светодиодах буду использовать массив: Код (C++): HSV_t LED_array_HSV [LED_qty]; // Массив со значениями в пространстве HSV_t по количеству светодиодов Все глобальные переменные: Код (C++): volatile uint8_t Color_array_RGB [RGB_qty]; // Перевод структуры RGB_t в массив цветов volatile uint8_t Color_current_RGB = RGB_qty; // Текущий элемент в массиве цветов RGB_t HSV_t LED_array_HSV [LED_qty]; // Массив со значениями в пространстве HSV_t по количеству светодиодов // uint16_t LED_current_in; volatile uint16_t LED_current_HSV_out; // Индекс массива LED_array_HSV для преобразования цвета в RGB volatile uint16_t Time_reset; // Фиксация времени для формирования паузы защелкивания значений в светодиодах Эффекты у меня будут самые простые: две бегущих друг на друга белых змейки с затухающим хвостом. Код (C++): // Обработка эффектов inline void EffectsProcessing(uint8_t *mode, uint16_t index, uint8_t max_brightness, uint8_t min_brightness) { switch(*mode) { case snowflake: LED_array_HSV[index].S = 0; LED_array_HSV[index].V = max_brightness; LED_array_HSV[(LED_qty - 1) - index].V = max_brightness; if (!index) { *mode = fade; } while((LED_qty - 1) - index) { LED_array_HSV[++index].V--; LED_array_HSV[(LED_qty - 1) - index].V--; if (LED_array_HSV[index].V == min_brightness) { break; } } break; case fade: for (uint16_t i = 0; i < LED_qty; i++) { LED_array_HSV[i].V--; if (LED_array_HSV[i].V < min_brightness) { LED_array_HSV[i].V = min_brightness; } } break; } } Для выдержки необходимой минимальной паузы для защелкивания данных в светодиодах (у меня это 40 мкс, но по факту можно ещё меньше), я буду использовать таймер B0: Код (C++): void TCB0_Init(void) { TCB0.CTRLA = TCB_ENABLE_bm | // Разрешение работы таймера TCB_CLKSEL_CLKDIV2_gc | // Прескалер = 2 TCB_RUNSTDBY_bm; // Разрешаем работу в режиме StandBy } Вывод текущего байта в ленту и преобразование HSV->RGB я буду осуществлять в прерывании от SPI: Код (C++): //.. Прерывание от SPI ISR(SPI0_INT_vect) { //SPI0.INTFLAGS = 0xFF; // Сбросятся все флаги, кроме DREIF - он сбросится только после чтения/записи DATA! (void)SPI0.INTFLAGS; // Читаем флаги. Флаги сбросятся после чтения/записи DATA! if (Color_current_RGB) { LED_out(Color_array_RGB[--Color_current_RGB]); } else { Color_current_RGB = RGB_qty; if (!LED_current_HSV_out) { Time_reset = TCB0.CNT; LED_out_stop(); LED_current_HSV_out = LED_qty; } else { HSV2RGB_no_pallete(&LED_array_HSV[--LED_current_HSV_out], (RGB_t*)Color_array_RGB); } } } Здесь я последовательно записываю в SPI байты с цветами R, G и B из промежуточного массива текущего светодиода. Как только я выкинул последний элемент массива, я перехожу к функции преобразования HSV2RGB следующего светодиода. Если я дошёл до последнего светодиода и записал последний байт с цветом, то процесс останавливается принудительно запретом прерывания от SPI: Код (C++): inline void LED_out_stop(void) { TCA0_Mode(stop); SPI0.INTCTRL = (0 << SPI_IE_bp) | (0 << SPI_DREIE_bp) | (0 << SPI_TXCIE_bp); } Запуск "конвейера" происходит так: Код (C++): inline void LED_out_start(uint8_t data) { TCA0.SINGLE.CNT = 0; TCA0_Mode(start); SPI_WriteByte(data); // Здесь сбрасываются флаги! SPI0.INTCTRL = (1 << SPI_IE_bp) // Разрешаем прерывания от блока SPI | (0 << SPI_DREIE_bp) // Разрешаем прерывание, если регистр данных пуст (в режиме буфера) | (0 << SPI_TXCIE_bp); } из главного цикла: Код (C++): while (1) { if (LED_current_HSV_in) { if ((LED_current_HSV_out == LED_qty) && (res_period < (TCB0.CNT - Time_reset))) { HSV2RGB_no_pallete(&LED_array_HSV[--LED_current_HSV_out], (RGB_t*)Color_array_RGB); LED_out_start(Color_array_RGB[--Color_current_RGB]); EffectsProcessing(&mode, --LED_current_HSV_in, Max_brightness, Min_brightness); } } else { LED_current_HSV_in = LED_qty; } }
Вся функция main: Код (C++): int main(void) { uint16_t LED_current_HSV_in; // Индекс массива LED_array_HSV для создания эффектов uint8_t mode = snowflake; CCP = CCP_IOREG_gc; CLKCTRL.MCLKCTRLB = 0;// CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm; // Прескалер главной тактовой частоты SPI_Init(); // <- Пин PA4 (выход LUT0) на выход конфигурируется здесь //PORTC.DIR |= (PIN0_bm); //Отладка //PORTB.DIR |= PIN2_bm; TCA_Init(); TCA0.SINGLE.PER = TCA_period; TCA0.SINGLE.CMP2 = TCA_cmp2; CCL.TRUTH0 = 0xA8; // Таблица истинности CCL.LUT0CTRLB = CCL_INSEL0_SPI0_gc // Выбор канала SPI SCK | CCL_INSEL1_SPI0_gc; // Выбор канала SPI MOSI CCL.LUT0CTRLC = CCL_INSEL2_TCA0_gc; // Выбор канала W02 таймера TCA CCL.LUT0CTRLA = 0 << CCL_CLKSRC_bp // Блок CCL не тактируем | 1 << CCL_ENABLE_bp // Разрешение работы LUT0 | 1 << CCL_OUTEN_bp; // Разрешение выхода LUT0 CCL.CTRLA = 1 << CCL_ENABLE_bp // Разрешение работы CCL | 0 << CCL_RUNSTDBY_bp; // В стендбае не рабоатем TCB0_Init(); sei(); LED_current_HSV_in = LED_qty; LED_current_HSV_out = LED_qty; while (1) { /* .....//см. выше */ } } Итог: вся обработка и преобразования происходят параллельно с выводом информации в ленту. Змейка всю ленту из 448 светодиодов пробегает за ~9 секунд. Алгоритм можно ещё оптимизировать с переходом на буферный режим работы SPI, но особого выигрыша это не даст - мы и так уперлись в ограничения протокола светодиодов WS2812. Enjoy!
Раз там такие простые эффекты, почему не использовать лишь половину количества светодиодов, а зеркальную часть выводить "вычитанием из хвоста"?