[F4] Neopixel driver using hardware timers

What are you developing?
flyboy74
Posts: 31
Joined: Fri Jan 03, 2020 10:12 am

Re: [F4] Neopixel driver using hardware timers

Post by flyboy74 »

stevestrong wrote: Sun Apr 26, 2020 9:06 am I think we have to thank to @flyboy74 because he shared his code.
On the other hand, a short description of the environment limitations (which core, with or w/o HAL) under the code works to be included in the readme would be helpful for the reader.
Steve if you can give me suggestions of what should be in the read me. People seem to be focused on the code that has nothing to do with the code that drives the LEDs

The 3 functions of code to note are

void Neopixel_setup(void) setups the timer by writing the needed values to the timer register using the macros in stm32f4xx.h (doesn't use HAL but rather just the pointers in the header file) and also calculates the register values needed (auto reload and capture/compare) for the timer to create the exact timings based upon the #defined T0H, T1H, T0L and T1L

void show_neopixels() will write the data stored in uint8_t LED_data[180] to the LED strip

void TIM4_IRQHandler(void)
the interupt handler that does the controlling of the timer to create the needed pulse train.

The code is designed not to be a plug and play finished driver but a working proof of the concept that neopixel can be driven by timers to produce very exact timing with MCUs as fast as a STM32F4 (not sure if slower MCU will be fast enough)

Though all my googling of drivers for Neopixels it seems they are a pain in the butt to drive and most drivers are not pretty in there implementation and are also hard to reconfigure to different timings for different clones that require different timings.

As far as I am aware I am the first to use this method of using timers to drive neo pixels and it is very simple and also flexable to change the timings for different clones. I was expecting with how simple the code was and the comments that most people would be able to look at the code and work it out.
20200426_192105.jpg
20200426_192105.jpg (62.16 KiB) Viewed 6507 times
User avatar
fpiSTM
Posts: 1757
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: [F4] Neopixel driver using hardware timers

Post by fpiSTM »

Note that the Adafruit neopixel library is compatible with the STM32 core and so the matrix one is also compatible
flyboy74
Posts: 31
Joined: Fri Jan 03, 2020 10:12 am

Re: [F4] Neopixel driver using hardware timers

Post by flyboy74 »

fpiSTM wrote: Sun Apr 26, 2020 10:39 am Note that the Adafruit neopixel library is compatible with the STM32 core and so the matrix one is also compatible
If you look at the code they have used on each board to achieve the timings it just shows how much of a pain in the butt it is to create these timings. On most boards they literally just experimented on how many thumb assembler "NOP" to execute that it created the right amount time. Have a read of their library https://github.com/adafruit/Adafruit_Ne ... oPixel.cpp

Here's a good video on how these methods were developed https://www.youtube.com/watch?v=VAa4duqMrgs&t=1196s just put the output pin on a scope and add NOPs till it reads right timings.

Although they get the job done they are not very flexible to easily change timing to suit the constant stream of new clone LEDs with different timings.
see just some that are currently available all with different timings.
http://www.normandled.com/upload/201808 ... asheet.pdf
http://www.normandled.com/upload/201605 ... asheet.pdf
https://cdn-shop.adafruit.com/datasheets/WS2811.pdf
https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
https://cdn-shop.adafruit.com/product-f ... sheet+.pdf
https://cdn.sparkfun.com/datasheets/Com ... -12877.pdf
http://www.ledlightinghut.com/files/APA ... asheet.pdf

1 of the advantages of my method is that it is flexible and any timing can be created by just simply setting the values for T0H, T1H, T0L and T1L. The other advantage is the code is quite simple compared to the complex tricks that many other drivers use.
User avatar
fpiSTM
Posts: 1757
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: [F4] Neopixel driver using hardware timers

Post by fpiSTM »

No, for the STM32 core this is not this method which is used.
https://github.com/adafruit/Adafruit_Ne ... .cpp#L1902

It uses the systick.
I agreed your way is probably best, did you used DMA?
flyboy74
Posts: 31
Joined: Fri Jan 03, 2020 10:12 am

Re: [F4] Neopixel driver using hardware timers

Post by flyboy74 »

fpiSTM wrote: Sun Apr 26, 2020 12:10 pm No, for the STM32 core this is not this method which is used.
https://github.com/adafruit/Adafruit_Ne ... .cpp#L1902

It uses the systick.
I agreed your way is probably best, did you used DMA?
It is a little hard for me to tell exactly how they have done it but it looks like the same method as the SPI method.

They create second array that is 3 times bigger than the array that already holds all the data to be sent to the LED strip. Then every bit in the orginal array that is a 1 becomes a 110 in the second array and every 0 bit in the original array becomes a 100 in the second array.

Adafruit uses the WS2812B in their branded Neo pixels which have timing of T1H = 0.8, T1L = 0.45 and T0H = 0.4, T0L = 0.85. Although not perfect it is close to 1/3, 2/3 of total period and the total period for a 1 bit and a 0 bit is the same

they set the systick to fire 3 times for every bit of data that has to be sent. Timings
uint32_t top = (F_CPU / 800000); // 1.25µs
uint32_t t0 = top - (F_CPU / 2500000); // 0.4µs
uint32_t t1 = top - (F_CPU / 1250000); // 0.8µs

Then on each fire it sets the output pin to the bit on the second array.

The difference in this method compared to mine is it uses 4 times the ram (original array plus second array 3 times bigger), it requires a CPU 3 times as quick because the interrupt has to fire 3 times for every bit compared to mine firing just once per bit. This method doesn't create perfect timings and can only be used for addressable RGB LEDs that have both the total length for a 1 bit and a 0 bit being the same and the ratios are very close to 1/3 and 2/3 where my method can have any timings with any ratios and different lengths for 1 bits and 0 bits and mine creates much more perfect timings.
User avatar
fpiSTM
Posts: 1757
Joined: Wed Dec 11, 2019 7:11 pm
Answers: 91
Location: Le Mans
Contact:

Re: [F4] Neopixel driver using hardware timers

Post by fpiSTM »

Yes agreed 😉
mrburnette
Posts: 633
Joined: Thu Dec 19, 2019 1:23 am
Answers: 7

Re: [F4] Neopixel driver using hardware timers

Post by mrburnette »

stevestrong wrote: Sun Apr 26, 2020 9:06 am I think we have to thank to @flyboy74 because he shared his code.
...
+1

Publishing in a public manner is always appreciated, while Googling may look like there is a huge volume of software examples for STM32, the hard truth is there is no such condition as too many examples.

Ray
ozcar
Posts: 144
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: [F4] Neopixel driver using hardware timers

Post by ozcar »

I specifically bought a Beaglebone Black to drive pixel LED strips, because I thought that was an ideal task for one of the PRU processors. However, in the end I found that for the number of LEDs I was working with (up to 300) it was possible to use a lowly Arduino nano, so the BBB sits in a box.
flyboy74 wrote: Sun Apr 26, 2020 1:25 pm ...
The difference in this method compared to mine is it uses 4 times the ram (original array plus second array 3 times bigger), it requires a CPU 3 times as quick because the interrupt has to fire 3 times for every bit compared to mine firing just once per bit.
...
I have not looked recently, but I can remember seeing some heroic efforts to drive these LEDs, and yes, some of them required an "expanded" version of the raw LED data, and so needed a lot of RAM. Frankly, I'm surprised that you can manage to handle one interrupt per bit, but maybe I'm forgetting just how fast these things are. But 3 interrupts per bit - are they really doing that? For an 800kHz LED I would have thought that would not leave much time to do anything else.

I took your code, and with a bit of tweaking, I got it to generate a good looking pulse train, but so far I have not actually connected any LEDs. Some observations:

I note you set ARPE for buffered write to ARR, but not OC1PE for buffered write to CCR1. Given you are writing to both ARR and CCR1 in the interrupt routine, I think you need to set both. As an aside, I'm not totally convinced that there are LEDs which would require you to keep changing ARR, despite what some of the data sheets might say.

I also see you doing this:

Code: Select all

TIM4->CCER &= ~TIM_CCER_CC1E; //disable output to pin so that it will be low. 
I had a problem with that, because that left the pin floating, not driven low. I suppose that could be overcome by setting the GPIO for low output, but I also found that I was getting a spurious pulse at both the start and end of the pulse train. My solution was to leave the timer output enabled (CC1E always set) but to create a "dummy" pulse at the start and end, by setting CCR1 to zero.
flyboy74
Posts: 31
Joined: Fri Jan 03, 2020 10:12 am

Re: [F4] Neopixel driver using hardware timers

Post by flyboy74 »

ozcar wrote: Thu Apr 30, 2020 10:40 am I took your code, and with a bit of tweaking, I got it to generate a good looking pulse train, but so far I have not actually connected any LEDs. Some observations:

I note you set ARPE for buffered write to ARR, but not OC1PE for buffered write to CCR1. Given you are writing to both ARR and CCR1 in the interrupt routine, I think you need to set both. As an aside, I'm not totally convinced that there are LEDs which would require you to keep changing ARR, despite what some of the data sheets might say.

I also see you doing this:

Code: Select all

TIM4->CCER &= ~TIM_CCER_CC1E; //disable output to pin so that it will be low. 
I had a problem with that, because that left the pin floating, not driven low. I suppose that could be overcome by setting the GPIO for low output, but I also found that I was getting a spurious pulse at both the start and end of the pulse train. My solution was to leave the timer output enabled (CC1E always set) but to create a "dummy" pulse at the start and end, by setting CCR1 to zero.
OK I never looked at the OC1PE bit. The code (and idea) came from my code I wrote for generating the acceleration puleses to drive stepper motors in which I never changed the CCR1 value (duty width) I just changed the ARR value. I am driving my LED strip with this code so it seems to be working even without the OC1PE bit set.

As you say most of these LEDs will drive without needing to change the ARR value but there are some around that need it like http://www.normandled.com/upload/201605 ... asheet.pdf and it is also nice to have this in the code then it can be used to create any pulse train for anything not just neopixels.

Yes if your using the systick method and calling an interrupt 3 times per bit then there wouldn't be much time for any other processes but this doesn't matter that much because a transmission does last long at 800Khz per bit. The Ardunio is totally blocking when doing a transmission. It certainly gave me confidence in my method because it only fires 1 interrupt per bit not 3.
ozcar
Posts: 144
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: [F4] Neopixel driver using hardware timers

Post by ozcar »

flyboy74 wrote: Mon May 04, 2020 9:00 am
...

OK I never looked at the OC1PE bit. The code (and idea) came from my code I wrote for generating the acceleration puleses to drive stepper motors in which I never changed the CCR1 value (duty width) I just changed the ARR value. I am driving my LED strip with this code so it seems to be working even without the OC1PE bit set.
You only touch ARPE in Neopixel_setup, and there you turn the bit on (so enabling preload, or buffered write to ARR), while your comment says it is to allow immediate updates to the register to take effect. So I was not sure of your intention, but it did seem strange that you did not do the same for OC1PE - because if they don't match maybe CCR1 and ARR could get out of phase.

In any event, your code did not work properly for me straight out of the box. This is what the pulse train for the entire 60 LEDs looked like:

neofirst.jpg
neofirst.jpg (75.75 KiB) Viewed 5837 times

That has triggered on the rising edge - trigger point in the centre, where the solid vertical cursor is. You can't see at that scale, but trust me, there is a pulse there that it triggered on, around 780 microseconds before the main body of pulses. At first I thought this was just some weird glitch during initialisation, but eventually I figured out what was happening. Because ARPE was set, but not OC1PE, the first value written CCR1 takes effect immediately, while the value written to ARR only comes into effect at the update event, when the first interrupt is triggered. For the very first time through, ARR has the reset value of 0xffff, so the low time for the first pulse is way longer than it should be. Maybe you won't notice this if you rerun the program under a debugger without going through a hardware reset, when it might start out with a reasonable value in ARR from the last execution.

There you can also see the slow falling off of the voltage at the end. Not only was it left floating, it was high at the time that happened. I did not try to work out exactly why it was still high at that point - perhaps also because of the preload mismatch between ARR and CCR1, or maybe something else.

I also noticed that depending on the optimisation level selected, there were sometimes glitches in the pulse train, I think that is because if you don't enable the preload (for both ARR and CCR1), then there is the chance that the interrupt routine might, or might not, get in fast enough to change the duration of the current pulse. After I enabled preload for both ARR and CCR1, and set it up to start and end with a "dummy" pulse, the pulse train is rock-steady.
flyboy74 wrote: Mon May 04, 2020 9:00 am
...

As you say most of these LEDs will drive without needing to change the ARR value but there are some around that need it like http://www.normandled.com/upload/201605 ... asheet.pdf and it is also nice to have this in the code then it can be used to create any pulse train for anything not just neopixels.
I don't see anything there to say that you must have the total time for high + low different for zero and one bits?
Post Reply

Return to “Projects”