STM Урок 119. WS2812B. Лента на умных светодиодах RGB. Часть 3



В предыдущей части нашего урока мы создали проект, изучили работу таймера в режиме PWM с использованием DMA, а также научились зажигать определённые светодиоды различными цветами.

Также мы написали код, который нам позволил увидеть свечение всех светодиодов различными цветами.

 

Только это была картина статичная. Теперь давайте напишем какие-нибудь динамические тесты.

Первый тест у нас будет — бегущие огни. Правда под бегущими огнями подразумевается несколько одиноких огоньков, бегущих по кругу, а у нас будет полностью все цвета светодиодов также бежать по кругу. И, оказывается, это всё проделывается не очень-то и сложно.

Перейдём в файл ws2812.c и добавим функцию для такого теста.

Прежде, чем мы её добавим, добавим глобальный массив для ещё одного буфера. который будет хранить цвета только одного светодиода. Данный буфер требуется для того, что мы будем передвигать цвета наших светодиодов на одну позицию в сторону самого первого светодиода, и потом в самую последнюю позицию мы будем добавлять цвет самой первой. И вот, чтобы он не забылся, и нужен данный буфер

 

uint8_t rgb_temp[12][3];

uint16_t DMA_BUF_TEMP[24];

 

А теперь собственно функция, которую мы напишем в самый низ нашего файла

 

//------------------------------------------------------------------

void ws2812_test01(void)

{

  uint8_t i,j;

  ws2812_prepareValue(128, 0, 0, 0, 128, 0, 0, 0, 128, 64, 64, 0,

      0, 64, 64, 64, 0, 64, 96, 32, 0, 96, 0, 32,

      32, 96, 0, 0, 96, 32, 0, 32, 96, 32, 0, 96);

  ws2812_setValue();

  for(j=0;j<50;j++)

  {

    memcpy((void*)DMA_BUF_TEMP,(void*)(BUF_DMA+48),48);

    for(i=0;i<143;i++)

    {

      memcpy((void*)(i*24+BUF_DMA+48),(void*)(i*24+BUF_DMA+72),48);

    }

    memcpy((void*)(BUF_DMA+48+3432),(void*)DMA_BUF_TEMP,48);

    ws2812_light();

    HAL_Delay(100);

  }

}

//----------------------------------------------------------------------------

 

Теперь несколько слов о содержимом тела данной функции.

Сначала мы заполним буфер некими цветами. Затем создадим цикл из 50 итераций. Это и будет количество перемещений для бегущей ленты. Потом мы копируем содержимое первой позиции основного буфера во временный буфер. Затем мы смещаем в сторону первой позиции все остальные 143 позиции. А потом копируем из временного буфера в последнюю позицию запомненный пиксель. Почему именно 48, а не 24? Потому что функция memcpy работает с байтами а не с полусловами. Затем запускаем таймер, цвета тем самым переместятся на 1 позицию. А затем задержка на 100 милисекунд. Вот примерно с таким периодом и будут бежать наши огоньки, если не учитывать время выполнения всего кода в цикле.

Напишем прототип на данную функцию и вызовем её в бесконечном цикле функции main() файла main.c

 

  /* USER CODE BEGIN 3 */

  ws2812_test01();

}

 

Соберём код, прошьём контроллер и посмотрим на результат выполнения. Только показывать результат я здесь не буду, так как это будет практически предыдущая картинка ибо здесь я динамику показать не смогу. Так что милости прошу на канал в Youtube.

Но один тест — это конечно мало. Напишем ещё один, который будет заставлять плавно мигать наши все светодиодом и они тем самым будут изменять только интенсивность свечения, но не цвет. Для этого мы опять перейдём в файл ws2812.c и добавим очередную функцию

 

//----------------------------------------------------------------------------------------

void ws2812_test02(void)

