Драйвер светодиодной ленты на WS2812B для STM32F103C8

Приобрел я на Aliexpress так называемую адресную светодиодную ленту на светодиодах WS2812B. В отличие от обычной RGB-ленты, тут есть возможность управлять цветом и яркостью свечения каждого светодиода отдельно. Однако, это накладывает некоторые сложности в управлении этой лентой. Для того, чтобы зажечь нужный цвет, необходимо загрузить в ленту последовательность бит данных, содержащую информацию о цвете свечения каждого светодиода. Есть готовые библиотеки, и для всяких Ардуино, и для тех же STM32, которые реализуют цифровой протокол обмена данными с WS2812B. Однако, я хочу изобрести свой велосипед в этой области, ну и заодно немного поупражняться в программировании, поэтому, поехали! 😉

Содержание:

Введение

Интерфейс светодиодов WS2812B является довольно жестким в отношении к временным интервалам, поэтому его реализация в виде Bit-banging-а является еще той задачей. В результате выбор пал на вариант с хитрой настройкой таймера в режиме ШИМ и контроллера прямого доступа к памяти (DMA). Идея была позаимствована с https://habr.com/post/257131/. Там была описана реализация для микроконтроллера STM32F407VE. В моем случае был взят STM32F103C8 в составе отладочной платы с Али за 2 бакса:

Рис. 1. Отладочная плата с STM32F103C8T6 на борту

Описание библиотеки управления

Описывать идею реализации протокола на таймере не буду, всегда можно обратиться к первоисточнику на Хабре, перейду сразу к конкретной реализации библиотеки. Из периферии задействован таймер-счетчик TIM2, контроллер DMA1 и, в зависимости от конфигурации, один из портов PA0, PA1, PA2 или PA3. Библиотека состоит из 3-х файлов:

  • ws2812b.h — прототипы функций и настройка количества светодиодов в ленте
  • ws2812b_config.h — тонкая настройка библиотеки, включает подстойку таймингов и выбор ножки порта, через которую производится управление лентой
  • ws2812b.c — сама реализация протокола

Библиотека использует только CMSIS без всякой высокоуровневой требухи в виде SPL. Только регистры, только хардкор!!!

Начнем с простого: файл ws2812b.h:

С помощью WS2812B_NUM_LEDS указываем количество светодиодов в ленте, в данном случае 144. Функция ws2812b_init() производит инициализацию интерфейса, ее надо вызвать один раз в самом начале программы, ws2812b_buff_claer() очищает внутренний буфер библиотеки, присваивая всем светодиодам выключенное состояние. С помощью ws2812b_set(int pixn, uint8_t r, uint8_t g, uint8_t b) присваиваем pixn-му светодиоду компоненты R, G, и B. Нумерация светодиодов идет с нуля, максимальное значение номера пикселя WS2812B_NUM_LEDS — 1. Функция ws2812b_send() запускает процесс отправки внутреннего буфера в светодиодную ленту, причем, если предыдущая операция отправки еще не завершена, то не оказывает ни какого влияния на процесс и возвращает единицу. С помощью ws2812b_is_ready() можно узнать текущий статус интерфейса: если эта функция вернула 0, то интерфейс сейчас занят операцией обмена, если 1, то интерфейс свободен.

Следующий файл — ws2812b_config.h:

С помощью WS2812B_TIMER_AAR производим точную настройку периода ШИМ-сигнала таймера, чтоб он соответствовал скорости передачи данных. Значение 0x59 соответствует периоду в 1.25 мкс при тактировании таймера частотой 72 МГц.

Если мы используем инвертирующий согласователь логических уровней между микроконтроллером и лентой, то сигнал с выхода микроконтроллера необходимо инвертировать, чтоб в итоге получить управляющие импульсы в нужной полярности. Для этих целей служит #define WS2812B_OUTPUT_INVERSE.

Далее идет выбор пина порта WS2812B_OUTPUT_PAx, с помощью которого будет производиться управление лентой. Варианта тут только 4-ре: PA0, PA1, PA2 или PA3. Выбирай тот, что удобней)).

Перейдем к основному: ws2812b.c. Немного поговорим о том, как реализована логика интерфейса WS2812B. В функции отправки данных в ленту производится настройка таймера TIM2 в режим ШИМ с частотой 800 КГц, что соответствует скорости передачи данных в ленту. После этого включаем DMA, который после каждого события обновления счетчика будет запихивать новое значение в регистр ШИМ-а таймера. После того, как все запихано, возникает прерывание DMA об окончании передачи, в котором производится останов TIM2, установка его периода, равного длительности сигнала RET, включение прерывания при обновлении значения регистров счетчика и снова пуск TIM2. После истечения периода RET, возникает прерывание от TIM2, в котором происходит финализация процесса передачи данных и установка флага готовности шины к следующей передачи данных.

В самом начале файла находится куча define-ов, с помощью которых происходит определение конкретных регистров таймера и DMA в соответствии с выбранной ножкой микроконтроллера WS2812B_OUTPUT_PAx. Конструкция монструозная, но как сделать проще не придумал:

