Source: https://mokxi.com/learn/stm32-blue-pill-registers
Updated: 2026-09-27

Learn

# Blink a Blue Pill by writing the registers yourself

Written by the Mokxi team, updated September 27, 2026

Sign up free or see every lesson

- 192 parts on the bench
- 25 boards running now
- 1.00x real time, on every board

Click to open it in the editor

PC13 driven through RCC, CRH and BSRR by hand, with the register values printed first.

pinMode and digitalWrite hide three register writes. On an STM32 those three writes are worth learning, because the day you move from the Arduino core to the vendor’s HAL or to bare metal, they are what every GPIO function is doing, and the most common STM32 bug of all lives in the first one.

The circuit above is an STM32F103 Blue Pill with an LED and a 1 k resistor from 3.3 V to PC13, the same pin as the board’s own green LED. The sketch blinks it without pinMode or digitalWrite, and before it starts it prints three register values. Press Run and the serial monitor reads:

The three lines are "CRH with port C’s clock off: 0x0", then "CRH after IOPCEN, reset value: 0x44444444", then "CRH with PC13 an output: 0x44244444". After that both LEDs blink once a second.

## Step 1: turn on the port’s clock

Every peripheral on an STM32 starts with its clock switched off to save power, and a peripheral with no clock does nothing. GPIO port C’s clock is bit 4 of RCC->APB2ENR, named IOPCEN. Until it is set, the port’s registers read as zero and ignore writes.

The sketch proves it. It writes a value to GPIOC->CRH before enabling the clock and reads back 0x0: the write went nowhere. After RCC->APB2ENR |= RCC_APB2ENR_IOPCEN, the same register reads 0x44444444, its reset value. On real hardware, forgetting this line produces a program that compiles, runs and does absolutely nothing, with no error anywhere. Mokxi models the gate the same way, on purpose.

## Step 2: configure the pin in CRH

The F103 describes each pin with four bits: two for MODE (input, or an output at 10, 2 or 50 MHz) and two for CNF (push-pull, open-drain, or an alternate function). Pins 0 to 7 live in CRL and pins 8 to 15 in CRH. PC13 is the sixth nibble of CRH, bits 20 to 23.

The reset value 0x4 in every nibble means CNF 01, MODE 00: a floating input. The sketch clears PC13’s nibble and writes 0x2, MODE 10 (output, 2 MHz) and CNF 00 (push-pull), which is why the third line reads 0x44244444: one nibble changed, the others untouched. The later STM32 families like the F4 use a different layout (MODER, OTYPER, OSPEEDR), which is the first thing that breaks when F1 code is moved to an F4.

firmware/bluepill/examples/registers (condensed from the program running above)

## Step 3: BSRR, and why not ODR

You could change the pin by reading ODR, the output register, flipping bit 13 and writing it back. BSRR is better. Writing a 1 to one of its low 16 bits sets that pin; writing a 1 to one of its high 16 bits resets it; zeros change nothing. It is a single write with no read first, so if an interrupt changes another pin on port C between your read and your write, BSRR cannot undo that change, and a read-modify-write of ODR can.

The LED on PC13 is wired from 3.3 V to the pin, so the pin sinks its current and the LED lights when the pin is low. That is the opposite of an Uno’s pin 13, and the reason the sketch resets the pin to turn the LED on.

## Why the resistor is 1 k, and where HAL fits

PC13, PC14 and PC15 are special on the F103. They are powered through the backup domain’s power switch, and ST’s datasheet limits them to sinking about 3 mA, at no more than 2 MHz, and says they must not be used as a current source, for example to drive an LED from the pin to ground. The Blue Pill’s LED is wired the other way, from 3.3 V into the pin, through a 1 k resistor: (3.3 - 2) / 1000 is about 1.3 mA. A 220 ohm resistor there would ask for about 6 mA, so this circuit uses 1 k to stay inside the limit. For a brighter LED on a real board, pick an ordinary pin such as PA5 and a 330 ohm resistor.

ST’s HAL library does the same three steps with named functions: __HAL_RCC_GPIOC_CLK_ENABLE(), HAL_GPIO_Init() with a GPIO_InitTypeDef, and HAL_GPIO_WritePin() or HAL_GPIO_TogglePin(), which writes BSRR underneath. Mokxi does not run HAL binaries, because they start by configuring the clock tree and the flash wait states, which are not modeled; the Blue Pill here runs at a fixed 72 MHz. The registers above are exactly what those HAL calls end up writing.

One real-world caution: many boards sold as Blue Pills carry a clone of the STM32F103 rather than ST’s chip. They usually run simple GPIO code like this fine, but can differ in flash size and in debugger support, so check the chip marking if a board behaves oddly.

## Questions

Why does my STM32 GPIO code do nothing?

The most common cause by far is the port clock left off in RCC. On the F1 family GPIO clocks are in RCC->APB2ENR; on the F4 they are in RCC->AHB1ENR. With the clock off, writes are ignored and reads return zero.

Is the Blue Pill LED active low?

Yes. It is wired from 3.3 V through a resistor to PC13, so it lights when PC13 is driven low.

What is the difference between BSRR and BRR?

BSRR can set pins (low half) and reset pins (high half) in one write. BRR can only reset them. Both leave every pin you did not name alone.

Can I use the Arduino functions and registers in the same sketch?

Yes. This sketch still uses Serial and delay() from the runtime. Just do not let both touch the same pin’s configuration, or the last one to write CRH wins.

Related

## Keep going

Blink an LED with Arduino: Wiring and the Resistor Arduino Uno vs ESP32 vs Pico: Which to Learn First? The Blue Pill simulator The STM32 simulator page What the Blue Pill model includes

## Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.

Start building Open the editor
