Making things do stuff – Part 2

Last time we looked at the basics of hardware manipulation in C++.   This time we’ll apply this to some real hardware and have a look at the code generated.

“Hello World” for embedded programmers

Although these articles are about general principles it’s always nice to have a concrete example to work with.  In this case I’m basing my code on an STM32F4 Discovery board based on an ARM Cortex M4 processor.

The classic “Hello World” for embedded programmers is to flash one of the LEDs on the target board.

On the STM Discovery board there are four LEDs.  The LEDs are controlled via one of the General Purpose Input-Output (GPIO) ports on the microcontroller.

GPIO basics

Central to modern microcontrollers is the ability to perform general purpose I/O (GPIO). External pins can then be connected to physical hardware to sense or control voltages.

Conceptually, we could imagine each pin being represented by a bit in a hardware register.  For example, we could have a output register where writing 1/0 to a bit would cause a 5V/0V output to be produced on the physical pin.  Similarly, applying 5V/0V to an input pin would cause the appropriate bit in the input register to signal 1/0.

Such a mechanism would lead to a profusion of pins, and be very inflexible – there would always be a fixed number of input and output pins.  In most real world microcontrollers each physical pin can be configured for a variety of uses.  Usually the I/O pins are multiplexed so they can not only act as either inputs or outputs, but also perform alternative operations (e.g. transmit pin for RS232).

This means for a typical GPIO port there are multiple hardware registers involved in its use:

  • A function-select register to specify whether the pin is being used for GPIO or some other function
  • A direction register to specify whether the pin is an input or output
  • One (or more) data registers for reading / writing data

The number of, and operation of, the GPIO registers is hardware-specific and will vary from manufacturer to manufacturer; but they typically all have the same basic principles.

STM32F407VG GPIO

The STM32F407VG microcontroller has 9 identical GPIO ports, labelled A – I.  Each GPIO port is highly configurable and each output pin can have several functions (usually digital input and output, analog; and up to 16 alternative functions)

Each peripheral on the the STM32F407 is clock gated.  The clock signal does not reach the peripheral until we tell it to do so by way of setting a bit in a register (known as the Reset and Clock Control, or RCC, register).   By default, clock signals never reach peripherals that are not in use, thus saving power.

The memory map

One of the very convenient features of the ARM Cortex M architecture is that the memory map is well-defined.  The basic memory map looks like this:

The STM32F407VG has three peripheral buses

  • 1 Advanced High-Performance Bus (AHB)
  • 2 Advanced Peripheral Bus (APB)

 

All the GPIO ports are on the Advanced High Performance bus.  The addresses of the ports are shown in the table below

The GPIO hardware registers

There are 10 configuration / data registers for each GPIO port but for our purposes we only need to consider three –

  • the Mode register, to configure GPIO operation
  • the Input Data register
  • the Output Data register.

Notice that all registers are 32-bit (although in many cases not all 32 bits are used).

To keep the code simple we’re going to do bare-minimum configuration and pretty much ignore good practices like error-checking, parameter validation, etc.

In a real-world system we would probably want to explicitly configure the output type, output speed and pull-up/pull-down settings for the port.  The default settings for these registers are fine for this example, so to save space I’m going to ignore those registers.

There are three steps to getting our “Hello World” flashing LED:

  • Declare our hardware register pointers
  • Enable the GPIO port clock
  • Configure the port for output
  • Flash the LED by turning the pin on/off

Declaring the hardware registers

The LEDs on the STM32F4-Discovery are all on GPIO port D; on pins 12 – 15.  For this exercise we’ll flash the blue LED (pin 15).

To add a (bare minimum) of flexibility I’ve declared the addresses of the hardware components as constant-expressions.

#include <cstdint>

// inline function to help
// reduce code clutter
//
using std::uint32_t;

inline
volatile uint32_t* reg32_ptr(uint32_t addr)
{
  return reinterpret_cast<volatile uint32_t*>(addr);
}

// Register addresses
//
constexpr auto RCC_addr  { 0x40023830 };
constexpr auto GPIO_addr { 0x40020C00 };

// Hardware access pointers
//
auto const RCC_AHB1ENR { reg32_ptr(RCC_addr) };

auto const GPIO_MODER  { reg32_ptr(GPIO_addr + 0x00) };
auto const GPIO_IDR    { reg32_ptr(GPIO_addr + 0x10) };
auto const GPIO_ODR    { reg32_ptr(GPIO_addr + 0x14) };

int main()
{
  // ...
}

Enabling the GPIO port clock

Each device on the AHB1 bus is enabled using a special configuration register, the AHB1 Reset and Clock Control (RCC) Enable Register

We could hard-code this value for our exercise (which would work just fine).  As a generic solution it’s worth noting that bits [13:10] of the port’s address map onto the bit position in the RCC AHB1 Enable Register:

40020000 =>  0b0100 0000 0000 0010 0000 0000 0000 0000  => Port A
40020400 =>  0b0100 0000 0000 0010 0000 0100 0000 0000  => Port B
40020800 =>  0b0100 0000 0000 0010 0000 1000 0000 0000  => Port C
40020C00 =>  0b0100 0000 0000 0010 0000 1100 0000 0000  => Port D

Thus we can mask off those 4 bits of a port’s address to work out which port number it is.

inline void enable_device(uint32_t address)
{
  // The 4 bits [13:10] identify the
  // port.
  //
  auto port_number = (address >> 10) & 0x0F;

  *RCC_AHB1ENR |= (1 << port_number);
}

Configuring the port for output

To enable a pin for output we must configure its port’s mode.

