© Warren Gay 2018

Warren Gay, Beginning STM32, https://doi.org/10.1007/978-1-4842-3624-6_4

4. GPIO

Warren Gay

(1)St. Catharines, Ontario, Canada

In this chapter, you’re going to use the libopencm3 library to build a blink program from source code. This example program demonstrates the configuration and use of GPIO (General Purpose Input/Output). The program presented is a slightly modified version of a libopencm3 example program named miniblink. It has been modified to provide a different timing so that it will be obvious that your newly flashed code is the one executing. After building and running this program, we’ll discuss the GPIO API (Application Programming Interface) that is provided by libopencm3.

Building miniblink

Change to the subdirectory miniblink as shown, and type make:

$ cd ~/stm32f103c8t6/miniblink
$ make
gmake: Nothing to be done for 'all'.

If you see the preceding message, it may be because you have already built all of the projects from the top level (there is nothing wrong with that). If, however, you made changes to the source-code files, make should automatically detect this and rebuild the affected components. Here, we just want to force the rebuilding of the miniblink project. To do this, type make clobber in the project directory, and then make afterward, as shown:

$ make clobber
rm -f *.o *.d generated.* miniblink.o miniblink.d
rm -f *.elf *.bin *.hex *.srec *.list *.map
$ make
...
arm-none-eabi-size miniblink.elf
   text   data    bss    dec    hex    filename
    696      0      0    696    2b8    miniblink.elf
arm-none-eabi-objcopy -Obinary miniblink.elf miniblink.bin

When you do this, you will see a few long command lines executed to compile and link your executable named miniblink.elf. To flash your device, however, we also need an image file. The last step of the build shows how the ARM-specific objcopy utility is used to convert miniblink.elf into the image file miniblink.bin.

Just prior to the last step, however, you can see that the ARM-specific size command has dumped out the sizes of the data and text sections of your program. Our miniblink program consists only of 696 bytes of flash (section text) and uses no allocated SRAM (section data). While this is accurate, there is still SRAM being used for a call stack.

Flashing miniblink

Using the make framework again, we can now flash your device with the new program image. Hook up your ST-Link V2 programmer, check the jumpers, and execute the following:

$ make flash
/usr/local/bin/st-flash write miniblink.bin 0x8000000
st-flash 1.3.1-9-gc04df7f-dirty
2017-07-30T12:57:56 INFO src/common.c: Loading device parameters....
2017-07-30T12:57:56 INFO src/common.c: Device connected is:
    F1 Medium-density device, id 0x20036410
2017-07-30T12:57:56 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB),
    Flash: 0x20000 bytes (128 KiB) in pages of 1024 bytes
2017-07-30T12:57:56 INFO src/common.c: Attempting to write 696 (0x2b8)
    bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08000000 erased
2017-07-30T12:57:56 INFO src/common.c: Finished erasing 1 pages of
    1024 (0x400) bytes
...
2017-07-30T12:57:57 INFO src/common.c: Flash written and verified!
    jolly good!

Once this is done, your device should automatically reset and start the flashed miniblink program. With the modified time constants used, you should see it now blinking frequently, with a mostly-on 70/30 duty cycle. Your supplied device blink program likely used a slower 50/50 duty cycle instead. If your blink pattern varies somewhat from what is described, don’t worry. The important point is that you’ve flashed and run a different program.

This program does not use a crystal-controlled CPU clock. It uses the internal RC clock (resistor/capacitor clock). For this reason, your unit may flash quite a bit faster or slower than someone else’s unit.

miniblink.c Source Code

Let’s now examine the source code for the miniblink program you just ran. If not still in the subdirectory miniblink, change to there now:

$ cd ~/stm32f103c8t6/miniblink

Within this subdirectory, you should find the source program file miniblink.c. Listing 4-1 illustrates the program without comment boilerplate:

Listing 4-1 Listing of miniblink.c
0019: #include <libopencm3/stm32/rcc.h>
0020: #include <libopencm3/stm32/gpio.h>
0021:
0022: static void
0023: gpio_setup(void) {
0024:
0025:   /* Enable GPIOC clock. */
0026:   rcc_periph_clock_enable(RCC_GPIOC);
0027:
0028:   /* Set GPIO8 (in GPIO port C) to 'output push-pull'. */
0029:   gpio_set_mode(GPIOC,GPIO_MODE_OUTPUT_2_MHZ,
0030:                 GPIO_CNF_OUTPUT_PUSHPULL,GPIO13);
0031: }
0032:
0033: int
0034: main(void) {
0035:   int i;
0036:
0037:   gpio_setup();
0038:
0039:   for (;;) {
0040:       gpio_clear(GPIOC,GPIO13);      /* LED on */
0041:       for (i = 0; i < 1500000; i++)  /* Wait a bit. */
0042:           __asm__("nop");
0043:
0044:       gpio_set(GPIOC,GPIO13);        /* LED off */
0045:       for (i = 0; i < 500000; i++)   /* Wait a bit. */
0046:           __asm__("nop");
0047:   }
0048:
0049:   return 0;
0050: }
Note

The line numbers appearing at the left in the listings are not part of the source file. These are used for ease of reference only.

The structure of the program is rather simple. It consists of the following:

  1. A main program function declared in lines 33–50. Note that unlike a POSIX program, there are no argc or argv arguments to function main.

  2. Within the main program, function gpio_setup() is called to perform some initialization.

  3. Lines 39–47 form an infinite loop, where an LED is turned on and off. Note that the return statement in line 49 is never executed and is provided only to keep the compiler from complaining.

Even in this simple program there is much to discuss. As we will see later, this example program runs at a default CPU frequency since none is defined. This will be explored later.

Let’s drill down on the simple things first. Figure 4-1 illustrates how the LED that we’re flashing is attached to the MCU on the Blue Pill PCB. In this schematic view, we see that power enters the LED from the +3.3-volt supply through limiting resistor R1. To complete the circuit, the GPIO PC13 must connect the LED to ground to allow the current to flow. This is why the comment on line 40 says that the LED is being turned on, even though the function call is gpio_clear(). Line 44 uses gpio_set() to turn the LED off. This inverted logic is used simply because of the way the LED is wired.

A465982_1_En_4_Fig1_HTML.jpg
Figure 4-1 LED connected to PC13 on the Blue Pill PCB

Look again at these function calls:

gpio_clear(GPIOC,GPIO13);    /* LED on */
...
gpio_set(GPIOC,GPIO13);      /* LED off */

Notice that these two calls require two arguments, as follows:

  1. A GPIO port name

  2. A GPIO pin number

If you are used to the Arduino environment, you are used to using something like the following:

    int ledPin = 13;  // LED on digital pin 13

    digitalWrite(ledPin,HIGH);
    ...
    digitalWrite(ledPin,LOW);

In the non-Arduino world, you generally work directly with a port and a pin. Within the libopencm3 library, you specify whether you are clearing or setting a bit based upon the function name (gpio_clear() or gpio_set()). You can also toggle a bit with the use of gpio_toggle(). Finally, it is possible to read and write the full set of pins by port alone, using gpio_port_read() and gpio_port_write() respectively.

GPIO API

This is a good place to discuss the libopencm3 functions that are available for GPIO use. The first thing you need to do is include the appropriate header files, as follows:

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>

The rcc.h file is needed for definitions so as to enable the GPIO clock. The gpio.h file is necessary for the remainder:

void gpio_set(uint32_t gpioport, uint16_t gpios);
void gpio_clear(uint32_t gpioport, uint16_t gpios);
uint16_t gpio_get(uint32_t gpioport, uint16_t gpios);
void gpio_toggle(uint32_t gpioport, uint16_t gpios);
uint16_t gpio_port_read(uint32_t gpioport);
void gpio_port_write(uint32_t gpioport, uint16_t data);
void gpio_port_config_lock(uint32_t gpioport, uint16_t gpios);   