Затем идет расчет длины буфера, в котором хранятся значения цвета светодиодов:

Каждый светодиод добавляет 24 байта к занимаемой памяти. Так, для светодиодной ленты, состоящей из 144-х пикселей буфер будет занимать 144*24+2=3458~=3.4 Кбайт ОЗУ!!! AVR-щики в шоке!!! Ардуинщики чешут репу. Много это или мало, зависит от конкретной задачи: где-то такой расход памяти допустим, а где-то нет.

Поехали далее. Функция инициализации интерфейса выглядит следующим образом:

Первым делом включаем тактирование всех нужных нам модулей. Затем переключаем GPIO в режим альтернативных функций и задаем максимальную частоту вывода 10Мгц. После этого идет настройка таймера, DMA, и включение прерываний от этих модулей.

Дальше функция запуска отправки данных:

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

После завершения передачи данных возникает прерывание от DMA DMA1_Channelx_IRQHandler():

В этом прерывании запускается следующая стадия работы логики интерфейса,  а именно формирование сигнала RET. В функции bus_retcode() происходит вся настройка таймера для этой стадии. После того, как таймер отсчитал чуть больше 50мкс, возникает прерывание TIM2_IRQHandler():

Здесь мы сбрасываем все флаги прерываний таймера TIM2 и останавливаем его, а так же устанавливаем флаг готовности интерфейса flag_rdy.

Из интересного осталось только рассмотреть функцию установки цвета пикселя светодиодной ленты ws2812b_set():

На вход принимаем номер светодиода и компоненты RGB. Согласно даташиту на светодиоды, байты цвета идут в следующей последовательности: зеленый, красный, синий, а последовательности бит от старшего к младшему. Массив led_array[] как раз тот самый массив, который выводится через DMA в ШИМ-регистр таймера.

Подключаем ленту

Светодиоды WS2812B питаются напряжением 5 вольт. Максимальный ток потребления одного светодиода при включении на полную яркость всех трех цветов составляет 60mA, таким образом, метр ленты со 144 может сожрать почти 9 ампер тока, что, мягко говоря, дофига и еще немного. Так что если есть желание жечь по полной, нужно организовать хороший теплоотвод ленты и подавать ей питание не только в самом начале, но и еще в нескольких точка по всей длине ленты.

Теперь что касается цифрового входа. Потенциал логической единицы начинается с 0,7Vdd, и при питании ленты от 5-и вольт это будет составлять 3,5 вольт. Так как выводы у STM32 3,3 вольтные, то напрямую подключить ленту к микроконтроллеру не получится, нужен согласователь уровней. Причем это не занудные заморочки любителя «все делать по инструкции», а реальная необходимость: на практике при питании от 5-и вольт лента у меня не завелась при прямом подключении к 3,3 вольтной логике. Чтож, не проблема, сделаем согласователь уровней на полевом транзюке:

Рис. 2. Согласование уровней с помощью транзистора 2N7002

И вот тут нам как раз и пригодится возможность инвертирования управляющего сигнала с выхода микроконтроллера с помощью #define WS2812B_OUTPUT_INVERSE.

Рис. 3. Вот так вся конструкция выглядит в живую

В моем случае в качестве управляющей ножки микроконтроллера была выбрана PA1. Подключаем вывод GPIO (см. рис. 2) к PA1, WS2812B_DATA к управляющему входу ленты, +5V к плюс пяти вольтам, землю к земле))) Одеваем каску и включаем питание)) Если ни чего не бахнуло, то переходим к следующему шагу 😉

Примеры на IAR ARM

В качестве примера я придумал несколько демонстраций работы с библиотекой. Среда разработки — IAR ARM 7.50.2. Начнем с простого: зажжем все светодиоды красным светом на половину яркости.  Вот минимальный код запуска:

Из комментариев, думаю, все ясно без пояснений.

В живую это выглядит вот так:

Рис. 4. Подсветка для дома красных фонарей готова))

Перейдем к радуге. Не вдаваясь в подробности, для того, чтобы нарисовать радугу нам нужно уметь делать преобразование из цветового пространства HSV в RGB. Вот немного Википедии. Конвертер позаимствовал из одной статьи на Хабре, о чем честно сообщаю в комментариях))

Изменяя H-компоненту от 0 до 360, получаем самую обычную радугу 😉

Рис. 5. Радуга

Для справки: на яркости 100 лента в режиме радуги потребляет всего 130мА. А вот на полной яркости ест все 3 ампера.

Ну и небольшая демка с плавным зажиганием-погасанием радуги:

Вот демка:

На видео видна некая неравномерность скорости затухания, но в реале этого не видно.

На этом все, спасибо за внимание! 🙂

Ссылки:

Проект на github: https://github.com/DiMoonElec/ws2812b_stm32f103c8_demo

Зеркало на Я.Диск: https://yadi.sk/d/K4gsANjZ3Z4Rby

Статья на Хабре, с которой было кое-что позаимствовано: https://habr.com/post/257131/

Метки: , , , . Закладка Постоянная ссылка.

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

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