文章目录
- GPIO概念
-
- GPIO的使用注意
- STM32IO口哪些兼容5V
- 一定不要接超过5V的电压
- 默认不能做输出的GPIO
- GPIO硬件
-
- GPIO的八种工作模式
- 浮空输入
- 带上拉输入
- 带下拉输入
- 模拟输入
- 开漏输出
- 推挽输出
- 复用功能的推挽输出
- 复用功能的开漏输出
- STM32F407的GPIO寄存器
- GPIOx_MODER端口模式寄存器
-
- GPIOx_OTYPER端口输出类型寄存器
-
- GPIOx_OSPEEDR端口输出速度寄存器
- OSPEEDRY 输出速度
- STM32F103的GPIO的寄存器
- 寄存器总览
- CRL和CRH寄存器
- IDR寄存器
- ODR寄存器
- BSRR寄存器
- BRR寄存器
- LCKR寄存器
- 端口低配置寄存器 CRL
- GPIO相关库函数
- GPIO某一位赋值与清零
- GPIO_SetBits/ResetBits
- PBin() 和PBoutn()
- GPIO_ReadInputDataBit()
- GPIO初始化配置
- 使能IO口时钟
-
- GPIO初始化
- F103GPIO初始化
- GPIO_Pin
- GPIO_Mode
- GPIO_Speed
- F407GPIO初始化
- GPIO_Mode
- GPIO_Speed
- GPIO_OType
- GPIO_PuPd
- 解析F103的GPIO_Init()
- 端口复用
- STM32F103端口复用
-
- STM32F407端口复用
- 复用器
- GPIOx_AFRL 寄存器位描述:
- 407端口复用
-
- LED实验设置
- LED硬件连接图
- 读取输入电平
- 读取输出电平
- 设置输出电平
- 初始化IO代码
- 主函数
GPIO概念
General-purpose input/output通用型输入输出接口,类似于51的p0-p3
GPI 通用输入
GPO 通用输出,通过寄存器来控制其输入输出的电平。
在Stm32F103ZET6中,有7组IO,每组16个。GPIOA到GPIOG,GPIOA0到GPIOA15.共112个。
每个IO口还有复用功能。
在Stm32F407中,STM32F407ZGT6
- 一共有7组IO口,GPIOA到GPIOG。
- 每组IO口有16个IO
- 一共16X7=112个IO
- 外加2个PH0和PH1
一共114个IO口
TXD与RXD
Transmit external data 发送端口 PA9
Receive external data 接收端口 PA10
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;
然后定位到:
#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)。
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 输出速度
STM32F103的GPIO的寄存器
寄存器总览
IO 口寄存器必须要按 32 位字被访问。
STM32 的每个 IO 端口都有 7 个寄存器来控制。
CRL和CRH寄存器
配置模式的 2 个 32 位的端口配置寄存器 CRL 和 CRH;
端口低配置寄存器 CRL
CRL 和 CRH 控制着每个 IO 口的模式及输出速率。
2 个 32 位的数据寄存器 IDR 和 ODR;
IDR寄存器
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相关库函数
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_SetBits/ResetBits
实现功能:控制某个GPIO引脚的输出电平(拉高 / 拉低)
GPIO_SetBits 拉高引脚输出电平
GPIO_ResetBits 拉低引脚输出电平
PBin() 和PBoutn()
顾名思义
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n)
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)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
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;
}
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)
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
#define KEY0_PRES 1
#define KEY1_PRES 2
#define KEY2_PRES 3
#define WKUP_PRES 4
void KEY_Init(void);
u8 KEY_Scan(u8);
#endif
主函数的代码
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "beep.h"
int main(void)
{
u8 key;
delay_init();
LED_Init();
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;break;
case KEY1_PRES:
LED1=!LED1;break;
case KEY0_PRES:
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);
这行代码的作用是使能 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);
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;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
上面代码的意思是设置 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
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_Mode
F407的GPIO_Mode与F103是有区别的,具体如下
typedef enum
{
GPIO_Mode_IN = 0x00,
GPIO_Mode_OUT = 0x01,
GPIO_Mode_AF = 0x02,
GPIO_Mode_AN = 0x03
}GPIOMode_TypeDef;
GPIO_Speed
F407的GPIO_Speed与F103也是有区别的,具体如下
typedef enum
{
GPIO_Low_Speed = 0x00,
GPIO_Medium_Speed = 0x01,
GPIO_Fast_Speed = 0x02,
GPIO_High_Speed = 0x03
}GPIOSpeed_TypeDef;
#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)
{
……
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)
#define GPIO_Pin_1 ((uint16_t)0x0002)
#define GPIO_Pin_2 ((uint16_t)0x0004)
#define GPIO_Pin_3 ((uint16_t)0x0008)
#define GPIO_Pin_4 ((uint16_t)0x0010)
……
#define GPIO_Pin_14 ((uint16_t)0x4000)
#define GPIO_Pin_15 ((uint16_t)0x8000)
#define GPIO_Pin_All ((uint16_t)0xFFFF)
#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);
端口复用
每个GPIO口有很多功能,默认的是作为普通IO口,此时不配置复用相关的东西。
- 当GPIO作为其他功能时,这个时候就要配置复用相关代码了
复用:内置外设基本上与I/O口共用管脚的,也就是I/O管脚的复用。
- 比如串口 1 的引脚对应的 IO 为 PA9,PA10.PA9,PA10 默认功能是 GPIO,所以当PA9,PA10 引脚作为串口 1 的 TX,RX 引脚使用的时候,那就是端口复用。
内置外设:
除单片机内核外,单片机内部可实现功能的设备都是内置外设,如串口,ADC 等。
区别:
- stm32f1的外设有默认引脚,当使用默认引脚时就不需要使用AFIO的重映射功能,如果需要重映射就使用。
- tm32f4的所有外设没有默认引脚,当使用外设时必须要使用GPIO_PinAFConfig函数选择引脚进行端口复用。
- stm32f1的外部中断与其他外设的端口复用都要用AFIO,而stm32f4的外部中断与其他外设分开了,外部中断使用SYSCFG进行操作,外设端口选择使用GPIO_PinAFConfig函数。
- stm32f4的SYSCFG有独立的时钟,而stm32f4的AF没有独立时钟。
- 当使用AD时,选择模拟模式而不是复用(特殊)。
STM32F103端口复用
I/O口不是随意复用,而是有专门的映射关系:
先使能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 管脚配置为浮空输入或者带上拉输入。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
重映射
很多复用内置的外设的I/O引脚可以通过重映射功能,从不同的I/O管脚引出。但只能映射到固定的管脚。
使用顺序:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
该语句打开了GPIOD重映射功能。该功能复用重映射和调试 IO 配置寄存器(AFIO_MAPR)控制。
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
部分重映射
- 功能外设的部分引脚重新映射,还有一部分引脚是原来的默认引脚
完全重映射
- 功能外设的所有引脚都重新映射
AFIO时钟
AFIO_MAPR:配置复用功能重映射
AFIO_EXTICRX:配置外部中断线映射
AFIO_EVCR: 配置EVENTOUT事件输出
三个寄存器及AFIO的详情
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);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
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))
LED实验设置
LED硬件连接图
当输出1的时候无电压差,灭。
当输出0 的时候有压差,亮。
应该设置推挽输出,上拉,默认就是灭。
读取输入电平
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
第一个作用:读取某个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);
作用:读取某个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);
作用:设置某个IO口输出为高电平(1)。实际操作BSRRL寄存器
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为低电平(0)。实际操作的BSRRH寄存器。
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
这两个函数不常用,也是用来设置IO口输出电平。
初始化IO代码
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
}
主函数
int main(void)
{
delay_init(168);
LED_Init();
while(1)
{
GPIO_ResetBits(GPIOF,GPIO_Pin_9);
GPIO_SetBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
GPIO_SetBits(GPIOF,GPIO_Pin_9);
GPIO_ResetBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)