In all of the preceding functions, the argument gpioport can be one of the macros from Table 4-1 (on other STM32 platforms, there can be additional ports). Only one port can be specified at a time.

Table 4-1 libopencm3 GPIO Macros for STM32F103C8T6

Port Macro

Description

GPIOA

GPIO port A

GPIOB

GPIO port B

GPIOC

GPIO port C

In the libopencm3 GPIO functions, one or more GPIO bits may be set or cleared at once. Table 4-2 lists the macro names supported. Note also the macro named GPIO_ALL.

Table 4-2 libopencm3 GPIO pin designation macros

Pin Macro

Definition

Description

GPIO0

(1 << 0)

Bit 0

GPIO1

(1 << 1)

Bit 1

GPIO2

(1 << 2)

Bit 2

GPIO3

(1 << 3)

Bit 3

GPIO4

(1 << 4)

Bit 4

GPIO5

(1 << 5)

Bit 5

GPIO6

(1 << 6)

Bit 6

GPIO7

(1 << 7)

Bit 7

GPIO8

(1 << 8)

Bit 8

GPIO9

(1 << 9)

Bit 9

GPIO10

(1 << 10)

Bit 10

GPIO11

(1 << 11)

Bit 11

GPIO12

(1 << 12)

Bit 12

GPIO13

(1 << 13)

Bit 13

GPIO14

(1 << 14)

Bit 14

GPIO15

(1 << 15)

Bit 15

GPIO_ALL

0xffff

All bits 0 through 15

An example of GPIO_ALL might be the following:

gpio_clear(PORTB,GPIO_ALL); // clear all PORTB pins

A special feature of the STM32 series, which libopencm3 supports, is the ability to lock a GPIO I/O definition, as follows:

void gpio_port_config_lock(uint32_t gpioport, uint16_t gpios);

After calling gpio_port_config_lock() on a port for the selected GPIO pins, the I/O configuration is frozen until the next system reset. This can be helpful in safety-critical systems where you don’t want an errant program to change these. When a selected GPIO is made an input or an output, it is guaranteed to remain so.

GPIO Configuration

Let’s now examine how the GPIO was set up in function gpio_setup(). Line 26 of Listing 4-1 has the following curious call:

rcc_periph_clock_enable(RCC_GPIOC);

You will discover throughout this book that the STM32 series is very configurable. This includes the underlying clocks needed for the various GPIO ports and peripherals. The shown libopencm3 function is used to turn on the system clock for GPIO port C. If this clock were not enabled, GPIO port C wouldn’t function. Sometimes the affected software will have operations ignored (visible result), while in other situations the system can seize up. Consequently, this is one of those critical “ducks” that needs to be “in a row.”

The reason that clocks are disabled at all is to save on power consumption. This is important for battery conservation.

Tip

If your peripheral or GPIO is not functioning, check that you have enabled the necessary clock(s).

The next call made is to gpio_set_mode() in line 29:

    gpio_set_mode(
        GPIOC,                      // Table 4-1
        GPIO_MODE_OUTPUT_2_MHZ,     // Table 4-3
        GPIO_CNF_OUTPUT_PUSHPULL,   // Table 4-4
        GPIO13                      // Table 4-2
    );

This function requires four arguments. The first argument specifies the affected GPIO port (Table 4-1). The fourth argument specifies the GPIO pins affected (Table 4-2). The third argument’s macro values are listed in Table 4-3 and define the general mode of the GPIO port.

Table 4-3 GPIO Mode Definitions

Mode Macro Name

Value

Description

GPIO_MODE_INPUT

0x00

Input mode

GPIO_MODE_OUTPUT_2_MHZ

0x02

Output mode, at 2 MHz

GPIO_MODE_OUTPUT_10_MHZ

0x01

Output mode, at 10 MHz

GPIO_MODE_OUTPUT_50_MHZ

0x03

Output mode, at 50 MHz

The macro GPIO_MODE_INPUT defines the GPIO pin as an input, as you would expect. But there are three output mode macros listed.

