450 ws2812 на attiny1614 и немного кода

Тема в разделе "Глядите, что я сделал", создана пользователем parovoZZ, 22 апр 2020.

  1. parovoZZ

    parovoZZ Гуру

    Я уже писал про то, как максимально эффективно управлять адресными светодиодами 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;
                }              

        }
     
    Un_ka, Daniil, KindMan и ещё 1-му нравится это.
  2. parovoZZ

    parovoZZ Гуру

    Вся функция 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!
     
    Daniil нравится это.
  3. Tomasina

    Tomasina Сушитель лампочек Модератор

    Раз там такие простые эффекты, почему не использовать лишь половину количества светодиодов, а зеркальную часть выводить "вычитанием из хвоста"?
     
  4. Un_ka

    Un_ka Гик

    А видео самих эффектов где?