STM32---GPIO

目录

一、GPIO原理图

二、操作BSRR/BRR寄存器保证原子性

[1. I/O端口位的编程和访问限制](#1. I/O端口位的编程和访问限制)

[2. GPIOx_BSRR和GPIOx_BRR寄存器的作用](#2. GPIOx_BSRR和GPIOx_BRR寄存器的作用)

[3. IRQ不会发生危险的含义](#3. IRQ不会发生危险的含义)

[4. 具体例子](#4. 具体例子)

[5. 总结](#5. 总结)

三、C++封装标准库的GPIO示例


在学习STM32的时候,我们最开始学习的就是控制GPIO成为点灯大师。本文将基于STM32系统结构图,解读GPIO的电路结构与工作模式,帮助初学者快速掌握STM32的GPIO控制核心基础。并使用C++进行封装,提高代码的可读性、可移植性。

一、GPIO原理图

在芯片手册中有一个描述了GPIO的电路图,通过对电路图的不同配置可以配置成不同的模式。

下面我们根据这个原理图分析一下各个电路模块有什么作用

VDD/VSS保护二极管:

位于 GPIO 引脚的上下两端,**用于防止引脚外部过高或过低的电压输入。**当引脚电压高于芯片供电电压(VDD)时,上方的二极管导通,将多余的电流引导到电源;当引脚电压低于地电位(VSS)时,下方的二极管导通,将电流引导到地,从而保护芯片免受损坏。

P-MOS 管和 N-MOS 管

组成推挽输出电路的核心部分。在推挽输出模式下,当输出高电平时,P-MOS 管导通,N-MOS 管截止,引脚输出高电平;当输出低电平时,P-MOS 管截止,N-MOS 管导通,引脚输出低电平。推挽输出模式具有输出电流能力强、负载能力大等优点,适用于驱动外部负载。

不过单片机的P-MOS通常设置为弱上拉,主要是为了平衡驱动能力与功耗。让单片机既能生成高电平防止浮空状态的干扰,又能有效降低功耗。如果真的需要大电流的上拉功能,可以自行在芯片外部接一个上拉电阻或者驱动增强模块,一般情况下,这个弱上拉只是用做控制信号,而并非直接驱动。

施密特触发器

图中写的是TTL肖特基触发器,可能是翻译的时候出问题了,实际上是叫做施密特触发器。它具有稳压和滤波的作用,能够将输入的模拟信号转换为稳定的数字信号。在输入模式下,施密特触发器可以过滤掉输入信号中的噪声干扰,确保输入电平的稳定性。

二、操作BSRR/BRR寄存器保证原子性

使用手册中有这样一句话:每个I/O端口位可以自由编程,然而必须按照32位字访问I/O端口寄存器(不允许半字或字节访问)。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器进行读/更改的独立访问;这样,在读和更改访问之间产生IRQ时不会发生危险。

如何理解

1. I/O端口位的编程和访问限制

  • 每个I/O端口位可以自由编程
    • 这意味着GPIO的每个引脚(位)都可以独立设置为输入或输出,并且可以单独控制其状态(高电平或低电平)。
  • 必须按照32位字访问I/O端口寄存器
    • GPIO寄存器通常是一个32位的寄存器,访问时必须以32位为单位进行操作。尽管每个位都可以单独控制,但是必须要先记录所有位,然后统一写回操作。
    • 不允许半字(16位)或字节(8位)访问
      • 如果尝试以16位或8位的方式访问这些寄存器,可能会导致未定义的行为或硬件错误。

2. GPIOx_BSRRGPIOx_BRR寄存器的作用

  • GPIOx_BSRR(端口位设置/复位寄存器)
    • 这是一个特殊的寄存器,允许同时设置(置1)或复位(清0)GPIO引脚的状态,而无需读取或修改其他引脚的状态。
    • 通过写入不同的位,可以独立地设置或复位某个引脚,而不会影响其他引脚。
  • GPIOx_BRR(端口位复位寄存器)
    • 类似于BSRR,但专门用于复位(清0)GPIO引脚的状态。
    • 在某些微控制器中,GPIOx_BRR可能被整合到GPIOx_BSRR中,通过特定的位操作实现复位功能。
  • 独立访问
    • GPIOx_BSRRGPIOx_BRR的设计允许对GPIO寄存器进行独立的读/更改操作,而不需要先读取整个寄存器的值,修改后再写回。
    • 这种设计避免了在多任务或中断环境下可能出现的竞争条件(Race Condition)或数据不一致问题。

3. IRQ不会发生危险的含义

  • IRQ(中断请求)
    • 当一个中断发生时,CPU会暂停当前任务,转而执行中断服务程序(ISR)。在ISR执行期间,如果其他任务或中断试图访问同一个GPIO寄存器,可能会导致数据竞争或不一致。
  • 避免危险
    • 使用GPIOx_BSRRGPIOx_BRR寄存器时,由于它们是独立访问的,不会读取或修改其他引脚的状态,因此即使在中断发生时,也不会影响其他引脚的状态。
    • 这种设计减少了因中断导致的潜在错误,确保了系统的稳定性和可靠性。

4. 具体例子

假设你正在修改某个GPIO引脚的状态(如设置高电平或低电平),以下操作可能导致问题:

  • 传统方式:先读取当前状态,修改后再写回,可能导致数据竞争或冲突。
  • **使用GPIOx_BSRRGPIOx_BRR直接修改寄存器值,可能导致其他任务(如显示控制)被中断或覆盖。
  • 使用GPIOx_BSRRGPIOx_BRR
    • GPIOx_BSRR通过位操作直接修改寄存器值,不会影响其他引脚。
    • GPIOx_BRR通过位清除操作实现复位,但可能覆盖其他位。

5. 总结

  • 核心优势
    • GPIOx_BSRRGPIOx_BRR寄存器的设计允许对GPIO引脚进行独立、安全的操作,避免了在多任务或中断环境下的数据竞争问题。
  • 应用场景
    • 在实时性要求高的场景(如电机控制、传感器数据采集)中,这种设计可以确保在中断发生时,不会因为数据竞争或覆盖问题导致系统错误。

通过这种方式,系统能够在高并发或中断环境下稳定运行,确保GPIO操作的原子性和一致性。

三、C++封装标准库的GPIO示例

cpp 复制代码
#pragma once
#include "stm32f10x.h" 

#include "stm32f10x_gpio.h"         //GPIO模块
#include "stm32f10x_rcc.h"          //RCC时钟控制模块 


class GPIO_Base 
{
private:
    GPIO_TypeDef* port;    // GPIO端口,如GPIOA, GPIOB等
    uint16_t pin;          // GPIO引脚号,如GPIO_Pin_0, GPIO_Pin_1等

public:
    // 构造函数
    GPIO_Base(GPIO_TypeDef* gpioPort, uint16_t gpioPin)
        : port(gpioPort), pin(gpioPin) {}

    // 初始化GPIO引脚(假设为输出模式)
    void initAsOutput(uint32_t speed = GPIO_Speed_50MHz) 
    {
        GPIO_InitTypeDef GPIO_InitStruct;

        // 使能GPIO时钟(根据你的具体端口选择)
        if (port == GPIOA) 
        {
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        } 
        else if (port == GPIOB) 
        {
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        }
        // 可以根据需要添加更多端口的时钟使能

        // 配置GPIO引脚
        GPIO_InitStruct.GPIO_Pin = pin;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出模式
        GPIO_InitStruct.GPIO_Speed = (GPIOSpeed_TypeDef)speed;
        GPIO_Init(port, &GPIO_InitStruct);
    }

    // 初始化GPIO引脚(假设为输入模式)
    void initAsInput(GPIOMode_TypeDef mode = GPIO_Mode_IN_FLOATING) 
    {
        GPIO_InitTypeDef GPIO_InitStruct;

        // 使能GPIO时钟(根据你的具体端口选择)
        if (port == GPIOA) 
        {
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        } 
        else if (port == GPIOB) 
        {
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        }
        // 可以根据需要添加更多端口的时钟使能

        // 配置GPIO引脚
        GPIO_InitStruct.GPIO_Pin = pin;
        GPIO_InitStruct.GPIO_Mode = mode;  // 输入模式
        GPIO_Init(port, &GPIO_InitStruct);
    }

    // 设置引脚为高电平
    void setHigh() 
    {
        GPIO_SetBits(port, pin);
    }

    // 设置引脚为低电平
    void setLow() 
    {
        GPIO_ResetBits(port, pin);
    }

    // 读取引脚状态
    bool read() 
    {
        return GPIO_ReadInputDataBit(port, pin);
    }
};
cpp 复制代码
#include"GPIO_Base.h"

// 示例用法
int main(void) 
{
  // 初始化系统时钟等(这里省略)
  //通常不需要手动配置,stm32f10x系列默认是在启动文件中配置好了72MHz


  // 创建一个GPIO_Base对象,假设使用GPIOA的Pin5
  GPIO_Base led(GPIOA, GPIO_Pin_5);

  // 初始化引脚为输出
  led.initAsOutput();

  while (1) 
  {
      // 设置引脚为高电平
      led.setHigh();
      // 延时(这里省略具体的延时实现)
      for (volatile int i = 0; i < 500000; i++); // 简单延时

      // 设置引脚为低电平
      led.setLow();
      // 延时(这里省略具体的延时实现)
      for (volatile int i = 0; i < 500000; i++); // 简单延时

  }
}

调整为静态函数:

因为本人在后续封装别的模块的时候,发现如果对GPIO所有的功能都写到一个类中,非常不利于调整,所以修改为不再含有成员变量,仅仅封装处理函数,使用者直接输入GPIOx以及引脚pin号。

cpp 复制代码
#include "stm32f10x.h"  // 包含STM32F10x系列的标准外设库头文件

class GPIOHandler {
public:
    // 静态方法:初始化GPIO引脚
    static void InitGPIO(GPIO_TypeDef* gpioPort, uint16_t gpioPin, GPIOMode_TypeDef gpioMode) {
        if (gpioPort == nullptr) return;  // 简单的空指针检查

        GPIO_InitTypeDef GPIO_InitStructure;

        // 使能GPIO时钟
        if (gpioPort == GPIOA) {
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        } else if (gpioPort == GPIOB) {
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        }
        // 可以根据需要添加其他GPIO端口

        // 配置GPIO
        GPIO_InitStructure.GPIO_Pin = gpioPin;
        GPIO_InitStructure.GPIO_Mode = gpioMode;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(gpioPort, &GPIO_InitStructure);
    }

    // 静态方法:设置GPIO引脚输出高电平
    static void SetPinHigh(GPIO_TypeDef* gpioPort, uint16_t gpioPin) {
        if (gpioPort != nullptr) {
            GPIO_SetBits(gpioPort, gpioPin);
        }
    }

    // 静态方法:设置GPIO引脚输出低电平
    static void SetPinLow(GPIO_TypeDef* gpioPort, uint16_t gpioPin) {
        if (gpioPort != nullptr) {
            GPIO_ResetBits(gpioPort, gpioPin);
        }
    }
};
相关推荐
嵌入式-老费7 小时前
Linux上位机开发实践(以MCU小系统入门嵌入式电路)
单片机·嵌入式硬件
WPG大大通8 小时前
基于意法半导体STM32G473和STDRIVE 101的电池供电BLDC/PMSM电动工具
stm32·单片机·嵌入式硬件·方案·大大通
宽容人厚载物8 小时前
Jetson Orin NX 部署YOLOv12笔记
嵌入式硬件·yolo·jetson·yolov12·英伟达开发板
云山工作室9 小时前
基于单片机的温湿度采集系统(论文+源码)
人工智能·单片机·嵌入式硬件·毕业设计·毕设
完成大叔10 小时前
嵌入式:ARM公司发展史与核心技术演进
arm开发·嵌入式硬件
jacklood10 小时前
基于cubeMX的hal库STM32实现硬件IIC通信控制OLED屏
stm32·单片机·嵌入式硬件
硬匠的博客12 小时前
C++面向对象
开发语言·c++·单片机
define菜鸟#12 小时前
51单片机实验一:点亮led灯
单片机·嵌入式硬件
深圳信迈科技DSP+ARM+FPGA13 小时前
基于龙芯 2K1000处理器和复旦微 FPGA K7 的全国产RapidIO 解决方案研究
嵌入式硬件·fpga开发·机器人·信号处理