Each output selection affects how quickly each output pin responds to a change. In our example program, the 2 MHz option was selected. This was chosen because the speed of an LED signal change is not going to be noticed by human eyes. By choosing 2 MHz, power is saved and EMI (electromagnetic interference) is reduced.

The third argument further specializes how the port should be configured. Table 4-4 lists the macro names provided.

Table 4-4 I/O Configuration Specializing Macros

Specialization Macro Name

Value

Description

GPIO_CNF_INPUT_ANALOG

0x00

Analog input mode

GPIO_CNF_INPUT_FLOAT

0x01

Digital input, floating (default)

GPIO_CNF_INPUT_PULL_UPDOWN

0x02

Digital input, pull up and down

GPIO_CNF_OUTPUT_PUSHPULL

0x00

Digital output, push/pull

GPIO_CNF_OUTPUT_OPENDRAIN

0x01

Digital output, open drain

GPIO_CNF_OUTPUT_ALTFN_PUSHPULL

0x02

Alternate function output, push/pull

GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN

0x03

Alternate function output, open drain

Input Ports

The macro names including INPUT only apply when the second argument implies an input port. We see from Table 4-4 that inputs can be specialized three different ways:

  • Analog

  • Digital, floating input

  • Digital, pull up and down

To make greater sense of the GPIO input and its configuration, examine the simplified Figure 4-2.

A465982_1_En_4_Fig2_HTML.jpg
Figure 4-2 Basic structure of the GPIO input

The MCU receiving side is on the left side, while the external input comes in from the right. There are two protection diodes attached, which normally only come into play if the static voltage goes negative or exceeds the supply.

When the input port is configured as an analog input, the switches connected to resistors R1 and R2 are switched off. This is to avoid pulling the analog signal up or down. With the resistors disconnected, the analog input is routed to the line labeled “Analog Input” with no further signal effect for the ADC (analog-to-digital conversion) peripheral. The Schmitt trigger is also disabled to save on power consumption.

When the input port is configured for digital input, resistors R1 or R2 are in operation unless you select the “float” option GPIO_CNF_INPUT_FLOAT. For both digital input modes, the Schmitt trigger is enabled to provide a cleaner signal with hystersis. The output of the Schmitt trigger then goes to the “Alternate Function Input” and to the input data (GPIO) register. More will be said about alternate functions later, but the simple answer is that an input can act as a GPIO input or as a peripheral input.

The 5-volt-tolerant inputs are identical to the diagram shown in Figure 4-2, except that the high side protective diode allows the voltage to rise above 3.3 volts to at least +5 volts.

Note

When configuring a peripheral output, be sure to use one of the alternate function macros. Otherwise, only GPIO signals will be configured.

Output Ports

When the GPIO port is configured for output, you have four specializations to choose from:

  • GPIO push/pull

  • GPIO open drain

  • Alternate function push/pull

  • Alternate function open drain

For GPIO operation, you always choose the non-alternate function modes. For peripheral use like the USART, you choose from the alternate function modes instead. A common mistake is to configure for GPIO use, like GPIO_CNF_OUTPUT_PUSHPULL for the TX output of the USART. The correct macro is GPIO_CNF_OUTPUT_ALTFN_PUSHPULL for the peripheral. If you’re not seeing peripheral output, ask yourself if you chose from the one of the alternate function values.

Figure 4-3 illustrates the block diagram for GPIO outputs. For 5-volt-tolerant outputs (like the inputs), the only change to the circuit is that the high side protective diode is capable of accepting voltages as high as +5 volts. For non-5-volt-tolerant ports, the high side protective diode can only rise to +3.3 volts (actually, it can rise to one diode drop above 3.3 volts).

The “output control” circuit determines if it is driving the P-MOS and N-MOS transistors (in push/pull mode) or just the N-MOS (in open-drain mode). In open-drain mode, the P-MOS transistor is always kept off. Only when you write a zero to the output will the N-MOS transistor turn on and pull the output pin low. Writing a 1-bit to an open-drain port effectively disconnects the port since both transistors are put into the “off” state.

