More fun with less.  4K RAM + 100 x WS2812Bs - Page 1
Forum section: electronics / general

author: paulca
author_rank: Super Contributor
timestamp: November 09, 2022, 01:59:49 pm
content: This is just for fun and finally? finding a use for the STM32F030F4. 16Kib flash, 4kib RAM.A single WS2812B requires 24bits per pixel. 8 Blue, 8 Green, 8 Red.100 * 24 / 8 = 300 bytes! plenty.Ahhhh... but no.. the output is PWM. 33% duty is a 0, 66% duty is a 1. 50uS low = end of stream. The LEDs flip their registers and display their colour. Otherwise each 24 bits get shift registered into the first LED, which then forwards any further data to it's next peer. So the LEDs build in sequence.This means I need 1 PWM duty cycle value per bit. Which is 2400 bytes for the 100 LEDs + 35 or so for the "low end of stream signal". 2435 bytes fits in ram ok.But the register that takes the PWM values is 16 bit! So now we have a problem. 2435 16 bit words is 4870 bytes. OOM.Right about now the "standard DMA->800kHz PWM technique" isn't really looking viable.As a last ditch effort to say it, I split the LED series into segments. Trying to start the next segments DMA transfer after the first one finished, even using (non DMA) buffer ping-pong, so I was as ready as I could be with the next batch.... alas, not fast enough. On the scope the gap is far, far too close to 50uS and the LEDs flip their registers. Oddly the first segment gets out ok, the second sometimes does. Probably as it's on the cusp.I am going to try actual DMA double buffering with the halfComplete call back to see if I can maintain a contiguous stream for the full 100.However, an idea occurred to me. I have 16Kib of flash, which is only 50% used. That leaves me a whole 8 KiB to pre-sequence 2 or 3 streams. Or rather, I can create an animation sequence which is roughtly 300 LEDs long and rotate it out the DMA, intervening every 100 LEDs or every 2400 bits and either insert a blank bit of memory for 35 bits OR do something fancy with timer registers to halt the DMA and hold a constant PWM DC of 0 for 50uS and then resume. I mean the PWM is responding to a 1.2uS timer, a 50uS delay should be easy?Other more desparate options includebit banging it in ASM with interrupts disabled (except the 50uS pause maybe) and just running it in the CPU at 100%Removing the HAL libraries from the equation and do it all in register land for pure speed and I might get the original segmented DMA version to work.I considered creating a "serial bit field" packed into bytes, the timing are such I would need 3 bits. 100 is a low. 110 is a high. It's still 3 bits per output bit. Thus 100 leds * 3 bytes per pixel * 8 bits per byte * 3 bits per bit = too much.Manually setting the CCR register and the RCR (Repetition count) register to send a (hammed?) bit stream. All I need to do is count bits of the same value (probably 0s), set the RCR register to that count, the CCR to the duty cycle for that bit state and wait for the next interrupt. I this this will fail on timing. It's 1.5uS per bit. That's not a lot of time to re-preload the registers for the next bit.Giving up and using an ESP32 with 2.4Mb of static library code like everyone else. That's just boring.

author: GromBeestje
author_rank: Frequent Contributor
timestamp: November 09, 2022, 02:11:14 pm
content: I believe the STM32F030 should be able to do a DMA transfer where it reads 8 bits from memory and writes 16 bits to a peripheral.I wrote this for the F1, but I believe it could run on an F0 as well. The Timer/DMA don't differ that much across the STM32 series.https://github.com/a-v-s/libws2812dma/blob/master/drivers/ws2812_stm32.cHere I configure it asCode:dma->CCR |= (0x01 << DMA_CCR_PSIZE_Pos); // Peripheral size 16 bitdma->CCR |= (0x00 << DMA_CCR_MSIZE_Pos); // Memory size 8 bitThus, 8 bits memory per bit transmitted, so you can use your initial calculation