{

  uint8_t i,j,jj;

  int k=0;

  for(jj=0;jj<10;jj++)

  {

    for(j=0;j<32;j++)

    {

      for(i=0;i<144;i++)

      {

        k=1000*(32-j)/32;

        ws2812_pixel_rgb_to_buf_dma(rgb_temp[i%12][0]*k/1000,rgb_temp[i%12][1]*k/1000,rgb_temp[i%12][2]*k/1000,i);

      }

      ws2812_light();

      HAL_Delay(10);

    }

    for(j=0;j<32;j++)

    {

      for(i=0;i<144;i++)

      {

        k=1000*(j+1)/32;

        ws2812_pixel_rgb_to_buf_dma(rgb_temp[i%12][0]*k/1000,rgb_temp[i%12][1]*k/1000,rgb_temp[i%12][2]*k/1000,i);

      }

      ws2812_light();

      HAL_Delay(10);

    }

  }

}

//-----------------------------------------------------------------------------

 

Данная функция, несмотря на циклы тройной вложенности, не является сложной.

 

 

В данной функции мы добавляем 10 циклов. Это количество миганий светодиодов. Затем идёт два цикла, в первом из которых происходит плавное уменьшение свечения светодиодов, а во втором — обратное увеличение. 32 градации для плавности, думаю будет достаточно. В первом из этих циклов мы добавляем цикл из 144 итераций, соответствующих количеству наших светодиодов. В теле данного цикла, являющегося циклом самого нижнего уровня, мы рассчитаем коэффициент, который будет в дальнейшем служить множителем для расчёта цветов данного светодиода. Чтобы избежать работы с плавающей точкой, который будет давать лишнюю нагрузку на АЛУ, мы получим коэффициент, в 1000 раз больший нужного, потом мы это учтём. Далее мы вызываем функцию заполнения позиции ленточки цветами, в которой и учитываем наш коэффициент. Выйдя из цикла самого нижнего уровня, мы вызываем функцию старта таймера, ждём 10 милисекунд и процесс повторяется уже с увеличенным значением коэффициента. Достигнув максимальной величины, мы попадаем в следующий подобный цикл, в котором практически проделывается всё то же самое, но только с той разницей, что идёт увеличение коэффициента за счёт использования в расчёте которого хитрого множителя.

Также напишем прототип на данную функцию и вызовем её в бесконечном цикле функции main() файла main.c

 

ws2812_test01();

ws2812_test02();

 

Соберём код, прошьём контроллер и посмотрим результат. Я покажу один из кадров, опять же не поленившись повторить, что на видео это выглядит гораздо лучше

 

 

Перейдём в файл ws2812.c и добавим функцию для очередного теста, в котором мы будем аналогичным образом плавно мигать всеми светодиодами, только 12 из них будут мигать в сторону уменьшения, а 12 следующих в это время — в сторону увеличения, затем наоборот. Создаётся впечатление очень красивого эффекта

 

ws2812_test03

void ws2812_test03(void)

{

  uint8_t i,j,jj;

  int k=0;

  for(jj=0;jj<10;jj++)

  {

    for(j=0;j<32;j++)

    {

      for(i=0;i<144;i++)

      {

        if((i<12)||((i>=24)&&(i<36))||((i>=48)&&(i<60))||((i>=72)&&(i<84))||((i>=96)&&(i<108))||((i>=120)&&(i<132)))

        {

          k=1000*(32-j)/32;

          ws2812_pixel_rgb_to_buf_dma(rgb_temp[i%12][0]*k/1000,rgb_temp[i%12][1]*k/1000,rgb_temp[i%12][2]*k/1000,i);

        }

        else

        {

          k=1000*(j+1)/32;

          ws2812_pixel_rgb_to_buf_dma(rgb_temp[i%12][0]*k/1000,rgb_temp[i%12][1]*k/1000,rgb_temp[i%12][2]*k/1000,i);

        }

      }

      ws2812_light();

      HAL_Delay(15);

    }

    for(j=0;j<32;j++)

    {

      for(i=0;i<144;i++)

      {

        if((i<12)||((i>=24)&&(i<36))||((i>=48)&&(i<60))||((i>=72)&&(i<84))||((i>=96)&&(i<108))||((i>=120)&&(i<132)))

        {

          k=1000*(j+1)/32;

          ws2812_pixel_rgb_to_buf_dma(rgb_temp[i%12][0]*k/1000,rgb_temp[i%12][1]*k/1000,rgb_temp[i%12][2]*k/1000,i);

        }

        else

        {

          k=1000*(32-j)/32;

          ws2812_pixel_rgb_to_buf_dma(rgb_temp[i%12][0]*k/1000,rgb_temp[i%12][1]*k/1000,rgb_temp[i%12][2]*k/1000,i);

        }

      }

      ws2812_light();

      HAL_Delay(15);

    }

  }

}