The weak input resistors shown in Figure 4-2 are disabled in output mode. For this reason, they were omitted from Figure 4-3.

The output data bits are selected from either the output (GPIO) data register or the alternate function source. GPIO outputs go to the output data register, which can be written as an entire word or as individual bits. The bit set/reset register permits individual GPIO bits to be altered as if they were one atomic operation. In other words, an interrupt cannot occur in the middle of an “and/or” operation on a bit.

Because GPIO output data is captured in the output data register, it is possible to read back what the current output settings are. This doesn’t work for alternate function configurations, however.

A465982_1_En_4_Fig3_HTML.jpg
Figure 4-3 The output GPIO driver circuit

When the output is configured for a peripheral like the USART, the data comes from the peripheral through the alternate function output line. Seeing how the data is steered in Figure 4-3 should emphasize the fact that you must configure the port for GPIO or alternate functions. I am harping on this so that you won’t waste your time having to debug this kind of problem.

Ducks in a Row

While the origin of the saying “to have one’s ducks in a row” is unclear, the one possibility that I like refers to the fairground amusement of shooting at a row of mechanical ducks. This arrangement makes it easier for the shooter to get them all and win the prize.

Peripherals on the STM32 platform are highly configurable, which also leaves more than the usual opportunity for mistakes. Consequently, I’ll refer often to this idea of getting your ducks in a row, as a shorthand recipe for success. When your peripheral configuration is not working as expected, review the ducks-in-a-row list.

Often, the problem is an omission or the use of an incorrect macro that failed to raise a compiler warning. Sequence is also often important—you need to enable a clock before configuring a device that needs that clock, for example.

GPIO Inputs

When configuring GPIO input pins, use the following procedure to configure it. This applies to GPIO inputs only—not a peripheral input, like the USART. Peripherals require other considerations, especially if alternate pin configurations are involved (they will be covered later in the book).

  1. Enable the GPIO port clock. For example, if the GPIO pin is on port C, then enable the clock with a call to rcc_periph_clock_enable(RCC_GPIOC). You must enable each port used individually, using the RCC_GPIOx macros.

  2. Set the mode of the input pin with gpio_set_mode(), specifying the port in argument one, and the GPIO_MODE_INPUT macro in argument two.

  3. Within the gpio_set_mode() call, choose the appropriate specialization macro GPIO_CNF_INPUT_ANALOG, GPIO_CNF_INPUT_FLOAT, or GPIO_INPUT_PULL_UPDOWN as appropriate.

  4. Finally, specify in the last argument in the gpio_set_mode() call all pin numbers that apply. These are or-ed together, as in GPIO12|GPIO15, for example.

Digital Output, Push/Pull

Normally, digital outputs are configured for push/pull mode. This ducks-in-a-row advice is provided for this normal case:

  1. Enable the GPIO port clock. For example, if the GPIO pin is on port B, then enable the clock with a call to rcc_periph_clock_enable(RCC_GPIOB). You must enable each port used individually, using the RCC_GPIOx macros.

  2. Set the mode of the output pin with gpio_set_mode(), specifying the port in argument one and one of the GPIO_MODE_OUTPUT_*_MHZ macros in argument two. For non-critical signal rates, choose the lowest value GPIO_MODE_OUTPUT_2_MHZ to save power and to lower EMI.

  3. Specify GPIO_CNF_OUTPUT_PUSHPULL in argument three in the call to gpio_set_mode(). Do not use any of the ALTFN macros for GPIO use (those are for peripheral use only).

  4. Finally, specify in the last argument in the gpio_set_mode() call all pin numbers that apply. These are or-ed together, as in GPIO12|GPIO15, for example.

Digital Output, Open Drain

