Hey Phil, great video as usual, however, something I noticed with your driver is that I believe that the bit order is wrong. it should be g7..g0, r7..r0, b7..b0, in MSB order. I have a feeling you didn't notice any issues since you were using 255 to test, so all the values for each bit were high anyway, but using any other value gives the incorrect brightness. I used the following and it is working for me with WS2812B leds: uint16_t bufIndex = 0; for (uint8_t led = 0; led < NUM_LEDS; led++) { // for each LED for (uint8_t bits = 0; bits < BITS_PER_LED; bits++, bufIndex++) { // loop through all 24 bits uint8_t byte = (bits / 8) * 8; uint8_t bit = 7 - (bits % 8); uint8_t bitIndex = byte + bit; if ((LED_DATA[led].data >> bitIndex) & 0x01) // If bit set DMA_BUF[bufIndex] = T1H; // set pulse high else DMA_BUF[bufIndex] = T0H; // set pulse low } }
also in the struct the order of rgb is wrong due to memory endiannes should be typedef union { struct { uint8_t blue; uint8_t red; uint8_t green; } color; uint32_t data; } sk6805dataRgb; instead of typedef union { struct { uint8_t green; uint8_t red; uint8_t blue; } color; uint32_t data; } sk6805dataRgb;
I love these kind of elegant solutions. At work, I always take the time to write libraries that take advantage of the hardware on this level. One thing I don't really like though is the fact that each bit needs a byte of buffer space to be sent out. This may limit the capabilities of lower end microcontrollers with less memory (And I love the G0 series from ST). I have recently given some thought about using the SPI peripheral with DMA and a timer with some minimal external hardware so that I can control a lot of these LEDs basically without any CPU and memory overhead. It didn't get far but I figured that by using 2 timer channels to generate low and high pulses selecting the right one using MOSI and resetting the timer using the SPI clock, it can be achieved by purely using the SPI peripheral in DMA mode. It would of course be a trade-off but in projects where memory is a constraint, it could be useful. I will certainly do it during summer.
That's very true regarding the space needed for the DMA buffer. Interesting idea you have there with SPI + the two timer channels - looking forward to hearing about the results!
It's really sad. I guess they really think that all these hobbyists are going to love their tools so much that they'll pay. Good luck with that. I'll just stay with KiCad thank you...
If you want you can optimize the color DMA update byt using a lookup table of 16x32bits values (LED 4bits). Then you only need 6 writes for RGB values. This works due to you using 1byte for the PWM.
The STM32U5 seems to handle the DMA/TIM transfer quite differently than other STM32s. I once implemented a WS2812B driver with 4 channels of 128+ LEDs each on an STM32F30, using the same principle. The key to making it work there was to 1) trigger DMA transfers via the TIM CC event (not at the end of the period) and 2) enable AutoReload preload, such that the new value is written to the register before the end of the PWM period. Otherwise it would output each bit value twice because the AR value was not updated fast enough. In this video for the STM32U5, it seems that DMA (or the HAL?) appropriately times the transfer such that there is a new AR value ready for each PWM period, without having to enable AR preload.
Absolutely fantastic video. I’m not new to C but I get lost pretty heavily when I see functions passing pointers as arguments or by reference. It would help a lot if you could make someday a video on why you use them or how is the thought process in the context of microcontrollers. Fantastic work Phil!
I used the WS28xx LEDs in a previous project, and just used DMA=>SPI peripheral; expanding out the bits in a frame-buffer in RAM so that a high bit = 110, and a low bit = 100 in the stream from the MOSI port (CLK not used). Those LEDs don't really need precise timing, so it was reliable and pretty simple to implement really - although a little bit wasteful of RAM, as it obviously needed 9 bytes for each 24bit colour value. 😁
By additionally using a down count timer in capture mode and directing the MOSI and MISO data to a port (preferably a port that has only the least significant two bits, PORTF - in many MCUs), and using one more DMA channel from overflow, this can be done completely in hardware, using only as many bits (RAM) as SPI needs for such sending.
Perfect timing! I was just wondering how to use DMA to update PWM/brightness levels for a custom seven segment video display wall I'm building. Good concise demo. I'm guessing this is how libraries like SmartLED (nee SmartMatrix) work under the hood?
Thank you, hope it's useful! From what I've seen, many openly available libraries have a very similar foundation to this. There are however a number of improvements that could be made (memory requirements, speed, ..).
I was literally looking into how to do this for a set of leds last week and finally figure out how to configurate the DMA with the timer after not being able to find a good guide that use the GPDMA!!! Now im looking into options that allow me to do a similar function with simpler microcontrollers without DMA to Peripheral interfaces that dont rely on CPU nop instructions to delays pulses
Great video as usual! I tried your implementation and found a quirk When running at a slower clock speed (64MHz) where the driver didn't work properly. After sending the PWM data frame once, *_SK6805_DMA_COMPLETE_FLAG_* would get stuck at 0 forever. Turned out, when the clock speed is low, *_HAL_TIM_PWM_Start_DMA_* would finish transfering PWM data to the timer so quickly that *_SK6805_Callback()_* is called BEFORE the code enters *_if (halStatus == HAL_OK)_* in order to clear *_SK6805_DMA_COMPLETE_FLAG_*. Basically, *_HAL_TIM_PWM_Start_DMA_* is called, then *_SK6805_DMA_COMPLETE_FLAG_* is set to 1 by *_SK6805_Callback()_***, but immediatly after ***_SK6805_DMA_COMPLETE_FLAG_* is set to 0 when we finally run *_if (halStatus == HAL_OK)_*. My quick and dirty solution is to ditch *_if (halStatus == HAL_OK)_* and clear *_SK6805_DMA_COMPLETE_FLAG_* before *_HAL_TIM_PWM_Start_DMA_*. There's for sure a more elegant solution.
That's very interesting - thanks for sharing! Oddly enough, I haven't come across that myself (even on a G0 MCU running at 64MHz). Another (not particularly elegant) approach would be to check the complete flag in the while(1) loop in main.
Thank you, Ankur! Mainly by working on various projects (personal and for work). It's predominantly self-taught via online resources, books, example code, etc..
Hey Phil, your symbols are looking clean! Could you go over your current libraries in Altium? How you structure them, how you put text in the center of the symbol aka caps, res, ic, and any other useful tips you believe are worth mentioning. Thank you!
Nicely put together! What's your opinion on hijacking the SPI peripheral for this purpose? (sorry if this is mentioned in the video, can't watch it right now)
Thank you! Haven't actually tried with SPI yet, but my first thought is that would by default enable more pins than needed on the MCU. Question is how to repurposes the unused SPI lines (CLK) if needed. The timer approach is quite straightforwardy (also peripheral/pinning-wise), so I don't really see a reason to not use it - unless free timers aren't available.
Great video! Just curious why you chose C34/35 to be 220nF as opposed to the value shown in section 16 of the datasheet of 104 (which corresponds to 100nF).
Thanks! That's for BoM consolidation. I needed a specific 220n cap for a pin of the magnetometer, but the actual decoupling value for the rest of these parts is not a precise, required quantity.
Love your videos, the next step in my project happened to be designing a ws2812 driver for those exact led chips for a stm32u0 lol. You didn't need to use logic voltage translator? I have run into problems int the past when I didn't use on. Although with that project, I was using a bq24075 as my battery charger which outputs constant 5V no matter the battery charge, so that may be the difference.
very nice video as usual. a little question, how can i detect if there are some abnormalities concerning the functionality of the rgb led (e.g. LED Strip ) Danke.
Thanks. I really enjoyed how you abstracted your code from low-level (hardware) to high-level (firmware). I have not used a STM IDE since 2010. Any opinions on this current IDE and a beginners (for the IDE) learning curve?
Thank you! Despite some occasional bugs, CubeIDE is my favourite (Eclipse-based) IDE. The whole pin-out, peripheral, clock, etc.. config makes set-up far more straightforward than many other system, in my eyes. Plus there's a large community behind all of this.
Are the bits order reversed in your code. Bit-7 of the color code is to be sent first, and not bit 0. ua-cam.com/video/MqbJTj0Cw6o/v-deo.html. This code works for me //Fill DMA buf with LED data int bufIndex = 0; for (int i=0; i=0; b--) { // reverse if ( (RGBLED_DATA[i].data>>b)&0x01) RGBLED_DMA_BUF[bufIndex] = RGBLED_HI_PULSE; else RGBLED_DMA_BUF[bufIndex] = RGBLED_LO_PULSE; bufIndex++; } } struct needs to be redefined typedef union { //RGBLED data order is GGGGGGGGRRRRRRRRBBBBBBBB struct { uint8_t b; uint8_t r; uint8_t g; } colour; uint32_t data; } RGBLED_DATA_TYPE;
Hey Phil, great video as usual, however, something I noticed with your driver is that I believe that the bit order is wrong. it should be g7..g0, r7..r0, b7..b0, in MSB order. I have a feeling you didn't notice any issues since you were using 255 to test, so all the values for each bit were high anyway, but using any other value gives the incorrect brightness. I used the following and it is working for me with WS2812B leds:
uint16_t bufIndex = 0;
for (uint8_t led = 0; led < NUM_LEDS; led++)
{ // for each LED
for (uint8_t bits = 0; bits < BITS_PER_LED; bits++, bufIndex++)
{ // loop through all 24 bits
uint8_t byte = (bits / 8) * 8;
uint8_t bit = 7 - (bits % 8);
uint8_t bitIndex = byte + bit;
if ((LED_DATA[led].data >> bitIndex) & 0x01) // If bit set
DMA_BUF[bufIndex] = T1H; // set pulse high
else
DMA_BUF[bufIndex] = T0H; // set pulse low
}
}
Thank you! You are completely right - sorry about that!
@@PhilsLab no worries, it happens! I thought I was losing my mind at first. Appreciate everything you do!
@@daflamingfox can you please send me e link where i can find a full code for this led's driver (sk6805) thank you
for (uint8_t ledIdx = 0; ledIdx < SK_NUM_LEDS; ledIdx++)
{
for (uint8_t bitIdx = 0; bitIdx < SK_LED_BITS; bitIdx++)
{
if ( (skLedData[ledIdx].data >> (SK_LED_BITS - 1 - bitIdx)) & 0x01 )
skDmaBuffer[dmaBufIdx] = SK_H_VAL;
else
skDmaBuffer[dmaBufIdx] = SK_L_VAL;
dmaBufIdx++;
}
}
this should do the trick
also in the struct the order of rgb is wrong due to memory endiannes
should be
typedef union
{
struct
{
uint8_t blue;
uint8_t red;
uint8_t green;
} color;
uint32_t data;
} sk6805dataRgb;
instead of
typedef union
{
struct
{
uint8_t green;
uint8_t red;
uint8_t blue;
} color;
uint32_t data;
} sk6805dataRgb;
I love these kind of elegant solutions. At work, I always take the time to write libraries that take advantage of the hardware on this level. One thing I don't really like though is the fact that each bit needs a byte of buffer space to be sent out. This may limit the capabilities of lower end microcontrollers with less memory (And I love the G0 series from ST). I have recently given some thought about using the SPI peripheral with DMA and a timer with some minimal external hardware so that I can control a lot of these LEDs basically without any CPU and memory overhead. It didn't get far but I figured that by using 2 timer channels to generate low and high pulses selecting the right one using MOSI and resetting the timer using the SPI clock, it can be achieved by purely using the SPI peripheral in DMA mode. It would of course be a trade-off but in projects where memory is a constraint, it could be useful. I will certainly do it during summer.
That's very true regarding the space needed for the DMA buffer. Interesting idea you have there with SPI + the two timer channels - looking forward to hearing about the results!
Wow, that sounds awesome. I would love to see a reference implementation!
I'll never understand why Altium sponsors a hobby channel but doesn't offer a hobby license -_-
It's really sad. I guess they really think that all these hobbyists are going to love their tools so much that they'll pay. Good luck with that. I'll just stay with KiCad thank you...
Even fusion 360 is free for hobbyist.
It’s actually insane desperately need a hobby license.
These videos are SO good, thank you for producing them!
Thank you, I'm glad to hear that!
If you want you can optimize the color DMA update byt using a lookup table of 16x32bits values (LED 4bits).
Then you only need 6 writes for RGB values. This works due to you using 1byte for the PWM.
The STM32U5 seems to handle the DMA/TIM transfer quite differently than other STM32s. I once implemented a WS2812B driver with 4 channels of 128+ LEDs each on an STM32F30, using the same principle. The key to making it work there was to 1) trigger DMA transfers via the TIM CC event (not at the end of the period) and 2) enable AutoReload preload, such that the new value is written to the register before the end of the PWM period. Otherwise it would output each bit value twice because the AR value was not updated fast enough.
In this video for the STM32U5, it seems that DMA (or the HAL?) appropriately times the transfer such that there is a new AR value ready for each PWM period, without having to enable AR preload.
Interesting to hear that that's the case for that MCU, thanks!
Absolutely fantastic video. I’m not new to C but I get lost pretty heavily when I see functions passing pointers as arguments or by reference. It would help a lot if you could make someday a video on why you use them or how is the thought process in the context of microcontrollers. Fantastic work Phil!
Thanks Phil, I'm just working on my first Addressable LED project: 50+ LEDs per row.
Very cool, good luck with your project!
These videos are so darn good! I don't mind having to go through them multiple times. There is so much to learn. Just subscribed And became a patreon.
Thank you very much for your support, I really appreciate it!
I used the WS28xx LEDs in a previous project, and just used DMA=>SPI peripheral; expanding out the bits in a frame-buffer in RAM so that a high bit = 110, and a low bit = 100 in the stream from the MOSI port (CLK not used). Those LEDs don't really need precise timing, so it was reliable and pretty simple to implement really - although a little bit wasteful of RAM, as it obviously needed 9 bytes for each 24bit colour value. 😁
By additionally using a down count timer in capture mode and directing the MOSI and MISO data to a port (preferably a port that has only the least significant two bits, PORTF - in many MCUs), and using one more DMA channel from overflow, this can be done completely in hardware, using only as many bits (RAM) as SPI needs for such sending.
Perfect timing! I was just wondering how to use DMA to update PWM/brightness levels for a custom seven segment video display wall I'm building. Good concise demo. I'm guessing this is how libraries like SmartLED (nee SmartMatrix) work under the hood?
Thank you, hope it's useful! From what I've seen, many openly available libraries have a very similar foundation to this. There are however a number of improvements that could be made (memory requirements, speed, ..).
I was literally looking into how to do this for a set of leds last week and finally figure out how to configurate the DMA with the timer after not being able to find a good guide that use the GPDMA!!! Now im looking into options that allow me to do a similar function with simpler microcontrollers without DMA to Peripheral interfaces that dont rely on CPU nop instructions to delays pulses
Yeah I haven't found a good guide on setting up GPDMA either :(
Great video as usual!
I tried your implementation and found a quirk When running at a slower clock speed (64MHz) where the driver didn't work properly.
After sending the PWM data frame once, *_SK6805_DMA_COMPLETE_FLAG_* would get stuck at 0 forever.
Turned out, when the clock speed is low, *_HAL_TIM_PWM_Start_DMA_* would finish transfering PWM data to the timer so quickly that *_SK6805_Callback()_* is called BEFORE the code enters *_if (halStatus == HAL_OK)_* in order to clear *_SK6805_DMA_COMPLETE_FLAG_*.
Basically, *_HAL_TIM_PWM_Start_DMA_* is called, then *_SK6805_DMA_COMPLETE_FLAG_* is set to 1 by *_SK6805_Callback()_***, but immediatly after ***_SK6805_DMA_COMPLETE_FLAG_* is set to 0 when we finally run *_if (halStatus == HAL_OK)_*.
My quick and dirty solution is to ditch *_if (halStatus == HAL_OK)_* and clear *_SK6805_DMA_COMPLETE_FLAG_* before *_HAL_TIM_PWM_Start_DMA_*. There's for sure a more elegant solution.
That's very interesting - thanks for sharing! Oddly enough, I haven't come across that myself (even on a G0 MCU running at 64MHz). Another (not particularly elegant) approach would be to check the complete flag in the while(1) loop in main.
Really nice presentation. Thanks
Thank you!
Thank you so much Phil! nice Video!
Thanks for watching!
great video there Phil! May i ask from where did you learn Embedded Coding and Firmware coding since your codes are immaculate.
Thank you, Ankur! Mainly by working on various projects (personal and for work). It's predominantly self-taught via online resources, books, example code, etc..
Hey Phil, your symbols are looking clean! Could you go over your current libraries in Altium? How you structure them, how you put text in the center of the symbol aka caps, res, ic, and any other useful tips you believe are worth mentioning. Thank you!
very interesting and informative video, thank you :)
Thank you!
Awesome video and really interested in the FC
Thank you, Fritz - more to come on that!
Love your videos! How do you figure this stuff out in the first place? Keep up the good work!
Thank you! Mainly via the datasheet, past experience/knowing the peripherals, looking at open-source implementations, etc..
Nicely put together! What's your opinion on hijacking the SPI peripheral for this purpose? (sorry if this is mentioned in the video, can't watch it right now)
Thank you! Haven't actually tried with SPI yet, but my first thought is that would by default enable more pins than needed on the MCU. Question is how to repurposes the unused SPI lines (CLK) if needed. The timer approach is quite straightforwardy (also peripheral/pinning-wise), so I don't really see a reason to not use it - unless free timers aren't available.
Great video! Just curious why you chose C34/35 to be 220nF as opposed to the value shown in section 16 of the datasheet of 104 (which corresponds to 100nF).
Thanks! That's for BoM consolidation. I needed a specific 220n cap for a pin of the magnetometer, but the actual decoupling value for the rest of these parts is not a precise, required quantity.
Love your videos, the next step in my project happened to be designing a ws2812 driver for those exact led chips for a stm32u0 lol. You didn't need to use logic voltage translator? I have run into problems int the past when I didn't use on. Although with that project, I was using a bq24075 as my battery charger which outputs constant 5V no matter the battery charge, so that may be the difference.
Nice job, especially going through the schematic, which a lot of people struggle with.
Thank you!
Best guide how to control adressed leds
Thank you!
I prefer the sk9822 led using the spi peripheral or simply bit-bang it. Just takes one more pin.
The protocol is very simple but getting the timing right without sacrificing a lot of processing time is quite difficult in my experience 😅
very nice video as usual. a little question, how can i detect if there are some abnormalities concerning the functionality of the rgb led (e.g. LED Strip ) Danke.
Thank you! Not that I'm aware of with a single-wire implementation.
Thanks. I really enjoyed how you abstracted your code from low-level (hardware) to high-level (firmware).
I have not used a STM IDE since 2010. Any opinions on this current IDE and a beginners (for the IDE) learning curve?
Thank you! Despite some occasional bugs, CubeIDE is my favourite (Eclipse-based) IDE. The whole pin-out, peripheral, clock, etc.. config makes set-up far more straightforward than many other system, in my eyes. Plus there's a large community behind all of this.
@@PhilsLab Thanks!
This is a very elegant solution!!! Thanks for the detailed explanation Phil!!!
Thank you!
Thank you for the great Video as always.
Is it professionally ok to always use HAL libraries instead of writing our own bare metal code?
Thank you! Sure, I've seen HALs used in development for many products.
👍🙏❤️
Due to the nested for loop the set function is really slow.. Not suitable for fast led driving
Can be yes, but for my needs in this case this is sufficient and for a tutorial this is a simple demo to get the basics across.
HAL is hideous.
Are the bits order reversed in your code. Bit-7 of the color code is to be sent first, and not bit 0. ua-cam.com/video/MqbJTj0Cw6o/v-deo.html. This code works for me
//Fill DMA buf with LED data
int bufIndex = 0;
for (int i=0; i=0; b--) { // reverse
if ( (RGBLED_DATA[i].data>>b)&0x01)
RGBLED_DMA_BUF[bufIndex] = RGBLED_HI_PULSE;
else
RGBLED_DMA_BUF[bufIndex] = RGBLED_LO_PULSE;
bufIndex++;
}
}
struct needs to be redefined
typedef union {
//RGBLED data order is GGGGGGGGRRRRRRRRBBBBBBBB
struct {
uint8_t b;
uint8_t r;
uint8_t g;
} colour;
uint32_t data;
} RGBLED_DATA_TYPE;
ignore this. I just noticed somebody has asked similar question
@@WhoAmI-g2o can you please send me e link where i can find a full code for this led's driver thank you