Library for addressable STM32 LEDs

Driver for STM32 to implement the protocol targeted LEDs (WS2812, WS2811, SK6812etc.), with rational using buffer memory and DMA.

Link to the library on GitHub:

Video on YouTube:

Connection and setup

On a pin DIN the first LED (beginning of the tape) receives a signal generated by the STM32. Due to the difference in supply voltages, the signal follows raise to a level of 5 volts using a special translator chips logic or by setting the GPIO pin in the mode open drainby pulling it up with a resistor.

IMPORTANT!

When using Open Drain, you need to make sure that the pin can withstand 5 volts. You can find out in the datasheet on your MK.

Example:

Timer pin without tolerance
Timer pin without tolerance
5 volt tolerant pins
5 volt tolerant pins

Timer setting in CubeMX

RESTRICTIONS: Due to the nature of the timers, the minimum stable frequency of the microcontroller is 32 MHz.

First you need to set the timer in mode PWM. Pay attention to the settings marked with arrows.

Sending values ​​to the timer is done using DMAso let’s configure this block as well.

The leg must have top speed from available. If the mode is selected open drainthen don’t forget to switch.

Also check that the generation DMA_Init worth higher than TIM_Init. Otherwise, the timer will not know about DMA, the signal will not be generated.

Library setup

Let’s generate the code, add files libraries to the project. Let’s open .h-file and see what you can customize.

#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

Function Reference

Now, to check, you can try score project and look at the available features. All methods return enum statuses.

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);  // Обновить диоды

Usage example

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();
}

Description

Addressable tape LEDs are used for various indications, both household and commercial. key difference from conventional RGB diodes in that they can be lit separatelyeach with its own color.

This behavior is due to the fact that each diode has a chip driver. Outside, as in the case of WS2811, or inside, as in WS2812 and others.

The chip receives a signal, remembers the first pulses, and transmits the rest further along the chain.

WS2811
WS2811
WS2812
WS2812

Data protocol

The glow of everyone subpixel is encoded 8 bits. Those. for RGB (WS281X) 24 bitsfor RGBW (SK6812) 32 bits.

The bit code is given by the pulse length, i.e. duty cycle.

Signal encoding
Signal encoding

There is also a code RET – a pause, indicating the end of the transmission.

For all controllers different timings:

WS2811 (slow)

WS2811 (fast, SET=1)

WS2812(b)

SK6812

Frequency

400 kHz

800 kHz

800 kHz

800 kHz

Period (T)

2.5 µs

1.25 µs

1.25 µs

1.25 µs

T0H

0.5 µs (20%)

0.25 µs (20%)

0.35 µs (28%)

0.3 µs (24%)

T1H

1.2 µs (48%)

0.6 µs (48%)

0.7 µs (56%)

0.6 µs (48%)

T0L

2.0 µs

1.0 µs

0.8 µs

0.9 µs

T1L

1.3 µs

0.65 µs

0.6 µs

0.6 µs

Tolerance

+/- 150 ns

+/- 150 ns

+/- 150 ns

+/- 150 ns

RET

> 50 µs (20T)

> 50 µs (40T)

> 50 µs (40T)

> 80 µs (64T)

Code Example "0"
Code example “0”
Code Example "one"
Code example “1”

Implementation on STM32

Most of the solutions are based on the use of empty cycles. It means that the whole the processor slows down for the duration of the signal. This method not only wastes a lot of CPU time, but also risks breaking if an interrupt occurs.

Let’s calculate the signal transmission length for 1 diode: 1.25 µs * 24 bits = 30 µs.
For n diodes: T=30*n+50 ms.

30 diodes – already 1 millisecond.

In other words, the delay protocol should only be used for small number of diodes so as not to interfere with the main program.

It was because of this problem that I first turned to the STM32.

Other options use a tire SPIwhich is tuned to the frequency 800 kHz. I did not check, but many write about a noticeable loss in signal accuracy.

What to do?

In almost all STM32 microcontrollers, there is a block DMA (Direct Memory Access). It allows you to transfer data between periphery and memory in different directions without the participation of the processor.

Used as an execution peripheral timerconfigured in mode PWM.

buffer array

Any method of signal transmission implies a buffer in which values ​​are stored duty cycle signal.

There are many buffer options on the web just below all diodes. Most often, the duty cycle is 8-bit, so this will weigh N diodes * 24 bytes. Already under 100 diodes he will take more than 2 KB of RAM.

And if you write the duty cycle with the width 32 bitsas required by some MK series, under 100 diodes the buffer will be more 9 KB.

The implementation of my method was not invented by me. She is very cunningly memory is used.

buffer here double. The first one has the size N diodes * 3 bytes. It stores the color in the view RGB.

