当前位置: 首页 > news >正文

GPIO相关介绍

文章目录

  • GPIO概念
  • TXD与RXD
  • STM32IO口哪些兼容5V
  • GPIO的使用注意
  • GPIO地址
  • GPIO的八种工作模式
    • 浮空输入
    • 带上拉输入
    • 带下拉输入
    • 模拟输入
    • 开漏输出
    • 推挽输出
    • 复用功能的推挽输出
    • 复用功能的开漏输出
  • STM32F103的GPIO的寄存器
    • 寄存器总览
    • CRL和CRH寄存器
    • IDE寄存器
    • ODR寄存器
    • BSRR寄存器
    • BRR寄存器
    • LCKR寄存器
    • 端口低配置寄存器 CRL
  • GPIO某一位赋值与清零
  • GPIO相关库函数
    • GPIO_SetBits/ResetBits
  • GPIO初始化配置
    • 使能IO口时钟
      • F103时钟使能
      • F407时钟使能
    • GPIO初始化
      • F103GPIO初始化
        • GPIO_Pin
        • GPIO_Mode
        • GPIO_Speed
      • F407GPIO初始化
        • GPIO_Mode
        • GPIO_Speed
        • GPIO_OType
        • GPIO_PuPd
    • 解析F103的GPIO_Init()
  • 串口的端口复用
    • STM32F103串口端口复用
    • STM32F407端口复用
      • 复用外设选择
  • F407的GPIO寄存器介绍
  • 默认不能做输出的GPIO

GPIO概念

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

在Stm32F103ZET6中,有7组IO,每组16个。GPIOA到GPIOG,GPIOA0到GPIOA15.共112个。
每个IO口还有复用功能。

TXD与RXD

Transmit external data 发送端口 PA9
Receive external data 接收端口 PA10

STM32IO口哪些兼容5V

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

GPIO的使用注意

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

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;

然后定位到:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
可以看出,GPIOA 是将 GPIOA_BASE 强制转换为 GPIO_TypeDef 指针,
GPIOA 指向地址 GPIOA_BASE,GPIOA_BASE 存放的数据类型为 GPIO_TypeDef。
查看 GPIOA_BASE的宏定义:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
依次类推,可以找到最顶层:
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
所以我们便可以算出 GPIOA 的基地址位:
GPIOA_BASE= 0x40000000+0x10000+0x0800=0x40010800
GPIOA 的各个寄存器对于 GPIOA 基地址的偏移地址,所以我们自然可以算出来每个寄存器的地址。
GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相对 GPIOA 基地址的偏移值
那就是结构体存储的成员他们的地址是连续的
在这里插入图片描述

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)。

STM32F103的GPIO的寄存器

寄存器总览

在这里插入图片描述

IO 口寄存器必须要按 32 位字被访问。
STM32 的每个 IO 端口都有 7 个寄存器来控制。

CRL和CRH寄存器

配置模式的 2 个 32 位的端口配置寄存器 CRL 和 CRH;
端口低配置寄存器 CRL
CRL 和 CRH 控制着每个 IO 口的模式及输出速率。
2 个 32 位的数据寄存器 IDR 和 ODR;

IDE寄存器

IDR 是一个端口输入数据寄存器,只用了低 16 位。
该寄存器为只读寄存器,并且只能以
16 位的形式读出。
在这里插入图片描述

要想知道某个 IO 口的电平状态,你只要读这个寄存器,再看某个位的状态就可以了。
在固件库中操作 IDR 寄存器读取 IO 端口数据是通过 GPIO_ReadInputDataBit 函数实现的:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
比如我要读 GPIOA.5 的电平状态,那么方法是:
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);
返回值是 1(Bit_SET)或者 0(Bit_RESET);

ODR寄存器

ODR 是一个端口输出数据寄存器,也只用了低 16 位。
该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前 IO 口的输出状态。
向该寄存器写数据,则可以控制某个 IO 口的输出电平。
在这里插入图片描述

在固件库中设置 ODR 寄存器的值来控制 IO 口的输出状态是通过函数 GPIO_Write 来实现的:
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
该函数一般用来往一次性一个 GPIO 的多个端口设值。

BSRR寄存器

1 个 32 位的置位/复位寄存器BSRR;
BSRR 寄存器是端口位设置/清除寄存器。该寄存器和 ODR 寄存器具有类似的作用,都可以用来设置 GPIO 端口的输出位是 1 还是 0。
在这里插入图片描述

