{"id":4349,"date":"2018-10-31T20:00:57","date_gmt":"2018-10-31T19:00:57","guid":{"rendered":"https:\/\/msalamon.pl\/?p=4349"},"modified":"2025-12-27T20:08:31","modified_gmt":"2025-12-27T19:08:31","slug":"addressable-ws2812b-leds-on-stm32-part-1","status":"publish","type":"post","link":"https:\/\/msalamon.pl\/en\/addressable-ws2812b-leds-on-stm32-part-1\/","title":{"rendered":"Addressable WS2812B LEDs on STM32, Part 1"},"content":{"rendered":"\n<p>Inevitably, the time is coming when a red truck will roll out of the TV, and at every turn we\u2019ll hear a beautiful song about a broken heart that the community considers the creation of a holiday mood. Yes, Christmas is coming soon and, of course, there are no holidays without <\/p>\n\n\n\n<!--more-->\n\n\n\n<p>lights! Every electronics enthusiast has been pondering for months how to surprise the neighbor. Ordinary RGB LEDs have unfortunately long become commonplace, but \u201caddressable\u201d WS2812B LEDs come to the rescue, and for several years they have been reigning when it comes to lighting effects.<\/p>\n\n\n\n<p>This article is part of the series:7<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"http:\/\/msalamon.pl\/adresowalne-diody-ws2812b-na-stm32-cz-1\/\">Part 1<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/msalamon.pl\/adresowalne-diody-ws2812b-na-stm32-cz-2\/\">Part 2<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/msalamon.pl\/adresowalne-diody-ws2812b-na-stm32-cz-3\/\">Part 3<\/a><\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=ws2812b&amp;utm_content=nucleo\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"400\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg\" alt=\"\" class=\"wp-image-1593\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-768x256.jpg 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">What are WS2812B LEDs?<\/h1>\n\n\n\n<p>These are RGB LEDs in a 5050 package with a built-in PWM controller with 8-bit resolution per color. PWM duty values are delivered digitally via just a single data line. We communicate with WS2812B LEDs using one-wire NZR communication similar to 1-Wire, except the communication for the LEDs is unidirectional. This means we can send the LED information about how it should be set, but we won\u2019t learn from it what state it is in. Fortunately, that\u2019s not a particular problem. What does it look like? Of course, the documentation explains everything best (<a data-e-disable-page-transition=\"true\" class=\"download-link\" title=\"\" href=\"https:\/\/msalamon.pl\/download\/410\/?tmstv=1766861371\" rel=\"nofollow\" id=\"download-link-410\" data-redirect=\"false\"><br>WS2812B Datasheet<\/a><br>). Even though it\u2019s very short, I\u2019ll summarize the most important information here.<\/p>\n\n\n\n<p>The transmitted signals, just like for 1-Wire, have predefined timing constants. For complete operation we only need 3 signals: logic 0, logic 1, and RESET.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings.png\"><img loading=\"lazy\" decoding=\"async\" width=\"508\" height=\"337\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings.png\" alt=\"\" class=\"wp-image-398\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings.png 508w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings-300x199.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings-360x240.png 360w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings-24x16.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings-36x24.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings-121x80.png 121w\" sizes=\"auto, (max-width: 508px) 100vw, 508px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>From the figure above it follows that the communication frequency is around 800 kHz.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout.png\"><img loading=\"lazy\" decoding=\"async\" width=\"516\" height=\"306\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout.png\" alt=\"\" class=\"wp-image-399\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout.png 516w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout-300x178.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout-24x14.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout-36x21.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_pinout-135x80.png 135w\" sizes=\"auto, (max-width: 516px) 100vw, 516px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>An important feature of the LEDs is the ability to chain them. The LEDs have 4 pins. Two are for power (in the range 3.5\u20135.3 V) and two for data\u2014input and output. After an LED receives all the bytes it needs about colors, it automatically passes the input signal to its output. Thanks to this, they can theoretically be connected into infinite chains. <\/p>\n\n\n\n<p>The manufacturer also notes that a 100 nF capacitor should be placed next to each LED in the chain. Strips available from the Middle Kingdom come with such capacitors.<\/p>\n\n\n\n<p>The data write sequence to the LEDs starts with a RESET signal lasting at least 50 \u00b5s. Next come the data for the LEDs in GRB order from the first to the last LED. It\u2019s important that the data cannot have any interruptions during transmission. From the moment an anomaly occurs, the colors will start to differ from what was intended.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">STM32 code<\/h2>\n\n\n\n<p>Today I\u2019ll be basing this on the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/stm32f103c8t6-dev-board-2\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ws2812b&amp;utm_content=Text\">STM32F103C8T6 found in the cheap boards from China widely known as BluePill<\/a>. These Chinese modules are very popular and inexpensive. I have a few myself, so why not use them.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/sklep.msalamon.pl\/produkt\/stm32f103c8t6-dev-board-2\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=ws2812b&amp;utm_content=bluepill\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"400\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2.jpg\" alt=\"\" class=\"wp-image-1263\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2-768x256.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2-24x8.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2-36x12.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/11\/BluePill-baner2-160x53.jpg 160w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>The microcontrollers don\u2019t have hardware support for the interface required by WS2812B LEDs. We have to handle it another way. The first thing that comes to mind is bit-banging the GPIO. On AVR, programmers manage via the assembly instruction \u2018nop\u2019, which allows you to wait the required time to change the state in a more or less controlled way. But using nops on STM32 won\u2019t make me write a universal and portable library\u2014we have a lot of different MCU configurations and use many different clock speeds. I need something better. You can guard the GPIO with a Timer and change its state after specific times. Maybe that would work, but I didn\u2019t even try that method. I came across the idea on the internet of using the MOSI signal from the SPI interface. It seemed interesting, so I started digging into it. The wiring diagram for the LED strip is trivially simple. I will use SPI number 1. <strong>Warning! Do not power directly from the STM32 module.<\/strong> WS2812B LEDs require around 50 mA per LED at maximum brightness. A chain of 60 LEDs\/meter will therefore require around 3 A\/meter at full, bright white! Supply power from an external, capable power supply.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi.png\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"227\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi-300x227.png\" alt=\"\" class=\"wp-image-400\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi-300x227.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi-768x580.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi-24x18.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi-36x27.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi-106x80.png 106w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_schematic_spi.png 927w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Pin PA5 serves as SCK of the SPI1 interface. It won\u2019t be needed, but in this case it cannot be used for anything other than SPI.<\/p>\n\n\n\n<p>To control the pulse width of the NRZ signal, I had to use an entire SPI byte as a single bit for the LED. Thus the duration of one byte should be about 1.25 \u00b5s, so one SPI bit should last about 0.156 \u00b5s. This gives an SPI clock of 6.4 MHz. In the F103C8T6 the SPI prescaler values to choose from are 2, 4, 8, 16, 32, 64, 128 and 256, which correspondingly gives the MCU clock:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>2 * 6.4 = 12.8 MHz<\/li>\n\n\n\n<li>4 * 6.4 = 25.6 MHz<\/li>\n\n\n\n<li>8 * 6.4 = 51.2 MHz<\/li>\n\n\n\n<li>16 * 6.4 = 102.4 MHz\u2026<\/li>\n<\/ol>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler.png\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"614\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler.png\" alt=\"\" class=\"wp-image-401\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler.png 640w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler-300x288.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler-24x24.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler-36x36.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler-83x80.png 83w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Frankly, these values are not great. It\u2019s hard to set them like that in Cube. I encountered a claim on a certain popular forum that it MUST BE 6.4 MHz, PERIOD! Well, unfortunately, it doesn\u2019t have to be. We have something called tolerance (after all, there\u2019s so much fighting for it everywhere), and for WS2812B the tolerance of input signals can be \u00b10.15 \u00b5s, i.e., \u00b10.66 MHz. It will be much easier to get 6 MHz on SPI, e.g., by using a 48 MHz MCU clock and the SPI prescaler set to 8. Cube additionally suggests the SPI baud rate after selecting the prescaler. If you want a higher\/lower MCU clock, look for one where the prescaler gives about 6 MHz on SPI.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset.png\"><img loading=\"lazy\" decoding=\"async\" width=\"641\" height=\"102\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset.png\" alt=\"\" class=\"wp-image-402\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset.png 641w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset-300x48.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset-24x4.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset-36x6.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_spi_prescaler_preset-160x25.png 160w\" sizes=\"auto, (max-width: 641px) 100vw, 641px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>6 MHz means bit durations of:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>1 bit \u2013 0.166 \u00b5s<\/li>\n\n\n\n<li>2 bits \u2013 0.333 \u00b5s<\/li>\n\n\n\n<li>3 bits \u2013 0.499 \u00b5s<\/li>\n\n\n\n<li>4 bits \u2013 0.666 \u00b5s<\/li>\n\n\n\n<li>5 bits \u2013 0.833 \u00b5s<\/li>\n\n\n\n<li>6 bits \u2013 0.999 \u00b5s<\/li>\n\n\n\n<li>7 bits \u2013 1.166 \u00b5s<\/li>\n\n\n\n<li>8 bits \u2013 1.333 \u00b5s<\/li>\n<\/ol>\n\n\n\n<p>Do any of the times above match the WS2812B timing table?<\/p>\n\n\n\n<p>For T0H, 2 bits fit (0.35 \u00b5s \u00b10.15), and for T1H, 5 bits fit (0.9 \u00b5s \u00b10.15). The rest naturally fits too \ud83d\ude09<\/p>\n\n\n\n<p>Since SPI transfers from the MSB (thanks, Piotr!), the definitions of the logic states will look like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#define zero 0b11000000\n#define one 0b11111000\n<\/pre>\n\n\n\n<p>For convenience, I created a structure that contains the color data for a single LED.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">typedef struct ws2812b_color {\n  uint8_t red, green, blue;\n} ws2812b_color;\n<\/pre>\n\n\n\n<p>The basic library contains only three self-explanatory functions.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void WS2812B_Init(SPI_HandleTypeDef * spi_handler);\nvoid WS2812B_SetDiodeColor(int16_t diode_id, ws2812b_color color);\nvoid WS2812B_Refresh();<\/pre>\n\n\n\n<p>Initialization consists only of assigning a pointer to SPI to the library.<\/p>\n\n\n\n<p>Setting an LED is done by the LED number in the chain and providing a struct variable with that LED\u2019s colors.<\/p>\n\n\n\n<p>Refresh sets the SPI buffer bytes according to the LED colors. It creates a big buffer containing all the data for all LEDs. Unfortunately, this eats huge amounts of RAM, but there\u2019s a remedy for that, which I\u2019ll get to in a moment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sending data<\/h2>\n\n\n\n<p>I think sending data to the LEDs is worth discussing. It\u2019s an interesting challenge to have the data reach the LEDs in one uninterrupted stream with a small MCU overhead.<\/p>\n\n\n\n<p>Preparing data in the buffer according to colors involves quite a lot of bit shifts, which, fortunately, are light for the MCU.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">for(uint8_t i = 0; i &amp;lt; 72; i++)\n\tbuffer[i] = 0x00;\n\nfor(uint16_t i=0, j=72; i&amp;lt;WS2812B_LEDS; i++)\n{\n\t\/\/GREEN\n\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t{\n\t\tif((ws2812b_array[i].green &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\tbuffer[j] = zero;\n\t\telse\n\t\t\tbuffer[j] = one;\n\t\tj++;\n\t}\n\n\t\/\/RED\n\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t{\n\t\tif((ws2812b_array[i].red &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\tbuffer[j] = zero;\n\t\telse\n\t\t\tbuffer[j] = one;\n\t\tj++;\n\t}\n\n\t\/\/BLUE\n\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t{\n\t\tif((ws2812b_array[i].blue &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\tbuffer[j] = zero;\n\t\telse\n\t\t\tbuffer[j] = one;\n\t\tj++;\n\t}\n}\n\nHAL_SPI_Transmit(hspi_ws2812b, buffer, (WS2812B_LEDS+3) * 24, 1000);\n<\/pre>\n\n\n\n<p>And this works, but not quite correctly. Unfortunately, the last 3\u20134 LEDs on a 100-LED strip most often have random colors. This is probably because an interrupt occurs during the SPI transfer. It\u2019s probably the SysTick Timer interrupt, but I didn\u2019t check\u2014there\u2019s no point. You can disable it, but I don\u2019t recommend it. What now? Every STM32 has something called DMA. In plain terms, it\u2019s a peripheral with direct memory access. You can delegate to it an operation involving memory or another peripheral. Thanks to this, the MCU has time to perform other tasks, such as the SysTick interrupt that disrupted the data transfer to the LEDs.<\/p>\n\n\n\n<p>Configuring DMA in Cube is simple. In the Configuration tab and the System table, there\u2019s a DMA configuration button. Add a DMA configuration for SPI1_TX, which was configured earlier. Set the priority to Very High because this is a key operation. In the settings at the bottom, select Normal mode and memory increment, which in this case will be the RAM buffer.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma.png\"><img loading=\"lazy\" decoding=\"async\" width=\"629\" height=\"477\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma.png\" alt=\"\" class=\"wp-image-403\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma.png 629w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma-300x228.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma-24x18.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma-36x27.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_cube_dma-105x80.png 105w\" sizes=\"auto, (max-width: 629px) 100vw, 629px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>How should it work now? Only the data transfer call over SPI in the refresh function changes. Now it looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">HAL_SPI_Transmit_DMA(hspi_ws2812b, buffer, (WS2812B_LEDS+3) * 24);\nwhile(HAL_DMA_STATE_READY != HAL_DMA_GetState(hspi_ws2812b-&amp;gt;hdmatx));<\/pre>\n\n\n\n<p>The second line waits for the transfer to complete. You can skip it, but you should do so consciously and be careful not to cause a \u201ccollision\u201d on the DMA, because everything will break.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"176\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-1024x176.png\" alt=\"\" class=\"wp-image-404\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-1024x176.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-300x51.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-768x132.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-1536x263.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-24x4.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-36x6.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer-160x27.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_data_transfer.png 1680w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Now the LEDs work like a charm. Each one has exactly the color I intended.<\/p>\n\n\n\n<p>Sending data for 35 LEDs (that\u2019s how many I have connected for testing) is just over 1 ms, specifically 1.22 ms. The time needed to prepare the send buffer is 0.27 ms of MCU life. For simple applications, you can wait out the transfer time as I did. However, it\u2019s worth trying to use that time for other tasks. That\u2019s almost a millisecond with just 35 LEDs. Controlling 100 LEDs, the saved time will be about 2.8 ms, and a thousand LEDs is already 28 ms. Quite a saving, because during that time you can refresh a TFT instead of idly waiting.<\/p>\n\n\n\n<p>Unfortunately, a huge downside of this solution is the data buffer passed to the SPI transfer. Each LED consumes 24 bytes of RAM. For the STM32F103C8T6 the compiler reports memory issues already at around 300 LEDs. A bit sad \ud83d\ude41<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reducing RAM usage<\/h2>\n\n\n\n<p>There is a way! Every STM32 not only has DMA but also a few interesting interrupts that it can trigger during its operation.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Half-transfer<\/li>\n\n\n\n<li>Transfer complete<\/li>\n\n\n\n<li>Transfer Error<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"240\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-1024x240.png\" alt=\"\" class=\"wp-image-406\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-1024x240.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-300x70.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-768x180.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-1536x360.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-24x6.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-36x8.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half-160x38.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_half.png 1680w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>The first two will be perfect. After all, you can create a small data buffer, run cyclic DMA, and when the half-transfer interrupt occurs, replace the first half of the buffer. Brilliant! Note also that preparing a complete buffer takes significantly less time than sending half of that buffer. The MCU should manage it with a finger in\u2026 GND \ud83d\ude09<\/p>\n\n\n\n<p>Important: The function that starts the SPI transfer via DMA takes the size of the buffer holding the data, not the total amount of data you want to send! Starting DMA in circular buffer mode will keep sending data until you stop it manually. Therefore, in the half-transfer and full-transfer interrupts, you need to count how many LEDs\u2019 data have already been sent and stop the DMA after the last batch.<\/p>\n\n\n\n<p>I\u2019ll shorten the data buffer to 48 bytes, i.e., it will fit the data for two LEDs. This will pack nicely. The operating scheme is as follows:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Load 2*24 bytes of the reset signal and start cyclic DMA transmission.<\/li>\n\n\n\n<li>Half-transfer trigger \u2013 load the next 24 bytes into the first half of the buffer.<\/li>\n\n\n\n<li>Full-transfer trigger \u2013 data of the first LED into the second half of the buffer<\/li>\n\n\n\n<li>Half-transfer trigger \u2013 data of the second (even) LED into the first half of the buffer<\/li>\n\n\n\n<li>Full-transfer trigger \u2013 data of the third (odd) LED into the second half of the buffer<\/li>\n\n\n\n<li>Repeat 4 and 5 until all LEDs have been sent<\/li>\n\n\n\n<li>Enjoy the effect<\/li>\n<\/ol>\n\n\n\n<p>A fun fact. The HAL library is written in such a way that the callbacks of individual DMA interrupts are declared in it with the <em>weak<\/em> symbol. This means that you can override them in your source files, but they must have the same name, arguments, and return type. ST wrote HAL so that you don\u2019t have to worry about enabling the appropriate DMA interrupts via bits and registers. If we declare our own functions appropriate to the interrupt we need, inside the <em>HAL_SPI_Transmit_DM<\/em> function this will be detected and the library will activate the appropriate interrupts for us. You don\u2019t have to worry about anything. Nice, isn\u2019t it? The only thing to remember is to enable the global DMA interrupt in Cube and assign it a priority. Moving on to my code:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void WS2812B_Refresh()\n{\n\tCurrentLed = 0;\n\tResetSignal = 0;\n\n\tfor(uint8_t i = 0; i &amp;lt; 48; i++)\n\t\tbuffer[i] = 0x00;\n\n\tHAL_SPI_Transmit_DMA(hspi_ws2812b, buffer, 48); \/\/ Additional 3 for reset signal\n\twhile(HAL_DMA_STATE_READY != HAL_DMA_GetState(hspi_ws2812b-&amp;gt;hdmatx));\n}\n\nvoid HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi)\n{\n\tif(hspi == hspi_ws2812b)\n\t{\n\t\tif(!ResetSignal)\n\t\t{\n\t\t\tfor(uint8_t k = 0; k &amp;lt; 24; k++) \/\/ To 72 impulses of reset\n\t\t\t{\n\t\t\t\tbuffer[k] = 0x00;\n\t\t\t}\n\t\t\tResetSignal = 1; \/\/ End reset signal\n\t\t}\n\t\telse \/\/ LEDs Odd 1,3,5,7...\n\t\t{\n\t\t\tif(CurrentLed &amp;gt; WS2812B_LEDS)\n\t\t\t{\n\t\t\t\tHAL_SPI_DMAStop(hspi_ws2812b);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tuint8_t j = 0;\n\t\t\t\t\/\/GREEN\n\t\t\t\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t\t\t\t{\n\t\t\t\t\tif((ws2812b_array[CurrentLed].green &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\t\t\t\tbuffer[j] = zero;\n\t\t\t\t\telse\n\t\t\t\t\t\tbuffer[j] = one;\n\t\t\t\t\tj++;\n\t\t\t\t}\n\n\t\t\t\t\/\/RED\n\t\t\t\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t\t\t\t{\n\t\t\t\t\tif((ws2812b_array[CurrentLed].red &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\t\t\t\tbuffer[j] = zero;\n\t\t\t\t\telse\n\t\t\t\t\t\tbuffer[j] = one;\n\t\t\t\t\tj++;\n\t\t\t\t}\n\n\t\t\t\t\/\/BLUE\n\t\t\t\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t\t\t\t{\n\t\t\t\t\tif((ws2812b_array[CurrentLed].blue &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\t\t\t\tbuffer[j] = zero;\n\t\t\t\t\telse\n\t\t\t\t\t\tbuffer[j] = one;\n\t\t\t\t\tj++;\n\t\t\t\t}\n\t\t\t\tCurrentLed++;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)\n{\n\tif(hspi == hspi_ws2812b)\n\t{\n\t\tif(CurrentLed &amp;gt; WS2812B_LEDS)\n\t\t{\n\t\t\tHAL_SPI_DMAStop(hspi_ws2812b);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\/\/ Even LEDs 0,2,0\n\t\t\tuint8_t j = 24;\n\t\t\t\/\/GREEN\n\t\t\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t\t\t{\n\t\t\t\tif((ws2812b_array[CurrentLed].green &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\t\t\tbuffer[j] = zero;\n\t\t\t\telse\n\t\t\t\t\tbuffer[j] = one;\n\t\t\t\tj++;\n\t\t\t}\n\n\t\t\t\/\/RED\n\t\t\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t\t\t{\n\t\t\t\tif((ws2812b_array[CurrentLed].red &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\t\t\tbuffer[j] = zero;\n\t\t\t\telse\n\t\t\t\t\tbuffer[j] = one;\n\t\t\t\tj++;\n\t\t\t}\n\n\t\t\t\/\/BLUE\n\t\t\tfor(int8_t k=7; k&amp;gt;=0; k--)\n\t\t\t{\n\t\t\t\tif((ws2812b_array[CurrentLed].blue &amp;amp; (1&amp;lt;&amp;lt;k)) == 0)\n\t\t\t\t\tbuffer[j] = zero;\n\t\t\t\telse\n\t\t\t\t\tbuffer[j] = one;\n\t\t\t\tj++;\n\t\t\t}\n\t\t\tCurrentLed++;\n\t\t}\n\t}\n}\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Results of the reduced buffer<\/h2>\n\n\n\n<p>What do the waveform and the individual stages of preparing and sending data from the buffer look like now?<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"240\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-1024x240.png\" alt=\"\" class=\"wp-image-408\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-1024x240.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-300x70.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-768x180.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-1536x360.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-24x6.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-36x8.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer-160x38.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2018\/10\/ws2812b_timings_dma_small_buffer.png 1680w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>The total frame transfer time has not changed significantly. Overall, the transfer increased slightly to 1.23 ms. The time required to send half the buffer (24 bytes) via DMA is 31 \u00b5s. Very little. Preparing the next batch of 24 bytes of data takes the MCU only 7 \u00b5s. That gives 26 \u00b5s of CPU time saved per LED, which it can use for something else. Too little? With 100 LEDs that\u2019s 2.6 ms, while with 1000 we have 26 ms of CPU time. The transfer time differences compared to sending one large buffer are small. But what RAM savings! 912 bytes for a full buffer vs. 48 bytes for chunking\u2014and that\u2019s with just 35 LEDs. Increasing the number of lit points in the chain and sticking with the concept of one big buffer causes the buffer size to grow drastically. 100 LEDs is already about 2.4 kB of data. Meanwhile, using a small buffer and half-transfer DMA interrupts, the buffer remains unchanged! Beautiful.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>WS2812B LEDs are great. With a really minimal number of connections, we can set <strong>EVERY<\/strong> LED individually. It\u2019s true there\u2019s no actual addressing of the LEDs here, but you can easily move through them in the buffer. We achieve this effect without multiplexing and without separating color channels. Control may seem troublesome, but skillful use of standard interfaces in STM32 allows for trouble-free control of really long chains. The use of SPI presented in this post has one basic drawback. The pin responsible for the SCK of the serial interface is unused and can\u2019t be used for anything else (at least not that I know of). When creating a packed and complicated project, that pin could come in handy. Fortunately, most projects I know using flashy combinations on LEDs don\u2019t have too many circuits connected to the MCU. Go ahead and create your own! In two weeks I\u2019ll present some ready-made lighting effects that will certainly come in handy on the Christmas tree.<\/p>\n\n\n\n<p>Thank you for reading this post. If you like this topic, let me know in the comments. I\u2019d also be grateful for suggestions of topics you\u2019d like me to cover.<\/p>\n\n\n\n<p>The code is, as usual, available on my GitHub: <a href=\"https:\/\/github.com\/lamik\/WS2812B_STM32_HAL\" target=\"_blank\" rel=\"noopener\">link<\/a><\/p>\n\n\n\n<p><span>If you noticed any error, disagree with something, would like to add something important, or simply think you\u2019d like to discuss this topic, write a comment. Remember that the discussion should be polite and in accordance with the rules of the Polish language.<\/span><\/p>\n\n\n\n<p class=\"has-text-align-left\">This article is part of the series:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"http:\/\/msalamon.pl\/adresowalne-diody-ws2812b-na-stm32-cz-1\/\">Part 1<\/a><\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"http:\/\/msalamon.pl\/adresowalne-diody-ws2812b-na-stm32-cz-2\/\">Part 2<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/msalamon.pl\/adresowalne-diody-ws2812b-na-stm32-cz-3\/\">Part 3<\/a><\/li>\n<\/ul>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;4349&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;0&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;0&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;0&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;0\\\/5 - (0 votes)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;title&quot;:&quot;Addressable WS2812B LEDs on STM32, Part 1&quot;,&quot;width&quot;:&quot;0&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} - ({count} {votes})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 0px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 19.2px;\">\n            <span class=\"kksr-muted\"><\/span>\n    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Inevitably, the time is coming when a red truck will roll out of the TV, and at every turn we\u2019ll hear a beautiful song about a broken heart that the community considers the creation of a holiday mood. Yes, Christmas is coming soon and, of course, there are no holidays [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3029,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[160],"tags":[175,176,174,177],"class_list":["post-4349","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stm32","tag-electronics","tag-programming","tag-stm32","tag-stm32cubemx"],"_links":{"self":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4349","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/comments?post=4349"}],"version-history":[{"count":3,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4349\/revisions"}],"predecessor-version":[{"id":4457,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4349\/revisions\/4457"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media\/3029"}],"wp:attachment":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media?parent=4349"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/categories?post=4349"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/tags?post=4349"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}