We first put into practice the foundation built in Chapter 13 (CMSIS·RCC·clock tree). The STM32's "Hello, World" — LED blinking — is implemented using only registers without HAL. On Blue Pill series boards, the onboard LED is connected to PC13 as active low, so you can make this pin an output and toggle it to see the LED blink.
The key is the STM32F1's unique GPIO configuration method. Unlike the MODER of the F4/L4 series, F1 uses 4 bits per pin (MODE 2 bits + CNF 2 bits) in CRL (pins 0–7)·CRH (pins 8–15). Understanding this 4-bit combination allows all GPIO settings of the STM32F1 to follow the same pattern.
Learning Objectives
- Turn on the GPIO clock using RCC and configure the F1 With-CRL/CRH pin mode.
- Use the 4-bit MODE/CNF combination to create a "push-pull output 2MHz".
- Toggle the pin with
ODRand perform atomic set/reset withBSRR/BRR. - Understand the ON/OFF logic of the active low LED (PC13).
- Confirm pin toggling with the simulator Register/Logic Analyzer.
Target Environment
- Compiler: Keil MDK-ARM (Arm Compiler 6).
- Device: STM32F103C8 (μVision simulator).
- Verification: μVision simulator (GPIOC registers, Logic Analyzer PC13). Bit manipulation uses PC
gcc(pc_test.c).
Key Concepts
1) GPIO Configuration of F1 — with-CRL/CRH 4 bits
The STM32F1 sets the mode with 4 bits per pin. Pins 0–7 are in CRL, and pins 8–15 are in CRH. The 4 bits for pin n are placed at (n % 8) * 4.
| Bit | Name | Meaning |
|---|---|---|
| [1:0] | MODE | 00=input, 01=output 10MHz, 10=output 2MHz, 11=output 50MHz |
| [3:2] | CNF(output) | 00=push-pull, 01=open-drain, 10=AF push-pull, 11=AF open-drain |
| [3:2] | CNF(input) | 00=analog, 01=floating, 10=Pull-up/Pulldown, 11=reserved |
2) "Push-pull output 2MHz" = 0x2
Since LED driving does not need to be fast, 2MHz is sufficient. MODE=10 (2MHz output), CNF=00 (general-purpose push-pull) → 4-bit 0010 = 0x2.
PC13 → CRH, position = (13 - 8) * 4 = 4 bits starting from bit 20
Write 0x2 to [23:20] of CRH.
3) Proper setting — clear then set
To change only 4 bits, first clear that spot to 0, then OR in the new value. This prevents affecting the settings of other pins.
GPIOC->CRH &= ~(0xFu << 20); /*Clear 4-bit at PC13*/
GPIOC->CRH |= (0x2u << 20); /*Set 0010*/
4) Pin output — ODR vs BSRR/BRR
ODR(Output Data Register): read and write the entire port. Convenient for toggling (ODR ^= (1<<13)).BSRR(Bit Set/Reset): lower 16 bits=set, upper 16 bits=reset. Atomically sets a specific pin to 1 without reading.BRR(Bit Reset): sets a specific pin to 0.
GPIOC->BSRR = (1u << 13); /*PC13 = 1 (If active low, LED OFF)*/
GPIOC->BRR = (1u << 13); /* PC13 = 0 (LED ON) */
GPIOC->ODR ^= (1u << 13); /*Toggle*/
5) Active Low LED (PC13)
The PC13 LED on the Blue Pill is arranged as VCC—LED—resistor—pin, so the LED is ON when pin=0. Therefore, when toggled, it is ON for half the cycle and OFF for the other half.
Creating a μVision Project
Project → New µVision Project…→ name itstm32_gpio_out.- Device: STMicroelectronics → STM32F1 Series → STM32F103 → STM32F103C8.
- Check
Manage Run-Time Environment→ CMSIS:CORE, Device:Startup. - After adding
main.c, copy and paste the code below. - Apply
STM32F10X_MDtoOptions → C/C++ → Define. - In
Options → Debug → Use Simulator, set Dialog parameter-pSTM32F103C8. - Rebuild(F7) → Start Debug(Ctrl+F5).
Examples
Example 1 — main.c: PC13 LED Blinking
#include "stm32f10x.h"
/*
*Lesson 14 Example — Onboard LED (PC13) Blinking.
*Target: STM32F103C8 (Blue Pill series), Compiler: Keil MDK-ARM.
*
*The Blue Pill board has an LED on PC13 with active-low (PIN=0 means ON).
*GPIO configuration of STM32F1 uses CRL/CRH registers, 4 bits per pin (MODE+CNF).
*/
#define LED_PIN 13u /* PC13 */
static void delay(volatile uint32_t count)
{
while (count--) { }
}
int main(void)
{
/*1) Turn on GPIOC clock (APB2 bus)*/
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
/*2) Set PC13 as push-pull output (2MHz).
*PC13 is in pin 8~15 range, so use CRH. 4-bit position = (13-8)*4 = 20.
*MODE=10 (2MHz output), CNF=00 (general-purpose push-pull) → 4-bit value 0b0010 = 0x2.*/
GPIOC->CRH &= ~(0xFu << ((LED_PIN - 8u) * 4u)); /*Clear the corresponding 4 bits*/
GPIOC->CRH |= (0x2u << ((LED_PIN - 8u) * 4u)); /*Set 0010*/
while (1) {
GPIOC->ODR ^= (1u << LED_PIN); /*Toggle PC13*/
delay(200000u);
}
}
Example 2 — pc_test.c: CRH Bit Manipulation Check
#include <stdio.h>
#include <stdint.h>
/*
*Lecture 14 PC Check — Imitating CRL/CRH Pin Setting Bit Manipulation of STM32F1.
*Calculates read-modify-write to change a single pin to push-pull output (2MHz, 4-bit value 0x2) without hardware.
*The reset value of CRH is 0x44444444 (all pins floating input).
*/
/*Returns the new CRH value with pin(8~15) changed to mode4 (4-bit setting).*/
static uint32_t set_pin_config(uint32_t crh, unsigned int pin, uint32_t mode4)
{
unsigned int shift = (pin - 8u) * 4u;
crh &= ~(0xFu << shift); /*Clear the existing 4 bits.*/
crh |= (mode4 << shift); /*Insert the new 4 bits.*/
return crh;
}
int main(void)
{
uint32_t crh = 0x44444444u; /*CRH reset value.*/
printf("reset CRH = 0x%08lX\n", (unsigned long)crh);
crh = set_pin_config(crh, 13u, 0x2u); /*PC13 = push-pull output 2MHz.*/
printf("after PC13 output = 0x%08lX\n", (unsigned long)crh);
return 0;
}
Checking with Simulator
- Confirm in
Peripherals → General Purpose I/O → GPIOC(or Registers) thatCRHbecomes0x44244444(only bit 0x2 for PC13). - Add
PORTC.13to the Logic Analyzer in Bit mode and run it; you will see a square wave on PC13 toggling periodically. - Expected behavior: PC13 toggles 0↔1 at a regular interval (if on a real board, the onboard LED blinks).
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
reset CRH = 0x44444444
after PC13 output = 0x44244444
Common Mistakes
Q. I set it clearly, but the pin doesn’t move.
A. Make sure you turn on the GPIO clock (RCC->APB2ENR |= RCC_APB2ENR_IOPCEN) first. Without the clock, using to-CRH/ODR will not have any effect.
Q. Why is PC13 (13-8)*4 if it’s in CRH?
A. CRL holds pins 07, and CRH holds pins 815. Within CRH, pin 8 corresponds to the first 4 bits, so pin 13 is the (13-8)=5-th one → starting from bit 20.
Q. Changing this also changes the adjacent pin configuration.
A. If you overwrite with = as a whole, the other pins will be affected. Always clear that spot with &= ~(0xF<<shift) and insert the new value with |=.
Q. The LED is always on or off.
A. Remember that it is active low (PC13=0 is ON). Also, if delay is not used, it may seem to turn on too quickly. To check the toggle itself, using a Logic Analyzer is accurate.
Summary
- F1 GPIO uses 4 bits per pin (MODE+CNF) in in-CRL/CRH. Push-pull output 2MHz = 0x2.
- When changing only 4 bits, use the "clear and set" pattern to preserve the other pins.
- Output is done with
ODR(toggle) andBSRR/BRR(atomic set/reset). - PC13 LED is active low — pin=0 is ON.
- The prerequisite for all operations is RCC clock enable.
Try it yourself
Solve the assignment below. The reference solution is shown below.