//----------------------------------------------------------------------------

 

Код подобен коду предыдущей функции, только мы уже здесь отслеживаем участки, рассчитывая тем самым для чётных участков по 12 светодиодов один коэффициент, для нечётных — другой. Поэтому и вложенность здесь ещё увеличилась на 1.

Снова добавим прототип на данную функцию и вызовем её в бесконечном цикле функции main() файла main.c

 

ws2812_test02();

ws2812_test03();

 

Соберём код, прошьём контроллер и посмотрим результат

 

 

 

Перейдём в файл ws2812.c и добавим ещё один тест, который будет бежать огнями, плавно зажжёнными одним цветом. Объяснить это словами трудно, лучше один раз увидеть, хотя бы на картинке

 

ws2812_test04

//----------------------------------------------------------------------------

void ws2812_test04(uint8_t col)

{

  uint8_t i,j,jj;

  for(j=0;j<12;j++)

  {

    for(i=0;i<12;i++)

    {

      switch(col)

      {

        case 1:

          if(i<6)

            {rgb_temp[j*12+i][0]=128/((i+1)*2); rgb_temp[j*12+i][1]=0; rgb_temp[j*12+i][2]=0;}

          else

            {rgb_temp[j*12+i][0]=128/((12-i)*2); rgb_temp[j*12+i][1]=0; rgb_temp[j*12+i][2]=0;}

          break;

        case 2:

          if(i<6)

            {rgb_temp[j*12+i][0]=0; rgb_temp[j*12+i][1]=128/((i+1)*2); rgb_temp[j*12+i][2]=0;}

          else

            {rgb_temp[j*12+i][0]=0; rgb_temp[j*12+i][1]=128/((12-i)*2); rgb_temp[j*12+i][2]=0;}

          break;

        case 3:

          if(i<6)

            {rgb_temp[j*12+i][0]=0; rgb_temp[j*12+i][1]=0; rgb_temp[j*12+i][2]=128/((i+1)*2);}

          else

            {rgb_temp[j*12+i][0]=0; rgb_temp[j*12+i][1]=0; rgb_temp[j*12+i][2]=128/((12-i)*2);}

          break;

        case 4:

          if(i<6)

            {rgb_temp[j*12+i][0]=64/((i+1)*2); rgb_temp[j*12+i][1]=64/((i+1)*2); rgb_temp[j*12+i][2]=0;}

          else

            {rgb_temp[j*12+i][0]=64/((12-i)*2); rgb_temp[j*12+i][1]=64/((12-i)*2); rgb_temp[j*12+i][2]=0;}

          break;

        case 5:

          if(i<6)

            {rgb_temp[j*12+i][0]=64/((i+1)*2); rgb_temp[j*12+i][1]=0; rgb_temp[j*12+i][2]=64/((i+1)*2);}

          else

            {rgb_temp[j*12+i][0]=64/((12-i)*2); rgb_temp[j*12+i][1]=0; rgb_temp[j*12+i][2]=64/((12-i)*2);}

          break;

        case 6:

          if(i<6)

            {rgb_temp[j*12+i][0]=0; rgb_temp[j*12+i][1]=64/((i+1)*2); rgb_temp[j*12+i][2]=64/((i+1)*2);}

          else

            {rgb_temp[j*12+i][0]=0; rgb_temp[j*12+i][1]=64/((12-i)*2); rgb_temp[j*12+i][2]=64/((12-i)*2);}

          break;

      }

    }

  }

  ws2812_setValue();

  ws2812_light();

  for(jj=0;jj<144;jj++)

  {

    memcpy((void*)DMA_BUF_TEMP,(void*)(BUF_DMA+48),48);

    for(i=0;i<143;i++)

    {

      memcpy((void*)(i*24+BUF_DMA+48),(void*)(i*24+BUF_DMA+72),48);

    }

    memcpy((void*)(BUF_DMA+48+3432),(void*)DMA_BUF_TEMP,48);

    ws2812_light();

    HAL_Delay(50);

  }

}

