• GPIO相关介绍


    GPIO概念

    General-purpose input/output通用型输入输出接口,类似于51的p0-p3
    GPI 通用输入
    GPO 通用输出,通过寄存器来控制其输入输出的电平。

    在Stm32F407中,STM32F407ZGT6

    • 一共有7组IO口,GPIOA到GPIOG。
    • 每组IO口有16个IO
    • 一共16X7=112个IO
    • 外加2个PH0和PH1

    一共114个IO口

    GPIO的使用注意

    STM32IO口哪些兼容5V

    凡是数据手册中引脚描述,标有FT的标志就是兼容5V

    一定不要接超过5V的电压

    不要直接用IO口驱动感性负载(电机,继电器等),因为断开瞬间会产生很大的反电动势,将IO烧坏。(可以接一个泄放二极管)

    默认不能做输出的GPIO

    默认的五个口不能作为IO口输出

    在这里插入图片描述

    只有禁止了相应的端口,才能释放IO引脚

    GPIO硬件

    原理图

    在这里插入图片描述
    注释:I/O引脚:接收外部发出的信号或者向外部发出信号。

    GPIO地址

    在这里插入图片描述
    GPIOA 的 7 个寄存器都是 32 位的,所以每个寄存器占有 4个地址,一共占用 28 个地址,地址偏移范围为(000h~01Bh)。
    因为 GPIO 都是挂载在 APB2 总线之上,所以它的基地址是由 APB2 总线的基地址+GPIOA 在 APB2 总线上的偏移地址决定的。
    打开 stm32f10x.h 定位到 GPIO_TypeDef 定义处:

    typedef struct
    {
    __IO uint32_t CRL;
    __IO uint32_t CRH;
    __IO uint32_t IDR;
    __IO uint32_t ODR;
    __IO uint32_t BSRR;
    __IO uint32_t BRR;
    __IO uint32_t LCKR;
    } GPIO_TypeDef;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后定位到:

    #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
    
    • 1

    可以看出,GPIOA 是将 GPIOA_BASE 强制转换为 GPIO_TypeDef 指针,
    GPIOA 指向地址 GPIOA_BASE,GPIOA_BASE 存放的数据类型为 GPIO_TypeDef。
    查看 GPIOA_BASE的宏定义:

    #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
    
    • 1

    依次类推,可以找到最顶层:

    #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
    #define PERIPH_BASE ((uint32_t)0x40000000)
    
    • 1
    • 2

    所以我们便可以算出 GPIOA 的基地址位:

    GPIOA_BASE= 0x40000000+0x10000+0x0800=0x40010800
    
    • 1

    GPIOA 的各个寄存器对于 GPIOA 基地址的偏移地址,所以我们自然可以算出来每个寄存器的地址。
    GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相对 GPIOA 基地址的偏移值
    那就是结构体存储的成员他们的地址是连续的
    在这里插入图片描述

    GPIO的八种工作模式

    GPIO的八种工作模式详解

    浮空输入

    浮空输入GPIO_IN_FLOATING,可以做KEY识别,RX1。

    带上拉输入

    GPIO_IPU——IO内部上拉电阻输入。

    带下拉输入

    GPIO_IPD—— IO内部下拉电阻输入。

    模拟输入

    GPIO_AIN ——应用ADC模拟输入,或者低功耗下省电。

    开漏输出

    输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
    在这里插入图片描述

    GPIO_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能。

    开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。

    • 开漏输出的这一特性一个明显的优势就是可以很方便的调节输出的电平,因为输出电平完全由上拉电阻连接的电源电平决定。所以在需要进行电平转换的地方,非常适合使用开漏输出。
    • 开漏输出的这一特性另一个好处在于可以实现"线与"功能,所谓的"线与"指的是多个信号线直接连接在一起,只有当所有信号全部为高电平时,合在一起的总线为高电平;只要有任意一个或者多个信号为低电平,则总线为低电平。而推挽输出就不行,如果高电平和低电平连在一起,会出现电流倒灌,损坏器件。所以总线一般会使用开漏输出.
      在这里插入图片描述

    推挽输出

    在这里插入图片描述

    GPIO_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的。
    推挽输出结构是由两个MOS或者三极管受到互补控制的信号控制,两个管子时钟一个在导通,一个在截止。在这里插入图片描述
    推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。

    • 所谓的驱动能力,就是指输出电流的能力。对于驱动大负载(即负载内阻越小,负载越大)时,例如IO输出为5V,驱动的负载内阻为10ohm,于是根据欧姆定律可以正常情况下负载上的电流为0.5A(推算出功率为2.5W)。一般的IO不可能输出这么大的电流。于是造成的结果就是输出电压会被拉下来,达不到标称的5V。
    • 推挽输出高低电平的电流都能达到几十mA。

    推挽输出的缺点是,如果当两个推挽输出结构相连在一起,一个输出高电平,另一个输出低电平,电流会从第一个引脚的VCC通过上端MOS再经过第二个引脚的下端MOS直接流向GND,也就是会发生短路,进而可能造成端口的损害。这也是为什么推挽输出不能实现" 线与"的原因。

    • 推挽输出在输出的时候是通过单片机内部的电压,所以他的电压是不能改变的。
    • 一般情况下,使用推挽输出。

    复用功能的推挽输出

    GPIO_AF_PP ——片内外设功能(I2C的SCL,SDA)。

    复用功能的开漏输出

    GPIO_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)。

    STM32F407的GPIO寄存器

    10个寄存器可以控制一组GPIO的16个IO口。

    一个端口模式寄存器(GPIOx_MODER)
    一个端口输出类型寄存器(GPIOx_OTYPER)
    一个端口输出速度寄存器(GPIOx_OSPEEDR)
    一个端口上拉下拉寄存器(GPIOx_PUPDR)
    一个端口输入数据寄存器(GPIOx_IDR)
    一个端口输出数据寄存器(GPIOx_ODR)
    一个端口置位/复位寄存器(GPIOx_BSRR)
    一个端口配置锁存寄存器(GPIOx_LCKR)
    两个复用功能寄存器(低位GPIOx_AFRL & GPIOx_AFRH)
    在这里插入图片描述

    GPIOx_MODER端口模式寄存器

    x = A…I
    在这里插入图片描述

    1-0 ModeRy 输入还是输出

    在这里插入图片描述

    GPIOx_OTYPER端口输出类型寄存器

    x = A…I
    中文参考手册7.4.2,187
    在这里插入图片描述

    15-0 OTy端口配置

    在这里插入图片描述

    GPIOx_OSPEEDR端口输出速度寄存器

    GPIO port output speed register
    在这里插入图片描述

    OSPEEDRY 输出速度

    在这里插入图片描述

    GPIO相关库函数

    GPIO某一位赋值与清零

    可以先对寄存器的值进行&清零操作

    GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0
    
    • 1

    然后再与需要设置的值进行|或运算

    GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值
    
    • 1

    将 BSRR 寄存器的第 pinpos 位设置为 1,1左移了pinpos位

    GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
    
    • 1

    可以提高代码的可读性以及可重用性。
    类似这样的代码很多:
    GPIOA->ODR|=1<<5; //PA.5 输出高,不改变其他位

    GPIO_SetBits/ResetBits

    实现功能:控制某个GPIO引脚的输出电平(拉高 / 拉低)

    GPIO_SetBits   拉高引脚输出电平
    GPIO_ResetBits 拉低引脚输出电平
    
    • 1
    • 2

    PBin() 和PBoutn()

    顾名思义

    #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
    #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
    
    • 1
    • 2

    GPIO_ReadInputDataBit()

    通过调用函数 GPIO_ReadInputDataBit()来读取 IO 口的状态的。

    实现通过4 个按钮(WK_UP、KEY0、KEY1 和 KEY2),来控制板上的 2 个 LED(DS0 和 DS1)和蜂鸣器
    其中 WK_UP 控制蜂鸣器,按一次叫,再按一次停;
    KEY2 控制 DS0,按一次亮,再按一次灭;
    KEY1 控制 DS1,效果同KEY2;
    KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
    按键与端口的对应关系见(按键)

    按键初始化

    #include "key.h"
    #include "sys.h"
    #include "delay.h"
    //按键初始化函数
    void KEY_Init(void) //IO 初始化
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
    RCC_APB2Periph_GPIOE,ENABLE); //使能 PORTA,PORTE 时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;//GPIOE.2~4
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
    GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化 GPIOE2,3,4
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //初始化 WK_UP-->GPIOA.0
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 设置成输入,下拉
    GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0
    }
    //按键处理函数
    //返回按键值
    //mode:0,不支持连续按;1,支持连续按;
    //0,没有任何按键按下;1,KEY0 按下;2,KEY1 按下;3,KEY2 按下 ;4,KEY3 按下 WK_UP
    //注意此函数有响应优先级,KEY0>KEY1>KEY2>KEY3!!
    u8 KEY_Scan(u8 mode)
    {
    static u8 key_up=1; //按键按松开标志
    if(mode)key_up=1; //支持连按
    if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==1))
    {
    delay_ms(10); //去抖动
    key_up=0;
    if(KEY0==0)return KEY0_PRES;
    else if(KEY1==0)return KEY1_PRES;
    else if(KEY2==0)return KEY2_PRES;
    else if(KEY3==1)return WKUP_PRES;
    }else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;
    return 0; // 无按键按下
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    KEY_Scan()函数,则是用来扫描这 4 个 IO 口是否有按键按下。
    KEY_Scan()函数,支持两种扫描方式,通过 mode 参数来设置。
    当 mode 为 0 的时候,KEY_Scan()函数将不支持连续按,扫描某个按键,该按键按下之后必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多次触发,而坏处就是在需要长按的时候比较不合适。
    当 mode 为 1 的时候,KEY_Scan()函数将支持连续按,如果某个按键一直按下,则会一直返回这个按键的键值,这样可以方便的实现长按检测。
    要注意的就是,该函数的按键扫描是有优先级的,最优先的是 KEY0,第二优先的是 KEY1,接着 KEY2,最后是 WK_UP 按键。
    该函数有返回值,如果有按键按下,则返回非 0 值,如果没有或者按键不正确,则返回 0。

    头文件 key.h 里面的代码:

    #ifndef __KEY_H
    #define __KEY_H
    #include "sys.h"
    #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)//读取按键 0
    #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//读取按键 1
    #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)//读取按键 2
    #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键 3(WK_UP)
    #define KEY0_PRES 1 //KEY0 按下
    #define KEY1_PRES 2 //KEY1 按下
    #define KEY2_PRES 3 //KEY2 按下
    #define WKUP_PRES 4 //WK_UP 按下(即 WK_UP/WK_UP)
    void KEY_Init(void); //IO 初始化
    u8 KEY_Scan(u8); //按键扫描函数
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    主函数的代码

    #include "led.h"
    #include "delay.h"
    #include "key.h"
    #include "sys.h"
    #include "beep.h"
    //ALIENTEK 战舰 STM32 开发板实验 3
    //按键输入实验
    int main(void)
    {
    u8 key;
    delay_init(); //延时函数初始化
    LED_Init(); //LED 端口初始化
    KEY_Init(); //初始化与按键连接的硬件接口
    BEEP_Init(); //初始化蜂鸣器端口
    LED0=0; //先点亮红灯
    while(1)
    {
    key =KEY_Scan(0); //得到键值
    if(key)
    { switch(t)
    { case WKUP_PRES: //控制蜂鸣器
    	BEEP=!BEEP;break;
    	case KEY2_PRES: //控制 LED0 翻转
    	LED0=!LED0;break;
    	case KEY1_PRES: //控制 LED1 翻转
    	LED1=!LED1;break;
    	case KEY0_PRES: //同时控制 LED0,LED1 翻转
    	LED0=!LED0;
    	LED1=!LED1;break;
    }
    }else delay_ms(10);
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在仿真调试的时候MDK 不会考虑 STM32 自带的上拉和下拉,所以我们得自己手动设置一下,来使得其初始状态和外部硬件的状态一摸一样。
    在 General Purpose I/O E 窗口内的 Pins 里面勾选 2、3、4 位,

    要改变状态就把 Pins 的 PE2 取消勾选,再次执行过这句,得到 key 的值为 3

    GPIO初始化配置

    使能IO口时钟

    这里需要注意的是:在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟。

    F407时钟使能

    F407的挂载和F103是有差别的。 可以先参考407时钟的RCC相关函数
    官方库提供了五个打开 GPIO 和外设时钟的函数分别为:

    void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
    void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
    void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
    void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
    void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    F407中GPIO是挂载在AHB下的。

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能 GPIOA 时钟
    
    • 1

    GPIO初始化

    GPIO的每个 IO 口可以自由编程,但 IO 口寄存器必须要按 32 位字被访问。

    F407GPIO初始化

    typedef struct
    {
    uint32_t GPIO_Pin;
    GPIOMode_TypeDef GPIO_Mode;
    GPIOSpeed_TypeDef GPIO_Speed;
    GPIOOType_TypeDef GPIO_OType;
    GPIOPuPd_TypeDef GPIO_PuPd;
    }GPIO_InitTypeDef; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    初始化代码:

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9//GPIOF9
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
    GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化 GPIO
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    GPIO_Mode

    一般外设使用时都用的是复用模式。

    typedef enum
    {
    	GPIO_Mode_IN = 0x00, /* 复位状态的输入!< GPIO Input Mode */
    	GPIO_Mode_OUT = 0x01, /*通用输出模式!< GPIO Output Mode */
    	GPIO_Mode_AF = 0x02, /*复用功能模式!< GPIO Alternate function Mode */
    	GPIO_Mode_AN = 0x03 /*模拟输入模式!< GPIO Analog Mode */
    }GPIOMode_TypeDef;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    GPIO_Speed

    F407的GPIO_Speed与F103也是有区别的,一般使用50M就可以。

    typedef enum
    {
    GPIO_Low_Speed = 0x00, /*!< Low speed */
    GPIO_Medium_Speed = 0x01, /*!< Medium speed */
    GPIO_Fast_Speed = 0x02, /*!< Fast speed */
    GPIO_High_Speed = 0x03 /*!< High speed */
    }GPIOSpeed_TypeDef;
    /* Add legacy definition */
    #define GPIO_Speed_2MHz GPIO_Low_Speed
    #define GPIO_Speed_25MHz GPIO_Medium_Speed
    #define GPIO_Speed_50MHz GPIO_Fast_Speed
    #define GPIO_Speed_100MHz GPIO_High_Speed
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    有四个可选值。实际上这就是配置的 GPIO对应的 OSPEEDR 寄存器的值。

    GPIO_OType

    GPIO_OType是F407新增的,GPIO 的输出类型设置,实际上是配置的 GPIO 的 OTYPER 寄存器的值。

     typedef enum
    {
    	GPIO_OType_PP = 0x00,
    	GPIO_OType_OD = 0x01
    }GPIOOType_TypeDef;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出推挽模式,那么选择值 GPIO_OType_PP。
    输出开漏模式,那么设置值为 GPIO_OType_OD。

    GPIO_PuPd

    F407新增,设置 IO 口的上下拉,实际上就是设置 GPIO 的 PUPDR 寄存器的值。

    typedef enum
    {
    	GPIO_PuPd_NOPULL = 0x00,
    	GPIO_PuPd_UP = 0x01,
    	GPIO_PuPd_DOWN = 0x02
    }GPIOPuPd_TypeDef;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • GPIO_PuPd_NOPULL 为不使用上下拉
    • GPIO_PuPd_UP 为上拉,
    • GPIO_PuPd_DOWN 为下拉。所谓的下拉就是空闲的时候是低电平。

    端口复用

    每个GPIO口有很多功能,默认的是作为普通IO口,此时不配置复用相关的东西。

    • 当GPIO作为其他功能时,这个时候就要配置复用相关代码了

    复用:内置外设基本上与I/O口共用管脚的,也就是I/O管脚的复用。

    • 比如串口 1 的引脚对应的 IO 为 PA9,PA10.PA9,PA10 默认功能是 GPIO,所以当PA9,PA10 引脚作为串口 1 的 TX,RX 引脚使用的时候,那就是端口复用。

    内置外设:
    除单片机内核外,单片机内部可实现功能的设备都是内置外设,如串口,ADC 等。

    区别:

    • stm32f1的外设有默认引脚,当使用默认引脚时就不需要使用AFIO的重映射功能,如果需要重映射就使用。
    • stm32f4的所有外设没有默认引脚,当使用外设时必须要使用GPIO_PinAFConfig函数选择引脚进行端口复用。
    • stm32f1的外部中断与其他外设的端口复用都要用AFIO,而stm32f4的外部中断与其他外设分开了,外部中断使用SYSCFG进行操作,外设端口选择使用GPIO_PinAFConfig函数。
      • stm32f4的SYSCFG有独立的时钟,而stm32f4的AF没有独立时钟。
    • 当使用AD时,选择模拟模式而不是复用(特殊)。

    STM32F407端口复用

    复用器

    STM32F4 系列微控制器 IO 引脚通过一个复用器连接到内置外设或模块。

    • 该复用器一次只允许一个外设的复用功能(AF)连接到对应的 IO 口。这样可以确保共用同一个 IO 引脚的外设之间不会发生冲突。
    • 每个 IO 引脚(如GPIOA_Pin0)都有一个复用器,该复用器采用 16 路复用功能输入(AF0 到 AF15),可通过GPIOx_AFRL(针对引脚 0-7)和 GPIOx_AFRH(针对引脚 8-15)寄存器对这些输入进行配置,每四位控制一路复用:
      1)完成复位后,所有 IO 都会连接到系统的复用功能 0(AF0)。
      2)外设的复用功能映射到 AF1 到 AF13。
      3)Cortex-M4 EVENTOUT 映射到 AF15。
      复用器的图:
      在这里插入图片描述

    GPIOx_AFRL 寄存器位描述:

    在这里插入图片描述
    32 位寄存器 GPIOx_AFRL 每四个位控制一个 IO 口,所以每个寄存器控制32/4=8 个 IO 口。寄存器对应四位的值配置决定这个 IO 映射到哪个复用功能 AF。

    配置 GPIOx_AFRL 或者 GPIOx_AFRH 寄存器

    407端口复用

    同样的,串口1的发送接收引脚是PA9,PA10。当我们把PA9,PA10不用做GPIO,而用做复用功能串口1的发送接收引脚的时候,叫端口复用。

    • USART2_RX串口2除了PA2,PA3。PD6,PD5也有USART2_RX的功能。

    使用配置:

    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
     
    	//串口1对应引脚复用映射
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //*PA9 连接 AF7,复用为 USART1_TX */
    	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); ///* PA10 连接 AF7,复用为 USART1_RX*/
    	
    	//USART1端口配置
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    GPIO_PinAFConfig
    • 使用GPIO_PinAFConfig函数进行指定引脚的挂载。除ADC和DAC外的所有非GPIO功能,都要使用GPIO_PinAFConfig函数进行端口复用
      • 端口复用没有时钟,直接使用即可。
      • 作用就是将 IO 连接到所需的 AFx,AF代表的就是端口复用

    复用外设选择:

    #define IS_GPIO_AF(AF) (((AF) == GPIO_AF_RTC_50Hz) ||((AF) == GPIO_AF_TIM14) || \
    ((AF) == GPIO_AF_MCO) || ((AF) == GPIO_AF_TAMPER) || \
    ((AF) == GPIO_AF_SWJ) || ((AF) == GPIO_AF_TRACE) || \
    ((AF) == GPIO_AF_TIM1) || ((AF) == GPIO_AF_TIM2) || \
    ((AF) == GPIO_AF_TIM3) || ((AF) == GPIO_AF_TIM4) || \
    ((AF) == GPIO_AF_TIM5) || ((AF) == GPIO_AF_TIM8) || \
    ((AF) == GPIO_AF_I2C1) || ((AF) == GPIO_AF_I2C2) || \
    ((AF) == GPIO_AF_I2C3) || ((AF) == GPIO_AF_SPI1) || \
    ((AF) == GPIO_AF_SPI2) || ((AF) == GPIO_AF_TIM13) || \
    ((AF) == GPIO_AF_SPI3) || ((AF) == GPIO_AF_TIM14) || \
    ((AF) == GPIO_AF_USART1) || ((AF) == GPIO_AF_USART2) || \
    ((AF) == GPIO_AF_USART3) || ((AF) == GPIO_AF_UART4) || \
    ((AF) == GPIO_AF_UART5) || ((AF) == GPIO_AF_USART6) || \
    ((AF) == GPIO_AF_CAN1) || ((AF) == GPIO_AF_CAN2) || \
    ((AF) == GPIO_AF_OTG_FS) || ((AF) == GPIO_AF_OTG_HS) || \
    ((AF) == GPIO_AF_ETH) || ((AF) == GPIO_AF_OTG_HS_FS) || \
    ((AF) == GPIO_AF_SDIO) || ((AF) == GPIO_AF_DCMI) || \
    ((AF) == GPIO_AF_EVENTOUT) || ((AF) == GPIO_AF_FSMC))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    LED实验设置

    LED硬件连接图

    在这里插入图片描述
    当输出1的时候无电压差,灭。
    当输出0 的时候有压差,亮。

    应该设置推挽输出,上拉,默认就是灭。

    读取输入电平

    uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
    
    • 1
    • 2

    第一个作用:读取某个GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
    GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平

    第二个作用:读取某组GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
    GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平

    读取输出电平

     uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
    
    • 1
    • 2

    作用:读取某个GPIO的输出电平。实际操作的是GPIO_ODR寄存器。
    GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输出电平

    作用:读取某组GPIO的输出电平。实际操作的是GPIO_ODR寄存器。
    GPIO_ReadOutputData(GPIOA);//读取GPIOA组中所有io口输出电平

    设置输出电平

    void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    
    • 1

    作用:设置某个IO口输出为高电平(1)。实际操作BSRRL寄存器

    void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    
    • 1

    作用:设置某个IO口输出为低电平(0)。实际操作的BSRRH寄存器。

    void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
    void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
    
    • 1
    • 2

    这两个函数不常用,也是用来设置IO口输出电平。

    初始化IO代码

    void LED_Init(void)
    {    	 
      GPIO_InitTypeDef  GPIO_InitStructure;
    
      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
    
      //GPIOF9,F10初始化设置
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0和LED1对应IO口
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
      GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
    	
    	GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    主函数

    int main(void)
    { 
     
    	delay_init(168);		  //初始化延时函数
    	LED_Init();		        //初始化LED端口
    	
      /**下面是通过直接操作库函数的方式实现IO控制**/	
    	
    	while(1)
    	{
    	GPIO_ResetBits(GPIOF,GPIO_Pin_9);  //LED0对应引脚GPIOF.9拉低,亮  等同LED0=0;
    	GPIO_SetBits(GPIOF,GPIO_Pin_10);   //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1;
    	delay_ms(500);  		   //延时300ms
    	GPIO_SetBits(GPIOF,GPIO_Pin_9);	   //LED0对应引脚GPIOF.0拉高,灭  等同LED0=1;
    	GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0;
    	delay_ms(500);                     //延时300ms
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    学生如何利用假期提升个人能力?
    计算机毕业设计ssm人力资源管理系统0600t系统+程序+源码+lw+远程部署
    使用pytorch搭建MobileNetV2并基于迁移学习训练
    51单片机项目(9)——基于51单片机的电子琴设计
    调优过程中缓存的处理
    Redis面试题(三)
    stm32 - 中断/定时器
    微信小程序精准扶贫数据收集小程序平台设计与实现
    IB选课指南及热门专业选课建议
    【MySQL】深入理解MySQL索引原理(MySQL专栏启动)
  • 原文地址:https://blog.csdn.net/qq_45578181/article/details/126531198