STM32 GPIO输入输出实战教程:江协经验分享,避免常见误区

江协stm32学习:3-1~3-4 GPIO输出、GPIO输入

一、GPIO简介

  • GPIO(General Purpose Input Output)通用输入输出口
  • 引脚电平:0V~3.3V,部分引脚可容忍5V(可看上一篇博客中引脚定义部分,带FT的可以接5V stm32芯片命名规则、外设介绍——江协教程踩坑经验分享)
  • 可配置为8种输入输出模式
  • 输出模式下可控制端口输出高低电平,来驱动LED、控制蜂鸣器、模拟通信协议输出时序等(如果是功率比较大的设备再加入驱动电路即可)
  • 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等
  • 二、GPIO的结构

    1、基本结构

  • GPIO外设按照GPIOA、GPIOB等来命名,每个外设有16个引脚,编号0~15
  • 每个GPIO模块中主要包含寄存器和驱动器。寄存器是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,从而完成输出和读取电平的功能,寄存器的每一位对应一个引脚(所以只是用了低16位),输出/读取寄存器写1,对应引脚输出/读取高电平,写0则低电平。而驱动器用来增加信号的驱动能力
  • 2、位结构

    3、驱动器配置:输入、输出模式

    1)输入模式

    输入模式下,输出都是无效的。

    上拉模式相当于默认高电平模式,下拉模式则是默认低电平模式,不接属于浮空输入,极易受到外界环境影响。由于浮空输入的电平是不确定的,所以使用浮空输入的时候,端口一定要接上一个连续的驱动源,不能出现悬空的状态。

    而模拟输入的特征是GPIO无效,引脚直接接入内部ADC。是ADC模数转换器的专属配置。

    2)输出模式

    输出模式下,输入仍然有效。

    推挽输出模式下,P-MOS和N-MOS均有效,数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平;为0时,反之。这种模式下,高低电平均有较强的驱动能力,所以也叫强推输出模式。在此模式下,STM32对IO口具有绝对的控制权

    在开漏模式下,P-MOS无效,只有N-MOS在工作。数据寄存器为1时,下管断开,输出相当于断开,也就是高阻模式;数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平。这种模式下,低电平有驱动能力,高电平无。可以作为通信协议的驱动方式,在多机通信的情况下,可以避免多个设备的相互干扰;还可以用于输出5V的电平信号。

    复用的输出引脚电平是由片上外设控制。

    关闭状态,是当引脚配置为输入模式时,两MOS管均无效,也就是输出关闭,端口的电平由外部信号控制。

    (想要详细了解每个模式对应的电路,可以从19:42开始看,到23:18 江协 3-1GPIO)

    4、GPIO寄存器配置

    1)端口配置寄存器

    每个端口模式需要4位进行配置,16个端口就需要64位,所以这里的配置寄存器有两个。一个是端口配置低寄存器,一个是端口配置高寄存器。

    GPIO的输出速度可以限制输出引脚的最大翻转速度,为了低功耗的稳定性的,一般设置为50MHz

    2)端口输入寄存器和端口输出寄存器

    低16位对应16个引脚,高16位没有使用

    3)端口位设置/清除寄存器

    高16位进行位清除,低16位进行位设置,写1:设置或者清除;0:不影响

    4)端口位清除寄存器

    低16位进行位清除。存在的意义是方便操作,如果只想单一的进行设置或者位清除,那么位设置时使用3),位清除时使用4)。对多个的话,用3),可以保证同步性

    5)端口配置锁定寄存器

    对端口的配置进行锁定,防止意外更改。

     三、GPIO初始化

    1、操作STM32的GPIO的三个步骤

    1、使用RCC开启GPIO的时钟 (RCC使能时钟)
    2、使用GPIO_Init函数初始化GPIO
    3、使用输出或者输入的函数控制GPIO口

    一共涉及RCC和GPIO两个外设

    2、RCC库函数介绍(使能时钟)

    library->rcc.h,在h文件的最下面时库函数所有函数的声明

    常用的有三种:第一个参数是选择哪个外设。第二个参数是使能enabled或失能disable。

    void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
    void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
    void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

    //第一个参数是选择哪个外设。第二个参数是使能enabled或失能disable。
    
    void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);//RCC AHB外设时钟控制
    void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);//RCC APB2外设时钟控制
    void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);//RCC APB1外设时钟控制
    

    3、GPIO库函数介绍——初始化函数

    (输入输出函数在后续输入输出部分分开写)

    void GPIO_DeInit(GPIO_TypeDef* GPIOx);    //将指定的GPIO端口恢复到默认状态。

    void GPIO_AFIODeInit(void);    //将AFIO复位到默认状态。

    void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);    //非常重要,是使用结构体的参数来初始化GPIO口    //一般我们初始化外设都是使用init函数

    void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);    //给GPIO初始化结构体赋一个默认值

    ※结构体配置 GPIO_InitTypeDef

  • Mode: 指定GPIO的工作模式(如输入、输出、复用功能等)。

  • Speed: 指定输出速度(仅适用于输出模式)。

  • Pull: 指定是否启用内部上拉或下拉电阻。

  • Alternate: 指定复用功能编号(仅适用于复用功能模式)。

  • Pin: 指定要配置的引脚。

  • 模式宏定义 描述 备注
    GPIO_MODE_INPUT 输入模式 用于接收来自外部的信号
    GPIO_MODE_OUTPUT_PP 推挽输出模式 用于输出信号,能够驱动较高或较低的电平
    GPIO_MODE_OUTPUT_OD 开漏输出模式 用于输出信号,需要外接上拉电阻
    GPIO_MODE_AF_PP 复用功能推挽输出模式 用于复用功能(如I2C、USART等)的推挽输出
    GPIO_MODE_AF_OD 复用功能开漏输出模式 用于复用功能(如I2C、USART等)的开漏输出
    GPIO_MODE_ANALOG 模拟模式 引脚配置为模拟输入,用于ADC等模拟外设
    GPIO_MODE_IT_RISING 上升沿触发中断输入模式 输入信号的上升沿触发中断
    GPIO_MODE_IT_FALLING 下降沿触发中断输入模式 输入信号的下降沿触发中断
    GPIO_MODE_IT_BOTH_EDGE 双边沿触发中断输入模式 输入信号的上升沿和下降沿都触发中断
    #include "stm32f10x.h"  // Device header
    
    int main(void)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
        //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;//可以同时初始化多个引脚
        //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_ALL;//初始化所有引脚
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure)
    
        while (1)
        {
        }
    }

    四、GPIO输出

    1、硬件电路(LED灯和蜂鸣器为例)

    对于功率较低的,可以使用左侧的两种电路。又由于很多单片机或者芯片都使用了高电平弱驱动,低电平强驱动的规则,这样可以一定程度上避免高低电平打架。所以通常会使用。左上的电路。这样在输出低电平的时候电路可以工作,是强驱动。

    对于功率稍微大一点的,直接用IO口驱动会导致stm32负担过重,这时可以使用一个三极管驱动电路(最简单的驱动电路)来完成驱动的任务。图中右侧上方是PNP电路,下方是NPN。

    浅谈单片机的GPIO外部驱动电路(三极管驱动电路)这一篇文我觉得讲的挺好,简单的电路和驱动电路都有,而且讲解了设计思路,告诉你了为什么这样设计,我觉得初学者也可以看。

    2、写入函数(GPIO输出)

    //第一个参数是端口类型GPIOA、GPIOB等,第二个是要设置的引脚编号

    void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);    //将指定的端口设置为高电平

    void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);   //将指定的端口设置为低电平

    //第一个参数是端口类型GPIOA、GPIOB等,第二个是要设置的引脚编号,第三个是引脚输出值,使用Bit_SET表示高电平,Bit_RESET表示低电平,如果想使用0、1来表示低电平和高电平,需要加上强制类型转换

    void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);

    GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET); // 将GPIOA的第5引脚设置为高电平
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET); // 将GPIOA的第5引脚设置为低电平
    
    //如果想使用0、1来表示低电平和高电平,需要加上强制类型转换
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)0);
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)1);

    //第一个参数是端口类型GPIOA、GPIOB等,第二个是指定要写入的端口值,每个位对应一个引脚的输出值(0表示低电平,1表示高电平)。

    void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);  //同时对16个端口进行写入操作

    GPIO_Write(GPIOA, 0x00FF); // 将GPIOA的低8位设置为高电平,高8位保持不变

    五、GPIO输入

    1、硬件电路(按键和传感器为例)

    对于按键电路,一般使用上两种,也就是下接的方式,这是电路设计的习惯与规范。

    左上和左下的都需要配置上拉/下拉模式,以免出现悬空状态;右边两个随意,其实就是把stm32内部的上拉、下拉电阻移在了外面。

    上面两种接法,按键按下时引脚时低电平,松手是高电平;下面两种接法,按键按下时引脚是高电平,松手是低电平。

    2、读取函数(GPIO输入)

    1)读取输入寄存器

    //第一个参数指定外设,第二个参数指定引脚

    uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);   //用来读取输入数据寄存器某个端口的输入值

    uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);   //读取整个输入寄存器//返回一个16位的数据,每一位代表一个端口值

    2)读取输出寄存器

    uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);   //用来读取输出数据寄存器某个端口的输入值,所以严格来说它不属于读取输入,而是在输出的时候看看自己在输出啥,方便检查的

    uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);   //读取整个输出寄存器

    六、模块化封装

    一般来说,对于这种驱动代码而言,我们一般都会把它封装起来,方便管理和移植。.c用来存放驱动程序的主体代码,.h用来存放这个驱动程序可以对外提供的函数或变量的声明

    1、h文件

    h文件的格式很固定,indef+define+endif

  • #ifndef __LED_H:检查__LED_H这个宏是否未被定义。如果__LED_H未被定义,那么编译器会执行#ifndef#endif之间的代码。

  • #define __LED_H:定义一个名为__LED_H的宏,这个宏定义的作用是标记这个头文件已经被包含过,防止后续代码再次包含这个头文件。

  • #endif:“结束条件编译”(End If)的指令,表示#ifndef块的结束。

  • #ifndef#endif之间的代码:c文件中定义的函数的声明,表明它可以被外部引用。后加分号。

  • #ifndef __LED_H
    #define __LED_H
    
    void led_init(void);
    void led_on(void);
    void led_off(void);
    
    #endif

    2、c文件

    首先引入stm32fx10x.h头文件

    然后开始写驱动程序的函数,初始化可以全部放在一个函数里面,这样之后主程序只需要调用一个函数就可以完成全部初始化

    #include "stm32f10x.h"  // Device header
    
    void LED_Init(void)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    }
    
    void LED_On(void)
    {
        GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);  // 将引脚1和2设置为高电平,点亮LED
    }
    
    void LED_Off(void)
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);  // 将引脚1和2设置为低电平,熄灭LED
    }

    作者:白鱼不小白

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 GPIO输入输出实战教程:江协经验分享,避免常见误区

    发表回复