STM Урок 145. WS2812B. Новые эффекты и оптимизация кода. Часть 1



Очень немалый интерес у посетителей ресурса вызвал урок 142 по подключению светодиодных лент количеством 150 и 300 светодиодов к контроллеру STM32. Поэтому хотелось бы немного продолжить и развить данную тему.

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

Мне также понравилась данная модель, так как для наших конкретных целей она подходит больше, чем модель RGB, которую мы также в своём проекте без внимания не оставим.

Также мне в его проекте понравилось не только это, а также настройка DMA на 8-битный режим, что позволило в значительной мере сэкономить пространство в оперативной памяти.

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

Немного расскажу о цветовом пространстве HSV.

В отличии от RGB, в котором мы имеем три настройки, каждая из которых управляет интенсивностью свечения трёх цветов — красного, зелёного и синего, цветовая модель HSV имеет также три настройки, только немного других.

Первая — это Hue — цветовой тон.

Вторая настройка — это Saturation или насыщенность.

Третья — Value или яркость.

Модель HSV делится также на несколько модификаций, но нас это сегодня не интересует. Нас интересует возможность удобного использования данной модели.

Цветовой тон измеряется в градусах и тем самым обеспечивается плавный переход между основными цветами и их смешивание.

0o — красный, 120o — зеленый, 240o — синий.

Посмотрим визуализацию цветовой модели HSV в виде цилиндра

 

 

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

 

 

Мы будем тон назначать также в градусах, от 0 до 359, а другие два параметра, не как принято от 0 до 1, а как нам привычнее — от 0 до 255, чтобы уместиться в байт.

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

Проект сделаем из проекта урока 142 с именем WS2812_300 и назовём его, например WS2812_HSV.

А тренироваться будем на ленточке маленькой с количеством светодиодов в ней 144, не снимать же ленту с фронтона. Потом отнесём прошитый контроллер на чердак и подключим его к основным («парадным») лентам.

Откроем наш проект в проектогенераторе Cube MX и в настройках DMA таймера 2 колонке с памятью изменим тип данных на Byte

 

 

Сгенерируем проект, откроем его в Keil, подключим файл ws2812.c, настроим программатор на автоперезагрузку и попробуем собрать проект.

Если всё нормально, то идём в файл ws2812.h и первым делом там пока настроим проект на количество светодиодов 144

 

#define LED_COUNT 144

 

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

 

 

 

Добавим также две структуры для хранения параметров различных цветовых моделей

 

 

В файле main.c из функции main() удалим вот этот код

 

ws2812_prepareValue(255, 0, 0,

0, 255, 0,

0, 0, 255,

128, 128, 0,

0, 128, 128,

128, 0, 128,

192, 64, 0,

192, 0, 64,

64, 192, 0,

0, 192, 64,

0, 64, 192,

64, 0, 192,

192, 32, 32,

32, 32, 192,

32, 192, 32

);

ws2812_setValue();

ws2812_light();

 

Также удалим весь пользовательский код из бесконечного цикла.

Идём теперь в файл ws2812.c и изменим тип переменных в массиве основного буфера для DMA

 

uint8_t BUF_DMA [ARRAY_LEN] = {0};

 

добавим ещё один буфер для хранения массива полных настроек RGB, то есть полностью для всех светодиодов ленты

 

 

Аналогичный буфер добавим только в виде массива переменных типа структуры для HSV, а также ещё одну глобальную переменную того же типа

 

 

После функции ws2812_pixel_rgb_to_buf_dma добавим функцию, которая будет забирать значения из массива HSV, конвертировать в модель RGB, более понятную ленте. Пока в нашей функции мы только объявим некоторые переменные и массив

 

 

Начнём писать тело данной функции.

Создадим цикл на количество итераций, равное количеству наших светодиодов в ленте

 

 

Начнём писать тело данной функции.

В цикле начнём конвертацию.

 

 

Если параметр насыщенности будет 0, то все цвета пространства RGB примут значение яркости

 

 

В противном случае уже будет другой расчёт. Для начала посчитаем смещение

 

 

