认识 GPIO

GPIO 是什么

  1. GPIO - 通用输入/输出口(General Purpose Input/Output),在集成电路板的上以针脚的形式呈现,通常称为引脚,是嵌入式系统开发中的核心组件,芯片通过引脚与不同的外设进行通信,输出信息控制外设(LED灯、电机等)或通过外设(温湿度传感器、光敏电阻等)采集信息。
  2. GPIO 本质是一组可通过软件配置的可编程引脚。没有固定功能,可以通过寄存器配置来实现与不同外设的交互。

作用

  1. 输入:可读取外部电压信号(高电平 / 低电平),感知按键、传感器等设备的状态;
  2. 输出:可输出高电平(3.3V)或低电平(0V),控制 LED、继电器、电机等设备的工作。

内部结构

  • 保护二极管:防止引脚输入电压过高(高于 VDD)或过低(低于 GND)损坏芯片;
  • 上拉 / 下拉电阻:可通过配置使引脚默认保持高电平(上拉)或低电平(下拉);
  • 输入缓冲器:将外部信号传入内部电路(用于输入模式);
  • 输出驱动器:将内部信号输出到引脚(用于输出模式,分推挽和开漏两种结构);
  • 复用功能切换开关:可将引脚切换到 “复用模式”,作为其他外设(如 UART 的 TX/RX)的专用引脚。

分类

  1. 电源引脚:为芯片及外设提供工作电压,是电路正常运行的基础。
  2. 晶振引脚:为芯片提供稳定的时钟信号,是定时器、UART 等外设正常工作的基础。
  3. 复位引脚:控制芯片复位,使芯片恢复到初始状态。
  4. 下载引脚:用于程序下载、在线调试(单步执行、断点等),是开发阶段的核心引脚。
  5. BOOT引脚:设置芯片启动模式,决定上电后程序从哪个存储区域加载执行。
  6. GPIO引脚:通用输入 / 输出引脚,可通过软件配置为输入、输出或复用功能,实现与外设的灵活交互(如控制 LED、读取按键、连接传感器等)。
  7. 因为STM32芯片类型比较多,不同类型的芯片对引脚的定义不同,所以在使用过程中要根据芯片的数据手册来看引脚的具体定义,当然,使用CubeMX图形化界面进行配置时,也能看到不同的功能默认的引脚是哪个,比如启用I2C1时,默认的PB6和PB7会变成绿色,再比如DEBUG功能,默认引脚是PA13和PA14。所以在使用时要注意引脚冲突。
  8. 当多个外设需要用到同一个引脚时,会产生冲突,当然,引脚是可以复用的,但是启用复用比较复杂,新手不建议使用利用功能。可以通过对应的芯片数据手册来查看各个引脚的默认功能,避免引脚冲突。

STM32 引脚

8种工作模式

  1. 输入浮空(Floating):引脚内部无上拉 / 下拉电阻,电平完全由外部信号决定(如悬空时电平不稳定)。适合读取明确的外部驱动信号(如传感器输出的数字信号)。
  2. 输入上拉(Pull-Up):引脚内部接一个上拉电阻(默认拉到 VDD),无外部信号时默认高电平。适合读取 “低电平有效” 的信号(如按键接地,按下时为低电平)。
  3. 输入下拉(Pull-Down):引脚内部接一个下拉电阻(默认拉到 GND),无外部信号时默认低电平。适合读取 “高电平有效” 的信号(如按键接 VDD,按下时为高电平)。
  4. 模拟输入(Analog):输入信号直接接入 ADC(模数转换器),用于读取模拟量(如电压)。此时上拉 / 下拉电阻和数字输入缓冲器均关闭,避免干扰模拟信号。
  5. 推挽输出(Push-Pull):输出级由两个 MOS 管组成(一个接 VDD,一个接 GND),可直接输出高电平(VDD)或低电平(GND),驱动能力强(可直接驱动 LED、继电器)。
  6. 开漏输出(Open-Drain):输出级仅一个接 GND 的 MOS 管,高电平时 MOS 管截止,引脚电平由外部上拉电阻决定(需外接上拉或使用内部上拉)。适合电平转换(如 5V 设备通信)、线与(多个设备共用总线,如 I2C)。
  7. 推挽式复用(Alternate Open-Drain):复用功能下的推挽输出(如 UART_TX 输出高 / 低电平),驱动能力由外设决定。
  8. 开漏式复用(Alternate Open-Drain):复用功能下的开漏输出(如 I2C_SDA/SCL,需外部上拉电阻)。
  9. **注意:**复用模式需同时配置Alternate成员(指定具体外设,如GPIO_AF7_USART1),并确保外设时钟已使能。此时引脚作为其他外设的专用引脚(如 UART 的 TX/RX、SPI 的 MOSI/MISO),引脚功能由外设控制,而非直接通过 GPIO 输出。

