GPIO相关寄存器
104.2.7GPIO相关寄存器
/* ==================== 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 */
一、GPIO相关寄存器
STM32F103 的每组 GPIO 端口由一组寄存器控制:
| 寄存器 | 偏移 | 用途 |
|---|---|---|
| CRL | 0x00 | 配置 0~7 号引脚 |
| CRH | 0x04 | 配置 8~15 号引脚 |
| IDR | 0x08 | 读取引脚实际输入电平 |
| ODR | 0x0C | 输出数据锁存值,也用于选择输入上拉/下拉 |
| BSRR | 0x10 | 原子置位或复位输出位 |
| BRR | 0x14 | 原子复位输出位 |
| LCKR | 0x18 | 锁定端口配置,直到下次复位 |
课程工程的 PB5 连接 LED1。原理图中 LED 的阳极接 3.3 V,阴极经 1 kΩ 电阻接 PB5,因此 MCU 需要向外“吸电流”才能点亮:
3.3 V ── LED ── 1 kΩ ── PB5
PB5=0:3.3 V → LED → 电阻 → PB5 → GND,LED 亮
PB5=1:两端接近 3.3 V,几乎无电流,LED 灭
这就是低电平有效。GPIO 读写函数中的 SET/RESET 表示引脚电平,不直接等同于外设“开/关”。
1.1 上节回顾
使用 GPIOB 的基本顺序:
- 置 RCC_APB2ENR.IOPBEN,开启 GPIOB 时钟;
- 通过 CRL/CRH 配置引脚方向、电气类型和最大翻转速度;
- 输入用 IDR 读取,输出用 BSRR/BRR 改变;
- 根据板载电路确认高低电平与灯亮、蜂鸣器响之间的关系。
GPIOB 基地址为:
APB2PERIPH_BASE = 0x40010000
GPIOB_BASE = APB2PERIPH_BASE + 0x0C00
= 0x40010C00
因此 GPIOB_IDR 位于 0x40010C08,GPIOB_BSRR 位于 0x40010C10。
1.2 GPIO初始化
课程代码把 PB5 配置为低速通用推挽输出并启用上拉:
GPIO_InitStruct.Pin = LED1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);
STM32F1 的每个引脚占用 CRL/CRH 中连续 4 位:
高两位 CNFy[1:0] 低两位 MODEy[1:0]
┌───────────────┬────────────────┐
│ CNF1 CNF0 │ MODE1 MODE0 │
└───────────────┴────────────────┘
bit3 bit2 bit1 bit0
MODE 的含义:
| MODE[1:0] | 模式 |
|---|---|
| 00 | 输入模式 |
| 01 | 输出,最大 10 MHz |
| 10 | 输出,最大 2 MHz |
| 11 | 输出,最大 50 MHz |
当 MODE=00 时,CNF 表示输入类型:
| CNF[1:0] | 输入类型 |
|---|---|
| 00 | 模拟输入 |
| 01 | 浮空输入 |
| 10 | 上拉/下拉输入,由 ODR 对应位选择方向 |
| 11 | 保留 |
当 MODE≠00 时,CNF 表示输出类型:
| CNF[1:0] | 输出类型 |
|---|---|
| 00 | 通用推挽 |
| 01 | 通用开漏 |
| 10 | 复用推挽 |
| 11 | 复用开漏 |
PB5 位于 0~7,所以使用 CRL;其四位字段位于 CRL[23:20]。低速在当前 HAL 中对应最大 2 MHz,即 MODE=10;通用推挽 CNF=00,因此字段值为二进制 0010。
GPIOB_CRL[23:20] = CNF5[1:0] MODE5[1:0]
= 00 10
= 0x2
“速度”指输出驱动的最大翻转能力,不是让 CPU 或程序自动变慢。普通 LED 选择低速可减小边沿噪声和电磁干扰。
1.3 GPIO功能描述
GPIO 输入和输出并非只经过一个寄存器:
flowchart LR
PIN[物理引脚] --> SCH[施密特输入缓冲]
SCH --> IDR[IDR 输入数据寄存器]
ODR[ODR 输出锁存器] --> DRV[推挽/开漏输出驱动]
BSRR[BSRR 原子置位/复位] --> ODR
BRR[BRR 原子复位] --> ODR
DRV --> PIN
CR[CRL/CRH 模式配置] --> SCH
CR --> DRV
IDR 反映输入缓冲器看到的引脚电平;ODR 反映输出锁存器的值。输出引脚被外部强拉、发生短路或配置成开漏时,IDR 与 ODR 不一定相同。
1.3.1 端口配置高寄存器
CRH 控制 8~15 号引脚,每个引脚仍占四位:
| 引脚 | CRH 位段 |
|---|---|
| 8 | [3:0] |
| 9 | [7:4] |
| 10 | [11:8] |
| 11 | [15:12] |
| 12 | [19:16] |
| 13 | [23:20] |
| 14 | [27:24] |
| 15 | [31:28] |
计算某引脚字段位置的方法:
pin 0~7:使用 CRL,shift = pin × 4
pin 8~15:使用 CRH,shift = (pin - 8) × 4
HAL_GPIO_Init 会先清除目标四位,再写入新配置。这样不会改动同一端口的其他引脚。
1.3.2 端口输入数据寄存器
正确的输入寄存器名称是 IDR,位 15:0 分别对应 Px15:Px0,位 31:16 保留:
GPIOx_IDR
31 16 15 0
┌────────────────────────────┬─────────────────────────┐
│ 保留 │ IDR15 ... IDR1 IDR0 │
└────────────────────────────┴─────────────────────────┘
HAL_GPIO_ReadPin() 的核心是:
if ((GPIOx->IDR & GPIO_Pin) != GPIO_PIN_RESET)
return GPIO_PIN_SET;
else
return GPIO_PIN_RESET;
GPIO_Pin 是位掩码。例如 GPIO_PIN_3=0x0008。按位与后只保留第 3 位,结果非零表示高电平,结果为零表示低电平。
输出时 HAL 使用 BSRR:
if (PinState != GPIO_PIN_RESET)
GPIOx->BSRR = GPIO_Pin; /* 低 16 位:置 1 */
else
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U; /* 高 16 位:复位 */
BSRR 的关键优势是原子写操作:
| BSRR 位 | 写 1 的作用 | 写 0 的作用 |
|---|---|---|
| [15:0] BSy | 将 ODRy 置 1 | 无作用 |
| [31:16] BRy | 将 ODRy 清 0 | 无作用 |
若用 ODR = ODR | mask 进行读—改—写,中断可能夹在读取和写回之间,造成并发更新丢失;直接向 BSRR 写入掩码没有这个窗口。HAL_GPIO_TogglePin 则先读取 ODR,再一次性构造 BSRR 的置位与复位掩码,因此“翻转”本身仍依赖读到的旧状态,多个执行上下文同时翻转同一位时要自行同步。
1.3.3 应用案例
1.3.3.1 例题: GPIO读取引脚状态值
读取 PE3 按键:
GPIO_PinState key = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3);
if (key == GPIO_PIN_RESET)
{
/* PE3 按键按下,低电平有效 */
}
它对应的寄存器运算是:
uint32_t masked = GPIOE->IDR & (1U << 3);
若 IDR=0x0008:
IDR 0000 0000 0000 1000
GPIO_PIN3 0000 0000 0000 1000
按位与 0000 0000 0000 1000 → 非 0 → SET
若按键把 PE3 拉到地,IDR 第 3 位为 0:
IDR xxxx xxxx xxxx 0xxx
GPIO_PIN3 0000 0000 0000 1000
按位与 0000 0000 0000 0000 → 0 → RESET
实际按键有机械抖动。轮询时要延时确认或使用状态机;中断方式下不应在中断服务程序中长时间阻塞,而应记录事件后交给主循环消抖。