Each pin has four modes of operation, thus requiring two configuration bits per pin:

0b00  Input
0b01  Output
0b10  Alternative function (configured via the AFRH and AFRL registers)
0b11  Analogue

We can generalise this into a simple function.  Note that, because there are two configuration bits per pin, we must multiply the pin number by two to get the correct offset into the register.

inline void set_as_output(unsigned int pin_num)
{
  *GPIO_MODER |= (0b01 << (pin_num * 2));
}

Flashing the LED

Flashing the LED requires turning on / off the appropriate pin.  A pair of functions will suffice.  Notice, unlike the mode configuration, controlling a pin only requires one bit (on/off).

inline void turn_on_pin(unsigned int pin_num)
{
  *GPIO_ODR |= (1 << pin_num);
}

inline void turn_off_pin(unsigned int pin_num)
{
  *GPIO_ODR &= ~(1 << pin_num);
}

Finally, our main() function puts all this together:

int main()
{
  enable_device(GPIO_addr);  // GPIO_addr defined above

  set_as_output(15);         // Pin 15 is the blue LED

  while(true)
  {
    turn_on_pin(15);
    sleep(1000);             // Some generic busy-wait...
    turn_off_pin(15);
    sleep(1000);
  }
}

Under the hood

Just to close, let’s have a quick look at the generated assembler for this code.  For this example I’m using the ARM gcc compiler toolchain, generating Thumb2 instructions.  I’ve turned the optimiser off (O0).

I don’t want to dwell on this code much here.  Since this is pretty much the minimum code we could write to get our “Hello World” working it’s a useful benchmark for any code we generate later.

In the code below I’ve removed the opcodes for the call to the generic sleep() function since they aren’t really relevant to what we’re examining.

A couple of points on the assembly code below:

  • The addresses of the registers are stored as constants at the end of main() (DCD stands for Define Constant Double-word).  They are loaded indirectly into the registers as offsets from the program counter.
  • When clearing a pin the compiler generates a (more efficient) bit-clear operation (BIC) rather than a bitwise AND.
; main()
;
08000d14:   ldr     r2, [pc, #36]       ; r2 = 0x40023830 <RCC_AHB1ENR>
08000d16:   ldr     r3, [r2, #0]        ; r3 = *r2
08000d18:   orr.w   r3, r3, #8          ; r3 = r3 | 0x08
08000d1c:   str     r3, [r2, #0]        ; *r2 = r3

; *GPIO_MODER |= (0b01 << (pin_num * 2));
;
08000d1e:   ldr     r2, [pc, #32]       ; r2  = 0x40020C00 <GPIO_MODER>
08000d20:   ldr     r3, [r2, #0]        ; r3  = *r2
08000d22:   orr.w   r3, r3, #1073741824 ; r3  = r3 | 0x40000000
08000d26:   str     r3, [r2, #0]        ; *r2 = r3

; while(true) {
; *GPIO_ODR |= (1 << pin_num);
;
loop:
08000d28:   ldr     r2, [pc, #24]       ; r2  = 0x40020C14 <GPIO_ODR>
08000d2a:   ldr     r3, [r2, #0]        ; r3  = *r2
08000d2c:   orr.w   r3, r3, #32768      ; r3  = r3 | 0x8000
08000d30:   str     r3, [r2, #0]        ; *r2 = r3

; *GPIO_ODR &= ~(1 << pin_num);
;
08000d32:   ldr     r3, [r2, #0]        ; r3  = *r2 <GPIO_ODR>
08000d34:   bic.w   r3, r3, #32768      ; r3  = r3 & ~0x8000
08000d38:   str     r3, [r2, #0]        ; *r2 = r3

; }
;
08000d3a:   b.n     0x8000d28           ; goto loop
                                        ; Register addresses:
08000d3c:   dcd     1073887280          ; 0x40023830
08000d40:   dcd     1073875968          ; 0x40020C00
08000d44:   dcd     1073875988          ; 0x40020C14

Summary

In this article we’ve looked at the basics of implementing GPIO on an ARM Cortex M processor.  The code we’ve generated is pretty crude to be sure.  However, the purpose of this article was to give us a concrete example we can extend and compare against.

In the next article we’ll look at how we can extend this code by encapsulating it within a class.

Glennan Carnie
Dislike (0)
Website | + posts

Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

About Glennan Carnie

Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry. He specialises in C++, UML, software modelling, Systems Engineering and process development.
This entry was posted in C/C++ Programming, Cortex, General and tagged , , , , , , , . Bookmark the permalink.

4 Responses to Making things do stuff – Part 2

  1. Evgeny Astigeevich says:

    Hi,

    A typo in reg32_ptr: "return reinterpret_cast(addr);"
    It should be reinterpret_cast()

    Like (0)
    Dislike (0)
  2. Evgeny Astigeevich says:

    In reinterpret_cast it should be volatile uint32_t * .

    Like (0)
    Dislike (0)
  3. Thanks Evgeny. Well spotted!

    Like (0)
    Dislike (0)
  4. Andreas Sundstrom says:

    Hi,

    Thanks for the nice articles.
    I was following along with my STM32L476 Nucleo-64 (STM32L476RG)

    I had issues with this, function as the init state for the register in my case was all ones.

    I ended up with this, not sure if it's the best way to do it?
    inline void set_as_output(unsigned int pin_num)
    {
    *GPIO_MODER &= ~(0b11 << (pin_num * 2)) | 0b01 << ( pin_num * 2 );
    }

    The leading zero in 0b01 was never committed otherwise as 1 ored with 0 still is one.

    Like (0)
    Dislike (0)

Leave a Reply