//----------------------------------------------------------------------------

 

Несмотря на то, что код большой, от также несложный, так как здесь есть переключатель с кейсами, соответствующими определённым цветам из 6 возможных. Поэтому в функции теста присутствует входной целочисленный аргумент. Тест повторяется 12 раз, в цикле мы добавляем ещё один цикл из 12 итераций, каждая из которых инициализирует во временном буфере одну из 12 позиций цветом. В данном цикле мы вызываем оператор switch, который определит, с каким базовым цветом мы хотим работать. И в зависимости от номера цвета мы в соответствующем ему кейсе заносим настройки в соответствующие элементы временного буфера. После того, как данные будут занесены, мы вызываем функцию распределения данных по основному буферу и зажигаем наши светодиоды. А дальше код идёт такой же как в самом первом тесте, то есть наши огни аналогично бегут по кругу ленточки

 

Добавим прототип на данную функцию и вызовем её в бесконечном цикле функции main() файла main.c, используя все возможные цветовые гаммы

 

ws2812_test03();

ws2812_test04(1);

ws2812_test04(2);

ws2812_test04(3);

ws2812_test04(4);

ws2812_test04(5);

ws2812_test04(6);

 

 

Соберём код, прошьём контроллер и посмотрим результат

 

 

Переходим в файл ws2812.c и добавим ещё один тест. Данный тест будет последним в данном уроке и он будет перемигивать каждым светодиодом из 12 в блоке какими-то цветом, который будет определяться для каждого из этих светодиодов определённым образом. Только мы должны неукоснительно чтить наше золотое правило — суммарное свечение светодиода — не более 1/6 максимального. Остальные блоки будут, соответственно, повторять поведение данного блока

 

//----------------------------------------------------------------------------

void ws2812_test05(void)

{

  uint8_t i,j,jj;

  int k=0;

  for(jj=0;jj<40;jj++)

  {

    for(j=0;j<32;j++)

    {

      for(i=0;i<144;i++)

      {

        if(j<16) k=1000*(j)/16;

        else k=1000*(32-j)/16;

        if(i%12==0) ws2812_pixel_rgb_to_buf_dma(128*k/1000,128*(1000-k)/1000,0,i);

        if(i%12==1) ws2812_pixel_rgb_to_buf_dma(0,128*k/1000,128*(1000-k)/1000,i);

        if(i%12==2) ws2812_pixel_rgb_to_buf_dma(128*k/1000,0,128*(1000-k)/1000,i);

        if(i%12==3) ws2812_pixel_rgb_to_buf_dma(128*(1000-k)/1000,128*k/2000,128*k/2000,i);

        if(i%12==4) ws2812_pixel_rgb_to_buf_dma(128*k/2000,128*k/2000,128*(1000-k)/1000,i);

        if(i%12==5) ws2812_pixel_rgb_to_buf_dma(128*k/2000,128*(1000-k)/1000,128*k/2000,i);

        if(i%12==6) ws2812_pixel_rgb_to_buf_dma(128*(1000-k)/2000,128*(1000-k)/2000,128*k/1000,i);

        if(i%12==7) ws2812_pixel_rgb_to_buf_dma(128*k/1000,128*(1000-k)/2000,128*(1000-k)/2000,i);

        if(i%12==8) ws2812_pixel_rgb_to_buf_dma(128*(1000-k)/2000,128*k/1000,128*(1000-k)/2000,i);

        if(i%12==9) ws2812_pixel_rgb_to_buf_dma(128*(1000-k)/3000,128*(1000-k)/667,128*k/1000,i);

        if(i%12==10) ws2812_pixel_rgb_to_buf_dma(128*k/3000,128*(1000-k)/667,128*(1000-k)/1000,i);

        if(i%12==11) ws2812_pixel_rgb_to_buf_dma(128*(1000-k)/3000,128*k/667,128*(1000-k)/3000,i);

      }

      ws2812_light();

      HAL_Delay(20);

    }

  }

}

