STM32F103五分钟入门系列GPIO的常用库函数使用方法总结+一个网络上的误区

Posted 自信且爱笑‘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列GPIO的常用库函数使用方法总结+一个网络上的误区相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

前言

上一博客全部用寄存器版编的程序,本博就全程用库函数编程好了~为了方便代码移植,将代码都写在主函数里。

对GPIO进行读写操作前,需要进行GPIO模式的配置,而配置之前还需要先使能对应GPIO的时钟。既然是时钟,就去RCC头文件中找:
在这里插入图片描述

GPIO是挂载在APB2总线上的外设,所以在对GPIO的时钟进行设置时,通过函数RCC_APB2PeriphClockCmd()来实现。

函数第1个参数:

在这里插入图片描述

第二个参数:

在这里插入图片描述

比如要使用PB5,需要配置GPIOB,首先得使能GPIOB的时钟,第一个参数是:RCC_APB2Periph_GPIOB;第二个参数是:ENABLE

代码:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

一、GPIO_Init()

GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

1、详述

该函数初始化GPIO,用来配置对应IO的输入输出模式、输出时的速度。其中第一个参数为GPIOx(x=A…G),为一个结构体指针,指向的结构体是本系列的博客(二)总结的七大寄存器;参数2也是结构体指针,指向的结构体成员包括:GPIO_Pin、GPIO_Speed、GPIO_Mode。

本系列的博客(一)中总结过,参数1是结构体指针,所以传递参数时应该传递一个地址,按理说,应该有:

	GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 
	GPIO_Init(&GPIO_B,&GPIO_InitStruct);

但是传递过去的参数1是指向一个包含GPIO的七大寄存器的结构体,而现在程序中定义的指针,传递的地址只是编译时分配的存储地址,就算传递过去也没法指向七大寄存器的结构体。所以传递的地址应该是指令地址。

打开参数1的详情:

在这里插入图片描述在这里插入图片描述

继续往下找:

在这里插入图片描述
在这里插入图片描述

发现在底层中定义了GPIOx(x=A~G)都是地址!!!!!

所以该函数的第一个参数就是GPIOx(x=A~G),而且它本身是地址,所以传递时没必要再用取地址符(&)

参数1搞定,程序:

	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	
	 GPIO_Init(GPIOB, &GPIO_InitStruct);

再说参数2,传递的程序编译时分配的存储地址,按理说是没办法指向GPIO_Pin、GPIO_Speed、GPIO_Mode的,不过仔细看函数详情:

在这里插入图片描述

传递过去的GPIO_InitStruct参数虽然是程序分配的存储地址,它指向的结构体也的确有GPIO_Pin、GPIO_Speed、GPIO_Mode,但是无法与真实的pin、mode、speed来取得联系,充其量结构体里的成员是临时变量!但是在函数里面给了地址,就是指令传输的地址,函数体后面的配置就是给刚刚所述的那些个临时变量设置指令地址,如此这般…就和真实的、物理层的地址联系在了一起。通过函数参数设置,也就设置了底层GPIO_Pin、GPIO_Speed、GPIO_Mode。

接下来看看参数2的内容:(其实本系列博客(一)已经总结过了)

在这里插入图片描述

看到参数2指向的结构体,然后返回上一步

在这里插入图片描述

分别察看参数

在这里插入图片描述

GPIO_Mode:

在这里插入图片描述

GPIO_Pin:

在这里插入图片描述

GPIO_Speed:

在这里插入图片描述

2、GPIO_Init()IO口配置完整程序

以点亮LED0为例

在这里插入图片描述
在这里插入图片描述

因为是PB5,所以:
GPIO_Pin:GPIO_Pin_5
输出模式,通用推挽输出,GPIO_Mode:GPIO_Mode_Out_PP
速度一般选50M,GPIO_Speed:GPIO_Speed_50MHz

代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init( GPIOB,&GPIO_InitStruct);
 }

二、GPIO_SetBits()

GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

1、详述

该函数是给对应GPIO_Pin设置高电平,其中参数1:GPIOx是对应的GPIO,可以是GPIOA~GPIOG;参数2是GPIO_Pin,可以是0 ~15,分别对应PX0 ~PX15(x=A…G)

打开函数:

在这里插入图片描述

发现前俩行代码是处理参数的,第三行代码是给IO口置高电平的。

详述一下第三行代码:

 GPIOx->BSRR = GPIO_Pin;

这行代码用到BSRR寄存器,那么它应该是一个32位2进制数转换来的16进制数,这样才能对BSRR寄存器的低16位置1,进而控制ODR寄存器对应位置1,进而使对应IO口置1。