When working with a bus, where more than one transistor may be used to pull down a voltage, an open-drain output may be required. Examples are found in I2C or CAN bus communications. The following procedure is recommended for GPIO open-drain outputs only (do not use this procedure for peripherals):

  1. Enable the GPIO port clock. For example, if the GPIO pin is on port B, then enable the clock with a call to rcc_periph_clock_enable(RCC_GPIOB). You must enable each port used individually, using the RCC_GPIOx macros.

  2. Set the mode of the output pin with gpio_set_mode(), specifying the port in argument one, and one of the GPIO_MODE_OUTPUT_*_MHZ macros in argument two. For non-critical signal rates, choose the lowest value GPIO_MODE_OUTPUT_2_MHZ to save power and to lower EMI.

  3. Specify GPIO_CNF_OUTPUT_OPENDRAIN in argument three in the call to gpio_set_mode(). Do not use any of the ALTFN macros for GPIO use (those are for peripheral use).

  4. Finally, specify in the last argument in the gpio_set_mode() call all pin numbers that apply (one or more). These are or-ed together, as in GPIO12|GPIO15, for example.

GPIO Characteristics

This is a good place to summarize the capabilities of the STM32 GPIO pins. Many are 5-volt tolerant as inputs, while a few others are current limited for output. Using the STM32 documentation convention, ports are often referenced as PB5, for example, to refer to GPIO port B pin GPIO5. I’ll be using this convention throughout this book. Table 4-5 summarizes these important GPIO characteristics as they apply to the Blue Pill device.

Table 4-5 GPIO Capabilities: Except Where Noted, All GPIO Pins Can Source or Sink a Maximum of 25 mA of Current

Pin

GPIO_PORTA

GPIO_PORTB

GPIO_PORTC

 

3V/5V

Reset

Alt

3V/5V

Reset

Alt

3V/5V

Reset

Alt

GPIO0

3V

PA0

Yes

3V

PB0

Yes

   

GPIO1

3V

PA1

Yes

3V

PB1

Yes

   

GPIO2

3V

PA2

Yes

5V

PB2/BOOT1

No

   

GPIO3

3V

PA3

Yes

5V

JTDO

Yes

   

GPIO4

3V

PA4

Yes

5V

JNTRST

Yes

   

GPIO5

3V

PA5

Yes

3V

PB5

Yes

   

GPIO6

3V

PA6

Yes

5V

PB6

Yes

   

GPIO7

3V

PA7

Yes

5V

PB7

Yes

   

GPIO8

5V

PA8

No

5V

PB8

Yes

   

GPIO9

5V

PA9

No

5V

PB9

Yes

   

GPIO10

5V

PA10

No

5V

PB10

Yes

   

GPIO11

5V

PA11

No

5V

PB11

Yes

   

GPIO12

5V

PA12

No

5V

PB12

No

   

GPIO13

5V

JTMS/SWDIO

Yes

5V

PB13

No

3V

3 mA @ 2 MHz

Yes

GPIO14

5V

JTCK/SWCLK

Yes

5V

PB14

No

3V

3 mA @ 2 MHz

Yes

GPIO15

5V

JTDI

Yes

5V

PB15

No

3V

3 mA @ 2 MHz

Yes

The column ALT in Table 4-5 indicates where alternate functions can apply. Input GPIOs marked with “5V” can safely tolerate a 5-volt signal, whereas the others marked “3V” can only accept signals up to +3.3 volts. The column labeled Reset indicates the state of the GPIO configuration after an MCU reset has occurred.

GPIO pins PC13, PC14, and PC15 are current limited. These can sink a maximum of 3 mA and should never be used to source a current. Additionally, the documentation indicates that these should never be configured for operations of more than 2 MHz when configured as outputs.

Input Voltage Thresholds

Given that the STM32F103C8T6 can operate over a range of voltages, the GPIO input-threshold voltages follow a formula. Table 4-6 documents what you can expect for the Blue Pill device, operating at +3.3 volts.

Table 4-6 Input-Voltage Thresholds Based Upon $$ {V}_{DD}=+3.3 volts $$

Symbol

Description

Range

$$ {V}_{IL} $$

Standard low-input voltage

0 to 1.164 volts

5-volt-tolerant inputs

0 to 1.166 volts

