- PVSM.RU - https://www.pvsm.ru -
Драйвер для STM32 для реализации протокола адресных светодиодов (WS2812, WS2811, SK6812, и т.д.), с рациональным использованием буферной памяти и DMA.
Ссылка на библиотеку на GitHub:
Ролик на YouTube:
На пин DIN первого светодиода (начало ленты) подаётся сигнал, формируемый STM32. Из-за разницы питающих напряжений, сигнал следует поднять до уровня 5 вольт с помощью специальной микросхемы-транслятора логики или с помощью настройки GPIO-пина в режиме Open Drain, подтянув его резистором.
ВАЖНО!
При использовании Open Drain нужно убедиться, что пин выдержит 5 вольт. Узнать это можно в даташите на свой МК.
Пример:
ОГРАНИЧЕНИЯ: Из-за особенностей таймеров, минимально стабильная частота работы микроконтроллера — 32 МГц.
Сперва нужно настроить таймер в режиме ШИМ. Обратите внимание на отмеченные стрелками настройки.
Отправка значений в таймер происходит с использованием DMA, поэтому настроим и этот блок.
Ножка должна иметь наивысшую скорость из доступных. Если выбран режим Open Drain, то не забудьте переключиться.
Также проверьте, что генерация DMA_Init стоит выше, чем TIM_Init. Иначе таймер не узнает про DMA, сигнал генерироваться не будет.
Сгенерируем код, добавим файлы [2] библиотеки в проект. Откроем .h-файл и посмотрим, что можно настроить.
#define WS2811 ///< Семейство: {WS2811S, WS2811F, WS2812, SK6812}
// WS2811S — RGB, 400kHz;
// WS2811F — RGB, 800kHz;
// WS2812 — GRB, 800kHz;
// SK6812 — RGBW, 800kHz
#define NUM_PIXELS 4 ///< Кол-во диодов в цепочке
// Гамма-коррекция, должна чинить красный и зелёный, пробуйте и смотрите
#define USE_GAMMA_CORRECTION 1
#define TIM_NUM 2 ///< Номер таймера
#define TIM_CH TIM_CHANNEL_2 ///< ШИМ-канал таймера
#define DMA_HANDLE hdma_tim2_ch2 ///< Канал DMA
// Канал DMA можно найти в main.c / tim.c
Теперь, для проверки, можно попробовать забилдить проект и посмотреть на доступные функции. Все методы возвращают enum-статусы.
typedef enum ARGB_STATE {
ARGB_BUSY = 0, ///< DMA-отправка в процессе
ARGB_READY = 1, ///< DMA Готов к отправке
ARGB_OK = 2, ///< Успешное выполнение функции
ARGB_PARAM_ERR = 3, ///< Ошибка входных параметров
} ARGB_STATE;
ARGB_STATE ARGB_Init(void); // Инициализация
ARGB_STATE ARGB_Clear(void); // Очистка ленты
ARGB_STATE ARGB_SetBrightness(u8_t br); // Установить глобальную яркость
ARGB_STATE ARGB_SetRGB(u16_t i, u8_t r, u8_t g, u8_t b); // Зажечь диод в RGB
ARGB_STATE ARGB_SetHSV(u16_t i, u8_t hue, u8_t sat, u8_t val); // Зажечь диод в HSV
ARGB_STATE ARGB_SetWhite(u16_t i, u8_t w); // Зажечь белый компонент (для RGBW)
ARGB_STATE ARGB_FillRGB(u8_t r, u8_t g, u8_t b); // Залить всё в RGB
ARGB_STATE ARGB_FillHSV(u8_t hue, u8_t sat, u8_t val); // Залить всё в HSV
ARGB_STATE ARGB_FillWhite(u8_t w); // Заливка белого компонента (для RGBW)
ARGB_STATE ARGB_Ready(void); // Получить статус DMA
ARGB_STATE ARGB_Show(void); // Обновить диоды
void main(void) {
ARGB_Init();
ARGB_Clear();
while (ARGB_Show() == ARGB_BUSY) ; // Вариант 1
ARGB_SetRGB(0, 255, 0, 128);
ARGB_SetHSV(1, 230, 250, 255);
while (!ARGB_Show()) ; // Вариант 2
ARGB_SetRGB(3, 200, 0, 200);
// Вариант 3:
while (ARGB_GetState() != ARGB_READY) ;
ARGB_Show();
}
Адресные светодиоды ленты используют для различной индикации, как бытовой, так и коммерческой. Ключевое отличие от обычных RGB-диодов в том, что их можно зажигать отдельно, каждый своим цветом.
Это поведение обусловлено тем, что у каждого диода стоит чип-драйвер. Снаружи, как в случае с WS2811, или внутри, как у WS2812 и остальных.
Чип принимает сигнал, запоминает первые импульсы, а остальные передаёт далее по цепочке.
Свечение каждого субпикселя кодируется 8 битами. Т.е. для RGB (WS281X) 24 бита, для RGBW (SK6812) 32 бита.
Код бита задаётся длиной импульса, то есть скважностью.
Существует и код RET — пауза, означающая конец передачи.
У всех контроллеров разные тайминги:
|
WS2811 (slow) |
WS2811 (fast, SET=1) |
WS2812(b) |
SK6812 |
Частота |
400 КГц |
800 КГц |
800 КГц |
800 КГц |
Период (Т) |
2,5 мкс |
1,25 мкс |
1,25 мкс |
1,25 мкс |
T0H |
0,5 мкс (20%) |
0,25 мкс (20%) |
0,35 мкс (28%) |
0,3 мкс (24%) |
T1H |
1,2 мкс (48%) |
0,6 мкс (48%) |
0,7 мкс (56%) |
0,6 мкс (48%) |
T0L |
2,0 мкс |
1,0 мкс |
0,8 мкс |
0,9 мкс |
T1L |
1,3 мкс |
0,65 мкс |
0,6 мкс |
0,6 мкс |
Допуск |
+/- 150 нс |
+/- 150 нс |
+/- 150 нс |
+/- 150 нс |
RET |
> 50 мкс (20Т) |
> 50 мкс (40Т) |
> 50 мкс (40Т) |
> 80 мкс (64Т) |
Большинство решений основаны на использовании пустых тактов. Это означает, что весь процессор тормозит на время отправки сигнала. Такой способ не только тратит уйму процессорного времени, но и рискует сломаться, в случае возникновения прерывания.
Посчитаем длину передачи сигнала на 1 диод: 1,25 мкс * 24 бит = 30 мкс.
Для n диодов: T = 30 * n + 50 мкс.
30 диодов — уже 1 миллисекунда.
Иными словами, протокол на задержках стоит использовать только для малого количества диодов, чтобы не мешать основной программе.
Именно из-за этой проблемы я в своё время впервые обратился к STM32.
В других вариантах используется шина SPI, которую настраивают на частоту 800 КГц. Я не проверял, но многие пишут про ощутимую потерю точности сигнала.
Что же делать?
В почти всех микроконтроллерах STM32 существует блок DMA (Direct Memory Access). Он позволяет передавать данные между периферией и памятью в разных направлениях без участия процессора.
В качестве исполнительной периферии используется таймер, настроенный в режиме ШИМ.
Любой способ передачи сигнала подразумевает буфер, в котором хранятся значения скважности сигнала.
В сети встречаются множество вариантов буфера сразу под все диоды. Чаще всего скважность 8-битная, поэтому такой будет весить N диодов * 24 байт. Уже под 100 диодов он займёт более 2 КБ ОЗУ.
А если записывать скважность с шириной 32 бита, как требуют некоторые серии МК, под 100 диодов буфер будет более 9 КБ.
Реализация моего метода была придумана не мной. В ней очень хитро используется память.
Буфер здесь двойной. Первый имеет размер N диодов * 3 байта. В нём хранится цвет в представлении RGB.
Второй буфер — для скважностей. Он фиксированный, занимает всего 48 байт, или 64 байта для RGBW. В него вмещаются всего 2 диода.
Прерывания DMA позволяют заполнять одну часть буфера, пока отправляется вторая. Используя такой подход, можно растягивать цепочку диодов почти до бесконечности, покуда хватит памяти для первичного буфера или частоты обновления.
Дело в том, что адресные диоды воспринимают сигнал, опираясь на напряжение своего питания.
Открыв даташит на WS2812b, мы увидим такие строки:
|
Min |
Max |
VIH |
0,7 VDD |
— |
VIL |
— |
0,3 VDD |
Это — границы восприятия сигнала. Иными словами, при питании от 5.1—5.2 Вольт, минимальный уровень сигнала — 3.57 Вольт.
Так как STM32 выдаёт сигнал величиной 3.0—3.3 Вольта, его нужно увеличить.
Вариантов это сделать несколько:
Уменьшить напряжение питания ленты
Отрегулировать напряжение на БП
Для небольшого отрезка запитать всю ленту через диод
Отрезать первый светодиод, и запитать только его через диод
Поднять потенциал GND микроконтроллера (подробнее [3])
Воспользоваться преобразованием логики
Так как предполагается использование в коммерческих проектах, где необходима не только надёжность, но и возможность беспроблемной замены отдельных компонентов пользователем, то самый безопасный вариант — последний.
Способы преобразования логики были рассмотрены в данной статье [4]. В ней сделаны выводы о том, что самый подходящий преобразователь — SN74LVC.
Однако, при его отсутствии или для удешевления BOM, можно воспользоваться режимом Open Drain.
DMA настраивается в кольцевом режиме передачи. Новые транзакции будут возникать до тех пор, пока не будут остановлены вручную в коде.
DMA генерирует прерывания каждую половину транзакции. Поэтому наш буфер размером в 2 диода. Пока идёт передача сигнала для первого диода, просчитывается и загружается сигнал для второго.
1-я половина |
2-я половина |
Счётчик |
LED [0] |
LED [1] |
0 |
LED [2] |
LED [1] |
1 |
LED [2] |
LED [3] |
2 |
LED [4] |
LED [3] |
3 |
LED [4] |
RET {1} |
4 |
RET {2} |
RET {1} |
5 |
RET {2} |
DMA_STOP |
6 |
Состояние буфера. Bold — текущая передача
В первую очередь я столкнулся с согласованием логики. От USB компьютера всё работало, а от любого блока питания — нет. Решение пришло после пары тыков вольтметром и чтения даташита. Оказалось, что порты компьютера под просадкой выдавали порядка 4.6 Вольт, что есть 3,2 Вольта логической единицы. А все блоки питания стандартно выдавали в районе 5.2 Вольт, поэтому лента даже не зажигалась.
Вторую проблему принесла библиотека HAL. Дело в том, что у базовых таймеров нет IDLE-состояния ног. Поэтому, после остановки таймера, ножка входила в Z-состояние [5], а подтяжка выкидывала сигнал вверх.
На осцилограмме видно последовательность: сигнал, RET (пауза), Z-state, запуск таймера, сигнал, RET.
Это приводило к тому, что первый диод в ленте считывал этот импульс и зажигался.
Даже с помощью остановки-запуска таймера невозможно было достичь нормальной работы. Либо из-за массивности HAL-функций, либо из-за особенностей работы периферии, возникала небольшая временная задержка, которой было достаточно для зажигания этого диода.
Решение было не таким очевидным, но нашлось довольно быстро. В функции HAL_TIM_PWM_Stop_DMA
была обнаружена такая строчка:
/* Disable the Capture compare channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_DISABLE);
Это и есть отключение GPIO-канала таймера. После её удаления, удалось достичь стабильной работы. Поэтому пришлось скопировать весь код этого метода к себе и немного отредактировать.
Третья проблема — фундаментальная. Заключается в особенностях работы таймеров. Если задать частоту ниже 32 МГц, то ощутимо теряется точность сигнала. Например, для 8 МГц: Для получения частоты 800 КГц задаётся ARR = 9. Значит регистру CCRx доступны только значения 0..8. А это примерно 100 КГц точности или разброс в 10 мкс, что уже очень критично.
Вариант борьбы с этим — уничтожение этой концепции генерации сигнала, переход на ассемблерные задержки или прерывания.
В любом случае, зачастую в проектах используют максимальную тактовую частоту, а снижают её только для энергосбережения, когда светодиоды уже должны быть выключены.
Другой вариант — использование отдельного МК, например F0 или G0, как UART/SPI/I2C -> ARGB драйвер. Такие проекты уже существуют [6].
Мой выбор — принять все ограничения, а для open-source сделать пометку.
Максимальная частота обновления адресной ленты упирается напрямую в протокол. Посчитаем предел для 25 FPS.
25 Гц -> 40 мс = 40.000 мкс. Передача для 1 диода занимает 30 мкс.
Таким образом, предельное значение — порядка 1300 шт.
https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led [8]
https://narodstream.ru/stm-urok-119-ws2812b-lenta-na-umnyx-svetodiodax-rgb-chast-2/ [9]
http://www.normandled.com/upload/201808/WS2815%20LED%20Datasheet.pdf [12]
https://cdn-shop.adafruit.com/product-files/2757/p2757_SK6812RGBW_REV01.pdf [13]
Автор: Дмитрий
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/gadzhety/374954
Ссылки в тексте:
[1] github.com: https://github.com/Crazy-Geeks/STM32-ARGB-DMA
[2] файлы: https://crazygeeks.ru/stm32-argb-lib/#lib-link
[3] подробнее: https://alexgyver.ru/ws2812_guide/#%D0%9E%D0%A1%D0%9E%D0%91%D0%95%D0%9D%D0%9D%D0%9E%D0%A1%D0%A2%D0%98_%D0%9F%D0%9E%D0%94%D0%9A%D0%9B%D0%AE%D0%A7%D0%95%D0%9D%D0%98%D0%AF
[4] данной статье: https://crazygeeks.ru/log-level-test/
[5] Z-состояние: https://ru.wikipedia.org/wiki/%D0%92%D1%8B%D1%81%D0%BE%D0%BA%D0%BE%D0%B8%D0%BC%D0%BF%D0%B5%D0%B4%D0%B0%D0%BD%D1%81%D0%BD%D0%BE%D0%B5_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5
[6] уже существуют: https://www.bhencke.com/serial-led-driver
[7] https://crazygeeks.ru/stm32-argb-lib/: https://crazygeeks.ru/stm32-argb-lib/
[8] https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led: https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led
[9] https://narodstream.ru/stm-urok-119-ws2812b-lenta-na-umnyx-svetodiodax-rgb-chast-2/: https://narodstream.ru/stm-urok-119-ws2812b-lenta-na-umnyx-svetodiodax-rgb-chast-2/
[10] https://cdn-shop.adafruit.com/datasheets/WS2812.pdf: https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
[11] https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf: https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
[12] http://www.normandled.com/upload/201808/WS2815%20LED%20Datasheet.pdf: http://www.normandled.com/upload/201808/WS2815%20LED%20Datasheet.pdf
[13] https://cdn-shop.adafruit.com/product-files/2757/p2757_SK6812RGBW_REV01.pdf: https://cdn-shop.adafruit.com/product-files/2757/p2757_SK6812RGBW_REV01.pdf
[14] Источник: https://habr.com/ru/post/664934/?utm_source=habrahabr&utm_medium=rss&utm_campaign=664934
Нажмите здесь для печати.