配置函数和配置结构体

配置函数
  • void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, const GPIO_InitTypeDef *GPIO_Init);
  • GPIOx: STM32的引脚区域,比如GPIOA、GPIOB、GPIOC
  • GPIO_Init: 引脚初始化结构体,下面详细介绍
配置结构体
typedef struct{
  uint32_t Pin;
  uint32_t Mode;
  uint32_t Pull;
  uint32_t Speed; 
  uint32_t Alternate; 
} GPIO_InitTypeDef;
  • uint8_t Pin: 需要配置的引脚号,可以使用位或运算配置多个引脚

    • GPIO_PIN_0 ~ GPIO_PIN_15: 每个区域都有16个引脚
    • GPIO_PIN_All: 全部引脚
  • uint8_t Mode: 引脚的输入/输出模式

    • GPIO_MODE_INPUT: 输入浮空
    • GPIO_MODE_OUTPUT_PP: 推挽输出
    • GPIO_MODE_OUTPUT_OD: 开漏输出
    • GPIO_MODE_AF_PP: 推挽复用
    • GPIO_MODE_AF_OD: 开漏复用
    • GPIO_MODE_ANALOG: 模拟模式
    • GPIO_MODE_IT_RISING: 上升沿触发中断模式,IT(Interrupt)-外部中断
    • GPIO_MODE_IT_FALLING: 下降沿触发中断模式
    • GPIO_MODE_IT_RISING_FALLING: 上升和下降沿触发中断模式
    • GPIO_MODE_EVT_RISING: 上升沿触发事件模式,EVT(Event)-外部事件
    • GPIO_MODE_EVT_FALLING: 下降沿触发事件模式
    • GPIO_MODE_EVT_RISING_FALLING: 上升和下降沿触发事件模式
  • uint8_t Pull: 上拉/下拉

    • GPIO_NOPULL: 不上拉也不下拉,默认
    • GPIO_PULLUP: 上拉
    • GPIO_PULLDOWN: 下拉
  • uint8_t Speed: 速度

    • GPIO_SPEED_FREQ_LOW: 低速,最大2MHz
    • GPIO_SPEED_FREQ_MEDIUM: 中速,最大25MHz
    • GPIO_SPEED_FREQ_HIGH: 高速,最大50MHz
    • GPIO_SPEED_FREQ_VERY_HIGH: 超高速,最大100~200MHz,根据不同型号而定,可以查看数据手册
    • 中低速用于LED、按键等对响应速度无要求的外设;SPI、UART 等高速通信外设,建议使用50MHz以上的速度,避免信号失真。
  • uint8_t Alternate: 要连接的外设,只有模式为复用模式才需要配置,其它模式不用配置

    • 比如USART1:GPIO_AF7_USART1
    • 复用功能宏的命名规则为 GPIO_AFx_外设名,x 是 “复用功能组编号”(0~15,不同外设对应不同组),外设名 是具体外设(如 USART1、SPI2、TIM1 等)
    • 具体定义可以在 stm32h7xx_hal_gpio_ex.h 头文件中查看
  • 中断:当 GPIO 引脚触发指定边沿时,通过 NVIC(嵌套向量中断控制器)向 CPU 发送中断请求,强制 CPU 暂停当前任务,转而去执行对应的中断服务程序(ISR),处理完后返回原任务。** 用于需要 CPU 立即响应的场景 **(如按键按下后立即点亮 LED、紧急故障报警)。

  • 事件:当 GPIO 引脚触发指定边沿时,仅产生一个内部硬件事件信号(脉冲),不通知 CPU,而是直接连接到其他外设(如 DMA 控制器、定时器),触发外设自动执行预设操作(如 DMA 开始传输、定时器开始计数)。 用于无需 CPU 干预的外设联动场景 **(如外部信号触发 ADC 采样并通过 DMA 存储数据,全程 CPU 可处理其他任务)。

