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

学习板:STM32F103ZET6

(三)GPIO的常用库函数使用方法总结+一个网络上的误区

  • 前言
  • 一、GPIO_Init()
    • 1、详述
    • 2、GPIO_Init()IO口配置完整程序
  • 二、GPIO_SetBits()
    • 1、详述
    • 2、GPIO_SetBits()使用程序
  • 三、GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
    • 1、详述
    • 2、例子
  • 四、GPIO_DeInit()
    • 1、详述
    • 2、例子
  • 五、GPIO_Write()
    • 1、详述
    • 2、例子
  • 六、 GPIO_WriteBit()
    • 1、详述
    • 2、例子
    • 3、 GPIO_WriteBit()与 GPIO_SetBits()的多IO同时输出(+网络误区
      • 1、详述(高电平同时输出)
      • 2、测试1
      • 3、详述(低电平同时输出)
      • 4、测试2
      • 5、总结
  • 七、GPIO_ReadOutputData()与GPIO_ReadInputData()
    • 1、详述
  • 八、GPIO_ReadInputDataBit()和GPIO_ReadOutputDataBit()
    • 1、详述
    • 2、例子
  • 九、GPIO_AFIODeInit()
  • 十、GPIO_StructInit()
  • 十一、GPIO_PinLockConfig()

前言

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

对GPIO进行读写操作前,需要进行GPIO模式的配置,而配置之前还需要先使能对应GPIO的时钟。既然是时钟,就去RCC头文件中找:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第1张图片

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

函数第1个参数:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第2张图片

第二个参数:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第3张图片

比如要使用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的详情:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第4张图片STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第5张图片

继续往下找:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第6张图片
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第7张图片

发现在底层中定义了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的,不过仔细看函数详情:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第8张图片

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

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

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第9张图片

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

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第10张图片

分别察看参数

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第11张图片

GPIO_Mode:

在这里插入图片描述

GPIO_Pin:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第12张图片

GPIO_Speed:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第13张图片

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

以点亮LED0为例

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第14张图片
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第15张图片

因为是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)

打开函数:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第16张图片

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

详述一下第三行代码:

 GPIOx->BSRR = GPIO_Pin;

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

点击察看一下GPIO_Pin:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第17张图片STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第18张图片
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第19张图片

发现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_TypeDef* GPIOx, uint16_t GPIO_Pin)

1、详述

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

打开函数详情:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第20张图片

前两行代码与(二)中函数代码一样,第三行代码使用的是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时钟。

打开库函数
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第21张图片
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第22张图片

可以发现,对传递的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。

打开函数:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第23张图片
可以看到传递过来的俩个参数:第一个参数GPIOx(A…G)表示IO口组别,第二个参数为16进制数。

这个16进制数怎么来的呢?从函数中可以看到用是ODR寄存器,那么这个16进制数就是配置ORD的那个16进制数。如控制PB5和PB10输出高电平,则:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第24张图片
寄存器版程序:

	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闪烁
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第25张图片
代码:

#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输出高电平和低电平。察看函数:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第26张图片

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

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

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第27张图片STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第28张图片

所以当参数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()函数:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第29张图片

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

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第30张图片

由上图可知:
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()
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第31张图片

以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()函数。
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第32张图片
第三个参数为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);//点亮
		 }
	 }
 }
 

5、总结

由上面的测试可知,并不像网上许多朋友说的那样:GPIO_WriteBit()只能操作一IO口,GPIO_SetBits()能操作多个IO口。

理论分析和实践证明,这俩个函数都可以同时操作多个IO。

不过有以下几个区别:
①GPIO_SetBits()只能输出高电平;GPIO_WriteBit()即可以输出高电平,又可以输出低电平

②GPIO_SetBits()使用的是BSRR寄存器;GPIO_WriteBit()使用的是BSRR寄存器和BRR寄存器。

③GPIO_WriteBit()的参数与GPIO_SetBits()的参数前俩个相同,但GPIO_WriteBit()有第3个参数,为0和非0,控制输出高电平还是低电平。

七、GPIO_ReadOutputData()与GPIO_ReadInputData()

1、详述

GPIO_ReadOutputData()函数是读取GPIOx现在输出的状态值,返回的是一个16进制的数,GPIO_ReadInputData()函数是读取GPIOx现在输入的状态值,返回的也是一个16进制的数。

先看GPIO_ReadOutputData():
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第33张图片
可以看到返回的是GPIOx->ODR。

再看GPIO_ReadInputData():
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第34张图片
可以看到返回的是GPIOx->IDR。

其实这俩个函数就是鸡肋【狗头】,在程序中,直接用GPIOx->ODR、GPIOx->IDR来代替这俩个函数就行了。

比如第(六)部分的测试例程:

		 if(GPIOE->ODR==0x0410)
			 {
     
				GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
			 }
	 if(GPIOE->ODR==0)
			 {
     
				GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
			 }

可以分别写成:

	 if(GPIO_ReadOutputData(GPIOB)==0x0410)
			 {
     
				GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
			 }
	 if(GPIO_ReadOutputData(GPIOB)==0)
			 {
     
				GPIO_WriteBit(GPIOB,GPIO_Pin_5,0);//点亮
			 }

显然第一种写法更方便一点

八、GPIO_ReadInputDataBit()和GPIO_ReadOutputDataBit()

1、详述

与第(七)部分的俩个函数相比较,这俩个函数返回的是某个引脚的状态:0或非0数。

