5 years, 11 months ago.

Getting NeoPixels (WS2812) working on an STM32F103

Hello everyone!

This is my first time posting here. I have worked quite a bit with MCUs but I am just starting out with the MBed framework.

I have a Nucleo board (STM32F103) I have been trying, to no success, to get some WS2812 working with the MBED framework (Something very simple on Arduinos or bit-banging C)

But with MBed I have been quite disappointed so far (I have tried numerous libraries hosted by MBed online and none have worked so far.

The main ones I tried (and should be working considering their code) are: 1. NEOPIXEL : https://os.mbed.com/users/MightyPork/code/NeoPixel/ This one bit-bangs the LEDs. It uses nops for timing. I basically haven' t been able to get any of the timings right for my board... (Using optimisation flags O0 - much too big and O2 - reasonable timings, I had huge differences in timing) I ended up getting close to the required timings from the Datasheet, except if I add some more nops, they are not taken into account (except if I add a large amount but then it' s much too much)... And I can' t seem to get the timing required...

I then saw on the ARM help (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/CHDJJGFB.html) that nop is not necessarily a time consuming cmd... Great...

So I decided to try and use an SPI solution. I found this library, which seems to be the most popular one to use for WS2812 and MBed: 2. PIXELARRAY : https://os.mbed.com/users/JacobBramley/code/PixelArray/

I used the example code provided:

Eg: ... void generate(neopixel::Pixel * out, uint32_t index, uintptr_t extra) { uint32_t brightness = (index + extra) >> 3; out->red = ((index + extra) & 0x1) ? brightness : 0; out->green = ((index + extra) & 0x2) ? brightness : 0; out->blue = ((index + extra) & 0x4) ? brightness : 0; }

int main() { Create a temporary DigitalIn so we can configure the pull-down resistor. (The mbed API doesn't provide any other way to do this.) An alternative is to connect an external pull-down resistor. DigitalIn(p5, PullDown);

The pixel array control class. neopixel::PixelArray array(p5);

uint32_t offset = 0; while (1) { array.update(generate, 100, offset++); wait_ms(250); } ...

I plugged in a logic analyser and I don' t seem to be getting the whole commands sent out to the LED (let alone a proper whole byte)...

Could anyone shed some light on this please? I assume others have got it working (considering it's only WS2812 and there are a bunch of libraries) Is there some flag that I have overlooked to get either of these libraries working?

Thanks, J

1 Answer

5 years, 11 months ago.

Hello Jim,

In order to make high speed SPI possible with mbed the PixelArray library is utilizing the BurstSPI library. Unfortunately, BurstSPI doesn't support STM32F103 yet. However, according to the info here it seems that STM32F1 uses the same registers as the STM32F4 one. It means that the BurstSPI library in your project can be extended to support also the STM32F103 board by modifying the related files for example as follows:

PixelArray/BurstSPI/BurstSPI_Unsupported.cpp

#if !(defined(TARGET_KL25Z) || defined(TARGET_KL46Z))
#if !(defined(TARGET_LPC1768) || defined(TARGET_LPC1114) || defined(TARGET_LPC11U24) || defined(TARGET_LPC13XX) || defined(TARGET_LPC1549))
#if !(defined(TARGET_NUCLEO_L152RE) || defined(TARGET_STM32F4) || defined(TARGET_STM32F1))
 
#warning BurstSPI target not supported, reverting to regular SPI
 
#include "BurstSPI.h"
 
void BurstSPI::fastWrite(int data) {
    write(data);
}
 
void BurstSPI::clearRX( void ) {
 
}
#endif          //Freescale
#endif          //NXP
#endif          //NUCLEO

PixelArray/BurstSPI/BurstSPI_STM32F4.cpp

#if defined(TARGET_STM32F1) || defined(TARGET_STM32F4)
#include "BurstSPI.h"

void BurstSPI::fastWrite(int data)
{
    uint8_t*            pData = (uint8_t*) &data;
    SPI_HandleTypeDef*  hspi = &(_spi.spi.handle);

    hspi->Init.Mode = SPI_MODE_MASTER;
    hspi->TxXferCount = sizeof(data);;
    SPI_1LINE_TX(hspi);

    /* Check if the SPI is already enabled */
    if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
        __HAL_SPI_ENABLE(hspi); /* Enable SPI peripheral */

    while (hspi->TxXferCount > 0U)
    {
        /* Wait until TXE flag is set to send data */
        if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
        {
            if (hspi->Init.DataSize == SPI_DATASIZE_16BIT)
            {
                /* Transmit data in 16 Bit mode */
                hspi->Instance->DR = *((uint16_t*)pData);
                pData += sizeof(uint16_t);
            }
            else
            {
                /* Transmit data in 8 Bit mode */
                *((__IO uint8_t *) &hspi->Instance->DR) = (*pData);
                pData += sizeof(uint8_t);
            }

            hspi->TxXferCount--;
        }
    }
}

void BurstSPI::clearRX(void)
{
    //Check if the RX buffer is busy
    SPI_TypeDef*    spi = _spi.spi.handle.Instance;
    //While busy, keep checking
    while (spi->SR & SPI_SR_BSY)
    {
        // Check RX buffer readable
        while ((spi->SR & SPI_SR_RXNE) == 0);

        int dummy = spi->DR;
    }
}
#endif

Accepted Answer

Hello!

Thank you very much for your response, it made a lot of sense.

I went through and modified those files but now I get this compilation error:

BurstSPI_STM32F4.cpp:6:48: error: invalid cast from type 'spi_s' to type 'SPI_TypeDef*'

posted by Jim B 16 May 2018

Try to modify BurstSPI_STM32F4.cpp as below:

PixelArray/BurstSPI/BurstSPI_STM32F4.cpp

#if defined(TARGET_STM32F4) 

#include "BurstSPI.h"
 
void BurstSPI::fastWrite(int data) {    
    SPI_TypeDef *spi = (SPI_TypeDef *)(_spi.spi);
    // Check if data is transmitted
    while ((spi->SR & SPI_SR_TXE) == 0);
    spi->DR = data;
}
    
void BurstSPI::clearRX( void ) {
    //Check if the RX buffer is busy
    SPI_TypeDef *spi = (SPI_TypeDef *)(_spi.spi);
    //While busy, keep checking
    while (spi->SR & SPI_SR_BSY){   
        // Check RX buffer readable
        while ((spi->SR & SPI_SR_RXNE) == 0);
        int dummy = spi->DR;
    }
}

#elif defined(TARGET_STM32F1)

#include "BurstSPI.h"
 
void BurstSPI::fastWrite(int data) {  
    SPI_TypeDef *spi = (SPI_TypeDef *)(_spi.spi.handle.Instance);
    // Check if data is transmitted
    while ((spi->SR & SPI_SR_TXE) == 0);
    spi->DR = data;
}
    
void BurstSPI::clearRX( void ) {
    //Check if the RX buffer is busy
    SPI_TypeDef *spi = (SPI_TypeDef *)(_spi.spi.handle.Instance);
    //While busy, keep checking
    while (spi->SR & SPI_SR_BSY){   
        // Check RX buffer readable
        while ((spi->SR & SPI_SR_RXNE) == 0);
        int dummy = spi->DR;
    }
}

#endif
posted by Zoltan Hudak 16 May 2018

I found this issue on BurstSPI: https://os.mbed.com/users/Sissors/code/BurstSPI/issues/2

I ended trying both suggestions: SPI_TypeDef *spi = reinterpret_cast<SPI_TypeDef*&>(_spi.spi); SPI_TypeDef *spi = _spi.spi.handle.Instance;

To no success :(

posted by Jim B 16 May 2018

Oopps posted before I saw your reply!

I tried that as well and no success :/

posted by Jim B 16 May 2018

I have used a logic analyser to check the output

Before I modified BurstSPI_STM32F4.cpp I was getting some sort of output But since nothing is appearing...

posted by Jim B 16 May 2018

May we see your application code to understand how you send data? NOTE: To keep the format after pasting it to the edit box use tags as below.

<<code>>
Code of your program.
<</code>>
posted by Zoltan Hudak 16 May 2018

Yes of course,

here is my code (used some examples found on this site)

#include <mbed.h>
#include <neopixel.h>

#define NUM_LEDS        8
#define LED_PIN         PB_15


void setPixel(neopixel::Pixel * buffer, uint32_t index, uint8_t red, uint8_t green, uint8_t blue, uint32_t length)
{
    if (index >= length ) {
        return;
    }
    buffer[index].red = red;
    buffer[index].green = green;
    buffer[index].blue = blue;
}

int main() {

    // Create a temporary DigitalIn so we can configure the pull-down resistor.
    // (The mbed API doesn't provide any other way to do this.)
    // An alternative is to connect an external pull-down resistor.
    DigitalIn(LED_PIN, PullDown);

    // The pixel array control class.
    neopixel::PixelArray array(LED_PIN);
    neopixel::Pixel buffer[NUM_LEDS];

    while (1) {
        // All LEDs on
        for (int i = 0; i < NUM_LEDS; ++i) {
            setPixel(buffer, i,0xFF, 0xFF, 0xFF, NUM_LEDS);
        }

        array.update(buffer, NUM_LEDS);
        wait_ms(1000);

    }
    
}
posted by Jim B 16 May 2018

Hello Jim, Since the length of my response is limited to max 3000 characters a I was not able to add new code here. So I modified my original answer. I hope that this time it will do the job.

posted by Zoltan Hudak 17 May 2018

Hello Zoltan, thank you very much for taking the time to answer and respond to this issue. I have tried your modified code in the original answer, and BurstSPI::fastWrite compiles with no issue, except it is still hanging on:

.piolibdeps/BurstSPI_ID2973/BurstSPI_STM32F4.cpp:84:52: error: invalid cast from type 'spi_s' to type 'SPI_TypeDef*'

in BurstSPI::clearRX

posted by Jim B 17 May 2018

I'm sorry. It should compile now and I was able to see some pulses at the SPI MOSI pin with my logic analyzer.

posted by Zoltan Hudak 17 May 2018

Hello Zoltan, thanks a lot for your time on this matter. I was now able to compile! (On both the online Mbed compiler and my offline mbed setup)

I uploaded the code onto my board and it doesn't work :/ I hooked it up to my logic analyser and the timings are off. I used the code I posted above, plus the example code provided with the PixelArray library. Nothing worked. I tried using the online MBed compiler and my offline setup, no dice.

I think with the amount of hours I have put in trying to get this simple example to work, it has killed quite a bit of my motivation for mbed at the moment. I have been testing this framework recently and seemingly simple things turn out to be complicated. (I will come back to mbed again, as the concept does interest me and to practise and learn a framework that should be able to be applied to numerous devices - I might need to start on more simple examples than WS2812 LEDs ;D even though I find this to be a good example for testing out timings on MCUs)

I'll revert to another framework for the time being as I really wish to get one of my test ideas into a project and bring back some of that MCU fun.

As the code compiles with no errors, I have accepted your answer Zoltan, maybe someone else with a bit more know-how than myself with MBed will be able to get this working on the F103.

Thanks again for your time as otherwise I would have been even more lost

(as an aside the online MBed compiler also acts a bit odd... I imported the PixelArray library and it imported BurstSPI automatically but conveniently leaves out the BurstSPI_Unsupported.cpp and BurstSPI_STM32F4.cpp O.O probably because I only have a F103 board setup in the config but anyway... not very helpful when it includes all the other files for other boards instead...)

posted by Jim B 17 May 2018