Getting started with Neopixel (on STM32F4)

Sunday, March 29, 2015

I recently discovered Neopixels from Adafruit. These are cheap and simple to use little RGB LED modules. Multiple form factors are available : single LED, board with 8 LEDs, rings, strips, matrix,…). They can be driven by a lot of different processors (ranging from little 8-bits microcontroller to the BealgleBone Black, goind through Arduinos, for example.

You can think of lot of applications for this kind of modules. On the net, I found people that use them to create high-tech jewelry, a luminous drum kit, clocks, and a lot of other very funny creations.

I found that so funny that I wanted to buy one of these modules. I chose the 16 LED ring. It is cheap, not too large, not so small, not too power-hungry and… quite cute!

As I didn’t want to buy directly from Adafruit (they are located in the US, and I live in Belgium), I ordered it from MC-Hobby. It is a Belgian shop, which resells a goot part of the catalog from Adafruit. I didn’t have any issue with the order, and it was pretty fast! Indeed, in the same order, I added the new RaspberryPi 2, which was out of stock nearly anywhere. But it didn’t prevent MC-Hobby from delivering the 2 items in 2 days! (ordered Sunday evening, delivered Tuesday afternoon).

The RaspberryPi is not recommended to drive the NeoPixels. In fact, the signal on 1 wire needs a very good timing accuracy (400ns and 800ns pulse width, +/-150ns). The RaspberryPi running Linux cannot achieve such accuracy, as it is not a hard real-time kernel. Arduinos seem to be often used with Neopixels, but I don’t have one. But I have some development boards for STM32 processors on hand.

Good! I’m ready to start with Neopixels!

In this first article, I will describe my setup, and a little quick and dirty program that will make yours LEDs brighten!

Hardware setup

Let’s start with the step I’m less comfortable with : the electronic. Fortunately, this is quite simple. Adafruit provides a very complete guide about it. I manage to use my LED ring by following this guide to the letter.

The wiring is very simple : 2 power wires (5v) and one signal wire. Note that there are 3 difficulties:

Adafruit advises to add a resistor between the processor and the device (for the signal wire) and one capacitor to the power supply terminals. I assumed I know what I do, that I’m impatient, and I didn’t mount these components. It works, but it is not recommended. My processor cannot output enough power for the device to work correctly. I had to use an external power supply. I took care to connect the ground from the development board to the ground of the external power supply. Neopixel works on 5V, and my processor on 3.3V. I should have used intermediate component between the CPU and the device in order to convert the 3.3V to 5V but… it worked without it.

In any cases, I recommend you to read carefully the documentation from Adafruit about the installation and the connection of these devices because I’m really not an expert, and I know that my setup is not the best one.

Finally, here is my setup:

It is not easy to take a picture of the device because it is very bright! On the picture, you can see my power supply set to ~5V, the multimeter to check the voltage, my development board with its JTAG emulator, and, finally, the shining NeoPixel device.

The software

Now that all these details have been taken care of, let’s go the the next step : the programming!

As I said in the introduction, I’ll use a development board base on the processor STM32F4. It’s a relatively powerful CPU running at 168Mhz with 192KB of RAM memory and 1MB of FLASH memory, a lot of timers, communication ports and other kinds of peripherals. I would say that it is a tank to kill a fly… Such a big processor is not needed to drive Neopixels, and the same result could be obtained with a little 8-bits processor @16Mhz. I decided to use this board because I had it on hands, and I know it well.

The very first program I wrote is a “quick and dirty” program, written very quickly in order to see a result as fast as possible, because I wanted to confirm that my setup was good, and that I understood the way the device is working. In fact, I started from an example project delivered with the development board.

For this example, the control signal from the CPU to the Neopixel is connected on PA6 on the processor. Let’s start by initializing this pin:

void LEDGpio_Init(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); }

As I do not use an OS, I’ll have to handle all the timings manually. Thus, I need a “Sleep()” function. The easiest way to implement it to to write a loop that does nothing:

void Delay(int counter) { while(counter–); }

The difficulty is to know the execution time of this loop, which is needed for a precise timing. It’s not easy to know in advance this timing because of the compiler optimizations, cache memories, parallel execution of the instructions,… But we’ll figure that out!

Then, I’ll need to generate the correct signals on the control pin in order to drive the LEDs of the ring. The signal to generate is composed of 24 bits of data (8 bits for red, 8 bits for green, and 8 bits for green) for each LED. I’ll need to send these 3 bytes (3*8 bits) for each LED, meaning that for 16 LED, I’ll need to generate 48 bytes (3 * 16 Bytes) of data. Before sending the next frame, the signal must be held low during 50µs.

As the signal transmit the clock and the signal on 1 wire, it must continuously go from low to high and from high to low levels. It is the duration of these pulses that indicates the value of each bit:

To write a “1” : logical 1 during 800ns and logical 0 during 450ns To write a “0” : logical 1 during 400ns and logical 0 during 850ns

Knowing that, we can write functions that will configure a LED to red, green and blue:

Red

void draw_red(void) { char i; /* 0 */ for(i = 0; i < 8; i++) { GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET); Delay(11); GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET); Delay(30); }

/* 1 */
for(i = 0; i < 8; i++)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
    Delay(28);
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
    Delay(13);
}

/* 0 */
for(i = 0; i < 8; i++)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
    Delay(11);
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
    Delay(30);
}

}

Green:

void draw_green(void) { char i; /* 1 */ for(i = 0; i < 8; i++) { GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET); Delay(28); GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET); Delay(13); }

/* 0 */
for(i = 0; i < 8; i++)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
    Delay(11);
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
    Delay(30);
}
    
/* 0 */
for(i = 0; i < 8; i++)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
    Delay(11);
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
    Delay(30);
}

}

And blue

void draw_blue(void) { char i; /* 0 */ for(i = 0; i < 8; i++) { GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET); Delay(11); GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET); Delay(30); }

/* 0 */
for(i = 0; i < 8; i++)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
    Delay(11);
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
    Delay(30);
}

/* 1 */
for(i = 0; i < 8; i++)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
    Delay(28);
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
    Delay(13);
}

}

And finally, here is the main(), which will set all the LEDs, alternatively in red, then green and then blue:

int main(void) { char i; char c = 0; KEYGpio_Init();

while (1)
{
    for(i = 0; i < 16; i++)
    {
        switch(c)
        {
            case 0 :
                draw_red();	
                c = 1;
                break;
            case 1:
                draw_green();
                c = 2;
                break;
            case 2:
                draw_blue();
                c = 0;
                break;
        }
    }        
    Delay(5000000);			
}

}

This is a very simple program. Now I know that it is working correctly, I’ll be able to improve the implementation by using a PWM timer with DMA. This will be so sexy! To be continued…

EmbeddedSTM32Neopixel

JF

I am passionate about IT, (embedded) software development and open source technologies in general. I’m mainly working on the InfiniTime project , an open source firmware for the PineTime smartwatch from Pine64 .

ESP32, ESP-IDF, CMake & CLion