点击察看一下GPIO_Pin:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

发现GPIO_Pin的确是16进制数,因为本板子(许多板子)与、或运算、赋值运算都是低位对齐,高位补零的,所以该16位的16进制数与32位的进制数一样,都可以操作BSRR寄存器的低16位。

所以该函数功能就一目了然了:
传递进来的GPIOx参数1和GPIO_Pin参数2,通过前俩行代码使参数与物理层地址匹配,然后经过第三行代码控制BSRR寄存器设置对应IO口为1。

2、GPIO_SetBits()使用程序

由之前的点亮LED0和原理图可知,LED0在对应引脚是低电平时处于点亮状态,单片机上电复位后,引脚都清零,此时LED是点亮的,所以可以通过GPIO_SetBits()函数,让LED0在初始状态下熄灭。
代码接(一)中程序:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 GPIO_Init( GPIOB,&GPIO_InitStruct);//初始化
	 GPIO_SetBits(GPIOB,GPIO_Pin_5);//LED0初始熄灭
 }

三、GPIO_ResetBits()

GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

1、详述

该函数是对传递过来的IO引脚置低电平

打开函数详情:
在这里插入图片描述

前两行代码与(二)中函数代码一样,第三行代码使用的是BRR寄存器,该寄存器就是通过给某位置1来控制ODR寄存器的对应位置0,从而控制对应IO口输出低电平。

无论是第(二)部分的BSRR寄存器还是这里的BRR寄存器,设置时都是用的赋值语句,而不是移位运算、位或、位与运算,因为使用移位运算会造成IO口赋值紊乱,位或、位与运算每次使用后需要对BSRR寄存器和BRR寄存器清零,造成很大的不便。同时,第(二)部分和本部分的BSRR寄存器和BRR寄存器,使用时,用BSRR寄存器置1,用BRR寄存器置0,而不是只用BSRR来置0和置1。这些都与本系列博客(二)中最后的小总结部分不谋而合。

2、例子

例:控制LED0闪烁

LED0的时钟使能、IO口配置前几部分的总结中已经实现,现附完整的LED0闪烁代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 GPIO_SetBits(GPIOB,GPIO_Pin_5);//LED0初始熄灭
	 
	 delay_init();//初始化延时函数
	 while(1)
	 {
		 GPIO_ResetBits(GPIOB,GPIO_Pin_5);//PB5置0,点亮LED0
		 delay_ms(1000);//延时1000ms
		 GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB5置高电平,LED0初始熄灭
		 delay_ms(1000);//延时1000ms
	 } 
 }

四、GPIO_DeInit()

GPIO_DeInit(GPIO_TypeDef* GPIOx)

1、详述

该库函数的作用是取消GPIO初始化,关闭GPIO时钟。

打开库函数
在这里插入图片描述
在这里插入图片描述

可以发现,对传递的GPIOx先进行使能enable,然后使失能disable,关闭了GPIOx的时钟,GPIOx也就不能工作了。

2、例子

例:对刚刚写的LED0闪烁实验,取消其闪烁

代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 GPIO_SetBits(GPIOB,GPIO_Pin_5);//LED0初始熄灭
	 
	 delay_init();//初始化延时函数
	 while(1)
	 {
		 GPIO_ResetBits(GPIOB,GPIO_Pin_5);//PB5置0,点亮LED0
		 delay_ms(1000);//延时1000ms
		 GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB5置高电平,LED0初始熄灭
		 delay_ms(1000);//延时1000ms
		 
		 GPIO_DeInit(GPIOB);//取消GPIOB配置
	 }
 }

下载程序后,发现LED0闪烁1次后不再闪烁,因为死循环中执行闪烁后, GPIO_DeInit(GPIOB)取消了GPIOB的配置。

注意: GPIO_DeInit()一般不要用,因为它是关闭整个GPIOx的时钟,如果GPIOx的1~15个IO有几个正在使用,执行该函数后,这几个功能都会丧失。

五、GPIO_Write()

GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)

1、详述

该函数可以对多个IO口置0或置1。

打开函数:
在这里插入图片描述
可以看到传递过来的俩个参数:第一个参数GPIOx(A…G)表示IO口组别,第二个参数为16进制数。

这个16进制数怎么来的呢?从函数中可以看到用是ODR寄存器,那么这个16进制数就是配置ORD的那个16进制数。如控制PB5和PB10输出高电平,则:
在这里插入图片描述
寄存器版程序:

	GPIOB—>ODR=0x0420;
