【STM32】GPIO
在学习单片机的初期,最容易接触到的外设就是GPIO,作为STM32单片机最常见的外设,它可以说是无处不在,如果我们想要全面掌握STM32相关的知识,理解和掌握GPIO的原理、作用和使用方法是十分重要的一步。为了深入了解和学习GPIO的知识,我去查找了STM32的相关手册和查阅网上关于GPIO介绍的相关文章,将学习到的内容做了总结归纳,这里我作为例子和记录所用到的是STM32F103C8T6。
目录
⚙GPIO的原理
🔨GPIO的定义
🔨GPIO结构
🔨GPIO工作模式
📻输入模式
📻输出模式
📻复用功能模式
📻模拟输入/输出模式
🔨GPIO寄存器
📻端口配置寄存器
📻端口输入数据寄存器
📻端口输出数据寄存器
📻端口位设置/清除寄存器
📻端口位清除寄存器
📻端口配置锁定寄存器
🔨GPIO库函数
📻GPIO_DeInit
📻GPIO_Init
📻GPIO_StructInit
📻GPIO_ReadInputDataBit
📻GPIO_ReadInputData
📻GPIO_ReadOutputDataBit
📻GPIO_ReadOutputData
📻GPIO_SetBits
📻GPIO_ResetBits
📻GPIO_WriteBit
📻GPIO_Write
📻GPIO_PinLockConfig
📻GPIO_EventOutputConfig
📻GPIO_EventOutputCmd
📻GPIO_PinRemapConfig
📻GPIO_EXTILineConfig
📻GPIO_AFIODeInit
⚙GPIO应用示例
🔨头文件配置
🔨初始化GPIO
⚙实物
⚙GPIO的原理
🔨GPIO的定义
GPIO全称是General Purpose Input Output,中文名字为微处理器的通用输入和输出接口。顾名思义,就是实现微处理器信号输出和外界信号读入的接口,通过设置它不同的工作模式来实现不同的信号采集或者控制外设。
STM32系列的GPIO有许多组,每组有GPIOx0~GPIOx15这16个引脚,这里的x对应的是GPIO的组号,通常由A开始,也就是说我们单片机中这些A7、A1、B5等都对应这一组GPIO的某个引脚。通常情况下,GPIOA的引脚2我们就记为PA2。
通过查阅STM32F103C8T6的数据手册发现,其引脚对应的GPIO组的引脚发现有下面这几个,可以发现,并不是GPIOx的0~15脚都会在上面。对于GPIO来说,最基本的功能就是作为通用的收发端口。输出高、低电平,点亮LED或者接收外界的输入信息。
每一个通用的GPIO有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
🔨GPIO结构
每个GPIO口的电路基本结构如下图所示:
🔨GPIO工作模式
前文中提到了GPIO的工作模式,关于GPIO的工作模式总体可以划分为4种:输入模式、输出模式、复用功能和模拟输入输出。下面我们来具体看看这四种模式有什么特点,以及它们如何工作的。
📻输入模式
GPIO的输入模式包括上拉、下拉和浮空等三种输入模式。在设置GPIO为输入模式时,数据寄存器每隔1个AHB1的时钟周期就会更新一次,我们可以在每次更新数据寄存器的时候通过读取输入数据寄存器上的指定值来判断IO口的状态。
①上拉输入模式:上拉输入模式是指将连接VDD端的开关合上,此时GPIO的结构如下图所示。不难看出,如果引脚没有电信号输入,那么此时读进去的是低电平,如果外界输入为零,那么读进去的是低电压。总结来说,上拉输入模式是如果IO口悬空(没有高低电压),那么默认输入状态为高电平,如果IO口有低电平输入,那么输入状态为低电平。
②下拉输入模式:下拉输入模式是指将连接VSS端的开关合上,此时GPIO的结构如下图所示。不难看出,如果引脚没有电信号输入,那么此时读进去的是低电平,如果外界输入为高电平,那么读进去的是低电压。总结来说,下拉输入模式是如果IO口悬空(没有高低电压),那么默认输入状态为低电平,如果IO口有高电平输入,那么输入状态为高电平。
③浮空输入模式:浮空模式就比较好理解了,它表示无论是VDD还是VSS端的开关都不合上,直接引脚输入什么电平就读什么电平。此时如果引脚浮空,那么输入状态就不确定,可能会是高电平,也可能会是低电平,总之就是不确定。
📻输出模式
GPIO的输出模式可以大概分为推挽和开漏两大种模式,其中在推挽模式中,我们可以控制引脚输出高电平或者低电平,但是如果我们选择的是开漏输出模式,那么我们可以控制引脚输出高阻态或者低电平。
对于输出模式来说,我们可以控制其输出的速度这里的速度是指芯片支持IO口高低电平的切换频率最大值。这个速度IO口给我们提供了有许多选项,例如2MHz、50MHz等,一般情况下我们选择的是最大的频率。
📻复用功能模式
什么是复用功能呢?对于现在的我们来说,IO口是用来输出低电平或者高电平等等的,但是如果我们查阅资料可以发现,其实IO口还可以作为USART通用同步异步收发器、CAN、定时器等功能。这种不仅用于单纯的输出和输入的功能的模式就叫做复用。
而在复用的状态下,IO口可以作为推挽和开漏两种工作模式,但是它的输出信号是来源于其它的外部设备。
📻模拟输入/输出模式
前面讲了很多,最后一种工作模式是模拟输入/输出模式,在这种模式下通过对GPIO相应寄存器进行写入操作,使得外设通过模拟通道来进行输入和输出。这样就可以将外部的模拟信号进行采集并且在单片机的外设上进行模拟信号的处理。
🔨GPIO寄存器
既然都提到GPIO了,那就不能避免提及相关控制它的寄存器了。我们知道,对寄存器进行操作可以在软件程度上对硬件进行配置操作,虽然我们这里主要是用到了标准库,但是我们还是得对GPIO的一些寄存器进行了解和掌握,这样更有利于我们深度了解GPIO的模式配置等内容。
前面说过,GPIO有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
📻端口配置寄存器
端口配置寄存器分为两个,一个是端口配置低寄存器(GPIOx_CRL)、一个是端口配置高寄存器(GPIOx_CRH),它们各自负责配置输入或者输出模式、输出模式的输出速度等功能,其中高位寄存器负责配置一个GPIO组中的引脚8到引脚15,低位寄存器负责配置GPIO组中的引脚0到引脚7。
端口配置低寄存器(GPIOx_CRL)
下面这个就是端口配置低位寄存器,初看可能感觉很复杂,但是不着急,我们慢慢看。
首先我们给上面的图划分一个区块,这里我大致分成了绿色、紫色、蓝色、黄色和红色五个区域。首先我们来看绿色,绿色表明这个寄存器的存储区域,可以看出它是由一个个紫色的矩形拼成的,并且拼好后拆分成了上下两行。上面行是16~31,下面行是0~15,首先我们回忆一下,这个寄存器是32位的,也就是说这里面一个紫色矩形就表明是一个2位的存储空间,16个2位的矩形刚好表面是32位。也就是说上面绿色区域表面的是实际的存储区域的图形化展示,而它里面由很多紫色的小矩形,这些就是将这块大的存储区域拆分成的每一块小区域,我们往里面填入数字就可以实现控制寄存器。但是怎么划分这些区域哪个控制哪个呢?这时候就需要对其进行区域划分,划分出一个个小的紫色区域,明确哪几个位控制哪个引脚的输出配置、输入模式等。每一块小紫色矩形里面都会有“MODEx[1:0]”或者“CNFx[1:0]”的符号,它的意思我们可以从红色的区域看出来。
红色的区域由黄色的区域和蓝色区域组成,其中黄色区域里面记录的是对紫色方块里面的字体的解释,也是对这几个位的所控制的功能的解释。以下面的寄存器为例,标有CNF的紫色方块是控制GPIO的输入模式和输出模式的,标有MODE的紫色方块是控制GPIO的工作模式的。而MODEx中的x就表示哪个引脚。例如MODE6就是引脚6,CNF0就是引脚0。往MODE里写入10就是输出模式、00就是输入模式等等。而蓝色区域就表示0~31个位中哪几个位属于当前黄色区域的控制范围。MODE[1:0]里面的[1:0]则表示一个紫色矩形的长度,它不一定是2位的,有些功能的工作模式可能是8个,16个等,那它就需要更多4位等更多位了。
端口配置高寄存器(GPIOx_CRH)
端口高寄存器的功能与端口低寄存器的功能一样,都是用来控制GPIO的工作模式和输入和输出模式的,只不过这个寄存器控制的是高8位的引脚,而低寄存器控制的是低8位的引脚。
📻端口输入数据寄存器
端口输入数据寄存器(GPIOx_IDR)的高16位是保留的,它实际上起到功能的就是低16位,并且都被用于记录16个引脚的输入数据,其中它的每一个位只允许我们去读它的数据,我们可以根据读进来的数据来记录这个引脚的输入数据。
📻端口输出数据寄存器
端口输出数据寄存器(GPIOx_ODR),这个寄存器也是高16位保留,低十六位用于写入引脚的输出状态。由于我们让引脚输出需要定义它的状态,所以这里的每一位都是可读可写的。
📻端口位设置/清除寄存器
端口位设置/清除寄存器(GPIOx_BSRR),可以看出,这个寄存器是32位的,高16位用于将0写入到前面的端口输出数据寄存器中的ODRy,而低16位用于将1写入到ODRy。前面我们不是说ODRy的1/0值表示引脚的输出吗?而这个1/0值则通过BSRR寄存器的高16和低16位写入。当BSRR的高16位为1,则写入的为0,如果是0,则不影响ODRy位;如果BSRR的低16位位0,则写入的是1,如果是0,则不影响ODRy位。也就是说BSRR是写0/1进入ODR寄存器的笔。
📻端口位清除寄存器
端口位清除寄存器(GPIOx_BRR),它的作用其实和BSRR的高16位一样。
📻端口配置锁定寄存器
端口配置锁定寄存器(GPIOx_LCKR),这个我比较少了解接触到,有兴趣的可以查阅中文手册来了解。
🔨GPIO库函数
在上面我们可以看到,关于GPIO的操作有许多相关的寄存器,但是在平常我们使用STM32时,一般是使用封装好的函数来操作寄存器以代替我们自己手动去设置每一个寄存器,目前常用的是HAL库和标准库实现具体的操作,而HAL库封装地比较完善,一些更加底层的原理可能比较难看到,所以这里我使用的是标准库来进行学习。
📻GPIO_DeInit
首先是GPIO_DeInit函数,这个函数是一个缺省值设置函数,它的输入参数是一个代表GPIO配置的结构体,这个结构体代表了一个GPIO口。而GPIO_DeInit这个函数就是对这个结构体代表的GPIO口进行默认值配置,也就是将GPIO的各个参数设置为默认值。
例如我们对GPIOC进行默认值配置:
GPIO_DeInit(GPIOC);
📻GPIO_Init
这个函数就是我们常见的对GPIO口进行初始化的函数了,它和GPIO_DeInit不一样,后者是对GPIO进行缺省值配置,前者是根据我们的需求对每个参数进行设置,也就是初始化。
GPIO_Init的输入参数有两个,一个是代表着我们要配置的GPIO口的结构体(这个结构体和DeInit里的一样),另一个是一个结构体指针,这个指针指向一个含有GPIO配置信息的结构体,这个结构体是GPIO_InitStruct类型的。
这个结构体定义如下所示,它包括了对GPIO口引脚的选择、GPIO口速度的选择和工作模式的选择。
typedef struct
{
u16 GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
} GPIO_InitTypeDef;
其中引脚、速度和工作模式可供选择的参数如下图所示:
引脚:
速度:
工作模式:
例如将GPIOA的引脚2设置为推挽输出,输出速度是50MHz:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
📻GPIO_StructInit
这个函数是针对我们前面提到的GPIO_InitStruct类型的GPIO配置结构体进行缺省值设置的函数,它需要传递的参数毫无疑问就是一个GPIO_InitStruct类型的结构体指针。
而该函数默认配置结构体里面三个元素的缺省值是GPIO_Pin是全选、速度是最低的2MHz、模式是浮空输入。
例如我们将前面提到的配置结构体进行缺省值设置:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_Structinit(&GPIO_InitStructure);
📻GPIO_ReadInputDataBit
既然GPIO有能够读入引脚电平的能力和模式,那么就会有相应的库函数来实现这项功能,这个函数就是对某个GPIO引脚的电平进行读取的函数。它返回的就是当前引脚的管脚数值,可能是0或者1等。
例如我们读取GPIOA的引脚12的电平:
u8 ReadValue;
ReadValue = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12);
📻GPIO_ReadInputData
我们能够读取一个引脚的输入电平,而在此基础上,我们还有一个库函数能够读取一整个GPIO口所有引脚的电平状态,由于最多有16个引脚,所以需要开辟一个u16类型的变量来存取读取到的数值。
例如我们读取整个GPIOA所有引脚的电平:
u16 ReadValue;
ReadValue = GPIO_ReadInputData(GPIOA);
📻GPIO_ReadOutputDataBit
有输入引脚状态的读取函数,就会有输出引脚状态读取的函数,有时候我们设置了某个引脚输出状态,但是不确定实际输出的状态是否就是我们预想的状态,此时我们可以利用这个函数来进行读取。可以看到它的参数和GPIO_ReadInputDataBit的参数其实是一样的。
假如我们要读取GPIOA的引脚1的输出状态:
u8 ReadValue;
ReadValue = GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1);
📻GPIO_ReadOutputData
这个就不多说了,就是对应GPIO_ReadInputData函数,不过读取的是输出IO口的状态。
读取GPIOA的输出状态:
u16 ReadValue;
ReadValue = GPIO_ReadOutputData(GPIOA);
📻GPIO_SetBits
前面我们提到很多初始化和读引脚状态的函数,而现在提到的这个函数是一个改变引脚电平状态的函数,它的作用是将引脚的电平拉高,所需的输入参数是GPIO号和GPIO的引脚号。
例如我们将GPIOB的引脚6和引脚10拉高:
GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_10);
📻GPIO_ResetBits
既然有将引脚拉高的函数,那肯定会有将引脚输出电平拉低的函数,而这个函数就是我们现在提到的GPIO_ResetBits,它所需的输入也和GPIO_SetBits一样。
让我们现在将之前拉高的(GPIOB6)PB6和PB10(GPIOB10)拉低:
GPIO_ResetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_10);
📻GPIO_WriteBit
这个函数可以理解为是前面的ResetBits和SetBits的结合体,它可以通过一个函数来设置引脚的高电平和低电平,采取的方式是在前面函数的基础上再向这个函数传入第三个参数,这个参数就是设置高或者低电平的。
例如我们采用这个函数来实现将GPIOB的6和10引脚拉高,后面再拉低:
GPIO_WriteBit(GPIOB, GPIO_Pin_6|GPIO_Pin_10, Bit_SET);
...
GPIO_WriteBit(GPIOB, GPIO_Pin_6|GPIO_Pin_10, Bit_RESET);
📻GPIO_Write
我们除了可以操作单个引脚输出高/低电平以外,我们还可以同时操作一个GPIO下的所有引脚,采用的就是GPIO_Write这个函数,它可以同时操作所有的引脚,而所有的引脚的状态就是根据这个函数的第二个参数确定的。
例如我们将PB5和PB11拉高,其它都清零:
PB15~PB0:0000 | 1000 | 0010 | 0000
GPIO_Write(GPIOB, 0x0820);
📻GPIO_PinLockConfig
这个函数用来锁定某个GPIO口下面对应的某个引脚的寄存器,一旦某个引脚被锁定了,那么直到下一次系统复位之前它都不能被更改,而锁定的是该引脚的输入输出模式、上拉下拉、速度、模式等设置,这些锁定能够保证GPIO在运行过程中不被改变,保证安全性。
将GPIOB的引脚7锁定:
GPIO_PinLockConfig(GPIOB, GPIO_Pin_7);
📻GPIO_EventOutputConfig
这个函数不是一个常用的函数,它一般用来输出配置某个引脚输出某些事件,而这些输出的事件一般是系统级别的事件(例如Cortex-M3内核的事件、以及多个内核通讯传输),而不是我们常见的GPIO操作,一般配合GPIO_EventOutputCmd使用。虽然参数1和2都表示GPIO和引脚号,但是它们的表达形式不同。
其中第一个参数的取值为:
如果我们要将系统事件利用GPIOB的引脚5输出:
GPIO_EventOutputConfig(GPIO_PortSourceGPIOE, GPIO_PinSource5);
📻GPIO_EventOutputCmd
这个是前面提到的使能/失能系统事件通过引脚输出的函数,它一般与前面函数配合使用。
例如我们将前面的利用GPIO引脚输出系统事件的函数使能:
GPIO_EventOutputConfig(GPIO_PortSourceGPIOE, GPIO_PinSource5);
GPIO_EventOutputCmd(ENABLE);
📻GPIO_PinRemapConfig
这个函数是修改引脚映射的函数,我们知道一些GPIO口的引脚平常默认作为某些外设所用,但是在很多情况下,它们还能被重映射为另外一种功能,而将这些引脚的默认功能修改就是利用的这个函数。这个函数有两个参数,第一个参数是你想要选择的新功能,也就是重映射后的功能,第二个参数是是否开启重映射。如果我们选择了某个重映射后的功能,并且开启了,那么这个函数就会帮助我们将对应引脚的默认功能改为其重映射后的功能。
而一些重映射后的功能如下所示:
例如我们启动I2C1的重映射功能:
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
📻GPIO_EXTILineConfig
这个函数用来配置GPIO的引脚作为外部中断的输入线路,当我们配置好了这个函数后,我们可以通过GPIO的引脚来接受外部的中断源信号,从而实现采集外部中断信号。这个函数一般在我们需要外部器件输入的特定信号来中断内部正在执行的程序时所用,用来配置哪个引脚来接收这种外部中断信号,然后芯片会根据这些GPIO引脚采集的外部中断执行相应的中断处理操作。
例如我们配置GPIOA的引脚8作为外部中断信号的输入引脚:
GPIO_EXTILineConfig(GPIO_PortSource_GPIOA, GPIO_PinSource8);
📻GPIO_AFIODeInit
前面我们提到了GPIO引脚可以进行重映射、作为外部中断的中断线等,这些功能都是我们后面赋予的,前期都是没有的,这个函数是将这些GPIO对应的这些功能重新设为默认值,也就是初始我们还没设置的缺省值,它没有参数,默认是全部GPIO的引脚同时操作。
例如将所有GPIO的重映射和外部中断功能都恢复成缺省值:
GPIO_AFIODeInit();
⚙GPIO应用示例
终于到了最爱的点灯环节了,提到GPIO最基础的应用,那肯定是逃不过点灯的啦,作为每一个初学GPIO的人,我们在点灯的过程中也会加深对GPIO的了解,所以这个过程是十分重要的。要想完成这个项目,首先我们需要准备好一个工程,并且新建了led.h和led.c两个文件。接下来要想完成用GPIO来点亮灯,我们需要按下面的顺序来。
🔨头文件配置
在配置GPIO之前,我们需要对led.h头文件进行配置,这个头文件的配置主要有两个方面,一个是引入sys.h这个系统的库,另一个是进行宏定义,如果没有定义这个宏,那么需要进行定义,否则可以跳过,这样可以很好节约了时间。
#ifndef __LED_H
#define __LED_H
#include "sys.h"
#endif
🔨初始化GPIO
接下来就是GPIO的配置了,我们要想使用GPIO,首先需要进行初始化,所以我们首先需要写一个初始化函数,这里命名为LED_Init,它没有返回值和参数,都是void,并且在led.h里面进行声明。
#ifndef __LED_H
#define __LED_H
#include "sys.h"
void LED_Init(void);
#endif
然后第一步是使能控制该GPIO的时钟,也就是躯壳注入灵魂或者车辆加满油一样。这个是能很简单,调用下面的函数。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
它需要两个参数。参数我们可以转到定义去查看,发现需要传入一个需要使能的外设型号,然后再传入使能状态。所以我们直接根据要求首先传入要使能的是GPIO组,然后再传入使能参数ENABLE即可。
/**************************函数定义*********************************/
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->APB2ENR |= RCC_APB2Periph;
}
else
{
RCC->APB2ENR &= ~RCC_APB2Periph;
}
}
/**************************函数定义*********************************/
接下来我们就需要初始化GPIO,这里需要调用下面这个函数,照旧地,我们点进去看看这个函数需要哪些参数:
GPIO_Init(GPIOA, &GPIO_InitStructure);
可以发现该函数需要传入两个参数,其中一个为结构体指针,另一个也是结构体指针,我们点进去看看它有哪些具体的参数可以配置,下面就是初始化函数的内容。
/**************************函数定义*********************************/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* 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));
.....
/**************************函数定义*********************************/
第一个参数是GPIOx是配置需要哪个是哪个GPIO组,例如GPIOA、GPIOB、GPIOC等等。
第二个参数是GPIO_InitStruct是一个指向配置GPIO口工作模式、工作速度和引脚的结构体的指针,这个我们可以到时候先配置好结构体再传入其地址就行。
/**************************结构体定义*********************************/
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
/**************************结构体定义*********************************/
接下来我们需要根据这些参数要求向初始化函数里面传递进去需要的参数GPIOC以及一个指向配置结构体的结构体指针。这个配置结构体我们就命名为GPIO_InitStructure,配置引脚PC13、推挽输出以及输出速度为50Mz。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
行了,现在我们已经完全初始化好了一个GPIO了 ,整个的初始化函数如下图所示:
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
接下来我们需要对该IO脚进行输出高电平1和低电平0的操作,所以我们需要有一个操作BSRR寄存器的函数,非常凑巧的是,这个函数已经在标准库中封装好了,总共分为两个,一个是写入低电平,一个是写入高电平,它们参数一个是GPIO组,一个是引脚号。
GPIO_SetBits(GPIOC,GPIO_Pin_13);
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
/**************************函数定义*********************************/
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
/**************************函数定义*********************************/
所以我们就可以通过调用这两个函数来设置BSRR寄存器从而设置GPIOx某个引脚的状态了。 最后在main函数中写入初始化GPIO然后IO的输出设置成低电平,就可以点亮LED灯了。
#include "led.h"
#include "sys.h"
int main(void)
{
LED_Init();
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
}
⚙实物
参考资料:
GPIO输入模式 – 知乎
《基于STM32嵌入式接口与传感器应用开发》
正点原子开发板相关资料
STM32F10xxx参考手册
STM32F103C8T6数据手册
STM32固件库说明
GPIO的工作模式_gpio四种输入输出模式-CSDN博客
深刻理解GPIO(上拉输入、下拉输入、模拟输入、浮空输入,开漏输出,推挽输出的区别,以STM32为例)_单片机上拉下拉浮空配置-CSDN博客
作者:KKK3号