外设时钟使能寄存器
103.2.6外设时钟使能寄存器
/* ==================== Core\Inc\main.h ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.h
* @brief : Header for main.c file.
* This file contains the common defines of the application.
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */
/* USER CODE END EC */
/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */
/* USER CODE END EM */
/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);
/* USER CODE BEGIN EFP */
/* USER CODE END EFP */
/* Private defines -----------------------------------------------------------*/
#define KEY1_Pin GPIO_PIN_3
#define KEY1_GPIO_Port GPIOE
#define KEY2_Pin GPIO_PIN_4
#define KEY2_GPIO_Port GPIOE
#define LED2_Pin GPIO_PIN_5
#define LED2_GPIO_Port GPIOE
#define SUN_Pin GPIO_PIN_11
#define SUN_GPIO_Port GPIOB
#define LED1_Pin GPIO_PIN_5
#define LED1_GPIO_Port GPIOB
#define BEEP_Pin GPIO_PIN_8
#define BEEP_GPIO_Port GPIOB
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
#ifdef __cplusplus
}
#endif
#endif /* __MAIN_H */
/* ==================== Core\Inc\gpio.h ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file gpio.h
* @brief This file contains all the function prototypes for
* the gpio.c file
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __GPIO_H__
#define __GPIO_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_GPIO_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /*__ GPIO_H__ */
/* ==================== Core\Src\main.c ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
// HAL_Delay(1000);
if(0 == HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin))
{
HAL_Delay(120);
if(0 == HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin))
{
HAL_GPIO_TogglePin(BEEP_GPIO_Port, BEEP_Pin);
}
}
if(0 == HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin))
{
HAL_Delay(120);
if(0 == HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin))
{
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
}
}
if(0 == HAL_GPIO_ReadPin(SUN_GPIO_Port, SUN_Pin))
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, 1);
}else{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, 0);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/* ==================== Core\Src\gpio.c ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file gpio.c
* @brief This file provides code for the configuration
* of all used GPIO pins.
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "gpio.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/*----------------------------------------------------------------------------*/
/* Configure GPIO */
/*----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/** Configure pins as
* Analog
* Input
* Output
* EVENT_OUT
* EXTI
*/
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : PEPin PEPin */
GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED2_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = SUN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(SUN_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PBPin PBPin */
GPIO_InitStruct.Pin = LED1_Pin|BEEP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
一、初始化代码分析
STM32F103 的外设默认并非全部运行。复位后,大多数外设时钟处于关闭状态,以降低动态功耗。使用 GPIOB 之前,程序必须先让 RCC 把 APB2 时钟送到 GPIOB:
__HAL_RCC_GPIOB_CLK_ENABLE();
可以把 RCC 想成整栋楼的配电柜,GPIOB 是其中一个房间。配置 CRL、CRH 等寄存器相当于操作房间内的设备;若配电柜没有合闸,外设寄存器就不能正常工作。
flowchart LR
HSE[8 MHz HSE] --> PLL[PLL ×9]
PLL --> SYSCLK[SYSCLK 72 MHz]
SYSCLK --> AHB[HCLK 72 MHz]
AHB --> APB2[PCLK2 72 MHz]
APB2 --> GATE{APB2ENR.IOPBEN}
GATE -->|0| OFF[GPIOB 时钟关闭]
GATE -->|1| GPIOB[GPIOB 寄存器与端口逻辑工作]
1.1 时钟使能寄存器配置
HAL 宏的关键展开如下:
#define __HAL_RCC_GPIOB_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN); \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN); \
UNUSED(tmpreg); \
} while (0U)
逐句含义:
RCC->APB2ENR定位 RCC 的 APB2 外设时钟使能寄存器;RCC_APB2ENR_IOPBEN是第 3 位掩码,即1U << 3;SET_BIT对该位执行按位或,只把 IOPBEN 置 1,不破坏其他外设的使能位;- 随后的回读形成时钟开启后的必要延迟,并确认这次总线写操作已经推进;
do { ... } while (0U)让多语句宏在if/else中仍表现得像一条 C 语句。
逻辑上等价于:
RCC->APB2ENR |= (1U << 3);
volatile uint32_t delay = RCC->APB2ENR & (1U << 3);
(void)delay;
直接写 RCC->APB2ENR = (1U << 3) 会把该寄存器的其他使能位清零,因此不应使用。置位时要进行“读—改—写”。
1.1.1 RCC寄存器描述
RCC 是 Reset and Clock Control,即复位和时钟控制。STM32F103 大容量器件的 RCC 寄存器结构按 32 位顺序排列:
| 结构体成员 | 偏移 | 作用 |
|---|---|---|
| CR | 0x00 | 时钟源开关与就绪标志 |
| CFGR | 0x04 | 系统时钟选择、分频、PLL 等 |
| CIR | 0x08 | 时钟中断 |
| APB2RSTR | 0x0C | APB2 外设复位 |
| APB1RSTR | 0x10 | APB1 外设复位 |
| AHBENR | 0x14 | AHB 外设时钟使能 |
| APB2ENR | 0x18 | APB2 外设时钟使能 |
| APB1ENR | 0x1C | APB1 外设时钟使能 |
| BDCR | 0x20 | 备份域控制 |
| CSR | 0x24 | 控制/状态与复位原因 |
APB2ENR 的有效位如下,复位值为 0:
| 位 | 名称 | 置 1 后启用 |
|---|---|---|
| 15 | ADC3EN | ADC3 接口时钟 |
| 14 | USART1EN | USART1 接口时钟 |
| 13 | TIM8EN | TIM8 定时器时钟 |
| 12 | SPI1EN | SPI1 时钟 |
| 11 | TIM1EN | TIM1 定时器时钟 |
| 10 | ADC2EN | ADC2 接口时钟 |
| 9 | ADC1EN | ADC1 接口时钟 |
| 8 | IOPGEN | GPIOG 时钟 |
| 7 | IOPFEN | GPIOF 时钟 |
| 6 | IOPEEN | GPIOE 时钟 |
| 5 | IOPDEN | GPIOD 时钟 |
| 4 | IOPCEN | GPIOC 时钟 |
| 3 | IOPBEN | GPIOB 时钟 |
| 2 | IOPAEN | GPIOA 时钟 |
| 1 | 保留 | 必须保持复位值 |
| 0 | AFIOEN | 复用功能 I/O 时钟 |
不同容量型号并不一定实现表中的全部外设。例如没有 GPIOF/G 或 ADC3 的型号,对应位应按该型号参考手册处理。
1.1.1.1 存储器和总线构架
外设寄存器被映射到处理器的统一地址空间。CMSIS 头文件中的地址推导为:
PERIPH_BASE = 0x40000000
AHBPERIPH_BASE = PERIPH_BASE + 0x00020000
RCC_BASE = AHBPERIPH_BASE + 0x00001000
= 0x40021000
APB2ENR 地址 = RCC_BASE + 0x18
= 0x40021018
这里容易混淆两件事:RCC 寄存器块位于 AHB 外设区域,但 APB2ENR 这个 RCC 寄存器控制的是 APB2 外设时钟门。寄存器放在哪条总线上,与它控制哪条总线的时钟门,不是同一个概念。
Cortex-M3 CPU
│
├── AHB ── RCC 寄存器块(0x40021000)
│ │
│ └── APB2ENR.IOPBEN
│ │
└── APB2 桥 ──────────────┴── GPIOB
1.1.1.2 代码走读
RCC 并不是一个普通变量,而是指向固定地址的结构体指针:
#define RCC_BASE (0x40021000UL)
#define RCC ((RCC_TypeDef *)RCC_BASE)
所以 RCC->APB2ENR 等价于“从 0x40021000 开始,按 RCC_TypeDef 的成员偏移,访问 0x18 处的 32 位寄存器”。成员声明中的 __IO 最终带有 volatile 语义,告诉编译器每次读写都可能对应真实硬件,不能像普通内存变量那样随意消除或缓存。
执行顺序为:
sequenceDiagram
participant CPU
participant RCC as RCC_APB2ENR
participant GPIOB
CPU->>RCC: 读当前 32 位值
CPU->>RCC: OR IOPBEN 掩码
CPU->>RCC: 写回,bit3=1
RCC-->>GPIOB: 打开端口时钟门
CPU->>RCC: 回读 bit3,形成延迟
CPU->>GPIOB: 配置 CRL/CRH、ODR 等寄存器
二、结束
外设初始化应遵循“先开时钟,再配寄存器,最后启动功能”的顺序。GPIOB 的最小关键链是:
__HAL_RCC_GPIOB_CLK_ENABLE()
↓
RCC_APB2ENR.IOPBEN = 1
↓
GPIOB 寄存器获得时钟
↓
HAL_GPIO_Init / HAL_GPIO_WritePin 可以正常工作
若忘记开启时钟,常见现象是引脚没有动作、读值异常,调试时寄存器写入也可能看不到预期结果。若错误地整体赋值 APB2ENR,则可能让 USART1、AFIO 或其他 GPIO 端口突然失去时钟。
三、寄存器详解
3.1 结构体定义
RCC 结构体中的每个成员都是 32 位,因而相邻成员通常相差 4 字节:
typedef struct
{
__IO uint32_t CR; /* 0x00 */
__IO uint32_t CFGR; /* 0x04 */
__IO uint32_t CIR; /* 0x08 */
__IO uint32_t APB2RSTR; /* 0x0C */
__IO uint32_t APB1RSTR; /* 0x10 */
__IO uint32_t AHBENR; /* 0x14 */
__IO uint32_t APB2ENR; /* 0x18 */
__IO uint32_t APB1ENR; /* 0x1C */
__IO uint32_t BDCR; /* 0x20 */
__IO uint32_t CSR; /* 0x24 */
} RCC_TypeDef;
偏移不是程序运行时搜索出来的,而是编译器依据结构体布局计算的。该结构体必须与芯片手册的寄存器映射完全一致。
3.2 时钟初始化
课程工程中的 SystemClock_Config() 使用 8 MHz 外部高速晶振 HSE,经过 PLL ×9 得到 72 MHz 系统时钟:
| 时钟节点 | 配置 | 结果 |
|---|---|---|
| HSE | 外部 8 MHz | 8 MHz |
| PLL | HSE ×9 | 72 MHz |
| SYSCLK | 选择 PLLCLK | 72 MHz |
| AHB/HCLK | DIV1 | 72 MHz |
| APB1/PCLK1 | DIV2 | 36 MHz |
| APB2/PCLK2 | DIV1 | 72 MHz |
APB1 的上限是 36 MHz,因此配置为 HCLK/2;APB2 可运行在 72 MHz。GPIOB 属于 APB2。
3.3 系统时钟配置
时钟树配置与外设门控要分开理解:
SystemClock_Config()决定 HCLK、PCLK1、PCLK2 的频率来源和分频;__HAL_RCC_GPIOB_CLK_ENABLE()决定 PCLK2 是否真正送入 GPIOB;- 即使 PCLK2 已经是 72 MHz,IOPBEN 为 0 时,GPIOB 仍处于无时钟状态;
- 开启 GPIOB 时钟不会自动设置 PB5 的输入输出模式,仍要配置 GPIO 寄存器。
调试时可以按以下顺序核查:
- RCC_CR 中 HSEON、HSERDY、PLLON、PLLRDY;
- RCC_CFGR 中 SWS、HPRE、PPRE1、PPRE2;
- RCC_APB2ENR 中对应 IOPxEN;
- 对应 GPIOx_CRL/CRH 的四位配置字段;
- GPIOx_IDR、ODR、BSRR 的实时状态。