5
\$\begingroup\$

I have an STM32 that toggles nine GPIO pins repeatedly (one clock pin and eight data pins to load an FPGA image using SelectMap). I am doing this using the standard library function GPIO_WriteBit that modifies one GPIO bit, and changing one pin at a time.

Unfortunately, this turns out to be quite slow. Is there a way I can make "raw GPIO toggles" very very fast? Is there a clock parameter I should change? Can I use some sort of FIFO, or interrupt-based method?

I have configured the GPIO pins as follows:

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

(Note: The reference manual (see page 133) states that the GPIO is capable of:

Fast toggle capable of changing every two clock cycles

But I cannot see how to activate this fast toggling.)

\$\endgroup\$
  • \$\begingroup\$ Why are you changing each bit at a time? If you want to load quickly You should be using GPIO_Write instead. \$\endgroup\$ – Oli Glaser Jul 30 '12 at 16:15
  • \$\begingroup\$ @OliGlaser: Not all the pins are on the same GPIO bank. Those in the same bank are written at the same time. \$\endgroup\$ – Randomblue Jul 30 '12 at 16:15
  • \$\begingroup\$ Ah okay, then use the GPIOx->ODR register(s) instead. Also, make sure your APB prescaler is set to 1 (no divide) \$\endgroup\$ – Oli Glaser Jul 30 '12 at 16:20
  • 1
    \$\begingroup\$ Is this an STM32F4? \$\endgroup\$ – Oli Glaser Jul 30 '12 at 16:21
  • \$\begingroup\$ @OliGlaser: It's an STM32F2, and I've made sure that both appropriate APB prescalers (the APB and APB1 prescalers) are set to 1 \$\endgroup\$ – Randomblue Jul 30 '12 at 16:24
1
\$\begingroup\$

I answered to your related question why you could only toggle at 4 MHz when you expected 100 MHz:

If the 4 MHz is about 4 MHz, and not exactly 100 MHz/25 then the problem is probably with the C function GPIO_WriteBit.

For high speed operations and operations which require accurate timing you better code in assembly than in C. If you look at the assembly code created by GPIO_WriteBit it may be half a page long, depending on what kind of features the function has, and how much the compiler's optimizer can do with it.

You don't say which development toolchain you're using, but many/most C compilers can handle in-line assembly.

So, write the functions in assembly. A function like

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

should take no more than 2 instructions in assembly, while the compiled C code may take 20 times as much. Or more.

\$\endgroup\$
  • 1
    \$\begingroup\$ So I rewrote the code by directly writing to registers and unwrapping the loop, and I got it to 20Mhz, but it's still nowhere close to 100Mhz. \$\endgroup\$ – Randomblue Jul 30 '12 at 16:15
6
\$\begingroup\$

Setting or clearing any combination of bits in an I/O port (even setting some and clearing others) should require at most three instructions; when repeated such instructions should take one instruction each. Unfortunately, it seems many vendors (ST among them) tend to define I/O libraries which generate subroutine calls even for common operations such as that.

I would suggest defining your own methods to set and clear I/O bits, and, include the code for those methods, marked with an __inline qualifier, in your .h file [as opposed to merely including a prototype in the .h and the method code in a separate .c file].

If within a .h file, you write:

__inline void SetPorts(GPIO_TypeDef* Addr, uint16_t data) { *Addr = data; }
__inline void ClearPorts(GPIO_TypeDef* Addr, uint16_t data) { *Addr = data; }

Then the code:

setPorts(GPIOA, 1);
clearPorts(GPIOA, 2);
clearPorts(GPIOA, 1);

will likely generate something like (I'm not positive about some details):

    ldr  r0,=GPIOA
    mov  r1,#1
    strh r1,[r0+20]
    mov  r2,#2
    strh r2,[r0+22]
    strh r1,[r0+22]

By contrast, if one uses GPIO_WriteBit the code would end up being more like:

    ldr  r0,=GPIOA
    mov  r1,#1
    mov  r2,#1
    bl   _GPIO_WriteBit
    ldr  r0,=GPIOA
    mov  r1,#2
    mov  r2,#0
    bl   _GPIO_WriteBit
    ldr  r0,=GPIOA
    mov  r1,#2
    mov  r2,#0
    bl   _GPIO_WriteBit

_GPIO_WriteBit: ; Code below assumes pretty good compiler optimization 
    cmp   r2,#0
    itteq
    streq r1,[r0+20]
    strne r1,[r0+22]
    bx    lr

The first example executes six instructions total for all three operations. The second example executes twelve in the main code once, and about four instructions in the WriteBits function three times each, for a total of 24 [skipped instructions sometimes add to execution time and sometimes not]. Normally the purpose of calling subroutines is to trade off code size for execution speed, but in this case the subroutine call is a disaster from the standpoints of both space and time. The sole useful work done by the instruction is a single store operation; the code will likely leave registers r0-r2 undisturbed, but the calling code will have no way of knowing that. Consequently, all three parameters must be explicitly set before each each method call.

I don't know why chip vendors define methods which write GPIO bits but don't bother to make them inline, but I would suggest that in most cases one should avoid using chip-vendor-supplied functions to write GPIO ports if one cares anything about efficiency.

Incidentally, as a general further note, I tend to be skeptical of vendor-supplied I/O functions in general. While they can sometimes present a programmer with a higher level of abstraction than the raw hardware, which is useful, in many cases they make code harder to write, harder to read, and less efficient. They can also sometimes have unwanted side-effects that may cause problems elsewhere. For example, if a peripheral needs a clock to be set to one of two modes, and generally works better with one of them, a vendor-supplied library for the peripheral might configure the clock to the "better" mode even if that clock is shared with another peripheral that needs it to be in the other one. When peripherals share resources (as they often do), it may be impossible to properly use the libraries without reading and understanding all the code therein. If the whole purpose of the library was to save a couple of register writes, the consequences of library call may be a lot harder to figure out than the consequences of the code it replaces.

\$\endgroup\$
2
\$\begingroup\$

The speed you set only controls the slew rate of the pin. The faster it is, the faster the rising edge. It does not directly represent how fast you can toggle the ports.

The feature allows you to appropriately interface with other devices that require specific rise/fall times.

\$\endgroup\$
1
\$\begingroup\$

You set the GPIOs' speed to 100 MHz; this is the speed limit the hardware can support. But the final data exchange speed may also be limited by how fast your code can run, because the data clock, etc. is driven by your code now.

In my opinion, not all STM32 family members support the 100 MHz GPIO speed, and some family's MCU speed is lower than that speed, such as 72 MHz. If your MCU supports 100 MHz GPIO, then optimize your code. Otherwise, choose a MCU with a higher clock speed; STM32F429 supports a 180 MHz clock speed.

And please check the MCU's datasheet. In the "I/O AC characteristics definition" section, it gives the electrical characteristics of the GPIO, and how the "100 MHz" is defined. And you should note: "For maximum frequencies above 50 MHz, the compensation cell should be used."

\$\endgroup\$
0
\$\begingroup\$

Make sure you also get your clock registers set right; download the STM32F clock configuration tool.

Also the STM32F cube has an automatic clock generator. You have to set these right, or you will not get good (or any) results.

If you're not worried about clock drift, scale the external clock to 1 MHz, and then use the other registers to get the results you want.

\$\endgroup\$

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.