0
\$\begingroup\$

I want to send data to a LED strip. This has to be done roughly every 400 (or 800 ns), depending on the data (a 0 or 1 to be sent).

unsigned long  counter  = 0;

I did a test with my STM32F103C8T6 with this code in the loop:

while (1)
{
    GPIOC->BSRR = GPIO_PIN_13; // Set GPIO
    counter++;
}

I ran in debug mode, to be able to check the counter value. After roughly 10 seconds, I suspended the program and checked the value for counter. It had value 27822623. This means that each counter takes about 10 / 27822623 = 3.5942E-07 seconds / counter increment, which is 359 ns/counter increment.

However, all I do here is setting the GPIO pin which is one offset pointer, an assignment and an increment. And this takes the 'gigantic' long time of 359 ns. In my real application I need to go to the next byte, check if it is a 0 or 1, depending on that to send the above command to set, or a reset command of the pin (which takes an additional bit shift). How can I perform this within (or actually appr. around 400 ns?)

The STM32 is set to 72 MHz (maximum speed). I haven't any additional interrupt running.

How can I send data at 400 ns manually?

(I tried SPI: not reliable enough, sometimes clock pulses are skipped and resulting data). Interrupts via a timer will be even taking more time since it contains interrupt flow (via HAL).

Update

New application to test better:

unsigned long  counter  = 0;

In main:

while (1)
{
    GPIOC->BSRR = GPIO_PIN_13; // SET
counter++;

    if (counter == 10000000)
{
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_14);
    counter = 0;
    }
}

Update Below the disassembled code (more than I thought):

152                 GPIOC->BSRR = GPIO_PIN_13; // SET
0800122e:   mov.w   r2, #8192       ; 0x2000
08001232:   ldr     r3, [pc, #36]   ; (0x8001258 <main+64>)
08001234:   str     r2, [r3, #16]
158               counter++;
08001236:   ldr     r2, [pc, #36]   ; (0x800125c <main+68>)
08001238:   ldr     r3, [r2, #0]
0800123a:   adds    r3, #1
0800123c:   str     r3, [r2, #0]
159               if (counter == 10000000)
0800123e:   ldr     r2, [pc, #32]   ; (0x8001260 <main+72>)
08001240:   cmp     r3, r2
08001242:   bne.n   0x800122e <main+22>
161                   HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_14);
08001244:   mov.w   r1, #16384      ; 0x4000
08001248:   ldr     r0, [pc, #12]   ; (0x8001258 <main+64>)
0800124a:   bl      0x80004e0 <HAL_GPIO_TogglePin>
162                   counter = 0;
0800124e:   movs    r2, #0
08001250:   ldr     r3, [pc, #8]    ; (0x800125c <main+68>)
08001252:   str     r2, [r3, #0]
08001254:   b.n     0x800122e <main+22>
08001256:   nop  
\$\endgroup\$
  • 1
    \$\begingroup\$ 400ns is like 2.5MHz, so you should have like 30 instructions per this period. you have (at least) 1 cycle for GPIO, 1 for loop (if compiler use plain jump) and few instructions for incrementing the counter (depends, if it is read/write from memory or resides in register, also depends on it size). What type is your counter? Is it possible, that it is just 16 bit wide, so you had it overflown many times in those 10 seconds? \$\endgroup\$ – gilhad Apr 14 '18 at 23:01
  • \$\begingroup\$ @gilhad the counter is an unsigned long (since it needs to count high not to overthrown. I'm sure it doesn't overthrown. I changed the program a bit so it every 10,000,000 it flips another GPIO and I now have 438 ns/counter (including some overhead for the toggle of the extra pin). See update in my question. I wonder how they do this on an Arduino which only has 16 MHz and also should be capable of sending pulses at 2.5 MHz speed. \$\endgroup\$ – Michel Keijzers Apr 14 '18 at 23:04
  • 1
    \$\begingroup\$ Don't use the wrong tool for the job. Do you need to use this MCU? Do you have to do anything else during this operation? How often do you need to transmit and how many bits at a time? \$\endgroup\$ – pipe Apr 14 '18 at 23:12
  • 1
    \$\begingroup\$ Look at github.com/adafruit/Adafruit_NeoPixel the file Adafruit_NeoPixel.cpp - there is the code for it \$\endgroup\$ – gilhad Apr 14 '18 at 23:21
  • \$\begingroup\$ @pipe I can use a STM32F4 which is faster, but I don't like that much. the STM32F1 is much smaller (I use small dev boards, blue pill for that) and the STM32F4 is much bigger. Also it's a bit about the principle. On a 16 MHz Arduino it is even possible. I don't have to do anything else (although I want to read MIDI data, but can be switched off during sending led strip data). I don't know how often I need to write, depends on the update frequency, max 50 times per second, but probably less. \$\endgroup\$ – Michel Keijzers Apr 15 '18 at 9:11
1
\$\begingroup\$

This might be a fine time to look at the assembly your compiler generated, and actually consider improving it.

It's possible your stm32 has a "bit-band" region for the gpio registers, so that you can directly access that control register without any bit manipulations directly as byte address.

You might do crazily efficient stuff by not doing a manual comparison with your 0 or 1 byte, but by using conditional moves that depend on that value; that way, the fetching the value and setting the right register to the right value operations could be pipelined. If you haven't, first try different compiler flags (-O2 might be a good start) to make sure your compiler doesn't do that for you.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer... I will check if I can find the assembly code. I don't see where bit banding helps, since in the set (making high) of a GPIO I don't need bit manipulations, only for the Reset. The conditional jump might help (was hoping I didn't have to dive to deep in assembly anymore though). And the compiler flags might indeed matter. I used debugging so some flags might no non optimal. \$\endgroup\$ – Michel Keijzers Apr 15 '18 at 9:06
  • \$\begingroup\$ I accepted your answer, since it worked. What I did was optimizing the code as it should be eventually, checking the assembly code, than I used a logic analyzer to check the times. I added some 'nop' statements and I have all lights white now. Although I'm still surprised I only need 5 nop commands. I can check for the o2 debug flag but since it works now, it's ok. \$\endgroup\$ – Michel Keijzers Apr 15 '18 at 12:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.