Nội Dung Bài Viết
Trong quá trình lập trình STM32 thanh ghi, việc cấu hình clock STM32F103 là bước nền tảng và bắt buộc nếu bạn muốn làm chủ vi điều khiển ở mức bare-metal (không HAL, không CubeMX). Bài viết này hướng dẫn chi tiết cách cấu hình RCC, HSE, PLL để đưa STM32F103 chạy ở 72MHz, đồng thời giải thích cách tính toán xung clock và kiểm tra thanh ghi trực tiếp trong Keil.
Mục tiêu bài lập trình STM32 thanh ghi cấu hình clock
- Hiểu rõ clock tree STM32F103
- Tự cấu hình HSE 8MHz + PLL x9 = 72MHz bằng thanh ghi
- Cấu hình đúng AHB, APB1, APB2 theo RM0008
- Biết cách kiểm tra RCC register trong Keil uVision
Define struct thanh ghi cho lập trình thanh ghi cấu hình clock stm32f103rct6
Thanh ghi RCC (Reset and Clock Control)
Thanh ghi clock control register (RCC_CR)
Dựa trên RM0008, Section 7.3 (trang 99-122):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Thanh ghi Điều khiển RCC (RCC Control Register) typedef union { struct { // Structure for bit fields __IOM uint32_t HSION : 1; // Bit 0: Internal high-speed clock enable __IM uint32_t HSIRDY : 1; // Bit 1: Internal high-speed clock ready uint32_t reserved1 : 1; // Bit 2: Reserved __IOM uint32_t HSITRIM : 5; // Bit 3-7: HSI trimming __IM uint32_t HSICAL : 8; // Bit 8-15: HSI calibration value __IOM uint32_t HSEON : 1; // Bit 16: External high-speed clock enable __IM uint32_t HSERDY : 1; // Bit 17: External high-speed clock ready __IOM uint32_t HSEBYP : 1; // Bit 18: HSE bypass __IOM uint32_t CSSON : 1; // Bit 19: Clock security system enable uint32_t reserved2 : 4; // Bit 20-23: Reserved __IOM uint32_t PLLON : 1; // Bit 24: PLL enable __IM uint32_t PLLRDY : 1; // Bit 25: PLL ready uint32_t reserved3 : 6; // Bit 26-31: Reserved }; __IOM uint32_t reg; // Access entire register } RCC_CR_Type; |
Thanh ghi Clock Configuration Register (RCC_CFGR)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Thanh ghi Cấu hình RCC (RCC Configuration Register) typedef union { struct { // Structure for bit fields __IOM uint32_t SW : 2; // Bit 0-1: System clock switch __IM uint32_t SWS : 2; // Bit 2-3: System clock switch status __IOM uint32_t HPRE : 4; // Bit 4-7: AHB prescaler __IOM uint32_t PPRE1 : 3; // Bit 8-10: APB1 prescaler __IOM uint32_t PPRE2 : 3; // Bit 11-13: APB2 prescaler __IOM uint32_t ADCPRE : 2; // Bit 14-15: ADC prescaler __IOM uint32_t PLLSRC : 1; // Bit 16: PLL clock source __IOM uint32_t PLLXTPRE : 1; // Bit 17: HSE divider for PLL entry __IOM uint32_t PLLMUL : 4; // Bit 18-21: PLL multiplication factor __IOM uint32_t USBPRE : 1; // Bit 22: USB prescaler uint32_t reserved1 : 1; // Bit 23: Reserved __IOM uint32_t MCO : 3; // Bit 24-26: Microcontroller clock output uint32_t reserved2 : 5; // Bit 27-31: Reserved }; __IOM uint32_t reg; // Access entire register } RCC_CFGR_Type; |
Thanh ghi APB2 Peripheral Clock Enable Register (RCC_APB2ENR)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // Thanh ghi Bật Clock cho ngoại vi APB2 (APB2 peripheral clock enable register) typedef union { struct { // Structure for bit fields __IOM uint32_t AFIOEN : 1; // Bit 0: Alternate function IO clock enable uint32_t reserved0 : 1; // Bit 1: Reserved __IOM uint32_t IOPAEN : 1; // Bit 2: GPIOA clock enable __IOM uint32_t IOPBEN : 1; // Bit 3: GPIOB clock enable __IOM uint32_t IOPCEN : 1; // Bit 4: GPIOC clock enable __IOM uint32_t IOPDEN : 1; // Bit 5: GPIOD clock enable __IOM uint32_t IOPEEN : 1; // Bit 6: GPIOE clock enable __IOM uint32_t IOPFEN : 1; // Bit 7: GPIOF clock enable __IOM uint32_t IOPGEN : 1; // Bit 8: GPIOG clock enable __IOM uint32_t ADC1EN : 1; // Bit 9: ADC1 clock enable __IOM uint32_t ADC2EN : 1; // Bit 10: ADC2 clock enable __IOM uint32_t TIM1EN : 1; // Bit 11: TIM1 clock enable __IOM uint32_t SPI1EN : 1; // Bit 12: SPI1 clock enable __IOM uint32_t TIM8EN : 1; // Bit 13: TIM8 clock enable __IOM uint32_t USART1EN : 1; // Bit 14: USART1 clock enable __IOM uint32_t ADC3EN : 1; // Bit 15: ADC3 clock enable uint32_t reserved1 : 3; // Bit 16-18: Reserved __IOM uint32_t TIM9EN : 1; // Bit 19: TIM9 clock enable __IOM uint32_t TIM10EN : 1; // Bit 20: TIM10 clock enable __IOM uint32_t TIM11EN : 1; // Bit 21: TIM11 clock enable uint32_t reserved2 : 10; // Bit 22-31: Reserved }; __IOM uint32_t reg; // Access entire register } RCC_APB2ENR_Type; |
Thanh ghi APB1 Peripheral Clock Enable Register (RCC_APB1ENR)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // Thanh ghi Bật Clock cho ngoại vi APB1 (APB1 peripheral clock enable register) typedef union { struct { // Structure for bit fields __IOM uint32_t TIM2EN : 1; // Bit 0: TIM2 clock enable __IOM uint32_t TIM3EN : 1; // Bit 1: TIM3 clock enable __IOM uint32_t TIM4EN : 1; // Bit 2: TIM4 clock enable __IOM uint32_t TIM5EN : 1; // Bit 3: TIM5 clock enable __IOM uint32_t TIM6EN : 1; // Bit 4: TIM6 clock enable __IOM uint32_t TIM7EN : 1; // Bit 5: TIM7 clock enable __IOM uint32_t TIM12EN : 1; // Bit 6: TIM12 clock enable __IOM uint32_t TIM13EN : 1; // Bit 7: TIM13 clock enable __IOM uint32_t TIM14EN : 1; // Bit 8: TIM14 clock enable uint32_t reserved0 : 2; // Bit 9-10: Reserved __IOM uint32_t WWDGEN : 1; // Bit 11: Window watchdog clock enable uint32_t reserved1 : 2; // Bit 12-13: Reserved __IOM uint32_t SPI2EN : 1; // Bit 14: SPI2 clock enable __IOM uint32_t SPI3EN : 1; // Bit 15: SPI3 clock enable uint32_t reserved2 : 1; // Bit 16: Reserved __IOM uint32_t USART2EN : 1; // Bit 17: USART2 clock enable __IOM uint32_t USART3EN : 1; // Bit 18: USART3 clock enable __IOM uint32_t UART4EN : 1; // Bit 19: UART4 clock enable __IOM uint32_t UART5EN : 1; // Bit 20: UART5 clock enable __IOM uint32_t I2C1EN : 1; // Bit 21: I2C1 clock enable __IOM uint32_t I2C2EN : 1; // Bit 22: I2C2 clock enable __IOM uint32_t USBEN : 1; // Bit 23: USB clock enable uint32_t reserved3 : 1; // Bit 24: Reserved __IOM uint32_t CANEN : 1; // Bit 25: CAN clock enable uint32_t reserved4 : 1; // Bit 26: Reserved __IOM uint32_t BKPEN : 1; // Bit 27: Backup interface clock enable __IOM uint32_t PWREN : 1; // Bit 28: Power interface clock enable __IOM uint32_t DACEN : 1; // Bit 29: DAC clock enable uint32_t reserved5 : 2; // Bit 30-31: Reserved }; __IOM uint32_t reg; // Access entire register } RCC_APB1ENR_Type; |
Những thanh ghi khác như CIR, APB2RSTR, APB1RSTR, AHBENR, BDCR, CSR không được tạo thay struct ở đây. Bạn có thể tự mình định nghĩa nếu cần dùng.
Bản đồ thanh ghi RCC (RCC register map)
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Bản đồ thanh ghi RCC (7.3.11 RCC register map – Trang 121) typedef struct { RCC_CR_Type CR; // 0x00: Điều khiển clock RCC_CFGR_Type CFGR; // 0x04: Cấu hình clock uint32_t CIR; // 0x08: Ngắt clock uint32_t APB2RSTR; // 0x0C: Reset APB2 uint32_t APB1RSTR; // 0x10: Reset APB1 uint32_t AHBENR; // 0x14: Bật clock AHB RCC_APB2ENR_Type APB2ENR; // 0x18: Bật clock APB2 RCC_APB1ENR_Type APB1ENR; // 0x1C: Bật clock APB1 uint32_t BDCR; // 0x20: Điều khiển miền dự phòng uint32_t CSR; // 0x24: Trạng thái/điều khiển } RCC_TypeDef; |

1 | #define RCC ((RCC_TypeDef *)0x40021000UL) |
Giải thích:
- CR: Điều khiển các dao động (HSION, HSEON, PLLON). Bit HSIRDY cho biết dao động nội bộ đã ổn định, PLLRDY cho biết PLL đã khóa.
- CFGR: Cấu hình nguồn clock, các hệ số chia tần, và nhân PLL. Bit SWS hiển thị nguồn clock hiện tại.
- APB2ENR, APB1ENR: Bật clock cho các ngoại vi (ví dụ: GPIOA, USART1). Các bit như IOPDEN và IOPEEN hỗ trợ phiên bản high-density.
Tạo file system cho cấu hình clock STM32F103
Tạo file system.h
1 2 3 4 5 6 7 8 | #ifndef __SYSTEM_H #define __SYSTEM_H void SystemClock_Config(void); #endif |
Tạo file system.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include "stm32f10x.h" #include "system.h" void SystemClock_Config(void) { // Enable HSE (8MHz) RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); // Configure PLL: HSE x9 = 72MHz RCC->CFGR |= RCC_CFGR_PLLSRC; RCC->CFGR |= RCC_CFGR_PLLMULL9; // Enable PLL RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // Prescalers configuration RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK = SYSCLK RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // PCLK1 = HCLK/2 RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // PCLK2 = HCLK // Select PLL as system clock RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); } |
Chỉnh sửa startup để dùng clock tự cấu hình
Mở file startup_stm32f10x_hd.s

- Chuột phải → Properties
- Bỏ chọn Read-only
Ghi đè hàm SystemInit

1 2 3 4 5 6 | IMPORT SystemClock_Config SystemInit BL SystemClock_Config BX LR |
Sửa function SystemInit thành function mà chúng ta đã định nghĩa lại (SystemClock_Config)
👉 Mục đích: thay thế clock mặc định của CMSIS bằng clock do người dùng cấu hình.
Cấu hình RCC và ngoại vi STM32F103 bằng thanh ghi
Bật clock cho GPIOA
1 | RCC->APB2ENR.IOPAEN = 1 |
Bật clock: RCC->APB2ENR.IOPAEN = 1 cho GPIOA (RM0008, Section 7.3.7, trang 112).
Reset ngoại vi nếu cần
1 | RCC->APB2RSTR.IOPAEN = 1 |
Reset nếu cần: RCC->APB2RSTR.IOPAEN = 1 rồi clear (Section 7.3.4, trang 106).
Ví dụ cấu hình GPIO Output
1 2 | GPIOA->CRL &= ~(0xF << (5 * 4)); GPIOA->CRL |= (0x1 << (5 * 4)); |
Clock Tree STM32F103 và tính toán xung clock
Cấu trúc clock tree
Clock tree (RM0008, Figure 11, trang 126):

- HSE: 8MHz (Section 7.2.1, trang 95).
- PLL: HSE x9 = 72MHz (PLLMUL = 7, Section 7.3.2, trang 101).
- HCLK: SYSCLK / HPRE = 72MHz / 1 = 72MHz.
- PCLK1: HCLK / PPRE1 = 72MHz / 2 = 36MHz.
- PCLK2: HCLK / PPRE2 = 72MHz / 1 = 72MHz.
Tính toán xung Timer


– Timer clock (APB1): PPRE1 > 1 nên TIMCLK = 2 x PCLK1 = 72MHz (Section 7.2, trang 93). – Ví dụ: Timer 1ms tại 72MHz, PSC = 71, ARR = 999 (72MHz / 72 / 1000 = 1kHz).
Cấu hình thanh ghi RCC
- Enable HSE: Set bit HSEON to 1

- Enable PLL: Set bit PLLON to 1

- Configure PLL:
PLL HSE source: Set bit PLLSRC to 1

PLL MUL: Set PLL input clock x9, set PLLMUL to 7 (b0111)

- Set prescalers:
AHB prescaler: HCLCK/1 = SYSCLK, set HPRE to 0

APB low-speed prescaler (APB1): PLCK1 = HCLK/2 = SYSCLK/2, set PPRE1 to 4 (100)

APB high-speed prescaler (APB2): PLCK2 = HCLK = SYSCLK, set PPRE2 to 0

Chọn system clock: Set PLL như system clock

6. Hàm main trong lập trình STM32 thanh ghi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | void SystemClock_Config(void) { // Enable HSE (RM0008, Section 7.3.1, page 99) RCC->CR.HSEON = 1; while (!RCC->CR.HSERDY); // Wait for HSE ready // Configure PLL: HSE source, x9 (RM0008, Section 7.3.2, page 101) RCC->CFGR.PLLSRC = 1; // HSE as PLL source RCC->CFGR.PLLMUL = 7; // PLL x9 RCC->CR.PLLON = 1; // Enable PLL while (!RCC->CR.PLLRDY); // Wait for PLL ready // Set prescalers: HCLK=/1, PCLK1=/2, PCLK2=/1 RCC->CFGR.HPRE = 0; // HCLK = SYSCLK RCC->CFGR.PPRE1 = 4; // PCLK1 = HCLK/2 RCC->CFGR.PPRE2 = 0; // PCLK2 = HCLK // Select PLL as system clock RCC->CFGR.SW = 2; // PLL as SYSCLK while (RCC->CFGR.SWS != 2); // Wait for switch } int main(void) { SystemClock_Config(); // Initialize system clock // Peripheral configurations while (1) { // Application code } } |
Link Github: Download code chương trình cấu hình Clock STM32F103 thanh ghi
7. Kiểm tra cấu hình clock STM32F103 trong Keil

- Debug → Start Debug Session
- Peripherals → System Viewer → RCC
- Kiểm tra:
- HSEON = 1
- PLLON = 1
- PLLSRC = HSE
- PLLMUL = x9
- SW = PLL
- HPRE = /1
- PPRE1 = /2
- PPRE2 = /1

Tham chiếu: RM0008, Section 7.2 (trang 92-98), Table 19 (trang 157).
8. Tổng kết
Bài viết đã hướng dẫn đầy đủ lập trình STM32 thanh ghi cấu hình clock STM32F103, giúp bạn hiểu bản chất RCC, clock tree và cách tính toán xung. Đây là nền tảng quan trọng để tiếp tục các bài GPIO, Timer, UART theo hướng bare-metal chuyên sâu.
