Skip to content

Driveway Gate Opener Part 3 - Controller

Updated: at 01:39 PM

Table of contents

Open Table of contents

Introduction

Before prototyping the controller, it was necessary to re-orient to the way hardware programmed. When the microcontroller first starts up it is necessary to configure the peripherals to support the desired actions.

Initial SOW includes:

Controlling peripherals involues two steps: i.) Configuring registers during startup, and ii.) changing registers values to control external peripherals.

Bare Metal GPIO on STM32

One of the first surprises in bare metal embedded programming is that hardware control does not require special instructions or magical APIs. It looks like C, but it behaves like direct interaction with silicon.

This becomes especially clear when working with GPIO registers such as:

GPIOA_CRL &= ~(0xF << 4*1);
GPIOA_CRL |=  (0x2 << 4*1);

For C programmers, the above code is readable, but will a little foreign for those that write code for desktop applications. Here is a quick orientation to write to hardware.

If you look at typical microcontroller, each pin is labelled with VSS, VDD, PA0, PA1, PB0, etc. These labels originate from manufactures reference manuals. In the case of Blue Pill, STM32F101XX to STM32F103XX, it has several ports with 15 general purpose input / output (GPIO) pins. See section “9.1 GPIO function description” for a succinct explanation.

A key when writing firmware is recognizing GPIO usage structure:

Let’s start with an example code and dissect it:

Here is a prototype of a Blue Pill driving two LEDs using GPIO ports A and B, pins 0 for each. The pin labels on the board is PA0 and PB0 respectively: Blue Pill - 2 LEDs Prototype

Here is the bare-metal code to drive the two LEDs.

/* Peripheral Base Addresses */
#define RCC_BASE      0x40021000
#define RCC_APB2ENR   (*(volatile uint32_t *)(RCC_BASE + 0x18))

/* CRL = Configuration Register Low: Controls pins 0–7 */
#define GPIOA_CRL     (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_BSRR    (*(volatile uint32_t *)(GPIOA_BASE + 0x10))
#define GPIOA_BRR     (*(volatile uint32_t *)(GPIOA_BASE + 0x14))

#define GPIOB_CRL     (*(volatile uint32_t *)(GPIOB_BASE + 0x00))
#define GPIOB_BSRR    (*(volatile uint32_t *)(GPIOB_BASE + 0x10))
#define GPIOB_BRR     (*(volatile uint32_t *)(GPIOB_BASE + 0x14))

static void GPIO_Init(void);
void delay(volatile uint32_t);

int main(void) {

  GPIO_Init();

    while (1)
    {
        GPIOA_BSRR = (1 << 0);  // Drive PA0 HIGH → LED ON
        GPIOB_BRR  = (1 << 0);  // Drive PB0 LOW  → LED OFF
        delay(500000);

        GPIOB_BSRR = (1 << 0);  // Drive PB0 HIGH → LED ON
        GPIOA_BRR  = (1 << 0);  // Drive PA0 LOW  → LED OFF
        delay(500000);
    }
}

static void GPIO_Init(void) {

    /* Step 1: Enable clock for GPIOA and GPIOB */
    RCC_APB2ENR |= (1 << 2);      // Bit 2 (IOPAEN) enables GPIOA peripheral
    RCC_APB2ENR |= (1 << 3);      // Bit 3 (IOPBEN) enables GPIOB peripheral

    /* Step 2: Configure PA0 and PB0 as Output Push-Pull
      Each pin uses 4 bits. Pin 0 → shift by (0 × 4) = 0 bits
       MODE = 10 → Output 2 MHz
       CNF  = 00 → Push-pull
    */

    GPIOA_CRL &= ~(0xF << 4*0);   // Clear bits 3:0
    GPIOA_CRL |=  (0x2 << 4*0);   // Set MODE=10, CNF=00

    GPIOB_CRL &= ~(0xF << 4*0);   // Clear bits 3:0
    GPIOB_CRL |=  (0x2 << 4*0);   // Set MODE=10, CNF=00
}

void delay(volatile uint32_t count) {
    while (count--);
}

To use a GPIO port pin, the entire port must be enabled using the Rest and clock control register (RCC). The reference manual specifies the RCC start address is: 0x40021000.

#define RCC_BASE      0x40021000

Next step is to enable one of the internal clocks for the desired GPIO port(s), we enabled the port via one of the internal clocks, in this case the fast peripheral APB2:

                RCC (clock controller)
                      |
      -----------------------------------
      |               |                 |
    AHB             APB1              APB2
 (fast core)   (slow peripherals)  (fast peripherals)

The following code enables GPIO ports A and B on the fast peripherals clock:

 /* Step 1: Enable clock for GPIOA and GPIOB */
 RCC_APB2ENR |= (1 << 2);      // Bit 2 (IOPAEN) enables GPIOA peripheral
 RCC_APB2ENR |= (1 << 3);      // Bit 3 (IOPBEN) enables GPIOB peripheral

This is achieved by offsetting 0x18 bits from RCC_BASE base address:

#define RCC_APB2ENR   (*(volatile uint32_t *)(RCC_BASE + 0x18))

An aside: why volatile? Without volatile, the compiler might assume repeated reads return the same value and silently remove operations. That would break hardware interaction entirely.

Now that GPIO ports A and B are enabled with the fast peripheral clock, the next step is to configure the individual pin. As per the reference manual, the C registor low

 GPIOA_CRL &= ~(0xF << 4*0);   // Clear bits 3:0
 GPIOA_CRL |=  (0x2 << 4*0);   // MODE=10 (Output 2 MHz), CNF=00 (Push-pull)

The first line clears the bits 3:0

xxxx xxxx xxxx xxxx xxxx xxxx 0000 xxxx

and the second line configures MODE and CNF:

xxxx xxxx xxxx xxxx xxxx xxxx 0010 xxxx

Actually, line two only configures MODE, CNF was cleared to 00 by line one. Since MODE > 00 the reference manual states the configuration is then “00: General purpose output push-pull”

The GPIOA_CRL is defined is defined 0x00 bits offset from the GPIOA_BASE

 #define GPIOA_CRL     (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
 #define GPIOA_BSRR    (*(volatile uint32_t *)(GPIOA_BASE + 0x10))
 #define GPIOA_BRR     (*(volatile uint32_t *)(GPIOA_BASE + 0x14))

The same pattern is repeated for GPIOB_CRL.

With ports configured by the GPIO_Init() function, the while loop alternatly drives the pins high and low to turn on or off the LED:

int main(void) {

  GPIO_Init();

    while (1)
    {
        GPIOA_BSRR = (1 << 0);  // Drive PA0 HIGH → LED ON
        GPIOB_BRR  = (1 << 0);  // Drive PB0 LOW  → LED OFF
        delay(500000);

        GPIOB_BSRR = (1 << 0);  // Drive PB0 HIGH → LED ON
        GPIOA_BRR  = (1 << 0);  // Drive PA0 LOW  → LED OFF
        delay(500000);
    }
}

The driving of two LEDs is basic, however, understanding how to enable ports and configure pins now empowers us to further configure ports and pins to control our project.


Next Post
Designing and Building and Automated Driveway Gate Opener