使用的单片机机型为STM32F103C8T6

文章目录

  • GPIO基本结构
  • GPIO模式(重点)
  • 编程实例
  • GPIO闪烁灯
  • 流水灯
  • 引脚输出控制蜂鸣器
  • 按键控制LED
  • 光敏电阻控制蜂鸣器
  • GPIO(General Purpose Input/Output)通用输入输出接口,是一种可以在微控制器(MCU)或其他数字电子设备上配置为输入或输出的接口。常用于与外部设备进行通信和控制。

    GPIO的基本功能包括:

  • 输入模式(Input):将GPIO引脚配置为输入模式时,设备可以读取外部设备(如传感器、开关等)的信号。例如,接收温度传感器的输出,或检测按键的状态。

  • 输出模式(Output):将GPIO引脚配置为输出模式时,设备可以向外部设备发送信号。例如,控制LED灯的开关,或者驱动继电器、蜂鸣器等硬件。


  • STM32系统结构

  • Cortex-M3:CPU核心
  • DMA1/2:辅助CPU,做一些频繁的数据搬运的工作
  • APB1/2:外设总线
  • APB1:适用于低功耗、低速、低带宽的外设,适合一些控制任务和低速通信任务
  • APB2:适用于对时钟频率要求较高的外设,适合需要更高带宽、更高精度的任务
  • GPIO 就处于 APB2 总线上

    GPIO基本结构

    GPIO 总共有 A ~ G,7个外设,每个 GPIO 有一个寄存器和驱动器,因为引脚为16根,所以寄存器为16位,每位控制一个一个引脚。STM32地址为32位,所以寄存器只用到了地址的低16位,高16位没有使用

    输出:当寄存器写入0,通过驱动器输出对应端口低电平;当寄存器写入1,通过驱动器输出对应端口高电平

    输入:驱动器将端口的高低电平转化为0/1,写入寄存器,再通过 APB2 总线读取

    GPIO模式(重点)

    GPIO内部,由输入输出寄存器输入输出驱动器外部引脚组成


    I/O引脚及保护电路

    首先讲解一些 I/O引脚及其保护电路

    输出模式时,I/O引脚由输出驱动器控制,不会有问题。但为输入模式时,I/O引脚由外部控制。若无保护电路,当外部电压过高或过低时,都会对驱动器及寄存器造成损害

    保护二极管作用如下:
    VDD为电源正极,通常为3.3V;VSS为负极

    当电压过高时,电流会导通到VDD

    当电压过低时,电流会导通到外部

    如此就不会损害内部电路


    输入模式

    输入有四个模式:浮空输入,上拉输入,下拉输入,模拟输入

    数据从 I/O引脚进入输入驱动器,会先经过两个开关和电阻,一个接VDD(正极)为上拉电阻,一个接VSS(负极)位下拉电阻,可通过程序配置哪个开关导通

    当上面开关导通,下面开关关闭,为上拉输入模式
    可读取引脚电平,若引脚悬空,默认输入高电平

    当上面开关关闭,下面开关打开,为下拉输入模式
    可读取引脚电平,若引脚悬空,默认输入低电平

    当上下开关都不导通,为浮空输入模式
    可读取引脚电平,当引脚悬空时,电平不确定,极易受外界影响。
    使用浮空输入时,需要确保输出源是连续不断的,不能出现浮空的状态

    上拉电阻和下拉电阻的阻值都较大,防止影响电路,为弱上/下拉


    左侧为施密特触发器(肖特基为翻译错误),作用是将不稳定的电平转化为稳定的电平。因为电平转化为数字只有0/1,而不稳定的电平会造成不稳定的输入

    原理:设定上下阈值,当电平高于上阈值,切换为高电平;当电平低于下阈值,切换为低电平

    示例:

    最往左将输入写入输入数据寄存器,可通过APB2总线读出数据


    模拟输入

    GPIO无效,引脚直接接入内部ADC,用于AD电模转换。因为读取电平值,所以在施密特触发器前

    复用功能输入

    直接接在一个需要输入的外设上,如串口的输入引脚,因为需要的是数据量,所以在施密特触发器后


    输出模式

    输出有四种模式:开漏输出,推挽输出,复用开漏输出,复用推挽输出

    推挽输出

    首先,通过寄存器写入数据0/1。
    当写入数据1时,上面的晶体管导通,连接VDD输出高电平

    当写入数据0时,下面的晶体管导通,连接VSS输出低电平

    推挽输出的高低电平由正负极决定,有较强的电流和驱动能力
    典型的应用包括LED驱动、电机控制、PWM信号输出、串行通信、信号发生器、继电器驱动以及高频信号传输等领域。


    开漏输出

    开漏输出不使用上面的晶体管,通常还需要外接一颗上拉电阻

    当寄存器写入数据0,下面的晶体管导通,输出低电平

    当寄存器写入数据1,下面的晶体管断开,此时为高阻态,不会有电流流过,由上拉电阻接的电源提供高电平

    为什么要这样设计呢?

    因为单片机有多机通信的场景,例如 I2C、SPI。
    多个设备挂载在一条通信总线上

    如果采用推挽输出,假设一个设备输出高电平,一个设备输出低电平,会出现以下场景

    此时必有一个晶体管会被烧毁
    而开漏输出搭配上拉电阻就可以规避这个场景

    高电平由上拉电阻输出
    对于每个设备,想输出低电平,就导通N-MOS晶体管;想输出高电平,就关闭N-MOS,由上拉电阻输出高电平

    此时,可能场景如下:

    1. 两个都输出低电平,导通N-MOS,VDD会导向任意一个,此时输出低电平

    2. 两个都输出高电平,都关闭N-MOS,为高阻态,不会有电流流过,由上拉电阻输出高电平

    3. 一个输出高电平,一个输出低电平。一个关闭N-MOS,一个开启N-MOS。此时VDD被拉低,输出低电平

    尽管无法满足部分设备的输出,但是避免了晶体管烧毁的情况


    推挽输出 和 开漏输出由左侧的数据输出寄存器控制,而复用推挽输出 和 复用开漏输出由片上外设控制

    编程实例

    GPIO闪烁灯

    ST封装了GPIO的库函数,步骤如下:

    1. 开启指定的GPIO外设时钟使能
    //使能时钟
    //接在A0端口,属于GPIOA
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    1. 初始化/配置GPIOA
    //初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;			//A0引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//50MHz速度
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
  • GPIO_Mode:GPIO的8种模式
  • GPIO_Pin:要配置的引脚,共16个

    GPIO_All 代表全部16个引脚,多个引脚可以同时配置,使用 |
  • GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
    
  • GPIO_Speed:I/O口翻转的速度,配置 GPIO 引脚工作速度
  • 低速(GPIO_Speed_Level_1):
    适用于一些不需要快速响应的简单应用,比如低频信号的数字输入输出(例如按键扫描、开关控制等)

  • 中速(GPIO_Speed_Level_2):
    适合一般用途的 GPIO 引脚,响应速度适中。例如常见的外设接口(如 UART),或者中等速度的信号控制

  • 高速(GPIO_Speed_Level_3):
    用于要求较高数据传输速率的应用,如 SPI、I2C 通信,或者需要频繁切换的数字输出

    1. 读/写数据

    ST同样提供了数据读写的方法,在stm32f10x_gpio.h 最后可以看到定义

    API 参数 返回值 说明
    uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); GPIOX:指定GPIO外设,GPIOA ~ GPIOG
    GPIO_Pin:指定引脚,0 ~ 15
    从指定引脚读到的数据 读取指定GPIO外设的输入寄存器数据的某一位
    uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); GPIOX:指定GPIO外设,GPIOA ~ GPIOG 输入寄存器的16位数据 读取指定GPIO外设的16位输入寄存器数据
    uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); GPIOX:指定GPIO外设,GPIOA ~ GPIOG
    GPIO_Pin:指定引脚,0 ~ 15
    从指定引脚读到的数据 读取指定GPIO外设的输出寄存器数据的某一位
    uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); GPIOX:指定GPIO外设,GPIOA ~ GPIOG 输出寄存器的16位数据 读取指定GPIO外设的16位输出寄存器数据
    void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); GPIOX:指定GPIO外设,GPIOA ~ GPIOG
    GPIO_Pin:指定引脚,0 ~ 15
    将指定GPIO外设的指定引脚置1
    void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); GPIOX:指定GPIO外设,GPIOA ~ GPIOG
    GPIO_Pin:指定引脚,0 ~ 15
    将指定GPIO外设的指定引脚置0
    void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); GPIOX:指定GPIO外设,GPIOA ~ GPIOG
    GPIO_Pin:指定引脚,0 ~ 15
    BitVal:要写入的数据,0/1
    将BitVal写入GPIO外设的指定引脚
    void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); GPIOX:指定GPIO外设,GPIOA ~ GPIOG
    PortVal:要写入的16位数据
    将PortVal写入GPIO外设

    我们通过操作GPIO输出寄存器输出高低电平,高低电平就可以控制LED的亮灭

    LED灯的介绍可参看【51单片机】点亮LED

    简单来说,引脚输出低电平——灯亮,输出高电平——灯灭

    接线图 如下:
    我们将LED接在A0端口

    要实现LED闪烁,需要周期控制LED亮灭,此处直接提供 Delay(延迟) 方法

    Delay.h

    #ifndef __DELAY_H
    #define __DELAY_H
    
    void Delay_us(uint32_t us);
    void Delay_ms(uint32_t ms);
    void Delay_s(uint32_t s);
    
    #endif
    
    

    Delay.c

    #include "stm32f10x.h"
    
    /**
      * @brief  微秒级延时
      * @param  xus 延时时长,范围:0~233015
      * @retval 无
      */
    void Delay_us(uint32_t xus)
    {
    	SysTick->LOAD = 72 * xus;				//设置定时器重装值
    	SysTick->VAL = 0x00;					//清空当前计数值
    	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
    	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
    	SysTick->CTRL = 0x00000004;				//关闭定时器
    }
    
    /**
      * @brief  毫秒级延时
      * @param  xms 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_ms(uint32_t xms)
    {
    	while(xms--)
    	{
    		Delay_us(1000);
    	}
    }
     
    /**
      * @brief  秒级延时
      * @param  xs 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_s(uint32_t xs)
    {
    	while(xs--)
    	{
    		Delay_ms(1000);
    	}
    } 
    

    周期控制LED亮灭就可以实现闪烁灯效果
    代码如下:

    /**
      * @brief		A0端口LED闪烁,以一定频率
      * @parm		无
      * @retval		无
      */
    void FlashingLED()
    {
    	//使能时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	//初始化GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;			//A0引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//50MHz速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	while(1)
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_0);	//低电平,灯亮
    		Delay_ms(500);
    		GPIO_SetBits(GPIOA, GPIO_Pin_0);	//高电平,灯灭
    		Delay_ms(500);
    	}
    }
    

    流水灯

    接线图如下:

    流水灯是在闪烁灯的基础上,周期控制不同灯亮灭

    代码如下:

    /**
      * @brief		A0 ~ A7端口流水灯
      * @parm		无
      * @retval		无
      */
    void WaterfallLED()
    {
    	//使能时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	//初始化GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;			//A0 ~ A15
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//50MHz速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	while(1)
    	{
    		unsigned int LED = 0x0001;
    		for(int i = 0; i < 8; ++i)
    		{
    			GPIO_Write(GPIOA, ~LED);
    			LED <<= 1;
    			Delay_ms(300);
    		}
    	}
    }
    

    引脚输出控制蜂鸣器

    蜂鸣器的介绍可参看【51单片机】蜂鸣器演奏天空之城

    STM32的蜂鸣器是有源蜂鸣器,不需要我们控制振荡频率,所以发声频率一定。低电平发声,高电平不发声

    接线图如下:

    因为蜂鸣器也是低电平导通,本质和LED相同,区别就是我们将I/O口接在了GPIOB的B12引脚
    代码如下:

    /**
      * @brief		B12控制蜂鸣器
      * @parm		无
      * @retval		无
      */
    void Buzzer()
    {
    	//使能时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	//初始化GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;			//B12
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//50MHz速度
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	while(1)
    	{		
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);	//低电平,蜂鸣器响
    		Delay_ms(100);
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);	//高电平,蜂鸣器不响
    		Delay_ms(100);
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);	//低电平,蜂鸣器响
    		Delay_ms(100);
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);	//低电平,蜂鸣器响
    		Delay_ms(700);
    	}
    }
    

    按键控制LED

    接线图如下

    按键

    详细的介绍可参看【51单片机】独立按键

    按键:是常见的输入设备,按下导通,松手断开

    按键抖动:由于按键内部使用的是机械式弹簧片进行通断,所以在按下和松手的瞬间会伴随一连串的抖动

    规避按键抖动的方法之一就是,检测到按键按下后,延迟10 ~ 20ms,跳过抖动。松开后同样要延迟

    因为按键是输入设备,所以配置GPIO时,需要配置为上拉输入或下拉输入

    Key.c

    /**
      * @brief		初始化GPIOB部分引脚服务按键
      * @parm		GPIO_Pin:要配置的引脚
      * @retval		无
      */
    void Key_Init(uint16_t GPIO_Pin)
    {
    	//时钟使能
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	//配置GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//上拉输入
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin;				//配置引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//I/O口翻转速度
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /**
      * @brief		获取按键按下,若两个按键先后相近按下,会返回编号靠后的按键
      * @parm		无
      * @retval		按键按下,根据引脚编号 范围:1 ~ 16
      */
    uint8_t Key(void)
    {
    	uint8_t Key = 0;
    	
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
    	{
    		Delay_ms(20);									//过滤抖动
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0);	//等待按键松开
    		Delay_ms(20);
    		Key = 1;
    	}
    	
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0)
    	{
    		Delay_ms(20);									//过滤抖动
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0);	//等待按键松开
    		Delay_ms(20);
    		Key = 11;
    	}
    	return Key;
    }
    

    主程序逻辑就是循环检测按键按下,检测有按键按下后,根据按键控制LED

    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "LED.h"
    #include "Key.h"
    
    int main()
    {
    	LED_Init(GPIO_Pin_0 | GPIO_Pin_1);
    	Key_Init(GPIO_Pin_0 | GPIO_Pin_10);
    	while(1)
    	{
    		uint8_t KeyNum = Key();
    		if(KeyNum == 1)
    			LED_Tun(GPIO_Pin_0);
    		if(KeyNum == 11)
    			LED_Tun(GPIO_Pin_1);
    	}
    }
    

    完整项目链接:【STM32】按键控制LED

    光敏电阻控制蜂鸣器

    光敏电阻用于传感器模块

    传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随着外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出

    硬件电路如下:

    我们逐步分析

    首先,N1为光敏电阻/热敏电阻,与R1进行串联分压。

    一旁的C2电容,一端接电路,一端接GND,起到了滤波电容的作用,过滤一些不稳定的电压,使得电压更加平滑

    如此,AO 和 IN+ 随着 N1 阻值的改变就会获得不同的电压。

    利用上下拉电阻分析
    N1 阻值变小,下拉能力增强AO 电压变小。当 N1 无限小时,相当于AO直接接在GND,输出低电平
    N1 阻值变大,下拉能力减弱,AO 被 R1 的上拉拉高,AO 电压变大。当 N1 无限大时,相当于断路,AO 与 VCC 相连,输出高电平

    左侧的 IN+ 接在电压比较器,用于获得数字电压;AO 接在 外部引脚,用于输出模拟电压


    电压比较器

    右侧的 R2 为可调电阻,用于与 IN+ 输入的电压进行比较

    左侧的两个电压比较器构成运算放大器,当 IN+ 电压高于 IN-,输出高电平;当 IN+ 电压低于 IN-,输出低电平。如此就实现了二值化,获得了数字电压
    DO输入接在DO引脚


    LED1 为电源指示灯,通电即亮

    D0引脚用于输入,获取数字电压。AO用于模拟电压输出

    LED2为数字电压指示灯,当DO输入为0点亮,输入为1熄灭

    R5为上拉电阻,DO默认输入高电平

    到此传感器模块就介绍完了


    接线图如下:

    根据光敏电阻传感器控制蜂鸣器
    当光亮减弱,光敏电阻阻值变大,输出高电平,反之输出低电平

    我们设置光亮时蜂鸣器不响,昏暗时蜂鸣器响

    Buzzer.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * @brief		初始化蜂鸣器相关引脚
      * @parm		无
      * @retval		无
      */
    void Buzzer_Init(void)
    {
    	//使能时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	//初始化GPIOB相关引脚
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	//初始化默认高电平
    	GPIO_SetBits(GPIOB, GPIO_Pin_12);
    }
    /**
      * @brief		蜂鸣器发声
      * @parm		无
      * @retval		无
      */
    void Buzzer_On(void)
    {
    	GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    }
    /**
      * @brief		蜂鸣器不发声
      * @parm		无
      * @retval		无
      */
    void Buzzer_Off(void)
    {
    	GPIO_SetBits(GPIOB, GPIO_Pin_12);
    }
    
    /**
      * @brief		蜂鸣器发声切换
      * @parm		无
      * @retval		无
      */
    void Buzzer_Tun(void)
    {
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0)
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);
    	else
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    }
    

    LightSensor.c

    #include "stm32f10x.h"                  // Device header
    
    
    /**
    * @brief		光敏电阻初始化,配置引脚为GPIOB_13
      * @parm		无
      * @retval		无
      */
    void LightSensor_Init(void)
    {
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	//配置GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//上拉输入
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;			//配置引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//I/O口翻转速度
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /**
      * @brief		获取当前光敏传感器输出的高低电平
      * @parm		无
      * @retval		光敏传感器输出的高低电平,范围:0/1
      */
    uint8_t LightSensor_Get(void)
    {
    	return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
    }
    

    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Buzzer.h"
    #include "LightSensor.h"
    
    int main()
    {
    	Buzzer_Init();
    	LightSensor_Init();
    	uint8_t Light;
    	while(1)
    	{
    		Light = LightSensor_Get();
    		if(Light == 1)
    			Buzzer_On();
    		else
    			Buzzer_Off();
    	}
    }
    

    完整项目链接:【STM32】光敏电阻控制蜂鸣器


    以上就是本篇博客的所有内容,感谢你的阅读
    如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。

    作者:好想有猫猫

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32】GPIO(超详细)

    发表回复