//GPIOB—>ODR|=0x0420;

这里的0x0402就是GPIO_Write()函数传递的参数2。

库函数程序:

	GPIO_Write(GPIOB,0x0420)

注意:
该函数用的是GPIOx->ODR = PortVal配置IO口,这里用的是赋值语句,也就是说传递的这个16位数,必须是想要执行后,单片机IO(0~15)的状态值,如果IO口有好几个被占用,则每个IO的状态都必须考虑到,才能求出这个16进制数!

所以这个库函数写的一点都不好(所以得慎用),如果改成以下代码就好了,只考虑现在要改变的IO口的状态就行了。

对GPIO_Write()库函数修改(官方库函数缺点太明显):

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  //GPIOx->ODR = PortVal;
  GPIOx->ODR| = PortVal;//修改这里,变成或运算
}

建议:既然这个函数用到16进制数,同时也得求这个16进制数,那还不如直接用寄存器的方式编写本条代码!直接:GPIOx->ODR=0x…

2、例子

例:用GPIO_Write()控制LED0闪烁
在这里插入图片描述
代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 GPIO_Write(GPIOB,0);//LED0初始熄灭0=0x0000
	 delay_init();//初始化延时函数
	 while(1)
	 {
		 GPIO_Write(GPIOB,0);//点亮
		 delay_ms(1000);//延时1000ms
		  GPIO_Write(GPIOB,0x0020);//熄灭LED0
		 delay_ms(1000);//延时1000ms 
	 } 
 }

六、 GPIO_WriteBit()

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

1、详述

该函数也是控制IO输出高电平和低电平。察看函数:

在这里插入图片描述

可以看到有三个参数,参数1:GPIOx(A…G);参数2:GPIO_Pinx(x=0…15);参数3: 0或!0

前俩个参数好理解,现看一下第3个参数:

在这里插入图片描述在这里插入图片描述

所以当参数3:BitVal== 0时,即BitVal==Bit_RESET,执行:

else
  {
    GPIOx->BRR = GPIO_Pin;
  }

上述代码通过BRR寄存器,为对应引脚置0

当参数3:BitVal== !0时,即BitVal==Bit_SET,执行:

if (BitVal != Bit_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }

上述代码通过BSRR寄存器,为对应引脚置1。

还是之前强调的东西,这个函数对寄存器的操作,也是用到赋值,但是BRR寄存器和BSRR寄存器对应IO置0后,是不改变IO输出状态的,所以这里可以用赋值语句,并且不会造成IO口紊乱,要与GPIO_Write()函数中对ODR寄存器操作形成区别…(这也是更多情况下选择使用GPIO_WriteBit()而不使用GPIO_Write()的原因)。

2、例子

例:用GPIO_WriteBit()函数实现LED0的闪烁

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	//GPIO_TypeDef GPIO_B;//定义结构体指针,参数1
	
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 GPIO_WriteBit(GPIOB,GPIO_Pin_5,1);//LED0初始熄灭 第3个参数非零就行
	 delay_init();//初始化延时函数
	 while(1)
	 {
		 GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
		 delay_ms(1000);//延时1000ms
		 GPIO_WriteBit(GPIOB,GPIO_Pin_5,1);
		 delay_ms(1000);//延时1000ms
	 } 
 }