中断

  1. GPIO Mode 中有中断参数要结合NVIC(Nested Vectored Interrupt Controller:嵌套向量中断控制器)来使用,后面会单独学习NVIC。
    可编程按键原理图

  2. 以H743板载的可编程按键为例来介绍GPIO中断的配置过程。根据原理图得H743板载USER按键内部连接PB3,且按下接地。

  3. 所以要把引脚电平上拉,且使用下降沿触发中断,否则检测不到电平变化,如果内部接GND时,要使用下拉,且上升沿触发中断,否则检测不到电平变化。

  4. 执行过程:

    • 触发中断:按键触发中断后,EXIT(External interrupt/event controller: 外部中断控制器)会向NVIC发送中断请求;
    • NVIC响应:NVIC 接收到中断请求后,会根据中断优先级判断是否允许中断响应。若允许,CPU 会暂停当前正在执行的任务,从中断向量表中找到 EXTI3_IRQHandler 函数的入口地址。
    • 自动调用中断服务程序:CPU 跳转到 EXTI3_IRQHandler 函数的地址,自动执行该函数中的代码(这一步是硬件机制,无需软件显式调用)。

配置过程

  1. 使能GPIO时钟,非常重要,如果不使能,所有的配置都无效。函数:__HAL_RCC_GPIOA_CLK_ENABLE();
  2. 配置GPIO,函数:void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, const GPIO_InitTypeDef *GPIO_Init);
  3. 读或者写,非必须,
    • 读函数:GPIO_PinState HAL_GPIO_ReadPin(const GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
    • 写函数:void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
  4. 中断函数,非必须
    • 配置PB3引脚上拉,下降沿触发中断
    • 配置NVIC中断优先级void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);
    • 全能NVIC中断:void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
    • 编写中断服务函数:void EXTI4_IRQHandler(void); – 函数名固定
    • 编写中断回调函数:void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); – 函数名固定
  5. 写函数中的参数GPIO_PinState PinState选项如下:
    • GPIO_PIN_RESET: 设为低电平
    • GPIO_PIN_SET: 设为高电平
  6. 电平转换函数:void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin),自动检测引脚电平状态,如果为高电平,则转换成低电平,反之转换成高电平

示例程序

  • 以 H743VIT6 为例,其可操作的板载LED有4个,LED1(LED-RED)内部连接PA13引脚,LED2(LED-GREEN)内部连接PA14引脚,LED3(LED-YELLOW)内部连接PA15引脚,LED4(LED-BLUE)内部连接PB4引脚,示例程序使用LED3来演示。

  • 四个LED是共地的,也就是内部连接了GND引脚,所以都是高电平点亮,附原理图。

  • LED原理图

  • 不使用PA13和PA14是因为我使用ST-Link调试下载程序,其默认引脚使用的是PA13和PA14,如果使用LED1或者LED2会引起引脚冲突,配置过PA13或者PA14后,第一次可以下载程序,后面会报错连接不上ST-Link,如果已经下载并提示连接不上,可以使用其它方法下载一个正常的程序进去,我是这样解决的,肯定还有其它方法,这是不过多研究。下载方法前面的文章“STM32问题集锦”中有详细介绍。

  • 当然,最好的办法是查看完数据手册后,启用引脚复用功能,或者其它可以避免引脚冲突的方法。

// 示例1:配置 GPIO 并让 LED3 间隔一秒亮灭一次
// 使能 GPIO 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();

// 定义 GPIO 引脚配置结构体
GPIO_InitTypeDef GPIO_InitStruct = {0};

