Here, after graduating from the 8051, we move the stage to 32-bit Arm Cortex-M (STM32F103). Compared to the 8-bit 8051, the register width, address space, and the number of peripherals increase a level, and most importantly, thanks to the CMSIS standard, you can access registers of any Cortex-M in a similar way. This chapter focuses on building the three foundations — Cortex-M architecture, CMSIS header, clock tree — that will support the next seven chapters (14~20), rather than writing a lot of code.
This track controls registers directly without using the STM32 HAL/SPL library. This is for the learning purpose of seeing the actions hidden behind abstraction functions directly. Instead, we use the standard symbols provided by CMSIS (RCC->APB2ENR, GPIOC->ODR, SystemCoreClock, etc.) to maintain readability and portability.
Simulator Note: The μVision STM32F103 simulator models the core and basic registers like RCC·GPIO. In the examples in this chapter, you observe RCC register bit changes and variable increments in the Register/Watch window, and clock tree calculations are verified on the PC using
pc_test.c.
Learning Objectives
- Explains structural differences (width, address, bus) between Cortex-M (STM32) and 8051.
- Understands what CMSIS is and what symbols
stm32f10x.hprovides. - Applies the RCC rule of "turn on the clock before using peripherals."
- Follows the clock tree from HSE/HSI·PLL·AHB/APB prescalers.
- Calculate SYSCLK/HCLK/PCLK1/PCLK2 from the settings value.
Target Environment
- Compiler: Keil MDK-ARM (Arm Compiler 6).
- Device: STM32F103C8 (μVision simulator).
- Verification: μVision simulator (Peripherals/Registers, Watch window). Clock calculation is PC
gcc(pc_test.c).
Key Concepts
1) Cortex-M vs 8051 — What changes
| Item | 8051 (AT89C52) | Cortex-M3 (STM32F103) |
|---|---|---|
| Width | 8-bit | 32-bit |
| Clock | Usually ~12MHz | Up to 72MHz |
| Register access | SFR (P1, TMOD …) |
Memory-mapped structures (GPIOC->ODR …) |
| Peripheral clock | Always on | Individual enable required via RCC |
| Standard header | reg52.h |
CMSIS stm32f10x.h |
The most important new habit is clock gating. In 8051, ports are always on, but in STM32, to save power, all peripheral clocks are off by default, so you need to enable them via RCC before use.
2) CMSIS and stm32f10x.h
CMSIS (Cortex Microcontroller Software Interface Standard) is a standard layer defined by Arm. The parts we use are two-fold.
- CMSIS-CORE: Core peripherals (NVIC, SysTick) and intrinsics (
__NOP,NVIC_EnableIRQ, etc.).core_cm3.h. - Device Header
stm32f10x.h: Provided by ST, defines all peripherals as C structures.
/*The form (concept) provided by stm32f10x.h*/
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; /*Bit names are defined as macros*/
GPIOC->ODR ^= (1u << 13); /*Port = Struct pointer with an address*/
RCC and GPIOC are structure pointers pointing to fixed addresses, and bit masks like RCC_APB2ENR_IOPCEN are also defined in the header, allowing you to avoid magic numbers.
3) RCC — The Gateway to Turning on Clocks
RCC (Reset and Clock Control) manages both the clock sources and peripheral clock gates. The enable registers differ depending on which bus each peripheral is attached to.
| Bus | Enable Register | Representative Peripherals |
|---|---|---|
| AHB | RCC->AHBENR |
DMA, CRC |
| APB2 (High-speed) | RCC->APB2ENR |
GPIOA~E, ADC1/2, USART1, TIM1, AFIO |
| APB1 (Low-speed) | RCC->APB1ENR |
TIM2~4, USART2/3, I2C, SPI2 |
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN; /*Turn on GPIOA, GPIOC*/
4) Clock Tree
The STM32 clock starts from the source and flows through Busy/Body-ratio to each bus.
HSI (internal 8MHz) ─┐
├─► (PLL × multiplier) ─► SYSCLK ─► AHB(/HPRE) ─► HCLK ─┬─► APB1(/PPRE1) ─► PCLK1
HSE (external 8MHz) ─┘ └─► APB2(/PPRE2) ─► PCLK2
- HSI: Internal RC oscillator (~8MHz), inaccurate but no external components needed.
- HSE: External crystal (usually 8MHz), precise. Combined with PLL to generate high-speed clocks.
- PLL: Multiplies the input by an integer. For F103, 8MHz × 9 = 72MHz is standard.
- Prescaler: Divides SYSCLK by each bus to fit within the limits.
5) Bus Clock Limits — Why APB1 is part/2-people
The limits for F103 are SYSCLK/HCLK ≤ 72MHz, PCLK1 (APB1) ≤ 36MHz, PCLK2 (APB2) ≤ 72MHz. Therefore, with the standard 72MHz setting, APB1 is /2 (=36MHz), APB2 is /1 (=72MHz). We directly calculate this in pc_test.c.
Note: If the APB prescaler is not 1, the timer clock on that bus becomes 2 times the PCLK (covered in Timer Chapter 17). For simplification, this calculation only deals with the bus clock.
Creating a μVision Project
Project → New µVision Project…→ Folder·Namestm32_intro.- Device: STMicroelectronics → STM32F1 Series → STM32F103 → STM32F103C8.
- In the
Manage Run-Time Environmentwindow, check CMSIS → CORE, check Device → Startup. (This automatically includesstm32f10x.halong with startup andsystem_stm32f10x.c.) - Add
main.cto the project and copy-paste the code below. - Add device macro
STM32F10X_MDtoOptions for Target → C/C++ → Define. (C8 is 64KB flash = Medium Density) - Select
Options for Target → Debug → Use Simulator. Enter-pSTM32F103C8in the Dialog DLL parameters. - Rebuild(F7) → Start Debug(Ctrl+F5).
Examples
Example 1 — main.c: Turning On Peripheral Clocks
This is a CMSIS standard idiom for turning on the clocks of GPIOA and GPIOC. The goal is to visually confirm the changes in the RCC register bits rather than the operation itself.
#include "stm32f10x.h"
/*
*Lesson 13 Example — Basic Form of CMSIS Register Access.
*Target: STM32F103C8, Compiler: Keil MDK-ARM (Arm Compiler 6).
*
*The goal of this section is 'understanding the structure.' Actually turning on an LED with GPIO output will be done in Lesson 14.
*Here, we show the standard idiom for enabling peripheral clocks using CMSIS headers,
*and observe the result (RCC register bits) changing in the simulator register window.
*
*STM32's rule: "Enable the peripheral clock first before using the peripheral."
*A peripheral with its clock off will not respond even if you write to its registers.
*/
int main(void)
{
volatile uint32_t heartbeat = 0; /*A counter showing it is alive.*/
/*GPIOA and GPIOC are attached to the APB2 bus. Enable the clocks for both ports.
*Before enabling, the GPIOA->/GPIOC-> registers do not operate.*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;
/*SystemCoreClock is a global variable managed by CMSIS representing the current core clock (Hz).
*If added to the Watch window, the system_stm32f10x.c setting (usually 72000000) can be seen.*/
while (1) {
heartbeat++; /*Observe the value increasing in the Watch window.*/
}
}
Example 2 — pc_test.c: Clock Tree Calculation
Calculate the frequency of each bus from the settings (HSE·PLL multiplier·prescaler). This is an exercise in tracing the clock tree by hand without hardware.
#include <stdio.h>
#include <stdint.h>
/*
*Lesson 13 PC Check — Manually calculating the STM32F103 clock tree.
*
*HSE (external 8MHz) → PLL (× multiplier) → SYSCLK → AHB (/HPRE) → HCLK
* → APB1(/PPRE1) → PCLK1
* → APB2(/PPRE2) → PCLK2
*
*Determine the frequency of each bus using only the configuration values, without hardware.
*(F103 constraints: SYSCLK/HCLK ≤ 72MHz, PCLK1 ≤ 36MHz, PCLK2 ≤ 72MHz)
*/
typedef struct {
uint32_t hse_hz; /*External crystal frequency*/
uint32_t pll_mul; /*PLL multiplication factor (2~16)*/
uint32_t ahb_div; /*AHB prescaler (1,2,...,512)*/
uint32_t apb1_div; /*APB1 prescaler*/
uint32_t apb2_div; /*APB2 prescaler*/
} clock_cfg_t;
static void report(const char *name, clock_cfg_t c)
{
uint32_t sysclk = c.hse_hz * c.pll_mul;
uint32_t hclk = sysclk / c.ahb_div;
uint32_t pclk1 = hclk / c.apb1_div;
uint32_t pclk2 = hclk / c.apb2_div;
printf("[%s]\n", name);
printf(" SYSCLK = %lu Hz\n", (unsigned long)sysclk);
printf(" HCLK = %lu Hz\n", (unsigned long)hclk);
printf(" PCLK1 = %lu Hz\n", (unsigned long)pclk1);
printf(" PCLK2 = %lu Hz\n", (unsigned long)pclk2);
}
int main(void)
{
/*Standard 72MHz configuration: HSE 8MHz × 9 = 72MHz, AHB/1, APB1/2, APB2/1*/
clock_cfg_t std72 = { 8000000u, 9u, 1u, 2u, 1u };
/*Another example: HSE 8MHz × 4 = 32MHz, AHB/1, APB1/1, APB2/1*/
clock_cfg_t cfg32 = { 8000000u, 4u, 1u, 1u, 1u };
report("72MHz standard", std72);
report("32MHz example", cfg32);
return 0;
}
Checking with Simulator
- After starting debugging, look at
APB2ENRinPeripherals → System Viewer → RCC(or the Registers window). After passing one line ofmain, IOPAEN (bit 2)·IOPCEN (bit 4) become 1, and the value becomes0x00000014. - If you add
heartbeatto the Watch window and run it, the value keeps increasing. - If you add
SystemCoreClockto Watch, the configured core clock (usually72000000) is visible. - Expected behavior: Clock enable bits are set, and counters increase in an infinite loop.
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
[72MHz standard]
SYSCLK = 72000000 Hz
HCLK = 72000000 Hz
PCLK1 = 36000000 Hz
PCLK2 = 72000000 Hz
[32MHz example]
SYSCLK = 32000000 Hz
HCLK = 32000000 Hz
PCLK1 = 32000000 Hz
PCLK2 = 32000000 Hz
Common Mistakes
Q. I wrote a value to the GPIO register, but nothing happens.
A. In most cases, the clock is not turned on. STM32 peripherals are off by default, so you need to do RCC->APB2ENR |= RCC_APB2ENR_IOPxEN; first for the registers to work. This is the biggest difference from 8051.
Q. I can't find stm32f10x.h, or it says RCC is not defined.
A. Check whether CMSIS:CORE and Device:Startup are checked in Manage Run-Time Environment, and whether a device macro like STM32F10X_MD is added in C/C++ Defines. Without the density macro, the header doesn't know which chip it is, and compilation fails.
Q. I always get confused about which peripheral is APB1 and which is APB2.
A. Roughly remember 'fast ones (high-speed) are APB2, slow ones are APB1', but the clock tree diagram in the datasheet is the correct reference. GPIO, ADC, USART1, TIM1 are on APB2, while TIM2~4, USART2/3, I2C, SPI2 are on APB1.
Q. HSI is 8MHz, so how does it become 72MHz?
A. The PLL multiplies it. If you input HSE (or HSI/2) and multiply by 9, you get 72MHz. In system_stm32f10x.c, SystemInit() applies this setting at the startup stage and records the result in SystemCoreClock.
Summary
- STM32 is a 32-bit Cortex-M, and each peripheral requires the clock to be enabled via RCC to operate.
- CMSIS
stm32f10x.hprovides registers as structures and bit macros to help direct control. - Clock tree: source (HSI/HSE) → PLL → SYSCLK → AHB/APB prescaler → each bus.
- Due to F103 limitations, APB1 is divided by 2 (36MHz), and APB2 is divided by 1 (72MHz) as standard.
- Without HAL/SPL, directly manipulate registers while maintaining readability using CMSIS standard symbols.
Try it yourself
Solve the assignment below. The reference solution is shown below.