Смещение потребуется для вычисления величины соседнего цвета. Смысл такой: Если цвет строго красный, полнонасыщенный, то будет R = яркость, G = 0 и B = 0. Но если цвет будет уходить в сторону зелёного, то R убавляться будет не сразу, а только ровно после достижению середины между этими цветами на цветовом круге, то есть 60 градусов. Если будет H = 60 градусов, то и R и G будут равны яркости. А уж затем по мере увеличения H к 120 градусам G останется на отметке 255, а R будет плавно убывать. Аналогично с остальными основными цветами. То есть base_V или смещение нужно будет для определения яркости соседнего цвета. Только равно оно ему не будет. base_V — это минимальное значение яркости. Если значение тона находится в секторе между красным и зелёным, то это значение основного цвета, не входящего в данный сектор, то есть синего. В случае максимального значения насыщенности данное значение будет равно, соответственно, 0. По мере уменьшения насыщенности у нас будут добавляться постепенно значения цветов всех цветов, отличных от основного, то есть тог, к которому ближе заданный тон, поэтому в формуле определения смещения мы учитываем также и величину насыщенности. Не совсем понятен, может быть, битовый сдвиг влево на 8 бит. Это сделано для того, чтобы более быстро разделить на 256.

Затем разобьём наш круг на секторы по 60 градусов с помощью оператора вариантов, то есть разделим значение цветового тона на 60, тем самым определим, в каком из этих шести секторов находится наш тон

 

 

Так как в результате деления мы получаем целое число без остатка, как раз на секторы всё и разделится. В первый кейс (вариант) попадут значения от 0 до 59, во второй — от 60 до 119, в третий — от 120 до 179 и т.д.

Осталось лишь посчитать теперь каждый из трёх цветов в каждом варианте. Начнём с первого

 

 

В данном секторе, как я и объяснял до этого, красный цвет будет равен значению основной яркости, зелёный рассчитывается по формуле, учитывающей смещение, а синий ему равен.

Далее следующий сектор

 

 

Аналогично вычислим значение трёх составляющих остальных четырёх секторов

 

 

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

 

 

Ниже добавим ещё одну функцию, в которой значения параметров всех светодиодов, находящиеся в массиве RGB, будут заполнять буфер DMA

 

 

Функцию ws2812_prepareValue удалим вместе с телом, а также удалим её прототип из заголовочного файла.

Функции тестов типа ws2812_test0x также все удалим вместе с телами и прототипами, иначе у нас код не будет собираться. Тесты мы быстро напишем.

Начнём с первого.

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

Первый тест будет выводить радугу в количестве стольких штук или повторений, сколько будет задано в первом параметре функции теста, а во втором параметре мы зададим количество полных прогонов этой радуге по кругу, то есть количество кругов, так как ленты свои мы располагаем замкнуто, чтобы присутствовал эффект непрерывности.

Первым делом в нашем тесте мы заполним буфер в формате HSV, так как при использовании данного цветового пространства нам стало делать это легко, мы справимся с этим быстро.

В самом низу файла начнём добавлять наши тесты

 

 

Выйдем из цикла и добавим теперь цикл, в котором с некоторой задержкой наш буфер будет продвигаться по «кругу» и в каждой итерации мы будем копировать наш буфер в буфер DMA и затем зажигать нашу ленту нужными цветами

 

 

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

 

 

Тем самым мы выводим две радуги и вращаем их по кругу дважды.

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

 

 

Очень даже плавные переходы. Представьте себе, если бы нам пришлось всё это писать с использованием цветового пространства RGB.

В следующей части урока мы напишем несколько тестов и проверим их на практике.

 

 

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

 

 

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

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

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

Импульсный источник питания 5 В в 40A 200 Вт можно приобрести здесь 5 В в 40A 200 Вт

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

 

 

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

STM WS2812B. Новые эффекты и оптимизация кода

 

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

STM WS2812B. Новые эффекты и оптимизация кода

7 комментариев на “STM Урок 145. WS2812B. Новые эффекты и оптимизация кода. Часть 1
  1. Алек:

    Сори что суда (не нашёл почту), А не планируется уроки по STM32+norFlash? вроде я понимаю что это FMC но так не понял как работает, зарание спасибо

  2. alex:

    не могли бы вы объяснить небольшую проблему с дма.
    суть в следующем, первые 2 старта DMA, откуда то появляется лишняя информация в виде появления на первом месте непонятных мне данных, поэтому при отправке буфера на, скажем, 7 байтов в таймер, надо писать 8, а после 2-х стартов все в норму приходит. (у вас вроде была такая проблема, приходилось вместо 1, два писать)

  3. Владислав:

    Здравствуйте.
    На 407VE в CubeMX в настройка DMA нет возможности раздельно изменять Data Width. Только если вместе с Peripheral. Подскажите как быть в такой ситуации?

  4. Александр:

    Добрый вечер, установил длинну в кубе байт каки в уроке но когда меняю массив uint16_t BUF_DMA на uint8_t начинается хардкор))
    весь код только с uint16_t BUF_DMA работает, единственное что не меняются все светодиоды — пришлось делать
    HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_2, (uint32_t*) &BUF_DMA,ARRAY_LEN*2+DELAY_LEN);
    все 10 раз проверил — хоть убей

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

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

*