外设时钟使能寄存器

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

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)

逐句含义:

  1. RCC->APB2ENR 定位 RCC 的 APB2 外设时钟使能寄存器;
  2. RCC_APB2ENR_IOPBEN 是第 3 位掩码,即 1U << 3
  3. SET_BIT 对该位执行按位或,只把 IOPBEN 置 1,不破坏其他外设的使能位;
  4. 随后的回读形成时钟开启后的必要延迟,并确认这次总线写操作已经推进;
  5. 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 位顺序排列:

结构体成员偏移作用
CR0x00时钟源开关与就绪标志
CFGR0x04系统时钟选择、分频、PLL 等
CIR0x08时钟中断
APB2RSTR0x0CAPB2 外设复位
APB1RSTR0x10APB1 外设复位
AHBENR0x14AHB 外设时钟使能
APB2ENR0x18APB2 外设时钟使能
APB1ENR0x1CAPB1 外设时钟使能
BDCR0x20备份域控制
CSR0x24控制/状态与复位原因

APB2ENR 的有效位如下,复位值为 0:

名称置 1 后启用
15ADC3ENADC3 接口时钟
14USART1ENUSART1 接口时钟
13TIM8ENTIM8 定时器时钟
12SPI1ENSPI1 时钟
11TIM1ENTIM1 定时器时钟
10ADC2ENADC2 接口时钟
9ADC1ENADC1 接口时钟
8IOPGENGPIOG 时钟
7IOPFENGPIOF 时钟
6IOPEENGPIOE 时钟
5IOPDENGPIOD 时钟
4IOPCENGPIOC 时钟
3IOPBENGPIOB 时钟
2IOPAENGPIOA 时钟
1保留必须保持复位值
0AFIOEN复用功能 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 MHz8 MHz
PLLHSE ×972 MHz
SYSCLK选择 PLLCLK72 MHz
AHB/HCLKDIV172 MHz
APB1/PCLK1DIV236 MHz
APB2/PCLK2DIV172 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 寄存器。

调试时可以按以下顺序核查:

  1. RCC_CR 中 HSEON、HSERDY、PLLON、PLLRDY;
  2. RCC_CFGR 中 SWS、HPRE、PPRE1、PPRE2;
  3. RCC_APB2ENR 中对应 IOPxEN;
  4. 对应 GPIOx_CRL/CRH 的四位配置字段;
  5. GPIOx_IDR、ODR、BSRR 的实时状态。