//----------------------------------------------------------------------------

 

Код также несложен. Мы вообще не используем временный буфер, а работаем сразу с основным, используя для этого в цикле деление по модулю. Как видим, для каждого светодиода в базовом цвете преобладают какие-то определённые и уникальные для него цвета.

Следуя традиции, добавим прототип на данную функцию и вызовем её в бесконечном цикле функции main() файла main.c

 

ws2812_test04(6);

ws2812_test05();

 

Соберём код, прошьём контроллер и посмотрим результат. Конечно, опять же смотреть его статически — это не то. Надо смотреть именно видео

 

 

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

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Ленты светодиодные WS2812B разные можно приобрести здесь WS2812B

Логический анализатор 16 каналов можно приобрести здесь

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM Name

11 комментариев на “STM Урок 119. WS2812B. Лента на умных светодиодах RGB. Часть 3
  1. Владимир:

    Здравствуйте, спасибо за ваши уроки очень помогают в изучении SMT32.
    Есть один момент о котором хотелось бы чтобы Вы тоже рассказали, это формирование с помощь DMA-PWM сигнала разной скважности, очень давно интересует этот вопрос, и еще у меня есть WS2811(WS2812) которым нужно формировать сигнал разной длительности, в нашел в инете неплохую статью по теме но мне там непонятны многие моменты например это:
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &GPIOA->ODR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) WS2812_IO_High.

  2. Владимир::

    Здравствуйте,
    как изменить направление движения?
    Т.е. я хочу чтобы огонек двигался не почасовой стрелке, а против

    • Продумать свой алгоритм и воспользоваться.

      • Владимир::

        Ну да, конечно

      • Roman:

        Здравствуйте, спасибо вам за ваши уроки, очень круто рассказываете и показываете. У меня такой вопрос я все сделал как вы показали с, но только с платой 401ccu8, даже сверял сигнал на осциллографе с арлуиновским, и они одинаковы, но на ардуино лента работает, а на stm не работает. Может ли это быть из-за того что сигнал не 5 вольт?

  3. Владимир::

    Алгоритм такой,
    копируем содержимое последней позиции основного буфера во временный буфер.
    Затем мы смещаем в сторону последней позиции все остальные 143 позиции.
    потом копируем из временного буфера в первую позицию запомненный пиксель.

    for(j=0;j<50;j++)
    {
    memcpy((void*)DMA_BUF_TEMP,(void*)(BUF_DMA+48+3432),48);
    for(i=0;i<143;i++)
    {
    memcpy((void*)(i*24+BUF_DMA+72),(void*)(i*24+BUF_DMA+48),48);
    }
    memcpy((void*)(BUF_DMA+48),(void*)DMA_BUF_TEMP,48);

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

  4. Владимир::

    Все получилось

  5. Сергей:

    Здравствуйте. Занимался с данными светодиодами, когда у вас данного видео не было. Максимальное количество на канал вроде бы 1024. Надо Хотел бы просто узнать можно ли по завершении определенного количества светодиодов переключиться на другой канал? Весь массив преобразованное видео читается с файла на флешь. Делал год назад на STM32F4. Так пока и не доделал. С данным чипом только начал, Прошу строго не судить.

  6. Сергей:

    Да, не дописал, на одном канале 500шт запустил,все работает по кругу.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*