俩个函数:

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
参数都一样,第一个参数为GPIOx(x=A…G),第二个参数为GPIO_Pin:GPIO_Pin_x(x=0…15)

看一下GPIO_ReadOutputDataBit()函数:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第35张图片
上图程序中,Bit_RESET和Bit_SET之前总结GPIO_WriteBit()函数时就用到过,Bit_RESET==0,Bit_SET ==(!0)

上图程序:

	if ((GPIOx->ODR & GPIO_Pin) != (uint32_t)Bit_RESET)
	  {
     
	    bitstatus = (uint8_t)Bit_SET;
	  }

在if语句中,先GPIOx->ODR & GPIO_Pin,若不为0,说明此时IO口的状态为高电平。以PB5为例,GPIO_Pin_5=0x0020=0x0000000000100000
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第36张图片

若ODR第5位不为0,则位与运算后得到一个非零的数,返回Bit_SET(注意:Bit_SET的值没有定义,只能说它是非零

同理:

 else
  {
     
    bitstatus = (uint8_t)Bit_RESET;
  }

当进行与运算后为0,说明此时PB5位0,返回0。

再看一下GPIO_ReadInputDataBit()函数:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第37张图片

该函数与GPIO_ReadOutputDataBit()相同,不再赘述。

2、例子

例:让LED1跟随LED2亮、灭。无论什么时候,只要LED0亮了,LED1就必须跟随亮,只要LED0灭了,LED1就必须跟随灭。其中key0按下后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;//LED0
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 GPIO_Init(GPIOB,&GPIO_InitStruct);//初始化
	 
	 
	 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;	    		
	 GPIO_Init(GPIOE, &GPIO_InitStruct);	  //PE5 LED1				 
	
	 GPIO_SetBits(GPIOB,GPIO_Pin_5); 
	 GPIO_SetBits(GPIOE,GPIO_Pin_5); //LED0和LED1初始熄灭	 
	 
	 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4;
	 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;//key0 PE4
	 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	 GPIO_Init(GPIOE, &GPIO_InitStruct);
	
	 delay_init();
	 GPIO_SetBits(GPIOE,GPIO_Pin_4);
	 while(1)
	 {
     
		 
		 if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)//key0按下
			GPIO_ResetBits(GPIOB,GPIO_Pin_5); //点亮LED0
		 if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_5)==0)
		 {
     
			GPIO_ResetBits(GPIOE,GPIO_Pin_5);//点亮LED0
			 delay_ms(1000);
			 GPIO_SetBits(GPIOE,GPIO_Pin_5);
			 GPIO_SetBits(GPIOB,GPIO_Pin_5);//熄灭
			  delay_ms(1000);
		 }
	 } 
 }

上述程序前面部分是PB5、PE5、PE4的配置,由于LED和KEY都现在了主函数中,显的比较乱,一般情况下应该创建各自的.h和.c文件,将初始化函数放入.c文件中。

死循环中,第一个if是判断key0是否按下,因为key0是输入,所以用GPIO_ReadInputDataBit()。按下后点亮LED0;在第二个if中检测LED是否被点亮,如果点亮了,就把LED1也点亮。

九、GPIO_AFIODeInit()

AFIO顾名思义,就是IO口复用,之前输出模式:GPIO_Mode_AF_OD、GPIO_Mode_AF_PP中的AF就是复用的意思。这块东西之后博客会总结到。

现看一下这个函数:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第38张图片
可见它和之前总结的GPIO_DeInit()类似,先使能了AFIO时钟,又失能时钟,从而取消端口复用。

使用时直接用如下代码即可:

	GPIO_AFIODeInit();

十、GPIO_StructInit()

GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct)

该函数的作用是将GPIOx(x=A…E)的所有IO口(0~15)都重新初始化。其中IO模式都设置为 GPIO_Mode_IN_FLOATING,speed都设置为GPIO_Speed_2MHz,

看一下函数:

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第39张图片

该函数的参数为GPIO_InitTypeDef* GPIO_InitStruct,可以是配置IO口时定义的那个结构体,也可以是程序中重新定义的结构体。

注意在这个函数中没有指出是哪个GPIO,并且Pin为GPIO_Pin_All,所以是将所有IO都设置为浮空输入。当然这里的所有IO口为STM32F103的所有GPIO口,为7×16=112。

用法为:

	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_StructInit( &GPIO_InitStruct)

注意1:上面程序中GPIO_InitStruct是指向结构体的指针,所以传递需要用取地址符(&)

注意2:若程序中配置IO使用了GPIO_InitStruct,则可以重新定义一个别的名字的指针,或者用已经定义的这个指针。

十一、GPIO_PinLockConfig()

GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

该函数是锁定某个IO,用到的寄存器是GPIOx_LCKR

看一下该函数:
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区_第40张图片
参数1::GPIOx(x=A…E),参数2:IO口:GPIO_Pin_x
(x=0…15)

上述图片中的程序分别对应:写1、写0、写1、读0、读1来激活“键锁”

这个库函数版的配置不好理解,推荐看本系列博客(二)的总结

使用时只需:

	GPIO_PinLockConfig(GPIOB, GPIO_Pin_B);//锁住PB5不让LED0点亮

需要注意的是它锁住的是IO口配置时(mode、speed)用到的CRL或CRH,所以要使该函数发挥作用,最好把它放在时钟配置之后,IO口配置之前。

你可能感兴趣的:(单片机,STM32)