$$ {V}_{IH} $$

High-input voltage

1.155 to 3.3/5.0 volts

You may have noticed that there is a small overlap between the high end of the $$ {V}_{IL} $$ and the low end of the $$ {V}_{IH} $$ range. The STM32 documentation indicates that there is about 200 mV of hysterisis between these input states.

Output-Voltage Thresholds

The output GPIO thresholds are documented in Table 4-7, based upon the Blue Pill device operating at +3.3 volts. Note that the ranges degrade as current increases.

Table 4-7 GPIO Output-Voltage Levels with Current <= 20 mA

Symbol

Description

Range

$$ {V}_{OL} $$

Output voltage low

0.4 to 1.3 volts

$$ {V}_{OH} $$

Output voltage high

2 to 3.3 volts

Programmed Delays

Returning now to the program illustrated in Listing 4-1, let’s examine the timing aspect of that program, repeated here for convenience:

0039:   for (;;) {
0040:       gpio_clear(GPIOC,GPIO13);      /* LED on */
0041:       for (i = 0; i < 1500000; i++)  /* Wait a bit. */
0042:           __asm__("nop");
0043:
0044:       gpio_set(GPIOC,GPIO13);        /* LED off */
0045:       for (i = 0; i < 500000; i++)   /* Wait a bit. */
0046:           __asm__("nop");
0047:   }

The first thing to notice about this segment is that the loop counts differ: 1,500,000 in line 41 and 500,000 in line 45. This causes the LED to remain on 75 percent of the time and turn off for 25 percent.

The __asm__("nop") statement forces the compiler to emit the ARM assembler instruction nop as the body of both loops. Why is this necessary? Why not code an empty loop like the following?

0041:       for (i = 0; i < 1500000; i++)    /* Wait a bit. */
0042:           ;  /* empty loop */

The problem with an empty loop is that the compiler may optimize it away. Compiler optimization is always being improved, and this type of construct could be seen as redundant and be removed from the compiled result. This feature is also sensitive to the optimize options used for the compile. This __asm__ trick is one way to force the compiler to always produce the loop code and perform the nop (no operation) instruction.

The Problem with Programmed Delay

The good thing about programmed delays is that they are easy to code. But, beyond that, there are problems:

  • How many iterations do I need for a timed delay?

  • Poor source code portability:

    • the delay will vary for different platforms

    • the delay will vary by CPU clock rate

    • the delay will vary by different execution contexts

  • Wastes CPU, which could be used by other tasks in a multi-tasking environment

  • The delays are unreliable when preemptive multi-tasking is used

The first problem is the difficulty of computing the number of iterations needed to achieve a delay. This loop count depends upon several factors, as follows:

  • The CPU clock rate

  • The instruction cycle times used

  • Single or multi-tasking environment

In the miniblink program, there was no CPU clock rate established. Consequently, this code is at the mercy of the default used. By experiment, loop counts that “seem to work” can be derived. But if you run the same loops from SRAM instead of flash, the delays will be shorter. This is because there are no wait cycles necessary to fetch the instruction words from SRAM. Fetching instructions from flash, on the other hand, may involve wait cycles, depending upon the CPU clock rate chosen.

In a multi-tasking environment, like FreeRTOS, programmed delays are a poor choice. One reason is because you don’t know how much time is consumed by the other tasks.

Finally, programmed delays are not portable to other platforms. Perhaps the source code will be reused on an STM32F4 device, where the execution efficiency is different. The code will need manual intervention to correct the timing deficiency.

All of these reasons are why FreeRTOS provides an API for timing and delay. This will be examined later when we apply FreeRTOS in our demo programs.

Summary

This chapter has necessarily covered a lot of ground, even though we’re just getting started. You’ve exercised the st-flash utility and programmed your device with the miniblink program, which was a different blink program than the one supplied with your unit.

More interestingly, the libopencm3 GPIO API was discussed, and the miniblink program was examined in detail. This explained GPIO configuration and operations. Finally, the problems of programmed delays were discussed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.26.221