author: newbrain
author_rank: Super Contributor
timestamp: November 09, 2022, 03:37:41 pm
content: My solution to avoid using a lot of memory was to translate RGB (3x8 bits) values in sequences of 24 duty cycle bytes (I used SPI rather then PWM, but it's exactly the same concept) under a timer control, then use DMA to send them out, using ping pong buffering.It was on a 72 MHz STM32F103, so count your cycles, as the F0 clock is slower.

author: paulca
author_rank: Super Contributor
timestamp: November 09, 2022, 04:01:04 pm
content: Quote from: paulca on November 09, 2022, 01:59:49 pmThis worked! After some hurdles. It is fast enough, or at least it seems so to write 20 LEDs in each half the DMA buffer and ping-pong.When I hit led 100 written to the buffer, I wait on 2 buffer flips. (my calculations said it should be 1, but I'm not arguing with the last 20 LEDs working) Stop the DMA and write the CCR register as 0. Then I can go to sleep for a long, long time, 100ms and start again.I started with color values of 0x20 for safety, as I'm running it off the MCU board. The STMLink got slightly warm, at only 80mA mind. The board has an AMS1117 capable of 1A, so I gave it some lovely USB power on the USB connector and turned the LEDs up and up and up and at 50% brightness they still haven't exceeded 200mA. I might try 100%, just to see what happens.QuoteStill actually going to make use of this method instead. My rotating sequence of 6 colours is all very well, but it's boring. Preloading much longer more interest sequences into flash memory and just DMA->PWM them from there and I only need to move pointers around in the CPU to sequence the sequences.

author: paulca
author_rank: Super Contributor
timestamp: November 09, 2022, 04:03:40 pm
content: Plenty of room!

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 09, 2022, 04:13:13 pm
content: I already answered in the CubeIDE thread. SPI DMA will be much more efficient!

author: paulca
author_rank: Super Contributor
timestamp: November 09, 2022, 05:03:11 pm
content: I may do round 2 tomorrow with your suggestion on bit packed 8bit SPI, parking it for today though. Dead day in work over. I have PCB design and 3D case design/printing to do tonight now I can use the other monitorHere it is anyway

author: paulca
author_rank: Super Contributor
timestamp: November 09, 2022, 05:16:13 pm
content: A little fact I luckily side stepped is that WS2812Bs are supposedly 5V devices.However, the datasheet is that short you have to .... have some liberties... It says VDD can be stupidly low, but that DIN must be within 0.3V of VDD for high and 0.3V above GND for low.So I ran them on 3.3V. It works. It might help stop them being current monsters too.

author: hans
author_rank: Super Contributor
timestamp: November 09, 2022, 06:33:53 pm
content: The SPI implementation is nice if you can match the bitrates.But if you want to stick to the timer.. you could also use a circular DMA buffer and make use of the half and full transfer interrupts to update the front and back half of the buffer, respectively.I've not used this for WS2812B LEDs themselves, but have used it for other streaming systems. E.g. I needed to transmit a packet of databits over a GPIO port. It used 3GPIOs/symbol, 2symbols/bit, and packets up 64bytes long. 64x8x2x3=3072 bytes of DMA bufferspace for a 64byte packet.I've since then refactored it to use the circular DMA approach. It calculates only 2bytes worth of data ahead of time, hence it only takes 2x48 bytes buffer space. The nice thing it scales independent of the 64byte packet size, which I could now increase up to say 1Kbyte or greater if I want.For a bursty stream like a serial packet , the last byte would disable the DMA stream entirely.Since my application was a communication system, I also added a mode where I can send a PNx sequence to test bit error rates. But that's quite offtopic.The downside is that the statemachine of the circular DMA needs to do some bookkeeping what source data to encode for the timer outputs. But it needs to do this anyway: either all at once or in small chunks. There is some extra IRQ overhead from the extra generated interrupts, which perhaps for 800kbit/s WS2812B LEDs is considerable. I was testing my system at ~1-40kbit/s.Concerning voltage levels: the inputs are CMOS, not TTL, which is annoying since CMOS thresholds at 2/3Vcc for "H" and 1/3VCC for "L". 2/3*5=3.33V, so itcouldwork if you MCU runs at 3.35V, but it could also very well not for 3.3V+/-5%....If you decrease the LED voltage to say 4,5V you've a bit more headroom. If you're lucky the blue LEDs also stay on working on a long strip.. (this is a common issue for long strips consuming high current). Otherwise you need to feed in extra power at multiple supply points across the strip.I don't think the blue LED would work at 3.3V though. The LED drivers achieve proportional scaling with PWM and a constant current driver, so constant current x lower voltage is indeed less power consumed.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 09, 2022, 07:07:09 pm
content: SPI@3MHz (3 spi clocks = 1 led bit) will fill bandwidth these leds can offer (1us cycle time).The 100-led strip will update at 400Hz.

author: ozcar
author_rank: Frequent Contributor
timestamp: November 09, 2022, 08:52:25 pm
content: Quote from: newbrain on November 09, 2022, 03:37:41 pmOne of the (many!) solutions I tried at one point was to translate/expand the RGB data to timer PWM values in DMA interrupt routine, using a circular buffer big enough for 2 LEDs worth of data (so, one interrupt per LED, but that buffer size was something of an arbitrary choice).https://www.stm32duino.com/viewtopic.php?t=357&start=30However that was on a 168MHz F407, and earlier in the same thread it had been determined that you could get away with processing one interrupt per RGB bit, and still only be using about 50% of the processor time.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 09, 2022, 09:09:51 pm
content: Ensure to optimize reserved stack/heap when using HAL, it's too conservative and wastes a lot of memory.- If not using usb or *printf* functions, usually you can set heap to 0.- After a build, check static stack analyzer for most expensive functions, that's teh heap yopu shoudl allocate and just a little more.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 09, 2022, 11:12:40 pm
content: You're very capable of expanding RGB data with proper programming.- You don't need to set the whole data, but only 1 bit every 3 "outs". You know the rest of the data so you can preload it.- Skip placing if/else everywhere creating lots of jumpsEdit: After some brainstorming, I figured out that each subpixel can be broken down into just 20 values.So, a small look-up table can be used, working a lot faster.Edit2: By using bitfields, the compiler optimizes much better in all modes!Edit3: Updated code here:https://www.eevblog.com/forum/microcontrollers/more-fun-with-less-4k-ram-100-x-ws2812bs/msg4525124/#msg4525124I don't have any M0 processor, but compiled forcing m0 instructions, then tested in a STM32F103.-O2=5520cy -Os=5817cy -Ofast=5520cy918 Bytes @3MHz SPI = ~2.5ms6K cycles @48MHz = ~125usYou have lots of processing power left between DMA transfers!

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 13, 2022, 04:10:16 pm
content: Sooo did you ever test this?

author: GromBeestje
author_rank: Frequent Contributor
timestamp: November 13, 2022, 06:41:59 pm
content: I've created a demo running on therabbit pill, based on the code I linked earlier.https://github.com/a-v-s/ucdev/tree/ws2812f0/demos/ws2812/stm32f0

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 13, 2022, 06:47:15 pm
content: Meh, it uses the slow approach

author: NorthGuy
author_rank: Super Contributor
timestamp: November 13, 2022, 07:55:09 pm
content: Use UART and invert the output. This way you can pack 3 pulses into a single byte and waste only one stop bit. UART uses 10 bits to transfer the data:----> first pulse starts hereStart bit - always 0 (1 after inversion)Data bit 0 - 0 if you want long pulse (1 after inversion), 1 otherwiseData bt 1 - always 1 (0 after inversion)----> second pulse starts hereData bit 2 - always 0 (1 after inversion)Data bit 3 - 0 if you want long pulse (1 after inversion), 1 otherwiseData bt 4 - always 1 (0 after inversion)----> third pulse starts hereData bit 5 - always 0 (1 after inversion)Data bit 6 - 0 if you want long pulse (1 after inversion), 1 otherwiseData bt 7 - always 1 (0 after inversion)----> one bit is wastedStop bit - always 1 (0 after inversion)

author: paulca
author_rank: Super Contributor
timestamp: November 13, 2022, 08:10:18 pm
content: I have a PI Pico. When I get round to it (in rotation), I have to try using it's PIO modules. My figuring is you can program them to do the 110, 100 and just write 24bit RGB values to the port and have it deal with it.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 13, 2022, 08:42:57 pm
content: That's a good idea Northguy, some MCUs will let you invert the UART level, then low idle level will create a reset condition automatically.Uses 11% less memory than the SPI approach, but it's also 11% slower due the wasted bits.Anyways, what I was really focusing on was on making a faster bit parser.Most libraries out here use bitshiftting and arithmetic operations, speed is heavily dependant on the architecture (M3 was 2x faster than M0) while this lookup table and rgb bitfields do it 3x fasterI really can't stand how the modern libraries are getting dumber and dumber, crunching for/for/for loops in the most inefficient ways.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 13, 2022, 08:43:10 pm
content: Quote from: paulca on November 13, 2022, 08:10:18 pmWell yeah, you can hardly compare a 16Kib flash, 4kib RAM MCU with no peripheral really adapted for the WS2812B protocol, with a RP2040 that has 65 times the RAM, several MB of Flash, two cores and the PIO which which it's very easy to implement WS2812B and feed it through DMA, basically taking up almost zero CPU time. With the RP2040 you can definitely implement much, much more complex control of those LED strips, much more easily. And there is a starting point here:https://github.com/raspberrypi/pico-examples/tree/master/pio/ws2812

author: paulca
author_rank: Super Contributor
timestamp: November 13, 2022, 10:43:17 pm
content: Speaking of neat ways to do things. I borrowed this idea from a Stuart Bacon's video where a viewer sent him a better solution for processing rotary encoders.It stores the A/B states in bits, shifts them 2 bits left adds the current states and looks a valid transition. There are only 2.His version seemed more concerned in filtering out the illegal states so I just flipped it upside down and ignored everything I don't like.And... oh boy is the ESP32 sensitive about it's interrupts. I think they are re-entrant too which would explain it crashing if you put more than about 2 lines of code in there.

author: paulca
author_rank: Super Contributor
timestamp: November 14, 2022, 11:35:16 am
content: Quote from: DavidAlfa on November 13, 2022, 04:10:16 pmIf you meant me and the bit packed SPI approach, not yet.I have a few ideas I am working on in the application layer of what I want the LEDs to actually do. This becomes important as optimisations can be made at that level to save even more memory.We have 24bit colour values which can't be avoided will take 3 bytes at least.However, no application for LEDs out there really needs to use a 16 million color palate. This is why libraries like FastLED and appliances like WLED support "palates". Basically a look up table which allow you to turn numbers like 0-F into 24bit BGR values from a lookup. You store the color once, use it however many times.I have already used the "Sequence" pattern. Combining the palete and the sequence that takes care of an awful lot of LED animations like static patterns, rotating patterns, ping pong patterns, chasers, shooting stars etc. etc.The other "pattern" needed is what I will call "HAM" Hold and modify. (Only because it's natsalgic and vaguely similar to Amiga HAM GFX mode). A HAM pattern modifies the palete through a callback on each "iteration". This provides the way to do colour cycling, colour wheels, randbows, noise, breathing, pulsing etc. etc.HAM because it's going to hold the sequence and pallete in memory and modify them each iteration, rather than stream out a fixed set of values like the sequencer.If numbers get beyond 8 bits at anytime I'll try and introduce the other hamming technique and statistically order them such that I can put the most commonly used colours into just 2 bits. For example, 00 could be black, 11 white. No other colour can start with 00 or 11. I figure this would be an extreme and the hamming of the paletes would take a lot of effort and not really save that much memory, while increasing processing overhead a lot. Doesn't sound like a good reward for effort ratio.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 14, 2022, 02:07:24 pm
content: Well, as the expander is a lot faster than the SPI you don't really need a whole 918byte buffer.You can use 1/4 of it (230bytes) and run the expander on every half DMA transfer (every 115 bytes, 300us), the expander should take about 16us.

author: paulca
author_rank: Super Contributor
timestamp: November 14, 2022, 03:35:26 pm
content: Quote from: DavidAlfa on November 14, 2022, 02:07:24 pmYou probably wrote that at the same time I wrote this LOL *Note the TODO:Code:void WS2812_Init(SPI_HandleTypeDef *hspi1, DMA_HandleTypeDef *hdma_spi1_tx, uint8_t num_leds){// Can we allocate the whole arrayconst uint8_t bytes_per_pixel = 9;int bytesForStrip = bytes_per_pixel * num_leds;uint8_t* buffer = (uint8_t*)malloc(bytesForStrip);if(buffer == NULL) {// TODO support segmented longer strips than we can allocate.Error_Handler();}}void WS2812_RenderSequence(Sequence_t* sequence, uint8_t num_leds){// bit stuff buffer.}

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 14, 2022, 03:49:07 pm
content: Damn you got me, yeah, I was stalking at you.Sure, it could handle virtually unlimited strip length, but refresh rate will be [ 3000000 / ((LEDS+3)*72) ], probably won't make much sense beyond 4000 leds, already 10Hz.

author: paulca
author_rank: Super Contributor
timestamp: November 14, 2022, 08:12:19 pm
content: Quote from: DavidAlfa on November 09, 2022, 11:12:40 pmFor about an hour I was considering there to be 8 possible 8 bit values, bit no. I forgot the 2 bit phase offset values.I'm not going to look at the rest of that message, though I know there are 20 now.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 14, 2022, 08:27:44 pm
content: I mean, every sub-pixel (8bits) expands into 3 spi bytes.Code:{ [1*0][1*0][1*]} // bits 7-5{ [1*0][1*0] } // bits 4-3{ [*0][1*0][1*0] } // bits 2-0First byte takes 3bits, second 2, third 3.So the 1st and 3rd bytes can only have 8 different states, and the 2nd byte only 4.So you can take these bits and use them as index, removing all the processing power required.This can be used for the UART implementation too, It only needs different table values.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 16, 2022, 01:09:35 pm
content: Added the UART parser version.Only slighly slower (6.2K vs 5.5 K cycles) than the SPI version.Code:#define PIXELS 100uint32_t pixels; // Input buffer// Choose expander type. Must be set to 3Mbit/3MHz.// #define SPI_RGB // SPI mode. Slightly faster, uses more RAM, doesn't require any timing control.// #define UART_RGB // UART mode. Level must be inverted in HW config. Wait 50us after sending the buffer to issue a led reset.#ifdef SPI_RGBuint8_t bf[PIXELS+3]; // Output buffer. Extra 3 pixels for reset signal (3*9=27bytes, 72us@3MHz SPI).typedef union{uint32_t rgb;/*struct{unsigned b0:3; unsigned b1:2; unsigned b2:3; // Input is in BRG orderunsigned r0:3; unsigned r1:2; unsigned r2:3;unsigned g0:3; unsigned g1:2; unsigned g2:3;};*/struct{unsigned b0:3; unsigned b1:2; unsigned b2:3; // Input is in RGB orderunsigned g0:3; unsigned g1:2; unsigned g2:3;unsigned r0:3; unsigned r1:2; unsigned r2:3;};}col_fields_t;col_fields_t c;struct { // Subpixel bits look-up table, placed in RAM (Don't make this constant, access might be slower)uint8_t p0; uint8_t p1; uint8_t p2;} px = { // Subpixel bits.p2 = { 0x92,0x93,0x9a,0x9b,0xd2,0xd3,0xda,0xdb }, // Bits 7:5.p1 = { 0x49,0x4d,0x69,0x6d }, // Bits 4:3.p0 = { 0x24,0x26,0x34,0x36,0xa4,0xa6,0xb4,0xb6 }, // Bits 2:0};void rgb_expand(void){for(uint32_t p=0; p<PIXELS; p++){c.data = pixels ;bf = px.p2[c.g2];bf = px.p1[c.g1];bf = px.p0[c.g0];bf = px.p2[c.r2];bf = px.p1[c.r1];bf = px.p0[c.r0];bf = px.p2[c.b2];bf = px.p1[c.b1];bf = px.p0[c.b0];}}#elif defined UART_RGBuint8_t bf; // Output buffertypedef union{uint32_t data;/*struct{unsigned g20 :3; // Input is in BRG orderunsigned g53 :3;unsigned r0_g76 :3;unsigned r31 :3;unsigned r64 :3;unsigned b10_r7 :3;unsigned b42 :3;unsigned b75 :3;};*/struct{unsigned b20 :3; // Input is in RGB orderunsigned b53 :3;unsigned g0_b76 :3;unsigned g31 :3;unsigned g64 :3;unsigned r10_g7 :3;unsigned r42 :3;unsigned r75 :3;};}col_fields_t;col_fields_t c;// Subpixel bits look-up table, placed in RAM (Don't make this constant, access might be slower)uint8_t px = { 0xDB, 0xD9, 0xCB, 0xC9, 0x5B, 0x59, 0x4B, 0x49 };void rgb_expand(void){for(uint8_t p=0; p<PIXELS; p++){c.data = pixels ;bf = px[c.g75];bf = px[c.g42];bf = px[c.g10_r7];bf = px[c.r64];bf = px[c.r31];bf = px[c.r0_b76];bf = px[c.b53];bf = px[c.b20];}}#endifHow this works:Quote from: DavidAlfa on November 17, 2022, 11:52:58 am

author: paulca
author_rank: Super Contributor
timestamp: November 17, 2022, 11:19:58 am
content: I'm ignorantly remaining on my own stuborn course and trying not to take your code but just follow some of your realisations. Probably going to end up in the same place.This is this morning realisation.Code:/*** 10010010 - 000* 10010011 - 001* 10011010 - 010* 10011011 - 011* 11010010 - 100* 11010011 - 101* 11011010 - 110* 11011011 - 111** 10010010-01001001-00100100 = 00000000* !00!00!0-0!00!00!-00!00!00 = 00000000** !10!10!1-0!10!10!-10!10!10 = 11111111* !1O!1O!1-O!1O!1O!-1O!1O!1O = 11111111* X X X X X X X X* 1 4 7 2 5 1 3 5* 6 3 0 5 2 7 4 1*/I think I'm seeing that a 3 byte output is just the 8 bit color value interleaved with "knowns"? I made them ! for 1 and O for zero, not sure if it's helpful.So I just create a 32bit word. No I need a few of those... I take the 32 bit input number and I ...Shift in a 1. Shift in the next bit from the 32bit color. Shift in a 0. Stop at bit 24, write to DMA buffer. At MAX_LED transmit, rinse, repeat.EDIT: The process might be more efficient like a teletype. I keep shifting bits until I run out of uint32_t. I write one uin32_t to the DMA buffer and if it reaches it's size limit, I transmit it and start writing a new buffer. Ping pong style. I like it, it has a kind of bit stream feel to it.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 17, 2022, 11:52:58 am
content: Exactly."1" is sent as 110 (666ns high, 333ns low)"0" is sent as 100 (333ns high, 666ns low)SPI{170.160.15} {0.140.130. 1} {20.110.100}{170.160.15} {0.140.130. 1} {20.110.100}{170.160.15} {0.140.130. 1} {20.110.100}UART(START=1, STOP=0 ){S70160150T} {S40130120T}{S10100170T} {S60150140T}{S30120110T} {S00170160T}{S50140130T} {S20110100T}

author: paulca
author_rank: Super Contributor
timestamp: November 17, 2022, 03:16:09 pm
content: Yeah, it works. Without ping pong buffering the SPI I get 58fps. So 5800 LEDs per second.Code complete. Excuse my "playschool" style with bit shifting etc. I was having issues, so broke it out and did it bit by bit very explicitly. A lot of that shiftBit function could be macroized or inlined.Now I just gotta write some sequences appropriate for my "Black with blue" xmas tree!Code:void shiftBit(uint8_t value) {static int shiftCounter = 0;static uint8_t wordBuilder = 0;if (value == 1) {wordBuilder |= 1U;} else {wordBuilder &= ~1U;}shiftCounter++;if (shiftCounter == 8) {shiftCounter = 0;*bufferPtr++ = wordBuilder;wordBuilder = 0;if (bufferPtr - buffer > bufferLength) {Error_Handler();}} else {wordBuilder <<= 1U;}}void WS2812_RenderSequence(Sequence_t *sequence, uint8_t num_leds) {// bit stuff buffer.bufferPtr = buffer;for (int ledIdx = 0; ledIdx < num_leds; ledIdx++) {uint8_t colorIdx = sequence->colorIdx[sequence->currentIdx++];if(sequence->currentIdx == sequence->length) {sequence->currentIdx=0;}RGBColor_t *color = &sequence->palette;uint32_t word = (uint32_t) color->bgrValue;for (int inBit = 0; inBit < 24; inBit++) {shiftBit(1);if (word & (1 << inBit)) {shiftBit(1);} else {shiftBit(0);}shiftBit(0);}}for (int bit = 0; bit < bytes_per_50_us_gap * 8; bit++) {shiftBit(0);}while (dma->State != (HAL_DMA_StateTypeDef) HAL_SPI_STATE_READY) {}HAL_SPI_Transmit_DMA(spi, buffer, bufferLength);}Still only 49% RAM. << EDIT: Oh no you aren't. Dynamical allocation is not included in that figure so it's 49% - 916bytes. About 25% remaining.Although I am at 90% Flash, think I need to go on a cull.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 17, 2022, 05:13:34 pm
content: My gosh. What's the excuse?You complained about the expander being slow.Someone though the fastest way, made examples, yet you took the bit shifting way, and the worst possible, calling a function 3 times per pixel bit.That must be the most ineficient way in universe of doing this.Having issues? Convert a single rgb pixel using both ways, compare the output.Not matching? Some issue in the expander, wrong tables, whatever.Matching? The problem is in the sending function then.I did compare the output and it was fine...Edit: Your approach uses almost 82K cycles to parse 100 leds, optimizing for speed! 15x slower omg

author: paulca
author_rank: Super Contributor
timestamp: November 17, 2022, 05:37:56 pm
content: The excuse is I haven't been doing C bitwise operations for long. I always get confused and assume they are at fault. So I expand them out to be more explicit.The function is just to shift the bit in and roll out each 8bit byte into the buffer. It's literally an 8 bit shift register. Someone better at bitwise spehgetti might do that in 2 lines. I tried an failed.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 17, 2022, 06:06:50 pm
content: Anyways, your code doesn't make much sense. What's the input format? RGB? GRB?You're starting to parse the input from the LSB first:Code:for (int inBit = 0; inBit < 24; inBit++) {shiftBit(1);if (color.d & (1 << inBit)) {Code:Input RGBD201A7 11010010.00000001.10100111Output WS2812_RenderSequenceDB49A6 110.110.110.100.100.110.100.110 // B, REVERSEDD24924 110.100.100.100.100.100.100.100 // R, REVERSED9A4D36 100.110.100.100.110.100.110.110 // G, REVERSEDOutput RGB_expand924926 100.100.100.100.100.100.100.110 // GDA6934 110.110.100.110.100.100.110.100 // RD349B6 110.100.110.100.100.110.110.110 // BLEDs take MSB first in GRB order, your code makes LSB BRG.What biwise operations do you need for this? Just load the rgb value into .rgb, let the rest of the code do the work.Code:void rgb_expand(void){for(uint8_t p=0; p<PIXELS; p++){rgb_t c = { .rgb = rgb };bf = px.p2[c.g2];bf = px.p1[c.g1];bf = px.p0[c.g0];bf = px.p2[c.r2];bf = px.p1[c.r1];bf = px.p0[c.r0];bf = px.p2[c.b2];bf = px.p1[c.b1];bf = px.p0[c.b0];}}

author: paulca
author_rank: Super Contributor
timestamp: November 17, 2022, 06:33:16 pm
content: Quote from: DavidAlfa on November 17, 2022, 06:06:50 pmAlready spotted and fixed. Just reversed the bit order. 23-0, rather than 0-23.QuoteYou use a look up table, I just shift out the bits.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 17, 2022, 06:55:23 pm
content: When parsing 100 leds takes 1700us vs 120us, it's not "just".Complaining about the mcu being slow, but it's you causing the issue, got the solution served in silver tray, threw right to the bin.

author: paulca
author_rank: Super Contributor
timestamp: November 17, 2022, 09:38:24 pm
content: Calm down. If I'd just copied your code it wouldn't have been as much fun would it.I deliberately ignored your solution because I wanted to work it out myself. If you look back at one of my first messages I considered a "bit field" of 1s and 0s forming the PWM wave. You suggested SPI and then the lookup table. I deliberately ignored the lookup table as it was a complete solution and went off on my own route. As I said, if I'd just copied and pasted your code it would have been very boring. Mine could be optimised a lot. Most of those bitwise operations can be companded together etc, the function removed.Don't think I recall saying it was slow. We started this because I didn't have enough memory.Actually, as to how fast it is? I don't care. I have a HAL_Delay(250) each "frame". Go faster and "animations" just look like flashing, the effect of patterns walking alone the strip is lost.What is the take away achievement and I'm grateful for your help, is that now (-Debug) I have a load more memory to put sequences and maybe even get away with a button to cycle them.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 17, 2022, 10:03:45 pm
content: I was slightly irritated, as I had crunched lots of ways, testing them all until finding the most efficient one, but not mad! We all like challengesThe high refresh rate doesn't seem means you'll drive them in epilepsy mode, but will allow much smoother transitions, wave effects and so on.

author: paulca
author_rank: Super Contributor
timestamp: November 18, 2022, 10:42:59 am
content: I kinda had a feeling we had different objectives from about halfway through the thread. I suppose I wasn't asking for a solution, just the discussion as it was very interesting doing things in different, odd, unique ways. It definitely filled in a few dead work days for me.And I got a set of xmas lights out of it. I can have the coolest set of irritating christmas lights in my window this year.The main issue presented to me that all that verbose code DOES still make a difference in platforms like this. I have the choice of abreviating all my labels or running without debug symbols.