// GPIO 配置
GPIO_InitStruct.Pin = GPIO_PIN_15;  // PA15, 要结合区域参数:GPIOA
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
GPIO_InitStruct.Pull = GPIO_PULLDOWN;  // 下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;  // 低速输出就行
// 不需要复用功能,不用配置 Alternate

// 初始化函数初始化 GPIO 引脚
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 循环点亮和熄灭 LED3
while(1){
	// 因为引脚已经下拉,且需要高电平点亮,所以使用写函数把引脚设置为高电平
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
	HAL_Delay(1000);  // 延迟1秒
	// 使用写函数把引脚设置为低电平,熄灭LED
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
	HAL_Delay(1000);
	
	// 还可以使用电平转换函数
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15);
	HAL_Delay(1000);
	
	// 使用读取函数读取引脚数据
}
// 示例2:读取PA4引脚电平状态,如果是高电平,LED3常亮,如果是低电平,LED3间隔一秒亮灭一次

__HAL_RCC_GPIOA_CLK_ENABLE();  // 全能GPIOA区域时钟

GPIO_InitTypeDef GPIO_InitStruct = {0};  // 定义GPIO配置结构体

// 配置 GPIO  PA15 参数
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 配置 GPIO PA4 参数
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
while(1){
	GPIO_PinState pa4_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4);
	if(pa4_state == GPIO_PIN_SET){
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
	}else{
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15);
		HAL_Delay(1000);
	}
}
// 示例3:按键中断
// 配置PB3和PA15
__HAL_RCC_GPIOA_CLK_ENABLE();  // 全能GPIOA区域时钟
__HAL_RCC_GPIOB_CLK_ENABLE();  // 全能GPIOB区域时钟

GPIO_InitTypeDef GPIO_InitStruct = {0};  // 定义GPIO配置结构体

// 配置 GPIO  PA15 参数
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 原理图中此按键内部接的VDD,按下接地,电平由高变低,所以要把引脚电平上拉,且使用下降沿触发中断,否则检测不到电平变化
// 内部接GND时,要使用下拉,且上升沿触发中断,否则检测不到电平变化
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发中断
GPIO_InitStruct.Pull = GPIO_PULLUP;  // 上拉
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// 中断服务程序(在stm32h7xx_it.c中)
void EXTI4_IRQHandler(void) {
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); // 调用HAL库中断处理函数
}
// 中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  if (GPIO_Pin == GPIO_PIN_4) {
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15); // 翻转LED
  }
}

未整理完整代码

代码有点乱,上面的所有程序都可以通过取消注释来测试。


#include "main.h"


void MPU_Config(void);
void EXTI3_IRQHandler(void);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