3、 GPIO_WriteBit()与 GPIO_SetBits()的多IO同时输出(+网络误区

1、详述(高电平同时输出)

从名字上也可以看出,GPIO_SetBits()函数后面加了“S”,GPIO_SetBits()可以同时对多个IO口设置高电平(注意是同组IO,如都属于GPIOB下的0~15 IO)

网络上都说GPIO_WriteBit()只能一个IO设置高电平,其实是不对的。

重新看一下GPIO_SetBits()函数:
在这里插入图片描述

它是对BSRR寄存器的操作,而对BSRR寄存器的操作是需要一个32位数(可以是16位数,低位对齐)。传递过来的参数2:uint16_t GPIO_Pin是16位数的pin,如GPIO_Pin_5又如GPIO_Pin_10

在这里插入图片描述

由上图可知:
GPIO_Pin_5=0x0020; GPIO_Pin_10=0x0400;

若分别对PB和PB10设置:

    GPIO_SetBits(GPIOB,GPIO_Pin_5);
	GPIO_SetBits(GPIOB,GPIO_Pin_10);

即在该库函数中执行:

GPIOx->BSRR = 0x0020;
GPIOx->BSRR = 0x0400;

即:第一行代码把ODR寄存器第5位置1,第二行代码将ODR寄存器第10位置1

即:

	GPIOB->ODR|=0x0020;
	GPIOB->ODR|=0x0400;

合起来就是:

	GPIOB->ODR|=0x0420;

而0x0420=0x0020|0x0400;
即:0x0420=GPIO_Pin_5|GPIO_Pin_10

即只需执行:

	GPIO_SetBits(GPIOB,GPIO_Pin_5|GPIO_Pin_5);

就同时使PB5和PB10输出高电平

再察看GPIO_WriteBit()
在这里插入图片描述

以PE4和PE10为例,同时输出高电平

GPIO_WriteBit()传递的第一个参数:GPIOE,第二个参数GPIO_Pin_4和GPIO_Pin_10;第三个参数1(!0)

分开输出高电平:

	GPIO_WriteBit(GPIOE,GPIO_Pin_4,1);
	GPIO_WriteBit(GPIOE,GPIO_Pin_10,1);

而GPIO_Pin_4=0x0010;GPIO_Pin_10=0x0400

即在GPIO_WriteBit()中对BSRR寄存器操作:

	GPIOE->BSRR=0x0010;
	GPIOE->BSRR=0x0400;

因为BSRR寄存器置0时,对IO输出没有影响,即上述代码第二行对第4位的置0作用不能影响第一行代码。即上面代码可以实现PE4和PE10置同时1。

即对ODR寄存器分别置1:

	GPIOE->ODR=0x0010;
	GPIOE->ODR=0x0400;

合起来就是:

	GPIOE->ODR=0x0410;

即:

	GPIOE->ODR=GPIO_Pin_4|GPIO_Pin_10;

即:

	GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,1);

所以该函数是可以同时输出多个IO口高电平的。

2、测试1

例:给PE4和PE5进行GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,1)操作后,察看PE4和PE10输出是否都为高电平。

直接附测试代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);//GPIOB时钟使能
	
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 GPIO_WriteBit(GPIOB,GPIO_Pin_5,1);//LED0初始熄灭 第3个参数非零就行
	// delay_init();//初始化延时函数
	 GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,1);//置PE4和PE5为高电平
	 while(1)
	 {
		 if(GPIOE->ODR==0x0410)//GPIO_ReadOutputData(GPIOB)==0x0410
		 {
			GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
		 }
	 } 
 }
 

下载程序后发现LED0处于常亮状态,说明此时PE4和PE10都处于高电平状态,说明之前我们总结的是正确的!

3、详述(低电平同时输出)

因为GPIO_SetBits()库函数只能输出高电平,所以不与GPIO_WriteBit()做比较,现只分析GPIO_WriteBit()函数。
在这里插入图片描述
第三个参数为0时,执行else语句,此时是对BRR寄存器的操作。

再以PE4和PE10为例,同时输出低电平:GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,0);

	GPIO_WriteBit(GPIOE,GPIO_Pin_4,0);
	GPIO_WriteBit(GPIOE,GPIO_Pin_10,0);

即有:

	GPIOE->BRR=GPIO_Pin_4;
	GPIOE->BRR=GPIO_Pin_10;

即有:

	GPIOE->BRR=GPIO_Pin_4|GPIO_Pin_10;

即可以有:

	GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,0);

4、测试2

修改《测试1》的例子,PE4和PE5同时输出低电平时LED0常亮

代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 GPIO_InitTypeDef  GPIO_InitStruct;//定义结构体指针 参数2
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIOB时钟使能
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);//GPIOB时钟使能
	
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 GPIO_WriteBit(GPIOB,GPIO_Pin_5,1);//LED0初始熄灭 第3个参数非零就行
	// delay_init();//初始化延时函数
	 GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,1);//开始时置PE4和PE5为高电平,<测试1>已经测试可以这样用
	 while(1)
	 {
		  GPIO_WriteBit(GPIOE,GPIO_Pin_4|GPIO_Pin_10,0);//PE4和PE10置低电平
		 if(GPIOE->ODR==0)//GPIO_ReadOutputData(GPIOB)==0x0
		 {
			GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
		 以上是关于STM32F103五分钟入门系列GPIO的常用库函数使用方法总结+一个网络上的误区的主要内容,如果未能解决你的问题,请参考以下文章

精通《IIC通信》

STM32F103(二十)DAC(贼详细)

STM32F103(十八)ADC总结(贼详细)

STM32F103你学不会系列(十七)电容触摸按键实现

STM32F103(二十一)DMA(超详细的~)

STM32F103(二十四)一篇博客精通《485通信》