如你要设置 GPIOA 的第 1 个端口值为 1,那么你只需要往寄存器 BSRR 的低 16 位对应位写 1 即可:
GPIOA->BSRR=1<<1;
如果你要设置 GPIOA 的第 1 个端口值为 0,你只需要往寄存器高 16 位对应为写 1 即可:
GPIOA->BSRR=1<<(16+1)//清除第十七位的内容。
该寄存器往相应位写 0 是无影响的,所以我们要设置某些位,我们不用管其他位的值。
在 STM32 固件库中,通过 BSRR 和 BRR 寄存器设置 GPIO 端口输出是通过函数GPIO_SetBits()和函数 GPIO_ResetBits()来完成的
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
比如我们要设
置 GPIOB.5 输出 1,那么方法为:
GPIO_SetBits(GPIOB, GPIO_Pin_5);
反之如果要设置 GPIOB.5 输出位 0,方法为:
GPIO_ResetBits (GPIOB, GPIO_Pin_5);
也可以通过直接操作寄存器
BRR 和 BSRR 的方式来操作 IO 口输出高低电平,方法如下:
GPIOB->BRR=GPIO_Pin_5; //设置 GPIOB.5 输出 1,等同 LED0=1;
GPIOB->BSRR=GPIO_Pin_5; //设置 GPIOB.5 输出 0,等同 LED0=0;

BRR寄存器

Port bit reset register(GPIOx_BRR)32位寄存器,高16位保留,重置。
BRR 寄存器是端口位清除寄存器。该寄存器的作用跟 BSRR 的高 16 位雷同。

LCKR寄存器

1 个 32 位的锁存寄存器 LCKR。

端口低配置寄存器 CRL

GPIOA这一组,有GPIOA0~GPIOA15一共16个IO口。每一个IO口需要寄存器的4位用来配置工作模式,两位配置位,两位工作模式位。
那么一组GPIO就需要16x4=64位的寄存器来存放这一组GPIO的工作模式的配置,但STM32的寄存器都是32位的,所以只能使用2个32位的寄存器来存放了。CRL用来存放低八位的IO口(GPIOx0—GPIOx7)的配置,CRH用来存放高八位的IO口(GPIOx8—GPIOx15)的配置。
在这里插入图片描述
在这里插入图片描述
STM32 的 CRL 控制着每组 IO 端口(A~G)的低 8 位的模式。每个 IO 端口的位占用 CRL 的 4 个位,高两位为 CNF,低两位为 MODE。

CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8位输出口。

GPIO某一位赋值与清零

可以先对寄存器的值进行&清零操作
GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0
然后再与需要设置的值进行|或运算
GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值

将 BSRR 寄存器的第 pinpos 位设置为 1,1左移了pinpos位
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
可以提高代码的可读性以及可重用性。
类似这样的代码很多:
GPIOA->ODR|=1<<5; //PA.5 输出高,不改变其他位

GPIO相关库函数

GPIO_SetBits/ResetBits

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

IO作为输入使用
通过调用函数 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&&(KEY00||KEY10||KEY20||KEY31))
{
delay_ms(10); //去抖动
key_up=0;
if(KEY00)return KEY0_PRES;
else if(KEY1
0)return KEY1_PRES;
else if(KEY20)return KEY2_PRES;
else if(KEY3
1)return WKUP_PRES;
}else if(KEY01&&KEY11&&KEY21&&KEY30)key_up=1;
return 0; // 无按键按下
}
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

主函数的代码

#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);
}
}

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

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

GPIO初始化配置

使能IO口时钟

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

F103时钟使能

在 stm32f10x.h 文件里面我们可以看到如下的宏定义:

#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB1Periph_TIM2 ((uint32_t)0x00000001)
#define RCC_APB1Periph_TIM3 ((uint32_t)0x00000002)
#define RCC_APB1Periph_TIM4 ((uint32_t)0x00000004)
#define RCC_AHBPeriph_DMA1 ((uint32_t)0x00000001)
#define RCC_AHBPeriph_DMA2 ((uint32_t)0x00000002)

可以很明显的看出

  • GPIOA~GPIOC 是挂载在 APB2 下面,在使能 GPIO 的时候调用的是 RCC_APB2PeriphResetCmd()函数使能。
  • TIM2~TIM4 是挂载在 APB1下 面
  • DMA 是 挂 载 在 AHB 下 面 。 所 以 在 使 能 DMA 的 时 候 记 住 要 调 用 的 是RCC_AHBPeriphClock()函数使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能 GPIOB,GPIOE 端口时钟

