STM32基础 - GPIO
文章目录
认识 GPIO
GPIO 是什么
- GPIO - 通用输入/输出口(General Purpose Input/Output),在集成电路板的上以针脚的形式呈现,通常称为引脚,是嵌入式系统开发中的核心组件,芯片通过引脚与不同的外设进行通信,输出信息控制外设(LED灯、电机等)或通过外设(温湿度传感器、光敏电阻等)采集信息。
- GPIO 本质是一组可通过软件配置的可编程引脚。没有固定功能,可以通过寄存器配置来实现与不同外设的交互。
作用
- 输入:可读取外部电压信号(高电平 / 低电平),感知按键、传感器等设备的状态;
- 输出:可输出高电平(3.3V)或低电平(0V),控制 LED、继电器、电机等设备的工作。
内部结构
- 保护二极管:防止引脚输入电压过高(高于 VDD)或过低(低于 GND)损坏芯片;
- 上拉 / 下拉电阻:可通过配置使引脚默认保持高电平(上拉)或低电平(下拉);
- 输入缓冲器:将外部信号传入内部电路(用于输入模式);
- 输出驱动器:将内部信号输出到引脚(用于输出模式,分推挽和开漏两种结构);
- 复用功能切换开关:可将引脚切换到 “复用模式”,作为其他外设(如 UART 的 TX/RX)的专用引脚。
分类
- 电源引脚:为芯片及外设提供工作电压,是电路正常运行的基础。
- 晶振引脚:为芯片提供稳定的时钟信号,是定时器、UART 等外设正常工作的基础。
- 复位引脚:控制芯片复位,使芯片恢复到初始状态。
- 下载引脚:用于程序下载、在线调试(单步执行、断点等),是开发阶段的核心引脚。
- BOOT引脚:设置芯片启动模式,决定上电后程序从哪个存储区域加载执行。
- GPIO引脚:通用输入 / 输出引脚,可通过软件配置为输入、输出或复用功能,实现与外设的灵活交互(如控制 LED、读取按键、连接传感器等)。
- 因为STM32芯片类型比较多,不同类型的芯片对引脚的定义不同,所以在使用过程中要根据芯片的数据手册来看引脚的具体定义,当然,使用CubeMX图形化界面进行配置时,也能看到不同的功能默认的引脚是哪个,比如启用I2C1时,默认的PB6和PB7会变成绿色,再比如DEBUG功能,默认引脚是PA13和PA14。所以在使用时要注意引脚冲突。
- 当多个外设需要用到同一个引脚时,会产生冲突,当然,引脚是可以复用的,但是启用复用比较复杂,新手不建议使用利用功能。可以通过对应的芯片数据手册来查看各个引脚的默认功能,避免引脚冲突。
STM32 引脚
8种工作模式
- 输入浮空(Floating):引脚内部无上拉 / 下拉电阻,电平完全由外部信号决定(如悬空时电平不稳定)。适合读取明确的外部驱动信号(如传感器输出的数字信号)。
- 输入上拉(Pull-Up):引脚内部接一个上拉电阻(默认拉到 VDD),无外部信号时默认高电平。适合读取 “低电平有效” 的信号(如按键接地,按下时为低电平)。
- 输入下拉(Pull-Down):引脚内部接一个下拉电阻(默认拉到 GND),无外部信号时默认低电平。适合读取 “高电平有效” 的信号(如按键接 VDD,按下时为高电平)。
- 模拟输入(Analog):输入信号直接接入 ADC(模数转换器),用于读取模拟量(如电压)。此时上拉 / 下拉电阻和数字输入缓冲器均关闭,避免干扰模拟信号。
- 推挽输出(Push-Pull):输出级由两个 MOS 管组成(一个接 VDD,一个接 GND),可直接输出高电平(VDD)或低电平(GND),驱动能力强(可直接驱动 LED、继电器)。
- 开漏输出(Open-Drain):输出级仅一个接 GND 的 MOS 管,高电平时 MOS 管截止,引脚电平由外部上拉电阻决定(需外接上拉或使用内部上拉)。适合电平转换(如 5V 设备通信)、线与(多个设备共用总线,如 I2C)。
- 推挽式复用(Alternate Open-Drain):复用功能下的推挽输出(如 UART_TX 输出高 / 低电平),驱动能力由外设决定。
- 开漏式复用(Alternate Open-Drain):复用功能下的开漏输出(如 I2C_SDA/SCL,需外部上拉电阻)。
- **注意:**复用模式需同时配置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 可处理其他任务)。
中断
-
GPIO Mode 中有中断参数要结合NVIC(Nested Vectored Interrupt Controller:嵌套向量中断控制器)来使用,后面会单独学习NVIC。

-
以H743板载的可编程按键为例来介绍GPIO中断的配置过程。根据原理图得H743板载USER按键内部连接PB3,且按下接地。
-
所以要把引脚电平上拉,且使用下降沿触发中断,否则检测不到电平变化,如果内部接GND时,要使用下拉,且上升沿触发中断,否则检测不到电平变化。
-
执行过程:
- 触发中断:按键触发中断后,EXIT(External interrupt/event controller: 外部中断控制器)会向NVIC发送中断请求;
- NVIC响应:NVIC 接收到中断请求后,会根据中断优先级判断是否允许中断响应。若允许,CPU 会暂停当前正在执行的任务,从中断向量表中找到 EXTI3_IRQHandler 函数的入口地址。
- 自动调用中断服务程序:CPU 跳转到 EXTI3_IRQHandler 函数的地址,自动执行该函数中的代码(这一步是硬件机制,无需软件显式调用)。
配置过程
- 使能GPIO时钟,非常重要,如果不使能,所有的配置都无效。函数:
__HAL_RCC_GPIOA_CLK_ENABLE(); - 配置GPIO,函数:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, const GPIO_InitTypeDef *GPIO_Init); - 读或者写,非必须,
- 读函数:
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)
- 读函数:
- 中断函数,非必须
- 配置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); – 函数名固定
- 写函数中的参数
GPIO_PinState PinState选项如下:- GPIO_PIN_RESET: 设为低电平
- GPIO_PIN_SET: 设为高电平
- 电平转换函数:
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引脚,所以都是高电平点亮,附原理图。
-

-
不使用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
}
}
未有知而不行者,知而不行,只是未知。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)