int main(void){

	// 初始化 HAL 库
	HAL_Init();

	// 定义结构体
//	MPU_Region_InitTypeDef MPU_InitStruct = {0};  // 配置 MPU 内存保护单元结构体

	RCC_OscInitTypeDef OscilltaorStruct = {0};  // 配置时钟源结构体
	RCC_ClkInitTypeDef SysClockStruct = {0};  // 配置系统时钟结构体
	GPIO_InitTypeDef GPIO_InitStruct = {0};  // GPIO 配置结构体

/*
typedef struct{
  uint8_t Enable;  // 开启或关闭 MPU 指定区域  MPU_REGION_ENABLE/DISABLE
  uint8_t Number;  // 要配置的 MPU 区域编号。
  uint32_t BaseAddress;  // 该保护区域的起始地址。必须按区域大小对齐
  uint8_t Size;  // 该保护区域的大小,大小需为 2 的幂次方,且需与BaseAddress对齐。
  uint8_t SubRegionDisable;  // 指定该区域中需要禁用的子区域(每个区域可划分为 8 个子区域)。
  // 0x01(二进制00000001)表示禁用子区域 0,其余 7 个子区域启用。禁用后不再受保护,为普通内存区域
  uint8_t TypeExtField;  // 用于细化内存类型(如普通内存、外设内存、共享内存等)
  uint8_t AccessPermission;  // 指定该区域的访问权限(如读写限制、特权 / 用户模式限制)
  uint8_t DisableExec;  // 指定该区域是否禁止指令执行(防止从数据区域执行代码,增强安全性)。
  uint8_t IsShareable;  // 指定该区域是否为可共享内存(多总线 / 多核心访问时的数据一致性控制)。
  uint8_t IsCacheable;  // 指定该区域是否可缓存(通过 CPU 缓存提升访问速度)。
  uint8_t IsBufferable;  // 指定该区域是否可缓冲(通过写缓冲区优化写操作性能)。
}MPU_Region_InitTypeDef
 * */

	// 配置 MPU,建议在GPIO_PIN_13使用之前都要设置内存保护(简化版)
//	HAL_MPU_Disable();  // 先禁用内存保护,相当于恢复初始状态,所有内存区域为未配置状态
//	MPU_InitStruct.Enable = MPU_REGION_ENABLE;  // 开启内存保护,MPU_REGION_DISABLE(关闭)
//	MPU_InitStruct.Number = MPU_REGION_NUMBER0; // H7 有0~15个区域,M4 有8个区域,具体区域划分可以查看数据手册
//	MPU_InitStruct.BaseAddress = 0x0;  // 必须注意字节对齐,否则配置无效。
//	MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;  // 32B ~ 4GB,这里设置全部保护
//	MPU_InitStruct.SubRegionDisable = 0x87;  // 10000111 1为禁用,0为启用
//	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;  // 普通内存
//	MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;  // 禁止任何访问
//	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;  // 禁止从该区域执行指令
//	MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;  // 可共享内存
//	MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;  // 禁用该区域的缓存功能
//	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;  // 禁用该区域的写缓存功能
//	// HAL_MPU_ConfigRegion() 配置 MPU(Memory Protection Unit,内存保护单元)
//	HAL_MPU_ConfigRegion(&MPU_InitStruct);
//	HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
	MPU_Config();
/*
typedef struct {
  uint32_t OscillatorType; // RCC_OSCILLATORTYPE_NONE/HSE/HSI/CSI/LSE/LSI/HSI48  不同的时钟源
  uint32_t HSEState;  // 不同时钟源的开启/关闭或者模式,旁路模式:接收使用外部时钟源,不使用内部时钟或者板载晶振,普通外部时钟源、对精度要求不极致的场景
  // 数字旁路模式,与旁路模式一样,但是性能更高,高频同步系统、高精度时钟(如与 FPGA/DSP 协同),只有高性能板才有。
  uint32_t LSEState;
  uint32_t HSIState;
  uint32_t HSICalibrationValue;  // 如果时钟源是 HSI 需要再配置一下校准微调值。由于内部振荡器频率受温度、电压影响,可通过此值微调频率精度。
  // RCC_HSICALIBRATION_DEFAULT  STM32H7 rev.V及以上版本的默认HSI校准微调值。
  uint32_t LSIState;
  uint32_t HSI48State;
  uint32_t CSIState;
  uint32_t CSICalibrationValue;  // RCC_CSICALIBRATION_DEFAULT  STM32H7 rev.V及以上版本的默认HSI校准微调值。
  RCC_PLLInitTypeDef PLL;  // 用来配置 PLL 的结构体
} RCC_OscInitTypeDef;
// 配置 PLL 结构体
typedef struct{
  uint32_t PLLState;  // PLL 状态
  uint32_t PLLSource;  // PLL 时钟源
  uint32_t PLLM;  // 对应 CubeMX - DIVM1 分频系数
  uint32_t PLLN;  // 对应 CubeMX - PLL1 - DIVN1 分频系统
  uint32_t PLLP;  // 对应 CubeMX - PLL1 - DIVP1 分频系统
  uint32_t PLLQ;  // 对应 CubeMX - PLL1 - DIVQ1 分频系统
  uint32_t PLLR;  // 对应 CubeMX - PLL1 - DIVR1 分频系统
  uint32_t PLLRGE;  // PLL1 输入时钟频率范围,CubeMX 会根据 PLLM 分频系数自动设置,有四个值,1~2,2~4,4~8,8~16
  uint32_t PLLVCOSEL;  // DIVP1 的输出范围,两个值,宽频:420MHz ~ 836MHz,中低频:192MHz ~ 420MHz
  uint32_t PLLFRACN;  // PLL1 中的 fracn1 小数分频配置
} RCC_PLLInitTypeDef;
 * */
	// 默认配置
//	OscilltaorStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;  // 使用 HSI 时钟
//	OscilltaorStruct.HSIState = RCC_HSI_DIV1;  // DIV2/DIV4/DIV8  分频系数,分别为 1 2 4 8,在 HSI RC 后面的配置框设置
//	OscilltaorStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;  // H7及以上唯一默认值
//	OscilltaorStruct.PLL.PLLState = RCC_PLL_NONE; // PLL 为空,不启用(个人理解)

	// 使用 HSE 配置
	OscilltaorStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
	OscilltaorStruct.HSEState = RCC_HSE_ON;
	OscilltaorStruct.PLL.PLLState = RCC_PLL_ON;
	OscilltaorStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
	OscilltaorStruct.PLL.PLLM = 5;  // 输入频率 25/5 = 5
	OscilltaorStruct.PLL.PLLN = 160; // 输出频率
	OscilltaorStruct.PLL.PLLP = 2;
	OscilltaorStruct.PLL.PLLQ = 2;
	OscilltaorStruct.PLL.PLLR = 2;
	OscilltaorStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;  // 根据 PLLM 自动配置 4 ~ 8
	OscilltaorStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;  // 自动配置为宽频
	OscilltaorStruct.PLL.PLLFRACN = 0;

	// HAL_RCC_OscConfig  时钟源配置函数
	if(HAL_RCC_OscConfig(&OscilltaorStruct) != HAL_OK){
		Error_Handler();
	}

	// 同时配置所有的时钟分频系数
	SysClockStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK  // 可以同时配置多个时钟类型,通过位或运算组合
								|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2  // 从 SYSCLK 开始向后配置
								|RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
	SysClockStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
	// DIV1/DIV2/.../DIV512 代表 1/2/.../512 分频系数
	SysClockStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;  // SYSCLK 分频系数
	SysClockStruct.AHBCLKDivider = RCC_HCLK_DIV2;  // AHB 分频系数USR KEY
	SysClockStruct.APB3CLKDivider = RCC_APB3_DIV2;
	SysClockStruct.APB1CLKDivider = RCC_APB1_DIV2;
	SysClockStruct.APB2CLKDivider = RCC_APB2_DIV2;
	SysClockStruct.APB4CLKDivider = RCC_APB4_DIV2;
	if(HAL_RCC_ClockConfig(&SysClockStruct,FLASH_LATENCY_2) != HAL_OK){
		Error_Handler();
	}

	// GPIO 配置,PA15控制LED3,内部已经连接
	GPIO_InitStruct.Pin = GPIO_PIN_15;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	__HAL_RCC_GPIOA_CLK_ENABLE();
	HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
	// PA4 连接人体感应模块
	GPIO_InitStruct.Pin = GPIO_PIN_4;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	// PB3 检测 user 按键状态,内部已经连接
	// 原理图中此按键内部接的VDD,按下接地,电平由高变低,所以要把引脚电平上拉,且使用下降沿触发中断,否则检测不到电平变化
	// 内部接GND时,要使用下拉,且上升沿触发中断,否则检测不到电平变化
	GPIO_InitStruct.Pin = GPIO_PIN_3;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发中断
	GPIO_InitStruct.Pull = GPIO_PULLUP;  // 上拉
	__HAL_RCC_GPIOB_CLK_ENABLE();
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

	// 配置NVIC中断优先级
	HAL_NVIC_SetPriority(EXTI3_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(EXTI3_IRQn);

	HAL_NVIC_SetPriority(EXTI4_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(EXTI4_IRQn);



	while(1){
		// 控制LED3亮灭
//		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
//		HAL_Delay(500);
//		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
//		HAL_Delay(500);
		// 直接通过电平翻转函数实现,效果一样
//		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15);
//		HAL_Delay(500);

		// 读取引脚4电平状态控制LED3的亮灭
//		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4)){
//			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
//		}else{
//			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
//		}

		// 通过按键控制LED3的亮灭


	}
}



void MPU_Config(void){
	MPU_Region_InitTypeDef MPU_InitStruct = {0};  // 定义 MPU 配置属性结构体

	// 第一步:禁用 MPU
	HAL_MPU_Disable();

	// 第二步:配置MPU参数
	/*区域0:闪存区域(代码区域),只读,可执行*/
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;  // 启用 MPU
	MPU_InitStruct.Number = MPU_REGION_NUMBER0;  // 区域0
	MPU_InitStruct.BaseAddress = 0x08000000;  // 闪存地址
	MPU_InitStruct.Size = MPU_REGION_SIZE_2MB; // 根据实际闪存大小调整
	MPU_InitStruct.SubRegionDisable = 0x0;  // 全部启用,不禁用子区域
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;  // 设置区域类型
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;  //  禁止任何访问,两种模式都不能读写。
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;  // 允许执行指令
	MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;  // 不可共享
	MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;  // 允许缓存
	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;  // 禁止缓冲
	HAL_MPU_ConfigRegion(&MPU_InitStruct);  // MPU 配置函数

	/* 区域1: DTCM RAM (112KB) 完全访问,可执行 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;  // 启用
	MPU_InitStruct.Number = MPU_REGION_NUMBER1;  // 区域1
	MPU_InitStruct.BaseAddress = 0x20000000;  // DTCM RAM 固定起始地址
	MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 区域2: AXI SRAM (384KB),完全访问,不可执行 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;
	MPU_InitStruct.Number = MPU_REGION_NUMBER2;
	MPU_InitStruct.BaseAddress = 0x24000000;  // AXI SRAM Base Address
	MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 区域3: SRAM1-4(最大512KB),完全访问,不可执行 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;
	MPU_InitStruct.Number = MPU_REGION_NUMBER3;
	MPU_InitStruct.BaseAddress = 0x30000000;  // SRAM1-4 Base Address
	MPU_InitStruct.Size = MPU_REGION_SIZE_1MB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 区域4:外围设备(AHB1/2、APB1/2),仅限特权访问 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;
	MPU_InitStruct.Number = MPU_REGION_NUMBER4;
	MPU_InitStruct.BaseAddress = 0x40000000;  // Peripheral Base Address
	MPU_InitStruct.Size = MPU_REGION_SIZE_512MB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 区域5:QSPI内存(如果使用)-只读,可执行 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;
	MPU_InitStruct.Number = MPU_REGION_NUMBER5;
	MPU_InitStruct.BaseAddress = 0x90000000;  // QSPI Memory Base Address
	MPU_InitStruct.Size = MPU_REGION_SIZE_128MB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 区域6:SDRAM(如果使用),完全访问,不可执行 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;
	MPU_InitStruct.Number = MPU_REGION_NUMBER6;
	MPU_InitStruct.BaseAddress = 0xD0000000;  // SDRAM Base Address
	MPU_InitStruct.Size = MPU_REGION_SIZE_64MB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 区域7:系统和核心外围设备,仅限特权访问 */
	MPU_InitStruct.Enable = MPU_REGION_ENABLE;
	MPU_InitStruct.Number = MPU_REGION_NUMBER7;
	MPU_InitStruct.BaseAddress = 0xE0000000;  // System Peripherals Base
	MPU_InitStruct.Size = MPU_REGION_SIZE_1MB;
	MPU_InitStruct.SubRegionDisable = 0x0;
	MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
	MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW;
	MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
	MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
	MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
	MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	// 第三步:使能 MPU
	/* Enable the MPU with privileged default access */
	HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

// 中断服务程序(在stm32h7xx_it.c中)
void EXTI3_IRQHandler(void) {
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); // 调用HAL库中断处理函数
}

// 中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  if (GPIO_Pin == GPIO_PIN_3) {
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15); // 翻转LED
  }
}




未有知而不行者,知而不行,只是未知。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