这行代码的作用是使能 APB2 总线上的 GPIOB 和 GPIOE 的时钟。

F407时钟使能

F407的挂载和F103是有差别的。 可以先参考407系统上挂载的图。
官方库提供了五个打开 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);

F407中GPIO是挂载在AHB下的。

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

GPIO初始化

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

F103GPIO初始化

  • 在固件库中,GPIO 端口操作对应的库函数函数以及相关定义在文件stm32f10x_gpio.h 和 stm32f10x_gpio.c 中。
  • 操作寄存器 CRH 和 CRL 来配置 IO 口的模式和速度是通过 GPIO 初始化函数完成:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。
通过初始化结构体初始化 GPIO 的常用格式是:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数配置 GPIO

上面代码的意思是设置 GPIOB 的第 5 个端口为推挽输出模式,同时速度为 50M。

GPIO_Pin

第一个成员变量 GPIO_Pin 用来设置是要初始化哪个或者哪些 IO 口;

GPIO_Mode

GPIO_Mode 是用来设置对应 IO 端口的输出输入模式,
这些模式是上面我们讲解的 8 个模式,在 MDK 中是通过一个枚举类型定义的:

typedef enum
{ GPIO_Mode_AIN = 0x0, //模拟输入
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
GPIO_Mode_IPD = 0x28, //下拉输入
GPIO_Mode_IPU = 0x48, //上拉输入
GPIO_Mode_Out_OD = 0x14, //开漏输出
GPIO_Mode_Out_PP = 0x10, //通用推挽输出
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
GPIO_Mode_AF_PP = 0x18 //复用推挽
}GPIOMode_TypeDef;

GPIO_Speed

第三个参数是 IO 口速度设置,有三个可选值,在 MDK 中同样是通过枚举类型定义:

typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

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;
初始化代码:

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

GPIO_Mode

F407的GPIO_Mode与F103是有区别的,具体如下

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;

GPIO_Speed

F407的GPIO_Speed与F103也是有区别的,具体如下

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

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

GPIO_OType

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

 typedef enum
{
GPIO_OType_PP = 0x00,
GPIO_OType_OD = 0x01
}GPIOOType_TypeDef;

输出推挽模式,那么选择值 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;
  • GPIO_PuPd_NOPULL 为不使用上下拉
  • GPIO_PuPd_UP 为上拉,
  • GPIO_PuPd_DOWN 为下拉

解析F103的GPIO_Init()

GPIO_Init()函数的实现处,同样,双击 GPIO_Init,右键点击“Go to definition of …”

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
……
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
……
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
……
}

assert_param 函数式对入口参数的有效性进行判断,所以我们可以从这个函数入手,确定我们的入口参数的范围。
第一行是对第一个参数 GPIOx 进行有效性判断,双击“IS_GPIO_ALL_PERIPH”右键点击“go to defition of…” 定位到了下面的定义:

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
((PERIPH) == GPIOB) || \
((PERIPH) == GPIOC) || \
((PERIPH) == GPIOD) || \
((PERIPH) == GPIOE) || \
((PERIPH) == GPIOF) || \
((PERIPH) == GPIOG))

很明显可以看出,GPIOx 的取值规定只允许是 GPIOA~GPIOG。
同样的办法,我们双击“IS_GPIO_MODE” 右键点击“go to defition of…”,定位到下面的定义:

typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || \
((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || \
((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || \
((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || \
((MODE) == GPIO_Mode_AF_PP))

所以 GPIO_InitStruct->GPIO_Mode 成员的取值范围只能是上面定义的 8 种。这 8 中模式是通过一个枚举类型组织在一起的。
同样的方法可以找出 GPIO_Speed 的参数限制:

typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_Speed_10MHz) || \
((SPEED) == GPIO_Speed_2MHz) || \
((SPEED) == GPIO_Speed_50MHz))

在这里插入图片描述

同样的方法我们双击“IS_GPIO_PIN” 右键点击“go to defition of…”,定位到下面的定义:

#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) !=(uint16_t)0x00))

可以看出,GPIO_Pin 成员变量的取值范围为 0x0000 到 0xffff

MDK 会将这些数字的意思通过宏定义定义出来,,看到在 IS_GPIO_PIN(PIN)宏定义的上面还有数行宏定义:

#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
……
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) !=
(uint16_t)0x00))

