STM32F103五分钟入门系列GPIO的常用库函数使用方法总结+一个网络上的误区
Posted 自信且爱笑‘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列GPIO的常用库函数使用方法总结+一个网络上的误区相关的知识,希望对你有一定的参考价值。
学习板:STM32F103ZET6
(三)GPIO的常用库函数使用方法总结+一个网络上的误区
前言
上一博客全部用寄存器版编的程序,本博就全程用库函数编程好了~为了方便代码移植,将代码都写在主函数里。
对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的常用库函数使用方法总结+一个网络上的误区的主要内容,如果未能解决你的问题,请参考以下文章