The second buffer is for duty cycles. It is fixed and takes only 48 bytesor 64 bytes for RGBW. It contains all 2 diodes.

Interrupts DMA allow one part of the buffer to be filled while the other part is being sent. Using this approach, it is possible to stretch the diode chain almost up to infinityas long as there is enough memory for the primary buffer or refresh rate.

Logic transformation

The fact is that the addressable diodes perceive the signal based on the voltage of their supply.

Opening the datasheet WS2812bwe will see lines like this:

Min

Max

VIH

0.7VDD

VIL

0.3VDD

These are the limits of signal perception. In other words, when powered by 5.15.2 Volt, minimum signal level — 3.57 Volt.

Since the STM32 outputs a signal of magnitude 3.03.3 Volta, you need it increase.

There are several options to do this:

  1. Reduce the supply voltage of the tape

    • adjust power supply voltage

    • For a small segment, ask the entire tape through the diode

    • Cut off first LED, and power only it through the diode

  2. Raise the GND potential of the microcontroller (more)

  3. Take advantage of logic transformation

Since it is intended to be used in commercial projects where not only reliability is required, but also the ability to seamlessly replace individual components by the user, the safest option is the last one.

Ways to transform logic have been discussed in this article. It concludes that the most suitable converter is SN74LVC.

However, in its absence or to reduce the cost of BOM, you can use the mode open drain.

Buffer bypass

DMA is configured in ring transmission mode. New transactions will occur until they are stopped manually in code.

DMA generates interrupts every half transactions. So our buffer is 2 diodes. While the signal is being transmitted first diode, the signal is calculated and loaded for second.

1st half

2nd half

Counter

LED [0]

LED [1]

0

LED [2]

LED [1]

one

LED [2]

LED [3]

2

LED [4]

LED [3]

3

LED [4]

RET {1}

4

RET {2}

RET {one}

5

RET {2}

DMA_STOP

6

Buffer state. Bold – current transmission

Development issues

First of all, I encountered logic matching. Everything worked from a USB computer, but not from any power supply. The decision came after a couple of pokes with a voltmeter and reading the datasheet. It turned out that the computer ports under the drawdown gave out about 4.6 Volt, what is 3.2 Volt logical unit. And all power supplies were standardly issued in the area 5.2 Volt, so the tape did not even light up.

The second problem was brought by the library HAL. The fact is that basic timers do not have IDLE states legs. Therefore, after stops timer, the leg was included in Z-stateand the pull-up threw the signal up.

The oscillogram shows the sequence: signal, RET (pause), Z-state, launch timer, signal, RET.

This caused the first diode in the tape to read this pulse and ignited.

Even with the help stop-start timer, normal operation could not be achieved. Either due to the massiveness of the HAL functions, or due to the peculiarities of the peripherals, there was a small time delay, which was enough to ignite this diode.

The solution was not so obvious, but it was found pretty quickly. In function HAL_TIM_PWM_Stop_DMA the following line was found:

/* Disable the Capture compare channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_DISABLE);

That’s what it is disable GPIO channel timer. After removing it, it was possible to achieve stable operation. Therefore, I had to copy the entire code of this method to myself and edit it a little.

The third problem is fundamental. It lies in the features of the timers. If you set the frequency below 32 MHzthen it is noticeably lost accuracy signal. For example, for 8 MHz: To obtain a frequency of 800 kHz, set ARR = 9. Means register CCRx only values ​​are available 0..8. And this is about 100 kHz accuracy or spread in 10 µswhich is very critical.

An option to combat this is to destroy this concept of signal generation, switch to assembler delays or interrupts.

In any case, projects often use the maximum clock frequency, and reduce it only to save energy, when the LEDs should already be turned off.

Another option is to use a separate MC, such as F0 or G0, as UART/SPI/I2C -> ARGB driver. Such projects already exist.

My choice is to accept all restrictions, and make a mark for open-source.

Speed ​​rating

Maximum frequency updates of the address tape rests directly on the protocol. Let’s calculate the limit for 25 fps.

25 Hz -> 40ms = 40.000 µs. Transfer for 1 diode takes 30 µs.
Thus, the limiting value is of the order 1300 pcs.

Links

  1. https://crazygeeks.ru/stm32-argb-lib/

  2. https://github.com/Crazy-Geeks/STM32-ARGB-DMA

  3. https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led

  4. https://narodstream.ru/stm-urok-119-ws2812b-lenta-na-umnyx-svetodiodax-rgb-chast-2/

  5. https://cdn-shop.adafruit.com/datasheets/WS2812.pdf

  6. https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf

  7. http://www.normandled.com/upload/201808/WS2815%20LED%20Datasheet.pdf

  8. https://cdn-shop.adafruit.com/product-files/2757/p2757_SK6812RGBW_REV01.pdf