这些宏定义 GPIO_Pin_0~GPIO_Pin_ All 就是 MDK 事先定义好的于是我们可以组织起来下面的代码:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
初始化多个 IO 口的方式可以是如下:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6| GPIO_Pin_7; //指定端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //端口模式:推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化

串口的端口复用

英文:usart
MCU 都有串口,STM32 有好几个串口。比如说 STM32F103ZET6 有 5 个串口,我们可以查手册知道,串口 1 的引脚对应的 IO 为 PA9,PA10.PA9,PA10 默认功能是 GPIO,所以当PA9,PA10 引脚作为串口 1 的 TX,RX 引脚使用的时候,那就是端口复用。

STM32F103串口端口复用

先使能IO端口时钟,再使能外设,也就是串口时钟,然后配置端口模式
1) GPIO 端口时钟使能。
要使用到端口复用,当然要使能端口的时钟了。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
2) 复用的外设时钟使能。
比如你要将端口 PA9,PA10 复用为串口,所以要使能串口时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
3) 端口模式配置。
在 IO 复用位内置外设功能引脚的时候,必须设置 GPIO 端口的复用模式。

这里我们拿 Usart1 举例
在这里插入图片描述

我们要配置全双工的串口 1,那么 TX 管脚需要配置为推挽复用输出,RX 管脚配置为浮空输入或者带上拉输入。

//USART1_TX PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

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 寄存器,将 IO 连接到所需的 AFx

/*PA9 连接 AF7,复用为 USART1_TX */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
/* PA10 连接 AF7,复用为 USART1_RX*/
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);

复用外设选择

#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))

F407的GPIO寄存器介绍

F407的每个通用 I/O 端口包括 4 个 32 位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)、2 个 32 位数据寄存器(GPIOx_IDR 和GPIOx_ODR)、1 个 32 位置位/复位寄存器 (GPIOx_BSRR)、1 个 32 位锁定寄存器(GPIOx_LCKR) 和 2 个 32 位复用功能选择寄存器(GPIOx_AFRH 和 GPIOx_AFRL) ,共10个寄存器,与F103大不相同。

默认不能做输出的GPIO

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

在这里插入图片描述

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

相关文章:

  • 软件工程、软件生命周期、软件定义阶段、需求的层次/特征、概要设计、详细设计
  • 台式机电源更换笔记
  • 从文件资源管理器中隐藏文件
  • # Maven错误Error executing Maven
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (2020)Java后端开发----(面试题和笔试题)
  • lt;JVM调优gt;为什么内存过大?
  • InputMismatchException异常
  • 定时器及案例
  • 代谢组学研究的十大误区——误区十
  • 微服务项目:尚融宝(8)(后端接口:积分等级CRUD)
  • 《用Go语言自制解释器》之第4章 扩展解释器
  • SQL每日一练(牛客新题库)——第9天:检索数据
  • JQuery系列之事件切换
  • 【第5天】SQL快速入门-必会的常用函数(SQL 小虚竹)
  • “大数据应用场景”之隔壁老王(连载四)
  • 【技术性】Search知识
  • AngularJS指令开发(1)——参数详解
  • CSS居中完全指南——构建CSS居中决策树
  • Elasticsearch 参考指南(升级前重新索引)
  • Idea+maven+scala构建包并在spark on yarn 运行
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • js继承的实现方法
  • js中的正则表达式入门
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • Vue 重置组件到初始状态
  • 番外篇1:在Windows环境下安装JDK
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 解析 Webpack中import、require、按需加载的执行过程
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 如何合理的规划jvm性能调优
  • 使用API自动生成工具优化前端工作流
  • 学习Vue.js的五个小例子
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • Prometheus VS InfluxDB
  • python最赚钱的4个方向,你最心动的是哪个?
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • #数学建模# 线性规划问题的Matlab求解
  • (13):Silverlight 2 数据与通信之WebRequest
  • (3)llvm ir转换过程
  • (C)一些题4
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (多级缓存)多级缓存
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (转)h264中avc和flv数据的解析
  • (转)IOS中获取各种文件的目录路径的方法
  • (转载)PyTorch代码规范最佳实践和样式指南
  • ./和../以及/和~之间的区别
  • .NET Standard 的管理策略
  • .NET 常见的偏门问题
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .net程序集学习心得
  • .NET命令行(CLI)常用命令